node-red-contrib-velbus-2026 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG_FORUM.md +784 -0
  2. package/LICENSE +21 -0
  3. package/README.md +140 -0
  4. package/examples/velbus-basic-relay-dimmer.json +328 -0
  5. package/index.js +3 -0
  6. package/lib/blind-types-20.js +26 -0
  7. package/lib/blind-types-s.js +46 -0
  8. package/lib/blind-types.js +38 -0
  9. package/lib/dimmer-types-20.js +120 -0
  10. package/lib/dimmer-types.js +34 -0
  11. package/lib/glass-panel-types.js +296 -0
  12. package/lib/pir-types-20.js +51 -0
  13. package/lib/pir-types.js +61 -0
  14. package/lib/relay-types-20.js +43 -0
  15. package/lib/relay-types.js +39 -0
  16. package/lib/sensor-types-20.js +29 -0
  17. package/lib/sensor-types.js +49 -0
  18. package/lib/velbus-utils.js +80 -0
  19. package/nodes/velbus-blind/velbus-blind.html +165 -0
  20. package/nodes/velbus-blind/velbus-blind.js +251 -0
  21. package/nodes/velbus-blind-20/velbus-blind-20.html +179 -0
  22. package/nodes/velbus-blind-20/velbus-blind-20.js +300 -0
  23. package/nodes/velbus-blind-s/velbus-blind-s.html +181 -0
  24. package/nodes/velbus-blind-s/velbus-blind-s.js +282 -0
  25. package/nodes/velbus-bridge/velbus-bridge.html +72 -0
  26. package/nodes/velbus-bridge/velbus-bridge.js +304 -0
  27. package/nodes/velbus-button/velbus-button.html +172 -0
  28. package/nodes/velbus-button/velbus-button.js +117 -0
  29. package/nodes/velbus-clock/velbus-clock.html +198 -0
  30. package/nodes/velbus-clock/velbus-clock.js +273 -0
  31. package/nodes/velbus-dimmer/velbus-dimmer.html +174 -0
  32. package/nodes/velbus-dimmer/velbus-dimmer.js +457 -0
  33. package/nodes/velbus-dimmer-20/velbus-dimmer-20.html +271 -0
  34. package/nodes/velbus-dimmer-20/velbus-dimmer-20.js +602 -0
  35. package/nodes/velbus-glass-panel/velbus-glass-panel.html +337 -0
  36. package/nodes/velbus-glass-panel/velbus-glass-panel.js +492 -0
  37. package/nodes/velbus-meteo/velbus-meteo.html +120 -0
  38. package/nodes/velbus-meteo/velbus-meteo.js +279 -0
  39. package/nodes/velbus-pir/velbus-pir.html +187 -0
  40. package/nodes/velbus-pir/velbus-pir.js +269 -0
  41. package/nodes/velbus-pir-20/velbus-pir-20.html +186 -0
  42. package/nodes/velbus-pir-20/velbus-pir-20.js +352 -0
  43. package/nodes/velbus-relay/velbus-relay.html +215 -0
  44. package/nodes/velbus-relay/velbus-relay.js +404 -0
  45. package/nodes/velbus-relay-20/velbus-relay-20.html +123 -0
  46. package/nodes/velbus-relay-20/velbus-relay-20.js +434 -0
  47. package/nodes/velbus-scan/velbus-scan.html +87 -0
  48. package/nodes/velbus-scan/velbus-scan.js +313 -0
  49. package/nodes/velbus-sensor/velbus-sensor.html +154 -0
  50. package/nodes/velbus-sensor/velbus-sensor.js +259 -0
  51. package/nodes/velbus-sensor-20/velbus-sensor-20.html +158 -0
  52. package/nodes/velbus-sensor-20/velbus-sensor-20.js +314 -0
  53. package/nodes/velbus-thermostat/velbus-thermostat.html +191 -0
  54. package/nodes/velbus-thermostat/velbus-thermostat.js +322 -0
  55. package/package.json +55 -0
  56. package/velbus-2026-repo.bundle +0 -0
@@ -0,0 +1,269 @@
1
+ 'use strict';
2
+
3
+ const { pkt, parsePkt } = require('../../lib/velbus-utils');
4
+ const { PIR_TYPES, PIR_TYPE_IDS } = require('../../lib/pir-types');
5
+
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+ // Helpers
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+
10
+ // Decode a bitmask byte into named channel states using the type's channel list
11
+ function decodeBitmask(byte, channelNames) {
12
+ const result = {};
13
+ for (let i = 0; i < channelNames.length; i++) {
14
+ result[channelNames[i]] = !!(byte & (1 << i));
15
+ }
16
+ return result;
17
+ }
18
+
19
+ // Return array of active channel names from a bitmask byte
20
+ function activeChannels(byte, channelNames) {
21
+ const active = [];
22
+ for (let i = 0; i < channelNames.length; i++) {
23
+ if (byte & (1 << i)) active.push(channelNames[i]);
24
+ }
25
+ return active;
26
+ }
27
+
28
+ // Signed 16-bit / 16 → °C at 0.0625° resolution (same as 0xE6 on glass panel)
29
+ function tempFrom16(hi, lo) {
30
+ const raw = (hi << 8) | lo;
31
+ const signed = raw > 32767 ? raw - 65536 : raw;
32
+ return signed / 16;
33
+ }
34
+
35
+ // ─────────────────────────────────────────────────────────────────────────────
36
+ // Node
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+
39
+ module.exports = function(RED) {
40
+
41
+ function VelbusPirNode(config) {
42
+ RED.nodes.createNode(this, config);
43
+ const node = this;
44
+
45
+ node.bridge = RED.nodes.getNode(config.bridge);
46
+ node.address = typeof config.address === 'number'
47
+ ? config.address // legacy saves stored decimal numbers
48
+ : (parseInt(config.address, 16) || 0); // editor stores hex strings
49
+ node.moduleName = config.moduleName || '';
50
+ node.typeId = config.typeId ? parseInt(config.typeId) : null;
51
+
52
+ if (!node.bridge) {
53
+ node.status({ fill: 'red', shape: 'ring', text: 'no bridge' });
54
+ node.error('velbus-pir: no bridge configured');
55
+ return;
56
+ }
57
+ if (!node.address || node.address < 1 || node.address > 254) {
58
+ node.status({ fill: 'red', shape: 'ring', text: 'invalid address' });
59
+ node.error('velbus-pir: invalid address ' + node.address);
60
+ return;
61
+ }
62
+
63
+ const typeDesc = node.typeId !== null ? (PIR_TYPES[node.typeId] || null) : null;
64
+ const hasTempSensor = typeDesc ? typeDesc.hasTempSensor : false;
65
+ const channelNames = typeDesc ? typeDesc.channels : [
66
+ 'dark', 'light', 'motion1', 'ldMotion1', 'motion2', 'ldMotion2', 'absence',
67
+ ];
68
+
69
+ const _label = () => {
70
+ const name = node.moduleName ||
71
+ (typeDesc ? typeDesc.name : 'PIR') + ' 0x' +
72
+ node.address.toString(16).padStart(2, '0').toUpperCase();
73
+ return name;
74
+ };
75
+
76
+ function setStatus(text, fill, shape) {
77
+ node.status({ fill: fill || 'green', shape: shape || 'dot',
78
+ text: _label() + ' ' + text });
79
+ }
80
+
81
+ // ── Packet handler ────────────────────────────────────────────────────
82
+
83
+ function onPacket(raw) {
84
+ const p = parsePkt(raw);
85
+ if (!p || p.rtr) return;
86
+ if (p.addr !== node.address) return;
87
+
88
+ const { cmd, body } = p;
89
+
90
+ switch (cmd) {
91
+
92
+ // ── 0x00 Channel status (motion/dark/light/absence/temp alarms) ──
93
+ case 0x00: {
94
+ if (body.length < 4) return;
95
+ // body[0]=cmd, body[1]=pressed, body[2]=released, body[3]=longPressed
96
+ const pressed = activeChannels(body[1], channelNames);
97
+ const released = activeChannels(body[2], channelNames);
98
+ const longPressed = activeChannels(body[3], channelNames);
99
+ const on = pressed.length > 0 || longPressed.length > 0;
100
+
101
+ const payload = { type: 'channel', on, pressed, released, longPressed };
102
+ setStatus(pressed.length ? pressed.join(',') + ' on' : 'idle');
103
+ node.send([{ payload }, null]);
104
+ break;
105
+ }
106
+
107
+ // ── 0xED Module status ───────────────────────────────────────────
108
+ case 0xED: {
109
+ if (body.length < 8) return;
110
+ // body[0]=cmd, body[1]=channel bitmask, body[2-3]=light hi/lo,
111
+ // body[4]=locked/test, body[5]=prog disabled, body[6]=alarm&program,
112
+ // body[7]=auto-send interval
113
+ const channels = decodeBitmask(body[1], channelNames);
114
+ const lightRaw = (body[2] << 8) | body[3];
115
+ const testMode = !!(body[4] & 0x80);
116
+ const locked = decodeBitmask(body[4] & 0x7F, channelNames.slice(0, 6));
117
+ const progDisabled = decodeBitmask(body[5], channelNames.slice(0, 6));
118
+
119
+ const progByte = body[6];
120
+ const program = ['none', 'summer', 'winter', 'holiday'][progByte & 0x03];
121
+ const alarm1Active = !!(progByte & 0x04);
122
+ const alarm2Active = !!(progByte & 0x10);
123
+ const sunriseEnabled = !!(progByte & 0x40);
124
+ const sunsetEnabled = !!(progByte & 0x80);
125
+
126
+ const autoSendInterval = body[7];
127
+
128
+ const on = Object.values(channels).some(v => v);
129
+
130
+ const payload = {
131
+ type: 'status',
132
+ on,
133
+ channels,
134
+ lightRaw,
135
+ testMode,
136
+ locked,
137
+ progDisabled,
138
+ program,
139
+ alarms: { alarm1Active, alarm2Active },
140
+ sunrise: sunriseEnabled,
141
+ sunset: sunsetEnabled,
142
+ autoSendInterval,
143
+ };
144
+ node.send([{ payload }, null]);
145
+ break;
146
+ }
147
+
148
+ // ── 0xA9 Light raw value ─────────────────────────────────────────
149
+ case 0xA9: {
150
+ if (body.length < 3) return;
151
+ // body[0]=cmd, body[1]=hi, body[2]=lo
152
+ const raw = (body[1] << 8) | body[2];
153
+ const payload = { type: 'light', on: raw > 0, raw };
154
+ node.send([{ payload }, null]);
155
+ break;
156
+ }
157
+
158
+ // ── 0xE6 Temperature (hasTempSensor types only) ─────────────────
159
+ case 0xE6: {
160
+ if (!hasTempSensor) return;
161
+ if (body.length < 3) return;
162
+ // body[0]=cmd, body[1-2]=current, body[3-4]=min, body[5-6]=max
163
+ const current = tempFrom16(body[1], body[2]);
164
+ const min = body.length >= 5 ? tempFrom16(body[3], body[4]) : null;
165
+ const max = body.length >= 7 ? tempFrom16(body[5], body[6]) : null;
166
+
167
+ const payload = { type: 'temperature', current };
168
+ if (min !== null) payload.min = min;
169
+ if (max !== null) payload.max = max;
170
+ node.send([null, { payload }]);
171
+ break;
172
+ }
173
+
174
+ // ── 0xE8 Temperature sensor settings (compact format) ───────────
175
+ case 0xE8: {
176
+ if (!hasTempSensor) return;
177
+ if (body.length < 8) return;
178
+ // body[0]=cmd, body[1]=calOffset, body[2]=calGain,
179
+ // body[3]=lowAlarm, body[4]=highAlarm, body[5]=zone, body[6]=autoSend
180
+ function signed05(b) { return (b > 127 ? b - 256 : b) * 0.5; }
181
+ const payload = {
182
+ type: 'temp_settings',
183
+ calOffset: signed05(body[1]),
184
+ calGain: body[2],
185
+ lowAlarm: signed05(body[3]),
186
+ highAlarm: signed05(body[4]),
187
+ zone: body[5],
188
+ autoSend: body[6],
189
+ };
190
+ node.send([null, { payload }]);
191
+ break;
192
+ }
193
+
194
+ // ── 0xF0/F1/F2 Sensor name parts ────────────────────────────────
195
+ case 0xF0:
196
+ case 0xF1:
197
+ case 0xF2: {
198
+ if (!hasTempSensor) return;
199
+ // body[0]=cmd, body[1]=sensor bit number, body[2..]=chars
200
+ const part = cmd - 0xF0;
201
+ let text = '';
202
+ for (let i = 2; i < body.length; i++) {
203
+ if (body[i] === 0 || body[i] === 0xFF) break;
204
+ text += String.fromCharCode(body[i]);
205
+ }
206
+ const payload = { type: 'name_part', part, text };
207
+ node.send([null, { payload }]);
208
+ break;
209
+ }
210
+
211
+ default:
212
+ break;
213
+ }
214
+ }
215
+
216
+ node.bridge.register(node.address, onPacket);
217
+
218
+ // ── Input commands ────────────────────────────────────────────────────
219
+
220
+ node.on('input', function(msg) {
221
+ const cmd = msg.payload && msg.payload.cmd;
222
+ if (!cmd) return;
223
+
224
+ // Request module status
225
+ if (cmd === 'get_status') {
226
+ node.bridge.send(pkt(0xFB, node.address, [0xFA, 0x00]));
227
+ return;
228
+ }
229
+
230
+ // Request light value (0xAA), optional auto-send interval
231
+ if (cmd === 'get_light') {
232
+ const interval = parseInt(msg.payload.interval) || 0;
233
+ node.bridge.send(pkt(0xF8, node.address, [0xAA, interval]));
234
+ return;
235
+ }
236
+
237
+ // Request temperature (hasTempSensor only)
238
+ if (cmd === 'get_temp') {
239
+ if (!hasTempSensor) { node.warn('velbus-pir: this module has no temperature sensor'); return; }
240
+ const interval = parseInt(msg.payload.interval) || 0;
241
+ node.bridge.send(pkt(0xF8, node.address, [0xE5, interval]));
242
+ return;
243
+ }
244
+
245
+ // Request temperature settings
246
+ if (cmd === 'get_temp_settings') {
247
+ if (!hasTempSensor) { node.warn('velbus-pir: this module has no temperature sensor'); return; }
248
+ node.bridge.send(pkt(0xF8, node.address, [0xE7, 0x00]));
249
+ return;
250
+ }
251
+
252
+ // Test mode on/off
253
+ if (cmd === 'test_on') { node.bridge.send(pkt(0xF8, node.address, [0xB5, 0x01])); return; }
254
+ if (cmd === 'test_off') { node.bridge.send(pkt(0xF8, node.address, [0xB5, 0x00])); return; }
255
+
256
+ node.warn('velbus-pir: unknown cmd: ' + cmd);
257
+ });
258
+
259
+ // ── Cleanup ───────────────────────────────────────────────────────────
260
+
261
+ node.on('close', function() {
262
+ node.bridge.deregister(node.address, onPacket);
263
+ });
264
+
265
+ setStatus('ready', 'grey', 'dot');
266
+ }
267
+
268
+ RED.nodes.registerType('velbus-pir', VelbusPirNode);
269
+ };
@@ -0,0 +1,186 @@
1
+ <script type="text/javascript">
2
+ (function() {
3
+
4
+ const PIR_TYPES_20 = {
5
+ 0x4D: { name: 'VMBPIR-20', hasTempSensor: false },
6
+ 0x59: { name: 'VMBPIRO-20', hasTempSensor: true },
7
+ };
8
+
9
+ RED.nodes.registerType('velbus-pir-20', {
10
+ category: 'Velbus (inputs)',
11
+ color: '#3A8C8C',
12
+ defaults: {
13
+ name: { value: '' },
14
+ bridge: { value: '', type: 'velbus-bridge', required: true },
15
+ address: { value: '', required: true, validate: function(v) { return /^(0x)?[0-9a-fA-F]{1,2}$/.test(v); } },
16
+ moduleName: { value: '' },
17
+ typeId: { value: '' },
18
+ },
19
+ inputs: 1,
20
+ outputs: 2,
21
+ outputLabels: ['channel / status / light', 'temperature'],
22
+ icon: 'font-awesome/fa-eye',
23
+ paletteLabel: 'pir-20',
24
+
25
+ label: function() {
26
+ const t = this.typeId ? PIR_TYPES_20[parseInt(this.typeId, 16)] : null;
27
+ const n = this.name || this.moduleName || (t ? t.name : 'PIR-20');
28
+ return n + ' 0x' + (parseInt(this.address) || 0).toString(16).padStart(2,'0').toUpperCase();
29
+ },
30
+
31
+ oneditprepare: function() {
32
+ const savedAddr = this.address;
33
+ const savedTypeId = this.typeId ? parseInt(this.typeId, 16) : null;
34
+
35
+ $('#node-input-typeId').prop('readonly', true).css('background', '#f0f0f0');
36
+
37
+ function populateDropdown(bridgeId) {
38
+ if (!bridgeId) return;
39
+ $.getJSON('velbus/scan-results?bridge=' + bridgeId, function(results) {
40
+ const modules = (results && results.modules) ? results.modules : [];
41
+ if (!modules.length) return;
42
+
43
+ const $addr = $('#node-input-address');
44
+ $addr.empty().append($('<option>').val('').text('— select address —'));
45
+
46
+ const pirTypeIds = Object.keys(PIR_TYPES_20).map(k => parseInt(k));
47
+ const pirModules = modules.filter(m => pirTypeIds.includes(parseInt(m.typeId, 16)));
48
+
49
+ if (!pirModules.length) {
50
+ $addr.append($('<option>').val('').text('No V2 PIR modules found on bus'));
51
+ return;
52
+ }
53
+
54
+ pirModules.forEach(function(m) {
55
+ const tid = parseInt(m.typeId, 16);
56
+ const label = m.address + ' — ' + m.module;
57
+ const $opt = $('<option>').val(m.addressDec).text(label).data('typeid', tid);
58
+ if (m.addressDec === parseInt(savedAddr)) $opt.attr('selected', true);
59
+ $addr.append($opt);
60
+ });
61
+
62
+ if (savedAddr) updateHint(parseInt(savedAddr));
63
+ });
64
+ }
65
+
66
+ function updateHint(addrDec) {
67
+ const $opt = $('#node-input-address option[value="' + addrDec + '"]');
68
+ const tid = $opt.data('typeid');
69
+ if (tid && PIR_TYPES_20[tid]) {
70
+ const t = PIR_TYPES_20[tid];
71
+ $('#pir20-type-hint').text(
72
+ t.name + ' · V2' + (t.hasTempSensor ? ' · temperature sensor' : '')
73
+ ).show();
74
+ $('#node-input-typeId').val('0x' + tid.toString(16).padStart(2,'0').toUpperCase());
75
+ } else {
76
+ $('#pir20-type-hint').hide();
77
+ }
78
+ }
79
+
80
+ $('#node-input-bridge').on('change', function() { populateDropdown($(this).val()); });
81
+ $('#node-input-address').on('change', function() { updateHint(parseInt($(this).val())); });
82
+
83
+ // Deferred load — TypedInput sets value async after oneditprepare
84
+
85
+
86
+ $('#node-input-bridge').trigger('change');
87
+
88
+
89
+ setTimeout(function() { $('#node-input-bridge').trigger('change'); }, 150);
90
+ },
91
+
92
+ oneditsave: function() {
93
+ this.address = parseInt($('#node-input-address').val()) || 0;
94
+ const tid = $('#node-input-typeId').val();
95
+ this.typeId = tid || '';
96
+ if (tid) {
97
+ const t = PIR_TYPES_20[parseInt(tid, 16)];
98
+ if (t) this.moduleName = t.name;
99
+ }
100
+ },
101
+ });
102
+
103
+ })();
104
+ </script>
105
+
106
+ <script type="text/html" data-template-name="velbus-pir-20">
107
+ <div class="form-row">
108
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
109
+ <input type="text" id="node-input-name" placeholder="Optional label">
110
+ </div>
111
+ <div class="form-row">
112
+ <label for="node-input-bridge"><i class="fa fa-plug"></i> Bridge</label>
113
+ <input type="text" id="node-input-bridge">
114
+ </div>
115
+ <div class="form-row">
116
+ <label for="node-input-address"><i class="fa fa-map-marker"></i> Address</label>
117
+ <select id="node-input-address" style="width:70%">
118
+ <option value="">— select bridge first —</option>
119
+ </select>
120
+ </div>
121
+ <div class="form-row">
122
+ <label></label>
123
+ <span id="pir20-type-hint" style="color:#888; font-size:0.9em; display:none;"></span>
124
+ </div>
125
+ <div class="form-row" style="display:none">
126
+ <input type="text" id="node-input-typeId">
127
+ <input type="text" id="node-input-moduleName">
128
+ </div>
129
+ </script>
130
+
131
+ <script type="text/html" data-help-name="velbus-pir-20">
132
+ <p style="background:#fff3cd; border-left:4px solid #ffc107; padding:8px 12px; margin-bottom:12px; font-size:0.85em;">
133
+ <strong>&#9888; Testing status:</strong> This node was generated with Claude.ai and is in
134
+ need of extensive field testing before being deployed into a commercial project.
135
+ It is presented "as is" &mdash; any use beyond testing is entirely at your own risk and liability.
136
+ Constructive feedback is welcomed, accompanied by as many examples and debug captures as possible.
137
+ <a href="https://github.com/MDAR/node-red-contrib-velbus-2026/issues" target="_blank">File an issue on GitHub</a>.
138
+ </p>
139
+ <p>
140
+ PIR motion sensor node for Velbus V2 (-20) series modules.
141
+ For original and -10 series use <strong>velbus-pir</strong>.
142
+ </p>
143
+
144
+ <h3>Supported modules</h3>
145
+ <p>VMBPIR-20 (0x4D), VMBPIRO-20 (0x59).</p>
146
+ <p>VMBPIRO-20 includes a temperature sensor — output 2 is active on this type.</p>
147
+ <p>Performs firmware check on startup. Module name auto-retrieved from VelbusLink.</p>
148
+
149
+ <h3>Output 1 — Channel / status / light</h3>
150
+ <p>Channel event (0x00):</p>
151
+ <pre>{
152
+ "type": "channel",
153
+ "on": true,
154
+ "pressed": ["motion1", "dark"],
155
+ "released": [],
156
+ "longPressed": []
157
+ }</pre>
158
+ <p>VMBPIRO-20 channel names also include <code>"lowTempAlarm"</code> and <code>"highTempAlarm"</code>.</p>
159
+ <p>Module status (0xED):</p>
160
+ <pre>{
161
+ "type": "status",
162
+ "on": true,
163
+ "channels": { "dark": false, "light": true, "motion1": true, ... },
164
+ "lightRaw": 1024,
165
+ "testMode": false,
166
+ "locked": { "dark": false, ... },
167
+ "program": "summer",
168
+ "alarms": { "alarm1Active": false, "alarm2Active": false }
169
+ }</pre>
170
+ <p>Light value (0xA9):</p>
171
+ <pre>{ "type": "light", "on": true, "raw": 1024 }</pre>
172
+
173
+ <h3>Output 2 — Temperature (VMBPIRO-20 only)</h3>
174
+ <pre>{ "type": "temperature", "current": 21.5, "min": 18.0, "max": 25.0 }</pre>
175
+
176
+ <h3>Input commands</h3>
177
+ <dl>
178
+ <dt><code>{ "cmd": "get_status" }</code></dt><dd>Request module status.</dd>
179
+ <dt><code>{ "cmd": "get_light", "interval": 30 }</code></dt><dd>Request light value and set auto-send interval (0 = no change).</dd>
180
+ <dt><code>{ "cmd": "get_temp" }</code></dt><dd>Request temperature (VMBPIRO-20 only).</dd>
181
+ <dt><code>{ "cmd": "get_temp_settings" }</code></dt><dd>Request temperature settings.</dd>
182
+ <dt><code>{ "cmd": "lock", "channel": 3, "duration": 3600 }</code></dt><dd>Lock a channel by number (1-7). duration in seconds; omit for permanent.</dd>
183
+ <dt><code>{ "cmd": "unlock", "channel": 3 }</code></dt><dd>Unlock a channel. channel 0xFF for all.</dd>
184
+ <dt><code>{ "cmd": "test_on" }</code> / <code>{ "cmd": "test_off" }</code></dt><dd>Enable/disable test mode (30 minute timeout).</dd>
185
+ </dl>
186
+ </script>