# Spezifikationen

Technische Spezifikationen und API-Anforderungen zum Safe Fire House

# API Schnittstellenspezifikation

# API-Anforderungen für Alarmierungen

**Version:** 1.2  
**Stand:** März 2026  
**Herausgeber:** Dexa Solutions GmbH  
**Produkt:** Safe Fire House (SFH)  

---

## 1. Übersicht

Diese Spezifikation definiert die REST-API-Schnittstelle für die Übermittlung von Alarmdaten von der Safe Fire House Brandwarnanlage an externe Alarmierungsdienste. Die API ermöglicht sowohl die Erstmeldung eines Alarms als auch nachfolgende Updates bei Broadcast-Alarmen.

### 1.1 Alarmtypen

| Typ | Beschreibung | Methode |
|-----|--------------|---------|
| `ALARM` | Lokaler Rauchalarm – Einzelner Rauchsensor hat ausgelöst | POST |
| `ALARM` (Broadcast) | Folge-Alarm – Weitere Rauchsensoren in Funkreichweite | PUT |
| `TEST` | Schnittstellen-Test zur Validierung der Verbindung | POST |

---

## 2. API-Parameter

| Parameter | Wert |
|-----------|------|
| **Base-URL** | `https://{partner-domain}/api/v1` |
| **Content-Type** | `application/json; charset=UTF-8` |
| **Accept** | `application/json` |
| **Zeichenkodierung** | UTF-8 |

### 2.1 Authentifizierung

Die Authentifizierung erfolgt via **Bearer Token** im HTTP-Header:

```http
Authorization: Bearer {access_token}
```

| Parameter | Beschreibung |
|-----------|--------------|
| `access_token` | Vom Partner bereitgestellter API-Schlüssel (min. 32 Zeichen) |

**Hinweis:** Der Token wird pro Kunde/Standort vom Partner generiert und im SFH-System hinterlegt.

### 2.2 Rate Limiting

| Parameter | Wert |
|-----------|------|
| Max. Requests | 60 pro Minute |
| Retry-After | Bei HTTP 429 im Header angegeben |

---

## 3. Erstalarm (POST)

Sendet einen neuen Alarm an das Partner-System. Der Partner legt einen neuen Alarm-Datensatz an und gibt eine eindeutige `alarmId` zurück.

### 3.1 Request

```http
POST /api/v1/alarms HTTP/1.1
Host: {partner-domain}
Authorization: Bearer {access_token}
Content-Type: application/json
Accept: application/json
```

### 3.2 Request-Body

```json
{
  "externalCreatedAt": "2026-03-13T15:30:00Z",
  "externalId": "SFH-20260313-153000-001",
  "keyword": "ALARM",
  "keywordAddition": "RAUCHSENSOR-ALARM",
  "info": "Rauchsensor hat durch lokale Raucherkennung ausgelöst!",
  "priority": false,
  "send_push": true,
  "send_sms": false,
  "send_call": false,
  "group": "FW-Musterstadt-Zug1",
  "destination": {
    "objectName": "Feuerwehrgerätehaus Musterstadt",
    "info": "Fahrzeughalle",
    "street": "Hauptstraße",
    "houseNumber": "112",
    "zipCode": "12345",
    "city": "Musterstadt",
    "coordinates": {
      "latitude": 51.123456,
      "longitude": 7.654321
    },
    "fireAlarmSystem": "Safe Fire House"
  },
  "publisherInfos": {
    "systemName": "DXO-SFH-CU-X-02",
    "version": "2.0"
  },
  "reporter": [
    {
      "name": "HLF20-Kabine",
      "info": "Raucherkennung"
    }
  ]
}
```

### 3.3 Request-Felder

#### Root-Objekt

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `externalCreatedAt` | string | ✓ | Zeitstempel der Alarmerstellung (ISO 8601, UTC) |
| `externalId` | string | ✓ | Eindeutige Alarm-ID aus dem SFH-System (für Idempotenz) |
| `keyword` | string | ✓ | Alarmtyp: `ALARM` oder `TEST` |
| `keywordAddition` | string | ✓ | Detailbeschreibung: `RAUCHSENSOR-ALARM`, `SCHNITTSTELLEN-TEST` |
| `info` | string | ✓ | Freitext-Information zum Alarm |
| `priority` | boolean | ✓ | Prioritäts-Flag (reserviert für zukünftige Nutzung) |
| `send_push` | boolean | ✓ | Push-Benachrichtigung senden |
| `send_sms` | boolean | ✓ | SMS-Benachrichtigung senden |
| `send_call` | boolean | ✓ | Telefonanruf auslösen |
| `group` | string | ✓ | Alarmierungsgruppe/RIC beim Partner |
| `destination` | object | ✓ | Standort-Objekt (siehe unten) |
| `publisherInfos` | object | ✓ | System-Informationen (siehe unten) |
| `reporter` | array | ✓ | Array von Rauchsensor-Objekten (siehe unten) |

#### destination-Objekt

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `objectName` | string | ✓ | Name des Gebäudes/Objekts |
| `info` | string | | Zusatzinformation zum Standort |
| `street` | string | ✓ | Straßenname |
| `houseNumber` | string | ✓ | Hausnummer |
| `zipCode` | string | ✓ | Postleitzahl |
| `city` | string | ✓ | Stadt/Ort |
| `coordinates` | object | ✓ | Koordinaten-Objekt mit `latitude` und `longitude` |
| `fireAlarmSystem` | string | ✓ | Systemkennung, immer `Safe Fire House` |

#### coordinates-Objekt

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `latitude` | number | ✓ | Breitengrad (WGS84, Dezimalgrad) |
| `longitude` | number | ✓ | Längengrad (WGS84, Dezimalgrad) |

#### publisherInfos-Objekt

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `systemName` | string | ✓ | Produktkennung, z.B. `DXO-SFH-CU-X-02` |
| `version` | string | ✓ | Produktversion: `1.0` oder `2.0` |

#### reporter-Objekt (Array-Element)

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `name` | string | ✓ | Gerätename/OPTA des auslösenden Rauchsensors |
| `info` | string | ✓ | Art der Erkennung, z.B. `Raucherkennung` |

### 3.4 Response (Erfolg)

```http
HTTP/1.1 201 Created
Content-Type: application/json
```

```json
{
  "status": "created",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "received": "2026-03-13T15:30:01Z"
}
```

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `status` | string | `created` bei erfolgreichem Anlegen |
| `alarmId` | string | **Eindeutige ID des angelegten Alarms** (UUID oder PK) – wird für PUT benötigt! |
| `received` | string | Zeitstempel der Verarbeitung beim Partner (ISO 8601, UTC) |

---

## 4. Alarm-Update (PUT)

Aktualisiert einen bestehenden Alarm (z.B. bei Broadcast-Alarm, wenn weitere Rauchsensoren auslösen). Die `alarmId` aus der POST-Response wird im URL-Pfad übergeben.

### 4.1 Request

```http
PUT /api/v1/alarms/{alarmId} HTTP/1.1
Host: {partner-domain}
Authorization: Bearer {access_token}
Content-Type: application/json
Accept: application/json
```

**URL-Parameter:**

| Parameter | Beschreibung |
|-----------|--------------|
| `alarmId` | Die vom Partner beim POST zurückgegebene Alarm-ID |

### 4.2 Request-Body

```json
{
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "externalId": "SFH-20260313-153000-001",
  "externalUpdatedAt": "2026-03-13T15:30:10Z",
  "keyword": "ALARM",
  "keywordAddition": "RAUCHSENSOR-ALARM (BROADCAST)",
  "info": "Weitere Rauchsensoren haben durch Broadcast-Alarm ausgelöst!",
  "priority": false,
  "send_push": true,
  "send_sms": false,
  "send_call": false,
  "group": "FW-Musterstadt-Zug1",
  "destination": {
    "objectName": "Feuerwehrgerätehaus Musterstadt",
    "info": "Fahrzeughalle",
    "street": "Hauptstraße",
    "houseNumber": "112",
    "zipCode": "12345",
    "city": "Musterstadt",
    "coordinates": {
      "latitude": 51.123456,
      "longitude": 7.654321
    },
    "fireAlarmSystem": "Safe Fire House"
  },
  "publisherInfos": {
    "systemName": "DXO-SFH-CU-X-02",
    "version": "2.0"
  },
  "reporter": [
    {
      "name": "HLF20-Kabine",
      "info": "Raucherkennung"
    },
    {
      "name": "HLF20-Mannschaftsraum",
      "info": "Raucherkennung (Broadcast)"
    }
  ]
}
```

### 4.3 Unterschiede zum POST

| Feld | POST | PUT |
|------|------|-----|
| `alarmId` | Nicht vorhanden | ✓ Pflicht (im Body UND URL) |
| `externalCreatedAt` | ✓ | Nicht vorhanden |
| `externalUpdatedAt` | Nicht vorhanden | ✓ Pflicht |
| `keywordAddition` | `RAUCHSENSOR-ALARM` | `RAUCHSENSOR-ALARM (BROADCAST)` |
| `reporter` | 1 Rauchsensor | 1+ Rauchsensoren (kumulativ) |

### 4.4 Response (Erfolg)

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "status": "updated",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "received": "2026-03-13T15:30:11Z"
}
```

---

## 5. Alarm-Status abrufen (GET)

Ruft den aktuellen Status eines Alarms ab, einschließlich der Rückmeldungen der alarmierten Einsatzkräfte.

### 5.1 Request

```http
GET /api/v1/alarms/{alarmId} HTTP/1.1
Host: {partner-domain}
Authorization: Bearer {access_token}
Accept: application/json
```

**URL-Parameter:**

| Parameter | Beschreibung |
|-----------|--------------|
| `alarmId` | Die vom Partner beim POST zurückgegebene Alarm-ID |

### 5.2 Response (Erfolg)

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "status": "ok",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "externalId": "SFH-20260313-153000-001",
  "alarmStatus": "active",
  "createdAt": "2026-03-13T15:30:01Z",
  "updatedAt": "2026-03-13T15:30:11Z",
  "feedback": {
    "total": 12,
    "responses": [
      {
        "type": "coming",
        "label": "Komme",
        "count": 7
      },
      {
        "type": "coming_delayed",
        "label": "Komme später",
        "count": 2,
        "details": [
          { "eta": 5, "count": 1 },
          { "eta": 10, "count": 1 }
        ]
      },
      {
        "type": "not_available",
        "label": "Nicht verfügbar",
        "count": 3
      }
    ],
    "pending": 5
  },
  "received": "2026-03-13T15:31:00Z"
}
```

### 5.3 Response-Felder

#### Root-Objekt

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `status` | string | `ok` bei erfolgreicher Abfrage |
| `alarmId` | string | Partner-interne Alarm-ID (UUID/PK) |
| `externalId` | string | Ursprüngliche ID aus dem SFH-System (für Abgleich) |
| `alarmStatus` | string | Aktueller Alarmstatus (siehe Enum) |
| `createdAt` | string | Zeitpunkt der Alarmerstellung beim Partner (ISO 8601) |
| `updatedAt` | string | Zeitpunkt der letzten Aktualisierung (ISO 8601) |
| `feedback` | object | Rückmeldungs-Objekt (siehe unten) |
| `received` | string | Zeitstempel dieser Abfrage (ISO 8601) |

#### alarmStatus Enum

| Wert | Beschreibung |
|------|--------------|
| `active` | Alarm ist aktiv, Alarmierung läuft |
| `acknowledged` | Alarm wurde quittiert |
| `closed` | Alarm wurde abgeschlossen |
| `cancelled` | Alarm wurde storniert |

#### feedback-Objekt

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `total` | integer | Gesamtzahl der alarmierten Einsatzkräfte |
| `responses` | array | Array von Rückmelde-Objekten (siehe unten) |
| `pending` | integer | Anzahl noch ausstehender Rückmeldungen |

#### responses-Objekt (Array-Element)

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `type` | string | ✓ | Maschinenlesbarer Rückmeldetyp (siehe Enum) |
| `label` | string | ✓ | Menschenlesbarer Text (Sprache des Partners) |
| `count` | integer | ✓ | Anzahl der Rückmeldungen dieses Typs |
| `details` | array | | Optional: Detailierte Aufschlüsselung (z.B. ETA-Zeiten) |

#### response.type Enum (Standardisiert)

| Typ | Beschreibung |
|-----|--------------|
| `coming` | Kommt zum Einsatz |
| `coming_delayed` | Kommt später (mit ETA) |
| `not_available` | Nicht verfügbar / Kommt nicht |
| `standby` | Bereitschaft / Evtl. verfügbar |
| `on_scene` | Bereits vor Ort |
| `unknown` | Sonstiger/Unbekannter Status |

#### details-Objekt (Optional, für coming_delayed)

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `eta` | integer | Geschätzte Ankunftszeit in Minuten |
| `count` | integer | Anzahl mit dieser ETA |

### 5.4 Beispiel: Status-Abfrage

```bash
curl -X GET "https://partner.example.com/api/v1/alarms/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Accept: application/json"
```

**Response:**
```json
{
  "status": "ok",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "externalId": "SFH-20260313-153000-001",
  "alarmStatus": "active",
  "createdAt": "2026-03-13T15:30:01Z",
  "updatedAt": "2026-03-13T15:30:11Z",
  "feedback": {
    "total": 12,
    "responses": [
      { "type": "coming", "label": "Komme", "count": 7 },
      { "type": "coming_delayed", "label": "Komme später", "count": 2, "details": [{ "eta": 5, "count": 1 }, { "eta": 10, "count": 1 }] },
      { "type": "not_available", "label": "Nicht verfügbar", "count": 3 }
    ],
    "pending": 5
  },
  "received": "2026-03-13T15:31:00Z"
}
```

---

## 6. Fehlerbehandlung

### 6.1 HTTP-Statuscodes

| Code | Bedeutung | Beschreibung |
|------|-----------|--------------|
| `200` | OK | Alarm erfolgreich aktualisiert (PUT) |
| `201` | Created | Alarm erfolgreich angelegt (POST) |
| `400` | Bad Request | Ungültiger Request-Body oder fehlende Pflichtfelder |
| `401` | Unauthorized | Fehlender oder ungültiger Bearer Token |
| `403` | Forbidden | Token gültig, aber keine Berechtigung für diese Ressource |
| `404` | Not Found | Alarm-ID nicht gefunden (bei PUT) |
| `409` | Conflict | Alarm mit dieser `externalId` existiert bereits (bei POST) |
| `429` | Too Many Requests | Rate Limit überschritten |
| `500` | Internal Server Error | Serverfehler beim Partner |
| `503` | Service Unavailable | Partner-System temporär nicht verfügbar |

### 6.2 Fehler-Response

```json
{
  "status": "error",
  "error": "invalid_payload",
  "message": "Field 'externalId' is required",
  "received": "2026-03-13T15:30:01Z"
}
```

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `status` | string | Immer `error` |
| `error` | string | Fehlercode (siehe unten) |
| `message` | string | Menschenlesbare Fehlerbeschreibung |
| `received` | string | Zeitstempel der Fehlerverarbeitung |

### 6.3 Fehlercodes

| Code | Beschreibung |
|------|--------------|
| `invalid_payload` | JSON-Syntax ungültig oder Pflichtfeld fehlt |
| `invalid_field` | Feldwert entspricht nicht dem erwarteten Format |
| `unauthorized` | Token fehlt oder ist ungültig |
| `forbidden` | Keine Berechtigung für diese Operation |
| `not_found` | Ressource (Alarm) nicht gefunden |
| `duplicate` | Alarm mit dieser `externalId` existiert bereits |
| `rate_limited` | Zu viele Anfragen |
| `internal_error` | Interner Serverfehler |

---

## 7. Idempotenz & Retry-Verhalten

### 7.1 Idempotenz

Das Feld `externalId` dient der Idempotenz-Sicherung:

- Bei wiederholtem POST mit gleicher `externalId` sollte der Partner **HTTP 409 Conflict** zurückgeben
- Alternativ kann der Partner ein Upsert-Verhalten implementieren (Update statt Insert)

### 7.2 Retry-Strategie (SFH-seitig)

| Fehlertyp | Retry | Wartezeit |
|-----------|-------|-----------|
| Netzwerkfehler | Ja | 5s, 10s, 30s |
| HTTP 5xx | Ja | 5s, 10s, 30s |
| HTTP 429 | Ja | Retry-After Header beachten |
| HTTP 4xx (außer 429) | Nein | – |

---

## 8. Sicherheitsanforderungen

| Anforderung | Beschreibung |
|-------------|--------------|
| **Transport** | Ausschließlich HTTPS (TLS 1.2+) |
| **Token-Speicherung** | Access Token verschlüsselt auf der SFH-Zentrale |
| **Token-Rotation** | Empfohlen: Jährliche Erneuerung |
| **IP-Whitelisting** | Optional: Partner kann SFH-IPs whitelisten |

---

## 9. Beispiel: Vollständiger Alarm-Flow

### 9.1 Schritt 1: Lokaler Alarm (POST)

Ein Rauchsensor im HLF20 löst aus:

```bash
curl -X POST "https://partner.example.com/api/v1/alarms" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "externalCreatedAt": "2026-03-13T15:30:00Z",
    "externalId": "SFH-20260313-153000-001",
    "keyword": "ALARM",
    "keywordAddition": "RAUCHSENSOR-ALARM",
    "info": "Rauchsensor hat durch lokale Raucherkennung ausgelöst!",
    "priority": false,
    "send_push": true,
    "send_sms": false,
    "send_call": false,
    "group": "FW-Musterstadt-Zug1",
    "destination": {
      "objectName": "Feuerwehrgerätehaus Musterstadt",
      "info": "Fahrzeughalle",
      "street": "Hauptstraße",
      "houseNumber": "112",
      "zipCode": "12345",
      "city": "Musterstadt",
      "coordinates": { "latitude": 51.123456, "longitude": 7.654321 },
      "fireAlarmSystem": "Safe Fire House"
    },
    "publisherInfos": { "systemName": "DXO-SFH-CU-X-02", "version": "2.0" },
    "reporter": [{ "name": "HLF20-Kabine", "info": "Raucherkennung" }]
  }'
```

**Response:**
```json
{
  "status": "created",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "received": "2026-03-13T15:30:01Z"
}
```

### 9.2 Schritt 2: Broadcast-Alarm (PUT)

~10 Sekunden später lösen weitere Rauchsensoren im Fahrzeug aus:

```bash
curl -X PUT "https://partner.example.com/api/v1/alarms/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "alarmId": "550e8400-e29b-41d4-a716-446655440000",
    "externalId": "SFH-20260313-153000-001",
    "externalUpdatedAt": "2026-03-13T15:30:10Z",
    "keyword": "ALARM",
    "keywordAddition": "RAUCHSENSOR-ALARM (BROADCAST)",
    "info": "Weitere Rauchsensoren haben durch Broadcast-Alarm ausgelöst!",
    "priority": false,
    "send_push": true,
    "send_sms": false,
    "send_call": false,
    "group": "FW-Musterstadt-Zug1",
    "destination": {
      "objectName": "Feuerwehrgerätehaus Musterstadt",
      "info": "Fahrzeughalle",
      "street": "Hauptstraße",
      "houseNumber": "112",
      "zipCode": "12345",
      "city": "Musterstadt",
      "coordinates": { "latitude": 51.123456, "longitude": 7.654321 },
      "fireAlarmSystem": "Safe Fire House"
    },
    "publisherInfos": { "systemName": "DXO-SFH-CU-X-02", "version": "2.0" },
    "reporter": [
      { "name": "HLF20-Kabine", "info": "Raucherkennung" },
      { "name": "HLF20-Mannschaftsraum", "info": "Raucherkennung (Broadcast)" }
    ]
  }'
```

**Response:**
```json
{
  "status": "updated",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "received": "2026-03-13T15:30:11Z"
}
```

---

## Anhang A: Constraints

| Feld | Constraint |
|------|------------|
| `externalId` | Max. 50 Zeichen, Pattern: `[A-Za-z0-9\-]+` |
| `keyword` | Enum: `ALARM`, `TEST` |
| `keywordAddition` | Max. 50 Zeichen |
| `info` | Max. 500 Zeichen |
| `group` | Max. 100 Zeichen |
| `destination.street` | Max. 100 Zeichen |
| `destination.houseNumber` | Max. 10 Zeichen |
| `destination.zipCode` | 5 Zeichen (DE) |
| `destination.city` | Max. 100 Zeichen |
| `coordinates.latitude` | -90.0 bis 90.0 |
| `coordinates.longitude` | -180.0 bis 180.0 |
| `reporter[].name` | Max. 50 Zeichen |
| `reporter` Array | Min. 1 Element |

---

# API-Anforderungen für Telemetrie-Upload

**Version:** 2.0  
**Stand:** 13.07.2026  
**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, z. B. `"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 bzw. `"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` | i. d. R. 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 (z. B. `"0.000000"`)            |
| `smokeLevel`        | Raucherkennungsgrad                   | `string` | float-String (z. B. `"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 bzw. `"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"
        }
      ]
    }
  ]
}
```

# API-Beschreibung für Telemetrie-Abruf (GET)

**Version:** 1.6  
**Stand:** Juni 2026  
**Herausgeber:** Dexa Solutions GmbH  
**Produkt:** Safe Fire House (SFH)  

---

## 1. Übersicht

Dieses Dokument beschreibt den REST-API-Endpoint, über den ein externes System die aktuellen Telemetriedaten der Safe Fire House Brandwarnanlage **aktiv abruft** (Pull). Die Zentrale ist hier der **Server**, das abrufende System der **Client**. Die Antwort enthält den Datenbaum aus Wache, Fahrzeugen und Rauchsensoren.

| Parameter         | Wert                                                         |
| ----------------- | ------------------------------------------------------------ |
| **Method**        | `GET`                                                        |
| **Pfad**          | `/api/health/telemetry` (mit oder ohne abschließenden `/`)   |
| **Accept**        | `application/json`                                           |
| **Response-Type** | `application/json; charset=utf-8`                            |
| **Frequenz**      | On-Demand (Client-gesteuert)                                 |
| **Rate Limit**    | 5 Requests/Sekunde pro Client-IP, Burst 10 (sonst `429`)     |
| **Caching**       | Antwort bis zu 20 s serverseitig gecacht (siehe Abschnitt 9) |

### 1.1 Authentifizierung

Der Endpoint erfordert ein **Bearer-Token** im `Authorization`-Header. Das Token wird je Zentrale vergeben und vertraulich an das abrufende System übergeben.

| Methode          | Header / Mechanismus    | Beispiel                                     |
| ---------------- | ----------------------- | -------------------------------------------- |
| **Bearer Token** | `Authorization: Bearer` | `Authorization: Bearer <token>` |

Fehlt der Header oder ist das Token ungültig, antwortet der Endpoint mit `401`. Ist serverseitig kein Token konfiguriert, antwortet er mit `503` (fail-closed, keine Datenausgabe).

---

## 2. Endpoint

```
GET https://<zentrale-host>/api/health/telemetry
```

- `<zentrale-host>` = IP oder Hostname der Zentrale im lokalen Netz (z. B. `192.168.1.16`).
- Erreichbar über den Reverse-Proxy der Zentrale; der `Authorization`-Header wird unverändert durchgereicht.
- Der abschließende Slash ist optional (`/api/health/telemetry` und `/api/health/telemetry/` sind gleichwertig).

---

## 3. Request

Es wird **kein** Request-Body gesendet. Erforderlicher Header:

| Header          | Pflicht | Wert             |
| --------------- | ------- | ---------------- |
| `Authorization` | ja      | `Bearer <token>` |

**Beispiel:**

```bash
curl --location 'https://192.168.1.16/api/health/telemetry/' \
  --header 'Authorization: Bearer <token>'
```

---

## 4. Payload-Struktur (Response)

```
Root
├── timestamp
├── fireStation
├── deviceId
└── objects[]
    ├── type            (vehicle | room | hall)
    ├── vehicleId
    ├── licencePlate
    ├── callSign
    ├── vehicleType
    └── smokeDetectors[]
        ├── name
        ├── address
        ├── type
        └── ...
```

> **Hinweis zu Wertetypen:** Alle Schlüssel sind **camelCase**. Alle skalaren Werte werden als **JSON-String** ausgegeben (auch Zahlen und Flags, z. B. `"rssiDevice": "-71"`, `"battery": "false"`, `"alarmState": "0"`). `objects` und `smokeDetectors` sind echte JSON-Arrays.

---

## 5. 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 6) |

**Beispiel:**

```json
{
  "timestamp": "2026-06-09T11:24:13Z",
  "fireStation": "Feuerwehr Feuerstadt, Hauptstr. 112, 01234 Feuerstadt",
  "deviceId": "001A2B3C4D5E6F",
  "objects": [ ... ]
}
```

---

## 6. 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 bzw. `"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 8.1)      |
| `licencePlate`   | Kennzeichen                          | `string` | Max. 10 Zeichen; `"n.a."` falls nicht hinterlegt (siehe 8.1) |
| `callSign`       | Funkrufname                          | `string` | Max. 50 Zeichen                                              |
| `vehicleType`    | Fahrzeugtyp                          | `string` | Max. 50 Zeichen; `"n.a."` falls nicht hinterlegt (siehe 8.1) |
| `smokeDetectors` | Auflistung der Rauchsensoren         | `array`  | Array von SmokeDetector-Objekten; `[]` falls keine Melder zugeordnet (siehe 8.2) |

**Beispiel:**

```json
{
  "type": "vehicle",
  "vehicleId": "WVWZZZ3CZWE123456",
  "licencePlate": "FS-FW 112",
  "callSign": "1-HLF20-1",
  "vehicleType": "HLF20",
  "smokeDetectors": [ ... ]
}
```

---

## 7. SmokeDetector-Objekt

Alle Werte sind Strings (siehe Hinweis in Abschnitt 4). Fehlt ein einzelner Datapoint, wird ein typ-konformer Default geliefert (nie `null`) — siehe Abschnitt 8.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` | i. d. R. 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 8.2) |
| `operationTime`     | Betriebszeit (Tage)                   | `string` | numerisch, 0–9999                            |
| `dirtLevel`         | Verschmutzungsgrad                    | `string` | float-String (z. B. `"0.000000"`)            |
| `smokeLevel`        | Raucherkennungsgrad                   | `string` | float-String (z. B. `"0.000000"`)            |
| `alarmState`        | Alarmstatus                           | `string` | `"0"`–`"3"` (siehe Enum, Abschnitt 7.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"
}
```

### 7.1 Enum: 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 |

### 7.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) |

---

## 8. Sonderfälle & Defaults

### 8.1 Fahrzeug-Metadaten nicht deklariert

`vehicleId`, `licencePlate` und `vehicleType` werden je `callSign` aus der Fahrzeug-Stammdatenpflege der Zentrale gelesen. Verhalten pro Feld (einzeln):

| 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.

### 8.2 Gerät nicht erreichbar / nicht gepairt

| Fall                             | Verhalten                                                    |
| -------------------------------- | ------------------------------------------------------------ |
| Melder **gepairt, aber offline** | erscheint im Baum; `unreachState` = `"true"`; `unreachCumulative` = Tage seit letztem Kontakt bzw. `"n.a."`; übrige Werte = **zuletzt bekannter Stand** (kein Live-Funk-Poll beim Abruf) |
| Melder **nicht (mehr) gepairt**  | Melder fehlt im Array. Ein Fahrzeug ohne zugeordnete Melder liefert `"smokeDetectors": []` |

> `unreachCumulative` = `"n.a."` bedeutet „nicht in der Erreichbarkeits-Historie der Zentrale geführt", **nicht** zwingend „erreichbar".

### 8.3 Fehlender Datapoint → typ-konformer Default

Existiert ein einzelner Sensor-Datapoint nicht (abweichendes Geräteprofil o. Ä.), wird statt `null` ein Default ausgegeben:

| Feld(er)                                                     | Default      |
| ------------------------------------------------------------ | ------------ |
| `battery`, `unreachState`, `chamber`                         | `"false"`    |
| `rssiDevice`, `rssiPeer`, `errorCode`, `operationTime`, `alarmState` | `"0"`        |
| `voltage`, `smokeLevel`, `dirtLevel`                         | `"0.000000"` |
| `firmware`, `group`, `version`, `teams` (keine Geräte-Metadaten) | `""`         |
| `unreachCumulative` (keine Historie)                         | `"n.a."`     |

---

## 9. Caching & Nebenläufigkeit

- **TTL-Cache:** Die Antwort wird serverseitig bis zu **20 s** zwischengespeichert. Aufeinanderfolgende Abrufe innerhalb dieses Fensters liefern denselben (bis zu 20 s alten) Stand, ohne die Zentrale erneut abzufragen.
- **Single-Flight:** Pro Cache-Miss läuft höchstens **eine** Datenerhebung. Treffen mehrere Abrufe gleichzeitig ein, teilen sie sich das laufende Ergebnis; es werden keine parallelen Erhebungen gestartet.
- Die Werte spiegeln den zuletzt in der Zentrale bekannten Zustand der Sensoren wider (kein aktiver Funk-Poll der Geräte beim Abruf).

---

## 10. Response (HTTP)

### 10.1 Status Codes

| Code                      | Bedeutung                                           |
| ------------------------- | --------------------------------------------------- |
| `200 OK`                  | Telemetrie erfolgreich geliefert (Body = Datenbaum) |
| `401 Unauthorized`        | Fehlender oder ungültiger `Authorization`-Header    |
| `429 Too Many Requests`   | Rate Limit überschritten                            |
| `502 Bad Gateway`         | Telemetrie nicht lesbar (Zentrale nicht erreichbar) |
| `503 Service Unavailable` | Serverseitig kein API-Token konfiguriert            |

### 10.2 Success Response

Body ist der vollständige Telemetrie-Datenbaum (siehe Abschnitt 11).

### 10.3 Error Response

```json
{
  "error": "unauthorized"
}
```

```json
{
  "error": "telemetryUnavailable",
  "detail": "telemetry source returned HTTP 500"
}
```

| `error`                 | HTTP  | Bedeutung                               |
| ----------------------- | ----- | --------------------------------------- |
| `unauthorized`          | `401` | Token fehlt/falsch                      |
| `telemetryUnavailable`  | `502` | Datenerhebung fehlgeschlagen (`detail`) |
| `apiTokenNotConfigured` | `503` | Kein Token gesetzt                      |

---

## 11. Vollständiges Response-Beispiel

```json
{
  "timestamp": "2026-06-09T11: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"
        }
      ]
    }
  ]
}
```

# Funktionserfüllung Safe Fire House Anlage

<p class="callout info">Alle Infos finden Sie unter https://dexa.gmbh</p>

### 1. Alleinstellungsmerkmale (in Kombination)

- System ausgelegt für Einsatz in (Einsatz-)Fahrzeugen. Kein manueller Eingriff bei Aus- und Einrücken von Fahrzeugen, kein "leer ziehen der Batterien".
- Multistandortfähigkeit, Fahrzeuge werden an allen Standorten überwacht, Alarme werden mit Standort übertragen.
- Bidirektionale vollwertige Schnittstellenanbingung, rückmeldeabhängiger Alarmüberlauf und Redundanz. Kein "fire and forget" Integration in alle BOS-Systeme mit Leitstellenanbindung.
- Ohne Cloud-Dienste betreibbar.
- Leise und stumme Melder (Sensoren).
- Als einziges System für den Einsatz in (Einsatz-)Fahrzeugen von einer staatlichen Prüfstelle geprüft und testiert (Prüfstelle für Brandschutztechnik des österreichischen Bundesfeuerwehrverbandes. [Prüfbericht hier herunterladen](https://dexa.gmbh/uploads/downloads/PruefberichtOeBFVSFH2026.pdf)

### 2. Preiskalkulation

- Angebot eines All-Inklusive-Preises, unabhängig von Anzahl, Art und Beschaffenheit der Komponenten, Standorte oder Fahrzeuge.
- Preis unabhängig von Kommunikationsschnittstellen oder auszuführenden Aktionen – stets inkludiert – als Pauschalpreis pro Fahrzeug, Halle oder Raum, mit Bruttopreissumme.
- Inkludierung aller Service-Nebenkosten und Stundensätze, außer Reisekosten. Keine laufenden bzw. konsumtiven Kosten oder Lizenzen.
- Inkludierung aller Kosten für laufenden Betrieb, Projektierung, Programmierung, Installation, Inbetriebnahme und Dokumentation.

### 3. Systemaufbau

- Modularer, individueller und flexibler Aufbau entsprechend den Anforderungen von Fahrzeugen und Standorten.
- Verwendung namhafter Komponentenhersteller
- Betrieb ausschließlich on-premise (nicht cloud-basiert). Keine Abhängigkeit von Mobilfunk- oder Clouddiensten bei der Verbindung von Meldern/Sensoren zu Alarmsystemen, oder Angebot einer gleichwertigen lokalen Redundanz.
- Permanente Überwachung aller Komponenten und Kommunikationswege, die größtenteils redundant ausgelegt werden müssen.
- Ausführbar als Mehr-Standort-Lösung, sprich standortübergreifende Überwachung von Fahrzeugen ohne manuellen Eingriff, ohne An- oder Abmelden bzw. An- oder Ablernen. Alarmierung inklusive aktueller Standortdaten (Rauchalarm in Fahrzeug X, Melder Y aktuell an Standort Z).
- Keine manuelle Eingriffsnotwendigkeit bei Verlassen des Standortes, keine Notwendigkeit zu quittieren. Auch bei Wechsel des Fahrzeugs an einen anderen überwachten Standort.
- Anlage nahtlos erweiterbar von 1 Fahrzeug (bzw. Halle oder Raum) an 1 Standort bis zu 1.000 Fahrzeugen (bzw. Halle oder Raum) an 200 Standorten.
- TCP/IP-Kommunikation als Grundarchitektur, alle Kommunikationswege (auch Funk) sind bidirektional, verschlüsselt und protokollierbar auszuführen. Ausführung aller IT-Komponenten in PoE.
- Anbindungsmöglichkeit an alle gängigen Leitstellensysteme aus dem BOS-Bereich.
- Anbindungsmöglichkeit an Brandmeldeanlagen über potenzialfreien Schaltkontakt. Aufschaltung auf eine Kreisleitstelle mit behördlicher Genehmigung im Rahmen der geltenden Aufschaltbedingungen möglich.
- Auslegung der Anlage als Gefahrenwarnanlage in Anlehnung an VDE 0826.
- Anbindungsmöglichkeit an gängige Gebäudeleitsysteme (BUS- oder TCP/IP-basiert).
- Schriftliche Bestätigung des Herstellers des Gesamtsystems, dass es zum Einsatzzweck der Überwachung der Fahrzeuginnenräume geeignet ist, zur Haftungssicherheit.
- Testat einer namhaften Prüforganisation über die Eignung zum Einsatz in Einsatzfahrzeugen.
- Regel-Lebensdauer der Anlage von bis zu 10 Jahren, wartungsfrei.

### 4. Branderkennung und Alarmverarbeitung

- Zentrales Alarmmanagement
- Photoelektrische Erkennung
- Batterielebensdauer: ausgelegt auf bis zu 10 Jahre
- Kommunikationsprotokolle: bidirektionales Funkband (z. B. 868 MHz) und TCP/IP
- Verschlüsselung: AES (mind. 128 Bit), optional HMAC
- Reichweite: bis zu 300 m (Freifeld), mehrfach erweiterbar durch Repeater
- Zertifizierungen der Rauchmelder (nur bei Meldern in Gebäuden, nicht in Fahrzeugen): VdS, Q-Label, DIN EN 14604/14676
- Schutzklassen: IP20 bis IP44 (je nach Komponente)
- Alarmierung: Piezo-Signalgeber (85 dB/3 m), zusätzlich auch ausführbar in leise (50 dB/3 m) und stumm
- (Optional): Server-Variante (Central Unit Pro) zur Installation in einem 19-Zoll Server-Rack für große Installationen zur Virtualisierung des Systems.
- Selbstkalibrierend
- Selbsttestfunktion, Watchdog-Funktion, zentrale Batterieüberwachung
- Hochpräzise Sensoren für Partikelerkennung, erweiterter Insektenschutz
- LED-Notbeleuchtung bei Alarm
- Autarker Betrieb der Rauchsensoren bei Zentralenausfall – lokale Alarmierung weiter möglich
- (Optional): Infrastrukturmodul: Schaltkasten oder mobiler Koffer mit unterbrechungsfreier Batteriepufferung (USV) bis zu 72 Stunden und redundanter Internetanbindung (WAN + LTE/5G) im M2M-Betrieb für höchste Ausfallsicherheit.
- (Optional): Alarmsirene: Externe LED-Alarmsirene zur optischen und akustischen Signalisierung, außeninstallationstauglich (IP44), Batterie-/Solar- oder 230V-Betrieb, mit integrierter Blitzleuchte
- (Optional): Schaltkontakt: Potenzialfreier Schaltkontakt zur Anbindung von Brandmeldeanlagen und Gebäudeleittechnik, verfügbar als 1-fach und 4-fach Variante mit bis zu 4 Eingangs- und 4 Ausgangskanälen
- Erweiterte API-Unterstützung: REST-API (POST/PUT/GET), MQTT optional, Webhooks
- Bidirektionale Anbindung von Schnittstellen: Alle gängigen Alarmsysteme aus dem BOS-Bereich, z. B. DIVERA24/7, Alamos, Groupalarm, Dräger, WeberRescue, Feuersoftware, Fireboard, Feuernetz, DE-Alarm, alarmdispatcher, FF-Agent, Handyalarm.com, Solaris/Blaulicht SMS, …
- Anbindung an Verwaltungssysteme wie Fireplan, Solaris, FWPortal, MPFeuer
- Wartungsinformationen als Schnittstellenanbindung zu Flottenmanagement- und Rettungsdienst-Systemen, z. B. ZF Rescue Connect, Dräger Smart Rescue System, Weber Rescue RetterAlarm
- Alarmanbindung durch Softwareschnittstellen für TR-BOS-konforme digitale Meldeempfänger, zum Beispiel über LTE (Swissphone oder Oelmann Electronics) über die Dexa Pager Plattform
- Leitstellenanbindung: gängige Leitstellensysteme, z. B. ISE Cobra4, CKS Celios, VivaSecur LVS, ELIS, Hexagon, …
- Schaltanbindung an Brandmeldeanlagen über potenzialfreien Schaltkontakt
- Niederschwellige Alarmwege wie Telefonansage mit Rückmeldestatus, SMS, E-Mail
- Alarmüberlauf auf andere Gruppen und Schnittstellen, abhängig von Rückmeldungen der alarmierten Einheiten
- Mindestens wöchentlicher Statusbericht bei Nichterreichbarkeit von Schnittstellen oder Komponenten
- Mindestens monatlicher Statusbericht über alle Komponenten mit Batterieständen, Verschmutzungsgraden und weiteren Zuständen
- Sichere Anbringung in Fahrzeugen (z. B. 3M Dual-Lock o.ä.) oder Verschraubung
- Gebäudeleittechnik: anbindbar mit TCP/IP, Webhooks, an KNX, Home Assistant, Symcon, DALI, BACnet, MODBUS
- (Optional): Mini-Display mit E-Ink-Technologie zur Alarmanzeige, auch als konfigurierbares Taster-Element nutzbar, 230V-stromversorgt
- (Optional): Fest installierbares Netzwerkdisplay (PoE) zur Alarmanzeige, Steuerung und Überwachung der Anlage in verschiedenen Größen und Formfaktoren, versorgt über PoE, mit Möglichkeit zur Alarmquittierung, Anlagenkonfiguration und Unscharfschaltung
- Netzwerkkompatibilität im Enterprise-Umfeld, Firewall-Betrieb (z. B. Sophos)
- Grafische Bedienoberfläche aus internem und externem Netzwerk über Internetfreigabe (verschlüsselt und geschützt) erreichbar.
- Umfangreiche Zustandsüberwachung und Steuerung über digitales Bedientableau und grafische Oberfläche (GUI) möglich (GUI verfügbar im Rahmen einer Wartungsvereinbarung)

### 5. Installation und Inbetriebnahme

- Durchführung durch erfahrene technische Angestellte mit Erfahrung in TGA-, ITK-Systemen und/oder Fahrzeugtechnik
- Individuelle standortabhängige Programmierung
- Vorab-Tests und Simulationen zur Funktionssicherung
- Schulungen für Bedienpersonal, Wartungstechniker und IT-Administratoren

### 6. Dokumentation und Güte

- Vollständige technische Dokumentation: Installation, Betriebstest, Messwerte, Konfiguration, Nachweis eines Alarmtests
- Positiver Prüfbericht einer namhaften Prüfstelle für Brandschutztechnik, in diesem Falle der Prüfstelle für Brandschutztechnik des Österreichischen Bundesfeuerwehrverbandes (Prüfbericht-Nr. FT 14/953/25, gültig bis 02.12.2027).

### 7. Service

- Dediziertes Supportteam, optional Vor-Ort-Service und Remote-Unterstützung
- Umfassende Möglichkeiten zur Fernwartung
- Optionale Möglichkeit zum Abschluss einer Wartungsvereinbarung für erweiterte Funktionalitäten und externes Monitoring der Anlagenverfügbarkeit
- Zentrales Service-Logbuch zur Überwachung der Systemzuverlässigkeit durch Anbieter