# Spezifikationen

Technische Spezifikationen und API-Anforderungen zum Safe Fire House

# API Schnittstellenspezifikation

# API-Anforderungen für Alarmierungen

**Version:** 1.2  
*Stand: 2026-03*  
**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, beispielsweise `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, beispielsweise `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 (beispielsweise 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 (beispielsweise 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 Gebäudezustandsmeldungen

**Version:** 1.0  
*Stand: 2026-10*  
**Herausgeber:** Dexa Solutions GmbH  
**Produkt:** Safe Fire House (SFH) – Einbruchwarnsystem (EWS)  

---

## 1. Übersicht

Diese Spezifikation beschreibt die REST-Schnittstelle, über die ein Drittsystem – zum Beispiel eine Zutrittskontrollanlage, eine Gebäudeleittechnik oder eine Hausautomation – dem Einbruchwarnsystem der Safe-Fire-House-Zentrale den **Gebäudezustand** meldet und den Zustand der Anlage abfragt.

Die Schnittstelle ersetzt den potentialfreien Kontakt eines Schlüsselschalters: Statt einen Draht zu schalten, teilt das Drittsystem der Zentrale über das Netzwerk mit, ob das Gebäude **zugeschlossen** oder **aufgeschlossen** ist.

### 1.1 Grundprinzip: Zustand statt Befehl

Das Drittsystem meldet ausschließlich einen **Zustand**, keinen Schaltbefehl. Ob daraus ein Scharf- oder Unscharfschalten folgt, entscheidet das Regelwerk der Zentrale – nach denselben Regeln wie beim Schlüsselschalter (Abschnitt 6).

<p class="callout info">Ein fehlerhaftes oder kompromittiertes Drittsystem kann dadurch höchstens einen falschen Gebäudezustand behaupten. Die Schutzregeln der Anlage – aktiver Alarm, Wartung, Störung – bleiben immer davor.</p>

### 1.2 Funktionen

| Funktion | Methode | Pfad | Recht |
|----------|---------|------|-------|
| Zustand der Anlage abfragen | GET | `/api/intrusion/access/state` | Lesen |
| Gebäudezustand melden | POST | `/api/intrusion/access/report` | Schreiben |

---

## 2. API-Parameter

| Parameter | Wert |
|-----------|------|
| **Adresse** | `https://{adresse-der-zentrale}` – wird bei der Einrichtung mitgeteilt |
| **Content-Type** | `application/json; charset=UTF-8` |
| **Accept** | `application/json` |
| **Zeichenkodierung** | UTF-8 |
| **Max. Größe einer Anfrage** | 4 KB |

Jede Zentrale hat **eine** Adresse, auch wenn sie mehrere Standorte überwacht. Welcher Standort gemeint ist, steht als Standort-Kennung in der Anfrage (Abschnitt 2.2).

Die Schnittstelle ist in der Voreinstellung nur im internen Netz des Kunden erreichbar. Ein Zugang von außen wird nur auf ausdrücklichen Wunsch eingerichtet.

### 2.1 Authentifizierung

Die Authentifizierung erfolgt über einen **Zugangsschlüssel (Token)** im HTTP-Kopf:

```http
Authorization: Bearer {zugangsschluessel}
```

| Eigenschaft | Beschreibung |
|-------------|--------------|
| Ausgabe | Wird bei der Einrichtung auf der Zentrale erzeugt und **einmalig** angezeigt. Er kann danach nicht erneut angezeigt werden – geht er verloren, wird ein neuer erzeugt. |
| Speicherung | Die Zentrale speichert den Schlüssel nicht im Klartext, sondern nur einen daraus abgeleiteten Prüfwert. |
| Recht | Je Schlüssel festgelegt: **Lesen** oder **Schreiben**. Ein Schlüssel mit Recht „Schreiben“ darf auch abfragen. |
| Standorte | Je Schlüssel festgelegt, für welche Standorte er gilt. |
| Ablauf | Ohne Ablaufdatum (Voreinstellung) oder befristet, z. B. für Testzugänge. |
| Widerruf | Jederzeit auf der Zentrale möglich. |
| Absender | Optional auf bestimmte Absenderadressen beschränkbar. |

<p class="callout info">Der Schlüssel gehört ausschließlich in den Kopf der Anfrage, niemals in die Adresse. Adressen werden in Protokollen gespeichert, der Kopf nicht.</p>

**Empfehlung:** Für Systeme, die den Zustand nur anzeigen, einen Schlüssel mit Recht „Lesen“ verwenden. Wird ein solches System kompromittiert, kann die Anlage darüber nicht beeinflusst werden.

### 2.2 Standort-Kennung

Jeder Standort einer Zentrale hat eine Standort-Kennung (`locationHash`, 32 Zeichen hexadezimal). Sie wird bei der Einrichtung mitgeteilt.

<p class="callout info">Die Standort-Kennung ist **keine** Berechtigung. Sie sagt nur, welcher Standort gemeint ist. Ob er gemeldet oder abgefragt werden darf, entscheidet allein der Zugangsschlüssel. Eine Meldung für einen Standort, für den der Schlüssel nicht freigegeben ist, wird abgewiesen.</p>

### 2.3 Rate Limiting

| Parameter | Wert |
|-----------|------|
| Max. Anfragen | 1 pro Sekunde je Absender, kurzzeitig bis zu 5 |
| Bei Überschreitung | HTTP 429 |

Ein Zutrittssystem meldet bei Zustandswechsel und höchstens ein Lebenszeichen pro Minute. Mehr Anfragen sind im Betrieb nicht vorgesehen.

---

## 3. Zustand abfragen (GET)

Liefert den Zustand der Anlage für alle Standorte, für die der Schlüssel freigegeben ist.

### 3.1 Request

```http
GET /api/intrusion/access/state HTTP/1.1
Host: {adresse-der-zentrale}
Authorization: Bearer {zugangsschluessel}
Accept: application/json
```

### 3.2 Response (Erfolg)

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "locations": [
    {
      "locationHash": "9f2c4e7a1b3d5f6081a2c3d4e5f60718",
      "name": "Lagerhalle Nord",
      "armingState": "armed",
      "alarmState": "none",
      "fault": false,
      "lastReportAt": "2026-10-14T06:58:12.000Z"
    }
  ],
  "timestamp": "2026-10-14T07:02:40.000Z"
}
```

### 3.3 Response-Felder

#### Root-Objekt

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `locations` | array | Ein Eintrag je freigegebenem Standort |
| `timestamp` | string | Zeitpunkt der Antwort (ISO 8601, UTC) |

#### locations-Objekt (Array-Element)

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `locationHash` | string | Standort-Kennung |
| `name` | string | Name des Standorts |
| `armingState` | string | Scharfzustand der Anlage (siehe Enum) |
| `alarmState` | string | Alarmzustand der Anlage (siehe Enum) |
| `fault` | boolean | `true`, wenn an diesem Standort eine Störung vorliegt – der Schnittstelle oder der Anlage (z. B. Kamera, Netzwerk) |
| `lastReportAt` | string \| null | Zeitpunkt der letzten angenommenen Meldung (ISO 8601, UTC), `null` wenn noch keine Meldung einging |

#### armingState Enum

| Wert | Beschreibung |
|------|--------------|
| `armed` | Standort ist scharf |
| `arming` | Scharfschaltung läuft (Vorlaufzeit, Kameras starten) |
| `disarmed` | Standort ist unscharf |

#### alarmState Enum

| Wert | Beschreibung |
|------|--------------|
| `none` | Kein Alarm |
| `prealarm` | Voralarm läuft |
| `alarm` | Alarm ist ausgelöst und noch nicht quittiert |

<p class="callout info">Scharfzustand und Alarmzustand sind bewusst zwei getrennte Felder. Der Zustand „unscharf, Alarm noch offen“ entsteht, wenn bei laufendem Alarm aufgeschlossen wird (Abschnitt 6).</p>

Den Gebäudezustand selbst (auf- oder zugeschlossen) liefert die Abfrage **nicht** zurück – diesen kennt das Drittsystem selbst.

---

## 4. Gebäudezustand melden (POST)

Meldet der Zentrale, ob das Gebäude eines Standorts zugeschlossen oder aufgeschlossen ist. Erfordert einen Schlüssel mit Recht „Schreiben“.

### 4.1 Request

```http
POST /api/intrusion/access/report HTTP/1.1
Host: {adresse-der-zentrale}
Authorization: Bearer {zugangsschluessel}
Content-Type: application/json
Accept: application/json
```

### 4.2 Request-Body

```json
{
  "locationHash": "9f2c4e7a1b3d5f6081a2c3d4e5f60718",
  "buildingLocked": true,
  "timestamp": "2026-10-14T06:58:12Z"
}
```

### 4.3 Request-Felder

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `locationHash` | string | ✓ | Standort-Kennung des gemeldeten Standorts |
| `buildingLocked` | boolean | ✓ | `true` = Gebäude zugeschlossen, `false` = Gebäude aufgeschlossen |
| `timestamp` | string | | Zeitpunkt der Meldung (ISO 8601). Empfohlen – aktiviert den Wiederholschutz (Abschnitt 7.3) |

<p class="callout info">`buildingLocked` muss ein echter Wahrheitswert sein: `true` oder `false`. Zeichenketten wie `"true"`, Zahlen wie `1` oder fehlende Werte werden mit HTTP 400 abgewiesen.</p>

### 4.4 Response (Erfolg)

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "ok": true,
  "accepted": { "buildingLocked": true },
  "timestamp": "2026-10-14T06:58:12.000Z"
}
```

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `ok` | boolean | `true`, wenn die Meldung angenommen wurde |
| `accepted.buildingLocked` | boolean | Der angenommene Gebäudezustand |
| `timestamp` | string | Zeitpunkt der Verarbeitung (ISO 8601, UTC) |

<p class="callout info">„Angenommen“ heißt: Die Zentrale kennt jetzt den neuen Gebäudezustand. Ob die Anlage daraufhin geschaltet hat, zeigt die Abfrage (Abschnitt 3) – die Scharfschaltung kann je nach Regelwerk ausbleiben oder mit Vorlaufzeit erfolgen.</p>

---

## 5. Fehlerbehandlung

### 5.1 HTTP-Statuscodes

| Code | Bedeutung | Beschreibung |
|------|-----------|--------------|
| `200` | OK | Abfrage beantwortet bzw. Meldung angenommen |
| `400` | Bad Request | Ungültiger Inhalt, `buildingLocked` kein Wahrheitswert, oder `timestamp` unlesbar, zu alt bzw. bereits verwendet |
| `401` | Unauthorized | Schlüssel fehlt oder ist unbekannt |
| `403` | Forbidden | Schlüssel widerrufen oder abgelaufen, Absender nicht freigegeben, Standort für diesen Schlüssel nicht freigegeben, Schlüssel nur lesend, oder Anfrage nicht verschlüsselt (http statt https) |
| `404` | Not Found | Unbekannter Pfad |
| `413` | Payload Too Large | Anfrage größer als 4 KB |
| `429` | Too Many Requests | Rate Limit überschritten |
| `503` | Service Unavailable | Schnittstelle auf der Zentrale vorübergehend nicht verfügbar |

### 5.2 Fehler-Response

```json
{
  "error": "forbidden"
}
```

| Fehlercode | Beschreibung |
|------------|--------------|
| `unauthorized` | Zugang abgewiesen (Schlüssel fehlt, unbekannt, widerrufen, abgelaufen oder Absender nicht freigegeben) |
| `read_only` | Schlüssel hat nur Leserecht |
| `forbidden` | Standort für diesen Schlüssel nicht freigegeben |
| `bad_request` | Inhalt ungültig |
| `not_found` | Unbekannter Pfad |
| `unavailable` | Schnittstelle vorübergehend nicht verfügbar |

<p class="callout info">Die Fehlermeldungen nach außen sind bewusst knapp und nennen nicht, woran eine Anmeldung gescheitert ist. Der genaue Grund steht im Prüfprotokoll der Zentrale und kann vom Errichter bzw. von dexa eingesehen werden.</p>

### 5.3 Retry-Strategie (Drittsystem)

| Fehlertyp | Wiederholen | Wartezeit |
|-----------|-------------|-----------|
| Netzwerkfehler | Ja | 5 s, 10 s, 30 s |
| HTTP 503 | Ja | 5 s, 10 s, 30 s |
| HTTP 429 | Ja | mindestens 1 s |
| HTTP 400, 401, 403, 404 | Nein | – Konfiguration prüfen |

Bei einer Wiederholung mit Zeitstempel bitte einen **neuen** Zeitstempel verwenden – eine Anfrage mit bereits verwendetem Zeitstempel wird als Wiederholung abgewiesen.

---

## 6. Verhalten der Anlage

Eine angenommene Meldung wirkt wie das Schalten eines Schlüsselschalters:

| Meldung | Wirkung |
|---------|---------|
| Aufgeschlossen (`false`) | Standort wird **sofort unscharf** geschaltet, ohne Vorlaufzeit |
| Zugeschlossen (`true`) | Standort wird über den **normalen Weg scharf** geschaltet: Vorlaufzeit, Kamerastart, Kameraprüfung |
| Gleicher Zustand wie zuvor | Keine Wirkung |
| Erste Meldung nach Einrichtung | Zustand wird übernommen, es wird noch nicht geschaltet |

### 6.1 Schutzregeln

| Situation | Verhalten |
|-----------|-----------|
| Alarm aktiv, Meldung „aufgeschlossen“ | Standort wird unscharf, der Alarm bleibt **unquittiert** |
| Alarm aktiv, Meldung „zugeschlossen“ | Es wird **nicht** scharf geschaltet. Nach Quittierung des Alarms holt die Anlage die Scharfschaltung nach, wenn das Gebäude weiterhin als zugeschlossen gemeldet ist |
| Wartung aktiv | Keine Schaltwirkung, nur Protokoll |
| Störung der Schnittstelle | Keine Schaltwirkung, nur Protokoll |

Der zuletzt gemeldete Gebäudezustand bleibt auch über einen Neustart der Zentrale erhalten.

### 6.2 Lebenszeichen

Anders als beim Kontakt eines Schlüsselschalters meldet sich ein Ausfall des Drittsystems nicht von selbst – ein Drahtbruch wird erkannt, ein stummes Drittsystem nicht.

<p class="callout info">Deshalb kann je Zugangsschlüssel ein **Lebenszeichen** eingestellt werden: „Meldung erwartet alle … Minuten“ (Vorschlag 15 Minuten). **Jede** Anfrage zählt als Lebenszeichen, auch eine reine Abfrage – ein eigener Aufruf ist nicht nötig.</p>

| Einstellung | Verhalten bei ausbleibender Meldung |
|-------------|-------------------------------------|
| Lebenszeichen eingestellt | Die Zentrale meldet **Störung** und friert den Gebäudezustand ein. Der Scharfzustand bleibt unverändert – eine scharfe Anlage wird durch einen Ausfall des Drittsystems **nicht** unscharf. Mit der nächsten Meldung gilt wieder der gemeldete Zustand. |
| Kein Lebenszeichen (Voreinstellung) | Der zuletzt gemeldete Zustand gilt unbegrenzt weiter. Ein Ausfall des Drittsystems bleibt unbemerkt. Nur sinnvoll für Systeme, die ausschließlich bei Änderung melden können. |

**Empfehlung:** Das Drittsystem fragt in regelmäßigen Abständen den Zustand ab (Abschnitt 3) und die Zentrale wird mit Lebenszeichen betrieben.

---

## 7. Sicherheitsanforderungen

### 7.1 Übersicht

| Anforderung | Beschreibung |
|-------------|--------------|
| **Transport** | Ausschließlich HTTPS. Anfragen über unverschlüsseltes HTTP werden abgewiesen. |
| **Schlüsselspeicherung** | Auf der Zentrale nur als abgeleiteter Prüfwert, Vergleich in konstanter Zeit |
| **Rechtetrennung** | Getrennte Schlüssel für Lesen und für Schreiben |
| **Standortbindung** | Ein Schlüssel gilt nur für die ihm zugeordneten Standorte |
| **Absenderbeschränkung** | Optional je Schlüssel |
| **Erreichbarkeit** | Voreinstellung nur internes Netz, von außen nur auf ausdrücklichen Wunsch |
| **Methoden** | Nur GET und POST |
| **Gehäufte Fehlversuche** | Ab 10 abgewiesenen Anfragen innerhalb von 10 Minuten meldet die Zentrale eine Störung an den Betreiber (Abschnitt 7.2) |
| **Prüfprotokoll** | Jede Anfrage wird auf der Zentrale protokolliert: Zeitpunkt, Absender, Kennung des Schlüssels, Standort, Aktion, Ergebnis. Der Schlüssel selbst wird nie protokolliert. |

### 7.2 Gehäufte Fehlversuche

Die Zentrale **sperrt nicht**, sondern **meldet**: Werden innerhalb von 10 Minuten 10 Anfragen wegen ungültigem Zugangsschlüssel, fehlender Berechtigung oder nicht freigegebenem Standort abgewiesen, setzt sie eine Störung und benachrichtigt den Betreiber mit den Absenderadressen. Die Störung endet 60 Minuten nach dem letzten Fehlversuch.

<p class="callout info">Bewusst keine Sperre: Eine Sperre würde auch ein falsch eingerichtetes Drittsystem aussperren – oder ein korrektes System, das hinter derselben Internetadresse wie ein Angreifer liegt. Dann kämen auch echte Meldungen wie „aufgeschlossen“ nicht mehr an. Die Meldung macht Angriffe und Fehlkonfiguration sichtbar, ohne den Betrieb zu blockieren. Durchprobieren ist durch die Länge des Zugangsschlüssels und die Begrenzung auf 1 Anfrage pro Sekunde praktisch ausgeschlossen.</p>

Während die Störung besteht, liefert die Abfrage (Abschnitt 3) für alle Standorte `"fault": true`.

### 7.3 Wiederholschutz

Wird ein `timestamp` mitgesendet, prüft die Zentrale:

- Er darf höchstens **5 Minuten** von der Uhrzeit der Zentrale abweichen.
- Derselbe Zeitstempel wird je Schlüssel nur **einmal** angenommen.

Damit kann eine mitgeschnittene Anfrage nicht später erneut eingespielt werden. Ohne `timestamp` entfällt dieser Schutz für die betreffende Anfrage; die Uhr des Drittsystems sollte deshalb über NTP synchronisiert sein und der Zeitstempel immer mitgesendet werden.

---

## 8. Beispiel: Vollständiger Ablauf

### 8.1 Schritt 1: Gebäude wird abends zugeschlossen

```bash
curl -X POST "https://{adresse-der-zentrale}/api/intrusion/access/report" \
  -H "Authorization: Bearer {zugangsschluessel}" \
  -H "Content-Type: application/json" \
  -d '{
    "locationHash": "9f2c4e7a1b3d5f6081a2c3d4e5f60718",
    "buildingLocked": true,
    "timestamp": "2026-10-14T18:05:00Z"
  }'
```

**Response:**
```json
{ "ok": true, "accepted": { "buildingLocked": true }, "timestamp": "2026-10-14T18:05:00.412Z" }
```

### 8.2 Schritt 2: Scharfschaltung prüfen

```bash
curl -X GET "https://{adresse-der-zentrale}/api/intrusion/access/state" \
  -H "Authorization: Bearer {zugangsschluessel}" \
  -H "Accept: application/json"
```

**Response während der Vorlaufzeit:**
```json
{
  "locations": [
    { "locationHash": "9f2c4e7a1b3d5f6081a2c3d4e5f60718", "name": "Lagerhalle Nord",
      "armingState": "arming", "alarmState": "none", "fault": false,
      "lastReportAt": "2026-10-14T18:05:00.412Z" }
  ],
  "timestamp": "2026-10-14T18:05:20.000Z"
}
```

Nach Ablauf der Vorlaufzeit wechselt `armingState` auf `armed`.

### 8.3 Schritt 3: Gebäude wird morgens aufgeschlossen

```bash
curl -X POST "https://{adresse-der-zentrale}/api/intrusion/access/report" \
  -H "Authorization: Bearer {zugangsschluessel}" \
  -H "Content-Type: application/json" \
  -d '{
    "locationHash": "9f2c4e7a1b3d5f6081a2c3d4e5f60718",
    "buildingLocked": false,
    "timestamp": "2026-10-15T06:30:00Z"
  }'
```

Der Standort wird sofort unscharf; die Abfrage liefert danach `"armingState": "disarmed"`.

---

## 9. Einrichtung

| Schritt | Wer | Inhalt |
|---------|-----|--------|
| 1 | Errichter / dexa | Auf der Zentrale eine Zutrittskontroll-Schnittstelle vom Typ „API“ anlegen |
| 2 | Errichter / dexa | Zugangsschlüssel erzeugen: Standorte, Recht, optional Lebenszeichen, Absender, Ablauf |
| 3 | Errichter / dexa | Adresse, Standort-Kennung(en) und Zugangsschlüssel sicher an den Errichter des Drittsystems übergeben |
| 4 | Errichter Drittsystem | Schnittstelle einrichten, Abfrage testen, dann Meldungen „zu“ und „auf“ testen |
| 5 | Beide | Ergebnis über die Abfrage und das Prüfprotokoll der Zentrale gemeinsam kontrollieren |

---

## Anhang A: Constraints

| Feld | Constraint |
|------|------------|
| `locationHash` | 32 Zeichen, hexadezimal (`0-9`, `a-f`), Groß-/Kleinschreibung egal |
| `buildingLocked` | JSON-Wahrheitswert `true` oder `false` |
| `timestamp` | ISO 8601, höchstens 5 Minuten Abweichung zur Zentrale, je Schlüssel nur einmal verwendbar |
| Größe einer Anfrage | Max. 4 KB |
| Methoden | GET, POST |
| Anfragerate | 1 pro Sekunde je Absender, kurzzeitig bis zu 5 |

---

# 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"
        }
      ]
    }
  ]
}
```

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

**Version:** 1.6  
*Stand: 2026-06*  
**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 (beispielsweise `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, beispielsweise `"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 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 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` | 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 8.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 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 beziehungsweise `"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.
- Kein Eingriff in die Fahrzeugelektronik notwendig.
- 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.

### 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