예제 #1
0
 def __init__(self, data):
     self._type = data['type']
     self._action = data['action']
     self._device_nr = data['device_nr']
     self._data = data['data']
     self._word_helper = WordField('')
     self._address_helper = AddressField('', length=3)
예제 #2
0
    def __init__(self,
                 instruction,
                 request_fields=None,
                 response_fields=None
                 ):  # type: (Instruction, List[Field], List[Field]) -> None
        self.instruction = instruction
        self.address = None  # type: Optional[bytearray]
        self.expected_response_hash = None  # type: Optional[int]
        self._address_field = AddressField('destination')

        self._request_fields = [] if request_fields is None else request_fields
        self.response_fields = [] if response_fields is None else response_fields

        self.header_length = 6  # Literal 'ST/RC' + 4 address bytes
        self._instruction_length = len(self.instruction.instruction)
        self._request_padded_suffix = bytearray(
            [0] * self.instruction.padding) + b'\r\n'
        self._response_prefix_length = len(SlaveCommandSpec.RESPONSE_PREFIX)
        self._response_footer_length = 3 + len(
            SlaveCommandSpec.RESPONSE_SUFFIX
        )  # Literal 'C' + 2 CRC bytes + RESPONSE_SUFFIX
        if any(not isinstance(field.length, int)
               for field in self.response_fields):
            raise RuntimeError(
                'SlaveCommandSpec expects fields with integer lengths')
        self.response_length = (self.header_length + self._instruction_length +
                                sum(field.length
                                    for field in self.response_fields
                                    if isinstance(field.length, int)) +
                                self._response_footer_length)
예제 #3
0
    def test_bootload_lock(self):
        core_communicator = Mock()
        ucan_communicator = UCANCommunicator(
            master_communicator=core_communicator)
        cc_address = '000.000.000.000'
        ucan_address = '000.000.000'

        command = UCANCommandSpec(
            sid=SID.NORMAL_COMMAND,
            instructions=[Instruction(instruction=[0, 0])],
            identifier=AddressField('ucan_address', 3))
        ucan_communicator.do_command(cc_address,
                                     command,
                                     ucan_address, {},
                                     timeout=None)

        command = UCANPalletCommandSpec(identifier=AddressField(
            'ucan_address', 3),
                                        pallet_type=PalletType.MCU_ID_REPLY)
        ucan_communicator.do_command(cc_address,
                                     command,
                                     ucan_address, {},
                                     timeout=None)
        pallet_consumer = ucan_communicator._consumers[cc_address][
            -1]  # Load last consumer

        command = UCANCommandSpec(
            sid=SID.NORMAL_COMMAND,
            instructions=[Instruction(instruction=[0, 0])],
            identifier=AddressField('ucan_address', 3))
        with self.assertRaises(BootloadingException):
            ucan_communicator.do_command(cc_address,
                                         command,
                                         ucan_address, {},
                                         timeout=None)

        command = UCANPalletCommandSpec(identifier=AddressField(
            'ucan_address', 3),
                                        pallet_type=PalletType.MCU_ID_REPLY)
        with self.assertRaises(BootloadingException):
            ucan_communicator.do_command(cc_address,
                                         command,
                                         ucan_address, {},
                                         timeout=None)

        try:
            pallet_consumer.get(0.1)
        except Exception:
            pass  #

        command = UCANCommandSpec(
            sid=SID.NORMAL_COMMAND,
            instructions=[Instruction(instruction=[0, 0])],
            identifier=AddressField('ucan_address', 3))
        ucan_communicator.do_command(cc_address,
                                     command,
                                     ucan_address, {},
                                     timeout=None)
예제 #4
0
파일: core_api.py 프로젝트: rolaya/gateway
 def ucan_tx_transport_message():  # type: () -> CoreCommandSpec
     """ uCAN transport layer packages """
     return CoreCommandSpec(instruction='FM',
                            request_fields=[
                                AddressField('cc_address'),
                                ByteField('nr_can_bytes'),
                                ByteField('sid'),
                                RawByteArrayField('payload', 8)
                            ],
                            response_fields=[AddressField('cc_address')])
예제 #5
0
파일: core_api.py 프로젝트: rolaya/gateway
 def get_ucan_address():  # type: () -> CoreCommandSpec
     """ Receives the uCAN address of a specific uCAN """
     return CoreCommandSpec(instruction='FS',
                            request_fields=[
                                AddressField('cc_address'),
                                LiteralBytesField(1),
                                ByteField('ucan_nr')
                            ],
                            response_fields=[
                                AddressField('cc_address'),
                                PaddingField(2),
                                AddressField('ucan_address', 3)
                            ])
예제 #6
0
파일: core_api.py 프로젝트: rolaya/gateway
 def get_amount_of_ucans():  # type: () -> CoreCommandSpec
     """ Receives amount of uCAN modules """
     return CoreCommandSpec(instruction='FS',
                            request_fields=[
                                AddressField('cc_address'),
                                LiteralBytesField(0),
                                LiteralBytesField(0)
                            ],
                            response_fields=[
                                AddressField('cc_address'),
                                PaddingField(2),
                                ByteField('amount'),
                                PaddingField(2)
                            ])
예제 #7
0
 def read_ucan_config():  # type: () -> UCANCommandSpec
     """ Reads the full uCAN config """
     return UCANCommandSpec(
         sid=SID.NORMAL_COMMAND,
         identifier=AddressField('ucan_address', 3),
         instructions=[Instruction(instruction=[0, 199])],
         response_instructions=[
             Instruction(instruction=[i, 199], checksum_byte=7)
             for i in range(1, 14)
         ],
         response_fields=[
             ByteField('input_link_0'),
             ByteField('input_link_1'),
             ByteField('input_link_2'),
             ByteField('input_link_3'),
             ByteField('input_link_4'),
             ByteField('input_link_5'),
             ByteField('sensor_link_0'),
             ByteField('sensor_link_1'),
             ByteField('sensor_type'),
             VersionField('firmware_version'),
             ByteField('bootloader'),
             ByteField('new_indicator'),
             ByteField('min_led_brightness'),
             ByteField('max_led_brightness'),
             WordField('adc_input_2'),
             WordField('adc_input_3'),
             WordField('adc_input_4'),
             WordField('adc_input_5'),
             WordField('adc_dc_input')
         ])
예제 #8
0
 def set_min_led_brightness():  # type: () -> UCANCommandSpec
     """ Sets the minimum brightness for a uCAN led """
     return UCANCommandSpec(
         sid=SID.NORMAL_COMMAND,
         identifier=AddressField('ucan_address', 3),
         instructions=[Instruction(instruction=[0, 246])],
         request_fields=[[ByteField('brightness')]])
예제 #9
0
    def test_string_parsing(self):
        core_communicator = Mock()
        ucan_communicator = UCANCommunicator(master_communicator=core_communicator)
        cc_address = '000.000.000.000'
        ucan_address = '000.000.000'
        pallet_type = PalletType.MCU_ID_REQUEST  # Not important for this test
        foo = 'XY'  # 2 chars max, otherwise more segments are needed and the test might get too complex

        # Build response-only command
        command = UCANPalletCommandSpec(identifier=AddressField('ucan_address', 3),
                                        pallet_type=pallet_type,
                                        response_fields=[StringField('foo')])
        ucan_communicator.do_command(cc_address, command, ucan_address, {}, timeout=None)
        consumer = ucan_communicator._consumers[cc_address][0]

        # Build and validate fake reply from Core
        payload_segment_1 = bytearray([0, 0, 0, 0, 0, 0, PalletType.MCU_ID_REPLY])
        payload_segment_2 = bytearray([ord(x) for x in '{0}\x00'.format(foo)])
        crc_payload = self._uint32_helper.encode(UCANPalletCommandSpec.calculate_crc(payload_segment_1 + payload_segment_2))
        payload_segment_2 += crc_payload
        ucan_communicator._process_transport_message({'cc_address': cc_address,
                                                      'nr_can_bytes': 8,
                                                      'sid': 1,
                                                      'payload': bytearray([129]) + payload_segment_1})
        ucan_communicator._process_transport_message({'cc_address': cc_address,
                                                      'nr_can_bytes': 8,
                                                      'sid': 1,
                                                      'payload': bytearray([0]) + payload_segment_2})
        self.assertDictEqual(consumer.get(1), {'foo': foo})
예제 #10
0
    def test_multi_messages(self):
        core_communicator = Mock()
        ucan_communicator = UCANCommunicator(master_communicator=core_communicator)
        cc_address = '000.000.000.000'
        ucan_address = '000.000.000'

        command = UCANCommandSpec(sid=SID.NORMAL_COMMAND,
                                  identifier=AddressField('ucan_address', 3),
                                  instructions=[Instruction(instruction=[0, 10]), Instruction(instruction=[0, 10])],
                                  request_fields=[[LiteralBytesField(1)], [LiteralBytesField(2)]],
                                  response_instructions=[Instruction(instruction=[1, 10], checksum_byte=7),
                                                         Instruction(instruction=[2, 10], checksum_byte=7)],
                                  response_fields=[ByteArrayField('foo', 4)])
        ucan_communicator.do_command(cc_address, command, ucan_address, {}, timeout=None)
        consumer = ucan_communicator._consumers[cc_address][0]

        # Build and validate fake reply from Core
        payload_reply_1 = bytearray([1, 10, 0, 0, 0, 20, 21])
        payload_reply_2 = bytearray([2, 10, 0, 0, 0, 22, 23])
        ucan_communicator._process_transport_message({'cc_address': cc_address,
                                                      'nr_can_bytes': 8,
                                                      'sid': 5,
                                                      'payload': payload_reply_1 + bytearray([UCANCommandSpec.calculate_crc(payload_reply_1)])})
        ucan_communicator._process_transport_message({'cc_address': cc_address,
                                                      'nr_can_bytes': 8,
                                                      'sid': 5,
                                                      'payload': payload_reply_2 + bytearray([UCANCommandSpec.calculate_crc(payload_reply_2)])})
        self.assertDictEqual(consumer.get(1), {'foo': [20, 21, 22, 23]})
예제 #11
0
 def erase_flash():  # type: () -> UCANCommandSpec
     """
     Erases uCAN flash
     Note: uCAN needs to be in bootloader
     """
     return UCANPalletCommandSpec(
         identifier=AddressField('ucan_address', 3),
         pallet_type=PalletType.FLASH_ERASE_REQUEST,
         response_fields=[ByteField('success')])
예제 #12
0
 def get_bootloader_id():  # type: () -> UCANCommandSpec
     """
     Gets the uCAN bootloader ID
     Note: uCAN needs to be in bootloader
     """
     return UCANPalletCommandSpec(
         identifier=AddressField('ucan_address', 3),
         pallet_type=PalletType.BOOTLOADER_ID_REQUEST,
         response_fields=[StringField('bootloader_id')])
예제 #13
0
 def reset(sid=SID.NORMAL_COMMAND):  # type: (int) -> UCANCommandSpec
     """ Resets the uCAN """
     return UCANCommandSpec(sid=sid,
                            identifier=AddressField('ucan_address', 3),
                            instructions=[Instruction(instruction=[0, 94])],
                            response_instructions=[
                                Instruction(instruction=[94, 94],
                                            checksum_byte=6)
                            ],
                            response_fields=[ByteField('application_mode')])
예제 #14
0
 def set_bootloader_safety_flag():  # type: () -> UCANCommandSpec
     """ Sets the bootloader's safety flag """
     return UCANCommandSpec(
         sid=SID.BOOTLOADER_COMMAND,
         identifier=AddressField('ucan_address', 3),
         instructions=[Instruction(instruction=[0, 125])],
         request_fields=[[ByteField('safety_flag')]],
         response_instructions=[
             Instruction(instruction=[125, 125], checksum_byte=6)
         ],
         response_fields=[ByteField('safety_flag')])
예제 #15
0
 def ping(sid=SID.NORMAL_COMMAND):  # type: (int) -> UCANCommandSpec
     """ Basic action spec """
     return UCANCommandSpec(sid=sid,
                            identifier=AddressField('ucan_address', 3),
                            instructions=[Instruction(instruction=[0, 96])],
                            request_fields=[[ByteField('data')]],
                            response_instructions=[
                                Instruction(instruction=[1, 96],
                                            checksum_byte=6)
                            ],
                            response_fields=[ByteField('data')])
예제 #16
0
    def test_pallet_reconstructing(self):
        received_commands = []

        def send_command(_cid, _command, _fields):
            received_commands.append(_fields)

        core_communicator = CoreCommunicator(controller_serial=Mock())
        core_communicator._send_command = send_command
        ucan_communicator = UCANCommunicator(master_communicator=core_communicator)
        cc_address = '000.000.000.000'
        ucan_address = '000.000.000'
        pallet_type = PalletType.MCU_ID_REQUEST  # Not important for this test

        for length in [1, 3]:
            # Build command
            command = UCANPalletCommandSpec(identifier=AddressField('ucan_address', 3),
                                            pallet_type=pallet_type,
                                            request_fields=[ByteField('foo'), ByteField('bar')],
                                            response_fields=[ByteArrayField('other', length)])

            # Send command to mocked Core communicator
            received_commands = []
            ucan_communicator.do_command(cc_address, command, ucan_address, {'foo': 1, 'bar': 2}, timeout=None)

            # Validate whether the correct data was send to the Core
            self.assertEqual(len(received_commands), 2)
            self.assertDictEqual(received_commands[0], {'cc_address': cc_address,
                                                        'nr_can_bytes': 8,
                                                        'payload': bytearray([129, 0, 0, 0, 0, 0, 0, pallet_type]),
                                                        #                          +--------------+ = source and destination uCAN address
                                                        'sid': SID.BOOTLOADER_PALLET})
            self.assertDictEqual(received_commands[1], {'cc_address': cc_address,
                                                        'nr_can_bytes': 7,
                                                        'payload': bytearray([0, 1, 2, 219, 155, 250, 178, 0]),
                                                        #                        |  |  +----------------+ = checksum
                                                        #                        |  + = bar
                                                        #                        + = foo
                                                        'sid': SID.BOOTLOADER_PALLET})

            # Build fake reply from Core
            consumer = ucan_communicator._consumers[cc_address][0]
            fixed_payload = bytearray([0, 0, 0, 0, 0, 0, pallet_type])
            variable_payload = bytearray(list(range(7, 7 + length)))  # [7] or [7, 8, 9]
            crc_payload = self._uint32_helper.encode(UCANPalletCommandSpec.calculate_crc(fixed_payload + variable_payload))
            ucan_communicator._process_transport_message({'cc_address': cc_address,
                                                          'nr_can_bytes': 8,
                                                          'sid': 1,
                                                          'payload': bytearray([129]) + fixed_payload})
            ucan_communicator._process_transport_message({'cc_address': cc_address,
                                                          'nr_can_bytes': length + 5,
                                                          'sid': 1,
                                                          'payload': bytearray([0]) + variable_payload + crc_payload})
            self.assertDictEqual(consumer.get(1), {'other': list(variable_payload)})
예제 #17
0
 def set_bootloader_timeout(
         sid=SID.NORMAL_COMMAND):  # type: (int) -> UCANCommandSpec
     """ Sets the bootloader timeout """
     return UCANCommandSpec(
         sid=sid,
         identifier=AddressField('ucan_address', 3),
         instructions=[Instruction(instruction=[0, 123])],
         request_fields=[[ByteField('timeout')]],
         response_instructions=[
             Instruction(instruction=[123, 123], checksum_byte=6)
         ],
         response_fields=[ByteField('timeout')])
예제 #18
0
 def read_flash(data_length):  # type: (int) -> UCANCommandSpec
     """
     Reads uCAN flash
     Note: uCAN needs to be in bootloader
     """
     return UCANPalletCommandSpec(
         identifier=AddressField('ucan_address', 3),
         pallet_type=PalletType.FLASH_READ_REQUEST,
         request_fields=[
             UInt32Field('start_address'),
             ByteField('data_length')
         ],
         response_fields=[ByteArrayField('data', data_length)])
예제 #19
0
파일: core_api.py 프로젝트: rolaya/gateway
 def ucan_module_information():  # type: () -> CoreCommandSpec
     """ Receives information from a uCAN module """
     return CoreCommandSpec(instruction='CD',
                            response_fields=[
                                AddressField('ucan_address', 3),
                                WordArrayField('input_links', 6),
                                ByteArrayField('sensor_links', 2),
                                ByteField('sensor_type'),
                                VersionField('version'),
                                ByteField('bootloader'),
                                CharField('new_indicator'),
                                ByteField('min_led_brightness'),
                                ByteField('max_led_brightness')
                            ])
예제 #20
0
파일: core_api.py 프로젝트: rolaya/gateway
 def module_information():  # type: () -> CoreCommandSpec
     """ Receives module information """
     return CoreCommandSpec(instruction='MC',
                            request_fields=[
                                ByteField('module_nr'),
                                ByteField('module_family')
                            ],
                            response_fields=[
                                ByteField('module_nr'),
                                ByteField('module_family'),
                                ByteField('module_type'),
                                AddressField('address'),
                                WordField('bus_errors'),
                                ByteField('module_status')
                            ])
예제 #21
0
 def get_version():  # type: () -> UCANCommandSpec
     """ Gets a uCAN version """
     return UCANCommandSpec(sid=SID.NORMAL_COMMAND,
                            identifier=AddressField('ucan_address', 3),
                            instructions=[
                                Instruction(instruction=[0, 198]),
                                Instruction(instruction=[0, 198])
                            ],
                            request_fields=[[LiteralBytesField(5)],
                                            [LiteralBytesField(6)]],
                            response_instructions=[
                                Instruction(instruction=[5, 199],
                                            checksum_byte=7),
                                Instruction(instruction=[6, 199],
                                            checksum_byte=7)
                            ],
                            response_fields=[
                                ByteField('sensor_type'),
                                VersionField('firmware_version')
                            ])
예제 #22
0
class SlaveCommandSpec(object):
    """
    Defines payload handling and de(serialization)
    """

    REQUEST_PREFIX = b'ST'
    RESPONSE_PREFIX = b'RC'
    RESPONSE_SUFFIX = b'\r\n'

    def __init__(self, instruction, request_fields=None, response_fields=None):  # type: (Instruction, List[Field], List[Field]) -> None
        self.instruction = instruction
        self.address = None  # type: Optional[bytearray]
        self.expected_response_hash = None  # type: Optional[int]
        self._address_field = AddressField('destination')

        self._request_fields = [] if request_fields is None else request_fields
        self.response_fields = [] if response_fields is None else response_fields

        self.header_length = 6  # Literal 'ST/RC' + 4 address bytes
        self._instruction_length = len(self.instruction.instruction)
        self._request_padded_suffix = bytearray([0] * self.instruction.padding) + b'\r\n\r\n'
        self._response_prefix_length = len(SlaveCommandSpec.RESPONSE_PREFIX)
        self._response_footer_length = 3 + len(SlaveCommandSpec.RESPONSE_SUFFIX)  # Literal 'C' + 2 CRC bytes + RESPONSE_SUFFIX
        if any(not isinstance(field.length, int) for field in self.response_fields):
            raise RuntimeError('SlaveCommandSpec expects fields with integer lengths')
        self.response_length = (
            self.header_length +
            self._instruction_length +
            sum(field.length for field in self.response_fields if isinstance(field.length, int)) +
            self._response_footer_length
        )

    def set_address(self, address):  # type: (str) -> None
        self.address = self._address_field.encode(address)
        self.expected_response_hash = SlaveCommandSpec.hash(self.address + self.instruction.instruction.encode())

    def create_request_payload(self, fields):  # type: (Dict[str, Any]) -> bytearray
        """ Create the request payloads for slaves using this spec and the provided fields. """
        if self.address is None:
            raise RuntimeError('Cannot create request payload when address is not set.')
        prefix = bytearray(SlaveCommandSpec.REQUEST_PREFIX)
        payload = self.address + self.instruction.instruction.encode()
        for field in self._request_fields:
            payload += field.encode(fields.get(field.name))
        checksum = bytearray(b'C') + SlaveCommandSpec.calculate_crc(payload)
        return prefix + payload + checksum + self._request_padded_suffix

    def consume_response_payload(self, payload):  # type: (bytearray) -> Optional[Dict[str, Any]]
        """ Consumes the payload bytes """
        crc = SlaveCommandSpec.decode_crc(payload[-(self._response_footer_length - 1):-len(SlaveCommandSpec.RESPONSE_SUFFIX)])
        expected_crc = SlaveCommandSpec.decode_crc(SlaveCommandSpec.calculate_crc(payload[self._response_prefix_length:-self._response_footer_length]))
        if crc != expected_crc:
            logger.info('Unexpected CRC ({0} vs expected {1}): {2}'.format(crc, expected_crc, printable(payload)))
            return None
        payload_data = payload[self.header_length + self._instruction_length:-self._response_footer_length]
        return self._parse_payload(payload_data)

    def _parse_payload(self, payload_data):  # type: (bytearray) -> Dict[str, Any]
        result = {}
        payload_length = len(payload_data)
        for field in self.response_fields:
            if field.length is None:
                continue
            field_length = field.length  # type: Union[int, Callable[[int], int]]
            if callable(field_length):
                field_length = field_length(payload_length)
            if len(payload_data) < field_length:
                logger.warning('Payload did not contain all the expected data: {0}'.format(printable(payload_data)))
                break
            data = payload_data[:field_length]  # type: bytearray
            if not isinstance(field, PaddingField):
                result[field.name] = field.decode(data)
            payload_data = payload_data[field_length:]
        return result

    def extract_hash_from_payload(self, payload):  # type: (bytearray) -> Optional[int]
        if len(payload) < self.header_length + self._instruction_length:
            return None
        address = payload[self._response_prefix_length:self._response_prefix_length + 4]
        instruction = payload[self.header_length:self.header_length + self._instruction_length]
        return SlaveCommandSpec.hash(address + instruction)

    @staticmethod
    def calculate_crc(data):  # type: (bytearray) -> bytearray
        crc = 0
        for data_byte in data:
            crc += data_byte
        crc = crc % 65536
        return bytearray([crc // 256, crc % 256])

    @staticmethod
    def decode_crc(crc):  # type: (bytearray) -> List[int]
        return [crc[0], crc[1]]

    @staticmethod
    def hash(entries):  # type: (bytearray) -> int
        times = 1
        result = 0
        for entry in entries:
            result += (entry * 256 * times)
            times += 1
        return result
예제 #23
0
class Event(object):
    class Types(object):
        OUTPUT = 'OUTPUT'
        INPUT = 'INPUT'
        SENSOR = 'SENSOR'
        THERMOSTAT = 'THERMOSTAT'
        SYSTEM = 'SYSTEM'
        POWER = 'POWER'
        EXECUTED_BA = 'EXECUTED_BA'
        BUTTON_PRESS = 'BUTTON_PRESS'
        LED_ON = 'LED_ON'
        LED_BLINK = 'LED_BLINK'
        UCAN = 'UCAN'
        EXECUTE_GATEWAY_API = 'EXECUTE_GATEWAY_API'
        UNKNOWN = 'UNKNOWN'

    class OutputEventTypes(object):
        STATUS = 'STATUS'
        LOCKING = 'LOCKING'

    class UCANEventTypes(object):
        POWER_OUT_ERROR = 'POWER_OUT_ERROR'
        POWER_OUT_RESTORED = 'POWER_OUT_RESTORED'
        POWER_OUT_ON = 'POWER_OUT_ON'
        POWER_OUT_OFF = 'POWER_OUT_OFF'
        I2C_ERROR = 'I2C_ERROR'
        UNKNOWN = 'UNKNOWN'

    class SensorType(object):
        TEMPERATURE = 'TEMPERATURE'
        HUMIDITY = 'HUMIDITY'
        BRIGHTNESS = 'BRIGHTNESS'
        UNKNOWN = 'UNKNOWN'

    class SystemEventTypes(object):
        EEPROM_ACTIVATE = 'EEPROM_ACTIVATE'
        ONBOARD_TEMP_CHANGED = 'ONBOARD_TEMP_CHANGED'
        UNKNOWN = 'UNKNOWN'

    class ThermostatOrigins(object):
        SLAVE = 'SLAVE'
        MASTER = 'MASTER'
        UNKNOWN = 'UNKNOWN'

    class Bus(object):
        RS485 = 'RS485'
        CAN = 'CAN'

    class Leds(object):
        LED_0 = 0
        LED_1 = 1
        LED_2 = 2
        LED_3 = 3
        LED_4 = 4
        LED_5 = 5
        LED_6 = 6
        LED_7 = 7
        LED_8 = 8
        LED_9 = 9
        LED_10 = 10
        LED_11 = 11
        LED_12 = 12
        LED_13 = 13
        LED_14 = 14
        LED_15 = 15

    class LedStates(object):
        OFF = 'OFF'
        ON = 'ON'

    class LedFrequencies(object):
        BLINKING_25 = 'BLINKING_25'
        BLINKING_50 = 'BLINKING_50'
        BLINKING_75 = 'BLINKING_75'
        SOLID = 'SOLID'

    class Buttons(object):
        SETUP = 0
        ACTION = 1
        CAN_POWER = 2
        SELECT = 3

    class ButtonStates(object):
        RELEASED = 0
        PRESSED = 1
        PRESSED_5S = 2
        PRESSED_LONG = 3

    def __init__(self, data):
        self._type = data['type']
        self._action = data['action']
        self._device_nr = data['device_nr']
        self._data = data['data']
        self._word_helper = WordField('')
        self._address_helper = AddressField('', length=3)

    @property
    def type(self):
        type_map = {0: Event.Types.OUTPUT,
                    1: Event.Types.INPUT,
                    2: Event.Types.SENSOR,
                    20: Event.Types.THERMOSTAT,
                    21: Event.Types.UCAN,
                    22: Event.Types.EXECUTED_BA,
                    249: Event.Types.EXECUTE_GATEWAY_API,
                    250: Event.Types.BUTTON_PRESS,
                    251: Event.Types.LED_BLINK,
                    252: Event.Types.LED_ON,
                    253: Event.Types.POWER,
                    254: Event.Types.SYSTEM}
        return type_map.get(self._type, Event.Types.UNKNOWN)

    @property
    def data(self):
        if self.type == Event.Types.OUTPUT:
            data = {'output': self._device_nr}
            if self._action in [0, 1]:
                timer_type = self._data[1]  # type: int
                timer_value = self._word_decode(self._data[2:]) or 0  # type: int
                timer = Timer.event_timer_type_to_seconds(timer_type, timer_value)
                data.update({'type': Event.OutputEventTypes.STATUS,
                             'status': self._action == 1,
                             'dimmer_value': self._data[0],
                             'timer': timer})
            else:
                data.update({'type': Event.OutputEventTypes.LOCKING,
                             'locked': self._data[0] == 1})
            return data
        if self.type == Event.Types.INPUT:
            return {'input': self._device_nr,
                    'status': self._action == 1}
        if self.type == Event.Types.SENSOR:
            sensor_type = Event.SensorType.UNKNOWN
            sensor_value = None
            if self._action == 0:
                sensor_type = Event.SensorType.TEMPERATURE
                sensor_value = Temperature.system_value_to_temperature(self._data[1])
            elif self._action == 1:
                sensor_type = Event.SensorType.HUMIDITY
                sensor_value = Humidity.system_value_to_humidity(self._data[1])
            elif self._action == 2:
                sensor_type = Event.SensorType.BRIGHTNESS
                sensor_value = self._word_decode(self._data[0:2])
            return {'sensor': self._device_nr,
                    'type': sensor_type,
                    'value': sensor_value}
        if self.type == Event.Types.THERMOSTAT:
            origin_map = {0: Event.ThermostatOrigins.SLAVE,
                          1: Event.ThermostatOrigins.MASTER}
            return {'origin': origin_map.get(self._action, Event.ThermostatOrigins.UNKNOWN),
                    'thermostat': self._device_nr,
                    'mode': self._data[0],
                    'setpoint': self._data[1]}
        if self.type == Event.Types.BUTTON_PRESS:
            return {'button': self._device_nr,
                    'state': self._data[0]}
        if self.type == Event.Types.LED_BLINK:
            word_25 = self._device_nr
            word_50 = self._word_decode(self._data[0:2])
            word_75 = self._word_decode(self._data[2:4])
            leds = {}
            for i in range(16):
                if word_25 & (1 << i):
                    leds[i] = Event.LedFrequencies.BLINKING_25
                elif word_50 & (1 << i):
                    leds[i] = Event.LedFrequencies.BLINKING_50
                elif word_75 & (1 << i):
                    leds[i] = Event.LedFrequencies.BLINKING_75
                else:
                    leds[i] = Event.LedFrequencies.SOLID
            return {'chip': self._device_nr,
                    'leds': leds}
        if self.type == Event.Types.LED_ON:
            word_on = self._word_decode(self._data[0:2])
            leds = {}
            for i in range(16):
                leds[i] = Event.LedStates.ON if word_on & (1 << i) else Event.LedStates.OFF
            return {'chip': self._device_nr,
                    'leds': leds}
        if self.type == Event.Types.POWER:
            return {'bus': Event.Bus.RS485 if self._device_nr == 0 else Event.Bus.CAN,
                    'power': self._data[0 > 1]}
        if self.type == Event.Types.SYSTEM:
            type_map = {0: Event.SystemEventTypes.EEPROM_ACTIVATE,
                        1: Event.SystemEventTypes.ONBOARD_TEMP_CHANGED}
            event_type = type_map.get(self._action, Event.SystemEventTypes.UNKNOWN)
            event_data = {'type': event_type}
            if event_type == Event.SystemEventTypes.ONBOARD_TEMP_CHANGED:
                event_data['temperature'] = self._data[0]
            return event_data
        if self.type == Event.Types.UCAN:
            event_data = {'address': self._address_helper.decode(bytearray([self._device_nr & 0xFF]) + self._data[0:2])}
            if self._data[2] == 0 and self._data[3] == 0:
                event_data['type'] = Event.UCANEventTypes.POWER_OUT_ERROR
            elif self._data[2] == 0 and self._data[3] == 1:
                event_data['type'] = Event.UCANEventTypes.POWER_OUT_RESTORED
            elif self._data[2] == 0 and self._data[3] == 2:
                event_data['type'] = Event.UCANEventTypes.POWER_OUT_OFF
            elif self._data[2] == 2 and self._data[3] == 3:
                event_data['type'] = Event.UCANEventTypes.POWER_OUT_ON
            elif self._data[2] == 1:
                event_data.update({'type': Event.UCANEventTypes.I2C_ERROR,
                                   'i2c_address': self._data[3]})
            else:
                event_data.update({'type': Event.UCANEventTypes.UNKNOWN,
                                   'data': self._data[2:4]})
            return event_data
        if self.type == Event.Types.EXECUTED_BA:
            return {'basic_action': BasicAction(action_type=self._data[0],
                                                action=self._data[1],
                                                device_nr=self._device_nr,
                                                extra_parameter=self._word_decode(self._data[2:4]))}
        if self.type == Event.Types.EXECUTE_GATEWAY_API:
            return {'action': self._data[3],
                    'device_nr': self._device_nr,
                    'extra_parameter': self._word_decode(self._data[0:2])}
        return None

    def _word_decode(self, data):  # type: (List[int]) -> int
        return self._word_helper.decode(bytearray(data[0:2]))

    def __str__(self):
        return '{0} ({1})'.format(self.type, self.data if self.type != Event.Types.UNKNOWN else self._type)
예제 #24
0
 def firmware_information():  # type: () -> CoreCommandSpec
     """ Firmware information """
     return CoreCommandSpec(
         instruction='FW',
         response_fields=[AddressField('address'),
                          VersionField('version')])