Skip to content

feat: add SMPSerialRawTransport for Zephyr raw UART SMP#106

Open
JPHutchins wants to merge 1 commit into
mainfrom
feature/unencoded-serial-transport
Open

feat: add SMPSerialRawTransport for Zephyr raw UART SMP#106
JPHutchins wants to merge 1 commit into
mainfrom
feature/unencoded-serial-transport

Conversation

@JPHutchins
Copy link
Copy Markdown
Collaborator

Summary

Adds SMPSerialRawTransport for Zephyr 4.4's CONFIG_MCUMGR_TRANSPORT_RAW_UART — the raw (unencoded) SMP-over-UART transport that sends each SMP message as [8-byte header][payload] bytes with no framing, base64, or CRC. Smaller code size and faster transfers than the historical "SMP over console" framing, at the cost of being unable to share the UART with shell or log output.

The existing serial.py file is split into a package so the two serial transports can share connection management via a @final-method base class:

serial/
  __init__.py    re-exports SMPSerialTransport, SMPSerialRawTransport
  common.py      _SerialTransportBase: pyserial holder, connect/disconnect,
                 SerialException -> SMPTransportDisconnected translation
  encoded.py     SMPSerialTransport (behavior unchanged)
  unencoded.py   SMPSerialRawTransport (new)

from smpclient.transport.serial import SMPSerialTransport continues to work — MCUboot serial recovery still needs the encoded transport (its Kconfig unconditionally selects BASE64), no migration required for existing users.

Related

Test plan

  • uv run task all (ruff + pydoclint + mypy + pytest) passes
  • 205 tests pass, 14 skipped; coverage 92.07% (gate 91%)
  • New transport at 100% line + branch coverage
  • Existing SMPSerialTransport test suite passes unchanged (behavior-preserving refactor)
  • MTU-parametrized end-to-end upload test exercises SMPClient.upload() chunking against the raw transport over 7 MTU values
  • Manual smoke test against a CONFIG_MCUMGR_TRANSPORT_RAW_UART=y target

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 21, 2026 00:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new raw (unencoded) SMP-over-UART transport for Zephyr’s CONFIG_MCUMGR_TRANSPORT_RAW_UART, while refactoring the existing base64/framed serial transport into a package with shared connection management.

Changes:

  • Introduces SMPSerialRawTransport implementing Zephyr “raw UART” SMP message exchange ([8-byte header][payload]) with MTU-based send limiting.
  • Refactors existing SMPSerialTransport to share connect/disconnect/TX drain/read helpers via a _SerialTransportBase in serial/common.py.
  • Updates and expands the test suite, including a new end-to-end MTU-parametrized upload test for the raw transport.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/test_smp_serial_transport.py Updates serial mocking target to the new serial.common.Serial location.
tests/test_smp_serial_raw_transport.py Adds unit tests for the new raw serial transport behavior.
tests/test_smp_client.py Adds an MTU-parametrized upload test exercising chunking with the raw transport.
tests/test_base64.py Updates internal base64 helper imports to the new serial.encoded module.
src/smpclient/transport/serial/init.py Re-exports SMPSerialTransport and SMPSerialRawTransport from the new package layout.
src/smpclient/transport/serial/common.py Adds shared serial connection management and SerialException translation utilities.
src/smpclient/transport/serial/encoded.py Moves encoded transport onto the shared serial base class; behavior largely preserved.
src/smpclient/transport/serial/unencoded.py Implements the new raw SMP serial transport.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/smpclient/transport/serial/unencoded.py
Comment thread src/smpclient/transport/serial/common.py Outdated
Comment thread src/smpclient/transport/serial/common.py Outdated
Comment thread src/smpclient/transport/serial/common.py Outdated
Comment thread tests/test_smp_client.py
Zephyr 4.4 introduced CONFIG_MCUMGR_TRANSPORT_RAW_UART -- an SMP-over-UART
transport that sends each SMP message as raw [8-byte header][payload] bytes
with no framing, base64, or CRC. Smaller code size and faster transfers
than the historical "SMP over console" framing, at the cost of being unable
to share the UART with shell or log output.

Splits the single transport/serial.py into a package so the two
transports share connection management via a final-method base class:

  serial/
    __init__.py    re-exports both transports
    common.py      _SerialTransportBase: pyserial holder, connect/disconnect,
                   SerialException -> SMPTransportDisconnected translation,
                   TX drain, RX polling helper
    encoded.py     SMPSerialTransport (unchanged: base64 + CRC + delimiters,
                   shell interleave via read_serial)
    unencoded.py   SMPSerialRawTransport (new: writes bytes verbatim,
                   reads SMP header then payload, raises on overrun and on
                   header lengths exceeding max_unencoded_size)

The from smpclient.transport.serial import SMPSerialTransport import path
is preserved -- existing users (including MCUboot serial recovery, which
still requires the base64 framing) need no changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment on lines +101 to +105
logger.debug(
f"Failed to connect to {self._conn.port=}: {e}, "
f"retrying in {self._CONNECTION_RETRY_INTERVAL_S} seconds"
)
await asyncio.sleep(self._CONNECTION_RETRY_INTERVAL_S)
Comment on lines +95 to +105
try:
self._conn.open()
self._conn.reset_input_buffer()
logger.debug(f"Connected to {self._conn.port=}")
return
except SerialException as e:
logger.debug(
f"Failed to connect to {self._conn.port=}: {e}, "
f"retrying in {self._CONNECTION_RETRY_INTERVAL_S} seconds"
)
await asyncio.sleep(self._CONNECTION_RETRY_INTERVAL_S)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants