class ThermostatControllerMasterTest(unittest.TestCase): @classmethod def setUpClass(cls): SetTestMode() def setUp(self): self.pubsub = PubSub() SetUpTestInjections( pubsub=self.pubsub, master_controller=mock.Mock(MasterClassicController), output_controller=mock.Mock(OutputController)) self.controller = ThermostatControllerMaster() 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 test_eeprom_events(self): master_event = MasterEvent(MasterEvent.Types.EEPROM_CHANGE, {}) with mock.patch.object(self.controller, 'invalidate_cache') as handle_event: self.pubsub.publish_master_event(PubSub.MasterTopics.EEPROM, master_event) self.pubsub._publish_all_events() handle_event.assert_called()
class InputControllerTest(unittest.TestCase): @classmethod def setUpClass(cls): SetTestMode() cls.test_db = SqliteDatabase(':memory:') def setUp(self): self.test_db.bind(MODELS, bind_refs=False, bind_backrefs=False) self.test_db.connect() self.test_db.create_tables(MODELS) self.master_controller = mock.Mock(MasterController) self.pubsub = PubSub() # triggernig manually self.master_controller = mock.Mock(MasterController) SetUpTestInjections(master_controller=self.master_controller, maintenance_controller=mock.Mock(), pubsub=self.pubsub) self.controller = InputController() def tearDown(self): self.test_db.drop_tables(MODELS) self.test_db.close() 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_full_loaded_inputs(self): master_dtos = {1: InputDTO(id=1, name='one'), 2: InputDTO(id=2, name='two')} orm_inputs = [Input(id=10, number=1, event_enabled=False), Input(id=11, number=2, event_enabled=True)] select_mock = mock.Mock() select_mock.join_from.return_value = orm_inputs with mock.patch.object(Input, 'select', return_value=select_mock), \ mock.patch.object(self.master_controller, 'load_input', side_effect=lambda input_id: master_dtos.get(input_id)): dtos = self.controller.load_inputs() self.assertEqual(2, len(dtos)) self.assertIn(InputDTO(id=1, name='one', event_enabled=False), dtos) self.assertIn(InputDTO(id=2, name='two', event_enabled=True), dtos)
class ShutterControllerTest(unittest.TestCase): """ Tests for ShutterController. """ SHUTTER_CONFIG = [ShutterDTO(id=0, steps=None, up_down_config=0, timer_up=200, timer_down=200), ShutterDTO(id=1, steps=None, up_down_config=1, timer_up=100, timer_down=100), ShutterDTO(id=2, steps=80, up_down_config=0), ShutterDTO(id=3, steps=0, up_down_config=0)] TIMING_BASED_STEPS = 100 @classmethod def setUpClass(cls): SetTestMode() fakesleep.monkey_patch() cls.test_db = SqliteDatabase(':memory:') @classmethod def tearDownClass(cls): fakesleep.monkey_restore() def setUp(self): self.maxDiff = None self.test_db.bind(MODELS, bind_refs=False, bind_backrefs=False) self.test_db.connect() self.test_db.create_tables(MODELS) self.pubsub = PubSub() SetUpTestInjections(pubsub=self.pubsub) def tearDown(self): self.test_db.drop_tables(MODELS) self.test_db.close() def test_update_config(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) for shutter_id in range(3): self.assertIn(shutter_id, controller._shutters) self.assertEqual(controller._shutters[shutter_id], ShutterControllerTest.SHUTTER_CONFIG[shutter_id]) self.assertIn(shutter_id, controller._actual_positions) self.assertIn(shutter_id, controller._desired_positions) self.assertIn(shutter_id, controller._directions) self.assertIn(shutter_id, controller._states) # Config removal config = copy.deepcopy(ShutterControllerTest.SHUTTER_CONFIG) config.pop(0) controller.update_config(config) self.assertNotIn(0, controller._shutters) self.assertNotIn(0, controller._actual_positions) self.assertNotIn(0, controller._desired_positions) self.assertNotIn(0, controller._directions) self.assertNotIn(0, controller._states) self.assertEqual(controller._get_shutter(1), ShutterControllerTest.SHUTTER_CONFIG[1]) with self.assertRaises(RuntimeError) as ex: controller._get_shutter(0) self.assertEqual(str(ex.exception), 'Shutter 0 is not available') # Config update controller._actual_positions[1] = 'foo' controller._desired_positions[1] = 'foo' controller._directions[1] = 'foo' controller._states[1] = 'foo' config[0].up_down_config = 0 controller.update_config(config) self.assertIsNone(controller._actual_positions.get(1, 'incorrect')) self.assertIsNone(controller._desired_positions.get(1, 'incorrect')) self.assertEqual(controller._directions.get(1), ShutterEnums.Direction.STOP) self.assertEqual(controller._states.get(1), (0.0, ShutterEnums.State.STOPPED)) def test_basic_actions_non_positional(self): calls = {} def shutter_direction(direction, _shutter_id, timer=None): _ = timer calls.setdefault(_shutter_id, []).append((direction, timer)) master_controller = Mock() master_controller.shutter_up = lambda id, timer: shutter_direction('up', id, timer) master_controller.shutter_down = lambda id, timer: shutter_direction('down', id, timer) master_controller.shutter_stop = lambda id: shutter_direction('stop', id) SetUpTestInjections(master_controller=master_controller, maintenance_controller=Mock()) controller = ShutterController() controller.update_config(ShutterControllerTest.SHUTTER_CONFIG) # +- shutter id # Valid calls | +- desired position calls = {} # v v for shutter_id, data in {0: 99, 1: 99, 2: 79}.items(): controller.shutter_down(shutter_id) self.assertEqual(controller._desired_positions[shutter_id], data) self.assertEqual(controller._directions[shutter_id], ShutterEnums.Direction.DOWN) self.assertEqual(calls.get(shutter_id)[-1], ('down', None)) # +- shutter id # | +- desired position calls = {} # v v for shutter_id, data in {0: 0, 1: 0, 2: 0}.items(): controller.shutter_up(shutter_id) self.assertEqual(controller._desired_positions[shutter_id], data) self.assertEqual(controller._directions[shutter_id], ShutterEnums.Direction.UP) self.assertEqual(calls.get(shutter_id)[-1], ('up', None)) calls = {} for shutter_id in range(3): controller.shutter_stop(shutter_id) self.assertIsNone(controller._desired_positions[shutter_id]) self.assertEqual(controller._directions[shutter_id], ShutterEnums.Direction.STOP) self.assertEqual(calls.get(shutter_id)[-1], ('stop', None)) def test_basic_actions_positional(self): calls = {} def shutter_direction(direction, _shutter_id, timer=None): calls.setdefault(_shutter_id, []).append((direction, timer)) master_controller = Mock() master_controller.shutter_up = lambda id, timer: shutter_direction('up', id, timer) master_controller.shutter_down = lambda id, timer: shutter_direction('down', id, timer) master_controller.shutter_stop = lambda id: shutter_direction('stop', id) SetUpTestInjections(master_controller=master_controller, maintenance_controller=Mock()) controller = ShutterController() controller.update_config(ShutterControllerTest.SHUTTER_CONFIG) # Positionned calls on non-positional shutters should fail calls = {} for shutter_id in [0, 1]: message = 'Shutter {0} has a position limit of 0 <= position <= {1}'.format(shutter_id, self.TIMING_BASED_STEPS - 1) with self.assertRaises(RuntimeError) as ex: controller.shutter_up(shutter_id, -1) self.assertEqual(str(ex.exception), message) with self.assertRaises(RuntimeError) as ex: controller.shutter_down(shutter_id, 201) self.assertEqual(str(ex.exception), message) self.assertEqual(len(calls), 0) # Out of range positions should fail calls = {} for shutter_id in [2]: message = 'Shutter {0} has a position limit of 0 <= position <= {1}'.format(shutter_id, ShutterControllerTest.SHUTTER_CONFIG[shutter_id].steps - 1) with self.assertRaises(RuntimeError) as ex: controller.shutter_up(shutter_id, -1) self.assertEqual(str(ex.exception), message) with self.assertRaises(RuntimeError) as ex: controller.shutter_down(shutter_id, 85) self.assertEqual(str(ex.exception), message) self.assertEqual(len(calls), 0) # Valid calls calls = {} for shutter_id in [2]: controller.shutter_up(shutter_id, 50) controller.shutter_down(shutter_id, 50) self.assertEqual(calls[shutter_id], [('up', None), ('down', None)]) self.assertEqual(len(calls), 1) def test_goto_position(self): calls = {} def shutter_direction(direction, _shutter_id, timer=None): calls.setdefault(_shutter_id, []).append((direction, timer)) master_controller = Mock() master_controller.shutter_up = lambda id, timer: shutter_direction('up', id, timer) master_controller.shutter_down = lambda id, timer: shutter_direction('down', id, timer) master_controller.shutter_stop = lambda id: shutter_direction('stop', id) SetUpTestInjections(master_controller=master_controller, maintenance_controller=Mock()) controller = ShutterController() controller.update_config(ShutterControllerTest.SHUTTER_CONFIG) # +- starting actual position # | +- position to go to # | | +- expected direction after the call # | | | +- expected BA to be executed calls = {} # v v v v for shutter_id, data in {1: [[0, 50, ShutterEnums.Direction.DOWN, 'down', 50], # down = 100, up = 0 [80, 50, ShutterEnums.Direction.UP, 'up', 30]]}.items(): # Out of range calls need to fail message = 'Shutter {0} has a position limit of 0 <= position <= {1}'.format(shutter_id, self.TIMING_BASED_STEPS - 1) with self.assertRaises(RuntimeError) as ex: controller.shutter_goto(shutter_id, 105) self.assertEqual(str(ex.exception), message) # Validate correct calls for entry in data: controller._actual_positions[shutter_id] = entry[0] controller.shutter_goto(shutter_id, entry[0]) direction, timer = calls[shutter_id].pop() self.assertEqual(direction, 'stop', shutter_id) controller.shutter_goto(shutter_id, entry[1]) self.assertEqual(controller._directions[shutter_id], entry[2]) direction, timer = calls[shutter_id].pop() self.assertEqual(direction, entry[3]) self.assertEqual(timer, entry[4]) # +- starting actual position # | +- position to go to # | | +- expected direction after the call # | | | +- expected BA to be executed calls = {} # v v v v for shutter_id, data in {2: [[10, 50, ShutterEnums.Direction.DOWN, 'down', None], # down = 79, up = 0 [10, 5, ShutterEnums.Direction.UP, 'up', None]]}.items(): # Out of range calls need to fail message = 'Shutter {0} has a position limit of 0 <= position <= {1}'.format(shutter_id, ShutterControllerTest.SHUTTER_CONFIG[shutter_id].steps - 1) with self.assertRaises(RuntimeError) as ex: controller.shutter_goto(shutter_id, 105) self.assertEqual(str(ex.exception), message) # A shutter with unknown position can't be instructed controller._actual_positions[shutter_id] = None with self.assertRaises(RuntimeError) as ex: controller.shutter_goto(shutter_id, 50) self.assertEqual(str(ex.exception), 'Shutter {0} has unknown actual position'.format(shutter_id)) # Validate correct calls for entry in data: controller._actual_positions[shutter_id] = entry[0] controller.shutter_goto(shutter_id, entry[0]) self.assertEqual(calls[shutter_id].pop(), ('stop', None)) controller.shutter_goto(shutter_id, entry[1]) direction, timer = calls[shutter_id].pop() self.assertEqual(direction, entry[3]) self.assertEqual(timer, entry[4]) def test_position_reached(self): for expected_result, data in [[False, {'direction': ShutterEnums.Direction.UP, 'desired_position': 50, 'actual_position': 60}], [True, {'direction': ShutterEnums.Direction.UP, 'desired_position': 50, 'actual_position': 50}], [True, {'direction': ShutterEnums.Direction.UP, 'desired_position': 50, 'actual_position': 40}], [False, {'direction': ShutterEnums.Direction.DOWN, 'desired_position': 50, 'actual_position': 40}], [True, {'direction': ShutterEnums.Direction.DOWN, 'desired_position': 50, 'actual_position': 50}], [True, {'direction': ShutterEnums.Direction.DOWN, 'desired_position': 50, 'actual_position': 60}]]: self.assertEqual(expected_result, ShutterController._is_position_reached(**data)) def test_position_reporting(self): calls = {} def shutter_direction(direction, _shutter_id, timer=None): calls.setdefault(_shutter_id, []).append((direction, timer)) master_controller = Mock() master_controller.shutter_up = lambda id, timer: shutter_direction('up', id) master_controller.shutter_down = lambda id, timer: shutter_direction('down', id) master_controller.shutter_stop = lambda id: shutter_direction('stop', id) SetUpTestInjections(master_controller=master_controller, maintenance_controller=Mock()) controller = ShutterController() controller.update_config(ShutterControllerTest.SHUTTER_CONFIG) # +- desired position # | +- reported position # | | +- internal direction of the shutter # Validate correct calls | | | +- reported position calls = {} # v v v v for shutter_id, data in {2: [[50, 60, ShutterEnums.Direction.UP, None], # down = 0, up = 100 [50, 60, ShutterEnums.Direction.UP, ShutterEnums.Direction.UP], [50, 60, ShutterEnums.Direction.UP, ShutterEnums.Direction.DOWN], [50, 40, ShutterEnums.Direction.DOWN, None], [50, 40, ShutterEnums.Direction.DOWN, ShutterEnums.Direction.DOWN], [50, 40, ShutterEnums.Direction.DOWN, ShutterEnums.Direction.UP], [50, 50, ShutterEnums.Direction.UP, None], [50, 50, ShutterEnums.Direction.UP, ShutterEnums.Direction.STOP], [50, 50, ShutterEnums.Direction.UP, ShutterEnums.Direction.UP], [50, 50, ShutterEnums.Direction.UP, ShutterEnums.Direction.DOWN]]}.items(): for entry in data: controller._desired_positions[shutter_id] = entry[0] controller._directions[shutter_id] = entry[2] controller.report_shutter_position(shutter_id, entry[1], entry[3]) if entry[0] == entry[1] or (entry[3] is not None and entry[2] != entry[3]): # If desired and reported are equal, or if the direction changed direction, timer = calls[shutter_id].pop() self.assertEqual(direction, 'stop') self.assertEqual(controller._directions[shutter_id], ShutterEnums.Direction.STOP) elif entry[3] is None: self.assertEqual(controller._directions[shutter_id], entry[2]) else: self.assertEqual(controller._directions[shutter_id], entry[3]) def test_events_and_state(self): fakesleep.reset(0) calls = {} SetUpTestInjections(master_communicator=Mock(), configuration_controller=Mock(), eeprom_controller=Mock()) master_controller = MasterClassicController() master_controller._master_version = (3, 143, 103) master_controller._shutter_config = {shutter.id: shutter for shutter in ShutterControllerTest.SHUTTER_CONFIG} SetUpTestInjections(master_controller=master_controller, maintenance_controller=Mock()) controller = ShutterController() controller.update_config(ShutterControllerTest.SHUTTER_CONFIG) def shutter_callback(event): calls.setdefault(event.data['id'], []).append(event.data['status']['state']) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, shutter_callback) self.pubsub._publish_all_events() def validate(_shutter_id, _entry): self.pubsub._publish_all_events() self.assertEqual(controller._actual_positions.get(_shutter_id), _entry[0]) self.assertEqual(controller._desired_positions.get(_shutter_id), _entry[1]) self.assertEqual(controller._directions.get(_shutter_id), _entry[2]) timer, state = controller._states.get(_shutter_id) self.assertEqual(timer, _entry[3][0]) self.assertEqual(state, _entry[3][1]) if len(_entry) == 4 or _entry[4]: self.assertEqual(calls[_shutter_id].pop(), _entry[3][1].upper()) master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00000000}) self.pubsub._publish_all_events() for shutter_id in range(3): # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v validate(shutter_id, [None, None, ShutterEnums.Direction.STOP, (0.0, ShutterEnums.State.STOPPED), False]) ################################################################################################### # set stutters to a known initial state for shutter in self.SHUTTER_CONFIG: controller._directions[shutter.id] = ShutterEnums.Direction.UP controller._actual_positions[shutter.id] = 0 ################################################################################################### for shutter_id in range(3): controller.shutter_down(shutter_id, None) self.pubsub._publish_all_events() time.sleep(20) master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00011001}) self.pubsub._publish_all_events() # +- actual position # | +- desired position # | | +- direction +- state # v v v v for shutter_id, entry in {0: [0, 99, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN)], 1: [0, 99, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN)], # this shutter is inverted 2: [0, 79, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN)]}.items(): validate(shutter_id, entry) self.pubsub._publish_all_events() time.sleep(50) # Standard shutters will still be going down controller._actual_positions[2] = 20 # Simulate position reporting master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00011000}) # First shutter motor stop self.pubsub._publish_all_events() # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v for shutter_id, entry in {0: [25, 99, ShutterEnums.Direction.STOP, (70, ShutterEnums.State.STOPPED)], 1: [0, 99, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN), False], 2: [20, 79, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN), False]}.items(): validate(shutter_id, entry) self.pubsub._publish_all_events() time.sleep(50) # Standard shutters will be down now controller._actual_positions[2] = 50 # Simulate position reporting master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00010000}) # Second shutter motor stop # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v for shutter_id, entry in {0: [25, 99, ShutterEnums.Direction.STOP, (70, ShutterEnums.State.STOPPED), False], 1: [99, 99, ShutterEnums.Direction.STOP, (120, ShutterEnums.State.DOWN)], 2: [50, 79, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN), False]}.items(): validate(shutter_id, entry) time.sleep(10) controller._actual_positions[2] = 50 # Simulate position reporting master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00000000}) # Third motor stopped # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v for shutter_id, entry in {0: [25, 99, ShutterEnums.Direction.STOP, (70, ShutterEnums.State.STOPPED), False], 1: [99, 99, ShutterEnums.Direction.STOP, (120, ShutterEnums.State.DOWN), False], 2: [50, 79, ShutterEnums.Direction.STOP, (130, ShutterEnums.State.STOPPED)]}.items(): validate(shutter_id, entry) controller._actual_positions[2] = 60 # Simulate position reporting master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00010000}) # Third motor started again # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v for shutter_id, entry in {0: [25, 99, ShutterEnums.Direction.STOP, (70, ShutterEnums.State.STOPPED), False], 1: [99, 99, ShutterEnums.Direction.STOP, (120, ShutterEnums.State.DOWN), False], 2: [60, 79, ShutterEnums.Direction.DOWN, (130, ShutterEnums.State.GOING_DOWN)]}.items(): validate(shutter_id, entry) controller._actual_positions[2] = 79 # Simulate position reporting master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00000000}) # Third motor stopped again # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v for shutter_id, entry in {0: [25, 99, ShutterEnums.Direction.STOP, (70, ShutterEnums.State.STOPPED), False], 1: [99, 99, ShutterEnums.Direction.STOP, (120, ShutterEnums.State.DOWN), False], 2: [79, 79, ShutterEnums.Direction.STOP, (130, ShutterEnums.State.DOWN)]}.items(): validate(shutter_id, entry) states = controller.get_states() states['status'].pop(3) # Remove the "unused" shutter states['detail'].pop(3) self.assertDictEqual(states, {'detail': {0: {'actual_position': 25, 'desired_position': 99, 'state': 'stopped', 'last_change': 70}, 1: {'actual_position': 99, 'desired_position': 99, 'state': 'down', 'last_change': 120}, 2: {'actual_position': 79, 'desired_position': 79, 'state': 'down', 'last_change': 130}}, 'status': ['stopped', 'down', 'down']}) def test_master_event_failsafe(self): _ = self SetUpTestInjections(master_communicator=Mock(), configuration_controller=Mock(), eeprom_controller=Mock()) master_controller = MasterClassicController() master_controller._shutter_config = {shutter.id: shutter for shutter in ShutterControllerTest.SHUTTER_CONFIG} master_controller._shutter_config.pop(0) # Got data for an unconfigured shutter. This should not raise. master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00000000}) 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 test_exception_during_sync(self): _ = self def _raise(): raise RuntimeError() master_controller = Mock() SetUpTestInjections(master_controller=master_controller, maintenance_controller=Mock()) controller = ShutterController() controller._sync_orm() controller.load_shutters = _raise controller._sync_orm() # Should not raise an exception
class VentilationControllerTest(unittest.TestCase): @classmethod def setUpClass(cls): SetTestMode() def setUp(self): self.pubsub = PubSub() SetUpTestInjections(pubsub=self.pubsub) self.controller = VentilationController() def test_set_status(self): plugin = Plugin(id=2, name='dummy', version='0.0.1') with mock.patch.object(Select, 'count', return_value=1), \ mock.patch.object(Ventilation, 'get', side_effect=[Ventilation(id=42, amount_of_levels=4, source='plugin', plugin=plugin), Ventilation(id=43, amount_of_levels=4, source='plugin', plugin=plugin)]), \ mock.patch.object(Ventilation, 'select', return_value=[Ventilation(id=42, amount_of_levels=4, source='plugin', plugin=plugin), Ventilation(id=43, amount_of_levels=4, source='plugin', plugin=plugin)]): self.controller.set_status( VentilationStatusDTO(42, 'manual', level=0)) self.controller.set_status( VentilationStatusDTO(43, 'manual', level=2, timer=60.0)) status = self.controller.get_status() assert {'manual'} == set(x.mode for x in status) assert {42, 43} == set(x.id for x in status) assert {0, 2} == set(x.level for x in status) assert {None, 60.0} == set(x.timer for x in status) def test_set_level(self): plugin = Plugin(id=2, name='dummy', version='0.0.1') with mock.patch.object(Select, 'count', return_value=1), \ mock.patch.object(Ventilation, 'get', side_effect=[Ventilation(id=42, amount_of_levels=4, source='plugin', plugin=plugin), Ventilation(id=43, amount_of_levels=4, source='plugin', plugin=plugin)]), \ mock.patch.object(Ventilation, 'select', return_value=[Ventilation(id=42, amount_of_levels=4, source='plugin', plugin=plugin), Ventilation(id=43, amount_of_levels=4, source='plugin', plugin=plugin)]): self.controller.set_level(42, 0) self.controller.set_level(43, 2, timer=60.0) status = self.controller.get_status() assert {'manual'} == set(x.mode for x in status) assert {42, 43} == set(x.id for x in status) assert {0, 2} == set(x.level for x in status) assert {None, 60.0} == set(x.timer for x in status) def test_mode_auto(self): plugin = Plugin(id=2, name='dummy', version='0.0.1') with mock.patch.object(Select, 'count', return_value=1), \ mock.patch.object(Ventilation, 'get', side_effect=[Ventilation(id=42, amount_of_levels=4, source='plugin', plugin=plugin), Ventilation(id=43, amount_of_levels=4, source='plugin', plugin=plugin), Ventilation(id=43, amount_of_levels=4, source='plugin', plugin=plugin)]), \ mock.patch.object(Ventilation, 'select', return_value=[Ventilation(id=42, amount_of_levels=4, source='plugin', plugin=plugin), Ventilation(id=43, amount_of_levels=4, source='plugin', plugin=plugin)]): self.controller.set_mode_auto(42) self.controller.set_level(43, 2, timer=60.0) status = self.controller.get_status() assert {'auto', 'manual'} == set(x.mode for x in status) self.controller.set_mode_auto(43) status = self.controller.get_status() assert {'auto'} == set(x.mode for x in status) assert {42, 43} == set(x.id for x in status) assert {None} == set(x.level for x in status) assert {None} == set(x.timer for x in status) def test_set_invalid_level(self): plugin = Plugin(id=2, name='dummy', version='0.0.1') with mock.patch.object(Select, 'count', return_value=1), \ mock.patch.object(Ventilation, 'get', return_value=Ventilation(id=42, amount_of_levels=4, souurce='plugin', plugin=plugin)): self.assertRaises(ValueError, self.controller.set_level, 42, 5) self.assertRaises(ValueError, self.controller.set_level, 42, -1) def test_load_ventilation(self): with mock.patch.object(Ventilation, 'get', return_value=Ventilation( id=42, source='plugin', external_id='device-000001', name='foo', amount_of_levels=4, device_vendor='example', device_type='model-0', device_serial='device-000001', plugin=Plugin(id=2, name='dummy', version='0.0.1'))): ventilation_dto = self.controller.load_ventilation(42) assert 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') def test_create_ventilation(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=None) as get_or_none, \ mock.patch.object(Ventilation, 'save', return_value=1) as save: ventilation_dto = VentilationDTO(None, 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) get_or_none.assert_called_with(source='plugin', plugin=plugin, external_id='device-000001') save.assert_called() def test_update_ventilation(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)) as get_or_none, \ mock.patch.object(Ventilation, 'save', return_value=1) as save: ventilation_dto = VentilationDTO(None, 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) get_or_none.assert_called_with(source='plugin', plugin=plugin, external_id='device-000001') save.assert_called() def test_update_existing_ventilation(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)) as get_or_none, \ mock.patch.object(Ventilation, 'save', return_value=1) as save: 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) get_or_none.assert_called_with(id=42, source='plugin', plugin=plugin, external_id='device-000001') save.assert_called() 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 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 test_ventilation_status_timeout(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(43)]): events = [] def callback(event): events.append(event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, callback) self.controller.set_status( VentilationStatusDTO(43, 'manual', level=2, timer=60.0, last_seen=(time.time() - 600))) 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': False }) in events assert len(events) == 1, events def test_ventilation_controller_inactive_status(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(43)]): events = [] def callback(event): events.append(event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, callback) self.controller.set_status( VentilationStatusDTO(43, 'manual', level=2, timer=60.0, last_seen=(time.time() - 600))) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) self.controller._check_connected_timeout() self.pubsub._publish_all_events() self.assertEqual(2, len(events)) self.assertEqual(1, len(self.controller._status)) # Check that the last event that has been send is Null last_event = events[-1] self.assertEqual(None, last_event.data['mode']) self.assertEqual(None, last_event.data['level']) self.controller.set_status( VentilationStatusDTO(43, 'manual', level=2, timer=60.0)) self.pubsub._publish_all_events() self.assertEqual(3, len(events)) self.assertEqual(1, len(self.controller._status)) self.controller._check_connected_timeout() self.pubsub._publish_all_events() # Now there would no timeout occur self.assertEqual(3, len(events)) self.assertEqual(1, len(self.controller._status)) def test_ventilation_timer_expire_manual(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(43)]): events = [] def callback(event): events.append(event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, callback) # first timer is running self.controller.set_status( VentilationStatusDTO(43, 'manual', level=2, timer=60.0, remaining_time=5.0, last_seen=(time.time() - 10))) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) # This should not trigger an event self.controller._check_connected_timeout() # This should trigger an update event. self.controller._periodic_event_update() self.pubsub._publish_all_events() self.assertEqual(2, len(events)) self.assertEqual(1, len(self.controller._status)) # Clear all current events events = [] # event that timer has been done self.controller.set_status( VentilationStatusDTO(43, 'automatic', level=1, timer=None, remaining_time=None, last_seen=time.time())) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) self.assertEqual(None, events[-1].data['remaining_time']) self.assertEqual(None, events[-1].data['timer']) # Clear all current events events = [] # event that timer has been started self.controller.set_status( VentilationStatusDTO(43, 'automatic', level=1, timer=30, remaining_time=None, last_seen=time.time())) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) self.assertEqual(None, events[-1].data['remaining_time']) self.assertEqual(30, events[-1].data['timer']) # Clear all current events events = [] # event from ventilation plugin self.controller.set_status( VentilationStatusDTO(43, 'manual', level=1, timer=None, remaining_time=29, last_seen=time.time())) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) self.assertEqual(29, events[-1].data['remaining_time']) self.assertEqual( 30, events[-1].data['timer']) # this value should be kept in cache # Clear all current events events = [] # event from ventilation plugin self.controller.set_status( VentilationStatusDTO(43, 'automatic', level=1, timer=None, remaining_time=15, last_seen=time.time())) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) self.assertEqual(15, events[-1].data['remaining_time'] ) # this value should update from the event self.assertEqual( 30, events[-1].data['timer']) # this value should be kept in cache # Clear all current events events = [] # event from ventilation plugin (same value) self.controller.set_status( VentilationStatusDTO(43, 'automatic', level=1, timer=None, remaining_time=15, last_seen=time.time())) self.pubsub._publish_all_events() self.assertEqual(0, len(events)) self.assertEqual(1, len(self.controller._status)) # event from ventilation plugin self.controller.set_status( VentilationStatusDTO(43, 'automatic', level=1, timer=None, remaining_time=14, last_seen=time.time())) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) self.assertEqual(14, events[-1].data['remaining_time'] ) # this value should update from the event self.assertEqual( 30, events[-1].data['timer']) # this value should be kept in cache # Clear all current events events = [] # event from ventilation plugin -> Timer has expired, but is still in manual mode self.controller.set_status( VentilationStatusDTO(43, 'manual', level=1, timer=None, remaining_time=None, last_seen=time.time())) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) self.assertEqual(None, events[-1].data['remaining_time']) self.assertEqual( None, events[-1].data['timer'] ) # this value should now be cleared when timer has done def test_ventilation_timer_expire_automatic(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(43)]): events = [] def callback(event): events.append(event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, callback) # event that ventilation box is running in automatic mode self.controller.set_status( VentilationStatusDTO(43, 'automatic', level=1, timer=None, remaining_time=None, last_seen=time.time())) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) self.assertEqual( None, events[-1].data['remaining_time']) # no timers running self.assertEqual(None, events[-1].data['timer']) # no timers running # Clear all current events events = [] # event that timer has been started self.controller.set_status( VentilationStatusDTO(43, 'automatic', level=1, timer=30, remaining_time=None, last_seen=time.time())) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) self.assertEqual( None, events[-1].data['remaining_time'] ) # There has not been an update from the ventilation box or plugin self.assertEqual(30, events[-1].data['timer']) for i in range(30, 0, -1): # Clear all current events events = [] # event from ventilation plugin mode = 'automatic' if i % 2 == 0 else 'manual' self.controller.set_status( VentilationStatusDTO(43, mode, level=1, timer=None, remaining_time=i, last_seen=time.time())) self.pubsub._publish_all_events() print(events) self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) self.assertEqual(i, events[-1].data['remaining_time']) self.assertEqual(30, events[-1].data['timer'] ) # this value should be kept in cache # Clear all current events events = [] # event from ventilation plugin -> Timer has expired, and has switched to automatic mode self.controller.set_status( VentilationStatusDTO(43, 'automatic', level=1, timer=None, remaining_time=None, last_seen=time.time())) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) self.assertEqual(1, len(self.controller._status)) self.assertEqual(None, events[-1].data['remaining_time']) self.assertEqual( None, events[-1].data['timer'] ) # this value should now be cleared when timer has done def test_ventilation_status_equal_evaluation(self): status_dto_1 = VentilationStatusDTO(1, 'automatic', level=1, timer=None, remaining_time=None, last_seen=time.time()) status_dto_2 = VentilationStatusDTO(1, 'manual', level=1, timer=30, remaining_time=None, last_seen=time.time()) status_dto_3 = VentilationStatusDTO(1, 'manual', level=1, timer=None, remaining_time=15, last_seen=time.time()) status_dto_4 = VentilationStatusDTO(1, 'manual', level=1, timer=None, remaining_time=None, last_seen=time.time()) status_dto_5 = VentilationStatusDTO(1, 'automatic', level=1, timer=None, remaining_time=None, last_seen=time.time()) self.assertEqual(True, (status_dto_1 == status_dto_5)) self.assertEqual(False, (status_dto_1 == status_dto_2)) self.assertNotEqual( status_dto_1, status_dto_2) # Difference between no timer and setup timer self.assertNotEqual( status_dto_1, status_dto_3) # difference between no timer and running timer self.assertNotEqual( status_dto_2, status_dto_3) # Difference between status and start timer status self.assertNotEqual(status_dto_1, status_dto_4) # difference in mode self.assertEqual(status_dto_1, status_dto_5) # Equal values, but different objects def test_ventilation_plugin_anti_ping_pong(self): # This test will see if the gateway will not keep sending events back and forth when an event change happens # This can be caused by ping-ponging back and forth between the plugin and gateway when sending updates 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(Ventilation, 'get', side_effect=get_ventilation), \ mock.patch.object(Ventilation, 'select', return_value=[get_ventilation(43)]): events = [] def callback(event, self=self): events.append(event) if len(events) > 20: self.fail( 'There should never be more than 20 events due to ventilation in this test' ) # resend the same event to mock the plugin who will send an event back with the same status # id, mode, level=None, timer=None, remaining_time=None, last_seen=None status_dto = VentilationStatusDTO( id=event.data['id'], mode=event.data['mode'], level=event.data['level'], remaining_time=event.data['timer'], ) self.controller.set_status(status_dto) self.pubsub._publish_all_events() self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, callback) # event that ventilation box is running in automatic mode self.controller.set_status( VentilationStatusDTO(43, 'automatic', level=1, timer=None, remaining_time=None, last_seen=time.time())) self.pubsub._publish_all_events() self.assertEqual(1, len(events)) # Clear all current events events = [] # event that timer has been started self.controller.set_status( VentilationStatusDTO(43, 'manual', level=1, timer=30, remaining_time=None, last_seen=time.time())) self.pubsub._publish_all_events() self.assertGreaterEqual(10, len(events)) # event that timer is running self.controller.set_status( VentilationStatusDTO(43, 'manual', level=1, timer=None, remaining_time=15, last_seen=time.time())) self.pubsub._publish_all_events() self.assertGreaterEqual(10, len(events)) # event that timer is done self.controller.set_status( VentilationStatusDTO(43, 'automatic', level=1, timer=None, remaining_time=None, last_seen=time.time())) self.pubsub._publish_all_events() self.assertGreaterEqual(10, len(events))
class PowerControllerTest(unittest.TestCase): @classmethod def setUpClass(cls): SetTestMode() def setUp(self): self.pubsub = PubSub() SetUpTestInjections(pubsub=self.pubsub) self.power_communicator = mock.Mock() SetUpTestInjections(power_communicator=self.power_communicator, power_store=mock.Mock()) self.controller = PowerController() def test_get_module_current(self): with mock.patch.object(self.power_communicator, 'do_command') as cmd: self.controller.get_module_current({ 'version': POWER_MODULE, 'address': '11.0' }) assert cmd.call_args_list == [ mock.call( '11.0', PowerCommand('G', 'CUR', '', '8f', module_type=bytearray(b'E'))) ] def test_get_module_frequency(self): with mock.patch.object(self.power_communicator, 'do_command') as cmd: self.controller.get_module_frequency({ 'version': POWER_MODULE, 'address': '11.0' }) assert cmd.call_args_list == [ mock.call( '11.0', PowerCommand('G', 'FRE', '', 'f', module_type=bytearray(b'E'))) ] def test_get_module_power(self): with mock.patch.object(self.power_communicator, 'do_command') as cmd: self.controller.get_module_power({ 'version': POWER_MODULE, 'address': '11.0' }) assert cmd.call_args_list == [ mock.call( '11.0', PowerCommand('G', 'POW', '', '8f', module_type=bytearray(b'E'))) ] def test_get_module_voltage(self): with mock.patch.object(self.power_communicator, 'do_command') as cmd: self.controller.get_module_voltage({ 'version': POWER_MODULE, 'address': '11.0' }) assert cmd.call_args_list == [ mock.call( '11.0', PowerCommand('G', 'VOL', '', 'f', module_type=bytearray(b'E'))) ] def test_get_module_day_energy(self): with mock.patch.object(self.power_communicator, 'do_command') as cmd: self.controller.get_module_day_energy({ 'version': POWER_MODULE, 'address': '11.0' }) assert cmd.call_args_list == [ mock.call( '11.0', PowerCommand('G', 'EDA', '', '8L', module_type=bytearray(b'E'))) ] def test_get_module_night_energy(self): with mock.patch.object(self.power_communicator, 'do_command') as cmd: self.controller.get_module_night_energy({ 'version': POWER_MODULE, 'address': '11.0' }) assert cmd.call_args_list == [ mock.call( '11.0', PowerCommand('G', 'ENI', '', '8L', module_type=bytearray(b'E'))) ] 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
class OutputControllerTest(unittest.TestCase): @classmethod def setUpClass(cls): SetTestMode() cls.test_db = SqliteDatabase(':memory:') def setUp(self): self.test_db.bind(MODELS, bind_refs=False, bind_backrefs=False) self.test_db.connect() self.test_db.create_tables(MODELS) self.master_controller = mock.Mock(MasterController) self.pubsub = PubSub() SetUpTestInjections( maintenance_controller=mock.Mock(MaintenanceController), master_controller=self.master_controller, message_client=mock.Mock(MessageClient), pubsub=self.pubsub) self.controller = OutputController() def tearDown(self): self.test_db.drop_tables(MODELS) self.test_db.close() 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) output_dto = OutputDTO(id=42) with mock.patch.object(self.master_controller, 'load_outputs', return_value=[output_dto]): self.controller.run_sync_orm() self.pubsub._publish_all_events() assert Output.select().where( Output.number == output_dto.id).count() == 1 assert GatewayEvent(GatewayEvent.Types.CONFIG_CHANGE, {'type': 'output'}) in events assert len(events) == 1 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_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 test_get_output_status(self): 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: OutputDTO(id=output_id)), \ mock.patch.object(self.master_controller, 'load_output_status', return_value=[OutputStateDTO(id=2, status=False), OutputStateDTO(id=40, status=True)]): self.controller._sync_state() status = self.controller.get_output_status(40) assert status == OutputStateDTO(id=40, status=True) def test_get_output_statuses(self): select_mock = mock.Mock() select_mock.join_from.return_value = [ Output(id=0, number=2), Output(id=1, number=40, module_type='D', 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: OutputDTO(id=output_id)), \ mock.patch.object(self.master_controller, 'load_output_status', return_value=[OutputStateDTO(id=2, status=False, dimmer=0), OutputStateDTO(id=40, status=True, dimmer=50)]): self.controller._sync_state() status = self.controller.get_output_statuses() assert len(status) == 2 assert OutputStateDTO(id=2, status=False) in status assert OutputStateDTO(id=40, status=True, dimmer=50) in status def test_load_output(self): where_mock = mock.Mock() where_mock.get.return_value = Output(id=1, number=42, room=Room(id=2, number=3)) join_from_mock = mock.Mock() join_from_mock.where.return_value = where_mock select_mock = mock.Mock() select_mock.join_from.return_value = join_from_mock with mock.patch.object(Output, 'select', return_value=select_mock), \ mock.patch.object(self.master_controller, 'load_output', return_value=OutputDTO(id=42)) as load: output = self.controller.load_output(42) assert output == OutputDTO(id=42, room=3) load.assert_called_with(output_id=42) def test_load_outputs(self): select_mock = mock.Mock() select_mock.join_from.return_value = [ Output(id=1, number=42, room=Room(id=2, number=3)) ] with mock.patch.object(Output, 'select', return_value=select_mock), \ mock.patch.object(self.master_controller, 'load_output', return_value=OutputDTO(id=42)) as load: outputs = self.controller.load_outputs() assert OutputDTO(id=42, room=3) in outputs load.assert_called_with(output_id=42) def test_output_actions(self): floor = Floor.create(number=5) room = Room.create(number=10, floor=floor) Output.create(number=2, room=room) Output.create(number=3) with mock.patch.object(self.master_controller, 'set_all_lights') as call: self.controller.set_all_lights(action='OFF') call.assert_called_once_with(action='OFF') with mock.patch.object(self.master_controller, 'set_all_lights') as call: self.controller.set_all_lights(action='ON') call.assert_called_once_with(action='ON') with mock.patch.object(self.master_controller, 'set_all_lights') as call: self.controller.set_all_lights(action='OFF', floor_id=1) call.assert_called_once_with(action='OFF', floor_id=1, output_ids=[]) with mock.patch.object(self.master_controller, 'set_all_lights') as call: self.controller.set_all_lights(action='OFF', floor_id=5) call.assert_called_once_with(action='OFF', floor_id=5, output_ids=[2]) with mock.patch.object(self.master_controller, 'set_all_lights') as call: self.controller.set_all_lights(action='ON', floor_id=5) call.assert_called_once_with(action='ON', floor_id=5, output_ids=[2])
class MasterCoreControllerTest(unittest.TestCase): """ Tests for MasterCoreController. """ @classmethod def setUpClass(cls): SetTestMode() def setUp(self): self.memory = {} self.return_data = {} def _do_command(command, fields, timeout=None): _ = timeout instruction = ''.join(str(chr(c)) for c in command.instruction) if instruction == 'MR': page = fields['page'] start = fields['start'] length = fields['length'] return {'data': self.memory.get(page, bytearray([255] * 256))[start:start + length]} elif instruction == 'MW': page = fields['page'] start = fields['start'] page_data = self.memory.setdefault(page, bytearray([255] * 256)) for index, data_byte in enumerate(fields['data']): page_data[start + index] = data_byte elif instruction in self.return_data: return self.return_data[instruction] else: raise AssertionError('unexpected instruction: {0}'.format(instruction)) def _do_basic_action(action_type, action, device_nr=0, extra_parameter=0, timeout=2, log=True): _ = device_nr, extra_parameter, timeout, log if action_type == 200 and action == 1: # Send EEPROM_ACTIVATE event eeprom_file._handle_event({'type': 254, 'action': 0, 'device_nr': 0, 'data': 0}) self.communicator = mock.Mock(CoreCommunicator) self.communicator.do_command = _do_command self.communicator.do_basic_action = _do_basic_action self.pubsub = PubSub() SetUpTestInjections(master_communicator=self.communicator, pubsub=self.pubsub) eeprom_file = MemoryFile(MemoryTypes.EEPROM) eeprom_file._cache = self.memory SetUpTestInjections(memory_files={MemoryTypes.EEPROM: eeprom_file, MemoryTypes.FRAM: MemoryFile(MemoryTypes.FRAM)}, ucan_communicator=UCANCommunicator(), slave_communicator=SlaveCommunicator()) self.controller = MasterCoreController() # For testing purposes, remove read-only flag from certain properties for field_name in ['device_type', 'address', 'firmware_version']: for model_type in [OutputModuleConfiguration, InputModuleConfiguration, SensorModuleConfiguration]: if hasattr(model_type, '_{0}'.format(field_name)): getattr(model_type, '_{0}'.format(field_name))._read_only = False else: getattr(model_type, field_name)._read_only = False def test_master_output_event(self): events = [] def _on_event(master_event): events.append(master_event) self.pubsub.subscribe_master_events(PubSub.MasterTopics.OUTPUT, _on_event) events = [] self.controller._handle_event({'type': 0, 'device_nr': 0, 'action': 0, 'data': bytearray([255, 0, 0, 0])}) self.controller._handle_event({'type': 0, 'device_nr': 2, 'action': 1, 'data': bytearray([100, 2, 0xff, 0xfe])}) self.pubsub._publish_all_events() self.assertEqual([MasterEvent(MasterEvent.Types.OUTPUT_STATUS, {'id': 0, 'status': False, 'dimmer': 255, 'ctimer': 0}), MasterEvent(MasterEvent.Types.OUTPUT_STATUS, {'id': 2, 'status': True, 'dimmer': 100, 'ctimer': 65534})], events) def test_master_shutter_event(self): events = [] def _on_event(master_event): events.append(master_event) self.pubsub.subscribe_master_events(PubSub.MasterTopics.SHUTTER, _on_event) self.controller._output_states = {0: OutputStateDTO(id=0, status=False), 10: OutputStateDTO(id=10, status=False), 11: OutputStateDTO(id=11, status=False)} self.controller._output_shutter_map = {10: 1, 11: 1} self.controller._shutter_status = {1: (False, False)} self.pubsub._publish_all_events() with mock.patch.object(gateway.hal.master_controller_core, 'ShutterConfiguration', side_effect=get_core_shutter_dummy): events = [] self.controller._handle_event({'type': 0, 'device_nr': 10, 'action': 0, 'data': [None, 0, 0, 0]}) self.controller._handle_event({'type': 0, 'device_nr': 11, 'action': 0, 'data': [None, 0, 0, 0]}) self.pubsub._publish_all_events() assert [] == events events = [] self.controller._handle_event({'type': 0, 'device_nr': 10, 'action': 1, 'data': [None, 0, 0, 0]}) self.pubsub._publish_all_events() assert [MasterEvent('SHUTTER_CHANGE', {'id': 1, 'status': 'going_up', 'location': {'room_id': 255}})] == events events = [] self.controller._handle_event({'type': 0, 'device_nr': 11, 'action': 1, 'data': [None, 0, 0, 0]}) self.pubsub._publish_all_events() assert [MasterEvent('SHUTTER_CHANGE', {'id': 1, 'status': 'stopped', 'location': {'room_id': 255}})] == events events = [] self.controller._handle_event({'type': 0, 'device_nr': 10, 'action': 0, 'data': [None, 0, 0, 0]}) self.pubsub._publish_all_events() assert [MasterEvent('SHUTTER_CHANGE', {'id': 1, 'status': 'going_down', 'location': {'room_id': 255}})] == events events = [] self.controller._handle_event({'type': 0, 'device_nr': 11, 'action': 0, 'data': [None, 0, 0, 0]}) self.pubsub._publish_all_events() assert [MasterEvent('SHUTTER_CHANGE', {'id': 1, 'status': 'stopped', 'location': {'room_id': 255}})] == events def test_master_shutter_refresh(self): events = [] def _on_event(master_event): events.append(master_event) self.pubsub.subscribe_master_events(PubSub.MasterTopics.SHUTTER, _on_event) output_status = [{'device_nr': 0, 'status': False, 'dimmer': 0}, {'device_nr': 1, 'status': False, 'dimmer': 0}, {'device_nr': 10, 'status': False, 'dimmer': 0}, {'device_nr': 11, 'status': False, 'dimmer': 0}] with mock.patch.object(gateway.hal.master_controller_core, 'ShutterConfiguration', side_effect=get_core_shutter_dummy), \ mock.patch.object(self.controller, 'load_output_status', return_value=output_status): events = [] self.controller._refresh_shutter_states() self.pubsub._publish_all_events() assert [MasterEvent('SHUTTER_CHANGE', {'id': 1, 'status': 'stopped', 'location': {'room_id': 255}})] == events output_status = [{'device_nr': 0, 'status': False, 'dimmer': 0}, {'device_nr': 1, 'status': True, 'dimmer': 0}, {'device_nr': 10, 'status': True, 'dimmer': 0}, {'device_nr': 11, 'status': False, 'dimmer': 0}] with mock.patch.object(gateway.hal.master_controller_core, 'ShutterConfiguration', side_effect=get_core_shutter_dummy), \ mock.patch.object(self.controller, 'load_output_status', return_value=output_status): events = [] self.controller._refresh_shutter_states() self.pubsub._publish_all_events() assert [MasterEvent('SHUTTER_CHANGE', {'id': 1, 'status': 'going_up', 'location': {'room_id': 255}})] == events output_status = [{'device_nr': 0, 'status': False, 'dimmer': 0}, {'device_nr': 1, 'status': True, 'dimmer': 0}, {'device_nr': 10, 'status': False, 'dimmer': 0}, {'device_nr': 11, 'status': True, 'dimmer': 0}] with mock.patch.object(gateway.hal.master_controller_core, 'ShutterConfiguration', side_effect=get_core_shutter_dummy), \ mock.patch.object(self.controller, 'load_output_status', return_value=output_status): events = [] self.controller._refresh_shutter_states() self.pubsub._publish_all_events() assert [MasterEvent('SHUTTER_CHANGE', {'id': 1, 'status': 'going_down', 'location': {'room_id': 255}})] == events def test_input_module_type(self): with mock.patch.object(gateway.hal.master_controller_core, 'InputConfiguration', return_value=get_core_input_dummy(1)): data = self.controller.get_input_module_type(1) self.assertEqual('I', data) def test_load_input(self): data = self.controller.load_input(1) self.assertEqual(data.id, 1) def test_load_inputs(self): input_modules = list(map(get_core_input_dummy, range(1, 17))) self.return_data['GC'] = {'input': 2} with mock.patch.object(gateway.hal.master_controller_core, 'InputConfiguration', side_effect=input_modules): inputs = self.controller.load_inputs() self.assertEqual([x.id for x in inputs], list(range(1, 17))) def test_save_inputs(self): data = [(InputDTO(id=1, name='foo', module_type='I'), ['id', 'name', 'module_type']), (InputDTO(id=2, name='bar', module_type='I'), ['id', 'name', 'module_type'])] input_mock = mock.Mock(InputConfiguration) with mock.patch.object(InputConfiguration, 'deserialize', return_value=input_mock) as deserialize, \ mock.patch.object(input_mock, 'save', return_value=None) as save: self.controller.save_inputs(data) self.assertIn(mock.call({'id': 1, 'name': 'foo'}), deserialize.call_args_list) self.assertIn(mock.call({'id': 2, 'name': 'bar'}), deserialize.call_args_list) save.assert_called_with() def test_inputs_with_status(self): from gateway.hal.master_controller_core import MasterInputState with mock.patch.object(MasterInputState, 'get_inputs', return_value=[]) as get: self.controller.get_inputs_with_status() get.assert_called_with() def test_recent_inputs(self): from gateway.hal.master_controller_core import MasterInputState with mock.patch.object(MasterInputState, 'get_recent', return_value=[]) as get: self.controller.get_recent_inputs() get.assert_called_with() def test_event_consumer(self): with mock.patch.object(gateway.hal.master_controller_core, 'BackgroundConsumer', return_value=None) as new_consumer: controller = MasterCoreController() expected_call = mock.call(CoreAPI.event_information(), 0, mock.ANY) self.assertIn(expected_call, new_consumer.call_args_list) def test_subscribe_input_events(self): consumer_list = [] def new_consumer(*args): consumer = BackgroundConsumer(*args) consumer_list.append(consumer) return consumer subscriber = mock.Mock() with mock.patch.object(gateway.hal.master_controller_core, 'BackgroundConsumer', side_effect=new_consumer) as new_consumer: controller = MasterCoreController() self.pubsub.subscribe_master_events(PubSub.MasterTopics.INPUT, subscriber.callback) new_consumer.assert_called() event_data = {'type': 1, 'action': 1, 'device_nr': 2, 'data': {}} with mock.patch.object(Queue, 'get', return_value=event_data): consumer_list[0].deliver() self.pubsub._publish_all_events() expected_event = MasterEvent.deserialize({'type': 'INPUT_CHANGE', 'data': {'id': 2, 'status': True, 'location': {'room_id': 255}}}) subscriber.callback.assert_called_with(expected_event) def test_get_modules(self): from master.core.memory_models import ( InputModuleConfiguration, OutputModuleConfiguration, SensorModuleConfiguration, GlobalConfiguration ) global_configuration = GlobalConfiguration() global_configuration.number_of_output_modules = 5 global_configuration.number_of_input_modules = 4 global_configuration.number_of_sensor_modules = 2 global_configuration.number_of_can_control_modules = 2 global_configuration.save() for module_id, module_class, device_type, address in [(0, InputModuleConfiguration, 'I', '{0}.123.123.123'.format(ord('I'))), (1, InputModuleConfiguration, 'i', '{0}.123.123.123'.format(ord('i'))), (2, InputModuleConfiguration, 'i', '{0}.000.000.000'.format(ord('i'))), (3, InputModuleConfiguration, 'b', '{0}.123.132.123'.format(ord('b'))), (0, OutputModuleConfiguration, 'o', '{0}.000.000.000'.format(ord('o'))), (1, OutputModuleConfiguration, 'o', '{0}.000.000.001'.format(ord('o'))), (2, OutputModuleConfiguration, 'o', '{0}.000.000.002'.format(ord('o'))), (3, OutputModuleConfiguration, 'o', '{0}.123.123.123'.format(ord('o'))), (4, OutputModuleConfiguration, 'O', '{0}.123.123.123'.format(ord('O'))), (0, SensorModuleConfiguration, 's', '{0}.123.123.123'.format(ord('s'))), (1, SensorModuleConfiguration, 'T', '{0}.123.123.123'.format(ord('T')))]: instance = module_class(module_id) instance.device_type = device_type instance.address = address instance.save() self.assertEqual({'can_inputs': ['I', 'T', 'C', 'E'], 'inputs': ['I', 'i', 'J', 'T'], 'outputs': ['P', 'P', 'P', 'o', 'O'], 'shutters': []}, self.controller.get_modules()) def test_master_eeprom_event(self): master_event = MasterEvent(MasterEvent.Types.EEPROM_CHANGE, {}) self.controller._output_last_updated = 1603178386.0 self.pubsub.publish_master_event(PubSub.MasterTopics.EEPROM, master_event) self.pubsub._publish_all_events() assert self.controller._output_last_updated == 0
class PowerCommunicatorTest(unittest.TestCase): """ Tests for PowerCommunicator class """ @classmethod def setUpClass(cls): SetTestMode() def setUp(self): self.pubsub = PubSub() SetUpTestInjections(pubsub=self.pubsub) self.power_data = [] # type: list SetUpTestInjections(power_db=':memory:') self.serial = RS485(SerialMock(self.power_data)) self.store = PowerStore() SetUpTestInjections(power_serial=self.serial, power_store=self.store) self.communicator = PowerCommunicator() def tearDown(self): self.communicator.stop() self.serial.stop() def test_do_command(self): """ Test for standard behavior PowerCommunicator.do_command. """ action = power_api.get_voltage(power_api.POWER_MODULE) self.power_data.extend([ sin(action.create_input(1, 1)), sout(action.create_output(1, 1, 49.5)) ]) self.serial.start() self.communicator.start() output = self.communicator.do_command(1, action) self.assertEqual((49.5, ), output) self.assertEqual(14, self.communicator.get_communication_statistics()['bytes_written']) self.assertEqual(18, self.communicator.get_communication_statistics()['bytes_read']) def test_do_command_timeout_once(self): """ Test for timeout in PowerCommunicator.do_command. """ action = power_api.get_voltage(power_api.POWER_MODULE) self.power_data.extend([ sin(action.create_input(1, 1)), sout(bytearray()), sin(action.create_input(1, 2)), sout(action.create_output(1, 2, 49.5)) ]) self.serial.start() self.communicator.start() output = self.communicator.do_command(1, action) self.assertEqual((49.5, ), output) def test_do_command_timeout_twice(self): """ Test for timeout in PowerCommunicator.do_command. """ action = power_api.get_voltage(power_api.POWER_MODULE) self.power_data.extend([ sin(action.create_input(1, 1)), sout(bytearray()), sin(action.create_input(1, 2)), sout(bytearray()) ]) self.serial.start() self.communicator.start() with self.assertRaises(CommunicationTimedOutException): self.communicator.do_command(1, action) def test_do_command_split_data(self): """ Test PowerCommunicator.do_command when the data is split over multiple reads. """ action = power_api.get_voltage(power_api.POWER_MODULE) out = action.create_output(1, 1, 49.5) self.power_data.extend([ sin(action.create_input(1, 1)), sout(out[:5]), sout(out[5:]) ]) self.serial.start() self.communicator.start() output = self.communicator.do_command(1, action) self.assertEqual((49.5, ), output) def test_wrong_response(self): """ Test PowerCommunicator.do_command when the power module returns a wrong response. """ action_1 = power_api.get_voltage(power_api.POWER_MODULE) action_2 = power_api.get_frequency(power_api.POWER_MODULE) self.power_data.extend([ sin(action_1.create_input(1, 1)), sout(action_2.create_output(3, 2, 49.5)) ]) self.serial.start() self.communicator.start() with self.assertRaises(Exception): self.communicator.do_command(1, action_1) @mark.slow def test_address_mode(self): """ Test the address mode. """ events = [] def handle_events(master_event): events.append(master_event) self.pubsub.subscribe_master_events(PubSub.MasterTopics.POWER, handle_events) sad = power_api.set_addressmode(power_api.POWER_MODULE) sad_p1c = power_api.set_addressmode(power_api.P1_CONCENTRATOR) self.power_data.extend([ sin(sad.create_input(power_api.BROADCAST_ADDRESS, 1, power_api.ADDRESS_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 2, power_api.ADDRESS_MODE)), sout(power_api.want_an_address(power_api.POWER_MODULE).create_output(0, 0)), sin(power_api.set_address(power_api.POWER_MODULE).create_input(0, 0, 1)), sout(power_api.want_an_address(power_api.ENERGY_MODULE).create_output(0, 0)), sin(power_api.set_address(power_api.ENERGY_MODULE).create_input(0, 0, 2)), sout(power_api.want_an_address(power_api.P1_CONCENTRATOR).create_output(0, 0)), sin(power_api.set_address(power_api.P1_CONCENTRATOR).create_input(0, 0, 3)), sout(bytearray()), # Timeout read after 1 second sin(sad.create_input(power_api.BROADCAST_ADDRESS, 3, power_api.NORMAL_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 4, power_api.NORMAL_MODE)) ]) self.serial.start() self.communicator.start() self.assertEqual(self.store.get_free_address(), 1) self.communicator.start_address_mode() self.assertTrue(self.communicator.in_address_mode()) self.pubsub._publish_all_events() time.sleep(0.5) assert [] == events self.communicator.stop_address_mode() self.pubsub._publish_all_events() assert MasterEvent(MasterEvent.Types.POWER_ADDRESS_EXIT, {}) in events assert len(events) == 1 self.assertEqual(self.store.get_free_address(), 4) self.assertFalse(self.communicator.in_address_mode()) @mark.slow def test_do_command_in_address_mode(self): """ Test the behavior of do_command in address mode.""" action = power_api.get_voltage(power_api.POWER_MODULE) sad = power_api.set_addressmode(power_api.POWER_MODULE) sad_p1c = power_api.set_addressmode(power_api.P1_CONCENTRATOR) self.power_data.extend([ sin(sad.create_input(power_api.BROADCAST_ADDRESS, 1, power_api.ADDRESS_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 2, power_api.ADDRESS_MODE)), sout(bytearray()), # Timeout read after 1 second sin(sad.create_input(power_api.BROADCAST_ADDRESS, 3, power_api.NORMAL_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 4, power_api.NORMAL_MODE)), sin(action.create_input(1, 5)), sout(action.create_output(1, 5, 49.5)) ]) self.serial.start() self.communicator.start() self.communicator.start_address_mode() with self.assertRaises(InAddressModeException): self.communicator.do_command(1, action) self.communicator.stop_address_mode() self.assertEqual((49.5, ), self.communicator.do_command(1, action)) @mark.slow def test_address_mode_timeout(self): """ Test address mode timeout. """ action = power_api.get_voltage(power_api.POWER_MODULE) sad = power_api.set_addressmode(power_api.POWER_MODULE) sad_p1c = power_api.set_addressmode(power_api.P1_CONCENTRATOR) self.power_data.extend([ sin(sad.create_input(power_api.BROADCAST_ADDRESS, 1, power_api.ADDRESS_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 2, power_api.ADDRESS_MODE)), sout(bytearray()), # Timeout read after 1 second sin(sad.create_input(power_api.BROADCAST_ADDRESS, 3, power_api.NORMAL_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 4, power_api.NORMAL_MODE)), sin(action.create_input(1, 5)), sout(action.create_output(1, 5, 49.5)) ]) self.communicator = PowerCommunicator(address_mode_timeout=1) self.serial.start() self.communicator.start() self.communicator.start_address_mode() time.sleep(1.1) self.assertEqual((49.5, ), self.communicator.do_command(1, action)) @mark.slow def test_timekeeper(self): """ Test the TimeKeeper. """ self.store.register_power_module(1, power_api.POWER_MODULE) time_action = power_api.set_day_night(power_api.POWER_MODULE) times = [power_api.NIGHT for _ in range(8)] action = power_api.get_voltage(power_api.POWER_MODULE) self.power_data.extend([ sin(time_action.create_input(1, 1, *times)), sout(time_action.create_output(1, 1)), sin(action.create_input(1, 2)), sout(action.create_output(1, 2, 243)) ]) self.communicator = PowerCommunicator(time_keeper_period=1) self.serial.start() self.communicator.start() time.sleep(1.5) self.assertEqual((243, ), self.communicator.do_command(1, action))