node-red-contrib-knx-ultimate 7.0.3 → 7.1.0-beta.0
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.
- package/CHANGELOG.md +11 -0
- package/README.md +2 -0
- package/examples/HA Translator - Payload Conversion.json +18 -8
- package/examples/KNX Utility - All functions.json +389 -0
- package/nodes/commonFunctions.js +5 -4
- package/nodes/knxUltimateAlerter.html +15 -4
- package/nodes/knxUltimateAutoResponder.html +15 -4
- package/nodes/knxUltimateDateTime.html +16 -5
- package/nodes/knxUltimateDateTime.js +2 -26
- package/nodes/knxUltimateGarage.html +15 -4
- package/nodes/knxUltimateGlobalContext.html +15 -4
- package/nodes/knxUltimateHATranslator.html +15 -4
- package/nodes/knxUltimateHueController.html +3 -4
- package/nodes/knxUltimateLoadControl.html +15 -4
- package/nodes/knxUltimateLogger.html +15 -4
- package/nodes/knxUltimateSceneController.html +15 -4
- package/nodes/knxUltimateStaircase.html +15 -4
- package/nodes/knxUltimateUtility.html +325 -0
- package/nodes/knxUltimateUtility.js +24 -0
- package/nodes/knxUltimateViewer.html +16 -3
- package/nodes/knxUltimateViewer.js +106 -27
- package/nodes/knxUltimateWatchDog.html +15 -4
- package/nodes/locales/de/knxUltimateAlerter.html +2 -0
- package/nodes/locales/de/knxUltimateAlerter.json +5 -3
- package/nodes/locales/de/knxUltimateAutoResponder.html +2 -0
- package/nodes/locales/de/knxUltimateAutoResponder.json +4 -2
- package/nodes/locales/de/knxUltimateDateTime.html +2 -0
- package/nodes/locales/de/knxUltimateDateTime.json +5 -3
- package/nodes/locales/de/knxUltimateGarage.html +2 -0
- package/nodes/locales/de/knxUltimateGarage.json +5 -2
- package/nodes/locales/de/knxUltimateGlobalContext.html +2 -0
- package/nodes/locales/de/knxUltimateGlobalContext.json +21 -18
- package/nodes/locales/de/knxUltimateHATranslator.html +2 -0
- package/nodes/locales/de/knxUltimateHATranslator.json +4 -2
- package/nodes/locales/de/knxUltimateHueController.html +2 -1
- package/nodes/locales/de/knxUltimateHueController.json +4 -3
- package/nodes/locales/de/knxUltimateLoadControl.html +2 -0
- package/nodes/locales/de/knxUltimateLoadControl.json +5 -2
- package/nodes/locales/de/knxUltimateLogger.html +2 -0
- package/nodes/locales/de/knxUltimateLogger.json +6 -3
- package/nodes/locales/de/knxUltimateSceneController.html +2 -0
- package/nodes/locales/de/knxUltimateSceneController.json +8 -6
- package/nodes/locales/de/knxUltimateStaircase.html +2 -0
- package/nodes/locales/de/knxUltimateStaircase.json +5 -2
- package/nodes/locales/de/knxUltimateUtility.html +922 -0
- package/nodes/locales/de/knxUltimateUtility.json +56 -0
- package/nodes/locales/de/knxUltimateViewer.html +19 -453
- package/nodes/locales/de/knxUltimateViewer.json +9 -1
- package/nodes/locales/de/knxUltimateWatchDog.html +2 -0
- package/nodes/locales/de/knxUltimateWatchDog.json +5 -2
- package/nodes/locales/en/knxUltimateAlerter.html +2 -0
- package/nodes/locales/en/knxUltimateAlerter.json +5 -3
- package/nodes/locales/en/knxUltimateAutoResponder.html +2 -0
- package/nodes/locales/en/knxUltimateAutoResponder.json +4 -2
- package/nodes/locales/en/knxUltimateDateTime.html +2 -0
- package/nodes/locales/en/knxUltimateDateTime.json +5 -3
- package/nodes/locales/en/knxUltimateGarage.html +2 -0
- package/nodes/locales/en/knxUltimateGarage.json +5 -2
- package/nodes/locales/en/knxUltimateGlobalContext.html +2 -0
- package/nodes/locales/en/knxUltimateGlobalContext.json +21 -18
- package/nodes/locales/en/knxUltimateHATranslator.html +2 -0
- package/nodes/locales/en/knxUltimateHATranslator.json +4 -2
- package/nodes/locales/en/knxUltimateHueController.html +2 -1
- package/nodes/locales/en/knxUltimateHueController.json +4 -3
- package/nodes/locales/en/knxUltimateLoadControl.html +2 -0
- package/nodes/locales/en/knxUltimateLoadControl.json +5 -2
- package/nodes/locales/en/knxUltimateLogger.html +2 -0
- package/nodes/locales/en/knxUltimateLogger.json +6 -3
- package/nodes/locales/en/knxUltimateSceneController.html +2 -0
- package/nodes/locales/en/knxUltimateSceneController.json +15 -14
- package/nodes/locales/en/knxUltimateStaircase.html +2 -0
- package/nodes/locales/en/knxUltimateStaircase.json +5 -2
- package/nodes/locales/en/knxUltimateUtility.html +1100 -0
- package/nodes/locales/en/knxUltimateUtility.json +56 -0
- package/nodes/locales/en/knxUltimateViewer.html +18 -457
- package/nodes/locales/en/knxUltimateViewer.json +9 -1
- package/nodes/locales/en/knxUltimateWatchDog.html +2 -0
- package/nodes/locales/en/knxUltimateWatchDog.json +5 -3
- package/nodes/locales/es/knxUltimateAlerter.html +2 -0
- package/nodes/locales/es/knxUltimateAlerter.json +5 -2
- package/nodes/locales/es/knxUltimateAutoResponder.html +2 -0
- package/nodes/locales/es/knxUltimateAutoResponder.json +4 -1
- package/nodes/locales/es/knxUltimateDateTime.html +2 -0
- package/nodes/locales/es/knxUltimateDateTime.json +5 -3
- package/nodes/locales/es/knxUltimateGarage.html +2 -0
- package/nodes/locales/es/knxUltimateGarage.json +5 -2
- package/nodes/locales/es/knxUltimateGlobalContext.html +2 -0
- package/nodes/locales/es/knxUltimateGlobalContext.json +5 -2
- package/nodes/locales/es/knxUltimateHATranslator.html +2 -0
- package/nodes/locales/es/knxUltimateHATranslator.json +4 -1
- package/nodes/locales/es/knxUltimateHueController.html +2 -1
- package/nodes/locales/es/knxUltimateHueController.json +4 -3
- package/nodes/locales/es/knxUltimateLoadControl.html +2 -0
- package/nodes/locales/es/knxUltimateLoadControl.json +5 -2
- package/nodes/locales/es/knxUltimateLogger.html +2 -0
- package/nodes/locales/es/knxUltimateLogger.json +5 -2
- package/nodes/locales/es/knxUltimateSceneController.html +2 -0
- package/nodes/locales/es/knxUltimateSceneController.json +5 -2
- package/nodes/locales/es/knxUltimateStaircase.html +2 -0
- package/nodes/locales/es/knxUltimateStaircase.json +5 -2
- package/nodes/locales/es/knxUltimateUtility.html +1080 -0
- package/nodes/locales/es/knxUltimateUtility.json +56 -0
- package/nodes/locales/es/knxUltimateViewer.html +18 -450
- package/nodes/locales/es/knxUltimateViewer.json +16 -8
- package/nodes/locales/es/knxUltimateWatchDog.html +2 -0
- package/nodes/locales/es/knxUltimateWatchDog.json +5 -2
- package/nodes/locales/fr/knxUltimateAlerter.html +2 -0
- package/nodes/locales/fr/knxUltimateAlerter.json +5 -2
- package/nodes/locales/fr/knxUltimateAutoResponder.html +2 -0
- package/nodes/locales/fr/knxUltimateAutoResponder.json +4 -1
- package/nodes/locales/fr/knxUltimateDateTime.html +2 -0
- package/nodes/locales/fr/knxUltimateDateTime.json +5 -3
- package/nodes/locales/fr/knxUltimateGarage.html +2 -0
- package/nodes/locales/fr/knxUltimateGarage.json +5 -2
- package/nodes/locales/fr/knxUltimateGlobalContext.html +2 -0
- package/nodes/locales/fr/knxUltimateGlobalContext.json +5 -2
- package/nodes/locales/fr/knxUltimateHATranslator.html +2 -0
- package/nodes/locales/fr/knxUltimateHATranslator.json +4 -1
- package/nodes/locales/fr/knxUltimateHueController.html +2 -1
- package/nodes/locales/fr/knxUltimateHueController.json +4 -3
- package/nodes/locales/fr/knxUltimateLoadControl.html +2 -0
- package/nodes/locales/fr/knxUltimateLoadControl.json +5 -2
- package/nodes/locales/fr/knxUltimateLogger.html +2 -0
- package/nodes/locales/fr/knxUltimateLogger.json +5 -2
- package/nodes/locales/fr/knxUltimateSceneController.html +2 -0
- package/nodes/locales/fr/knxUltimateSceneController.json +5 -2
- package/nodes/locales/fr/knxUltimateStaircase.html +2 -0
- package/nodes/locales/fr/knxUltimateStaircase.json +5 -2
- package/nodes/locales/fr/knxUltimateUtility.html +1081 -0
- package/nodes/locales/fr/knxUltimateUtility.json +56 -0
- package/nodes/locales/fr/knxUltimateViewer.html +18 -450
- package/nodes/locales/fr/knxUltimateViewer.json +16 -8
- package/nodes/locales/fr/knxUltimateWatchDog.html +2 -0
- package/nodes/locales/fr/knxUltimateWatchDog.json +5 -2
- package/nodes/locales/it/knxUltimateAlerter.html +2 -0
- package/nodes/locales/it/knxUltimateAlerter.json +5 -3
- package/nodes/locales/it/knxUltimateAutoResponder.html +2 -0
- package/nodes/locales/it/knxUltimateAutoResponder.json +4 -2
- package/nodes/locales/it/knxUltimateDateTime.html +2 -0
- package/nodes/locales/it/knxUltimateDateTime.json +5 -3
- package/nodes/locales/it/knxUltimateGarage.html +2 -0
- package/nodes/locales/it/knxUltimateGarage.json +5 -2
- package/nodes/locales/it/knxUltimateGlobalContext.html +2 -0
- package/nodes/locales/it/knxUltimateGlobalContext.json +21 -18
- package/nodes/locales/it/knxUltimateHATranslator.html +2 -0
- package/nodes/locales/it/knxUltimateHATranslator.json +4 -2
- package/nodes/locales/it/knxUltimateHueController.html +2 -1
- package/nodes/locales/it/knxUltimateHueController.json +4 -3
- package/nodes/locales/it/knxUltimateLoadControl.html +2 -0
- package/nodes/locales/it/knxUltimateLoadControl.json +5 -2
- package/nodes/locales/it/knxUltimateLogger.html +2 -0
- package/nodes/locales/it/knxUltimateLogger.json +6 -3
- package/nodes/locales/it/knxUltimateSceneController.html +2 -0
- package/nodes/locales/it/knxUltimateSceneController.json +8 -7
- package/nodes/locales/it/knxUltimateStaircase.html +2 -0
- package/nodes/locales/it/knxUltimateStaircase.json +5 -2
- package/nodes/locales/it/knxUltimateUtility.html +959 -0
- package/nodes/locales/it/knxUltimateUtility.json +56 -0
- package/nodes/locales/it/knxUltimateViewer.html +19 -453
- package/nodes/locales/it/knxUltimateViewer.json +9 -1
- package/nodes/locales/it/knxUltimateWatchDog.html +2 -0
- package/nodes/locales/it/knxUltimateWatchDog.json +6 -6
- package/nodes/locales/zh-CN/knxUltimateAlerter.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateAlerter.json +5 -3
- package/nodes/locales/zh-CN/knxUltimateAutoResponder.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateAutoResponder.json +4 -2
- package/nodes/locales/zh-CN/knxUltimateDateTime.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateDateTime.json +5 -3
- package/nodes/locales/zh-CN/knxUltimateGarage.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateGarage.json +5 -2
- package/nodes/locales/zh-CN/knxUltimateGlobalContext.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateGlobalContext.json +21 -18
- package/nodes/locales/zh-CN/knxUltimateHATranslator.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateHATranslator.json +4 -2
- package/nodes/locales/zh-CN/knxUltimateHueController.html +2 -1
- package/nodes/locales/zh-CN/knxUltimateHueController.json +4 -3
- package/nodes/locales/zh-CN/knxUltimateLoadControl.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateLoadControl.json +5 -2
- package/nodes/locales/zh-CN/knxUltimateLogger.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateLogger.json +6 -3
- package/nodes/locales/zh-CN/knxUltimateSceneController.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateSceneController.json +12 -10
- package/nodes/locales/zh-CN/knxUltimateStaircase.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateStaircase.json +5 -2
- package/nodes/locales/zh-CN/knxUltimateUtility.html +930 -0
- package/nodes/locales/zh-CN/knxUltimateUtility.json +56 -0
- package/nodes/locales/zh-CN/knxUltimateViewer.html +19 -452
- package/nodes/locales/zh-CN/knxUltimateViewer.json +9 -1
- package/nodes/locales/zh-CN/knxUltimateWatchDog.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateWatchDog.json +5 -3
- package/nodes/plugins/knxUltimateViewer-vue/assets/app.css +1 -1
- package/nodes/plugins/knxUltimateViewer-vue/assets/app.js +2 -2
- package/nodes/utils/knxDateTimeSendNow.js +30 -0
- package/nodes/utils/knxUtilityProfileAdapter.js +66 -0
- package/nodes/utils/knxUtilityProfiles/runtime/alerter.js +305 -0
- package/nodes/utils/knxUtilityProfiles/runtime/autoresponder.js +266 -0
- package/nodes/utils/knxUtilityProfiles/runtime/datetime.js +279 -0
- package/nodes/utils/knxUtilityProfiles/runtime/garage.js +513 -0
- package/nodes/utils/knxUtilityProfiles/runtime/globalcontext.js +218 -0
- package/nodes/utils/knxUtilityProfiles/runtime/hatranslator.js +95 -0
- package/nodes/utils/knxUtilityProfiles/runtime/loadcontrol.js +448 -0
- package/nodes/utils/knxUtilityProfiles/runtime/logger.js +233 -0
- package/nodes/utils/knxUtilityProfiles/runtime/profileTimers.js +35 -0
- package/nodes/utils/knxUtilityProfiles/runtime/scenecontroller.js +487 -0
- package/nodes/utils/knxUtilityProfiles/runtime/staircase.js +472 -0
- package/nodes/utils/knxUtilityProfiles/runtime/watchdog.js +240 -0
- package/nodes/utils/knxViewerHistory.js +424 -0
- package/package.json +9 -6
- package/resources/flowMigrationBackup.js +81 -0
- package/resources/hueControllerMigrationDialog.js +15 -4
- package/resources/knxUtilityMigration.js +276 -0
- package/resources/knxUtilityProfiles.js +2488 -0
|
@@ -0,0 +1,2488 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// GENERATED FILE — do not edit directly.
|
|
3
|
+
// Canonical sources: scripts/knx-utility-profiles/
|
|
4
|
+
// Rebuild with: npm run knx-utility:generate
|
|
5
|
+
//
|
|
6
|
+
// This bundle is deliberately self-contained. It does not import, query or
|
|
7
|
+
// require any deprecated KNX node, editor template or localization namespace.
|
|
8
|
+
(function (root, factory) {
|
|
9
|
+
// UMD-style export: Node-RED receives the browser global, while unit tests can
|
|
10
|
+
// require the same artifact through CommonJS without maintaining a test copy.
|
|
11
|
+
const api = factory(root)
|
|
12
|
+
if (typeof module === 'object' && module.exports) module.exports = api
|
|
13
|
+
if (root) root.KNXUltimateUtilityProfiles = api
|
|
14
|
+
}(typeof globalThis !== 'undefined' ? globalThis : this, function (root) {
|
|
15
|
+
'use strict'
|
|
16
|
+
|
|
17
|
+
// Values on the left are persisted in utilityType. Values on the
|
|
18
|
+
// right are private editor/translation namespace identifiers, not registry
|
|
19
|
+
// lookups. Keeping them preserves all existing field and i18n contracts.
|
|
20
|
+
const PROFILE_TYPES = Object.freeze( {
|
|
21
|
+
"alerter": "knxUltimateAlerter",
|
|
22
|
+
"autoresponder": "knxUltimateAutoResponder",
|
|
23
|
+
"datetime": "knxUltimateDateTime",
|
|
24
|
+
"watchdog": "knxUltimateWatchDog",
|
|
25
|
+
"globalcontext": "knxUltimateGlobalContext",
|
|
26
|
+
"logger": "knxUltimateLogger",
|
|
27
|
+
"staircase": "knxUltimateStaircase",
|
|
28
|
+
"garage": "knxUltimateGarage",
|
|
29
|
+
"scenecontroller": "knxUltimateSceneController",
|
|
30
|
+
"loadcontrol": "knxUltimateLoadControl",
|
|
31
|
+
"hatranslator": "knxUltimateHATranslator"
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Factories are the private copies of the mature editor definitions. They are
|
|
35
|
+
// inert until getDefinition() asks for one of them.
|
|
36
|
+
const PROFILE_FACTORIES = {
|
|
37
|
+
"alerter": function (RED) {
|
|
38
|
+
RED.nodes.registerType('knxUltimateAlerter', {
|
|
39
|
+
category: "KNX Ultimate",
|
|
40
|
+
color: '#C7E9C0',
|
|
41
|
+
defaults: {
|
|
42
|
+
server: { type: "knxUltimate-config", required: false },
|
|
43
|
+
name: { value: "" },
|
|
44
|
+
rules: { value: [] },
|
|
45
|
+
whentostart: { value: "ifnewalert" },
|
|
46
|
+
timerinterval: { value: "2" },
|
|
47
|
+
initialreadGAInRules: { value: "1" },
|
|
48
|
+
},
|
|
49
|
+
inputs: 1,
|
|
50
|
+
outputs: 3,
|
|
51
|
+
outputLabels: function (index) {
|
|
52
|
+
if (index === 0) return "Emits a message for each alerted device, at selectable intervals.";
|
|
53
|
+
if (index === 1) return "Emits a unique message containing all alerted devices.";
|
|
54
|
+
if (index === 2) return "Emits a message containing only the last alerted device.";
|
|
55
|
+
},
|
|
56
|
+
icon: "node-alerter-icon.svg",
|
|
57
|
+
label: function () {
|
|
58
|
+
return (this.outputRBE == true ? "|rbe| " : "") + (this.name || this.topic || "KNX Alerter") + (this.inputRBE == true ? " |rbe|" : "")
|
|
59
|
+
},
|
|
60
|
+
paletteLabel: "KNX Alerter",
|
|
61
|
+
oneditprepare: function () {
|
|
62
|
+
// Go to the help panel
|
|
63
|
+
try {
|
|
64
|
+
RED.sidebar.show("help");
|
|
65
|
+
} catch (error) { }
|
|
66
|
+
|
|
67
|
+
var node = this;
|
|
68
|
+
var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
|
|
69
|
+
|
|
70
|
+
// 19/02/2020 Used to get the server sooner als deploy.
|
|
71
|
+
$("#node-input-server").off('.knxUtilityProfile').on('change.knxUtilityProfile', function () {
|
|
72
|
+
try {
|
|
73
|
+
oNodeServer = RED.nodes.node($(this).val());
|
|
74
|
+
} catch (error) { }
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
// Scene configuration
|
|
81
|
+
function resizeRule(rule) {
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
$("#node-input-rule-container").css('min-height', '350px').css('min-width', '450px').editableList({
|
|
85
|
+
addItem: function (container, i, opt) { // row, index, data
|
|
86
|
+
|
|
87
|
+
// opt.r is: { topic: rowRuleTopic, devicename: rowRuleDeviceName, longdevicename: rowRuleLongDeviceName}
|
|
88
|
+
|
|
89
|
+
var rule = opt.r || { topic: '', devicename: '', longdevicename: '' };
|
|
90
|
+
if (!opt.hasOwnProperty('i')) {
|
|
91
|
+
opt._i = Math.floor((0x99999 - 0x10000) * Math.random()).toString();
|
|
92
|
+
}
|
|
93
|
+
container.css({
|
|
94
|
+
overflow: 'hidden',
|
|
95
|
+
whiteSpace: 'nowrap'
|
|
96
|
+
});
|
|
97
|
+
var row = $('<div class="form-row"/>').appendTo(container);
|
|
98
|
+
|
|
99
|
+
var oTopicField = $("<input/>", { class: "rowRuleTopic", type: "text", placeholder: "GA or devicename", style: "width:20%; margin-left: 5px; text-align: left;" }).appendTo(row);
|
|
100
|
+
var finalspan = $('<span/>', { style: "" }).appendTo(row);
|
|
101
|
+
finalspan.append(' <span class="node-input-rule-index"></span> ');
|
|
102
|
+
var orowRuleDeviceName = $('<input/>', { maxlength: "14", class: "rowRuleDeviceName", type: "text", style: "width:30%; margin-left: 0px; text-align: left;font-style: italic;", placeholder: "Name (max 14 chars)" }).appendTo(row);
|
|
103
|
+
var orowRuleLongDeviceName = $('<input/>', { class: "rowRuleLongDeviceName", type: "text", style: "width:45%; margin-left: 0px; text-align: left;", placeholder: "Long name" }).appendTo(row);
|
|
104
|
+
|
|
105
|
+
oTopicField.on("change", function () {
|
|
106
|
+
resizeRule(container);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
// Autocomplete suggestion with ETS csv File
|
|
111
|
+
oTopicField.autocomplete({
|
|
112
|
+
minLength: 0,
|
|
113
|
+
source: function (request, response) {
|
|
114
|
+
if (!oNodeServer || !oNodeServer.id) { response([]); return; }
|
|
115
|
+
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
116
|
+
response($.map(data, function (value, key) {
|
|
117
|
+
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
118
|
+
if (htmlUtilsfullCSVSearch(sSearch, request.term + " 1.")) {
|
|
119
|
+
return {
|
|
120
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
121
|
+
value: value.ga // Value
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}));
|
|
127
|
+
});
|
|
128
|
+
}, select: function (event, ui) {
|
|
129
|
+
// Sets Datapoint and device name automatically
|
|
130
|
+
var sDevName = ui.item.label.split("#")[1].trim();
|
|
131
|
+
try {
|
|
132
|
+
sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
|
|
133
|
+
orowRuleDeviceName.val(sDevName.substr(0, 14));
|
|
134
|
+
orowRuleLongDeviceName.val(sDevName);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
oTopicField.on('focus.knxUltimateAlerter click.knxUltimateAlerter', function () {
|
|
141
|
+
try {
|
|
142
|
+
$(this).autocomplete('search', '');
|
|
143
|
+
} catch (error) { /* empty */ }
|
|
144
|
+
});
|
|
145
|
+
try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting(oTopicField, oNodeServer.id); } catch (e) {}
|
|
146
|
+
|
|
147
|
+
oTopicField.val(rule.topic);
|
|
148
|
+
orowRuleDeviceName.val(rule.devicename);
|
|
149
|
+
orowRuleLongDeviceName.val(rule.longdevicename);
|
|
150
|
+
oTopicField.change();
|
|
151
|
+
|
|
152
|
+
},
|
|
153
|
+
removeItem: function (opt) {
|
|
154
|
+
|
|
155
|
+
},
|
|
156
|
+
resizeItem: resizeRule,
|
|
157
|
+
sortItems: function (rules) {
|
|
158
|
+
},
|
|
159
|
+
sortable: true,
|
|
160
|
+
removable: true
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// 10/03/2020 For each rule, create a row
|
|
164
|
+
for (var i = 0; i < (this.rules || []).length; i++) {
|
|
165
|
+
var rule = this.rules[i];
|
|
166
|
+
$("#node-input-rule-container").editableList('addItem', { r: rule, i: i });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
},
|
|
171
|
+
oneditsave: function () {
|
|
172
|
+
// Return to the info tab
|
|
173
|
+
try {
|
|
174
|
+
RED.sidebar.show("info");
|
|
175
|
+
} catch (error) { }
|
|
176
|
+
|
|
177
|
+
var node = this;
|
|
178
|
+
|
|
179
|
+
var rules = $("#node-input-rule-container").editableList('items');
|
|
180
|
+
node.rules = [];
|
|
181
|
+
rules.each(function (i) {
|
|
182
|
+
var rule = $(this);
|
|
183
|
+
var rowRuleTopic = rule.find(".rowRuleTopic").val();
|
|
184
|
+
var rowRuleDeviceName = rule.find(".rowRuleDeviceName").val();
|
|
185
|
+
var rowRuleLongDeviceName = rule.find(".rowRuleLongDeviceName").val();
|
|
186
|
+
node.rules.push({ topic: rowRuleTopic, devicename: rowRuleDeviceName, longdevicename: rowRuleLongDeviceName });
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
oneditcancel: function () {
|
|
190
|
+
$("#node-input-server").off('.knxUtilityProfile');
|
|
191
|
+
try { RED.sidebar.show('info'); } catch (error) { }
|
|
192
|
+
},
|
|
193
|
+
oneditresize: function (size) {
|
|
194
|
+
const list = $("#node-input-rule-container");
|
|
195
|
+
const form = $('#dialog-form');
|
|
196
|
+
const listTop = list.offset();
|
|
197
|
+
const formTop = form.offset();
|
|
198
|
+
if (!size || !listTop || !formTop) return;
|
|
199
|
+
// The fragment is nested in Utility, so measure the actual list
|
|
200
|
+
// offset instead of assuming it is a direct child of dialog-form.
|
|
201
|
+
list.editableList('height', Math.max(180, size.height - (listTop.top - formTop.top) - 45));
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
},
|
|
205
|
+
"autoresponder": function (RED) {
|
|
206
|
+
RED.nodes.registerType('knxUltimateAutoResponder', {
|
|
207
|
+
category: "KNX Ultimate",
|
|
208
|
+
color: '#C7E9C0',
|
|
209
|
+
defaults: {
|
|
210
|
+
server: { type: "knxUltimate-config", required: true },
|
|
211
|
+
name: { value: "Auto responder", required: false },
|
|
212
|
+
commandText: {
|
|
213
|
+
value: '[]', required: false,
|
|
214
|
+
validate: function (value) {
|
|
215
|
+
try {
|
|
216
|
+
const entries = JSON.parse(value);
|
|
217
|
+
return Array.isArray(entries) && entries.every((entry) => (
|
|
218
|
+
entry && typeof entry === 'object' &&
|
|
219
|
+
typeof entry.ga === 'string' && entry.ga.trim() !== '' &&
|
|
220
|
+
Object.prototype.hasOwnProperty.call(entry, 'default')
|
|
221
|
+
));
|
|
222
|
+
} catch (error) { return false; }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
inputs: 0,
|
|
227
|
+
outputs: 0,
|
|
228
|
+
icon: "node-knx-icon.svg",
|
|
229
|
+
label: function () {
|
|
230
|
+
return (this.name || "KNX Auto Responder");
|
|
231
|
+
},
|
|
232
|
+
paletteLabel: "KNX Auto Responder",
|
|
233
|
+
oneditprepare: function () {
|
|
234
|
+
// Go to the help panel
|
|
235
|
+
try {
|
|
236
|
+
RED.sidebar.show("help");
|
|
237
|
+
} catch (error) { }
|
|
238
|
+
|
|
239
|
+
var node = this;
|
|
240
|
+
|
|
241
|
+
$("#node-input-commandText").typedInput({
|
|
242
|
+
type: "json",
|
|
243
|
+
types: ["json"]
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
if (node.commandText !== undefined) {
|
|
248
|
+
$("#node-input-commandText").typedInput('value', node.commandText)
|
|
249
|
+
}
|
|
250
|
+
} catch (error) { }
|
|
251
|
+
|
|
252
|
+
},
|
|
253
|
+
oneditsave: function () {
|
|
254
|
+
// Return to the info tab
|
|
255
|
+
try {
|
|
256
|
+
RED.sidebar.show("info");
|
|
257
|
+
} catch (error) { }
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
this.commandText = $("#node-input-commandText").typedInput('value')
|
|
261
|
+
} catch (error) { }
|
|
262
|
+
|
|
263
|
+
},
|
|
264
|
+
oneditcancel: function () {
|
|
265
|
+
// Return to the info tab
|
|
266
|
+
try {
|
|
267
|
+
RED.sidebar.show("info");
|
|
268
|
+
} catch (error) { }
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
},
|
|
274
|
+
"datetime": function (RED) {
|
|
275
|
+
const KNX_ULTIMATE_DATETIME_KEYWORDS = {
|
|
276
|
+
datetime: [/date\s*\/\s*time/i, /date\s*time/i, /datetime/i, /data\s*\/\s*ora/i, /data\s*ora/i],
|
|
277
|
+
date: [/\bdate\b/i, /\bdata\b/i, /\bdatum\b/i, /\bfecha\b/i],
|
|
278
|
+
time: [/\btime\b/i, /\bora\b/i, /\bheure\b/i, /\bzeit\b/i, /\bhora\b/i, /\borologio\b/i, /\bclock\b/i]
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const KNX_ULTIMATE_DATETIME_STOPWORDS = new Set([
|
|
282
|
+
'date', 'datetime', 'time', 'data', 'ora', 'orologio', 'clock', 'bus', 'knx', 'set', 'sync', 'sincro', 'synchronization'
|
|
283
|
+
])
|
|
284
|
+
|
|
285
|
+
const knxDateTimeNormalizeTokens = (value) => {
|
|
286
|
+
const str = (value || '').toString().toLowerCase()
|
|
287
|
+
const cleaned = str
|
|
288
|
+
.replace(/dpt\s*\d+(\.\d+)?/g, ' ')
|
|
289
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
290
|
+
.trim()
|
|
291
|
+
if (!cleaned) return []
|
|
292
|
+
return cleaned
|
|
293
|
+
.split(/\s+/)
|
|
294
|
+
.map((t) => t.trim())
|
|
295
|
+
.filter((t) => t.length > 1 && !KNX_ULTIMATE_DATETIME_STOPWORDS.has(t))
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const knxDateTimeTokenSimilarity = (aTokens, bTokens) => {
|
|
299
|
+
if (!Array.isArray(aTokens) || !Array.isArray(bTokens) || aTokens.length === 0 || bTokens.length === 0) return 0
|
|
300
|
+
const a = new Set(aTokens)
|
|
301
|
+
const b = new Set(bTokens)
|
|
302
|
+
let inter = 0
|
|
303
|
+
a.forEach((t) => { if (b.has(t)) inter += 1 })
|
|
304
|
+
const union = a.size + b.size - inter
|
|
305
|
+
return union > 0 ? inter / union : 0
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const knxDateTimeParseGA = (ga) => {
|
|
309
|
+
const parts = (ga || '').toString().trim().split('/')
|
|
310
|
+
if (parts.length !== 3) return null
|
|
311
|
+
const nums = parts.map((p) => Number(p))
|
|
312
|
+
if (nums.some((n) => !Number.isInteger(n) || n < 0)) return null
|
|
313
|
+
return nums
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const knxDateTimeCompareGA = (a, b) => {
|
|
317
|
+
const aa = knxDateTimeParseGA(a)
|
|
318
|
+
const bb = knxDateTimeParseGA(b)
|
|
319
|
+
if (!aa && !bb) return 0
|
|
320
|
+
if (!aa) return 1
|
|
321
|
+
if (!bb) return -1
|
|
322
|
+
for (let i = 0; i < 3; i++) {
|
|
323
|
+
if (aa[i] !== bb[i]) return aa[i] - bb[i]
|
|
324
|
+
}
|
|
325
|
+
return 0
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const knxGetKnxUltimateConfigs = () => {
|
|
329
|
+
const configs = []
|
|
330
|
+
try {
|
|
331
|
+
if (RED && RED.nodes) {
|
|
332
|
+
if (typeof RED.nodes.eachConfig === 'function') {
|
|
333
|
+
RED.nodes.eachConfig((cfg) => {
|
|
334
|
+
if (cfg && cfg.type === 'knxUltimate-config') configs.push(cfg)
|
|
335
|
+
})
|
|
336
|
+
} else if (typeof RED.nodes.eachNode === 'function') {
|
|
337
|
+
RED.nodes.eachNode((n) => {
|
|
338
|
+
if (n && n.type === 'knxUltimate-config') configs.push(n)
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
if (configs.length === 0 && typeof RED.nodes.filterNodes === 'function') {
|
|
342
|
+
try {
|
|
343
|
+
const filtered = RED.nodes.filterNodes({ type: 'knxUltimate-config' })
|
|
344
|
+
if (Array.isArray(filtered)) filtered.forEach((n) => configs.push(n))
|
|
345
|
+
} catch (error) { /* ignore */ }
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
} catch (error) { /* ignore */ }
|
|
349
|
+
return configs
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const knxFetchGroupAddresses = (serverId) => {
|
|
353
|
+
return new Promise((resolve) => {
|
|
354
|
+
if (!serverId) return resolve([])
|
|
355
|
+
$.getJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
|
|
356
|
+
resolve(Array.isArray(data) ? data : [])
|
|
357
|
+
}).fail(() => resolve([]))
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const knxScoreEntry = (entry, kind, baseTokens) => {
|
|
362
|
+
if (!entry) return -1
|
|
363
|
+
const dpt = (entry.dpt || '').toString()
|
|
364
|
+
const name = (entry.devicename || '').toString()
|
|
365
|
+
const tokens = knxDateTimeNormalizeTokens(name)
|
|
366
|
+
|
|
367
|
+
let score = 0
|
|
368
|
+
|
|
369
|
+
if (kind === 'datetime') {
|
|
370
|
+
if (dpt === '19.001') score += 60
|
|
371
|
+
else if (dpt.startsWith('19.')) score += 45
|
|
372
|
+
KNX_ULTIMATE_DATETIME_KEYWORDS.datetime.forEach((re) => { if (re.test(name)) score += 20 })
|
|
373
|
+
KNX_ULTIMATE_DATETIME_KEYWORDS.time.forEach((re) => { if (re.test(name)) score += 6 })
|
|
374
|
+
KNX_ULTIMATE_DATETIME_KEYWORDS.date.forEach((re) => { if (re.test(name)) score += 6 })
|
|
375
|
+
} else if (kind === 'date') {
|
|
376
|
+
if (dpt === '11.001') score += 60
|
|
377
|
+
else if (dpt.startsWith('11.')) score += 45
|
|
378
|
+
KNX_ULTIMATE_DATETIME_KEYWORDS.date.forEach((re) => { if (re.test(name)) score += 18 })
|
|
379
|
+
} else if (kind === 'time') {
|
|
380
|
+
if (dpt === '10.001') score += 60
|
|
381
|
+
else if (dpt.startsWith('10.')) score += 45
|
|
382
|
+
KNX_ULTIMATE_DATETIME_KEYWORDS.time.forEach((re) => { if (re.test(name)) score += 18 })
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (Array.isArray(baseTokens) && baseTokens.length > 0) {
|
|
386
|
+
score += Math.round(knxDateTimeTokenSimilarity(tokens, baseTokens) * 30)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return score
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const knxPickBest = (entries, kind, baseTokens) => {
|
|
393
|
+
if (!Array.isArray(entries) || entries.length === 0) return null
|
|
394
|
+
let best = null
|
|
395
|
+
let bestScore = -1
|
|
396
|
+
entries.forEach((e) => {
|
|
397
|
+
const s = knxScoreEntry(e, kind, baseTokens)
|
|
398
|
+
if (s > bestScore) {
|
|
399
|
+
bestScore = s
|
|
400
|
+
best = e
|
|
401
|
+
} else if (s === bestScore && best) {
|
|
402
|
+
const cmp = knxDateTimeCompareGA(e.ga, best.ga)
|
|
403
|
+
if (cmp < 0) best = e
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
return best
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const knxSuggestFromCsv = (csvRows) => {
|
|
410
|
+
const rows = Array.isArray(csvRows) ? csvRows : []
|
|
411
|
+
const datetimeRows = rows.filter((r) => (r && typeof r.dpt === 'string' && r.dpt.startsWith('19.')))
|
|
412
|
+
const dateRows = rows.filter((r) => (r && typeof r.dpt === 'string' && r.dpt.startsWith('11.')))
|
|
413
|
+
const timeRows = rows.filter((r) => (r && typeof r.dpt === 'string' && r.dpt.startsWith('10.')))
|
|
414
|
+
|
|
415
|
+
const bestDateTime = knxPickBest(datetimeRows, 'datetime', [])
|
|
416
|
+
const baseTokens = bestDateTime ? knxDateTimeNormalizeTokens(bestDateTime.devicename || '') : []
|
|
417
|
+
|
|
418
|
+
const bestDate = knxPickBest(dateRows, 'date', baseTokens)
|
|
419
|
+
const bestTime = knxPickBest(timeRows, 'time', baseTokens)
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
dateTime: bestDateTime,
|
|
423
|
+
date: bestDate,
|
|
424
|
+
time: bestTime
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const knxAutoConfigureDateTimeNode = async (node, { updateDom = false, preferExistingServer = true, canApply = () => true } = {}) => {
|
|
429
|
+
try {
|
|
430
|
+
if (!node || !canApply()) return
|
|
431
|
+
|
|
432
|
+
const hasAnyGA = !!((node.gaDateTime || '').trim() || (node.gaDate || '').trim() || (node.gaTime || '').trim())
|
|
433
|
+
if (hasAnyGA) return
|
|
434
|
+
|
|
435
|
+
// If server already selected and it has ETS rows, reuse it.
|
|
436
|
+
const currentServerId = preferExistingServer ? (node.server || '') : ''
|
|
437
|
+
if (currentServerId && currentServerId !== '_ADD_') {
|
|
438
|
+
const rows = await knxFetchGroupAddresses(currentServerId)
|
|
439
|
+
if (rows.length > 0) {
|
|
440
|
+
const suggestions = knxSuggestFromCsv(rows)
|
|
441
|
+
return knxApplySuggestions(node, currentServerId, suggestions, { updateDom, canApply })
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Otherwise, select the first knxUltimate-config that has an ETS CSV imported (non-empty parsed GA list).
|
|
446
|
+
const configs = knxGetKnxUltimateConfigs()
|
|
447
|
+
if (configs.length === 0) return
|
|
448
|
+
|
|
449
|
+
// Fast path: config node already carries an ETS file/path in its `csv` property.
|
|
450
|
+
for (let i = 0; i < configs.length; i++) {
|
|
451
|
+
const cfg = configs[i]
|
|
452
|
+
const id = cfg && cfg.id ? cfg.id : null
|
|
453
|
+
const csvHint = cfg && typeof cfg.csv === 'string' ? cfg.csv.trim() : ''
|
|
454
|
+
if (!id || !csvHint) continue
|
|
455
|
+
const rows = await knxFetchGroupAddresses(id)
|
|
456
|
+
if (rows.length === 0) continue
|
|
457
|
+
const suggestions = knxSuggestFromCsv(rows)
|
|
458
|
+
return knxApplySuggestions(node, id, suggestions, { updateDom, canApply })
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const maxCandidates = Math.min(10, configs.length)
|
|
462
|
+
const checks = configs.slice(0, maxCandidates).map((cfg) => {
|
|
463
|
+
const id = cfg && cfg.id ? cfg.id : null
|
|
464
|
+
return knxFetchGroupAddresses(id).then((rows) => ({ id, rows }))
|
|
465
|
+
})
|
|
466
|
+
const results = await Promise.all(checks)
|
|
467
|
+
|
|
468
|
+
let selected = null
|
|
469
|
+
for (let i = 0; i < results.length; i++) {
|
|
470
|
+
if (results[i] && results[i].id && Array.isArray(results[i].rows) && results[i].rows.length > 0) {
|
|
471
|
+
selected = results[i]
|
|
472
|
+
break
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (!selected) return
|
|
476
|
+
|
|
477
|
+
const suggestions = knxSuggestFromCsv(selected.rows)
|
|
478
|
+
return knxApplySuggestions(node, selected.id, suggestions, { updateDom, canApply })
|
|
479
|
+
} catch (error) {
|
|
480
|
+
try { console.warn('knxUltimateDateTime auto-config failed', error) } catch (e) { /* ignore */ }
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const knxAutoConfigureDateTimeNodeForServer = async (node, serverId, { updateDom = false, overwrite = false, canApply = () => true } = {}) => {
|
|
485
|
+
try {
|
|
486
|
+
if (!node || !serverId || !canApply()) return
|
|
487
|
+
const rows = await knxFetchGroupAddresses(serverId)
|
|
488
|
+
if (!Array.isArray(rows) || rows.length === 0) return
|
|
489
|
+
const suggestions = knxSuggestFromCsv(rows)
|
|
490
|
+
return knxApplySuggestions(node, serverId, suggestions, { updateDom, overwrite, canApply })
|
|
491
|
+
} catch (error) {
|
|
492
|
+
try { console.warn('knxUltimateDateTime auto-config for server failed', error) } catch (e) { /* ignore */ }
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const knxApplySuggestions = (node, serverId, suggestions, { updateDom = false, overwrite = false, canApply = () => true } = {}) => {
|
|
497
|
+
// The Utility wrapper can replace this fragment while ETS requests are in
|
|
498
|
+
// flight. Never let a late response update its successor's fields.
|
|
499
|
+
if (!node || !serverId || !canApply()) return
|
|
500
|
+
if (!suggestions) return
|
|
501
|
+
|
|
502
|
+
// Avoid repeating the same automation multiple times.
|
|
503
|
+
if (node._knxDateTimeAutoConfigured === true && overwrite !== true) return
|
|
504
|
+
|
|
505
|
+
node.server = serverId
|
|
506
|
+
|
|
507
|
+
if (suggestions.dateTime && suggestions.dateTime.ga && (overwrite || !(node.gaDateTime || '').trim())) {
|
|
508
|
+
node.gaDateTime = suggestions.dateTime.ga
|
|
509
|
+
node.nameDateTime = suggestions.dateTime.devicename || ''
|
|
510
|
+
}
|
|
511
|
+
if (suggestions.date && suggestions.date.ga && (overwrite || !(node.gaDate || '').trim())) {
|
|
512
|
+
node.gaDate = suggestions.date.ga
|
|
513
|
+
node.nameDate = suggestions.date.devicename || ''
|
|
514
|
+
}
|
|
515
|
+
if (suggestions.time && suggestions.time.ga && (overwrite || !(node.gaTime || '').trim())) {
|
|
516
|
+
node.gaTime = suggestions.time.ga
|
|
517
|
+
node.nameTime = suggestions.time.devicename || ''
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
node._knxDateTimeAutoConfigured = true
|
|
521
|
+
node._knxDateTimeAutoConfiguredServer = serverId
|
|
522
|
+
|
|
523
|
+
if (updateDom) {
|
|
524
|
+
try {
|
|
525
|
+
$('#node-input-server').val(serverId).trigger('change')
|
|
526
|
+
if (node.gaDateTime) $('#node-input-gaDateTime').val(node.gaDateTime)
|
|
527
|
+
if (node.nameDateTime) $('#node-input-nameDateTime').val(node.nameDateTime)
|
|
528
|
+
if (node.gaDate) $('#node-input-gaDate').val(node.gaDate)
|
|
529
|
+
if (node.nameDate) $('#node-input-nameDate').val(node.nameDate)
|
|
530
|
+
if (node.gaTime) $('#node-input-gaTime').val(node.gaTime)
|
|
531
|
+
if (node.nameTime) $('#node-input-nameTime').val(node.nameTime)
|
|
532
|
+
} catch (error) { /* ignore */ }
|
|
533
|
+
} else {
|
|
534
|
+
try {
|
|
535
|
+
if (RED && RED.nodes && typeof RED.nodes.dirty === 'function') RED.nodes.dirty(true)
|
|
536
|
+
} catch (error) { /* ignore */ }
|
|
537
|
+
try { if (RED && RED.view && typeof RED.view.redraw === 'function') RED.view.redraw() } catch (error) { /* ignore */ }
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const knxDateTimeOptionalNumber = (toggle) => function (value) {
|
|
542
|
+
let enabled = this[toggle] === undefined || this[toggle] === true || this[toggle] === 'true';
|
|
543
|
+
// The wrapper validates the saved node while its draft is open. Read the
|
|
544
|
+
// active checkbox only in that case; unrelated nodes use their saved flag.
|
|
545
|
+
if (this._utilityEditor && typeof $ === 'function') {
|
|
546
|
+
const field = $('#node-input-' + toggle)
|
|
547
|
+
if (field.length) enabled = field.is(':checked')
|
|
548
|
+
}
|
|
549
|
+
return !enabled || RED.validators.number()(value)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
RED.nodes.registerType('knxUltimateDateTime', {
|
|
553
|
+
category: 'KNX Ultimate',
|
|
554
|
+
color: '#C7E9C0',
|
|
555
|
+
defaults: {
|
|
556
|
+
server: { type: 'knxUltimate-config', required: true },
|
|
557
|
+
name: { value: '' },
|
|
558
|
+
outputtopic: { value: '' },
|
|
559
|
+
gaDateTime: { value: '' },
|
|
560
|
+
nameDateTime: { value: '' },
|
|
561
|
+
dptDateTime: { value: '19.001' },
|
|
562
|
+
gaDate: { value: '' },
|
|
563
|
+
nameDate: { value: '' },
|
|
564
|
+
dptDate: { value: '11.001' },
|
|
565
|
+
gaTime: { value: '' },
|
|
566
|
+
nameTime: { value: '' },
|
|
567
|
+
dptTime: { value: '10.001' },
|
|
568
|
+
sendOnDeploy: { value: true },
|
|
569
|
+
sendOnDeployDelay: { value: 30, validate: knxDateTimeOptionalNumber('sendOnDeploy') },
|
|
570
|
+
periodicSend: { value: true },
|
|
571
|
+
periodicSendInterval: { value: 60, validate: knxDateTimeOptionalNumber('periodicSend') },
|
|
572
|
+
periodicSendUnit: { value: 'm' }
|
|
573
|
+
},
|
|
574
|
+
inputs: 0,
|
|
575
|
+
outputs: 0,
|
|
576
|
+
icon: 'node-knx-icon.svg',
|
|
577
|
+
label: function () {
|
|
578
|
+
return this.name || 'KNX DateTime'
|
|
579
|
+
},
|
|
580
|
+
paletteLabel: function () {
|
|
581
|
+
try {
|
|
582
|
+
return RED._('node-red-contrib-knx-ultimate/knxUltimateDateTime:knxUltimateDateTime.paletteLabel') || 'DateTime'
|
|
583
|
+
} catch (error) {
|
|
584
|
+
return 'DateTime'
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
onadd: function () {
|
|
588
|
+
// Auto-select a KNX gateway (first one with ETS CSV imported) and prefill coherent group addresses.
|
|
589
|
+
// This runs when the node is dragged from the palette. Best-effort, non-blocking.
|
|
590
|
+
const node = this
|
|
591
|
+
setTimeout(() => {
|
|
592
|
+
knxAutoConfigureDateTimeNode(node, {
|
|
593
|
+
updateDom: false,
|
|
594
|
+
preferExistingServer: true,
|
|
595
|
+
canApply: () => node.utilityType === 'datetime'
|
|
596
|
+
})
|
|
597
|
+
}, 50)
|
|
598
|
+
},
|
|
599
|
+
button: {
|
|
600
|
+
enabled: function () {
|
|
601
|
+
return !this.changed
|
|
602
|
+
},
|
|
603
|
+
visible: function () {
|
|
604
|
+
return true
|
|
605
|
+
},
|
|
606
|
+
onclick: function () {
|
|
607
|
+
const node = this
|
|
608
|
+
$.ajax({
|
|
609
|
+
type: 'POST',
|
|
610
|
+
url: 'knxUltimateUtility/sendNow',
|
|
611
|
+
data: { id: node.id },
|
|
612
|
+
success: function (response) {
|
|
613
|
+
const queued = response && response.queued === true
|
|
614
|
+
const message = queued
|
|
615
|
+
? (RED._('node-red-contrib-knx-ultimate/knxUltimateDateTime:knxUltimateDateTime.notifyQueued') || 'Queued (gateway not connected yet)')
|
|
616
|
+
: (RED._('node-red-contrib-knx-ultimate/knxUltimateDateTime:knxUltimateDateTime.notifySent') || 'Sent to KNX')
|
|
617
|
+
RED.notify(message, 'success')
|
|
618
|
+
},
|
|
619
|
+
error: function (xhr) {
|
|
620
|
+
let message = 'Error'
|
|
621
|
+
try {
|
|
622
|
+
if (xhr && xhr.responseJSON && xhr.responseJSON.error) message = xhr.responseJSON.error
|
|
623
|
+
} catch (error) { /* ignore */ }
|
|
624
|
+
RED.notify(message, 'error')
|
|
625
|
+
}
|
|
626
|
+
})
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
oneditprepare: function () {
|
|
630
|
+
const node = this
|
|
631
|
+
if (node._knxDateTimeEditorSession) node._knxDateTimeEditorSession.active = false
|
|
632
|
+
const editorSession = { active: true }
|
|
633
|
+
node._knxDateTimeEditorSession = editorSession
|
|
634
|
+
const $knxServerInput = $('#node-input-server')
|
|
635
|
+
const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none'])
|
|
636
|
+
const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map())
|
|
637
|
+
|
|
638
|
+
try { RED.sidebar.show('help') } catch (error) { /* ignore */ }
|
|
639
|
+
|
|
640
|
+
const resolveKnxServerValue = () => {
|
|
641
|
+
const domValue = $knxServerInput.val()
|
|
642
|
+
if (domValue !== undefined && domValue !== null && domValue !== '') return domValue
|
|
643
|
+
if (node.server !== undefined && node.server !== null && node.server !== '') return node.server
|
|
644
|
+
return ''
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const addressFields = ['gaDateTime', 'nameDateTime', 'dptDateTime', 'gaDate', 'nameDate', 'dptDate', 'gaTime', 'nameTime', 'dptTime']
|
|
648
|
+
const addressSnapshot = () => JSON.stringify(addressFields.map((field) => $('#node-input-' + field).val() || ''))
|
|
649
|
+
|
|
650
|
+
const hasKnxServerSelected = () => {
|
|
651
|
+
const val = resolveKnxServerValue()
|
|
652
|
+
return !(val === undefined || val === null || KNX_EMPTY_VALUES.has(String(val)))
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const fetchGroupAddresses = (serverId) => {
|
|
656
|
+
if (!serverId) return Promise.resolve([])
|
|
657
|
+
if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId))
|
|
658
|
+
return new Promise((resolve) => {
|
|
659
|
+
$.getJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
|
|
660
|
+
const list = Array.isArray(data) ? data : []
|
|
661
|
+
KNX_GA_CACHE.set(serverId, list)
|
|
662
|
+
resolve(list)
|
|
663
|
+
}).fail(() => resolve([]))
|
|
664
|
+
})
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const setupGA = (gaSelector, nameSelector, dptSelector, allowedPrefixes, defaultDpt) => {
|
|
668
|
+
const $gaInput = $(gaSelector)
|
|
669
|
+
const $nameInput = $(nameSelector)
|
|
670
|
+
const $dptInput = $(dptSelector)
|
|
671
|
+
if (!$gaInput.length) return
|
|
672
|
+
|
|
673
|
+
if ($dptInput.length && (!$dptInput.val() || $dptInput.val() === '')) $dptInput.val(defaultDpt)
|
|
674
|
+
|
|
675
|
+
const sourceFn = (request, response) => {
|
|
676
|
+
if (!hasKnxServerSelected()) {
|
|
677
|
+
response([])
|
|
678
|
+
return
|
|
679
|
+
}
|
|
680
|
+
const serverId = resolveKnxServerValue()
|
|
681
|
+
fetchGroupAddresses(serverId).then((data) => {
|
|
682
|
+
if (!editorSession.active || resolveKnxServerValue() !== serverId) return
|
|
683
|
+
const items = []
|
|
684
|
+
data.forEach((entry) => {
|
|
685
|
+
const dpt = entry.dpt || ''
|
|
686
|
+
const allowed = allowedPrefixes.some((prefix) => prefix === '' || dpt.startsWith(prefix))
|
|
687
|
+
if (!allowed) return
|
|
688
|
+
const devName = entry.devicename || ''
|
|
689
|
+
const searchStr = `${entry.ga} (${devName}) DPT${dpt}`
|
|
690
|
+
if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return
|
|
691
|
+
items.push({
|
|
692
|
+
label: `${entry.ga} # ${devName} # ${dpt}`,
|
|
693
|
+
value: entry.ga,
|
|
694
|
+
ga: entry.ga
|
|
695
|
+
})
|
|
696
|
+
})
|
|
697
|
+
response(items)
|
|
698
|
+
})
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if ($gaInput.data('knx-ga-initialised')) {
|
|
702
|
+
$gaInput.autocomplete('option', 'source', sourceFn)
|
|
703
|
+
} else {
|
|
704
|
+
$gaInput
|
|
705
|
+
.autocomplete({
|
|
706
|
+
minLength: 0,
|
|
707
|
+
source: sourceFn,
|
|
708
|
+
select: (event, ui) => {
|
|
709
|
+
let deviceName = ''
|
|
710
|
+
try {
|
|
711
|
+
deviceName = ui.item.label.split('#')[1].trim()
|
|
712
|
+
} catch (error) { deviceName = '' }
|
|
713
|
+
if ($nameInput.length) {
|
|
714
|
+
$nameInput.val(deviceName || '')
|
|
715
|
+
}
|
|
716
|
+
try {
|
|
717
|
+
const parts = ui.item.label.split('#')
|
|
718
|
+
const dptFromLabel = parts.length >= 3 ? parts[2].trim() : ''
|
|
719
|
+
$dptInput.val(dptFromLabel || defaultDpt)
|
|
720
|
+
} catch (error) {
|
|
721
|
+
$dptInput.val(defaultDpt)
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
})
|
|
725
|
+
.on('focus.knxUltimateDateTime click.knxUltimateDateTime', function () {
|
|
726
|
+
const currentValue = $(this).val() || ''
|
|
727
|
+
try { $(this).autocomplete('search', `${currentValue} exactmatch`) } catch (error) { /* ignore */ }
|
|
728
|
+
})
|
|
729
|
+
$gaInput.data('knx-ga-initialised', true)
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
try {
|
|
733
|
+
if (hasKnxServerSelected()) {
|
|
734
|
+
const srv = RED.nodes.node(resolveKnxServerValue())
|
|
735
|
+
if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id)
|
|
736
|
+
}
|
|
737
|
+
} catch (error) { /* ignore */ }
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const refresh = () => {
|
|
741
|
+
setupGA('#node-input-gaDateTime', '#node-input-nameDateTime', '#node-input-dptDateTime', ['19.'], '19.001')
|
|
742
|
+
setupGA('#node-input-gaDate', '#node-input-nameDate', '#node-input-dptDate', ['11.'], '11.001')
|
|
743
|
+
setupGA('#node-input-gaTime', '#node-input-nameTime', '#node-input-dptTime', ['10.'], '10.001')
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
$knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
|
|
747
|
+
KNX_GA_CACHE.clear()
|
|
748
|
+
refresh()
|
|
749
|
+
try {
|
|
750
|
+
const sid = resolveKnxServerValue()
|
|
751
|
+
if (!sid || sid === '_ADD_') return
|
|
752
|
+
const hasAnyGAInUi = !!(
|
|
753
|
+
($('#node-input-gaDateTime').val() || '').toString().trim() ||
|
|
754
|
+
($('#node-input-gaDate').val() || '').toString().trim() ||
|
|
755
|
+
($('#node-input-gaTime').val() || '').toString().trim()
|
|
756
|
+
)
|
|
757
|
+
// If the current values were auto-filled, allow overwrite when server changes.
|
|
758
|
+
const shouldOverwrite = node._knxDateTimeAutoConfigured === true && node._knxDateTimeAutoConfiguredServer && node._knxDateTimeAutoConfiguredServer !== sid
|
|
759
|
+
const beforeRequest = addressSnapshot()
|
|
760
|
+
// Otherwise fill only empty fields (do not override manual config).
|
|
761
|
+
knxAutoConfigureDateTimeNodeForServer(node, sid, {
|
|
762
|
+
updateDom: true,
|
|
763
|
+
overwrite: shouldOverwrite || !hasAnyGAInUi,
|
|
764
|
+
canApply: () => editorSession.active && resolveKnxServerValue() === sid && addressSnapshot() === beforeRequest
|
|
765
|
+
})
|
|
766
|
+
} catch (error) { /* ignore */ }
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
refresh()
|
|
770
|
+
|
|
771
|
+
// Auto-select server + fill GAs only for a brand new node (all GAs empty).
|
|
772
|
+
const initialServer = resolveKnxServerValue()
|
|
773
|
+
const initialAddresses = addressSnapshot()
|
|
774
|
+
editorSession.timer = setTimeout(() => {
|
|
775
|
+
knxAutoConfigureDateTimeNode(node, {
|
|
776
|
+
updateDom: true,
|
|
777
|
+
preferExistingServer: true,
|
|
778
|
+
canApply: () => editorSession.active && resolveKnxServerValue() === initialServer && addressSnapshot() === initialAddresses
|
|
779
|
+
})
|
|
780
|
+
}, 50)
|
|
781
|
+
|
|
782
|
+
const syncUi = () => {
|
|
783
|
+
const sendOnDeploy = $('#node-input-sendOnDeploy').is(':checked')
|
|
784
|
+
$('.knx-datetime-deploy-options').toggle(sendOnDeploy)
|
|
785
|
+
const periodicSend = $('#node-input-periodicSend').is(':checked')
|
|
786
|
+
$('.knx-datetime-periodic-options').toggle(periodicSend)
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
$('#node-input-sendOnDeploy').on('change.knxUtilityProfile', syncUi)
|
|
790
|
+
$('#node-input-periodicSend').on('change.knxUtilityProfile', syncUi)
|
|
791
|
+
syncUi()
|
|
792
|
+
},
|
|
793
|
+
oneditcancel: function () {
|
|
794
|
+
const session = this._knxDateTimeEditorSession
|
|
795
|
+
if (session) {
|
|
796
|
+
session.active = false
|
|
797
|
+
clearTimeout(session.timer)
|
|
798
|
+
}
|
|
799
|
+
$('#node-input-server').off('.knxUtilityProfile')
|
|
800
|
+
$('#node-input-sendOnDeploy').off('.knxUtilityProfile')
|
|
801
|
+
$('#node-input-periodicSend').off('.knxUtilityProfile')
|
|
802
|
+
}
|
|
803
|
+
})
|
|
804
|
+
},
|
|
805
|
+
"watchdog": function (RED) {
|
|
806
|
+
const utilityFieldValue = (node, key) => {
|
|
807
|
+
if (node._utilityEditor && typeof $ === 'function') {
|
|
808
|
+
const field = $('#node-input-' + key);
|
|
809
|
+
if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
|
|
810
|
+
}
|
|
811
|
+
return node[key];
|
|
812
|
+
};
|
|
813
|
+
const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
|
|
814
|
+
if (!enabled(this)) return true;
|
|
815
|
+
const numeric = Number(value);
|
|
816
|
+
return value !== null && value !== undefined && String(value).trim() !== '' &&
|
|
817
|
+
Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
RED.nodes.registerType('knxUltimateWatchDog', {
|
|
821
|
+
category: "KNX Ultimate",
|
|
822
|
+
color: '#C7E9C0',
|
|
823
|
+
defaults: {
|
|
824
|
+
server: { type: "knxUltimate-config", required: true },
|
|
825
|
+
topic: { value: "12/0/0" },
|
|
826
|
+
maxRetry: { value: 6, validate: utilityNumber(0, true) }, // Zero reports failure on the first unsuccessful check.
|
|
827
|
+
retryInterval: { value: 10, validate: utilityNumber(0.001) },
|
|
828
|
+
name: { value: "" },
|
|
829
|
+
autoStart: { value: true },
|
|
830
|
+
listenToKnxUltimateNodeErrors: { value: true },
|
|
831
|
+
checkLevel: { value: "Ethernet" }
|
|
832
|
+
},
|
|
833
|
+
inputs: 1,
|
|
834
|
+
outputs: 1,
|
|
835
|
+
outputLabels: ["Output"],
|
|
836
|
+
icon: "node-watchdog-icon.svg",
|
|
837
|
+
label: function () {
|
|
838
|
+
return ((this.name || "KNX Watchdog") + " " + (this.checkLevel == "Ethernet" ? "Gateway IP" : this.topic));
|
|
839
|
+
},
|
|
840
|
+
paletteLabel: "KNX WatchDog",
|
|
841
|
+
oneditprepare: function () {
|
|
842
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
843
|
+
const editorSession = { active: true };
|
|
844
|
+
this._knxUtilityProfileSession = editorSession;
|
|
845
|
+
// Ignore responses belonging to a closed editor or a previous gateway.
|
|
846
|
+
const profileGetJSON = (url, callback) => {
|
|
847
|
+
const serverId = $('#node-input-server').val();
|
|
848
|
+
return $.getJSON(url, (data) => {
|
|
849
|
+
if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
|
|
850
|
+
});
|
|
851
|
+
};
|
|
852
|
+
const node = this;
|
|
853
|
+
const $knxServerInput = $("#node-input-server");
|
|
854
|
+
const $gaInput = $("#node-input-topic");
|
|
855
|
+
const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none']);
|
|
856
|
+
|
|
857
|
+
// Go to the help panel
|
|
858
|
+
try {
|
|
859
|
+
RED.sidebar.show("help");
|
|
860
|
+
} catch (error) { }
|
|
861
|
+
|
|
862
|
+
$("#advancedOptionsAccordion").accordion({
|
|
863
|
+
header: "h3",
|
|
864
|
+
heightStyle: "content",
|
|
865
|
+
collapsible: true,
|
|
866
|
+
active: false
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map());
|
|
870
|
+
|
|
871
|
+
const resolveKnxServerValue = () => {
|
|
872
|
+
const domValue = $knxServerInput.val();
|
|
873
|
+
if (domValue !== undefined && domValue !== null) {
|
|
874
|
+
return KNX_EMPTY_VALUES.has(String(domValue)) ? '' : domValue;
|
|
875
|
+
}
|
|
876
|
+
if (node.server !== undefined && node.server !== null && !KNX_EMPTY_VALUES.has(String(node.server))) {
|
|
877
|
+
return node.server;
|
|
878
|
+
}
|
|
879
|
+
return '';
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
const fetchGroupAddresses = (serverId) => {
|
|
883
|
+
if (!serverId) return Promise.resolve([]);
|
|
884
|
+
if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId));
|
|
885
|
+
return new Promise((resolve) => {
|
|
886
|
+
profileGetJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
|
|
887
|
+
const list = Array.isArray(data) ? data : [];
|
|
888
|
+
KNX_GA_CACHE.set(serverId, list);
|
|
889
|
+
resolve(list);
|
|
890
|
+
}).fail(() => resolve([]));
|
|
891
|
+
});
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
const ensureGaAutocomplete = () => {
|
|
895
|
+
const serverId = resolveKnxServerValue();
|
|
896
|
+
if (!serverId) {
|
|
897
|
+
if ($gaInput.data('ui-autocomplete')) {
|
|
898
|
+
$gaInput.autocomplete('disable');
|
|
899
|
+
}
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const sourceFn = (request, response) => {
|
|
904
|
+
fetchGroupAddresses(serverId).then((data) => {
|
|
905
|
+
if (!editorSession.active || resolveKnxServerValue() !== serverId) return;
|
|
906
|
+
const items = [];
|
|
907
|
+
(data || []).forEach((entry) => {
|
|
908
|
+
const dpt = typeof entry.dpt === 'string' ? entry.dpt : '';
|
|
909
|
+
if (!dpt.startsWith('1.')) return; // Watchdog only accepts boolean GAs
|
|
910
|
+
const devName = entry.devicename || '';
|
|
911
|
+
const searchStr = `${entry.ga} (${devName}) DPT${dpt}`;
|
|
912
|
+
if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return;
|
|
913
|
+
items.push({
|
|
914
|
+
label: `${entry.ga} # ${devName} # ${dpt}`,
|
|
915
|
+
value: entry.ga
|
|
916
|
+
});
|
|
917
|
+
});
|
|
918
|
+
response(items);
|
|
919
|
+
});
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
if ($gaInput.data('knx-watchdog-ga')) {
|
|
923
|
+
$gaInput.autocomplete('option', 'source', sourceFn);
|
|
924
|
+
$gaInput.autocomplete('enable');
|
|
925
|
+
} else {
|
|
926
|
+
$gaInput.autocomplete({
|
|
927
|
+
minLength: 0,
|
|
928
|
+
source: sourceFn,
|
|
929
|
+
select: function (event, ui) {
|
|
930
|
+
event.preventDefault();
|
|
931
|
+
$(this).val(ui.item.value);
|
|
932
|
+
const parts = ui.item.label.split('#');
|
|
933
|
+
let deviceName = (parts[1] || '').trim().replace(/^\)/, '').trim();
|
|
934
|
+
if (deviceName.indexOf('/') > -1) {
|
|
935
|
+
deviceName = deviceName.split('/').pop().trim();
|
|
936
|
+
}
|
|
937
|
+
const $nameInput = $('#node-input-name');
|
|
938
|
+
if (deviceName && $nameInput.length) {
|
|
939
|
+
$nameInput.val(deviceName);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}).on('focus.knxUltimateWatchDog click.knxUltimateWatchDog', function () {
|
|
943
|
+
const currentValue = $(this).val() || '';
|
|
944
|
+
try {
|
|
945
|
+
$(this).autocomplete('search', currentValue ? `${currentValue} exactmatch` : '');
|
|
946
|
+
} catch (error) { }
|
|
947
|
+
});
|
|
948
|
+
$gaInput.data('knx-watchdog-ga', true);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
try {
|
|
952
|
+
const srv = RED.nodes.node(serverId);
|
|
953
|
+
if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id);
|
|
954
|
+
} catch (error) { }
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
$knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
|
|
958
|
+
KNX_GA_CACHE.clear();
|
|
959
|
+
ensureGaAutocomplete();
|
|
960
|
+
});
|
|
961
|
+
ensureGaAutocomplete();
|
|
962
|
+
|
|
963
|
+
const syncDivHost = () => {
|
|
964
|
+
const level = $("#node-input-checkLevel").val() || node.checkLevel || "Ethernet";
|
|
965
|
+
if (level === "Ethernet") {
|
|
966
|
+
$("#divHost").hide();
|
|
967
|
+
} else {
|
|
968
|
+
$("#divHost").show();
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
$("#node-input-checkLevel").on('change.knxUtilityProfile', function () {
|
|
973
|
+
syncDivHost();
|
|
974
|
+
});
|
|
975
|
+
syncDivHost();
|
|
976
|
+
},
|
|
977
|
+
oneditcancel: function () {
|
|
978
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
979
|
+
$('#node-input-server').off('.knxUtilityProfile');
|
|
980
|
+
try { RED.sidebar.show('info'); } catch (error) { }
|
|
981
|
+
},
|
|
982
|
+
oneditsave: function () {
|
|
983
|
+
// Return to the info tab
|
|
984
|
+
try {
|
|
985
|
+
RED.sidebar.show("info");
|
|
986
|
+
} catch (error) { }
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
})
|
|
992
|
+
},
|
|
993
|
+
"globalcontext": function (RED) {
|
|
994
|
+
const utilityFieldValue = (node, key) => {
|
|
995
|
+
if (node._utilityEditor && typeof $ === 'function') {
|
|
996
|
+
const field = $('#node-input-' + key);
|
|
997
|
+
if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
|
|
998
|
+
}
|
|
999
|
+
return node[key];
|
|
1000
|
+
};
|
|
1001
|
+
const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
|
|
1002
|
+
if (!enabled(this)) return true;
|
|
1003
|
+
const numeric = Number(value);
|
|
1004
|
+
return value !== null && value !== undefined && String(value).trim() !== '' &&
|
|
1005
|
+
Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
RED.nodes.registerType('knxUltimateGlobalContext', {
|
|
1009
|
+
category: "KNX Ultimate",
|
|
1010
|
+
color: '#C7E9C0',
|
|
1011
|
+
defaults: {
|
|
1012
|
+
server: { type: "knxUltimate-config", required: true },
|
|
1013
|
+
name: { value: "KNXGlobalContext", validate: function (value) { return /^[a-zA-Z]+$/.test(value || ''); } },
|
|
1014
|
+
exposeAsVariable: { value: "exposeAsVariableREADWRITE", required: false },
|
|
1015
|
+
writeExecutionInterval: { value: 1000, validate: utilityNumber(1) },
|
|
1016
|
+
contextStorage: { value: "" }
|
|
1017
|
+
},
|
|
1018
|
+
inputs: 0,
|
|
1019
|
+
outputs: 0,
|
|
1020
|
+
icon: "node-knx-icon.svg",
|
|
1021
|
+
label: function () {
|
|
1022
|
+
return (this.name);
|
|
1023
|
+
},
|
|
1024
|
+
paletteLabel: "KNX Global Context",
|
|
1025
|
+
oneditprepare: function () {
|
|
1026
|
+
// Go to the help panel
|
|
1027
|
+
try {
|
|
1028
|
+
RED.sidebar.show("help");
|
|
1029
|
+
} catch (error) { }
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
},
|
|
1034
|
+
oneditsave: function () {
|
|
1035
|
+
// Return to the info tab
|
|
1036
|
+
try {
|
|
1037
|
+
RED.sidebar.show("info");
|
|
1038
|
+
} catch (error) { }
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
},
|
|
1043
|
+
oneditcancel: function () {
|
|
1044
|
+
// Return to the info tab
|
|
1045
|
+
try {
|
|
1046
|
+
RED.sidebar.show("info");
|
|
1047
|
+
} catch (error) { }
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
}
|
|
1051
|
+
})
|
|
1052
|
+
},
|
|
1053
|
+
"logger": function (RED) {
|
|
1054
|
+
const utilityFieldValue = (node, key) => {
|
|
1055
|
+
if (node._utilityEditor && typeof $ === 'function') {
|
|
1056
|
+
const field = $('#node-input-' + key);
|
|
1057
|
+
if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
|
|
1058
|
+
}
|
|
1059
|
+
return node[key];
|
|
1060
|
+
};
|
|
1061
|
+
const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
|
|
1062
|
+
if (!enabled(this)) return true;
|
|
1063
|
+
const numeric = Number(value);
|
|
1064
|
+
return value !== null && value !== undefined && String(value).trim() !== '' &&
|
|
1065
|
+
Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
RED.nodes.registerType('knxUltimateLogger', {
|
|
1069
|
+
category: "KNX Ultimate",
|
|
1070
|
+
color: '#C7E9C0',
|
|
1071
|
+
defaults: {
|
|
1072
|
+
server: { type: "knxUltimate-config", required: true },
|
|
1073
|
+
topic: { value: "" },
|
|
1074
|
+
intervalCreateETSXML: { value: 15, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'autoStartTimerCreateETSXML') !== false) },
|
|
1075
|
+
name: { value: "" },
|
|
1076
|
+
autoStartTimerCreateETSXML: { value: true },
|
|
1077
|
+
maxRowsInETSXML: { value: 0, validate: utilityNumber(0, true) },
|
|
1078
|
+
saveMode: { value: "emit" },
|
|
1079
|
+
filePath: { value: "" },
|
|
1080
|
+
autoStartTimerTelegramCounter: { value: false },
|
|
1081
|
+
intervalTelegramCount: { value: 60, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'autoStartTimerTelegramCounter') === true) }
|
|
1082
|
+
|
|
1083
|
+
},
|
|
1084
|
+
inputs: 1,
|
|
1085
|
+
outputs: 2,
|
|
1086
|
+
outputLabels: ["ETS Diag file", "Telegram Count"],
|
|
1087
|
+
icon: "node-logger-icon.svg",
|
|
1088
|
+
label: function () {
|
|
1089
|
+
return ((this.name || "KNX Logger") + " " + this.topic);
|
|
1090
|
+
},
|
|
1091
|
+
paletteLabel: "KNX Logger",
|
|
1092
|
+
oneditprepare: function () {
|
|
1093
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
1094
|
+
const editorSession = { active: true };
|
|
1095
|
+
this._knxUtilityProfileSession = editorSession;
|
|
1096
|
+
// Ignore responses belonging to a closed editor or a previous gateway.
|
|
1097
|
+
const profileGetJSON = (url, callback) => {
|
|
1098
|
+
const serverId = $('#node-input-server').val();
|
|
1099
|
+
return $.getJSON(url, (data) => {
|
|
1100
|
+
if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
|
|
1101
|
+
});
|
|
1102
|
+
};
|
|
1103
|
+
// Go to the help panel
|
|
1104
|
+
try {
|
|
1105
|
+
RED.sidebar.show("help");
|
|
1106
|
+
} catch (error) { }
|
|
1107
|
+
|
|
1108
|
+
$("#mlxETSFileAccordion").accordion({
|
|
1109
|
+
header: "h3",
|
|
1110
|
+
heightStyle: "content",
|
|
1111
|
+
collapsible: true,
|
|
1112
|
+
active: false
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
const nodeId = this.id;
|
|
1116
|
+
const resolveAdminRoot = () => {
|
|
1117
|
+
const raw = (RED.settings && typeof RED.settings.httpAdminRoot === "string") ? RED.settings.httpAdminRoot : "/";
|
|
1118
|
+
const trimmed = String(raw || "/").trim();
|
|
1119
|
+
if (trimmed === "" || trimmed === "/") return "";
|
|
1120
|
+
return "/" + trimmed.replace(/^\/+|\/+$/g, "");
|
|
1121
|
+
};
|
|
1122
|
+
const resolveAccessToken = () => {
|
|
1123
|
+
try {
|
|
1124
|
+
const tokens = (RED.settings && typeof RED.settings.get === "function") ? RED.settings.get("auth-tokens") : null;
|
|
1125
|
+
const token = tokens && typeof tokens.access_token === "string" ? tokens.access_token.trim() : "";
|
|
1126
|
+
return token;
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
return "";
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
const toggleFilePath = () => {
|
|
1133
|
+
const isEmitSave = $("#node-input-saveMode").val() === "emit_save";
|
|
1134
|
+
const $filePathRow = $("#knx-logger-filePath-row");
|
|
1135
|
+
if (isEmitSave) {
|
|
1136
|
+
$filePathRow.show();
|
|
1137
|
+
} else {
|
|
1138
|
+
$filePathRow.hide();
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
$("#node-input-saveMode").on("change.knxUtilityProfile", toggleFilePath);
|
|
1142
|
+
|
|
1143
|
+
const currentFilePath = this.filePath || "";
|
|
1144
|
+
$("#node-input-filePath").val(currentFilePath);
|
|
1145
|
+
toggleFilePath();
|
|
1146
|
+
|
|
1147
|
+
$("#knx-logger-downloadButton").on("click.knxUtilityProfile", function (evt) {
|
|
1148
|
+
evt.preventDefault();
|
|
1149
|
+
const filePathVal = $("#node-input-filePath").val() || "";
|
|
1150
|
+
if (!filePathVal) {
|
|
1151
|
+
try {
|
|
1152
|
+
const msg = (RED._ && RED._("node-red-contrib-knx-ultimate/knxUltimateLogger:knxUltimateLogger.noFilePath")) || "File path is empty";
|
|
1153
|
+
RED.notify(msg, "warning");
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
alert("File path is empty");
|
|
1156
|
+
}
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const adminRoot = resolveAdminRoot();
|
|
1160
|
+
const targetBase = adminRoot + "/knxUltimateUtility/logger/download";
|
|
1161
|
+
const params = new URLSearchParams();
|
|
1162
|
+
if (nodeId) params.set("nodeId", nodeId);
|
|
1163
|
+
params.set("_", String(Date.now()));
|
|
1164
|
+
const accessToken = resolveAccessToken();
|
|
1165
|
+
if (accessToken) params.set("access_token", accessToken);
|
|
1166
|
+
const target = targetBase + "?" + params.toString();
|
|
1167
|
+
const wnd = window.open(target, "_blank", "noopener,noreferrer");
|
|
1168
|
+
try { if (wnd && typeof wnd.focus === "function") wnd.focus(); } catch (e) { }
|
|
1169
|
+
});
|
|
1170
|
+
},
|
|
1171
|
+
oneditcancel: function () {
|
|
1172
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
1173
|
+
$('#node-input-server').off('.knxUtilityProfile');
|
|
1174
|
+
try { RED.sidebar.show('info'); } catch (error) { }
|
|
1175
|
+
},
|
|
1176
|
+
oneditsave: function () {
|
|
1177
|
+
// Return to the info tab
|
|
1178
|
+
try {
|
|
1179
|
+
RED.sidebar.show("info");
|
|
1180
|
+
} catch (error) { }
|
|
1181
|
+
|
|
1182
|
+
this.filePath = $("#node-input-filePath").val() || "";
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
})
|
|
1188
|
+
},
|
|
1189
|
+
"staircase": function (RED) {
|
|
1190
|
+
const utilityFieldValue = (node, key) => {
|
|
1191
|
+
if (node._utilityEditor && typeof $ === 'function') {
|
|
1192
|
+
const field = $('#node-input-' + key);
|
|
1193
|
+
if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
|
|
1194
|
+
}
|
|
1195
|
+
return node[key];
|
|
1196
|
+
};
|
|
1197
|
+
const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
|
|
1198
|
+
if (!enabled(this)) return true;
|
|
1199
|
+
const numeric = Number(value);
|
|
1200
|
+
return value !== null && value !== undefined && String(value).trim() !== '' &&
|
|
1201
|
+
Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
RED.nodes.registerType('knxUltimateStaircase', {
|
|
1205
|
+
category: 'KNX Ultimate',
|
|
1206
|
+
color: '#C7E9C0',
|
|
1207
|
+
defaults: {
|
|
1208
|
+
server: { type: 'knxUltimate-config', required: true },
|
|
1209
|
+
name: { value: '' },
|
|
1210
|
+
outputtopic: { value: '' },
|
|
1211
|
+
gaTrigger: { value: '', required: true },
|
|
1212
|
+
nameTrigger: { value: '' },
|
|
1213
|
+
dptTrigger: { value: '1.001' },
|
|
1214
|
+
gaOutput: { value: '', required: true },
|
|
1215
|
+
nameOutput: { value: '' },
|
|
1216
|
+
dptOutput: { value: '1.001' },
|
|
1217
|
+
gaStatus: { value: '' },
|
|
1218
|
+
nameStatus: { value: '' },
|
|
1219
|
+
dptStatus: { value: '1.001' },
|
|
1220
|
+
gaOverride: { value: '' },
|
|
1221
|
+
nameOverride: { value: '' },
|
|
1222
|
+
dptOverride: { value: '1.001' },
|
|
1223
|
+
gaBlock: { value: '' },
|
|
1224
|
+
nameBlock: { value: '' },
|
|
1225
|
+
dptBlock: { value: '1.001' },
|
|
1226
|
+
timerSeconds: { value: 120, required: true, validate: utilityNumber(1) },
|
|
1227
|
+
extendMode: { value: 'restart' },
|
|
1228
|
+
triggerOffCancels: { value: 'yes' },
|
|
1229
|
+
preWarnEnable: { value: false },
|
|
1230
|
+
preWarnSeconds: { value: 15, validate: utilityNumber(0, false, (node) => utilityFieldValue(node, 'preWarnEnable') === true) },
|
|
1231
|
+
preWarnMode: { value: 'status' },
|
|
1232
|
+
preWarnFlashMs: { value: 400, validate: utilityNumber(100, false, (node) => utilityFieldValue(node, 'preWarnEnable') === true && utilityFieldValue(node, 'preWarnMode') === 'flash') },
|
|
1233
|
+
blockAction: { value: 'off' },
|
|
1234
|
+
emitEvents: { value: false }
|
|
1235
|
+
},
|
|
1236
|
+
inputs: 1,
|
|
1237
|
+
outputs: 1,
|
|
1238
|
+
icon: 'node-knx-icon.svg',
|
|
1239
|
+
label: function () {
|
|
1240
|
+
return this.name || 'KNX Staircase';
|
|
1241
|
+
},
|
|
1242
|
+
paletteLabel: 'KNX Staircase',
|
|
1243
|
+
oneditprepare: function () {
|
|
1244
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
1245
|
+
const editorSession = { active: true };
|
|
1246
|
+
this._knxUtilityProfileSession = editorSession;
|
|
1247
|
+
// Ignore responses belonging to a closed editor or a previous gateway.
|
|
1248
|
+
const profileGetJSON = (url, callback) => {
|
|
1249
|
+
const serverId = $('#node-input-server').val();
|
|
1250
|
+
return $.getJSON(url, (data) => {
|
|
1251
|
+
if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
|
|
1252
|
+
});
|
|
1253
|
+
};
|
|
1254
|
+
const node = this;
|
|
1255
|
+
const $knxServerInput = $('#node-input-server');
|
|
1256
|
+
try { RED.sidebar.show('help'); } catch (error) { /* ignore */ }
|
|
1257
|
+
const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none']);
|
|
1258
|
+
|
|
1259
|
+
const resolveKnxServerValue = () => {
|
|
1260
|
+
const domValue = $knxServerInput.val();
|
|
1261
|
+
if (domValue !== undefined && domValue !== null) return KNX_EMPTY_VALUES.has(String(domValue)) ? '' : domValue;
|
|
1262
|
+
if (node.server !== undefined && node.server !== null && node.server !== '') return node.server;
|
|
1263
|
+
return '';
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
const hasKnxServerSelected = () => {
|
|
1267
|
+
const val = resolveKnxServerValue();
|
|
1268
|
+
return !(val === undefined || val === null || KNX_EMPTY_VALUES.has(String(val)));
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map());
|
|
1272
|
+
|
|
1273
|
+
const fetchGroupAddresses = (serverId) => {
|
|
1274
|
+
if (!serverId) return Promise.resolve([]);
|
|
1275
|
+
if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId));
|
|
1276
|
+
return new Promise((resolve) => {
|
|
1277
|
+
profileGetJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
|
|
1278
|
+
const list = Array.isArray(data) ? data : [];
|
|
1279
|
+
KNX_GA_CACHE.set(serverId, list);
|
|
1280
|
+
resolve(list);
|
|
1281
|
+
}).fail(() => resolve([]));
|
|
1282
|
+
});
|
|
1283
|
+
};
|
|
1284
|
+
|
|
1285
|
+
const getGroupAddress = (gaSelector, nameSelector, dptSelector, prefixes) => {
|
|
1286
|
+
const $gaInput = $(gaSelector);
|
|
1287
|
+
const $nameInput = $(nameSelector);
|
|
1288
|
+
const $dptInput = $(dptSelector);
|
|
1289
|
+
if (!$gaInput.length) return;
|
|
1290
|
+
|
|
1291
|
+
const ensureAutocomplete = () => {
|
|
1292
|
+
const sourceFn = (request, response) => {
|
|
1293
|
+
if (!hasKnxServerSelected()) {
|
|
1294
|
+
response([]);
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
const serverId = resolveKnxServerValue();
|
|
1298
|
+
fetchGroupAddresses(serverId).then((data) => {
|
|
1299
|
+
if (!editorSession.active || resolveKnxServerValue() !== serverId) return;
|
|
1300
|
+
const items = [];
|
|
1301
|
+
data.forEach((entry) => {
|
|
1302
|
+
const dpt = entry.dpt || '';
|
|
1303
|
+
const allowed = prefixes.some((prefix) => prefix === '' || dpt.startsWith(prefix));
|
|
1304
|
+
if (!allowed) return;
|
|
1305
|
+
const devName = entry.devicename || '';
|
|
1306
|
+
const searchStr = `${entry.ga} (${devName}) DPT${dpt}`;
|
|
1307
|
+
if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return;
|
|
1308
|
+
items.push({
|
|
1309
|
+
label: `${entry.ga} # ${devName} # ${dpt}`,
|
|
1310
|
+
value: entry.ga,
|
|
1311
|
+
ga: entry.ga
|
|
1312
|
+
});
|
|
1313
|
+
});
|
|
1314
|
+
response(items);
|
|
1315
|
+
});
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
if ($gaInput.data('knx-ga-initialised')) {
|
|
1319
|
+
$gaInput.autocomplete('option', 'source', sourceFn);
|
|
1320
|
+
} else {
|
|
1321
|
+
$gaInput
|
|
1322
|
+
.autocomplete({
|
|
1323
|
+
minLength: 0,
|
|
1324
|
+
source: sourceFn,
|
|
1325
|
+
select: (event, ui) => {
|
|
1326
|
+
let deviceName = '';
|
|
1327
|
+
try {
|
|
1328
|
+
deviceName = ui.item.label.split('#')[1].trim();
|
|
1329
|
+
deviceName = deviceName.replace(/^\)/, '').trim();
|
|
1330
|
+
} catch (error) { deviceName = ''; }
|
|
1331
|
+
if ($nameInput.length) {
|
|
1332
|
+
if (deviceName && deviceName !== '') {
|
|
1333
|
+
$nameInput.val(deviceName);
|
|
1334
|
+
} else if (!$nameInput.val()) {
|
|
1335
|
+
$nameInput.val('');
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
try {
|
|
1339
|
+
const parts = ui.item.label.split('#');
|
|
1340
|
+
const dptFromLabel = parts.length >= 3 ? parts[2].trim() : '';
|
|
1341
|
+
if (dptFromLabel !== '') {
|
|
1342
|
+
$dptInput.val(dptFromLabel);
|
|
1343
|
+
}
|
|
1344
|
+
} catch (error) { /* ignore */ }
|
|
1345
|
+
}
|
|
1346
|
+
})
|
|
1347
|
+
.on('focus.knxUltimateStaircase click.knxUltimateStaircase', function () {
|
|
1348
|
+
const currentValue = $(this).val() || '';
|
|
1349
|
+
try { $(this).autocomplete('search', `${currentValue} exactmatch`); } catch (error) { /* ignore */ }
|
|
1350
|
+
});
|
|
1351
|
+
$gaInput.data('knx-ga-initialised', true);
|
|
1352
|
+
}
|
|
1353
|
+
try {
|
|
1354
|
+
if (hasKnxServerSelected()) {
|
|
1355
|
+
const srv = RED.nodes.node(resolveKnxServerValue());
|
|
1356
|
+
if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id);
|
|
1357
|
+
}
|
|
1358
|
+
} catch (error) { /* ignore */ }
|
|
1359
|
+
};
|
|
1360
|
+
|
|
1361
|
+
ensureAutocomplete();
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
const BINARY_PREFIX = ['1.'];
|
|
1365
|
+
const ANY_ANALOG_PREFIX = ['1.', '2.', '5.', '6.', '7.', '8.', '9.', '12.', '13.', '14.', '16.', '20.'];
|
|
1366
|
+
|
|
1367
|
+
const refreshKnxBindings = () => {
|
|
1368
|
+
if (!hasKnxServerSelected()) {
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
getGroupAddress('#node-input-gaTrigger', '#node-input-nameTrigger', '#node-input-dptTrigger', BINARY_PREFIX);
|
|
1373
|
+
getGroupAddress('#node-input-gaOutput', '#node-input-nameOutput', '#node-input-dptOutput', ANY_ANALOG_PREFIX);
|
|
1374
|
+
getGroupAddress('#node-input-gaStatus', '#node-input-nameStatus', '#node-input-dptStatus', ANY_ANALOG_PREFIX);
|
|
1375
|
+
getGroupAddress('#node-input-gaOverride', '#node-input-nameOverride', '#node-input-dptOverride', BINARY_PREFIX);
|
|
1376
|
+
getGroupAddress('#node-input-gaBlock', '#node-input-nameBlock', '#node-input-dptBlock', BINARY_PREFIX);
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
$knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
|
|
1380
|
+
KNX_GA_CACHE.clear();
|
|
1381
|
+
refreshKnxBindings();
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
refreshKnxBindings();
|
|
1385
|
+
|
|
1386
|
+
const $preWarnRows = $('.knx-staircase-prewarn');
|
|
1387
|
+
const $flashRow = $('.knx-staircase-prewarn-flash');
|
|
1388
|
+
|
|
1389
|
+
function syncPreWarnMode() {
|
|
1390
|
+
const enabled = $('#node-input-preWarnEnable').is(':checked');
|
|
1391
|
+
$preWarnRows.toggle(enabled);
|
|
1392
|
+
if (!enabled) {
|
|
1393
|
+
$flashRow.hide();
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
const mode = $('#node-input-preWarnMode').val();
|
|
1397
|
+
$flashRow.toggle(mode === 'flash');
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
$('#node-input-preWarnEnable').on('change.knxUtilityProfile', syncPreWarnMode);
|
|
1401
|
+
$('#node-input-preWarnMode').on('change.knxUtilityProfile', syncPreWarnMode);
|
|
1402
|
+
syncPreWarnMode();
|
|
1403
|
+
},
|
|
1404
|
+
oneditcancel: function () {
|
|
1405
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
1406
|
+
$('#node-input-server').off('.knxUtilityProfile');
|
|
1407
|
+
try { RED.sidebar.show('info'); } catch (error) { }
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
},
|
|
1411
|
+
"garage": function (RED) {
|
|
1412
|
+
const utilityFieldValue = (node, key) => {
|
|
1413
|
+
if (node._utilityEditor && typeof $ === 'function') {
|
|
1414
|
+
const field = $('#node-input-' + key);
|
|
1415
|
+
if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
|
|
1416
|
+
}
|
|
1417
|
+
return node[key];
|
|
1418
|
+
};
|
|
1419
|
+
const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
|
|
1420
|
+
if (!enabled(this)) return true;
|
|
1421
|
+
const numeric = Number(value);
|
|
1422
|
+
return value !== null && value !== undefined && String(value).trim() !== '' &&
|
|
1423
|
+
Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
RED.nodes.registerType('knxUltimateGarage', {
|
|
1427
|
+
category: 'KNX Ultimate',
|
|
1428
|
+
color: '#C7E9C0',
|
|
1429
|
+
defaults: {
|
|
1430
|
+
server: { type: 'knxUltimate-config', required: true },
|
|
1431
|
+
name: { value: '' },
|
|
1432
|
+
outputtopic: { value: '' },
|
|
1433
|
+
gaCommand: { value: '', required: true },
|
|
1434
|
+
nameCommand: { value: '' },
|
|
1435
|
+
dptCommand: { value: '1.001' },
|
|
1436
|
+
gaImpulse: { value: '' },
|
|
1437
|
+
nameImpulse: { value: '' },
|
|
1438
|
+
dptImpulse: { value: '1.017' },
|
|
1439
|
+
gaHoldOpen: { value: '' },
|
|
1440
|
+
nameHoldOpen: { value: '' },
|
|
1441
|
+
dptHoldOpen: { value: '1.001' },
|
|
1442
|
+
gaDisable: { value: '' },
|
|
1443
|
+
nameDisable: { value: '' },
|
|
1444
|
+
dptDisable: { value: '1.001' },
|
|
1445
|
+
gaPhotocell: { value: '' },
|
|
1446
|
+
namePhotocell: { value: '' },
|
|
1447
|
+
dptPhotocell: { value: '1.001' },
|
|
1448
|
+
gaMoving: { value: '' },
|
|
1449
|
+
nameMoving: { value: '' },
|
|
1450
|
+
dptMoving: { value: '1.001' },
|
|
1451
|
+
gaObstruction: { value: '' },
|
|
1452
|
+
nameObstruction: { value: '' },
|
|
1453
|
+
dptObstruction: { value: '1.001' },
|
|
1454
|
+
autoCloseEnable: { value: true },
|
|
1455
|
+
autoCloseSeconds: { value: 120, validate: utilityNumber(0, false, (node) => utilityFieldValue(node, 'autoCloseEnable') !== false) },
|
|
1456
|
+
emitEvents: { value: false }
|
|
1457
|
+
},
|
|
1458
|
+
inputs: 1,
|
|
1459
|
+
outputs: 1,
|
|
1460
|
+
icon: 'node-knx-icon.svg',
|
|
1461
|
+
label: function () {
|
|
1462
|
+
return this.name || 'KNX Garage';
|
|
1463
|
+
},
|
|
1464
|
+
paletteLabel: 'KNX Garage',
|
|
1465
|
+
oneditprepare: function () {
|
|
1466
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
1467
|
+
const editorSession = { active: true };
|
|
1468
|
+
this._knxUtilityProfileSession = editorSession;
|
|
1469
|
+
// Ignore responses belonging to a closed editor or a previous gateway.
|
|
1470
|
+
const profileGetJSON = (url, callback) => {
|
|
1471
|
+
const serverId = $('#node-input-server').val();
|
|
1472
|
+
return $.getJSON(url, (data) => {
|
|
1473
|
+
if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
|
|
1474
|
+
});
|
|
1475
|
+
};
|
|
1476
|
+
const node = this;
|
|
1477
|
+
const $knxServerInput = $('#node-input-server');
|
|
1478
|
+
try { RED.sidebar.show('help'); } catch (error) { /* ignore */ }
|
|
1479
|
+
const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none']);
|
|
1480
|
+
|
|
1481
|
+
const resolveKnxServerValue = () => {
|
|
1482
|
+
const domValue = $knxServerInput.val();
|
|
1483
|
+
if (domValue !== undefined && domValue !== null) return KNX_EMPTY_VALUES.has(String(domValue)) ? '' : domValue;
|
|
1484
|
+
if (node.server !== undefined && node.server !== null && node.server !== '') return node.server;
|
|
1485
|
+
return '';
|
|
1486
|
+
};
|
|
1487
|
+
|
|
1488
|
+
const hasKnxServerSelected = () => {
|
|
1489
|
+
const val = resolveKnxServerValue();
|
|
1490
|
+
return !(val === undefined || val === null || KNX_EMPTY_VALUES.has(String(val)));
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1493
|
+
const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map());
|
|
1494
|
+
|
|
1495
|
+
const fetchGroupAddresses = (serverId) => {
|
|
1496
|
+
if (!serverId) return Promise.resolve([]);
|
|
1497
|
+
if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId));
|
|
1498
|
+
return new Promise((resolve) => {
|
|
1499
|
+
profileGetJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
|
|
1500
|
+
const list = Array.isArray(data) ? data : [];
|
|
1501
|
+
KNX_GA_CACHE.set(serverId, list);
|
|
1502
|
+
resolve(list);
|
|
1503
|
+
}).fail(() => resolve([]));
|
|
1504
|
+
});
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
const getGroupAddress = (gaSelector, nameSelector, dptSelector, prefixes) => {
|
|
1508
|
+
const $gaInput = $(gaSelector);
|
|
1509
|
+
const $nameInput = $(nameSelector);
|
|
1510
|
+
const $dptInput = $(dptSelector);
|
|
1511
|
+
if (!$gaInput.length) return;
|
|
1512
|
+
|
|
1513
|
+
const ensureAutocomplete = () => {
|
|
1514
|
+
const sourceFn = (request, response) => {
|
|
1515
|
+
if (!hasKnxServerSelected()) {
|
|
1516
|
+
response([]);
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
const serverId = resolveKnxServerValue();
|
|
1520
|
+
fetchGroupAddresses(serverId).then((data) => {
|
|
1521
|
+
if (!editorSession.active || resolveKnxServerValue() !== serverId) return;
|
|
1522
|
+
const items = [];
|
|
1523
|
+
data.forEach((entry) => {
|
|
1524
|
+
const dpt = entry.dpt || '';
|
|
1525
|
+
const allowed = prefixes.some((prefix) => prefix === '' || dpt.startsWith(prefix));
|
|
1526
|
+
if (!allowed) return;
|
|
1527
|
+
const devName = entry.devicename || '';
|
|
1528
|
+
const searchStr = `${entry.ga} (${devName}) DPT${dpt}`;
|
|
1529
|
+
if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return;
|
|
1530
|
+
items.push({
|
|
1531
|
+
label: `${entry.ga} # ${devName} # ${dpt}`,
|
|
1532
|
+
value: entry.ga,
|
|
1533
|
+
dpt
|
|
1534
|
+
});
|
|
1535
|
+
});
|
|
1536
|
+
response(items);
|
|
1537
|
+
});
|
|
1538
|
+
};
|
|
1539
|
+
|
|
1540
|
+
if ($gaInput.data('knx-ga-initialised')) {
|
|
1541
|
+
$gaInput.autocomplete('option', 'source', sourceFn);
|
|
1542
|
+
} else {
|
|
1543
|
+
$gaInput
|
|
1544
|
+
.autocomplete({
|
|
1545
|
+
minLength: 0,
|
|
1546
|
+
source: sourceFn,
|
|
1547
|
+
select: (event, ui) => {
|
|
1548
|
+
let deviceName = '';
|
|
1549
|
+
try {
|
|
1550
|
+
deviceName = ui.item.label.split('#')[1].trim();
|
|
1551
|
+
deviceName = deviceName.replace(/^\)/, '').trim();
|
|
1552
|
+
} catch (error) { deviceName = ''; }
|
|
1553
|
+
if ($nameInput.length) {
|
|
1554
|
+
if (deviceName && deviceName !== '') {
|
|
1555
|
+
$nameInput.val(deviceName);
|
|
1556
|
+
} else if (!$nameInput.val()) {
|
|
1557
|
+
$nameInput.val('');
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
try {
|
|
1561
|
+
const parts = ui.item.label.split('#');
|
|
1562
|
+
const dptFromLabel = parts.length >= 3 ? parts[2].trim() : '';
|
|
1563
|
+
if (dptFromLabel !== '') {
|
|
1564
|
+
$dptInput.val(dptFromLabel);
|
|
1565
|
+
}
|
|
1566
|
+
} catch (error) { /* ignore */ }
|
|
1567
|
+
}
|
|
1568
|
+
})
|
|
1569
|
+
.on('focus.knxUltimateGarage click.knxUltimateGarage', function () {
|
|
1570
|
+
const currentValue = $(this).val() || '';
|
|
1571
|
+
try { $(this).autocomplete('search', `${currentValue} exactmatch`); } catch (error) { /* ignore */ }
|
|
1572
|
+
});
|
|
1573
|
+
$gaInput.data('knx-ga-initialised', true);
|
|
1574
|
+
}
|
|
1575
|
+
try {
|
|
1576
|
+
if (hasKnxServerSelected()) {
|
|
1577
|
+
const srv = RED.nodes.node(resolveKnxServerValue());
|
|
1578
|
+
if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id);
|
|
1579
|
+
}
|
|
1580
|
+
} catch (error) { /* ignore */ }
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
ensureAutocomplete();
|
|
1584
|
+
};
|
|
1585
|
+
|
|
1586
|
+
const BINARY_PREFIX = ['1.'];
|
|
1587
|
+
|
|
1588
|
+
const refreshKnxBindings = () => {
|
|
1589
|
+
if (!hasKnxServerSelected()) return;
|
|
1590
|
+
|
|
1591
|
+
getGroupAddress('#node-input-gaCommand', '#node-input-nameCommand', '#node-input-dptCommand', BINARY_PREFIX);
|
|
1592
|
+
getGroupAddress('#node-input-gaImpulse', '#node-input-nameImpulse', '#node-input-dptImpulse', BINARY_PREFIX);
|
|
1593
|
+
getGroupAddress('#node-input-gaHoldOpen', '#node-input-nameHoldOpen', '#node-input-dptHoldOpen', BINARY_PREFIX);
|
|
1594
|
+
getGroupAddress('#node-input-gaDisable', '#node-input-nameDisable', '#node-input-dptDisable', BINARY_PREFIX);
|
|
1595
|
+
getGroupAddress('#node-input-gaPhotocell', '#node-input-namePhotocell', '#node-input-dptPhotocell', BINARY_PREFIX);
|
|
1596
|
+
getGroupAddress('#node-input-gaMoving', '#node-input-nameMoving', '#node-input-dptMoving', BINARY_PREFIX);
|
|
1597
|
+
getGroupAddress('#node-input-gaObstruction', '#node-input-nameObstruction', '#node-input-dptObstruction', BINARY_PREFIX);
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
$knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
|
|
1601
|
+
KNX_GA_CACHE.clear();
|
|
1602
|
+
refreshKnxBindings();
|
|
1603
|
+
});
|
|
1604
|
+
if (hasKnxServerSelected()) refreshKnxBindings();
|
|
1605
|
+
|
|
1606
|
+
const syncAutoClose = () => {
|
|
1607
|
+
const enabled = $('#node-input-autoCloseEnable').is(':checked');
|
|
1608
|
+
const $seconds = $('#node-input-autoCloseSeconds').closest('.form-row');
|
|
1609
|
+
$seconds.toggle(enabled);
|
|
1610
|
+
};
|
|
1611
|
+
|
|
1612
|
+
$('#node-input-autoCloseEnable').on('change.knxUtilityProfile', syncAutoClose);
|
|
1613
|
+
syncAutoClose();
|
|
1614
|
+
},
|
|
1615
|
+
oneditcancel: function () {
|
|
1616
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
1617
|
+
$('#node-input-server').off('.knxUtilityProfile');
|
|
1618
|
+
try { RED.sidebar.show('info'); } catch (error) { }
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
},
|
|
1622
|
+
"scenecontroller": function (RED) {
|
|
1623
|
+
RED.nodes.registerType('knxUltimateSceneController', {
|
|
1624
|
+
category: "KNX Ultimate",
|
|
1625
|
+
color: '#C7E9C0',
|
|
1626
|
+
defaults: {
|
|
1627
|
+
server: { type: "knxUltimate-config", required: true },
|
|
1628
|
+
name: { value: "" },
|
|
1629
|
+
outputtopic: { value: "" },
|
|
1630
|
+
topic: { value: "" },
|
|
1631
|
+
dpt: { value: "" },
|
|
1632
|
+
topicTrigger: { value: "true" },
|
|
1633
|
+
topicSave: { value: "" },
|
|
1634
|
+
dptSave: { value: "" },
|
|
1635
|
+
topicSaveTrigger: { value: "true" },
|
|
1636
|
+
|
|
1637
|
+
rules: { value: [] }
|
|
1638
|
+
},
|
|
1639
|
+
inputs: 1,
|
|
1640
|
+
outputs: 1,
|
|
1641
|
+
icon: "node-scene-icon.svg",
|
|
1642
|
+
label: function () {
|
|
1643
|
+
return (this.outputRBE == true ? "|rbe| " : "") + (this.name || this.topic || "KNX Scene Controller") + (this.inputRBE == true ? " |rbe|" : "")
|
|
1644
|
+
},
|
|
1645
|
+
paletteLabel: "KNX Scene Controller",
|
|
1646
|
+
oneditprepare: function () {
|
|
1647
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
1648
|
+
const editorSession = { active: true };
|
|
1649
|
+
this._knxUtilityProfileSession = editorSession;
|
|
1650
|
+
// Ignore responses belonging to a closed editor or a previous gateway.
|
|
1651
|
+
const profileGetJSON = (url, callback) => {
|
|
1652
|
+
const serverId = $('#node-input-server').val();
|
|
1653
|
+
return $.getJSON(url, (data) => {
|
|
1654
|
+
if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
|
|
1655
|
+
});
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
const dptFields = [];
|
|
1659
|
+
const dptRequests = new Map();
|
|
1660
|
+
const loadDptOptions = (field, savedValue, track = true) => {
|
|
1661
|
+
if (track) {
|
|
1662
|
+
dptFields.push(field);
|
|
1663
|
+
// Preserve persisted values even when Done is pressed before
|
|
1664
|
+
// the asynchronous DPT catalog has arrived.
|
|
1665
|
+
field.append($('<option></option>').attr('value', savedValue || '').text(savedValue || ''));
|
|
1666
|
+
field.val(savedValue || '');
|
|
1667
|
+
}
|
|
1668
|
+
const serverId = $('#node-input-server').val();
|
|
1669
|
+
if (!dptRequests.has(serverId)) {
|
|
1670
|
+
dptRequests.set(serverId, new Promise((resolve) => {
|
|
1671
|
+
profileGetJSON('knxUltimateDpts?serverId=' + encodeURIComponent(serverId || ''), resolve);
|
|
1672
|
+
}));
|
|
1673
|
+
}
|
|
1674
|
+
dptRequests.get(serverId).then((data) => {
|
|
1675
|
+
if (!editorSession.active || $('#node-input-server').val() !== serverId) return;
|
|
1676
|
+
const selected = field.val();
|
|
1677
|
+
field.empty().append($('<option></option>').attr('value', '').text(''));
|
|
1678
|
+
const rows = Array.isArray(data) ? data : [];
|
|
1679
|
+
rows.forEach((dpt) => field.append($('<option></option>').attr('value', dpt.value).text(dpt.text)));
|
|
1680
|
+
if (selected && !rows.some((dpt) => String(dpt.value) === String(selected))) {
|
|
1681
|
+
field.append($('<option></option>').attr('value', selected).text(selected));
|
|
1682
|
+
}
|
|
1683
|
+
field.val(selected || '');
|
|
1684
|
+
});
|
|
1685
|
+
};
|
|
1686
|
+
// Go to the help panel
|
|
1687
|
+
try {
|
|
1688
|
+
RED.sidebar.show("help");
|
|
1689
|
+
} catch (error) { }
|
|
1690
|
+
|
|
1691
|
+
var node = this;
|
|
1692
|
+
var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
|
|
1693
|
+
|
|
1694
|
+
// 19/02/2020 Used to get the server sooner als deploy.
|
|
1695
|
+
$("#node-input-server").off(".knxUtilityProfile").on("change.knxUtilityProfile", function () {
|
|
1696
|
+
try {
|
|
1697
|
+
oNodeServer = RED.nodes.node($(this).val());
|
|
1698
|
+
dptFields.forEach((field) => loadDptOptions(field, field.val(), false));
|
|
1699
|
+
} catch (error) { }
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
|
|
1703
|
+
|
|
1704
|
+
// DPT of Scene Recall
|
|
1705
|
+
// ########################
|
|
1706
|
+
loadDptOptions($("#node-input-dpt"), this.dpt);
|
|
1707
|
+
|
|
1708
|
+
// Autocomplete suggestion with ETS csv File
|
|
1709
|
+
$("#node-input-topic").autocomplete({
|
|
1710
|
+
minLength: 0,
|
|
1711
|
+
source: function (request, response) {
|
|
1712
|
+
//profileGetJSON("csv", request, function( data, status, xhr ) {
|
|
1713
|
+
if (!oNodeServer || !oNodeServer.id) { response([]); return; }
|
|
1714
|
+
profileGetJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
1715
|
+
response($.map(data, function (value, key) {
|
|
1716
|
+
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
1717
|
+
if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
|
|
1718
|
+
return {
|
|
1719
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
1720
|
+
value: value.ga // Value
|
|
1721
|
+
}
|
|
1722
|
+
} else {
|
|
1723
|
+
return null;
|
|
1724
|
+
}
|
|
1725
|
+
}));
|
|
1726
|
+
});
|
|
1727
|
+
}, select: function (event, ui) {
|
|
1728
|
+
// Sets Datapoint and device name automatically
|
|
1729
|
+
var sDevName = ui.item.label.split("#")[1].trim();
|
|
1730
|
+
try {
|
|
1731
|
+
sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
}
|
|
1734
|
+
$('#node-input-name').val("Recall: " + sDevName + "/" + $('#node-input-name').val().split("/")[1]);
|
|
1735
|
+
var optVal = $("#node-input-dpt option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
|
|
1736
|
+
var $dptSelect = $("#node-input-dpt");
|
|
1737
|
+
if (optVal !== undefined && optVal !== null) {
|
|
1738
|
+
$dptSelect.val(optVal).trigger('change');
|
|
1739
|
+
} else {
|
|
1740
|
+
$dptSelect.trigger('change');
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}).focus(function () {
|
|
1744
|
+
$(this).autocomplete('search', $(this).val() + 'exactmatch');
|
|
1745
|
+
});
|
|
1746
|
+
try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting($("#node-input-topicSave"), oNodeServer.id); } catch (e) {}
|
|
1747
|
+
try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting($("#node-input-topic"), oNodeServer.id); } catch (e) {}
|
|
1748
|
+
|
|
1749
|
+
// 19/03/2020 Adjust trigger value accordingly
|
|
1750
|
+
$("#node-input-dpt").on("change.knxUtilityProfile", function () {
|
|
1751
|
+
if ($(this).val() !== null) {
|
|
1752
|
+
if ($(this).val().indexOf("3.007") > -1) {
|
|
1753
|
+
// It's a DIM. Suggest the right value
|
|
1754
|
+
$("#node-input-topicTrigger").val("{decr_incr:1, data:5}");
|
|
1755
|
+
var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT3007"),
|
|
1756
|
+
{
|
|
1757
|
+
modal: true,
|
|
1758
|
+
fixed: true,
|
|
1759
|
+
type: 'info',
|
|
1760
|
+
buttons: [
|
|
1761
|
+
{
|
|
1762
|
+
text: "OK",
|
|
1763
|
+
click: function (e) {
|
|
1764
|
+
myNotification.close();
|
|
1765
|
+
}
|
|
1766
|
+
}]
|
|
1767
|
+
})
|
|
1768
|
+
} else if ($(this).val().indexOf("18.001") > -1) {
|
|
1769
|
+
// It's a scene actuator. Suggest the right value
|
|
1770
|
+
$("#node-input-topicTrigger").val("{save_recall:0, scenenumber:2}");
|
|
1771
|
+
var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT18001"),
|
|
1772
|
+
{
|
|
1773
|
+
modal: true,
|
|
1774
|
+
fixed: true,
|
|
1775
|
+
type: 'info',
|
|
1776
|
+
buttons: [
|
|
1777
|
+
{
|
|
1778
|
+
text: "OK",
|
|
1779
|
+
click: function (e) {
|
|
1780
|
+
myNotification.close();
|
|
1781
|
+
}
|
|
1782
|
+
}]
|
|
1783
|
+
})
|
|
1784
|
+
|
|
1785
|
+
} else if ($(this).val().indexOf("232.600") > -1) {
|
|
1786
|
+
// It's a scene actuator. Suggest the right value
|
|
1787
|
+
$("#node-input-topicTrigger").val("{red:0, green:0, blue:0}");
|
|
1788
|
+
var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT232600"),
|
|
1789
|
+
{
|
|
1790
|
+
modal: true,
|
|
1791
|
+
fixed: true,
|
|
1792
|
+
type: 'info',
|
|
1793
|
+
buttons: [
|
|
1794
|
+
{
|
|
1795
|
+
text: "OK",
|
|
1796
|
+
click: function (e) {
|
|
1797
|
+
myNotification.close();
|
|
1798
|
+
}
|
|
1799
|
+
}]
|
|
1800
|
+
})
|
|
1801
|
+
|
|
1802
|
+
} else if ($(this).val().indexOf("251.600") > -1) {
|
|
1803
|
+
// It's a scene actuator. Suggest the right value
|
|
1804
|
+
$("#node-input-topicTrigger").val("{red:0, green:0, blue:0, white:0, mR:1, mG:1, mB:1, mW:1}");
|
|
1805
|
+
var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT251600"),
|
|
1806
|
+
{
|
|
1807
|
+
modal: true,
|
|
1808
|
+
fixed: true,
|
|
1809
|
+
type: 'info',
|
|
1810
|
+
buttons: [
|
|
1811
|
+
{
|
|
1812
|
+
text: "OK",
|
|
1813
|
+
click: function (e) {
|
|
1814
|
+
myNotification.close();
|
|
1815
|
+
}
|
|
1816
|
+
}]
|
|
1817
|
+
})
|
|
1818
|
+
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
// ########################
|
|
1824
|
+
|
|
1825
|
+
|
|
1826
|
+
|
|
1827
|
+
// DPT of Scene Save
|
|
1828
|
+
// ########################
|
|
1829
|
+
loadDptOptions($("#node-input-dptSave"), this.dptSave);
|
|
1830
|
+
|
|
1831
|
+
// Autocomplete suggestion with ETS csv File
|
|
1832
|
+
$("#node-input-topicSave").autocomplete({
|
|
1833
|
+
minLength: 0,
|
|
1834
|
+
source: function (request, response) {
|
|
1835
|
+
if (!oNodeServer || !oNodeServer.id) { response([]); return; }
|
|
1836
|
+
profileGetJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
1837
|
+
response($.map(data, function (value, key) {
|
|
1838
|
+
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
1839
|
+
if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
|
|
1840
|
+
return {
|
|
1841
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
1842
|
+
value: value.ga // Value
|
|
1843
|
+
}
|
|
1844
|
+
} else {
|
|
1845
|
+
return null;
|
|
1846
|
+
}
|
|
1847
|
+
}));
|
|
1848
|
+
});
|
|
1849
|
+
}, select: function (event, ui) {
|
|
1850
|
+
// Sets Datapoint and device name automatically
|
|
1851
|
+
var sDevName = ui.item.label.split("#")[1].trim();
|
|
1852
|
+
try {
|
|
1853
|
+
sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
|
|
1854
|
+
} catch (error) {
|
|
1855
|
+
}
|
|
1856
|
+
$('#node-input-name').val($('#node-input-name').val().split("/")[0] + "/Save: " + sDevName);
|
|
1857
|
+
var optVal = $("#node-input-dptSave option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
|
|
1858
|
+
var $dptSelect = $("#node-input-dptSave");
|
|
1859
|
+
if (optVal !== undefined && optVal !== null) {
|
|
1860
|
+
$dptSelect.val(optVal).trigger('change');
|
|
1861
|
+
} else {
|
|
1862
|
+
$dptSelect.trigger('change');
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}).focus(function () {
|
|
1866
|
+
$(this).autocomplete('search', $(this).val() + 'exactmatch');
|
|
1867
|
+
});
|
|
1868
|
+
|
|
1869
|
+
// 02/04/2020 Adjust trigger value accordingly
|
|
1870
|
+
$("#node-input-dptSave").on("change.knxUtilityProfile", function () {
|
|
1871
|
+
if ($(this).val() !== null) {
|
|
1872
|
+
if ($(this).val().indexOf("3.007") > -1) {
|
|
1873
|
+
// It's a DIM. Suggest the right value
|
|
1874
|
+
$("#node-input-topicSaveTrigger").val("{decr_incr:1, data:5}");
|
|
1875
|
+
var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT3007"),
|
|
1876
|
+
{
|
|
1877
|
+
modal: true,
|
|
1878
|
+
fixed: true,
|
|
1879
|
+
type: 'info',
|
|
1880
|
+
buttons: [
|
|
1881
|
+
{
|
|
1882
|
+
text: "OK",
|
|
1883
|
+
click: function (e) {
|
|
1884
|
+
myNotification.close();
|
|
1885
|
+
}
|
|
1886
|
+
}]
|
|
1887
|
+
})
|
|
1888
|
+
} else if ($(this).val().indexOf("18.001") > -1) {
|
|
1889
|
+
// It's a scene actuator. Suggest the right value
|
|
1890
|
+
$("#node-input-topicSaveTrigger").val("{save_recall:1, scenenumber:2}");
|
|
1891
|
+
var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT18001"),
|
|
1892
|
+
{
|
|
1893
|
+
modal: true,
|
|
1894
|
+
fixed: true,
|
|
1895
|
+
type: 'info',
|
|
1896
|
+
buttons: [
|
|
1897
|
+
{
|
|
1898
|
+
text: "OK",
|
|
1899
|
+
click: function (e) {
|
|
1900
|
+
myNotification.close();
|
|
1901
|
+
}
|
|
1902
|
+
}]
|
|
1903
|
+
})
|
|
1904
|
+
|
|
1905
|
+
} else if ($(this).val().indexOf("232.600") > -1) {
|
|
1906
|
+
// It's a scene actuator. Suggest the right value
|
|
1907
|
+
$("#node-input-topicSaveTrigger").val("{red:0, green:0, blue:0}");
|
|
1908
|
+
var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT232600"),
|
|
1909
|
+
{
|
|
1910
|
+
modal: true,
|
|
1911
|
+
fixed: true,
|
|
1912
|
+
type: 'info',
|
|
1913
|
+
buttons: [
|
|
1914
|
+
{
|
|
1915
|
+
text: "OK",
|
|
1916
|
+
click: function (e) {
|
|
1917
|
+
myNotification.close();
|
|
1918
|
+
}
|
|
1919
|
+
}]
|
|
1920
|
+
})
|
|
1921
|
+
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
// ########################
|
|
1927
|
+
|
|
1928
|
+
|
|
1929
|
+
// Scene configuration
|
|
1930
|
+
function resizeRule(rule) { }
|
|
1931
|
+
$("#node-input-rule-container").css('min-height', '150px').css('min-width', '450px').editableList({
|
|
1932
|
+
addItem: function (container, i, opt) { // row, index, data
|
|
1933
|
+
// opt.r is: { topic: rowRuleTopic, devicename: rowRuleDeviceName, dpt:rowRuleDPT, send: rowRuleSend}
|
|
1934
|
+
|
|
1935
|
+
if (!opt.hasOwnProperty('r')) {
|
|
1936
|
+
opt.r = {};
|
|
1937
|
+
}
|
|
1938
|
+
var rule = opt.r;
|
|
1939
|
+
if (!opt.hasOwnProperty('i')) {
|
|
1940
|
+
opt._i = Math.floor((0x99999 - 0x10000) * Math.random()).toString();
|
|
1941
|
+
}
|
|
1942
|
+
container.css({
|
|
1943
|
+
overflow: 'hidden',
|
|
1944
|
+
whiteSpace: 'nowrap'
|
|
1945
|
+
});
|
|
1946
|
+
|
|
1947
|
+
|
|
1948
|
+
var row = $('<div class="form-row"/>').appendTo(container);
|
|
1949
|
+
var row2 = $('<div class="form-row"/>', { style: "padding-top: 5px; padding-left: 5px;" }).appendTo(container);
|
|
1950
|
+
|
|
1951
|
+
var oTopicField = $("<input/>", { class: "rowRuleTopic", type: "text", placeholder: "1/1/1, wait or device name", style: "width:140px; margin-left: 5px; text-align: left;" }).appendTo(row);
|
|
1952
|
+
var oDPTField = $('<select/>', { class: "rowRuleDPT", type: "text", style: "width:160px; margin-left: 5px; text-align: left;" }).appendTo(row);
|
|
1953
|
+
var finalspan = $('<span/>', { style: "" }).appendTo(row);
|
|
1954
|
+
finalspan.append(' → <span class="node-input-rule-index"></span> ');
|
|
1955
|
+
var oSendField = $('<input/>', { class: "rowRuleSend", type: "text", placeholder: "Value", style: "width:150px; margin-left: 5px; text-align: left;" }).appendTo(row);
|
|
1956
|
+
var orowRuleDeviceName = $('<input/>', { class: "rowRuleDeviceName", type: "text", style: "width:95%; margin-left: 0px; text-align: left;font-style: italic;", placeholder: "Device Name" }).appendTo(row2);
|
|
1957
|
+
|
|
1958
|
+
oTopicField.on("change.knxUtilityProfile", function () {
|
|
1959
|
+
resizeRule(container);
|
|
1960
|
+
});
|
|
1961
|
+
|
|
1962
|
+
loadDptOptions(oDPTField, rule.dpt);
|
|
1963
|
+
|
|
1964
|
+
// Autocomplete suggestion with ETS csv File
|
|
1965
|
+
oTopicField.autocomplete({
|
|
1966
|
+
minLength: 0,
|
|
1967
|
+
source: function (request, response) {
|
|
1968
|
+
if (!oNodeServer || !oNodeServer.id) { response([]); return; }
|
|
1969
|
+
profileGetJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
1970
|
+
response($.map(data, function (value, key) {
|
|
1971
|
+
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
1972
|
+
if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
|
|
1973
|
+
return {
|
|
1974
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
1975
|
+
value: value.ga // Value
|
|
1976
|
+
}
|
|
1977
|
+
} else {
|
|
1978
|
+
return null;
|
|
1979
|
+
}
|
|
1980
|
+
}));
|
|
1981
|
+
});
|
|
1982
|
+
}, select: function (event, ui) {
|
|
1983
|
+
// Sets Datapoint and device name automatically
|
|
1984
|
+
var sDevName = ui.item.label.split("#")[1].trim();
|
|
1985
|
+
try {
|
|
1986
|
+
sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
|
|
1987
|
+
orowRuleDeviceName.val(sDevName);
|
|
1988
|
+
} catch (error) {
|
|
1989
|
+
}
|
|
1990
|
+
var optVal = $(".rowRuleDPT option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
|
|
1991
|
+
var $dptSelect = oDPTField;
|
|
1992
|
+
if (optVal !== undefined && optVal !== null) {
|
|
1993
|
+
$dptSelect.val(optVal).trigger('change');
|
|
1994
|
+
} else {
|
|
1995
|
+
$dptSelect.trigger('change');
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
});
|
|
1999
|
+
oTopicField.on('focus.knxUltimateSceneController click.knxUltimateSceneController', function () {
|
|
2000
|
+
try {
|
|
2001
|
+
$(this).autocomplete('search', '');
|
|
2002
|
+
} catch (error) { /* empty */ }
|
|
2003
|
+
});
|
|
2004
|
+
try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting(oTopicField, oNodeServer.id); } catch (e) {}
|
|
2005
|
+
|
|
2006
|
+
oTopicField.val(rule.topic);
|
|
2007
|
+
oSendField.val(rule.send);
|
|
2008
|
+
orowRuleDeviceName.val(rule.devicename);
|
|
2009
|
+
oTopicField.change();
|
|
2010
|
+
},
|
|
2011
|
+
removeItem: function (opt) {
|
|
2012
|
+
},
|
|
2013
|
+
resizeItem: resizeRule,
|
|
2014
|
+
sortItems: function (rules) {
|
|
2015
|
+
},
|
|
2016
|
+
sortable: true,
|
|
2017
|
+
removable: true
|
|
2018
|
+
});
|
|
2019
|
+
|
|
2020
|
+
|
|
2021
|
+
// 10/03/2020 For each rule, create a row
|
|
2022
|
+
for (var i = 0; i < (this.rules || []).length; i++) {
|
|
2023
|
+
var rule = this.rules[i];
|
|
2024
|
+
$("#node-input-rule-container").editableList('addItem', { r: rule, i: i });
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
|
|
2028
|
+
},
|
|
2029
|
+
oneditcancel: function () {
|
|
2030
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
2031
|
+
$('#node-input-server').off('.knxUtilityProfile');
|
|
2032
|
+
try { RED.sidebar.show('info'); } catch (error) { }
|
|
2033
|
+
},
|
|
2034
|
+
oneditsave: function () {
|
|
2035
|
+
// Return to the info tab
|
|
2036
|
+
try {
|
|
2037
|
+
RED.sidebar.show("info");
|
|
2038
|
+
} catch (error) { }
|
|
2039
|
+
|
|
2040
|
+
var node = this;
|
|
2041
|
+
|
|
2042
|
+
var rules = $("#node-input-rule-container").editableList('items');
|
|
2043
|
+
node.rules = [];
|
|
2044
|
+
rules.each(function (i) {
|
|
2045
|
+
var rule = $(this);
|
|
2046
|
+
var rowRuleTopic = rule.find(".rowRuleTopic").val();
|
|
2047
|
+
var rowRuleDPT = rule.find(".rowRuleDPT").val();
|
|
2048
|
+
var rowRuleSend = rule.find(".rowRuleSend").val();
|
|
2049
|
+
var rowRuleDeviceName = rule.find(".rowRuleDeviceName").val();
|
|
2050
|
+
node.rules.push({ topic: rowRuleTopic, devicename: rowRuleDeviceName, dpt: rowRuleDPT, send: rowRuleSend });
|
|
2051
|
+
});
|
|
2052
|
+
},
|
|
2053
|
+
oneditresize: function (size) {
|
|
2054
|
+
const list = $('#node-input-rule-container');
|
|
2055
|
+
const listTop = list.offset();
|
|
2056
|
+
const formTop = $('#dialog-form').offset();
|
|
2057
|
+
if (!size || !listTop || !formTop) return;
|
|
2058
|
+
list.editableList('height', Math.max(180, size.height - (listTop.top - formTop.top) - 45));
|
|
2059
|
+
}
|
|
2060
|
+
})
|
|
2061
|
+
},
|
|
2062
|
+
"loadcontrol": function (RED) {
|
|
2063
|
+
const utilityFieldValue = (node, key) => {
|
|
2064
|
+
if (node._utilityEditor && typeof $ === 'function') {
|
|
2065
|
+
const field = $('#node-input-' + key);
|
|
2066
|
+
if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
|
|
2067
|
+
}
|
|
2068
|
+
return node[key];
|
|
2069
|
+
};
|
|
2070
|
+
const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
|
|
2071
|
+
if (!enabled(this)) return true;
|
|
2072
|
+
const numeric = Number(value);
|
|
2073
|
+
return value !== null && value !== undefined && String(value).trim() !== '' &&
|
|
2074
|
+
Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
|
|
2075
|
+
};
|
|
2076
|
+
|
|
2077
|
+
RED.nodes.registerType('knxUltimateLoadControl', {
|
|
2078
|
+
category: "KNX Ultimate",
|
|
2079
|
+
color: '#C7E9C0',
|
|
2080
|
+
defaults: {
|
|
2081
|
+
server: { type: "knxUltimate-config", required: false },
|
|
2082
|
+
name: { value: "" },
|
|
2083
|
+
controlMode: { value: "auto" },
|
|
2084
|
+
topic: { value: "" },
|
|
2085
|
+
dpt: { value: "" },
|
|
2086
|
+
wattLimit: { value: 3000, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'controlMode') !== 'msg') },
|
|
2087
|
+
sheddingCheckInterval: { value: 15, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'controlMode') !== 'msg') },
|
|
2088
|
+
sheddingRestoreDelay: { value: 60, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'controlMode') !== 'msg') },
|
|
2089
|
+
GA1: { value: "" },
|
|
2090
|
+
DPT1: { value: "" },
|
|
2091
|
+
Name1: { value: "" },
|
|
2092
|
+
autoRestore1: { value: true },
|
|
2093
|
+
MonitorGA1: { value: "" },
|
|
2094
|
+
MonitorDPT1: { value: "" },
|
|
2095
|
+
MonitorName1: { value: "" },
|
|
2096
|
+
GA2: { value: "" },
|
|
2097
|
+
DPT2: { value: "" },
|
|
2098
|
+
Name2: { value: "" },
|
|
2099
|
+
autoRestore2: { value: true },
|
|
2100
|
+
MonitorGA2: { value: "" },
|
|
2101
|
+
MonitorDPT2: { value: "" },
|
|
2102
|
+
MonitorName2: { value: "" },
|
|
2103
|
+
GA3: { value: "" },
|
|
2104
|
+
DPT3: { value: "" },
|
|
2105
|
+
Name3: { value: "" },
|
|
2106
|
+
autoRestore3: { value: true },
|
|
2107
|
+
MonitorGA3: { value: "" },
|
|
2108
|
+
MonitorDPT3: { value: "" },
|
|
2109
|
+
MonitorName3: { value: "" },
|
|
2110
|
+
GA4: { value: "" },
|
|
2111
|
+
DPT4: { value: "" },
|
|
2112
|
+
Name4: { value: "" },
|
|
2113
|
+
autoRestore4: { value: true },
|
|
2114
|
+
MonitorGA4: { value: "" },
|
|
2115
|
+
MonitorDPT4: { value: "" },
|
|
2116
|
+
MonitorName4: { value: "" },
|
|
2117
|
+
GA5: { value: "" },
|
|
2118
|
+
DPT5: { value: "" },
|
|
2119
|
+
Name5: { value: "" },
|
|
2120
|
+
autoRestore5: { value: true },
|
|
2121
|
+
MonitorGA5: { value: "" },
|
|
2122
|
+
MonitorDPT5: { value: "" },
|
|
2123
|
+
MonitorName5: { value: "" }
|
|
2124
|
+
},
|
|
2125
|
+
inputs: 1,
|
|
2126
|
+
outputs: 1,
|
|
2127
|
+
icon: "node-alerter-icon.svg",
|
|
2128
|
+
label: function () {
|
|
2129
|
+
return (this.name || this.topic || "KNX Load Control");
|
|
2130
|
+
},
|
|
2131
|
+
paletteLabel: "KNX Load Control",
|
|
2132
|
+
oneditprepare: function () {
|
|
2133
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
2134
|
+
const editorSession = { active: true };
|
|
2135
|
+
this._knxUtilityProfileSession = editorSession;
|
|
2136
|
+
// Ignore responses belonging to a closed editor or a previous gateway.
|
|
2137
|
+
const profileGetJSON = (url, callback) => {
|
|
2138
|
+
const serverId = $('#node-input-server').val();
|
|
2139
|
+
return $.getJSON(url, (data) => {
|
|
2140
|
+
if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
|
|
2141
|
+
});
|
|
2142
|
+
};
|
|
2143
|
+
|
|
2144
|
+
const dptFields = [];
|
|
2145
|
+
const dptRequests = new Map();
|
|
2146
|
+
const loadDptOptions = (field, savedValue, track = true) => {
|
|
2147
|
+
if (track) {
|
|
2148
|
+
dptFields.push(field);
|
|
2149
|
+
// Preserve persisted values even when Done is pressed before
|
|
2150
|
+
// the asynchronous DPT catalog has arrived.
|
|
2151
|
+
field.append($('<option></option>').attr('value', savedValue || '').text(savedValue || ''));
|
|
2152
|
+
field.val(savedValue || '');
|
|
2153
|
+
}
|
|
2154
|
+
const serverId = $('#node-input-server').val();
|
|
2155
|
+
if (!dptRequests.has(serverId)) {
|
|
2156
|
+
dptRequests.set(serverId, new Promise((resolve) => {
|
|
2157
|
+
profileGetJSON('knxUltimateDpts?serverId=' + encodeURIComponent(serverId || ''), resolve);
|
|
2158
|
+
}));
|
|
2159
|
+
}
|
|
2160
|
+
dptRequests.get(serverId).then((data) => {
|
|
2161
|
+
if (!editorSession.active || $('#node-input-server').val() !== serverId) return;
|
|
2162
|
+
const selected = field.val();
|
|
2163
|
+
field.empty().append($('<option></option>').attr('value', '').text(''));
|
|
2164
|
+
const rows = Array.isArray(data) ? data : [];
|
|
2165
|
+
rows.forEach((dpt) => field.append($('<option></option>').attr('value', dpt.value).text(dpt.text)));
|
|
2166
|
+
if (selected && !rows.some((dpt) => String(dpt.value) === String(selected))) {
|
|
2167
|
+
field.append($('<option></option>').attr('value', selected).text(selected));
|
|
2168
|
+
}
|
|
2169
|
+
field.val(selected || '');
|
|
2170
|
+
});
|
|
2171
|
+
};
|
|
2172
|
+
// Go to the help panel
|
|
2173
|
+
try {
|
|
2174
|
+
RED.sidebar.show("help");
|
|
2175
|
+
} catch (error) { }
|
|
2176
|
+
|
|
2177
|
+
var node = this;
|
|
2178
|
+
var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
|
|
2179
|
+
|
|
2180
|
+
// 19/02/2020 Used to get the server sooner als deploy.
|
|
2181
|
+
$("#node-input-server").off(".knxUtilityProfile").on("change.knxUtilityProfile", function () {
|
|
2182
|
+
try {
|
|
2183
|
+
oNodeServer = RED.nodes.node($(this).val());
|
|
2184
|
+
dptFields.forEach((field) => loadDptOptions(field, field.val(), false));
|
|
2185
|
+
} catch (error) { }
|
|
2186
|
+
});
|
|
2187
|
+
|
|
2188
|
+
|
|
2189
|
+
|
|
2190
|
+
loadDptOptions($('#node-input-dpt'), this.dpt);
|
|
2191
|
+
for (let index = 1; index < 6; index++) {
|
|
2192
|
+
loadDptOptions($('#node-input-DPT' + index), this['DPT' + index]);
|
|
2193
|
+
loadDptOptions($('#node-input-MonitorDPT' + index), this['MonitorDPT' + index]);
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
const toggleAutoFields = () => {
|
|
2197
|
+
const mode = ($("#node-input-controlMode").val() || "auto").toString();
|
|
2198
|
+
if (mode === "msg") {
|
|
2199
|
+
$(".knx-lc-auto-only").hide();
|
|
2200
|
+
} else {
|
|
2201
|
+
$(".knx-lc-auto-only").show();
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
|
|
2205
|
+
$("#node-input-controlMode").on("change.knxUtilityProfile", toggleAutoFields);
|
|
2206
|
+
|
|
2207
|
+
// Autocomplete suggestion with ETS csv File
|
|
2208
|
+
this.autoComplete = (_Name, _DPT) => {
|
|
2209
|
+
let paramAutoComplete = {
|
|
2210
|
+
minLength: 0,
|
|
2211
|
+
source: function (request, response) {
|
|
2212
|
+
if (!oNodeServer || !oNodeServer.id) { response([]); return; }
|
|
2213
|
+
profileGetJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
2214
|
+
response($.map(data, function (value, key) {
|
|
2215
|
+
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
2216
|
+
if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
|
|
2217
|
+
return {
|
|
2218
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
2219
|
+
value: value.ga // Value
|
|
2220
|
+
}
|
|
2221
|
+
} else {
|
|
2222
|
+
return null;
|
|
2223
|
+
}
|
|
2224
|
+
}));
|
|
2225
|
+
});
|
|
2226
|
+
}, select: function (event, ui) {
|
|
2227
|
+
// Sets Datapoint and device name automatically
|
|
2228
|
+
var sDevName = ui.item.label.split("#")[1].trim();
|
|
2229
|
+
try {
|
|
2230
|
+
sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
|
|
2231
|
+
} catch (error) {
|
|
2232
|
+
}
|
|
2233
|
+
if (_Name === "name") {
|
|
2234
|
+
$("#node-input-" + _Name).val("Load Control for " + sDevName);
|
|
2235
|
+
} else {
|
|
2236
|
+
$("#node-input-" + _Name).val(sDevName);
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
var optVal = $("#node-input-dpt option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
|
|
2240
|
+
var $dptSelect = $("#node-input-" + _DPT);
|
|
2241
|
+
if (optVal !== undefined && optVal !== null) {
|
|
2242
|
+
$dptSelect.val(optVal).trigger('change');
|
|
2243
|
+
} else {
|
|
2244
|
+
$dptSelect.trigger('change');
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
};
|
|
2248
|
+
return paramAutoComplete;
|
|
2249
|
+
};
|
|
2250
|
+
|
|
2251
|
+
$("#node-input-topic").autocomplete(this.autoComplete("name", "dpt")).on('focus.knxUltimateLoadControl click.knxUltimateLoadControl', function () {
|
|
2252
|
+
try {
|
|
2253
|
+
$(this).autocomplete('search', '');
|
|
2254
|
+
} catch (error) { /* empty */ }
|
|
2255
|
+
});
|
|
2256
|
+
try { var srv = RED.nodes.node($("#node-input-server").val()); if (srv && srv.id) KNX_enableSecureFormatting($("#node-input-topic"), srv.id); } catch (e) {}
|
|
2257
|
+
for (let index = 1; index < 6; index++) {
|
|
2258
|
+
$("#node-input-GA" + index).autocomplete(this.autoComplete("Name" + index, "DPT" + index)).on('focus.knxUltimateLoadControl click.knxUltimateLoadControl', function () {
|
|
2259
|
+
try {
|
|
2260
|
+
$(this).autocomplete('search', '');
|
|
2261
|
+
} catch (error) { /* empty */ }
|
|
2262
|
+
});
|
|
2263
|
+
$("#node-input-MonitorGA" + index).autocomplete(this.autoComplete("MonitorName" + index, "MonitorDPT" + index)).on('focus.knxUltimateLoadControl click.knxUltimateLoadControl', function () {
|
|
2264
|
+
try {
|
|
2265
|
+
$(this).autocomplete('search', '');
|
|
2266
|
+
} catch (error) { /* empty */ }
|
|
2267
|
+
});
|
|
2268
|
+
try { var srv2 = RED.nodes.node($("#node-input-server").val()); if (srv2 && srv2.id) KNX_enableSecureFormatting($("#node-input-GA" + index), srv2.id); } catch (e) {}
|
|
2269
|
+
try { var srv3 = RED.nodes.node($("#node-input-server").val()); if (srv3 && srv3.id) KNX_enableSecureFormatting($("#node-input-MonitorGA" + index), srv3.id); } catch (e) {}
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
toggleAutoFields();
|
|
2273
|
+
|
|
2274
|
+
|
|
2275
|
+
},
|
|
2276
|
+
oneditcancel: function () {
|
|
2277
|
+
if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
|
|
2278
|
+
$('#node-input-server').off('.knxUtilityProfile');
|
|
2279
|
+
try { RED.sidebar.show('info'); } catch (error) { }
|
|
2280
|
+
},
|
|
2281
|
+
oneditsave: function () {
|
|
2282
|
+
// Return to the info tab
|
|
2283
|
+
try {
|
|
2284
|
+
RED.sidebar.show("info");
|
|
2285
|
+
} catch (error) { }
|
|
2286
|
+
|
|
2287
|
+
|
|
2288
|
+
},
|
|
2289
|
+
oneditresize: function (size) {
|
|
2290
|
+
|
|
2291
|
+
}
|
|
2292
|
+
})
|
|
2293
|
+
},
|
|
2294
|
+
"hatranslator": function (RED) {
|
|
2295
|
+
const defaultTranslations = 'on:true\noff:false\nactive:true\ninactive:false\nopen:true\nclosed:false\nclose:false\n1:true\n0:false\ntrue:true\nfalse:false\nhome:true\nnot_home:false';
|
|
2296
|
+
|
|
2297
|
+
RED.nodes.registerType('knxUltimateHATranslator', {
|
|
2298
|
+
category: 'KNX Ultimate',
|
|
2299
|
+
defaults: {
|
|
2300
|
+
name: { value: '' },
|
|
2301
|
+
payloadPropName: { value: 'payload', required: false },
|
|
2302
|
+
haTranslationTable: { value: defaultTranslations, required: false }
|
|
2303
|
+
},
|
|
2304
|
+
inputs: 1,
|
|
2305
|
+
outputs: 1,
|
|
2306
|
+
icon: 'node-ha-icon.svg',
|
|
2307
|
+
label: function () { return this.name || 'HA -> KNX'; },
|
|
2308
|
+
color: '#AED6F1',
|
|
2309
|
+
paletteLabel: 'Home Assistant translator',
|
|
2310
|
+
oneditprepare: function () {
|
|
2311
|
+
this.editor = RED.editor.createEditor({
|
|
2312
|
+
id: 'node-input-editorcommandText',
|
|
2313
|
+
mode: 'ace/mode/text',
|
|
2314
|
+
value: this.haTranslationTable === undefined || this.haTranslationTable === null ? defaultTranslations : this.haTranslationTable
|
|
2315
|
+
});
|
|
2316
|
+
},
|
|
2317
|
+
oneditsave: function () {
|
|
2318
|
+
// The Utility collects drafts before switching function. Cleanup is a
|
|
2319
|
+
// separate step, so repeated collection must leave Ace usable.
|
|
2320
|
+
if (this.editor) this.haTranslationTable = this.editor.getValue();
|
|
2321
|
+
},
|
|
2322
|
+
oneditresize: function () {
|
|
2323
|
+
if (this.editor) this.editor.resize();
|
|
2324
|
+
},
|
|
2325
|
+
oneditcancel: function () {
|
|
2326
|
+
// Called for both Done and Cancel, and whenever the profile unmounts.
|
|
2327
|
+
// Clear the reference first so cleanup remains idempotent.
|
|
2328
|
+
const editor = this.editor;
|
|
2329
|
+
delete this.editor;
|
|
2330
|
+
if (editor) editor.destroy();
|
|
2331
|
+
}
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// Form fragments are mounted inside #knx-utility-profile-editor. The gateway
|
|
2337
|
+
// and name fields remain owned by the outer Utility template.
|
|
2338
|
+
const PROFILE_TEMPLATES = {
|
|
2339
|
+
"alerter": "<div class=\"form-row\">\n <label for=\"node-input-whentostart\"><i class=\"fa fa-repeat\"></i> <span data-i18n=\"knxUltimateAlerter.properties.node-input-whentostart\"></span> </label>\n <select id=\"node-input-whentostart\">\n <option value=\"manualstart\" data-i18n=\"knxUltimateAlerter.selectlists.manualstart\"></option>\n <option value=\"ifnewalert\" data-i18n=\"knxUltimateAlerter.selectlists.ifnewalert\"></option>\n </select>\n</div>\n\n<div class=\"form-row\">\n <label for=\"node-input-timerinterval\" style=\"width:70%\"><i class=\"fa fa-clock-o\"></i> <span data-i18n=\"knxUltimateAlerter.properties.node-input-timerinterval\"></span> </label>\n <input type=\"text\" id=\"node-input-timerinterval\" style=\"width:10%\"> \n</div>\n\n<br/>\n<br/>\n<dt><i class=\"fa fa-code-fork\"></i> <span data-i18n=\"knxUltimateAlerter.other.sceneConfig\"></span></dt>\n <br/>\n <div class=\"form-row\" id=\"divNode-input-initialreadGAInRules\">\n <label style=\"width:60%\" for=\"node-input-initialreadGAInRules\">\n <i class=\"fa fa-question-circle-o\"></i>\n <span data-i18n=\"knxUltimateAlerter.properties.node-input-initialreadGAInRules\"></span>\n </label>\n <select style=\"width:30%\" id=\"node-input-initialreadGAInRules\">\n <option value=\"0\" data-i18n=\"knxUltimateAlerter.properties.node-input-initialread0\"></option>\n <option value=\"1\" data-i18n=\"knxUltimateAlerter.properties.node-input-initialread1\"></option>\n </select>\n \n </div>\n<div class=\"form-row node-input-rule-container-row\">\n <ol id=\"node-input-rule-container\"></ol>\n</div>\n\n<div class=\"form-row\">\n <p><span data-i18n=\"knxUltimateAlerter.other.add\"></span></p>\n</div>",
|
|
2340
|
+
"autoresponder": "<div class=\"form-row\">\n <label for=\"node-input-commandText\"><i class=\"fa fa-tasks\"></i> <span data-i18n=\"knxUltimateAutoResponder.respondTo\"></span></label>\n <input type=\"text\" id=\"node-input-commandText\">\n</div>",
|
|
2341
|
+
"datetime": "<hr>\n <div class=\"form-row\" style=\"margin:4px 0 2px;\">\n <span style=\"font-weight:bold;\" data-i18n=\"knxUltimateDateTime.section_addresses\"></span>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaDateTime\" style=\"width:155px\"><i class=\"fa fa-calendar\"></i> <span data-i18n=\"knxUltimateDateTime.gaDateTime\"></span></label>\n <input type=\"text\" id=\"node-input-gaDateTime\" style=\"width:110px\" placeholder=\"1/7/1\" data-i18n=\"[placeholder]knxUltimateDateTime.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameDateTime\" style=\"flex:1; min-width:70px\" placeholder=\"DateTime object\" data-i18n=\"[placeholder]knxUltimateDateTime.placeholders.nameDateTime\">\n <label for=\"node-input-dptDateTime\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptDateTime\" style=\"width:75px\" readonly>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaDate\" style=\"width:155px\"><i class=\"fa fa-calendar-o\"></i> <span data-i18n=\"knxUltimateDateTime.gaDate\"></span></label>\n <input type=\"text\" id=\"node-input-gaDate\" style=\"width:110px\" placeholder=\"1/7/2\" data-i18n=\"[placeholder]knxUltimateDateTime.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameDate\" style=\"flex:1; min-width:70px\" placeholder=\"Date object\" data-i18n=\"[placeholder]knxUltimateDateTime.placeholders.nameDate\">\n <label for=\"node-input-dptDate\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptDate\" style=\"width:75px\" readonly>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaTime\" style=\"width:155px\"><i class=\"fa fa-clock-o\"></i> <span data-i18n=\"knxUltimateDateTime.gaTime\"></span></label>\n <input type=\"text\" id=\"node-input-gaTime\" style=\"width:110px\" placeholder=\"1/7/3\" data-i18n=\"[placeholder]knxUltimateDateTime.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameTime\" style=\"flex:1; min-width:70px\" placeholder=\"Time object\" data-i18n=\"[placeholder]knxUltimateDateTime.placeholders.nameTime\">\n <label for=\"node-input-dptTime\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptTime\" style=\"width:75px\" readonly>\n </div>\n\n <hr>\n <div class=\"form-row\" style=\"margin:4px 0 2px;\">\n <span style=\"font-weight:bold;\" data-i18n=\"knxUltimateDateTime.section_send\"></span>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-sendOnDeploy\" style=\"width:180px\"><i class=\"fa fa-play\"></i> <span data-i18n=\"knxUltimateDateTime.node-input-sendOnDeploy\"></span></label>\n <input type=\"checkbox\" id=\"node-input-sendOnDeploy\" style=\"width:auto\">\n </div>\n\n <div class=\"form-row knx-datetime-deploy-options\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-sendOnDeployDelay\" style=\"width:180px\"><i class=\"fa fa-clock-o\"></i> <span data-i18n=\"knxUltimateDateTime.node-input-sendOnDeployDelay\"></span></label>\n <input type=\"number\" id=\"node-input-sendOnDeployDelay\" style=\"width:120px\">\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-periodicSend\" style=\"width:180px\"><i class=\"fa fa-repeat\"></i> <span data-i18n=\"knxUltimateDateTime.node-input-periodicSend\"></span></label>\n <input type=\"checkbox\" id=\"node-input-periodicSend\" style=\"width:auto\">\n </div>\n\n <div class=\"form-row knx-datetime-periodic-options\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-periodicSendInterval\" style=\"width:180px\"><i class=\"fa fa-hourglass\"></i> <span data-i18n=\"knxUltimateDateTime.node-input-periodicSendInterval\"></span></label>\n <input type=\"number\" id=\"node-input-periodicSendInterval\" style=\"width:120px\">\n <select id=\"node-input-periodicSendUnit\" style=\"width:160px\">\n <option value=\"s\" data-i18n=\"knxUltimateDateTime.unit_seconds\"></option>\n <option value=\"m\" data-i18n=\"knxUltimateDateTime.unit_minutes\"></option>\n </select>\n </div>",
|
|
2342
|
+
"watchdog": "<div class=\"form-row\">\n <label for=\"node-input-checkLevel\"><i class=\"fa fa-search\"></i> <span data-i18n=\"knxUltimateWatchDog.properties.node-input-checkLevel\"></span> </label>\n <select id=\"node-input-checkLevel\">\n <option value=\"Ethernet\" data-i18n=\"knxUltimateWatchDog.selectlists.Ethernet\"></option>\n <option value=\"Eth+KNX\" data-i18n=\"knxUltimateWatchDog.selectlists.EthKNX\"></option>\n </select>\n </div>\n <div class=\"form-row\" id=\"divHost\">\n <label for=\"node-input-topic\"><i class=\"fa fa-tasks\"></i> <span data-i18n=\"knxUltimateWatchDog.properties.node-input-topic\"></span></label>\n <input style=\"width:90px;\" type=\"text\" id=\"node-input-topic\" data-i18n=\"[placeholder]knxUltimateWatchDog.placeholder.monitor\"> <span data-i18n=\"knxUltimateWatchDog.booleanHint\"></span>\n </div> \n \n <div class=\"form-row\">\n <input type=\"checkbox\" id=\"node-input-autoStart\" style=\"display:inline-block; width:auto; vertical-align:top;\">\n <label style=\"width:auto\" for=\"node-input-autoStart\"> <i class=\"fa fa-play-circle\"></i> <span data-i18n=\"knxUltimateWatchDog.properties.node-input-autoStart\"></span> </label>\n </div>\n <div class=\"form-row\">\n <input type=\"checkbox\" id=\"node-input-listenToKnxUltimateNodeErrors\" style=\"display:inline-block; width:auto; vertical-align:top;\">\n <label style=\"width:auto\" for=\"node-input-listenToKnxUltimateNodeErrors\"> <i class=\"fa fa-exclamation-triangle\"></i> <span data-i18n=\"knxUltimateWatchDog.properties.node-input-listenToKnxUltimateNodeErrors\"></span> </label>\n </div>\n \n <div id=\"advancedOptionsAccordion\">\n <h3><span data-i18n=\"knxUltimateWatchDog.properties.advancedOptionsAccordion\"></span></h3>\n <div>\n <p>\n <div class=\"form-row\">\n <label for=\"node-input-retryInterval\"><i class=\"fa fa-clock-o\"></i> <span data-i18n=\"knxUltimateWatchDog.properties.node-input-retryInterval\"></span></label>\n <input type=\"text\" id=\"node-input-retryInterval\">\n </div>\n <div class=\"form-row\">\n <label for=\"node-input-maxRetry\"><i class=\"fa fa-undo\"></i> <span data-i18n=\"knxUltimateWatchDog.properties.node-input-maxRetry\"></span></label>\n <input type=\"text\" id=\"node-input-maxRetry\">\n </div>\n </p>\n </div>\n </div>",
|
|
2343
|
+
"globalcontext": "<div class=\"form-tips\" style=\"margin-bottom:16px\" data-i18n=\"knxUltimateGlobalContext.advanced.warning\"></div>\n\n<div class=\"form-row\">\n <label for=\"node-input-exposeAsVariable\" style=\"width:60%;\">\n <i class=\"fa fa-link\"></i>\n <span data-i18n=\"knxUltimateGlobalContext.advanced.exposeAsVariable\"></span>\n </label>\n <select id=\"node-input-exposeAsVariable\" style=\"width:35%;\">\n <option value=\"exposeAsVariableNO\" data-i18n=\"knxUltimateGlobalContext.advanced.exposeAsVariableNO\"></option>\n <option value=\"exposeAsVariableREADONLY\" data-i18n=\"knxUltimateGlobalContext.advanced.exposeAsVariableREADONLY\"></option>\n <option value=\"exposeAsVariableREADWRITE\" data-i18n=\"knxUltimateGlobalContext.advanced.exposeAsVariableREADWRITE\"></option>\n </select>\n</div>\n\n<div class=\"form-row\">\n <label for=\"node-input-writeExecutionInterval\" style=\"width:60%;\">\n <i class=\"fa fa-link\"></i>\n <span data-i18n=\"knxUltimateGlobalContext.advanced.writeExecutionInterval\"></span>\n </label>\n <select id=\"node-input-writeExecutionInterval\" style=\"width:35%;\">\n <option value=250 data-i18n=\"knxUltimateGlobalContext.interval_250ms\"></option>\n <option value=500 data-i18n=\"knxUltimateGlobalContext.interval_500ms\"></option>\n <option value=1000 data-i18n=\"knxUltimateGlobalContext.interval_1000ms_default\"></option>\n <option value=1500 data-i18n=\"knxUltimateGlobalContext.interval_1500ms\"></option>\n <option value=2000 data-i18n=\"knxUltimateGlobalContext.interval_2000ms\"></option>\n </select>\n</div>\n\n<div class=\"form-row\">\n <label for=\"node-input-contextStorage\" style=\"width:60%;\">\n <i class=\"fa fa-tag\"></i> <span data-i18n=\"knxUltimateGlobalContext.contextStorage\"></span>\n </label>\n <input style=\"width:35%;\" type=\"text\" id=\"node-input-contextStorage\" data-i18n=\"[placeholder]knxUltimateGlobalContext.contextStoragePlaceholder\" />\n</div>",
|
|
2344
|
+
"logger": "<div class=\"form-row\">\n <label for=\"node-input-topic\"><i class=\"fa fa-tasks\"></i> <span data-i18n=\"knxUltimateLogger.properties.node-input-topic\"></span></label>\n <input type=\"text\" id=\"node-input-topic\" data-i18n=\"[placeholder]knxUltimateLogger.properties.node-input-topic\">\n </div>\n \n \n \n <div id=\"mlxETSFileAccordion\">\n <h3><span data-i18n=\"knxUltimateLogger.properties.mlxETSFileAccordion\"></span></h3>\n <div>\n <p>\n <div class=\"form-row\">\n <input type=\"checkbox\" id=\"node-input-autoStartTimerCreateETSXML\" style=\"display:inline-block; width:auto; vertical-align:top;\">\n <label style=\"width:auto\" for=\"node-input-autoStartTimerCreateETSXML\"> <i class=\"fa fa-play-circle\"></i> <span data-i18n=\"knxUltimateLogger.properties.node-input-autoStartTimerCreateETSXML\"></span> </label>\n </div> \n <div class=\"form-row\">\n <label style=\"width:290px\" for=\"node-input-saveMode\"><i class=\"fa fa-save\"></i> <span data-i18n=\"knxUltimateLogger.properties.node-input-saveMode\"></span></label>\n <select style=\"width:100%\" id=\"node-input-saveMode\">\n <option value=\"emit\" data-i18n=\"knxUltimateLogger.selectlists.saveMode.emit\"></option>\n <option value=\"emit_save\" data-i18n=\"knxUltimateLogger.selectlists.saveMode.emit_save\"></option>\n </select>\n </div>\n <div class=\"form-row\" id=\"knx-logger-filePath-row\">\n <label style=\"width:290px\" for=\"node-input-filePath\"><i class=\"fa fa-file-text-o\"></i> <span data-i18n=\"knxUltimateLogger.properties.node-input-filePath\"></span></label>\n <input style=\"width:calc(100% - 80px)\" type=\"text\" id=\"node-input-filePath\" data-i18n=\"[placeholder]knxUltimateLogger.placeholder.node-input-filePath\">\n <button type=\"button\" class=\"red-ui-button\" id=\"knx-logger-downloadButton\" style=\"margin-left:6px;\" title=\"Download\">\n <i class=\"fa fa-download\"></i>\n </button>\n </div> \n <div class=\"form-row\">\n <label style=\"width:290px\" for=\"node-input-intervalCreateETSXML\"><i class=\"fa fa-clock-o\"></i> <span data-i18n=\"knxUltimateLogger.properties.node-input-intervalCreateETSXML\"></span></label>\n <input style=\"width:90px\" type=\"number\" id=\"node-input-intervalCreateETSXML\">\n </div>\n <div class=\"form-row\">\n <label style=\"width:290px\" for=\"node-input-maxRowsInETSXML\"><i class=\"fa fa-bars\"></i> <span data-i18n=\"knxUltimateLogger.properties.node-input-maxRowsInETSXML\"></span></label>\n <input style=\"width:90px\" type=\"number\" id=\"node-input-maxRowsInETSXML\">\n </div> \n </p>\n </div>\n\n <h3><span data-i18n=\"knxUltimateLogger.properties.telegramCounter\"></span></h3>\n <div>\n <p>\n <div class=\"form-row\">\n <input type=\"checkbox\" id=\"node-input-autoStartTimerTelegramCounter\" style=\"display:inline-block; width:auto; vertical-align:top;\">\n <label style=\"width:auto\" for=\"node-input-autoStartTimerTelegramCounter\"> <i class=\"fa fa-play-circle\"></i> <span data-i18n=\"knxUltimateLogger.properties.node-input-autoStartTimerCreateETSXML\"></span> </label>\n </div> \n <div class=\"form-row\">\n <label style=\"width:290px\" for=\"node-input-intervalTelegramCount\"><i class=\"fa fa-clock-o\"></i> <span data-i18n=\"knxUltimateLogger.properties.node-input-intervalTelegramCount\"></span></label>\n <input style=\"width:90px\" type=\"number\" id=\"node-input-intervalTelegramCount\">\n </div> \n </p>\n </div>\n </div>",
|
|
2345
|
+
"staircase": "<div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-outputtopic\" style=\"width:180px\"><i class=\"fa fa-comment\"></i> <span data-i18n=\"knxUltimateStaircase.node-input-outputtopic\"></span></label>\n <input type=\"text\" id=\"node-input-outputtopic\" style=\"flex:1\" placeholder=\"events/staircase\" data-i18n=\"[placeholder]knxUltimateStaircase.placeholders.outputtopic\">\n </div>\n\n <hr>\n <div class=\"form-row\" style=\"margin:4px 0 2px;\">\n <span style=\"font-weight:bold;\" data-i18n=\"knxUltimateStaircase.section_commands\"></span>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaOutput\" style=\"width:155px\"><i class=\"fa fa-lightbulb-o\"></i> <span data-i18n=\"knxUltimateStaircase.output\"></span></label>\n <input type=\"text\" id=\"node-input-gaOutput\" style=\"width:110px\" placeholder=\"1/1/2\" data-i18n=\"[placeholder]knxUltimateStaircase.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameOutput\" style=\"flex:1; min-width:70px\" placeholder=\"Staircase actuator\" data-i18n=\"[placeholder]knxUltimateStaircase.placeholders.outputName\">\n <label for=\"node-input-dptOutput\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptOutput\" style=\"width:75px\" readonly>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaStatus\" style=\"width:155px\"><i class=\"fa fa-info-circle\"></i> <span data-i18n=\"knxUltimateStaircase.status\"></span></label>\n <input type=\"text\" id=\"node-input-gaStatus\" style=\"width:110px\" placeholder=\"1/1/3\" data-i18n=\"[placeholder]knxUltimateStaircase.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameStatus\" style=\"flex:1; min-width:70px\" placeholder=\"Status LED\" data-i18n=\"[placeholder]knxUltimateStaircase.placeholders.statusName\">\n <label for=\"node-input-dptStatus\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptStatus\" style=\"width:75px\" readonly>\n </div>\n\n <div style=\"border-top:1px solid #ccc; margin:10px 0 6px;\"></div>\n\n <div class=\"form-row\" style=\"margin:4px 0 2px;\">\n <span style=\"font-weight:bold;\" data-i18n=\"knxUltimateStaircase.section_inputs\"></span>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaTrigger\" style=\"width:155px\"><i class=\"fa fa-bolt\"></i> <span data-i18n=\"knxUltimateStaircase.trigger\"></span></label>\n <input type=\"text\" id=\"node-input-gaTrigger\" style=\"width:110px\" placeholder=\"1/1/1\" data-i18n=\"[placeholder]knxUltimateStaircase.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameTrigger\" style=\"flex:1; min-width:70px\" placeholder=\"Living room switch\" data-i18n=\"[placeholder]knxUltimateStaircase.placeholders.triggerName\">\n <label for=\"node-input-dptTrigger\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptTrigger\" style=\"width:75px\" readonly>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaOverride\" style=\"width:155px\"><i class=\"fa fa-toggle-on\"></i> <span data-i18n=\"knxUltimateStaircase.override\"></span></label>\n <input type=\"text\" id=\"node-input-gaOverride\" style=\"width:110px\" placeholder=\"1/1/4\" data-i18n=\"[placeholder]knxUltimateStaircase.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameOverride\" style=\"flex:1; min-width:70px\" placeholder=\"Maintenance override\" data-i18n=\"[placeholder]knxUltimateStaircase.placeholders.overrideName\">\n <label for=\"node-input-dptOverride\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptOverride\" style=\"width:75px\" readonly>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaBlock\" style=\"width:155px\"><i class=\"fa fa-hand-paper-o\"></i> <span data-i18n=\"knxUltimateStaircase.block\"></span></label>\n <input type=\"text\" id=\"node-input-gaBlock\" style=\"width:110px\" placeholder=\"1/1/5\" data-i18n=\"[placeholder]knxUltimateStaircase.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameBlock\" style=\"flex:1; min-width:70px\" placeholder=\"Block command\" data-i18n=\"[placeholder]knxUltimateStaircase.placeholders.blockName\">\n <label for=\"node-input-dptBlock\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptBlock\" style=\"width:75px\" readonly>\n </div>\n\n <hr>\n <div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-timerSeconds\" style=\"width:180px\"><i class=\"fa fa-clock-o\"></i> <span data-i18n=\"knxUltimateStaircase.node-input-timerSeconds\"></span></label>\n <input type=\"number\" id=\"node-input-timerSeconds\" style=\"width:140px\" min=\"1\">\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-extendMode\" style=\"width:180px\"><i class=\"fa fa-repeat\"></i> <span data-i18n=\"knxUltimateStaircase.node-input-extendMode\"></span></label>\n <select id=\"node-input-extendMode\" style=\"flex:1\">\n <option value=\"restart\" data-i18n=\"knxUltimateStaircase.extend_restart\"></option>\n <option value=\"extend\" data-i18n=\"knxUltimateStaircase.extend_extend\"></option>\n <option value=\"ignore\" data-i18n=\"knxUltimateStaircase.extend_ignore\"></option>\n </select>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-triggerOffCancels\" style=\"width:180px\"><i class=\"fa fa-power-off\"></i> <span data-i18n=\"knxUltimateStaircase.node-input-triggerOffCancels\"></span></label>\n <select id=\"node-input-triggerOffCancels\" style=\"flex:1\">\n <option value=\"yes\" data-i18n=\"knxUltimateStaircase.opt_yes\"></option>\n <option value=\"no\" data-i18n=\"knxUltimateStaircase.opt_no\"></option>\n </select>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-blockAction\" style=\"width:180px\"><i class=\"fa fa-ban\"></i> <span data-i18n=\"knxUltimateStaircase.node-input-blockAction\"></span></label>\n <select id=\"node-input-blockAction\" style=\"flex:1\">\n <option value=\"off\" data-i18n=\"knxUltimateStaircase.block_off\"></option>\n <option value=\"keep\" data-i18n=\"knxUltimateStaircase.block_keep\"></option>\n </select>\n </div>\n\n <hr>\n <div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-preWarnEnable\" style=\"width:180px\"><i class=\"fa fa-exclamation-triangle\"></i> <span data-i18n=\"knxUltimateStaircase.node-input-preWarnEnable\"></span></label>\n <input type=\"checkbox\" id=\"node-input-preWarnEnable\" style=\"width:auto\">\n </div>\n\n <div class=\"form-row knx-staircase-prewarn\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-preWarnSeconds\" style=\"width:180px\"><i class=\"fa fa-hourglass-end\"></i> <span data-i18n=\"knxUltimateStaircase.node-input-preWarnSeconds\"></span></label>\n <input type=\"number\" id=\"node-input-preWarnSeconds\" style=\"width:140px\" min=\"1\">\n </div>\n\n <div class=\"form-row knx-staircase-prewarn\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-preWarnMode\" style=\"width:180px\"><i class=\"fa fa-bullhorn\"></i> <span data-i18n=\"knxUltimateStaircase.node-input-preWarnMode\"></span></label>\n <select id=\"node-input-preWarnMode\" style=\"flex:1\">\n <option value=\"status\" data-i18n=\"knxUltimateStaircase.prewarn_status\"></option>\n <option value=\"flash\" data-i18n=\"knxUltimateStaircase.prewarn_flash\"></option>\n </select>\n </div>\n\n <div class=\"form-row knx-staircase-prewarn knx-staircase-prewarn-flash\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-preWarnFlashMs\" style=\"width:180px\"><i class=\"fa fa-lightbulb-o\"></i> <span data-i18n=\"knxUltimateStaircase.node-input-preWarnFlashMs\"></span></label>\n <input type=\"number\" id=\"node-input-preWarnFlashMs\" style=\"width:140px\" min=\"50\">\n </div>\n\n <hr>\n <div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-emitEvents\" style=\"width:180px\"><i class=\"fa fa-sign-out\"></i> <span data-i18n=\"knxUltimateStaircase.node-input-emitEvents\"></span></label>\n <input type=\"checkbox\" id=\"node-input-emitEvents\" style=\"width:auto\">\n </div>\n\n <br/><br/><br/><br/>",
|
|
2346
|
+
"garage": "<div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-outputtopic\" style=\"width:180px\"><i class=\"fa fa-comment\"></i> <span data-i18n=\"knxUltimateGarage.node-input-outputtopic\"></span></label>\n <input type=\"text\" id=\"node-input-outputtopic\" style=\"flex:1\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.outputtopic\">\n </div>\n\n <hr>\n <div class=\"form-row\" style=\"margin:4px 0 2px;\">\n <span style=\"font-weight:bold;\" data-i18n=\"knxUltimateGarage.section_commands\"></span>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaCommand\" style=\"width:155px\"><i class=\"fa fa-exchange\"></i> <span data-i18n=\"knxUltimateGarage.command\"></span></label>\n <input type=\"text\" id=\"node-input-gaCommand\" style=\"width:110px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameCommand\" style=\"flex:1; min-width:70px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.commandName\">\n <label for=\"node-input-dptCommand\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptCommand\" style=\"width:75px\" readonly>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaImpulse\" style=\"width:155px\"><i class=\"fa fa-bolt\"></i> <span data-i18n=\"knxUltimateGarage.impulse\"></span></label>\n <input type=\"text\" id=\"node-input-gaImpulse\" style=\"width:110px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameImpulse\" style=\"flex:1; min-width:70px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.impulseName\">\n <label for=\"node-input-dptImpulse\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptImpulse\" style=\"width:75px\" readonly>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaMoving\" style=\"width:155px\"><i class=\"fa fa-arrows-h\"></i> <span data-i18n=\"knxUltimateGarage.moving\"></span></label>\n <input type=\"text\" id=\"node-input-gaMoving\" style=\"width:110px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameMoving\" style=\"flex:1; min-width:70px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.movingName\">\n <label for=\"node-input-dptMoving\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptMoving\" style=\"width:75px\" readonly>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaObstruction\" style=\"width:155px\"><i class=\"fa fa-exclamation-triangle\"></i> <span data-i18n=\"knxUltimateGarage.obstruction\"></span></label>\n <input type=\"text\" id=\"node-input-gaObstruction\" style=\"width:110px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameObstruction\" style=\"flex:1; min-width:70px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.obstructionName\">\n <label for=\"node-input-dptObstruction\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptObstruction\" style=\"width:75px\" readonly>\n </div>\n\n <div style=\"border-top:1px solid #ccc; margin:10px 0 6px;\"></div>\n\n <div class=\"form-row\" style=\"margin:4px 0 2px;\">\n <span style=\"font-weight:bold;\" data-i18n=\"knxUltimateGarage.section_inputs\"></span>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaHoldOpen\" style=\"width:155px\"><i class=\"fa fa-pause\"></i> <span data-i18n=\"knxUltimateGarage.holdOpen\"></span></label>\n <input type=\"text\" id=\"node-input-gaHoldOpen\" style=\"width:110px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameHoldOpen\" style=\"flex:1; min-width:70px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.holdOpenName\">\n <label for=\"node-input-dptHoldOpen\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptHoldOpen\" style=\"width:75px\" readonly>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaDisable\" style=\"width:155px\"><i class=\"fa fa-ban\"></i> <span data-i18n=\"knxUltimateGarage.disable\"></span></label>\n <input type=\"text\" id=\"node-input-gaDisable\" style=\"width:110px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.ga\">\n <input type=\"text\" id=\"node-input-nameDisable\" style=\"flex:1; min-width:70px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.disableName\">\n <label for=\"node-input-dptDisable\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptDisable\" style=\"width:75px\" readonly>\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px;\">\n <label for=\"node-input-gaPhotocell\" style=\"width:155px\"><i class=\"fa fa-lightbulb-o\"></i> <span data-i18n=\"knxUltimateGarage.photocell\"></span></label>\n <input type=\"text\" id=\"node-input-gaPhotocell\" style=\"width:110px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.ga\">\n <input type=\"text\" id=\"node-input-namePhotocell\" style=\"flex:1; min-width:70px\" data-i18n=\"[placeholder]knxUltimateGarage.placeholders.photocellName\">\n <label for=\"node-input-dptPhotocell\" style=\"width:30px; text-align:right\">DPT</label>\n <input type=\"text\" id=\"node-input-dptPhotocell\" style=\"width:75px\" readonly>\n </div>\n\n <hr>\n <div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-autoCloseEnable\" style=\"width:180px\"><i class=\"fa fa-clock-o\"></i> <span data-i18n=\"knxUltimateGarage.autoCloseEnable\"></span></label>\n <input type=\"checkbox\" id=\"node-input-autoCloseEnable\" style=\"width:auto\">\n </div>\n\n <div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-autoCloseSeconds\" style=\"width:180px\"><i class=\"fa fa-hourglass-end\"></i> <span data-i18n=\"knxUltimateGarage.autoCloseSeconds\"></span></label>\n <input type=\"number\" id=\"node-input-autoCloseSeconds\" style=\"width:140px\" min=\"1\">\n </div>\n\n <hr>\n <div class=\"form-row\" style=\"display:flex; align-items:center;\">\n <label for=\"node-input-emitEvents\" style=\"width:180px\"><i class=\"fa fa-sign-out\"></i> <span data-i18n=\"knxUltimateGarage.node-input-emitEvents\"></span></label>\n <input type=\"checkbox\" id=\"node-input-emitEvents\" style=\"width:auto\">\n </div>\n\n <br/><br/><br/><br/>",
|
|
2347
|
+
"scenecontroller": "<div id=\"GAandDPT\">\n <div class=\"form-row\">\n <label for=\"node-input-topic\" style=\"width:100px;\"><i class=\"fa fa-play\"></i> <span data-i18n=\"knxUltimateSceneController.properties.node-input-topic\"></span></label>\n <input type=\"text\" id=\"node-input-topic\" placeholder=\"Ex: 1/1/1\" style=\"width:80px;margin-left: 5px; text-align: left;\">\n \n <label for=\"node-input-dpt\" style=\"width:90px; margin-left: 0px; text-align: right;\"><i class=\"fa fa-microchip\"></i>\n <span data-i18n=\"knxUltimateSceneController.properties.node-input-dpt\"></span> </label>\n <select id=\"node-input-dpt\" style=\"width:140px;\"></select>\n \n <label for=\"node-input-topicTrigger\" style=\"width:60px;text-align: right;\"><i class=\"fa fa-bolt\"></i> <span data-i18n=\"knxUltimateSceneController.properties.node-input-topicTrigger\"></span></label>\n <input type=\"text\" id=\"node-input-topicTrigger\" placeholder=\"Ex: 5 or true\" style=\"width:100px;margin-left: 5px; text-align: left;\">\n \n </div>\n <div class=\"form-row\">\n <label for=\"node-input-topicSave\" style=\"width:100px;\"><i class=\"fa fa-floppy-o\"></i> <span data-i18n=\"knxUltimateSceneController.properties.node-input-topicSave\"></span></label>\n <input type=\"text\" id=\"node-input-topicSave\" placeholder=\"Ex: 1/1/2\" style=\"width:80px;margin-left: 5px; text-align: left;\">\n \n <label for=\"node-input-dptSave\" style=\"width:90px; margin-left: 0px; text-align: right;\"><i class=\"fa fa-microchip\"></i>\n <span data-i18n=\"knxUltimateSceneController.properties.node-input-dpt\"></span> </label>\n <select id=\"node-input-dptSave\" style=\"width:140px;\"></select>\n\n <label for=\"node-input-topicSaveTrigger\" style=\"width:60px;text-align: right;\"><i class=\"fa fa-bolt\"></i> <span data-i18n=\"knxUltimateSceneController.properties.node-input-topicTrigger\"></span></label>\n <input type=\"text\" id=\"node-input-topicSaveTrigger\" data-i18n=\"[placeholder]knxUltimateSceneController.placeholder.valueexample\" style=\"width:100px;margin-left: 5px; text-align: left;\">\n </div>\n</div>\n\n<div class=\"form-row\" id=\"divTopic\">\n <label for=\"node-input-outputtopic\"><i class=\"fa fa-tasks\"></i> <span data-i18n=\"knxUltimateSceneController.properties.node-input-outputtopic\"></span> </label>\n <input type=\"text\" id=\"node-input-outputtopic\" data-i18n=\"[placeholder]knxUltimateSceneController.placeholder.leaveempty\">\n</div>\n\n\n<dt><i class=\"fa fa-code-fork\"></i> <span data-i18n=\"knxUltimateSceneController.other.sceneConfig\"></span></dt>\n<div class=\"form-row node-input-rule-container-row\">\n <ol id=\"node-input-rule-container\"></ol>\n</div>\n\n<div class=\"form-row\">\n <p><span data-i18n=\"knxUltimateSceneController.other.add\"></span></p>\n</div>",
|
|
2348
|
+
"loadcontrol": "<div class=\"form-row\">\n <i class=\"fa fa-sliders\"></i>\n <label style=\"width:100px\" for=\"node-input-controlMode\">\n <span data-i18n=\"knxUltimateLoadControl.properties.node-input-controlMode\"></span>\n </label>\n <select style=\"width:40%\" id=\"node-input-controlMode\">\n <option value=\"auto\" data-i18n=\"knxUltimateLoadControl.selectlists.controlModeAuto\"></option>\n <option value=\"msg\" data-i18n=\"knxUltimateLoadControl.selectlists.controlModeMsg\"></option>\n </select>\n</div>\n\n<div class=\"form-row knx-lc-auto-only\">\n <i class=\"fa fa-battery-full\"></i>\n <label style=\"width:100px\" for=\"node-input-topic\">\n <span data-i18n=\"knxUltimateLoadControl.properties.node-input-topic\"></span>\n </label>\n <input style=\"width:15%\" type=\"text\" id=\"node-input-topic\" placeholder=\"Ex: 1/1/1\" />\n <select style=\"width:15%\" id=\"node-input-dpt\"></select>\n</div>\n\n<div class=\"form-row knx-lc-auto-only\">\n <i class=\"fa fa-exclamation\"></i>\n <label style=\"width:125px\" for=\"node-input-wattLimit\">\n <span data-i18n=\"knxUltimateLoadControl.properties.node-input-wattLimit\"></span>\n </label>\n <input style=\"width:40%\" type=\"text\" id=\"node-input-wattLimit\" placeholder=\"Ex. 3000\"> \n</div>\n\n<div class=\"form-row knx-lc-auto-only\">\n <i class=\"fa fa-toggle-off\"></i>\n <label style=\"width:125px\" for=\"node-input-sheddingCheckInterval\">\n <span data-i18n=\"knxUltimateLoadControl.properties.node-input-sheddingCheckInterval\"></span>\n </label>\n <input style=\"width:40%\" type=\"text\" id=\"node-input-sheddingCheckInterval\" placeholder=\"\"> \n</div>\n\n<div class=\"form-row knx-lc-auto-only\">\n <i class=\"fa fa-toggle-on\"></i>\n <label style=\"width:125px\" for=\"node-input-sheddingRestoreDelay\">\n <span data-i18n=\"knxUltimateLoadControl.properties.node-input-sheddingRestoreDelay\"></span>\n </label>\n <input style=\"width:40%\" type=\"text\" id=\"node-input-sheddingRestoreDelay\" placeholder=\"\"> \n</div>\n\n\n\n<!-- LOAD CONTROL -->\n<hr>\n<b><span data-i18n=\"knxUltimateLoadControl.title\"></span> 1</b> <span data-i18n=\"knxUltimateLoadControl.primoaldistacco\"></span> <br/><br/>\n\n\n<div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px; flex-wrap:wrap;\">\n <i class=\"fa fa-power-off\"></i>\n \n <input style=\"width:95px; flex:0 0 95px; min-width:0\" type=\"text\" id=\"node-input-GA1\" placeholder=\"Ex: 1/1/1\" />\n <select style=\"width:105px; flex:0 0 105px; min-width:0\" id=\"node-input-DPT1\"></select>\n <input style=\"flex:1 1 120px; min-width:90px\" type=\"text\" id=\"node-input-Name1\" data-i18n=\"[placeholder]knxUltimateLoadControl.properties.node-input-name\">\n</div>\n<div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px; flex-wrap:wrap;\">\n <i class=\"fa fa-battery-half\"></i>\n \n <input style=\"width:95px; flex:0 0 95px; min-width:0\" type=\"text\" id=\"node-input-MonitorGA1\" placeholder=\"Optional\" />\n <select style=\"width:105px; flex:0 0 105px; min-width:0\" id=\"node-input-MonitorDPT1\"></select>\n <input style=\"flex:1 1 120px; min-width:90px\" type=\"text\" id=\"node-input-MonitorName1\" data-i18n=\"[placeholder]knxUltimateLoadControl.properties.node-input-name\">\n</div>\n<div class=\"form-row\">\n <input type=\"checkbox\" id=\"node-input-autoRestore1\" style=\"display:inline-block; width:auto; vertical-align:top;\">\n \n <label style=\"width:85%\" for=\"node-input-autoRestore1\">\n <i class=\"fa fa-toggle-on\"></i>\n <span data-i18n=\"knxUltimateLoadControl.properties.node-input-autoRestore\"></span>\n </label>\n</div>\n\n<hr>\n<b><span data-i18n=\"knxUltimateLoadControl.title\"></span> 2</b><br/><br/>\n\n\n<div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px; flex-wrap:wrap;\">\n <i class=\"fa fa-power-off\"></i>\n \n <input style=\"width:95px; flex:0 0 95px; min-width:0\" type=\"text\" id=\"node-input-GA2\" placeholder=\"Control GA\" />\n <select style=\"width:105px; flex:0 0 105px; min-width:0\" id=\"node-input-DPT2\"></select>\n <input style=\"flex:1 1 120px; min-width:90px\" type=\"text\" id=\"node-input-Name2\" data-i18n=\"[placeholder]knxUltimateLoadControl.properties.node-input-name\">\n</div>\n<div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px; flex-wrap:wrap;\">\n <i class=\"fa fa-battery-half\"></i>\n \n <input style=\"width:95px; flex:0 0 95px; min-width:0\" type=\"text\" id=\"node-input-MonitorGA2\" placeholder=\"Optional\" />\n <select style=\"width:105px; flex:0 0 105px; min-width:0\" id=\"node-input-MonitorDPT2\"></select>\n <input style=\"flex:1 1 120px; min-width:90px\" type=\"text\" id=\"node-input-MonitorName2\" data-i18n=\"[placeholder]knxUltimateLoadControl.properties.node-input-name\">\n</div>\n<div class=\"form-row\">\n <input type=\"checkbox\" id=\"node-input-autoRestore2\" style=\"display:inline-block; width:auto; vertical-align:top;\">\n \n <label style=\"width:85%\" for=\"node-input-autoRestore2\">\n <i class=\"fa fa-toggle-on\"></i>\n <span data-i18n=\"knxUltimateLoadControl.properties.node-input-autoRestore\"></span>\n </label>\n</div>\n\n<hr>\n<b><span data-i18n=\"knxUltimateLoadControl.title\"></span> 3</b><br/><br/>\n\n\n<div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px; flex-wrap:wrap;\">\n <i class=\"fa fa-power-off\"></i>\n \n <input style=\"width:95px; flex:0 0 95px; min-width:0\" type=\"text\" id=\"node-input-GA3\" placeholder=\"Control GA\" />\n <select style=\"width:105px; flex:0 0 105px; min-width:0\" id=\"node-input-DPT3\"></select>\n <input style=\"flex:1 1 120px; min-width:90px\" type=\"text\" id=\"node-input-Name3\" data-i18n=\"[placeholder]knxUltimateLoadControl.properties.node-input-name\">\n</div>\n<div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px; flex-wrap:wrap;\">\n <i class=\"fa fa-battery-half\"></i>\n \n <input style=\"width:95px; flex:0 0 95px; min-width:0\" type=\"text\" id=\"node-input-MonitorGA3\" placeholder=\"Optional\" />\n <select style=\"width:105px; flex:0 0 105px; min-width:0\" id=\"node-input-MonitorDPT3\"></select>\n <input style=\"flex:1 1 120px; min-width:90px\" type=\"text\" id=\"node-input-MonitorName3\" data-i18n=\"[placeholder]knxUltimateLoadControl.properties.node-input-name\">\n</div>\n<div class=\"form-row\">\n <input type=\"checkbox\" id=\"node-input-autoRestore3\" style=\"display:inline-block; width:auto; vertical-align:top;\">\n \n <label style=\"width:85%\" for=\"node-input-autoRestore3\">\n <i class=\"fa fa-toggle-on\"></i>\n <span data-i18n=\"knxUltimateLoadControl.properties.node-input-autoRestore\"></span>\n </label>\n</div>\n\n<hr>\n<b><span data-i18n=\"knxUltimateLoadControl.title\"></span> 4</b><br/><br/>\n\n\n<div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px; flex-wrap:wrap;\">\n <i class=\"fa fa-power-off\"></i>\n \n <input style=\"width:95px; flex:0 0 95px; min-width:0\" type=\"text\" id=\"node-input-GA4\" placeholder=\"Control GA\" />\n <select style=\"width:105px; flex:0 0 105px; min-width:0\" id=\"node-input-DPT4\"></select>\n <input style=\"flex:1 1 120px; min-width:90px\" type=\"text\" id=\"node-input-Name4\" data-i18n=\"[placeholder]knxUltimateLoadControl.properties.node-input-name\">\n</div>\n<div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px; flex-wrap:wrap;\">\n <i class=\"fa fa-battery-half\"></i>\n \n <input style=\"width:95px; flex:0 0 95px; min-width:0\" type=\"text\" id=\"node-input-MonitorGA4\" placeholder=\"Optional\" />\n <select style=\"width:105px; flex:0 0 105px; min-width:0\" id=\"node-input-MonitorDPT4\"></select>\n <input style=\"flex:1 1 120px; min-width:90px\" type=\"text\" id=\"node-input-MonitorName4\" data-i18n=\"[placeholder]knxUltimateLoadControl.properties.node-input-name\">\n</div>\n<div class=\"form-row\">\n <input type=\"checkbox\" id=\"node-input-autoRestore4\" style=\"display:inline-block; width:auto; vertical-align:top;\">\n \n <label style=\"width:85%\" for=\"node-input-autoRestore4\">\n <i class=\"fa fa-toggle-on\"></i>\n <span data-i18n=\"knxUltimateLoadControl.properties.node-input-autoRestore\"></span>\n </label>\n</div>\n\n<hr>\n<b><span data-i18n=\"knxUltimateLoadControl.title\"></span> 5</b><br/><br/>\n\n\n<div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px; flex-wrap:wrap;\">\n <i class=\"fa fa-power-off\"></i>\n \n <input style=\"width:95px; flex:0 0 95px; min-width:0\" type=\"text\" id=\"node-input-GA5\" placeholder=\"Control GA\" />\n <select style=\"width:105px; flex:0 0 105px; min-width:0\" id=\"node-input-DPT5\"></select>\n <input style=\"flex:1 1 120px; min-width:90px\" type=\"text\" id=\"node-input-Name5\" data-i18n=\"[placeholder]knxUltimateLoadControl.properties.node-input-name\">\n</div>\n<div class=\"form-row\" style=\"display:flex; align-items:center; gap:8px; flex-wrap:wrap;\">\n <i class=\"fa fa-battery-half\"></i>\n \n <input style=\"width:95px; flex:0 0 95px; min-width:0\" type=\"text\" id=\"node-input-MonitorGA5\" placeholder=\"Optional\" />\n <select style=\"width:105px; flex:0 0 105px; min-width:0\" id=\"node-input-MonitorDPT5\"></select>\n <input style=\"flex:1 1 120px; min-width:90px\" type=\"text\" id=\"node-input-MonitorName5\" data-i18n=\"[placeholder]knxUltimateLoadControl.properties.node-input-name\">\n</div>\n<div class=\"form-row\">\n <input type=\"checkbox\" id=\"node-input-autoRestore5\" style=\"display:inline-block; width:auto; vertical-align:top;\">\n \n <label style=\"width:85%\" for=\"node-input-autoRestore5\">\n <i class=\"fa fa-toggle-on\"></i>\n <span data-i18n=\"knxUltimateLoadControl.properties.node-input-autoRestore\"></span>\n </label>\n</div>\n\n</br>\n</br>\n</br>\n</br>",
|
|
2349
|
+
"hatranslator": "<div class=\"form-row\">\n <label for=\"node-input-payloadPropName\"><i class=\"fa fa-ellipsis-h\"></i> <span data-i18n=\"knxUltimateHATranslator.inputProperty\"></span></label>\n <input type=\"text\" id=\"node-input-payloadPropName\" placeholder=\"payload\">\n</div>\n<div class=\"form-tips\" style=\"margin-bottom:16px\" data-i18n=\"knxUltimateHATranslator.inputHint\"></div>\n<div class=\"form-row\">\n <label for=\"node-input-editorcommandText\" style=\"width:100%\"><i class=\"fa fa-tasks\"></i> <span data-i18n=\"knxUltimateHATranslator.translations\"></span></label>\n <div style=\"height:250px; min-height:150px; width:100%;\" class=\"node-text-editor\" id=\"node-input-editorcommandText\"></div>\n</div>\n<div class=\"form-tips\" data-i18n=\"knxUltimateHATranslator.translationHint\"></div>"
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
// All supported locales travel with the Utility. The bundle therefore
|
|
2353
|
+
// keeps working after the legacy locale files and node types are removed.
|
|
2354
|
+
const PROFILE_TRANSLATIONS = {"en":{"knxUltimateAlerter":{"knxUltimateAlerter":{"paletteLabel":"KNX Alerter","title":"Alerter node","properties":{"node-input-server":"Gateway","node-input-name":"Name","node-input-timerinterval":"Interval between each MSG (in seconds)","node-input-whentostart":"Alerting cycle start type","node-input-initialread":"Read value of each device on connection/reconnect","node-input-initialread0":"No","node-input-initialread1":"Read from KNX BUS","node-input-initialreadGAInRules":"Read states at start/reconnection"},"selectlists":{"manualstart":"Start alert cycle manually via incoming message","ifnewalert":"Start the alert cycle with each new alerted device"},"other":{"sceneConfig":"Devices to monitor (DPT MUST BE BOOLEAN)","add":"Press Add, to add a device"}}},"knxUltimateAutoResponder":{"knxUltimateAutoResponder":{"paletteLabel":"KNX Auto Responder","respondTo":"Respond to"}},"knxUltimateDateTime":{"knxUltimateDateTime":{"title":"Date/Time","paletteLabel":"DateTime","node-input-server":"KNX Gateway","node-input-name":"Name","node-input-outputtopic":"Topic for node output","section_addresses":"Group addresses","gaDateTime":"DateTime GA (DPT 19.001)","gaDate":"Date GA (DPT 11.001)","gaTime":"Time GA (DPT 10.001)","section_send":"Send options","node-input-sendOnDeploy":"Send on deploy/startup","node-input-sendOnDeployDelay":"Startup delay (seconds)","node-input-periodicSend":"Periodic send","node-input-periodicSendInterval":"Interval","unit_seconds":"Seconds","unit_minutes":"Minutes","notifySent":"Sent to KNX","notifyQueued":"Queued (gateway not connected yet)","placeholders":{"outputtopic":"Optional output topic","ga":"e.g. 1/7/1","nameDateTime":"Optional ETS device name","nameDate":"Optional ETS device name","nameTime":"Optional ETS device name"}}},"knxUltimateWatchDog":{"knxUltimateWatchDog":{"paletteLabel":"KNX WatchDog","title":"Watchdog","properties":{"node-input-server":"Gateway","node-input-checkLevel":"Check level (please see the wiki)","node-input-topic":"Group Address to monitor","node-input-name":"Node Name","node-input-autoStart":"Auto start the watchdog timer","node-input-listenToKnxUltimateNodeErrors":"Listen to KNX-Ultimate node errors","advancedOptionsAccordion":"Advanced Options","node-input-retryInterval":"Retry interval (in seconds)","node-input-maxRetry":"Number of retry before giving an error"},"placeholder":{"monitor":"Ex: 12/0/0. For 'Only Ethernet' checks, please use a non existent Group Address."},"selectlists":{"Ethernet":"Only Ethernet unicast (Default), using ping. Works ONLY with KNX Interfaces (not routers)","EthKNX":"Ethernet + KNX Twisted Pair, using a real KNX device"},"booleanHint":"DPT must be 1.x (Boolean)"}},"knxUltimateGlobalContext":{"knxUltimateGlobalContext":{"paletteLabel":"KNX Global Context","title":"KNX Global Context","node-input-name":"Variable Name (no spaces, only chars [a-z])","advanced":{"exposeAsVariable":"Expose as Global variable","exposeAsVariableNO":"No","exposeAsVariableREADONLY":"Read Only","exposeAsVariableREADWRITE":"Read/Write","node-input-server":"Gateway","writeExecutionInterval":"BUS write interval","warning":"Warning: a single node is shared between ALL FLOWS. You don't need more than one node; regardless of where it is, you can see the variable GLOBALLY."},"interval_250ms":"250ms","interval_500ms":"500ms","interval_1000ms_default":"1000ms (Default)","interval_1500ms":"1500ms","interval_2000ms":"2000ms","contextStorage":"Context storage","contextStoragePlaceholder":"Optional context storage name"}},"knxUltimateLogger":{"knxUltimateLogger":{"paletteLabel":"KNX Logger","title":"KNX logger for ETS","properties":{"node-input-server":"Gateway","node-input-topic":"Topic","node-input-name":"Node Name","node-input-autoStartTimerCreateETSXML":"Auto start timer","mlxETSFileAccordion":"ETS compatible BUS Diagnostic File","node-input-intervalCreateETSXML":"New payload every (in minutes)","node-input-maxRowsInETSXML":"Max number of rows (0 = no limit)","node-input-saveMode":"Action","node-input-filePath":"File path (absolute or relative)","telegramCounter":"KNX Telegram Counter","node-input-intervalTelegramCount":"Count interval (in seconds)"},"placeholder":{"node-input-filePath":"/var/tmp/knx-logger.xml"},"selectlists":{"saveMode":{"emit":"Emit payload only","emit_save":"Emit payload and save to file"}},"noFilePath":"File path is empty"}},"knxUltimateStaircase":{"knxUltimateStaircase":{"title":"Staircase light","paletteLabel":"Staircase","node-input-server":"KNX Gateway","node-input-name":"Name","node-input-outputtopic":"Topic for emitted events","trigger":"Trigger GA","output":"Output GA","status":"Status GA","override":"Override GA","block":"Block GA","node-input-timerSeconds":"Timer duration (seconds)","node-input-extendMode":"On new trigger","extend_restart":"Restart timer","extend_extend":"Extend remaining time","extend_ignore":"Ignore the trigger","node-input-triggerOffCancels":"0 command cancels the cycle","opt_yes":"Yes","opt_no":"No","node-input-blockAction":"When blocked","block_off":"Force light OFF","block_keep":"Leave current state","node-input-preWarnEnable":"Send pre-warning before timeout","node-input-preWarnSeconds":"Pre-warning offset (seconds)","node-input-preWarnMode":"Pre-warning mode","prewarn_status":"Toggle status GA","prewarn_flash":"Flash output","node-input-preWarnFlashMs":"Flash duration (ms)","node-input-emitEvents":"Emit events on node output","help":{"intro":"Implements a staircase light logic with KNX integration, handling timer, pre-warning, manual override and blocking.","trigger":"Triggers","trigger_ga":"A rising edge (value = 1) on the trigger GA starts the timer. If enabled, a value of 0 will immediately cancel the cycle.","extend":"The \"On new trigger\" option controls how subsequent pulses behave: restart the timer, extend the remaining time or ignore the pulse.","off":"When override is active the timer is paused and the light stays on. Blocking disables new pulses and optionally forces the output off.","outputs":"Group addresses","output_ga":"Output GA is driven with DPT 1.001 by default. Configure other DPTs if required.","status_ga":"Status GA mirrors the stair light state (and the pre-warning flag when enabled).","override_ga":"Override GA keeps the light on while true and suspends the timer.","block_ga":"Block GA prevents new activations. When false, normal behaviour resumes.","prewarn":"Pre-warning","prewarn_desc":"Enable a pre-warning to alert occupants before the light turns off. You can either flash the output or toggle the status GA.","events":"Node output","events_desc":"When enabled the node emits structured events (trigger, extend, prewarn, timeout, override, block) with the remaining time and state information."},"placeholders":{"outputtopic":"Optional topic for node events","ga":"e.g. 1/1/5","triggerName":"Trigger device name","outputName":"Output actuator name","statusName":"Status feedback name","overrideName":"Override channel name","blockName":"Block command name"},"section_commands":"Command group addresses","section_inputs":"Input group addresses"}},"knxUltimateGarage":{"knxUltimateGarage":{"title":"Garage door","paletteLabel":"Garage","node-input-server":"KNX Gateway","node-input-name":"Name","node-input-outputtopic":"Topic for emitted events","command":"Command GA (true=open, false=close)","impulse":"Impulse GA","holdOpen":"Hold-open GA","disable":"Disable GA","photocell":"Photocell GA","moving":"Moving GA","obstruction":"Obstruction GA","autoCloseEnable":"Enable auto re-close","autoCloseSeconds":"Auto re-close delay (seconds)","node-input-emitEvents":"Emit events on node output","placeholders":{"outputtopic":"Optional topic for node events","ga":"e.g. 1/2/3","commandName":"Door actuator","impulseName":"Impulse input","holdOpenName":"Hold-open switch","disableName":"Disable command","photocellName":"Photocell sensor","movingName":"Movement indicator","obstructionName":"Obstruction indicator"},"help":{"intro":"Controls a KNX garage door with direct open/close commands, impulse inputs, safety sensors and automatic re-close.","commands":"Commands","command_ga":"Use the command GA for boolean control: true opens, false closes. The node also emits this when the auto close triggers.","impulse_ga":"An impulse GA toggles the door. Rising edges toggle the internal state; it is also used if no direct command GA is configured.","holdopen_ga":"Hold-open GA keeps the door open and cancels the auto re-close until it returns to false.","disable_ga":"Disable GA stops the node from issuing new commands. Useful for maintenance or manual mode.","safety":"Safety and status","photocell_ga":"The photocell GA should go true when the beam is interrupted. The node re-opens the door and flags obstruction.","moving_ga":"Movement GA (optional) is pulsed whenever the node commands the door to move, allowing other devices to follow the motion.","obstruction_ga":"Obstruction GA mirrors the obstruction state so other KNX components can react.","auto":"Automatic re-close","auto_close":"Enable the auto re-close timer to close the door automatically after the configured delay once it is open, unless hold-open or disable are active.","events":"Node output","events_desc":"With event emission enabled the node outputs structured messages (open, close, toggle, obstruction, disabled, hold-open, auto-close) for flow logic."},"section_commands":"Command group addresses","section_inputs":"Input / sensor group addresses"}},"knxUltimateSceneController":{"knxUltimateSceneController":{"paletteLabel":"KNX Scene Controller","title":"Scene controller","properties":{"node-input-server":"Gateway","node-input-topic":"Scene Recall","node-input-dpt":"DPT","node-input-topicTrigger":"Trigger","node-input-topicSave":"Scene Save","node-input-name":"Node Name","node-input-outputtopic":"Topic"},"placeholder":{"leaveempty":"Leave empty to use Group Address","valueexample":"Ex: false, true, otherwise any value"},"other":{"sceneConfig":"Scene default values configuration","add":"Press ADD, to add a device in the scene"},"advanced":{"notify-DPT3007":"You selected a relative DIM. Please <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---Dimming'>click here to view this sample</a> and learn how to handle such payload.","notify-DPT18001":"You selected a scene DPT. Please <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---Control-a-scene-actuator'>click here to view this sample</a> and learn how to handle such payload.","notify-DPT232600":"You selected an RGB DPT. Please <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---RGB-Color'>click here to view this sample</a> and learn how to handle such payload.","notify-DPT251600":"You selected an RGBW DPT. Please <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---RGBW-Color-plus-White'>click here to view this sample</a> and learn how to handle such payload."}}},"knxUltimateLoadControl":{"knxUltimateLoadControl":{"paletteLabel":"KNX Load Control","title":"Load control","primoaldistacco":"(this is the first to be switched off)","properties":{"node-input-server":"Gateway","node-input-name":"NoNameme","node-input-controlMode":"Mode","node-input-dpt":"DPT","node-input-topic":"Monitor W","node-input-controlGA":"Load","node-input-monitorGA":"In use","node-input-wattLimit":"Limit W","node-input-sheddingCheckInterval":"Delay switch off (s)","node-input-sheddingRestoreDelay":"Delay switch on (s)","node-input-autoRestore":"Automatic recovery"},"selectlists":{"controlModeAuto":"Automatic (internal)","controlModeMsg":"Manual (msg.shedding)"},"other":{}}},"knxUltimateHATranslator":{"knxUltimateHATranslator":{"paletteLabel":"Home Assistant Translator","inputProperty":"Input property","inputHint":"Message property to translate, for example payload or payload.state.","translations":"Translations (Home Assistant → KNX)","translationHint":"One translation per line, in the format state:true or state:false. For example: on:true and off:false."}}},"it":{"knxUltimateAlerter":{"knxUltimateAlerter":{"paletteLabel":"Allertatore KNX","title":"Nodo Alerter","properties":{"node-input-server":"Gateway","node-input-name":"Nome nodo","node-input-timerinterval":"Intervallo tra ciascun messaggio (in secondi)","node-input-whentostart":"Modalità avvio allerta","node-input-initialread":"Leggi il valore di ogni dispositivo alla connessione/riconnessione","node-input-initialread0":"No","node-input-initialread1":"Leggi dal BUS KNX","node-input-initialreadGAInRules":"Leggi stati alla connessione/riconnessione"},"selectlists":{"manualstart":"Avvia ciclo allerta manualmente tramite messaggio in ingresso","ifnewalert":"Avvia il ciclo di allerta a ogni nuovo dispositivo in allarme"},"other":{"sceneConfig":"Dispositivi da monitorare (IL DPT DEVE ESSERE BOOLEANO)","add":"Premi Aggiungi per aggiungere un dispositivo"}}},"knxUltimateAutoResponder":{"knxUltimateAutoResponder":{"paletteLabel":"KNX Auto Responder","respondTo":"Rispondi a"}},"knxUltimateDateTime":{"knxUltimateDateTime":{"title":"Data/Ora","paletteLabel":"DataOra","node-input-server":"Gateway KNX","node-input-name":"Nome","node-input-outputtopic":"Topic in uscita del nodo","section_addresses":"Indirizzi di gruppo","gaDateTime":"GA Data/Ora (DPT 19.001)","gaDate":"GA Data (DPT 11.001)","gaTime":"GA Ora (DPT 10.001)","section_send":"Opzioni invio","node-input-sendOnDeploy":"Invia al deploy/avvio","node-input-sendOnDeployDelay":"Ritardo avvio (secondi)","node-input-periodicSend":"Invio periodico","node-input-periodicSendInterval":"Intervallo","unit_seconds":"Secondi","unit_minutes":"Minuti","notifySent":"Inviato su KNX","notifyQueued":"Accodato (gateway non ancora connesso)","placeholders":{"outputtopic":"Topic opzionale in uscita","ga":"es. 1/7/1","nameDateTime":"Nome dispositivo ETS (opz.)","nameDate":"Nome dispositivo ETS (opz.)","nameTime":"Nome dispositivo ETS (opz.)"}}},"knxUltimateWatchDog":{"knxUltimateWatchDog":{"paletteLabel":"WatchDog KNX","title":"Watchdog","properties":{"node-input-server":"Gateway","node-input-checkLevel":"Livello controllo (vedi Wiki)","node-input-topic":"Indirizzo di gruppo da monitorare","node-input-name":"Nome nodo","node-input-autoStart":"Avvia il watchdog automaticamente","node-input-listenToKnxUltimateNodeErrors":"Ascolta gli errori dei nodi KNX-Ultimate","advancedOptionsAccordion":"Opzioni avanzate","node-input-retryInterval":"Riprova ogni (in secondi)","node-input-maxRetry":"Dopo questo numero di tentativi, segnala l'errore"},"placeholder":{"monitor":"Es: 12/0/0. Per 'Solo Ethernet', usa un GA inesistente."},"selectlists":{"Ethernet":"Solo Ethernet unicast (predefinito), tramite ping. Funziona SOLO con interfacce IP (non con router)","EthKNX":"Ethernet + KNX Twisted Pair, usando un vero dispositivo KNX esistente"},"booleanHint":"Il DPT deve essere 1.x (booleano)"}},"knxUltimateGlobalContext":{"knxUltimateGlobalContext":{"paletteLabel":"Contesto globale KNX","title":"Variabile Globale KNX","node-input-name":"Nome variabile (no spazi, solo [a-z])","advanced":{"exposeAsVariable":"Esponi come variabile globale","exposeAsVariableNO":"No","exposeAsVariableREADONLY":"Sola lettura","exposeAsVariableREADWRITE":"Lettura/Scrittura","node-input-server":"Gateway","writeExecutionInterval":"Intervallo scrittura su BUS","warning":"Attenzione: è sufficiente UN SOLO NODO in tutto il progetto. La variabile verrà vista da tutti i flow, indipendentemente da dove sia stato messo il nodo."},"interval_250ms":"250ms","interval_500ms":"500ms","interval_1000ms_default":"1000ms (Default)","interval_1500ms":"1500ms","interval_2000ms":"2000ms","contextStorage":"Archivio del contesto","contextStoragePlaceholder":"Nome facoltativo dell’archivio del contesto"}},"knxUltimateLogger":{"knxUltimateLogger":{"paletteLabel":"Logger KNX","title":"Logger KNX per ETS","properties":{"node-input-server":"Gateway","node-input-topic":"Topic","node-input-name":"Nome","node-input-autoStartTimerCreateETSXML":"Avvia il timer automaticamente","mlxETSFileAccordion":"File compatibile con diagnostica ETS","node-input-intervalCreateETSXML":"Nuovo payload ogni (in minuti)","node-input-maxRowsInETSXML":"Numero massimo di righe (0 = nessun limite)","node-input-saveMode":"Azione","node-input-filePath":"Percorso file (assoluto o relativo)","telegramCounter":"Contatore telegrammi KNX","node-input-intervalTelegramCount":"Intervallo conteggio (in secondi)"},"placeholder":{"node-input-filePath":"/var/tmp/knx-logger.xml"},"selectlists":{"saveMode":{"emit":"Emetti solo il payload","emit_save":"Emetti il payload e salva su file"}},"noFilePath":"Il percorso del file è vuoto"}},"knxUltimateStaircase":{"knxUltimateStaircase":{"title":"Temporizzatore scale","paletteLabel":"Temporizzatore scale","node-input-server":"Gateway KNX","node-input-name":"Nome","node-input-outputtopic":"Topic eventi emessi","trigger":"GA impulso","output":"GA uscita","status":"GA stato","override":"GA override","block":"GA blocco","node-input-timerSeconds":"Durata timer (secondi)","node-input-extendMode":"Nuovo impulso","extend_restart":"Riavvia il timer","extend_extend":"Estendi il tempo residuo","extend_ignore":"Ignora l'impulso","node-input-triggerOffCancels":"Il valore 0 annulla il ciclo","opt_yes":"Sì","opt_no":"No","node-input-blockAction":"Quando è bloccato","block_off":"Forza lo spegnimento","block_keep":"Lascia lo stato attuale","node-input-preWarnEnable":"Invia preavviso prima dello spegnimento","node-input-preWarnSeconds":"Preavviso (secondi)","node-input-preWarnMode":"Modalità preavviso","prewarn_status":"Attiva GA stato","prewarn_flash":"Fai lampeggiare l'uscita","node-input-preWarnFlashMs":"Durata lampeggio (ms)","node-input-emitEvents":"Emetti eventi in uscita","help":{"intro":"Gestisce una luce scala con timer KNX, includendo preavviso, override manuale e blocco.","trigger":"Impulsi","trigger_ga":"Un fronte attivo (valore = 1) sulla GA di impulso avvia il timer. Se abilitato, il valore 0 interrompe immediatamente il ciclo.","extend":"L'opzione \"Nuovo impulso\" definisce come comportarsi: riavviare il timer, aggiungere tempo residuo oppure ignorare l'impulso.","off":"Con l'override attivo il timer è sospeso e la luce rimane accesa. Il blocco inibisce nuovi impulsi e, se configurato, spegne l'uscita.","outputs":"Indirizzi di gruppo","output_ga":"La GA di uscita è pilotata in DPT 1.001 (accensione/spegnimento). È possibile indicare altri DPT se necessario.","status_ga":"La GA di stato replica lo stato della scala (e il flag di preavviso quando attivato).","override_ga":"La GA di override mantiene la luce accesa finché vale 1 e sospende il timer.","block_ga":"La GA di blocco evita nuove attivazioni. A 0 il funzionamento torna normale.","prewarn":"Preavviso","prewarn_desc":"Il preavviso avvisa gli occupanti prima dello spegnimento: si può far lampeggiare l'uscita oppure cambiare lo stato sulla GA dedicata.","events":"Uscita del nodo","events_desc":"Se abilitato il nodo emette messaggi strutturati (trigger, extend, prewarn, timeout, override, block) con tempo residuo e stato corrente."},"placeholders":{"outputtopic":"Topic opzionale per gli eventi","ga":"es. 1/1/5","triggerName":"Nome dispositivo impulso","outputName":"Nome attuatore uscita","statusName":"Nome indicatore stato","overrideName":"Nome canale override","blockName":"Nome comando blocco"},"section_commands":"GA di comando","section_inputs":"GA di ingresso"}},"knxUltimateGarage":{"knxUltimateGarage":{"title":"Portone garage","paletteLabel":"Garage","node-input-server":"Gateway KNX","node-input-name":"Nome","node-input-outputtopic":"Topic eventi emessi","command":"GA comando (true=apre, false=chiude)","impulse":"GA impulso","holdOpen":"GA blocco richiusura","disable":"GA disabilitazione","photocell":"GA fotocellula","moving":"GA in movimento","obstruction":"GA ostruzione","autoCloseEnable":"Abilita richiusura automatica","autoCloseSeconds":"Tempo richiusura automatica (secondi)","node-input-emitEvents":"Emetti eventi in uscita","placeholders":{"outputtopic":"Topic opzionale per gli eventi","ga":"es. 1/2/3","commandName":"Attuatore portone","impulseName":"Ingresso impulso","holdOpenName":"Comando blocco richiusura","disableName":"Comando disabilitazione","photocellName":"Sensore fotocellula","movingName":"Indicatore movimento","obstructionName":"Indicatore ostruzione"},"help":{"intro":"Gestisce un portone garage KNX con comandi diretti, impulso, sensori di sicurezza e richiusura automatica.","commands":"Comandi","command_ga":"La GA comando esegue apertura (true) o chiusura (false). Il nodo la usa anche per la richiusura automatica.","impulse_ga":"La GA impulso commuta il portone. I fronti attivi aggiornano lo stato interno e vengono usati se non è disponibile un comando diretto.","holdopen_ga":"La GA blocco richiusura mantiene il portone aperto e annulla il timer finché torna a false.","disable_ga":"La GA disabilitazione sospende qualsiasi automatismo del nodo (utile in manutenzione).","safety":"Sicurezze e stati","photocell_ga":"La GA fotocellula deve andare a true quando l'ostruzione interrompe il fascio: il nodo riapre subito il portone e segnala l'ostruzione.","moving_ga":"La GA in movimento (opzionale) viene pulsata quando il nodo comanda il portone, così altri dispositivi possono sincronizzarsi.","obstruction_ga":"La GA ostruzione replica lo stato d'allarme per altri componenti KNX.","auto":"Richiusura automatica","auto_close":"Abilita il timer per chiudere automaticamente il portone dopo il tempo impostato, salvo blocco richiusura o disabilitazione attiva.","events":"Uscita del nodo","events_desc":"Con gli eventi attivi il nodo genera messaggi strutturati (open, close, toggle, obstruction, disabled, hold-open, auto-close) per la logica del flow."},"section_commands":"GA di comando","section_inputs":"GA di ingresso/sensori"}},"knxUltimateSceneController":{"knxUltimateSceneController":{"paletteLabel":"Controller Scene KNX","title":"Controller scena","properties":{"node-input-server":"Gateway","node-input-topic":"Richiama Scena","node-input-dpt":"DPT","node-input-topicTrigger":"Trigger","node-input-topicSave":"Salva Scena","node-input-name":"Nome nodo","node-input-outputtopic":"Topic"},"placeholder":{"leaveempty":"Lascia vuoto per usare l'indirizzo di gruppo","valueexample":"Es: false, true, altrimenti qualsiasi valore"},"other":{"sceneConfig":"Configurazione dei valori predefiniti della scena","add":"Premi Aggiungi per aggiungere un dispositivo alla scena"},"advanced":{"notify-DPT3007":"Hai selezionato un DIM relativo. Per favore <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---Dimming'>clicca qui per vedere un esempio</a> e capire come gestire il relativo payload.","notify-DPT18001":"Hai selezionato un DPT scena. Per favore <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---Control-a-scene-actuator'>clicca qui per vedere un esempio</a> per capire come gestire il relativo payload.","notify-DPT232600":"Hai selezionato un DPT RGB. Per favore <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---RGB-Color'>clicca qui per vedere un esempio</a> per capire come gestire il relativo payload.","notify-DPT251600":"Hai selezionato un DPT RGBW. Per favore <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---RGBW-Color-plus-White'>clicca qui per vedere un esempio</a> per capire come gestire il relativo payload."}}},"knxUltimateLoadControl":{"knxUltimateLoadControl":{"paletteLabel":"Controllo carichi KNX","title":"Controllo Carichi","primoaldistacco":"(è il primo che verrà staccato)","properties":{"node-input-server":"Gateway","node-input-name":"Nome","node-input-dpt":"DPT","node-input-topic":"Monitor W","node-input-controlGA":"Carico","node-input-monitorGA":"In uso","node-input-controlMode":"Modalità","node-input-wattLimit":"Soglia W","node-input-sheddingCheckInterval":"Ritardo distacco (s)","node-input-sheddingRestoreDelay":"Ritardo ripristino (s)","node-input-autoRestore":"Ripristino automatico"},"selectlists":{"controlModeAuto":"Automatica (interna)","controlModeMsg":"Manuale (msg.shedding)"},"other":{}}},"knxUltimateHATranslator":{"knxUltimateHATranslator":{"paletteLabel":"Traduttore Home Assistant","inputProperty":"Proprietà in ingresso","inputHint":"Proprietà del messaggio da tradurre, ad esempio payload o payload.state.","translations":"Traduzioni (Home Assistant → KNX)","translationHint":"Una traduzione per riga, nel formato stato:true oppure stato:false. Ad esempio: on:true e off:false."}}},"de":{"knxUltimateAlerter":{"knxUltimateAlerter":{"paletteLabel":"KNX Alarmierer","title":"Alerter Knot","properties":{"node-input-server":"Gateway","node-input-name":"Name","node-input-timerinterval":"Intervall zwischen jedem MSG (in Sekunden)","node-input-whentostart":"Starttyp des Alarmierungszyklus","node-input-initialread":"Bei Verbindung/Wiederverbindung je Gerat-Wert lesen","node-input-initialread0":"Nein","node-input-initialread1":"Lesen vom KNX-BUS","node-input-initialreadGAInRules":"Bei Verbindung/Wiederverbindung je Gerat-Wert lesen"},"selectlists":{"manualstart":"Starten den Alarmzyklus manuell über eine eingehende MSG","ifnewalert":"Starten den Alarmzyklus mit jedem neuen alarmierten Gerät"},"other":{"sceneConfig":"Zu überwachende Geräte (DPT MUSS BOOLEAN SEIN)","add":"Drücken Sie Hinzufügen, um ein Gerät hinzuzufügen"}}},"knxUltimateAutoResponder":{"knxUltimateAutoResponder":{"paletteLabel":"KNX Auto-Responder","respondTo":"Antworten auf"}},"knxUltimateDateTime":{"knxUltimateDateTime":{"title":"Datum/Uhrzeit","paletteLabel":"Datum/Uhrzeit","node-input-server":"KNX Gateway","node-input-name":"Name","node-input-outputtopic":"Topic für Node-Ausgabe","section_addresses":"Gruppenadressen","gaDateTime":"Datum/Uhrzeit GA (DPT 19.001)","gaDate":"Datum GA (DPT 11.001)","gaTime":"Uhrzeit GA (DPT 10.001)","section_send":"Sendeoptionen","node-input-sendOnDeploy":"Beim Deploy/Start senden","node-input-sendOnDeployDelay":"Startverzögerung (Sekunden)","node-input-periodicSend":"Periodisch senden","node-input-periodicSendInterval":"Intervall","unit_seconds":"Sekunden","unit_minutes":"Minuten","notifySent":"An KNX gesendet","notifyQueued":"In Warteschlange (Gateway noch nicht verbunden)","placeholders":{"outputtopic":"Optionales Output-Topic","ga":"z.B. 1/7/1","nameDateTime":"Optionaler ETS-Name","nameDate":"Optionaler ETS-Name","nameTime":"Optionaler ETS-Name"}}},"knxUltimateWatchDog":{"knxUltimateWatchDog":{"paletteLabel":"KNX WatchDog","title":"Watchdog","properties":{"node-input-server":"Gateway","node-input-checkLevel":"Check level (siehe wiki)","node-input-topic":"Gruppenadresse monitor","node-input-name":"Name","node-input-autoStart":"Watchdog-Timer automatisch starten","node-input-listenToKnxUltimateNodeErrors":"Fehler von KNX-Ultimate-Nodes überwachen","advancedOptionsAccordion":"Erweiterte Optionen","node-input-retryInterval":"Wiederholungsintervall (in Sekunden)","node-input-maxRetry":"Anzahl der Wiederholungen, bevor ein Fehler ausgegeben wird"},"placeholder":{"monitor":"Beispiel: 12/0/0. Bitte für 'Nur Ethernet', verwenden Sie eine nicht vorhandene Gruppenadresse."},"selectlists":{"Ethernet":"Nur Ethernet unicast (Default) mit ping. Funktioniert NUR mit KNX Interface (kein Router)","EthKNX":"Ethernet + KNX Twisted Pair, mit einem echten KNX-Gerät"},"booleanHint":"Der DPT muss 1.x (Boolesch) sein"}},"knxUltimateGlobalContext":{"knxUltimateGlobalContext":{"paletteLabel":"KNX Globaler Kontext","title":"KNX Global Context","node-input-name":"Variablenname (keine Leerzeichen, nur Zeichen [a-z])","advanced":{"exposeAsVariable":"Als globale Variable verfügbar machen","exposeAsVariableNO":"Nein","exposeAsVariableREADONLY":"Nur Lesen","exposeAsVariableREADWRITE":"Lesen/Schreiben","node-input-server":"Gateway","writeExecutionInterval":"BUS-Schreibintervall","warning":"Achtung: Ein einzelner Knoten wird von ALLEN FLOWS gemeinsam genutzt. Sie benötigen nicht mehr als einen Knoten. Unabhängig davon, wo es sich befindet, sie können die Variable auf globaler Ebene sehen."},"interval_250ms":"250ms","interval_500ms":"500ms","interval_1000ms_default":"1000ms (Standard)","interval_1500ms":"1500ms","interval_2000ms":"2000ms","contextStorage":"Kontextspeicher","contextStoragePlaceholder":"Optionaler Name des Kontextspeichers"}},"knxUltimateLogger":{"knxUltimateLogger":{"paletteLabel":"KNX Logger","title":"KNX Logger für ETS","properties":{"node-input-server":"Gateway","node-input-topic":"Topic","node-input-name":"Node Name","node-input-autoStartTimerCreateETSXML":"Timer für automatischen Start","mlxETSFileAccordion":"ETS-kompatible BUS-Diagnosedatei","node-input-intervalCreateETSXML":"Neue Payload-Ausgabe alle (in Minuten)","node-input-maxRowsInETSXML":"Maximale Anzahl von Zeilen (0 = keine Begrenzung)","node-input-saveMode":"Aktion","node-input-filePath":"Dateipfad (absolut oder relativ)","telegramCounter":"KNX Telegrammzähler","node-input-intervalTelegramCount":"Zählintervall (in Sekunden)"},"placeholder":{"node-input-filePath":"/var/tmp/knx-logger.xml"},"selectlists":{"saveMode":{"emit":"Nur Payload senden","emit_save":"Payload senden und in Datei speichern"}},"noFilePath":"Der Dateipfad ist leer"}},"knxUltimateStaircase":{"knxUltimateStaircase":{"title":"Staircase light","paletteLabel":"Staircase","node-input-server":"KNX Gateway","node-input-name":"Name","node-input-outputtopic":"Topic for emitted events","trigger":"Trigger GA","output":"Output GA","status":"Status GA","override":"Override GA","block":"Block GA","node-input-timerSeconds":"Timer duration (seconds)","node-input-extendMode":"On new trigger","extend_restart":"Restart timer","extend_extend":"Extend remaining time","extend_ignore":"Ignore the trigger","node-input-triggerOffCancels":"0 command cancels the cycle","opt_yes":"Yes","opt_no":"No","node-input-blockAction":"When blocked","block_off":"Force light OFF","block_keep":"Leave current state","node-input-preWarnEnable":"Send pre-warning before timeout","node-input-preWarnSeconds":"Pre-warning offset (seconds)","node-input-preWarnMode":"Pre-warning mode","prewarn_status":"Toggle status GA","prewarn_flash":"Flash output","node-input-preWarnFlashMs":"Flash duration (ms)","node-input-emitEvents":"Emit events on node output","help":{"intro":"Implements a staircase light logic with KNX integration, handling timer, pre-warning, manual override and blocking.","trigger":"Triggers","trigger_ga":"A rising edge (value = 1) on the trigger GA starts the timer. If enabled, a value of 0 will immediately cancel the cycle.","extend":"The \"On new trigger\" option controls how subsequent pulses behave: restart the timer, extend the remaining time or ignore the pulse.","off":"When override is active the timer is paused and the light stays on. Blocking disables new pulses and optionally forces the output off.","outputs":"Group addresses","output_ga":"Output GA is driven with DPT 1.001 by default. Configure other DPTs if required.","status_ga":"Status GA mirrors the stair light state (and the pre-warning flag when enabled).","override_ga":"Override GA keeps the light on while true and suspends the timer.","block_ga":"Block GA prevents new activations. When false, normal behaviour resumes.","prewarn":"Pre-warning","prewarn_desc":"Enable a pre-warning to alert occupants before the light turns off. You can either flash the output or toggle the status GA.","events":"Node output","events_desc":"When enabled the node emits structured events (trigger, extend, prewarn, timeout, override, block) with the remaining time and state information."},"placeholders":{"outputtopic":"Optional topic for node events","ga":"e.g. 1/1/5","triggerName":"Trigger device name","outputName":"Output actuator name","statusName":"Status feedback name","overrideName":"Override channel name","blockName":"Block command name"},"section_commands":"Command group addresses","section_inputs":"Input group addresses"}},"knxUltimateGarage":{"knxUltimateGarage":{"title":"Garage door","paletteLabel":"Garage","node-input-server":"KNX Gateway","node-input-name":"Name","node-input-outputtopic":"Topic for emitted events","command":"Command GA (true=open, false=close)","impulse":"Impulse GA","holdOpen":"Hold-open GA","disable":"Disable GA","photocell":"Photocell GA","moving":"Moving GA","obstruction":"Obstruction GA","autoCloseEnable":"Enable auto re-close","autoCloseSeconds":"Auto re-close delay (seconds)","node-input-emitEvents":"Emit events on node output","placeholders":{"outputtopic":"Optional topic for node events","ga":"e.g. 1/2/3","commandName":"Door actuator","impulseName":"Impulse input","holdOpenName":"Hold-open switch","disableName":"Disable command","photocellName":"Photocell sensor","movingName":"Movement indicator","obstructionName":"Obstruction indicator"},"help":{"intro":"Controls a KNX garage door with direct open/close commands, impulse inputs, safety sensors and automatic re-close.","commands":"Commands","command_ga":"Use the command GA for boolean control: true opens, false closes. The node also emits this when the auto close triggers.","impulse_ga":"An impulse GA toggles the door. Rising edges toggle the internal state; it is also used if no direct command GA is configured.","holdopen_ga":"Hold-open GA keeps the door open and cancels the auto re-close until it returns to false.","disable_ga":"Disable GA stops the node from issuing new commands. Useful for maintenance or manual mode.","safety":"Safety and status","photocell_ga":"The photocell GA should go true when the beam is interrupted. The node re-opens the door and flags obstruction.","moving_ga":"Movement GA (optional) is pulsed whenever the node commands the door to move, allowing other devices to follow the motion.","obstruction_ga":"Obstruction GA mirrors the obstruction state so other KNX components can react.","auto":"Automatic re-close","auto_close":"Enable the auto re-close timer to close the door automatically after the configured delay once it is open, unless hold-open or disable are active.","events":"Node output","events_desc":"With event emission enabled the node outputs structured messages (open, close, toggle, obstruction, disabled, hold-open, auto-close) for flow logic."},"section_commands":"Command group addresses","section_inputs":"Input / sensor group addresses"}},"knxUltimateSceneController":{"knxUltimateSceneController":{"paletteLabel":"KNX Szenensteuerung","title":"Szene Kontroller","properties":{"node-input-server":"Gateway","node-input-topic":"Szenenrückruf","node-input-dpt":"DPT","node-input-topicTrigger":"Trigger","node-input-topicSave":"Szene speichern","node-input-name":"Node Name","node-input-outputtopic":"Topic"},"placeholder":{"leaveempty":"Lassen Sie leer, um die Gruppenadresse zu verwenden","valueexample":"Beispiel: false, true, sonst ein beliebiger Wert"},"other":{"sceneConfig":"Szenenkonfiguration, default Werte","add":"Drücken Sie Hinzufuegen, um ein Gerät zur Szene hinzuzufügen"},"advanced":{"notify-DPT3007":"Du haßt ein relatives DIM ausgewählt. Bitte <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---Dimming'>klicke hier, um dieses Beispiel anzuzeigen</a> und lernen, wie den Payload handeln.","notify-DPT18001":"Du haßt Szene DPT ausgewählt. Bitte <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---Control-a-scene-actuator'>klicke hier, um dieses Beispiel anzuzeigen</a> und lernen, wie den Payload handeln.","notify-DPT232600":"Du haßt einen RGB DPT ausgewählt. Bitte <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---RGB-Color'>klicke hier, um dieses Beispiel anzuzeigen</a> und lernen, wie den Payload handeln.","notify-DPT251600":"Du haßt einen RGBW DPT ausgewählt. Bitte <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---RGBW-Color-plus-White'>klicke hier, um dieses Beispiel anzuzeigen</a> und lernen, wie den Payload handeln."}}},"knxUltimateLoadControl":{"knxUltimateLoadControl":{"paletteLabel":"KNX Laststeuerung","title":"Load control","primoaldistacco":"(this is the first to be switched off)","properties":{"node-input-server":"Gateway","node-input-name":"Name","node-input-controlMode":"Modus","node-input-dpt":"DPT","node-input-topic":"Monitor W","node-input-controlGA":"Load","node-input-monitorGA":"In use","node-input-wattLimit":"Limit W","node-input-sheddingCheckInterval":"Ausschaltverzögerung (s)","node-input-sheddingRestoreDelay":"Einschaltverzögerung (s)","node-input-autoRestore":"Automatic recovery"},"selectlists":{"controlModeAuto":"Automatisch (intern)","controlModeMsg":"Manuell (msg.shedding)"},"other":{}}},"knxUltimateHATranslator":{"knxUltimateHATranslator":{"paletteLabel":"Home Assistant Übersetzer","inputProperty":"Eingangseigenschaft","inputHint":"Zu übersetzende Nachrichteneigenschaft, zum Beispiel payload oder payload.state.","translations":"Übersetzungen (Home Assistant → KNX)","translationHint":"Eine Übersetzung pro Zeile im Format zustand:true oder zustand:false. Zum Beispiel: on:true und off:false."}}},"fr":{"knxUltimateAlerter":{"knxUltimateAlerter":{"paletteLabel":"Alerter KNX","title":"Nœud d'alerte","properties":{"node-input-server":"Porte","node-input-name":"Nom","node-input-timerinterval":"Intervalle entre chaque MSG (en secondes)","node-input-whentostart":"Type de démarrage du cycle d'alerte","node-input-initialread":"Lire la valeur de chaque périphérique sur la connexion / reconnecter","node-input-initialread0":"Non","node-input-initialread1":"Lire depuis KNX Bus","node-input-initialreadGAInRules":"Lire les états au début / reconnexion"},"selectlists":{"manualstart":"Démarrer le cycle d'alerte manuellement via un message entrant","ifnewalert":"Démarrez le cycle d'alerte avec chaque nouvel appareil alerté"},"other":{"sceneConfig":"Appareils à surveiller (DPT doit être booléen)","add":"Appuyez sur Ajouter, pour ajouter un appareil"}}},"knxUltimateAutoResponder":{"knxUltimateAutoResponder":{"paletteLabel":"Répondant automatique KNX","respondTo":"Répondre à"}},"knxUltimateDateTime":{"knxUltimateDateTime":{"title":"Date/Heure","paletteLabel":"Date/Heure","node-input-server":"Passerelle KNX","node-input-name":"Nom","node-input-outputtopic":"Topic de sortie du nœud","section_addresses":"Adresses de groupe","gaDateTime":"GA Date/Heure (DPT 19.001)","gaDate":"GA Date (DPT 11.001)","gaTime":"GA Heure (DPT 10.001)","section_send":"Options d'envoi","node-input-sendOnDeploy":"Envoyer au déploiement/démarrage","node-input-sendOnDeployDelay":"Délai au démarrage (secondes)","node-input-periodicSend":"Envoi périodique","node-input-periodicSendInterval":"Intervalle","unit_seconds":"Secondes","unit_minutes":"Minutes","notifySent":"Envoyé sur KNX","notifyQueued":"Mis en file d'attente (passerelle pas encore connectée)","placeholders":{"outputtopic":"Topic de sortie optionnel","ga":"ex. 1/7/1","nameDateTime":"Nom ETS (optionnel)","nameDate":"Nom ETS (optionnel)","nameTime":"Nom ETS (optionnel)"}}},"knxUltimateWatchDog":{"knxUltimateWatchDog":{"paletteLabel":"Chien de garde de KNX","title":"Chien de garde","properties":{"node-input-server":"Porte","node-input-checkLevel":"Vérifier le niveau (veuillez consulter le wiki)","node-input-topic":"Adresse du groupe à surveiller","node-input-name":"Nom de nœud","node-input-autoStart":"Démarrer automatiquement la minuterie de chien de garde","node-input-listenToKnxUltimateNodeErrors":"Écouter les erreurs des nœuds KNX-Ultimate","advancedOptionsAccordion":"Options avancées","node-input-retryInterval":"Réessier l'intervalle (en quelques secondes)","node-input-maxRetry":"Nombre de réessayer avant de donner une erreur"},"placeholder":{"monitor":"Ex: 12/0/0. Pour les vérifications «seulement Ethernet», veuillez utiliser une adresse de groupe non existante."},"selectlists":{"Ethernet":"Seul Ethernet Unicast (par défaut), en utilisant un ping. Fonctionne uniquement avec les interfaces KNX (pas les routeurs)","EthKNX":"Paire torsadé Ethernet + KNX, en utilisant un réel appareil KNX"},"booleanHint":"Le DPT doit être 1.x (booléen)"}},"knxUltimateGlobalContext":{"knxUltimateGlobalContext":{"paletteLabel":"Contexte mondial KNX","title":"Contexte mondial KNX","node-input-name":"Nom variable (pas d'espaces, seulement Chars [A-Z])","advanced":{"exposeAsVariable":"Exposer en tant que variable globale","exposeAsVariableNO":"Non","exposeAsVariableREADONLY":"Lire uniquement","exposeAsVariableREADWRITE":"Lire / écrire","node-input-server":"Porte","writeExecutionInterval":"Intervalle d'écriture de bus","warning":"Avertissement: un seul nœud est partagé entre tous les flux. Vous n'avez pas besoin de plus d'un nœud; Peu importe où il se trouve, vous pouvez voir la variable à l'échelle mondiale."},"interval_250ms":"250 ms","interval_500ms":"500 ms","interval_1000ms_default":"1000 ms (par défaut)","interval_1500ms":"1500 ms","interval_2000ms":"2000 ms","contextStorage":"Stockage du contexte","contextStoragePlaceholder":"Nom facultatif du stockage du contexte"}},"knxUltimateLogger":{"knxUltimateLogger":{"paletteLabel":"KNX Logger","title":"KNX Logger pour ETS","properties":{"node-input-server":"Porte","node-input-topic":"Sujet","node-input-name":"Nom de nœud","node-input-autoStartTimerCreateETSXML":"Minuterie de démarrage automatique","mlxETSFileAccordion":"Fichier de diagnostic de bus compatible ETS","node-input-intervalCreateETSXML":"Nouveau payload toutes les (en minutes)","node-input-maxRowsInETSXML":"Nombre maximum de lignes (0 = pas de limite)","node-input-saveMode":"Action","node-input-filePath":"Chemin de fichier (absolu ou relatif)","telegramCounter":"Compteur de télégramme KNX","node-input-intervalTelegramCount":"Compter l'intervalle (en quelques secondes)"},"placeholder":{"node-input-filePath":"/var/tmp/knx-logger.xml"},"selectlists":{"saveMode":{"emit":"Envoyer uniquement le payload","emit_save":"Envoyer le payload et enregistrer dans un fichier"}},"noFilePath":"Le chemin du fichier est vide"}},"knxUltimateStaircase":{"knxUltimateStaircase":{"title":"Minuterie d'escalier","paletteLabel":"Escalier","node-input-server":"Passerelle KNX","node-input-name":"Nom","node-input-outputtopic":"Topic des événements","trigger":"GA impulsion","output":"GA sortie","status":"GA état","override":"GA override","block":"GA blocage","node-input-timerSeconds":"Durée du minuteur (secondes)","node-input-extendMode":"Nouvelle impulsion","extend_restart":"Redémarrer le minuteur","extend_extend":"Ajouter du temps","extend_ignore":"Ignorer","node-input-triggerOffCancels":"La valeur 0 annule le cycle","opt_yes":"Oui","opt_no":"Non","node-input-blockAction":"En cas de blocage","block_off":"Forcer l'arrêt","block_keep":"Conserver l'état","node-input-preWarnEnable":"Envoyer un préavis avant l'extinction","node-input-preWarnSeconds":"Préavis (secondes)","node-input-preWarnMode":"Mode de préavis","prewarn_status":"Basculer la GA d'état","prewarn_flash":"Faire clignoter la sortie","node-input-preWarnFlashMs":"Durée du clignotement (ms)","node-input-emitEvents":"Émettre des événements sur la sortie","help":{"intro":"Gère une lumière d'escalier avec temporisation KNX, préavis, override manuel et blocage.","trigger":"Impulsions","trigger_ga":"Une valeur 1 sur la GA d'impulsion démarre le minuteur. Si activé, la valeur 0 stoppe immédiatement le cycle.","extend":"L'option \"Nouvelle impulsion\" définit si le minuteur repart, s'allonge ou ignore la nouvelle impulsion.","off":"Avec l'override actif la temporisation est suspendue et la lumière reste allumée. Le blocage interdit les nouvelles impulsions et peut forcer l'arrêt.","outputs":"Adresses de groupe","output_ga":"La GA de sortie utilise le DPT 1.001 par défaut. Modifiez-le si nécessaire.","status_ga":"La GA d'état reflète l'activité de la minuterie et le préavis lorsqu'il est actif.","override_ga":"La GA d'override maintient l'éclairage tant qu'elle vaut 1.","block_ga":"La GA de blocage empêche les activations. Avec 0 le fonctionnement redevient normal.","prewarn":"Préavis","prewarn_desc":"Vous pouvez prévenir avant l'extinction en faisant clignoter la sortie ou en agissant sur la GA d'état.","events":"Sortie du nœud","events_desc":"Si activé, le nœud émet des événements structurés (trigger, extend, prewarn, timeout, override, block) avec le temps restant et l'état courant."},"placeholders":{"outputtopic":"Topic optionnel pour les événements","ga":"ex. 1/1/5","triggerName":"Nom dispositif impulsion","outputName":"Nom actionneur sortie","statusName":"Nom indicateur d'état","overrideName":"Nom canal override","blockName":"Nom commande blocage"},"section_commands":"Adresses de commande","section_inputs":"Adresses d’entrée"}},"knxUltimateGarage":{"knxUltimateGarage":{"title":"Porte de garage","paletteLabel":"Garage","node-input-server":"Passerelle KNX","node-input-name":"Nom","node-input-outputtopic":"Topic des événements","command":"GA commande (true=ouvre, false=ferme)","impulse":"GA impulsion","holdOpen":"GA maintien ouvert","disable":"GA désactivation","photocell":"GA cellule photo","moving":"GA en mouvement","obstruction":"GA obstruction","autoCloseEnable":"Activer la refermeture automatique","autoCloseSeconds":"Délai de refermeture (secondes)","node-input-emitEvents":"Émettre des événements en sortie","placeholders":{"outputtopic":"Topic optionnel pour les événements","ga":"ex. 1/2/3","commandName":"Actionneur porte","impulseName":"Entrée impulsion","holdOpenName":"Maintien ouvert","disableName":"Commande désactivation","photocellName":"Capteur phot cellule","movingName":"Indicateur mouvement","obstructionName":"Indicateur obstruction"},"help":{"intro":"Pilote une porte de garage KNX avec commandes directes, impulsion, sécurités et refermeture automatique.","commands":"Commandes","command_ga":"La GA commande reçoit true pour ouvrir et false pour fermer. Le noeud l'utilise aussi pour le rappel automatique.","impulse_ga":"La GA impulsion commute la porte via un front actif et sert si aucune commande booléenne n'est disponible.","holdopen_ga":"La GA maintien ouvert annule le timer tant qu'elle reste à true.","disable_ga":"La GA désactivation bloque toute action du nœud (mode maintenance).","safety":"Sécurité et état","photocell_ga":"La GA cellule photo passe à true en cas d'obstacle; le nœud rouvre et signale l'obstruction.","moving_ga":"La GA en mouvement (optionnelle) est pulsée lorsque le nœud commande la porte, pour informer les autres équipements.","obstruction_ga":"La GA obstruction diffuse l'état d'alarme aux autres participants KNX.","auto":"Refermeture automatique","auto_close":"Active la refermeture après le délai défini sauf maintien ouvert ou désactivation.","events":"Sortie du nœud","events_desc":"Avec l'émission d'événements activée le nœud génère des messages structurés (open, close, toggle, obstruction, disabled, hold-open, auto-close) pour la logique du flow."},"section_commands":"Adresses de commande","section_inputs":"Adresses d’entrée / capteurs"}},"knxUltimateSceneController":{"knxUltimateSceneController":{"paletteLabel":"Contrôleur de scène KNX","title":"Contrôleur de scène","properties":{"node-input-server":"Porte","node-input-topic":"Rappel de la scène","node-input-dpt":"DPT","node-input-topicTrigger":"Déclenchement","node-input-topicSave":"Sage à l'exception","node-input-name":"Nom de nœud","node-input-outputtopic":"Sujet"},"placeholder":{"leaveempty":"Laisser vide pour utiliser l'adresse du groupe","valueexample":"Ex: faux, vrai, sinon toute valeur"},"other":{"sceneConfig":"Configuration des valeurs par défaut de scène","add":"Appuyez sur Ajouter, pour ajouter un appareil dans la scène"},"advanced":{"notify-DPT3007":"Vous avez sélectionné un dim relatif. Veuillez __ph_0__ cliquez ici pour afficher cet exemple__ph_1__ et apprenez à gérer une telle charge utile.","notify-DPT18001":"Vous avez sélectionné un DPT de scène. Veuillez __ph_0__ cliquez ici pour afficher cet exemple__ph_1__ et apprenez à gérer une telle charge utile.","notify-DPT232600":"Vous avez sélectionné un DPT RVB. Veuillez __ph_0__ cliquez ici pour afficher cet exemple__ph_1__ et apprenez à gérer une telle charge utile.","notify-DPT251600":"Vous avez sélectionné un DPT RGBW. Veuillez __ph_0__ cliquez ici pour afficher cet exemple__ph_1__ et apprenez à gérer une telle charge utile."}}},"knxUltimateLoadControl":{"knxUltimateLoadControl":{"paletteLabel":"Contrôle de charge KNX","title":"Contrôle de la charge","primoaldistacco":"(c'est le premier à être éteint)","properties":{"node-input-server":"Porte","node-input-name":"Non-mémoire","node-input-controlMode":"Mode","node-input-dpt":"DPT","node-input-topic":"Surveiller W","node-input-controlGA":"Charger","node-input-monitorGA":"Utilisé","node-input-wattLimit":"Limiter w","node-input-sheddingCheckInterval":"Retarder l'éteint (s)","node-input-sheddingRestoreDelay":"Retarder l'interrupteur sur (s)","node-input-autoRestore":"Récupération automatique"},"selectlists":{"controlModeAuto":"Automatique (interne)","controlModeMsg":"Manuel (msg.shedding)"},"other":{}}},"knxUltimateHATranslator":{"knxUltimateHATranslator":{"paletteLabel":"Traducteur Home Assistant","inputProperty":"Propriété d’entrée","inputHint":"Propriété du message à traduire, par exemple payload ou payload.state.","translations":"Traductions (Home Assistant → KNX)","translationHint":"Une traduction par ligne, au format état:true ou état:false. Par exemple : on:true et off:false."}}},"es":{"knxUltimateAlerter":{"knxUltimateAlerter":{"paletteLabel":"Knx alerter","title":"Nodo de alerter","properties":{"node-input-server":"Puerta","node-input-name":"Nombre","node-input-timerinterval":"Intervalo entre cada MSG (en segundos)","node-input-whentostart":"Tipo de inicio del ciclo de alerta","node-input-initialread":"Valor de lectura de cada dispositivo en conexión/reconectación","node-input-initialread0":"No","node-input-initialread1":"Leer del autobús KNX","node-input-initialreadGAInRules":"Leer estados al inicio/reconexión"},"selectlists":{"manualstart":"Inicie el ciclo de alerta manualmente a través del mensaje entrante","ifnewalert":"Inicie el ciclo de alerta con cada nuevo dispositivo alertado"},"other":{"sceneConfig":"Dispositivos para monitorear (DPT debe ser booleano)","add":"Presione Agregar, para agregar un dispositivo"}}},"knxUltimateAutoResponder":{"knxUltimateAutoResponder":{"paletteLabel":"KNX Auto Responder","respondTo":"Responder a"}},"knxUltimateDateTime":{"knxUltimateDateTime":{"title":"Fecha/Hora","paletteLabel":"Fecha/Hora","node-input-server":"Gateway KNX","node-input-name":"Nombre","node-input-outputtopic":"Topic de salida del nodo","section_addresses":"Direcciones de grupo","gaDateTime":"GA Fecha/Hora (DPT 19.001)","gaDate":"GA Fecha (DPT 11.001)","gaTime":"GA Hora (DPT 10.001)","section_send":"Opciones de envío","node-input-sendOnDeploy":"Enviar al desplegar/iniciar","node-input-sendOnDeployDelay":"Retardo de inicio (segundos)","node-input-periodicSend":"Envío periódico","node-input-periodicSendInterval":"Intervalo","unit_seconds":"Segundos","unit_minutes":"Minutos","notifySent":"Enviado a KNX","notifyQueued":"En cola (gateway aún no conectado)","placeholders":{"outputtopic":"Topic de salida opcional","ga":"p.ej. 1/7/1","nameDateTime":"Nombre ETS (opc.)","nameDate":"Nombre ETS (opc.)","nameTime":"Nombre ETS (opc.)"}}},"knxUltimateWatchDog":{"knxUltimateWatchDog":{"paletteLabel":"KNX Watchdog","title":"Perro guardián","properties":{"node-input-server":"Puerta","node-input-checkLevel":"Verificar el nivel (consulte el wiki)","node-input-topic":"Dirección de grupo para monitorear","node-input-name":"Nombre de nodo","node-input-autoStart":"Auto Iniciar el temporizador de vigilancia","node-input-listenToKnxUltimateNodeErrors":"Escuchar errores de los nodos KNX-Ultimate","advancedOptionsAccordion":"Opciones avanzadas","node-input-retryInterval":"Vuelva a intentar el intervalo (en segundos)","node-input-maxRetry":"Número de reintento antes de dar un error"},"placeholder":{"monitor":"Ej: 12/0/0. Para verificaciones 'únicas Ethernet', utilice una dirección de grupo no existente."},"selectlists":{"Ethernet":"Solo Ethernet Unicast (predeterminado), usando ping. Funciona solo con interfaces KNX (no enrutadores)","EthKNX":"Ethernet + KNX Twisted Par, usando un dispositivo KNX real"},"booleanHint":"El DPT debe ser 1.x (booleano)"}},"knxUltimateGlobalContext":{"knxUltimateGlobalContext":{"paletteLabel":"Contexto global de KNX","title":"Contexto global de KNX","node-input-name":"Nombre de la variable (sin espacios, solo chars [A-Z])","advanced":{"exposeAsVariable":"Exponer como variable global","exposeAsVariableNO":"No","exposeAsVariableREADONLY":"Solo lectura","exposeAsVariableREADWRITE":"Leer/escribir","node-input-server":"Puerta","writeExecutionInterval":"Intervalo de escritura de autobús","warning":"Advertencia: se comparte un solo nodo entre todos los flujos. No necesitas más de un nodo; Independientemente de dónde esté, puede ver la variable a nivel mundial."},"interval_250ms":"250 ms","interval_500ms":"500 ms","interval_1000ms_default":"1000 ms (predeterminado)","interval_1500ms":"1500 ms","interval_2000ms":"2000 ms","contextStorage":"Almacén de contexto","contextStoragePlaceholder":"Nombre opcional del almacén de contexto"}},"knxUltimateLogger":{"knxUltimateLogger":{"paletteLabel":"Logger KNX","title":"Logger KNX para ETS","properties":{"node-input-server":"Puerta","node-input-topic":"Tema","node-input-name":"Nombre de nodo","node-input-autoStartTimerCreateETSXML":"Temporizador de inicio automático","mlxETSFileAccordion":"Archivo de diagnóstico de bus compatible con ETS","node-input-intervalCreateETSXML":"Nuevo payload cada (en minutos)","node-input-maxRowsInETSXML":"Número máximo de filas (0 = sin límite)","node-input-saveMode":"Acción","node-input-filePath":"Ruta de archivo (absoluta o relativa)","telegramCounter":"Contador de telegrama de knx","node-input-intervalTelegramCount":"Intervalo de conteo (en segundos)"},"placeholder":{"node-input-filePath":"/var/tmp/knx-logger.xml"},"selectlists":{"saveMode":{"emit":"Emitir solo el payload","emit_save":"Emitir el payload y guardar en archivo"}},"noFilePath":"La ruta del archivo está vacía"}},"knxUltimateStaircase":{"knxUltimateStaircase":{"title":"Escalera temporizada","paletteLabel":"Escalera","node-input-server":"Gateway KNX","node-input-name":"Nombre","node-input-outputtopic":"Topic para eventos","trigger":"GA disparo","output":"GA salida","status":"GA estado","override":"GA override","block":"GA bloqueo","node-input-timerSeconds":"Duración del temporizador (segundos)","node-input-extendMode":"Nuevo impulso","extend_restart":"Reiniciar temporizador","extend_extend":"Extender tiempo restante","extend_ignore":"Ignorar impulso","node-input-triggerOffCancels":"El valor 0 cancela el ciclo","opt_yes":"Sí","opt_no":"No","node-input-blockAction":"Cuando está bloqueado","block_off":"Forzar apagado","block_keep":"Mantener estado","node-input-preWarnEnable":"Enviar aviso antes de apagar","node-input-preWarnSeconds":"Aviso (segundos)","node-input-preWarnMode":"Modo de aviso","prewarn_status":"Conmutar GA estado","prewarn_flash":"Parpadear la salida","node-input-preWarnFlashMs":"Duración del parpadeo (ms)","node-input-emitEvents":"Emitir eventos por la salida","help":{"intro":"Implementa una luz de escalera con lógica KNX: temporizador, preaviso, override manual y bloqueo.","trigger":"Disparos","trigger_ga":"Un valor 1 en la GA de disparo inicia el temporizador. Si está activo, el valor 0 detiene inmediatamente el ciclo.","extend":"La opción \"Nuevo impulso\" define si se reinicia el temporizador, se suma tiempo o se ignora el nuevo impulso.","off":"Con override activo el temporizador se pausa y la luz permanece encendida. El bloqueo evita nuevos disparos y, opcionalmente, apaga la salida.","outputs":"Direcciones de grupo","output_ga":"La GA de salida se envía en DPT 1.001 por defecto. Cambie el DPT si necesita otro formato.","status_ga":"La GA de estado refleja si la luz está activa y el estado de preaviso.","override_ga":"La GA de override mantiene la luz encendida mientras sea 1.","block_ga":"La GA de bloqueo impide nuevas activaciones. Con 0 vuelve el funcionamiento normal.","prewarn":"Preaviso","prewarn_desc":"Puede avisar antes de apagar parpadeando la salida o activando la GA de estado.","events":"Salida del nodo","events_desc":"Si está habilitado, el nodo emite eventos estructurados (trigger, extend, prewarn, timeout, override, block) con el tiempo restante y el estado actual."},"placeholders":{"outputtopic":"Topic opcional para eventos","ga":"ej. 1/1/5","triggerName":"Nombre dispositivo de disparo","outputName":"Nombre actuador de salida","statusName":"Nombre indicador de estado","overrideName":"Nombre canal override","blockName":"Nombre comando bloqueo"},"section_commands":"Direcciones de mando","section_inputs":"Direcciones de entrada"}},"knxUltimateGarage":{"knxUltimateGarage":{"title":"Puerta de garaje","paletteLabel":"Garaje","node-input-server":"Gateway KNX","node-input-name":"Nombre","node-input-outputtopic":"Topic para eventos","command":"GA mando (true=abre, false=cierra)","impulse":"GA impulso","holdOpen":"GA mantener abierto","disable":"GA deshabilitación","photocell":"GA fotocélula","moving":"GA movimiento","obstruction":"GA obstrucción","autoCloseEnable":"Activar cierre automático","autoCloseSeconds":"Retardo cierre automático (segundos)","node-input-emitEvents":"Emitir eventos por la salida","placeholders":{"outputtopic":"Topic opcional para eventos","ga":"ej. 1/2/3","commandName":"Actuador puerta","impulseName":"Entrada impulso","holdOpenName":"Bloqueo cierre","disableName":"Comando deshabilitación","photocellName":"Sensor fotocélula","movingName":"Indicador movimiento","obstructionName":"Indicador obstrucción"},"help":{"intro":"Controla una puerta de garaje KNX con mando directo, impulsos, sensores de seguridad y cierre automático.","commands":"Mandos","command_ga":"La GA mando recibe true para abrir y false para cerrar. El nodo la utiliza también en el cierre automático.","impulse_ga":"La GA impulso conmuta la puerta con un flanco. Se usa también cuando no hay comando directo configurado.","holdopen_ga":"La GA mantener abierto cancela el temporizador de cierre hasta que vuelva a false.","disable_ga":"La GA deshabilitación bloquea cualquier comando emitido por el nodo (modo mantenimiento).","safety":"Seguridad y estado","photocell_ga":"La GA fotocélula debe ponerse a true cuando detecta una obstrucción; el nodo vuelve a abrir y marca la alarma.","moving_ga":"La GA movimiento (opcional) se activa cada vez que el nodo ordena movimiento, facilitando la sincronización en KNX.","obstruction_ga":"La GA obstrucción replica el estado de alarma para otros dispositivos.","auto":"Cierre automático","auto_close":"Activa el cierre automático tras el tiempo configurado, salvo que esté activo mantener abierto o la deshabilitación.","events":"Salida del nodo","events_desc":"Con la emisión de eventos el nodo produce mensajes estructurados (open, close, toggle, obstruction, disabled, hold-open, auto-close) para la lógica del flow."},"section_commands":"Direcciones de mando","section_inputs":"Direcciones de entrada/sensores"}},"knxUltimateSceneController":{"knxUltimateSceneController":{"paletteLabel":"Controlador de escena KNX","title":"Controlador de escena","properties":{"node-input-server":"Puerta","node-input-topic":"Recuerdo de la escena","node-input-dpt":"DPT","node-input-topicTrigger":"Desencadenar","node-input-topicSave":"Guardar escena","node-input-name":"Nombre de nodo","node-input-outputtopic":"Tema"},"placeholder":{"leaveempty":"Dejar vacío para usar la dirección de grupo","valueexample":"Ej: falso, verdadero, de lo contrario cualquier valor"},"other":{"sceneConfig":"Configuración de valores predeterminados de la escena","add":"Presione Agregar, para agregar un dispositivo en la escena"},"advanced":{"notify-DPT3007":"Usted seleccionó un tenue relativo. Por favor, __ph_0__ haga clic aquí para ver esta muestra__ph_1__ y aprenda cómo manejar dicha carga útil.","notify-DPT18001":"Usted seleccionó un DPT de la escena. Por favor, __ph_0__ haga clic aquí para ver esta muestra__ph_1__ y aprenda cómo manejar dicha carga útil.","notify-DPT232600":"Seleccionó un DPT RGB. Por favor, __ph_0__ haga clic aquí para ver esta muestra__ph_1__ y aprenda cómo manejar dicha carga útil.","notify-DPT251600":"Seleccionó un DPT RGBW. Por favor, __ph_0__ haga clic aquí para ver esta muestra__ph_1__ y aprenda cómo manejar dicha carga útil."}}},"knxUltimateLoadControl":{"knxUltimateLoadControl":{"paletteLabel":"Control de carga KNX","title":"Control de carga","primoaldistacco":"(Este es el primero en apagarse)","properties":{"node-input-server":"Puerta","node-input-name":"No ameme","node-input-controlMode":"Modo","node-input-dpt":"DPT","node-input-topic":"Monitor W","node-input-controlGA":"Carga","node-input-monitorGA":"En uso","node-input-wattLimit":"Límite w","node-input-sheddingCheckInterval":"Apagado (s) apagado (s)","node-input-sheddingRestoreDelay":"Interruptor de retraso (s) (s) (s)","node-input-autoRestore":"Recuperación automática"},"selectlists":{"controlModeAuto":"Automático (interno)","controlModeMsg":"Manual (msg.shedding)"},"other":{}}},"knxUltimateHATranslator":{"knxUltimateHATranslator":{"paletteLabel":"Traductor Home Assistant","inputProperty":"Propiedad de entrada","inputHint":"Propiedad del mensaje que se desea traducir, por ejemplo payload o payload.state.","translations":"Traducciones (Home Assistant → KNX)","translationHint":"Una traducción por línea, con el formato estado:true o estado:false. Por ejemplo: on:true y off:false."}}},"zh-CN":{"knxUltimateAlerter":{"knxUltimateAlerter":{"paletteLabel":"KNX 警报器","title":"KNX 警报节点","properties":{"node-input-server":"网关","node-input-name":"名称","node-input-timerinterval":"每条数据间隔发送时间 (秒)","node-input-whentostart":"警报周期启动类型","node-input-initialread":"在连接/重新连接时读取每个设备的值","node-input-initialread0":"不使用","node-input-initialread1":"从KNX总线读取","node-input-initialreadGAInRules":"Read states at start/reconnection"},"selectlists":{"manualstart":"通过传入消息手动启动警报周期","ifnewalert":"使用每个新的警报设备启动警报周期"},"other":{"sceneConfig":"监控设备 (DPT 必须是布尔值)","add":"点击添加,添加设备"}}},"knxUltimateAutoResponder":{"knxUltimateAutoResponder":{"paletteLabel":"KNX 自动应答","respondTo":"响应地址"}},"knxUltimateDateTime":{"knxUltimateDateTime":{"title":"日期/时间","paletteLabel":"日期/时间","node-input-server":"KNX 网关","node-input-name":"名称","node-input-outputtopic":"节点输出 Topic","section_addresses":"组地址","gaDateTime":"日期/时间 GA(DPT 19.001)","gaDate":"日期 GA(DPT 11.001)","gaTime":"时间 GA(DPT 10.001)","section_send":"发送选项","node-input-sendOnDeploy":"部署/启动时发送","node-input-sendOnDeployDelay":"启动延时(秒)","node-input-periodicSend":"周期发送","node-input-periodicSendInterval":"间隔","unit_seconds":"秒","unit_minutes":"分钟","notifySent":"已发送到 KNX","notifyQueued":"已加入队列(网关尚未连接)","placeholders":{"outputtopic":"可选输出 Topic","ga":"例如 1/7/1","nameDateTime":"ETS 名称(可选)","nameDate":"ETS 名称(可选)","nameTime":"ETS 名称(可选)"}}},"knxUltimateWatchDog":{"knxUltimateWatchDog":{"paletteLabel":"KNX 看门狗","title":"看门狗","properties":{"node-input-server":"网关","node-input-checkLevel":"检查等级 (请查阅wiki)","node-input-topic":"需要监控的组地址","node-input-name":"节点名称","node-input-autoStart":"自动启动看门狗定时器","node-input-listenToKnxUltimateNodeErrors":"监听 KNX-Ultimate 节点错误","advancedOptionsAccordion":"高级选项","node-input-retryInterval":"重试间隔 (秒)","node-input-maxRetry":"出错前的重试次数"},"placeholder":{"monitor":" 对于“仅以太网”检查,请使用不存在的组地址.例如:12/0/0。"},"selectlists":{"Ethernet":"仅以太网单播(默认),使用 ping。 仅适用于 KNX 接口(而非路由器)","EthKNX":"以太网 + KNX 双绞线,使用真正的 KNX 设备"},"booleanHint":"DPT 必须为 1.x(布尔值)"}},"knxUltimateGlobalContext":{"knxUltimateGlobalContext":{"paletteLabel":"KNX 全局上下文","title":"KNX 全局变量节点","node-input-name":"变量名 (不能有空格,只能用小写字母)","advanced":{"exposeAsVariable":"作为全局变量公开","exposeAsVariableNO":"No","exposeAsVariableREADONLY":"只读取","exposeAsVariableREADWRITE":"读取/写入","node-input-server":"网关","writeExecutionInterval":"总线写入间隔时间","warning":"警告:所有流之间共享单个节点。您不需要多个节点;不管它在哪里,你都可以看到变量 GLOBALLY."},"interval_250ms":"250毫秒","interval_500ms":"500毫秒","interval_1000ms_default":"1000毫秒(默认)","interval_1500ms":"1500毫秒","interval_2000ms":"2000毫秒","contextStorage":"上下文存储","contextStoragePlaceholder":"可选的上下文存储名称"}},"knxUltimateLogger":{"knxUltimateLogger":{"paletteLabel":"KNX 日志","title":"KNX 日志节点","properties":{"node-input-server":"网关","node-input-topic":"主题名","node-input-name":"节点名称","node-input-autoStartTimerCreateETSXML":"自动启动定时器","mlxETSFileAccordion":"ETS 兼容总线诊断文件","node-input-intervalCreateETSXML":"新的 payload 每 (分钟)","node-input-maxRowsInETSXML":"最大行数 (0 = 没有限制)","node-input-saveMode":"操作","node-input-filePath":"文件路径(绝对或相对)","telegramCounter":"KNX 电报计数","node-input-intervalTelegramCount":"计数间隔(秒)"},"placeholder":{"node-input-filePath":"/var/tmp/knx-logger.xml"},"selectlists":{"saveMode":{"emit":"仅发送 payload","emit_save":"发送 payload 并保存到文件"}},"noFilePath":"文件路径为空"}},"knxUltimateStaircase":{"knxUltimateStaircase":{"title":"Staircase light","paletteLabel":"Staircase","node-input-server":"KNX Gateway","node-input-name":"Name","node-input-outputtopic":"Topic for emitted events","trigger":"Trigger GA","output":"Output GA","status":"Status GA","override":"Override GA","block":"Block GA","node-input-timerSeconds":"Timer duration (seconds)","node-input-extendMode":"On new trigger","extend_restart":"Restart timer","extend_extend":"Extend remaining time","extend_ignore":"Ignore the trigger","node-input-triggerOffCancels":"0 command cancels the cycle","opt_yes":"Yes","opt_no":"No","node-input-blockAction":"When blocked","block_off":"Force light OFF","block_keep":"Leave current state","node-input-preWarnEnable":"Send pre-warning before timeout","node-input-preWarnSeconds":"Pre-warning offset (seconds)","node-input-preWarnMode":"Pre-warning mode","prewarn_status":"Toggle status GA","prewarn_flash":"Flash output","node-input-preWarnFlashMs":"Flash duration (ms)","node-input-emitEvents":"Emit events on node output","help":{"intro":"Implements a staircase light logic with KNX integration, handling timer, pre-warning, manual override and blocking.","trigger":"Triggers","trigger_ga":"A rising edge (value = 1) on the trigger GA starts the timer. If enabled, a value of 0 will immediately cancel the cycle.","extend":"The \"On new trigger\" option controls how subsequent pulses behave: restart the timer, extend the remaining time or ignore the pulse.","off":"When override is active the timer is paused and the light stays on. Blocking disables new pulses and optionally forces the output off.","outputs":"Group addresses","output_ga":"Output GA is driven with DPT 1.001 by default. Configure other DPTs if required.","status_ga":"Status GA mirrors the stair light state (and the pre-warning flag when enabled).","override_ga":"Override GA keeps the light on while true and suspends the timer.","block_ga":"Block GA prevents new activations. When false, normal behaviour resumes.","prewarn":"Pre-warning","prewarn_desc":"Enable a pre-warning to alert occupants before the light turns off. You can either flash the output or toggle the status GA.","events":"Node output","events_desc":"When enabled the node emits structured events (trigger, extend, prewarn, timeout, override, block) with the remaining time and state information."},"placeholders":{"outputtopic":"Optional topic for node events","ga":"e.g. 1/1/5","triggerName":"Trigger device name","outputName":"Output actuator name","statusName":"Status feedback name","overrideName":"Override channel name","blockName":"Block command name"},"section_commands":"Command group addresses","section_inputs":"Input group addresses"}},"knxUltimateGarage":{"knxUltimateGarage":{"title":"Garage door","paletteLabel":"Garage","node-input-server":"KNX Gateway","node-input-name":"Name","node-input-outputtopic":"Topic for emitted events","command":"Command GA (true=open, false=close)","impulse":"Impulse GA","holdOpen":"Hold-open GA","disable":"Disable GA","photocell":"Photocell GA","moving":"Moving GA","obstruction":"Obstruction GA","autoCloseEnable":"Enable auto re-close","autoCloseSeconds":"Auto re-close delay (seconds)","node-input-emitEvents":"Emit events on node output","placeholders":{"outputtopic":"Optional topic for node events","ga":"e.g. 1/2/3","commandName":"Door actuator","impulseName":"Impulse input","holdOpenName":"Hold-open switch","disableName":"Disable command","photocellName":"Photocell sensor","movingName":"Movement indicator","obstructionName":"Obstruction indicator"},"help":{"intro":"Controls a KNX garage door with direct open/close commands, impulse inputs, safety sensors and automatic re-close.","commands":"Commands","command_ga":"Use the command GA for boolean control: true opens, false closes. The node also emits this when the auto close triggers.","impulse_ga":"An impulse GA toggles the door. Rising edges toggle the internal state; it is also used if no direct command GA is configured.","holdopen_ga":"Hold-open GA keeps the door open and cancels the auto re-close until it returns to false.","disable_ga":"Disable GA stops the node from issuing new commands. Useful for maintenance or manual mode.","safety":"Safety and status","photocell_ga":"The photocell GA should go true when the beam is interrupted. The node re-opens the door and flags obstruction.","moving_ga":"Movement GA (optional) is pulsed whenever the node commands the door to move, allowing other devices to follow the motion.","obstruction_ga":"Obstruction GA mirrors the obstruction state so other KNX components can react.","auto":"Automatic re-close","auto_close":"Enable the auto re-close timer to close the door automatically after the configured delay once it is open, unless hold-open or disable are active.","events":"Node output","events_desc":"With event emission enabled the node outputs structured messages (open, close, toggle, obstruction, disabled, hold-open, auto-close) for flow logic."},"section_commands":"Command group addresses","section_inputs":"Input / sensor group addresses"}},"knxUltimateSceneController":{"knxUltimateSceneController":{"paletteLabel":"KNX 场景控制器","title":"KNX 场景节点","properties":{"node-input-server":"网关","node-input-topic":"调用场景","node-input-dpt":"DPT","node-input-topicTrigger":"调用","node-input-topicSave":"保存","node-input-name":"节点名称","node-input-outputtopic":"主题名称"},"placeholder":{"leaveempty":"如果留空则使用组地址","valueexample":"案例: 0 是 false, 1 是 true, 否则是任意值"},"other":{"sceneConfig":"场景配置 (default values)","add":"点击添加, 就在场景中添加一个设备"},"advanced":{"notify-DPT3007":"你选择了相对调光类型. 请 <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---Dimming'>点击这个连接参考案例</a> 并学习如何处理此类消息.","notify-DPT18001":"你选择了场景 DPT. 请 <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---Control-a-scene-actuator'>点击这个连接参考案例</a> 并学习如何处理此类消息.","notify-DPT232600":"你选择了 RGB DPT. 请 <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---RGB-Color'>点击这个连接参考案例</a> 并学习如何处理此类消息.","notify-DPT251600":"你选择了 RGBW DPT. 请 <a target='_blank' href='https://supergiovane.github.io/node-red-contrib-knx-ultimate/wiki/-Sample---RGBW-Color-plus-White'>点击这个连接参考案例</a> 并学习如何处理此类消息."}}},"knxUltimateLoadControl":{"knxUltimateLoadControl":{"paletteLabel":"KNX 负载控制","title":"Load control","primoaldistacco":"(this is the first to be switched off)","properties":{"node-input-server":"Gateway","node-input-name":"Name","node-input-controlMode":"模式","node-input-dpt":"DPT","node-input-topic":"Monitor W","node-input-controlGA":"Load","node-input-monitorGA":"In use","node-input-wattLimit":"Limit W","node-input-sheddingCheckInterval":"Delay switch off (s)","node-input-sheddingRestoreDelay":"Delay switch on (s)","node-input-autoRestore":"Automatic recovery"},"selectlists":{"controlModeAuto":"自动(内部)","controlModeMsg":"手动(msg.shedding)"},"other":{}}},"knxUltimateHATranslator":{"knxUltimateHATranslator":{"paletteLabel":"Home Assistant 转换器","inputProperty":"输入属性","inputHint":"需要转换的消息属性,例如 payload 或 payload.state。","translations":"转换映射(Home Assistant → KNX)","translationHint":"每行一条映射,格式为 state:true 或 state:false。例如:on:true 和 off:false。"}}}}
|
|
2355
|
+
|
|
2356
|
+
// Editor definitions contain closure state. Cache one definition per profile
|
|
2357
|
+
// and RED editor instance, matching Node-RED's normal registration lifetime.
|
|
2358
|
+
// WeakMap prevents a discarded test/editor RED object from being retained.
|
|
2359
|
+
const definitionCaches = new WeakMap()
|
|
2360
|
+
|
|
2361
|
+
const normalizeUtilityType = (utilityType) => (
|
|
2362
|
+
Object.prototype.hasOwnProperty.call(PROFILE_TYPES, utilityType) ? utilityType : 'alerter'
|
|
2363
|
+
)
|
|
2364
|
+
|
|
2365
|
+
const normalizeLocale = (locale) => {
|
|
2366
|
+
const value = String(locale || '').trim()
|
|
2367
|
+
if (!value) return 'en'
|
|
2368
|
+
if (/^zh(?:[-_]|$)/i.test(value)) return 'zh-CN'
|
|
2369
|
+
const shortLocale = value.split(/[-_]/)[0].toLowerCase()
|
|
2370
|
+
return Object.prototype.hasOwnProperty.call(PROFILE_TRANSLATIONS, shortLocale) ? shortLocale : 'en'
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
const UTILITY_LOCALE_KEY = 'node-red-contrib-knx-ultimate/knxUltimateUtility:knxUltimateUtility.locale'
|
|
2374
|
+
|
|
2375
|
+
const nodeRedLocale = (RED) => {
|
|
2376
|
+
try {
|
|
2377
|
+
if (RED && typeof RED._ === 'function') {
|
|
2378
|
+
const translated = RED._(UTILITY_LOCALE_KEY)
|
|
2379
|
+
if (translated && translated !== UTILITY_LOCALE_KEY) return translated
|
|
2380
|
+
}
|
|
2381
|
+
} catch (error) { /* use the compatibility fallbacks below */ }
|
|
2382
|
+
return undefined
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
const currentLocale = (RED) => {
|
|
2386
|
+
// Ask Node-RED's active catalog first. documentElement.lang can remain
|
|
2387
|
+
// English even while the editor has loaded another locale.
|
|
2388
|
+
const candidates = [
|
|
2389
|
+
nodeRedLocale(RED),
|
|
2390
|
+
RED && RED.settings && RED.settings.lang,
|
|
2391
|
+
root && root.document && root.document.documentElement && root.document.documentElement.lang,
|
|
2392
|
+
root && root.navigator && root.navigator.language
|
|
2393
|
+
]
|
|
2394
|
+
return normalizeLocale(candidates.find((candidate) => candidate))
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
const nestedValue = (object, key) => String(key || '').split('.').reduce((value, part) => (
|
|
2398
|
+
value && Object.prototype.hasOwnProperty.call(value, part) ? value[part] : undefined
|
|
2399
|
+
), object)
|
|
2400
|
+
|
|
2401
|
+
const interpolate = (value, replacements) => {
|
|
2402
|
+
if (typeof value !== 'string' || !replacements || typeof replacements !== 'object') return value
|
|
2403
|
+
return value.replace(/{{\s*([^{}]+?)\s*}}/g, (match, key) => (
|
|
2404
|
+
Object.prototype.hasOwnProperty.call(replacements, key) ? String(replacements[key]) : match
|
|
2405
|
+
))
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
const translationLookup = (utilityType, key, RED, replacements) => {
|
|
2409
|
+
let selectedType = normalizeUtilityType(utilityType)
|
|
2410
|
+
let localKey = String(key || '')
|
|
2411
|
+
const separatorIndex = localKey.lastIndexOf(':')
|
|
2412
|
+
if (separatorIndex >= 0) {
|
|
2413
|
+
// Fully qualified keys may name a different private profile. Resolve that
|
|
2414
|
+
// namespace locally instead of delegating to a legacy Node-RED node type.
|
|
2415
|
+
const namespace = localKey.slice(0, separatorIndex)
|
|
2416
|
+
localKey = localKey.slice(separatorIndex + 1)
|
|
2417
|
+
const namespaceNodeType = namespace.split('/').pop()
|
|
2418
|
+
const matchedType = Object.keys(PROFILE_TYPES).find((type) => PROFILE_TYPES[type] === namespaceNodeType)
|
|
2419
|
+
if (matchedType) selectedType = matchedType
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
const nodeType = PROFILE_TYPES[selectedType]
|
|
2423
|
+
const locale = currentLocale(RED)
|
|
2424
|
+
const localized = nestedValue(PROFILE_TRANSLATIONS[locale] && PROFILE_TRANSLATIONS[locale][nodeType], localKey)
|
|
2425
|
+
const fallback = nestedValue(PROFILE_TRANSLATIONS.en && PROFILE_TRANSLATIONS.en[nodeType], localKey)
|
|
2426
|
+
return interpolate(localized === undefined ? fallback : localized, replacements)
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
const translate = (utilityType, key, RED, replacements) => {
|
|
2430
|
+
const translated = translationLookup(utilityType, key, RED, replacements)
|
|
2431
|
+
if (translated !== undefined) return translated
|
|
2432
|
+
// Unknown keys may belong to Node-RED itself or the outer Utility. Only
|
|
2433
|
+
// those keys are allowed to fall through to the real editor translator.
|
|
2434
|
+
if (RED && typeof RED._ === 'function') {
|
|
2435
|
+
try { return RED._(key, replacements) } catch (error) { /* use the key below */ }
|
|
2436
|
+
}
|
|
2437
|
+
return key
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
const createDefinition = (utilityType, RED) => {
|
|
2441
|
+
const selectedType = normalizeUtilityType(utilityType)
|
|
2442
|
+
let capturedDefinition
|
|
2443
|
+
|
|
2444
|
+
// Object.create keeps every real RED editor service available (nodes.node,
|
|
2445
|
+
// sidebar, events, notify, and so on) while replacing only the registration
|
|
2446
|
+
// boundary and translation resolver used by the private editor source.
|
|
2447
|
+
const redFacade = Object.create(RED)
|
|
2448
|
+
redFacade.nodes = Object.create((RED && RED.nodes) || null)
|
|
2449
|
+
redFacade.nodes.registerType = (nodeType, definition) => {
|
|
2450
|
+
if (nodeType === PROFILE_TYPES[selectedType]) capturedDefinition = definition
|
|
2451
|
+
}
|
|
2452
|
+
redFacade._ = (key, replacements) => translate(selectedType, key, RED, replacements)
|
|
2453
|
+
|
|
2454
|
+
PROFILE_FACTORIES[selectedType](redFacade)
|
|
2455
|
+
if (!capturedDefinition) throw new Error('Unable to load KNX Utility editor profile: ' + selectedType)
|
|
2456
|
+
|
|
2457
|
+
// Node-RED normally attaches a scoped translator while registering a type.
|
|
2458
|
+
// Because registration is captured, attach the equivalent private resolver.
|
|
2459
|
+
capturedDefinition._ = (key, replacements) => translate(selectedType, key, RED, replacements)
|
|
2460
|
+
return capturedDefinition
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
const getDefinition = (utilityType, RED) => {
|
|
2464
|
+
const selectedType = normalizeUtilityType(utilityType)
|
|
2465
|
+
let cache = definitionCaches.get(RED)
|
|
2466
|
+
if (!cache) {
|
|
2467
|
+
cache = new Map()
|
|
2468
|
+
definitionCaches.set(RED, cache)
|
|
2469
|
+
}
|
|
2470
|
+
if (!cache.has(selectedType)) cache.set(selectedType, createDefinition(selectedType, RED))
|
|
2471
|
+
return cache.get(selectedType)
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
const getTemplate = (utilityType) => PROFILE_TEMPLATES[normalizeUtilityType(utilityType)]
|
|
2475
|
+
|
|
2476
|
+
// Expose only the narrow API consumed by knxUltimateUtility.html and
|
|
2477
|
+
// tests. The implementation tables remain private and cannot be mutated.
|
|
2478
|
+
return Object.freeze({
|
|
2479
|
+
PROFILE_TYPES,
|
|
2480
|
+
createDefinition,
|
|
2481
|
+
getDefinition,
|
|
2482
|
+
getTemplate,
|
|
2483
|
+
normalizeUtilityType,
|
|
2484
|
+
normalizeLocale,
|
|
2485
|
+
currentLocale,
|
|
2486
|
+
translate
|
|
2487
|
+
})
|
|
2488
|
+
}))
|