node-red-contrib-knx-ultimate 4.0.12 → 4.0.13
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 +7 -0
- package/nodes/knxUltimateGarage.html +309 -0
- package/nodes/knxUltimateGarage.js +501 -0
- package/nodes/knxUltimateStaircase.html +326 -0
- package/nodes/knxUltimateStaircase.js +461 -0
- package/nodes/locales/en/knxUltimateGarage.html +62 -0
- package/nodes/locales/en/knxUltimateGarage.json +48 -0
- package/nodes/locales/en/knxUltimateStaircase.html +66 -0
- package/nodes/locales/en/knxUltimateStaircase.json +59 -0
- package/nodes/locales/es/knxUltimateGarage.html +63 -0
- package/nodes/locales/es/knxUltimateGarage.json +48 -0
- package/nodes/locales/es/knxUltimateStaircase.html +66 -0
- package/nodes/locales/es/knxUltimateStaircase.json +59 -0
- package/nodes/locales/fr/knxUltimateGarage.html +63 -0
- package/nodes/locales/fr/knxUltimateGarage.json +48 -0
- package/nodes/locales/fr/knxUltimateStaircase.html +66 -0
- package/nodes/locales/fr/knxUltimateStaircase.json +59 -0
- package/nodes/locales/it/knxUltimateGarage.html +63 -0
- package/nodes/locales/it/knxUltimateGarage.json +48 -0
- package/nodes/locales/it/knxUltimateStaircase.html +66 -0
- package/nodes/locales/it/knxUltimateStaircase.json +59 -0
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,9 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 4.0.13** - October 2025<br/>
|
|
10
|
+
- NEW: Added KNX Garage Door node with boolean/impulse control, hold-open and disable GAs, safety integration and automatic re-close timer.<br/>
|
|
11
|
+
- KNX Staircase node: editor refinements with read-only DPTs, flow payload support and localisation polish across supported languages.<br/>
|
|
12
|
+
- KNX Staircase & Garage nodes: unified status handling and multilingual flow examples in docs/help.<br/>
|
|
13
|
+
|
|
9
14
|
**Version 4.0.12** - October 2025<br/>
|
|
15
|
+
- NEW: Added KNX Staircase Light node with timer handling, pre-warning, override and block support.<br/>
|
|
10
16
|
- KNX Config node: replaced the "errors only" status filter with a configurable status throttle (0/1/3/5/10/30 s) that emits only the latest status after the chosen delay, preventing editor memory growth when many nodes update.<br/>
|
|
11
17
|
- HUE Light node: the "Keep brightness" option now restores the last active dim level when toggled via KNX On/Off instead of forcing 100%.<br/>
|
|
18
|
+
- Common functions: hardened `/knxultimateCheckHueConnected` admin endpoint so missing/initialising HUE config nodes no longer raise exceptions.<br/>
|
|
12
19
|
|
|
13
20
|
**Version 4.0.11** - October 2025<br/>
|
|
14
21
|
- HUE nodes: hardened KNX telegram handling with a shared safe-send guard so editor events no longer ceases to function, when the KNX gateway is offline.<br/>
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script>
|
|
2
|
+
|
|
3
|
+
<script type="text/javascript">
|
|
4
|
+
RED.nodes.registerType('knxUltimateGarage', {
|
|
5
|
+
category: 'KNX Ultimate',
|
|
6
|
+
color: '#7dd484',
|
|
7
|
+
defaults: {
|
|
8
|
+
server: { type: 'knxUltimate-config', required: true },
|
|
9
|
+
name: { value: '' },
|
|
10
|
+
outputtopic: { value: '' },
|
|
11
|
+
gaCommand: { value: '', required: true },
|
|
12
|
+
nameCommand: { value: '' },
|
|
13
|
+
dptCommand: { value: '1.001' },
|
|
14
|
+
gaImpulse: { value: '' },
|
|
15
|
+
nameImpulse: { value: '' },
|
|
16
|
+
dptImpulse: { value: '1.017' },
|
|
17
|
+
gaHoldOpen: { value: '' },
|
|
18
|
+
nameHoldOpen: { value: '' },
|
|
19
|
+
dptHoldOpen: { value: '1.001' },
|
|
20
|
+
gaDisable: { value: '' },
|
|
21
|
+
nameDisable: { value: '' },
|
|
22
|
+
dptDisable: { value: '1.001' },
|
|
23
|
+
gaPhotocell: { value: '' },
|
|
24
|
+
namePhotocell: { value: '' },
|
|
25
|
+
dptPhotocell: { value: '1.001' },
|
|
26
|
+
gaMoving: { value: '' },
|
|
27
|
+
nameMoving: { value: '' },
|
|
28
|
+
dptMoving: { value: '1.001' },
|
|
29
|
+
gaObstruction: { value: '' },
|
|
30
|
+
nameObstruction: { value: '' },
|
|
31
|
+
dptObstruction: { value: '1.001' },
|
|
32
|
+
autoCloseEnable: { value: true },
|
|
33
|
+
autoCloseSeconds: { value: 120, validate: RED.validators.number() },
|
|
34
|
+
emitEvents: { value: false }
|
|
35
|
+
},
|
|
36
|
+
inputs: 1,
|
|
37
|
+
outputs: 1,
|
|
38
|
+
icon: 'node-knx-icon.svg',
|
|
39
|
+
label: function () {
|
|
40
|
+
return this.name || 'KNX Garage';
|
|
41
|
+
},
|
|
42
|
+
paletteLabel: 'KNX Garage',
|
|
43
|
+
oneditprepare: function () {
|
|
44
|
+
const node = this;
|
|
45
|
+
const $knxServerInput = $('#node-input-server');
|
|
46
|
+
try { RED.sidebar.show('help'); } catch (error) { /* ignore */ }
|
|
47
|
+
const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none']);
|
|
48
|
+
|
|
49
|
+
const resolveKnxServerValue = () => {
|
|
50
|
+
const domValue = $knxServerInput.val();
|
|
51
|
+
if (domValue !== undefined && domValue !== null && domValue !== '') return domValue;
|
|
52
|
+
if (node.server !== undefined && node.server !== null && node.server !== '') return node.server;
|
|
53
|
+
return '';
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const hasKnxServerSelected = () => {
|
|
57
|
+
const val = resolveKnxServerValue();
|
|
58
|
+
return !(val === undefined || val === null || KNX_EMPTY_VALUES.has(String(val)));
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map());
|
|
62
|
+
|
|
63
|
+
const fetchGroupAddresses = (serverId) => {
|
|
64
|
+
if (!serverId) return Promise.resolve([]);
|
|
65
|
+
if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId));
|
|
66
|
+
return new Promise((resolve) => {
|
|
67
|
+
$.getJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
|
|
68
|
+
const list = Array.isArray(data) ? data : [];
|
|
69
|
+
KNX_GA_CACHE.set(serverId, list);
|
|
70
|
+
resolve(list);
|
|
71
|
+
}).fail(() => resolve([]));
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const getGroupAddress = (gaSelector, nameSelector, dptSelector, prefixes) => {
|
|
76
|
+
const $gaInput = $(gaSelector);
|
|
77
|
+
const $nameInput = $(nameSelector);
|
|
78
|
+
const $dptInput = $(dptSelector);
|
|
79
|
+
if (!$gaInput.length) return;
|
|
80
|
+
|
|
81
|
+
const ensureAutocomplete = () => {
|
|
82
|
+
const sourceFn = (request, response) => {
|
|
83
|
+
if (!hasKnxServerSelected()) {
|
|
84
|
+
response([]);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const serverId = resolveKnxServerValue();
|
|
88
|
+
fetchGroupAddresses(serverId).then((data) => {
|
|
89
|
+
const items = [];
|
|
90
|
+
data.forEach((entry) => {
|
|
91
|
+
const dpt = entry.dpt || '';
|
|
92
|
+
const allowed = prefixes.some((prefix) => prefix === '' || dpt.startsWith(prefix));
|
|
93
|
+
if (!allowed) return;
|
|
94
|
+
const devName = entry.devicename || '';
|
|
95
|
+
const searchStr = `${entry.ga} (${devName}) DPT${dpt}`;
|
|
96
|
+
if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return;
|
|
97
|
+
items.push({
|
|
98
|
+
label: `${entry.ga} # ${devName} # ${dpt}`,
|
|
99
|
+
value: entry.ga,
|
|
100
|
+
dpt
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
response(items);
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if ($gaInput.data('knx-ga-initialised')) {
|
|
108
|
+
$gaInput.autocomplete('option', 'source', sourceFn);
|
|
109
|
+
} else {
|
|
110
|
+
$gaInput
|
|
111
|
+
.autocomplete({
|
|
112
|
+
minLength: 0,
|
|
113
|
+
source: sourceFn,
|
|
114
|
+
select: (event, ui) => {
|
|
115
|
+
let deviceName = '';
|
|
116
|
+
try {
|
|
117
|
+
deviceName = ui.item.label.split('#')[1].trim();
|
|
118
|
+
deviceName = deviceName.replace(/^\)/, '').trim();
|
|
119
|
+
} catch (error) { deviceName = ''; }
|
|
120
|
+
if ($nameInput.length) {
|
|
121
|
+
if (deviceName && deviceName !== '') {
|
|
122
|
+
$nameInput.val(deviceName);
|
|
123
|
+
} else if (!$nameInput.val()) {
|
|
124
|
+
$nameInput.val('');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const parts = ui.item.label.split('#');
|
|
129
|
+
const dptFromLabel = parts.length >= 3 ? parts[2].trim() : '';
|
|
130
|
+
if (dptFromLabel !== '') {
|
|
131
|
+
$dptInput.val(dptFromLabel);
|
|
132
|
+
}
|
|
133
|
+
} catch (error) { /* ignore */ }
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
.on('focus.knxUltimateGarage click.knxUltimateGarage', function () {
|
|
137
|
+
const currentValue = $(this).val() || '';
|
|
138
|
+
try { $(this).autocomplete('search', `${currentValue} exactmatch`); } catch (error) { /* ignore */ }
|
|
139
|
+
});
|
|
140
|
+
$gaInput.data('knx-ga-initialised', true);
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
if (hasKnxServerSelected()) {
|
|
144
|
+
const srv = RED.nodes.node(resolveKnxServerValue());
|
|
145
|
+
if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id);
|
|
146
|
+
}
|
|
147
|
+
} catch (error) { /* ignore */ }
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
ensureAutocomplete();
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const BINARY_PREFIX = ['1.'];
|
|
154
|
+
|
|
155
|
+
const refreshKnxBindings = () => {
|
|
156
|
+
if (!hasKnxServerSelected()) return;
|
|
157
|
+
|
|
158
|
+
getGroupAddress('#node-input-gaCommand', '#node-input-nameCommand', '#node-input-dptCommand', BINARY_PREFIX);
|
|
159
|
+
getGroupAddress('#node-input-gaImpulse', '#node-input-nameImpulse', '#node-input-dptImpulse', BINARY_PREFIX);
|
|
160
|
+
getGroupAddress('#node-input-gaHoldOpen', '#node-input-nameHoldOpen', '#node-input-dptHoldOpen', BINARY_PREFIX);
|
|
161
|
+
getGroupAddress('#node-input-gaDisable', '#node-input-nameDisable', '#node-input-dptDisable', BINARY_PREFIX);
|
|
162
|
+
getGroupAddress('#node-input-gaPhotocell', '#node-input-namePhotocell', '#node-input-dptPhotocell', BINARY_PREFIX);
|
|
163
|
+
getGroupAddress('#node-input-gaMoving', '#node-input-nameMoving', '#node-input-dptMoving', BINARY_PREFIX);
|
|
164
|
+
getGroupAddress('#node-input-gaObstruction', '#node-input-nameObstruction', '#node-input-dptObstruction', BINARY_PREFIX);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
$knxServerInput.on('change', () => {
|
|
168
|
+
KNX_GA_CACHE.clear();
|
|
169
|
+
refreshKnxBindings();
|
|
170
|
+
});
|
|
171
|
+
if (hasKnxServerSelected()) refreshKnxBindings();
|
|
172
|
+
|
|
173
|
+
const syncAutoClose = () => {
|
|
174
|
+
const enabled = $('#node-input-autoCloseEnable').is(':checked');
|
|
175
|
+
const $seconds = $('#node-input-autoCloseSeconds').closest('.form-row');
|
|
176
|
+
$seconds.toggle(enabled);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
$('#node-input-autoCloseEnable').on('change', syncAutoClose);
|
|
180
|
+
syncAutoClose();
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<script type="text/html" data-template-name="knxUltimateGarage">
|
|
186
|
+
<div class="form-row" style="display:flex; align-items:center;">
|
|
187
|
+
<label for="node-input-server" style="width:180px"><i class="fa fa-circle-o"></i> <span data-i18n="knxUltimateGarage.node-input-server"></span></label>
|
|
188
|
+
<input type="text" id="node-input-server" style="flex:1">
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<div class="form-row" style="display:flex; align-items:center;">
|
|
192
|
+
<label for="node-input-name" style="width:180px"><i class="fa fa-tag"></i> <span data-i18n="knxUltimateGarage.node-input-name"></span></label>
|
|
193
|
+
<input type="text" id="node-input-name" style="flex:1">
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div class="form-row" style="display:flex; align-items:center;">
|
|
197
|
+
<label for="node-input-outputtopic" style="width:180px"><i class="fa fa-comment"></i> <span data-i18n="knxUltimateGarage.node-input-outputtopic"></span></label>
|
|
198
|
+
<input type="text" id="node-input-outputtopic" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.outputtopic">
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<hr>
|
|
202
|
+
<div class="form-row" style="margin:4px 0 2px;">
|
|
203
|
+
<span style="font-weight:bold;" data-i18n="knxUltimateGarage.section_commands"></span>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div class="form-row" style="display:flex; align-items:center; gap:8px;">
|
|
207
|
+
<label for="node-input-gaCommand" style="width:180px"><i class="fa fa-exchange"></i> <span data-i18n="knxUltimateGarage.command"></span></label>
|
|
208
|
+
<input type="text" id="node-input-gaCommand" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga">
|
|
209
|
+
<input type="text" id="node-input-nameCommand" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.commandName">
|
|
210
|
+
<label for="node-input-dptCommand" style="width:60px; text-align:right">DPT</label>
|
|
211
|
+
<input type="text" id="node-input-dptCommand" style="width:160px" readonly>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<div class="form-row" style="display:flex; align-items:center; gap:8px;">
|
|
215
|
+
<label for="node-input-gaImpulse" style="width:180px"><i class="fa fa-bolt"></i> <span data-i18n="knxUltimateGarage.impulse"></span></label>
|
|
216
|
+
<input type="text" id="node-input-gaImpulse" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga">
|
|
217
|
+
<input type="text" id="node-input-nameImpulse" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.impulseName">
|
|
218
|
+
<label for="node-input-dptImpulse" style="width:60px; text-align:right">DPT</label>
|
|
219
|
+
<input type="text" id="node-input-dptImpulse" style="width:160px" readonly>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<div class="form-row" style="display:flex; align-items:center; gap:8px;">
|
|
223
|
+
<label for="node-input-gaMoving" style="width:180px"><i class="fa fa-arrows-h"></i> <span data-i18n="knxUltimateGarage.moving"></span></label>
|
|
224
|
+
<input type="text" id="node-input-gaMoving" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga">
|
|
225
|
+
<input type="text" id="node-input-nameMoving" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.movingName">
|
|
226
|
+
<label for="node-input-dptMoving" style="width:60px; text-align:right">DPT</label>
|
|
227
|
+
<input type="text" id="node-input-dptMoving" style="width:160px" readonly>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div class="form-row" style="display:flex; align-items:center; gap:8px;">
|
|
231
|
+
<label for="node-input-gaObstruction" style="width:180px"><i class="fa fa-exclamation-triangle"></i> <span data-i18n="knxUltimateGarage.obstruction"></span></label>
|
|
232
|
+
<input type="text" id="node-input-gaObstruction" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga">
|
|
233
|
+
<input type="text" id="node-input-nameObstruction" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.obstructionName">
|
|
234
|
+
<label for="node-input-dptObstruction" style="width:60px; text-align:right">DPT</label>
|
|
235
|
+
<input type="text" id="node-input-dptObstruction" style="width:160px" readonly>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<div style="border-top:1px solid #ccc; margin:10px 0 6px;"></div>
|
|
239
|
+
|
|
240
|
+
<div class="form-row" style="margin:4px 0 2px;">
|
|
241
|
+
<span style="font-weight:bold;" data-i18n="knxUltimateGarage.section_inputs"></span>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div class="form-row" style="display:flex; align-items:center; gap:8px;">
|
|
245
|
+
<label for="node-input-gaHoldOpen" style="width:180px"><i class="fa fa-pause"></i> <span data-i18n="knxUltimateGarage.holdOpen"></span></label>
|
|
246
|
+
<input type="text" id="node-input-gaHoldOpen" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga">
|
|
247
|
+
<input type="text" id="node-input-nameHoldOpen" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.holdOpenName">
|
|
248
|
+
<label for="node-input-dptHoldOpen" style="width:60px; text-align:right">DPT</label>
|
|
249
|
+
<input type="text" id="node-input-dptHoldOpen" style="width:160px" readonly>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div class="form-row" style="display:flex; align-items:center; gap:8px;">
|
|
253
|
+
<label for="node-input-gaDisable" style="width:180px"><i class="fa fa-ban"></i> <span data-i18n="knxUltimateGarage.disable"></span></label>
|
|
254
|
+
<input type="text" id="node-input-gaDisable" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga">
|
|
255
|
+
<input type="text" id="node-input-nameDisable" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.disableName">
|
|
256
|
+
<label for="node-input-dptDisable" style="width:60px; text-align:right">DPT</label>
|
|
257
|
+
<input type="text" id="node-input-dptDisable" style="width:160px" readonly>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div class="form-row" style="display:flex; align-items:center; gap:8px;">
|
|
261
|
+
<label for="node-input-gaPhotocell" style="width:180px"><i class="fa fa-lightbulb-o"></i> <span data-i18n="knxUltimateGarage.photocell"></span></label>
|
|
262
|
+
<input type="text" id="node-input-gaPhotocell" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga">
|
|
263
|
+
<input type="text" id="node-input-namePhotocell" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.photocellName">
|
|
264
|
+
<label for="node-input-dptPhotocell" style="width:60px; text-align:right">DPT</label>
|
|
265
|
+
<input type="text" id="node-input-dptPhotocell" style="width:160px" readonly>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<hr>
|
|
269
|
+
<div class="form-row" style="display:flex; align-items:center;">
|
|
270
|
+
<label for="node-input-autoCloseEnable" style="width:180px"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateGarage.autoCloseEnable"></span></label>
|
|
271
|
+
<input type="checkbox" id="node-input-autoCloseEnable" style="width:auto">
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<div class="form-row" style="display:flex; align-items:center;">
|
|
275
|
+
<label for="node-input-autoCloseSeconds" style="width:180px"><i class="fa fa-hourglass-end"></i> <span data-i18n="knxUltimateGarage.autoCloseSeconds"></span></label>
|
|
276
|
+
<input type="number" id="node-input-autoCloseSeconds" style="width:140px" min="1">
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<hr>
|
|
280
|
+
<div class="form-row" style="display:flex; align-items:center;">
|
|
281
|
+
<label for="node-input-emitEvents" style="width:180px"><i class="fa fa-sign-out"></i> <span data-i18n="knxUltimateGarage.node-input-emitEvents"></span></label>
|
|
282
|
+
<input type="checkbox" id="node-input-emitEvents" style="width:auto">
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<br/><br/><br/><br/>
|
|
286
|
+
</script>
|
|
287
|
+
|
|
288
|
+
<script type="text/html" data-help-name="knxUltimateGarage">
|
|
289
|
+
<div data-i18n="knxUltimateGarage.help.intro"></div>
|
|
290
|
+
<h3 data-i18n="knxUltimateGarage.help.commands"></h3>
|
|
291
|
+
<ul>
|
|
292
|
+
<li data-i18n="knxUltimateGarage.help.command_ga"></li>
|
|
293
|
+
<li data-i18n="knxUltimateGarage.help.impulse_ga"></li>
|
|
294
|
+
<li data-i18n="knxUltimateGarage.help.holdopen_ga"></li>
|
|
295
|
+
<li data-i18n="knxUltimateGarage.help.disable_ga"></li>
|
|
296
|
+
</ul>
|
|
297
|
+
<h3 data-i18n="knxUltimateGarage.help.safety"></h3>
|
|
298
|
+
<ul>
|
|
299
|
+
<li data-i18n="knxUltimateGarage.help.photocell_ga"></li>
|
|
300
|
+
<li data-i18n="knxUltimateGarage.help.moving_ga"></li>
|
|
301
|
+
<li data-i18n="knxUltimateGarage.help.obstruction_ga"></li>
|
|
302
|
+
</ul>
|
|
303
|
+
<h3 data-i18n="knxUltimateGarage.help.auto"></h3>
|
|
304
|
+
<ul>
|
|
305
|
+
<li data-i18n="knxUltimateGarage.help.auto_close"></li>
|
|
306
|
+
</ul>
|
|
307
|
+
<h3 data-i18n="knxUltimateGarage.help.events"></h3>
|
|
308
|
+
<p data-i18n="knxUltimateGarage.help.events_desc"></p>
|
|
309
|
+
</script>
|