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_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))
Exemple #3
0
 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_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
    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_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_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_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_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)
Exemple #10
0
    def test_position_reporting(self):
        calls = {}

        def shutter_direction(direction, _shutter_id):
            calls.setdefault(_shutter_id, []).append(direction)

        master_controller = Mock()
        master_controller.shutter_up = lambda id: shutter_direction('up', id)
        master_controller.shutter_down = lambda id: 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)

        # Positionned calls on non-positional shutters should fail
        calls = {}
        for shutter_id in [0, 1]:
            message = 'Shutter {0} does not support positioning'.format(
                shutter_id)
            with self.assertRaises(RuntimeError) as ex:
                controller.report_shutter_position(shutter_id, 5)
            self.assertEqual(str(ex.exception), message)
        self.assertEqual(len(calls), 0)

        #                             +- 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
                    self.assertEqual(calls[shutter_id].pop(), '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])
Exemple #11
0
    def test_goto_position(self):
        calls = {}

        def shutter_direction(direction, _shutter_id):
            calls.setdefault(_shutter_id, []).append(direction)

        master_controller = Mock()
        master_controller.shutter_up = lambda id: shutter_direction('up', id)
        master_controller.shutter_down = lambda id: 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)

        # Positionned calls on non-positional shutters should fail
        calls = {}
        for shutter_id in [0, 1]:
            message = 'Shutter {0} does not support positioning'.format(
                shutter_id)
            with self.assertRaises(RuntimeError) as ex:
                controller.shutter_goto(shutter_id, 5)
            self.assertEqual(str(ex.exception), message)
        self.assertEqual(len(calls), 0)

        #                             +- 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.UP,
                     'up'],  # down = 0, up = 100
                    [10, 5, ShutterEnums.Direction.DOWN, 'down']
                ]
        }.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')
                controller.shutter_goto(shutter_id, entry[1])
                self.assertEqual(controller._directions[shutter_id], entry[2])
                self.assertEqual(calls[shutter_id].pop(), entry[3])