{"id":48732505,"url":"https://github.com/abeggled/openbridgeserver","last_synced_at":"2026-06-14T01:02:16.326Z","repository":{"id":347170385,"uuid":"1193041765","full_name":"abeggled/openbridgeserver","owner":"abeggled","description":"Open Source Multiprotocol AI Server for Building Automation (KNX/Modbus/1-Wire via MQTT)","archived":false,"fork":false,"pushed_at":"2026-06-13T20:51:43.000Z","size":10104,"stargazers_count":16,"open_issues_count":75,"forks_count":8,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T22:24:06.645Z","etag":null,"topics":["1wire","building-automation","homeautomation","homeautomationsystem","knx","modbus","mqtt"],"latest_commit_sha":null,"homepage":"https://open-bridge.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/abeggled.png","metadata":{"files":{"readme":"README.de.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.MD","dco":null,"cla":null}},"created_at":"2026-03-26T20:16:38.000Z","updated_at":"2026-06-13T20:26:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/abeggled/openbridgeserver","commit_stats":null,"previous_names":["abeggled/opentws","abeggled/openbridgeserver"],"tags_count":53,"template":false,"template_full_name":null,"purl":"pkg:github/abeggled/openbridgeserver","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abeggled%2Fopenbridgeserver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abeggled%2Fopenbridgeserver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abeggled%2Fopenbridgeserver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abeggled%2Fopenbridgeserver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/abeggled","download_url":"https://codeload.github.com/abeggled/openbridgeserver/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abeggled%2Fopenbridgeserver/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34305789,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["1wire","building-automation","homeautomation","homeautomationsystem","knx","modbus","mqtt"],"created_at":"2026-04-12T02:09:30.869Z","updated_at":"2026-06-14T01:02:16.305Z","avatar_url":"https://github.com/abeggled.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# open bridge multiprotocol ai server\n\n![**open bridge server** Logo](logo/obs_logo_dark.svg)\n\n![Version](https://img.shields.io/github/v/release/abeggled/openbridgeserver?style=for-the-badge)\n[![Tests][tests-badge]][tests]\n[![Coverage][coverage-badge]][coverage]\n\n\u003e 🇬🇧 [English version](/README.md)\n\n**Offene Gebäudeautomations-Plattform — verbindet KNX, Modbus, MQTT, Home Assistant und mehr**\n\nopen bridge verbindet verschiedene Gebäudetechnik-Protokolle zu einem einheitlichen System. Alle Werte lassen sich über eine Weboberfläche überwachen, per Logik verknüpfen und über MQTT weitergeben — ohne proprietäre Konfigurationsdateien.\n\n---\n\n## Was kann open bridge?\n\n| Funktion | Beschreibung |\n|---|---|\n| **Protokolle** | KNX/IP (Tunneling + Routing + KNX IP Secure), Modbus TCP, Modbus RTU, 1-Wire, externes MQTT, Home Assistant, ioBroker, SNMP, Anwesenheitssimulation, Zeitschaltuhr |\n| **Mehrere Instanzen** | Beliebig viele Instanzen pro Protokoll (z. B. 2× KNX, 3× Modbus TCP) |\n| **Protokoll-Brücke** | Ein KNX-Wert wird automatisch in ein Modbus-Register geschrieben — und umgekehrt |\n| **Logik-Editor** | Visuelle Automatisierungslogik ohne Programmierung: 35+ Blocktypen, Zeitpläne, Formeln, Python-Skripte, Benachrichtigungen, HTTP-Anfragen, Sonnenstand |\n| **MQTT** | Stabiler UUID-Topic + lesbarer Alias-Topic; Retain-Unterstützung |\n| **Weboberfläche** | Vollständige Bedienung über den Browser — kein separates Programm nötig |\n| **Datenbank** | SQLite — keine externe Datenbank erforderlich |\n| **Verlauf** | Werteverlauf mit Diagramm, Aggregation nach Zeit (Std / Tag / Woche …); pro Datenpunkt konfigurierbar |\n| **Änderungsprotokoll** | Letzten N Wertänderungen einsehbar (RingBuffer) — aktualisiert sich live |\n| **Alles sofort** | Änderungen greifen ohne Neustart |\n| **Installation** | Docker Compose, direkt als Python-Programm oder als Proxmox LXC-Template |\n| **Lizenz** | MIT (kostenlos und quelloffen) |\n\n---\n\n## Inhaltsverzeichnis\n\n1. [Schnellstart — Proxmox LXC](#schnellstart--proxmox-lxc)\n2. [Konfiguration](#konfiguration)\n3. [Wie funktioniert open bridge?](#wie-funktioniert-open-bridge)\n4. [Datenpunkte](#datenpunkte)\n5. [Verknüpfungen (Bindings)](#verknüpfungen-bindings)\n6. [Suche](#suche)\n7. [Adapter](#adapter)\n8. [Verlauf (History)](#verlauf-history)\n9. [Änderungsprotokoll (RingBuffer)](#änderungsprotokoll-ringbuffer)\n10. [Sicherung \u0026 Wiederherstellung](#sicherung--wiederherstellung)\n11. [Systemstatus](#systemstatus)\n12. [Log-Viewer](#log-viewer)\n13. [Live-Verbindung (WebSocket)](#live-verbindung-websocket)\n14. [Logik-Editor](#logik-editor)\n15. [Adapter-Konfiguration](#adapter-konfiguration)\n16. [MQTT-Topics](#mqtt-topics)\n17. [Datentypen](#datentypen)\n18. [Einstellungen](#einstellungen)\n19. [Hilfsskripte](#hilfsskripte)\n20. [Visualisierung (Visu)](#visualisierung-visu)\n   - [Grundriss- und Anlagenschema-Widget](#grundriss--und-anlagenschema-widget)\n21. [Entwicklung](#entwicklung)\n   - [Lokale Entwicklung mit PyCharm](#lokale-entwicklung-mit-pycharm)\n   - [Lokale Git-Hooks (Pre-Push Gate)](#lokale-git-hooks-pre-push-gate)\n\n---\n\n## Schnellstart — Proxmox LXC\n\nDas LXC-Template enthält ein vollständiges Ubuntu 26.04-System mit **open bridge server** und startet den Dienst automatisch beim Hochfahren des Containers.\n\n**Schritt 1 — Template herunterladen**\n\n1. Auf der [Release-Seite](../../releases/latest) die URL der `.tar.zst`-Datei sowie den SHA512-Hash aus dem Abschnitt **LXC Template** kopieren.\n2. In der Proxmox-Weboberfläche zu **Datacenter → Storage → local → CT Templates** navigieren.\n3. **Download from URL** klicken.\n4. Die kopierte URL einfügen und auf **Query URL** klicken.\n5. Als Hash-Algorithmus **SHA512** auswählen.\n6. Den kopierten Hash einfügen.\n7. Auf **Download** klicken.\n\n![ProxmoxDownloadFromURL](docs/ProxmoxDownloadFromURL.png)\n\n**Schritt 2 — Container erstellen**\n\n1. Im Proxmox-Menü **Create CT** wählen.\n2. Als Template das gerade heruntergeladene `openbridgeserver-lxc_…` auswählen.\n3. Hostname, Passwort, CPU, RAM und Netzwerk nach Bedarf konfigurieren — empfohlen: mindestens 512 MB RAM.\n4. Container starten.\n\n**Schritt 3 — Zugriff**\n\n| Dienst | Adresse |\n|---|---|\n| **open bridge server** Weboberfläche + API | `http://\u003ccontainer-ip\u003e:8080` |\n\n**Standardzugang:** Benutzername `admin`, Passwort `admin`\n⚠️ Das Passwort sofort nach der ersten Anmeldung ändern (Einstellungen → Passwort).\n\n**Sicherheitskonfiguration** (erforderlich):\n\n```bash\n# Umgebungsvariablen in /etc/obs.env setzen, z. B.:\nOBS_MQTT__HOST=192.168.1.10\n# Wird im LXC-Template automatisch beim ersten Start gesetzt (zufällig, pro Container).\n# Nur bei manuellem Override setzen:\nOBS_SECURITY__JWT_SECRET=\u003cmindestens-32-zufällige-zeichen\u003e\n\n# Dienst neu starten\nsystemctl restart obs\n```\n\n---\n\n## Konfiguration\n\nDie Konfiguration wird in dieser Reihenfolge geladen (höher = Vorrang):\n\n1. Umgebungsvariablen (`OBS_\u003cABSCHNITT\u003e__\u003cSCHLÜSSEL\u003e`)\n2. `config.yaml` (Pfad über `OBS_CONFIG`, Standard: `./config.yaml`)\n3. Eingebaute Standardwerte\n\n```yaml\nserver:\n  host: 0.0.0.0               # Netzwerkschnittstelle\n  port: 8080                  # Port der Weboberfläche\n  log_level: INFO             # Protokollstufe: DEBUG|INFO|WARNING|ERROR\n\nmqtt:\n  host: localhost             # Interner Mosquitto-Broker\n  port: 1883\n  username: null              # Zugangsdaten für internen Broker\n  password: null\n\ndatabase:\n  path: /data/obs.db      # Datenbankdatei\n\nringbuffer:\n  storage: file               # Änderungsprotokoll: file-only (Datei)\n  max_entries: 10000          # Maximale Anzahl Einträge\n  max_file_size_bytes: null   # Optional: harte Dateigrenze für den Ringbuffer\n  max_age: null               # Optional: maximale Eintrags-Alterung in Sekunden\n\nsecurity:\n  jwt_secret: changeme        # Sitzungsschlüssel — unbedingt ändern!\n  jwt_expire_minutes: 1440    # Sitzungsdauer (Standard: 24 Stunden)\n  # Optionaler Override für die Allowlist privater/interner URL-Ziele.\n  # Standard: OBS_SECRET_FILE_DIR/url-target-allowlist.yaml, wenn OBS_SECRET_FILE_DIR gesetzt ist,\n  # sonst secrets/url-target-allowlist.yaml neben der konfigurierten Datenbank.\n  # url_target_allowlist_path: /data/secrets/url-target-allowlist.yaml\n```\n\n\u003e **Hinweis:** Der `mqtt`-Abschnitt betrifft den **internen** Mosquitto-Broker. Externe MQTT-Broker werden als separate Adapter-Instanzen eingerichtet (siehe [MQTT-Adapter](#mqtt-adapter-externer-broker)).\n\n### URL-Ziel-Allowlist für interne Dienste\n\nBackend-Abrufe aus Logik-API-Client-Knoten, iCalendar-Knoten, Pushover-`image_url`-Anhängen, dem Kamera-Proxy und der Wetter-API blockieren private/lokale Netzwerkziele standardmäßig. Admins können bewusst benötigte interne Ziele unter **Einstellungen → Sicherheit → URL-Ziel-Allowlist** freigeben oder die YAML-Datei bearbeiten, die über `security.url_target_allowlist_path` konfiguriert ist.\n\nStandardmäßig wird die YAML-Datei nach `OBS_SECRET_FILE_DIR/url-target-allowlist.yaml` geschrieben, wenn `OBS_SECRET_FILE_DIR` gesetzt ist. Sonst schreibt OBS nach `secrets/url-target-allowlist.yaml` neben der konfigurierten Datenbankdatei. Für private Ziele sollte eine IP-Adresse oder ein CIDR-Eintrag verwendet werden, zum Beispiel `192.168.1.23/32` für eine einzelne LAN-Kamera oder `10.38.113.0/24` für ein internes Netz.\n\nWenn ein Hostname wie `internal.example` auf eine private IP-Adresse auflöst, muss die aufgelöste IP beziehungsweise das passende CIDR-Netz freigegeben werden. Ein reiner Hostname-Eintrag hebt die private-IP-Sperre nicht auf und umgeht keine DNS-Validierung.\n\n---\n\n## Wie funktioniert open bridge?\n\n```\n┌──────────────────────────────────────────────────────────────┐\n│                        open bridge server                    │\n│                                                              │\n│  ┌─────────────────────┐  Wertänderung  ┌─────────────────┐  │\n│  │   Adapter-Instanzen │ ─────────────▶ │   Ereignisbus   │  │\n│  │                     │ ◀── schreiben  │  (verteilt an   │  │\n│  │  KNX, Modbus,       │                │  alle Abnehmer) │  │\n│  │  MQTT, 1-Wire …     │                └──┬──────┬───────┘  │\n│  └─────────────────────┘                   │      │          │\n│                                     ┌──────▼─┐ ┌──▼──────┐   │\n│                                     │ Werte- │ │ Verlauf │   │\n│                                     │ Abbild │ │ RingBuf │   │\n│                                     │        │ │ MQTT    │   │\n│                                     └────────┘ │ WS      │   │\n│                                                └─────────┘   │\n│                                                              │\n│  ┌───────────────────────────────────────────────────────┐   │\n│  │                  Logik-Editor                         │   │\n│  │  Wertänderung → Graph ausführen → DataPoint schreiben │   │\n│  └───────────────────────────────────────────────────────┘   │\n│                                                              │\n│  ┌───────────────────────────────────────────────────────┐   │\n│  │                   REST-API + WebSocket                │   │\n│  └───────────────────────────────────────────────────────┘   │\n└──────────────────────────────────────────────────────────────┘\n```\n\n**Kernprinzipien:**\n- **Adapter** lesen Werte aus dem Gebäude (KNX-Telegramm, Modbus-Register, MQTT-Nachricht, …) und melden sie an den Ereignisbus.\n- Der **Ereignisbus** verteilt jeden Wert gleichzeitig an: Werteabbild (aktueller Stand), Verlauf, Änderungsprotokoll, MQTT-Broker, WebSocket-Clients und den Logik-Editor.\n- Der **Logik-Editor** reagiert auf Wertänderungen, führt Automatisierungslogiken aus und schreibt Ergebnisse zurück in DataPoints.\n- **Protokoll-Brücke:** Wenn ein Wert über ein Protokoll empfangen wird, schreibt **open bridge server** ihn automatisch über alle anderen verknüpften Protokolle weiter — ohne zusätzliche Konfiguration.\n\n---\n\n## Datenpunkte\n\nEin Datenpunkt ist das zentrale Objekt in **open bridge server**. Jeder physische oder virtuelle Wert im System — eine Temperatur, ein Schaltzustand, ein Energiezähler — ist ein Datenpunkt.\n\n```\nGET    /api/v1/datapoints?page=0\u0026size=50       # Liste (seitenweise)\nPOST   /api/v1/datapoints                      # Neu anlegen\nGET    /api/v1/datapoints/{id}                 # Einzelnen laden (inkl. aktueller Wert)\nPATCH  /api/v1/datapoints/{id}                 # Ändern\nDELETE /api/v1/datapoints/{id}                 # Löschen (entfernt auch alle Verknüpfungen)\nGET    /api/v1/datapoints/{id}/value           # Nur den aktuellen Wert\n```\n\n**Felder:**\n\n| Feld | Beschreibung |\n|---|---|\n| `name` | Lesbarer Name, z. B. „Wohnzimmer Temperatur\" |\n| `data_type` | Datentyp: `BOOLEAN`, `INTEGER`, `FLOAT`, `STRING`, `DATE`, `TIME`, `DATETIME` |\n| `unit` | Einheit, z. B. `°C`, `%rH`, `kWh`, `lx`, `mm/h`, `nSv/h` |\n| `tags` | Schlagwörter zum Gruppieren und Filtern |\n| `persist_value` | Letzten Wert beim Neustart wiederherstellen (Standard: `true`) |\n| `record_history` | Werteverlauf in der Datenbank speichern (Standard: `true`). Auf `false` setzen um einen Datenpunkt von der History auszuschliessen. |\n| `mqtt_topic` | Automatisch vergeben: `dp/{uuid}/value` |\n| `mqtt_alias` | Lesbares Alias-Topic, z. B. `alias/klima/wohnzimmer/value` |\n\n```bash\n# Temperatur-Datenpunkt anlegen\ncurl -X POST http://localhost:8080/api/v1/datapoints \\\n  -H \"Authorization: Bearer {token}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Wohnzimmer Temperatur\",\n    \"data_type\": \"FLOAT\",\n    \"unit\": \"°C\",\n    \"tags\": [\"klima\", \"wohnzimmer\"]\n  }'\n```\n\n---\n\n## Verknüpfungen (Bindings)\n\nEine Verknüpfung verbindet einen Datenpunkt mit einer Adapter-Instanz und einer Adresse (z. B. KNX-Gruppenadresse oder Modbus-Register).\n\n```\nGET    /api/v1/datapoints/{id}/bindings\nPOST   /api/v1/datapoints/{id}/bindings\nPATCH  /api/v1/datapoints/{id}/bindings/{binding_id}\nDELETE /api/v1/datapoints/{id}/bindings/{binding_id}\n```\n\n**Richtungen:**\n\n| Richtung | Bedeutung |\n|---|---|\n| `SOURCE` | Lesen: Adapter empfängt Werte und leitet sie an **open bridge server** weiter |\n| `DEST` | Schreiben: **open bridge server** sendet Werte an den Adapter |\n| `BOTH` | Beides gleichzeitig |\n\n**Wert-Transformation (`value_formula`):**\n\nOptional: eine Formel, die auf den Wert angewendet wird, bevor er ins System eingeht (SOURCE) oder herausgeht (DEST). Die Variable ist immer `x`.\n\n```json\n{ \"value_formula\": \"x / 10\" }\n```\n\n| Formel | Wirkung |\n|---|---|\n| `x * 3600` | Stunden → Sekunden |\n| `x / 10` | Festkomma durch 10 |\n| `round(x, 2)` | Auf 2 Dezimalstellen runden |\n| `max(0, min(100, x))` | Auf 0–100 begrenzen |\n\nVerfügbare Funktionen: `abs`, `round`, `min`, `max` und alle `math.*`-Funktionen. Division durch null und ungültige Ergebnisse werden abgefangen — der ursprüngliche Wert bleibt erhalten.\n\n\u003e **Hinweis:** `round()` verwendet mathematisches Runden (0.5 → aufrunden), nicht das in der Programmierung übliche „Bankers Rounding\".\n\n**Wert-Zuordnung (`value_map`):**\n\nOptional: eine Tabelle, die Rohwerte auf andere Werte abbildet — nützlich z. B. bei Enumerationen oder Zustandstexten.\n\n```json\n{ \"value_map\": { \"0\": \"Aus\", \"1\": \"Ein\", \"2\": \"Standby\" } }\n```\n\nDer Schlüssel ist immer ein String (der Rohwert wird intern umgewandelt). Gibt es keinen passenden Eintrag, wird der Originalwert unverändert weitergegeben. `value_map` wird nach `value_formula` angewendet.\n\n**Sendefilter** (nur für DEST/BOTH, werden der Reihe nach geprüft):\n\n| Filter | Beschreibung |\n|---|---|\n| `send_throttle_ms` | Mindestabstand zwischen zwei Schreibvorgängen in Millisekunden |\n| `send_on_change` | Nur senden wenn der Wert sich geändert hat |\n| `send_min_delta` | Nur senden wenn die Abweichung zum letzten Wert mindestens so gross ist (absolut) |\n| `send_min_delta_pct` | Nur senden wenn die Abweichung mindestens so gross ist (prozentual) |\n\n**Beispiel: KNX-Temperatur → Modbus-Register**\n\n```bash\n# 1. Datenpunkt anlegen\nDP_ID=$(curl -s -X POST http://localhost:8080/api/v1/datapoints \\\n  -H \"Authorization: Bearer {token}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Wohnzimmer Temperatur\",\"data_type\":\"FLOAT\",\"unit\":\"°C\"}' \\\n  | jq -r .id)\n\n# 2. KNX-Verknüpfung (Lesen von GA 1/2/3)\ncurl -X POST http://localhost:8080/api/v1/datapoints/$DP_ID/bindings \\\n  -H \"Authorization: Bearer {token}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"adapter_instance_id\": \"KNX-UUID\", \"direction\": \"SOURCE\",\n       \"config\": {\"group_address\": \"1/2/3\", \"dpt_id\": \"DPT9.001\"}}'\n\n# 3. Modbus-Verknüpfung (Schreiben in Register 100)\ncurl -X POST http://localhost:8080/api/v1/datapoints/$DP_ID/bindings \\\n  -H \"Authorization: Bearer {token}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"adapter_instance_id\": \"MODBUS-UUID\", \"direction\": \"DEST\",\n       \"config\": {\"unit_id\": 1, \"register_type\": \"holding\", \"address\": 100, \"data_format\": \"float32\"}}'\n```\n\n---\n\n## Suche\n\nServergestützte Suche über alle Datenpunkte. Gibt nie den gesamten Datenbestand zurück.\n\n```\nGET /api/v1/search?q=\u0026tag=\u0026type=\u0026adapter=\u0026page=0\u0026size=50\n```\n\n| Parameter | Beschreibung |\n|---|---|\n| `q` | Suche im Namen |\n| `tag` | Nach Schlagwort filtern |\n| `type` | Nach Datentyp filtern (z. B. `FLOAT`) |\n| `adapter` | Nach Protokoll filtern (z. B. `KNX`) |\n\n---\n\n## Adapter\n\nJeder Adapter-Typ kann in mehreren unabhängigen Instanzen betrieben werden. Alle Instanzen werden über die Weboberfläche oder die API verwaltet.\n\nDie **Adapter-Konfiguration erfolgt vollständig über die Weboberfläche** — alle Felder werden aus dem JSON-Schema des jeweiligen Adapters dynamisch gerendert. Passwort-Felder erscheinen maskiert. Änderungen greifen sofort ohne Neustart.\n\n```\nGET    /api/v1/adapters/instances              # Alle Instanzen mit Status\nPOST   /api/v1/adapters/instances              # Neue Instanz anlegen\nPATCH  /api/v1/adapters/instances/{id}         # Konfiguration ändern + neu verbinden\nDELETE /api/v1/adapters/instances/{id}         # Stoppen + löschen\nPOST   /api/v1/adapters/instances/{id}/restart # Neu verbinden\nPOST   /api/v1/adapters/instances/{id}/test    # Verbindung testen\n\nGET    /api/v1/adapters/{type}/schema          # JSON-Schema der Instanz-Konfiguration\nGET    /api/v1/adapters/{type}/binding-schema  # JSON-Schema der Verknüpfungs-Konfiguration\n```\n\n### Anmeldung und Zugangsverwaltung\n\n**open bridge server** unterstützt zwei Anmeldemethoden:\n\n| Methode | Verwendung |\n|---|---|\n| Benutzername + Passwort → JWT-Token | Weboberfläche, Browser |\n| API-Schlüssel (`X-API-Key: obs_…`) | Skripte, Automatisierungen |\n\n```\nPOST   /api/v1/auth/login                              # Anmelden → Token erhalten\nPOST   /api/v1/auth/refresh                            # Token erneuern\n\nGET    /api/v1/auth/users                              # Alle Benutzer (nur Admin)\nPOST   /api/v1/auth/users                              # Benutzer anlegen (nur Admin)\nDELETE /api/v1/auth/users/{username}                   # Benutzer löschen (nur Admin)\nPOST   /api/v1/auth/me/change-password                 # Eigenes Passwort ändern\n\nPOST   /api/v1/auth/apikeys                            # API-Schlüssel anlegen\nDELETE /api/v1/auth/apikeys/{id}                       # API-Schlüssel widerrufen\n\nPOST   /api/v1/auth/users/{username}/mqtt-password     # MQTT-Zugang einrichten\nDELETE /api/v1/auth/users/{username}/mqtt-password     # MQTT-Zugang entziehen\n```\n\n**MQTT-Zugang:** Der interne Mosquitto-Broker ist passwortgeschützt. Jeder Benutzer kann einen separaten MQTT-Zugang (unabhängig vom Anmeldepasswort) erhalten, um sich direkt mit dem Broker zu verbinden.\n\n---\n\n## Verlauf (History)\n\nWerteverlauf eines Datenpunkts — roh oder als Zusammenfassung.\n\n```\nGET /api/v1/history/{id}?from=\u0026to=\u0026limit=\nGET /api/v1/history/{id}/aggregate?fn=avg\u0026interval=1h\u0026from=\u0026to=\n```\n\n**Zusammenfassungsfunktionen:** `avg` (Durchschnitt), `min`, `max`, `last`\n\n**Zeitintervalle:** `1m`, `5m`, `15m`, `30m`, `1h`, `6h`, `12h`, `1d`\n\nAlle Zeitangaben richten sich nach der in den Einstellungen konfigurierten Zeitzone.\n\n**Aufzeichnung steuern:** Das Feld `record_history` am Datenpunkt kontrolliert, ob Werte in den Verlauf geschrieben werden. Datenpunkte mit `record_history: false` werden vom History-Modul ignoriert. Die Verwaltung erfolgt unter Einstellungen → Verlauf.\n\n---\n\n## Änderungsprotokoll (RingBuffer)\n\nDer RingBuffer speichert die letzten N Wertänderungen als Protokoll. In der Weboberfläche aktualisiert sich die Liste **sofort** (ohne Neuladen), da neue Einträge live über die WebSocket-Verbindung übertragen werden.\n\n```\nGET  /api/v1/ringbuffer?q=\u0026adapter=\u0026from=\u0026limit=   # Einträge abfragen\nPOST /api/v1/ringbuffer/query                       # v2 Query-DSL (Filtergruppen + Pagination + Sortierung)\nPOST /api/v1/ringbuffer/export/csv                  # CSV-Export der vollständigen gefilterten Ergebnismenge\nGET  /api/v1/ringbuffer/stats                       # Anzahl Einträge, Kapazität\nPOST /api/v1/ringbuffer/config                      # file-only + Kapazität ändern\n```\n\nDer Parameter `q` durchsucht sowohl den Namen als auch die ID des Datenpunkts.\n\n`POST /api/v1/ringbuffer/query` verwendet eine Filter-DSL mit klarer Semantik:\n- `filters.adapters.any_of`: OR innerhalb der Adapterliste.\n- `filters.values`: typbewusste Wertfilter (`eq/ne/gt/gte/lt/lte/between/contains/regex`) passend zu `data_type`.\n- `filters.metadata`: filterbare Snapshot-Metadaten aus DataPoint/Binding-Kontext (`tags`, `adapter_types`, `group_addresses`, `topics`, `entity_ids`, `register_types`, `register_addresses`).\n- Filtergruppen (`time`, `adapters`, `datapoints`, `values`, `metadata`, `q`) werden per AND kombiniert.\n- Zeitfilter unterstützen offene Ränder (`from` ohne `to`, `to` ohne `from`) und die Kombination aus absoluten Grenzen (`from`/`to`) plus relativen Offsets (`from_relative_seconds`/`to_relative_seconds`).\n- Pagination über `pagination.limit` + `pagination.offset`, Sortierung über `sort.field` (`id|ts`) und `sort.order` (`asc|desc`).\n- Das versionierte Metadatenmodell ist dokumentiert in `docs/ringbuffer-metadata-model-v1.md` (`metadata_version: 1`).\n\n`POST /api/v1/ringbuffer/export/csv` nutzt denselben Request-Body wie `/query`, exportiert aber immer die vollständige gefilterte Ergebnismenge (Pagination der UI wird ignoriert).  \nCSV-Spalten: `id`, `ts`, `datapoint_id`, `name`, `topic`, `old_value_json`, `new_value_json`, `source_adapter`, `quality`, `metadata_version`, `metadata_json`.\n\n---\n\n## Sicherung \u0026 Wiederherstellung\n\nVollständige Konfigurationssicherung und -wiederherstellung. Bestehende Einträge werden aktualisiert, fehlende neu angelegt.\n\n```\nGET  /api/v1/config/export    # Sicherungsdatei herunterladen (JSON)\nPOST /api/v1/config/import    # Sicherungsdatei einspielen\n```\n\nDie Sicherung enthält: alle Datenpunkte, Verknüpfungen, Adapter-Instanzen und KNX-Gruppenadressen.\n\n**KNX-Projektdatei importieren:**\n\n```\nPOST /api/v1/knxproj/import   # .knxproj-Datei hochladen (multipart/form-data)\nGET  /api/v1/knxproj/ga       # Importierte Gruppenadressen anzeigen\nDELETE /api/v1/knxproj/ga     # Alle importierten Adressen löschen\n```\n\nNach dem Import erscheinen die Gruppenadressen als Suchvorschläge im Verknüpfungs-Formular.\n\n---\n\n## Systemstatus\n\n```\nGET /api/v1/system/health      # Erreichbarkeit prüfen (kein Login nötig)\nGET /api/v1/system/adapters    # Adapter-Status + Anzahl Verknüpfungen\nGET /api/v1/system/datatypes   # Alle verfügbaren Datentypen\nGET /api/v1/system/settings    # Systemeinstellungen lesen (z. B. Zeitzone)\nPUT /api/v1/system/settings    # Systemeinstellungen ändern\n\nGET /api/v1/adapters/knx/dpts  # Alle registrierten KNX-DPT-Typen auflisten\n```\n\n---\n\n## Log-Viewer\n\nDer Log-Viewer zeigt aktuelle Anwendungsmeldungen in Echtzeit. Die Admin-GUI zeigt die letzten 500 Einträge und streamt neue Einträge live via WebSocket.\n\n```\nGET /api/v1/system/logs?limit=N   # Aktuelle Log-Einträge (neueste zuerst, max. 500)\nGET /api/v1/system/log-level      # Aktuellen Log-Level lesen (nur Admin)\nPUT /api/v1/system/log-level      # Log-Level zur Laufzeit ändern (nur Admin)\n```\n\nLog-Einträge enthalten folgende Felder:\n\n| Feld | Beschreibung |\n|---|---|\n| `ts` | Zeitstempel (ISO 8601, UTC) |\n| `level` | Log-Level: `DEBUG`, `INFO`, `WARNING`, `ERROR` |\n| `logger` | Logger-Name (Modulpfad) |\n| `message` | Log-Meldung |\n\nDer Log-Level kann ohne Neustart zur Laufzeit geändert werden (nur Admin). Der Puffer hält die letzten 500 Einträge und wird beim Neustart nicht gespeichert.\n\n---\n\n## Live-Verbindung (WebSocket)\n\nÜber die WebSocket-Verbindung werden Wertänderungen und neue RingBuffer-Einträge sofort an alle verbundenen Browser übertragen — kein manuelles Neuladen nötig.\n\n```\nWS /api/v1/ws?token={jwt}\n```\n\n**Datenpunkt abonnieren:**\n```json\n{\"action\": \"subscribe\", \"datapoint_ids\": [\"uuid-1\", \"uuid-2\"]}\n```\n\n**Eingehende Wertänderung:**\n```json\n{\n  \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"v\": 21.4,\n  \"u\": \"°C\",\n  \"t\": \"2026-03-27T10:23:41.123Z\",\n  \"q\": \"good\"\n}\n```\n\n**Neuer RingBuffer-Eintrag** (an alle Verbindungen, ohne Abo):\n```json\n{\n  \"action\": \"ringbuffer_entry\",\n  \"entry\": {\n    \"ts\": \"2026-03-27T10:23:41.123Z\",\n    \"datapoint_id\": \"550e8400-...\",\n    \"name\": \"Wohnzimmer Temperatur\",\n    \"new_value\": 21.4,\n    \"old_value\": 21.1,\n    \"quality\": \"good\",\n    \"source_adapter\": \"KNX\"\n  }\n}\n```\n\n**Datenqualität (`q`):**\n\n| Wert | Bedeutung |\n|---|---|\n| `good` | Wert erfolgreich empfangen, Verbindung aktiv |\n| `bad` | Adapter getrennt oder Lesefehler |\n| `uncertain` | Verbindung wird wiederhergestellt oder Wert möglicherweise veraltet |\n\n---\n\n## Logik-Editor\n\n### Übersicht\n\nDer Logik-Editor ermöglicht das visuelle Erstellen von Automatisierungsregeln — ohne Programmierkenntnisse. Blöcke werden per Drag \u0026 Drop auf einer Arbeitsfläche platziert und mit Verbindungslinien verknüpft.\n\n**Ablauf:**\n1. Ein **DP Lesen**-Block beobachtet einen Datenpunkt.\n2. Ändert sich der Wert, führt **open bridge server** den gesamten Graphen aus.\n3. Die Blöcke werden der Reihe nach berechnet.\n4. Ein **DP Schreiben**-Block schreibt das Ergebnis zurück — das löst automatisch alle Adapter, MQTT, den Verlauf und den RingBuffer aus.\n5. Der **Trigger**-Block löst den Graphen nach einem Zeitplan aus (z. B. täglich um 07:00 Uhr).\n\nDer Graph kann auch manuell über den **▶ Ausführen**-Button gestartet werden.\n\n**Zustände** (Hysterese, Statistik, Betriebsstunden, Min/Max-Tracker, Verbrauchszähler) werden in der Datenbank gespeichert und überleben einen Neustart.\n\n---\n\n### Blocktypen\n\n#### Konstante\n\n| Block | Ausgänge | Beschreibung |\n|---|---|---|\n| **Festwert** | Wert | Gibt einen festen Wert aus — Zahl, Ein/Aus oder Text. |\n\n#### Logik\n\n| Block | Eingänge | Ausgänge | Beschreibung |\n|---|---|---|---|\n| **UND** | A, B | Aus | Wahr wenn **alle** Eingänge wahr sind. |\n| **ODER** | A, B | Aus | Wahr wenn **mindestens ein** Eingang wahr ist. |\n| **NICHT** | Ein | Aus | Kehrt den Eingang um. |\n| **EXKLUSIV-ODER** | A, B | Aus | Wahr wenn **genau ein** Eingang wahr ist. |\n| **Vergleich** | A, B | Ergebnis | Vergleicht zwei Werte. Auswahl: `\u003e` `\u003c` `=` `\u003e=` `\u003c=` `≠` |\n| **Hysterese** | Wert | Aus | Schaltet ein wenn der Wert über „Schwelle EIN\" steigt, und erst wieder aus wenn er unter „Schwelle AUS\" fällt. Verhindert schnelles Hin- und Herschalten. |\n\n#### Datenpunkt\n\n| Block | Eingänge | Ausgänge | Beschreibung |\n|---|---|---|---|\n| **DP Lesen** | — | Wert, Geändert | Liest einen Datenpunkt. Löst den Graphen bei Wertänderung automatisch aus. Optionale Filter (Mindestabstand, Mindeständerung) und Wert-Transformation. |\n| **DP Schreiben** | Wert, Trigger | — | Schreibt einen Wert in einen Datenpunkt. Optionaler Trigger-Eingang: nur schreiben wenn Trigger wahr. Optionale Filter und Wert-Transformation. |\n\n#### Mathematik\n\n| Block | Eingänge | Ausgänge | Beschreibung |\n|---|---|---|---|\n| **Formel** | a, b | Ergebnis | Berechnet einen Ausdruck aus den Eingängen `a` und `b`. Optional: eine zweite Formel zur Transformation des Ergebnisses (Variable `x`). |\n| **Skalieren** | Wert | Ergebnis | Rechnet einen Wert von einem Bereich in einen anderen um, z. B. 0–255 → 0–100 %. |\n| **Begrenzer** | Wert | Ergebnis | Begrenzt den Wert auf einen Bereich [Min, Max]. Werte darunter oder darüber werden auf den Grenzwert gesetzt. |\n| **Statistik** | Wert, Zurücksetzen | Min, Max, Mittelwert, Anzahl | Führt eine laufende Statistik über alle empfangenen Werte. Reset setzt alles zurück. Ergebnisse werden gespeichert und überleben einen Neustart. |\n| **Min/Max-Tracker** | Wert | Min tägl., Max tägl., Min wöch., Max wöch., Min monatl., Max monatl., Min jährl., Max jährl., Min abs., Max abs. | Verfolgt Minimal- und Maximalwerte über verschiedene Zeiträume (täglich, wöchentlich, monatlich, jährlich, absolut). Setzt sich automatisch bei Periodengrenze zurück. |\n| **Verbrauchszähler** | Wert (Zählerstand) | Täglich, Wöchentlich, Monatlich, Jährlich, Vorheriger Tag, Vorherige Woche, Vorheriger Monat, Vorheriges Jahr | Berechnet Verbrauch aus einem kumulierten Zählerstand. Speichert Vorperiodenwerte für Vergleiche. |\n| **Sommer/Winter (DIN)** | Wert (Aussentemperatur) | Heizungsmodus, Tagesdurchschnitt, Monatsdurchschnitt | Steuert die Heizungsumschaltung nach DIN-Norm anhand von drei Tageswerten (T1 ≈ 07:00, T2 ≈ 14:00, T3 ≈ 22:00). Tagesdurchschnitt = (T1 + T2 + 2×T3) / 4. |\n\n#### Text\n\n| Block | Eingänge | Ausgänge | Beschreibung |\n|---|---|---|---|\n| **Text verbinden** | 2–20 Eingänge (konfigurierbar) | Ergebnis | Verbindet mehrere Texte zu einem. Optionales Trennzeichen (z. B. `,` oder ` `). |\n\n#### Timer\n\n| Block | Eingänge | Ausgänge | Beschreibung |\n|---|---|---|---|\n| **Verzögerung** | Trigger | Trigger | Verzögert ein Signal um N Sekunden. |\n| **Impuls** | Trigger | Aus | Gibt für N Sekunden „Wahr\" aus, dann „Falsch\". |\n| **Trigger** | — | Trigger | Löst den Graphen nach einem Zeitplan aus (Cron-Format). Konfigurierbar über Vorlagen, einen visuellen Editor (Min/Std/Tag/Mon/Wochentag) oder direkte Eingabe des Ausdrucks. |\n| **Betriebsstunden** | Aktiv, Zurücksetzen | Stunden | Zählt Betriebsstunden solange „Aktiv\" wahr ist. Gespeicherter Zählerstand überlebt Neustarts. |\n\n#### Skript\n\n| Block | Eingänge | Ausgänge | Beschreibung |\n|---|---|---|---|\n| **Python-Skript** | a, b, c | Ergebnis | Führt Python-Code aus. Eingangswerte sind über `inputs['a']`, `inputs['b']`, `inputs['c']` verfügbar. Das Ergebnis wird mit `result = …` gesetzt. Nur mathematische Funktionen erlaubt — kein Dateizugriff, kein Netzwerk. |\n\n#### KI\n\n| Block | Eingänge | Ausgänge | Beschreibung |\n|---|---|---|---|\n| **KI-Logik** | Trigger | Ergebnis | Platzhalter für zukünftige KI-Integration. |\n\n#### MCP\n\n| Block | Eingänge | Ausgänge | Beschreibung |\n|---|---|---|---|\n| **MCP-Werkzeug** | Trigger, Eingabe | Ergebnis, Fertig | Ruft ein Werkzeug auf einem externen MCP-Server auf. |\n\n#### Astro\n\n| Block | Ausgänge | Beschreibung |\n|---|---|---|\n| **Astro Sonne** | Sonnenaufgang, Sonnenuntergang, Tagsüber | Berechnet Sonnenauf- und -untergang für den konfigurierten Standort. Gibt auch aus, ob es gerade hell ist. Konfiguration: Breitengrad, Längengrad. Berücksichtigt die eingestellte Zeitzone. |\n\n#### Benachrichtigung\n\n| Block | Eingänge | Ausgänge | Beschreibung |\n|---|---|---|---|\n| **Pushover** | Trigger, Nachricht | Gesendet | Sendet eine Push-Benachrichtigung auf das Handy via [Pushover](https://pushover.net). Konfiguration: App-Token, User-Key, Titel, Priorität. |\n| **SMS (seven.io)** | Trigger, Nachricht | Gesendet | Sendet eine SMS via [seven.io](https://seven.io). Konfiguration: API-Schlüssel, Empfänger, Absender. |\n\n#### Integration\n\n| Block | Eingänge | Ausgänge | Beschreibung |\n|---|---|---|---|\n| **API-Abfrage** | Trigger, Inhalt | Antwort, Statuscode, Erfolg | Sendet eine HTTP-Anfrage an eine externe Adresse. Methode wählbar (GET/POST/PUT/PATCH/DELETE). Antwortformat: JSON oder Text. SSL-Prüfung konfigurierbar. |\n| **JSON-Extraktor** | Daten (JSON-Text) | Wert | Parst einen JSON-String und extrahiert einen Wert anhand eines Pfads mit Punktnotation, z. B. `sensors.temperature`. |\n| **XML-Extraktor** | Daten (XML-Text) | Wert | Parst einen XML-String und extrahiert einen Wert per XPath-Ausdruck, z. B. `./sensor/temperature`. |\n\n---\n\n### Filter und Transformation bei DP-Blöcken\n\nBeide DataPoint-Blöcke haben drei Tabs: **Verbindung**, **Transformation** und **Filter**. Ein Punkt (•) erscheint im Tab wenn etwas aktiv ist.\n\n#### Transformation\n\nOptionale Formel die auf den Wert angewendet wird. Variable: `x`\n\nVordefinierte Vorlagen (Beispiele):\n\n| Vorlage | Formel |\n|---|---|\n| × 1.000 | `x * 1000` |\n| × 100 | `x * 100` |\n| ÷ 10 | `round(x / 10, 1)` |\n| ÷ 100 | `round(x / 100, 2)` |\n| Sekunden → Stunden | `x / 3600` |\n| Stunden → Sekunden | `x * 3600` |\n\n#### Filter bei DP Lesen\n\n| Filter | Beschreibung |\n|---|---|\n| Mindestabstand | Wie oft der Graph höchstens ausgelöst wird (z. B. maximal alle 10 Sekunden) |\n| Nur bei Änderung | Graph nur auslösen wenn der Wert sich wirklich geändert hat |\n| Mindeständerung (absolut) | Nur auslösen wenn der Wert sich um mindestens N geändert hat |\n| Mindeständerung (%) | Nur auslösen wenn die Änderung mindestens N Prozent beträgt |\n\n#### Filter bei DP Schreiben\n\n| Filter | Beschreibung |\n|---|---|\n| Mindestabstand | Wie oft höchstens geschrieben wird |\n| Nur bei Änderung | Nicht schreiben wenn der Wert gleich dem zuletzt geschriebenen ist |\n| Mindeständerung (absolut) | Nur schreiben wenn der Wert sich um mindestens N geändert hat |\n\n---\n\n### Zeitplan-Konfiguration (Trigger-Block)\n\nDer **Trigger**-Block löst Graphen nach einem Zeitplan aus. Drei Eingabewege, die sich gegenseitig synchronisieren:\n\n**1. Vorlagen** — über 30 vordefinierte Zeitpläne in 4 Gruppen (Minuten-Intervalle, Stunden-Intervalle, Täglich, Wöchentlich/Monatlich)\n\n**2. Visueller Editor** — fünf Felder: Minute / Stunde / Tag / Monat / Wochentag\n\n**3. Direkteingabe** — Standard Cron-Ausdruck\n\n```\n0 7 * * *         → täglich um 07:00\n*/15 * * * *      → alle 15 Minuten\n0 8 * * 1-5       → werktags um 08:00\n0 6,18 * * *      → täglich um 06:00 und 18:00\n```\n\nZur Überprüfung: [crontab.guru](https://crontab.guru) (Link direkt im Konfigurations-Panel)\n\n---\n\n### Formel-Referenz\n\nIn **allen** Formelfeldern (DP Lesen, DP Schreiben, Formel-Block, Verknüpfungs-Transformation) gilt:\n\n- Variable `x` = der eingehende Wert (immer als Zahl übergeben)\n- Kein Import nötig — alle Funktionen direkt verfügbar\n- `round()` verwendet mathematisches Runden (0.5 → aufrunden)\n\n| Funktion | Beispiel | Beschreibung |\n|---|---|---|\n| `abs(x)` | `abs(x - 50)` | Absolutbetrag (immer positiv) |\n| `round(x, n)` | `round(x, 2)` | Runden auf n Nachkommastellen |\n| `min(a, b)` | `min(x, 100)` | Kleinerer der beiden Werte |\n| `max(a, b)` | `max(x, 0)` | Grösserer der beiden Werte |\n| `sqrt(x)` | `sqrt(x)` | Quadratwurzel |\n| `floor(x)` | `floor(x)` | Abrunden auf ganze Zahl |\n| `ceil(x)` | `ceil(x)` | Aufrunden auf ganze Zahl |\n| `math.log(x)` | `math.log(x)` | Natürlicher Logarithmus |\n| `math.sin(x)` | `math.sin(x)` | Sinus |\n| `math.pi` | `x * math.pi / 180` | Kreiszahl π |\n\n**Praktische Beispiele:**\n\n| Ziel | Formel |\n|---|---|\n| Auf 0–100 begrenzen | `max(0, min(100, x))` |\n| Fahrenheit → Celsius | `(x - 32) * 5 / 9` |\n| Wh → kWh | `x / 1000` |\n| Auf halbe Stufen runden | `round(x * 2) / 2` |\n| Negativen Wert abschneiden | `max(0, x)` |\n\n**Formel-Block** (Eingänge `a` und `b`):\n\n```\na * 2 + b              # Eingang a verdoppeln, b addieren\nmax(a, b)              # Grösseren der beiden Werte nehmen\nround((a + b) / 2, 1)  # Mittelwert, 1 Nachkommastelle\nabs(a - b)             # Absolute Differenz\n```\n\nZusätzlich kann eine **Ausgangs-Transformation** konfiguriert werden — eine zweite Formel (Variable `x`) die auf das berechnete Ergebnis angewendet wird.\n\n---\n\n### Automatische Typumwandlung\n\nDie Logik-Engine wandelt Werte automatisch um:\n\n| Von | Nach | Regel |\n|---|---|---|\n| `true`/`false` | Zahl | Wahr → 1.0, Falsch → 0.0 |\n| Zahl | Ein/Aus | 0 → Falsch, alles andere → Wahr |\n| Text `\"123\"` | Zahl | 123.0 |\n| Text `\"true\"`, `\"on\"`, `\"1\"` | Ein/Aus | Wahr |\n| Text `\"false\"`, `\"off\"`, `\"0\"` | Ein/Aus | Falsch |\n| Kein Wert | Zahl | 0.0 |\n\nVerbindungen zwischen unterschiedlichen Blocktypen funktionieren damit immer.\n\n---\n\n### Debug-Modus\n\nZeigt berechnete Zwischenwerte direkt auf den Blöcken an — live und automatisch.\n\n1. Graph öffnen\n2. **🔍 Debug**-Button in der Werkzeugleiste klicken\n3. Jeder Block zeigt ein gelbes Band mit seinen aktuellen Ausgangswerten\n4. Die Anzeige aktualisiert sich automatisch nach jeder Ausführung (Wertänderung, Zeitplan, manueller Start)\n\n| Typ | Darstellung |\n|---|---|\n| Wahr | `out=✓` |\n| Falsch | `out=✗` |\n| Zahl | `value=230.45` |\n| DP Schreiben | `→ 21.5` |\n| Kein Wert | `value=—` |\n\n---\n\n## Adapter-Konfiguration\n\n### KNX-Adapter\n\n**Instanz-Konfiguration — Grundparameter:**\n\n| Feld | Werte | Beschreibung |\n|---|---|---|\n| `connection_type` | `tunneling` / `tunneling_secure` / `routing` / `routing_secure` | Verbindungstyp (siehe unten) |\n| `host` | IP-Adresse | IP der KNX/IP-Zentrale (Tunneling) oder Multicast-Adresse (Routing) |\n| `port` | Standard `3671` | Port der KNX/IP-Zentrale |\n| `individual_address` | z. B. `1.1.210` | Eigene KNX-Adresse des open bridge Servers |\n| `local_ip` | IP-Adresse | Lokale Netzwerkschnittstelle (optional). Bei Routing/Routing Secure: wählt die Netzwerkkarte für Multicast — bei mehreren Netzwerkkarten **empfohlen**. Bei Tunneling/Tunneling Secure: bindet den Socket an eine bestimmte Schnittstelle — meist nur bei Mehrfach-Netzwerkkarten nötig. Leer lassen = automatische Auswahl. |\n\n**Verbindungstypen:**\n\n| `connection_type` | Beschreibung |\n|---|---|\n| `tunneling` | UDP-Tunneling zur KNX/IP-Zentrale (Standard) |\n| `tunneling_secure` | KNX IP Secure Tunneling (verschlüsselt, TCP) |\n| `routing` | IP-Multicast-Routing |\n| `routing_secure` | KNX IP Secure Routing (verschlüsselt, Multicast) |\n\n**KNX IP Secure — Keyfile-Modus (empfohlen)**\n\nDer einfachste Weg für KNX IP Secure ist der Import der `.knxkeys`-Datei aus ETS:\n\n1. In ETS: **Sicherheit → Schlüsselsicherung exportieren** → `.knxkeys`-Datei speichern\n2. In open bridge server: **Einstellungen → Adapter → KNX-Instanz bearbeiten → Keyfile hochladen**\n3. Keyfile-Passwort eingeben — open bridge server zeigt alle verfügbaren Tunnel mit PA, User-ID und Anzahl gesicherter Gruppenadressen\n4. Gewünschten Tunnel wählen → `individual_address` wird automatisch gesetzt\n5. `connection_type` auf `tunneling_secure` (oder `routing_secure`) setzen\n\n| Feld | Beschreibung |\n|---|---|\n| `knxkeys_file_path` | Wird automatisch gesetzt nach dem Hochladen der Keyfile |\n| `knxkeys_password` | Passwort-Feld — Passwort zur `.knxkeys`-Datei |\n| `individual_address` | PA des gewählten Tunnels (aus der Tunnel-Liste) |\n\n**KNX IP Secure — Manueller Modus** (nur wenn kein Keyfile vorhanden):\n\nFür `tunneling_secure`:\n\n| Feld | Werte | Beschreibung |\n|---|---|---|\n| `user_id` | `1`–`127`, Standard `2` | Benutzer-ID am KNX/IP-Gateway |\n| `user_password` | Passwort-Feld | Benutzerpasswort |\n| `device_authentication_password` | Passwort-Feld | Geräte-Authentifizierungspasswort |\n\nFür `routing_secure`:\n\n| Feld | Werte | Beschreibung |\n|---|---|---|\n| `backbone_key` | Passwort-Feld | 128-Bit Backbone-Schlüssel als Hex-String (32 Zeichen, z. B. `0102030405060708090a0b0c0d0e0f10`; Trennzeichen `:` und Leerzeichen werden ignoriert) |\n\n\u003e **Hinweis:** Sind `knxkeys_file_path` und `knxkeys_password` gesetzt, haben sie Vorrang vor den manuellen Feldern. Alle Passwort-Felder werden in der Weboberfläche maskiert dargestellt.\n\n**Keyfile API** (für eigene Integrationen):\n\n```\nPOST /api/v1/knx/keyfile   # .knxkeys hochladen, Tunnel-Liste zurückgeben\nDELETE /api/v1/knx/keyfile/{file_id}  # Keyfile löschen\n```\n\nAntwort des Upload-Endpunkts:\n```json\n{\n  \"file_id\": \"uuid\",\n  \"file_path\": \"/data/knxkeys/uuid.knxkeys\",\n  \"project_name\": \"Mein KNX-Projekt\",\n  \"tunnels\": [\n    { \"individual_address\": \"1.1.100\", \"host\": \"1.1.50\", \"user_id\": 2, \"secure_ga_count\": 15 },\n    { \"individual_address\": \"1.1.101\", \"host\": \"1.1.50\", \"user_id\": 3, \"secure_ga_count\": 15 }\n  ],\n  \"backbone\": null\n}\n```\n\n**Verknüpfungs-Konfiguration:**\n\n| Feld | Beschreibung |\n|---|---|\n| `group_address` | KNX-Gruppenadresse (dreiteilig, z. B. `27/6/6`) |\n| `dpt_id` | DPT-Kennung — Tabelle unten |\n| `state_group_address` | Optionale Rückmelde-Adresse für DEST-Verknüpfungen |\n| `respond_to_read` | `true`: **open bridge server** beantwortet KNX-Leseanfragen (GroupValueRead) mit dem aktuellen Wert. Standard: `false` |\n\n**Unterstützte DPTs:**\n\n**open bridge server** unterstützt über 85 KNX-Datentypen. Die vollständige Liste ist über `GET /api/v1/adapters/knx/dpts` abrufbar.\n\n**DPT 1 — 1-Bit Boolean**\n\n| DPT | Typische Verwendung |\n|---|---|\n| `DPT1.001` | Schalten (Ein/Aus) |\n| `DPT1.002` | Boolean |\n| `DPT1.003` | Freigabe (Enable) |\n| `DPT1.007` | Schritt/Richtung |\n| `DPT1.008` | Auf/Ab |\n| `DPT1.009` | Öffnen/Schliessen |\n| `DPT1.010` | Start/Stopp |\n| `DPT1.011` | Zustandsanzeige |\n| `DPT1.017` | Auslöser (Trigger) |\n| `DPT1.018` | Anwesenheit |\n| `DPT1.019` | Fenster/Tür |\n| `DPT1.021` | Szene A/B |\n| `DPT1.022` | Jalousie-Modus |\n| `DPT1.023` | Tag/Nacht |\n| *(weitere DPT1.x)* | *1-Bit Steuerungen* |\n\n**DPT 2 — 2-Bit Gesteuerter Wert**\n\n| DPT | Typische Verwendung |\n|---|---|\n| `DPT2.001` | Schaltsteuerung (Priorität + Wert) |\n| `DPT2.002` | Boolsche Steuerung |\n\n**DPT 3 — 4-Bit Relativer Steuerwert**\n\n| DPT | Typische Verwendung |\n|---|---|\n| `DPT3.007` | Dimmen (Richtung + Geschwindigkeit) |\n| `DPT3.008` | Jalousie (Richtung + Geschwindigkeit) |\n\n**DPT 4 — 1-Byte Zeichen**\n\n| DPT | Grösse | Typ | Typische Verwendung |\n|---|---|---|---|\n| `DPT4.001` | 1 Byte | Text | ASCII-Zeichen |\n| `DPT4.002` | 1 Byte | Text | ISO-8859-1-Zeichen |\n\n**DPT 5 — 8-Bit Vorzeichenlos**\n\n| DPT | Grösse | Typ | Typische Verwendung |\n|---|---|---|---|\n| `DPT5.001` | 1 Byte | Zahl (0–100 %) | Dimmen / Jalousie-Position |\n| `DPT5.003` | 1 Byte | Zahl (0–360°) | Winkel |\n| `DPT5.004` | 1 Byte | Ganzzahl (0–255) | Prozent (unsigned) |\n| `DPT5.010` | 1 Byte | Ganzzahl | Zählerwert |\n\n**DPT 6 — 8-Bit Vorzeichenbehaftet**\n\n| DPT | Grösse | Typ | Typische Verwendung |\n|---|---|---|---|\n| `DPT6.001` | 1 Byte | Ganzzahl (−128…127) | Relativer Wert (%) |\n| `DPT6.010` | 1 Byte | Ganzzahl | Impulszähler (vorzeichenbehaftet) |\n\n**DPT 7 — 16-Bit Vorzeichenlos**\n\n| DPT | Grösse | Typ | Typische Verwendung |\n|---|---|---|---|\n| `DPT7.001` | 2 Byte | Ganzzahl (0–65535) | Impulszähler |\n| `DPT7.002` | 2 Byte | Ganzzahl | Zeitraum (ms) |\n| `DPT7.003` | 2 Byte | Ganzzahl | Zeitraum (10 ms) |\n| `DPT7.004` | 2 Byte | Ganzzahl | Zeitraum (100 ms) |\n| `DPT7.005` | 2 Byte | Ganzzahl | Zeitraum (s) |\n| `DPT7.006` | 2 Byte | Ganzzahl | Zeitraum (min) |\n| `DPT7.007` | 2 Byte | Ganzzahl | Zeitraum (h) |\n| `DPT7.011` | 2 Byte | Ganzzahl | Länge (mm) |\n| `DPT7.012` | 2 Byte | Ganzzahl | Stromstärke (mA) |\n| `DPT7.013` | 2 Byte | Ganzzahl | Helligkeit (lx) |\n| `DPT7.600` | 2 Byte | Ganzzahl | Farbtemperatur (K) |\n\n**DPT 8 — 16-Bit Vorzeichenbehaftet**\n\n| DPT | Grösse | Typ | Typische Verwendung |\n|---|---|---|---|\n| `DPT8.001` | 2 Byte | Ganzzahl | Impulszähler (vorzeichenbehaftet) |\n| `DPT8.002` | 2 Byte | Ganzzahl | Zeitraum (ms) |\n| `DPT8.005` | 2 Byte | Ganzzahl | Zeitraum (s) |\n| `DPT8.010` | 2 Byte | Ganzzahl | Drehzahl-Differenz (1/min) |\n| `DPT8.011` | 2 Byte | Ganzzahl | Prozent-Differenz |\n| `DPT8.012` | 2 Byte | Ganzzahl | Rotationswinkel (°) |\n\n**DPT 9 — 2-Byte KNX-Gleitkomma (EIS5)**\n\n| DPT | Typische Verwendung |\n|---|---|\n| `DPT9.001` | Temperatur (°C) |\n| `DPT9.002` | Temperaturdifferenz (K) |\n| `DPT9.003` | Kelvin/Stunde (K/h) |\n| `DPT9.004` | Windgeschwindigkeit (m/s) |\n| `DPT9.005` | Luftdruck (Pa) |\n| `DPT9.006` | Luftfeuchtigkeit (%) |\n| `DPT9.007` | Luftfeuchtigkeit (% rH) |\n| `DPT9.008` | CO₂-Konzentration (ppm) |\n| `DPT9.009` | Spannung (mV) |\n| `DPT9.010` | Leistung (W) |\n| `DPT9.011` | Zeit (s) |\n| `DPT9.020` | Spannung (mV) |\n| `DPT9.021` | Strom (mA) |\n| `DPT9.024` | Leistung (kW) |\n| `DPT9.025` | Volumenfluss (l/h) |\n| `DPT9.026` | Niederschlag (l/m²) |\n| `DPT9.027` | Luftdruck (Pa) |\n| `DPT9.028` | Windgeschwindigkeit (km/h) |\n| `DPT9.029` | Absolute Luftfeuchtigkeit (g/m³) |\n| `DPT9.030` | Einstrahlungsdichte (W/m²) |\n\n**DPT 10, 11 — Uhrzeit und Datum**\n\n| DPT | Grösse | Typ | Typische Verwendung |\n|---|---|---|---|\n| `DPT10.001` | 3 Byte | Text `HH:MM:SS` | Uhrzeit (inkl. Wochentag) |\n| `DPT11.001` | 3 Byte | Text `JJJJ-MM-TT` | Datum |\n\n**DPT 12, 13 — 32-Bit Integer**\n\n| DPT | Grösse | Typ | Typische Verwendung |\n|---|---|---|---|\n| `DPT12.001` | 4 Byte | Ganzzahl (0–4 Mrd.) | Energiezähler (vorzeichenlos) |\n| `DPT13.001` | 4 Byte | Ganzzahl (±2 Mrd.) | Impulszähler (vorzeichenbehaftet) |\n| `DPT13.010` | 4 Byte | Ganzzahl | Wirkenergie (Wh) |\n| `DPT13.013` | 4 Byte | Ganzzahl | Wirkenergie (kWh) |\n\n**DPT 14 — 32-Bit IEEE-754-Gleitkomma (physikalische Grössen)**\n\n| DPT | Typische Verwendung |\n|---|---|\n| `DPT14.000` | Beschleunigung (m/s²) |\n| `DPT14.005` | Winkelgeschwindigkeit (rad/s) |\n| `DPT14.007` | Fläche (m²) |\n| `DPT14.012` | Kapazität (F) |\n| `DPT14.017` | Dichte (kg/m³) |\n| `DPT14.019` | Elektrischer Strom (A) |\n| `DPT14.020` | Elektrische Feldstärke (V/m) |\n| `DPT14.023` | Elektrisches Potential (V) |\n| `DPT14.024` | Elektrische Spannung (V) |\n| `DPT14.027` | Energie (J) |\n| `DPT14.028` | Kraft (N) |\n| `DPT14.029` | Frequenz (Hz) |\n| `DPT14.033` | Wärmestrom (W) |\n| `DPT14.039` | Länge (m) |\n| `DPT14.046` | Lichtstrom (lm) |\n| `DPT14.050` | Masse (kg) |\n| `DPT14.055` | Leistung (W) |\n| `DPT14.056` | Leistungsfaktor |\n| `DPT14.058` | Druck (Pa) |\n| `DPT14.065` | Widerstand (Ω) |\n| `DPT14.066` | Winkelauflösung (°) |\n| `DPT14.067` | Drehzahl (1/min) |\n| `DPT14.068` | Geschwindigkeit (m/s) |\n| `DPT14.069` | Drehmoment (Nm) |\n| `DPT14.070` | Volumen (m³) |\n| `DPT14.071` | Volumenfluss (m³/s) |\n| `DPT14.075` | Scheinleistung (VA) |\n| *(weitere DPT14.x)* | *Physikalische Mess­grössen* |\n\n**DPT 16, 17, 18, 19 — Text, Szenen, Datum/Zeit**\n\n| DPT | Grösse | Typ | Typische Verwendung |\n|---|---|---|---|\n| `DPT16.000` | 14 Byte | Text | ASCII-Text (14 Zeichen) |\n| `DPT16.001` | 14 Byte | Text | ISO-8859-1-Text (14 Zeichen) |\n| `DPT17.001` | 1 Byte | Ganzzahl | Szenennummer (0–63) |\n| `DPT18.001` | 1 Byte | Ganzzahl | Szenen-Steuerung (inkl. Lernmodus) |\n| `DPT19.001` | 8 Byte | ISO-8601-Text | Datum und Uhrzeit |\n\n**DPT 20 — 1-Byte Enum/Modus**\n\n| DPT | Typische Verwendung |\n|---|---|\n| `DPT20.001` | HVAC-Modus (Auto/Komfort/Standby/Nacht/Schutz) |\n| `DPT20.002` | HVAC-Brennermodus |\n| `DPT20.003` | HVAC-Gebläsemodus |\n| `DPT20.004` | HVAC-Mastermodus |\n| `DPT20.005` | HVAC-Statusmeldung |\n| `DPT20.006` | HVAC-Positionswert |\n| `DPT20.007` | DALI-Verblend-Modus |\n| `DPT20.008` | Steuerungsverhalten |\n| `DPT20.011` | Priorität |\n| `DPT20.012` | Lichtsteuermodus |\n| `DPT20.013` | Heizungsregelungsmodus |\n| `DPT20.017` | Belüftungsmodus |\n| `DPT20.020` | Alarmschwere |\n| `DPT20.021` | Testmodus |\n| `DPT20.100` | Gebäude-Betriebsmodus |\n| `DPT20.102` | Aktiver Grundmodus |\n| `DPT20.105` | Warmwasser-Modus (DHW) |\n| `DPT20.111` | Heizklima-Modus |\n| `DPT20.113` | Zeitprogramm |\n| `DPT20.600` | Ventilator-Modus |\n| `DPT20.601` | Heizungstyp |\n| `DPT20.602` | Klappenventil-Modus |\n| `DPT20.603` | Heizkreis-Modus |\n| `DPT20.604` | Heizkörpermodus |\n| *(weitere DPT20.x)* | *1-Byte Enums/Modi* |\n\n**DPT 29 — 64-Bit Integer (Smart Metering)**\n\n| DPT | Grösse | Typ | Typische Verwendung |\n|---|---|---|---|\n| `DPT29.010` | 8 Byte | Ganzzahl | Wirkenergie (Wh), hochauflösend |\n| `DPT29.011` | 8 Byte | Ganzzahl | Scheinenergie (VAh) |\n| `DPT29.012` | 8 Byte | Ganzzahl | Blindenergie (VARh) |\n\n**DPT 219, 240 — Spezielle Typen**\n\n| DPT | Grösse | Typ | Typische Verwendung |\n|---|---|---|---|\n| `DPT219.001` | 2 Byte | Ganzzahl | AlarmInfo (Modus + Statusbits) |\n| `DPT240.800` | 3 Byte | JSON-Text | Jalousie-Kombination (Höhe % + Lamellen %) |\n\n\u003e **Hinweis für KNX-Dimmer:** Zwei separate Verknüpfungen anlegen — eine DEST für die Schreib-Adresse, eine SOURCE für die Rückmelde-Adresse.\n\n---\n\n### Modbus-TCP-Adapter\n\n**Instanz-Konfiguration:**\n\n| Feld | Standard | Beschreibung |\n|---|---|---|\n| `host` | — | IP-Adresse der Modbus-Gegenstelle |\n| `port` | `502` | TCP-Port |\n| `timeout` | `3.0` | Verbindungs-Timeout in Sekunden |\n\n**Verknüpfungs-Konfiguration:**\n\n| Feld | Werte | Beschreibung |\n|---|---|---|\n| `unit_id` | `1` | Modbus-Slave-ID (Geräteadresse) |\n| `register_type` | `holding`, `input`, `coil`, `discrete_input` | Registertyp |\n| `address` | Ganzzahl | Registeradresse (0-basiert) |\n| `count` | `1` | Anzahl zu lesender Register |\n| `data_format` | `uint16`, `int16`, `uint32`, `int32`, `float32`, `uint64`, `int64` | Datenformat |\n| `scale_factor` | `1.0` | Rohwert × Faktor = Messwert |\n| `byte_order` | `big` / `little` | Byte-Reihenfolge im Register |\n| `word_order` | `big` / `little` | Wort-Reihenfolge bei 32/64-Bit-Werten |\n| `poll_interval` | `1.0` | Abfrageintervall in Sekunden (nur SOURCE/BOTH) |\n\n\u003e **Praxistipp:** Die meisten Steuerungen (Siemens, Beckhoff …) verwenden `big`/`big`. Bei offensichtlich falschem Wert zuerst `word_order` auf `little` wechseln.\n\n---\n\n### Modbus-RTU-Adapter\n\nGleiche Verknüpfungs-Konfiguration wie TCP. Zusätzliche Instanz-Felder: `port` (z. B. `/dev/ttyUSB0`), `baudrate`, `parity`, `stopbits`, `bytesize`, `timeout`.\n\n---\n\n### 1-Wire-Adapter\n\nLiest Temperatursensoren über den Linux-Systemordner (`/sys/bus/w1/…`). Auf Windows funktioniert der Adapter nicht, startet aber ohne Fehlermeldung.\n\n**Instanz-Konfiguration:**\n\n| Feld | Standard | Beschreibung |\n|---|---|---|\n| `poll_interval` | `30.0` | Abfrageintervall in Sekunden |\n| `w1_path` | `/sys/bus/w1/devices` | Pfad zum 1-Wire-Systemordner |\n\n**Verknüpfungs-Konfiguration:**\n\n| Feld | Beschreibung |\n|---|---|\n| `sensor_id` | Sensor-ID, z. B. `28-0000012345ab` |\n| `sensor_type` | Sensortyp, z. B. `DS18B20` (Standard) |\n\nVerfügbare Sensor-IDs können über den Verbindungstest abgerufen werden.\n\n---\n\n### MQTT-Adapter (externer Broker)\n\nVerbindet sich mit einem **externen** MQTT-Broker (getrennt vom internen Mosquitto).\n\n**Instanz-Konfiguration:** `host`, `port`, `username`, `password`\n\n**Verknüpfungs-Konfiguration:**\n\n| Feld | Beschreibung |\n|---|---|\n| `topic` | Topic zum Empfangen (SOURCE/BOTH) |\n| `publish_topic` | Topic zum Senden (DEST/BOTH) — Standard: gleich wie `topic` |\n| `retain` | Retain-Flag beim Senden setzen |\n\n---\n\n### Home-Assistant-Adapter\n\nVerbindet **open bridge server** bidirektional mit einer Home-Assistant-Instanz. Empfängt Zustandsänderungen in Echtzeit über WebSocket (`state_changed`-Ereignisse) und schreibt Werte über die HA-REST-API (Dienst-Aufrufe).\n\n**Instanz-Konfiguration:**\n\n| Feld | Standard | Beschreibung |\n|---|---|---|\n| `host` | `homeassistant.local` | Hostname oder IP-Adresse der HA-Instanz |\n| `port` | `8123` | Port der HA-Weboberfläche |\n| `token` | — | Long-Lived Access Token (Passwort-Feld) |\n| `ssl` | `false` | HTTPS/WSS verwenden |\n\n**Verknüpfungs-Konfiguration:**\n\n| Feld | Beschreibung |\n|---|---|\n| `entity_id` | Home-Assistant-Entity-ID, z. B. `sensor.wohnzimmer_temperatur` |\n| `attribute` | Optionales Attribut statt dem Hauptzustand, z. B. `unit_of_measurement` |\n| `service_domain` | Dienst-Domain für Schreibbefehle, wird automatisch aus der Entity abgeleitet wenn leer |\n| `service_name` | Dienst-Name: Standard `turn_on`/`turn_off` für Boolean, `set_value` sonst |\n| `service_data_key` | Schlüssel für den Wert im Dienst-Aufruf, z. B. `brightness` oder `value` |\n\nTextzustände wie `\"on\"`/`\"off\"`, `\"true\"`/`\"false\"` werden automatisch in Boolean-Werte umgewandelt. Numerische Texte werden als Zahl übergeben.\n\n---\n\n### ioBroker-Adapter\n\nVerbindet **open bridge server** bidirektional mit einer ioBroker-Instanz über Socket.IO. Werte werden beim Verknüpfen initial gelesen und danach in Echtzeit über `stateChange`-Ereignisse aktualisiert; Schreibbefehle werden per `setState` an ioBroker gesendet.\n\n**Instanz-Konfiguration:**\n\n| Feld | Standard | Beschreibung |\n|---|---|---|\n| `host` | `iobroker.local` | Hostname oder IP-Adresse der ioBroker-Instanz |\n| `port` | `8084` | Port des ioBroker Socket.IO/Web-Adapters |\n| `username` | — | Optionaler Benutzername |\n| `password` | — | Optionales Passwort (Passwort-Feld) |\n| `ssl` | `false` | HTTPS verwenden |\n| `path` | `/socket.io` | Socket.IO-Pfad |\n| `access_token` | — | Optionaler Bearer/OAuth-Token (Passwort-Feld) |\n\n**Verknüpfungs-Konfiguration:**\n\n| Feld | Beschreibung |\n|---|---|\n| `state_id` | ioBroker-State-ID, z. B. `0_userdata.0.wohnzimmer.temperatur` |\n| `command_state_id` | Optional abweichender State für Schreibbefehle, z. B. ein `.SET`-State |\n| `ack` | Ack-Flag beim Schreiben (`false` = Befehl, `true` = bestätigter Status) |\n| `source_data_type` | Optionaler Datentyp für eingehende Werte: `string`, `int`, `float`, `bool`, `json` |\n| `json_key` | Optionaler Schlüssel zum Extrahieren eines Werts aus JSON |\n\nTextzustände wie `\"on\"`/`\"off\"`, `\"true\"`/`\"false\"` werden automatisch in Boolean-Werte umgewandelt. Numerische Texte werden als Zahl übergeben. Für getrennte Status- und Befehlsobjekte kann `state_id` auf den Status und `command_state_id` auf den Befehls-State zeigen.\n\nEntwicklungs- und Review-Notizen zur aktuellen Implementierung stehen in [`docs/iobroker-adapter.md`](docs/iobroker-adapter.md).\n\n---\n\n### SNMP-Adapter\n\nLiest OID-Werte von SNMP-fähigen Geräten (SNMPv1, v2c, v3) und schreibt Werte per SNMP SET. Jedes Binding konfiguriert seinen eigenen Host und OID — keine persistente TCP-Verbindung, zustandsloses UDP pro Anfrage.\n\n**Instanz-Konfiguration:**\n\n| Feld | Standard | Beschreibung |\n|---|---|---|\n| `version` | `2c` | SNMP-Version: `1`, `2c` oder `3` |\n| `community` | `public` | Community-String (nur v1/v2c) |\n| `security_name` | — | Security-Name / Benutzername (nur v3) |\n| `security_level` | `noAuthNoPriv` | Sicherheitsstufe (v3): `noAuthNoPriv`, `authNoPriv`, `authPriv` |\n| `auth_protocol` | `MD5` | Authentifizierungsprotokoll (v3): `MD5`, `SHA`, `SHA256`, `SHA512` |\n| `auth_key` | — | Authentifizierungsschlüssel (v3, Passwort-Feld) |\n| `priv_protocol` | `DES` | Privacy-Protokoll (v3): `DES`, `3DES`, `AES128`, `AES192`, `AES256` |\n| `priv_key` | — | Privacy-Schlüssel (v3, Passwort-Feld) |\n\n**Verknüpfungs-Konfiguration:**\n\n| Feld | Standard | Beschreibung |\n|---|---|---|\n| `host` | `192.168.1.1` | IP-Adresse oder DNS-Name des SNMP-Geräts |\n| `port` | `161` | UDP-Port |\n| `oid` | `1.3.6.1.2.1.1.1.0` | Objekt-Identifier, z. B. `1.3.6.1.2.1.1.3.0` |\n| `data_type` | `auto` | Werttyp: `auto`, `int`, `float`, `string`, `hex`, `counter`, `gauge`, `timeticks` |\n| `poll_interval` | `30.0` | Abfrageintervall in Sekunden (SOURCE/BOTH) |\n| `timeout` | `5.0` | Timeout pro Anfrage in Sekunden |\n| `retries` | `1` | Wiederholungen bei Fehler |\n\n\u003e **Hinweis:** `pysnmp` muss installiert sein (`pip install pysnmp`). Fehlt die Bibliothek, startet der Adapter ohne Fehlermeldung, kann aber keine Abfragen durchführen.\n\n---\n\n### Anwesenheitssimulation-Adapter\n\nWiederholt historische Schaltzustände während der Abwesenheit, damit das Gebäude bewohnt wirkt. Wenn die Simulation aktiv ist, liest der Adapter die Verlaufs-Datenbank der letzten N Tage und löst jeden historischen Schaltvorgang zur gleichen Uhrzeit heute aus.\n\n**Instanz-Konfiguration:**\n\n| Feld | Standard | Beschreibung |\n|---|---|---|\n| `offset_days` | `7` | Anzahl Tage in der Vergangenheit, deren Schaltzustände wiederholt werden (1–30) |\n| `control_dp_id` | — | Optionaler Boolean-Datenpunkt: Wert `1` = Anwesend (Simulation aus), Wert `0` = Abwesend (Simulation an) |\n| `control_invert` | `false` | Steuerobjekt invertieren |\n| `on_presence` | `behalten` | Verhalten bei Anwesenheitserkennung: `behalten` (aktuellen Zustand beibehalten), `zuruecksetzen` (alle auf falsch/0 setzen), `setzen` (auf einen bestimmten Wert setzen) |\n| `on_presence_value` | — | Wert der gesetzt wird wenn `on_presence = setzen` |\n\n**Verknüpfungs-Konfiguration:**\n\n| Feld | Standard | Beschreibung |\n|---|---|---|\n| `offset_override` | — | Überschreibt `offset_days` für diesen Datenpunkt (1–30) |\n| `on_presence_override` | — | Überschreibt `on_presence` für diesen Datenpunkt |\n| `on_presence_value` | — | Überschreibt den Wert für diesen Datenpunkt wenn `on_presence_override = setzen` |\n\n\u003e **Hinweis:** Nur SOURCE-Verknüpfungen sind gültig — der Adapter wiederholt historische Werte, akzeptiert aber keine eingehenden Schreibbefehle. DEST/BOTH-Verknüpfungen werden mit einer Warnung übersprungen.\n\n---\n\n### Zeitschaltuhr-Adapter\n\nErzeugt zeitgesteuerte Ereignisse ohne externe Hardware — für tageszeit- oder sonnenstandsbasierte Automatisierungen, Feiertags- und Ferienlogik.\n\n**Instanz-Konfiguration:**\n\n| Feld | Standard | Beschreibung |\n|---|---|---|\n| `latitude` | `47.5` | Breitengrad für Sonnenstandsberechnung |\n| `longitude` | `8.0` | Längengrad für Sonnenstandsberechnung |\n| `altitude` | `400.0` | Höhe über NN in Metern |\n| `timezone` | (App-Zeitzone) | IANA-Zeitzone; leer = Systemzeitzone von **open bridge server** verwenden |\n| `holiday_country` | `CH` | ISO-3166-Ländercode für Feiertagskalender |\n| `holiday_subdivision` | — | Kanton/Bundesland, z. B. `ZH` oder `BY` |\n| `holiday_language` | `de` | Sprache für Feiertagsnamen |\n| `vacation_1_start` … `vacation_6_end` | — | Bis zu 6 Ferienperioden im Format `JJJJ-MM-TT` |\n\n**Verknüpfungs-Konfiguration:**\n\n| Feld | Werte | Beschreibung |\n|---|---|---|\n| `timer_type` | `daily`, `annual`, `meta` | `daily` = täglich wiederkehrend; `annual` = einmaliges Datum; `meta` = Metadaten-Ausgang (Feiertag, Ferien) |\n| `meta_type` | `holiday_today`, `holiday_tomorrow`, `holiday_name_today`, `holiday_name_tomorrow`, `vacation_1`…`vacation_6` | Für `timer_type = meta`: welcher Metadatenwert ausgegeben wird |\n| `time_ref` | `absolute`, `sunrise`, `sunset`, `solar_noon`, `solar_altitude` | Zeitreferenz |\n| `hour` / `minute` | `0`–`23` / `0`–`59` | Absolute Uhrzeit oder Offset zur Zeitreferenz |\n| `offset_minutes` | Ganzzahl | Versatz zur Zeitreferenz in Minuten (positiv = später) |\n| `solar_altitude_deg` | `-18`–`90` | Sonnenstand-Schwellwert in Grad (nur `solar_altitude`) |\n| `sun_direction` | `rising`, `setting` | Aufsteigende oder absteigende Sonnenbahn (nur `solar_altitude`) |\n| `weekdays` | Liste `[0–6]` | Wochentage (0 = Montag). Leer = alle. |\n| `months` | Liste `[1–12]` | Monate. Leer = alle. |\n| `day_of_month` | `0`–`31` | Tag im Monat; `0` = alle. |\n| `every_hour` | `true`/`false` | Jede Stunde zur konfigurierten Minute auslösen |\n| `every_minute` | `true`/`false` | Jede Minute auslösen |\n| `holiday_mode` | `ignore`, `skip`, `only`, `as_sunday` | Verhalten an Feiertagen |\n| `vacation_mode` | `ignore`, `skip`, `only`, `as_sunday` | Verhalten in Ferienperioden |\n| `value` | Text | Wert der beim Auslösen geschrieben wird (Standard: `\"1\"`) |\n\n**Feiertagsmodi:**\n\n| Modus | Verhalten |\n|---|---|\n| `ignore` | Feiertage/Ferien werden wie normale Tage behandelt |\n| `skip` | An diesen Tagen wird nicht ausgelöst |\n| `only` | Auslösen nur an Feiertagen/Ferien |\n| `as_sunday` | Feiertag/Ferientag wird für die Wochentagsprüfung als Sonntag (6) behandelt |\n\n---\n\n## MQTT-Topics\n\n**open bridge server** verwendet zwei parallele Topic-Strategien:\n\n| Topic | Beschreibung |\n|---|---|\n| `dp/{uuid}/value` | Stabil — ändert sich nie, sicher für Automatisierungen. Mit Retain gespeichert. |\n| `dp/{uuid}/set` | Auf diesen Topic schreiben um einen Wert zu setzen |\n| `dp/{uuid}/status` | Verbindungsstatus des Adapters (mit Retain) |\n| `alias/{tag}/{name}/value` | Lesbar und durchsuchbar (nur wenn `mqtt_alias` gesetzt) |\n\n**Nachrichtenformat (`dp/{uuid}/value`):**\n\n```json\n{ \"v\": 21.4, \"u\": \"°C\", \"t\": \"2026-03-27T10:23:41.123Z\", \"q\": \"good\" }\n```\n\n| Schlüssel | Bedeutung |\n|---|---|\n| `v` | Wert |\n| `u` | Einheit |\n| `t` | Zeitstempel (ISO 8601) |\n| `q` | Qualität: `good` / `bad` / `uncertain` |\n\n**Wert setzen:**\n```bash\nmosquitto_pub -t \"dp/550e8400-.../set\" -m '{\"v\": 22.5}'\n```\n\n---\n\n## Datentypen\n\n| Typ | Beschreibung | MQTT-Format |\n|---|---|---|\n| `BOOLEAN` | Ein/Aus | `true` / `false` |\n| `INTEGER` | Ganze Zahl | Zahl |\n| `FLOAT` | Dezimalzahl | Zahl |\n| `STRING` | Text | Zeichenkette |\n| `DATE` | Datum | `JJJJ-MM-TT` |\n| `TIME` | Uhrzeit | `HH:MM:SS` |\n| `DATETIME` | Datum und Uhrzeit | ISO 8601 mit Zeitzone |\n| `UNKNOWN` | Unbekannt | Hexadezimal-Text |\n\nTypumwandlungen sind verlustfrei wo möglich — bei Verlust wird eine Meldung ins Protokoll geschrieben.\n\n---\n\n## Einstellungen\n\nDie Einstellungen sind über die Weboberfläche erreichbar (⚙ in der Seitenleiste).\n\n**Allgemein:**\n- **Zeitzone** — alle Zeitangaben in der Oberfläche werden in dieser Zeitzone dargestellt (Verlauf, RingBuffer, History-Suche, Astro-Block)\n- **KNX-Projektdatei importieren** — ETS-Projektdatei (`.knxproj`) hochladen, um Gruppenadressen als Suchvorschläge im Verknüpfungs-Formular zu nutzen\n\n**Verlauf:** Übersicht aller Datenpunkte mit History-Aufzeichnung. Datenpunkte mit deaktivierter Aufzeichnung (`record_history: false`) werden zuerst angezeigt. Aufzeichnung per Datenpunkt ein- und ausschalten.\n\n**Passwort:** Eigenes Anmeldepasswort ändern\n\n**Benutzer** (nur Administratoren): Benutzer anlegen, löschen, MQTT-Zugang verwalten\n\n**API-Schlüssel:** Schlüssel für die Anbindung externer Systeme erstellen und widerrufen\n\n**Sicherung:** Vollständige Konfiguration herunterladen oder einspielen\n\n---\n\n## Hilfsskripte\n\n### Import-EtsGaCsv.ps1 — ETS-GA-Export importieren\n\nDas Skript `scripts/Import-EtsGaCsv.ps1` liest einen ETS-GA-CSV-Export und legt je Gruppenadresse\nautomatisch einen DataPoint mit passendem Typ und Einheit an. Anschliessend wird eine\nVerknüpfung zur angegebenen KNX-Adapter-Instanz erstellt.\n\n**Voraussetzungen:** PowerShell 5.1 oder neuer, erreichbare **open bridge server**-Instanz, gültiger API-Schlüssel.\n\n**Parameter:**\n\n| Parameter | Pflicht | Beschreibung |\n|---|---|---|\n| `-Url` | ja | Basis-URL der **open bridge server**-Instanz, z.B. `http://localhost:8080` |\n| `-ApiKey` | ja | API-Schlüssel (`obs_…`) |\n| `-File` | ja | Pfad zur ETS-GA-CSV-Datei |\n| `-Adapter` | ja | Name der KNX-Adapter-Instanz in **open bridge server** |\n| `-LogFile` | nein | Pfad für Fehlerprotokoll; ohne Angabe werden Fehler auf der Konsole ausgegeben |\n| `-Direction` | nein | Verknüpfungsrichtung: `SOURCE` (Standard), `DEST` oder `BOTH` |\n| `-Encoding` | nein | Zeichenkodierung der CSV-Datei: `UTF8` (Standard) oder `Default` (ANSI/Windows-1252). ETS 5 exportiert i.d.R. ANSI, ETS 6 UTF-8. |\n\n**CSV-Format (ETS 5/6 GA-Export):**\n\nDer Export erfolgt in ETS über *Gruppenadressliste exportieren → CSV*. Das Skript erkennt Semikolon- und\nKomma-Trennzeichen sowie deutschsprachige und englischsprachige Spaltenköpfe automatisch.\n\n```\n\"Group name\";\"Address\";\"Central\";\"Unfiltered\";\"Description\";\"Comment\";\"DatapointType\";\"Security\"\n\"Wohnzimmer Temperatur\";\"1/1/1\";\"\";\"\";\"\";\"\";DPST-9-1;Auto\n\"Wohnzimmer Helligkeit\";\"1/1/2\";\"\";\"\";\"\";\"\";DPST-9-2;Auto\n\"Rolllade EG Auf/Ab\";\"1/2/1\";\"\";\"\";\"\";\"\";DPST-1-8;Auto\n```\n\nDPT-Angaben im Format `DPST-X-Y` (Haupt- und Subtyp) oder `DPT-X` (nur Haupttyp) werden\nautomatisch in das **open bridge server**-Format (`DPT9.001`) umgewandelt und der passende Datentyp (`FLOAT`,\n`INTEGER`, `BOOLEAN`, `STRING`) sowie die Einheit werden gesetzt. Fehlt der DPT, wird `FLOAT`\nohne Einheit verwendet.\n\n**Beispiel:**\n\n```powershell\n.\\scripts\\Import-EtsGaCsv.ps1 `\n    -Url    http://localhost:8080 `\n    -ApiKey obs_abc123 `\n    -File   C:\\Projekte\\GA_Export.csv `\n    -Adapter \"KNX/IP\"\n```\n\nETS 5 (ANSI-Kodierung):\n\n```powershell\n.\\scripts\\Import-EtsGaCsv.ps1 `\n    -Url      http://localhost:8080 `\n    -ApiKey   obs_abc123 `\n    -File     C:\\Projekte\\GA_Export.csv `\n    -Adapter  \"KNX/IP\" `\n    -Encoding Default\n```\n\nMit Fehlerprotokoll:\n\n```powershell\n.\\scripts\\Import-EtsGaCsv.ps1 `\n    -Url     http://localhost:8080 `\n    -ApiKey  obs_abc123 `\n    -File    C:\\Projekte\\GA_Export.csv `\n    -Adapter \"KNX/IP\" `\n    -LogFile C:\\Projekte\\import_errors.log\n```\n\nDas Skript läuft bei Einzelfehlern durch. Am Ende werden Anzahl der erfolgreich importierten,\nübersprungenen (Zeilen ohne Adresse) und fehlgeschlagenen GAs ausgegeben.\n\n---\n\n## Visualisierung (Visu)\n\nDie Visu-Oberfläche ist eine separate Single-Page-App (erreichbar unter `/visu/`), mit der interaktive Bedienoberflächen — sogenannte **Visu-Seiten** — erstellt und im Vollbildmodus auf Displays oder Tablets angezeigt werden können. Jede Seite besteht aus frei platzierbaren Widgets, die Datenpunkte anzeigen oder steuern.\n\n### Grundriss- und Anlagenschema-Widget\n\nDas **Grundriss-Widget** ermöglicht es, einen Gebäudegrundriss oder ein Anlagenschema als interaktiven Hintergrund in eine Visu-Seite einzubinden. Auf dem Bild lassen sich Bereiche (Polygone) definieren, beschriften und mit Aktionen verknüpfen — sowie Mini-Widgets direkt auf dem Plan platzieren.\n\n#### Bild einbinden\n\nIm Konfigurations-Panel des Widgets kann ein Bild hochgeladen werden (SVG, PNG oder JPG). Das Bild wird als Base64-Data-URL direkt im Konfig-JSON gespeichert — kein separater Upload-Endpunkt nötig. Bei Dateien über 2 MB erscheint ein Hinweis; für Grundrisse wird **SVG empfohlen**, da es verlustfrei skaliert.\n\nDie **Rotation** des Bildes lässt sich in 90°-Schritten einstellen (0° / 90° / 180° / 270°), um Landscape-Grafiken direkt im Portrait-Modus verwenden zu können. \n\n#### Bereiche (Polygone) zeichnen\n\nMit dem Polygon-Werkzeug im Vollbild-Canvas lassen sich Bereiche auf dem Grundriss einzeichnen:\n\n1. Im Konfigurations-Panel auf **Neuer Bereich** klicken — der Fullscreen-Canvas öffnet sich.\n2. Durch Klicken auf die Arbeitsfläche werden Eckpunkte des Polygons gesetzt.\n3. Das Polygon wird geschlossen, indem der erste Punkt erneut angeklickt oder **Enter** gedrückt wird.\n\nJedem Bereich können folgende Eigenschaften zugewiesen werden:\n\n| Eigenschaft | Beschreibung |\n|---|---|\n| **Name** | Bezeichnung des Bereichs (z. B. „Wohnzimmer\") |\n| **Beschriftung anzeigen** | Schaltet die Textbeschriftung auf dem Plan ein/aus |\n| **Beschriftungsfarbe** | Textfarbe der Bereichsbeschriftung |\n| **Beschriftungsposition** | Durch Klick auf den Bereich im Canvas frei positionierbar |\n| **Aktion bei Klick** | `Keine` oder `Navigation` — bei Navigation: Ziel-Visu-Seite auswählen |\n\n#### Navigation zwischen Seiten\n\nWenn als Klick-Aktion **Navigation** gewählt wird, öffnet sich eine Seitenauswahl. Die gewählte Visu-Seite wird beim Klick auf den Bereich im Viewer direkt aufgerufen. So lassen sich z. B. Etagenpläne miteinander verknüpfen — Klick auf einen Raum öffnet eine Detailansicht.\n\n#### Mini-Widgets platzieren\n\nAuf dem Grundriss können beliebige **Mini-Widgets** (z. B. Schalter, Temperaturanzeige, Dimmregler) direkt auf dem Plan positioniert werden:\n\n1. Im Konfigurations-Panel auf **Mini-Widget hinzufügen** klicken und den Widget-Typ wählen.\n2. Auf **Positionieren** klicken — der Fullscreen-Canvas öffnet sich.\n3. Das Mini-Widget per **Drag \u0026 Drop** an die gewünschte Stelle auf dem Plan ziehen.\n\nFür jedes Mini-Widget lassen sich einstellen:\n\n| Eigenschaft | Beschreibung |\n|---|---|\n| **Widget-Typ** | Beliebiger Visu-Widget-Typ (Schalter, Anzeige, Dimmer, …) |\n| **Datenpunkt** | Steuert den Wert des Widgets (Hauptdatenpunkt) |\n| **Status-Datenpunkt** | Optionaler zweiter Datenpunkt für den Anzeigestatus |\n| **Breite / Höhe** | Größe des Mini-Widgets in Pixeln |\n| **Sichtbar** | Blendet das Widget im Viewer ein oder aus |\n\nMini-Widgets drehen sich beim Rotieren des Grundrisses nicht mit — sie bleiben immer aufrecht und werden anhand der Bildkoordinaten korrekt über dem Grundriss positioniert.\n\n---\n\n## Entwicklung\n\n### Lokale Entwicklung mit PyCharm\n\nDas Repository enthält vorkonfigurierte [PyCharm](https://www.jetbrains.com/de-de/pycharm/)-Startkonfigurationen im Verzeichnis `.run/`. Nach dem Öffnen des Projekts stehen sie direkt in der Run-Auswahl zur Verfügung.\n\n#### Einmalige Einrichtung\n\n**1. Python-Umgebung anlegen**\n\n```bash\ncd openbridgeserver\npython3 -m venv .venv\nsource .venv/bin/activate          # Windows: .venv\\Scripts\\activate\npip install -r requirements.txt -r requirements_dev.txt\n```\n\nIn PyCharm unter **Settings → Project → Python Interpreter** den Interpreter `.venv/bin/python` auswählen.\n\n**2. Frontend-Abhängigkeiten installieren**\n\n```bash\ncd gui \u0026\u0026 npm install\n```\n\n**3. Konfigurationsdatei anlegen**\n\n```bash\ncp config.example.yaml config.yaml\n```\n\nFolgende Werte in `config.yaml` anpassen:\n\n```yaml\nmqtt:\n  username: obs\n  password: change-this-mqtt-service-password   # muss mit .env übereinstimmen\n\ndatabase:\n  path: /absoluter/pfad/zum/projekt/data/obs.db  # lokaler Pfad, kein /data\n\nmosquitto:\n  passwd_file: /absoluter/pfad/zum/projekt/data/mosquitto/passwd\n  reload_pid: null\n  reload_command: null\n  service_username: obs\n  service_password: change-this-mqtt-service-password\n```\n\n**4. Umgebungsvariablen einrichten**\n\n```bash\ncp .env.example .env   # falls noch nicht vorhanden\n```\n\nDie `.env`-Datei enthält das MQTT-Passwort, mit dem der Docker-Mosquitto initialisiert wird — dieser Wert muss mit `mqtt.password` in `config.yaml` übereinstimmen.\n\n#### Starten\n\n| Run-Konfiguration | Beschreibung |\n|---|---|\n| **OBS Mosquitto (Docker)** | Startet den MQTT-Broker via Docker |\n| **OBS Backend** | Startet den FastAPI-Server auf `localhost:8080` |\n| **OBS GUI (Admin)** | Startet den Vite-Dev-Server auf `localhost:5173` |\n| **OBS Full Dev Stack** | Startet alle drei gleichzeitig (Compound) |\n\n\u003e **Voraussetzung:** Docker Desktop muss laufen (für den Mosquitto-Broker).\n\n#### Erreichbare Dienste im Dev-Modus\n\n| Dienst | Adresse |\n|---|---|\n| Admin-GUI | http://localhost:5173 |\n| API (Swagger) | http://localhost:8080/docs |\n| MQTT | localhost:1883 |\n\n**Standardzugang:** `admin` / `admin`\n\n#### Tests ausführen\n\n```bash\n# Nur Unit- und Adapter-Tests (kein Docker nötig)\npytest tests/unit/ tests/adapters/\n\n# Alle Tests inkl. Integration (Docker muss laufen)\npytest tests/\n```\n\n#### Lint lokal (identisch zu GitHub CI)\n\n```bash\n# Nur prüfen (gleiches Verhalten wie CI-Job)\n./tools/lint.sh --check\n\n# Mit Auto-Fix\n./tools/lint.sh --fix\n```\n\n#### Lokale Builds (Docker-Image, LXC-Template, App-Bundle)\n\nVollständige Dokumentation zu `build-local.sh` — Befehle, Optionen und das Docker-Image-Namensschema — siehe **[tools/README.de.md](tools/README.de.md)**.\n\n### Lokale Git-Hooks (Pre-Push Gate)\n\nVersionierte Hooks liegen in `.githooks/`. Um sie in einem Klon zu aktivieren, `core.hooksPath` einmalig setzen:\n\n```bash\n./tools/setup-git-hooks.sh\n```\n\nBei jedem `git push` führt der Hook aus:\n\n- `./scripts/check-i18n-hardcoded-strings.sh`\n- `python3 -m ruff check .`\n- `python3 -m ruff format . --check`\n- `pytest tests/ -v --cov=obs --cov-report=xml --cov-report=term --junitxml=\"${TMPDIR:-/tmp}/openbridge-pre-push-junit.xml\"`\n\nEinmalig umgehen:\n\n```bash\ngit push --no-verify\n```\n\n---\n\n#### Übersetzungen (Weblate / wlc)\n\nDie GUI-Übersetzungen werden über [hosted.weblate.org](https://hosted.weblate.org/projects/openbridgeserver/) verwaltet. Quellsprache ist Deutsch (`de.json`); die Community übersetzt auf Weblate.\n\n**Voraussetzung:** `wlc` ist bereits in `requirements_dev.txt` enthalten und wird bei der normalen Einrichtung mitinstalliert.\n\nZugangsdaten einrichten — entweder in `~/.config/weblate`:\n\n```ini\n[weblate]\nurl = https://hosted.weblate.org/api/\n\n[keys]\nhttps://hosted.weblate.org/api/ = \u003cdein-api-key\u003e\n```\n\noder via Umgebungsvariablen: `WLC_URL` / `WLC_KEY`.\n\n**Quell-Strings hochladen** (nach Änderungen an `de.json`):\n\n```bash\nwlc push gui-admin       # Admin-GUI  (gui/src/locales/de.json)\nwlc push frontend-visu   # Visu-SPA   (frontend/src/locales/de.json)\n```\n\n**Übersetzungen herunterladen** (nach Community-Übersetzungen auf Weblate):\n\n```bash\nwlc pull gui-admin\nwlc pull frontend-visu\n```\n\nDie Weblate-Projektkonfiguration liegt in `.weblate` im Projektwurzelverzeichnis.\n\n---\n\n### Starten ohne Docker\n\n```bash\n# Mosquitto (temporär)\ndocker run -d -p 1883:1883 eclipse-mosquitto:2\n\n# Konfiguration\ncp config.example.yaml config.yaml\n\n# Server mit automatischem Neustart bei Codeänderungen\nuvicorn obs.main:create_app --factory --reload --host 0.0.0.0 --port 8080\n```\n\n### Datenbankstruktur\n\nDie Datenbank wird automatisch aktualisiert — jede neue Version fügt fehlende Tabellen und Spalten hinzu, ohne bestehende Daten zu verlieren. Aktuelle Version: **V21**.\n\n| Tabelle | Inhalt |\n|---|---|\n| `datapoints` | Alle Datenpunkte (inkl. `persist_value`- und `record_history`-Flag) |\n| `adapter_bindings` | Verknüpfungen zwischen Datenpunkten und Adaptern (inkl. `value_map`) |\n| `adapter_instances` | Adapter-Instanzen |\n| `users` | Benutzerkonten |\n| `api_keys` | API-Schlüssel (nur als Hashwert gespeichert) |\n| `history_values` | Werteverlauf (inkl. `source_adapter`) |\n| `logic_graphs` | Logik-Graphen (inkl. gespeichertem Block-Zustand) |\n| `app_settings` | Systemeinstellungen (z. B. Zeitzone) |\n| `datapoint_last_values` | Letzter bekannter Wert je Datenpunkt — wird beim Start wiederhergestellt |\n\n---\n\n## Übersetzungen\nZukünftig möchten wir [Weblate](https://hosted.weblate.org/projects/open-bridge-server) für Community-Übersetzungen verwenden. Sobald das möglich ist, sind Beiträge sind jederzeit willkommen.\n\n## Lizenz\n\nMIT — kostenlos und quelloffen.\n\n[tests]: https://github.com/abeggled/openbridgeserver/actions/workflows/unittest.yml\n[tests-badge]: https://img.shields.io/github/actions/workflow/status/abeggled/openbridgeserver/unittest.yml?style=for-the-badge\u0026logo=github\u0026logoColor=ccc\u0026label=Tests\n\n[coverage]: https://app.codecov.io/github/abeggled/openbridgeserver\n[coverage-badge]: https://img.shields.io/codecov/c/github/abeggled/openbridgeserver?style=for-the-badge\u0026logo=codecov\u0026logoColor=ccc\u0026label=Coverage\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabeggled%2Fopenbridgeserver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabeggled%2Fopenbridgeserver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabeggled%2Fopenbridgeserver/lists"}