1010import math
1111import time
1212from enum import IntEnum , unique
13- from functools import cached_property
14- from typing import Final
13+ from typing import Final , NamedTuple
1514
1615from serial import Serial , SerialException
1716from smp import packet as smppacket
@@ -69,11 +68,29 @@ def __init__(self) -> None:
6968 self .state = SMPSerialTransport ._ReadBuffer .State .SER
7069 """The state of the read buffer."""
7170
71+ class Auto (NamedTuple ):
72+ """Automatically determine buffer parameters from the SMP server.
73+
74+ On connect, queries the server's MCUMGR_PARAM for `buf_size`
75+ (CONFIG_MCUMGR_TRANSPORT_NETBUF_SIZE) and calculates:
76+ - line_length: 127 (standard MTU for uart/usb/shell)
77+ - line_buffers: buf_size / line_length
78+
79+ Falls back to BufferParams() if server doesn't support MCUMGR_PARAM.
80+ """
81+
82+ class BufferParams (NamedTuple ):
83+ """Buffer parameters for the serial transport."""
84+
85+ line_length : int = 127
86+ """The maximum SMP packet size."""
87+
88+ line_buffers : int = 1
89+ """The number of line buffers in the serial buffer."""
90+
7291 def __init__ ( # noqa: DOC301
7392 self ,
74- max_smp_encoded_frame_size : int = 256 ,
75- line_length : int = 128 ,
76- line_buffers : int = 2 ,
93+ fragmentation_strategy : Auto | BufferParams = Auto (),
7794 baudrate : int = 115200 ,
7895 bytesize : int = 8 ,
7996 parity : str = "N" ,
@@ -89,11 +106,10 @@ def __init__( # noqa: DOC301
89106 """Initialize the serial transport.
90107
91108 Args:
92- max_smp_encoded_frame_size: The maximum size of an encoded SMP
93- frame. The SMP server needs to have a buffer large enough to
94- receive the encoded frame packets and to store the decoded frame.
95- line_length: The maximum SMP packet size.
96- line_buffers: The number of line buffers in the serial buffer.
109+ fragmentation_strategy: The fragmentation strategy to use. Either
110+ `SMPSerialTransport.Auto()` to automatically determine buffer
111+ parameters from the SMP server, or `SMPSerialTransport.BufferParams`
112+ to manually specify buffer parameters.
97113 baudrate: The baudrate of the serial connection. OK to ignore for
98114 USB CDC ACM.
99115 bytesize: The number of data bits.
@@ -108,18 +124,7 @@ def __init__( # noqa: DOC301
108124 exclusive: The exclusive access timeout.
109125
110126 """
111- if max_smp_encoded_frame_size < line_length * line_buffers :
112- logger .error (
113- f"{ max_smp_encoded_frame_size = } is less than { line_length = } * { line_buffers = } !"
114- )
115- elif max_smp_encoded_frame_size != line_length * line_buffers :
116- logger .warning (
117- f"{ max_smp_encoded_frame_size = } is not equal to { line_length = } * { line_buffers = } !"
118- )
119-
120- self ._max_smp_encoded_frame_size : Final = max_smp_encoded_frame_size
121- self ._line_length : Final = line_length
122- self ._line_buffers : Final = line_buffers
127+ self ._fragmentation_strategy : Final = fragmentation_strategy
123128 self ._conn : Final = Serial (
124129 baudrate = baudrate ,
125130 bytesize = bytesize ,
@@ -136,6 +141,62 @@ def __init__( # noqa: DOC301
136141 self ._buffer = SMPSerialTransport ._ReadBuffer ()
137142 logger .debug (f"Initialized { self .__class__ .__name__ } " )
138143
144+ @property
145+ def _line_length (self ) -> int :
146+ """The maximum SMP packet size."""
147+ if isinstance (self ._fragmentation_strategy , SMPSerialTransport .Auto ):
148+ return self .BufferParams ().line_length
149+ else :
150+ return self ._fragmentation_strategy .line_length
151+
152+ @property
153+ def _line_buffers (self ) -> int :
154+ """The number of line buffers."""
155+ if isinstance (self ._fragmentation_strategy , SMPSerialTransport .Auto ):
156+ if self ._smp_server_transport_buffer_size is not None :
157+ return self ._smp_server_transport_buffer_size // self .BufferParams ().line_length
158+ return self .BufferParams ().line_buffers
159+ else :
160+ return self ._fragmentation_strategy .line_buffers
161+
162+ @property
163+ def _max_smp_encoded_frame_size (self ) -> int :
164+ """The maximum encoded frame size (line_length * line_buffers)."""
165+ if isinstance (self ._fragmentation_strategy , SMPSerialTransport .Auto ):
166+ if self ._smp_server_transport_buffer_size is not None :
167+ return self ._smp_server_transport_buffer_size
168+ return self ._line_length * self ._line_buffers
169+ else :
170+ return (
171+ self ._fragmentation_strategy .line_length * self ._fragmentation_strategy .line_buffers
172+ )
173+
174+ @override
175+ def initialize (self , smp_server_transport_buffer_size : int ) -> None :
176+ """Initialize with the server's buffer size from MCUMGR_PARAM.
177+
178+ Args:
179+ smp_server_transport_buffer_size: The server's CONFIG_MCUMGR_TRANSPORT_NETBUF_SIZE
180+ """
181+ super ().initialize (smp_server_transport_buffer_size )
182+
183+ if isinstance (self ._fragmentation_strategy , SMPSerialTransport .Auto ):
184+ logger .info (
185+ f"Auto-configured from server: { self ._line_length = } , "
186+ f"{ self ._line_buffers = } , mtu={ self ._max_smp_encoded_frame_size } "
187+ )
188+ else :
189+ # Validate user's BufferParams against server capabilities
190+ calculated_size = (
191+ self ._fragmentation_strategy .line_length * self ._fragmentation_strategy .line_buffers
192+ )
193+ if calculated_size > smp_server_transport_buffer_size :
194+ logger .warning (
195+ f"BufferParams (line_length={ self ._fragmentation_strategy .line_length } * "
196+ f"line_buffers={ self ._fragmentation_strategy .line_buffers } = { calculated_size } ) " # noqa: E501
197+ f"exceeds server buffer size ({ smp_server_transport_buffer_size } )"
198+ )
199+
139200 @override
140201 async def connect (self , address : str , timeout_s : float ) -> None :
141202 self ._conn .port = address
@@ -309,7 +370,7 @@ def mtu(self) -> int:
309370 return self ._max_smp_encoded_frame_size
310371
311372 @override
312- @cached_property
373+ @property
313374 def max_unencoded_size (self ) -> int :
314375 """The serial transport encodes each packet instead of sending SMP messages as raw bytes."""
315376
0 commit comments