node-red-contrib-knx-ultimate 6.3.32 → 6.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('knxUltimateAIHomeAssistant', {
3
+ category: "KNX Ultimate AI",
4
+ color: '#41BDF5',
5
+ defaults: {
6
+ name: { value: "Cerebrum Home Assistant" },
7
+ requestTimeoutMs: { value: 15000, validate: RED.validators.number() }
8
+ },
9
+ inputs: 1,
10
+ outputs: 1,
11
+ outputLabels: function () { return "ha-api request"; },
12
+ icon: "font-awesome/fa-home",
13
+ label: function () { return this.name || "Cerebrum Home Assistant"; },
14
+ paletteLabel: "Cerebrum Home Assistant"
15
+ });
16
+ </script>
17
+
18
+ <script type="text/html" data-template-name="knxUltimateAIHomeAssistant">
19
+ <div class="form-row">
20
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
21
+ <input type="text" id="node-input-name">
22
+ </div>
23
+ <div class="form-row">
24
+ <label for="node-input-requestTimeoutMs"><i class="fa fa-clock-o"></i> API timeout</label>
25
+ <input type="number" id="node-input-requestTimeoutMs" min="3000" max="60000" step="1000">
26
+ </div>
27
+ <div class="form-tips">
28
+ Connect this node's output to a Home Assistant <b>API</b> node (<code>ha-api</code>), then connect the API output back to this node's input. Home Assistant event nodes may also feed this input so Cerebrum can observe their state changes.
29
+ </div>
30
+ </script>
31
+
32
+ <script type="text/html" data-help-name="knxUltimateAIHomeAssistant">
33
+ <p>Connects Cerebrum to the Home Assistant Node-RED API node without storing Home Assistant credentials in KNX Ultimate.</p>
34
+ <h3>Required round trip</h3>
35
+ <p><code>Cerebrum Home Assistant → API (ha-api) → Cerebrum Home Assistant</code></p>
36
+ <p>The bridge sends dynamic WebSocket requests under <code>msg.payload</code>, correlates the returned message and exposes entity, state, service and event capabilities to the in-process Cerebrum registry.</p>
37
+ <p>Writes are only a provider capability. KNX AI must still apply its normal proposal, validation and confirmation policy before a Home Assistant service call can be executed.</p>
38
+ </script>
@@ -0,0 +1,152 @@
1
+ const {
2
+ getKnxAiHomeAutomationRegistry,
3
+ normalizeKnxAiHomeAutomationEvent
4
+ } = require('./utils/knxAiCerebrum')
5
+
6
+ const HOME_ASSISTANT_ADAPTER_ID = 'home-assistant'
7
+ const DEFAULT_REQUEST_TIMEOUT_MS = 15000
8
+
9
+ module.exports = function (RED) {
10
+ function knxUltimateAIHomeAssistant (config) {
11
+ RED.nodes.createNode(this, config)
12
+ const node = this
13
+ const registry = getKnxAiHomeAutomationRegistry()
14
+ const requestTimeoutMs = Math.max(3000, Math.min(60000, Number(config.requestTimeoutMs) || DEFAULT_REQUEST_TIMEOUT_MS))
15
+ const pendingRequests = new Map()
16
+ const listeners = new Set()
17
+ let requestSequence = 0
18
+ let closing = false
19
+
20
+ node.name = config.name || 'Cerebrum Home Assistant'
21
+
22
+ const updateStatus = ({ fill = 'grey', shape = 'ring', text = '' } = {}) => {
23
+ try { node.status({ fill, shape, text }) } catch (error) { /* ignore */ }
24
+ }
25
+
26
+ const notifyEvent = event => {
27
+ listeners.forEach(listener => {
28
+ try { listener(event) } catch (error) { /* ignore */ }
29
+ })
30
+ }
31
+
32
+ const sendApiRequest = data => new Promise((resolve, reject) => {
33
+ if (closing) return reject(new Error('Cerebrum Home Assistant is closing'))
34
+ requestSequence += 1
35
+ const requestId = `${node.id}:${Date.now()}:${requestSequence}`
36
+ const timer = setTimeout(() => {
37
+ pendingRequests.delete(requestId)
38
+ updateStatus({ fill: 'yellow', shape: 'ring', text: 'ha-api timeout' })
39
+ reject(new Error('Home Assistant ha-api request timed out; verify the round-trip wiring'))
40
+ }, requestTimeoutMs)
41
+ pendingRequests.set(requestId, { resolve, reject, timer })
42
+ node.send({
43
+ payload: {
44
+ protocol: 'websocket',
45
+ data,
46
+ location: 'payload',
47
+ locationType: 'msg'
48
+ },
49
+ knxAiCerebrum: {
50
+ requestId,
51
+ adapterId: HOME_ASSISTANT_ADAPTER_ID,
52
+ providerId: node.id,
53
+ direction: 'request'
54
+ }
55
+ })
56
+ updateStatus({ fill: 'blue', shape: 'dot', text: 'querying ha-api' })
57
+ })
58
+
59
+ const provider = {
60
+ id: node.id,
61
+ adapterId: HOME_ASSISTANT_ADAPTER_ID,
62
+ title: node.name,
63
+ capabilities: ['entities', 'events', 'services', 'states'],
64
+ listEntities: () => sendApiRequest({ type: 'get_states' }).then(result => Array.isArray(result) ? result : []),
65
+ getEntity: entityId => sendApiRequest({ type: 'get_states' }).then(result => {
66
+ const requested = String(entityId || '').trim().toLowerCase()
67
+ return (Array.isArray(result) ? result : []).find(entity => String(entity && entity.entity_id || '').trim().toLowerCase() === requested) || null
68
+ }),
69
+ listServices: () => sendApiRequest({ type: 'get_services' }),
70
+ callService: ({ domain, service, serviceData, target, authorization } = {}) => {
71
+ const safeDomain = String(domain || '').trim()
72
+ const safeService = String(service || '').trim()
73
+ if (!safeDomain || !safeService) return Promise.reject(new Error('Home Assistant domain and service are required'))
74
+ if (!authorization || authorization.confirmed !== true || authorization.source !== 'knxUltimateAI') {
75
+ return Promise.reject(new Error('Home Assistant service calls require an explicit KNX AI confirmation authorization'))
76
+ }
77
+ return sendApiRequest({
78
+ type: 'call_service',
79
+ domain: safeDomain,
80
+ service: safeService,
81
+ service_data: serviceData && typeof serviceData === 'object' ? serviceData : {},
82
+ target: target && typeof target === 'object' ? target : {}
83
+ })
84
+ },
85
+ subscribe: listener => {
86
+ if (typeof listener !== 'function') return () => {}
87
+ listeners.add(listener)
88
+ return () => listeners.delete(listener)
89
+ }
90
+ }
91
+
92
+ registry.registerAdapter({
93
+ id: HOME_ASSISTANT_ADAPTER_ID,
94
+ title: 'Home Assistant',
95
+ packageName: 'node-red-contrib-home-assistant-websocket',
96
+ capabilities: provider.capabilities
97
+ })
98
+ registry.registerProvider(provider)
99
+
100
+ node.on('input', function (msg, send, done) {
101
+ try {
102
+ const metadata = msg && msg.knxAiCerebrum && typeof msg.knxAiCerebrum === 'object' ? msg.knxAiCerebrum : {}
103
+ const requestId = String(metadata.requestId || '')
104
+ const pending = requestId ? pendingRequests.get(requestId) : null
105
+ if (pending) {
106
+ clearTimeout(pending.timer)
107
+ pendingRequests.delete(requestId)
108
+ if (msg && msg.error) pending.reject(new Error(String(msg.error.message || msg.error)))
109
+ else pending.resolve(msg ? msg.payload : undefined)
110
+ updateStatus({ fill: 'green', shape: 'dot', text: 'ha-api ready' })
111
+ if (typeof done === 'function') done()
112
+ return
113
+ }
114
+
115
+ const event = normalizeKnxAiHomeAutomationEvent(msg, {
116
+ adapterId: HOME_ASSISTANT_ADAPTER_ID,
117
+ providerId: node.id
118
+ })
119
+ if (event) {
120
+ notifyEvent(event)
121
+ updateStatus({ fill: 'green', shape: 'dot', text: event.entityId || event.eventType })
122
+ }
123
+ if (typeof done === 'function') done()
124
+ } catch (error) {
125
+ updateStatus({ fill: 'red', shape: 'dot', text: error.message || String(error) })
126
+ if (typeof done === 'function') done(error)
127
+ else node.error(error, msg)
128
+ }
129
+ })
130
+
131
+ node.on('close', function (done) {
132
+ closing = true
133
+ registry.unregisterProvider(node.id)
134
+ pendingRequests.forEach(pending => {
135
+ clearTimeout(pending.timer)
136
+ pending.reject(new Error('Cerebrum Home Assistant node closed'))
137
+ })
138
+ pendingRequests.clear()
139
+ listeners.clear()
140
+ if (typeof done === 'function') done()
141
+ })
142
+
143
+ updateStatus({ fill: 'yellow', shape: 'ring', text: 'wire output → ha-api → input' })
144
+ }
145
+
146
+ RED.nodes.registerType('knxUltimateAIHomeAssistant', knxUltimateAIHomeAssistant)
147
+ }
148
+
149
+ module.exports.__test = {
150
+ DEFAULT_REQUEST_TIMEOUT_MS,
151
+ HOME_ASSISTANT_ADAPTER_ID
152
+ }
@@ -3,12 +3,13 @@
3
3
  "title": "KNX AI (Traffic Analyzer)",
4
4
  "sections": {
5
5
  "groupAssistant": "KI-Assistent",
6
- "groupChatHome": "Gespräche & Zuhause",
6
+ "groupChatHome": "Cerebrum (BETA)",
7
7
  "setupDoctor": "Setup Doctor",
8
8
  "webIntelligence": "Web-Intelligenz",
9
9
  "detectedAdapters": "Erkannte und im Chat verwendete kompatible Nodes",
10
10
  "chatContextOverview": "Übersicht des Chat-Kontexts",
11
11
  "chatLearning": "KI-Chat-Lernen",
12
+ "cerebrumMemory": "Cerebrum-Speicher",
12
13
  "quickSetup": "Assistent einrichten",
13
14
  "etsAccess": "Zugriff auf ETS-Objekte",
14
15
  "llmConnection": "KI-Assistent-Verbindung",
@@ -129,6 +130,7 @@
129
130
  "chatContextLoading": "Zusammenfassung des Chat-Kontexts wird geladen…",
130
131
  "chatContextUnavailable": "Die Zusammenfassung des Chat-Kontexts ist vorübergehend nicht verfügbar.",
131
132
  "chatLearningOpenHint": "Öffnet die Weboberfläche direkt im gemeinsamen CHAT-Lerneditor, um die persistente Datei anzuzeigen, zu bearbeiten, zu kopieren oder zu sichern.",
133
+ "cerebrumMemoryOpenHint": "Öffnet den lesbaren Cerebrum-Speicher für gelernte Gewohnheiten, Entscheidungen der Bewohner und zwischengespeicherte Hauszustände.",
132
134
  "chatContextIntro": "Der Chat erhält diese Quellen automatisch. Die folgenden Pfade werden von dieser Node-RED-Installation tatsächlich verwendet.",
133
135
  "chatContextLimitLabel": "Maximaler operativer Kontext",
134
136
  "chatContextProviderManaged": "vom ausgewählten Anbieter/Modell verwaltet",
@@ -151,7 +153,7 @@
151
153
  "chatContextFileHomeMemory": "Nur begrenztes gelerntes Hausgedächtnis; die KI-Erziehung bleibt in der Node-Eigenschaft.",
152
154
  "chatContextFileSchedules": "Verbindlicher dauerhafter Laufzeitstatus der Pläne und Erinnerungen dieses Nodes.",
153
155
  "chatContextFileSchedulesReadable": "Erzeugte menschenlesbare Ansicht der Pläne und Erinnerungen dieses Nodes.",
154
- "chatContextFileAssistantConfig": "Dauerhafte Konfiguration des Web-Assistenten und semantische Bereiche dieses Nodes.",
156
+ "chatContextFileAssistantConfig": "Dauerhafte Cerebrum-Konfiguration und semantische Bereiche dieses Nodes.",
155
157
  "chatContextFileLastChatPrompt": "Temporäre lokale Kopie der letzten System- und Benutzernachricht an das Chatmodell; wird bei jeder Chatanfrage überschrieben.",
156
158
  "chatContextFileBadge": "Datei",
157
159
  "chatContextDirectoryRoot": "Stammverzeichnis des Telegrammarchivs",
@@ -219,6 +221,7 @@
219
221
  "ollamaLibrary": "Model library",
220
222
  "downloadOllamaModel": "1) Download model",
221
223
  "openChatLearning": "KI-Chat-Lernen öffnen",
224
+ "openCerebrumMemory": "Cerebrum-Speicher öffnen",
222
225
  "etsSelectAll": "Alle auswählen",
223
226
  "etsSelectNone": "Alle abwählen",
224
227
  "etsReadOnlyAll": "Nur lesen setzen",
@@ -1,7 +1,7 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateAI">
2
2
  This node listens to **all KNX telegrams** from the selected KNX Ultimate gateway, builds traffic statistics, detects anomalies, and can optionally query an LLM.
3
3
 
4
- The editor uses two horizontal tabs: **AI assistant** contains setup, knowledge/context and provider limits; **Conversations & home** contains chat input/output pins, proactive home and bounded memory.
4
+ The editor uses two horizontal tabs: **AI assistant** contains setup, knowledge/context and provider limits; **Cerebrum (BETA)** contains chat input/output pins, proactive home and bounded memory.
5
5
 
6
6
  ## Outputs
7
7
  1. **Summary/Stats** (`msg.payload` JSON)
@@ -12,6 +12,8 @@ The editor uses two horizontal tabs: **AI assistant** contains setup, knowledge/
12
12
 
13
13
  Every message emitted by outputs 3 and 4 also contains a clone of the original input message in `msg.inputMessage`. This preserves the original payload, topic, chat metadata, and any other input properties for downstream nodes. Cloning and output errors are contained and reported instead of escaping into the Node-RED runtime.
14
14
 
15
+ At node startup, output 3 emits a short Cerebrum supervision notice with `msg.boot = true`. When AI is enabled, the notice is generated by the selected model as a live inference test; `msg.knxAi.llmTest` is `passed`, while provider and model identify what answered. If AI is disabled or the call fails, the startup message is still emitted with an honest localized fallback and `llmTest` set to `disabled` or `failed`. This check never reads or writes KNX.
16
+
15
17
  ### Setup Doctor and safe first run
16
18
  The automatic **Setup Doctor** checks the selected gateway and ETS import, AI enablement, provider, model and API key, provider reachability, flow wiring, detected cameras and the optional TTS Ultimate connection. Its no-cost provider preflight calls only the provider's model-list endpoint: it never sends a chat request or consumes inference tokens. Cameras and TTS are optional, so leaving them unused does not reduce core readiness.
17
19
 
@@ -103,12 +105,14 @@ The node editor shows a compact card summarizing the sources available to the ch
103
105
  The model receives local ETS catalog retrieval, KNX read/write operations, camera adapters, TTS announcements, persistent memory, Web access and plans/reminders as structured tools. It can select and combine them semantically from the current request and trusted learned guidance, without linguistic intent routing. Catalog retrieval is deterministic and local; the runtime validates tool arguments, camera-adapter availability and safety boundaries, while KNX writes still use complete local ETS/DPT validation and the configured confirmation step.
104
106
 
105
107
  ### Editing and backing up CHAT learning
106
- The **Conversations & home** tab in the Node-RED KNX AI configuration includes an **Open AI Chat Learning** button that opens the Vue Web UI directly on this editor for the current node.
108
+ The **Cerebrum (BETA)** tab in the Node-RED KNX AI configuration includes an **Open AI Chat Learning** button that opens the Vue Web UI directly on this editor for the current node.
107
109
 
108
- In the Vue web UI, open **Settings → AI Chat Learning** to view and edit the exact shared `knxai-chat-context.knxctx` file and its absolute path. The file can be copied, downloaded as a backup or restored from another `.knxctx` file. **Reinitialize Memory**, protected by an explicit confirmation, replaces it with a new empty context and clears saved sessions, instructions, camera watches and pending chat confirmations across every KNX AI node using the same storage. The native tab-separated `KNXAI_CHAT_CONTEXT 3` records are authoritative and directly editable: `SESSION` contains `INSTRUCTION`, `TURN` and `CAMERA_WATCH` records until `END_SESSION`. Saving validates and bounds these records, atomically rewrites the file and updates the live context of every KNX AI node using the same storage. A revision check refuses to overwrite or reset learning that changed after the editor loaded it.
110
+ In the Vue web UI, open **Cerebrum → AI Chat Learning** to inspect the exact shared `knxai-chat-context.knxctx`. **Native file** exposes the authoritative editable records; **Simplified text** explains the same conversations, learned instructions and camera watches in a localized read-only view. Copy follows the selected view, while download and restore always use the complete native backup. **Reinitialize Memory**, protected by an explicit confirmation, replaces it with a new empty context and clears saved sessions, instructions, camera watches and pending chat confirmations across every KNX AI node using the same storage. Saving validates and bounds the native V3 records, atomically rewrites the file and updates every live KNX AI node sharing that storage. A revision check protects newer learning.
109
111
 
110
112
  Only the native V3 format is supported. Previous Markdown/JSON V2 and Base64 V1 files are deliberately not read, imported or migrated; the old `.md` file is left untouched and KNX AI starts a new `.knxctx` context. The 50-session and 512 KB limits still apply.
111
113
 
114
+ Open **Cerebrum → Cerebrum Memory** to inspect the shared `knxai-home-memory.md`. The **JSON** view contains the authoritative editable data, while **Simplified text** presents the same habits, occupant decisions, states, observations, notifications and known home objects as a localized read-only explanation. The browser remembers the selected view. Saving validates JSON; copy follows the current view, while download and restore remain complete backups. **Settings** contains only the strict KNX AI and Cerebrum backup import/export; it includes configuration, chat learning, home memory and schedule files.
115
+
112
116
  ### ETS object access
113
117
  ETS object access is the only operational authority. Every address selected in **ETS object access** is active and readable; a selected address is writable unless it is marked **Read only**. No inferred role classification is sent to the chat model or used to authorize a write.
114
118
 
@@ -117,6 +121,10 @@ From ETS hierarchy, names, roles and DPTs, the node builds a deterministic seman
117
121
 
118
122
  There is no separate switch or advanced proactive configuration. A candidate is evaluated only when the LLM is enabled and **AI Education** explicitly requests that notification. Education is the sole policy for conditions, open duration, quiet hours and repetition. The AI receives the current duration, local date/time and recent notification history; it decides whether to notify and when to reconsider the same open condition. Without an explicit Education rule, or when the LLM cannot evaluate it, no notification is sent.
119
123
 
124
+ Cerebrum also learns bounded weekday/weekend time patterns from KNX writes and HUE, Matter or Home Assistant state changes. A pattern becomes eligible only after at least eight consistent observations spread across six distinct dates and a minimum 14-day span, with confidence of at least 0.70. Repeated telegrams on the same day cannot accelerate confirmation. If **AI Education** explicitly asks Cerebrum to anticipate learned habits, it may send one suggestion up to 30 minutes before the usual time. The suggestion is never an execution: KNX and Home Assistant actions still require the normal authorization and confirmation path, and the global limit of three proactive messages per hour still applies.
125
+
126
+ Cerebrum passively observes bounded, sanitized outputs from useful Node-RED logic, HUE, Matter and Home Assistant event nodes through a runtime hook; no extra monitoring wires are required. Credentials, authorization headers, opaque media and binary payloads are discarded. For live Home Assistant state, add **Cerebrum Home Assistant** and wire `Cerebrum Home Assistant → API (ha-api) → Cerebrum Home Assistant`. Setup Doctor detects the Home Assistant add-on and reports the next missing step.
127
+
120
128
  The most recent chat session is remembered as the owner and receives spontaneous messages. Output 3 emits a localized message with `msg.knxAi.type = "proactive_notification"`; a synthetic `msg.inputMessage` preserves the session for the chat adapter. A hard safety limit of three proactive messages per hour prevents flooding. The node never emits output 4 or changes KNX autonomously; a subsequent user request still uses the normal validation and confirmation workflow.
121
129
 
122
130
  The shared learned reference is loaded at startup from `<userDir>/knxai/memory/knxai-home-memory.md`, rewritten atomically every 15 minutes and always hard-capped at 5 MB. It stores at most 120 significant observations, 80 aggregate habits, 80 notifications and 300 semantic ETS objects—never a raw unlimited telegram stream. Older low-priority entries are removed first.
@@ -3,12 +3,13 @@
3
3
  "title": "KNX AI (Traffic Analyzer)",
4
4
  "sections": {
5
5
  "groupAssistant": "AI assistant",
6
- "groupChatHome": "Conversations & home",
6
+ "groupChatHome": "Cerebrum (BETA)",
7
7
  "setupDoctor": "Setup Doctor",
8
8
  "webIntelligence": "Web intelligence",
9
9
  "detectedAdapters": "Compatible nodes detected and used in chat",
10
10
  "chatContextOverview": "Chat context overview",
11
11
  "chatLearning": "AI Chat Learning",
12
+ "cerebrumMemory": "Cerebrum memory",
12
13
  "quickSetup": "Assistant setup",
13
14
  "etsAccess": "ETS object access",
14
15
  "llmConnection": "AI Assistant Connection",
@@ -83,6 +84,7 @@
83
84
  "ollamaLibrary": "Model library",
84
85
  "downloadOllamaModel": "1) Download model",
85
86
  "openChatLearning": "Open AI Chat Learning",
87
+ "openCerebrumMemory": "Open Cerebrum memory",
86
88
  "etsSelectAll": "Select all",
87
89
  "etsSelectNone": "Select none",
88
90
  "etsReadOnlyAll": "Set read only",
@@ -136,6 +138,7 @@
136
138
  "chatContextLoading": "Loading chat context summary…",
137
139
  "chatContextUnavailable": "The chat context summary is temporarily unavailable.",
138
140
  "chatLearningOpenHint": "Open the Web UI directly on the shared CHAT learning editor to view, edit, copy or back up its persistent file.",
141
+ "cerebrumMemoryOpenHint": "Open the user-readable Cerebrum memory to inspect or edit learned habits, occupant decisions and cached home states.",
139
142
  "chatContextIntro": "The chat automatically receives these sources. The paths below are the actual paths used by this Node-RED installation.",
140
143
  "chatContextLimitLabel": "Maximum operational context",
141
144
  "chatContextProviderManaged": "managed by the selected provider/model",
@@ -158,7 +161,7 @@
158
161
  "chatContextFileHomeMemory": "Bounded learned home memory only; AI Education remains in the node property.",
159
162
  "chatContextFileSchedules": "Authoritative persistent runtime state for this node's plans and reminders.",
160
163
  "chatContextFileSchedulesReadable": "Generated human-readable view of this node's plans and reminders.",
161
- "chatContextFileAssistantConfig": "Persistent web Assistant configuration and semantic areas for this node.",
164
+ "chatContextFileAssistantConfig": "Persistent Cerebrum configuration and semantic areas for this node.",
162
165
  "chatContextFileLastChatPrompt": "Temporary local copy of the latest system and user messages sent to the chat model; overwritten on every chat request.",
163
166
  "chatContextFileBadge": "File",
164
167
  "chatContextDirectoryRoot": "Telegram archive root",
@@ -3,12 +3,13 @@
3
3
  "title": "KNX AI (Traffic Analyzer)",
4
4
  "sections": {
5
5
  "groupAssistant": "Asistente IA",
6
- "groupChatHome": "Conversaciones y hogar",
6
+ "groupChatHome": "Cerebrum (BETA)",
7
7
  "setupDoctor": "Setup Doctor",
8
8
  "webIntelligence": "Inteligencia Web",
9
9
  "detectedAdapters": "Nodos compatibles detectados y utilizados en el chat",
10
10
  "chatContextOverview": "Resumen del contexto del chat",
11
11
  "chatLearning": "Aprendizaje del chat IA",
12
+ "cerebrumMemory": "Memoria Cerebrum",
12
13
  "quickSetup": "Configurar el asistente",
13
14
  "etsAccess": "Acceso a objetos ETS",
14
15
  "llmConnection": "Conexion del Asistente IA",
@@ -122,6 +123,7 @@
122
123
  "chatContextLoading": "Cargando el resumen del contexto del chat…",
123
124
  "chatContextUnavailable": "El resumen del contexto del chat no está disponible temporalmente.",
124
125
  "chatLearningOpenHint": "Abre la interfaz web directamente en el editor de aprendizaje CHAT compartido para ver, editar, copiar o guardar una copia de su archivo persistente.",
126
+ "cerebrumMemoryOpenHint": "Abre la memoria Cerebrum legible para consultar o editar hábitos, decisiones de los ocupantes y estados de la casa.",
125
127
  "chatContextIntro": "El chat recibe automáticamente estas fuentes. Las rutas siguientes son las que usa realmente esta instalación de Node-RED.",
126
128
  "chatContextLimitLabel": "Contexto operativo máximo",
127
129
  "chatContextProviderManaged": "gestionado por el proveedor/modelo seleccionado",
@@ -144,7 +146,7 @@
144
146
  "chatContextFileHomeMemory": "Solo memoria doméstica aprendida y limitada; la Educación IA permanece en la propiedad del nodo.",
145
147
  "chatContextFileSchedules": "Estado de ejecución persistente y autoritativo de las planificaciones y recordatorios de este nodo.",
146
148
  "chatContextFileSchedulesReadable": "Vista legible generada de las planificaciones y recordatorios de este nodo.",
147
- "chatContextFileAssistantConfig": "Configuración persistente del Asistente web y áreas semánticas de este nodo.",
149
+ "chatContextFileAssistantConfig": "Configuración persistente de Cerebrum y áreas semánticas de este nodo.",
148
150
  "chatContextFileLastChatPrompt": "Copia local temporal de los últimos mensajes system y user enviados al modelo de chat; se sobrescribe en cada solicitud.",
149
151
  "chatContextFileBadge": "Archivo",
150
152
  "chatContextDirectoryRoot": "Raíz del archivo de telegramas",
@@ -219,6 +221,7 @@
219
221
  "ollamaLibrary": "Model library",
220
222
  "downloadOllamaModel": "1) Download model",
221
223
  "openChatLearning": "Abrir aprendizaje del chat IA",
224
+ "openCerebrumMemory": "Abrir memoria Cerebrum",
222
225
  "etsSelectAll": "Seleccionar todo",
223
226
  "etsSelectNone": "Deseleccionar todo",
224
227
  "etsReadOnlyAll": "Marcar solo lectura",
@@ -3,12 +3,13 @@
3
3
  "title": "KNX AI (Traffic Analyzer)",
4
4
  "sections": {
5
5
  "groupAssistant": "Assistant IA",
6
- "groupChatHome": "Conversations et maison",
6
+ "groupChatHome": "Cerebrum (BETA)",
7
7
  "setupDoctor": "Setup Doctor",
8
8
  "webIntelligence": "Intelligence Web",
9
9
  "detectedAdapters": "Nœuds compatibles détectés et utilisés dans le chat",
10
10
  "chatContextOverview": "Aperçu du contexte du chat",
11
11
  "chatLearning": "Apprentissage du chat IA",
12
+ "cerebrumMemory": "Mémoire Cerebrum",
12
13
  "quickSetup": "Configurer l'assistant",
13
14
  "etsAccess": "Accès aux objets ETS",
14
15
  "llmConnection": "Connexion Assistant IA",
@@ -122,6 +123,7 @@
122
123
  "chatContextLoading": "Chargement du résumé du contexte du chat…",
123
124
  "chatContextUnavailable": "Le résumé du contexte du chat est temporairement indisponible.",
124
125
  "chatLearningOpenHint": "Ouvre l’interface web directement dans l’éditeur d’apprentissage CHAT partagé pour afficher, modifier, copier ou sauvegarder son fichier persistant.",
126
+ "cerebrumMemoryOpenHint": "Ouvre la mémoire Cerebrum lisible pour consulter ou modifier les habitudes, les décisions des occupants et les états du logement.",
125
127
  "chatContextIntro": "Le chat reçoit automatiquement ces sources. Les chemins ci-dessous sont ceux réellement utilisés par cette installation Node-RED.",
126
128
  "chatContextLimitLabel": "Contexte opérationnel maximal",
127
129
  "chatContextProviderManaged": "géré par le fournisseur/modèle sélectionné",
@@ -144,7 +146,7 @@
144
146
  "chatContextFileHomeMemory": "Mémoire domestique apprise et limitée uniquement ; l’Éducation IA reste dans la propriété du nœud.",
145
147
  "chatContextFileSchedules": "État d’exécution persistant faisant autorité pour les planifications et rappels de ce nœud.",
146
148
  "chatContextFileSchedulesReadable": "Vue lisible générée des planifications et rappels de ce nœud.",
147
- "chatContextFileAssistantConfig": "Configuration persistante de l'Assistant web et zones sémantiques de ce nœud.",
149
+ "chatContextFileAssistantConfig": "Configuration persistante de Cerebrum et zones sémantiques de ce nœud.",
148
150
  "chatContextFileLastChatPrompt": "Copie locale temporaire des derniers messages système et utilisateur envoyés au modèle de chat ; remplacée à chaque requête.",
149
151
  "chatContextFileBadge": "Fichier",
150
152
  "chatContextDirectoryRoot": "Racine de l'archive des télégrammes",
@@ -219,6 +221,7 @@
219
221
  "ollamaLibrary": "Model library",
220
222
  "downloadOllamaModel": "1) Download model",
221
223
  "openChatLearning": "Ouvrir l’apprentissage du chat IA",
224
+ "openCerebrumMemory": "Ouvrir la mémoire Cerebrum",
222
225
  "etsSelectAll": "Tout sélectionner",
223
226
  "etsSelectNone": "Tout désélectionner",
224
227
  "etsReadOnlyAll": "Activer lecture seule",
@@ -1,7 +1,7 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateAI">
2
2
  Questo nodo ascolta **tutti i telegrammi KNX** dal gateway KNX Ultimate selezionato, costruisce statistiche di traffico, rileva anomalie e può interrogare opzionalmente un LLM.
3
3
 
4
- L'editor usa due schede orizzontali: **Assistente AI** contiene configurazione, conoscenza/contesto, provider e limiti; **Conversazioni e casa** contiene i PIN di input e output della chat, casa proattiva e memoria limitata.
4
+ L'editor usa due schede orizzontali: **Assistente AI** contiene configurazione, conoscenza/contesto, provider e limiti; **Cerebrum (BETA)** contiene i PIN di input e output della chat, casa proattiva e memoria limitata.
5
5
 
6
6
  ## Output
7
7
  1. **Summary/Statistiche** (`msg.payload` JSON)
@@ -12,6 +12,8 @@ L'editor usa due schede orizzontali: **Assistente AI** contiene configurazione,
12
12
 
13
13
  Ogni messaggio emesso dalle uscite 3 e 4 contiene anche una copia del messaggio originale in ingresso in `msg.inputMessage`. In questo modo payload, topic, metadati della chat e qualsiasi altra proprietà di ingresso restano disponibili per i nodi successivi. Gli errori di clonazione o di invio vengono intercettati e segnalati senza propagarsi al runtime di Node-RED.
14
14
 
15
+ All'avvio del nodo, l'uscita 3 emette un breve messaggio di supervisione Cerebrum con `msg.boot = true`. Quando l'AI è attiva, il testo viene generato dal modello selezionato come test reale di inferenza; `msg.knxAi.llmTest` vale `passed`, mentre provider e modello indicano chi ha risposto. Se l'AI è disattivata o la chiamata fallisce, il messaggio di avvio viene comunque emesso con un fallback localizzato e trasparente e `llmTest` vale `disabled` o `failed`. Questo controllo non legge e non scrive sul bus KNX.
16
+
15
17
  ### Setup Doctor e primo avvio sicuro
16
18
  Il **Setup Doctor** automatico controlla gateway selezionato e importazione ETS, attivazione AI, provider, modello e chiave API, raggiungibilità del provider, collegamenti del flow, telecamere rilevate e collegamento TTS Ultimate opzionale. Il preflight del provider, senza costi, chiama soltanto l'endpoint che elenca i modelli: non invia mai una richiesta chat e non consuma token di inferenza. Telecamere e TTS sono opzionali, quindi non usarli non riduce la prontezza di base.
17
19
 
@@ -103,12 +105,14 @@ L'editor del nodo mostra una scheda compatta con le fonti disponibili alla chat:
103
105
  Il modello riceve retrieval locale del catalogo ETS, letture e scritture KNX, adapter telecamera, annunci TTS, memoria persistente, accesso Web e pianificazioni/promemoria come strumenti strutturati. Può sceglierli e combinarli semanticamente partendo dalla richiesta corrente e dalle indicazioni autorevoli apprese, senza routing per intent linguistici. Il retrieval del catalogo è deterministico e locale; il runtime valida argomenti, disponibilità degli adapter telecamera e confini di sicurezza, mentre le scritture KNX conservano la validazione completa ETS/DPT locale e la conferma configurata.
104
106
 
105
107
  ### Modifica e backup dell'apprendimento CHAT
106
- La scheda **Conversazioni e casa** nella configurazione Node-RED di KNX AI include il pulsante **Apri Apprendimento AI Chat**, che apre la Web UI Vue direttamente su questo editor per il nodo corrente.
108
+ La scheda **Cerebrum (BETA)** nella configurazione Node-RED di KNX AI include il pulsante **Apri Apprendimento AI Chat**, che apre la Web UI Vue direttamente su questo editor per il nodo corrente.
107
109
 
108
- Nella UI web Vue, apri **Impostazioni → Apprendimento AI Chat** per visualizzare e modificare il file condiviso esatto `knxai-chat-context.knxctx` e il suo percorso assoluto. Il file può essere copiato, scaricato come backup o ripristinato da un altro file `.knxctx`. **Re-inizializza memoria**, protetto da una conferma esplicita, lo sostituisce con un nuovo contesto vuoto e cancella sessioni, istruzioni, sorveglianze telecamera e conferme chat pendenti in ogni nodo KNX AI che usa lo stesso archivio. I record nativi separati da tabulazioni `KNXAI_CHAT_CONTEXT 3` sono autorevoli e direttamente modificabili: `SESSION` contiene record `INSTRUCTION`, `TURN` e `CAMERA_WATCH` fino a `END_SESSION`. Il salvataggio valida e limita questi record, riscrive atomicamente il file e aggiorna il contesto attivo di ogni nodo KNX AI che usa lo stesso archivio. Un controllo di revisione impedisce di sovrascrivere o azzerare l'apprendimento cambiato dopo il caricamento nell'editor.
110
+ Nella UI web Vue, apri **Cerebrum → Apprendimento AI Chat** per esaminare il file condiviso esatto `knxai-chat-context.knxctx`. **File nativo** espone i record autorevoli modificabili; **Testo semplificato** spiega le stesse conversazioni, istruzioni apprese e sorveglianze telecamera in una vista localizzata di sola lettura. La copia segue la vista selezionata, mentre download e ripristino usano sempre il backup nativo completo. **Re-inizializza memoria**, protetto da una conferma esplicita, sostituisce il file con un nuovo contesto vuoto e cancella sessioni, istruzioni, sorveglianze telecamera e conferme chat pendenti in ogni nodo KNX AI che usa lo stesso archivio. Il salvataggio valida e limita i record nativi V3, riscrive atomicamente il file e aggiorna tutti i nodi KNX AI attivi che condividono l’archivio. Un controllo di revisione protegge l’apprendimento più recente.
109
111
 
110
112
  È supportato soltanto il formato nativo V3. I precedenti file Markdown/JSON V2 e Base64 V1 non vengono volutamente letti, importati né migrati; il vecchio file `.md` resta intatto e KNX AI avvia un nuovo contesto `.knxctx`. Restano validi i limiti di 50 sessioni e 512 KB.
111
113
 
114
+ Apri **Cerebrum → Memoria Cerebrum** per esaminare il file condiviso `knxai-home-memory.md`. La vista **JSON** contiene i dati autorevoli modificabili, mentre **Testo semplificato** presenta le stesse abitudini, decisioni degli occupanti, stati, osservazioni, notifiche e oggetti conosciuti come spiegazione localizzata di sola lettura. Il browser ricorda la vista scelta. Il salvataggio valida il JSON; la copia segue la vista corrente, mentre download e ripristino restano backup completi. **Impostazioni** contiene soltanto l’import/export del backup rigoroso KNX AI e Cerebrum, che comprende configurazione, apprendimento chat, memoria della casa e file delle pianificazioni.
115
+
112
116
  ### Accesso agli oggetti ETS
113
117
  L'accesso agli oggetti ETS è l'unica autorità operativa. Ogni indirizzo selezionato in **Accesso agli oggetti ETS** è attivo e leggibile; è anche scrivibile, a meno che sia marcato **Sola lettura**. Nessuna classificazione di ruolo dedotta viene inviata al modello della chat o usata per autorizzare una scrittura.
114
118
 
@@ -117,6 +121,10 @@ Da gerarchia ETS, nomi, ruoli e DPT, il nodo crea un modello semantico determini
117
121
 
118
122
  Non esistono un interruttore o impostazioni proattive avanzate separate. Una condizione viene valutata soltanto quando l'LLM è attivo e **Educazione AI** richiede esplicitamente quella notifica. L'Educazione è l'unica policy per condizioni, durata dell'apertura, ore silenziose e ripetizione. L'AI riceve durata attuale, data/ora locale e storico recente delle notifiche; decide se avvisare e quando rivalutare la stessa apertura. Senza una regola esplicita nell'Educazione, o se l'LLM non riesce a valutarla, non viene inviato alcun messaggio.
119
123
 
124
+ Cerebrum apprende anche pattern temporali limitati fra giorni feriali e fine settimana, ricavati dalle scritture KNX e dai cambi di stato HUE, Matter o Home Assistant. Un pattern diventa utilizzabile solo dopo almeno otto osservazioni coerenti, distribuite su sei date distinte e su un intervallo minimo di 14 giorni, con confidenza non inferiore a 0,70. Telegrammi ripetuti nella stessa giornata non possono anticipare la richiesta di conferma. Se **Educazione AI** chiede esplicitamente a Cerebrum di anticipare le abitudini apprese, può inviare un suggerimento fino a 30 minuti prima dell'orario abituale. Il suggerimento non è mai un'esecuzione: le azioni KNX e Home Assistant richiedono ancora il normale percorso di autorizzazione e conferma, e resta valido il limite globale di tre messaggi proattivi all'ora.
125
+
126
+ Cerebrum osserva passivamente, tramite un hook runtime, gli output limitati e sanificati dei nodi utili di logica Node-RED, HUE, Matter e degli eventi Home Assistant; non servono collegamenti di monitoraggio aggiuntivi. Credenziali, header di autorizzazione, contenuti multimediali opachi e payload binari vengono scartati. Per lo stato Home Assistant live, aggiungi **Cerebrum Home Assistant** e collega `Cerebrum Home Assistant → API (ha-api) → Cerebrum Home Assistant`. Setup Doctor rileva l'add-on Home Assistant e indica il prossimo passaggio mancante.
127
+
120
128
  L'ultima sessione chat viene ricordata come proprietario e riceve i messaggi spontanei. L'uscita 3 emette un messaggio localizzato con `msg.knxAi.type = "proactive_notification"`; un `msg.inputMessage` sintetico conserva la sessione per l'adattatore chat. Un limite rigido di tre notifiche proattive all'ora evita abusi. Il nodo non emette mai l'uscita 4 e non modifica autonomamente KNX; un'eventuale richiesta successiva passa sempre dalla normale validazione e conferma.
121
129
 
122
130
  Il riferimento appreso condiviso viene caricato all'avvio da `<userDir>/knxai/memory/knxai-home-memory.md`, riscritto atomicamente ogni 15 minuti e sempre limitato rigidamente a 5 MB. Conserva al massimo 120 osservazioni significative, 80 abitudini aggregate, 80 notifiche e 300 oggetti ETS semantici, mai un flusso illimitato di telegrammi raw. Gli elementi vecchi e meno importanti vengono eliminati per primi.
@@ -3,12 +3,13 @@
3
3
  "title": "KNX AI (Analisi Traffico)",
4
4
  "sections": {
5
5
  "groupAssistant": "Assistente AI",
6
- "groupChatHome": "Conversazioni e casa",
6
+ "groupChatHome": "Cerebrum (BETA)",
7
7
  "setupDoctor": "Setup Doctor",
8
8
  "webIntelligence": "Intelligenza Web",
9
9
  "detectedAdapters": "Nodi compatibili rilevati ed usati in chat",
10
10
  "chatContextOverview": "Contesto disponibile alla chat",
11
11
  "chatLearning": "Apprendimento AI Chat",
12
+ "cerebrumMemory": "Memoria Cerebrum",
12
13
  "quickSetup": "Configurazione assistente",
13
14
  "etsAccess": "Accesso agli oggetti ETS",
14
15
  "llmConnection": "Connessione Assistente AI",
@@ -83,6 +84,7 @@
83
84
  "ollamaLibrary": "Libreria modelli",
84
85
  "downloadOllamaModel": "1) Scarica il modello",
85
86
  "openChatLearning": "Apri Apprendimento AI Chat",
87
+ "openCerebrumMemory": "Apri memoria Cerebrum",
86
88
  "etsSelectAll": "Seleziona tutti",
87
89
  "etsSelectNone": "Deseleziona tutti",
88
90
  "etsReadOnlyAll": "Imposta sola lettura",
@@ -136,6 +138,7 @@
136
138
  "chatContextLoading": "Caricamento del riepilogo del contesto…",
137
139
  "chatContextUnavailable": "Il riepilogo del contesto della chat non è momentaneamente disponibile.",
138
140
  "chatLearningOpenHint": "Apri la Web UI direttamente nell’editor dell’apprendimento CHAT condiviso per visualizzare, modificare, copiare o salvare il suo file persistente.",
141
+ "cerebrumMemoryOpenHint": "Apri la memoria Cerebrum leggibile dall’utente per esaminare o modificare abitudini, decisioni degli occupanti e stati della casa.",
139
142
  "chatContextIntro": "La chat riceve automaticamente queste fonti. I percorsi sottostanti sono quelli effettivi usati da questa installazione Node-RED.",
140
143
  "chatContextLimitLabel": "Contesto operativo massimo",
141
144
  "chatContextProviderManaged": "gestito dal provider/modello selezionato",
@@ -158,7 +161,7 @@
158
161
  "chatContextFileHomeMemory": "Solo memoria domestica appresa e limitata; l'Educazione AI resta nella proprietà del nodo.",
159
162
  "chatContextFileSchedules": "Stato di runtime persistente e autorevole delle pianificazioni e dei promemoria di questo nodo.",
160
163
  "chatContextFileSchedulesReadable": "Vista leggibile generata delle pianificazioni e dei promemoria di questo nodo.",
161
- "chatContextFileAssistantConfig": "Configurazione persistente dell'Assistente web e aree semantiche di questo nodo.",
164
+ "chatContextFileAssistantConfig": "Configurazione persistente di Cerebrum e aree semantiche di questo nodo.",
162
165
  "chatContextFileLastChatPrompt": "Copia locale temporanea degli ultimi messaggi system e user inviati al modello chat; viene sovrascritta a ogni richiesta.",
163
166
  "chatContextFileBadge": "File",
164
167
  "chatContextDirectoryRoot": "Radice archivio telegrammi",
@@ -3,12 +3,13 @@
3
3
  "title": "KNX AI (Traffic Analyzer)",
4
4
  "sections": {
5
5
  "groupAssistant": "AI 助手",
6
- "groupChatHome": "对话与家庭",
6
+ "groupChatHome": "Cerebrum (BETA)",
7
7
  "setupDoctor": "Setup Doctor",
8
8
  "webIntelligence": "Web 智能",
9
9
  "detectedAdapters": "已检测并用于聊天的兼容节点",
10
10
  "chatContextOverview": "聊天上下文概览",
11
11
  "chatLearning": "AI 聊天学习",
12
+ "cerebrumMemory": "Cerebrum 记忆",
12
13
  "quickSetup": "助手配置",
13
14
  "etsAccess": "ETS 对象访问",
14
15
  "llmConnection": "AI 助手连接",
@@ -122,6 +123,7 @@
122
123
  "chatContextLoading": "正在加载聊天上下文摘要…",
123
124
  "chatContextUnavailable": "聊天上下文摘要暂时不可用。",
124
125
  "chatLearningOpenHint": "直接在 Web UI 中打开共享 CHAT 学习编辑器,以查看、编辑、复制或备份其持久文件。",
126
+ "cerebrumMemoryOpenHint": "打开用户可读的 Cerebrum 记忆,以查看或编辑学习到的习惯、住户决定和家庭状态。",
125
127
  "chatContextIntro": "聊天会自动接收这些来源。以下路径是此 Node-RED 安装实际使用的路径。",
126
128
  "chatContextLimitLabel": "最大运行上下文",
127
129
  "chatContextProviderManaged": "由所选提供商/模型管理",
@@ -144,7 +146,7 @@
144
146
  "chatContextFileHomeMemory": "仅包含有界的家庭学习记忆;AI 教育保留在节点属性中。",
145
147
  "chatContextFileSchedules": "此节点计划和提醒的权威持久运行时状态。",
146
148
  "chatContextFileSchedulesReadable": "此节点计划和提醒自动生成的易读视图。",
147
- "chatContextFileAssistantConfig": "此节点的持久化 Web 助手配置和语义区域。",
149
+ "chatContextFileAssistantConfig": "此节点的持久化 Cerebrum 配置和语义区域。",
148
150
  "chatContextFileLastChatPrompt": "发送给聊天模型的最新 system 和 user 消息的临时本地副本;每次聊天请求都会覆盖。",
149
151
  "chatContextFileBadge": "文件",
150
152
  "chatContextDirectoryRoot": "报文归档根目录",
@@ -219,6 +221,7 @@
219
221
  "ollamaLibrary": "Model library",
220
222
  "downloadOllamaModel": "1) Download model",
221
223
  "openChatLearning": "打开 AI 聊天学习",
224
+ "openCerebrumMemory": "打开 Cerebrum 记忆",
222
225
  "etsSelectAll": "全选",
223
226
  "etsSelectNone": "全部取消",
224
227
  "etsReadOnlyAll": "设为只读",
@@ -0,0 +1,79 @@
1
+ const {
2
+ getKnxAiHomeAutomationRegistry,
3
+ normalizeKnxAiFlowSendEvent
4
+ } = require('../utils/knxAiCerebrum')
5
+
6
+ const HOOK_ID = 'onSend.knxUltimateCerebrum'
7
+ const PROVIDER_ID = 'knx-ultimate:cerebrum-runtime'
8
+ const ADAPTER_ID = 'node-red-flow'
9
+ const DUPLICATE_WINDOW_MS = 750
10
+ const MAX_EVENTS_PER_MINUTE = 240
11
+
12
+ module.exports = RED => {
13
+ const listeners = new Set()
14
+ const recentFingerprints = new Map()
15
+ let eventWindowStartedAt = Date.now()
16
+ let eventWindowCount = 0
17
+
18
+ const provider = {
19
+ id: PROVIDER_ID,
20
+ adapterId: ADAPTER_ID,
21
+ title: 'Node-RED flow observer',
22
+ subscribe (listener) {
23
+ if (typeof listener !== 'function') return () => {}
24
+ listeners.add(listener)
25
+ return () => listeners.delete(listener)
26
+ }
27
+ }
28
+
29
+ const refreshEventWindow = now => {
30
+ if ((now - eventWindowStartedAt) >= 60 * 1000) {
31
+ eventWindowStartedAt = now
32
+ eventWindowCount = 0
33
+ recentFingerprints.clear()
34
+ }
35
+ }
36
+
37
+ const allowEvent = (event, now) => {
38
+ refreshEventWindow(now)
39
+ if (eventWindowCount >= MAX_EVENTS_PER_MINUTE) return false
40
+ const fingerprint = `${event.resourceId}|${event.details && event.details.topic}|${event.state}`
41
+ const previous = Number(recentFingerprints.get(fingerprint) || 0)
42
+ if (previous > 0 && (now - previous) < DUPLICATE_WINDOW_MS) return false
43
+ recentFingerprints.set(fingerprint, now)
44
+ eventWindowCount += 1
45
+ return true
46
+ }
47
+
48
+ const observeSendEvents = sendEvents => {
49
+ const events = Array.isArray(sendEvents) ? sendEvents : [sendEvents]
50
+ events.forEach(sendEvent => {
51
+ try {
52
+ const now = Date.now()
53
+ refreshEventWindow(now)
54
+ if (eventWindowCount >= MAX_EVENTS_PER_MINUTE) return
55
+ const event = normalizeKnxAiFlowSendEvent(sendEvent, { at: new Date(now).toISOString() })
56
+ if (!event || !allowEvent(event, now)) return
57
+ listeners.forEach(listener => {
58
+ try { listener(event) } catch (error) { /* observer listeners cannot interrupt the flow */ }
59
+ })
60
+ } catch (error) { /* observing must never alter Node-RED message delivery */ }
61
+ })
62
+ }
63
+
64
+ RED.plugins.registerPlugin('knxUltimateCerebrumRuntime', {
65
+ type: 'runtime',
66
+ onadd () {
67
+ const registry = getKnxAiHomeAutomationRegistry()
68
+ registry.registerAdapter({
69
+ id: ADAPTER_ID,
70
+ title: 'Node-RED flow observer',
71
+ capabilities: ['flow-events', 'hue-events', 'matter-events', 'home-assistant-events'],
72
+ access: 'observe'
73
+ })
74
+ registry.registerProvider(provider)
75
+ if (RED.hooks && typeof RED.hooks.remove === 'function') RED.hooks.remove(HOOK_ID)
76
+ if (RED.hooks && typeof RED.hooks.add === 'function') RED.hooks.add(HOOK_ID, observeSendEvents)
77
+ }
78
+ })
79
+ }