homebridge-elero-stick
Version:
Elero funkstick for homebridge: https://github.com/ma-ku/homebridge-elero-stick
330 lines • 12 kB
JavaScript
"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