Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5039ee2902
|
||
|
|
f9e23f45ef
|
||
|
|
61eb2cfeba
|
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
# DongLoRa Examples
|
# 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
|
## Running
|
||||||
|
|
||||||
@@ -15,4 +15,4 @@ just bridge --mode server # LoRa bridge over TCP
|
|||||||
|
|
||||||
## Depends On
|
## 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]
|
[tool.uv]
|
||||||
package = false
|
package = false
|
||||||
|
|
||||||
[tool.uv.sources]
|
|
||||||
donglora = { path = "../client-py", editable = true }
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = "py310"
|
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]]
|
[[package]]
|
||||||
name = "donglora"
|
name = "donglora"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = { editable = "../client-py" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cobs" },
|
{ name = "cobs" },
|
||||||
{ name = "pyserial" },
|
{ 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]
|
[package.optional-dependencies]
|
||||||
meshcore = [
|
meshcore = [
|
||||||
@@ -187,25 +191,6 @@ orac = [
|
|||||||
{ name = "pynacl" },
|
{ 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]]
|
[[package]]
|
||||||
name = "donglora-examples"
|
name = "donglora-examples"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -229,9 +214,9 @@ dev = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "donglora", editable = "../client-py" },
|
{ name = "donglora", specifier = ">=1.0.0,<2.0.0" },
|
||||||
{ name = "donglora", extras = ["meshcore"], marker = "extra == 'meshcore'", editable = "../client-py" },
|
{ name = "donglora", extras = ["meshcore"], marker = "extra == 'meshcore'", specifier = ">=1.0.0,<2.0.0" },
|
||||||
{ name = "donglora", extras = ["orac"], marker = "extra == 'orac'", editable = "../client-py" },
|
{ name = "donglora", extras = ["orac"], marker = "extra == 'orac'", specifier = ">=1.0.0,<2.0.0" },
|
||||||
]
|
]
|
||||||
provides-extras = ["meshcore", "orac"]
|
provides-extras = ["meshcore", "orac"]
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user