Skip to content

Ceddan/cedlora

Repository files navigation

cedlora

Python tooling for a three-gateway LoRaWAN stack — REST/CGI clients, CS4 gRPC-web client, MQTT subscribers, Milesight sensor decoders, and a TimescaleDB sink with a live dashboard.


Stack

layer component gateway
Gateway A Milesight UG63-868M (OpenWrt, ChirpStack v3)
Gateway B Milesight UG65-868M (LEDE, loraserver 4.0.6)
Gateway C RAK7391 WisGate Connect (RAKPiOS, ChirpStack v4)
Network server ChirpStack v3 (UG63/UG65) + CS4 native systemd (RAK7391)
Central broker Mosquitto on propella-server — bridges pull from UG65 + RAK7391 :1883
Persistence TimescaleDB hypertable uplinks on propella-server port 5433
Dashboard FastAPI + ceduix UI served on port 8000 http://propella:8000
Decoders Milesight-IoT/SensorDecoders (git submodule) + Node subprocess shared

Repository layout

path what
ug63/ UG63 client (REST + CGI + SSH), MQTT subscriber, CLI
ug65/ UG65 client (REST read-only), MQTT subscriber
cs4/ RAK7391 CS4 gRPC-web client + MQTT subscriber
cedlora/ Shared API (FastAPI), TimescaleDB sink, MQTT orchestration
ug63/sensors/ Decoder registry, canonical.py Node runner, _runner.js
vendor/milesight-decoders/ Official Milesight JS decoders (git submodule)
frontend/ dashboard.html (live charts + uplinks table), topology.html
docs/ API maps, device registry, gateway SSOT docs
migrations/ TimescaleDB schema migrations (.sql, numbered)
infra/ chirpstack-gateway-bridge config
scripts/ env-sync.sh, env-verify.sh, probe_cs4_wire.py, add_sensor.py
.claude/ LLM-agent context — rules, lexicon, stack-map, plans, decisions

Quick start

git clone --recurse-submodules <repo>
cd cedlora
python3 -m venv .venv && source .venv/bin/activate
pip install -e .

cp .env.example .env   # fill in credentials
cedlora tail           # stream uplinks from all gateways → TimescaleDB
cedlora serve          # dashboard at http://localhost:8000

Environment

cp .env.example .env
# edit .env — required vars:
#   DATABASE_URL   postgresql://cedlora:<pw>@<host>:5433/cedlora
#   CS4_HOST       http://<rak7391>:8080
#   CS4_API_KEY    create with: ssh rak@<rak7391> sudo chirpstack --config /etc/chirpstack create-api-key --name <name>
#   GW_HOST        http://<ug63-ip>
#   GW_PASS        UG63 admin password
#   LORAWAN_APP_KEY  32-char uppercase hex

LLM-agent entrypoint

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, auth, sensors, CS4, persistence, frontend)
cedlora-stack-map.md word topology — 8 layers, 9 roads, boundary-word table
cedlora-word-standards.md per-word SSOT + SOTA + rule + shape + verify + anti
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).


Status

Last updated: 2026-04-29

area status notes
UG63 REST + CGI ✅ operational REST + CGI + SSH all working
UG63 MQTT ⚠️ blocked CONNACK rc=5 — credential issue not yet resolved
UG65 MQTT ✅ operational uplinks flowing via central broker bridge
CS4 gRPC-web client ✅ operational provision_device_otaa, list tenants/apps/devices/gateways
CS4 MQTT stream ✅ operational eu868/gateway/+/event/up via propella bridge
TimescaleDB sink ✅ operational non-blocking queue, batched writes, health endpoint
Sensor decoders ✅ operational EM400-MUD, AM107 confirmed; full Milesight catalog vendored
Device registry ✅ operational DB catalog + docs/devices/ MD mirror + drift gate
Dashboard ✅ operational series charts (ced-charts), uplinks table, crosshair sync
Topology page ✅ done LR node tree — sensor → gateways → NS → broker → DB → UI
Autonomous OTAA provisioning ✅ done code-only via cs4/client.py:provision_device_otaa
UG63 MQTT fix 🔲 pending auth rc=5 root cause unknown; plan FEAT-ug63-llm-native

Changelog

2026-04-29 — CS4 provisioning + device registry + dashboard

  • cs4/client.py rewritten with [PROBED] field numbers from live JS bundle — full Create chain, idempotent provision_device_otaa, delete_device
  • Device catalog table (migrations/001_devices.sql) + cedlora/db.py register/get/list/delete
  • docs/devices/ — per-device MD files (frontmatter + restore SQL); scripts/verify-devices-ssot.py CI gate
  • Dashboard charts: ced-charts crosshair sync fixed — dispatches domain time value, receivers compute nearest index via binary search (was: local array index → wrong timestamp across charts)
  • Topology page: LR node tree (sensor → 3 gateways → 3 NS → broker → decoder → DB → API/UI)
  • API /topology route + /api/health endpoint (DB + CS4 liveness)
  • scripts/probe_cs4_wire.py — extracts CS4 proto field numbers from the live frontend JS bundle

2026-04-28 — UG65 + central broker + TimescaleDB + dashboard MVP

  • UG65 MQTT operational (loraserver, propella bridge)
  • Central broker on propella bridges UG65:1883 + RAK7391:1883 → propella:1883
  • TimescaleDB non-blocking sink — queue, batched flush, ThreadedConnectionPool, health()
  • Frame type classification + sentinel filtering
  • Dashboard MVP: ced-charts area charts per sensor field + uplinks table
  • env SSOT model: docs/env-runtime.md + scripts/env-sync.sh + scripts/env-verify.sh

2026-04-27 — RAK7391 CS4 native install

  • ChirpStack v4 native systemd install on RAKPiOS (no Docker)
  • pg_trgm extension required before first migration (documented in auto-improve)
  • CS4 gRPC-web client: discovered port 8080 only (not 8090), /api.{Service}/{Method} paths
  • api-key Bearer auth — static JWT from chirpstack create-api-key
  • Mosquitto bridge direction: propella initiates (gateway can't reach propella — AP isolation)

2026-04-28 — UG63 client + sensor decoders

  • UG63 REST + CGI client (AES-mangle auth, JWT-cookie)
  • Milesight decoder submodule + canonical.py Node subprocess runner
  • cedlora tail CLI entry point
  • Initial pytest smoke tests

Architecture decisions

decisions.md + .claude/decisions/decisions.md — every non-trivial choice documented with criteria, alternatives considered, reversal condition, and falsifier.

Key decisions recorded:

  • TimescaleDB over InfluxDB/SQLite (query patterns, existing infra, no external dependency)
  • propella as deploy host (DB + broker co-located, survives WSL restarts)
  • Central MQTT broker with inbound bridges (gateway AP isolation — gateways can't reach propella)
  • CS4 gRPC-web via handcrafted protobuf (no .proto files in deployed CS4 native install)

Contributing / dev

ruff check .          # linting
pytest tests/         # smoke tests (requires LAN access to gateways)

Tests are smoke-only — they hit real gateways on the LAN. No mock DB tests by design (see auto-improve.md 2026-04-28 SOTA performance rule).

About

LoRaWAN gateway tooling — UG63 (CS3) + UG65 (loraserver) + RAK7391 (CS4) — Python clients, sensor decoders, TimescaleDB sink, live dashboard

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors