README dashboard
version 0.2
TTN Dashboard – Nutzung, Konfiguration und Deployment
Dieses Repository erzeugt aus The Things Network (TTN) Storage ein interaktives HTML-Dashboard.
Das Script erkennt die Devices, zieht deren Uplinks, persistiert pro Gerät Parquet/CSV und baut eine HTML-Ansicht mit Plotly.
Neu in v0.2: - Strikte Trennung: Templates & CSS (versioniert) vs. Runtime-Builds (nicht versioniert). - Template + CSS aus assets/templates/ (Python enthält keinen HTML/CSS-Fallback mehr). - Runtime-Ausgabe nach assets/build/ (statt assets/). - Offline-Modus: Dashboard rendern ohne Netzwerkzugriffe (liest lokale data/). - Vorsichtige Defaults gegen Rate-Limits (Backoff + Jitter).
Verzeichnisstruktur
assets/
templates/
dashboard\_template.html # Dein HTML-Template
dashboard.css # Zentrales CSS (Info-Tab einspaltig)
build/
data.html # Laufzeit-Build (vom Script erzeugt)
debug.html
devices\_used.txt
devices\_used.csv
data/ # Parquet/CSV/NDJSON pro Device (lokal, nicht versioniert)
scripts/
pull\_all\_devices.py # Python-Script
docs/
assets/ # wird aus assets/build/ gespiegelt (für Pages)
data/ # gepushte Daten-Artefakte (optional)
Wichtig:
assets/build/unddata/sind Runtime-Verzeichnisse und gehören in.gitignore.
Empfohlene .gitignore (Root):
/data/
/assets/build/
/_site/
/.quarto/
__pycache__/
*.pyc
Quickstart - lokal
- .env (optional) neben
scripts/pull_all_devices.pyanlegen:
TTN_APP_ID=gisma-hydro-testbed
TTN_REGION=eu1
TTN_API_KEY=NNSXS_...
RUN_DASH=1
TTN_AFTER_DAYS=2
# Sicherer Standard gegen Rate-Limits:
DELAY_BETWEEN_DEVICES=2.0
JITTER_MAX_SECONDS=0.7
# Health / Filter
STALE_HOURS=3
# DEV_INCLUDE=dds75-lb-.*
# DEV_EXCLUDE=
- Template & CSS bereitstellen:
assets/templates/dashboard_template.htmlassets/templates/dashboard.css
Dein Template bindet die CSS z. B. so ein:
<link rel="stylesheet" href="dashboard.css?v=1">- Ausführen:
python scripts/pull_all_devices.py- Ergebnis:
assets/build/data.html(Dashboard)assets/build/debug.html- Health:
assets/build/devices_used.txt&assets/build/devices_used.csv - Daten (pro Gerät) in
data/<device>.parquet(+.csv,*_raw.ndjson)
Offline-Modus (ohne TTN Zugriffe)
Ideal zum Styling/Debuggen ohne Netzlast:
OFFLINE=1 RUN_DASH=1 python scripts/pull_all_devices.py- Es werden keine HTTP-Requests ausgeführt.
- Das Dashboard liest nur vorhandene
data/*.parquet/.csv.
Einbettung per <iframe>
Wenn du assets/build/data.html in einer Seite einbindest:
<div class="iframe-wrap">
<iframe id="ttn-dash" src="assets/build/data.html"
loading="lazy" width="100%" style="border:0;" height="900"
referrerpolicy="no-referrer"></iframe>
</div>Optionale Auto-Resize-Logik für die Hostseite (falls dein Template postMessage sendet):
<script>
window.addEventListener('message', (ev) => {
if (!ev || !ev.data || ev.data.type !== 'TTN_IFRAME_SIZE') return;
const ifr = document.getElementById('ttn-dash');
if (ifr && ev.data.height) ifr.style.height = (ev.data.height + 40) + 'px';
});
</script>Environment-Variablen
Pflicht
TTN_APP_ID– TTN Application IDTTN_REGION– TTN Cluster (eu1, …)TTN_API_KEY– NNSXS-Key (Scopes: View devices, View packages/storage, Read traffic)
Optional (Abruf/Backoff)
TTN_AFTER_DAYS(default 2) – Zeitfenster; script setzt bei vorhandenen Parquet am letzten TS fortDEVICES– Whitespace-Liste; wenn leer → Auto-DiscoveryDELAY_BETWEEN_DEVICES(default 2.0 s) – Pause pro DeviceJITTER_MAX_SECONDS(default 0.7) – Zufalls-Jitter zusätzlich zur PauseMAX_RETRIES(default 5) – HTTP-RetriesBACKOFF_BASE(default 1.2) – Exponentieller Backoff-Start
Optional (Health/Debug)
STALE_HOURS(default 3)DEV_INCLUDE(default.*)DEV_EXCLUDE(leer)DEBUG_RECENT_MINUTES(default 90)RAW_APPEND(0/1) –_raw.ndjsonanhängen statt überschreibenRAW_SNAPSHOT(0/1) – zusätzliche Schnappschüsse
Steuerung
RUN_DASH(1/0) – 1 = HTML bauen, 0 = nur CLI/Smoke-TestOFFLINE(1/0) – 1 = nur lokale Daten, kein HTTPASSETS_BUILD_SUBDIR(defaultbuild) – Zielunterordner inassets/
Template & CSS
Kein HTML/CSS-Fallback im Python-Script mehr – das Template muss existieren:
assets/templates/dashboard_template.htmlassets/templates/dashboard.css
Der Info-Tab ist im CSS einspaltig (
.info-grid { grid-template-columns: 1fr; }oder via Media-Queries).Tabs/Interaktionen sind im Template implementiert (kleiner JS-Block). Das Python-Script rendert nur Dateninhalte (Kacheln, Tabellen, Plots) in Platzhalter:
{OVERVIEW_CARDS},{OVERVIEW_TABLE},{TYPE_CARDS},{DEBUG_CARDS},{STAMP},{APP},{REG},{AFTER_DAYS},{AFTER}
GitHub Actions
Ein kombinierter Workflow holt stündlich Daten und rendert die Site (Pages):
name: TTN + Quarto Render (hourly)
on:
schedule:
- cron: "0 * * * *" # jede volle Stunde (UTC)
push:
branches: ["main"] # Content-Pushes bauen ebenfalls (mit denselben Regeln)
workflow_dispatch:
concurrency:
group: pages-build
cancel-in-progress: true
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
env:
TTN_APP_ID: ${{ secrets.TTN_APP_ID }}
TTN_REGION: ${{ secrets.TTN_REGION }}
TTN_API_KEY: ${{ secrets.TTN_API_KEY }}
ASSETS_BUILD_SUBDIR: build
DEBUG_RECENT_MINUTES: "90"
# Vorsichtige Defaults (kannst du per Secrets/ENV überschreiben)
DELAY_BETWEEN_DEVICES: "2.0"
JITTER_MAX_SECONDS: "0.7"
steps:
- name: Checkout (full history)
uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Setup Python
uses: actions/setup-python@v5
with: { python-version: "3.12" }
- name: Install Python deps
run: |
python -m pip install --upgrade pip wheel setuptools
pip install requests pandas plotly pyarrow python-dotenv
- name: Pull + build dashboard
run: |
set -e
RUN_DASH=1 TTN_AFTER_DAYS=7 python scripts/pull_all_devices.py
mkdir -p docs/assets
rsync -av assets/build/ docs/assets/
- name: Copy data artifacts (optional to Pages)
run: |
mkdir -p docs/data
cp -a data/*.parquet docs/data/ 2>/dev/null || true
cp -a data/*.csv docs/data/ 2>/dev/null || true
cp -a data/*.ndjson docs/data/ 2>/dev/null || true
- name: Build data index
run: |
python - << 'PY'
import json, pathlib
d = pathlib.Path("docs/data"); d.mkdir(parents=True, exist_ok=True)
items = []
for p in sorted(d.glob("*")):
suf = p.suffix.lower()
if suf not in (".parquet", ".csv", ".ndjson"): continue
st = p.stat()
items.append({"name": p.name, "size": st.st_size, "mtime": int(st.st_mtime),
"type": "Parquet" if suf==".parquet" else ("NDJSON" if suf==".ndjson" else "CSV")})
(d/"index.json").write_text(json.dumps(items, ensure_ascii=False), encoding="utf-8")
PY
- name: Setup Quarto
uses: quarto-dev/quarto-actions/setup@v2
- name: Render site
run: |
quarto render --no-cache
touch docs/.nojekyll
- name: Commit docs/ only
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/
git commit -m "site build $(date -u +%Y-%m-%dT%H:%M:%SZ)" || echo "no changes"
git pushHinweise
data/undassets/build/bleiben unversioniert. Es wird nurdocs/gepusht.- Content-Pushes (z. B. Template/CSS/Text) triggern ebenfalls ein Build: Script läuft, generiert Runtime-Assets neu, rendert Quarto und pusht
docs/.
Troubleshooting
Untracked würden überschrieben (git pull) Ursache:
data//assets/build/wurden versioniert. Lösung:data/&assets/build/sichern → aus dem Index entfernen → in.gitignore.- Nur
docs/committen (siehe Workflow).
HTTP 429 (Rate-Limit)
DELAY_BETWEEN_DEVICESauf 1.0–3.0 erhöhen,JITTER_MAX_SECONDS> 0 setzen. Backoff besteht ausBACKOFF_BASE * 2^i + jitter.Keine Daten / leeres Dashboard Prüfe Scopes des API-Keys, TTN Storage-Retention, Devices aktiv, Zeitfenster.
Offline testen
OFFLINE=1setzen. Das Script liest nur lokale Parquet/CSV und baut das Dashboard.
CLI Smoke-Test
Ohne Dashboard, einzelnes Device, kleines Fenster -> schnelle Diagnose, ob alles funktioniert – bevor du die stündlichen Builds/Pages anstößt.
RUN_DASH=0 python scripts/pull_all_devices.py --device dds75-lb-13 --hours 6 -v--hoursüberschreibtTTN_AFTER_DAYSnur für diesen Lauf.- Parquet/CSV werden dedupliziert fortgeführt.
Design-Notizen
- Auto-Discovery → keine Device-Liste pflegen (außer du setzt
DEVICES!!). - Pro-Device Parquet → schnelle inkrementelle Pulls.
- Robuste Requests → Retries/Backoff + Jitter.
- Saubere Trennung von Template/CSS (versioniert) und Runtime-Builds (ignoriert).
🌲📡 Viel Erfolg beim Monitoring!