def _rx_ext_packet(cls, msg: Message, command: int, rx_length: int, check_ack: bool = True, check_length: bool = True) -> Message: """ Parse a received packet :param msg: data to parse :param command: The extended command sent :param rx_length: Expected length of the received data :param check_ack: If true, checks the first byte as an AK byte :param tx_payload: what was sent, used if an exception needs to be thrown :return: The payload of the extended message """ assert (msg['command'] == 0x56) if not cls.VEX_CRC16.compute(msg.rx) == 0: raise VEXCommError( "CRC of message didn't match 0: {}".format( cls.VEX_CRC16.compute(msg.rx)), msg) assert (msg['payload'][0] == command) msg = msg['payload'][1:-2] if check_ack: nacks = { 0xFF: "General NACK", 0xCE: "CRC error on recv'd packet", 0xD0: "Payload too small", 0xD1: "Request transfer size too large", 0xD2: "Program CRC error", 0xD3: "Program file error", 0xD4: "Attempted to download/upload uninitialized", 0xD5: "Initialization invalid for this function", 0xD6: "Data not a multiple of 4 bytes", 0xD7: "Packet address does not match expected", 0xD8: "Data downloaded does not match initial length", 0xD9: "Directory entry does not exist", 0xDA: "Max user files, no more room for another user program", 0xDB: "User file exists" } if msg[0] in nacks.keys(): raise VEXCommError( "Device NACK'd with reason: {}".format(nacks[msg[0]]), msg) elif msg[0] != cls.ACK_BYTE: raise VEXCommError("Device didn't ACK", msg) msg = msg[1:] if len(msg) > 0: logger(cls).debug('Set msg window to {}'.format(bytes_to_str(msg))) if len(msg) != rx_length and check_length: raise VEXCommError( "Received length doesn't match {} (got {})".format( rx_length, len(msg)), msg) return msg
def _txrx_ack_packet(self, command: int, timeout=0.1): """ Goes through a send/receive cycle with a VEX device. Transmits the command with the optional additional payload, then reads and parses the outer layer of the response :param command: Command to send the device :param retries: Number of retries to attempt to parse the output before giving up and raising an error :return: Returns a dictionary containing the received command field and the payload. Correctly computes the payload length even if the extended command (0x56) is used (only applies to the V5). """ tx = self._tx_packet(command) self._rx_ack(timeout=timeout) logger(__name__).debug('TX: {}'.format(bytes_to_str(tx)))
def _rx_packet( self, timeout: Optional[float] = None ) -> Dict[str, Union[Union[int, bytes, bytearray], Any]]: # Optimized to read as quickly as possible w/o delay start_time = time.time() response_header = bytes([0xAA, 0x55]) response_header_stack = list(response_header) rx = bytearray() if timeout is None: timeout = self.default_timeout while (len(rx) > 0 or time.time() - start_time < timeout ) and len(response_header_stack) > 0: b = self.port.read(1) if len(b) == 0: continue b = b[0] if b == response_header_stack[0]: response_header_stack.pop(0) rx.append(b) else: logger(__name__).debug( "Tossing rx ({}) because {} didn't match".format( bytes_to_str(rx), b)) response_header_stack = bytearray(response_header) rx = bytearray() if not rx == bytearray(response_header): raise IOError( f"Couldn't find the response header in the device response after {timeout} s. " f"Got {rx.hex()} but was expecting {response_header.hex()}") rx.extend(self.port.read(1)) command = rx[-1] rx.extend(self.port.read(1)) payload_length = rx[-1] if command == 0x56 and (payload_length & 0x80) == 0x80: logger(__name__).debug('Found an extended message payload') rx.extend(self.port.read(1)) payload_length = ((payload_length & 0x7f) << 8) + rx[-1] payload = self.port.read(payload_length) rx.extend(payload) return {'command': command, 'payload': payload, 'raw': rx}
def __str__(self): return 'TX:{}\tRX:{}'.format(bytes_to_str(self.tx), bytes_to_str(self.rx))