Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5039ee2902
|
||
|
|
f9e23f45ef
|
||
|
|
61eb2cfeba
|
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# DongLoRa Examples
|
||||
|
||||
Example scripts demonstrating the [Python client library](https://github.com/donglora/client-py).
|
||||
Example scripts demonstrating the [Python client library](https://git.swaits.com/donglora/client-py).
|
||||
|
||||
## Running
|
||||
|
||||
@@ -15,4 +15,4 @@ just bridge --mode server # LoRa bridge over TCP
|
||||
|
||||
## Depends On
|
||||
|
||||
- [`donglora`](https://github.com/donglora/client-py) — the Python client library
|
||||
- [`donglora`](https://git.swaits.com/donglora/client-py) — the Python client library
|
||||
@@ -18,9 +18,6 @@ dev = ["ruff"]
|
||||
[tool.uv]
|
||||
package = false
|
||||
|
||||
[tool.uv.sources]
|
||||
donglora = { path = "../client-py", editable = true }
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py310"
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify the dongle applies ``LoRaConfig.sync_word`` to the radio.
|
||||
|
||||
Two roles. Run on TWO SEPARATE PHYSICAL DONGLES (not the same dongle
|
||||
shared via donglora-mux — a single chip can only hold one sync word at
|
||||
a time, and the mux delivers local TX back to other sessions as
|
||||
LOCAL_LOOPBACK events that bypass the chip's sync filter). Pass an
|
||||
explicit port path to each invocation so neither side discovers the
|
||||
mux socket:
|
||||
|
||||
# Dongle A — pin to one sync word, listen:
|
||||
python examples/sync_word_test.py --role rx /dev/ttyACM0 [--sync 0x5464]
|
||||
|
||||
# Dongle B — rotate through several sync words, transmitting on each:
|
||||
python examples/sync_word_test.py --role tx /dev/ttyACM1
|
||||
|
||||
Expected behavior with the firmware fix:
|
||||
RX hears ONLY the cycles whose sync word matches its own
|
||||
(one packet per rotation pass through SYNC_WORDS), and packets
|
||||
have realistic RSSI / SNR (not 0.0 / 0.0).
|
||||
|
||||
Without the fix:
|
||||
RX hears every transmission regardless of which sync word the TX
|
||||
side configured — the firmware was silently ignoring the field
|
||||
and leaving the SX126x on the boot-time default 0x1424.
|
||||
|
||||
The default RX sync word (0x5464) is deliberately off the MeshCore
|
||||
(0x1424) and LoRaWAN-public (0x3444) values so the test isn't polluted
|
||||
by ambient mesh traffic. The RX role drops `LOCAL_LOOPBACK` events
|
||||
defensively so accidental mux sharing fails loud (zero packets) rather
|
||||
than printing misleading "all sync words received" output.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import time
|
||||
|
||||
import donglora as dl
|
||||
from _common import MESHCORE_US
|
||||
|
||||
# SX126x register-encoded sync words. Each is (hi << 8) | lo where
|
||||
# [hi, lo] = convert_sync_word(byte) for the listed underlying byte:
|
||||
# byte 0x12 → 0x1424 (LoRa "private" — MeshCore default)
|
||||
# byte 0x34 → 0x3444 (LoRaWAN "public")
|
||||
# byte 0x56 → 0x5464
|
||||
# byte 0x78 → 0x7484
|
||||
# byte 0xAB → 0xA4B4
|
||||
# byte 0xCD → 0xC4D4
|
||||
SYNC_WORDS = [0x1424, 0x3444, 0x5464, 0x7484, 0xA4B4, 0xC4D4]
|
||||
TX_PAUSE_S = 1.5
|
||||
|
||||
parser = argparse.ArgumentParser(description="Sync-word verification test")
|
||||
parser.add_argument("--role", choices=["tx", "rx"], required=True)
|
||||
parser.add_argument(
|
||||
"--sync",
|
||||
type=lambda x: int(x, 0),
|
||||
default=0x5464,
|
||||
help="(rx) sync word to pin to, e.g. 0x5464 (default; avoids MeshCore noise)",
|
||||
)
|
||||
parser.add_argument("port", nargs="?", help="Serial port (auto-detect if omitted)")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.role == "tx":
|
||||
with dl.connect(args.port, config=MESHCORE_US) as d:
|
||||
print(
|
||||
f"TX: rotating {len(SYNC_WORDS)} sync words, "
|
||||
f"{TX_PAUSE_S}s between (Ctrl+C to stop)\n"
|
||||
)
|
||||
try:
|
||||
cycle = 0
|
||||
while True:
|
||||
for sw in SYNC_WORDS:
|
||||
cfg = dataclasses.replace(MESHCORE_US, sync_word=sw)
|
||||
d.set_config(cfg)
|
||||
msg = f"cycle={cycle} sync=0x{sw:04X}"
|
||||
td = d.tx(msg.encode())
|
||||
print(
|
||||
f" TX sync=0x{sw:04X} {msg!r} ({td.airtime_us} µs)"
|
||||
)
|
||||
time.sleep(TX_PAUSE_S)
|
||||
cycle += 1
|
||||
except KeyboardInterrupt:
|
||||
print("\nDone.")
|
||||
else:
|
||||
sw = args.sync
|
||||
cfg = dataclasses.replace(MESHCORE_US, sync_word=sw)
|
||||
with dl.connect(args.port, config=cfg) as d:
|
||||
print(f"RX: pinned to sync=0x{sw:04X} (Ctrl+C to stop)")
|
||||
print("Only matching-sync-word transmissions should arrive.\n")
|
||||
try:
|
||||
for pkt in d.rx():
|
||||
if pkt.origin != dl.RxOrigin.OTA:
|
||||
# Drop mux-injected local-loopback events. They
|
||||
# arrive without going through the chip's sync
|
||||
# filter and would falsely "match" every sync
|
||||
# word the TX side rotates through.
|
||||
continue
|
||||
text = pkt.data.decode("utf-8", errors="replace")
|
||||
print(
|
||||
f" RX: {text!r} RSSI:{pkt.rssi_dbm:.1f}dBm "
|
||||
f"SNR:{pkt.snr_db:.1f}dB"
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
print("\nDone.")
|
||||
@@ -170,11 +170,15 @@ wheels = [
|
||||
[[package]]
|
||||
name = "donglora"
|
||||
version = "1.0.0"
|
||||
source = { editable = "../client-py" }
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cobs" },
|
||||
{ name = "pyserial" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/f1/04a6befb8dac4e0d4b5be15f9589ac7800e15e5cf415fbf268e320a43aa3/donglora-1.0.0.tar.gz", hash = "sha256:6203ff8239a9d8a8e572d677dcd7783b457c2dd6e3dd1ae1b43eac670c7a86a9", size = 93130, upload-time = "2026-04-22T15:55:22.923Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/53/982d9ef76efae11c56ccaa4f92096d9849addb78111399d4c02d46cd3515/donglora-1.0.0-py3-none-any.whl", hash = "sha256:b2cae03149b54a7676c0c8e4850bcbd99d36edd6a4606aeb1980d50a0f5d94fe", size = 30566, upload-time = "2026-04-22T15:55:21.742Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
meshcore = [
|
||||
@@ -187,25 +191,6 @@ orac = [
|
||||
{ name = "pynacl" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "anthropic", marker = "extra == 'orac'" },
|
||||
{ name = "cobs" },
|
||||
{ name = "pycryptodome", marker = "extra == 'meshcore'" },
|
||||
{ name = "pycryptodome", marker = "extra == 'orac'" },
|
||||
{ name = "pynacl", marker = "extra == 'meshcore'" },
|
||||
{ name = "pynacl", marker = "extra == 'orac'" },
|
||||
{ name = "pyserial" },
|
||||
]
|
||||
provides-extras = ["meshcore", "orac"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "donglora-examples"
|
||||
version = "1.0.0"
|
||||
@@ -229,9 +214,9 @@ dev = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "donglora", editable = "../client-py" },
|
||||
{ name = "donglora", extras = ["meshcore"], marker = "extra == 'meshcore'", editable = "../client-py" },
|
||||
{ name = "donglora", extras = ["orac"], marker = "extra == 'orac'", editable = "../client-py" },
|
||||
{ name = "donglora", specifier = ">=1.0.0,<2.0.0" },
|
||||
{ name = "donglora", extras = ["meshcore"], marker = "extra == 'meshcore'", specifier = ">=1.0.0,<2.0.0" },
|
||||
{ name = "donglora", extras = ["orac"], marker = "extra == 'orac'", specifier = ">=1.0.0,<2.0.0" },
|
||||
]
|
||||
provides-extras = ["meshcore", "orac"]
|
||||
|
||||
|
||||
Reference in new issue
Block a user