Python tooling for a RAK7391 / ChirpStack v4 LoRaWAN edge — gRPC-web client with idempotent OTAA provisioning, MQTT subscriber, Milesight + Elsys sensor decoders, RAK-local SQLite uplink store, and a live dashboard.
| layer | component |
|---|---|
| Gateway | RAK7391 WisGate Connect on Raspberry Pi OS Lite Trixie (cedlora-edge-v1 target) |
| Network server | ChirpStack v4 native systemd on RAK7391 — gRPC-web on :8080, MQTT on :1883 |
| Persistence | RAK-local SQLite WAL at CEDLORA_DB_PATH; DuckDB optional for analytics |
| Dashboard | FastAPI app (cedlora.api:app) — ced-charts series + live uplinks table |
| Updates | Native apt/systemd workflow; no Docker or Portainer |
| Decoders | Milesight-IoT/SensorDecoders (git submodule) + Elsys (vendored) + Node subprocess |
| path | what |
|---|---|
docs/INDEX.md |
grep route index: active owners, historical-only files, generated/cache/build paths, and field-role workflows |
cedlora/ |
RAK-local SQLite store, CS4 MQTT subscriber, FastAPI dashboard, CLI, sensor decode pipeline |
cedlora/sensors/ |
Decoder registry, canonical.py Node runner, _runner.js, handler.py |
cs4/ |
RAK7391 CS4 gRPC-web client (idempotent provision_* chain, OTAA, devices, gateways) |
vendor/milesight-decoders/ |
Official Milesight JS decoders (git submodule) |
vendor/third-party/ |
Elsys and other third-party sensor decoders |
frontend/ |
dashboard.html (live charts + uplinks table), topology.html, blog.html |
docs/ |
device registry (devices/<eui>.md), site SSOT (sites/<id>.md), gateway SSOT |
migrations/ |
SQLite + legacy PostgreSQL migrations |
scripts/ |
provision-sensor.sh, add_sensor.py, env-sync.sh, env-verify.sh, probe_cs4_wire.py |
crates/ |
Rust edge runtime (cedlora-protocol + cedlora-edge: uplink-publisher, edge-ui) |
.claude/ |
LLM-agent context — rules, lexicon, stack-map, plans, decisions |
git clone --recurse-submodules <repo>
cd cedlora
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
cp .env.example .env # fill in CS4_HOST + CS4_API_KEY
cedlora tail # stream uplinks from RAK-local CS4 MQTT → SQLite
cedlora serve # dashboard at http://localhost:8000Native RAK update dry-run:
scripts/rak-native-update.sh --host [email protected] --dry-runProvision an OTAA sensor:
bash scripts/provision-sensor.sh \
--eui <16-hex> --key <32-hex> \
--join-eui <16-hex> \
--model elsys-ers2-lite \
--site cedlora-test-01cp .env.example .env
# edit .env — required vars:
# CS4_HOST http://<rak7391-wg-ip>:8080
# CS4_API_KEY create with: ssh rak@<rak7391-wg-ip> sudo chirpstack --config /etc/chirpstack create-api-key --name cedlora
# CEDLORA_DB_PATH /var/lib/cedlora/cedlora.db on RAK; .data/cedlora.db for dev
# MQTT_BROKER localhost on RAK; <rak7391-wg-ip> from WSL dev
# LORAWAN_APP_KEY 32-char uppercase hex (default AppKey for batch provisioning)CLAUDE.md is the agent boot card — route table for every domain concept, stack table, veto list.
.claude/rules/ contains the domain vocabulary:
| file | what |
|---|---|
cedlora-lexicon.md |
canonical words per domain (LoRaWAN, sensors, CS4, persistence, frontend) |
cedlora-stack-map.md |
word topology — layers + roads + boundary-word table |
cedlora-word-standards.md |
per-word SSOT + SOTA + rule + shape + verify + anti |
cedlora-edge-contract.md |
identity, IP plan, services, ingest envelope, banned patterns, falsifiers |
auto-improve.md |
append-only session learnings (corrections + calibration results) |
.claude/plans/open/ — active feature plans.
.claude/decisions/ — architecture decisions (append-only, criteria + alternatives + falsifier).
Field-role routes live in docs/INDEX.md: electrician gateway health, plumber placement handoff, installer OTAA provision + join proof, property technician dashboard checks, and software maintainer gates/bootstrap.
Last updated: 2026-05-21
| area | status | notes |
|---|---|---|
| CS4 gRPC-web client | ✅ operational | provision_tenant/app/profile/device, OTAA chain, idempotent find-or-create |
| CS4 MQTT stream | ✅ operational | RAK-local Mosquitto; dashboard ingest subscribes to application/+/device/+/event/up; gateway bridge publishes {region}/gateway/+/event/up upstream of CS4 |
| RAK-local persistence | ✅ operational | SQLite WAL via cedlora/db.py; non-blocking sink, batched flush |
| Sensor decoders | ✅ operational | Milesight EM/AM/CT catalog + Elsys ERS2 Lite vendored |
| Device registry | ✅ operational | DB catalog + docs/devices/<eui>.md + drift gate |
| Dashboard | FastAPI app exists; target deployment is on the RAK | |
| Topology page | ✅ done | LR node tree — sensor → gateway → NS → broker → DB → UI |
| Autonomous OTAA provisioning | ✅ done | scripts/provision-sensor.sh + cs4/client.py:provision_device_otaa, idempotent |
| Gateway register | gw.create_gateway() not yet in client; use ChirpStack UI or INSERT INTO gateway for now |
|
| WireGuard mesh | ✅ operational | cedlora-test-01 at 10.30.0.12 via koligo hub 89.167.42.17:51820 |
- RAK7391 freshly imaged with Raspberry Pi OS Lite Trixie; static eth0=10.42.0.1, WG=10.30.0.12
- LoRa data flow proven end-to-end: lora-pkt-fwd → gateway-bridge → MQTT → CS4 → SQLite
- CS4 cleaned to 1 tenant (
cedlora-test-01); single admin api-key (cedlora); gateway0016c001f1563ea5registered - Milesight UG63 + UG65 codepaths removed — RAK7391/CS4 is the only gateway/NS stack going forward
ug63/sensors/relocated tocedlora/sensors/(gateway-agnostic decode)
- Runtime moved off propella-PC dependency: RAK7391 owns CS4, MQTT, persistence, dashboard
- RAK runtime is native systemd/apt only; Docker/Portainer is not an accepted runtime or update path
- Live probe: RAK has ChirpStack 4.18, gateway-bridge, Mosquitto, PostgreSQL, Redis active
DATABASE_URLno longer required for normal runtime;CEDLORA_DB_PATHis the active storage knob
cs4/client.pywith [PROBED] field numbers from live JS bundle — full Create chain, idempotent provision_device_otaa, delete_device- Device catalog table +
docs/devices/MD mirror +scripts/verify-devices-ssot.pydrift gate - Dashboard charts: ced-charts crosshair sync fixed (dispatches domain time value, receivers compute nearest index)
- API
/topology+/api/healthendpoints scripts/probe_cs4_wire.py— extracts CS4 proto field numbers from the live frontend JS bundle
- ChirpStack v4 native systemd install on RAKPiOS (no Docker)
- pg_trgm extension required before first migration (documented in auto-improve)
- CS4 gRPC-web client: port 8080 only (not 8090), /api.{Service}/{Method} paths
- api-key Bearer auth — static JWT from
chirpstack create-api-key
decisions.md + .claude/decisions/ — every non-trivial choice documented with criteria, alternatives, reversal condition, and falsifier.
Key decisions:
- RAK-local SQLite WAL for live edge persistence; DuckDB optional for analytics; Timescale/PostgreSQL not load-bearing
- RAK7391 as deploy host for normal runtime; PC/propella is no longer a runtime dependency
- Central MQTT broker on RAK7391
- CS4 gRPC-web via handcrafted protobuf (no .proto files in deployed CS4 native install)
- Milesight UG63 + UG65 superseded 2026-05-21 — RAK7391/CS4 is the only gateway stack
ruff check . # linting
pytest tests/ # smoke tests (requires LAN/WG access to RAK7391)