def _read_frame_header(self, expected_frame_type: int = None) -> Tuple[int, int]: """Read frame header and frame type. Return them as tuple of integers. :param expected_frame_type: Check if the frame_type is exactly as expected :return: Tuple of integers representing frame header and frame type :raises McuBootDataAbortError: Target sens Data Abort frame :raises McuBootConnectionError: Unexpected frame header or frame type (if specified) :raises McuBootConnectionError: When received invalid ACK """ timeout = Timeout(self.timeout, "ms") while not timeout.overflow(): header = to_int(self._read(1)) if header != self.FRAME_START_BYTE_NOT_READY: break if header != self.FRAME_START_BYTE: raise McuBootConnectionError( f"Received invalid frame header '{header:#X}' expected '{self.FRAME_START_BYTE:#X}'" ) frame_type = to_int(self._read(1)) if frame_type == FPType.ABORT: raise McuBootDataAbortError() if expected_frame_type: if frame_type != expected_frame_type: raise McuBootConnectionError( f"received invalid ACK '{frame_type:#X}' expected '{expected_frame_type:#X}'" ) return header, frame_type
def ping(self) -> None: """Ping the target device, retrieve protocol version. :raises McuBootConnectionError: If the target device doesn't respond to ping :raises McuBootConnectionError: If the start frame is not received :raises McuBootConnectionError: If the header is invalid :raises McuBootConnectionError: If the frame type is invalid :raises McuBootConnectionError: If the ping response is not received :raises McuBootConnectionError: If crc does not match """ with self.ping_timeout(timeout=PING_TIMEOUT_MS): ping = struct.pack("<BB", self.FRAME_START_BYTE, FPType.PING) self._send_frame(ping, wait_for_ack=False) # after power cycle, MBoot v 3.0+ may respond to first command with a leading dummy data # we read data from UART until the FRAME_START_BYTE byte start_byte = b"" for i in range(MAX_PING_RESPONSE_DUMMY_BYTES): start_byte = self._read(1) if start_byte is None: raise McuBootConnectionError( "Failed to receive initial byte") if start_byte == self.FRAME_START_BYTE.to_bytes( length=1, byteorder="little"): logger.debug( f"FRAME_START_BYTE received in {i + 1}. attempt.") break else: raise McuBootConnectionError( "Failed to receive FRAME_START_BYTE") header = to_int(start_byte) if header != self.FRAME_START_BYTE: raise McuBootConnectionError("Header is invalid") frame_type = to_int(self._read(1)) if frame_type != FPType.PINGR: raise McuBootConnectionError("Frame type is invalid") response_data = self._read(8) response = PingResponse.parse(response_data) # ping response has different crc computation than the other responses # that's why we can't use calc_frame_crc method # crc data for ping excludes the last 2B of response data, which holds the CRC from device crc_data = struct.pack(f"<BB{len(response_data) -2}B", header, frame_type, *response_data[:-2]) crc = calc_crc(crc_data) if crc != response.crc: raise McuBootConnectionError("Received CRC doesn't match") self.protocol_version = response.version self.options = response.options
def close(self) -> None: """Close the UART interface. :raises McuBootConnectionError: In any case of fail of UART close operation. """ try: self.device.close() except Exception as e: raise McuBootConnectionError(str(e)) from e
def info(self) -> str: """Return information about the UART interface. :return: Description of UART interface :raises McuBootConnectionError: When no port is available """ try: return self.device.port except Exception as e: raise McuBootConnectionError(str(e)) from e
def open(self) -> None: """Open the UART interface. :raises McuBootConnectionError: In any case of fail of UART open operation. """ for n in range(MAX_UART_OPEN_ATTEMPTS): try: self.device.open() self.ping() logger.debug(f"Interface opened after {n + 1} attempts.") return except (McuBootConnectionError, TimeoutError) as e: self.device.close() logger.debug(f"Opening interface failed with: {repr(e)}") except Exception as exc: self.device.close() raise McuBootConnectionError( "UART Interface open operation fails.") from exc raise McuBootConnectionError( f"Cannot open UART interface after {MAX_UART_OPEN_ATTEMPTS} attempts." )
def test_catch_spsdk_error(): with pytest.raises(SystemExit) as exc: function_under_test(AssertionError()) assert exc.value.code == 2 with pytest.raises(SystemExit) as exc_2: function_under_test(McuBootConnectionError()) assert exc_2.value.code == 2 with pytest.raises(SystemExit) as exc_3: function_under_test(IndexError()) assert exc_3.value.code == 3 assert function_under_test(None) == 0
def _write(self, data: bytes) -> None: """Send data to device. :param data: Data to send :raises McuBootConnectionError: When sending the data fails """ logger.debug(f"[{' '.join(f'{b:02x}' for b in data)}]") try: self.device.reset_input_buffer() self.device.reset_output_buffer() self.device.write(data) self.device.flush() except Exception as e: raise McuBootConnectionError(str(e)) from e
def parse(cls, data: bytes) -> "PingResponse": """Parse raw data into PingResponse object. :param data: bytes to be unpacked to PingResponse object 4B version, 2B data, 2B CRC16 :raises McuBootConnectionError: Received invalid ping response :return: PingResponse """ try: version, options, crc = struct.unpack("<I2H", data) except struct.error as err: raise McuBootConnectionError( "Received invalid ping response") from err return cls(version, options, crc)
def _write(self, data: bytes) -> None: """Send data to device. :param data: Data to send :raises McuBootConnectionError: When sending the data fails :raises TimeoutError: When data NAKed or could not be written """ logger.debug(f"[{' '.join(f'{b:02x}' for b in data)}]") try: result = self.port.DeviceWrite(devAddr=self.i2c_address, txData=data) except Exception as e: raise McuBootConnectionError(str(e)) from e if result < 0: raise TimeoutError()
def _read(self, length: int) -> bytes: """Read 'length' amount for bytes from device. :param length: Number of bytes to read :return: Data read from the device :raises TimeoutError: Time-out :raises McuBootConnectionError: When reading data from device fails """ try: data = self.device.read(length) except Exception as e: raise McuBootConnectionError(str(e)) from e if not data: raise TimeoutError() logger.debug(f"<{' '.join(f'{b:02x}' for b in data)}>") return data
def _write(self, data: bytes) -> None: """Send data to device. :param data: Data to send :raises McuBootConnectionError: When sending the data fails :raises TimeoutError: When data could not be written """ logger.debug(f"[{' '.join(f'{b:02x}' for b in data)}]") try: (dummy, result) = self.port.Transfer(devSelectPort=self.spi_sselport, devSelectPin=self.spi_sselpin, txData=data) except Exception as e: raise McuBootConnectionError(str(e)) from e if result < 0: raise TimeoutError()
def _read(self, length: int) -> bytes: """Read 'length' amount for bytes from device. :param length: Number of bytes to read :return: Data read from the device :raises TimeoutError: Time-out :raises McuBootConnectionError: When reading data from device fails :raises TimeoutError: When no data received """ try: (data, result) = self.port.DeviceRead(devAddr=self.i2c_address, rxSize=length) except Exception as e: raise McuBootConnectionError(str(e)) from e if result < 0 or not data: raise TimeoutError() logger.debug(f"<{' '.join(f'{b:02x}' for b in data)}>") return data
def ping(self) -> None: """Ping the target device, retrieve protocol version. :raises AssertionError: If the target device doesn't respond to ping :raises McuBootConnectionError: If the start frame is not received """ ping = struct.pack('<BB', self.FRAME_START_BYTE, FPType.PING) self._send_frame(ping, wait_for_ack=False) # after power cycle, MBoot v 3.0+ may respond to first command with a leading dummy data # we read data from UART until the FRAME_START_BYTE byte start_byte = b'' for i in range(MAX_PING_RESPONSE_DUMMY_BYTES): start_byte = self._read(1) assert start_byte, f"Failed to receive initial byte" if start_byte == self.FRAME_START_BYTE.to_bytes( length=1, byteorder='little'): logger.debug(f"FRAME_START_BYTE received in {i + 1}. attempt.") break else: raise McuBootConnectionError(f"Failed to receive FRAME_START_BYTE") header = to_int(start_byte) assert header == self.FRAME_START_BYTE frame_type = to_int(self._read(1)) assert frame_type == FPType.PINGR response_data = self._read(8) assert response_data, f"Failed to receive ping response" response = PING_RESPONSE.parse(response_data) # ping response has different crc computation than the other responses # that's why we can't use calc_frame_crc method # crc data for ping excludes the last 2B of response data, which holds the CRC from device crc_data = struct.pack(f'<BB{len(response_data) -2}B', header, frame_type, *response_data[:-2]) crc = calc_crc(crc_data) assert crc == response.crc, \ f"Received CRC doesn't match" self.protocol_version = response.version self.options = response.options
def read(self) -> Union[CmdResponse, bytes]: """Read data from device. :return: read data :raises McuBootDataAbortError: Indicates data transmission abort :raises McuBootConnectionError: When received invalid CRC """ _, frame_type = self._read_frame_header() length = to_int(self._read(2)) crc = to_int(self._read(2)) if not length: self._send_ack() raise McuBootDataAbortError() data = self._read(length) self._send_ack() calculated_crc = self._calc_frame_crc(data, frame_type) if crc != calculated_crc: raise McuBootConnectionError("Received invalid CRC") if frame_type == FPType.CMD: return parse_cmd_response(data) return data
def _read(self, length: int) -> bytes: """Read 'length' amount for bytes from device. :param length: Number of bytes to read :return: Data read from the device :raises McuBootConnectionError: When reading data from device fails :raises TimeoutError: When no data received """ try: (data, result) = self.port.Transfer( devSelectPort=self.spi_sselport, devSelectPin=self.spi_sselpin, txData=None, size=length, ) except Exception as e: raise McuBootConnectionError(str(e)) from e if result < 0 or not data: raise TimeoutError() logger.debug(f"<{' '.join(f'{b:02x}' for b in data)}>") return data
def __init__(self, port: str = None, baudrate: int = 57600, timeout: int = 5000) -> None: """Initialize the UART interface. :param port: name of the serial port, defaults to None :param baudrate: baudrate of the serial port, defaults to 57600 :param timeout: read/write timeout in milliseconds, defaults to 5000 :raises McuBootConnectionError: when the port could not be opened """ super().__init__() try: self.timeout = timeout self.device = Serial(port=port, timeout=timeout / 1000, baudrate=baudrate) self.close() self.protocol_version = 0 self.options = 0 except Exception as e: raise McuBootConnectionError(str(e)) from e