def _load_eeprom(self, devdesc: dict, cfgdescs: Sequence[dict]) -> None: whole = self._version != 0x1000 # FT230X buf = self._eeprom if whole else self._eeprom[:0x100] chksum = self._checksum_eeprom(buf) if chksum: self.log.warning('Invalid EEPROM checksum, ignoring content') return # only apply a subset of what the EEPROM can configure for now devdesc['idVendor'] = sunpack('<H', self._eeprom[2:4])[0] devdesc['idProduct'] = sunpack('<H', self._eeprom[4:6])[0] devdesc['iManufacturer'] = self._decode_eeprom_string(0x0e) devdesc['iProduct'] = self._decode_eeprom_string(0x10) devdesc['iSerialNumber'] = self._decode_eeprom_string(0x12) for desc in cfgdescs: if desc.bConfigurationValue == 0: # only update first configuration desc['bMaxPower'] = self._eeprom[0x09] desc['bmAttributes'] = 0x80 | (self._eeprom[0x08] & 0x0F) cbus_dec = f'_decode_cbus_x{self._version:04x}' try: cbus_func = getattr(self._ports[0], cbus_dec) except AttributeError: self.log.debug('No CBUS support: %s', cbus_dec) return cbus_func()
def decode_tve_parameter(data): """Generic byte decoding function for TVE parameters. Given an array of bytes, tries to interpret a TVE parameter from the beginning of the array. Returns the decoded data and the number of bytes it read.""" # decode the TVE field's header (1 bit "reserved" + 7-bit type) (msgtype, ) = sunpack(tve_header, data[:tve_header_len]) if not msgtype & 0b10000000: # not a TV-encoded param return None, 0 msgtype = msgtype & 0x7f try: param_name, param_fmt = tve_param_formats[msgtype] logger.debug('found %s (type=%s)', param_name, msgtype) except KeyError as err: return None, 0 # decode the body nbytes = scalc(param_fmt) end = tve_header_len + nbytes try: unpacked = sunpack(param_fmt, data[tve_header_len:end]) return {param_name: unpacked}, end except serror: return None, 0
def _decode_eeprom(self): cfg = self._config cfg.clear() cfg['vendor_id'] = Hex4Int(sunpack('<H', self._eeprom[0x02:0x04])[0]) cfg['product_id'] = Hex4Int(sunpack('<H', self._eeprom[0x04:0x06])[0]) cfg['type'] = Hex4Int(sunpack('<H', self._eeprom[0x06:0x08])[0]) power_supply, power_max, conf = sunpack('<3B', self._eeprom[0x08:0x0b]) cfg['self_powered'] = bool(power_supply & (1 << 6)) cfg['remote_wakeup'] = bool(power_supply & (1 << 5)) cfg['power_max'] = power_max << 1 cfg['has_serial'] = bool(conf & (1 << 3)) cfg['suspend_pull_down'] = bool(conf & (1 << 2)) cfg['out_isochronous'] = bool(conf & (1 << 1)) cfg['in_isochronous'] = bool(conf & (1 << 0)) cfg['usb_version'] = Hex4Int(sunpack('<H', self._eeprom[0x0c:0x0e])[0]) cfg['manufacturer'] = self._decode_string(0x0e) cfg['product'] = self._decode_string(0x10) cfg['serial'] = self._decode_string(0x12) name = None try: name = Ftdi.DEVICE_NAMES[cfg['type']].replace('-', '') if name.startswith('ft'): name = name[2:] func = getattr(self, '_decode_%s' % name) except (KeyError, AttributeError): self.log.warning('No EEPROM decoder for device %s', name or '?') else: func()
def _decode_eeprom(self): cfg = self._config cfg.clear() cfg['vendor_id'] = Hex4Int(sunpack('<H', self._eeprom[0x02:0x04])[0]) cfg['product_id'] = Hex4Int(sunpack('<H', self._eeprom[0x04:0x06])[0]) cfg['type'] = Hex4Int(sunpack('<H', self._eeprom[0x06:0x08])[0]) power_supply, power_max, conf = sunpack('<3B', self._eeprom[0x08:0x0b]) cfg['self_powered'] = bool(power_supply & (1 << 6)) cfg['remote_wakeup'] = bool(power_supply & (1 << 5)) cfg['power_max'] = power_max << 1 cfg['has_usb_version'] = bool(conf & (1 << 4)) cfg['has_serial'] = bool(conf & (1 << 3)) cfg['suspend_pull_down'] = bool(conf & (1 << 2)) cfg['out_isochronous'] = bool(conf & (1 << 1)) cfg['in_isochronous'] = bool(conf & (1 << 0)) cfg['usb_version'] = Hex4Int(sunpack('<H', self._eeprom[0x0c:0x0e])[0]) cfg['manufacturer'] = self._decode_string(0x0e) cfg['product'] = self._decode_string(0x10) cfg['serial'] = self._decode_string(0x12) try: name = Ftdi.DEVICE_NAMES[cfg['type']] func = getattr(self, '_decode_%s' % name[2:]) except (KeyError, AttributeError): pass else: func()
def deserialize(self): """Turns a sequence of bytes into a message dictionary.""" if self.msgbytes is None: raise LLRPError('No message bytes to deserialize.') data = self.msgbytes msgtype, length, msgid = sunpack(self.full_hdr_fmt, data[:self.full_hdr_len]) ver = (msgtype >> 10) & BITMASK(3) msgtype = msgtype & BITMASK(10) try: name = Message_Type2Name[msgtype] logger.debug('deserializing %s command', name) decoder = Message_struct[name]['decode'] except KeyError: raise LLRPError('Cannot find decoder for message type ' '{}'.format(msgtype)) body = data[self.full_hdr_len:length] try: self.msgdict = {name: dict(decoder(body))} self.msgdict[name]['Ver'] = ver self.msgdict[name]['Type'] = msgtype self.msgdict[name]['ID'] = msgid logger.debug('done deserializing %s command', name) except LLRPError: logger.exception('Problem with %s message format', name) return '' return ''
def _decode_input_mpsse_request(self): if len(self._trace_tx) < 3: return False length = sunpack('<H', self._trace_tx[1:3])[0] + 1 self._expect_resp.append(length) self._trace_tx[:] = self._trace_tx[3:] return True
def parse_options(self, tail): self.log.debug('Parsing DHCP options') dhcp_tags = {} padding_count = 0 while tail: tag = tail[0] # padding if tag == 0: padding_count += 1 if padding_count > 255: raise ValueError('Padding overflow') continue padding_count = 0 if tag == 0xff: return dhcp_tags length = tail[1] (value, ) = sunpack('!%ss' % length, tail[2:2+length]) tail = tail[2+length:] try: option = DHCP_OPTIONS[tag] self.log.debug(" option %d: '%s', size:%d %s" % (tag, option, length, hexline(value))) except KeyError: self.log.debug(' unknown option %d, size:%d %s:' % (tag, length, hexline(value))) continue dhcp_tags[tag] = value
def _stream_sink(cls, port, size, results): pos = 0 first = None data = bytearray() sample_size = scalc('>I') rx_size = 0 port.timeout = 1.0 start = now() while rx_size < size: buf = port.read(1024) if not buf: print('T') break rx_size += len(buf) data.extend(buf) sample_count = len(data) // sample_size length = sample_count * sample_size samples = sunpack('>%dI' % sample_count, data[:length]) data = data[length:] for sample in samples: if first is None: first = sample pos = sample continue pos += 1 if sample != pos: results[1] = AssertionError('Lost byte @ %d', pos - first) return delta = now() - start results[1] = (rx_size, delta)
def poll_cond(self, address: int, fmt: str, mask: int, value: int, count: int, relax: bool = True) -> Optional[bytes]: """Poll a remove slave, watching for condition to satisfy. On each poll cycle, a repeated start condition is emitted, without releasing the I2C bus, and an ACK is returned to the slave. If relax is set, this method releases the I2C bus however it leaves. :param address: the address on the I2C bus, or None to discard start :param fmt: struct format for poll register :param mask: binary mask to apply on the condition register before testing for the value :param value: value to test the masked condition register against. Condition is satisfied when register & mask == value :param count: maximum poll count before raising a timeout :param relax: whether to relax the bus (emit STOP) or not :return: the polled register value, or None if poll failed """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH i2caddress |= self.BIT0 do_epilog = True with self._lock: try: retry = 0 while retry < count: retry += 1 size = scalc(fmt) self._do_prolog(i2caddress) data = self._do_read(size) self.log.debug("Poll data: %s", hexlify(data).decode()) cond, = sunpack(fmt, data) if (cond & mask) == value: self.log.debug('Poll condition matched') break else: data = None self.log.debug('Poll condition not fulfilled: %x/%x', cond & mask, value) do_epilog = relax if not data: self.log.warning('Poll condition failed') return data except I2cNackError: self.log.info('Not ready') return None finally: if do_epilog: self._do_epilog()
def _decode_eeprom_string(self, offset): str_offset, str_size = sunpack('<BB', self._eeprom[offset:offset + 2]) if str_size: str_size -= scalc('<H') str_offset += scalc('<H') manufacturer = self._eeprom[str_offset:str_offset + str_size] return manufacturer.decode('utf16', errors='ignore') return ''
def _cmd_drive_zero(self): if len(self._trace_tx) < 3: return False value, = sunpack('H', self._trace_tx[1:3]) self.log.info(' [%d]:Open collector [15:0] %04x %s', self._if, value, self.bitfmt(value, 16)) self._trace_tx[:] = self._trace_tx[3:] return True
def _cmd_set_bits_high(self): if len(self._trace_tx) < 3: return False value, direction = sunpack('BB', self._trace_tx[1:3]) self.log.info('Set gpio[15:8] %02x %s', value, self.bits2str(value, direction)) self._trace_tx[:] = self._trace_tx[3:] return True
def _cmd_set_bits_low(self): if len(self._trace_tx) < 3: return False value, direction = sunpack('BB', self._trace_tx[1:3]) self.log.info(' [%d]:Set gpio[7:0] %02x %s', self._if, value, self.bm2str(value, direction)) self._trace_tx[:] = self._trace_tx[3:] return True
def _handle_advertisement(self, adv): data = adv.service_data if not data: return type_fmt = f'<{self.SERVICE_FMT[0]}' size = scalc(type_fmt) if len(data) < size: return service, = sunpack(type_fmt, data[:size]) data = data[size:] if service != self.ESS: return data_fmt = f'>{self.SERVICE_FMT[1:]}' size = scalc(data_fmt) if len(data) < size: self._log.warning('to short') return macbytes, temp, humi, bat, batv, packet = sunpack(data_fmt, data) if packet == self._last_packet and not self._all_msgs: return self._last_packet = packet mac = sum([b << (o << 3) for o, b in enumerate(reversed(macbytes))]) oui = mac >> 24 if oui != self.XIAOMI_OUI: return temp = float(temp) / 10.0 batv = float(batv) / 1000.0 payload = { 'mac': adv.address.address, 'temperature_C': temp, 'humidity': humi, 'battery': bat, 'battery_v': batv, 'packet': packet, 'rssi': adv.rssi, } if 'event' in self._publish: jstr = f'{jdumps(payload)}\n' jbytes = jstr.encode() self._mqtt.publish(f'{self._source}/events', jbytes) if 'device' in self._publish: mac = adv.address.address for name, val in payload.items(): if name == 'mac': continue self._mqtt.publish(f'{self._source}/devices/{mac}/{name}', val)
def _cmd_set_tck_divisor(self): if len(self._trace_tx) < 3: return False value, = sunpack('<H', self._trace_tx[1:3]) base = self._clkdiv5 and 12E6 or 60E6 freq = base / ((1 + value) * 2) self.log.info('Set frequency %.3fMHZ', freq/1E6) self._trace_tx[:] = self._trace_tx[3:] return True
def find_interface(address: str) -> Optional[str]: iaddress = sunpack('!I', inet_aton(address))[0] for iface in interfaces(): for confs in ifaddresses(iface).values(): for conf in confs: if all([x in conf for x in ('addr', 'netmask')]): address = conf['addr'] if ':' in address: # IPv6 continue netmask = conf['netmask'] iaddr = sunpack('!I', inet_aton(address))[0] inet = sunpack('!I', inet_aton(netmask))[0] inic = iaddr & inet ires = iaddress & inet if inic == ires: return iface return None
def _cmd_set_tck_divisor(self): if len(self._trace_tx) < 3: return False value, = sunpack('<H', self._trace_tx[1:3]) base = self._clkdiv5 and 12E6 or 60E6 freq = base / ((1 + value) * 2) self.log.info('Set frequency %.3fMHZ', freq / 1E6) self._trace_tx[:] = self._trace_tx[3:] return True
def poll_modem_status(self): """Poll modem status information This function allows the retrieve the two status bytes of the device. """ value = self._ctrl_transfer_in(Ftdi.SIO_POLL_MODEM_STATUS, 2) if not value or len(value) != 2: raise FtdiError('Unable to get modem status') status, = sunpack('<H', value) return status
def poll_cond(self, address, fmt, mask, value, count, relax=True): """Poll a remove slave, watching for condition to satisfy. On each poll cycle, a repeated start condition is emitted, without releasing the I2C bus, and an ACK is returned to the slave. If relax is set, this method releases the I2C bus however it leaves. :param address: the address on the I2C bus, or None to discard start :type address: int or None :param str fmt: struct format for poll register :param int mask: binary mask to apply on the condition register before testing for the value :param int value: value to test the masked condition register against. Condition is satisfied when register & mask == value :param int count: maximum poll count before raising a timeout :param bool relax: whether to relax the bus (emit STOP) or not :return: the polled register value, or None if poll failed :rtype: array or None """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH i2caddress |= self.BIT0 do_epilog = True try: retry = 0 while retry < count: retry += 1 size = scalc(fmt) self._do_prolog(i2caddress) data = self._do_read(size) self.log.debug("Poll data: %s", hexlify(data).decode()) cond, = sunpack(fmt, data) if (cond & mask) == value: self.log.debug('Poll condition matched') break else: data = None self.log.debug('Poll condition not fulfilled: %x/%x', cond & mask, value) do_epilog = relax if not data: self.log.warning('Poll condition failed') return data except I2cNackError: self.log.info('Not ready') return None finally: if do_epilog: self._do_epilog()
def _handle_advertisement(self, adv): data = adv.service_data if not data: return type_fmt = f'<{self.SERVICE_FMT[0]}' size = scalc(type_fmt) if len(data) < size: return service, = sunpack(type_fmt, data[:size]) data = data[size:] if service != self.ESS: return data_fmt = f'>{self.SERVICE_FMT[1:]}' size = scalc(data_fmt) if len(data) < size: self._log.warning('to short') return macbytes, temp, humi, bat, batv, packet = sunpack(data_fmt, data) mac = sum([b << (o << 3) for o, b in enumerate(reversed(macbytes))]) oui = mac >> 24 if oui != self.XIAOMI_OUI: return temp = float(temp) / 10.0 batv = float(batv) / 1000.0 json = { 'mac': adv.address.address, 'temperature': temp, 'humidity': humi, 'battery': bat, 'battery_v': batv, 'packet': packet, 'rssi': adv.rssi, } jstr = f'{jdumps(json)}\n' jbytes = jstr.encode() with self._qlock: if self._channels: self._log.debug('Notify %d clients', len(self._channels)) for channel in self._channels: channel.queue.append(jbytes) channel.event.set()
def _decode_mpsse_bytes(self): caller = stack()[1].function if len(self._trace_tx) < 4: return False length = sunpack('<H', self._trace_tx[1:3])[0] + 1 if len(self._trace_tx) < 4 + length: return False payload = self._trace_tx[3:3 + length] funcname = caller[5:].title().replace('_', ' ') self.log.info('%s (%d) %s', funcname, length, hexlify(payload).decode('utf8')) self._trace_tx[:] = self._trace_tx[3 + length:] return True
def _cmd_set_bits_low(self): buf = self._trace_tx[1:3] if not super()._cmd_set_bits_low(): return False port = self._port byte, direction = sunpack('BB', buf) gpi = port.gpio & ~direction & self._mask gpo = byte & direction & self._mask msb = port.gpio & ~0xFF gpio = gpi | gpo | msb port.update_gpio(self, False, direction, gpio) self.log.debug('. bbwl %04x: %s', port.gpio, f'{port.gpio:016b}') return True
def _decode_output_mpsse_bytes(self, caller, expect_rx=False): if len(self._trace_tx) < 4: return False length = sunpack('<H', self._trace_tx[1:3])[0] + 1 if len(self._trace_tx) < 4 + length: return False if expect_rx: self._expect_resp.append(length) payload = self._trace_tx[3:3 + length] funcname = caller[5:].title().replace('_', '') self.log.info(' [%d]:%s> (%d) %s', self._if, funcname, length, hexlify(payload).decode('utf8')) self._trace_tx[:] = self._trace_tx[3 + length:] return True
def _read_raw(self, read_high): if read_high: cmd = bytearray( [Ftdi.GET_BITS_LOW, Ftdi.GET_BITS_HIGH, Ftdi.SEND_IMMEDIATE]) fmt = '<H' else: cmd = bytearray([Ftdi.GET_BITS_LOW, Ftdi.SEND_IMMEDIATE]) fmt = 'B' self._ftdi.write_data(cmd) size = scalc(fmt) data = self._ftdi.read_data_bytes(size, 4) if len(data) != size: raise SpiIOError('Cannot read GPIO') value, = sunpack(fmt, data) return value
def _decode_output_mpsse_bytes(self, expect_rx=False): caller = stack()[1].function if len(self._trace_tx) < 4: return False length = sunpack('<H', self._trace_tx[1:3])[0] + 1 if len(self._trace_tx) < 4 + length: return False if expect_rx: self._expect_resp.append(length) payload = self._trace_tx[3:3+length] funcname = caller[5:].title().replace('_', '') self.log.info('%s> (%d) %s', funcname, length, hexlify(payload).decode('utf8')) self._trace_tx[:] = self._trace_tx[3+length:] return True
def data_received(self, data): logger.debug('got %d bytes from reader: %s', len(data), hexlify(data).decode()) if self.expectingRemainingBytes: if len(data) >= self.expectingRemainingBytes: data = self.partialData + data self.partialData = '' self.expectingRemainingBytes -= len(data) else: # still not enough; wait until next time self.partialData += data self.expectingRemainingBytes -= len(data) return while data: # parse the message header to grab its length if len(data) >= LLRPMessage.full_hdr_len: msg_type, msg_len, message_id = \ sunpack(LLRPMessage.full_hdr_fmt, data[:LLRPMessage.full_hdr_len]) else: logger.warning('Too few bytes (%d) to unpack message header', len(data)) self.partialData = data self.expectingRemainingBytes = \ LLRPMessage.full_hdr_len - len(data) break logger.debug('expect %d bytes (have %d)', msg_len, len(data)) if len(data) < msg_len: # got too few bytes self.partialData = data self.expectingRemainingBytes = msg_len - len(data) break else: # got at least the right number of bytes self.expectingRemainingBytes = 0 try: lmsg = LLRPMessage(msgbytes=data[:msg_len]) self.handleMessage(lmsg) data = data[msg_len:] except LLRPError: logger.exception( 'Failed to decode LLRPMessage; ' 'will not decode %d remaining bytes', len(data)) break
def _read_raw(self, read_high: bool) -> int: if not self._ftdi.is_connected: raise SpiIOError("FTDI controller not initialized") if read_high: cmd = bytes( [Ftdi.GET_BITS_LOW, Ftdi.GET_BITS_HIGH, Ftdi.SEND_IMMEDIATE]) fmt = '<H' else: cmd = bytes([Ftdi.GET_BITS_LOW, Ftdi.SEND_IMMEDIATE]) fmt = 'B' self._ftdi.write_data(cmd) size = scalc(fmt) data = self._ftdi.read_data_bytes(size, 4) if len(data) != size: raise SpiIOError('Cannot read GPIO') value, = sunpack(fmt, data) return value
def _read_raw(self, read_high): if read_high: cmd = array('B', [Ftdi.GET_BITS_LOW, Ftdi.GET_BITS_HIGH, Ftdi.SEND_IMMEDIATE]) fmt = '<H' else: cmd = array('B', [Ftdi.GET_BITS_LOW, Ftdi.SEND_IMMEDIATE]) fmt = 'B' self._ftdi.write_data(cmd) size = scalc(fmt) data = self._ftdi.read_data_bytes(size, 4) if len(data) != size: raise SpiIOError('Cannot read GPIO') value, = sunpack(fmt, data) return value
def _read_raw(self, read_high): if read_high: cmd = array( 'B', [Ftdi.GET_BITS_LOW, Ftdi.GET_BITS_HIGH, Ftdi.SEND_IMMEDIATE]) fmt = '<H' self._ftdi.write_data(cmd) size = scalc(fmt) data = self._ftdi.read_data_bytes(size, 4) if len(data) != size: raise GpioException('Cannot read GPIO') value, = sunpack(fmt, data) else: # If not using read_high method, then also means this is # BIT BANG and not MPSSE, so just use read_pins() value = self._ftdi.read_pins() return value
def _read_mpsse(self, count: int) -> Tuple[int]: if self._width > 8: cmd = bytearray([Ftdi.GET_BITS_LOW, Ftdi.GET_BITS_HIGH] * count) fmt = '<%dH' % count else: cmd = bytearray([Ftdi.GET_BITS_LOW] * count) fmt = None cmd.append(Ftdi.SEND_IMMEDIATE) if len(cmd) > self.MPSSE_PAYLOAD_MAX_LENGTH: raise ValueError('Too many samples') self._ftdi.write_data(cmd) size = scalc(fmt) if fmt else count data = self._ftdi.read_data_bytes(size, 4) if len(data) != size: raise FtdiError('Cannot read GPIO') if fmt: return sunpack(fmt, data) return data
def _decode_230x(self): cfg = self._config misc, = sunpack('<H', self._eeprom[0x00:0x02]) cfg['channel_a_driver'] = 'VCP' if misc & (1 << 7) else 'D2XX' for bit in self.INVERT: value = self._eeprom[0x0B] cfg['invert_%s' % self.INVERT(bit).name] = bool(value & bit) max_drive = self.DRIVE.LOW | self.DRIVE.HIGH value = self._eeprom[0x0c] for grp in range(2): conf = value &0xF cfg['group_%d_drive' % grp] = bool((conf & max_drive) == max_drive) cfg['group_%d_schmitt' % grp] = conf & self.DRIVE.SCHMITT cfg['group_%d_slew' % grp] = conf & self.DRIVE.SLOW_SLEW value >>= 4 for bix in range(4): value = self._eeprom[0x1A + bix] cfg['cbus_func_%d' % bix] = self.CBUSX(value).name cfg['chip'] = Hex2Int(self._eeprom[0x1E])
def _decode_x(self): # FT-X series cfg = self._config misc, = sunpack('<H', self._eeprom[0x00:0x02]) cfg['channel_a_driver'] = 'VCP' if misc & (1 << 7) else 'D2XX' for bit in self.UART_BITS: value = self._eeprom[0x0B] cfg['invert_%s' % self.UART_BITS(bit).name] = bool(value & bit) max_drive = self.DRIVE.LOW.value | self.DRIVE.HIGH.value value = self._eeprom[0x0c] for grp in range(2): conf = value & 0xF bus = 'c' if grp else 'd' cfg['%sbus_drive' % bus] = 4 * (1+(conf & max_drive)) cfg['%sbus_schmitt' % bus] = bool(conf & self.DRIVE.SCHMITT) cfg['%sbus_slow_slew' % bus] = bool(conf & self.DRIVE.SLOW_SLEW) value >>= 4 for bix in range(4): value = self._eeprom[0x1A + bix] try: cfg['cbus_func_%d' % bix] = self.CBUSX(value).name except ValueError: pass
def handle(self): while True: chunk = self.connection.recv(4) if len(chunk) < 4: break slen = sunpack('>L', chunk)[0] chunk = self.connection.recv(slen) while len(chunk) < slen: chunk = chunk + self.connection.recv(slen - len(chunk)) record = self.unpickle(chunk) logger = logging.getLogger(record.name) if not logger.handlers: if config_dict.get("is_detail"): formatter = logging.Formatter(config_dict["dfmt"]) else: formatter = logging.Formatter(config_dict["fmt"]) handler = TimedRotatingFileHandler( record.name, when=config_dict["when"], backupCount=config_dict["backup_count"]) handler.setFormatter(formatter) logger.addHandler(handler) logger.handle(record)
def iptoint(ipstr): return sunpack('!I', inet_aton(ipstr))[0]