pamoja-serial 0.1.18__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.
- pamoja_serial-0.1.18/.gitignore +13 -0
- pamoja_serial-0.1.18/LICENSE-MIT +21 -0
- pamoja_serial-0.1.18/PKG-INFO +101 -0
- pamoja_serial-0.1.18/README.md +83 -0
- pamoja_serial-0.1.18/pamoja/serial/__init__.py +187 -0
- pamoja_serial-0.1.18/pamoja/serial/py.typed +0 -0
- pamoja_serial-0.1.18/pyproject.toml +30 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anthony Wiedman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pamoja-serial
|
|
3
|
+
Version: 0.1.18
|
|
4
|
+
Summary: SLIP and COBS byte stuffing with streaming decoders, so a UART byte stream carries discrete packets.
|
|
5
|
+
Project-URL: Repository, https://github.com/molexxxx/pamoja
|
|
6
|
+
Project-URL: Documentation, https://pamoja.molex.cloud/docs/guides/serial.html
|
|
7
|
+
Author: molexxxx
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE-MIT
|
|
10
|
+
Keywords: iot,pamoja,robotics,serial
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: pamoja-native==0.1.18
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# pamoja-serial
|
|
20
|
+
|
|
21
|
+
SLIP and COBS byte stuffing with streaming decoders, so a UART byte stream carries discrete packets. One capability of [pamoja](https://github.com/molexxxx/pamoja), one memory-safe Rust core with bindings for TypeScript, Python, and C#.
|
|
22
|
+
|
|
23
|
+
[](https://pamoja.molex.cloud/docs/guides/serial.html)
|
|
24
|
+
[](https://pamoja.molex.cloud/docs/)
|
|
25
|
+
[](https://pamoja.molex.cloud/docs/reference/python/pamoja/serial.html)
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
pip install pamoja-serial
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from pamoja import serial
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This pulls in `pamoja-native`, the compiled engine. `pip install pamoja` is the whole framework in one package.
|
|
38
|
+
|
|
39
|
+
## Example
|
|
40
|
+
|
|
41
|
+
The script the test suite runs, spliced here as it ran.
|
|
42
|
+
|
|
43
|
+
From [`bindings/python/guides/serial.py`](https://github.com/molexxxx/pamoja/blob/main/bindings/python/guides/serial.py):
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from pamoja.serial import COBS_DELIMITER, SLIP_END, SLIP_ESC, SlipDecoder, cobs, slip
|
|
47
|
+
|
|
48
|
+
# A UART carries bytes, not packets, so a framing has to mark where one packet ends. SLIP
|
|
49
|
+
# reserves two byte values for that, and the package names both: the end byte closes a
|
|
50
|
+
# frame, the escape byte carries a value that would otherwise look like one.
|
|
51
|
+
payload = b"lvl=" + bytes([SLIP_END, SLIP_ESC])
|
|
52
|
+
framed = slip.encode(payload)
|
|
53
|
+
print(f"slip {len(payload)} payload bytes framed as {len(framed)}")
|
|
54
|
+
|
|
55
|
+
# Decoding gives the payload back unchanged, reserved bytes and all.
|
|
56
|
+
restored = slip.decode(framed)
|
|
57
|
+
print(f"slip decoded back to {len(restored)} bytes")
|
|
58
|
+
|
|
59
|
+
# COBS trades that escaping for one code byte per run of up to 254 non-zero bytes, each
|
|
60
|
+
# run led by its own length, so a frame never grows by more than a byte per 254. Zero is
|
|
61
|
+
# the delimiter, and never appears inside a frame.
|
|
62
|
+
packet = b"lvl=" + bytes([COBS_DELIMITER]) + b"7"
|
|
63
|
+
cobs_framed = cobs.encode(packet)
|
|
64
|
+
print(f"cobs {len(packet)} payload bytes framed as {len(cobs_framed)}")
|
|
65
|
+
|
|
66
|
+
# A read from a port returns whatever arrived, which is rarely one whole frame. This chunk
|
|
67
|
+
# holds two good frames with a truncated one between them; the decoder hands over the good
|
|
68
|
+
# ones and discards only the bad frame.
|
|
69
|
+
decoder = SlipDecoder()
|
|
70
|
+
chunk = (
|
|
71
|
+
b"ok"
|
|
72
|
+
+ bytes([SLIP_END])
|
|
73
|
+
+ bytes([SLIP_ESC]) # a frame that ends before its escape pair completes
|
|
74
|
+
+ bytes([SLIP_END])
|
|
75
|
+
+ b"go"
|
|
76
|
+
+ bytes([SLIP_END])
|
|
77
|
+
)
|
|
78
|
+
frames = decoder.feed(chunk)
|
|
79
|
+
for frame in frames:
|
|
80
|
+
print(f"received {frame.decode()}")
|
|
81
|
+
print(f"discarded {decoder.discarded} frame the stream mangled")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## The same capability in every language
|
|
85
|
+
|
|
86
|
+
| Language | Package | Reference |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| Rust | [`pamoja-serial`](https://crates.io/crates/pamoja-serial) | [reference](https://pamoja.molex.cloud/docs/reference/rust/pamoja_serial/index.html), [docs.rs](https://docs.rs/pamoja-serial), [install](https://pamoja.molex.cloud/docs/reference/rust.html#rust-serial) |
|
|
89
|
+
| TypeScript | [`@pamoja/serial`](https://www.npmjs.com/package/@pamoja/serial) | [reference](https://pamoja.molex.cloud/docs/reference/node/modules/_pamoja_serial.html), [install](https://pamoja.molex.cloud/docs/reference/node.html#node-serial) |
|
|
90
|
+
| Python | [`pamoja-serial`](https://pypi.org/project/pamoja-serial/) | [reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/serial.html), [install](https://pamoja.molex.cloud/docs/reference/python.html#python-serial) |
|
|
91
|
+
| C# | [`Pamoja.Serial`](https://www.nuget.org/packages/Pamoja.Serial) | [reference](https://pamoja.molex.cloud/docs/reference/dotnet/api/Pamoja.Serial.html), [install](https://pamoja.molex.cloud/docs/reference/dotnet.html#dotnet-serial) |
|
|
92
|
+
|
|
93
|
+
## Documentation
|
|
94
|
+
|
|
95
|
+
- [`pamoja.serial` reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/serial.html), every class and function in this module.
|
|
96
|
+
- [The Serial framing guide](https://pamoja.molex.cloud/docs/guides/serial.html), with the same example in Rust, TypeScript, and C#.
|
|
97
|
+
- [Every capability](https://pamoja.molex.cloud/docs/), and the [install page](https://pamoja.molex.cloud/docs/install.html).
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# pamoja-serial
|
|
2
|
+
|
|
3
|
+
SLIP and COBS byte stuffing with streaming decoders, so a UART byte stream carries discrete packets. One capability of [pamoja](https://github.com/molexxxx/pamoja), one memory-safe Rust core with bindings for TypeScript, Python, and C#.
|
|
4
|
+
|
|
5
|
+
[](https://pamoja.molex.cloud/docs/guides/serial.html)
|
|
6
|
+
[](https://pamoja.molex.cloud/docs/)
|
|
7
|
+
[](https://pamoja.molex.cloud/docs/reference/python/pamoja/serial.html)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pip install pamoja-serial
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from pamoja import serial
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This pulls in `pamoja-native`, the compiled engine. `pip install pamoja` is the whole framework in one package.
|
|
20
|
+
|
|
21
|
+
## Example
|
|
22
|
+
|
|
23
|
+
The script the test suite runs, spliced here as it ran.
|
|
24
|
+
|
|
25
|
+
From [`bindings/python/guides/serial.py`](https://github.com/molexxxx/pamoja/blob/main/bindings/python/guides/serial.py):
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from pamoja.serial import COBS_DELIMITER, SLIP_END, SLIP_ESC, SlipDecoder, cobs, slip
|
|
29
|
+
|
|
30
|
+
# A UART carries bytes, not packets, so a framing has to mark where one packet ends. SLIP
|
|
31
|
+
# reserves two byte values for that, and the package names both: the end byte closes a
|
|
32
|
+
# frame, the escape byte carries a value that would otherwise look like one.
|
|
33
|
+
payload = b"lvl=" + bytes([SLIP_END, SLIP_ESC])
|
|
34
|
+
framed = slip.encode(payload)
|
|
35
|
+
print(f"slip {len(payload)} payload bytes framed as {len(framed)}")
|
|
36
|
+
|
|
37
|
+
# Decoding gives the payload back unchanged, reserved bytes and all.
|
|
38
|
+
restored = slip.decode(framed)
|
|
39
|
+
print(f"slip decoded back to {len(restored)} bytes")
|
|
40
|
+
|
|
41
|
+
# COBS trades that escaping for one code byte per run of up to 254 non-zero bytes, each
|
|
42
|
+
# run led by its own length, so a frame never grows by more than a byte per 254. Zero is
|
|
43
|
+
# the delimiter, and never appears inside a frame.
|
|
44
|
+
packet = b"lvl=" + bytes([COBS_DELIMITER]) + b"7"
|
|
45
|
+
cobs_framed = cobs.encode(packet)
|
|
46
|
+
print(f"cobs {len(packet)} payload bytes framed as {len(cobs_framed)}")
|
|
47
|
+
|
|
48
|
+
# A read from a port returns whatever arrived, which is rarely one whole frame. This chunk
|
|
49
|
+
# holds two good frames with a truncated one between them; the decoder hands over the good
|
|
50
|
+
# ones and discards only the bad frame.
|
|
51
|
+
decoder = SlipDecoder()
|
|
52
|
+
chunk = (
|
|
53
|
+
b"ok"
|
|
54
|
+
+ bytes([SLIP_END])
|
|
55
|
+
+ bytes([SLIP_ESC]) # a frame that ends before its escape pair completes
|
|
56
|
+
+ bytes([SLIP_END])
|
|
57
|
+
+ b"go"
|
|
58
|
+
+ bytes([SLIP_END])
|
|
59
|
+
)
|
|
60
|
+
frames = decoder.feed(chunk)
|
|
61
|
+
for frame in frames:
|
|
62
|
+
print(f"received {frame.decode()}")
|
|
63
|
+
print(f"discarded {decoder.discarded} frame the stream mangled")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## The same capability in every language
|
|
67
|
+
|
|
68
|
+
| Language | Package | Reference |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| Rust | [`pamoja-serial`](https://crates.io/crates/pamoja-serial) | [reference](https://pamoja.molex.cloud/docs/reference/rust/pamoja_serial/index.html), [docs.rs](https://docs.rs/pamoja-serial), [install](https://pamoja.molex.cloud/docs/reference/rust.html#rust-serial) |
|
|
71
|
+
| TypeScript | [`@pamoja/serial`](https://www.npmjs.com/package/@pamoja/serial) | [reference](https://pamoja.molex.cloud/docs/reference/node/modules/_pamoja_serial.html), [install](https://pamoja.molex.cloud/docs/reference/node.html#node-serial) |
|
|
72
|
+
| Python | [`pamoja-serial`](https://pypi.org/project/pamoja-serial/) | [reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/serial.html), [install](https://pamoja.molex.cloud/docs/reference/python.html#python-serial) |
|
|
73
|
+
| C# | [`Pamoja.Serial`](https://www.nuget.org/packages/Pamoja.Serial) | [reference](https://pamoja.molex.cloud/docs/reference/dotnet/api/Pamoja.Serial.html), [install](https://pamoja.molex.cloud/docs/reference/dotnet.html#dotnet-serial) |
|
|
74
|
+
|
|
75
|
+
## Documentation
|
|
76
|
+
|
|
77
|
+
- [`pamoja.serial` reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/serial.html), every class and function in this module.
|
|
78
|
+
- [The Serial framing guide](https://pamoja.molex.cloud/docs/guides/serial.html), with the same example in Rust, TypeScript, and C#.
|
|
79
|
+
- [Every capability](https://pamoja.molex.cloud/docs/), and the [install page](https://pamoja.molex.cloud/docs/install.html).
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Idiomatic serial-framing facade.
|
|
2
|
+
|
|
3
|
+
A serial line is a stream of bytes with no packet boundaries, so something has to
|
|
4
|
+
mark where one message ends and the next begins. SLIP and COBS are the two ways to
|
|
5
|
+
do that, and each is offered both as a one-shot call over a complete frame and as
|
|
6
|
+
a streaming decoder for the arbitrary chunks a port delivers.
|
|
7
|
+
|
|
8
|
+
The streaming decoders are what a real read loop uses. A corrupt frame does not
|
|
9
|
+
raise, because the frames around it are still good; it is dropped and counted on
|
|
10
|
+
:attr:`SlipDecoder.discarded`.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Protocol
|
|
16
|
+
|
|
17
|
+
from pamoja._native import CobsDecoder as _NativeCobsDecoder
|
|
18
|
+
from pamoja._native import SlipDecoder as _NativeSlipDecoder
|
|
19
|
+
from pamoja._native import cobs_decode as _cobs_decode
|
|
20
|
+
from pamoja._native import cobs_encode as _cobs_encode
|
|
21
|
+
from pamoja._native import cobs_max_encoded_len as _cobs_max_encoded_len
|
|
22
|
+
from pamoja._native import serial_framing_bytes as _serial_framing_bytes
|
|
23
|
+
from pamoja._native import slip_decode as _slip_decode
|
|
24
|
+
from pamoja._native import slip_encode as _slip_encode
|
|
25
|
+
from pamoja._native import slip_max_encoded_len as _slip_max_encoded_len
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"COBS_DELIMITER",
|
|
29
|
+
"CobsDecoder",
|
|
30
|
+
"Framing",
|
|
31
|
+
"SLIP_END",
|
|
32
|
+
"SLIP_ESC",
|
|
33
|
+
"SLIP_ESC_END",
|
|
34
|
+
"SLIP_ESC_ESC",
|
|
35
|
+
"SlipDecoder",
|
|
36
|
+
"cobs",
|
|
37
|
+
"slip",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Framing(Protocol):
|
|
42
|
+
"""One of the two byte-stuffing framings this module offers."""
|
|
43
|
+
|
|
44
|
+
def encode(self, payload: bytes) -> bytes:
|
|
45
|
+
"""Frame a payload for the wire.
|
|
46
|
+
|
|
47
|
+
:param payload: The bytes to send.
|
|
48
|
+
:returns: The frame, delimiter included.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def decode(self, frame: bytes) -> bytes:
|
|
52
|
+
"""Read the payload back out of a complete frame.
|
|
53
|
+
|
|
54
|
+
:param frame: The frame as it arrived.
|
|
55
|
+
:returns: The payload.
|
|
56
|
+
:raises PamojaError: If the frame is corrupt.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def max_encoded_len(self, payload_len: int) -> int:
|
|
60
|
+
"""Return the largest frame a payload of this length can produce.
|
|
61
|
+
|
|
62
|
+
:param payload_len: The payload length in bytes.
|
|
63
|
+
:returns: The worst-case frame length.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
_END, _ESC, _ESC_END, _ESC_ESC, _DELIMITER = _serial_framing_bytes()
|
|
68
|
+
|
|
69
|
+
#: The SLIP byte that ends a frame (RFC 1055).
|
|
70
|
+
SLIP_END = _END
|
|
71
|
+
#: The SLIP byte that escapes a reserved value inside a frame (RFC 1055).
|
|
72
|
+
SLIP_ESC = _ESC
|
|
73
|
+
#: The byte that follows an escape to stand for a literal end byte.
|
|
74
|
+
SLIP_ESC_END = _ESC_END
|
|
75
|
+
#: The byte that follows an escape to stand for a literal escape byte.
|
|
76
|
+
SLIP_ESC_ESC = _ESC_ESC
|
|
77
|
+
#: The byte that delimits a COBS frame, which never appears inside one.
|
|
78
|
+
COBS_DELIMITER = _DELIMITER
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class _Slip:
|
|
82
|
+
"""SLIP (RFC 1055): an ``END`` byte ends a packet, and an escape pair carries it."""
|
|
83
|
+
|
|
84
|
+
__slots__ = ()
|
|
85
|
+
|
|
86
|
+
def encode(self, payload: bytes) -> bytes:
|
|
87
|
+
"""Frame a payload as a SLIP packet."""
|
|
88
|
+
return _slip_encode(bytes(payload))
|
|
89
|
+
|
|
90
|
+
def decode(self, frame: bytes) -> bytes:
|
|
91
|
+
"""Read the payload back out of a SLIP frame."""
|
|
92
|
+
return _slip_decode(bytes(frame))
|
|
93
|
+
|
|
94
|
+
def max_encoded_len(self, payload_len: int) -> int:
|
|
95
|
+
"""Return the worst-case SLIP frame length for a payload."""
|
|
96
|
+
return _slip_max_encoded_len(payload_len)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class _Cobs:
|
|
100
|
+
"""COBS: removes the zero byte so one zero delimits packets unambiguously."""
|
|
101
|
+
|
|
102
|
+
__slots__ = ()
|
|
103
|
+
|
|
104
|
+
def encode(self, payload: bytes) -> bytes:
|
|
105
|
+
"""Frame a payload as a COBS packet."""
|
|
106
|
+
return _cobs_encode(bytes(payload))
|
|
107
|
+
|
|
108
|
+
def decode(self, frame: bytes) -> bytes:
|
|
109
|
+
"""Read the payload back out of a COBS frame."""
|
|
110
|
+
return _cobs_decode(bytes(frame))
|
|
111
|
+
|
|
112
|
+
def max_encoded_len(self, payload_len: int) -> int:
|
|
113
|
+
"""Return the worst-case COBS frame length for a payload."""
|
|
114
|
+
return _cobs_max_encoded_len(payload_len)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
#: SLIP framing, the simplest there is.
|
|
118
|
+
slip: Framing = _Slip()
|
|
119
|
+
|
|
120
|
+
#: COBS framing, for links where the overhead has to stay small and predictable.
|
|
121
|
+
cobs: Framing = _Cobs()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class SlipDecoder:
|
|
125
|
+
"""Reassembles whole SLIP frames from the chunks a serial port delivers.
|
|
126
|
+
|
|
127
|
+
Example::
|
|
128
|
+
|
|
129
|
+
decoder = SlipDecoder()
|
|
130
|
+
while True:
|
|
131
|
+
for frame in decoder.feed(port.read(256)):
|
|
132
|
+
handle(frame)
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
__slots__ = ("_native",)
|
|
136
|
+
|
|
137
|
+
def __init__(self) -> None:
|
|
138
|
+
"""Create an empty decoder, ready for the first chunk."""
|
|
139
|
+
self._native = _NativeSlipDecoder()
|
|
140
|
+
|
|
141
|
+
def feed(self, chunk: bytes) -> list[bytes]:
|
|
142
|
+
"""Feed a chunk of the stream.
|
|
143
|
+
|
|
144
|
+
:param chunk: The bytes just read from the port.
|
|
145
|
+
:returns: Every frame this chunk completed, in order, which is often none.
|
|
146
|
+
"""
|
|
147
|
+
return self._native.feed(bytes(chunk))
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def discarded(self) -> int:
|
|
151
|
+
"""How many corrupt frames this decoder has discarded."""
|
|
152
|
+
return self._native.discarded
|
|
153
|
+
|
|
154
|
+
def reset(self) -> None:
|
|
155
|
+
"""Discard any partly assembled frame."""
|
|
156
|
+
self._native.reset()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class CobsDecoder:
|
|
160
|
+
"""Reassembles whole COBS frames from the chunks a serial port delivers.
|
|
161
|
+
|
|
162
|
+
The counterpart to :class:`SlipDecoder`, for links where the framing overhead
|
|
163
|
+
has to stay small and predictable.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
__slots__ = ("_native",)
|
|
167
|
+
|
|
168
|
+
def __init__(self) -> None:
|
|
169
|
+
"""Create an empty decoder, ready for the first chunk."""
|
|
170
|
+
self._native = _NativeCobsDecoder()
|
|
171
|
+
|
|
172
|
+
def feed(self, chunk: bytes) -> list[bytes]:
|
|
173
|
+
"""Feed a chunk of the stream.
|
|
174
|
+
|
|
175
|
+
:param chunk: The bytes just read from the port.
|
|
176
|
+
:returns: Every frame this chunk completed, in order.
|
|
177
|
+
"""
|
|
178
|
+
return self._native.feed(bytes(chunk))
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def discarded(self) -> int:
|
|
182
|
+
"""How many corrupt frames this decoder has discarded."""
|
|
183
|
+
return self._native.discarded
|
|
184
|
+
|
|
185
|
+
def reset(self) -> None:
|
|
186
|
+
"""Discard any partly assembled frame."""
|
|
187
|
+
self._native.reset()
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pamoja-serial"
|
|
7
|
+
version = "0.1.18"
|
|
8
|
+
description = "SLIP and COBS byte stuffing with streaming decoders, so a UART byte stream carries discrete packets."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
license-files = ["LICENSE-MIT"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "molexxxx" }]
|
|
14
|
+
keywords = ["pamoja", "iot", "robotics", "serial"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"pamoja-native==0.1.18",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Repository = "https://github.com/molexxxx/pamoja"
|
|
27
|
+
Documentation = "https://pamoja.molex.cloud/docs/guides/serial.html"
|
|
28
|
+
|
|
29
|
+
[tool.hatch.build.targets.wheel]
|
|
30
|
+
packages = ["pamoja"]
|