Sensor-API

Beet Lotse nimmt Messwerte von jedem Gerät an, das eine HTTP-Anfrage mit HMAC-SHA-256 signieren kann – etwa ein ESP32, ein Raspberry Pi oder ein kleines Skript.

Kurzfassung

POST https://beetlotse.de/api/v1/sensors/{id}/readings
Header:   X-Sensor-Id, X-Sensor-Secret, X-Sensor-Signature
Body:     {"batch_id":"...", "readings":[{"value":..,"unit":"..","recorded_at":"..."}]}
Signatur: HMAC_SHA256(secret, rohe_body_bytes), hex

Drei Schritte: Sensor in der App anlegen → ID und Schlüssel aufs Gerät übertragen → Messwerte senden.


1. Sensortypen

Haupttypen

type Misst Einheit (metrisch) Imperial Bereich
soil_moisture Bodenfeuchte "%" (0–100) — 0 bis 100
temperature Luft-/Bodentemperatur "°C" "°F" −40 bis 85
humidity Relative Luftfeuchte "%" (0–100) — 0 bis 100
barometric_pressure Luftdruck "hPa" (= mbar) "inHg" 870 bis 1085

Der Server speichert value und unit so, wie sie ankommen. Die Apps rechnen Einheiten für die Anzeige um.

Bodenfeuchte: Prozent oder Rohwert

Kapazitive Bodenfeuchte-Sensoren liefern von sich aus keine Prozente, sondern einen Rohwert (meist 200–800 bei einem 12-Bit-ADC). Sende das, was dein Gerät hat:

Modus unit Wann
Prozent "%" Das Gerät kennt seine Trocken-/Nass-Werte bereits
Rohwert "raw" Kapazitiver Sensor ohne Kalibrierung auf dem Gerät

Im Rohwert-Modus kalibrierst du einmal in der App: Sensor an der Luft → „Trockenwert speichern“; Sensor in einem Glas Wasser → „Nasswert speichern“. Danach zeigt die App Prozent an:

pct = clamp((raw - value_dry) / (value_wet - value_dry) * 100, 0, 100)

Rohwerte bleiben gespeichert, du kannst also später neu kalibrieren, ohne den Verlauf zu verlieren. Automatische Gieß-Erinnerungen nutzen die kalibrierten Prozent.

Weitere Typen

type Einheit Hinweis
light "lux" Helligkeit
ph "pH" 0–14
ec "mS/cm" Leitfähigkeit der Nährlösung
co2 "ppm" CO₂-Konzentration
custom bis 20 Zeichen Freie Einheit

2. Voraussetzungen

  • Bestätigte E-Mail-Adresse.
  • Pro-Tarif, um Sensoren anzulegen (sonst 402). Sensoren, die du vor einem Wechsel zu Free angelegt hast, kannst du weiter auslesen.

3. Sensor anlegen

In der App (empfohlen)

Profil → Mehr → Sensoren → Sensor hinzufügen. Wähle, was der Sensor misst, gib ihm einen Namen und ordne ihn einem Tray zu. Danach zeigt die App Sensor-ID und geheimen Schlüssel – genau einmal. Übertrage beides auf dein Gerät, bevor du den Dialog schließt.

Über die API

Token holen:

TOKEN=$(curl -sS -X POST https://beetlotse.de/api/v1/auth/login \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'username=<DEINE_EMAIL>' \
  --data-urlencode 'password=<DEIN_PASSWORT>' \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")

Bodenfeuchte-Sensor für Tray 3 anlegen:

curl -sS -X POST https://beetlotse.de/api/v1/sensors \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Hochbeet A", "type": "soil_moisture", "tray_id": 3}'

Die Antwort enthält den Schlüssel – das einzige Mal, dass du ihn siehst:

{
  "id": 12,
  "name": "Hochbeet A",
  "type": "soil_moisture",
  "tray_id": 3,
  "secret": "<wird einmal angezeigt>"
}

4. Messwerte senden

Jeder POST ist ein Paket mit bis zu 2.000 Messwerten, signiert mit dem Schlüssel des Sensors. Doppelte Werte werden über (sensor_id, batch_id, recorded_at) erkannt; ein Paket erneut zu senden ist also unschädlich.

Header

Header Wert
X-Sensor-Id Die Sensor-ID (wie im Pfad)
X-Sensor-Secret Der Schlüssel aus der Anlage
X-Sensor-Signature HMAC_SHA256(secret, rohe_body_bytes) hex
Content-Type application/json

Signiere genau die Bytes, die du sendest – das JSON nach dem Signieren nicht neu serialisieren.

Body

{
  "batch_id": "eine-eindeutige-id-vom-geraet",
  "readings": [
    { "value": 36.2, "unit": "%",   "recorded_at": "2026-05-29T17:00:00Z" },
    { "value": 421,  "unit": "raw", "recorded_at": "2026-05-29T17:05:00Z" }
  ]
}

Beispiele je Typ:

// soil_moisture (Prozent)
{ "value": 36.2, "unit": "%", "recorded_at": "2026-05-29T17:00:00Z" }

// soil_moisture (Rohwert – in der App kalibrieren)
{ "value": 421, "unit": "raw", "recorded_at": "2026-05-29T17:00:00Z" }

// temperature
{ "value": 21.4, "unit": "°C", "recorded_at": "2026-05-29T17:00:00Z" }

// humidity
{ "value": 58.0, "unit": "%", "recorded_at": "2026-05-29T17:00:00Z" }

// barometric_pressure
{ "value": 1013.2, "unit": "hPa", "recorded_at": "2026-05-29T17:00:00Z" }

Vollständiges curl-Beispiel

SENSOR_ID=<DEINE_SENSOR_ID>
SECRET="<DEIN_SENSOR_SCHLUESSEL>"

BODY='{"batch_id":"demo-01","readings":[{"value":36.2,"unit":"%","recorded_at":"2026-05-29T17:00:00Z"}]}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')

curl -sS -X POST "https://beetlotse.de/api/v1/sensors/$SENSOR_ID/readings" \
  -H "X-Sensor-Id: $SENSOR_ID" \
  -H "X-Sensor-Secret: $SECRET" \
  -H "X-Sensor-Signature: $SIG" \
  -H "Content-Type: application/json" \
  -d "$BODY"

Antwort:

{ "batch_id": "demo-01", "received": 1, "inserted": 1, "deduped": 0 }

Fehler

HTTP Ursache Lösung
400 X-Sensor-Id passt nicht zum Pfad An beiden Stellen dieselbe ID verwenden
401 Falscher Schlüssel Sensor neu anlegen; der Schlüssel wird nur einmal angezeigt
401 Falsche Signatur Genau die gesendeten Bytes signieren
402 Kein Pro-Tarif Siehe Abschnitt 2
403 E-Mail nicht bestätigt E-Mail-Adresse bestätigen
404 Sensor wurde gelöscht Sensor neu anlegen
410 Sensor deaktiviert Neuen Sensor anlegen
422 Ungültiger Body unit max. 20 Zeichen; recorded_at ISO-8601 mit Zeitzone

5. Beispiel in Python

Läuft auf einem Raspberry Pi; mit MicroPython funktioniert es genauso mit urequests und hmac:

import json, hmac, hashlib, time, requests, datetime as dt

API = "https://beetlotse.de/api/v1"
SENSOR_ID = 12                      # deine Sensor-ID
SECRET = "<DEIN_SENSOR_SCHLUESSEL>"

def now():
    return dt.datetime.now(dt.timezone.utc).isoformat()

def send_batch(readings, batch_id=None):
    batch_id = batch_id or f"py-{int(time.time())}"
    body = json.dumps({"batch_id": batch_id, "readings": readings}).encode()
    sig = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
    r = requests.post(
        f"{API}/sensors/{SENSOR_ID}/readings",
        headers={
            "X-Sensor-Id": str(SENSOR_ID),
            "X-Sensor-Secret": SECRET,
            "X-Sensor-Signature": sig,
            "Content-Type": "application/json",
        },
        data=body,                  # rohe Bytes, nicht das Dict
        timeout=10,
    )
    r.raise_for_status()
    return r.json()

# Bodenfeuchte in Prozent
print(send_batch([{"value": 36.2, "unit": "%", "recorded_at": now()}]))

Halte die unit passend zum Sensortyp, damit die Diagramme stimmen.


6. Messwerte abrufen

curl -sS "https://beetlotse.de/api/v1/sensors/12/readings?limit=50" \
  -H "Authorization: Bearer $TOKEN"

Zeitraum: ?from=2026-05-01T00:00:00Z&to=2026-06-01T00:00:00Z (ISO-8601 mit Zeitzone; höchstens 10.000 Werte pro Antwort).


7. Sicherheit

  • Der Schlüssel wird nur einmal angezeigt. Wir speichern nur einen Hash davon; wenn du ihn verlierst, lege den Sensor neu an.
  • Immer über HTTPS senden; unverschlüsseltes HTTP wird abgelehnt.
  • Schlüssel verloren oder weitergegeben? Lösche den Sensor in der App. Das Gerät bekommt beim nächsten Paket 410 Gone.
  • Limit pro Sensor: 20 Pakete pro Minute.

8. Sensor, Tray und Standort

Ein Sensor gehört zu deinem Konto und kann einem Tray (z. B. ein Bodenfeuchte-Sensor im Hochbeet) oder einem Standort (z. B. ein Temperatursensor fürs ganze Gewächshaus) zugeordnet werden. Die Zuordnung änderst du in der App unter Profil → Mehr → Sensoren → Sensor auswählen.


9. Diese Anleitung in der App

Du findest diese Anleitung in der App unter Profil → Mehr → Sensoren. „Per E-Mail senden“ schickt sie dir zusammen mit der Liste deiner Sensor-IDs – praktisch, wenn du ein Gerät abseits vom Handy einrichtest.

Bereit, einen Sensor anzubinden? Preise ansehen oder Kontakt aufnehmen.