class SerialDriver(Driver): """ An implementation of a serial ANT+ device driver """ def __init__(self, device: str, baudRate: int = 115200, logger: Logger = None): super().__init__(logger=logger) self._device = device self._baudRate = baudRate self._serial = None def __str__(self): if self.isOpen(): return self._device + " @ " + str(self._baudRate) return None def _isOpen(self) -> bool: return self._serial is not None def _open(self) -> None: try: self._serial = Serial(port=self._device, baudrate=self._baudRate) except SerialException as e: raise DriverException(str(e)) if not self._serial.isOpen(): raise DriverException("Could not open specified device") def _close(self) -> None: self._serial.close() self._serial = None def _read(self, count: int, timeout=None) -> bytes: return self._serial.read(count) def _write(self, data: bytes) -> None: try: self._serial.write(data) self._serial.flush() except SerialTimeoutException as e: raise DriverException(str(e)) def _abort(self) -> None: if self._serial is not None: self._serial.cancel_read() self._serial.cancel_write() self._serial.reset_input_buffer() self._serial.reset_output_buffer()
class HdlcppSerial(Hdlcpp): def __init__(self, port, baudrate=115200, bufferSize=256, writeTimeout=100, writeRetries=1): self.serial = Serial(port, baudrate) super().__init__(self._transportRead, self._transportWrite, bufferSize, writeTimeout, writeRetries) def stop(self): self.serial.cancel_read() def _transportRead(self, length): return self.serial.read(length) def _transportWrite(self, data): return self.serial.write(data)
class SniffleHW: def __init__(self, serport): self.decoder_state = SniffleDecoderState() self.ser = Serial(serport, 2000000) self.ser.write(b'@@@@@@@@\r\n') # command sync self.recv_cancelled = False def _send_cmd(self, cmd_byte_list): b0 = (len(cmd_byte_list) + 3) // 3 cmd = bytes([b0, *cmd_byte_list]) msg = b64encode(cmd) + b'\r\n' self.ser.write(msg) def cmd_chan_aa_phy(self, chan=37, aa=0x8E89BED6, phy=0, crci=0x555555): if not (0 <= chan <= 39): raise ValueError("Channel must be between 0 and 39") if not (0 <= phy <= 2): raise ValueError("PHY must be 0 (1M), 1 (2M), or 2 (coded)") self._send_cmd([0x10, *list(pack("<BLBL", chan, aa, phy, crci))]) def cmd_pause_done(self, pause_when_done=False): if pause_when_done: self._send_cmd([0x11, 0x01]) else: self._send_cmd([0x11, 0x00]) def cmd_rssi(self, rssi=-80): self._send_cmd([0x12, rssi & 0xFF]) def cmd_mac(self, mac_byte_list=None, hop3=True): if mac_byte_list is None: self._send_cmd([0x13]) else: if len(mac_byte_list) != 6: raise ValueError("MAC must be 6 bytes!") self._send_cmd([0x13, *mac_byte_list]) if hop3: # hop with advertisements between 37/38/39 # unnecessary/detrimental with extended advertising self._send_cmd([0x14]) def cmd_follow(self, enable=True): if enable: self._send_cmd([0x15, 0x01]) else: self._send_cmd([0x15, 0x00]) def cmd_auxadv(self, enable=True): if enable: self._send_cmd([0x16, 0x01]) else: self._send_cmd([0x16, 0x00]) def cmd_reset(self): self._send_cmd([0x17]) def cmd_marker(self): self._send_cmd([0x18]) # for master or slave modes def cmd_transmit(self, llid, pdu): if not (0 <= llid <= 3): raise ValueError("Out of bounds LLID") if len(pdu) > 255: raise ValueError("Too long PDU") self._send_cmd([0x19, llid, len(pdu), *pdu]) def cmd_connect(self, peerAddr, llData, is_random=True): if len(peerAddr) != 6: raise ValueError("Invalid peer address") if len(llData) != 22: raise ValueError("Invalid LLData") self._send_cmd([0x1A, 1 if is_random else 0, *peerAddr, *llData]) def cmd_setaddr(self, addr, is_random=True): if len(addr) != 6: raise ValueError("Invalid MAC address") self._send_cmd([0x1B, 1 if is_random else 0, *addr]) def cmd_advertise(self, advData, scanRspData): if len(advData) > 31: raise ValueError("advData too long!") if len(scanRspData) > 31: raise ValueError("scanRspData too long!") paddedAdvData = [len(advData), *advData] + [0] * (31 - len(advData)) paddedScnData = [len(scanRspData), *scanRspData ] + [0] * (31 - len(scanRspData)) self._send_cmd([0x1C, *paddedAdvData, *paddedScnData]) def cmd_adv_interval(self, intervalMs): if not (20 < intervalMs < 0xFFFF): raise ValueError("Advertising interval out of bounds") self._send_cmd([0x1D, intervalMs & 0xFF, intervalMs >> 8]) def cmd_irk(self, irk=None, hop3=True): if irk is None: self._send_cmd([0x1E]) elif len(irk) != 16: raise ValueError("Invalid IRK length!") else: self._send_cmd([0x1E, *irk]) if hop3: self._send_cmd([0x14]) def recv_msg(self): got_msg = False while not got_msg: pkt = self.ser.readline() try: data = b64decode(pkt.rstrip()) except BAError as e: print(str(pkt, encoding='ascii').rstrip()) print("Ignoring message:", e, file=stderr) continue got_msg = True if self.recv_cancelled: self.recv_cancelled = False return -1, None, b'' # msg type, msg body return data[0], data[1:], pkt def recv_and_decode(self): mtype, mbody, pkt = self.recv_msg() try: if mtype == 0x10: return PacketMessage(mbody, self.decoder_state) elif mtype == 0x11: return DebugMessage(mbody) elif mtype == 0x12: return MarkerMessage(mbody, self.decoder_state) elif mtype == 0x13: return StateMessage(mbody, self.decoder_state) elif mtype == -1: return None # receive cancelled else: raise SniffleHWPacketError("Unknown message type 0x%02X!" % mtype) except BaseException as e: print(str(pkt, encoding='ascii').rstrip()) print("Ignoring message:", e, file=stderr) print_exc() return None def cancel_recv(self): self.recv_cancelled = True self.ser.cancel_read() def mark_and_flush(self): # use marker to zero time, flush every packet before marker # also tolerate errors from incomplete lines in UART buffer self.cmd_marker() while True: try: msg = self.recv_and_decode() except SniffleHWPacketError: print("WARNING: invalid message during flush, ignoring...") continue if isinstance(msg, MarkerMessage): break def random_addr(self): # generate a random static address, set it addr = [randint(0, 255) for i in range(6)] addr[5] |= 0xC0 # make it static self.cmd_setaddr(bytes(addr)) # automatically generate sane LLData def initiate_conn(self, peerAddr, is_random=True): llData = [] # access address llData.extend([randint(0, 255) for i in range(4)]) # initial CRC llData.extend([randint(0, 255) for i in range(3)]) # WinSize, WinOffset, Interval, Latency, Timeout llData.append(3) llData.extend(pack("<H", randint(5, 15))) llData.extend(pack("<H", 24)) llData.extend(pack("<H", 1)) llData.extend(pack("<H", 50)) # Channel Map llData.extend([0xFF, 0xFF, 0xFF, 0xFF, 0x1F]) # Hop, SCA = 0 llData.append(randint(5, 16)) self.cmd_connect(peerAddr, bytes(llData), is_random) # return the access address return unpack("<L", bytes(llData[:4]))[0]
class SniffleHW: def __init__(self, serport): self.decoder_state = SniffleDecoderState() self.ser = Serial(serport, 921600) self.ser.write(b'@@@@@@@@\r\n') # command sync self.recv_cancelled = False def _send_cmd(self, cmd_byte_list): b0 = (len(cmd_byte_list) + 3) // 3 cmd = bytes([b0, *cmd_byte_list]) msg = b64encode(cmd) + b'\r\n' self.ser.write(msg) def cmd_chan_aa_phy(self, chan=37, aa=0x8E89BED6, phy=0, crci=0x555555): if not (0 <= chan <= 39): raise ValueError("Channel must be between 0 and 39") if not (0 <= phy <= 2): raise ValueError("PHY must be 0 (1M), 1 (2M), or 2 (coded)") self._send_cmd([0x10, *list(pack("<BLBL", chan, aa, phy, crci))]) def cmd_pause_done(self, pause_when_done=False): if pause_when_done: self._send_cmd([0x11, 0x01]) else: self._send_cmd([0x11, 0x00]) def cmd_rssi(self, rssi=-80): self._send_cmd([0x12, rssi & 0xFF]) def cmd_mac(self, mac_byte_list=None, hop3=True): if mac_byte_list is None: self._send_cmd([0x13]) else: if len(mac_byte_list) != 6: raise ValueError("MAC must be 6 bytes!") self._send_cmd([0x13, *mac_byte_list]) if hop3: # hop with advertisements between 37/38/39 # unnecessary/detrimental with extended advertising self._send_cmd([0x14]) def cmd_endtrim(self, end_trim=0x10): self._send_cmd([0x15, *list(pack("<L", end_trim))]) def cmd_auxadv(self, enable=True): if enable: self._send_cmd([0x16, 0x01]) else: self._send_cmd([0x16, 0x00]) def cmd_reset(self): self._send_cmd([0x17]) def cmd_marker(self): self._send_cmd([0x18]) def recv_msg(self): got_msg = False while not got_msg: pkt = self.ser.readline() try: data = b64decode(pkt.rstrip()) except BAError as e: print("Ignoring message:", e, file=stderr) continue got_msg = True if self.recv_cancelled: self.recv_cancelled = False return -1, None # msg type, msg body return data[0], data[1:] def recv_and_decode(self): mtype, mbody = self.recv_msg() if mtype == 0x10: return PacketMessage(mbody, self.decoder_state) elif mtype == 0x11: return DebugMessage(mbody) elif mtype == 0x12: return MarkerMessage(mbody, self.decoder_state) elif mtype == -1: return None # receive cancelled else: raise SniffleHWPacketError("Unknown message type 0x%02X!" % mtype) def cancel_recv(self): self.recv_cancelled = True self.ser.cancel_read() def mark_and_flush(self): # use marker to zero time, flush every packet before marker # also tolerate errors from incomplete lines in UART buffer self.cmd_marker() while True: try: msg = self.recv_and_decode() except SniffleHWPacketError: print("WARNING: invalid message during flush, ignoring...") continue if isinstance(msg, MarkerMessage): break
class MockDev: """Connects a mock device to read and write a virtual serial port. This uses ``socat`` to open a virtual com port that is used by the mock serial device and a virtual com port to connect to for serial testing. Remaining kwargs are used for instantiating the serial port. Attributes: logger (obj): Class level logger based on class name bytes_read (int): Counts the number of bytes that are read from the mock serial port. bytes_written (int): Counts the number of bytes that the mock serial port writes. dev (obj): The serial device instance. """ def __init__(self, **kwargs): # pylint: disable=C0301 """Initialize the mock serial device. Keyword Args: **dev_driver (obj, optional): Already instantiated serial driver, if not present serial port will be created. **port (string): Serial port of mock dev, defaults to "/tmp/mm_pal_mock_dev0" **baudrate (int): Baudrate for mock dev, defaults to 115200 Note: Refer to the `Serial <https://pyserial.readthedocs.io/en/latest/pyserial_api.html#serial.Serial.__init__>`_ class for additional ``**kwargs`` functionality. """ # noqa: E501 self.logger = logging.getLogger(self.__class__.__name__) kwargs['port'] = kwargs.pop('port', "/tmp/mm_pal_mock_dev0") kwargs['baudrate'] = kwargs.pop('baudrate', 115200) self.mock_port = kwargs['port'] self.logger.debug("Mock device = Serial(%r)", kwargs) self.bytes_read = 0 self.bytes_written = 0 self.wr_bytes = None self.wr_index = None self.rr_data = None self.force_fails = 0 self.force_error_code = errno.EADDRNOTAVAIL self.force_timeout = 0 self.force_parse_error = 0 self.force_data_fail = 0 self.force_write_fail = 0 self._exit_thread = False self._run_thread = None if 'dev_driver' in kwargs: self.dev = kwargs.pop('dev_driver') else: self.dev = Serial(**kwargs) def __del__(self): """Destructor that ends the thread loop.""" self.end_thread_loop() self.dev.close() def close(self): """Close serial port.""" self.dev.close() def run_loopback_line(self): """Run loopback per line on mock serial dev. Read serial line and write line back to the serial port. Update the :py:attr:`~bytes_written` and :py:attr:`~bytes_read`. Loop until the :py:attr:`~_exit_thread` is set to `True`. """ while True: self.logger.debug("run_loopback_line: readline()") read = self.dev.readline() if self.force_timeout > 0: self.force_timeout -= 1 continue self.bytes_read += len(read) self.logger.debug("run_loopback_line: readline=%r", read) self.dev.write(read) self.bytes_written += len(read) self.logger.debug("bytes_read=%r", self.bytes_read) self.logger.debug("bytes_written=%r", self.bytes_written) if self._exit_thread: self._exit_thread = False break self.logger.debug("run_loopback_line exited") def run_loopback_bytes(self): """Run loopback for every byte on mock serial dev. Read byte and write it back. Update the :py:attr:`~bytes_written` and :py:attr:`~bytes_read`. Loop until the :py:attr:`~_exit_thread` is set to `True`. """ while True: self.logger.debug("run_loopback_bytes: read()") read = self.dev.read() self.bytes_read += len(read) self.logger.debug("run_loopback_bytes: read=%r", read) self.dev.write(read) self.bytes_written += len(read) self.logger.debug("bytes_read=%r", self.bytes_read) self.logger.debug("bytes_written=%r", self.bytes_written) if self._exit_thread: self._exit_thread = False break self.logger.debug("run_loopback_bytes exited") def _parse_wr_cmd(self, args): if self.force_write_fail > 0: self.force_write_fail -= 1 return {"result": errno.EINVAL} if len(args) < 3: response = {"result": errno.EINVAL} self.logger.debug("Invalid args") else: response = {"result": 0} index = int(args[1]) self.wr_index = index self.wr_bytes = [] for arg in args[2:]: num = int(arg) self.wr_bytes.append(num) if num > 255: response = {"result": errno.EOVERFLOW} break self.logger.debug("data[%r]=%r", index, num) index += 1 return response def _parse_rr_cmd(self, args): if self.force_data_fail > 0: self.force_data_fail -= 1 return {"result": 0, "data": "foo"} index = int(args[1]) size = int(args[2]) if size == 0: response = {"result": errno.EINVAL} self.logger.debug("Invalid size") else: if self.rr_data is None: data = list(range(index, index + size)) data = [i & 0xFF for i in data] else: try: data = list( self.rr_data.to_bytes(size, byteorder='little', signed=True)) except OverflowError: data = list( self.rr_data.to_bytes(size, byteorder='little', signed=False)) self.rr_data = None response = {"data": data, "result": 0} self.logger.debug("response=%r", response) return response def _parse_json_cmd(self, args): if self.force_fails > 0: self.force_fails -= 1 return {"result": self.force_error_code} if self.force_timeout > 0: self.force_timeout -= 1 return {} try: if args[0] == b'rr': response = self._parse_rr_cmd(args) elif args[0] == b'version': response = {"version": "0.0.1", "result": 0} elif (args[0] == b'ex' or args[0] == b'mcu_rst' or args[0] == b'special_cmd'): response = {"result": 0} elif args[0] == b'wr': response = self._parse_wr_cmd(args) else: response = {"result": errno.EPROTONOSUPPORT} except (IndexError) as exc: response = {"result": errno.EPROTONOSUPPORT} self.logger.debug("error=%r", exc) except (ValueError, TypeError) as exc: response = {"result": errno.EBADMSG} self.logger.debug("error=%r", exc) return response def run_app_json(self): """Run a basic json parsing app. Support a number a defined commands to simulate a device with parsed json structure. ``rr <index> <size>`` reads a byte value, mock values start at 0 when ``index`` is 0 and increase by one and truncate at 255. Respond with data and result. ``wr <index> <data0..datan>`` writes bytes to register. each number must be within 0-255 except for the index. Respond only with a result. ``ex`` or ``mcu_rst`` are basic commands that just respond with result. ``version`` indicates interface version. Respond with result and version. """ while True: self.logger.debug("run_app_json: readline()") read = self.dev.readline() args = read.split() self.logger.debug("cmd=%r", args) response = self._parse_json_cmd(args) if self.force_parse_error > 0: self.force_parse_error -= 1 response = f"foobar\n{{\"response\": {-999}}}\n" else: response = json.dumps(response) response = f"{response}\n" self.dev.write(response.encode()) self.bytes_read += len(read) self.bytes_written += len(response) self.logger.debug("bytes_read=%r", self.bytes_read) self.logger.debug("bytes_written=%r", self.bytes_written) if self._exit_thread: self._exit_thread = False break self.logger.debug("run_app_json exited") # pylint: disable=W1113 def start_thread_loop(self, func=None, *args): """Start a daemon thread to run a function. Only one thread can be active at a time. If another function is started the current thread will stop. The thread *should* get cleaned up in the :py:meth:`__del__` destructor. Args: func (function, optional): Function to run in the background, defaults to :py:meth:`run_loopback_line`. *args: Variable length arguments list. """ self.end_thread_loop() if func is None: func = self.run_loopback_line self._run_thread = Thread(target=func, args=args) self._run_thread.setDaemon(True) self.logger.debug("start_loopback_line_thread_loop") self._run_thread.start() def end_thread_loop(self): """End the thread started with :py:meth:`start_thread_loop`. Set the :py:attr:`~_exit_thread` to True and cancel any serial reads. Wait until the thread has ended before returning. """ self._exit_thread = True self.dev.cancel_read() if self._run_thread is not None: self.logger.debug("Exiting thread loop") while self._run_thread.is_alive(): sleep(0.1) self._exit_thread = False
class SniffleHW: def __init__(self, serport): self.decoder_state = SniffleDecoderState() self.ser = Serial(serport, 2000000) self.ser.write(b'@@@@@@@@\r\n') # command sync self.recv_cancelled = False self.rate_limiter = RateLimiter() def _send_cmd(self, cmd_byte_list): b0 = (len(cmd_byte_list) + 3) // 3 cmd = bytes([b0, *cmd_byte_list]) msg = b64encode(cmd) + b'\r\n' self.rate_limiter.do_cmd() self.ser.write(msg) def cmd_chan_aa_phy(self, chan=37, aa=0x8E89BED6, phy=0, crci=0x555555): if not (0 <= chan <= 39): raise ValueError("Channel must be between 0 and 39") if not (0 <= phy <= 3): raise ValueError("PHY must be 0 (1M), 1 (2M), 2 (coded S=8), or 3 (coded S=2)") self._send_cmd([0x10, *list(pack("<BLBL", chan, aa, phy, crci))]) def cmd_pause_done(self, pause_when_done=False): if pause_when_done: self._send_cmd([0x11, 0x01]) else: self._send_cmd([0x11, 0x00]) def cmd_rssi(self, rssi=-80): self._send_cmd([0x12, rssi & 0xFF]) def cmd_mac(self, mac_byte_list=None, hop3=True): if mac_byte_list is None: self._send_cmd([0x13]) else: if len(mac_byte_list) != 6: raise ValueError("MAC must be 6 bytes!") self._send_cmd([0x13, *mac_byte_list]) if hop3: # hop with advertisements between 37/38/39 # unnecessary/detrimental with extended advertising self._send_cmd([0x14]) def cmd_follow(self, enable=True): if enable: self._send_cmd([0x15, 0x01]) else: self._send_cmd([0x15, 0x00]) def cmd_auxadv(self, enable=True): if enable: self._send_cmd([0x16, 0x01]) else: self._send_cmd([0x16, 0x00]) def cmd_reset(self): self._send_cmd([0x17]) def cmd_marker(self): self._send_cmd([0x18]) # for master or slave modes def cmd_transmit(self, llid, pdu): if not (0 <= llid <= 3): raise ValueError("Out of bounds LLID") if len(pdu) > 255: raise ValueError("Too long PDU") self._send_cmd([0x19, llid, len(pdu), *pdu]) def cmd_connect(self, peerAddr, llData, is_random=True): if len(peerAddr) != 6: raise ValueError("Invalid peer address") if len(llData) != 22: raise ValueError("Invalid LLData") self._send_cmd([0x1A, 1 if is_random else 0, *peerAddr, *llData]) def cmd_setaddr(self, addr, is_random=True): if len(addr) != 6: raise ValueError("Invalid MAC address") self._send_cmd([0x1B, 1 if is_random else 0, *addr]) def cmd_advertise(self, advData, scanRspData): if len(advData) > 31: raise ValueError("advData too long!") if len(scanRspData) > 31: raise ValueError("scanRspData too long!") paddedAdvData = [len(advData), *advData] + [0]*(31 - len(advData)) paddedScnData = [len(scanRspData), *scanRspData] + [0]*(31 - len(scanRspData)) self._send_cmd([0x1C, *paddedAdvData, *paddedScnData]) def cmd_adv_interval(self, intervalMs): if not (20 < intervalMs < 0xFFFF): raise ValueError("Advertising interval out of bounds") self._send_cmd([0x1D, intervalMs & 0xFF, intervalMs >> 8]) def cmd_irk(self, irk=None, hop3=True): if irk is None: self._send_cmd([0x1E]) elif len(irk) != 16: raise ValueError("Invalid IRK length!") else: self._send_cmd([0x1E, *irk]) if hop3: self._send_cmd([0x14]) def cmd_instahop(self, enable=True): if enable: self._send_cmd([0x1F, 0x01]) else: self._send_cmd([0x1F, 0x00]) def cmd_setmap(self, chmap=b'\xFF\xFF\xFF\xFF\x1F'): if len(chmap) != 5: raise ValueError("Invalid channel map length!") self._send_cmd([0x20] + list(chmap)) # triplets should be a list of 3-tuples of integers # each 3-tuple is (WinOffset, Interval, delta_Instant) def cmd_interval_preload(self, triplets=[]): if len(triplets) > 4: raise ValueError("Too many preload triplets") cmd_bytes = [0x21] for t in triplets: if len(t) != 3: raise ValueError("Not a triplet") cmd_bytes.extend(list(pack("<HHH", *t))) self._send_cmd(cmd_bytes) def _recv_msg(self, desync=False): got_msg = False while not got_msg: if desync: # readline is inefficient, but a good way to synchronize pkt = self.ser.readline() try: data = b64decode(pkt.rstrip()) except BAError as e: print(str(pkt, encoding='ascii').rstrip()) print("Ignoring message:", e, file=stderr) continue else: # minimum packet is 4 bytes base64 + 2 bytes CRLF pkt = self.ser.read(6) # decode header to get length byte try: data = b64decode(pkt[:4]) except BAError as e: print(str(pkt, encoding='ascii').rstrip()) print("Ignoring message:", e, file=stderr) self.ser.readline() # eat CRLF continue # now read the rest of the packet (if there is anything) word_cnt = data[0] if word_cnt: pkt += self.ser.read((word_cnt - 1) * 4) # make sure CRLF is present if pkt[-2:] != b'\r\n': print("Ignoring message due to missing CRLF", file=stderr) self.ser.readline() # eat CRLF continue try: data = b64decode(pkt[:-2]) except BAError as e: print(str(pkt, encoding='ascii').rstrip()) print("Ignoring message:", e, file=stderr) self.ser.readline() # eat CRLF continue got_msg = True if self.recv_cancelled: self.recv_cancelled = False return -1, None, b'' # msg type, msg body, raw return data[1], data[2:], pkt def recv_and_decode(self): mtype, mbody, pkt = self._recv_msg() try: if mtype == 0x10: return PacketMessage(mbody, self.decoder_state) elif mtype == 0x11: return DebugMessage(mbody) elif mtype == 0x12: return MarkerMessage(mbody, self.decoder_state) elif mtype == 0x13: return StateMessage(mbody, self.decoder_state) elif mtype == 0x14: return MeasurementMessage.from_raw(mbody) elif mtype == -1: return None # receive cancelled else: raise SniffleHWPacketError("Unknown message type 0x%02X!" % mtype) except BaseException as e: print(str(pkt, encoding='ascii').rstrip()) print("Ignoring message:", e, file=stderr) print_exc() return None def cancel_recv(self): self.recv_cancelled = True self.ser.cancel_read() def mark_and_flush(self): # use marker to zero time, flush every packet before marker # also tolerate errors from incomplete lines in UART buffer self.cmd_marker() while True: mtype, _, _ = self._recv_msg(True) if mtype == 0x12: # MarkerMessage break def random_addr(self): # generate a random static address, set it addr = [randint(0, 255) for i in range(6)] addr[5] |= 0xC0 # make it static self.cmd_setaddr(bytes(addr)) # automatically generate sane LLData def initiate_conn(self, peerAddr, is_random=True): llData = [] # access address llData.extend([randint(0, 255) for i in range(4)]) # initial CRC llData.extend([randint(0, 255) for i in range(3)]) # WinSize, WinOffset, Interval, Latency, Timeout llData.append(3) llData.extend(pack("<H", randint(5, 15))) llData.extend(pack("<H", 24)) llData.extend(pack("<H", 1)) llData.extend(pack("<H", 50)) # Channel Map llData.extend([0xFF, 0xFF, 0xFF, 0xFF, 0x1F]) # Hop, SCA = 0 llData.append(randint(5, 16)) self.cmd_connect(peerAddr, bytes(llData), is_random) # return the access address return unpack("<L", bytes(llData[:4]))[0]