# RFIDS-API 3.0.0 (PHP/MySQL)

Autor/Hersteller/Generator: Andreas Gundelach / CODEX

Die Schnittstelle ist für die unabhängig entwickelte Firmware der LilyGO-T-Display-S3-Lesegeräte bestimmt. Ein Gerät wird über seine dauerhafte ID erkannt. Mehrere Lesegeräte dürfen während der externen Hosting-Testphase dieselbe Quell-IP verwenden.

## URLs

- Geräte-API: `POST https://hrsgreene.testdock.de/api/rfids`
- Firmwaredownload: `GET https://hrsgreene.testdock.de/api/rfids/firmware?id=GERAETE-ID&current=1.0.0`
- Simulator: `https://hrsgreene.testdock.de/simulator.html`
- Verwaltung: `https://hrsgreene.testdock.de/#adminRfids`

Die Geräte sollten die HTTPS-Adresse verwenden. Der Adminlogin ist über HTTP grundsätzlich gesperrt.

## Automatisches Anlernen und Geräte-ID

Ein neues Gerät sendet mit seiner dauerhaften Geräte-ID zuerst `start` oder `keepalive`. Ist die ID unbekannt, legt die Software automatisch einen noch nicht konfigurierten Leser an. Dieser erscheint gelb oben unter **Verwaltung → Leser**. Bis zur Bearbeitung sind Raumscans dieses Geräts gesperrt.

In der Verwaltung wird ausschließlich der Raum ausgewählt. ID und IP-Adresse wurden bereits automatisch erkannt. Der Raumname wird in jeder `start`- und `keepalive`-Antwort als `name` an das Display übertragen. Eventuell vom Gerät mitgesendete Felder für Name, Ort oder Richtung werden ignoriert. Nach der Konfiguration wird der Leser alphabetisch nach Raumname zwischen den übrigen Lesern einsortiert.

Die API protokolliert beim Anlernen die tatsächliche Quell-IP lediglich zur Diagnose. In der externen Testphase findet bewusst keine Bindung an diese IP statt, weil alle Geräte über dieselbe Hosting-IP erscheinen. Ausschlaggebend ist ausschließlich `id`. Eine IP-Bindung kann beim späteren Betrieb auf dem internen Server in der Konfiguration wieder aktiviert werden.

## Gemeinsame Anfrage

```json
{
  "id": "A7K2M9Q4",
  "event": "keepalive",
  "ip": "192.168.1.51",
  "rssi": -54,
  "uptime": 123,
  "firmware": "1.3.2"
}
```

- `id`: registrierte Leser-ID, höchstens 64 Zeichen
- `event`: `start`, `keepalive`, `scan`, `firmware_check` oder `error`
- `ip`: optionale lokale IP des Boards für Diagnosezwecke; für die Zuordnung ist ausschließlich `id` maßgeblich
- `rssi`: WLAN-Signalstärke in dBm
- `uptime`: Laufzeit des Geräts
- `firmware`: Version im Format `MAJOR.MINOR.PATCH`

Karten-IDs werden in Großbuchstaben normalisiert. Leerzeichen, Doppelpunkte, Bindestriche und ein führendes `0x` werden entfernt.

## `start` und `keepalive`

Beide Ereignisse aktualisieren letzten Kontakt, Online-Status, IP, RSSI, Laufzeit und Firmware. Die Antwort überträgt zugleich die globalen Displayeinstellungen:

```json
{
  "ok": true,
  "name": "Bibliothek",
  "time": 1787505000,
  "idleSymbol": "card",
  "idleSymbolCol": "#FFFFFF",
  "idleText": "Karte auflegen",
  "idleTextCol": "#FFFFFF",
  "idleBgCol": "#174EA6",
  "displayTimeoutEnabled": true,
  "displayTimeoutMinutes": 10
}
```

Das Offline-Zeitfenster ist global im Adminbereich einstellbar.

## `scan`

```json
{
  "id": "A7K2M9Q4",
  "event": "scan",
  "card": "04A1B29C7D6180",
  "ip": "192.168.1.51",
  "rssi": -54,
  "uptime": 123,
  "firmware": "1.3.2"
}
```

Ein gültiger Scan ist immer ein „Kommt“-Scan und meldet die Schülerin oder den Schüler in dem Raum an, der dem Leser zugeordnet ist. Eine vom Client mitgesendete Richtung wird ignoriert. Es gibt kein Ausloggen: Ein späterer Scan an einem anderen Raumleser verschiebt die Person direkt in diesen Raum und beendet damit die vorherige Raumzuordnung. Ein weiterer Scan im gleichen Raum bestätigt lediglich, dass sie bereits dort ist. Es erscheint keine Warnung wegen eines fehlenden Ausloggens.

Beispielantwort:

```json
{
  "ok": true,
  "name": "Bibliothek",
  "time": 1787505000,
  "idleSymbol": "card",
  "idleSymbolCol": "#FFFFFF",
  "idleText": "Karte auflegen",
  "idleTextCol": "#FFFFFF",
  "idleBgCol": "#174EA6",
  "displayTimeoutEnabled": true,
  "displayTimeoutMinutes": 10,
  "symbol": "check",
  "symbolCol": "#55F28B",
  "text": "Hallo Mia",
  "textCol": "#FFFFFF",
  "bgCol": "#174EA6",
  "validTime": 5
}
```

Texte werden auf höchstens fünf Wörter begrenzt. `#NAME` wird durch den ersten Vornamen ersetzt. Farben haben das Format `#RRGGBB`; `validTime` liegt zwischen 1 und 3.600 Sekunden.

Unterstützte Symbole: `card`, `check`, `cross`, `thumb_up`, `sun`, `cloud`, `warning`, `info`, `question`, `arrow_up`, `arrow_down`, `lock`, `unlock`, `gear`.

## `error`

```json
{
  "id": "A7K2M9Q4",
  "event": "error",
  "ip": "192.168.1.51",
  "rssi": -54,
  "uptime": 321,
  "firmware": "1.3.2",
  "error": {
    "code": "OTA_SHA256",
    "component": "OTA",
    "severity": "critical",
    "message": "Automatisches Update fehlgeschlagen",
    "details": "SHA-256-Pruefsumme stimmt nicht",
    "occurredAtUptimeMs": 320554
  }
}
```

`code` ist Pflicht und maximal 48 Zeichen lang, `component` maximal 24 Zeichen, `severity` ist `info`, `warning`, `error` oder `critical`, `message` ist Pflicht und maximal 240 Zeichen, `details` maximal 500 Zeichen.

Antwort: `{"ok":true,"received":true,"time":1787505000}`.

## `firmware_check`

Die gemeinsame Anfrage wird mit `event: "firmware_check"` und der installierten Firmware gesendet. Ohne neueres freigegebenes Update:

```json
{"ok":true,"firmware":{"available":false,"latestVersion":"1.3.2"}}
```

Mit Update:

```json
{
  "ok": true,
  "firmware": {
    "available": true,
    "latestVersion": "1.3.3",
    "version": "1.3.3",
    "url": "https://hrsgreene.testdock.de/api/rfids/firmware?id=A7K2M9Q4&current=1.3.2",
    "size": 1482752,
    "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
    "releaseNotes": "Fehlerkorrekturen und Verbesserungen"
  }
}
```

Nur numerisch höhere, aktive und freigegebene Versionen werden angeboten. Der Download antwortet mit `Content-Type: application/octet-stream`, exakter `Content-Length`, `X-RFIDS-Firmware-Version` und `X-RFIDS-Firmware-SHA256`. Die Datei wird in Blöcken gestreamt.

## Firmware-Upload

Die Verwaltung akzeptiert `.bin`-Dateien zwischen 32 KiB und 3 MiB. Das erste Byte muss `0xE9` sein. SHA-256 wird auf dem Server berechnet und angezeigt. Rollouts können global oder für ausgewählte Geräte freigegeben werden. Wird dieselbe Version erneut hochgeladen, wird die bisherige Datei zuerst im geschützten Firmwarearchiv gesichert.

## Fehlerantworten

Fehler sind nie leer:

```json
{
  "ok": false,
  "error": "Kurze verständliche Fehlermeldung",
  "code": "RFIDS_ERROR_CODE"
}
```

Verwendete Statuscodes: `200` erfolgreich, `204` OPTIONS, `400` ungültiges JSON oder Pflichtfeld, `404` Ressource nicht gefunden, `405` falsche Methode, `413` Anfrage zu groß, `422` fachlich ungültig, `500` interner Fehler.

## Testmodus

Der Testmodus wird global im Adminbereich geschaltet. Verfügbar sind echte Raumlogik, immer positiv, immer abgelehnt, zufällig und ein von der Karten-ID abhängiges Ergebnis sowie 0 bis 5.000 ms künstliche Verzögerung. Der getrennte Simulator zeigt gesendetes JSON, HTTP-Status, Laufzeit, Antwort-JSON und eine Displayvorschau.

## Sicherheit

Die Verwaltung ist nur über HTTPS nutzbar. In der externen Testphase ist die Geräte-API-Drosselung wegen der gemeinsamen Quell-IP deaktiviert; sämtliche Geräte-, Fehler- und Scanereignisse werden dennoch protokolliert. Für den späteren internen Server kann die Drosselung wieder aktiviert werden. Der Vertrag bleibt für die vorhandenen Clients ohne zusätzliche Pflichtfelder kompatibel.
