示例#1
0
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()
示例#2
0
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
示例#4
0
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))
示例#5
0
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
示例#6
0
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])