UNPKG

smart-bus

Version:

Node.js implementation of HDL SmartBus protocol

2,082 lines (1,624 loc) 47.6 kB
var word = { parse: function(buffer, offset) { return buffer.readUInt8(offset) * 256 + buffer.readUInt8(offset + 1); }, encode: function(buffer, value, offset) { buffer.writeUInt8(Math.floor(value / 256), offset); buffer.writeUInt8(value % 256, offset + 1); } }; var status = { parse: function(buffer, offset) { switch (buffer.readUInt8(offset)) { case 0xF8: return true; case 0xF5: return false; } }, encode: function(buffer, value, offset) { buffer.writeUInt8(value ? 0xF8 : 0xF5, offset); } }; var date = { parse: function(buffer, offset) { var year = buffer.readUInt8(offset++); var month = buffer.readUInt8(offset++); return new Date(2000 + year, month - 1, buffer.readUInt8(offset++), buffer.readUInt8(offset++), buffer.readUInt8(offset++), buffer.readUInt8(offset++)); }, encode: function(buffer, date, offset) { buffer.writeUInt8(date.getFullYear() - 2000, offset++); buffer.writeUInt8(date.getMonth() + 1, offset++); buffer.writeUInt8(date.getDate(), offset++); buffer.writeUInt8(date.getHours(), offset++); buffer.writeUInt8(date.getMinutes(), offset++); buffer.writeUInt8(date.getSeconds(), offset++); } }; var AC = { modes: ['cooling', 'heating', 'fan', 'auto', 'dry'], speeds: ['auto', 'high', 'medium', 'low'], parse: function(buffer) { var mode = buffer.readUInt8(7); var temperature = buffer.readUInt8(11); var swing = buffer.readUInt8(12); return { ac: buffer.readUInt8(0), temperature: { type: buffer.readUInt8(1) ? 'F' : 'C', current: buffer.readUInt8(2), cooling: buffer.readUInt8(3), heating: buffer.readUInt8(4), auto: buffer.readUInt8(5), dry: buffer.readUInt8(6) }, current: { mode: AC.modes[mode >> 4], speed: AC.speeds[mode & 15] }, status: Boolean(buffer.readUInt8(8)), setup: { mode: AC.modes[buffer.readUInt8(9)], speed: AC.speeds[buffer.readUInt8(10)], temperature: temperature }, swing: { enabled: Boolean(swing >> 4), active: Boolean(swing & 15) } }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(13); var temperature = data.temperature || {}; var current = data.current || {}; var setup = data.setup || {}; var swing = data.swing || {}; var modes = AC.modes; var speeds = AC.speeds; buffer.writeUInt8(data.ac, 0); buffer.writeUInt8(temperature.type === 'F' ? 1 : 0, 1); buffer.writeUInt8(temperature.current, 2); buffer.writeUInt8(temperature.cooling, 3); buffer.writeUInt8(temperature.heating, 4); buffer.writeUInt8(temperature.auto, 5); buffer.writeUInt8(temperature.dry, 6); buffer.writeUInt8( modes.indexOf(current.mode) << 4 | speeds.indexOf(current.speed), 7); buffer.writeUInt8(data.status ? 1 : 0, 8); buffer.writeUInt8(modes.indexOf(setup.mode), 9); buffer.writeUInt8(speeds.indexOf(setup.speed), 10); buffer.writeUInt8(setup.temperature, 11); buffer.writeUInt8( ((swing.enabled ? 1 : 0) << 4) | (swing.active ? 1 : 0), 12); return buffer; } }; /* Commands as decribed in "Operation Code of HDL Buspro v1.111.pdf" from http://hdlautomation.com All of these commands was tested on the real hdl installation */ module.exports = { /* 4.1 Scene */ // 4.1.1 Scene Control // FIXME: copy-paste from 0x0000 0x0002: { parse: function(buffer) { return { area: buffer.readUInt8(0), scene: buffer.readUInt8(1) }; }, encode: function(data) { return Buffer.from([data.area, data.scene]); }, response: 0x0003 }, // 4.1.2 Scene Control Response 0x0003: { parse: function(buffer) { var data = { area: buffer.readUInt8(0), scene: buffer.readUInt8(1), channels: new Array(buffer.readUInt8(2)) }; var channel = 0; var channels = data.channels; for (var i = 3, length = buffer.length; i < length; i++) { var byte = buffer.readUInt8(i); for (var n = 0; n < 8 && channel < channels.length; n++) channels[channel++] = { number: channel, status: !!(byte & (1 << n)) }; } return data; }, encode: function(data) { var channels = data.channels || []; var length = channels.length; var bytes = Math.ceil(length / 8); var buffer = Buffer.allocUnsafe(3 + bytes); buffer.writeUInt8(data.area, 0); buffer.writeUInt8(data.scene, 1); buffer.writeUInt8(length, 2); var channel = 0; for (var i = 0; i < bytes; i++) { var byte = 0; for (var n = 0; n < 8 && channel < length; n++) if (channels[channel++].status) byte |= 1 << n; buffer.writeUInt8(byte, i + 3); } return buffer; } }, // 4.1.3 Read Status of Scene 0x000C: { parse: function(buffer) { return { area: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.area]); }, response: 0x000D }, // 4.1.4 Response Read Status of Scene 0x000D: { parse: function(buffer) { return { area: buffer.readUInt8(0), scene: buffer.readUInt8(1) }; }, encode: function(data) { return Buffer.from([data.area, data.scene]); } }, // 4.1.5 Broadcast Status of Scene // Documentation is wrong 0xEFFF: { parse: function(buffer) { var areas = new Array(buffer.readUInt8(0)); var channels = new Array(buffer.readUInt8(areas.length + 1)); var i; for (i = 0; i < areas.length; i++) areas[i] = { number: i + 1, scene: buffer.readUInt8(i + 1) }; var channel = 0; for (i = i + 2; i < buffer.length; i++) { var byte = buffer.readUInt8(i); for (var n = 0; n < 8 && channel < channels.length; n++) channels[channel++] = { number: channel, status: !!(byte & (1 << n)) }; } var data = { areas: areas, channels: channels }; return data; }, encode: function(data) { var areas = data.areas || []; var channels = data.channels || []; var length = channels.length; var bytes = Math.ceil(length / 8); var buffer = Buffer.allocUnsafe(2 + areas.length + bytes); buffer.writeUInt8(areas.length, 0); var i; for (i = 0; i < areas.length; i++) buffer.writeUInt8(areas[i].scene, i + 1); buffer.writeUInt8(length, ++i); var channel = 0; for (var j = 0; j < bytes; j++) { var byte = 0; for (var n = 0; n < 8 && channel < length; n++) if (channels[channel++].status) byte |= 1 << n; buffer.writeUInt8(byte, i + 1 + j); } return buffer; } }, // 4.1.6 Read Area Information 0x0004: { response: 0x0005 }, // 4.1.7 Response Read Area Information 0x0005: { parse: function(buffer) { var data = { device: { type: word.parse(buffer, 0), subnet: buffer.readUInt8(2), id: buffer.readUInt8(3) }, // Total areas count // FIXME: undocumented areas: buffer.readUInt8(4), channels: [] }; var channels = data.channels; for (var i = 5, length = buffer.length; i < length; i++) channels.push({ number: channels.length + 1, area: buffer.readUInt8(i) }); return data; }, encode: function(data) { var device = data.device; var channels = data.channels; var length = channels.length; var buffer = Buffer.allocUnsafe(5 + length); word.encode(buffer, device.type, 0); buffer.writeUInt8(device.subnet, 2); buffer.writeUInt8(device.id, 3); var areas = Math.max.apply(Math, channels.map(function(channel) { return channel.area; })); // Total areas count // FIXME: undocumented buffer.writeUInt8(areas, 4); for (var i = 0; i < length; i++) buffer.writeUInt8(channels[i].area, i + 5); return buffer; } }, // 4.1.8 Read Scene Information 0x0000: { parse: function(buffer) { return { area: buffer.readUInt8(0), scene: buffer.readUInt8(1) }; }, encode: function(data) { return Buffer.from([data.area, data.scene]); }, response: 0x0001 }, // 4.1.9 Read Scene Information Response 0x0001: { parse: function(buffer) { var data = { area: buffer.readUInt8(0), scene: buffer.readUInt8(1), time: word.parse(buffer, 2), channels: [] }; var channels = data.channels; for (var i = 4, length = buffer.length; i < length; i++) channels.push({ number: i - 3, level: buffer.readUInt8(i) }); return data; }, encode: function(data) { var channels = data.channels || []; var length = channels.length; var buffer = Buffer.allocUnsafe(4 + length); buffer.writeUInt8(data.area, 0); buffer.writeUInt8(data.scene, 1); word.encode(buffer, data.time, 2); for (var i = 0; i < length; i++) buffer.writeUInt8(channels[i].level, i + 4); return buffer; } }, // 4.1.10 Modify Scene Information // Documentation is wrong 0x0008: { response: 0x0009 }, // 4.1.11 Response Modify Scene Information 0x0009: { parse: function(buffer) { return { success: status.parse(buffer, 0) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(1); status.encode(buffer, data.success, 0); return buffer; } }, /* 4.2 Sequence */ // 4.2.1 Sequence Control 0x001A: { parse: function(buffer) { return { area: buffer.readUInt8(0), sequence: buffer.readUInt8(1) }; }, encode: function(data) { return Buffer.from([data.area, data.sequence]); }, response: 0x001B }, // 4.2.2 Response Sequence Control 0x001B: { parse: function(buffer) { return { area: buffer.readUInt8(0), sequence: buffer.readUInt8(1) }; }, encode: function(data) { return Buffer.from([data.area, data.sequence]); } }, // 4.2.3 Read Status of Sequence 0xE014: { parse: function(buffer) { return { area: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.area]); }, response: 0xE015 }, // 4.2.4 Response Read Status of Sequence 0xE015: { parse: function(buffer) { return { area: buffer.readUInt8(0), sequence: buffer.readUInt8(1) }; }, encode: function(data) { return Buffer.from([data.area, data.sequence]); } }, // 4.2.5 Broadcast Status of Sequence 0xF036: { parse: function(buffer) { var length = buffer.length; var data = { areas: new Array(length) }; for (var i = 0; i < length; i++) data.areas[i] = { number: i + 1, sequence: buffer.readUInt8(i) }; return data; }, encode: function(data) { var areas = data.areas || []; var length = areas.length; var buffer = Buffer.allocUnsafe(length); for (var i = 0; i < length; i++) buffer.writeUInt8(areas[i].sequence, i); return buffer; } }, /* 4.3 Channels */ // 4.3.1 Single Channel Control 0x0031: { parse: function(buffer) { return { channel: buffer.readUInt8(0), level: buffer.readUInt8(1), time: word.parse(buffer, 2), }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(4); buffer.writeUInt8(data.channel, 0); buffer.writeUInt8(data.level, 1); word.encode(buffer, data.time, 2); return buffer; } }, // 4.3.2 Response Single Channel Control 0x0032: { parse: function(buffer) { return { channel: buffer.readUInt8(0), success: status.parse(buffer, 1), level: buffer.readUInt8(2) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(3); buffer.writeUInt8(data.channel, 0); status.encode(buffer, data.success, 1); buffer.writeUInt8(data.level, 2); return buffer; } }, // 4.3.3 Read Status of Channels 0x0033: { response: 0x0034 }, // 4.3.4 Response Read Status of Channels 0x0034: { parse: function(buffer) { var length = buffer.readUInt8(0); var data = { channels: new Array(length) }; for (var i = 0; i < length; i++) data.channels[i] = { number: i + 1, level: buffer.readUInt8(i + 1) }; return data; }, encode: function(data) { var channels = data.channels || []; var length = channels.length; var buffer = Buffer.allocUnsafe(length + 1); buffer.writeUInt8(length, 0); for (var i = 0; i < length; i++) buffer.writeUInt8(channels[i].level, i + 1); return buffer; } }, // 4.3.5 Read Current Level of Channels 0x0038: { response: 0x0039 }, // 4.3.6 Response Read Current Level of Channels // FIXME: same as 0x0034 0x0039: { parse: function(buffer) { var length = buffer.readUInt8(0); var data = { channels: new Array(length) }; for (var i = 0; i < length; i++) data.channels[i] = { number: i + 1, level: buffer.readUInt8(i + 1) }; return data; }, encode: function(data) { var channels = data.channels || []; var length = channels.length; var buffer = Buffer.allocUnsafe(length + 1); buffer.writeUInt8(length, 0); for (var i = 0; i < length; i++) buffer.writeUInt8(channels[i].level, i + 1); return buffer; } }, /* 5. Logic */ // 5.1.1 Logic Control 0xF116: { parse: function(buffer) { return { block: buffer.readUInt8(0), status: Boolean(buffer.readUInt8(1)) }; }, encode: function(data) { return Buffer.from([data.block, data.status ? 1 : 0]); } }, // 5.1.2 Response Logic Control // FIXME: same as 0xF116 0xF117: { parse: function(buffer) { return { block: buffer.readUInt8(0), status: Boolean(buffer.readUInt8(1)) }; }, encode: function(data) { return Buffer.from([data.block, data.status ? 1 : 0]); } }, // 5.1.3 Read Status of Logic Control 0xF112: { parse: function(buffer) { return { block: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.block]); } }, // 5.1.4 Response Read Status of Logic Control // FIXME: same as 0xF116 // FIXME: documentation is wrong 0xF113: { parse: function(buffer) { return { block: buffer.readUInt8(0), status: Boolean(buffer.readUInt8(1)) }; }, encode: function(data) { return Buffer.from([data.block, data.status ? 1 : 0]); } }, // 5.1.5 Broadcast Status of Status of Logic Control // FIXME: same as 0xF116 0xF12F: { parse: function(buffer) { return { block: buffer.readUInt8(0), status: Boolean(buffer.readUInt8(1)) }; }, encode: function(data) { return Buffer.from([data.block, data.status ? 1 : 0]); } }, // 5.1.6 Read System Date and Time 0xDA00: { response: 0xDA01 }, // 5.1.7 Response Read System Date and Time 0xDA01: { parse: function(buffer) { return { success: status.parse(buffer, 0), date: date.parse(buffer, 1) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(8); status.encode(buffer, data.success, 0); date.encode(buffer, data.date, 1); buffer.writeUInt8(data.date.getDay(), 7); return buffer; } }, // 5.1.8 Modify Read System Date and Time 0xDA02: { parse: function(buffer) { return { date: date.parse(buffer, 0) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(7); date.encode(buffer, data.date, 0); buffer.writeUInt8(data.date.getDay(), 6); return buffer; } }, // 5.1.9 Response Modify Read System Date and Time 0xDA03: { parse: function(buffer) { return { success: status.parse(buffer, 0) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(1); status.encode(buffer, data.success, 0); return buffer; } }, // 5.1.10 Broadcast System Date and Time (Every Minute) 0xDA44: { parse: function(buffer) { return { date: date.parse(buffer, 0) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(6); date.encode(buffer, data.date, 0); return buffer; } }, /* 6. Universal Switch */ // 6.1.1 UV Switch Control 0xE01C: { parse: function(data) { return { switch: data.readUInt8(0), status: Boolean(data.readUInt8(1)) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(2); buffer.writeUInt8(data.switch, 0); buffer.writeUInt8(data.status ? 255 : 0, 1); return buffer; }, response: 0xE01D }, // 6.1.2 Response UV Switch Control 0xE01D: { parse: function(data) { return { switch: data.readUInt8(0), status: Boolean(data.readUInt8(1)) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(2); buffer.writeUInt8(data.switch, 0); buffer.writeUInt8(data.status ? 1 : 0, 1); return buffer; } }, // 6.1.3 Read Status of UV Switch 0xE018: { parse: function(buffer) { return { switch: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.switch]); }, response: 0xE019 }, // 6.1.4 Response Read Status of UV Switch // FIXME: same as 0xE01D 0xE019: { parse: function(buffer) { return { switch: buffer.readUInt8(0), status: Boolean(buffer.readUInt8(1)) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(2); buffer.writeUInt8(data.switch, 0); buffer.writeUInt8(data.status ? 1 : 0, 1); return buffer; } }, // 6.1.5 Broadcast Status of Status of UV Switches 0xE017: { parse: function(buffer) { var length = buffer.readUInt8(0); var data = { switches: new Array(length) }; for (var i = 0; i < length; i++) data.switches[i] = { number: i + 1, status: Boolean(buffer.readUInt8(i + 1)) }; return data; }, encode: function(data) { var switches = data.switches || []; var length = switches.length; var buffer = Buffer.allocUnsafe(length + 1); buffer.writeUInt8(length, 0); for (var i = 0; i < length; i++) buffer.writeUInt8(switches[i].status ? 1 : 0, i + 1); return buffer; } }, /* 7. Curtain Switch */ // 7.1.1 Curtain Switch Control 0xE3E0: { parse: function(buffer) { return { curtain: buffer.readUInt8(0), status: buffer.readUInt8(1) }; }, encode: function(data) { return Buffer.from([data.curtain, data.status]); }, response: 0xE3E1 }, // 7.1.2 Response Curtain Switch Control // FIXME: same as 0xE3E0 0xE3E1: { parse: function(buffer) { return { curtain: buffer.readUInt8(0), status: buffer.readUInt8(1) }; }, encode: function(data) { return Buffer.from([data.curtain, data.status]); } }, // 7.1.3 Read Status of Curtain Switch 0xE3E2: { parse: function(buffer) { return { curtain: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.curtain]); }, response: 0xE3E3 }, // 7.1.4 Response Read Status of Curtain Switch // FIXME: same as 0xE3E1 0xE3E3: { parse: function(buffer) { var data = { curtain: buffer.readUInt8(0), status: buffer.readUInt8(1) }; // Duration field (4 bytes total) - in deciseconds (0.1 second units) if (buffer.length >= 4) { data.duration = buffer.readUInt16BE(2) / 10; } return data; }, encode: function(data) { // Support both 2-byte (legacy) and 4-byte (with duration) formats if (data.duration !== undefined) { var buffer = Buffer.allocUnsafe(4); buffer.writeUInt8(data.curtain, 0); buffer.writeUInt8(data.status, 1); var duration = data.duration * 10; buffer.writeUInt16BE(duration, 2); return buffer; } else { return Buffer.from([data.curtain, data.status]); } } }, // 7.1.5 Broadcast Status of Status of Curtain Switches 0xE3E4: { parse: function(buffer) { var length = buffer.length; var size = ~~(length / 2); var data = { curtains: new Array(size) }; for (var i = 0; i < size; i++) data.curtains[i] = { number: i + 1, level: buffer.readUInt8(i), status: buffer.readUInt8(size + i) }; return data; }, encode: function(data) { var curtains = data.curtains || []; var length = curtains.length; var buffer = Buffer.allocUnsafe(length * 2); for (var i = 0; i < length; i++) { var curtain = curtains[i]; buffer.writeUInt8(curtain.level, i); buffer.writeUInt8(curtain.status, length + i); } return buffer; } }, // 7.1.6 Get Curtain Duration Request 0xE800: { parse: function(buffer) { return { channel: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.channel]); }, response: 0xE801 }, // 7.1.7 Get Curtain Duration Response 0xE801: { parse: function(buffer) { return { channel: buffer.readUInt8(0), duration: buffer.readUInt16BE(2) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(4); buffer.writeUInt8(data.channel, 0); buffer.writeUInt8(0, 1); // Reserved buffer.writeUInt16BE(data.duration || 0, 2); return buffer; } }, /* 8. GPRS Control */ // 8.1.1 GPRS Control // 0xE3D4 // 8.1.2 Response GPRS Control // 0xE3D5 /* 9. Panel Control */ // 9.1.1 Panel Control 0xE3D8: { parse: function(buffer) { return { key: buffer.readUInt8(0), value: buffer.readUInt8(1) }; }, encode: function(data) { return Buffer.from([data.key, data.value]); }, response: 0xE3D9 }, // 9.1.2 Response Panel Control // FIXME: same as 0xE3D8 0xE3D9: { parse: function(buffer) { return { key: buffer.readUInt8(0), value: buffer.readUInt8(1) }; }, encode: function(data) { return Buffer.from([data.key, data.value]); } }, // 9.1.3 Read Status of Panel Control 0xE3DA: { parse: function(buffer) { return { key: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.key]); }, response: 0xE3DB }, // 9.1.4 Response Read Status of Panel Control // FIXME: same as 0xE3D8 0xE3DB: { parse: function(buffer) { return { key: buffer.readUInt8(0), value: buffer.readUInt8(1) }; }, encode: function(data) { return Buffer.from([data.key, data.value]); } }, /* 10. AC Control */ // 10.1.1 Read AC Status // 0x1938 // 10.1.2 Response Read AC Status // 0x1939 // 10.1.3 Control AC Status // Documetation is wrong 0x193A: { parse: AC.parse, encode: AC.encode, response: 0x193B }, // 10.1.4 Response Control AC Status 0x193B: { parse: AC.parse, encode: AC.encode }, /* 11.1 Floor Heating Control from DLP */ // 11.1.1 Read Floor Heating Status 0x1944: { response: 0x1945 }, // 11.1.2 Response Read Floor Heating Status 0x1945: { parse: function(buffer) { var length = buffer.length; var data = { status: Boolean(buffer.readUInt8(2)), mode: buffer.readUInt8(3), temperature: { type: buffer.readUInt8(0), current: buffer.readInt8(1), normal: buffer.readUInt8(4), day: buffer.readUInt8(5), night: buffer.readUInt8(6), away: buffer.readUInt8(7) } }; if (length >= 9) data.timer = buffer.readUInt8(8); return data; }, encode: function(data) { var timerPresent = (data.timer === undefined) ? 0 : 1; var buffer = Buffer.allocUnsafe(8 + timerPresent); var temperature = data.temperature; buffer.writeUInt8(temperature.type, 0); buffer.writeInt8(temperature.current, 1); buffer.writeUInt8(data.status ? 1 : 0, 2); buffer.writeUInt8(data.mode, 3); buffer.writeUInt8(temperature.normal, 4); buffer.writeUInt8(temperature.day, 5); buffer.writeUInt8(temperature.night, 6); buffer.writeUInt8(temperature.away, 7); if (timerPresent) buffer.writeUInt8(data.timer, 8); return buffer; } }, // 11.1.3 Control Floor Heating Status 0x1946: { parse: function(buffer) { return { status: Boolean(buffer.readUInt8(1)), mode: buffer.readUInt8(2), temperature: { type: buffer.readUInt8(0), normal: buffer.readUInt8(3), day: buffer.readUInt8(4), night: buffer.readUInt8(5), away: buffer.readUInt8(6) } }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(7); var temperature = data.temperature; buffer.writeInt8(temperature.type, 0); buffer.writeUInt8(data.status ? 1 : 0, 1); buffer.writeUInt8(data.mode, 2); buffer.writeUInt8(temperature.normal, 3); buffer.writeUInt8(temperature.day, 4); buffer.writeUInt8(temperature.night, 5); buffer.writeUInt8(temperature.away, 6); return buffer; }, response: 0x1947 }, // 11.1.4 Response Control Floor Heating Status 0x1947: { parse: function(buffer) { return { success: status.parse(buffer, 0), status: Boolean(buffer.readUInt8(2)), mode: buffer.readUInt8(3), temperature: { type: buffer.readUInt8(1), normal: buffer.readUInt8(4), day: buffer.readUInt8(5), night: buffer.readUInt8(6), away: buffer.readUInt8(7) } }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(8); var temperature = data.temperature; status.encode(buffer, data.success, 0); buffer.writeInt8(temperature.type, 1); buffer.writeUInt8(data.status ? 1 : 0, 2); buffer.writeUInt8(data.mode, 3); buffer.writeUInt8(temperature.normal, 4); buffer.writeUInt8(temperature.day, 5); buffer.writeUInt8(temperature.night, 6); buffer.writeUInt8(temperature.away, 7); return buffer; } }, /* 11.2 Floor Heating Control from Floor Heating Module */ // 11.2.1 Read Floor Heating Status 0x1C5E: { parse: function(buffer) { return { channel: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.channel]); }, response: 0x1C5F }, // 11.2.2 Response Read Floor Heating Status 0x1C5F: { parse: function(buffer) { var work = buffer.readUInt8(1); var watering = buffer.readUInt8(11); return { channel: buffer.readUInt8(0), work: { type: work >> 4, status: Boolean(work & 0x0F) }, temperature: { type: buffer.readUInt8(2), normal: buffer.readUInt8(4), day: buffer.readUInt8(5), night: buffer.readUInt8(6), away: buffer.readUInt8(7) }, mode: buffer.readUInt8(3), timer: buffer.readUInt8(8), valve: Boolean(buffer.readUInt8(9)), PWD: buffer.readUInt8(10), watering: { type: watering >> 4, status: Boolean(watering & 0x0F), time: buffer.readUInt8(12) } }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(13); var work = data.work; var watering = data.watering; var temperature = data.temperature; buffer.writeUInt8(data.channel, 0); buffer.writeUInt8(work.type << 4 | (work.status ? 1 : 0), 1); buffer.writeUInt8(temperature.type, 2); buffer.writeUInt8(data.mode, 3); buffer.writeUInt8(temperature.normal, 4); buffer.writeUInt8(temperature.day, 5); buffer.writeUInt8(temperature.night, 6); buffer.writeUInt8(temperature.away, 7); buffer.writeUInt8(data.timer, 8); buffer.writeUInt8(data.valve ? 1 : 0, 9); buffer.writeUInt8(data.PWD, 10); buffer.writeUInt8(watering.type << 4 | (watering.status ? 1 : 0), 11); buffer.writeUInt8(watering.time, 12); return buffer; } }, // 11.2.3 Control Floor Heating Status 0x1C5C: { parse: function(buffer) { var work = buffer.readUInt8(1); return { channel: buffer.readUInt8(0), work: { type: work >> 4, status: Boolean(work & 0x0F) }, temperature: { type: buffer.readUInt8(2), normal: buffer.readUInt8(4), day: buffer.readUInt8(5), night: buffer.readUInt8(6), away: buffer.readUInt8(7) }, mode: buffer.readUInt8(3), valve: Boolean(buffer.readUInt8(8)), watering: { time: buffer.readUInt8(9) } }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(10); var work = data.work; var temperature = data.temperature; buffer.writeUInt8(data.channel, 0); buffer.writeUInt8(work.type << 4 | (work.status ? 1 : 0), 1); buffer.writeUInt8(temperature.type, 2); buffer.writeUInt8(data.mode, 3); buffer.writeUInt8(temperature.normal, 4); buffer.writeUInt8(temperature.day, 5); buffer.writeUInt8(temperature.night, 6); buffer.writeUInt8(temperature.away, 7); buffer.writeUInt8(data.valve ? 1 : 0, 8); buffer.writeUInt8(data.watering.time, 9); return buffer; }, response: 0x1C5D }, // 11.2.4 Response Control Floor Heating Status // FIXME: same as 0x1C5C 0x1C5D: { parse: function(buffer) { var work = buffer.readUInt8(1); return { channel: buffer.readUInt8(0), work: { type: work >> 4, status: Boolean(work & 0x0F) }, temperature: { type: buffer.readUInt8(2), normal: buffer.readUInt8(4), day: buffer.readUInt8(5), night: buffer.readUInt8(6), away: buffer.readUInt8(7) }, mode: buffer.readUInt8(3), valve: Boolean(buffer.readUInt8(8)), watering: { time: buffer.readUInt8(9) } }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(10); var work = data.work; var temperature = data.temperature; buffer.writeUInt8(data.channel, 0); buffer.writeUInt8(work.type << 4 | (work.status ? 1 : 0), 1); buffer.writeUInt8(temperature.type, 2); buffer.writeUInt8(data.mode, 3); buffer.writeUInt8(temperature.normal, 4); buffer.writeUInt8(temperature.day, 5); buffer.writeUInt8(temperature.night, 6); buffer.writeUInt8(temperature.away, 7); buffer.writeUInt8(data.valve ? 1 : 0, 8); buffer.writeUInt8(data.watering.time, 9); return buffer; } }, /* 11.3 Floor Heating Settings (DLP Works as Master) */ // 11.3.1 Read Floor Heating Settings // 0x1940 // 11.3.2 Response Read Floor Heating Settings // 0x1941 // 11.3.3 Modify Floor Heating Settings // 0x1942 // 11.3.4 Response Modify Floor Heating Settings // 0x1943 /* 11.4 Floor Heating Settings (Floor Heating module Works as Master) */ // 11.4.1 Read Floor Heating Day Night Time Setting // 0x1D1E // 11.4.2 Response Read Floor Heating Day Night Time Setting // 0x1D1F // 11.4.3 Modify Floor Heating Day Night Time Setting // 0x1D1D // 11.4.4 Response Modify Floor Heating Day Night Time Setting // 0x1D1F /* 12.1 Read Sensors Status (8in1 DeviceType315) */ // 12.1.1 Read Sensors Status 0xDB00: { parse: function(buffer) { return { logic: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.logic]); }, response: 0xDB01 }, // 12.1.2 Response Read Sensors Status 0xDB01: { parse: function(buffer) { var data = { delay: word.parse(buffer, 6), movement: Boolean(buffer.readUInt8(3)), dryContacts: new Array(2) }; var contacts = data.dryContacts; for (var i = 0; i < 2; i++) contacts[i] = { number: i + 1, status: Boolean(buffer.readUInt8(i)) }; return data; }, encode: function(data) { var buffer = Buffer.allocUnsafe(8); var contacts = data.dryContacts || []; for (var i = 0; i < 2; i++) buffer.writeUInt8(contacts[i].status, i); buffer.writeUInt8(0, 2); buffer.writeUInt8(data.movement, 3); buffer.writeUInt16LE(0, 4); word.encode(buffer, data.delay, 6); return buffer; } }, /* 12.2 Read Sensors Status (8in1 DeviceType314) */ // 12.2.1 Read Sensors Status 0x1645: { response: 0x1646 }, // 12.2.2 Response Read Sensors Status 0x1646: { parse: function(buffer) { var data = { success: status.parse(buffer, 0), temperature: buffer.readUInt8(1) - 20, brightness: word.parse(buffer, 2), movement: Boolean(buffer.readUInt8(4)), dryContacts: [] }; var offset = 5; var length = buffer.length; var contacts = data.dryContacts; if (length >= 8) data.sonic = Boolean(buffer.readUInt8(offset++)); while (offset < 9 && offset < length) contacts[contacts.length] = { number: contacts.length + 1, status: Boolean(buffer.readUInt8(offset++)) }; return data; }, encode: function(data) { var contacts = data.dryContacts || []; var hasSonic = data.hasOwnProperty('sonic'); var buffer = Buffer.allocUnsafe(hasSonic ? 8 : 7); status.encode(buffer, data.success, 0); buffer.writeUInt8(data.temperature + 20, 1); word.encode(buffer, data.brightness, 2); buffer.writeUInt8(data.movement, 4); var offset = 5; if (hasSonic) buffer.writeUInt8(data.sonic, offset++); for (var i = 0; i < contacts.length; i++) buffer.writeUInt8(contacts[i].status ? 1 : 0, i + offset); return buffer; } }, /* 12.3 Read Sensors Status (12in1) */ // Same codes as for 12.2 // 12.3.3 Broadcast Sensors Status Automatically 0x1647: { parse: function(buffer) { var data = { temperature: buffer.readUInt8(0) - 20, brightness: word.parse(buffer, 1), movement: Boolean(buffer.readUInt8(3)), sonic: Boolean(buffer.readUInt8(4)), dryContacts: [] }; var offset = 5; var length = buffer.length; var contacts = data.dryContacts; while (offset < 7 && offset < length) contacts[contacts.length] = { number: contacts.length + 1, status: Boolean(buffer.readUInt8(offset++)) }; return data; }, encode: function(data) { var buffer = Buffer.allocUnsafe(7); var contacts = data.dryContacts || []; buffer.writeUInt8(data.temperature + 20, 0); word.encode(buffer, data.brightness, 1); buffer.writeUInt8(data.movement, 3); buffer.writeUInt8(data.sonic, 4); for (var i = 0; i < contacts.length; i++) buffer.writeUInt8(contacts[i].status ? 1 : 0, i + 5); return buffer; } }, /* 12.4 Read Sensors Status (SensorsInOne) */ // 12.4.1 Read Sensors Status 0x1604: { response: 0x1605 }, // 12.4.2 Response Read Sensors Status 0x1605: { parse: function(buffer) { var data = { success: status.parse(buffer, 0), temperature: buffer.readUInt8(1) - 20, brightness: word.parse(buffer, 2), humidity: buffer.readUInt8(4), air: buffer.readUInt8(5), gas: buffer.readUInt8(6), movement: Boolean(buffer.readUInt8(7)), dryContacts: [] }; var offset = 8; var length = buffer.length; var contacts = data.dryContacts; while (offset < 10 && offset < length) contacts[contacts.length] = { number: contacts.length + 1, status: Boolean(buffer.readUInt8(offset++)) }; return data; }, encode: function(data) { var buffer = Buffer.allocUnsafe(10); var contacts = data.dryContacts || []; status.encode(buffer, data.success, 0); buffer.writeUInt8(data.temperature + 20, 1); word.encode(buffer, data.brightness, 2); buffer.writeUInt8(data.humidity, 4); buffer.writeUInt8(data.air, 5); buffer.writeUInt8(data.gas, 6); buffer.writeUInt8(data.movement, 7); for (var i = 0; i < contacts.length; i++) buffer.writeUInt8(contacts[i].status ? 1 : 0, i + 8); return buffer; } }, // 12.4.3 Broadcast Sensors Status // FIXME: same as 0x1605 0x1630: { parse: function(buffer) { var data = { success: status.parse(buffer, 0), temperature: buffer.readUInt8(1) - 20, brightness: word.parse(buffer, 2), air: buffer.readUInt8(4), gas: buffer.readUInt8(5), movement: Boolean(buffer.readUInt8(6)), dryContacts: [] }; var offset = 7; var length = buffer.length; var contacts = data.dryContacts; while (offset < 9 && offset < length) contacts[contacts.length] = { number: contacts.length + 1, status: Boolean(buffer.readUInt8(offset++)) }; return data; }, encode: function(data) { var buffer = Buffer.allocUnsafe(9); var contacts = data.dryContacts || []; status.encode(buffer, data.success, 0); buffer.writeUInt8(data.temperature + 20, 1); word.encode(buffer, data.brightness, 2); buffer.writeUInt8(data.air, 4); buffer.writeUInt8(data.gas, 5); buffer.writeUInt8(data.movement, 6); for (var i = 0; i < contacts.length; i++) buffer.writeUInt8(contacts[i].status ? 1 : 0, i + 7); return buffer; } }, /* 13.1 Read Temperature */ // 13.1.1 Read Temperature 0xE3E7: { parse: function(buffer) { return { channel: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.channel]); }, response: 0xE3E8 }, // 13.1.2 Response Read Temperature 0xE3E8: { parse: function(buffer) { var temperature = buffer.readUInt8(1); var sign = (temperature >> 7) ? -1 : 1; return { channel: buffer.readUInt8(0), temperature: sign * (temperature & ~0x80) }; }, encode: function(data) { var temperature = data.temperature; var sign = data.temperature < 0 ? 0x80 : 0; if (temperature < 0) temperature = -temperature; return Buffer.from([data.channel, temperature | sign]); } }, // 13.1.3 Broadcast Temperature // FIXME: documentation is wrong. // DLP panel indeed use 4-byte float as described in // "Operation Code of HDL Buspro V1.111" but other devices // (like PIR sensors) use rest 3 bytes for other data or even // skip them. 0xE3E5: { parse: function(buffer) { var temperature = buffer.readUInt8(1); var sign = (temperature >> 7) ? -1 : 1; return { channel: buffer.readUInt8(0), temperature: sign * (temperature & ~0x80) }; }, encode: function(data) { var temperature = data.temperature; var sign = data.temperature < 0 ? 0x80 : 0; if (temperature < 0) temperature = -temperature; return Buffer.from([data.channel, temperature | sign]); } }, /* 13.2 Read Temperature New */ // 13.2.1 Read Temperature New 0x1948: { parse: function(buffer) { return { channel: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.channel]); }, response: 0x1949 }, // 13.2.2 Response Temperature 0x1949: { parse: function(buffer) { return { channel: buffer.readUInt8(0), temperature: buffer.readFloatLE(1) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(5); buffer.writeUInt8(data.channel, 0); buffer.writeFloatLE(data.temperature, 1); return buffer; } }, /* 14. Security Module */ // 14.1.1 Read Security Module // 0x011E // 14.1.2 Response Read Security Module // 0x011F // 14.1.3 Arm Security Module // 0x0104 // 14.1.4 Response Arm Security Module // 0x0105 // 14.1.5 Alarm Security Module // 0x010C // 14.1.6 Response Alarm Security Module // 0x010D /* 15. Music Control */ // 15.1.1 Music Control // 0x0218 // 15.1.2 Response Music Control // 0x0219 // 15.1.3 Read Read Music Control Status // 0x021A // 15.1.4 Response Music Control // 0x021B /* 16. Dry Contact */ // 16.1.1 Auto broadcast Dry Contact Status 0x15D0: { parse: function(buffer) { return { area: buffer.readUInt8(0), channel: buffer.readUInt8(1), status: buffer.readUInt8(2) === 0 }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(3); buffer.writeUInt8(data.area, 0); buffer.writeUInt8(data.channel, 1); buffer.writeUInt8(data.status ? 0 : 1, 2); return buffer; } }, // 16.1.2 Response Auto broadcast Dry Contact Status 0x15D1: { parse: function(buffer) { return { area: buffer.readUInt8(0), channel: buffer.readUInt8(1), status: buffer.readUInt8(2) === 0 }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(3); buffer.writeUInt8(data.area, 0); buffer.writeUInt8(data.channel, 1); buffer.writeUInt8(data.status ? 0 : 1, 2); return buffer; } }, // 16.1.3 Read Dry Contact Status 0x15CE: { parse: function(buffer) { return { area: buffer.readUInt8(0), switch: buffer.readUInt8(1) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(2); buffer.writeUInt8(data.area, 0); buffer.writeUInt8(data.switch, 1); return buffer; }, response: 0x15CF }, // 16.1.4 Response Read Dry Contact Status 0x15CF: { parse: function(buffer) { var data = { area: buffer.readUInt8(0), switch: buffer.readUInt8(1), contact: Boolean(buffer.readUInt8(2)) }; return data; }, encode: function(data) { var buffer = Buffer.allocUnsafe(3); buffer.writeUInt8(data.area, 0); buffer.writeUInt8(data.switch, 1); buffer.writeUInt8(data.contact ? 1 : 0, 2); return buffer; } }, // 16.1.5 Dry Contact Multi-Channel Status 0x6F00: { parse: function(buffer) { var data = { declaredChannels: buffer.readUInt8(1), statusMask: buffer.readUInt16BE(2), channels: [] }; // Include subnet if present if (buffer.length >= 4) { data.subnet = buffer.readUInt8(0); } // Parse 16-bit mask for up to 16 channels var mask = data.statusMask; var channels = data.channels; for (var i = 0; i < 16; i++) { channels.push({ number: i + 1, status: !!(mask & (1 << i)) }); } return data; }, encode: function(data) { var buffer = Buffer.allocUnsafe(4); buffer.writeUInt8(data.subnet || 0, 0); buffer.writeUInt8(data.declaredChannels || 16, 1); var mask = 0; var channels = data.channels || []; for (var i = 0; i < channels.length; i++) { if (channels[i].status) { mask |= 1 << (i); } } buffer.writeUInt16BE(mask, 2); return buffer; } }, /* 17. DLP Music Play Control Command */ // 17.1.1 Read Z-audio Current Status // 0x192E // 17.1.2 Response Read Z-audio Current Status // 0x192F // 17.1.7 Change Source // 0x192E /* 18. Z-audio Command */ // 18.1.1 Read Play Lists // 0x1364 // 18.1.2 Response Read Play Lists // 0x1365 /* 19. Power meter Command */ // 19.1.1 Read Voltage // 0xD902 // 19.1.2 Response Read Voltage // 0xD903 // 19.2.1 Read Current // 0xD908 // 19.2.2 Response Read Current // 0xD909 // 19.3.1 Read Power // 0xD90A // 19.3.2 Response Read Power // 0xD90B // 19.4.1 Read Power Factor // 0xD904 // 19.4.2 Response Read Power Factor // 0xD905 // 19.5.1 Read Electricity // 0xD91A // 19.5.2 Response Read Electricity // 0xD91B /* 20. Universal Control */ // 20.1.1 Read UV Control Setup // 0x16A4 // 20.1.2 Response Read UV Control Setup // 0x16A5 // 20.2.1 Universal control // 0x16A6 // 20.2.2 Response Universal Cotrol // 0x16A7 /* 21. Analog Value */ // 21.1.1 Read Analog Value // 0xE440 // 21.1.2 Response Read Analog Value // 0xE441 /* XX. Undocumented Operation Codes */ // XX.1.1 Panel brightness/lock 0xE012: { parse: function(buffer) { return { backlight: buffer.readUInt8(0), statusLights: buffer.readUInt8(1), autoLock: buffer.readUInt8(2) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(3); buffer.writeUInt8(data.backlight, 0); buffer.writeUInt8(data.statusLights, 1); buffer.writeUInt8(data.autoLock, 2); return buffer; }, response: 0xE013 }, // XX.1.2 Panel brightness/lock response 0xE013: { parse: function(buffer) { return { success: status.parse(buffer, 0) }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(1); status.encode(buffer, data.success, 0); return buffer; } }, // XX.1.3 Panel button color 0xE14E: { parse: function(buffer) { return { button: buffer.readUInt8(0), color: { on: [buffer.readUInt8(1), buffer.readUInt8(2), buffer.readUInt8(3)], off: [buffer.readUInt8(4), buffer.readUInt8(5), buffer.readUInt8(6)] } }; }, encode: function(data) { var buffer = Buffer.allocUnsafe(7); buffer.writeUInt8(data.button, 0); buffer.writeUInt8(data.color.on[0], 1); buffer.writeUInt8(data.color.on[1], 2); buffer.writeUInt8(data.color.on[2], 3); buffer.writeUInt8(data.color.off[0], 4); buffer.writeUInt8(data.color.off[1], 5); buffer.writeUInt8(data.color.off[2], 6); return buffer; }, response: 0xE14F }, // XX.1.4 Panel button color response 0xE14F: { parse: function(buffer) { return { button: buffer.readUInt8(0) }; }, encode: function(data) { return Buffer.from([data.button]); } } };