Beispiel #1
0
 def test_one_minute_schedule(self):
     now_offset = 1577836800  # 2020
     minute = 60
     fakesleep.reset(seconds=now_offset)
     SchedulingController.TIMEZONE = 'Europe/Brussels'
     schedule = ScheduleDTO(id=1,
                            name='schedule',
                            start=0 * minute,
                            repeat='* * * * *',
                            duration=None,
                            end=now_offset + 60 * minute,
                            action='GROUP_ACTION',
                            arguments=1,
                            status='ACTIVE')
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertFalse(schedule.is_due)
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertEqual(now_offset + 1 * minute, schedule.next_execution)
     time.sleep(1 * minute)
     self.assertTrue(schedule.is_due)
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertEqual(now_offset + 2 * minute, schedule.next_execution)
     time.sleep(1 * minute)
     self.assertTrue(schedule.is_due)
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertEqual(now_offset + 3 * minute, schedule.next_execution)
    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()
Beispiel #3
0
 def test_midnight_crossover(self):
     now_offset = 1615939080  # 16/03/21 23:58
     minute = 60
     fakesleep.reset(seconds=now_offset
                     )  # Tricks the system to be in the time we specified
     SchedulingController.TIMEZONE = 'Europe/Brussels'
     schedule = ScheduleDTO(id=1,
                            name='schedule',
                            start=0 * minute,
                            repeat='* * * * *',
                            duration=None,
                            end=now_offset + 10 * minute,
                            action='GROUP_ACTION',
                            arguments=1,
                            status='ACTIVE')
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertFalse(schedule.is_due)
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertEqual(now_offset + 1 * minute, schedule.next_execution)
     time.sleep(1 * minute)
     self.assertTrue(schedule.is_due)
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertEqual(now_offset + 2 * minute, schedule.next_execution)
     time.sleep(1 * minute)
     self.assertTrue(schedule.is_due)
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertEqual(now_offset + 3 * minute, schedule.next_execution)
Beispiel #4
0
    def set(self, seconds=None):
        """Set the global fake time to the given epoch or the real current one.

        :param seconds: Fake current time, in seconds since the epoch. If
             ``None``, use the real current time.
        """
        reset(seconds=seconds)
Beispiel #5
0
 def test_schedule_is_due(self):
     now_offset = 1577836800  # 2020-01-01
     offset_2018 = 1514764800  # 2018-01-01
     minute = 60
     hour = 60 * minute
     fakesleep.reset(seconds=offset_2018)
     SchedulingController.TIMEZONE = 'Europe/Brussels'
     schedule = ScheduleDTO(id=1,
                            name='schedule',
                            start=offset_2018,
                            repeat='0 * * * *',
                            duration=None,
                            end=now_offset + 24 * hour,
                            action='GROUP_ACTION',
                            arguments=1,
                            status='ACTIVE')
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertFalse(schedule.is_due)
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertEqual(SchedulingController.NO_NTP_LOWER_LIMIT + 1 * hour,
                      schedule.next_execution)
     time.sleep(1 * hour)
     self.assertFalse(schedule.is_due)  # Date is before 2019
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertEqual(SchedulingController.NO_NTP_LOWER_LIMIT + 1 * hour,
                      schedule.next_execution)
     fakesleep.reset(seconds=now_offset)
     self.assertFalse(schedule.is_due)  # Time jump is ignored
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertEqual(now_offset + 1 * hour, schedule.next_execution)
     time.sleep(1 * hour)
     self.assertTrue(schedule.is_due)
     schedule.next_execution = SchedulingController._get_next_execution(
         schedule)
     self.assertEqual(now_offset + 2 * hour, schedule.next_execution)
Beispiel #6
0
    def test_edge_cases_cron(self):
        now_offset = 1616371020  # Date and time: Sunday, March 21, 2021 11:57:00 PM GMT
        minute = 60
        SchedulingController.TIMEZONE = 'Europe/Brussels'
        fakesleep.reset(seconds=now_offset)
        SchedulingController.TIMEZONE = 'Europe/Brussels'
        schedule = ScheduleDTO(
            id=1,
            name='schedule',
            start=200,  # Thursday, January 1, 1970 1:03:20 AM
            repeat='* * * * *',  # every minute
            duration=None,
            end=now_offset + 10 * minute,  # for 10 minuted
            action='GROUP_ACTION',
            arguments=1,
            status='ACTIVE')
        # Should be 1616371020 + 60 = 1616371080
        schedule.next_execution = SchedulingController._get_next_execution(
            schedule)
        self.assertFalse(schedule.is_due)
        # Should still be 1616371020 + 60 = 1616371080
        for i in range(1, 11):
            schedule.next_execution = SchedulingController._get_next_execution(
                schedule)
            self.assertEqual(now_offset + i * minute, schedule.next_execution)
            time.sleep(minute)
            self.assertTrue(schedule.is_due)

        # now testing end of month behaviour
        now_offset = 1617235020  # 2021-03-31 23:57:00
        fakesleep.reset(seconds=now_offset)
        schedule.end = now_offset + 10 * minute  # Update when the schedule should end
        for i in range(1, 11):
            schedule.next_execution = SchedulingController._get_next_execution(
                schedule)
            self.assertEqual(now_offset + i * minute, schedule.next_execution)
            time.sleep(minute)
            self.assertTrue(schedule.is_due)
    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']})
Beispiel #8
0
 def setUpClass(cls):
     SetTestMode()
     fakesleep.monkey_patch()
     fakesleep.reset(seconds=0)
     cls.test_db = SqliteDatabase(':memory:')
Beispiel #9
0
 def setUpClass(cls):
     SetTestMode()
     fakesleep.monkey_patch()
     fakesleep.reset(seconds=0)
Beispiel #10
0
    def test_master_events_and_state(self):
        fakesleep.reset(0)
        calls = {}

        master_communicator = Mock()
        controller = ShutterController(master_communicator)
        controller.update_config(ShutterControllerTest.SHUTTER_CONFIG)

        def shutter_callback(_shutter_id, _shutter_data, _state):
            calls.setdefault(_shutter_id, []).append([_shutter_data, _state])

        controller.set_shutter_changed_callback(shutter_callback)

        def validate(_shutter_id, _entry):
            self.assertEquals(controller._actual_positions.get(_shutter_id),
                              _entry[0])
            self.assertEquals(controller._desired_positions.get(_shutter_id),
                              _entry[1])
            self.assertEquals(controller._directions.get(_shutter_id),
                              _entry[2])
            self.assertEquals(controller._states.get(_shutter_id), _entry[3])
            if len(_entry) == 4 or _entry[4]:
                self.assertEqual(calls[_shutter_id].pop()[1],
                                 _entry[3][1].upper())

        controller.update_from_master_state({
            'module_nr': 0,
            'status': 0b00000000
        })
        for shutter_id in xrange(3):
            #                     +- actual position
            #                     |     +- desired position
            #                     |     |     +- direction                      +- state                              +- optional skip call check
            #                     v     v     v                                 v                                     v
            validate(shutter_id, [
                None, None, ShutterController.Direction.STOP,
                [0, ShutterController.State.STOPPED], False
            ])

        for shutter_id in xrange(3):
            controller.shutter_down(shutter_id, None)

        time.sleep(20)

        controller.update_from_master_state({
            'module_nr': 0,
            'status': 0b00010101
        })
        #                             +- actual position
        #                             |     +- desired position
        #                             |     |     +- direction                      +- state
        #                             v     v     v                                 v
        for shutter_id, entry in {
                0: [
                    None, None, ShutterController.Direction.DOWN,
                    [20, ShutterController.State.GOING_DOWN]
                ],
                1: [
                    None, None, ShutterController.Direction.UP,
                    [20, ShutterController.State.GOING_UP]
                ],
                2: [
                    None, 99, ShutterController.Direction.DOWN,
                    [20, ShutterController.State.GOING_DOWN]
                ]
        }.iteritems():
            validate(shutter_id, entry)

        time.sleep(50)  # Standard shutters will be down now

        controller._actual_positions[2] = 20  # Simulate position reporting
        controller.update_from_master_state({
            'module_nr': 0,
            'status': 0b00010100
        })  # First shutter motor stop
        #                             +- actual position
        #                             |     +- desired position
        #                             |     |     +- direction                      +- state                                  +- optional skip call check
        #                             v     v     v                                 v                                         v
        for shutter_id, entry in {
                0: [
                    None, None, ShutterController.Direction.STOP,
                    [70, ShutterController.State.STOPPED]
                ],
                1: [
                    None, None, ShutterController.Direction.UP,
                    [20, ShutterController.State.GOING_UP], False
                ],
                2: [
                    20, 99, ShutterController.Direction.DOWN,
                    [20, ShutterController.State.GOING_DOWN], False
                ]
        }.iteritems():
            validate(shutter_id, entry)

        time.sleep(50)  # Standard shutters will be down now

        controller._actual_positions[2] = 50  # Simulate position reporting
        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: [
                    None, None, ShutterController.Direction.STOP,
                    [70, ShutterController.State.STOPPED], False
                ],
                1: [
                    None, None, ShutterController.Direction.STOP,
                    [120, ShutterController.State.UP]
                ],
                2: [
                    50, 99, ShutterController.Direction.DOWN,
                    [20, ShutterController.State.GOING_DOWN], False
                ]
        }.iteritems():
            validate(shutter_id, entry)

        time.sleep(10)

        controller._actual_positions[2] = 50  # Simulate position reporting
        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: [
                    None, None, ShutterController.Direction.STOP,
                    [70, ShutterController.State.STOPPED], False
                ],
                1: [
                    None, None, ShutterController.Direction.STOP,
                    [120, ShutterController.State.UP], False
                ],
                2: [
                    50, 99, ShutterController.Direction.STOP,
                    [130, ShutterController.State.STOPPED]
                ]
        }.iteritems():
            validate(shutter_id, entry)

        controller._actual_positions[2] = 60  # Simulate position reporting
        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: [
                    None, None, ShutterController.Direction.STOP,
                    [70, ShutterController.State.STOPPED], False
                ],
                1: [
                    None, None, ShutterController.Direction.STOP,
                    [120, ShutterController.State.UP], False
                ],
                2: [
                    60, 99, ShutterController.Direction.DOWN,
                    [130, ShutterController.State.GOING_DOWN]
                ]
        }.iteritems():
            validate(shutter_id, entry)

        controller._actual_positions[2] = 99  # Simulate position reporting
        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: [
                    None, None, ShutterController.Direction.STOP,
                    [70, ShutterController.State.STOPPED], False
                ],
                1: [
                    None, None, ShutterController.Direction.STOP,
                    [120, ShutterController.State.UP], False
                ],
                2: [
                    99, 99, ShutterController.Direction.STOP,
                    [130, ShutterController.State.DOWN]
                ]
        }.iteritems():
            validate(shutter_id, entry)

        states = controller.get_states()
        states['status'].pop(3)  # Remove the "unused" shutter
        states['detail'].pop(3)
        self.assertEqual(
            states, {
                'detail': {
                    0: {
                        'actual_position': None,
                        'desired_position': None,
                        'state': 'stopped'
                    },
                    1: {
                        'actual_position': None,
                        'desired_position': None,
                        'state': 'up'
                    },
                    2: {
                        'actual_position': 99,
                        'desired_position': 99,
                        'state': 'down'
                    }
                },
                'status': ['stopped', 'up', 'down']
            })