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)
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)
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)
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')])
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) ])
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) ])
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') ])
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')]])
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})
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]})
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')])
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')])
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')])
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')])
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')])
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)})
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')])
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)])
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') ])
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') ])
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') ])
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
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)
def firmware_information(): # type: () -> CoreCommandSpec """ Firmware information """ return CoreCommandSpec( instruction='FW', response_fields=[AddressField('address'), VersionField('version')])