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.
- package/CHANGELOG_FORUM.md +784 -0
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/examples/velbus-basic-relay-dimmer.json +328 -0
- package/index.js +3 -0
- package/lib/blind-types-20.js +26 -0
- package/lib/blind-types-s.js +46 -0
- package/lib/blind-types.js +38 -0
- package/lib/dimmer-types-20.js +120 -0
- package/lib/dimmer-types.js +34 -0
- package/lib/glass-panel-types.js +296 -0
- package/lib/pir-types-20.js +51 -0
- package/lib/pir-types.js +61 -0
- package/lib/relay-types-20.js +43 -0
- package/lib/relay-types.js +39 -0
- package/lib/sensor-types-20.js +29 -0
- package/lib/sensor-types.js +49 -0
- package/lib/velbus-utils.js +80 -0
- package/nodes/velbus-blind/velbus-blind.html +165 -0
- package/nodes/velbus-blind/velbus-blind.js +251 -0
- package/nodes/velbus-blind-20/velbus-blind-20.html +179 -0
- package/nodes/velbus-blind-20/velbus-blind-20.js +300 -0
- package/nodes/velbus-blind-s/velbus-blind-s.html +181 -0
- package/nodes/velbus-blind-s/velbus-blind-s.js +282 -0
- package/nodes/velbus-bridge/velbus-bridge.html +72 -0
- package/nodes/velbus-bridge/velbus-bridge.js +304 -0
- package/nodes/velbus-button/velbus-button.html +172 -0
- package/nodes/velbus-button/velbus-button.js +117 -0
- package/nodes/velbus-clock/velbus-clock.html +198 -0
- package/nodes/velbus-clock/velbus-clock.js +273 -0
- package/nodes/velbus-dimmer/velbus-dimmer.html +174 -0
- package/nodes/velbus-dimmer/velbus-dimmer.js +457 -0
- package/nodes/velbus-dimmer-20/velbus-dimmer-20.html +271 -0
- package/nodes/velbus-dimmer-20/velbus-dimmer-20.js +602 -0
- package/nodes/velbus-glass-panel/velbus-glass-panel.html +337 -0
- package/nodes/velbus-glass-panel/velbus-glass-panel.js +492 -0
- package/nodes/velbus-meteo/velbus-meteo.html +120 -0
- package/nodes/velbus-meteo/velbus-meteo.js +279 -0
- package/nodes/velbus-pir/velbus-pir.html +187 -0
- package/nodes/velbus-pir/velbus-pir.js +269 -0
- package/nodes/velbus-pir-20/velbus-pir-20.html +186 -0
- package/nodes/velbus-pir-20/velbus-pir-20.js +352 -0
- package/nodes/velbus-relay/velbus-relay.html +215 -0
- package/nodes/velbus-relay/velbus-relay.js +404 -0
- package/nodes/velbus-relay-20/velbus-relay-20.html +123 -0
- package/nodes/velbus-relay-20/velbus-relay-20.js +434 -0
- package/nodes/velbus-scan/velbus-scan.html +87 -0
- package/nodes/velbus-scan/velbus-scan.js +313 -0
- package/nodes/velbus-sensor/velbus-sensor.html +154 -0
- package/nodes/velbus-sensor/velbus-sensor.js +259 -0
- package/nodes/velbus-sensor-20/velbus-sensor-20.html +158 -0
- package/nodes/velbus-sensor-20/velbus-sensor-20.js +314 -0
- package/nodes/velbus-thermostat/velbus-thermostat.html +191 -0
- package/nodes/velbus-thermostat/velbus-thermostat.js +322 -0
- package/package.json +55 -0
- package/velbus-2026-repo.bundle +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { pkt, parsePkt } = require('../../lib/velbus-utils');
|
|
4
|
+
|
|
5
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
// Thermostat mode constants
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
const MODE_CMD = {
|
|
9
|
+
comfort: 0xDB,
|
|
10
|
+
day: 0xDC,
|
|
11
|
+
night: 0xDD,
|
|
12
|
+
safe: 0xDE,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const MODE_LABEL = {
|
|
16
|
+
0: 'comfort',
|
|
17
|
+
1: 'day',
|
|
18
|
+
2: 'night',
|
|
19
|
+
3: 'safe',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const THERM_STATUS_LABEL = {
|
|
23
|
+
0: 'disabled',
|
|
24
|
+
1: 'manual',
|
|
25
|
+
2: 'timer',
|
|
26
|
+
3: 'timer_override',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
// Node
|
|
31
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
module.exports = function(RED) {
|
|
33
|
+
|
|
34
|
+
function VelbusThermostatNode(config) {
|
|
35
|
+
RED.nodes.createNode(this, config);
|
|
36
|
+
const node = this;
|
|
37
|
+
|
|
38
|
+
node.bridge = RED.nodes.getNode(config.bridge);
|
|
39
|
+
node.address = typeof config.address === 'number'
|
|
40
|
+
? config.address // legacy saves stored decimal numbers
|
|
41
|
+
: (parseInt(config.address, 16) || 0); // editor stores hex strings
|
|
42
|
+
node.moduleName = config.moduleName || '';
|
|
43
|
+
|
|
44
|
+
if (!node.bridge) {
|
|
45
|
+
node.status({ fill: 'red', shape: 'ring', text: 'no bridge' });
|
|
46
|
+
node.error('velbus-thermostat: no bridge configured');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!node.address || node.address < 1 || node.address > 254) {
|
|
50
|
+
node.status({ fill: 'red', shape: 'ring', text: 'invalid address' });
|
|
51
|
+
node.error('velbus-thermostat: invalid address ' + node.address);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const _label = node.moduleName
|
|
56
|
+
? node.moduleName + ' (0x' + node.address.toString(16).padStart(2, '0').toUpperCase() + ')'
|
|
57
|
+
: '0x' + node.address.toString(16).padStart(2, '0').toUpperCase();
|
|
58
|
+
|
|
59
|
+
// Name retrieval state
|
|
60
|
+
let _nameParts = {};
|
|
61
|
+
let _nameTimer = null;
|
|
62
|
+
|
|
63
|
+
function assembleName() {
|
|
64
|
+
if (_nameTimer) { clearTimeout(_nameTimer); _nameTimer = null; }
|
|
65
|
+
const bytes = [
|
|
66
|
+
...(_nameParts[0] || []),
|
|
67
|
+
...(_nameParts[1] || []),
|
|
68
|
+
...(_nameParts[2] || []),
|
|
69
|
+
].filter(b => b !== 0 && b !== 0xFF);
|
|
70
|
+
const name = String.fromCharCode(...bytes).trim();
|
|
71
|
+
if (name) node.moduleName = name;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function setStatus(text, fill, shape) {
|
|
75
|
+
node.status({ fill: fill || 'green', shape: shape || 'dot', text: _label + ' ' + text });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Packet handler ───────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
function onPacket(raw) {
|
|
81
|
+
const p = parsePkt(raw);
|
|
82
|
+
if (!p || p.rtr) return;
|
|
83
|
+
// Note: no address re-check here. The bridge routes frames to this
|
|
84
|
+
// listener, including subaddress frames mapped to the primary address
|
|
85
|
+
// (e.g. GPO-20 thermostat status from subaddress 0x34).
|
|
86
|
+
|
|
87
|
+
const { cmd, body } = p;
|
|
88
|
+
|
|
89
|
+
switch (cmd) {
|
|
90
|
+
|
|
91
|
+
// ── 0xEA Thermostat status ────────────────────────────────────────
|
|
92
|
+
case 0xEA: {
|
|
93
|
+
if (body.length < 8) return;
|
|
94
|
+
// body[0]=0xEA cmd
|
|
95
|
+
// body[1]=DB2 operating mode
|
|
96
|
+
// body[2]=DB3 program step mode
|
|
97
|
+
// body[3]=DB4 output status (heater/cooler/pump/alarms)
|
|
98
|
+
// body[4]=DB5 current temp (signed byte, resolution 0.5°)
|
|
99
|
+
// body[5]=DB6 current target temp set (signed byte, resolution 0.5°)
|
|
100
|
+
// body[6]=DB7 sleep timer high byte
|
|
101
|
+
// body[7]=DB8 sleep timer low byte
|
|
102
|
+
|
|
103
|
+
const opMode = body[1];
|
|
104
|
+
const modeBits = (opMode >> 4) & 0x07;
|
|
105
|
+
const mode = modeBits === 4 ? 'comfort'
|
|
106
|
+
: modeBits === 2 ? 'day'
|
|
107
|
+
: modeBits === 1 ? 'night'
|
|
108
|
+
: 'safe';
|
|
109
|
+
const heaterMode = !(opMode & 0x80); // bit7: 0=heater, 1=cooler
|
|
110
|
+
// Bits 2-1: control state per GPO-20 protocol ed.3
|
|
111
|
+
const ctlBits = (opMode >> 1) & 0x03;
|
|
112
|
+
const controlState = ctlBits === 0 ? 'run'
|
|
113
|
+
: ctlBits === 1 ? 'manual'
|
|
114
|
+
: ctlBits === 2 ? 'sleep_timer'
|
|
115
|
+
: 'forced_safe_locked';
|
|
116
|
+
const thermostatOn = ctlBits !== 3; // legacy convenience flag
|
|
117
|
+
|
|
118
|
+
const outByte = body[3];
|
|
119
|
+
// Full DATABYTE4 output status per VMBGPO-20 protocol ed.3:
|
|
120
|
+
const heating = !!(outByte & 0x01);
|
|
121
|
+
const boostMode = !!(outByte & 0x02);
|
|
122
|
+
const pump = !!(outByte & 0x04);
|
|
123
|
+
const cooling = !!(outByte & 0x08);
|
|
124
|
+
const tempAlarm1 = !!(outByte & 0x10);
|
|
125
|
+
const tempAlarm2 = !!(outByte & 0x20);
|
|
126
|
+
const tempAlarm3 = !!(outByte & 0x40);
|
|
127
|
+
const tempAlarm4 = !!(outByte & 0x80);
|
|
128
|
+
|
|
129
|
+
const rawCurrent = body[4] > 127 ? body[4] - 256 : body[4];
|
|
130
|
+
const rawTarget = body[5] > 127 ? body[5] - 256 : body[5];
|
|
131
|
+
|
|
132
|
+
const payload = {
|
|
133
|
+
type: 'thermostat',
|
|
134
|
+
sourceAddr: '0x' + p.addr.toString(16).padStart(2, '0').toUpperCase(),
|
|
135
|
+
raw: [...raw].map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '),
|
|
136
|
+
currentTemp: rawCurrent * 0.5,
|
|
137
|
+
targetTemp: rawTarget * 0.5,
|
|
138
|
+
mode,
|
|
139
|
+
heaterMode,
|
|
140
|
+
heating,
|
|
141
|
+
cooling,
|
|
142
|
+
boostMode,
|
|
143
|
+
pump,
|
|
144
|
+
tempAlarm1,
|
|
145
|
+
tempAlarm2,
|
|
146
|
+
tempAlarm3,
|
|
147
|
+
tempAlarm4,
|
|
148
|
+
controlState,
|
|
149
|
+
thermostatOn,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
setStatus(
|
|
153
|
+
(thermostatOn ? 'on' : 'off') + ' · ' +
|
|
154
|
+
mode + ' · ' +
|
|
155
|
+
(rawCurrent * 0.5).toFixed(1) + '°C → ' + (rawTarget * 0.5).toFixed(1) + '°C'
|
|
156
|
+
);
|
|
157
|
+
node.send([{ payload }, null]);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── 0xE6 Current temperature ─────────────────────────────────────
|
|
162
|
+
case 0xE6: {
|
|
163
|
+
if (body.length < 3) return;
|
|
164
|
+
// body[0]=0xE6 cmd, body[1-2]=DB2-3 current (16-bit, /16 = °C at 0.0625° res)
|
|
165
|
+
// body[3-4]=DB4-5 min, body[5-6]=DB6-7 max (optional)
|
|
166
|
+
// 16-bit signed, °C × 512 (0.0625° resolution, low 5 bits unused)
|
|
167
|
+
const t16 = (hi, lo) => { const r = (hi << 8) | lo; return (r > 32767 ? r - 65536 : r) / 512; };
|
|
168
|
+
const current = t16(body[1], body[2]);
|
|
169
|
+
const min = body.length >= 5 ? t16(body[3], body[4]) : null;
|
|
170
|
+
const max = body.length >= 7 ? t16(body[5], body[6]) : null;
|
|
171
|
+
|
|
172
|
+
const payload = { type: 'temperature', current };
|
|
173
|
+
if (min !== null) payload.min = min;
|
|
174
|
+
if (max !== null) payload.max = max;
|
|
175
|
+
|
|
176
|
+
node.send([null, { payload }]);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── 0xE8 Temperature settings part 1 (heating presets) ──────────
|
|
181
|
+
case 0xE8: {
|
|
182
|
+
if (body.length < 9) return;
|
|
183
|
+
// body[0]=0xE8 cmd, body[1]=DB2 current set, body[2]=DB3 comfort heat,
|
|
184
|
+
// body[3]=DB4 day heat, body[4]=DB5 night heat, body[5]=DB6 safe heat,
|
|
185
|
+
// body[6]=DB7 boost diff, body[7]=DB8 hysteresis
|
|
186
|
+
// All signed bytes, resolution 0.5°
|
|
187
|
+
function signed05(b) { return (b > 127 ? b - 256 : b) * 0.5; }
|
|
188
|
+
const payload = {
|
|
189
|
+
type: 'temp_settings',
|
|
190
|
+
current: signed05(body[1]),
|
|
191
|
+
comfort: signed05(body[2]),
|
|
192
|
+
day: signed05(body[3]),
|
|
193
|
+
night: signed05(body[4]),
|
|
194
|
+
safe: signed05(body[5]),
|
|
195
|
+
};
|
|
196
|
+
node.send([{ payload }, null]);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── 0xED Module status (thermostat programme field) ──────────────
|
|
201
|
+
case 0xED: {
|
|
202
|
+
if (body.length < 7) return;
|
|
203
|
+
// body[0]=0xED cmd, body[6]=DB7 alarm & program selection
|
|
204
|
+
const progByte = body[6];
|
|
205
|
+
const thermostatProgram = THERM_STATUS_LABEL[progByte & 0x03] || 'unknown';
|
|
206
|
+
const alarm1Active = !!(progByte & 0x04);
|
|
207
|
+
const alarm2Active = !!(progByte & 0x08);
|
|
208
|
+
|
|
209
|
+
const payload = {
|
|
210
|
+
type: 'programme',
|
|
211
|
+
thermostatProgram,
|
|
212
|
+
alarms: { alarm1Active, alarm2Active },
|
|
213
|
+
};
|
|
214
|
+
node.send([{ payload }, null]);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── 0xF0/F1/F2 Name part response ─────────────────────────────────
|
|
219
|
+
case 0xF0:
|
|
220
|
+
case 0xF1:
|
|
221
|
+
case 0xF2: {
|
|
222
|
+
// body[0]=cmd, body[1]=DB2 channel number, body[2..]=DB3.. chars
|
|
223
|
+
const part = cmd - 0xF0;
|
|
224
|
+
_nameParts[part] = Array.from(body).slice(2);
|
|
225
|
+
if (part === 2) assembleName();
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
default: {
|
|
230
|
+
// Unhandled command — emit raw on output 2 so subaddress channel
|
|
231
|
+
// events (0x00 push-button status from the sensor sub carrying
|
|
232
|
+
// heater/boost/pump/cooler/alarm states) are visible for analysis.
|
|
233
|
+
const payload = {
|
|
234
|
+
type: 'raw',
|
|
235
|
+
sourceAddr: '0x' + p.addr.toString(16).padStart(2, '0').toUpperCase(),
|
|
236
|
+
cmd: '0x' + cmd.toString(16).padStart(2, '0').toUpperCase(),
|
|
237
|
+
raw: [...raw].map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '),
|
|
238
|
+
};
|
|
239
|
+
node.send([null, { payload }]);
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
node.bridge.register(node.address, onPacket);
|
|
246
|
+
|
|
247
|
+
// Auto-retrieve module name on startup
|
|
248
|
+
setTimeout(function() {
|
|
249
|
+
node.bridge.send(pkt(0xF8, node.address, [0xEF]));
|
|
250
|
+
_nameTimer = setTimeout(assembleName, 2000);
|
|
251
|
+
}, 500);
|
|
252
|
+
|
|
253
|
+
// ── Input command handler ────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
node.on('input', function(msg) {
|
|
256
|
+
const cmd = msg.payload && msg.payload.cmd;
|
|
257
|
+
if (!cmd) return;
|
|
258
|
+
|
|
259
|
+
// ── Mode switch ────────────────────────────────────────────────────
|
|
260
|
+
if (MODE_CMD[cmd] !== undefined) {
|
|
261
|
+
const sleep = (msg.payload.sleepTime !== undefined)
|
|
262
|
+
? parseInt(msg.payload.sleepTime) : 0;
|
|
263
|
+
const sleepHi = (sleep >> 8) & 0xFF;
|
|
264
|
+
const sleepLo = sleep & 0xFF;
|
|
265
|
+
node.bridge.send(pkt(0xF8, node.address, [MODE_CMD[cmd], sleepHi, sleepLo]));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ── Set target temperature ─────────────────────────────────────────
|
|
270
|
+
if (cmd === 'set_temp') {
|
|
271
|
+
const pointer = (msg.payload.pointer !== undefined)
|
|
272
|
+
? parseInt(msg.payload.pointer) : 0;
|
|
273
|
+
const tempRaw = Math.round((parseFloat(msg.payload.temp) || 0) * 100);
|
|
274
|
+
const tempHi = (tempRaw >> 8) & 0xFF;
|
|
275
|
+
const tempLo = tempRaw & 0xFF;
|
|
276
|
+
node.bridge.send(pkt(0xF8, node.address, [0xE4, pointer, tempHi, tempLo]));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ── Request thermostat status ──────────────────────────────────────
|
|
281
|
+
if (cmd === 'get_thermostat') {
|
|
282
|
+
node.bridge.send(pkt(0xF8, node.address, [0xE7]));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ── Request temperature preset settings ────────────────────────────
|
|
287
|
+
if (cmd === 'get_settings') {
|
|
288
|
+
node.bridge.send(pkt(0xF8, node.address, [0xE5]));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ── Request module status (thermostat programme field) ─────────────
|
|
293
|
+
if (cmd === 'get_status') {
|
|
294
|
+
node.bridge.send(pkt(0xFB, node.address, [0xFA]));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── Heat / cool mode ───────────────────────────────────────────────
|
|
299
|
+
if (cmd === 'heat_mode') {
|
|
300
|
+
node.bridge.send(pkt(0xF8, node.address, [0xE0]));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (cmd === 'cool_mode') {
|
|
304
|
+
node.bridge.send(pkt(0xF8, node.address, [0xDF]));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
node.warn('velbus-thermostat: unknown cmd: ' + cmd);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// ── Cleanup ──────────────────────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
node.on('close', function() {
|
|
314
|
+
if (_nameTimer) { clearTimeout(_nameTimer); _nameTimer = null; }
|
|
315
|
+
node.bridge.deregister(node.address, onPacket);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
setStatus('ready', 'grey', 'dot');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
RED.nodes.registerType('velbus-thermostat', VelbusThermostatNode);
|
|
322
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-red-contrib-velbus-2026",
|
|
3
|
+
"version": "0.8.1",
|
|
4
|
+
"description": "Node-RED palette for Velbus building automation \u2014 relay, dimmer, glass panel, PIR, blind, sensor, meteo, clock nodes. Original and V2 (-20) series.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"node-red",
|
|
7
|
+
"velbus",
|
|
8
|
+
"building-automation",
|
|
9
|
+
"home-automation",
|
|
10
|
+
"smart-home",
|
|
11
|
+
"relay",
|
|
12
|
+
"dimmer"
|
|
13
|
+
],
|
|
14
|
+
"node-red": {
|
|
15
|
+
"version": ">=2.0.0",
|
|
16
|
+
"nodes": {
|
|
17
|
+
"velbus-bridge": "nodes/velbus-bridge/velbus-bridge.js",
|
|
18
|
+
"velbus-scan": "nodes/velbus-scan/velbus-scan.js",
|
|
19
|
+
"velbus-relay": "nodes/velbus-relay/velbus-relay.js",
|
|
20
|
+
"velbus-relay-20": "nodes/velbus-relay-20/velbus-relay-20.js",
|
|
21
|
+
"velbus-dimmer": "nodes/velbus-dimmer/velbus-dimmer.js",
|
|
22
|
+
"velbus-dimmer-20": "nodes/velbus-dimmer-20/velbus-dimmer-20.js",
|
|
23
|
+
"velbus-glass-panel": "nodes/velbus-glass-panel/velbus-glass-panel.js",
|
|
24
|
+
"velbus-thermostat": "nodes/velbus-thermostat/velbus-thermostat.js",
|
|
25
|
+
"velbus-button": "nodes/velbus-button/velbus-button.js",
|
|
26
|
+
"velbus-pir": "nodes/velbus-pir/velbus-pir.js",
|
|
27
|
+
"velbus-pir-20": "nodes/velbus-pir-20/velbus-pir-20.js",
|
|
28
|
+
"velbus-meteo": "nodes/velbus-meteo/velbus-meteo.js",
|
|
29
|
+
"velbus-sensor": "nodes/velbus-sensor/velbus-sensor.js",
|
|
30
|
+
"velbus-sensor-20": "nodes/velbus-sensor-20/velbus-sensor-20.js",
|
|
31
|
+
"velbus-blind": "nodes/velbus-blind/velbus-blind.js",
|
|
32
|
+
"velbus-blind-s": "nodes/velbus-blind-s/velbus-blind-s.js",
|
|
33
|
+
"velbus-blind-20": "nodes/velbus-blind-20/velbus-blind-20.js",
|
|
34
|
+
"velbus-clock": "nodes/velbus-clock/velbus-clock.js"
|
|
35
|
+
},
|
|
36
|
+
"examples": "examples"
|
|
37
|
+
},
|
|
38
|
+
"main": "index.js",
|
|
39
|
+
"scripts": {
|
|
40
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
41
|
+
},
|
|
42
|
+
"author": "Stuart Hanlon <info@mdar.co.uk> (https://mdar.co.uk)",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=14.0.0"
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/MDAR/node-red-contrib-velbus-2026.git"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/MDAR/node-red-contrib-velbus-2026/issues"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/MDAR/node-red-contrib-velbus-2026#readme"
|
|
55
|
+
}
|
|
Binary file
|