861 lines
41 KiB
JSON
861 lines
41 KiB
JSON
[
|
||
{
|
||
"id": "smoke_flow_tab",
|
||
"type": "tab",
|
||
"label": "Rauchmelder System",
|
||
"disabled": false,
|
||
"info": "Rauchmelder-Benachrichtigungen V8 - Gruppen-Support\n\n✅ Mehrere Gruppen (z.B. Haus 1, Haus 2)\n✅ Alarm nur für Geräte der betroffenen Gruppe\n✅ STOPP pro Gruppe"
|
||
},
|
||
{
|
||
"id": "comment_header",
|
||
"type": "comment",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🔥 Rauchmelder V8 - Multi-Gruppen Support",
|
||
"info": "Jede Gruppe hat eigene Rauchmelder, Sirenen und Geräte.\nAlarm in Gruppe A benachrichtigt nur Geräte in Gruppe A.",
|
||
"x": 250,
|
||
"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 (V8 Gruppen)",
|
||
"func": "// =====================================================\n// PARSE CONFIG MIT GRUPPEN-SUPPORT - V8\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 node.warn('Config-Datei nicht gefunden. Warte auf erste Speicherung.');\n node.status({fill:'yellow', shape:'ring', text:'⚠️ Keine Config'});\n config = { groups: [] };\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:'❌ JSON ungültig!'});\n return null;\n}\n\n// Migration: Alte Config ohne groups\nif (!config.groups && (config.devices || config.detectors)) {\n node.log('Migriere alte Config zu Gruppen-Struktur...');\n config = {\n groups: [{\n id: 1,\n name: 'Zuhause',\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 detectors: config.detectors || [],\n devices: config.devices || []\n }]\n };\n}\n\n// Sicherstellen dass groups existiert\nconfig.groups = config.groups || [];\n\n// Config global speichern\nglobal.set('smoke_config', config);\n\n// TTS-Status für alle Geräte in allen Gruppen initialisieren\nconfig.groups.forEach(group => {\n (group.devices || []).forEach(device => {\n const key = `tts_active_${group.id}_${device.device_id}`;\n if (flow.get(key) === undefined) {\n flow.set(key, false);\n }\n });\n});\n\n// Statistik\nconst totalDetectors = config.groups.reduce((sum, g) => sum + (g.detectors?.length || 0), 0);\nconst totalDevices = config.groups.reduce((sum, g) => sum + (g.devices?.length || 0), 0);\n\nconst statusText = `✅ ${config.groups.length} Gruppen, ${totalDetectors} RM, ${totalDevices} Geräte`;\nnode.status({fill:'green', shape:'dot', text: statusText});\n\nif (config.groups.length === 0) {\n node.warn('Keine Gruppen konfiguriert!');\n node.status({fill:'yellow', shape:'ring', text:'⚠️ Keine Gruppen'});\n}\n\nnode.log('Config geladen: ' + statusText);\n\nmsg.config = config;\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 680,
|
||
"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": "const eventData = msg.payload;\n\nlet config = null;\n\nif (eventData && eventData.config) {\n config = eventData.config;\n} else if (eventData && eventData.event && eventData.event.config) {\n config = eventData.event.config;\n} else if (eventData && eventData.groups) {\n config = eventData;\n}\n\nif (!config) {\n node.warn('Keine Config im Event gefunden!');\n node.status({fill:'red', shape:'ring', text:'❌ Keine Config!'});\n return null;\n}\n\n// Sicherstellen dass groups existiert\nif (!config.groups) {\n config = { groups: [] };\n}\n\nmsg.payload = JSON.stringify(config, null, 2);\n\nconst totalGroups = config.groups?.length || 0;\nnode.status({fill:'blue', shape:'dot', text:'Speichere ' + totalGroups + ' Gruppen...'});\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 gespeichert');\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": 910,
|
||
"y": 140,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "comment_trigger",
|
||
"type": "comment",
|
||
"z": "smoke_flow_tab",
|
||
"name": "━━━━━━━━━━ RAUCHMELDER TRIGGER ━━━━━━━━━━",
|
||
"info": "",
|
||
"x": 220,
|
||
"y": 300,
|
||
"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": 340,
|
||
"wires": [["func_filter_smoke_groups"]]
|
||
},
|
||
{
|
||
"id": "func_filter_smoke_groups",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🎯 Filter: Welche Gruppe?",
|
||
"func": "// =====================================================\n// FINDE GRUPPE FÜR DIESEN RAUCHMELDER\n// =====================================================\n\nconst config = global.get('smoke_config');\n\nif (!config || !config.groups || config.groups.length === 0) {\n return null;\n}\n\nconst entityId = msg.data.entity_id || (msg.data.new_state ? msg.data.new_state.entity_id : null);\n\nif (!entityId) {\n return null;\n}\n\n// Finde die Gruppe, die diesen Sensor enthält\nlet foundGroup = null;\nlet foundDetector = null;\n\nfor (let group of config.groups) {\n const detector = (group.detectors || []).find(d => d.sensor === entityId);\n if (detector) {\n foundGroup = group;\n foundDetector = detector;\n break;\n }\n}\n\nif (!foundGroup) {\n // Sensor ist in keiner Gruppe konfiguriert\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;\nmsg.group = foundGroup;\nmsg.detector = foundDetector;\nmsg.config = config;\n\nif (newState === 'on' && oldState !== 'on') {\n node.status({fill:'red', shape:'dot', text:'🔥 ' + foundGroup.name + ': ' + entityId});\n msg.event = 'smoke_detected';\n return [msg, null];\n}\n\nif (newState === 'off' && oldState === 'on') {\n node.status({fill:'green', shape:'dot', text:'✅ ' + foundGroup.name + ': ' + entityId});\n msg.event = 'smoke_cleared';\n return [null, msg];\n}\n\nreturn null;",
|
||
"outputs": 2,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 430,
|
||
"y": 340,
|
||
"wires": [["func_process_alarm_group"], ["func_check_group_clear"]],
|
||
"outputLabels": ["Rauch erkannt", "Rauch weg"]
|
||
},
|
||
{
|
||
"id": "func_process_alarm_group",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🚨 Alarm für Gruppe",
|
||
"func": "const group = msg.group;\nconst detector = msg.detector;\nconst triggered = msg.entity_id;\n\n// Detector Name ermitteln\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 = group.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:'🔥 ' + group.name + ': ' + detector_name});\n\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 710,
|
||
"y": 320,
|
||
"wires": [["func_turn_on_group_siren"]]
|
||
},
|
||
{
|
||
"id": "func_turn_on_group_siren",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "📢 Gruppen-Sirene EIN",
|
||
"func": "// =====================================================\n// ALLE SIRENEN DER GRUPPE EINSCHALTEN\n// =====================================================\n\nconst group = msg.group;\n\n// Sammle ALLE Sirenen der Gruppe (nicht nur die des auslösenden Detectors)\nconst sirens = (group.detectors || [])\n .filter(d => d.siren)\n .map(d => d.siren);\n\nif (sirens.length === 0) {\n node.status({fill:'yellow', shape:'dot', text:'Keine Sirenen in Gruppe ' + group.name});\n return [null, msg];\n}\n\n// Duplikate entfernen (falls mehrere Detectors die gleiche Sirene haben)\nconst uniqueSirens = [...new Set(sirens)];\n\nconst alarmMsgs = uniqueSirens.map(siren => ({\n payload: {\n data: {\n entity_id: siren\n }\n }\n}));\n\nnode.status({fill:'red', shape:'dot', text: group.name + ': ' + uniqueSirens.length + ' Sirene(n) EIN'});\nnode.log('Gruppe ' + group.name + ': Schalte ' + uniqueSirens.length + ' Sirene(n) ein: ' + uniqueSirens.join(', '));\n\nreturn [alarmMsgs, msg];",
|
||
"outputs": 2,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 940,
|
||
"y": 320,
|
||
"wires": [["ha_switch_on"], ["func_notify_group_devices"]],
|
||
"outputLabels": ["Sirenen", "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": 1160,
|
||
"y": 300,
|
||
"wires": [[]]
|
||
},
|
||
{
|
||
"id": "comment_notifications",
|
||
"type": "comment",
|
||
"z": "smoke_flow_tab",
|
||
"name": "━━━━━━━━━━ BENACHRICHTIGUNGEN (NUR GRUPPEN-GERÄTE) ━━━━━━━━━━",
|
||
"info": "",
|
||
"x": 300,
|
||
"y": 400,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "func_notify_group_devices",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "📱 Nur Geräte dieser Gruppe",
|
||
"func": "// =====================================================\n// NUR GERÄTE DER BETROFFENEN GRUPPE BENACHRICHTIGEN\n// =====================================================\n\nconst group = msg.group;\nconst devices = group.devices || [];\n\nif (devices.length === 0) {\n node.warn('Keine Geräte in Gruppe \"' + group.name + '\" konfiguriert!');\n node.status({fill:'yellow', shape:'dot', text:'Keine Geräte in ' + group.name});\n return null;\n}\n\nconst messages = [];\n\nfor (let device of devices) {\n // TTS-Key enthält jetzt auch die Group ID\n const ttsKey = `tts_active_${group.id}_${device.device_id}`;\n const isActive = flow.get(ttsKey);\n \n if (isActive) {\n node.log('TTS bereits aktiv für: ' + device.device_name + ' in Gruppe ' + group.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 in ' + group.name + ' bereits aktiv'});\n return null;\n}\n\nnode.status({fill:'blue', shape:'dot', text: group.name + ': ' + messages.length + ' Geräte'});\n\nreturn [messages];",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 190,
|
||
"y": 460,
|
||
"wires": [["func_mark_group_active"]]
|
||
},
|
||
{
|
||
"id": "func_mark_group_active",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "✅ Markiere als aktiv",
|
||
"func": "const device = msg.device;\nconst group = msg.group;\n\n// TTS-Key mit Group ID\nconst ttsKey = `tts_active_${group.id}_${device.device_id}`;\nflow.set(ttsKey, true);\n\nnode.status({fill:'green', shape:'dot', text: '✅ ' + group.name + ': ' + device.device_name});\n\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 460,
|
||
"y": 460,
|
||
"wires": [["func_send_group_notification"]]
|
||
},
|
||
{
|
||
"id": "func_send_group_notification",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "📝 Gruppen-Notification",
|
||
"func": "const device = msg.device;\nconst group = msg.group;\nconst message = msg.message;\nconst triggered = msg.triggered_detector;\n\nconst serviceName = 'mobile_app_' + device.device_id;\n\n// Tag enthält Group ID für späteres Löschen\nconst tag = 'smoke_alarm_' + group.id + '_' + triggered.replace(/\\./g, '_');\n\n// Action ID enthält Group ID und Device ID\nconst actionId = 'STOP_SMOKE_' + group.id + '_' + device.device_id;\n\nmsg.notify_service = serviceName;\nmsg.notify_message = message;\nmsg.notify_title = '🚨 ' + group.name + ' - 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: group.name + ': ' + serviceName});\n\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 720,
|
||
"y": 460,
|
||
"wires": [["switch_notify_platform", "func_start_group_tts"]]
|
||
},
|
||
{
|
||
"id": "switch_notify_platform",
|
||
"type": "switch",
|
||
"z": "smoke_flow_tab",
|
||
"name": "📱 iOS / Android?",
|
||
"property": "device.is_ios",
|
||
"propertyType": "msg",
|
||
"rules": [
|
||
{"t": "true"},
|
||
{"t": "else"}
|
||
],
|
||
"checkall": "true",
|
||
"repair": false,
|
||
"outputs": 2,
|
||
"x": 170,
|
||
"y": 520,
|
||
"wires": [["ha_notify_ios"], ["ha_notify_android"]],
|
||
"outputLabels": ["iOS", "Android"]
|
||
},
|
||
{
|
||
"id": "ha_notify_ios",
|
||
"type": "api-call-service",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🍎 iOS Notification",
|
||
"server": "",
|
||
"version": 5,
|
||
"debugenabled": false,
|
||
"domain": "notify",
|
||
"service": "{{notify_service}}",
|
||
"areaId": [],
|
||
"deviceId": [],
|
||
"entityId": [],
|
||
"data": "{\"message\": \"{{notify_message}}\\n\\n👆 Lange drücken für STOPP\", \"title\": \"{{notify_title}}\", \"data\": {\"tag\": \"{{notify_tag}}\", \"push\": {\"sound\": {\"name\": \"default\", \"critical\": 0, \"volume\": 0.5}, \"interruption-level\": \"time-sensitive\"}, \"presentation_options\": [\"alert\", \"badge\"], \"actions\": [{\"action\": \"SILENCE\", \"title\": \"✓ OK\", \"authenticationRequired\": false}, {\"action\": \"{{notify_action_id}}\", \"title\": \"🛑 STOPP\", \"destructive\": true}]}}",
|
||
"dataType": "json",
|
||
"mergeContext": "",
|
||
"mustacheAltTags": false,
|
||
"outputProperties": [],
|
||
"queue": "none",
|
||
"x": 420,
|
||
"y": 500,
|
||
"wires": [[]]
|
||
},
|
||
{
|
||
"id": "ha_notify_android",
|
||
"type": "api-call-service",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🤖 Android Notification",
|
||
"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": 430,
|
||
"y": 540,
|
||
"wires": [[]]
|
||
},
|
||
{
|
||
"id": "comment_tts",
|
||
"type": "comment",
|
||
"z": "smoke_flow_tab",
|
||
"name": "━━━━━━━━━━ TTS LOOP ━━━━━━━━━━",
|
||
"info": "",
|
||
"x": 190,
|
||
"y": 580,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "func_start_group_tts",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🔊 Gruppen-TTS starten",
|
||
"func": "const device = msg.device;\nconst group = msg.group;\n\nmsg.tts_device = device;\nmsg.tts_group = group;\nmsg.loop_count = 0;\n\n// TTS Intervall aus Gruppe\nmsg.delay = (group.tts_interval || 10) * 1000;\n\nnode.status({fill:'blue', shape:'dot', text: group.name + ': TTS Start ' + device.device_name});\n\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 180,
|
||
"y": 640,
|
||
"wires": [["func_check_group_tts_active"]]
|
||
},
|
||
{
|
||
"id": "func_check_group_tts_active",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🔄 Gruppen-TTS aktiv?",
|
||
"func": "const device = msg.tts_device;\nconst group = msg.tts_group;\n\nif (!device || !group) {\n node.warn('TTS Check: device oder group fehlt!');\n return null;\n}\n\n// TTS-Key mit Group ID prüfen\nconst ttsKey = `tts_active_${group.id}_${device.device_id}`;\nconst isActive = flow.get(ttsKey);\n\nnode.log('TTS Check - Key: ' + ttsKey + ', Active: ' + isActive);\n\nif (!isActive) {\n node.status({fill:'green', shape:'dot', text: group.name + ': TTS gestoppt ' + device.device_name});\n node.log('TTS Loop beendet für ' + device.device_name);\n return null;\n}\n\nmsg.loop_count = (msg.loop_count || 0) + 1;\nnode.status({fill:'blue', shape:'ring', text: group.name + ' #' + msg.loop_count + ': ' + device.device_name});\n\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 440,
|
||
"y": 640,
|
||
"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": 690,
|
||
"y": 640,
|
||
"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;\nconst group = msg.tts_group;\n\nconst serviceName = 'mobile_app_' + device.device_id;\n\nmsg.notify_service = serviceName;\nmsg.tts_message = msg.message;\nmsg.tts_title = '🚨 ' + group.name + ' - ALARM';\n\nnode.status({fill:'red', shape:'dot', text: group.name + ': Critical ' + device.device_name});\n\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 190,
|
||
"y": 720,
|
||
"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\": \"{{tts_title}}\", \"data\": {\"push\": {\"sound\": {\"name\": \"default\", \"critical\": 1, \"volume\": 1.0}}, \"tag\": \"smoke_critical\"}}",
|
||
"dataType": "json",
|
||
"mergeContext": "",
|
||
"mustacheAltTags": false,
|
||
"outputProperties": [],
|
||
"queue": "none",
|
||
"x": 440,
|
||
"y": 720,
|
||
"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": 720,
|
||
"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": 720,
|
||
"wires": [[]]
|
||
},
|
||
{
|
||
"id": "func_tts_android",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🤖 Android TTS",
|
||
"func": "const device = msg.tts_device;\nconst group = msg.tts_group;\n\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: group.name + ': ' + serviceName});\n\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 180,
|
||
"y": 780,
|
||
"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": 780,
|
||
"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": 750,
|
||
"wires": [["func_check_group_tts_active"]]
|
||
},
|
||
{
|
||
"id": "comment_stop",
|
||
"type": "comment",
|
||
"z": "smoke_flow_tab",
|
||
"name": "━━━━━━━━━━ STOPP BUTTON (PRO GRUPPE) ━━━━━━━━━━",
|
||
"info": "",
|
||
"x": 250,
|
||
"y": 860,
|
||
"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": 920,
|
||
"wires": [["func_check_group_stop"]]
|
||
},
|
||
{
|
||
"id": "func_check_group_stop",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🔍 STOP_SMOKE Gruppe prüfen",
|
||
"func": "// =====================================================\n// STOPP NUR FÜR DIESE GRUPPE + GERÄT\n// =====================================================\n\nnode.log('STOPP Event empfangen: ' + JSON.stringify(msg.payload));\n\nconst action = msg.payload.event ? msg.payload.event.action : msg.payload.action;\n\nnode.log('Action: ' + action);\n\n// Format: STOP_SMOKE_{groupId}_{deviceId}\nif (!action || !action.startsWith('STOP_SMOKE_')) {\n node.log('Ignoriere Action (kein STOP_SMOKE_): ' + action);\n return null;\n}\n\n// Parse Group ID und Device ID\nconst parts = action.replace('STOP_SMOKE_', '').split('_');\nnode.log('Parts: ' + JSON.stringify(parts));\n\nif (parts.length < 2) {\n node.warn('Ungültiges Action-Format: ' + action);\n return null;\n}\n\n// Erste Zahl ist die Group ID, Rest ist Device ID\nconst groupId = parseInt(parts[0]);\nconst deviceId = parts.slice(1).join('_');\n\nnode.log('Parsed - GroupID: ' + groupId + ', DeviceID: ' + deviceId);\n\nif (isNaN(groupId)) {\n node.warn('Ungültige Group ID: ' + parts[0]);\n return null;\n}\n\nmsg.stop_group_id = groupId;\nmsg.stop_device_id = deviceId;\n\nnode.status({fill:'yellow', shape:'dot', text:'STOPP Gruppe ' + groupId + ': ' + deviceId});\n\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 470,
|
||
"y": 920,
|
||
"wires": [["func_stop_group_tts"]]
|
||
},
|
||
{
|
||
"id": "func_stop_group_tts",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "⏹️ Gruppen-TTS stoppen",
|
||
"func": "const groupId = msg.stop_group_id;\nconst deviceId = msg.stop_device_id;\nconst config = global.get('smoke_config');\n\nif (!config || !config.groups) {\n node.warn('Keine Config vorhanden!');\n return null;\n}\n\nconst group = config.groups.find(g => g.id === groupId);\nif (!group) {\n node.warn('Gruppe nicht gefunden: ' + groupId);\n node.warn('Verfügbare Gruppen: ' + config.groups.map(g => g.id + ':' + g.name).join(', '));\n return null;\n}\n\nconst device = group.devices.find(d => d.device_id === deviceId);\nif (!device) {\n node.warn('Gerät nicht in Gruppe gefunden: ' + deviceId);\n node.warn('Verfügbare Geräte: ' + group.devices.map(d => d.device_id).join(', '));\n return null;\n}\n\n// TTS-Key mit Group ID\nconst ttsKey = `tts_active_${groupId}_${deviceId}`;\nflow.set(ttsKey, false);\n\nnode.status({fill:'green', shape:'dot', text: group.name + ': TTS gestoppt ' + device.device_name});\nnode.log('TTS gestoppt für ' + device.device_name + ' in Gruppe ' + group.name + ' (Key: ' + ttsKey + ')');\n\nmsg.device = device;\nmsg.group = group;\n\n// Reset-Message für Delay-Node senden (löscht wartende Messages)\nconst resetMsg = { reset: true };\n\nreturn [msg, resetMsg];",
|
||
"outputs": 2,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 750,
|
||
"y": 920,
|
||
"wires": [["func_clear_group_notifications"], ["delay_tts"]]
|
||
},
|
||
{
|
||
"id": "func_clear_group_notifications",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🗑️ Gruppen-Notifications löschen",
|
||
"func": "const device = msg.device;\nconst group = msg.group;\n\nconst serviceName = 'mobile_app_' + device.device_id;\n\nconst messages = [];\n\nfor (let det of group.detectors) {\n // Tag enthält Group ID\n const tag = 'smoke_alarm_' + group.id + '_' + 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: group.name + ': ' + messages.length + ' Notifications gelöscht'});\n\nreturn [messages];",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 230,
|
||
"y": 980,
|
||
"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": 520,
|
||
"y": 980,
|
||
"wires": [[]]
|
||
},
|
||
{
|
||
"id": "comment_clear",
|
||
"type": "comment",
|
||
"z": "smoke_flow_tab",
|
||
"name": "━━━━━━━━━━ RAUCH WEG (PRO GRUPPE) ━━━━━━━━━━",
|
||
"info": "",
|
||
"x": 250,
|
||
"y": 1040,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "func_check_group_clear",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🔍 Alle RM in Gruppe klar?",
|
||
"func": "// =====================================================\n// PRÜFE OB ALLE RAUCHMELDER IN DIESER GRUPPE KLAR SIND\n// =====================================================\n\nconst group = msg.group;\nconst cleared = msg.entity_id;\nconst ha = global.get('homeassistant.homeAssistant.states');\n\n// Prüfe nur Rauchmelder in dieser Gruppe\nfor (let det of group.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: group.name + ': Noch aktiv ' + det.sensor});\n return null;\n }\n }\n}\n\nnode.status({fill:'green', shape:'dot', text:'✅ ' + group.name + ': Alle klar'});\n\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 720,
|
||
"y": 360,
|
||
"wires": [["func_turn_off_group_sirens"]]
|
||
},
|
||
{
|
||
"id": "func_turn_off_group_sirens",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🔇 Gruppen-Sirenen AUS",
|
||
"func": "// =====================================================\n// NUR SIRENEN DIESER GRUPPE AUSSCHALTEN\n// =====================================================\n\nconst group = msg.group;\n\nconst sirens = (group.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: group.name + ': ' + uniqueSirens.length + ' Sirenen aus'});\n\nreturn [messages];",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 190,
|
||
"y": 1100,
|
||
"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": 440,
|
||
"y": 1100,
|
||
"wires": [[]]
|
||
},
|
||
{
|
||
"id": "comment_emergency",
|
||
"type": "comment",
|
||
"z": "smoke_flow_tab",
|
||
"name": "━━━━━━━━━━ NOTFALL-STOPP (PRO GRUPPE) ━━━━━━━━━━",
|
||
"info": "",
|
||
"x": 260,
|
||
"y": 1160,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "event_stop_group",
|
||
"type": "server-events",
|
||
"z": "smoke_flow_tab",
|
||
"name": "🛑 STOPP GRUPPE Event",
|
||
"server": "",
|
||
"version": 2,
|
||
"eventType": "smoke_stop_group",
|
||
"exposeToHomeAssistant": false,
|
||
"haConfig": [],
|
||
"waitForRunning": true,
|
||
"outputProperties": [
|
||
{"property": "payload", "propertyType": "msg", "value": "", "valueType": "eventData"}
|
||
],
|
||
"x": 190,
|
||
"y": 1220,
|
||
"wires": [["func_stop_group_all_tts"]]
|
||
},
|
||
{
|
||
"id": "func_stop_group_all_tts",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "⏹️ Alle TTS in Gruppe stoppen",
|
||
"func": "// =====================================================\n// STOPPE ALLE TTS-LOOPS IN EINER BESTIMMTEN GRUPPE\n// =====================================================\n\nconst eventData = msg.payload;\nconst groupId = eventData.group_id || (eventData.event && eventData.event.group_id);\nconst groupName = eventData.group_name || (eventData.event && eventData.event.group_name) || 'Unbekannt';\n\nif (!groupId) {\n node.warn('Keine group_id im Event!');\n return null;\n}\n\nconst config = global.get('smoke_config');\n\nif (!config || !config.groups) {\n node.warn('Keine Config vorhanden!');\n return null;\n}\n\nconst group = config.groups.find(g => g.id === groupId);\nif (!group) {\n node.warn('Gruppe nicht gefunden: ' + groupId);\n return null;\n}\n\nlet stoppedCount = 0;\nfor (let device of group.devices) {\n const ttsKey = `tts_active_${groupId}_${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: `✅ ${group.name}: ${stoppedCount} TTS gestoppt`});\nnode.log('Gruppe ' + group.name + ': ' + stoppedCount + ' TTS-Loops gestoppt');\n\n// Reset-Message für Delay-Node senden (löscht wartende Messages)\nconst resetMsg = { reset: true };\n\nreturn [null, resetMsg];",
|
||
"outputs": 2,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 490,
|
||
"y": 1220,
|
||
"wires": [[], ["delay_tts"]]
|
||
},
|
||
{
|
||
"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": 1280,
|
||
"wires": [["func_stop_all_groups_tts"]]
|
||
},
|
||
{
|
||
"id": "func_stop_all_groups_tts",
|
||
"type": "function",
|
||
"z": "smoke_flow_tab",
|
||
"name": "⏹️ ALLE TTS stoppen (alle Gruppen)",
|
||
"func": "// =====================================================\n// STOPPE ALLE TTS-LOOPS IN ALLEN GRUPPEN\n// =====================================================\n\nconst config = global.get('smoke_config');\n\nif (!config || !config.groups) {\n node.warn('Keine Config vorhanden!');\n return null;\n}\n\nlet stoppedCount = 0;\nfor (let group of config.groups) {\n for (let device of group.devices) {\n const ttsKey = `tts_active_${group.id}_${device.device_id}`;\n if (flow.get(ttsKey)) {\n flow.set(ttsKey, false);\n stoppedCount++;\n }\n }\n}\n\nnode.status({fill:'green', shape:'dot', text: `✅ ${stoppedCount} TTS gestoppt (alle Gruppen)`});\nnode.log('NOTFALL: ' + stoppedCount + ' TTS-Loops in allen Gruppen gestoppt');\n\n// Reset-Message für Delay-Node senden (löscht wartende Messages)\nconst resetMsg = { reset: true };\n\nreturn [null, resetMsg];",
|
||
"outputs": 2,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 520,
|
||
"y": 1280,
|
||
"wires": [[], ["delay_tts"]]
|
||
}
|
||
]
|