marilib-pkg 0.6.0__py3-none-any.whl
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.
- examples/frames.py +23 -0
- examples/mari_cloud.py +72 -0
- examples/mari_cloud_minimal.py +38 -0
- examples/mari_edge.py +73 -0
- examples/mari_edge_minimal.py +37 -0
- examples/mari_edge_stats.py +156 -0
- examples/uart.py +35 -0
- marilib/__init__.py +10 -0
- marilib/communication_adapter.py +212 -0
- marilib/latency.py +78 -0
- marilib/logger.py +211 -0
- marilib/mari_protocol.py +76 -0
- marilib/marilib.py +35 -0
- marilib/marilib_cloud.py +193 -0
- marilib/marilib_edge.py +248 -0
- marilib/model.py +393 -0
- marilib/protocol.py +109 -0
- marilib/serial_hdlc.py +228 -0
- marilib/serial_uart.py +84 -0
- marilib/tui.py +13 -0
- marilib/tui_cloud.py +158 -0
- marilib/tui_edge.py +185 -0
- marilib_pkg-0.6.0.dist-info/METADATA +57 -0
- marilib_pkg-0.6.0.dist-info/RECORD +30 -0
- marilib_pkg-0.6.0.dist-info/WHEEL +4 -0
- marilib_pkg-0.6.0.dist-info/licenses/AUTHORS +2 -0
- marilib_pkg-0.6.0.dist-info/licenses/LICENSE +28 -0
- tests/__init__.py +0 -0
- tests/test_hdlc.py +76 -0
- tests/test_protocol.py +35 -0
marilib/tui_edge.py
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
from datetime import datetime, timedelta
|
2
|
+
|
3
|
+
from rich.columns import Columns
|
4
|
+
from rich.console import Console, Group
|
5
|
+
from rich.layout import Layout
|
6
|
+
from rich.live import Live
|
7
|
+
from rich.panel import Panel
|
8
|
+
from rich.table import Table
|
9
|
+
from rich.text import Text
|
10
|
+
|
11
|
+
from marilib import MarilibEdge
|
12
|
+
from marilib.model import MariNode, TestState
|
13
|
+
from marilib.tui import MarilibTUI
|
14
|
+
|
15
|
+
|
16
|
+
class MarilibTUIEdge(MarilibTUI):
|
17
|
+
"""A Text-based User Interface for MarilibEdge."""
|
18
|
+
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
max_tables=3,
|
22
|
+
re_render_max_freq=0.2,
|
23
|
+
test_state: TestState | None = None,
|
24
|
+
):
|
25
|
+
self.console = Console()
|
26
|
+
self.live = Live(console=self.console, auto_refresh=False, transient=True)
|
27
|
+
self.live.start()
|
28
|
+
self.max_tables = max_tables
|
29
|
+
self.re_render_max_freq = re_render_max_freq
|
30
|
+
self.last_render_time = datetime.now()
|
31
|
+
self.test_state = test_state
|
32
|
+
|
33
|
+
def get_max_rows(self) -> int:
|
34
|
+
"""Calculate maximum rows based on terminal height."""
|
35
|
+
terminal_height = self.console.height
|
36
|
+
available_height = terminal_height - 10 - 2 - 2 - 1 - 2
|
37
|
+
return max(2, available_height)
|
38
|
+
|
39
|
+
def render(self, mari: MarilibEdge):
|
40
|
+
"""Render the TUI layout."""
|
41
|
+
with mari.lock:
|
42
|
+
if datetime.now() - self.last_render_time < timedelta(seconds=self.re_render_max_freq):
|
43
|
+
return
|
44
|
+
self.last_render_time = datetime.now()
|
45
|
+
layout = Layout()
|
46
|
+
layout.split(
|
47
|
+
Layout(self.create_header_panel(mari), size=12),
|
48
|
+
Layout(self.create_nodes_panel(mari)),
|
49
|
+
)
|
50
|
+
self.live.update(layout, refresh=True)
|
51
|
+
|
52
|
+
def create_header_panel(self, mari: MarilibEdge) -> Panel:
|
53
|
+
"""Create the header panel with gateway and network stats."""
|
54
|
+
status = Text()
|
55
|
+
status.append("MarilibEdge is ", style="bold")
|
56
|
+
status.append(
|
57
|
+
"connected" if mari.serial_connected else "disconnected",
|
58
|
+
style="bold green" if mari.serial_connected else "bold red",
|
59
|
+
)
|
60
|
+
status.append(
|
61
|
+
f" via {mari.serial_interface.port} at {mari.serial_interface.baudrate} baud "
|
62
|
+
f"since {mari.started_ts.strftime('%Y-%m-%d %H:%M:%S')}"
|
63
|
+
)
|
64
|
+
status.append(" | ")
|
65
|
+
secs = int((datetime.now() - mari.last_received_serial_data_ts).total_seconds())
|
66
|
+
status.append(
|
67
|
+
f"last received: {secs}s ago",
|
68
|
+
style="bold green" if secs <= 1 else "bold red",
|
69
|
+
)
|
70
|
+
|
71
|
+
status.append("\n\nGateway: ", style="bold cyan")
|
72
|
+
status.append(f"0x{mari.gateway.info.address:016X} | ")
|
73
|
+
status.append("Network ID: ", style="bold cyan")
|
74
|
+
status.append(f"0x{mari.gateway.info.network_id:04X} | ")
|
75
|
+
|
76
|
+
status.append("\n\n")
|
77
|
+
status.append("Schedule: ", style="bold cyan")
|
78
|
+
status.append(f"#{mari.gateway.info.schedule_id} ({mari.gateway.info.schedule_name}) | ")
|
79
|
+
status.append(mari.gateway.info.repr_schedule_cells_with_colors())
|
80
|
+
status.append("\n\n")
|
81
|
+
|
82
|
+
if mari.gateway.latency_stats.last_ms > 0:
|
83
|
+
status.append("Latency: ", style="bold cyan")
|
84
|
+
lat = mari.gateway.latency_stats
|
85
|
+
status.append(
|
86
|
+
f"Last: {lat.last_ms:.1f}ms | Avg: {lat.avg_ms:.1f}ms | "
|
87
|
+
f"Min: {lat.min_ms:.1f}ms | Max: {lat.max_ms:.1f}ms"
|
88
|
+
)
|
89
|
+
|
90
|
+
status.append("\n\nStats: ", style="bold yellow")
|
91
|
+
if self.test_state and self.test_state.load > 0 and self.test_state.rate > 0:
|
92
|
+
status.append(
|
93
|
+
"Test load: ",
|
94
|
+
# style="bold yellow",
|
95
|
+
)
|
96
|
+
status.append(f"{self.test_state.load}% of {self.test_state.rate} pps")
|
97
|
+
status.append(" | ")
|
98
|
+
|
99
|
+
stats = mari.gateway.stats
|
100
|
+
status.append(f"Nodes: {len(mari.gateway.nodes)} | ")
|
101
|
+
status.append(f"Frames TX: {stats.sent_count(include_test_packets=False)} | ")
|
102
|
+
status.append(f"Frames RX: {stats.received_count(include_test_packets=False)} | ")
|
103
|
+
status.append(f"TX/s: {stats.sent_count(1, include_test_packets=False)} | ")
|
104
|
+
status.append(f"RX/s: {stats.received_count(1, include_test_packets=False)}")
|
105
|
+
|
106
|
+
return Panel(status, title="[bold]MarilibEdge Status", border_style="blue")
|
107
|
+
|
108
|
+
def create_nodes_table(self, nodes: list[MariNode], title="") -> Table:
|
109
|
+
"""Create a table displaying information about connected nodes."""
|
110
|
+
table = Table(
|
111
|
+
show_header=True,
|
112
|
+
header_style="bold cyan",
|
113
|
+
border_style="blue",
|
114
|
+
padding=(0, 1),
|
115
|
+
title=title,
|
116
|
+
)
|
117
|
+
table.add_column("Node Address", style="cyan")
|
118
|
+
table.add_column("TX", justify="right")
|
119
|
+
table.add_column("TX/s", justify="right")
|
120
|
+
table.add_column("RX", justify="right")
|
121
|
+
table.add_column("RX/s", justify="right")
|
122
|
+
table.add_column("SR(total)", justify="right")
|
123
|
+
table.add_column("PDR Down", justify="right")
|
124
|
+
table.add_column("PDR Up", justify="right")
|
125
|
+
table.add_column("RSSI", justify="right")
|
126
|
+
table.add_column("Latency (ms)", justify="right")
|
127
|
+
for node in nodes:
|
128
|
+
lat_str = (
|
129
|
+
f"{node.latency_stats.avg_ms:.1f}" if node.latency_stats.last_ms > 0 else "..."
|
130
|
+
)
|
131
|
+
table.add_row(
|
132
|
+
f"0x{node.address:016X}",
|
133
|
+
str(node.stats.sent_count(include_test_packets=False)),
|
134
|
+
str(node.stats.sent_count(1, include_test_packets=False)),
|
135
|
+
str(node.stats.received_count(include_test_packets=False)),
|
136
|
+
str(node.stats.received_count(1, include_test_packets=False)),
|
137
|
+
f"{node.stats.success_rate():>4.0%}",
|
138
|
+
f"{node.pdr_downlink:>4.0%}",
|
139
|
+
f"{node.pdr_uplink:>4.0%}",
|
140
|
+
f"{node.stats.received_rssi_dbm(5)}",
|
141
|
+
lat_str,
|
142
|
+
)
|
143
|
+
return table
|
144
|
+
|
145
|
+
def create_nodes_panel(self, mari: MarilibEdge) -> Panel:
|
146
|
+
"""Create the panel that contains the nodes table."""
|
147
|
+
nodes = mari.gateway.nodes
|
148
|
+
max_rows = self.get_max_rows()
|
149
|
+
max_displayable_nodes = self.max_tables * max_rows
|
150
|
+
nodes_to_display = nodes[:max_displayable_nodes]
|
151
|
+
remaining_nodes = max(0, len(nodes) - max_displayable_nodes)
|
152
|
+
tables = []
|
153
|
+
current_table_nodes = []
|
154
|
+
for i, node in enumerate(nodes_to_display):
|
155
|
+
current_table_nodes.append(node)
|
156
|
+
if len(current_table_nodes) == max_rows or i == len(nodes_to_display) - 1:
|
157
|
+
title = f"Nodes {i - len(current_table_nodes) + 2}-{i + 1}"
|
158
|
+
tables.append(self.create_nodes_table(current_table_nodes, title))
|
159
|
+
current_table_nodes = []
|
160
|
+
if len(tables) >= self.max_tables:
|
161
|
+
break
|
162
|
+
if len(tables) > 1:
|
163
|
+
content = Columns(tables, equal=True, expand=True)
|
164
|
+
else:
|
165
|
+
content = tables[0] if tables else Table()
|
166
|
+
if remaining_nodes > 0:
|
167
|
+
panel_content = Group(
|
168
|
+
content,
|
169
|
+
Text(
|
170
|
+
f"\n(...and {remaining_nodes} more nodes)",
|
171
|
+
style="bold yellow",
|
172
|
+
),
|
173
|
+
)
|
174
|
+
else:
|
175
|
+
panel_content = content
|
176
|
+
return Panel(
|
177
|
+
panel_content,
|
178
|
+
title="[bold]Connected Nodes",
|
179
|
+
border_style="blue",
|
180
|
+
)
|
181
|
+
|
182
|
+
def close(self):
|
183
|
+
"""Clean up the live display."""
|
184
|
+
self.live.stop()
|
185
|
+
print("")
|
@@ -0,0 +1,57 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: marilib-pkg
|
3
|
+
Version: 0.6.0
|
4
|
+
Summary: MariLib is a Python library for interacting with the Mari network.
|
5
|
+
Project-URL: Homepage, https://github.com/DotBots/marilib
|
6
|
+
Project-URL: Bug Tracker, https://github.com/DotBots/marilib/issues
|
7
|
+
Author-email: Geovane Fedrecheski <geovane.fedrecheski@inria.fr>
|
8
|
+
License: BSD
|
9
|
+
License-File: AUTHORS
|
10
|
+
License-File: LICENSE
|
11
|
+
Classifier: License :: OSI Approved :: BSD License
|
12
|
+
Classifier: Operating System :: MacOS
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
16
|
+
Requires-Python: >=3.8
|
17
|
+
Requires-Dist: click==8.1.7
|
18
|
+
Requires-Dist: paho-mqtt==2.1.0
|
19
|
+
Requires-Dist: pyserial==3.5
|
20
|
+
Requires-Dist: rich==14.0.0
|
21
|
+
Requires-Dist: structlog==24.4.0
|
22
|
+
Requires-Dist: tqdm==4.66.5
|
23
|
+
Description-Content-Type: text/markdown
|
24
|
+
|
25
|
+
# MariLib 💫 👀 🐍
|
26
|
+
|
27
|
+
MariLib is a Python library to interact with a local [Mari](https://github.com/DotBots/mari) network.
|
28
|
+
It connects to a Mari gateway via UART.
|
29
|
+
|
30
|
+
## Example with TUI
|
31
|
+
MariLib provides a stateful class with gateway and node information, network statistics, and a rich real-time TUI:
|
32
|
+
|
33
|
+
[mari-edge-2.webm](https://github.com/user-attachments/assets/fe50f2ba-8e67-4522-8700-69730f8e3aee)
|
34
|
+
|
35
|
+
See the how it works in `examples/basic.py`.
|
36
|
+
|
37
|
+
## Minimal example
|
38
|
+
Here is a minimal example showcasing how to use MariLib:
|
39
|
+
|
40
|
+
```python
|
41
|
+
import time
|
42
|
+
from marilib.marilib import MarilibEdge
|
43
|
+
from marilib.serial_uart import get_default_port
|
44
|
+
|
45
|
+
def main():
|
46
|
+
mari = MarilibEdge(lambda event, data: print(event.name, data), get_default_port())
|
47
|
+
while True:
|
48
|
+
for node in mari.gateway.nodes:
|
49
|
+
mari.send_frame(dst=node.address, payload=b"A" * 3)
|
50
|
+
statistics = [(f"{node.address:016X}", node.stats.received_rssi_dbm()) for node in mari.gateway.nodes]
|
51
|
+
print(f"Network statistics: {statistics}")
|
52
|
+
time.sleep(0.25)
|
53
|
+
|
54
|
+
if __name__ == "__main__":
|
55
|
+
main()
|
56
|
+
```
|
57
|
+
See it in action in `examples/minimal.py`.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
examples/frames.py,sha256=SSC36A66kFwL-U5HtFgAuK6_BI1ik_84EFJ7DoG0oBo,780
|
2
|
+
examples/mari_cloud.py,sha256=EQmQb6AiZt49j6bI4h9kAe8nfy6Zg5PfY0WTYKc8Kes,1860
|
3
|
+
examples/mari_cloud_minimal.py,sha256=zaLnd61gkqht5AclJC6Hfc1CPS_8BzTmGE-tItbWlao,1125
|
4
|
+
examples/mari_edge.py,sha256=VI5iv46f5ejbNn7H6A7pQjSDrdWsj3pDM6DrLos5SuI,1936
|
5
|
+
examples/mari_edge_minimal.py,sha256=9vOsIdW9WnS_GQg9fBw_G1wkvGFokwsiM83Q8etoUpA,1090
|
6
|
+
examples/mari_edge_stats.py,sha256=LQx4gtfFCp1rP0uBo1UCLg5e5eusDKsH_beoKj5_CAI,4340
|
7
|
+
examples/uart.py,sha256=pUvkpVf64T94XFLzgyHSzMOZISMrm0wqyurjNyS3JhY,823
|
8
|
+
marilib/__init__.py,sha256=PCxfi_8iZpMbc5DscErorPXqdO7qghp6t1MFU0NnYfk,130
|
9
|
+
marilib/communication_adapter.py,sha256=IEcSLm5lbtFMtFCZ784QdvZZ5zcqL1IZQ90CSSp-XvM,6998
|
10
|
+
marilib/latency.py,sha256=jz-7Yvgmk-BO40FHW_pltHbRV2OL1QD-dCkV9iSJiiY,2729
|
11
|
+
marilib/logger.py,sha256=IgkC1JM9sf3JxiSBYUcHeuYpiRrwcODVR2Y54GA2LPA,7867
|
12
|
+
marilib/mari_protocol.py,sha256=b1hQdSJX8CjPtKgosO7F-vt8ZHsnZ9QqMI2oKNL4r0E,2516
|
13
|
+
marilib/marilib.py,sha256=m7drdxhAMnjTrqYjUvwqunreYpffz7-AKgaOkuXHukg,939
|
14
|
+
marilib/marilib_cloud.py,sha256=0yPTea0J2h4EkWwTcU4QAr001srd9_P6LOPtmJmhH24,7346
|
15
|
+
marilib/marilib_edge.py,sha256=tzjeNtI_SpvghYxgV_y8j27d6xXX-1HIQc2TGKuNOg0,9743
|
16
|
+
marilib/model.py,sha256=0WAEtMxqnqziSG0epZfLXlXVAzVRNfou4n6suhtdB1k,12831
|
17
|
+
marilib/protocol.py,sha256=wOsG_oIk2Ls6gnbDYa5_g1sbHGuYgsj6liiax_yTn34,3786
|
18
|
+
marilib/serial_hdlc.py,sha256=6EDbjkJ5RU_1Gv3OhHaIhLAXscce_2-yCv-602sw2-8,8119
|
19
|
+
marilib/serial_uart.py,sha256=7sv8Ouj-y7k2Yry6cINil9WgVYvy1cRPPpIbW4JLVQo,2764
|
20
|
+
marilib/tui.py,sha256=doTpHnZqyHEglSck_a1cl9DU1uTCZavVOlcZjA8aTV4,231
|
21
|
+
marilib/tui_cloud.py,sha256=LZQVYLSwdLBubUcbUy7Eoahma_98b690ndCVgXSm_EA,5672
|
22
|
+
marilib/tui_edge.py,sha256=N2i0aUZ2zeaC0oPcCamNd5FY0wTDtmp2KWFF1yL6ABA,7573
|
23
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
+
tests/test_hdlc.py,sha256=SSgDpdTa2ga-P5tlXZtGvjliame4Ug9FwyreSw_nT9k,2450
|
25
|
+
tests/test_protocol.py,sha256=JJRtzCV2BygogSFFmss4FwYLb7x3Zy8sIEWxzopfxNE,1167
|
26
|
+
marilib_pkg-0.6.0.dist-info/METADATA,sha256=FuhL1nVncSyrIJ1mpqqReAajGZSCQcCJW-gEsQbE0h4,1992
|
27
|
+
marilib_pkg-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
28
|
+
marilib_pkg-0.6.0.dist-info/licenses/AUTHORS,sha256=gGgRHmEH6klwG7KQd4rYVy0AT8MkA9mMimfiN1NvOHU,96
|
29
|
+
marilib_pkg-0.6.0.dist-info/licenses/LICENSE,sha256=j97C1uBc5chpQWi4bv_2SrqExuvKaJK2Ch6L2LFkoc4,1492
|
30
|
+
marilib_pkg-0.6.0.dist-info/RECORD,,
|
@@ -0,0 +1,28 @@
|
|
1
|
+
BSD 3-Clause License
|
2
|
+
|
3
|
+
Copyright (c) 2024, Inria
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
9
|
+
list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
13
|
+
and/or other materials provided with the distribution.
|
14
|
+
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
16
|
+
contributors may be used to endorse or promote products derived from
|
17
|
+
this software without specific prior written permission.
|
18
|
+
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
tests/__init__.py
ADDED
File without changes
|
tests/test_hdlc.py
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
"""Test module for HDLC handler class."""
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from marilib.serial_hdlc import HDLCDecodeException, HDLCHandler, HDLCState
|
6
|
+
|
7
|
+
|
8
|
+
def test_hdlc_handler_states():
|
9
|
+
handler = HDLCHandler()
|
10
|
+
assert handler.state == HDLCState.IDLE
|
11
|
+
handler.handle_byte(b"A")
|
12
|
+
assert handler.state == HDLCState.IDLE
|
13
|
+
handler.handle_byte(b"~")
|
14
|
+
assert handler.state == HDLCState.RECEIVING
|
15
|
+
handler.handle_byte(b"A")
|
16
|
+
assert handler.state == HDLCState.RECEIVING
|
17
|
+
handler.handle_byte(b"~")
|
18
|
+
assert handler.state == HDLCState.READY
|
19
|
+
handler.handle_byte(b"~")
|
20
|
+
assert handler.state == HDLCState.RECEIVING
|
21
|
+
handler.handle_byte(b"A")
|
22
|
+
handler.handle_byte(b"\xf5")
|
23
|
+
handler.handle_byte(b"\xa3")
|
24
|
+
assert handler.state == HDLCState.RECEIVING
|
25
|
+
handler.handle_byte(b"~")
|
26
|
+
assert handler.state == HDLCState.READY
|
27
|
+
handler.handle_byte(b"~")
|
28
|
+
assert handler.state == HDLCState.RECEIVING
|
29
|
+
handler.handle_byte(b"~")
|
30
|
+
assert handler.output == bytearray()
|
31
|
+
assert handler.state == HDLCState.RECEIVING
|
32
|
+
handler.handle_byte(b"~")
|
33
|
+
assert handler.output == bytearray()
|
34
|
+
assert handler.state == HDLCState.RECEIVING
|
35
|
+
|
36
|
+
|
37
|
+
def test_hdlc_handler_decode():
|
38
|
+
handler = HDLCHandler()
|
39
|
+
for byte in b"~test\x88\x07~":
|
40
|
+
handler.handle_byte(int(byte).to_bytes(1, "little"))
|
41
|
+
assert handler.payload == b"test"
|
42
|
+
assert handler.state == HDLCState.IDLE
|
43
|
+
|
44
|
+
|
45
|
+
def test_hdlc_handler_decode_with_flags():
|
46
|
+
handler = HDLCHandler()
|
47
|
+
for byte in b"~}^test}]\x06\x94~":
|
48
|
+
handler.handle_byte(int(byte).to_bytes(1, "little"))
|
49
|
+
assert handler.state == HDLCState.READY
|
50
|
+
assert handler.payload == bytearray(b"~test}")
|
51
|
+
assert handler.state == HDLCState.IDLE
|
52
|
+
|
53
|
+
|
54
|
+
def test_hdlc_handler_invalid_state():
|
55
|
+
handler = HDLCHandler()
|
56
|
+
for byte in b"~test\x42\x42":
|
57
|
+
handler.handle_byte(int(byte).to_bytes(1, "little"))
|
58
|
+
with pytest.raises(HDLCDecodeException) as exc:
|
59
|
+
_ = handler.payload
|
60
|
+
assert str(exc.value) == "Incomplete HDLC frame"
|
61
|
+
|
62
|
+
|
63
|
+
def test_hdlc_handler_invalid_fcs():
|
64
|
+
handler = HDLCHandler()
|
65
|
+
for byte in b"~test\x42\x42~":
|
66
|
+
handler.handle_byte(int(byte).to_bytes(1, "little"))
|
67
|
+
payload = handler.payload
|
68
|
+
assert payload == bytearray()
|
69
|
+
|
70
|
+
|
71
|
+
def test_hdlc_handler_payload_too_short():
|
72
|
+
handler = HDLCHandler()
|
73
|
+
for byte in b"~a~":
|
74
|
+
handler.handle_byte(int(byte).to_bytes(1, "little"))
|
75
|
+
payload = handler.payload
|
76
|
+
assert payload == bytearray()
|
tests/test_protocol.py
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
from marilib.mari_protocol import Frame, Header
|
2
|
+
|
3
|
+
|
4
|
+
def test_header_size():
|
5
|
+
assert Header().size == 20
|
6
|
+
|
7
|
+
|
8
|
+
def test_header_from_bytes():
|
9
|
+
header = Header().from_bytes(
|
10
|
+
bytes.fromhex("0210170059291ba8fdcecef531eb7f2526ef0399f0f0f0f0f0")[0:20]
|
11
|
+
)
|
12
|
+
assert header.version == 2
|
13
|
+
assert header.type_ == 16
|
14
|
+
assert header.network_id == 23
|
15
|
+
assert header.destination == int.from_bytes(
|
16
|
+
bytes.fromhex("59291ba8fdcecef5"), byteorder="little"
|
17
|
+
)
|
18
|
+
assert header.source == int.from_bytes(bytes.fromhex("31eb7f2526ef0399"), byteorder="little")
|
19
|
+
|
20
|
+
|
21
|
+
def test_frame_from_bytes():
|
22
|
+
frame = Frame().from_bytes(
|
23
|
+
bytes.fromhex("0210170059291ba8fdcecef531eb7f2526ef0399dcf0f0f0f0f0")
|
24
|
+
)
|
25
|
+
assert frame.header.version == 2
|
26
|
+
assert frame.header.type_ == 16
|
27
|
+
assert frame.header.network_id == 23
|
28
|
+
assert frame.header.destination == int.from_bytes(
|
29
|
+
bytes.fromhex("59291ba8fdcecef5"), byteorder="little"
|
30
|
+
)
|
31
|
+
assert frame.header.source == int.from_bytes(
|
32
|
+
bytes.fromhex("31eb7f2526ef0399"), byteorder="little"
|
33
|
+
)
|
34
|
+
assert frame.stats.rssi_dbm == -35
|
35
|
+
assert frame.payload == bytes.fromhex("f0f0f0f0f0")
|