def received_message(self, message): if not hasattr(self, 'metadata'): return allowed_types = [ GatewayEvent.Types.OUTPUT_CHANGE, GatewayEvent.Types.THERMOSTAT_CHANGE, GatewayEvent.Types.THERMOSTAT_GROUP_CHANGE, GatewayEvent.Types.SHUTTER_CHANGE, GatewayEvent.Types.INPUT_CHANGE ] try: data = msgpack.loads(message.data) event = GatewayEvent.deserialize(data) if event.type == GatewayEvent.Types.ACTION: if event.data['action'] == 'set_subscription': subscribed_types = [ stype for stype in event.data['types'] if stype in allowed_types ] cherrypy.engine.publish( 'update-events-receiver', self.metadata['client_id'], {'subscribed_types': subscribed_types}) elif event.type == GatewayEvent.Types.PING: self.send(msgpack.dumps( GatewayEvent(event_type=GatewayEvent.Types.PONG, data=None).serialize()), binary=True) except Exception as ex: logger.exception('Error receiving message: %s', ex)
def test_shutter_sync_state(self): master_controller = Mock() master_controller.load_shutters = lambda: [] SetUpTestInjections(master_controller=master_controller, maintenance_controller=Mock()) controller = ShutterController() # Basic configuration controller.update_config(ShutterControllerTest.SHUTTER_CONFIG) self.assertEqual(len(controller._shutters), 4) events = [] def on_change(gateway_event): events.append(gateway_event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, on_change) controller.start() self.pubsub._publish_all_events() self.assertEqual([GatewayEvent('SHUTTER_CHANGE', {'id': 0, 'status': {'state': 'STOPPED', 'position': None, 'last_change': 0.0}, 'location': {'room_id': None}}), GatewayEvent('SHUTTER_CHANGE', {'id': 1, 'status': {'state': 'STOPPED', 'position': None, 'last_change': 0.0}, 'location': {'room_id': None}}), GatewayEvent('SHUTTER_CHANGE', {'id': 2, 'status': {'state': 'STOPPED', 'position': None, 'last_change': 0.0}, 'location': {'room_id': None}}), GatewayEvent('SHUTTER_CHANGE', {'id': 3, 'status': {'state': 'STOPPED', 'position': None, 'last_change': 0.0}, 'location': {'room_id': None}})], events) events = [] fakesleep.reset(100) controller.report_shutter_position(0, 89, 'UP') self.pubsub._publish_all_events() self.assertEqual([GatewayEvent('SHUTTER_CHANGE', {'id': 0, 'status': {'state': 'GOING_UP', 'position': 89, 'last_change': 100.0}, 'location': {'room_id': None}})], events) controller.stop()
def _handle_input_status(self, data, data_type='status'): event = GatewayEvent.deserialize( data) if data_type == 'event' else None for decorated_method in self._decorated_methods['input_status']: decorator_version = decorated_method.input_status.get('version', 1) if decorator_version not in PluginRuntime.SUPPORTED_DECORATOR_VERSIONS[ 'input_status']: error = NotImplementedError( 'Version {} is not supported for input status decorators'. format(decorator_version)) self._writer.log_exception('input status', error) else: if decorator_version == 1 and data_type == 'status': self._writer.with_catch('input status', decorated_method, [data]) elif decorator_version == 2 and event is not None: # get relevant event details input_id = event.data['id'] status = event.data['status'] # Version 2 will send ALL input status changes AND in a dict format self._writer.with_catch('input status', decorated_method, [{ 'input_id': input_id, 'status': status }])
def _publish_shutter_change(self, shutter_id, shutter_data, shutter_state): # type: (int, ShutterDTO, Tuple[float, str]) -> None gateway_event = GatewayEvent(event_type=GatewayEvent.Types.SHUTTER_CHANGE, data={'id': shutter_id, 'status': {'state': shutter_state[1].upper(), 'last_change': shutter_state[0]}, 'location': {'room_id': Toolbox.nonify(shutter_data.room, 255)}}) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.STATE, gateway_event)
def _handle_power_event(self, master_event): # type: (MasterEvent) -> None if master_event.type == MasterEvent.Types.POWER_ADDRESS_EXIT: # TODO add controller / orm sync for power modules. gateway_event = GatewayEvent(GatewayEvent.Types.CONFIG_CHANGE, {'type': 'powermodule'}) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.CONFIG, gateway_event)
def _handle_master_event(self, master_event): # type: (MasterEvent) -> None if master_event.type == MasterEvent.Types.INPUT_CHANGE: # TODO move to InputController gateway_event = GatewayEvent( event_type=GatewayEvent.Types.INPUT_CHANGE, data=master_event.data) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.STATE, gateway_event)
def _sync_orm(self): # type: () -> bool if self.SYNC_STRUCTURES is None: return False if self._sync_running: for structure in self.SYNC_STRUCTURES: orm_model = structure.orm_model logger.info('ORM sync ({0}): Already running'.format( orm_model.__name__)) return False self._sync_running = True try: for structure in self.SYNC_STRUCTURES: orm_model = structure.orm_model try: name = structure.name skip = structure.skip start = time.time() logger.info('ORM sync ({0})'.format(orm_model.__name__)) ids = [] for dto in getattr(self._master_controller, 'load_{0}s'.format(name))(): if skip is not None and skip(dto): continue id_ = dto.id ids.append(id_) if not orm_model.select().where( orm_model.number == id_).exists(): orm_model.create(number=id_) orm_model.delete().where( orm_model.number.not_in(ids)).execute() duration = time.time() - start logger.info( 'ORM sync ({0}): completed after {1:.1f}s'.format( orm_model.__name__, duration)) except CommunicationTimedOutException as ex: logger.error('ORM sync ({0}): Failed: {1}'.format( orm_model.__name__, ex)) except Exception: logger.exception('ORM sync ({0}): Failed'.format( orm_model.__name__)) if self._sync_dirty: type_name = orm_model.__name__.lower() gateway_event = GatewayEvent( GatewayEvent.Types.CONFIG_CHANGE, {'type': type_name}) self._pubsub.publish_gateway_event( PubSub.GatewayTopics.CONFIG, gateway_event) self._sync_dirty = False finally: self._sync_running = False return True
def _handle_master_event(self, master_event): # type: (MasterEvent) -> None if master_event.type in [ MasterEvent.Types.EEPROM_CHANGE, MasterEvent.Types.MODULE_DISCOVERY ]: self.invalidate_cache(THERMOSTATS) gateway_event = GatewayEvent(GatewayEvent.Types.CONFIG_CHANGE, {'type': 'thermostats'}) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.CONFIG, gateway_event)
def _publish_state(self, state_dto): # type: (VentilationStatusDTO) -> None event_data = { 'id': state_dto.id, 'mode': state_dto.mode, 'level': state_dto.level, 'timer': state_dto.timer } gateway_event = GatewayEvent(GatewayEvent.Types.VENTILATION_CHANGE, event_data) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.STATE, gateway_event)
def test_events_sent_to_cloud(self): event_sender = EventSender() # Don't start, trigger manually self.assertEqual(len(event_sender._queue), 0) self.assertFalse(event_sender._batch_send_events()) select_mock = Mock() select_mock.dicts.return_value = [{ 'number': 1, 'event_enabled': True }, { 'number': 2, 'event_enabled': False }] with patch.object(Input, 'select', return_value=select_mock): with patch.object(Config, 'get_entry', return_value=True): event_sender.enqueue_event( GatewayEvent(GatewayEvent.Types.OUTPUT_CHANGE, {'id': 1})) event_sender.enqueue_event( GatewayEvent(GatewayEvent.Types.THERMOSTAT_CHANGE, {'id': 1})) event_sender.enqueue_event( GatewayEvent(GatewayEvent.Types.INPUT_CHANGE, {'id': 1})) event_sender.enqueue_event( GatewayEvent(GatewayEvent.Types.INPUT_CHANGE, {'id': 2})) with patch.object(Config, 'get_entry', return_value=False): event_sender.enqueue_event( GatewayEvent(GatewayEvent.Types.INPUT_CHANGE, {'id': 3})) self.assertEqual(3, len(event_sender._queue)) self.assertTrue(event_sender._batch_send_events()) self.assertEqual(0, len(event_sender._queue)) events = self.sent_events.get('events', []) self.assertEqual(3, len(events)) input_event = [ event for event in events if event.type == GatewayEvent.Types.INPUT_CHANGE ][0] self.assertEqual(1, input_event.data['id'])
def _thermostat_group_changed(self, status): # type: (Dict[str,Any]) -> None gateway_event = GatewayEvent( GatewayEvent.Types.THERMOSTAT_GROUP_CHANGE, { 'id': 0, 'status': { 'state': status['state'], 'mode': status['mode'] }, 'location': {} }) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.STATE, gateway_event)
def test_orm_sync(self): events = [] def handle_event(gateway_event): events.append(gateway_event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.CONFIG, handle_event) input_dto = InputDTO(id=42) with mock.patch.object(self.master_controller, 'load_inputs', return_value=[input_dto]): self.controller.run_sync_orm() self.pubsub._publish_all_events() assert Input.select().where(Input.number == input_dto.id).count() == 1 assert GatewayEvent(GatewayEvent.Types.CONFIG_CHANGE, {'type': 'input'}) in events assert len(events) == 1
def test_config_event(self): events = [] def handle_events(gateway_event): events.append(gateway_event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.CONFIG, handle_events) master_event = MasterEvent(MasterEvent.Types.POWER_ADDRESS_EXIT, {}) self.pubsub.publish_master_event(PubSub.MasterTopics.POWER, master_event) self.pubsub._publish_all_events() assert GatewayEvent(GatewayEvent.Types.CONFIG_CHANGE, {'type': 'powermodule'}) in events assert len(events) == 1
def _thermostat_group_changed(self, thermostat_group): # type: (ThermostatGroup) -> None gateway_event = GatewayEvent( GatewayEvent.Types.THERMOSTAT_GROUP_CHANGE, { 'id': 0, 'status': { 'state': 'ON' if thermostat_group.on else 'OFF', 'mode': 'COOLING' if thermostat_group.mode == 'cooling' else 'HEATING' }, 'location': {} }) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.STATE, gateway_event)
def _handle_output_status(self, data, data_type='status'): event = GatewayEvent.deserialize( data) if data_type == 'event' else None for receiver in self._decorated_methods['output_status']: decorator_version = receiver.output_status.get('version', 1) if decorator_version not in PluginRuntime.SUPPORTED_DECORATOR_VERSIONS[ 'output_status']: error = NotImplementedError( 'Version {} is not supported for output status decorators'. format(decorator_version)) self._writer.log_exception('output status', error) else: if decorator_version == 1 and data_type == 'status': self._writer.with_catch('output status', receiver, [data]) elif decorator_version == 2 and event: self._writer.with_catch('output status', receiver, [event.data])
def _publish_state(self, state_dto): # type: (VentilationStatusDTO) -> None # if the timer or remaining time is set, the other value will not be set, # so cache the previous value so it does not get lost state_dto = self._save_status_cache(state_dto) event_data = { 'id': state_dto.id, 'mode': state_dto.mode, 'level': state_dto.level, 'timer': state_dto.timer, 'remaining_time': state_dto.remaining_time, 'is_connected': state_dto.is_connected } gateway_event = GatewayEvent(GatewayEvent.Types.VENTILATION_CHANGE, event_data) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.STATE, gateway_event)
def test_ventilation_config_events(self): plugin = Plugin(id=2, name='dummy', version='0.0.1') with mock.patch.object(Plugin, 'get', return_value=plugin), \ mock.patch.object(Ventilation, 'get_or_none', return_value=Ventilation(id=42, source='plugin', source_id=2, external_id='device-000001', name='foo', amount_of_levels=4, device_type='model-0', device_vendor='example', device_serial='device-000001', plugin=plugin)), \ mock.patch.object(Ventilation, 'save', side_effect=(0, 1)): events = [] def callback(event): events.append(event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.CONFIG, callback) ventilation_dto = VentilationDTO(id=42, external_id='device-000001', source=VentilationSourceDTO( id=2, name='dummy', type='plugin'), name='foo', amount_of_levels=4, device_vendor='example', device_type='model-0', device_serial='device-000001') self.controller.save_ventilation(ventilation_dto) self.pubsub._publish_all_events() assert len(events) == 0, events # No change ventilation_dto.name = 'bar' self.controller.save_ventilation(ventilation_dto) self.pubsub._publish_all_events() assert GatewayEvent(GatewayEvent.Types.CONFIG_CHANGE, {'type': 'ventilation'}) in events assert len(events) == 1, events
def _publish_output_change(self, output_dto): # type: (OutputDTO) -> None event_status = { 'on': output_dto.state.status, 'locked': output_dto.state.locked } if output_dto.module_type in ['d', 'D']: event_status['value'] = output_dto.state.dimmer event_data = { 'id': output_dto.id, 'status': event_status, 'location': { 'room_id': Toolbox.denonify(output_dto.room, 255) } } gateway_event = GatewayEvent(GatewayEvent.Types.OUTPUT_CHANGE, event_data) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.STATE, gateway_event)
def test_ventilation_change_events(self): plugin = Plugin(id=2, name='dummy', version='0.0.1') def get_ventilation(id): return Ventilation(id=id, amount_of_levels=4, source='plugin', plugin=plugin) with mock.patch.object(Select, 'count', return_value=1), \ mock.patch.object(Ventilation, 'get', side_effect=get_ventilation), \ mock.patch.object(Ventilation, 'select', return_value=[get_ventilation(42), get_ventilation(43)]): self.controller.set_status( VentilationStatusDTO(42, 'manual', level=0)) self.controller.set_status( VentilationStatusDTO(43, 'manual', level=2, timer=60.0)) self.pubsub._publish_all_events() events = [] def callback(event): events.append(event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, callback) self.controller.set_status( VentilationStatusDTO(42, 'manual', level=0)) self.controller.set_status( VentilationStatusDTO(43, 'manual', level=2, timer=60.0)) self.pubsub._publish_all_events() assert GatewayEvent( GatewayEvent.Types.VENTILATION_CHANGE, { 'id': 43, 'mode': 'manual', 'level': 2, 'timer': 60.0, 'remaining_time': None, 'is_connected': True }) in events assert len(events) == 1, events
def _thermostat_changed(self, thermostat_number, active_preset, current_setpoint, actual_temperature, percentages, room): # type: (int, str, float, Optional[float], List[float], int) -> None location = {'room_id': room} gateway_event = GatewayEvent( GatewayEvent.Types.THERMOSTAT_CHANGE, { 'id': thermostat_number, 'status': { 'preset': active_preset, 'current_setpoint': current_setpoint, 'actual_temperature': actual_temperature, 'output_0': percentages[0] if len(percentages) >= 1 else None, 'output_1': percentages[1] if len(percentages) >= 2 else None }, 'location': location }) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.STATE, gateway_event)
def _thermostat_changed(self, thermostat_id, status): # type: (int, Dict[str,Any]) -> None """ Executed by the Thermostat Status tracker when an output changed state """ location = { 'room_id': Toolbox.denonify(self._thermostats_config[thermostat_id].room, 255) } gateway_event = GatewayEvent( GatewayEvent.Types.THERMOSTAT_CHANGE, { 'id': thermostat_id, 'status': { 'preset': status['preset'], 'current_setpoint': status['current_setpoint'], 'actual_temperature': status['actual_temperature'], 'output_0': status['output_0'], 'output_1': status['output_1'] }, 'location': location }) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.STATE, gateway_event)
def test_thermostat_change_events(self): events = [] def handle_event(gateway_event): events.append(gateway_event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, handle_event) with mock.patch.object(self.controller, 'invalidate_cache') as handle_event: status = { 'act': None, 'csetp': None, 'setpoint': None, 'output0': None, 'output1': None } self.controller._thermostats_config = {1: ThermostatDTO(1)} self.controller._thermostat_status._report_change(1, status) self.pubsub._publish_all_events() event_data = { 'id': 1, 'status': { 'preset': 'AUTO', 'current_setpoint': None, 'actual_temperature': None, 'output_0': None, 'output_1': None }, 'location': { 'room_id': 255 } } assert GatewayEvent(GatewayEvent.Types.THERMOSTAT_CHANGE, event_data) in events
def _handle_input_status(self, data): event = GatewayEvent.deserialize(data) # get relevant event details input_id = event.data['id'] status = event.data['status'] for decorated_method in self._decorated_methods['input_status']: decorator_version = decorated_method.input_status.get('version', 1) if decorator_version == 1: # Backwards compatibility: only send rising edges of the input (no input releases) if status: self._writer.with_catch('input status', decorated_method, [(input_id, None)]) elif decorator_version == 2: # Version 2 will send ALL input status changes AND in a dict format self._writer.with_catch('input status', decorated_method, [{ 'input_id': input_id, 'status': status }]) else: error = NotImplementedError( 'Version {} is not supported for input status decorators'. format(decorator_version)) self._writer.log_exception('input status', error)
def _publish_config(self): # type: () -> None gateway_event = GatewayEvent(GatewayEvent.Types.CONFIG_CHANGE, {'type': 'ventilation'}) self._pubsub.publish_gateway_event(PubSub.GatewayTopics.CONFIG, gateway_event)
def test_get_shutter_decorators(self): """ Test getting shutter decorators on a plugin. """ controller = None try: PluginControllerTest._create_plugin( 'ShutterPlugin', """ from plugins.base import * class ShutterPlugin(OMPluginBase): name = 'ShutterPlugin' version = '0.1.0' interfaces = [('webui', '1.0')] def __init__(self, webservice, logger): OMPluginBase.__init__(self, webservice, logger) self._shutter_data_v1 = None self._shutter_data_v1_detail = None self._shutter_data_v2 = None self._shutter_data_v3 = None @om_expose(auth=True) def html_index(self): return 'HTML' @om_expose(auth=False) def get_log(self): return {'shutter_data_v1': self._shutter_data_v1, 'shutter_data_v1_detail': self._shutter_data_v1_detail, 'shutter_data_v2': self._shutter_data_v2, 'shutter_data_v3': self._shutter_data_v3} @shutter_status def shutter_v1(self, test_data): self._shutter_data_v1 = test_data @shutter_status def shutter_v1_detail(self, test_data, detail): self._shutter_data_v1_detail = (test_data, detail) @shutter_status(version=2) def shutter_v2(self, test_data, detail): self._shutter_data_v2 = (test_data, detail) @shutter_status(version=3) def shutter_v3(self, shutter_event): self._shutter_data_v3 = shutter_event """) shutter_controller = Mock(ShutterController) shutter_status = [ShutterEnums.State.STOPPED] detail_for_shutter = { 1: { 'state': ShutterEnums.State.STOPPED, 'actual_position': None, 'desired_position': None, 'last_change': 1596787761.147892 } } shutter_controller.get_states = lambda: { 'status': shutter_status, 'detail': detail_for_shutter } controller = PluginControllerTest._get_controller( shutter_controller=shutter_controller) controller.start() shutter_event = GatewayEvent( event_type=GatewayEvent.Types.SHUTTER_CHANGE, data={'some_random_key': 'some_random_value'}) controller.process_observer_event(shutter_event) keys = [ 'shutter_data_v1', 'shutter_data_v1_detail', 'shutter_data_v2', 'shutter_data_v3' ] start = time.time() response = None while time.time() - start < 2: response = controller._request('ShutterPlugin', 'get_log') if all(response[key] is not None for key in keys): break time.sleep(0.1) self.maxDiff = None self.assertEqual(response['shutter_data_v1'], shutter_status) self.assertEqual(response['shutter_data_v1_detail'], [shutter_status, detail_for_shutter]) self.assertEqual(response['shutter_data_v2'], [shutter_status, detail_for_shutter]) self.assertEqual(response['shutter_data_v3'], shutter_event.data) finally: if controller is not None: controller.stop() PluginControllerTest._destroy_plugin('ShutterPlugin')
def test_output_master_change(self): events = [] def on_change(gateway_event): events.append(gateway_event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, on_change) self.controller._cache.update_outputs( [OutputDTO(id=2), OutputDTO(id=40, module_type='D', room=3)]) self.controller._handle_master_event( MasterEvent('OUTPUT_STATUS', {'state': OutputStateDTO(id=2, status=False)})) self.controller._handle_master_event( MasterEvent( 'OUTPUT_STATUS', {'state': OutputStateDTO(id=40, status=True, dimmer=100)})) self.pubsub._publish_all_events() events = [] self.controller._handle_master_event( MasterEvent('OUTPUT_STATUS', {'state': OutputStateDTO(id=2, status=True)})) self.controller._handle_master_event( MasterEvent('OUTPUT_STATUS', {'state': OutputStateDTO(id=40, status=True)})) self.pubsub._publish_all_events() assert [ GatewayEvent( 'OUTPUT_CHANGE', { 'id': 2, 'status': { 'on': True, 'locked': False }, 'location': { 'room_id': 255 } }) ] == events events = [] self.controller._handle_master_event( MasterEvent('OUTPUT_STATUS', {'state': OutputStateDTO(id=40, dimmer=50)})) self.pubsub._publish_all_events() assert [ GatewayEvent( 'OUTPUT_CHANGE', { 'id': 40, 'status': { 'on': True, 'value': 50, 'locked': False }, 'location': { 'room_id': 3 } }) ] == events
def _handle_thermostat_group_status(self, data): event = GatewayEvent.deserialize(data) for receiver in self._decorated_methods['thermostat_group_status']: self._writer.with_catch('thermostat group status', receiver, [event.data])
def _handle_ventilation_status(self, data): event = GatewayEvent.deserialize(data) for receiver in self._decorated_methods['ventilation_status']: self._writer.with_catch('ventilation status', receiver, [event.data])
def test_output_sync_change(self): events = [] def on_change(gateway_event): events.append(gateway_event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, on_change) outputs = {2: OutputDTO(id=2), 40: OutputDTO(id=40, module_type='D')} select_mock = mock.Mock() select_mock.join_from.return_value = [ Output(id=0, number=2), Output(id=1, number=40, room=Room(id=2, number=3)) ] with mock.patch.object(Output, 'select', return_value=select_mock), \ mock.patch.object(self.master_controller, 'load_output', side_effect=lambda output_id: outputs.get(output_id)), \ mock.patch.object(self.master_controller, 'load_output_status', return_value=[OutputStateDTO(id=2, status=True), OutputStateDTO(id=40, status=True)]): self.controller._sync_state() self.pubsub._publish_all_events() assert [ GatewayEvent( 'OUTPUT_CHANGE', { 'id': 2, 'status': { 'on': True, 'locked': False }, 'location': { 'room_id': 255 } }), GatewayEvent( 'OUTPUT_CHANGE', { 'id': 40, 'status': { 'on': True, 'value': 0, 'locked': False }, 'location': { 'room_id': 3 } }) ] == events select_mock = mock.Mock() select_mock.join_from.return_value = [ Output(id=0, number=2), Output(id=1, number=40, room=Room(id=2, number=3)) ] with mock.patch.object(Output, 'select', return_value=select_mock), \ mock.patch.object(self.master_controller, 'load_output', side_effect=lambda output_id: outputs.get(output_id)), \ mock.patch.object(self.master_controller, 'load_output_status', return_value=[OutputStateDTO(id=2, status=True, dimmer=0), OutputStateDTO(id=40, status=True, dimmer=50)]): events = [] self.controller._sync_state() self.pubsub._publish_all_events() assert [ GatewayEvent( 'OUTPUT_CHANGE', { 'id': 2, 'status': { 'on': True, 'locked': False }, 'location': { 'room_id': 255 } }), GatewayEvent( 'OUTPUT_CHANGE', { 'id': 40, 'status': { 'on': True, 'value': 50, 'locked': False }, 'location': { 'room_id': 3 } }) ] == events
def test_get_special_methods(self): """ Test getting special methods on a plugin. """ controller = None try: PluginControllerTest._create_plugin( 'P1', """ import time from plugins.base import * class P1(OMPluginBase): name = 'P1' version = '0.1.0' interfaces = [('webui', '1.0')] def __init__(self, webservice, logger): OMPluginBase.__init__(self, webservice, logger) self._bg_running = False self._input_data = None self._input_data_version_2 = None self._output_data = None self._output_data_version_2 = None self._event_data = None @om_expose(auth=True) def html_index(self): return 'HTML' @om_expose(auth=False) def get_log(self): return {'bg_running': self._bg_running, 'input_data': self._input_data, 'input_data_version_2': self._input_data_version_2, 'output_data': self._output_data, 'output_data_version_2': self._output_data_version_2, 'event_data': self._event_data} @input_status def input(self, input_status_inst): self._input_data = input_status_inst @input_status(version=2) def input_version_2(self, input_status_inst): self._input_data_version_2 = input_status_inst @output_status def output(self, output_status_inst): self._output_data = output_status_inst @output_status(version=2) def output_version_2(self, output_status_inst): self._output_data_version_2 = output_status_inst @receive_events def recv_events(self, code): self._event_data = code @background_task def run(self): while True: self._bg_running = True time.sleep(1) """) output_controller = Mock(OutputController) output_controller.get_output_statuses = lambda: [ OutputStateDTO(id=1, status=True, dimmer=5) ] controller = PluginControllerTest._get_controller( output_controller=output_controller) controller.start() response = controller._request('P1', 'html_index') self.assertEqual(response, 'HTML') rising_input_event = { 'id': 1, 'status': True, 'location': { 'room_id': 1 } } controller.process_observer_event( GatewayEvent(event_type=GatewayEvent.Types.INPUT_CHANGE, data=rising_input_event)) falling_input_event = { 'id': 2, 'status': False, 'location': { 'room_id': 5 } } controller.process_observer_event( GatewayEvent(event_type=GatewayEvent.Types.INPUT_CHANGE, data=falling_input_event)) output_event = { 'id': 1, 'status': { 'on': True, 'value': 5, 'locked': True }, 'location': { 'room_id': 5 } } controller.process_observer_event( GatewayEvent(event_type=GatewayEvent.Types.OUTPUT_CHANGE, data=output_event)) controller.process_event(1) keys = [ 'input_data', 'input_data_version_2', 'output_data', 'output_data_version_2', 'event_data' ] start = time.time() while time.time() - start < 2: response = controller._request('P1', 'get_log') if all(response[key] is not None for key in keys): break time.sleep(0.1) self.assertEqual(response['bg_running'], True) self.assertEqual( response['input_data'], [1, None]) # only rising edges should be triggered self.assertEqual(response['output_data'], [[1, 5]]) self.assertEqual(response['output_data_version_2'], output_event) self.assertEqual(response['event_data'], 1) finally: if controller is not None: controller.stop() PluginControllerTest._destroy_plugin('P1')