def setUp(self):
        Relay.transition_wait_time = 0
        Relay.button_press_time = 0
        self.test_dir = 'file://' + tempfile.mkdtemp() + '/'

        # Creates the files used for testing
        self._set_status_page('0000000000000000')
        with open(self.test_dir[7:] + '00', 'w+') as file:
            file.write(self.RELAY_OFF_PAGE_CONTENTS)
        with open(self.test_dir[7:] + '01', 'w+') as file:
            file.write(self.RELAY_ON_PAGE_CONTENTS)

        self.config = ({
            'name':
            'SSBoard',
            'base_url':
            self.test_dir,
            'relays': [{
                'name': '0',
                'relay_pos': 0
            }, {
                'name': '1',
                'relay_pos': 1
            }, {
                'name': '2',
                'relay_pos': 7
            }]
        })
        self.ss_board = SainSmartBoard(self.config)
        self.r0 = Relay(self.ss_board, 0)
        self.r1 = Relay(self.ss_board, 1)
        self.r7 = Relay(self.ss_board, 7)
 def test_set_off(self):
     patch_path = 'acts.controllers.relay_lib.sain_smart_board.urlopen'
     with patch(patch_path) as urlopen:
         board = SainSmartBoard(self.config)
         board.status_dict = {}
         board.set(self.r0.position, RelayState.NO)
     urlopen.assert_called_once_with('%s%s' %
                                     (self.ss_board.base_url, '00'))
class ActsSainSmartBoardTest(unittest.TestCase):
    STATUS_MSG = ('<small><a href="{}"></a>'
                  '</small><a href="{}/{}TUX">{}TUX</a><p>')

    RELAY_ON_PAGE_CONTENTS = 'relay_on page'
    RELAY_OFF_PAGE_CONTENTS = 'relay_off page'

    def setUp(self):
        Relay.transition_wait_time = 0
        Relay.button_press_time = 0
        self.test_dir = 'file://' + tempfile.mkdtemp() + '/'

        # Creates the files used for testing
        self._set_status_page('0000000000000000')
        with open(self.test_dir[7:] + '00', 'w+') as file:
            file.write(self.RELAY_OFF_PAGE_CONTENTS)
        with open(self.test_dir[7:] + '01', 'w+') as file:
            file.write(self.RELAY_ON_PAGE_CONTENTS)

        self.config = ({
            'name':
            'SSBoard',
            'base_url':
            self.test_dir,
            'relays': [{
                'name': '0',
                'relay_pos': 0
            }, {
                'name': '1',
                'relay_pos': 1
            }, {
                'name': '2',
                'relay_pos': 7
            }]
        })
        self.ss_board = SainSmartBoard(self.config)
        self.r0 = Relay(self.ss_board, 0)
        self.r1 = Relay(self.ss_board, 1)
        self.r7 = Relay(self.ss_board, 7)

    def tearDown(self):
        shutil.rmtree(self.test_dir[7:])
        Relay.transition_wait_time = .2
        Relay.button_press_time = .25

    def test_get_url_code(self):
        result = self.ss_board._get_relay_url_code(self.r0.position,
                                                   RelayState.NO)
        self.assertEqual(result, '00')

        result = self.ss_board._get_relay_url_code(self.r0.position,
                                                   RelayState.NC)
        self.assertEqual(result, '01')

        result = self.ss_board._get_relay_url_code(self.r7.position,
                                                   RelayState.NO)
        self.assertEqual(result, '14')

        result = self.ss_board._get_relay_url_code(self.r7.position,
                                                   RelayState.NC)
        self.assertEqual(result, '15')

    def test_load_page_status(self):
        self._set_status_page('0000111100001111')
        result = self.ss_board._load_page(SainSmartBoard.HIDDEN_STATUS_PAGE)
        self.assertTrue(
            result.endswith(
                '0000111100001111TUX">0000111100001111TUX</a><p>'))

    def test_load_page_relay(self):
        result = self.ss_board._load_page('00')
        self.assertEqual(result, self.RELAY_OFF_PAGE_CONTENTS)

        result = self.ss_board._load_page('01')
        self.assertEqual(result, self.RELAY_ON_PAGE_CONTENTS)

    def test_load_page_no_connection(self):
        with self.assertRaises(RelayDeviceConnectionError):
            self.ss_board._load_page('**')

    def _set_status_page(self, status_16_chars):
        with open(self.test_dir[7:] + '99', 'w+') as status_file:
            status_file.write(
                self.STATUS_MSG.format(self.test_dir[:-1], self.test_dir[:-1],
                                       status_16_chars, status_16_chars))

    def _test_sync_status_dict(self, status_16_chars):
        self._set_status_page(status_16_chars)
        expected_dict = dict()

        for index, char in enumerate(status_16_chars):
            expected_dict[
                index] = RelayState.NC if char == '1' else RelayState.NO

        self.ss_board._sync_status_dict()
        self.assertDictEqual(expected_dict, self.ss_board.status_dict)

    def test_sync_status_dict(self):
        self._test_sync_status_dict('0000111100001111')
        self._test_sync_status_dict('0000000000000000')
        self._test_sync_status_dict('0101010101010101')
        self._test_sync_status_dict('1010101010101010')
        self._test_sync_status_dict('1111111111111111')

    def test_get_relay_status_status_dict_none(self):
        self._set_status_page('1111111111111111')
        self.ss_board.status_dict = None
        self.assertEqual(self.ss_board.get_relay_status(self.r0.position),
                         RelayState.NC)

    def test_get_relay_status_status_dict_on(self):
        self.r0.set(RelayState.NC)
        self.assertEqual(self.ss_board.get_relay_status(self.r0.position),
                         RelayState.NC)

    def test_get_relay_status_status_dict_off(self):
        self.r0.set(RelayState.NO)
        self.assertEqual(self.ss_board.get_relay_status(self.r0.position),
                         RelayState.NO)

    def test_set_on(self):
        patch_path = 'acts.controllers.relay_lib.sain_smart_board.urlopen'
        with patch(patch_path) as urlopen:
            board = SainSmartBoard(self.config)
            board.status_dict = {}
            board.set(self.r0.position, RelayState.NC)
        urlopen.assert_called_once_with('%s%s' %
                                        (self.ss_board.base_url, '01'))

    def test_set_off(self):
        patch_path = 'acts.controllers.relay_lib.sain_smart_board.urlopen'
        with patch(patch_path) as urlopen:
            board = SainSmartBoard(self.config)
            board.status_dict = {}
            board.set(self.r0.position, RelayState.NO)
        urlopen.assert_called_once_with('%s%s' %
                                        (self.ss_board.base_url, '00'))

    def test_connection_error_no_tux(self):
        default_status_msg = self.STATUS_MSG
        self.STATUS_MSG = self.STATUS_MSG.replace('TUX', '')
        try:
            self._set_status_page('1111111111111111')
            self.ss_board.get_relay_status(0)
        except RelayDeviceConnectionError:
            self.STATUS_MSG = default_status_msg
            return

        self.fail('Should have thrown an error without TUX appearing.')
示例#4
0
class RelayRig:
    """A group of relay boards and their connected devices.

    This class is also responsible for handling the creation of the relay switch
    boards, as well as the devices and relays associated with them.

    The boards dict can contain different types of relay boards. They share a
    common interface through inheriting from RelayBoard. This layer can be
    ignored by the user.

    The relay devices are stored in a dict of (device_name: device). These
    device references should be used by the user when they want to directly
    interface with the relay switches. See RelayDevice or GeneralRelayDevice for
    implementation.

    """
    DUPLICATE_ID_ERR_MSG = 'The {} "{}" is not unique. Duplicated in:\n {}'

    # A dict of lambdas that instantiate relay board upon invocation.
    # The key is the class type name, the value is the lambda.
    _board_constructors = {
        'SainSmartBoard': lambda x: SainSmartBoard(x),
    }

    # Similar to the dict above, except for devices.
    _device_constructors = {
        'GenericRelayDevice': lambda x, rig: GenericRelayDevice(x, rig),
        'FuguRemote': lambda x, rig: FuguRemote(x, rig),
        'SonyXB2Speaker': lambda x, rig: SonyXB2Speaker(x, rig),
    }

    def __init__(self, config):
        self.relays = dict()
        self.boards = dict()
        self.devices = dict()

        validate_key('boards', config, list, 'relay config file')

        for elem in config['boards']:
            board = self.create_relay_board(elem)
            if board.name in self.boards:
                raise RelayConfigError(
                    self.DUPLICATE_ID_ERR_MSG.format('name', elem['name'],
                                                     elem))
            self.boards[board.name] = board

        # Note: 'boards' is a necessary value, 'devices' is not.
        if 'devices' in config:
            for elem in config['devices']:
                relay_device = self.create_relay_device(elem)
                if relay_device.name in self.devices:
                    raise RelayConfigError(
                        self.DUPLICATE_ID_ERR_MSG.format(
                            'name', elem['name'], elem))
                self.devices[relay_device.name] = relay_device
        else:
            device_config = dict()
            device_config['name'] = 'GenericRelayDevice'
            device_config['relays'] = dict()
            for relay_id in self.relays:
                device_config['relays'][relay_id] = relay_id
            self.devices['device'] = self.create_relay_device(device_config)

    def create_relay_board(self, config):
        """Builds a RelayBoard from the given config.

        Args:
            config: An object containing 'type', 'name', 'relays', and
            (optionally) 'properties'. See the example json file.

        Returns:
            A RelayBoard with the given type found in the config.

        Raises:
            RelayConfigError if config['type'] doesn't exist or is not a string.

        """
        validate_key('type', config, str, '"boards" element')
        try:
            ret = self._board_constructors[config['type']](config)
        except LookupError:
            raise RelayConfigError(
                'RelayBoard with type {} not found. Has it been added '
                'to the _board_constructors dict?'.format(config['type']))
        for _, relay in ret.relays.items():
            self.relays[relay.relay_id] = relay
        return ret

    def create_relay_device(self, config):
        """Builds a RelayDevice from the given config.

        When given no 'type' key in the config, the function will default to
        returning a GenericRelayDevice with the relays found in the 'relays'
        array.

        Args:
            config: An object containing 'name', 'relays', and (optionally)
            type.

        Returns:
            A RelayDevice with the given type found in the config. If no type is
            found, it will default to GenericRelayDevice.

        Raises:
            RelayConfigError if the type given does not match any from the
            _device_constructors dictionary.

        """
        if 'type' in config:
            if config['type'] not in RelayRig._device_constructors:
                raise RelayConfigError(
                    'Device with type {} not found. Has it been added '
                    'to the _device_constructors dict?'.format(config['type']))
            else:
                device = self._device_constructors[config['type']](config,
                                                                   self)

        else:
            device = GenericRelayDevice(config, self)

        return device