tts for ios removed, browser mod removes, only critical for ios and only tts for android
This commit is contained in:
parent
6e7b43eea4
commit
81fe1ac188
200
README.md
200
README.md
|
|
@ -1,2 +1,200 @@
|
|||
# ha-smoke-detection-notify
|
||||
# Rauchmelder-Benachrichtigungen für Home Assistant
|
||||
|
||||
**Node-RED Lösung mit integrierter Settings-Seite**
|
||||
|
||||
## Features
|
||||
|
||||
- **Eine Settings-Seite** - Alles zentral konfigurieren (auch als Dashboard-Karte)
|
||||
- **KEIN Helper nötig** - Kein YAML editieren, keine Entities erstellen
|
||||
- **Sirene pro Rauchmelder** - Jeder Rauchmelder kann seine eigene Sirene haben
|
||||
- **Dropdown-Auswahl** - Entities werden automatisch aus Home Assistant geladen
|
||||
- **iOS & Android** - Unterschiedliche TTS-Methoden
|
||||
- **STOPP pro Gerät** - Individuelles Stoppen
|
||||
- **Visuelles Debugging** - Node-RED Debug-Panel
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Home Assistant
|
||||
- Node-RED Add-on
|
||||
- Mobile App auf allen Geräten (iOS & Android)
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Settings-Seite in Home Assistant ablegen
|
||||
|
||||
Kopiere `node-red/settings.html` nach `/config/www/settings.html`
|
||||
|
||||
Die Datei ist dann erreichbar unter:
|
||||
```
|
||||
http://homeassistant.local:8123/local/settings.html
|
||||
```
|
||||
|
||||
### 2. Node-RED Flow importieren
|
||||
|
||||
1. Öffne **Node-RED**
|
||||
2. **Menü** (☰) → **Import**
|
||||
3. Kopiere Inhalt von `node-red/flows/smoke_detector.json`
|
||||
4. **Importieren** → **Deploy**
|
||||
|
||||
### 3. Long-Lived Token erstellen
|
||||
|
||||
1. Home Assistant → **Profil** (unten links) → **Sicherheit**
|
||||
2. Runterscrollen zu **"Langlebige Zugriffstokens"**
|
||||
3. **Token erstellen** → Name: `Rauchmelder Settings`
|
||||
4. Token kopieren (sieht so aus: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3Mi...`)
|
||||
|
||||
### 4. Home Assistant Server verbinden
|
||||
|
||||
**Einmalig Token einrichten:**
|
||||
1. Doppelklick auf **einen** blauen Node
|
||||
2. **Stift-Symbol** neben Server
|
||||
3. **Base URL:** `http://homeassistant.local:8123`
|
||||
4. **Access Token:** Den eben erstellten Token einfügen
|
||||
5. **Update** → **Done**
|
||||
|
||||
**Alle blauen Nodes konfigurieren:**
|
||||
|
||||
Danach musst du **jeden blauen Node** einzeln durchgehen und den Server auswählen:
|
||||
1. Doppelklick auf den blauen Node
|
||||
2. Bei **Server**: Wähle den **zweiten Eintrag** in der Liste (der mit deiner URL)
|
||||
3. **Done**
|
||||
4. Wiederhole für alle blauen Nodes
|
||||
|
||||
> **Hinweis:** Den Token musst du nur einmal eingeben. Aber jeder blaue Node muss manuell auf den Server verweisen.
|
||||
|
||||
Wenn alle Nodes konfiguriert sind: **Deploy**
|
||||
|
||||
### 5. Settings-Seite öffnen
|
||||
|
||||
**Option A: Direkt im Browser**
|
||||
```
|
||||
http://homeassistant.local:8123/local/settings.html?token=DEIN_TOKEN
|
||||
```
|
||||
|
||||
**Option B: Als Dashboard-Karte (iframe)** - Empfohlen!
|
||||
|
||||
Dashboard bearbeiten → Karte hinzufügen → Webpage:
|
||||
```yaml
|
||||
type: iframe
|
||||
url: /local/settings.html?token=DEIN_TOKEN
|
||||
aspect_ratio: 100%
|
||||
```
|
||||
|
||||
**Option C: Als Seitenleisten-Panel**
|
||||
|
||||
> **Hinweis:** Seit Home Assistant 2024.4.0 ist `panel_iframe` über YAML deprecated. Nutze stattdessen Option B (Dashboard-Karte) oder erstelle ein eigenes Dashboard:
|
||||
|
||||
1. **Einstellungen** → **Dashboards** → **Dashboard hinzufügen**
|
||||
2. Name: `Rauchmelder`, Icon: `mdi:fire`
|
||||
3. **In Seitenleiste anzeigen** aktivieren
|
||||
4. Dashboard öffnen → **Dashboard bearbeiten** → Karte hinzufügen → **Webpage**
|
||||
5. URL: `/local/settings.html?token=DEIN_TOKEN`
|
||||
|
||||
### 6. Konfigurieren & Testen
|
||||
|
||||
1. Konfiguriere alles in der Settings-Seite:
|
||||
- Benachrichtigungstext
|
||||
- TTS Intervall & Sprache
|
||||
- Rauchmelder (aus Dropdown wählen)
|
||||
- Sirene pro Rauchmelder (optional)
|
||||
- Anzeigenamen (optional)
|
||||
- Geräte (iOS/Android)
|
||||
|
||||
2. Klicke **"Speichern"**
|
||||
|
||||
3. Die Config wird automatisch an Node-RED gesendet
|
||||
|
||||
4. Nutze den **Test-Button** oder setze einen Rauchmelder auf `on`
|
||||
|
||||
---
|
||||
|
||||
## Settings-Seite
|
||||
|
||||
Die Settings-Seite bietet:
|
||||
|
||||
| Bereich | Optionen |
|
||||
|---------|----------|
|
||||
| **Benachrichtigungen** | Text mit `{detector}` Platzhalter, TTS Intervall (5-60s), Sprache |
|
||||
| **Rauchmelder** | Dropdown-Auswahl der Rauchmelder-Sensoren aus HA |
|
||||
| **Sirene** | Pro Rauchmelder optional eine Sirene/Switch zuweisen |
|
||||
| **Namen** | Optionaler Anzeigename für jeden Rauchmelder |
|
||||
| **Geräte** | Android (Name + ID + TTS) oder iOS (Name + ID + Critical Alert) |
|
||||
|
||||
---
|
||||
|
||||
## iOS Setup (Critical Alerts)
|
||||
|
||||
Für iOS-Geräte werden **Critical Alerts** verwendet. Diese sind laute Benachrichtigungen die auch bei stummgeschaltetem Gerät durchkommen.
|
||||
|
||||
### Critical Alerts aktivieren
|
||||
|
||||
1. **Home Assistant Companion App** öffnen
|
||||
2. **Einstellungen** → **Companion App** → **Benachrichtigungen**
|
||||
3. **Critical Alerts erlauben** aktivieren
|
||||
|
||||
### iOS Einstellungen prüfen
|
||||
|
||||
1. **iOS Einstellungen** → **Mitteilungen** → **Home Assistant**
|
||||
2. Prüfe ob **Kritische Hinweise** aktiviert ist
|
||||
|
||||
### Wie es funktioniert
|
||||
|
||||
- Bei Rauchmelder-Alarm bekommt das iOS-Gerät einen **Critical Alert**
|
||||
- Der Alert spielt einen lauten Sound ab (auch bei Stumm-Modus!)
|
||||
- Die Nachricht zeigt an welcher Rauchmelder ausgelöst hat
|
||||
- Der Alert wiederholt sich alle X Sekunden bis STOPP gedrückt wird
|
||||
|
||||
### Wichtig
|
||||
|
||||
- Critical Alerts umgehen den Stumm-Modus und "Nicht Stören"
|
||||
- Der Sound ist laut - perfekt für Notfälle
|
||||
- Funktioniert auch im Hintergrund und bei gesperrtem Gerät
|
||||
|
||||
---
|
||||
|
||||
## So funktioniert's
|
||||
|
||||
1. **Trigger:** Rauchmelder geht auf `on`
|
||||
2. **Filter:** Ist es ein konfigurierter Rauchmelder?
|
||||
3. **Sirene:** Die zugehörige Sirene des Rauchmelders wird aktiviert
|
||||
4. **Benachrichtigungen:** Jedes Gerät bekommt:
|
||||
- Text-Notification mit STOPP-Button
|
||||
- TTS-Loop (alle X Sekunden)
|
||||
5. **STOPP:** User drückt Button → TTS stoppt für dieses Gerät
|
||||
6. **Rauch weg:** Alle Sirenen werden ausgeschaltet
|
||||
|
||||
**Die Config wird als JSON-Datei gespeichert (`/config/www/smoke_config.json`) und bleibt auch nach Neustarts erhalten.**
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Lösung |
|
||||
|---------|--------|
|
||||
| Config nicht geladen | Öffne Settings-Seite und klicke "Speichern" |
|
||||
| Keine Rauchmelder im Dropdown | Klicke "Entities laden" - prüfe ob Sensoren device_class "smoke" haben |
|
||||
| Benachrichtigung kommt nicht | Prüfe Gerätename (muss mit Mobile App übereinstimmen) |
|
||||
| TTS stoppt nicht | Prüfe ob STOPP-Event ankommt (Debug-Panel) |
|
||||
| Settings-Seite zeigt Fehler | Prüfe Token in der URL |
|
||||
| Sirene geht nicht an | Prüfe ob die richtige Switch-Entity zugewiesen ist |
|
||||
|
||||
---
|
||||
|
||||
## Dateien
|
||||
|
||||
```
|
||||
node-red/
|
||||
├── flows/
|
||||
│ └── smoke_detector.json # Node-RED Flow
|
||||
└── settings.html # Einstellungsseite (→ nach /config/www/ kopieren)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lizenz
|
||||
|
||||
MIT
|
||||
|
|
|
|||
|
|
@ -0,0 +1,182 @@
|
|||
# ESP32 Test-Rauchmelder für Node-RED Smoke Detection Flow
|
||||
#
|
||||
# Funktionen:
|
||||
# - Switch "Rauch erkannt setzen" -> steuert Binary Sensor
|
||||
# - Switch "Sirene" -> steuert die onboard LED
|
||||
# - Binary Sensor "Rauch erkannt" -> on/off basierend auf Switch
|
||||
# - BOOT Button (GPIO0) -> Toggle für Rauch-Status
|
||||
#
|
||||
# Die meisten ESP32 Boards haben eine LED an GPIO2
|
||||
# Der BOOT Button ist typischerweise an GPIO0
|
||||
|
||||
esphome:
|
||||
name: test-rauchmelder
|
||||
friendly_name: Test Rauchmelder
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
# WLAN-Konfiguration
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
|
||||
# Fallback Access Point falls WLAN nicht erreichbar
|
||||
ap:
|
||||
ssid: "Test-Rauchmelder"
|
||||
password: "12345678"
|
||||
|
||||
captive_portal:
|
||||
|
||||
# Logging aktivieren
|
||||
logger:
|
||||
|
||||
# Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret api_encryption_key
|
||||
|
||||
# OTA Updates
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
|
||||
# Webserver für Debugging (optional)
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
# ============================================
|
||||
# GPIO für Onboard LED (meist GPIO2 bei ESP32)
|
||||
# ============================================
|
||||
output:
|
||||
- platform: gpio
|
||||
pin: GPIO2
|
||||
id: onboard_led_output
|
||||
|
||||
light:
|
||||
- platform: binary
|
||||
name: "Sirene LED"
|
||||
id: sirene_led
|
||||
output: onboard_led_output
|
||||
# Wird intern gesteuert, nicht direkt exposed
|
||||
|
||||
# ============================================
|
||||
# SWITCHES
|
||||
# ============================================
|
||||
|
||||
# Globale Variable für Rauch-Status
|
||||
globals:
|
||||
- id: rauch_erkannt_status
|
||||
type: bool
|
||||
restore_value: no
|
||||
initial_value: 'false'
|
||||
|
||||
switch:
|
||||
# Switch 1: Rauch erkannt setzen
|
||||
- platform: template
|
||||
name: "Rauch erkannt setzen"
|
||||
id: switch_rauch_setzen
|
||||
icon: "mdi:fire"
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
on_turn_on:
|
||||
- globals.set:
|
||||
id: rauch_erkannt_status
|
||||
value: 'true'
|
||||
- binary_sensor.template.publish:
|
||||
id: binary_rauch_erkannt
|
||||
state: ON
|
||||
- logger.log: "Rauch erkannt wurde aktiviert!"
|
||||
on_turn_off:
|
||||
- globals.set:
|
||||
id: rauch_erkannt_status
|
||||
value: 'false'
|
||||
- binary_sensor.template.publish:
|
||||
id: binary_rauch_erkannt
|
||||
state: OFF
|
||||
- logger.log: "Rauch erkannt wurde deaktiviert!"
|
||||
|
||||
# Switch 2: Sirene (steuert die Onboard LED)
|
||||
- platform: template
|
||||
name: "Sirene"
|
||||
id: switch_sirene
|
||||
icon: "mdi:alarm-light"
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
on_turn_on:
|
||||
- light.turn_on: sirene_led
|
||||
- logger.log: "Sirene AN - LED leuchtet!"
|
||||
on_turn_off:
|
||||
- light.turn_off: sirene_led
|
||||
- logger.log: "Sirene AUS - LED aus!"
|
||||
|
||||
# ============================================
|
||||
# BINARY SENSOR
|
||||
# ============================================
|
||||
|
||||
binary_sensor:
|
||||
# BOOT Button (GPIO0) als Toggle für Rauch-Status
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO0
|
||||
mode: INPUT_PULLUP
|
||||
inverted: true
|
||||
name: "Boot Button"
|
||||
id: boot_button
|
||||
internal: true # Nicht in HA anzeigen
|
||||
filters:
|
||||
- delayed_on: 10ms # Entprellen
|
||||
on_press:
|
||||
then:
|
||||
- switch.toggle: switch_rauch_setzen
|
||||
- logger.log: "Boot Button gedrückt - Toggle Rauch-Status!"
|
||||
|
||||
# Binary Sensor: Rauch erkannt (gesteuert durch Switch)
|
||||
- platform: template
|
||||
name: "Rauch erkannt"
|
||||
id: binary_rauch_erkannt
|
||||
device_class: smoke
|
||||
icon: "mdi:smoke-detector"
|
||||
lambda: |-
|
||||
return id(rauch_erkannt_status);
|
||||
|
||||
# Status-Sensor für Verbindung
|
||||
- platform: status
|
||||
name: "Status"
|
||||
|
||||
# ============================================
|
||||
# ZUSÄTZLICHE SENSOREN (optional)
|
||||
# ============================================
|
||||
|
||||
sensor:
|
||||
# WLAN Signalstärke
|
||||
- platform: wifi_signal
|
||||
name: "WLAN Signal"
|
||||
update_interval: 60s
|
||||
|
||||
# Uptime
|
||||
- platform: uptime
|
||||
name: "Laufzeit"
|
||||
update_interval: 60s
|
||||
|
||||
# ============================================
|
||||
# TEXT SENSOR
|
||||
# ============================================
|
||||
|
||||
text_sensor:
|
||||
# IP Adresse
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Adresse"
|
||||
ssid:
|
||||
name: "Verbundenes WLAN"
|
||||
|
||||
# ============================================
|
||||
# BUTTON für Neustart
|
||||
# ============================================
|
||||
|
||||
button:
|
||||
- platform: restart
|
||||
name: "Neustart"
|
||||
|
|
@ -0,0 +1,784 @@
|
|||
[
|
||||
{
|
||||
"id": "smoke_flow_tab",
|
||||
"type": "tab",
|
||||
"label": "Rauchmelder System",
|
||||
"disabled": false,
|
||||
"info": "Rauchmelder-Benachrichtigungen V7\n\n✅ Config wird als JSON-Datei gespeichert\n✅ Persistiert über Neustarts"
|
||||
},
|
||||
{
|
||||
"id": "comment_header",
|
||||
"type": "comment",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🔥 Rauchmelder V7 - Config in /config/www/smoke_config.json",
|
||||
"info": "Config wird als JSON-Datei gespeichert.\nSettings-Seite lädt und speichert Config aus/in der Datei.",
|
||||
"x": 300,
|
||||
"y": 40,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "comment_config",
|
||||
"type": "comment",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "━━━━━━━━━━ CONFIG MANAGEMENT ━━━━━━━━━━",
|
||||
"info": "",
|
||||
"x": 220,
|
||||
"y": 80,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "inject_load_config",
|
||||
"type": "inject",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "▶️ Config laden (Auto-Start)",
|
||||
"props": [],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": true,
|
||||
"onceDelay": "3",
|
||||
"topic": "",
|
||||
"x": 180,
|
||||
"y": 120,
|
||||
"wires": [["file_read_config"]]
|
||||
},
|
||||
{
|
||||
"id": "inject_reload_config",
|
||||
"type": "inject",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🔄 Config neu laden",
|
||||
"props": [],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"x": 160,
|
||||
"y": 160,
|
||||
"wires": [["file_read_config"]],
|
||||
"icon": "font-awesome/fa-refresh"
|
||||
},
|
||||
{
|
||||
"id": "file_read_config",
|
||||
"type": "file in",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "📄 Config lesen",
|
||||
"filename": "/homeassistant/www/smoke_config.json",
|
||||
"filenameType": "str",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": true,
|
||||
"encoding": "utf8",
|
||||
"allProps": false,
|
||||
"x": 420,
|
||||
"y": 140,
|
||||
"wires": [["func_parse_config"]]
|
||||
},
|
||||
{
|
||||
"id": "func_parse_config",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "📋 Config parsen & laden",
|
||||
"func": "// =====================================================\n// PARSE UND LADE CONFIG AUS JSON DATEI - V7\n// =====================================================\n\nconst ha = global.get('homeassistant.homeAssistant.states');\nif (!ha) {\n node.error('Home Assistant nicht verbunden!');\n node.status({fill:'red', shape:'ring', text:'HA nicht verbunden!'});\n return null;\n}\n\nlet config;\n\n// Prüfe ob Datei gelesen wurde oder Fehler\nif (msg.error) {\n // Datei existiert nicht - warte auf erste Config\n node.warn('Config-Datei nicht gefunden. Warte auf erste Speicherung über Settings-Seite.');\n node.status({fill:'yellow', shape:'ring', text:'⚠️ Keine Config - Bitte in Settings speichern'});\n \n // Leere Default-Config setzen\n config = {\n devices: [],\n detectors: [],\n notification_text: '🔥 RAUCHMELDER ALARM! {detector} hat Rauch erkannt!',\n tts_interval: 10,\n tts_language: 'de'\n };\n global.set('smoke_config', config);\n return null;\n}\n\ntry {\n config = JSON.parse(msg.payload);\n} catch (e) {\n node.error('Config JSON ungültig: ' + e.message);\n node.status({fill:'red', shape:'ring', text:'❌ Config JSON ungültig!'});\n return null;\n}\n\n// Config validieren und aufbereiten\nconfig = {\n devices: config.devices || [],\n detectors: config.detectors || [],\n notification_text: config.notification_text || '🔥 RAUCHMELDER ALARM! {detector} hat Rauch erkannt!',\n tts_interval: config.tts_interval || 10,\n tts_language: config.tts_language || 'de'\n};\n\n// Config global speichern\nglobal.set('smoke_config', config);\n\n// TTS-Status für alle Geräte initialisieren\nconfig.devices.forEach(device => {\n const key = `tts_active_${device.device_id}`;\n if (flow.get(key) === undefined) {\n flow.set(key, false);\n }\n});\n\n// Status anzeigen\nconst statusText = `✅ ${config.detectors.length} RM, ${config.devices.length} Geräte`;\nnode.status({fill:'green', shape:'dot', text: statusText});\n\nif (config.devices.length === 0 || config.detectors.length === 0) {\n node.warn('Config unvollständig! Bitte in Settings-Seite konfigurieren.');\n node.status({fill:'yellow', shape:'ring', text:'⚠️ Config unvollständig'});\n}\n\nnode.log('Config geladen: ' + statusText);\n\nmsg.config = config;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 650,
|
||||
"y": 140,
|
||||
"wires": [["debug_config"]]
|
||||
},
|
||||
{
|
||||
"id": "event_config_update",
|
||||
"type": "server-events",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "📥 Config Update Event",
|
||||
"server": "",
|
||||
"version": 2,
|
||||
"eventType": "smoke_config_update",
|
||||
"exposeToHomeAssistant": false,
|
||||
"haConfig": [],
|
||||
"waitForRunning": true,
|
||||
"outputProperties": [
|
||||
{"property": "payload", "propertyType": "msg", "value": "", "valueType": "eventData"}
|
||||
],
|
||||
"x": 180,
|
||||
"y": 220,
|
||||
"wires": [["func_prepare_save"]]
|
||||
},
|
||||
{
|
||||
"id": "func_prepare_save",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "💾 Config vorbereiten",
|
||||
"func": "// Bereite Config zum Speichern vor\nconst eventData = msg.payload;\n\n// Debug: Was kommt an?\nnode.warn('Event empfangen: ' + JSON.stringify(eventData).substring(0, 200));\n\n// Das Event kann verschiedene Formate haben\nlet config = null;\n\nif (eventData && eventData.config) {\n // Format: { config: {...} }\n config = eventData.config;\n} else if (eventData && eventData.event && eventData.event.config) {\n // Format: { event: { config: {...} } }\n config = eventData.event.config;\n} else if (eventData && eventData.detectors) {\n // Format: Config direkt im payload\n config = eventData;\n}\n\nif (!config) {\n node.warn('Keine Config im Event gefunden! Struktur: ' + Object.keys(eventData || {}).join(', '));\n node.status({fill:'red', shape:'ring', text:'❌ Keine Config!'});\n return null;\n}\n\n// Config aufbereiten\nconst saveConfig = {\n devices: config.devices || [],\n detectors: config.detectors || [],\n notification_text: config.notification_text || '🔥 RAUCHMELDER ALARM! {detector} hat Rauch erkannt!',\n tts_interval: config.tts_interval || 10,\n tts_language: config.tts_language || 'de'\n};\n\n// Als JSON formatieren (schön lesbar)\nmsg.payload = JSON.stringify(saveConfig, null, 2);\n\nnode.status({fill:'blue', shape:'dot', text:'Speichere ' + saveConfig.detectors.length + ' RM, ' + saveConfig.devices.length + ' Geräte...'});\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 420,
|
||||
"y": 220,
|
||||
"wires": [["file_write_config"]]
|
||||
},
|
||||
{
|
||||
"id": "file_write_config",
|
||||
"type": "file",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "💾 In Datei speichern",
|
||||
"filename": "/homeassistant/www/smoke_config.json",
|
||||
"filenameType": "str",
|
||||
"appendNewline": false,
|
||||
"createDir": true,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "utf8",
|
||||
"x": 640,
|
||||
"y": 220,
|
||||
"wires": [["func_save_success"]]
|
||||
},
|
||||
{
|
||||
"id": "func_save_success",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "✅ Gespeichert + Neu laden",
|
||||
"func": "node.status({fill:'green', shape:'dot', text:'✅ Config gespeichert!'});\nnode.log('Config in /config/www/smoke_config.json gespeichert');\n\n// Trigger Neuladen der Config\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 860,
|
||||
"y": 220,
|
||||
"wires": [["file_read_config"]]
|
||||
},
|
||||
{
|
||||
"id": "debug_config",
|
||||
"type": "debug",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "Config Debug",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "config",
|
||||
"targetType": "msg",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 870,
|
||||
"y": 140,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "comment_trigger",
|
||||
"type": "comment",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "━━━━━━━━━━ RAUCHMELDER TRIGGER ━━━━━━━━━━",
|
||||
"info": "",
|
||||
"x": 220,
|
||||
"y": 380,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "trigger_smoke_all",
|
||||
"type": "server-state-changed",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🔥 Alle binary_sensor.*",
|
||||
"server": "",
|
||||
"version": 4,
|
||||
"exposeToHomeAssistant": false,
|
||||
"haConfig": [],
|
||||
"entityidfilter": "binary_sensor.",
|
||||
"entityidfiltertype": "substring",
|
||||
"outputinitially": false,
|
||||
"state_type": "str",
|
||||
"haltifstate": "",
|
||||
"halt_if_type": "str",
|
||||
"halt_if_compare": "is",
|
||||
"outputs": 1,
|
||||
"output_only_on_state_change": true,
|
||||
"for": "0",
|
||||
"forType": "num",
|
||||
"forUnits": "minutes",
|
||||
"ignorePrevStateNull": false,
|
||||
"ignorePrevStateUnknown": false,
|
||||
"ignorePrevStateUnavailable": false,
|
||||
"ignoreCurrentStateUnknown": false,
|
||||
"ignoreCurrentStateUnavailable": false,
|
||||
"outputProperties": [
|
||||
{"property": "payload", "propertyType": "msg", "value": "", "valueType": "entityState"},
|
||||
{"property": "data", "propertyType": "msg", "value": "", "valueType": "eventData"}
|
||||
],
|
||||
"x": 160,
|
||||
"y": 420,
|
||||
"wires": [["func_filter_smoke"]]
|
||||
},
|
||||
{
|
||||
"id": "func_filter_smoke",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🎯 Filter: Ist konfigurierter Rauchmelder?",
|
||||
"func": "const config = global.get('smoke_config');\n\nif (!config || !config.detectors || config.detectors.length === 0) {\n return null;\n}\n\n// Entity ID aus den Event-Daten holen\nconst entityId = msg.data.entity_id || (msg.data.new_state ? msg.data.new_state.entity_id : null);\n\nif (!entityId) {\n node.warn('Keine entity_id gefunden!');\n return null;\n}\n\nconst detector = config.detectors.find(d => d.sensor === entityId);\nif (!detector) {\n return null;\n}\n\nconst newState = msg.payload;\nconst oldState = msg.data.old_state ? msg.data.old_state.state : 'off';\n\nmsg.entity_id = entityId;\n\nif (newState === 'on' && oldState !== 'on') {\n node.status({fill:'red', shape:'dot', text:'🔥 RAUCH: ' + entityId});\n msg.event = 'smoke_detected';\n msg.config = config;\n msg.detector = detector;\n return [msg, null];\n}\n\nif (newState === 'off' && oldState === 'on') {\n node.status({fill:'green', shape:'dot', text:'✅ Klar: ' + entityId});\n msg.event = 'smoke_cleared';\n msg.config = config;\n msg.detector = detector;\n return [null, msg];\n}\n\nreturn null;",
|
||||
"outputs": 2,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 460,
|
||||
"y": 340,
|
||||
"wires": [["func_process_alarm"], ["func_check_all_clear"]],
|
||||
"outputLabels": ["Rauch erkannt", "Rauch weg"]
|
||||
},
|
||||
{
|
||||
"id": "func_process_alarm",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🚨 Verarbeite Alarm",
|
||||
"func": "const config = msg.config;\nconst detector = msg.detector;\nconst triggered = msg.entity_id;\n\nlet detector_name = detector.name;\nif (!detector_name && msg.data.new_state && msg.data.new_state.attributes) {\n detector_name = msg.data.new_state.attributes.friendly_name;\n}\nif (!detector_name) {\n detector_name = triggered.replace('binary_sensor.', '').replace(/_/g, ' ');\n}\n\nconst message = config.notification_text.replace(/{detector}/g, detector_name);\n\nmsg.triggered_detector = triggered;\nmsg.detector_name = detector_name;\nmsg.message = message;\n\nnode.status({fill:'red', shape:'dot', text:'🔥 ' + detector_name});\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 770,
|
||||
"y": 320,
|
||||
"wires": [["func_turn_on_alarms"]]
|
||||
},
|
||||
{
|
||||
"id": "func_turn_on_alarms",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "📢 Sirene EIN",
|
||||
"func": "const config = msg.config;\nconst detector = msg.detector;\n\nconst siren = detector.siren;\n\nif (!siren) {\n node.warn('Keine Sirene für ' + detector.sensor + ' konfiguriert');\n return [null, msg];\n}\n\nconst alarmMsg = {\n payload: {\n data: {\n entity_id: siren\n }\n }\n};\n\nnode.status({fill:'red', shape:'dot', text:'Sirene: ' + siren});\n\nreturn [[alarmMsg], msg];",
|
||||
"outputs": 2,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 990,
|
||||
"y": 300,
|
||||
"wires": [["ha_switch_on"], ["func_notify_all_devices"]],
|
||||
"outputLabels": ["Alarme", "Weiter"]
|
||||
},
|
||||
{
|
||||
"id": "ha_switch_on",
|
||||
"type": "api-call-service",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🔔 Switch EIN",
|
||||
"server": "",
|
||||
"version": 5,
|
||||
"debugenabled": false,
|
||||
"domain": "switch",
|
||||
"service": "turn_on",
|
||||
"areaId": [],
|
||||
"deviceId": [],
|
||||
"entityId": ["{{payload.data.entity_id}}"],
|
||||
"data": "",
|
||||
"dataType": "json",
|
||||
"mergeContext": "",
|
||||
"mustacheAltTags": false,
|
||||
"outputProperties": [],
|
||||
"queue": "none",
|
||||
"x": 1180,
|
||||
"y": 280,
|
||||
"wires": [[]]
|
||||
},
|
||||
{
|
||||
"id": "comment_notifications",
|
||||
"type": "comment",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "━━━━━━━━━━ BENACHRICHTIGUNGEN ━━━━━━━━━━",
|
||||
"info": "",
|
||||
"x": 220,
|
||||
"y": 480,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "func_notify_all_devices",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "📱 Für jedes Gerät",
|
||||
"func": "const config = msg.config;\nconst devices = config.devices || [];\n\nif (devices.length === 0) {\n node.warn('Keine Geräte konfiguriert!');\n return null;\n}\n\nconst messages = [];\n\nfor (let device of devices) {\n const ttsKey = `tts_active_${device.device_id}`;\n const isActive = flow.get(ttsKey);\n \n if (isActive) {\n node.log('TTS bereits aktiv für: ' + device.device_name);\n continue;\n }\n \n const newMsg = RED.util.cloneMessage(msg);\n newMsg.device = device;\n messages.push(newMsg);\n}\n\nif (messages.length === 0) {\n node.status({fill:'yellow', shape:'dot', text:'Alle Geräte bereits aktiv'});\n return null;\n}\n\nnode.status({fill:'blue', shape:'dot', text: messages.length + ' Geräte benachrichtigen'});\n\nreturn [messages];",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 170,
|
||||
"y": 540,
|
||||
"wires": [["func_mark_active"]]
|
||||
},
|
||||
{
|
||||
"id": "func_mark_active",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "✅ Markiere als aktiv",
|
||||
"func": "const device = msg.device;\nconst ttsKey = `tts_active_${device.device_id}`;\n\nflow.set(ttsKey, true);\n\nnode.status({fill:'green', shape:'dot', text: '✅ ' + device.device_name});\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 400,
|
||||
"y": 540,
|
||||
"wires": [["func_send_text_notification"]]
|
||||
},
|
||||
{
|
||||
"id": "func_send_text_notification",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "📝 Text-Notification erstellen",
|
||||
"func": "const device = msg.device;\nconst message = msg.message;\nconst triggered = msg.triggered_detector;\n\n// Device ID direkt verwenden für den Service-Namen\nconst serviceName = 'mobile_app_' + device.device_id;\n\nconst tag = 'smoke_alarm_' + triggered.replace(/\\./g, '_');\n// Action ID enthält unsere device_id für späteres Mapping\nconst actionId = 'STOP_SMOKE_' + device.device_id;\n\nmsg.notify_service = serviceName;\nmsg.notify_message = message;\nmsg.notify_title = '🚨 RAUCHMELDER ALARM';\nmsg.notify_tag = tag;\nmsg.notify_action_id = actionId;\n\nmsg.notification_tag = tag;\nmsg.service_name = serviceName;\nmsg.our_device_id = device.device_id;\n\nnode.status({fill:'green', shape:'dot', text: serviceName});\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 680,
|
||||
"y": 540,
|
||||
"wires": [["ha_notify", "func_start_tts_loop"]]
|
||||
},
|
||||
{
|
||||
"id": "ha_notify",
|
||||
"type": "api-call-service",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "📱 Benachrichtigung senden",
|
||||
"server": "",
|
||||
"version": 5,
|
||||
"debugenabled": false,
|
||||
"domain": "notify",
|
||||
"service": "{{notify_service}}",
|
||||
"areaId": [],
|
||||
"deviceId": [],
|
||||
"entityId": [],
|
||||
"data": "{\"message\": \"{{notify_message}}\\n\\n⬇️ Runterziehen für STOPP-Button\", \"title\": \"{{notify_title}}\", \"data\": {\"tag\": \"{{notify_tag}}\", \"channel\": \"alarm_stream_max\", \"importance\": \"high\", \"priority\": \"high\", \"sticky\": true, \"persistent\": true, \"color\": \"#ff0000\", \"clickAction\": \"noAction\", \"notification_icon\": \"mdi:fire\", \"visibility\": \"public\", \"sound\": \"none\", \"actions\": [{\"action\": \"{{notify_action_id}}\", \"title\": \"🛑 STOPP TTS\"}]}}",
|
||||
"dataType": "json",
|
||||
"mergeContext": "",
|
||||
"mustacheAltTags": false,
|
||||
"outputProperties": [],
|
||||
"queue": "none",
|
||||
"x": 200,
|
||||
"y": 600,
|
||||
"wires": [[]]
|
||||
},
|
||||
{
|
||||
"id": "comment_tts",
|
||||
"type": "comment",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "━━━━━━━━━━ TTS LOOP ━━━━━━━━━━",
|
||||
"info": "",
|
||||
"x": 190,
|
||||
"y": 660,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "func_start_tts_loop",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🔊 TTS Loop starten",
|
||||
"func": "const device = msg.device;\nconst config = msg.config;\n\nmsg.tts_device = device;\nmsg.tts_config = config;\nmsg.loop_count = 0;\n\nnode.status({fill:'blue', shape:'dot', text:'TTS Start: ' + device.device_name});\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 170,
|
||||
"y": 720,
|
||||
"wires": [["func_check_tts_active"]]
|
||||
},
|
||||
{
|
||||
"id": "func_check_tts_active",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🔄 TTS noch aktiv?",
|
||||
"func": "const device = msg.tts_device;\nconst ttsKey = `tts_active_${device.device_id}`;\nconst isActive = flow.get(ttsKey);\n\nif (!isActive) {\n node.status({fill:'green', shape:'dot', text:'TTS gestoppt: ' + device.device_name});\n return null;\n}\n\nmsg.loop_count = (msg.loop_count || 0) + 1;\nnode.status({fill:'blue', shape:'ring', text:'TTS #' + msg.loop_count + ': ' + device.device_name});\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 410,
|
||||
"y": 720,
|
||||
"wires": [["switch_platform"]]
|
||||
},
|
||||
{
|
||||
"id": "switch_platform",
|
||||
"type": "switch",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "📱 iOS / Android?",
|
||||
"property": "tts_device.is_ios",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{"t": "true"},
|
||||
{"t": "else"}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 2,
|
||||
"x": 650,
|
||||
"y": 720,
|
||||
"wires": [["func_tts_ios"], ["func_tts_android"]],
|
||||
"outputLabels": ["iOS", "Android"]
|
||||
},
|
||||
{
|
||||
"id": "func_tts_ios",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🍎 iOS Critical Alert",
|
||||
"func": "const device = msg.tts_device;\n\n// iOS Critical Alert mit Alarm-Sound\nconst serviceName = 'mobile_app_' + device.device_id;\n\nmsg.notify_service = serviceName;\nmsg.tts_message = msg.message;\n\nnode.status({fill:'red', shape:'dot', text: 'Critical: ' + device.device_name});\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 190,
|
||||
"y": 800,
|
||||
"wires": [["ha_tts_ios", "delay_tts"]]
|
||||
},
|
||||
{
|
||||
"id": "ha_tts_ios",
|
||||
"type": "api-call-service",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🍎 iOS Critical Alert",
|
||||
"server": "",
|
||||
"version": 5,
|
||||
"debugenabled": false,
|
||||
"domain": "notify",
|
||||
"service": "{{notify_service}}",
|
||||
"areaId": [],
|
||||
"deviceId": [],
|
||||
"entityId": [],
|
||||
"data": "{\"message\": \"{{tts_message}}\", \"title\": \"🚨 RAUCHMELDER ALARM\", \"data\": {\"push\": {\"sound\": {\"name\": \"default\", \"critical\": 1, \"volume\": 1.0}}, \"tag\": \"smoke_critical\"}}",
|
||||
"dataType": "json",
|
||||
"mergeContext": "",
|
||||
"mustacheAltTags": false,
|
||||
"outputProperties": [],
|
||||
"queue": "none",
|
||||
"x": 440,
|
||||
"y": 800,
|
||||
"wires": [["delay_clear_ios"]]
|
||||
},
|
||||
{
|
||||
"id": "delay_clear_ios",
|
||||
"type": "delay",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "⏱️ 2s warten",
|
||||
"pauseType": "delay",
|
||||
"timeout": "2",
|
||||
"timeoutUnits": "seconds",
|
||||
"rate": "1",
|
||||
"nbRateUnits": "1",
|
||||
"rateUnits": "second",
|
||||
"randomFirst": "1",
|
||||
"randomLast": "5",
|
||||
"randomUnits": "seconds",
|
||||
"drop": false,
|
||||
"allowrate": false,
|
||||
"outputs": 1,
|
||||
"x": 640,
|
||||
"y": 800,
|
||||
"wires": [["ha_clear_ios"]]
|
||||
},
|
||||
{
|
||||
"id": "ha_clear_ios",
|
||||
"type": "api-call-service",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🗑️ iOS Alert löschen",
|
||||
"server": "",
|
||||
"version": 5,
|
||||
"debugenabled": false,
|
||||
"domain": "notify",
|
||||
"service": "{{notify_service}}",
|
||||
"areaId": [],
|
||||
"deviceId": [],
|
||||
"entityId": [],
|
||||
"data": "{\"message\": \"clear_notification\", \"data\": {\"tag\": \"smoke_critical\"}}",
|
||||
"dataType": "json",
|
||||
"mergeContext": "",
|
||||
"mustacheAltTags": false,
|
||||
"outputProperties": [],
|
||||
"queue": "none",
|
||||
"x": 830,
|
||||
"y": 800,
|
||||
"wires": [[]]
|
||||
},
|
||||
{
|
||||
"id": "func_tts_android",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🤖 Android TTS",
|
||||
"func": "const device = msg.tts_device;\n\n// Device ID direkt verwenden (ist bereits im richtigen Format)\nconst serviceName = 'mobile_app_' + device.device_id;\n\nmsg.notify_service = serviceName;\nmsg.tts_text = msg.message;\n\nnode.status({fill:'blue', shape:'dot', text: serviceName});\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 180,
|
||||
"y": 860,
|
||||
"wires": [["ha_tts_android", "delay_tts"]]
|
||||
},
|
||||
{
|
||||
"id": "ha_tts_android",
|
||||
"type": "api-call-service",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🤖 TTS Android",
|
||||
"server": "",
|
||||
"version": 5,
|
||||
"debugenabled": false,
|
||||
"domain": "notify",
|
||||
"service": "{{notify_service}}",
|
||||
"areaId": [],
|
||||
"deviceId": [],
|
||||
"entityId": [],
|
||||
"data": "{\"message\": \"TTS\", \"data\": {\"media_stream\": \"alarm_stream\", \"tts_text\": \"{{tts_text}}\"}}",
|
||||
"dataType": "json",
|
||||
"mergeContext": "",
|
||||
"mustacheAltTags": false,
|
||||
"outputProperties": [],
|
||||
"queue": "none",
|
||||
"x": 400,
|
||||
"y": 860,
|
||||
"wires": [[]]
|
||||
},
|
||||
{
|
||||
"id": "delay_tts",
|
||||
"type": "delay",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "⏱️ Warte TTS Intervall",
|
||||
"pauseType": "delayv",
|
||||
"timeout": "10",
|
||||
"timeoutUnits": "seconds",
|
||||
"rate": "1",
|
||||
"nbRateUnits": "1",
|
||||
"rateUnits": "second",
|
||||
"randomFirst": "1",
|
||||
"randomLast": "5",
|
||||
"randomUnits": "seconds",
|
||||
"drop": false,
|
||||
"allowrate": false,
|
||||
"outputs": 1,
|
||||
"x": 640,
|
||||
"y": 830,
|
||||
"wires": [["func_check_tts_active"]]
|
||||
},
|
||||
{
|
||||
"id": "comment_stop",
|
||||
"type": "comment",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "━━━━━━━━━━ STOPP BUTTON ━━━━━━━━━━",
|
||||
"info": "",
|
||||
"x": 200,
|
||||
"y": 940,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "event_stop_button",
|
||||
"type": "server-events",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🛑 STOPP Button Event",
|
||||
"server": "",
|
||||
"version": 2,
|
||||
"eventType": "mobile_app_notification_action",
|
||||
"exposeToHomeAssistant": false,
|
||||
"haConfig": [],
|
||||
"waitForRunning": true,
|
||||
"outputProperties": [
|
||||
{"property": "payload", "propertyType": "msg", "value": "", "valueType": "eventData"}
|
||||
],
|
||||
"x": 180,
|
||||
"y": 1000,
|
||||
"wires": [["func_check_stop_action"]]
|
||||
},
|
||||
{
|
||||
"id": "func_check_stop_action",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🔍 STOP_SMOKE prüfen",
|
||||
"func": "const action = msg.payload.event.action;\n\nif (!action || !action.startsWith('STOP_SMOKE_')) {\n return null;\n}\n\n// Device ID aus der Action extrahieren (STOP_SMOKE_one_plus_7_pro_stefan -> one_plus_7_pro_stefan)\nconst ourDeviceId = action.replace('STOP_SMOKE_', '');\n\nif (!ourDeviceId) {\n node.warn('Keine Device ID in Action gefunden: ' + action);\n return null;\n}\n\nmsg.stop_device_id = ourDeviceId;\n\nnode.status({fill:'yellow', shape:'dot', text:'STOPP für: ' + ourDeviceId});\nnode.log('STOPP Action: ' + action + ' -> Device ID: ' + ourDeviceId);\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 440,
|
||||
"y": 1000,
|
||||
"wires": [["func_stop_tts"]]
|
||||
},
|
||||
{
|
||||
"id": "func_stop_tts",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "⏹️ TTS stoppen",
|
||||
"func": "const deviceId = msg.stop_device_id;\nconst config = global.get('smoke_config');\n\nconst device = config.devices.find(d => d.device_id === deviceId);\n\nif (!device) {\n node.warn('Gerät nicht gefunden: ' + deviceId);\n return null;\n}\n\nconst ttsKey = `tts_active_${deviceId}`;\nflow.set(ttsKey, false);\n\nnode.status({fill:'green', shape:'dot', text:'TTS gestoppt: ' + device.device_name});\n\nmsg.device = device;\nmsg.config = config;\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 680,
|
||||
"y": 1000,
|
||||
"wires": [["func_clear_notifications"]]
|
||||
},
|
||||
{
|
||||
"id": "func_clear_notifications",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🗑️ Notifications löschen",
|
||||
"func": "const device = msg.device;\nconst config = msg.config;\n\n// Device ID direkt verwenden für den Service-Namen\nconst serviceName = 'mobile_app_' + device.device_id;\n\nconst messages = [];\n\nfor (let det of config.detectors) {\n const tag = 'smoke_alarm_' + det.sensor.replace(/\\./g, '_');\n \n messages.push({\n notify_service: serviceName,\n clear_tag: tag\n });\n}\n\nnode.status({fill:'green', shape:'dot', text: messages.length + ' Notifications gelöscht'});\n\nreturn [messages];",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 190,
|
||||
"y": 1060,
|
||||
"wires": [["ha_clear_notification"]]
|
||||
},
|
||||
{
|
||||
"id": "ha_clear_notification",
|
||||
"type": "api-call-service",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🗑️ Notification löschen",
|
||||
"server": "",
|
||||
"version": 5,
|
||||
"debugenabled": false,
|
||||
"domain": "notify",
|
||||
"service": "{{notify_service}}",
|
||||
"areaId": [],
|
||||
"deviceId": [],
|
||||
"entityId": [],
|
||||
"data": "{\"message\": \"clear_notification\", \"data\": {\"tag\": \"{{clear_tag}}\"}}",
|
||||
"dataType": "json",
|
||||
"mergeContext": "",
|
||||
"mustacheAltTags": false,
|
||||
"outputProperties": [],
|
||||
"queue": "none",
|
||||
"x": 440,
|
||||
"y": 1060,
|
||||
"wires": [[]]
|
||||
},
|
||||
{
|
||||
"id": "comment_clear",
|
||||
"type": "comment",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "━━━━━━━━━━ RAUCH WEG ━━━━━━━━━━",
|
||||
"info": "",
|
||||
"x": 190,
|
||||
"y": 1120,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "func_check_all_clear",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🔍 Alle Rauchmelder klar?",
|
||||
"func": "const config = msg.config;\nconst cleared = msg.entity_id;\nconst ha = global.get('homeassistant.homeAssistant.states');\n\nfor (let det of config.detectors) {\n if (det.sensor !== cleared) {\n const state = ha[det.sensor];\n if (state && state.state === 'on') {\n node.status({fill:'yellow', shape:'dot', text:'Noch aktiv: ' + det.sensor});\n return null;\n }\n }\n}\n\nnode.status({fill:'green', shape:'dot', text:'✅ Alle Rauchmelder klar'});\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 780,
|
||||
"y": 360,
|
||||
"wires": [["func_turn_off_alarms"]]
|
||||
},
|
||||
{
|
||||
"id": "func_turn_off_alarms",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🔇 Alle Sirenen AUS",
|
||||
"func": "const config = msg.config;\n\nconst sirens = config.detectors\n .filter(d => d.siren)\n .map(d => d.siren);\n\nif (sirens.length === 0) {\n return null;\n}\n\nconst uniqueSirens = [...new Set(sirens)];\n\nconst messages = uniqueSirens.map(siren => ({\n payload: {\n data: {\n entity_id: siren\n }\n }\n}));\n\nnode.status({fill:'green', shape:'dot', text: uniqueSirens.length + ' Sirenen deaktiviert'});\n\nreturn [messages];",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 190,
|
||||
"y": 1180,
|
||||
"wires": [["ha_switch_off"]]
|
||||
},
|
||||
{
|
||||
"id": "ha_switch_off",
|
||||
"type": "api-call-service",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🔕 Switch AUS",
|
||||
"server": "",
|
||||
"version": 5,
|
||||
"debugenabled": false,
|
||||
"domain": "switch",
|
||||
"service": "turn_off",
|
||||
"areaId": [],
|
||||
"deviceId": [],
|
||||
"entityId": ["{{payload.data.entity_id}}"],
|
||||
"data": "",
|
||||
"dataType": "json",
|
||||
"mergeContext": "",
|
||||
"mustacheAltTags": false,
|
||||
"outputProperties": [],
|
||||
"queue": "none",
|
||||
"x": 400,
|
||||
"y": 1180,
|
||||
"wires": [[]]
|
||||
},
|
||||
{
|
||||
"id": "comment_emergency",
|
||||
"type": "comment",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "━━━━━━━━━━ NOTFALL-STOPP ━━━━━━━━━━",
|
||||
"info": "",
|
||||
"x": 200,
|
||||
"y": 1240,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "event_stop_all",
|
||||
"type": "server-events",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "🛑 STOPP ALLE Event",
|
||||
"server": "",
|
||||
"version": 2,
|
||||
"eventType": "smoke_stop_all",
|
||||
"exposeToHomeAssistant": false,
|
||||
"haConfig": [],
|
||||
"waitForRunning": true,
|
||||
"outputProperties": [
|
||||
{"property": "payload", "propertyType": "msg", "value": "", "valueType": "eventData"}
|
||||
],
|
||||
"x": 180,
|
||||
"y": 1300,
|
||||
"wires": [["func_stop_all_tts"]]
|
||||
},
|
||||
{
|
||||
"id": "func_stop_all_tts",
|
||||
"type": "function",
|
||||
"z": "smoke_flow_tab",
|
||||
"name": "⏹️ ALLE TTS stoppen",
|
||||
"func": "const config = global.get('smoke_config');\n\nif (!config || !config.devices) {\n node.warn('Keine Config vorhanden!');\n return null;\n}\n\nlet stoppedCount = 0;\nfor (let device of config.devices) {\n const ttsKey = `tts_active_${device.device_id}`;\n if (flow.get(ttsKey)) {\n flow.set(ttsKey, false);\n stoppedCount++;\n }\n}\n\nnode.status({fill:'green', shape:'dot', text: `✅ ${stoppedCount} TTS gestoppt`});\nnode.log('NOTFALL: Alle TTS-Loops gestoppt (' + stoppedCount + ' Geräte)');\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 420,
|
||||
"y": 1300,
|
||||
"wires": [[]]
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue