marilib-pkg 0.6.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.
- marilib_pkg-0.6.0/.gitignore +46 -0
- marilib_pkg-0.6.0/AUTHORS +2 -0
- marilib_pkg-0.6.0/LICENSE +28 -0
- marilib_pkg-0.6.0/PKG-INFO +57 -0
- marilib_pkg-0.6.0/README.md +33 -0
- marilib_pkg-0.6.0/examples/frames.py +23 -0
- marilib_pkg-0.6.0/examples/mari_cloud.py +72 -0
- marilib_pkg-0.6.0/examples/mari_cloud_minimal.py +38 -0
- marilib_pkg-0.6.0/examples/mari_edge.py +73 -0
- marilib_pkg-0.6.0/examples/mari_edge_minimal.py +37 -0
- marilib_pkg-0.6.0/examples/mari_edge_stats.py +156 -0
- marilib_pkg-0.6.0/examples/uart.py +35 -0
- marilib_pkg-0.6.0/marilib/__init__.py +10 -0
- marilib_pkg-0.6.0/marilib/communication_adapter.py +212 -0
- marilib_pkg-0.6.0/marilib/latency.py +78 -0
- marilib_pkg-0.6.0/marilib/logger.py +211 -0
- marilib_pkg-0.6.0/marilib/mari_protocol.py +76 -0
- marilib_pkg-0.6.0/marilib/marilib.py +35 -0
- marilib_pkg-0.6.0/marilib/marilib_cloud.py +193 -0
- marilib_pkg-0.6.0/marilib/marilib_edge.py +248 -0
- marilib_pkg-0.6.0/marilib/model.py +393 -0
- marilib_pkg-0.6.0/marilib/protocol.py +109 -0
- marilib_pkg-0.6.0/marilib/serial_hdlc.py +228 -0
- marilib_pkg-0.6.0/marilib/serial_uart.py +84 -0
- marilib_pkg-0.6.0/marilib/tui.py +13 -0
- marilib_pkg-0.6.0/marilib/tui_cloud.py +158 -0
- marilib_pkg-0.6.0/marilib/tui_edge.py +185 -0
- marilib_pkg-0.6.0/pyproject.toml +78 -0
- marilib_pkg-0.6.0/tests/__init__.py +0 -0
- marilib_pkg-0.6.0/tests/test_hdlc.py +76 -0
- marilib_pkg-0.6.0/tests/test_protocol.py +35 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
# Segger Studio specific files
|
2
|
+
|
3
|
+
*.emSession
|
4
|
+
*.jlink
|
5
|
+
|
6
|
+
# Python compiled files
|
7
|
+
*.pyc
|
8
|
+
|
9
|
+
# Git conflict files
|
10
|
+
*.orig
|
11
|
+
|
12
|
+
# Visual Studio specific files
|
13
|
+
.vscode/
|
14
|
+
|
15
|
+
# Segger Studio output directory
|
16
|
+
Output/
|
17
|
+
|
18
|
+
# Python package dist
|
19
|
+
dist/
|
20
|
+
|
21
|
+
# Virtual env folder
|
22
|
+
.venv/
|
23
|
+
|
24
|
+
# Tox
|
25
|
+
.tox/
|
26
|
+
|
27
|
+
# Venv files
|
28
|
+
.venv*
|
29
|
+
venv
|
30
|
+
|
31
|
+
# Python cache files
|
32
|
+
__pycache__/
|
33
|
+
|
34
|
+
# Build files
|
35
|
+
build/
|
36
|
+
|
37
|
+
# Distribution files
|
38
|
+
dist/
|
39
|
+
|
40
|
+
# Coverage reports
|
41
|
+
.coverage
|
42
|
+
coverage.*
|
43
|
+
|
44
|
+
# do not commit logs
|
45
|
+
logs_metrics
|
46
|
+
logs
|
@@ -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.
|
@@ -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,33 @@
|
|
1
|
+
# MariLib 💫 👀 🐍
|
2
|
+
|
3
|
+
MariLib is a Python library to interact with a local [Mari](https://github.com/DotBots/mari) network.
|
4
|
+
It connects to a Mari gateway via UART.
|
5
|
+
|
6
|
+
## Example with TUI
|
7
|
+
MariLib provides a stateful class with gateway and node information, network statistics, and a rich real-time TUI:
|
8
|
+
|
9
|
+
[mari-edge-2.webm](https://github.com/user-attachments/assets/fe50f2ba-8e67-4522-8700-69730f8e3aee)
|
10
|
+
|
11
|
+
See the how it works in `examples/basic.py`.
|
12
|
+
|
13
|
+
## Minimal example
|
14
|
+
Here is a minimal example showcasing how to use MariLib:
|
15
|
+
|
16
|
+
```python
|
17
|
+
import time
|
18
|
+
from marilib.marilib import MarilibEdge
|
19
|
+
from marilib.serial_uart import get_default_port
|
20
|
+
|
21
|
+
def main():
|
22
|
+
mari = MarilibEdge(lambda event, data: print(event.name, data), get_default_port())
|
23
|
+
while True:
|
24
|
+
for node in mari.gateway.nodes:
|
25
|
+
mari.send_frame(dst=node.address, payload=b"A" * 3)
|
26
|
+
statistics = [(f"{node.address:016X}", node.stats.received_rssi_dbm()) for node in mari.gateway.nodes]
|
27
|
+
print(f"Network statistics: {statistics}")
|
28
|
+
time.sleep(0.25)
|
29
|
+
|
30
|
+
if __name__ == "__main__":
|
31
|
+
main()
|
32
|
+
```
|
33
|
+
See it in action in `examples/minimal.py`.
|
@@ -0,0 +1,23 @@
|
|
1
|
+
"""
|
2
|
+
This script is used to generate a frame to send to a node.
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
python frames.py [destination]
|
6
|
+
|
7
|
+
If no destination is provided, the frame will be sent to the broadcast address.
|
8
|
+
|
9
|
+
Example usage sending to broadcast address via mosquitto_pub:
|
10
|
+
|
11
|
+
while [ 1 ]; do python examples/frames.py | xxd -r -p | base64 | mosquitto_pub -h localhost -p 1883 -t /mari/00A0/to_edge -l; done
|
12
|
+
"""
|
13
|
+
|
14
|
+
from marilib.mari_protocol import Frame, Header, MARI_BROADCAST_ADDRESS
|
15
|
+
from marilib.model import EdgeEvent
|
16
|
+
import sys
|
17
|
+
|
18
|
+
destination = sys.argv[1] if len(sys.argv) > 1 else MARI_BROADCAST_ADDRESS
|
19
|
+
|
20
|
+
header = Header(destination=destination)
|
21
|
+
frame = Frame(header=header, payload=b"NORMAL_APP_DATA")
|
22
|
+
frame_to_send = EdgeEvent.to_bytes(EdgeEvent.NODE_DATA) + frame.to_bytes()
|
23
|
+
print(frame_to_send.hex())
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
import click
|
4
|
+
from marilib.mari_protocol import MARI_BROADCAST_ADDRESS, MARI_NET_ID_DEFAULT, Frame
|
5
|
+
from marilib.marilib_cloud import MarilibCloud
|
6
|
+
from marilib.model import EdgeEvent, GatewayInfo, MariNode
|
7
|
+
from marilib.communication_adapter import MQTTAdapter
|
8
|
+
from marilib.tui_cloud import MarilibTUICloud
|
9
|
+
from marilib.logger import MetricsLogger
|
10
|
+
|
11
|
+
NORMAL_DATA_PAYLOAD = b"NORMAL_APP_DATA"
|
12
|
+
|
13
|
+
|
14
|
+
def on_event(event: EdgeEvent, event_data: MariNode | Frame | GatewayInfo):
|
15
|
+
"""An event handler for the application."""
|
16
|
+
pass
|
17
|
+
|
18
|
+
|
19
|
+
@click.command()
|
20
|
+
@click.option(
|
21
|
+
"--mqtt-url",
|
22
|
+
"-m",
|
23
|
+
type=str,
|
24
|
+
default="mqtt://localhost:1883",
|
25
|
+
show_default=True,
|
26
|
+
help="MQTT broker to use",
|
27
|
+
)
|
28
|
+
@click.option(
|
29
|
+
"--network-id",
|
30
|
+
"-n",
|
31
|
+
type=lambda x: int(x, 16),
|
32
|
+
default=MARI_NET_ID_DEFAULT,
|
33
|
+
help=f"Network ID to use [default: 0x{MARI_NET_ID_DEFAULT:04X}]",
|
34
|
+
)
|
35
|
+
@click.option(
|
36
|
+
"--log-dir",
|
37
|
+
default="logs",
|
38
|
+
show_default=True,
|
39
|
+
help="Directory to save metric log files.",
|
40
|
+
type=click.Path(),
|
41
|
+
)
|
42
|
+
def main(mqtt_url: str, network_id: int, log_dir: str):
|
43
|
+
"""A basic example of using the MariLibCloud library."""
|
44
|
+
|
45
|
+
mari = MarilibCloud(
|
46
|
+
on_event,
|
47
|
+
mqtt_interface=MQTTAdapter.from_url(mqtt_url, is_edge=False),
|
48
|
+
logger=MetricsLogger(
|
49
|
+
log_dir_base=log_dir, rotation_interval_minutes=1440, log_interval_seconds=1.0
|
50
|
+
),
|
51
|
+
network_id=network_id,
|
52
|
+
tui=MarilibTUICloud(),
|
53
|
+
main_file=__file__,
|
54
|
+
)
|
55
|
+
|
56
|
+
try:
|
57
|
+
while True:
|
58
|
+
mari.update()
|
59
|
+
if mari.nodes:
|
60
|
+
mari.send_frame(MARI_BROADCAST_ADDRESS, NORMAL_DATA_PAYLOAD)
|
61
|
+
mari.render_tui()
|
62
|
+
time.sleep(0.5)
|
63
|
+
|
64
|
+
except KeyboardInterrupt:
|
65
|
+
pass
|
66
|
+
finally:
|
67
|
+
mari.close_tui()
|
68
|
+
mari.logger.close()
|
69
|
+
|
70
|
+
|
71
|
+
if __name__ == "__main__":
|
72
|
+
main()
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import time
|
2
|
+
import sys
|
3
|
+
|
4
|
+
from marilib.mari_protocol import MARI_NET_ID_DEFAULT
|
5
|
+
from marilib.marilib_cloud import MarilibCloud
|
6
|
+
from marilib.communication_adapter import MQTTAdapter
|
7
|
+
from marilib.model import EdgeEvent
|
8
|
+
|
9
|
+
|
10
|
+
def on_event(event, event_data):
|
11
|
+
"""An event handler for the application."""
|
12
|
+
if event == EdgeEvent.GATEWAY_INFO:
|
13
|
+
return
|
14
|
+
print(".", end="", flush=True)
|
15
|
+
|
16
|
+
|
17
|
+
def main():
|
18
|
+
mari_cloud = MarilibCloud(
|
19
|
+
on_event,
|
20
|
+
mqtt_interface=MQTTAdapter("localhost", 1883, is_edge=False),
|
21
|
+
network_id=int(sys.argv[1], 16) if len(sys.argv) > 1 else MARI_NET_ID_DEFAULT,
|
22
|
+
)
|
23
|
+
|
24
|
+
while True:
|
25
|
+
mari_cloud.update()
|
26
|
+
for node in mari_cloud.nodes:
|
27
|
+
# print(f"Sending frame to {node.address:016X}")
|
28
|
+
mari_cloud.send_frame(dst=node.address, payload=b"NORMAL_APP_DATA")
|
29
|
+
print(",", end="", flush=True)
|
30
|
+
statistics = [
|
31
|
+
(f"{node.address:016X}", node.stats.received_rssi_dbm()) for node in mari_cloud.nodes
|
32
|
+
]
|
33
|
+
print(f"Network statistics: {statistics}")
|
34
|
+
time.sleep(1)
|
35
|
+
|
36
|
+
|
37
|
+
if __name__ == "__main__":
|
38
|
+
main()
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
import click
|
4
|
+
from marilib.logger import MetricsLogger
|
5
|
+
from marilib.mari_protocol import Frame, MARI_BROADCAST_ADDRESS
|
6
|
+
from marilib.model import EdgeEvent, MariNode
|
7
|
+
from marilib.communication_adapter import SerialAdapter, MQTTAdapter
|
8
|
+
from marilib.serial_uart import get_default_port
|
9
|
+
from marilib.tui_edge import MarilibTUIEdge
|
10
|
+
from marilib.marilib_edge import MarilibEdge
|
11
|
+
|
12
|
+
NORMAL_DATA_PAYLOAD = b"NORMAL_APP_DATA"
|
13
|
+
|
14
|
+
|
15
|
+
def on_event(event: EdgeEvent, event_data: MariNode | Frame):
|
16
|
+
"""An event handler for the application."""
|
17
|
+
pass
|
18
|
+
|
19
|
+
|
20
|
+
@click.command()
|
21
|
+
@click.option(
|
22
|
+
"--port",
|
23
|
+
"-p",
|
24
|
+
type=str,
|
25
|
+
default=get_default_port(),
|
26
|
+
show_default=True,
|
27
|
+
help="Serial port to use (e.g., /dev/ttyACM0)",
|
28
|
+
)
|
29
|
+
@click.option(
|
30
|
+
"--mqtt-url",
|
31
|
+
"-m",
|
32
|
+
type=str,
|
33
|
+
default="mqtt://localhost:1883",
|
34
|
+
show_default=True,
|
35
|
+
help="MQTT broker to use (default: empty, no cloud)",
|
36
|
+
)
|
37
|
+
@click.option(
|
38
|
+
"--log-dir",
|
39
|
+
default="logs",
|
40
|
+
show_default=True,
|
41
|
+
help="Directory to save metric log files.",
|
42
|
+
type=click.Path(),
|
43
|
+
)
|
44
|
+
def main(port: str | None, mqtt_url: str, log_dir: str):
|
45
|
+
"""A basic example of using the MarilibEdge library."""
|
46
|
+
|
47
|
+
mari = MarilibEdge(
|
48
|
+
on_event,
|
49
|
+
serial_interface=SerialAdapter(port),
|
50
|
+
mqtt_interface=MQTTAdapter.from_url(mqtt_url, is_edge=True) if mqtt_url else None,
|
51
|
+
logger=MetricsLogger(
|
52
|
+
log_dir_base=log_dir, rotation_interval_minutes=1440, log_interval_seconds=1.0
|
53
|
+
),
|
54
|
+
tui=MarilibTUIEdge(),
|
55
|
+
main_file=__file__,
|
56
|
+
)
|
57
|
+
|
58
|
+
try:
|
59
|
+
while True:
|
60
|
+
mari.update()
|
61
|
+
if not mari.uses_mqtt and mari.nodes:
|
62
|
+
mari.send_frame(MARI_BROADCAST_ADDRESS, NORMAL_DATA_PAYLOAD)
|
63
|
+
mari.render_tui()
|
64
|
+
time.sleep(0.5)
|
65
|
+
except KeyboardInterrupt:
|
66
|
+
pass
|
67
|
+
finally:
|
68
|
+
mari.close_tui()
|
69
|
+
mari.logger.close()
|
70
|
+
|
71
|
+
|
72
|
+
if __name__ == "__main__":
|
73
|
+
main()
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
from marilib.marilib_edge import MarilibEdge, EdgeEvent
|
4
|
+
from marilib.communication_adapter import MQTTAdapter, SerialAdapter
|
5
|
+
from marilib.serial_uart import get_default_port
|
6
|
+
|
7
|
+
|
8
|
+
def on_event(event, event_data):
|
9
|
+
"""An event handler for the application."""
|
10
|
+
if event == EdgeEvent.GATEWAY_INFO:
|
11
|
+
return
|
12
|
+
print(".", end="", flush=True)
|
13
|
+
|
14
|
+
|
15
|
+
def main():
|
16
|
+
mari_edge = MarilibEdge(
|
17
|
+
on_event,
|
18
|
+
serial_interface=SerialAdapter(get_default_port()),
|
19
|
+
mqtt_interface=MQTTAdapter("localhost", 1883, is_edge=True),
|
20
|
+
)
|
21
|
+
|
22
|
+
while True:
|
23
|
+
mari_edge.update()
|
24
|
+
if not mari_edge.uses_mqtt:
|
25
|
+
# only generate frames if not using MQTT
|
26
|
+
for node in mari_edge.nodes:
|
27
|
+
mari_edge.send_frame(dst=node.address, payload=b"NORMAL_APP_DATA")
|
28
|
+
print(",", end="", flush=True)
|
29
|
+
statistics = [
|
30
|
+
(f"{node.address:016X}", node.stats.received_rssi_dbm()) for node in mari_edge.nodes
|
31
|
+
]
|
32
|
+
print(f"Stats: {statistics}")
|
33
|
+
time.sleep(3)
|
34
|
+
|
35
|
+
|
36
|
+
if __name__ == "__main__":
|
37
|
+
main()
|
@@ -0,0 +1,156 @@
|
|
1
|
+
import sys
|
2
|
+
import threading
|
3
|
+
import time
|
4
|
+
|
5
|
+
import click
|
6
|
+
from marilib.logger import MetricsLogger
|
7
|
+
from marilib.mari_protocol import MARI_BROADCAST_ADDRESS, Frame
|
8
|
+
from marilib.marilib_edge import MarilibEdge
|
9
|
+
from marilib.model import EdgeEvent, GatewayInfo, MariNode, TestState
|
10
|
+
from marilib.serial_uart import get_default_port
|
11
|
+
from marilib.tui_edge import MarilibTUIEdge
|
12
|
+
from marilib.communication_adapter import SerialAdapter, MQTTAdapter
|
13
|
+
|
14
|
+
LOAD_PACKET_PAYLOAD = b"L"
|
15
|
+
NORMAL_DATA_PAYLOAD = b"NORMAL_APP_DATA"
|
16
|
+
|
17
|
+
|
18
|
+
class LoadTester(threading.Thread):
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
mari: MarilibEdge,
|
22
|
+
test_state: TestState,
|
23
|
+
stop_event: threading.Event,
|
24
|
+
):
|
25
|
+
super().__init__(daemon=True)
|
26
|
+
self.mari = mari
|
27
|
+
self.test_state = test_state
|
28
|
+
self._stop_event = stop_event
|
29
|
+
self.has_rate = False
|
30
|
+
self.delay = None
|
31
|
+
|
32
|
+
def run(self):
|
33
|
+
while not self._stop_event.is_set():
|
34
|
+
# wait for gateway schedule to be available and try to compute rate
|
35
|
+
if not self.has_rate:
|
36
|
+
self.set_rate()
|
37
|
+
if self.delay is None:
|
38
|
+
self._stop_event.wait(0.1) # fixed, waiting for gateway schedule to be available
|
39
|
+
continue
|
40
|
+
|
41
|
+
# once we have rate, send packets at that rate
|
42
|
+
with self.mari.lock:
|
43
|
+
nodes_exist = bool(self.mari.gateway.nodes)
|
44
|
+
|
45
|
+
if nodes_exist:
|
46
|
+
self.mari.send_frame(MARI_BROADCAST_ADDRESS, LOAD_PACKET_PAYLOAD)
|
47
|
+
self._stop_event.wait(self.delay)
|
48
|
+
|
49
|
+
def set_rate(self):
|
50
|
+
if self.test_state.load == 0:
|
51
|
+
return
|
52
|
+
max_rate = self.mari.get_max_downlink_rate()
|
53
|
+
if max_rate == 0:
|
54
|
+
sys.stderr.write("Error computing max rate")
|
55
|
+
return
|
56
|
+
self.test_state.rate = int(max_rate)
|
57
|
+
packets_per_second = max_rate * (self.test_state.load / 100.0)
|
58
|
+
self.delay = 1.0 / packets_per_second if packets_per_second > 0 else float("inf")
|
59
|
+
self.has_rate = True
|
60
|
+
|
61
|
+
|
62
|
+
def on_event(event: EdgeEvent, event_data: MariNode | Frame | GatewayInfo):
|
63
|
+
"""An event handler for the application."""
|
64
|
+
pass
|
65
|
+
|
66
|
+
|
67
|
+
@click.command()
|
68
|
+
@click.option(
|
69
|
+
"--port",
|
70
|
+
"-p",
|
71
|
+
type=str,
|
72
|
+
default=get_default_port(),
|
73
|
+
show_default=True,
|
74
|
+
help="Serial port to use (e.g., /dev/ttyACM0)",
|
75
|
+
)
|
76
|
+
@click.option(
|
77
|
+
"--mqtt-host",
|
78
|
+
"-m",
|
79
|
+
type=str,
|
80
|
+
default="",
|
81
|
+
show_default=True,
|
82
|
+
help="MQTT broker to use (default: empty, no cloud)",
|
83
|
+
)
|
84
|
+
@click.option(
|
85
|
+
"--load",
|
86
|
+
type=int,
|
87
|
+
default=0,
|
88
|
+
show_default=True,
|
89
|
+
help="Load percentage to apply (0–100)",
|
90
|
+
)
|
91
|
+
@click.option(
|
92
|
+
"--log-dir",
|
93
|
+
default="logs",
|
94
|
+
show_default=True,
|
95
|
+
help="Directory to save metric log files.",
|
96
|
+
type=click.Path(),
|
97
|
+
)
|
98
|
+
def main(port: str | None, mqtt_host: str, load: int, log_dir: str):
|
99
|
+
if not (0 <= load <= 100):
|
100
|
+
sys.stderr.write("Error: --load must be between 0 and 100.\n")
|
101
|
+
return
|
102
|
+
|
103
|
+
test_state = TestState(
|
104
|
+
load=load,
|
105
|
+
)
|
106
|
+
|
107
|
+
logger = MetricsLogger(log_dir_base=log_dir, rotation_interval_minutes=1440)
|
108
|
+
|
109
|
+
mari = MarilibEdge(
|
110
|
+
on_event,
|
111
|
+
serial_interface=SerialAdapter(port),
|
112
|
+
mqtt_interface=MQTTAdapter.from_host_port(mqtt_host, is_edge=True) if mqtt_host else None,
|
113
|
+
logger=logger,
|
114
|
+
main_file=__file__,
|
115
|
+
tui=MarilibTUIEdge(test_state=test_state),
|
116
|
+
)
|
117
|
+
|
118
|
+
stop_event = threading.Event()
|
119
|
+
|
120
|
+
mari.latency_test_enable()
|
121
|
+
|
122
|
+
load_tester = LoadTester(mari, test_state, stop_event)
|
123
|
+
if load > 0:
|
124
|
+
load_tester.start()
|
125
|
+
|
126
|
+
try:
|
127
|
+
normal_traffic_interval = 0.5
|
128
|
+
last_normal_send_time = 0
|
129
|
+
|
130
|
+
while not stop_event.is_set():
|
131
|
+
current_time = time.monotonic()
|
132
|
+
|
133
|
+
mari.update()
|
134
|
+
|
135
|
+
mari.render_tui()
|
136
|
+
|
137
|
+
if current_time - last_normal_send_time >= normal_traffic_interval:
|
138
|
+
if mari.nodes:
|
139
|
+
mari.send_frame(MARI_BROADCAST_ADDRESS, NORMAL_DATA_PAYLOAD)
|
140
|
+
last_normal_send_time = current_time
|
141
|
+
|
142
|
+
time.sleep(0.1)
|
143
|
+
|
144
|
+
except KeyboardInterrupt:
|
145
|
+
pass
|
146
|
+
finally:
|
147
|
+
stop_event.set()
|
148
|
+
mari.latency_test_disable()
|
149
|
+
if load_tester.is_alive():
|
150
|
+
load_tester.join()
|
151
|
+
mari.close_tui()
|
152
|
+
mari.logger.close()
|
153
|
+
|
154
|
+
|
155
|
+
if __name__ == "__main__":
|
156
|
+
main()
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
from marilib.serial_hdlc import (
|
4
|
+
HDLCDecodeException,
|
5
|
+
HDLCHandler,
|
6
|
+
HDLCState,
|
7
|
+
hdlc_encode,
|
8
|
+
)
|
9
|
+
from marilib.serial_uart import SerialInterface
|
10
|
+
|
11
|
+
BAUDRATE = 1000000
|
12
|
+
# BAUDRATE = 115200
|
13
|
+
|
14
|
+
hdlc_handler = HDLCHandler()
|
15
|
+
|
16
|
+
|
17
|
+
def on_byte_received(byte):
|
18
|
+
# print(f"Received byte: {byte}")
|
19
|
+
hdlc_handler.handle_byte(byte)
|
20
|
+
if hdlc_handler.state == HDLCState.READY:
|
21
|
+
try:
|
22
|
+
payload = hdlc_handler.payload
|
23
|
+
print(f"Received payload: {payload.hex()}")
|
24
|
+
except HDLCDecodeException as e:
|
25
|
+
print(f"Error decoding payload: {e}")
|
26
|
+
|
27
|
+
|
28
|
+
serial_interface = SerialInterface("/dev/ttyACM0", BAUDRATE, on_byte_received)
|
29
|
+
|
30
|
+
|
31
|
+
while True:
|
32
|
+
time.sleep(1)
|
33
|
+
payload = b"AAA"
|
34
|
+
print(f"Sending payload: {payload.hex()}")
|
35
|
+
serial_interface.write(hdlc_encode(payload))
|