README dashboard

version 0.2

Author

Chris Reudenbach

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/ und data/ sind Runtime-Verzeichnisse und gehören in .gitignore.

Empfohlene .gitignore (Root):

/data/
/assets/build/
/_site/
/.quarto/
__pycache__/
*.pyc

Quickstart - lokal

  1. .env (optional) neben scripts/pull_all_devices.py anlegen:
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=
  1. Template & CSS bereitstellen:
  • assets/templates/dashboard_template.html
  • assets/templates/dashboard.css

Dein Template bindet die CSS z. B. so ein:

<link rel="stylesheet" href="dashboard.css?v=1">
  1. Ausführen:
python scripts/pull_all_devices.py
  1. 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 ID
  • TTN_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 fort
  • DEVICES – Whitespace-Liste; wenn leer → Auto-Discovery
  • DELAY_BETWEEN_DEVICES (default 2.0 s) – Pause pro Device
  • JITTER_MAX_SECONDS (default 0.7) – Zufalls-Jitter zusätzlich zur Pause
  • MAX_RETRIES (default 5) – HTTP-Retries
  • BACKOFF_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.ndjson anhängen statt überschreiben
  • RAW_SNAPSHOT (0/1) – zusätzliche Schnappschüsse

Steuerung

  • RUN_DASH (1/0) – 1 = HTML bauen, 0 = nur CLI/Smoke-Test
  • OFFLINE (1/0) – 1 = nur lokale Daten, kein HTTP
  • ASSETS_BUILD_SUBDIR (default build) – Zielunterordner in assets/

Template & CSS

  • Kein HTML/CSS-Fallback im Python-Script mehr – das Template muss existieren:

    • assets/templates/dashboard_template.html
    • assets/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 push

Hinweise

  • data/ und assets/build/ bleiben unversioniert. Es wird nur docs/ 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:

    1. data/ & assets/build/ sichern → aus dem Index entfernen → in .gitignore.
    2. Nur docs/ committen (siehe Workflow).
  • HTTP 429 (Rate-Limit) DELAY_BETWEEN_DEVICES auf 1.0–3.0 erhöhen, JITTER_MAX_SECONDS > 0 setzen. Backoff besteht aus BACKOFF_BASE * 2^i + jitter.

  • Keine Daten / leeres Dashboard Prüfe Scopes des API-Keys, TTN Storage-Retention, Devices aktiv, Zeitfenster.

  • Offline testen OFFLINE=1 setzen. 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 überschreibt TTN_AFTER_DAYS nur 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!