node-red-contrib-knx-ultimate 6.3.18 → 6.3.21

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.
@@ -33,6 +33,9 @@ Bei DPT-1.xxx-Schreibvorgängen werden die sicheren KI-Entsprechungen `true`/`fa
33
33
  ### Aktuelle KNX-Lesewerte
34
34
  Wenn der Benutzer ausdrücklich einen aktuellen oder aktualisierten Zustand anfordert, kann die KI exakte Objekte aus dem importierten ETS-Katalog abfragen, einschließlich Status- und anderer schreibgeschützter Objekte. Ausgang 4 gibt `msg.destination`, `msg.dpt`, `msg.event = "GroupValue_Read"` und `msg.readstatus = true` aus. Der Node wartet bis zu 6 Sekunden auf jede `GroupValue_Response` oder ein aktuelles Write-Telegramm, gibt anschließend die dekodierten Werte an Ausgang 3 zurück und stellt Details in `msg.knxAi.readResults` bereit. Leseoperationen erfordern keine Bestätigung und werden niemals in Schreiboperationen umgewandelt.
35
35
 
36
+ ### Mehrstufige Gesprächsroutinen
37
+ Anfragen wie „Ich gehe“, „Gute Nacht“ oder „Kinomodus“ können ohne neue Editor-Option eine zustandsabhängige Routine koordinieren. Im ersten LLM-Durchlauf werden ausschließlich exakte ETS-Leseoperationen akzeptiert (maximal 20); KNX AI sendet sie und übergibt die aktuellen GA-/DPT-/Wert-Ergebnisse an einen zweiten isolierten Planungsdurchlauf. Dieser darf bis zu 12 validierte Schreiboperationen vorbereiten, aber keinen weiteren Lesezyklus starten. Bei aktivierter Bestätigung benötigt der gesamte Plan eine einzige lokalisierte Bestätigung; vorher werden weder Schreiboperationen noch angeforderte TTS-Ansagen ausgegeben. Nach der Bestätigung wird jede Schreiboperation erneut validiert, in Reihenfolge weitergegeben und bis zu 4 Sekunden auf eine passende unmittelbare Bus-Rückmeldung beobachtet. Die Abschlussmeldung unterscheidet beobachtete Rückmeldungen von Vorgängen ohne unmittelbare Rückmeldung, ohne daraus einen Gerätefehler abzuleiten. Details stehen in `msg.knxAi.routine`, `readResults`, `verifiedCount` und `unverifiedCount`.
38
+
36
39
  ### Bestätigungsanfrage für Chat-Schaltflächen
37
40
  Solange ein Plan aussteht, enthält Ausgang 3 `msg.knxAi.confirmationRequest`. Das Objekt enthält `required`, `status`, `sessionId`, `expiresAt`, `commandCount` und zwei Einträge in `actions`. Verwenden Sie `action.label` als Text der Telegram-Schaltfläche, `action.callbackData` als Callback und senden Sie `action.message` an KNX AI zurück, um ohne Texteingabe zu bestätigen oder abzubrechen.
38
41
 
@@ -48,6 +51,8 @@ Installierte Kamerapakete können KNX AI zur Laufzeit einen Kamera-Adapter berei
48
51
 
49
52
  Der Benutzer kann einen aktuellen Snapshot anfordern oder das Vision-Modell nach dem sichtbaren Inhalt fragen. Die Telegram- und RedBot-Vorlagen senden das Bild als natives Foto mit Bildunterschrift. Außerdem lassen sich dauerhafte Benachrichtigungen für Bewegung, das Überqueren einer intelligenten Linie oder das Betreten einer Einbruchs-/Verweilzone erstellen, optional auf erkannte Personen und eine genau benannte Linie oder Zone begrenzt. Diese Regeln werden in derselben Datei `knxai-chat-context.md` gespeichert und nach einem Neustart von Node-RED wiederhergestellt. UniFi-Ereignisse und Snapshot-Anfragen laufen direkt über den erkannten Anbieter; Ausgang 4 von KNX AI und zusätzliche Flow-Verkabelung sind nicht erforderlich.
50
53
 
54
+ Jedes von einem automatisch erkannten Adapter veröffentlichte Ereignis wird normalisiert und in eine tägliche Datei `YYYY-MM-DD.jsonl` unter `knxultimatestorage/knxai/adapter-history/<node-id>/` geschrieben. Das Archiv bewahrt 10 Tage auf, garantiert mehr als 24 Stunden Historie und speichert Ereignismetadaten, jedoch keine Snapshot-Bilder. Web-Assistent und alle CHAT-Kanäle fragen es zusammen mit dem täglichen KNX-Telegrammarchiv ab. Summen umfassen alle gespeicherten Zeilen; ausgewählte Details sind nur eine relevante Stichprobe.
55
+
51
56
  ### Ansagen mit TTS Ultimate
52
57
  Wenn das optionale Paket `node-red-contrib-tts-ultimate` installiert ist, erscheint es unter den automatisch erkannten Adaptern. Die Auswahl listet alle `ttsultimate`-Nodes in sämtlichen Projekt-Flows mit Flow, Node-Name und konfiguriertem Player auf. Wählen Sie den Node für Chat-Ansagen aus und deployen Sie den Flow.
53
58
 
@@ -90,6 +90,7 @@
90
90
  "chatContextFilesTitle": "Dauerhafte Kontextdateien",
91
91
  "chatContextDirectoriesTitle": "KNX-Telegrammarchiv",
92
92
  "chatContextSourceKnxTraffic": "Aktuelle KNX-Zusammenfassung, Anomalien, Topologie und ausgewählte Telegramme.",
93
+ "chatContextSourceAdapterHistory": "Dauerhaftes Tagesarchiv automatisch erkannter Adapterereignisse einschließlich Kameraerkennungen.",
93
94
  "chatContextSourceEtsProject": "ETS-Semantik und vollständiges Inventar des Node-RED-Projekts.",
94
95
  "chatContextSourceMemoryEducation": "Sitzungskontext, KI-Erziehung und begrenztes Hausgedächtnis.",
95
96
  "chatContextSourceCamerasDocs": "Erkannte Kameras und relevante Auszüge aus Hilfe, README und Beispielen.",
@@ -102,6 +103,9 @@
102
103
  "chatContextDirectoryRoot": "Stammverzeichnis des Telegrammarchivs",
103
104
  "chatContextDirectoryNode": "Telegrammarchiv dieses Nodes",
104
105
  "chatContextDirectoryBadge": "KNX",
106
+ "chatContextDirectoryAdapterRoot": "Stammverzeichnis des Adapterereignisarchivs",
107
+ "chatContextDirectoryAdapterNode": "Adapterereignisarchiv dieses Nodes",
108
+ "chatContextDirectoryAdapterBadge": "Adapter",
105
109
  "chatContextTelegramPattern": "Tagesdateien",
106
110
  "chatAdapterIntro": "Wählen Sie eine Vorlage, um den Code für Ein- und Ausgang einzufügen. Die Liste wird aus der mitgelieferten Chat-Adapter-Datei geladen; der erzeugte Code bleibt bearbeitbar.",
107
111
  "chatAdapterCodeHelp": "Zuordnungen laufen synchron. Geben Sie msg zurück, um fortzufahren, oder keinen Wert, um die Nachricht zu verwerfen. Fehler werden abgefangen und gemeldet, ohne Node-RED anzuhalten.",
@@ -33,6 +33,9 @@ For DPT 1.xxx writes, safe AI equivalents `true`/`false`, `1`/`0`, and `on`/`off
33
33
  ### Fresh KNX reads
34
34
  When the user explicitly asks for a fresh/current state, the AI may query exact objects from the imported ETS catalog, including status and other read-only objects. Output 4 emits `msg.destination`, `msg.dpt`, `msg.event = "GroupValue_Read"`, and `msg.readstatus = true`. The node waits up to 6 seconds for each `GroupValue_Response` or fresh write, then returns the decoded values on output 3 and exposes details in `msg.knxAi.readResults`. Reads never require confirmation and never become writes.
35
35
 
36
+ ### Conversational multi-step routines
37
+ Requests such as “I’m leaving”, “Good night”, or “Cinema mode” can coordinate a state-aware routine without a new editor option. In the first LLM pass, only exact ETS reads are accepted (up to 20); KNX AI sends them and supplies the fresh GA/DPT/value results to a second isolated planning pass. That pass may prepare up to 12 validated writes, but cannot request another read cycle. With confirmation enabled, the complete plan has one localized confirmation and no write or requested TTS announcement is emitted beforehand. After confirmation every write is revalidated, forwarded in order, and observed for up to 4 seconds for matching immediate bus feedback. The final reply distinguishes observed feedback from operations without immediate feedback—absence of feedback is not reported as device failure. Routine details are exposed in `msg.knxAi.routine`, `readResults`, `verifiedCount`, and `unverifiedCount`.
38
+
36
39
  ### Confirmation request for chat buttons
37
40
  While a plan is pending, output 3 contains `msg.knxAi.confirmationRequest`. The object includes `required`, `status`, `sessionId`, `expiresAt`, `commandCount`, and two entries in `actions`. Use `action.label` as the Telegram button text, `action.callbackData` as its callback, and send `action.message` back to KNX AI to confirm or cancel without typed text.
38
41
 
@@ -48,13 +51,15 @@ Installed camera packages can publish a camera adapter to KNX AI at runtime. The
48
51
 
49
52
  The user can ask for a current snapshot or ask the vision model what is visible. Telegram and RedBot presets emit the returned image as a native photo with a caption. The user can also create persistent notifications for motion, a smart line crossing or entry into an intrusion/loiter zone, optionally limited to detected people and to an exact named line or zone. These rules are stored in the same `knxai-chat-context.md` file and are restored after Node-RED restarts. UniFi event subscriptions and snapshot requests are made directly through the detected provider; KNX AI output 4 is not involved and no intermediate flow wiring is required.
50
53
 
54
+ Every event published by an automatically detected adapter is normalized and appended to a daily `YYYY-MM-DD.jsonl` file under `knxultimatestorage/knxai/adapter-history/<node-id>/`. The archive keeps 10 days, guarantees more than 24 hours of history and stores event metadata rather than snapshot images. The web Assistant and every CHAT channel query it together with the KNX daily telegram archive. Totals cover every stored row in the requested interval; selected details are only a relevant sample.
55
+
51
56
  ### TTS Ultimate announcements
52
57
  When the optional `node-red-contrib-tts-ultimate` package is installed, it appears among the automatically detected adapters. The selector lists every `ttsultimate` node in all project flows, with its flow, node name and configured player. Choose the node that must handle chat announcements and deploy the flow.
53
58
 
54
59
  Only an explicit request in the current chat message can create an announcement. KNX AI sends the exact text directly to the selected node as `msg.payload`, with `msg.topic = "knx_ai_announcement"`; no intermediate flow wiring is required. TTS Ultimate then handles the configured Sonos player, voice, volume, hailing and queue. Persistent context, AI Education, camera content and inferred events never trigger speech by themselves.
55
60
 
56
61
  ### Chat context overview
57
- The node editor shows a compact card summarizing the sources available to the chat: live KNX traffic, ETS semantics and the Node-RED project, session and home memory, AI Education, detected cameras and relevant documentation. It also lists `knxai-chat-context.md`, `knxai-home-memory.md` and `knxai-config-<node-id>.json`, together with the absolute KNX telegram archive root, the node-specific archive directory and the `YYYY-MM-DD.jsonl` daily-file pattern. The paths are resolved at runtime from the data directory actually used by the configured gateway.
62
+ The node editor shows a compact card summarizing the sources available to the chat: live and archived KNX traffic, persistent adapter events, ETS semantics and the Node-RED project, session and home memory, AI Education, detected cameras and relevant documentation. It also lists the absolute KNX telegram and adapter-event archive directories and the `YYYY-MM-DD.jsonl` daily-file pattern.
58
63
 
59
64
  ## Education-driven proactive home intelligence and bounded memory
60
65
  From ETS hierarchy, names, roles and DPTs, the node builds a deterministic semantic model for covers, windows, doors, lights, temperature, climate, occupancy and alarms using Italian, English, German, French, Spanish and Chinese terms. Its proactive detector watches only reliably recognized non-command cover/window/door states.
@@ -91,6 +91,7 @@
91
91
  "chatContextFilesTitle": "Persistent context files",
92
92
  "chatContextDirectoriesTitle": "KNX telegram archive",
93
93
  "chatContextSourceKnxTraffic": "Live KNX summary, anomalies, topology and selected telegrams.",
94
+ "chatContextSourceAdapterHistory": "Persistent daily history of automatically detected adapter events, including camera detections.",
94
95
  "chatContextSourceEtsProject": "ETS semantics and the full Node-RED project inventory.",
95
96
  "chatContextSourceMemoryEducation": "Session context, AI Education and bounded home memory.",
96
97
  "chatContextSourceCamerasDocs": "Detected cameras and relevant help, README and example snippets.",
@@ -103,6 +104,9 @@
103
104
  "chatContextDirectoryRoot": "Telegram archive root",
104
105
  "chatContextDirectoryNode": "This node's telegram archive",
105
106
  "chatContextDirectoryBadge": "KNX",
107
+ "chatContextDirectoryAdapterRoot": "Adapter event archive root",
108
+ "chatContextDirectoryAdapterNode": "This node's adapter event archive",
109
+ "chatContextDirectoryAdapterBadge": "Adapter",
106
110
  "chatContextTelegramPattern": "Daily files",
107
111
  "chatAdapterIntro": "Choose a mapping preset to insert its input and output code. The list is loaded from the packaged chat-adapter mappings file; the generated code remains editable.",
108
112
  "chatAdapterCodeHelp": "Mappings run synchronously. Return msg to continue or return no value to discard it. Errors are caught and reported without stopping Node-RED.",
@@ -33,6 +33,9 @@ Para las escrituras DPT 1.xxx, los equivalentes seguros producidos por la IA `tr
33
33
  ### Lecturas KNX actualizadas
34
34
  Cuando el usuario solicita explícitamente un estado actual o actualizado, la IA puede consultar objetos exactos del catálogo ETS importado, incluidos objetos de estado y otros objetos de solo lectura. La salida 4 emite `msg.destination`, `msg.dpt`, `msg.event = "GroupValue_Read"` y `msg.readstatus = true`. El nodo espera hasta 6 segundos cada `GroupValue_Response` o escritura reciente, devuelve los valores decodificados por la salida 3 y expone los detalles en `msg.knxAi.readResults`. Las lecturas nunca requieren confirmación ni se convierten en escrituras.
35
35
 
36
+ ### Rutinas conversacionales de varios pasos
37
+ Solicitudes como «Me voy», «Buenas noches» o «Modo cine» pueden coordinar una rutina basada en el estado actual sin añadir opciones al editor. En la primera pasada del LLM solo se aceptan lecturas ETS exactas (hasta 20); KNX AI las envía y proporciona los resultados actualizados de GA/DPT/valor a una segunda pasada de planificación aislada. Esta puede preparar hasta 12 escrituras validadas, pero no iniciar otro ciclo de lecturas. Con la confirmación activa, todo el plan requiere una sola confirmación localizada y antes no se emite ninguna escritura ni anuncio TTS solicitado. Tras confirmar, cada escritura se vuelve a validar, se envía en orden y se observa hasta 4 segundos para detectar una respuesta inmediata coincidente en el bus. La respuesta final distingue las respuestas observadas de las operaciones sin respuesta inmediata, sin declarar por ello un fallo del dispositivo. Los detalles están disponibles en `msg.knxAi.routine`, `readResults`, `verifiedCount` y `unverifiedCount`.
38
+
36
39
  ### Solicitud de confirmación para botones de chat
37
40
  Mientras un plan está pendiente, la salida 3 contiene `msg.knxAi.confirmationRequest`. El objeto incluye `required`, `status`, `sessionId`, `expiresAt`, `commandCount` y dos elementos en `actions`. Usa `action.label` como texto del botón de Telegram, `action.callbackData` como callback y devuelve `action.message` a KNX AI para confirmar o cancelar sin escribir texto.
38
41
 
@@ -48,6 +51,8 @@ Los paquetes de cámaras instalados pueden publicar en tiempo de ejecución un a
48
51
 
49
52
  El usuario puede pedir una captura actual o preguntar al modelo de visión qué se ve. Los preajustes de Telegram y RedBot envían la imagen como foto nativa con pie. También se pueden crear notificaciones persistentes por movimiento, cruce de una línea inteligente o entrada en una zona de intrusión/merodeo, limitadas opcionalmente a personas detectadas y a una línea o zona concreta por nombre. Estas reglas se guardan en el mismo archivo `knxai-chat-context.md` y se restauran después de reiniciar Node-RED. Las suscripciones a eventos UniFi y las solicitudes de captura se realizan directamente a través del proveedor detectado; no interviene la salida 4 de KNX AI ni hace falta cableado intermedio en el flujo.
50
53
 
54
+ Cada evento publicado por un adaptador detectado automáticamente se normaliza y se añade a un archivo diario `YYYY-MM-DD.jsonl` bajo `knxultimatestorage/knxai/adapter-history/<id-nodo>/`. El archivo conserva 10 días, garantiza más de 24 horas de historial y guarda metadatos, pero no imágenes. El Asistente web y todos los canales CHAT lo consultan junto con el archivo diario KNX. Los totales abarcan todas las filas almacenadas; los detalles seleccionados son solo una muestra relevante.
55
+
51
56
  ### Anuncios con TTS Ultimate
52
57
  Cuando está instalado el paquete opcional `node-red-contrib-tts-ultimate`, aparece entre los adaptadores detectados automáticamente. El selector muestra todos los nodos `ttsultimate` de todos los flows del proyecto, con el flow, el nombre del nodo y el reproductor configurado. Elige el nodo que gestionará los anuncios del chat y despliega el flow.
53
58
 
@@ -83,6 +83,7 @@
83
83
  "chatContextFilesTitle": "Archivos de contexto persistentes",
84
84
  "chatContextDirectoriesTitle": "Archivo de telegramas KNX",
85
85
  "chatContextSourceKnxTraffic": "Resumen KNX actual, anomalías, topología y telegramas seleccionados.",
86
+ "chatContextSourceAdapterHistory": "Historial diario persistente de eventos de los adaptadores detectados, incluidas las detecciones de cámaras.",
86
87
  "chatContextSourceEtsProject": "Semántica ETS e inventario completo del proyecto Node-RED.",
87
88
  "chatContextSourceMemoryEducation": "Contexto de sesión, Educación IA y memoria doméstica limitada.",
88
89
  "chatContextSourceCamerasDocs": "Cámaras detectadas y fragmentos relevantes de la ayuda, README y ejemplos.",
@@ -95,6 +96,9 @@
95
96
  "chatContextDirectoryRoot": "Raíz del archivo de telegramas",
96
97
  "chatContextDirectoryNode": "Archivo de telegramas de este nodo",
97
98
  "chatContextDirectoryBadge": "KNX",
99
+ "chatContextDirectoryAdapterRoot": "Raíz del archivo de eventos de adaptadores",
100
+ "chatContextDirectoryAdapterNode": "Archivo de eventos de adaptadores de este nodo",
101
+ "chatContextDirectoryAdapterBadge": "Adaptador",
98
102
  "chatContextTelegramPattern": "Archivos diarios",
99
103
  "chatAdapterIntro": "Elige un preajuste para insertar su código de mapeo de entrada y salida. La lista se carga desde el archivo de adaptadores de chat incluido; el código generado sigue siendo editable.",
100
104
  "chatAdapterCodeHelp": "Los mapeos se ejecutan de forma síncrona. Devuelve msg para continuar o ningún valor para descartarlo. Los errores se capturan y notifican sin detener Node-RED.",
@@ -33,6 +33,9 @@ Pour les écritures DPT 1.xxx, les équivalents sûrs produits par l’IA `true`
33
33
  ### Lectures KNX actualisées
34
34
  Lorsque l’utilisateur demande explicitement un état actuel ou actualisé, l’IA peut interroger les objets exacts du catalogue ETS importé, y compris les objets d’état et autres objets en lecture seule. La sortie 4 émet `msg.destination`, `msg.dpt`, `msg.event = "GroupValue_Read"` et `msg.readstatus = true`. Le nœud attend jusqu’à 6 secondes chaque `GroupValue_Response` ou écriture récente, puis renvoie les valeurs décodées sur la sortie 3 et les détails dans `msg.knxAi.readResults`. Les lectures ne nécessitent jamais de confirmation et ne sont jamais transformées en écritures.
35
35
 
36
+ ### Routines conversationnelles multi-étapes
37
+ Des demandes comme « Je pars », « Bonne nuit » ou « Mode cinéma » peuvent coordonner une routine tenant compte de l’état courant sans nouvelle option dans l’éditeur. Au premier passage LLM, seules les lectures ETS exactes sont acceptées (20 au maximum) ; KNX AI les envoie puis fournit les résultats actualisés GA/DPT/valeur à un second passage de planification isolé. Celui-ci peut préparer jusqu’à 12 écritures validées, sans lancer un nouveau cycle de lecture. Lorsque la confirmation est active, le plan complet ne demande qu’une confirmation localisée et aucune écriture ni annonce TTS demandée n’est émise auparavant. Après confirmation, chaque écriture est revalidée, transmise dans l’ordre et observée jusqu’à 4 secondes pour détecter un retour immédiat correspondant sur le bus. La réponse finale distingue les retours observés des opérations sans retour immédiat, sans déclarer pour autant une panne de l’appareil. Les détails figurent dans `msg.knxAi.routine`, `readResults`, `verifiedCount` et `unverifiedCount`.
38
+
36
39
  ### Demande de confirmation pour les boutons du chat
37
40
  Lorsqu'un plan est en attente, la sortie 3 contient `msg.knxAi.confirmationRequest`. L'objet comprend `required`, `status`, `sessionId`, `expiresAt`, `commandCount` et deux éléments dans `actions`. Utilisez `action.label` comme texte du bouton Telegram, `action.callbackData` comme callback et renvoyez `action.message` à KNX AI pour confirmer ou annuler sans saisir de texte.
38
41
 
@@ -48,6 +51,8 @@ Les paquets de caméra installés peuvent publier à l’exécution un adaptateu
48
51
 
49
52
  L’utilisateur peut demander une capture actuelle ou demander au modèle de vision ce qui est visible. Les préréglages Telegram et RedBot envoient l’image comme photo native avec une légende. L’utilisateur peut aussi créer des notifications persistantes pour un mouvement, le franchissement d’une ligne intelligente ou l’entrée dans une zone d’intrusion/de stationnement, avec une limitation facultative aux personnes détectées et à une ligne ou zone nommée précise. Ces règles sont stockées dans le même fichier `knxai-chat-context.md` et restaurées après les redémarrages de Node-RED. Les abonnements aux événements UniFi et les demandes de capture passent directement par le fournisseur détecté ; la sortie 4 de KNX AI et un câblage intermédiaire ne sont pas nécessaires.
50
53
 
54
+ Chaque événement publié par un adaptateur détecté automatiquement est normalisé puis ajouté à un fichier quotidien `YYYY-MM-DD.jsonl` sous `knxultimatestorage/knxai/adapter-history/<id-nœud>/`. L’archive conserve 10 jours, garantit plus de 24 heures d’historique et stocke les métadonnées, mais pas les images. L’Assistant web et tous les canaux CHAT l’interrogent avec l’archive quotidienne KNX. Les totaux couvrent toutes les lignes stockées ; les détails sélectionnés ne sont qu’un échantillon pertinent.
55
+
51
56
  ### Annonces avec TTS Ultimate
52
57
  Lorsque le paquet facultatif `node-red-contrib-tts-ultimate` est installé, il apparaît parmi les adaptateurs détectés automatiquement. Le sélecteur recense tous les nœuds `ttsultimate` de tous les flows du projet, avec le flow, le nom du nœud et le lecteur configuré. Sélectionnez le nœud chargé des annonces du chat, puis déployez le flow.
53
58
 
@@ -83,6 +83,7 @@
83
83
  "chatContextFilesTitle": "Fichiers de contexte persistants",
84
84
  "chatContextDirectoriesTitle": "Archive des télégrammes KNX",
85
85
  "chatContextSourceKnxTraffic": "Résumé KNX actuel, anomalies, topologie et télégrammes sélectionnés.",
86
+ "chatContextSourceAdapterHistory": "Historique quotidien persistant des événements des adaptateurs détectés, y compris les détections des caméras.",
86
87
  "chatContextSourceEtsProject": "Sémantique ETS et inventaire complet du projet Node-RED.",
87
88
  "chatContextSourceMemoryEducation": "Contexte de session, Éducation IA et mémoire domestique limitée.",
88
89
  "chatContextSourceCamerasDocs": "Caméras détectées et extraits pertinents de l'aide, du README et des exemples.",
@@ -95,6 +96,9 @@
95
96
  "chatContextDirectoryRoot": "Racine de l'archive des télégrammes",
96
97
  "chatContextDirectoryNode": "Archive des télégrammes de ce nœud",
97
98
  "chatContextDirectoryBadge": "KNX",
99
+ "chatContextDirectoryAdapterRoot": "Racine de l’archive des événements des adaptateurs",
100
+ "chatContextDirectoryAdapterNode": "Archive des événements des adaptateurs de ce nœud",
101
+ "chatContextDirectoryAdapterBadge": "Adaptateur",
98
102
  "chatContextTelegramPattern": "Fichiers quotidiens",
99
103
  "chatAdapterIntro": "Choisissez un préréglage pour insérer son code de mappage d’entrée et de sortie. La liste est chargée depuis le fichier d’adaptateurs de chat fourni ; le code généré reste modifiable.",
100
104
  "chatAdapterCodeHelp": "Les mappages s’exécutent de façon synchrone. Renvoyez msg pour continuer ou aucune valeur pour l’écarter. Les erreurs sont interceptées et signalées sans arrêter Node-RED.",
@@ -33,6 +33,9 @@ Per le scritture DPT 1.xxx, gli equivalenti sicuri prodotti dall'AI `true`/`fals
33
33
  ### Letture KNX aggiornate
34
34
  Quando l'utente chiede esplicitamente uno stato attuale o aggiornato, l'AI può interrogare gli oggetti esatti del catalogo ETS importato, compresi gli oggetti di stato e di sola lettura. L'uscita 4 emette `msg.destination`, `msg.dpt`, `msg.event = "GroupValue_Read"` e `msg.readstatus = true`. Il nodo attende fino a 6 secondi ogni `GroupValue_Response` o scrittura fresca, poi restituisce i valori decodificati sull'uscita 3 e i dettagli in `msg.knxAi.readResults`. Le letture non richiedono mai conferma e non vengono mai trasformate in scritture.
35
35
 
36
+ ### Routine conversazionali multi-step
37
+ Richieste come «Sto uscendo», «Buonanotte» o «Modalità cinema» possono coordinare una routine basata sullo stato corrente senza aggiungere opzioni all'editor. Nel primo passaggio LLM vengono accettate soltanto letture ETS esatte (massimo 20); KNX AI le invia e passa i risultati aggiornati GA/DPT/valore a un secondo passaggio di pianificazione isolato. Quest'ultimo può preparare fino a 12 scritture validate, ma non può avviare un altro ciclo di letture. Con la conferma attiva, l'intero piano richiede una sola conferma localizzata e nessuna scrittura o annuncio TTS richiesto viene emesso prima. Dopo la conferma ogni scrittura viene rivalidata, inoltrata in ordine e osservata fino a 4 secondi per un feedback immediato corrispondente sul bus. La risposta finale distingue il feedback osservato dalle operazioni senza feedback immediato, senza considerare queste ultime come dispositivi guasti. I dettagli sono disponibili in `msg.knxAi.routine`, `readResults`, `verifiedCount` e `unverifiedCount`.
38
+
36
39
  ### Richiesta di conferma per pulsanti chat
37
40
  Quando un piano è in attesa, l'uscita 3 contiene `msg.knxAi.confirmationRequest`. L'oggetto include `required`, `status`, `sessionId`, `expiresAt`, `commandCount` e due elementi in `actions`. Usa `action.label` per il testo del pulsante Telegram, `action.callbackData` per il callback e reinvia `action.message` al nodo KNX AI per confermare o annullare senza digitare testo.
38
41
 
@@ -48,13 +51,15 @@ I pacchetti di telecamere installati possono pubblicare a runtime un adapter per
48
51
 
49
52
  L'utente può chiedere uno snapshot aggiornato oppure domandare al modello vision che cosa è visibile. I preset Telegram e RedBot inviano l'immagine come foto nativa con didascalia. L'utente può anche creare notifiche persistenti per movimento, attraversamento di una linea intelligente o ingresso in una zona di intrusione/stazionamento, limitandole facoltativamente alle persone rilevate e a una linea o zona nominata esatta. Le regole vengono salvate nello stesso file `knxai-chat-context.md` e ripristinate dopo i riavvii di Node-RED. Le sottoscrizioni agli eventi UniFi e le richieste snapshot avvengono direttamente tramite il provider rilevato: l'uscita 4 di KNX AI non è coinvolta e non servono collegamenti intermedi nel flow.
50
53
 
54
+ Ogni evento pubblicato da un adapter rilevato automaticamente viene normalizzato e aggiunto a un file giornaliero `YYYY-MM-DD.jsonl` sotto `knxultimatestorage/knxai/adapter-history/<id-nodo>/`. L'archivio conserva 10 giorni, garantisce più di 24 ore di storico e salva i metadati degli eventi, non le immagini. Assistente web e canali CHAT lo interrogano insieme all'archivio giornaliero KNX. I totali comprendono tutte le righe memorizzate nell'intervallo richiesto; i dettagli selezionati sono soltanto un campione pertinente.
55
+
51
56
  ### Annunci con TTS Ultimate
52
57
  Quando è installato il pacchetto opzionale `node-red-contrib-tts-ultimate`, questo compare tra gli adapter rilevati automaticamente. Il selettore elenca tutti i nodi `ttsultimate` presenti in tutti i flow del progetto, indicando flow, nome del nodo e player configurato. Scegli il nodo che deve gestire gli annunci della chat e fai il deploy del flow.
53
58
 
54
59
  Solo una richiesta esplicita nel messaggio corrente della chat può creare un annuncio. KNX AI invia il testo esatto direttamente al nodo scelto come `msg.payload`, con `msg.topic = "knx_ai_announcement"`; non servono collegamenti intermedi nel flow. TTS Ultimate gestisce poi il player Sonos configurato, voce, volume, hailing e coda. Contesto persistente, Educazione AI, contenuto delle telecamere ed eventi dedotti non attivano mai autonomamente la voce.
55
60
 
56
61
  ### Riepilogo del contesto della chat
57
- L'editor del nodo mostra una scheda compatta con le fonti disponibili alla chat: traffico KNX corrente, semantica ETS e progetto Node-RED, memoria di sessione e domestica, Educazione AI, telecamere rilevate e documentazione pertinente. Elenca inoltre `knxai-chat-context.md`, `knxai-home-memory.md` e `knxai-config-<id-nodo>.json`, insieme alla radice assoluta dell'archivio telegrammi KNX, alla cartella specifica del nodo e al formato giornaliero `YYYY-MM-DD.jsonl`. I percorsi vengono risolti a runtime dalla directory dati realmente usata dal gateway configurato.
62
+ L'editor del nodo mostra una scheda compatta con le fonti disponibili alla chat: traffico KNX corrente e archiviato, eventi persistenti degli adapter, semantica ETS e progetto Node-RED, memoria di sessione e domestica, Educazione AI, telecamere rilevate e documentazione pertinente. Elenca anche le directory assolute degli archivi KNX e degli eventi adapter e il formato giornaliero `YYYY-MM-DD.jsonl`.
58
63
 
59
64
  ## Intelligenza domestica proattiva guidata dall'Educazione e memoria limitata
60
65
  Da gerarchia ETS, nomi, ruoli e DPT, il nodo crea un modello semantico deterministico per persiane, finestre, porte, luci, temperatura, clima, presenza e allarmi usando termini italiani, inglesi, tedeschi, francesi, spagnoli e cinesi. Il rilevatore proattivo osserva soltanto stati non di comando di persiane, finestre e porte riconosciuti con sufficiente affidabilità.
@@ -91,6 +91,7 @@
91
91
  "chatContextFilesTitle": "File persistenti di contesto",
92
92
  "chatContextDirectoriesTitle": "Archivio telegrammi KNX",
93
93
  "chatContextSourceKnxTraffic": "Riepilogo KNX corrente, anomalie, topologia e telegrammi selezionati.",
94
+ "chatContextSourceAdapterHistory": "Storico giornaliero persistente degli eventi degli adapter rilevati, comprese le rilevazioni delle telecamere.",
94
95
  "chatContextSourceEtsProject": "Semantica ETS e inventario completo del progetto Node-RED.",
95
96
  "chatContextSourceMemoryEducation": "Contesto di sessione, Educazione AI e memoria domestica limitata.",
96
97
  "chatContextSourceCamerasDocs": "Telecamere rilevate ed estratti pertinenti da help, README ed esempi.",
@@ -103,6 +104,9 @@
103
104
  "chatContextDirectoryRoot": "Radice archivio telegrammi",
104
105
  "chatContextDirectoryNode": "Archivio telegrammi di questo nodo",
105
106
  "chatContextDirectoryBadge": "KNX",
107
+ "chatContextDirectoryAdapterRoot": "Radice archivio eventi adapter",
108
+ "chatContextDirectoryAdapterNode": "Archivio eventi adapter di questo nodo",
109
+ "chatContextDirectoryAdapterBadge": "Adapter",
106
110
  "chatContextTelegramPattern": "File giornalieri",
107
111
  "chatAdapterIntro": "Scegli un preset per inserire il codice di mappatura in ingresso e in uscita. La lista viene caricata dal file degli adattatori chat incluso nel pacchetto; il codice generato resta modificabile.",
108
112
  "chatAdapterCodeHelp": "Le mappature sono sincrone. Restituisci msg per continuare oppure nessun valore per scartarlo. Gli errori vengono intercettati e segnalati senza arrestare Node-RED.",
@@ -33,6 +33,9 @@ Canvas 上的节点状态专门用于显示最近收到的请求,以及 LLM
33
33
  ### 最新 KNX 读取
34
34
  当用户明确要求当前或最新状态时,AI 可以查询已导入 ETS 目录中的精确对象,包括状态对象和其他只读对象。输出 4 会发送 `msg.destination`、`msg.dpt`、`msg.event = "GroupValue_Read"` 和 `msg.readstatus = true`。节点会为每个 `GroupValue_Response` 或最新写入等待最多 6 秒,然后在输出 3 返回解码值,并在 `msg.knxAi.readResults` 中提供详细信息。读取从不需要确认,也绝不会转换为写入。
35
35
 
36
+ ### 多步骤对话例行程序
37
+ “我要离家”“晚安”或“影院模式”等请求可以在不增加编辑器选项的情况下,根据当前状态协调例行程序。第一次 LLM 处理仅接受精确的 ETS 读取(最多 20 个);KNX AI 发送读取,并把最新的 GA/DPT/值结果交给隔离的第二次规划处理。第二次处理最多可准备 12 个已验证写入,但不能再次发起读取循环。启用确认时,整个计划只需一次本地化确认;确认前不会发送任何写入或所请求的 TTS 播报。确认后,每个写入都会重新验证、按顺序转发,并在最多 4 秒内观察总线上匹配的即时反馈。最终回复会区分已观察到反馈的操作与没有即时反馈的操作,但不会把后者报告为设备故障。详细信息位于 `msg.knxAi.routine`、`readResults`、`verifiedCount` 和 `unverifiedCount`。
38
+
36
39
  ### 用于聊天按钮的确认请求
37
40
  计划等待确认时,输出 3 包含 `msg.knxAi.confirmationRequest`。该对象包括 `required`、`status`、`sessionId`、`expiresAt`、`commandCount`,以及 `actions` 中的两个项目。使用 `action.label` 作为 Telegram 按钮文本,使用 `action.callbackData` 作为回调,并将 `action.message` 发送回 KNX AI,即可在无需输入文本的情况下确认或取消。
38
41
 
@@ -48,6 +51,8 @@ Canvas 上的节点状态专门用于显示最近收到的请求,以及 LLM
48
51
 
49
52
  用户可以请求当前快照,或询问视觉模型画面中可见的内容。Telegram 和 RedBot 预设会把图像作为带说明文字的原生照片发送。用户还可以为移动、智能越线或进入入侵/徘徊区域创建持久通知,并可按检测到的人员以及指定名称的线或区域进行限制。这些规则保存在同一个 `knxai-chat-context.md` 文件中,并在 Node-RED 重启后恢复。UniFi 事件订阅和快照请求直接通过检测到的提供方完成;不会使用 KNX AI 输出 4,也不需要中间 Flow 连线。
50
53
 
54
+ 自动检测到的适配器发布的每个事件都会被标准化,并追加到 `knxultimatestorage/knxai/adapter-history/<节点ID>/` 下的每日 `YYYY-MM-DD.jsonl` 文件。存档保留 10 天,保证超过 24 小时的历史,只保存事件元数据,不保存图像。Web 助手和所有 CHAT 渠道会同时查询它与每日 KNX 报文存档。总数涵盖所请求区间内的全部存档行;选出的详情仅是相关样本。
55
+
51
56
  ### 使用 TTS Ultimate 播报
52
57
  安装可选软件包 `node-red-contrib-tts-ultimate` 后,它会显示在自动检测的适配器中。选择器会列出项目所有 Flow 中的全部 `ttsultimate` 节点,并显示 Flow、节点名称和已配置的播放器。请选择负责聊天播报的节点,然后部署 Flow。
53
58
 
@@ -83,6 +83,7 @@
83
83
  "chatContextFilesTitle": "持久化上下文文件",
84
84
  "chatContextDirectoriesTitle": "KNX 报文归档",
85
85
  "chatContextSourceKnxTraffic": "当前 KNX 摘要、异常、拓扑和选定报文。",
86
+ "chatContextSourceAdapterHistory": "自动检测到的适配器事件的持久每日历史记录,包括摄像机检测事件。",
86
87
  "chatContextSourceEtsProject": "ETS 语义和完整的 Node-RED 项目清单。",
87
88
  "chatContextSourceMemoryEducation": "会话上下文、AI 教育和有界家庭记忆。",
88
89
  "chatContextSourceCamerasDocs": "检测到的摄像机以及相关帮助、README 和示例片段。",
@@ -95,6 +96,9 @@
95
96
  "chatContextDirectoryRoot": "报文归档根目录",
96
97
  "chatContextDirectoryNode": "此节点的报文归档",
97
98
  "chatContextDirectoryBadge": "KNX",
99
+ "chatContextDirectoryAdapterRoot": "适配器事件存档根目录",
100
+ "chatContextDirectoryAdapterNode": "此节点的适配器事件存档",
101
+ "chatContextDirectoryAdapterBadge": "适配器",
98
102
  "chatContextTelegramPattern": "每日文件",
99
103
  "chatAdapterIntro": "选择预设即可插入输入和输出映射代码。列表从随包提供的聊天适配器文件加载;生成的代码仍可编辑。",
100
104
  "chatAdapterCodeHelp": "映射同步运行。返回 msg 以继续,或不返回值以丢弃消息。错误会被捕获并报告,不会停止 Node-RED。",
@@ -0,0 +1,246 @@
1
+ const KNX_AI_ADAPTER_HISTORY_MIN_HOURS = 24
2
+ const KNX_AI_HISTORY_DETAILS_MAX_CHARS = 12000
3
+
4
+ const clampText = (value, maxChars = 500) => String(value === undefined || value === null ? '' : value)
5
+ .trim()
6
+ .slice(0, Math.max(0, Number(maxChars) || 0))
7
+
8
+ const normalizeSearchText = value => clampText(value, 4000)
9
+ .normalize('NFKD')
10
+ .replace(/[\u0300-\u036f]/g, '')
11
+ .toLocaleLowerCase()
12
+ .replace(/[^a-z0-9./_-]+/g, ' ')
13
+ .trim()
14
+
15
+ const parseTimestamp = (value, fallback = Date.now()) => {
16
+ if (typeof value === 'number' && Number.isFinite(value)) return value > 100000000000 ? value : value * 1000
17
+ const parsed = new Date(String(value || '')).getTime()
18
+ return Number.isFinite(parsed) ? parsed : fallback
19
+ }
20
+
21
+ const sanitizeHistoryValue = (value, depth = 0, seen = new Set()) => {
22
+ if (value === null || value === undefined) return value
23
+ if (typeof value === 'string') return clampText(value, 1000)
24
+ if (typeof value === 'number' || typeof value === 'boolean') return value
25
+ if (typeof value === 'bigint') return String(value)
26
+ if (Buffer.isBuffer(value)) return `[binary ${value.length} bytes]`
27
+ if (typeof value !== 'object') return clampText(value, 1000)
28
+ if (depth >= 3 || seen.has(value)) return '[nested]'
29
+ seen.add(value)
30
+ if (Array.isArray(value)) {
31
+ const out = value.slice(0, 30).map(item => sanitizeHistoryValue(item, depth + 1, seen))
32
+ seen.delete(value)
33
+ return out
34
+ }
35
+ const out = {}
36
+ Object.keys(value).slice(0, 60).forEach(key => {
37
+ const normalizedKey = clampText(key, 120)
38
+ if (!normalizedKey || /^(data|image|snapshot|buffer|base64)$/i.test(normalizedKey)) return
39
+ out[normalizedKey] = sanitizeHistoryValue(value[key], depth + 1, seen)
40
+ })
41
+ seen.delete(value)
42
+ return out
43
+ }
44
+
45
+ const normalizeKnxAiAdapterHistoryEvent = ({ event, adapter, provider, nowTs = Date.now() } = {}) => {
46
+ const source = event && typeof event === 'object' && !Array.isArray(event) ? event : {}
47
+ const adapterSource = adapter && typeof adapter === 'object' ? adapter : {}
48
+ const providerSource = provider && typeof provider === 'object' ? provider : {}
49
+ const ts = parseTimestamp(source.ts || source.timestamp || source.at || source.start || source.date, nowTs)
50
+ const eventType = clampText(source.eventType || source.type || source.event || source.kind, 120)
51
+ if (!eventType) return null
52
+ const cameraId = clampText(source.cameraId || source.resourceId || source.deviceId, 200)
53
+ const cameraName = clampText(source.cameraName || source.resourceName || source.deviceName, 300)
54
+ const details = sanitizeHistoryValue(source.raw || source.details || source.metadata || {})
55
+ let detailsText = ''
56
+ try { detailsText = JSON.stringify(details) } catch (error) { detailsText = '' }
57
+ return {
58
+ ts,
59
+ at: new Date(ts).toISOString(),
60
+ adapterId: clampText(source.adapterId || providerSource.adapterId || adapterSource.id, 160),
61
+ adapterTitle: clampText(source.adapterTitle || adapterSource.title || adapterSource.name, 240),
62
+ providerId: clampText(source.providerId || providerSource.id, 220),
63
+ providerTitle: clampText(source.providerTitle || providerSource.title || providerSource.name, 240),
64
+ controllerId: clampText(source.controllerId || providerSource.controllerId, 180),
65
+ controllerName: clampText(source.controllerName || providerSource.controllerName, 240),
66
+ resourceType: clampText(source.resourceType || (cameraId || cameraName ? 'camera' : 'adapter'), 80),
67
+ resourceId: cameraId,
68
+ resourceName: cameraName,
69
+ eventType,
70
+ eventId: clampText(source.eventId || source.id, 200),
71
+ active: source.active !== false,
72
+ scopeId: clampText(source.scopeId, 180),
73
+ scopeName: clampText(source.scopeName, 240),
74
+ objectTypes: Array.from(new Set((Array.isArray(source.objectTypes) ? source.objectTypes : [])
75
+ .map(value => clampText(value, 100))
76
+ .filter(Boolean))).slice(0, 24),
77
+ details: detailsText.length <= KNX_AI_HISTORY_DETAILS_MAX_CHARS ? details : { truncated: true }
78
+ }
79
+ }
80
+
81
+ const HISTORY_STOP_WORDS = new Set([
82
+ 'a', 'al', 'alla', 'alle', 'anche', 'and', 'auf', 'aux', 'avec', 'che', 'con', 'da', 'dal', 'dalla', 'das', 'de', 'dei', 'del', 'della', 'des', 'di', 'die', 'do', 'du', 'e', 'el', 'en', 'et', 'for', 'gli', 'ha', 'hanno', 'i', 'il', 'in', 'is', 'la', 'le', 'les', 'lo', 'mit', 'nel', 'nella', 'of', 'on', 'or', 'per', 'pour', 'que', 'qui', 'se', 'sono', 'su', 'the', 'to', 'tra', 'un', 'una', 'und', 'was', 'what', 'with', 'zu'
83
+ ])
84
+
85
+ const historyQuestionTokens = question => Array.from(new Set(normalizeSearchText(question)
86
+ .split(/\s+/)
87
+ .filter(token => token.length >= 2 && !HISTORY_STOP_WORDS.has(token) && !/^\d{1,4}$/.test(token))))
88
+ .slice(0, 24)
89
+
90
+ const incrementCount = (map, key) => {
91
+ const normalized = clampText(key, 500) || '(unknown)'
92
+ map.set(normalized, Number(map.get(normalized) || 0) + 1)
93
+ }
94
+
95
+ const topCounts = (map, limit) => Array.from(map.entries())
96
+ .map(([key, count]) => ({ key, count }))
97
+ .sort((left, right) => right.count - left.count || left.key.localeCompare(right.key))
98
+ .slice(0, Math.max(1, Number(limit) || 1))
99
+
100
+ const buildEventSearchText = (event, kind) => {
101
+ if (kind === 'knx') {
102
+ return normalizeSearchText([
103
+ event.event,
104
+ event.source,
105
+ event.destination,
106
+ event.devicename,
107
+ event.dpt,
108
+ event.payloadmeasureunit,
109
+ typeof event.payload === 'object' ? JSON.stringify(event.payload) : event.payload
110
+ ].join(' '))
111
+ }
112
+ return normalizeSearchText([
113
+ event.adapterId,
114
+ event.adapterTitle,
115
+ event.providerId,
116
+ event.providerTitle,
117
+ event.controllerId,
118
+ event.controllerName,
119
+ event.resourceType,
120
+ event.resourceId,
121
+ event.resourceName,
122
+ event.eventType,
123
+ event.scopeId,
124
+ event.scopeName,
125
+ ...(Array.isArray(event.objectTypes) ? event.objectTypes : []),
126
+ event.details && typeof event.details === 'object' ? JSON.stringify(event.details) : ''
127
+ ].join(' '))
128
+ }
129
+
130
+ const buildKnxAiHistoryEventKey = (event, kind = 'adapter') => {
131
+ if (!event || typeof event !== 'object') return ''
132
+ if (kind === 'knx') {
133
+ let payload = ''
134
+ try { payload = typeof event.payload === 'object' ? JSON.stringify(event.payload) : String(event.payload) } catch (error) { payload = '' }
135
+ return [Number(event.ts || 0), event.event, event.source, event.destination, payload, event.rawHex].join('|')
136
+ }
137
+ return [Number(event.ts || 0), event.adapterId, event.providerId, event.resourceId, event.eventType, event.eventId, event.scopeId, (event.objectTypes || []).join(',')].join('|')
138
+ }
139
+
140
+ const createKnxAiHistoryAccumulator = ({ kind = 'adapter', question = '', limit = 120 } = {}) => {
141
+ const normalizedKind = kind === 'knx' ? 'knx' : 'adapter'
142
+ const maxItems = Math.max(1, Number(limit) || 120)
143
+ const tokens = historyQuestionTokens(question)
144
+ const recent = []
145
+ const relevant = []
146
+ const counts = {
147
+ byEvent: new Map(),
148
+ bySource: new Map(),
149
+ byResource: new Map(),
150
+ byObjectType: new Map(),
151
+ byCombination: new Map()
152
+ }
153
+ let total = 0
154
+ let active = 0
155
+ let firstTs = 0
156
+ let lastTs = 0
157
+
158
+ const add = event => {
159
+ if (!event || typeof event !== 'object') return
160
+ const ts = Number(event.ts || new Date(event.at || '').getTime() || 0)
161
+ if (!Number.isFinite(ts) || ts <= 0) return
162
+ total += 1
163
+ if (event.active !== false) active += 1
164
+ firstTs = firstTs > 0 ? Math.min(firstTs, ts) : ts
165
+ lastTs = Math.max(lastTs, ts)
166
+ const eventName = normalizedKind === 'knx' ? event.event : event.eventType
167
+ const sourceName = normalizedKind === 'knx'
168
+ ? event.source
169
+ : (event.adapterTitle || event.adapterId || event.providerTitle || event.providerId)
170
+ const resourceName = normalizedKind === 'knx'
171
+ ? `${event.destination || '?'}${event.devicename ? ` (${event.devicename})` : ''}`
172
+ : (event.resourceName || event.resourceId || event.controllerName || event.controllerId)
173
+ incrementCount(counts.byEvent, eventName)
174
+ incrementCount(counts.bySource, sourceName)
175
+ incrementCount(counts.byResource, resourceName)
176
+ ;(Array.isArray(event.objectTypes) ? event.objectTypes : []).forEach(value => incrementCount(counts.byObjectType, value))
177
+ const combination = normalizedKind === 'knx'
178
+ ? `${resourceName || '?'} | ${eventName || '?'} | ${clampText(typeof event.payload === 'object' ? JSON.stringify(event.payload) : event.payload, 160)}`
179
+ : `${resourceName || '?'} | ${eventName || '?'}${event.scopeName ? ` | ${event.scopeName}` : ''}${event.objectTypes && event.objectTypes.length ? ` | ${event.objectTypes.join(',')}` : ''}`
180
+ incrementCount(counts.byCombination, combination)
181
+
182
+ recent.push(event)
183
+ if (recent.length > maxItems) recent.shift()
184
+ if (tokens.length) {
185
+ const haystack = buildEventSearchText(event, normalizedKind)
186
+ let score = 0
187
+ tokens.forEach(token => { if (haystack.includes(token)) score += token.length + 2 })
188
+ if (score > 0) {
189
+ relevant.push({ event, score, ts })
190
+ if (relevant.length > maxItems * 4) {
191
+ relevant.sort((left, right) => right.score - left.score || right.ts - left.ts)
192
+ relevant.length = maxItems * 2
193
+ }
194
+ }
195
+ }
196
+ }
197
+
198
+ const finish = () => {
199
+ let events = recent
200
+ if (relevant.length) {
201
+ const selected = relevant
202
+ .sort((left, right) => right.score - left.score || right.ts - left.ts)
203
+ .slice(0, maxItems)
204
+ .map(item => item.event)
205
+ events = selected.sort((left, right) => Number(left.ts || 0) - Number(right.ts || 0))
206
+ }
207
+ return {
208
+ events,
209
+ summary: {
210
+ kind: normalizedKind,
211
+ totalEvents: total,
212
+ activeEvents: active,
213
+ inactiveEvents: total - active,
214
+ firstAt: firstTs ? new Date(firstTs).toISOString() : '',
215
+ lastAt: lastTs ? new Date(lastTs).toISOString() : '',
216
+ selection: relevant.length ? 'question-relevant' : 'most-recent',
217
+ selectedEvents: events.length,
218
+ byEvent: topCounts(counts.byEvent, 20),
219
+ bySource: topCounts(counts.bySource, 12),
220
+ byResource: topCounts(counts.byResource, 30),
221
+ byObjectType: topCounts(counts.byObjectType, 20),
222
+ byCombination: topCounts(counts.byCombination, 40)
223
+ }
224
+ }
225
+ }
226
+
227
+ return { add, finish }
228
+ }
229
+
230
+ const formatKnxAiAdapterHistoryEventForPrompt = event => {
231
+ if (!event || typeof event !== 'object') return ''
232
+ const resource = event.resourceName || event.resourceId || event.controllerName || event.controllerId || '?'
233
+ const scope = event.scopeName || event.scopeId
234
+ const objects = Array.isArray(event.objectTypes) && event.objectTypes.length ? event.objectTypes.join(',') : ''
235
+ return `${event.at || new Date(event.ts || Date.now()).toISOString()} | adapter ${event.adapterTitle || event.adapterId || '?'} | ${event.resourceType || 'resource'} ${resource} | ${event.eventType || '?'} | ${event.active === false ? 'inactive' : 'active'}${scope ? ` | scope ${scope}` : ''}${objects ? ` | objects ${objects}` : ''}`
236
+ }
237
+
238
+ module.exports = {
239
+ KNX_AI_ADAPTER_HISTORY_MIN_HOURS,
240
+ KNX_AI_HISTORY_DETAILS_MAX_CHARS,
241
+ buildKnxAiHistoryEventKey,
242
+ createKnxAiHistoryAccumulator,
243
+ formatKnxAiAdapterHistoryEventForPrompt,
244
+ normalizeKnxAiAdapterHistoryEvent,
245
+ sanitizeHistoryValue
246
+ }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=20.18.1"
5
5
  },
6
- "version": "6.3.18",
6
+ "version": "6.3.21",
7
7
  "description": "KNX Ultimate is the most advanced KNX integration for Node-RED, providing secure KNX/IP communication, routing, ETS project import, Philips Hue, Matter Controller and Matter Bridge (control matter device via KNX and expose KNX GA via Matter), MQTT, diagnostics with AI, virtual devices, and powerful automation nodes. Build professional, reliable, and scalable smart home and building automation projects with minimal effort.",
8
8
  "files": [
9
9
  "nodes/",