# API-Anforderungen für Telemetrie-Upload

**Version:** 2.0  
*Stand: 2026-07-13*  
**Herausgeber:** Dexa Solutions GmbH  
**Produkt:** Safe Fire House (SFH)  


---

## 1. Übersicht

Dieses Dokument beschreibt die REST-API-Anforderungen für die **aktive Übermittlung (Push)** der Telemetriedaten der Safe Fire House Brandwarnanlage an einen empfangenden REST-Endpoint. Die Zentrale ist hier der **Client**, das empfangende System der **Server**. Der übermittelte Datenbaum entspricht dem Response-Baum des Pull-Endpoints (Seite 241).

| Parameter        | Wert                                                         |
| ---------------- | ------------------------------------------------------------ |
| **Method**       | `POST`                                                       |
| **Content-Type** | `application/json`                                           |
| **Accept**       | `application/json`                                           |
| **Frequenz**     | Konfigurierbarer Zeitplan (täglich / wöchentlich / monatlich), azyklisch bei Alarm — **[zu bestätigen]** |
| **Rate Limit**   | Max. 60 Requests/Minute                                      |

### 1.1 Authentifizierung

Eine der folgenden Methoden muss vom empfangenden System unterstützt werden:

| Methode                     | Header / Mechanismus    | Beispiel                           |
| --------------------------- | ----------------------- | ---------------------------------- |
| **API-Key**                 | `X-API-Key`             | `X-API-Key: <key>`                 |
| **Bearer Token (JWT)**      | `Authorization: Bearer` | `Authorization: Bearer <token>`    |
| **X.509 Client-Zertifikat** | mTLS (Mutual TLS)       | Client-Zertifikat im TLS-Handshake |

---

## 2. Payload-Struktur

```
Root
├── timestamp
├── fireStation
├── deviceId
└── objects[]
    ├── type            (vehicle | room | hall)
    ├── vehicleId
    ├── licencePlate
    ├── callSign
    ├── vehicleType
    └── smokeDetectors[]
        ├── name
        ├── address
        ├── type
        └── ...
```

**Wertetypen:** Alle Schlüssel sind **camelCase**. Alle skalaren Werte werden als **JSON-String** ausgegeben (auch Zahlen und Flags, beispielsweise `"rssiDevice": "-71"`, `"battery": "false"`, `"alarmState": "0"`). `objects` und `smokeDetectors` sind echte JSON-Arrays.

**Hinweis zu `type`:** Das Feld `type` tritt auf **zwei Ebenen** auf — auf Objekt-Ebene als Träger-Art (`vehicle`|`room`|`hall`), auf SmokeDetector-Ebene als Melder-Typ (konstant `"SFHSS02"`).

---

## 3. Root-Objekt

| Key           | Description                                  | Type     | Constraints                                    |
| ------------- | -------------------------------------------- | -------- | ---------------------------------------------- |
| `timestamp`   | Zeitstempel der Erstellung                   | `string` | ISO 8601 UTC (`YYYY-MM-DDTHH:mm:ssZ`)          |
| `fireStation` | Wache (Name, Adresse)                        | `string` | Max. 150 Zeichen                               |
| `deviceId`    | Seriennummer der Zentrale                    | `string` | 14 Zeichen, hexadezimal                        |
| `objects`     | Auflistung der Objekte (Fahrzeug/Raum/Halle) | `array`  | Array von Objekt-Einträgen (siehe Abschnitt 4) |

**Beispiel:**

```json
{
  "timestamp": "2026-07-13T11:24:13Z",
  "fireStation": "Feuerwehr Feuerstadt, Hauptstr. 112, 01234 Feuerstadt",
  "deviceId": "001A2B3C4D5E6F",
  "objects": [ ... ]
}
```

---

## 4. Objekt-Eintrag

Ein Objekt-Eintrag bündelt die Rauchsensoren eines Trägers. Das Feld `type` unterscheidet die Träger-Art. Die fahrzeugspezifischen Felder (`vehicleId`, `licencePlate`, `callSign`, `vehicleType`) sind bei `type = "vehicle"` befüllt; für `room`/`hall` können sie leer beziehungsweise `"n.a."` sein.

| Key              | Description                          | Type     | Constraints                                                  |
| ---------------- | ------------------------------------ | -------- | ------------------------------------------------------------ |
| `type`           | Art des Trägers                      | `string` | Enum: `"vehicle"` \| `"room"` \| `"hall"` (derzeit nur `"vehicle"` belegt) |
| `vehicleId`      | Fahrzeug-Identifikationsnummer (VIN) | `string` | 17 Zeichen; `"n.a."` falls nicht hinterlegt (siehe 7.1)      |
| `licencePlate`   | Kennzeichen                          | `string` | Max. 10 Zeichen; `"n.a."` falls nicht hinterlegt (siehe 7.1) |
| `callSign`       | Funkrufname                          | `string` | Max. 50 Zeichen                                              |
| `vehicleType`    | Fahrzeugtyp                          | `string` | Max. 50 Zeichen; `"n.a."` falls nicht hinterlegt (siehe 7.1) |
| `smokeDetectors` | Auflistung der Rauchsensoren         | `array`  | Array von SmokeDetector-Objekten; `[]` falls keine Melder zugeordnet (siehe 7.2) |

**Beispiel:**

```json
{
  "type": "vehicle",
  "vehicleId": "WVWZZZ3CZWE123456",
  "licencePlate": "FS-FW 112",
  "callSign": "1-HLF20-1",
  "vehicleType": "HLF20",
  "smokeDetectors": [ ... ]
}
```

---

## 5. SmokeDetector-Objekt

Alle Werte sind Strings (siehe Hinweis in Abschnitt 2). Fehlt ein einzelner Datapoint, wird ein typ-konformer Default geliefert (nie `null`) — siehe Abschnitt 7.3.

| Key                 | Description                           | Type     | Constraints                                  |
| ------------------- | ------------------------------------- | -------- | -------------------------------------------- |
| `name`              | Rauchsensorbezeichnung                | `string` | Max. 30 Zeichen                              |
| `address`           | Rauchsensoradresse                    | `string` | 14 Zeichen, hexadezimal                      |
| `type`              | Rauchsensortyp                        | `string` | Konstant `"SFHSS02"`                         |
| `version`           | Hardware-Version                      | `string` | numerisch, ≥ 1                               |
| `group`             | Gruppierung                           | `string` | `0`–`9` oder leer                            |
| `teams`             | Reserviert                            | `string` | in der Regel leer                                |
| `firmware`          | Firmware-Version                      | `string` | Max. 9 Zeichen, Pattern `[0-9.]+`            |
| `rssiDevice`        | Funkempfangswert Gerät (dBm)          | `string` | numerisch, −128 bis 128                      |
| `rssiPeer`          | Funkempfangswert Sender (dBm)         | `string` | numerisch, −128 bis 128                      |
| `battery`           | Flag: Batterieleistung niedrig        | `string` | `"true"` / `"false"`                         |
| `unreachState`      | Flag: Gerät nicht erreichbar          | `string` | `"true"` / `"false"`                         |
| `unreachCumulative` | Kumulierte Nichterreichbarkeit (Tage) | `string` | numerisch (0–9999) oder `"n.a."` (siehe 7.2) |
| `operationTime`     | Betriebszeit (Tage)                   | `string` | numerisch, 0–9999                            |
| `dirtLevel`         | Verschmutzungsgrad                    | `string` | float-String (beispielsweise `"0.000000"`)            |
| `smokeLevel`        | Raucherkennungsgrad                   | `string` | float-String (beispielsweise `"0.000000"`)            |
| `alarmState`        | Alarmstatus                           | `string` | `"0"`–`"3"` (siehe Enum, Abschnitt 6.1)      |
| `voltage`           | Batteriespannung (V)                  | `string` | float-String (0.0–3.2)                       |
| `chamber`           | Flag: Rauchkammer verschmutzt         | `string` | `"true"` / `"false"`                         |
| `errorCode`         | Fehlercode                            | `string` | numerisch, 0–99                              |

**Beispiel:**

```json
{
  "name": "1-HLF20-1 RM1",
  "address": "00AABBCCDDEE11",
  "type": "SFHSS02",
  "version": "1",
  "group": "",
  "teams": "",
  "firmware": "1.0.6",
  "rssiDevice": "-65",
  "rssiPeer": "0",
  "battery": "false",
  "unreachState": "false",
  "unreachCumulative": "0",
  "operationTime": "180",
  "dirtLevel": "0.000000",
  "smokeLevel": "0.000000",
  "alarmState": "0",
  "voltage": "3.000000",
  "chamber": "false",
  "errorCode": "0"
}
```

---

## 6. Enums & Flag-Logik

### 6.1 alarmState

| Wert  | Bedeutung                                                    |
| ----- | ------------------------------------------------------------ |
| `"0"` | Ruhezustand – Kein Rauch erkannt                             |
| `"1"` | Lokaler Alarm – Rauch erkannt                                |
| `"2"` | Reserviert                                                   |
| `"3"` | Broadcast-Alarm – Anderer Sensor in Funkreichweite hat Rauch erkannt |

### 6.2 Flag-Logik

| Flag           | Bedeutung wenn `"true"`  | Zusatzinfo                       |
| -------------- | ------------------------ | -------------------------------- |
| `chamber`      | Rauchkammer verschmutzt  | Siehe `dirtLevel`                |
| `battery`      | Batterieleistung niedrig | Siehe `voltage` (V)              |
| `unreachState` | Gerät nicht erreichbar   | Siehe `unreachCumulative` (Tage) |

---

## 7. Sonderfälle & Defaults

### 7.1 Fahrzeug-Metadaten nicht deklariert

`vehicleId`, `licencePlate` und `vehicleType` werden je `callSign` aus der Fahrzeug-Stammdatenpflege der Zentrale gelesen:

| Situation                                    | Ausgabe              |
| -------------------------------------------- | -------------------- |
| `callSign` **fehlt** in der Stammdatenpflege | `"n.a."`             |
| Eintrag vorhanden, **Wert leer**             | `""` (leerer String) |
| Eintrag + Wert vorhanden                     | der Wert             |

`callSign` selbst stammt aus der Fahrzeugliste und ist immer gesetzt.

### 7.2 Gerät nicht erreichbar / nicht gepairt

| Fall                             | Verhalten                                                    |
| -------------------------------- | ------------------------------------------------------------ |
| Melder **gepairt, aber offline** | erscheint im Baum; `unreachState` = `"true"`; `unreachCumulative` = Tage seit letztem Kontakt beziehungsweise `"n.a."`; übrige Werte = **zuletzt bekannter Stand** |
| Melder **nicht (mehr) gepairt**  | Melder fehlt im Array. Ein Fahrzeug ohne zugeordnete Melder liefert `"smokeDetectors": []` |

### 7.3 Fehlender Datapoint → typ-konformer Default

| Feld(er)                                                     | Default      |
| ------------------------------------------------------------ | ------------ |
| `battery`, `unreachState`, `chamber`                         | `"false"`    |
| `rssiDevice`, `rssiPeer`, `errorCode`, `operationTime`, `alarmState` | `"0"`        |
| `voltage`, `smokeLevel`, `dirtLevel`                         | `"0.000000"` |
| `firmware`, `group`, `version`, `teams`                      | `""`         |
| `unreachCumulative` (keine Historie)                         | `"n.a."`     |

---

## 8. Response

### 8.1 Erwartete HTTP Status Codes (vom empfangenden System)

| Code                        | Bedeutung                                         |
| --------------------------- | ------------------------------------------------- |
| `200 OK` / `201 Created`    | Erfolgreich verarbeitet                           |
| `400 Bad Request`           | Ungültiger Payload                                |
| `401 Unauthorized`          | Fehlende oder ungültige Authentifizierung         |
| `403 Forbidden`             | Keine Berechtigung                                |
| `429 Too Many Requests`     | Rate Limit überschritten (`Retry-After` beachten) |
| `500 Internal Server Error` | Serverfehler                                      |
| `503 Service Unavailable`   | Service nicht verfügbar                           |

### 8.2 Success Response

```json
{
  "status": "ok",
  "received": "2026-07-13T11:24:13Z"
}
```

### 8.3 Error Response

```json
{
  "error": "invalid_payload",
  "message": "Field 'address' invalid"
}
```

---

## 9. Retry & Idempotenz (Zentrale-seitig)

| Fehlertyp            | Retry | Wartezeit                     |
| -------------------- | ----- | ----------------------------- |
| Netzwerkfehler       | ja    | 5 s, 10 s, 30 s               |
| HTTP 5xx             | ja    | 5 s, 10 s, 30 s               |
| HTTP 429             | ja    | `Retry-After`-Header beachten |
| HTTP 4xx (außer 429) | nein  | –                             |

Wiederholte Übermittlungen desselben Snapshots sind unkritisch (Telemetrie ist ein Zustands-Snapshot, kein Ereignis-Insert). Ein optionales Idempotenz-Merkmal kann über `timestamp` + `deviceId` gebildet werden.

---

## 10. Sicherheit

| Anforderung        | Beschreibung                                                 |
| ------------------ | ------------------------------------------------------------ |
| **Transport**      | ausschließlich HTTPS (TLS 1.2+)                              |
| **Zugangsdaten**   | verschlüsselt auf der Zentrale; Werte im dexa-Credstore, nie im Klartext in Doku/Skripten |
| **Token-Rotation** | nach Vorgabe des empfangenden Systems                        |

---

## 11. Vollständiges Payload-Beispiel

```json
{
  "timestamp": "2026-07-13T11:24:13Z",
  "fireStation": "Feuerwehr Feuerstadt, Hauptstr. 112, 01234 Feuerstadt",
  "deviceId": "001A2B3C4D5E6F",
  "objects": [
    {
      "type": "vehicle",
      "vehicleId": "WVWZZZ3CZWE123456",
      "licencePlate": "FS-FW 112",
      "callSign": "1-HLF20-1",
      "vehicleType": "HLF20",
      "smokeDetectors": [
        {
          "name": "1-HLF20-1 RM1",
          "address": "00AABBCCDDEE11",
          "type": "SFHSS02",
          "version": "1",
          "group": "",
          "teams": "",
          "firmware": "1.0.6",
          "rssiDevice": "-65",
          "rssiPeer": "0",
          "battery": "false",
          "unreachState": "false",
          "unreachCumulative": "0",
          "operationTime": "180",
          "dirtLevel": "0.000000",
          "smokeLevel": "0.000000",
          "alarmState": "0",
          "voltage": "3.000000",
          "chamber": "false",
          "errorCode": "0"
        },
        {
          "name": "1-HLF20-1 RM2",
          "address": "00AABBCCDDEE22",
          "type": "SFHSS02",
          "version": "1",
          "group": "",
          "teams": "",
          "firmware": "1.0.6",
          "rssiDevice": "-72",
          "rssiPeer": "0",
          "battery": "false",
          "unreachState": "false",
          "unreachCumulative": "0",
          "operationTime": "180",
          "dirtLevel": "0.000000",
          "smokeLevel": "0.000000",
          "alarmState": "0",
          "voltage": "3.000000",
          "chamber": "false",
          "errorCode": "0"
        }
      ]
    }
  ]
}
```