smpclient 4.2.0__tar.gz → 4.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {smpclient-4.2.0 → smpclient-4.3.0}/PKG-INFO +7 -1
  2. {smpclient-4.2.0 → smpclient-4.3.0}/README.md +5 -0
  3. {smpclient-4.2.0 → smpclient-4.3.0}/pyproject.toml +16 -0
  4. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/__init__.py +174 -29
  5. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/generics.py +38 -5
  6. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/transport/__init__.py +29 -6
  7. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/transport/_udp_client.py +13 -4
  8. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/transport/ble.py +5 -3
  9. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/transport/serial.py +23 -20
  10. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/transport/udp.py +6 -1
  11. {smpclient-4.2.0 → smpclient-4.3.0}/LICENSE +0 -0
  12. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/exceptions.py +0 -0
  13. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/extensions/__init__.py +0 -0
  14. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/extensions/intercreate.py +0 -0
  15. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/mcuboot.py +0 -0
  16. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/py.typed +0 -0
  17. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/requests/__init__.py +0 -0
  18. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/requests/file_management.py +0 -0
  19. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/requests/image_management.py +0 -0
  20. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/requests/os_management.py +0 -0
  21. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/requests/settings_management.py +0 -0
  22. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/requests/shell_management.py +0 -0
  23. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/requests/statistics_management.py +0 -0
  24. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/requests/user/__init__.py +0 -0
  25. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/requests/user/intercreate.py +0 -0
  26. {smpclient-4.2.0 → smpclient-4.3.0}/smpclient/requests/zephyr_management.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: smpclient
3
- Version: 4.2.0
3
+ Version: 4.3.0
4
4
  Summary: Simple Management Protocol (SMP) Client for remotely managing MCU firmware
5
5
  Home-page: https://github.com/intercreate/smpclient
6
6
  License: Apache-2.0
@@ -18,6 +18,7 @@ Requires-Dist: bleak (>=0.22.1,<0.23.0)
18
18
  Requires-Dist: intelhex (>=2.3.0,<3.0.0)
19
19
  Requires-Dist: pyserial (>=3.5,<4.0)
20
20
  Requires-Dist: smp (>=3.1.1,<4.0.0)
21
+ Project-URL: Documentation, https://intercreate.github.io/smpclient
21
22
  Project-URL: Repository, https://github.com/intercreate/smpclient
22
23
  Description-Content-Type: text/markdown
23
24
 
@@ -39,6 +40,11 @@ If you'd like an SMP CLI application instead of a library, then you should try
39
40
 
40
41
  `smpclient` is [distributed by PyPI](https://pypi.org/project/smpclient/) and can be installed with `poetry`, `pip`, and other dependency managers.
41
42
 
43
+ ## User Documentation
44
+
45
+ Documentation is in the source code so that it is available to your editor.
46
+ An online version is generated and available [here](https://intercreate.github.io/smpclient/).
47
+
42
48
  ## Development Quickstart
43
49
 
44
50
  > Assumes that you've already [setup your development environment](#development-environment-setup).
@@ -16,6 +16,11 @@ If you'd like an SMP CLI application instead of a library, then you should try
16
16
 
17
17
  `smpclient` is [distributed by PyPI](https://pypi.org/project/smpclient/) and can be installed with `poetry`, `pip`, and other dependency managers.
18
18
 
19
+ ## User Documentation
20
+
21
+ Documentation is in the source code so that it is available to your editor.
22
+ An online version is generated and available [here](https://intercreate.github.io/smpclient/).
23
+
19
24
  ## Development Quickstart
20
25
 
21
26
  > Assumes that you've already [setup your development environment](#development-environment-setup).
@@ -7,6 +7,7 @@ authors = [
7
7
  "J.P. Hutchins <jp@intercreate.io",
8
8
  ]
9
9
  repository = "https://github.com/intercreate/smpclient"
10
+ documentation = "https://intercreate.github.io/smpclient"
10
11
  readme = "README.md"
11
12
  license = "Apache-2.0"
12
13
  packages = [{ include = "smpclient" }]
@@ -36,6 +37,14 @@ mypy-extensions = "^1.0.0"
36
37
  pytest-asyncio = "^0.23.2"
37
38
  types-pyserial = "^3.5.0.11"
38
39
  tox = "^4.15.0"
40
+ pydoclint = "^0.5.8"
41
+
42
+ [tool.poetry.group.doc.dependencies]
43
+ mkdocstrings = { extras = ["python"], version = "^0.26.1" }
44
+ mike = "^2.1.3"
45
+ mkdocs-material = "^9.5.38"
46
+ griffe-inherited-docstrings = "^1.0.1"
47
+ griffe = "^1.3.1"
39
48
 
40
49
  [tool.black]
41
50
  line-length = 100
@@ -52,6 +61,13 @@ skip = [".venv", "dutfirmware", ".tox"]
52
61
  disallow_untyped_defs = true
53
62
  exclude = ['.venv', 'dutfirmware', '.tox']
54
63
 
64
+ [tool.pydoclint]
65
+ style = "google"
66
+ arg-type-hints-in-docstring = false
67
+ allow-init-docstring = true
68
+ check-return-types = false
69
+ check-yield-types = false
70
+
55
71
  [tool.pytest.ini_options]
56
72
  norecursedirs = "dutfirmware/*"
57
73
  filterwarnings = ["ignore:The --rsyncdir:DeprecationWarning"]
@@ -1,4 +1,27 @@
1
- """Simple Management Protocol (SMP) Client."""
1
+ """Simple Management Protocol (SMP) Client.
2
+
3
+ This package implements transport layers for the Simple Management Protocol (SMP).
4
+ The SMP Protocol defines common management operations for MCUs like firmware
5
+ updates, file management, configuration, and statistics retrieval.
6
+
7
+ Additionally, SMP is extensible, allowing for custom commands to be defined to
8
+ meet the specific needs of the product.
9
+
10
+ ### Operating Systems
11
+
12
+ | | Windows 11 | Ubuntu | macOS (Arm/x86) |
13
+ |---|------------|-------|-------|
14
+ | Serial (UART, USB, CAN, ...) | ✅ | ✅ | ✅ |
15
+ | Bluetooth Low Energy (BLE) | ✅ | ✅ | ✅ |
16
+ | UDP (Ethernet, Wi-Fi) | ✅ | ✅ | ✅ |
17
+
18
+ ### Examples
19
+
20
+ Many usage examples are available on
21
+ [GitHub](https://github.com/intercreate/smpclient/tree/main/examples)
22
+ or in your local clone at `examples/`.
23
+
24
+ """
2
25
 
3
26
  from __future__ import annotations
4
27
 
@@ -6,7 +29,7 @@ import asyncio
6
29
  import logging
7
30
  from hashlib import sha256
8
31
  from types import TracebackType
9
- from typing import AsyncIterator, Final, Tuple, Type
32
+ from typing import AsyncIterator, Final, NoReturn, Tuple, Type
10
33
 
11
34
  from pydantic import ValidationError
12
35
  from smp import header as smpheader
@@ -28,13 +51,51 @@ logger = logging.getLogger(__name__)
28
51
 
29
52
 
30
53
  class SMPClient:
31
- def __init__(self, transport: SMPTransport, address: str):
32
- """Create a client to the SMP server `address`, using `transport`."""
54
+ """Create a client to the SMP server `address`, using `transport`.
55
+
56
+ This class provides a high-level interface to an SMP server. Other than
57
+ the `request` method, all methods are abstractions of common SMP routines,
58
+ such as uploading a FW image or downloading a file.
59
+
60
+ The `request` method is used to send an SMP request to the server and return
61
+ the response or error.
62
+
63
+ Args:
64
+ transport: the `SMPTransport` to use
65
+ address: the address of the SMP server, see `smpclient.transport` for details
66
+
67
+ Example:
68
+
69
+ ```python
70
+ import asyncio
71
+ from smpclient import SMPClient
72
+ from smpclient.requests.os_management import EchoWrite
73
+ from smpclient.transport.ble import SMPBLETransport
74
+
75
+ async def main():
76
+ async with SMPClient(SMPBLETransport(), "00:11:22:33:44:55") as client:
77
+ response = await client.request(EchoWrite(d="Hello, World!"))
78
+
79
+ if success(response):
80
+ print(f"Response: {response=}")
81
+ elif error(response):
82
+ print(f"Error: {response=}")
83
+
84
+ if __name__ == "__main__":
85
+ asyncio.run(main())
86
+ ```
87
+ """
88
+
89
+ def __init__(self, transport: SMPTransport, address: str): # noqa: DOC301
33
90
  self._transport: Final = transport
34
91
  self._address: Final = address
35
92
 
36
93
  async def connect(self, timeout_s: float = 5.0) -> None:
37
- """Connect to the SMP server."""
94
+ """Connect to the SMP server.
95
+
96
+ Args:
97
+ timeout_s: the timeout for the connection attempt in seconds
98
+ """
38
99
  await self._transport.connect(self._address, timeout_s)
39
100
  await self._initialize()
40
101
 
@@ -45,7 +106,53 @@ class SMPClient:
45
106
  async def request(
46
107
  self, request: SMPRequest[TRep, TEr1, TEr2], timeout_s: float = 120.000
47
108
  ) -> TRep | TEr1 | TEr2:
48
- """Make an `SMPRequest` to the SMP server."""
109
+ """Make an `SMPRequest` to the SMP server and return the Response or Error.
110
+
111
+ Args:
112
+ request: the `SMPRequest` to send
113
+ timeout_s: the timeout for the request in seconds
114
+
115
+ Returns:
116
+ The typed and validated Response or Error
117
+
118
+ Raises:
119
+ TimeoutError: if the request times out
120
+ SMPBadSequence: if the response sequence does not match the request sequence
121
+ ValidationError: if the response cannot be parsed as a Response or Error
122
+
123
+ Examples:
124
+
125
+ Usage:
126
+
127
+ ```python
128
+ response = await client.request(EchoWrite(d="Hello, World!"))
129
+ if success(response):
130
+ print(f"Response: {response=}")
131
+ elif error(response):
132
+ print(f"Error: {response=}")
133
+ ```
134
+
135
+ Type Safety with Generic Typing:
136
+
137
+ ```python
138
+ response = await client.request(EchoWrite(d="Hello, World!"))
139
+ reveal_type(response)
140
+ # Revealed type is 'Union[EchoWriteResponse, EchoWriteErrorV1, EchoWriteErrorV2]'
141
+ if success(response):
142
+ reveal_type(response)
143
+ # Revealed type is 'EchoWriteResponse'
144
+ elif error(response):
145
+ reveal_type(response)
146
+ # Revealed type is 'Union[EchoWriteErrorV1, EchoWriteErrorV2]'
147
+ if error_v1(response):
148
+ reveal_type(response)
149
+ # Revealed type is 'EchoWriteErrorV1'
150
+ else:
151
+ reveal_type(response)
152
+ # Revealed type is 'EchoWriteErrorV2'
153
+ ```
154
+
155
+ """
49
156
 
50
157
  try:
51
158
  async with timeout(timeout_s):
@@ -91,22 +198,29 @@ class SMPClient:
91
198
  ) -> AsyncIterator[int]:
92
199
  """Iteratively upload an `image` to `slot`, yielding the offset.
93
200
 
94
- Parameters:
95
- - `image`: the `bytes` to upload
96
- - `slot`: the slot to upload to (0 for default)
97
- - `upgrade`: `True` to mark the image as confirmed. This is unsafe and
98
- can cause a boot-loop that could brick the device. This setting
99
- should be left at the default `False` and the image should be
100
- confirmed from within the upgraded application. Zephyr provides
101
- [boot_write_img_confirmed()](https://docs.zephyrproject.org/apidoc/latest/group__mcuboot__api.html#ga95ccc9e1c7460fec16b9ce9ac8ad7a72)
102
- for this purpose.
103
- - `first_timeout_s`: the timeout for the first `ImageUploadWrite` request
104
- - `subsequent_timeout_s`: the timeout for subsequent `ImageUploadWrite` requests
105
- - `use_sha`: `True` to include the SHA256 hash of the image in the first
106
- packet.
107
- - Zephyr's SMP server will fail with `MGMT_ERR.EINVAL` if the
108
- MTU is too small to include both the SHA256 and the first 32-bytes
109
- of the image. Increase the MTU or set `use_sha=False` in this case.
201
+ Args:
202
+ image: the `bytes` to upload
203
+ slot: the slot to upload to (0 for default)
204
+ upgrade: `True` to mark the image as confirmed. This is unsafe and
205
+ can cause a boot-loop that could brick the device. This setting
206
+ should be left at the default `False` and the image should be
207
+ confirmed from within the upgraded application. Zephyr provides
208
+ [boot_write_img_confirmed()](https://docs.zephyrproject.org/apidoc/latest/group__mcuboot__api.html#ga95ccc9e1c7460fec16b9ce9ac8ad7a72)
209
+ for this purpose.
210
+ first_timeout_s: the timeout for the first `ImageUploadWrite` request
211
+ subsequent_timeout_s: the timeout for subsequent `ImageUploadWrite` requests
212
+ use_sha: `True` to include the SHA256 hash of the image in the first
213
+ packet.
214
+
215
+ Zephyr's SMP server will fail with `MGMT_ERR.EINVAL` if the
216
+ MTU is too small to include both the SHA256 and the first 32-bytes
217
+ of the image. Increase the MTU or set `use_sha=False` in this case.
218
+
219
+ Yields:
220
+ the offset of the image upload
221
+
222
+ Raises:
223
+ SMPUploadError: if the upload routine fails
110
224
  """
111
225
 
112
226
  response = await self.request(
@@ -131,7 +245,7 @@ class SMPClient:
131
245
  raise SMPUploadError(f"No offset received: {response=}")
132
246
  yield response.off
133
247
  else: # pragma: no cover
134
- raise Exception("Unreachable")
248
+ _unreachable()
135
249
 
136
250
  # send chunks until the SMP server reports that the offset is at the end of the image
137
251
  while response.off != len(image):
@@ -155,7 +269,7 @@ class SMPClient:
155
269
  raise SMPUploadError(f"No offset received: {response=}")
156
270
  yield response.off
157
271
  else: # pragma: no cover
158
- raise Exception("Unreachable")
272
+ _unreachable()
159
273
 
160
274
  logger.info("Upload complete")
161
275
 
@@ -172,6 +286,19 @@ class SMPClient:
172
286
  file_path: str,
173
287
  timeout_s: float = 2.500,
174
288
  ) -> AsyncIterator[int]:
289
+ """Iteratively upload a `file_data` to `file_path`, yielding the offset.
290
+
291
+ Args:
292
+ file_data: the `bytes` to upload
293
+ file_path: the path to upload to
294
+ timeout_s: the timeout for each `FileUpload` request
295
+
296
+ Yields:
297
+ int: the offset of the file upload
298
+
299
+ Raises:
300
+ SMPUploadError: if the upload routine fails
301
+ """
175
302
  response = await self.request(
176
303
  self._maximize_file_upload_packet(
177
304
  FileUpload(name=file_path, off=0, data=b"", len=len(file_data)),
@@ -187,7 +314,7 @@ class SMPClient:
187
314
  raise SMPUploadError(f"No offset received: {response=}")
188
315
  yield response.off
189
316
  else: # pragma: no cover
190
- raise Exception("Unreachable")
317
+ _unreachable()
191
318
 
192
319
  # send chunks until the SMP server reports that the offset is at the end of the image
193
320
  while response.off != len(file_data):
@@ -202,7 +329,7 @@ class SMPClient:
202
329
  elif success(response):
203
330
  yield response.off
204
331
  else: # pragma: no cover
205
- raise Exception("Unreachable")
332
+ _unreachable()
206
333
 
207
334
  logger.info("Upload complete")
208
335
 
@@ -211,6 +338,18 @@ class SMPClient:
211
338
  file_path: str,
212
339
  timeout_s: float = 2.500,
213
340
  ) -> bytes:
341
+ """Download a file from the SMP server.
342
+
343
+ Args:
344
+ file_path: the path to download
345
+ timeout_s: the timeout for each `FileDownload` request
346
+
347
+ Returns:
348
+ The downloaded file as `bytes`
349
+
350
+ Raises:
351
+ SMPUploadError: if the download routine fails
352
+ """
214
353
  response = await self.request(FileDownload(off=0, name=file_path), timeout_s=timeout_s)
215
354
  file_length = 0
216
355
 
@@ -221,7 +360,7 @@ class SMPClient:
221
360
  raise SMPUploadError(f"No length received: {response=}")
222
361
  file_length = response.len
223
362
  else: # pragma: no cover
224
- raise Exception("Unreachable")
363
+ _unreachable()
225
364
 
226
365
  file_data = response.data
227
366
 
@@ -236,13 +375,15 @@ class SMPClient:
236
375
  elif success(response):
237
376
  file_data += response.data
238
377
  else: # pragma: no cover
239
- raise Exception("Unreachable")
378
+ _unreachable()
240
379
 
241
380
  logger.info("Download complete")
242
381
  return file_data
243
382
 
244
383
  @property
245
384
  def address(self) -> str:
385
+ """The SMP server address."""
386
+
246
387
  return self._address
247
388
 
248
389
  async def __aenter__(self) -> "SMPClient":
@@ -353,6 +494,10 @@ class SMPClient:
353
494
  elif error(mcumgr_parameters):
354
495
  logger.warning(f"Error reading MCUMgr parameters: {mcumgr_parameters}")
355
496
  else:
356
- raise Exception("Unreachable")
497
+ _unreachable()
357
498
  except asyncio.TimeoutError:
358
499
  logger.warning("Timeout waiting for MCUMgr parameters")
500
+
501
+
502
+ def _unreachable() -> NoReturn:
503
+ raise Exception("Unreachable")
@@ -1,4 +1,4 @@
1
- """Some Generic helpers for the SMP module."""
1
+ """Generics and TypeGuards for SMP Requests and Responses."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -10,8 +10,13 @@ from smp import message as smpmessage
10
10
  from typing_extensions import TypeGuard
11
11
 
12
12
  TEr1 = TypeVar("TEr1", bound=smperror.ErrorV1)
13
+ """Type of SMP Error V1."""
14
+
13
15
  TEr2 = TypeVar("TEr2", bound=smperror.ErrorV2)
16
+ """Type of SMP Error V2."""
17
+
14
18
  TRep = TypeVar("TRep", bound=Union[smpmessage.ReadResponse, smpmessage.WriteResponse])
19
+ """Type of successful SMP Response (ReadResponse or WriteResponse)."""
15
20
 
16
21
 
17
22
  class SMPRequest(Protocol[TRep, TEr1, TEr2]):
@@ -43,20 +48,48 @@ class SMPRequest(Protocol[TRep, TEr1, TEr2]):
43
48
 
44
49
 
45
50
  def error_v1(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeGuard[smperror.ErrorV1]:
46
- """`TypeGuard` that returns `True` if the `response` is an `ErrorV1`."""
51
+ """`TypeGuard` that returns `True` if the `response` is an `ErrorV1`.
52
+
53
+ Args:
54
+ response: The response to check.
55
+
56
+ Returns:
57
+ `True` if the `response` is an `ErrorV1`.
58
+ """
47
59
  return response.RESPONSE_TYPE == smpmessage.ResponseType.ERROR_V1
48
60
 
49
61
 
50
62
  def error_v2(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeGuard[TEr2]:
51
- """`TypeGuard` that returns `True` if the `response` is an `ErrorV2`."""
63
+ """`TypeGuard` that returns `True` if the `response` is an `ErrorV2`.
64
+
65
+ Args:
66
+ response: The response to check.
67
+
68
+ Returns:
69
+ `True` if the `response` is an `ErrorV2`.
70
+ """
52
71
  return response.RESPONSE_TYPE == smpmessage.ResponseType.ERROR_V2
53
72
 
54
73
 
55
74
  def error(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeGuard[smperror.ErrorV1 | TEr2]:
56
- """`TypeGuard` that returns `True` if the `response` is an `ErrorV1` or `ErrorV2`."""
75
+ """`TypeGuard` that returns `True` if the `response` is an `ErrorV1` or `ErrorV2`.
76
+
77
+ Args:
78
+ response: The response to check.
79
+
80
+ Returns:
81
+ `True` if the `response` is an `ErrorV1` or `ErrorV2`.
82
+ """
57
83
  return error_v1(response) or error_v2(response)
58
84
 
59
85
 
60
86
  def success(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeGuard[TRep]:
61
- """`TypeGuard` that returns `True` if the `response` is a successful `Response`."""
87
+ """`TypeGuard` that returns `True` if the `response` is a successful `Response`.
88
+
89
+ Args:
90
+ response: The response to check.
91
+
92
+ Returns:
93
+ `True` if the `response` is a successful `Response`.
94
+ """
62
95
  return response.RESPONSE_TYPE == smpmessage.ResponseType.SUCCESS
@@ -6,7 +6,7 @@ from typing import Protocol
6
6
 
7
7
 
8
8
  class SMPTransportDisconnected(Exception):
9
- ...
9
+ """Raised when the SMP transport is disconnected."""
10
10
 
11
11
 
12
12
  class SMPTransport(Protocol):
@@ -14,22 +14,45 @@ class SMPTransport(Protocol):
14
14
  """The SMP server transport buffer size, in 8-bit bytes."""
15
15
 
16
16
  async def connect(self, address: str, timeout_s: float) -> None: # pragma: no cover
17
- """Connect the `SMPTransport`."""
17
+ """Connect the `SMPTransport`.
18
+
19
+ Args:
20
+ address: The SMP server address.
21
+ timeout_s: The connection timeout in seconds."""
18
22
 
19
23
  async def disconnect(self) -> None: # pragma: no cover
20
24
  """Disconnect the `SMPTransport`."""
21
25
 
22
26
  async def send(self, data: bytes) -> None: # pragma: no cover
23
- """Send the encoded `SMPRequest` `data`."""
27
+ """Send the encoded `SMPRequest` `data`.
28
+
29
+ Args:
30
+ data: The encoded `SMPRequest`.
31
+ """
24
32
 
25
33
  async def receive(self) -> bytes: # pragma: no cover
26
- """Receive the decoded `SMPResponse`."""
34
+ """Receive the decoded `SMPResponse` data.
35
+
36
+ Returns:
37
+ The `SMPResponse` bytes.
38
+ """
27
39
 
28
40
  async def send_and_receive(self, data: bytes) -> bytes: # pragma: no cover
29
- """Send the encoded `SMPRequest` `data` and receive the decoded `SMPResponse`."""
41
+ """Send the encoded `SMPRequest` `data` and receive the decoded `SMPResponse`.
42
+
43
+ Args:
44
+ data: The encoded `SMPRequest`.
45
+
46
+ Returns:
47
+ The `SMPResponse` bytes.
48
+ """
30
49
 
31
50
  def initialize(self, smp_server_transport_buffer_size: int) -> None: # pragma: no cover
32
- """Initialize the `SMPTransport` with the server transport buffer size."""
51
+ """Initialize the `SMPTransport` with the server transport buffer size.
52
+
53
+ Args:
54
+ smp_server_transport_buffer_size: The SMP server transport buffer size, in 8-bit bytes.
55
+ """
33
56
  self._smp_server_transport_buffer_size = smp_server_transport_buffer_size
34
57
 
35
58
  @property
@@ -22,9 +22,11 @@ class UDPClient:
22
22
  async def connect(self, remote_addr: Addr, _local_addr: Addr | None = None) -> None:
23
23
  """Create a UDP connection to the given `Addr`.
24
24
 
25
- Parameters:
26
- - remote_addr: The remote address to connect to.
27
- - _local_addr: For unit tests only! The local address to connect from.
25
+ Args:
26
+ remote_addr: The remote address to connect to.
27
+ _local_addr: For unit tests only! The local address to connect from.
28
+
29
+ Example:
28
30
 
29
31
  ```python
30
32
 
@@ -44,12 +46,19 @@ class UDPClient:
44
46
 
45
47
  This does not block; it buffers the data and arranges for it to be sent
46
48
  out asynchronously.
49
+
50
+ Args:
51
+ data: The data to send.
47
52
  """
48
53
 
49
54
  self._transport.sendto(data)
50
55
 
51
56
  async def receive(self) -> bytes:
52
- """Receive data from the transport."""
57
+ """Receive data from the transport.
58
+
59
+ Returns:
60
+ bytes: The data received
61
+ """
53
62
 
54
63
  return await self._protocol.receive_queue.get()
55
64
 
@@ -51,21 +51,23 @@ UUID_PATTERN: Final = re.compile(
51
51
 
52
52
 
53
53
  class SMPBLETransportException(SMPClientException):
54
- ...
54
+ """Base class for SMP BLE transport exceptions."""
55
55
 
56
56
 
57
57
  class SMPBLETransportDeviceNotFound(SMPBLETransportException):
58
- ...
58
+ """Raised when a BLE device is not found."""
59
59
 
60
60
 
61
61
  class SMPBLETransportNotSMPServer(SMPBLETransportException):
62
- ...
62
+ """Raised when the SMP characteristic UUID is not found."""
63
63
 
64
64
 
65
65
  logger = logging.getLogger(__name__)
66
66
 
67
67
 
68
68
  class SMPBLETransport(SMPTransport):
69
+ """A Bluetooth Low Energy (BLE) SMPTransport."""
70
+
69
71
  def __init__(self) -> None:
70
72
  self._buffer = bytearray()
71
73
  self._notify_condition = asyncio.Condition()
@@ -1,4 +1,7 @@
1
- """A serial SMPTransport."""
1
+ """A serial SMPTransport.
2
+
3
+ In addition to UART, this transport can be used with USB CDC ACM and CAN.
4
+ """
2
5
 
3
6
  from __future__ import annotations
4
7
 
@@ -66,7 +69,7 @@ class SMPSerialTransport(SMPTransport):
66
69
  self.state = SMPSerialTransport._ReadBuffer.State.SER
67
70
  """The state of the read buffer."""
68
71
 
69
- def __init__(
72
+ def __init__( # noqa: DOC301
70
73
  self,
71
74
  max_smp_encoded_frame_size: int = 256,
72
75
  line_length: int = 128,
@@ -85,24 +88,24 @@ class SMPSerialTransport(SMPTransport):
85
88
  ) -> None:
86
89
  """Initialize the serial transport.
87
90
 
88
- Parameters:
89
- - `max_smp_encoded_frame_size`: The maximum size of an encoded SMP
90
- frame. The SMP server needs to have a buffer large enough to
91
- receive the encoded frame packets and to store the decoded frame.
92
- - `line_length`: The maximum SMP packet size.
93
- - `line_buffers`: The number of line buffers in the serial buffer.
94
- - `baudrate`: The baudrate of the serial connection. OK to ignore for
95
- USB CDC ACM.
96
- - `bytesize`: The number of data bits.
97
- - `parity`: The parity setting.
98
- - `stopbits`: The number of stop bits.
99
- - `timeout`: The read timeout.
100
- - `xonxoff`: Enable software flow control.
101
- - `rtscts`: Enable hardware (RTS/CTS) flow control.
102
- - `write_timeout`: The write timeout.
103
- - `dsrdtr`: Enable hardware (DSR/DTR) flow control.
104
- - `inter_byte_timeout`: The inter-byte timeout.
105
- - `exclusive`: The exclusive access timeout.
91
+ 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.
97
+ baudrate: The baudrate of the serial connection. OK to ignore for
98
+ USB CDC ACM.
99
+ bytesize: The number of data bits.
100
+ parity: The parity setting.
101
+ stopbits: The number of stop bits.
102
+ timeout: The read timeout.
103
+ xonxoff: Enable software flow control.
104
+ rtscts: Enable hardware (RTS/CTS) flow control.
105
+ write_timeout: The write timeout.
106
+ dsrdtr: Enable hardware (DSR/DTR) flow control.
107
+ inter_byte_timeout: The inter-byte timeout.
108
+ exclusive: The exclusive access timeout.
106
109
 
107
110
  """
108
111
  if max_smp_encoded_frame_size < line_length * line_buffers:
@@ -1,4 +1,4 @@
1
- """A UDP SMPTransport."""
1
+ """A UDP SMPTransport for Network connections like Wi-Fi or Ethernet."""
2
2
 
3
3
  import asyncio
4
4
  import logging
@@ -16,6 +16,11 @@ logger = logging.getLogger(__name__)
16
16
 
17
17
  class SMPUDPTransport(SMPTransport):
18
18
  def __init__(self, mtu: int = 1500) -> None:
19
+ """Initialize the SMP UDP transport.
20
+
21
+ Args:
22
+ mtu: The Maximum Transmission Unit (MTU) in 8-bit bytes.
23
+ """
19
24
  self._mtu = mtu
20
25
 
21
26
  self._client: Final = UDPClient()
File without changes
File without changes