UNPKG

homebridge-elero-stick

Version:

Elero funkstick for homebridge: https://github.com/ma-ku/homebridge-elero-stick

330 lines 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EleroParser = exports.EleroStick = exports.ELERO_STATES = void 0; const events_1 = require("events"); const serialport_1 = require("serialport"); var ELERO_COMMANDS; (function (ELERO_COMMANDS) { ELERO_COMMANDS[ELERO_COMMANDS["UP"] = 32] = "UP"; ELERO_COMMANDS[ELERO_COMMANDS["INTERM_POS"] = 68] = "INTERM_POS"; ELERO_COMMANDS[ELERO_COMMANDS["VENT_POS"] = 36] = "VENT_POS"; ELERO_COMMANDS[ELERO_COMMANDS["DOWN"] = 64] = "DOWN"; ELERO_COMMANDS[ELERO_COMMANDS["STOP"] = 16] = "STOP"; })(ELERO_COMMANDS || (ELERO_COMMANDS = {})); ; var ELERO_STATES; (function (ELERO_STATES) { ELERO_STATES[ELERO_STATES["NO_INFORMATION"] = 0] = "NO_INFORMATION"; ELERO_STATES[ELERO_STATES["TOP_POS_STOP"] = 1] = "TOP_POS_STOP"; ELERO_STATES[ELERO_STATES["BOTTOM_POS_STOP"] = 2] = "BOTTOM_POS_STOP"; ELERO_STATES[ELERO_STATES["INTERM_POS_STOP"] = 3] = "INTERM_POS_STOP"; ELERO_STATES[ELERO_STATES["TILT_POS_STOP"] = 4] = "TILT_POS_STOP"; ELERO_STATES[ELERO_STATES["BLOCKING"] = 5] = "BLOCKING"; ELERO_STATES[ELERO_STATES["OVERHEATED"] = 6] = "OVERHEATED"; ELERO_STATES[ELERO_STATES["TIMEOUT"] = 7] = "TIMEOUT"; ELERO_STATES[ELERO_STATES["START_MOVE_UP"] = 8] = "START_MOVE_UP"; ELERO_STATES[ELERO_STATES["START_MOVE_DOWN"] = 9] = "START_MOVE_DOWN"; ELERO_STATES[ELERO_STATES["MOVING_UP"] = 10] = "MOVING_UP"; ELERO_STATES[ELERO_STATES["MOVING_DOWN"] = 11] = "MOVING_DOWN"; ELERO_STATES[ELERO_STATES["STOP_UNDEFINED_POS"] = 13] = "STOP_UNDEFINED_POS"; ELERO_STATES[ELERO_STATES["TOP_VENT_POS_STOP"] = 14] = "TOP_VENT_POS_STOP"; ELERO_STATES[ELERO_STATES["BOTTOM_INTERM_POS_STOP"] = 15] = "BOTTOM_INTERM_POS_STOP"; ELERO_STATES[ELERO_STATES["SWITCHING_DEVICE_OFF"] = 16] = "SWITCHING_DEVICE_OFF"; ELERO_STATES[ELERO_STATES["SWITCHING_DEVICE_ON"] = 17] = "SWITCHING_DEVICE_ON"; })(ELERO_STATES = exports.ELERO_STATES || (exports.ELERO_STATES = {})); ; const DEFAULT_BAUDRATE = 38400; const DEFAULT_BYTESIZE = 8; const DEFAULT_PARITY = 'none'; const DEFAULT_STOPBITS = 1; function hex(data) { let msg = "["; data.forEach(d => { msg += d.toString(16).padStart(2, '0') + " "; }); msg += "]"; return msg; } class EleroStick extends events_1.EventEmitter { constructor(port, log) { super(); this.sendInterval = 250; this.connectionBusy = false; this.commandQueue = []; this.channels = []; this._currentTimer = undefined; this.log = log; this.port = port; if (this.log) this.log.info('Logging enabled for EleroStick'); this.serial = new serialport_1.SerialPort({ path: port, baudRate: DEFAULT_BAUDRATE, dataBits: DEFAULT_BYTESIZE, parity: DEFAULT_PARITY, stopBits: DEFAULT_STOPBITS, autoOpen: false, }); // We put a parser into place to check for valid // Elero responses and that emits only full response packages this.parser = this.serial.pipe(new EleroParser(this.log)); this.parser.on('data', (data) => { this.incomingData(data); }) .on('connect', () => { if (this.log) this.log.debug("Serial port connected"); }) .on('close', () => { if (this.log) this.log.debug("Serial port closed"); this.serial.open(); }); this.serial.open(); } checksum(data) { var sum = 0; data.forEach(element => { sum += element; }); return sum & 0xff; } sendCommand(command, urgent = false) { if (urgent == true) { this.commandQueue.unshift(command); } else { this.commandQueue.push(command); } if (this.log) this.log.debug("sendCommand: %s, command queue length: %d", hex(command), this.commandQueue.length); if (!this.connectionBusy) { this.connectionBusy = true; this.sendNext(); } } send(data) { if (this._currentTimer) { clearTimeout(this._currentTimer); this._currentTimer = undefined; } var msg = data; // Compute checksum byte for the message var checksum = this.checksum(data); var csByte = 0x100 - (checksum & 0xff); msg[data.length] = csByte; this.serial.write(msg, (err) => { if (err) { this.connectionBusy = false; if (this.log) this.log.error('Error on write: ', err.message); return; } }); var stick = this; this._currentTimer = setTimeout(() => { stick.sendNext(); }, this.sendInterval); } sendNext() { var command = this.commandQueue.shift(); if (command) { this.send(command); } else { this.connectionBusy = false; } } /** * * @param {[number]} channels * @param {ELERO_COMMAND} command * @param {[Boolean]} urgent */ easySend(channels, command, urgent = false) { channels = this.checkChannels(channels); this.sendCommand([0xaa, 0x05, 0x4c, this.highChannelBits(channels), this.lowChannelBits(channels), command], urgent || false); } commandUp(channels) { if (this.log) this.log.debug("commandUp", channels); this.easySend(channels, ELERO_COMMANDS.UP, true); } commandDown(channels) { if (this.log) this.log.debug("commandDown", channels); this.easySend(channels, ELERO_COMMANDS.DOWN, true); } commandStop(channels) { if (this.log) this.log.debug("commandStop", channels); this.easySend(channels, ELERO_COMMANDS.STOP, true); } commandVentilationPosition(channels) { if (this.log) this.log.debug("commandVentilationPosition", channels); this.easySend(channels, ELERO_COMMANDS.VENT_POS, true); } commandIntermediatePosition(channels) { if (this.log) this.log.debug("commandIntermediatePosition", channels); this.easySend(channels, ELERO_COMMANDS.INTERM_POS, true); } easyInfo(channels) { if (this.log) this.log.debug("easyInfo", channels); channels = this.checkChannels(channels); if (channels.length == 0) { channels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; } var index = 0; for (index = 0; index < channels.length; ++index) { this.sendCommand([0xaa, 0x04, 0x4e, this.highChannelBits([channels[index]]), this.lowChannelBits([channels[index]])]); } } easyCheck() { if (this.log) this.log.debug("Easy Check"); this.sendCommand([0xaa, 0x02, 0x4a]); } incomingData(data) { if (this.log) this.log.debug("Incoming data: " + hex(data)); if (this.checksum(data) == 0) { var valid = false; do { // Header if (data[0] != 0xAA) break; if ((data[1] == 0x05) && (data[2] == 0x4D)) { // Easy_Ack var channels = this.decodeChannels(data[3], data[4]); const state = data[5]; var conn = this; channels.forEach(channel => { conn.statusReceived(channel, state); }); } else if ((data[1] == 0x04) && (data[2] == 0x4B)) { // Easy_Confirm this.channels = this.decodeChannels(data[3], data[4]); this.emit('connect', this.channels); } else break; } while (false); } else { if (this.log) this.log.error("Invalid checksum ", this.checksum(data)); } this.sendNext(); } checkChannels(channels) { return channels || Array.from({ length: 15 }, (v, k) => k); } statusReceived(channel, state) { if (this.log) this.log.debug("Status of channel %d: %s", channel, ELERO_STATES[state]); this.emit('status', channel, state); } highChannelBits(channels) { return this.channelBits(channels, 8, 7); } lowChannelBits(channels) { return this.channelBits(channels, 0, 8); } channelBits(channels, offset, count) { var bits = 0; for (var i = offset; i < (offset + count); i++) { if (channels.includes(i)) { bits |= 1 << (i - offset); } } return bits; } decodeChannels(highBits, lowBits) { var channels = []; for (var i = 0; i < 8; i++) { if (lowBits & (1 << i)) { channels.push(i); } } for (var i = 8; i < 15; i++) { if (highBits & (1 << (i - 8))) { channels.push(i); } } return channels; } getKeyByValue(obj, value) { return Object.keys(obj).find(key => obj[key] === value); } } exports.EleroStick = EleroStick; /** * Defines a custom parser for the SerialPort library that processes * the datagrams received from the Elero Stick and collects all data * for a single datagram before sending the data back to the caller */ const stream_1 = require("stream"); /** * Emit data for a single Elero datagram. The length varies depending on the content header * * @extends Transform * @param {Object} options parser options object * @summary A transform stream that emits data as a buffer after a specific number of bytes are received. Runs in O(n) time. * @example */ class EleroParser extends stream_1.Transform { constructor(log) { super(); this.log = log; // Current position in the receiving buffer this.position = 0; // This is the maximum length of a datagram this.maxLength = 7; // The expected length for the current datagram this.length = 0; // We expect not more than 7 bytes per datagram this.buffer = Buffer.alloc(this.maxLength); } _transform(chunk, encoding, callback) { if (this.log) this.log.debug('Received data from elero stick: ' + hex(chunk)); let cursor = 0; while (cursor < chunk.length) { this.buffer[this.position] = chunk[cursor]; cursor++; // Check if this is an Elero message, if not, flush the data if (this.position == 0) { if (this.buffer[0] != 0xAA) { this.push(this.buffer); this.buffer = Buffer.alloc(this.maxLength); this.position = 0; continue; } } // When the second byte is received, we can determine the length // of the payload and add one header byte and one checksum byte if (this.position == 1) { this.length = 1 + this.buffer[1] + 1; } // We can proceed this.position++; // Check if we read all data if (this.position == this.length) { this.push(this.buffer); this.buffer = Buffer.alloc(this.maxLength); this.position = 0; } } callback(); } _flush(callback) { this.push(this.buffer.slice(0, this.position)); this.buffer = Buffer.alloc(this.maxLength); this.position = 0; callback(); } } exports.EleroParser = EleroParser; //# sourceMappingURL=elero-stick.js.map