ha-smoke-detection-notify/node-red/flows/smoke_detector.json

785 lines
33 KiB
JSON
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[
{
"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": [[]]
}
]