homebridge-smartsystem
Version:
SmartServer (Proxy Websockets to TCP sockets, Smappee MQTT, Duotecno IP Nodes, Homekit interface)
862 lines • 36.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Protocol = exports.Unit = exports.Node = exports.recName = exports.Rec = exports.TempPreset = exports.cmdName = exports.reqNodeAttributes = void 0;
const types_1 = require("./types");
const logger_1 = require("./logger");
// Duotecno master IP protocol implementation
// Johan Coppieters
//
// Dec 2018 - v1 - first version based on 1 smartbox
// Mar 2019 - v2 - rewrite to support multiple masters
//
// Dec 2019 - v3 - for app side only
// May-Nov 2021 - v4 - changes for pro-app
// Nov 2022 - Register and merge with gateway
///////////////////////////
// Commands + attributes //
///////////////////////////
var Cmd;
(function (Cmd) {
Cmd[Cmd["SetSensorValue"] = 7] = "SetSensorValue";
Cmd[Cmd["Internal"] = 9] = "Internal";
Cmd[Cmd["SetBasicAudio"] = 159] = "SetBasicAudio";
Cmd[Cmd["SetExtendedAudio"] = 208] = "SetExtendedAudio";
Cmd[Cmd["SetAVMatrix"] = 202] = "SetAVMatrix";
Cmd[Cmd["SetSwitch"] = 163] = "SetSwitch";
Cmd[Cmd["SetDimmer"] = 162] = "SetDimmer";
Cmd[Cmd["SetControl"] = 168] = "SetControl";
Cmd[Cmd["SetMotor"] = 182] = "SetMotor";
Cmd[Cmd["SetSensor"] = 136] = "SetSensor";
Cmd[Cmd["SetDateTime"] = 170] = "SetDateTime";
Cmd[Cmd["Login"] = 214] = "Login";
Cmd[Cmd["Heartbeat"] = 215] = "Heartbeat";
Cmd[Cmd["DatabaseInfo"] = 209] = "DatabaseInfo";
Cmd[Cmd["SetSchedule"] = 217] = "SetSchedule";
Cmd[Cmd["reqSchedule"] = 218] = "reqSchedule";
Cmd[Cmd["reqNodeManagement"] = 220] = "reqNodeManagement";
Cmd[Cmd["Register"] = 224] = "Register";
})(Cmd || (Cmd = {}));
// for Set Switch/Dimmer/Control/Motor/Sensor = 162, 163, 168, 182, 136
const reqDim = 3;
const reqOff = 9;
const reqOn = 10;
// for Login = 214
const reqDisconnect = 0;
const reqConnect = 3;
// for DatabaseInfo = 209;
const reqDBInfo = 0;
const reqNodeInfo = 1;
const reqUnitInfo = 2;
const reqUnitStatus = 3;
// for reqNodeMgt, versions et al
var reqNodeAttributes;
(function (reqNodeAttributes) {
reqNodeAttributes[reqNodeAttributes["masterSupported"] = 0] = "masterSupported";
reqNodeAttributes[reqNodeAttributes["isMaster"] = 4] = "isMaster";
reqNodeAttributes[reqNodeAttributes["nodeInfo"] = 5] = "nodeInfo";
reqNodeAttributes[reqNodeAttributes["nodeVersion"] = 6] = "nodeVersion";
reqNodeAttributes[reqNodeAttributes["nodeProtocol"] = 7] = "nodeProtocol";
})(reqNodeAttributes = exports.reqNodeAttributes || (exports.reqNodeAttributes = {}));
function cmdName(cmd) {
return Cmd[cmd] || "cmd" + cmd;
}
exports.cmdName = cmdName;
var TempPreset;
(function (TempPreset) {
TempPreset[TempPreset["off"] = -1] = "off";
TempPreset[TempPreset["sun"] = 0] = "sun";
TempPreset[TempPreset["halfsun"] = 1] = "halfsun";
TempPreset[TempPreset["hsun"] = 1] = "hsun";
TempPreset[TempPreset["moon"] = 2] = "moon";
TempPreset[TempPreset["halfmoon"] = 3] = "halfmoon";
TempPreset[TempPreset["hmoon"] = 3] = "hmoon";
})(TempPreset = exports.TempPreset || (exports.TempPreset = {}));
//////////////////////
// Received results //
//////////////////////
var Rec;
(function (Rec) {
// return info from cmdDatabaseInfo
Rec[Rec["DBInfo"] = 0] = "DBInfo";
Rec[Rec["NodeInfo"] = 1] = "NodeInfo";
Rec[Rec["UnitInfo"] = 2] = "UnitInfo";
Rec[Rec["Internal"] = 9] = "Internal";
Rec[Rec["ErrorMessage"] = 17] = "ErrorMessage";
Rec[Rec["ConnectStatus"] = 67] = "ConnectStatus";
Rec[Rec["AudioStatus"] = 23] = "AudioStatus";
Rec[Rec["AudioExtendedStatus"] = 70] = "AudioExtendedStatus";
Rec[Rec["TimeDateStatus"] = 71] = "TimeDateStatus";
Rec[Rec["HeartbeatStatus"] = 72] = "HeartbeatStatus";
Rec[Rec["ScheduleStatus"] = 73] = "ScheduleStatus";
Rec[Rec["NodeMgtInfo"] = 74] = "NodeMgtInfo";
Rec[Rec["Register"] = 77] = "Register";
// return info from recDBInfo
Rec[Rec["Info"] = 64] = "Info";
// return info from reqUnitStatus
Rec[Rec["Mood"] = 4] = "Mood";
Rec[Rec["Dimmer"] = 5] = "Dimmer";
Rec[Rec["Switch"] = 6] = "Switch";
Rec[Rec["Sensor"] = 7] = "Sensor";
Rec[Rec["Motor"] = 38] = "Motor";
Rec[Rec["Macro"] = 69] = "Macro";
})(Rec = exports.Rec || (exports.Rec = {}));
;
function recName(rec) {
return Rec[rec] || "rec" + rec;
}
exports.recName = recName;
/////////////////////////
// Node in the network //
/////////////////////////
class Node {
constructor(master, params) {
this.master = master;
types_1.Sanitizers.nodeInfo(params, this);
this.units = [];
// remove | in names
let separ = this.name.indexOf("|");
this.name = (separ < 0) ? this.name : this.name.substring(0, separ) + " " + (this.name.substring(separ + 1));
}
inMultiNode() {
return this.master.inMultiNode();
}
typeName() {
switch (this.type) {
case types_1.NodeType.kStandardNode: return "Standard";
case types_1.NodeType.kGatewayNode: return "Gateway";
case types_1.NodeType.kModemNode: return "Modem";
case types_1.NodeType.kGUINode: return "GUI";
default: return "Unknown node type (" + this.type + ")";
}
}
getName() {
return this.name;
}
getSort() {
return this.getName().toLowerCase();
}
getNumber() {
return (0, types_1.hex)(this.logicalAddress);
}
getDescription() {
return this.getName() + ", active: " + this.active + ", type: " + this.typeName() + ", node: " + this.getName();
}
findUnit(logicalAddress) {
return this.units.find(u => u && (u.logicalAddress === logicalAddress));
}
findUnitIndex(logicalAddress) {
return this.units.findIndex(u => u && (u.logicalAddress === logicalAddress));
}
}
exports.Node = Node;
/////////////////////////
// Unit within a Node //
/////////////////////////
class Unit {
constructor(node, params, moodName = "mood") {
this.group = 0;
this.resetTimer = null;
this.node = node;
types_1.Sanitizers.unitInfo(params, this);
this.extendedType = this.extendedType || this.calcExtendedType();
this.name = this.name || this.getSerialNr();
// delete the "<" and ">" for power meters
// if (this.extendedType === UnitExtendedType.kPower)
// this.name = this.name.substring(1);
// make a name for homekit, without the | but add § is 'specials' to add "sfeer", etc...
// if the display name is empty make a N[nodeAdr]-U[unitAdr] name.
// delete all type modifiers ( $, * and ! )
this.displayName = this.displayName || this.name.replace(/\|/g, this.hasSpecials() ? (" " + moodName + " ") : " ").replace(/\$|\*|\!/g, '') || this.getSerialNr();
}
hasSpecials() {
let special = this.name.indexOf("|20");
if (special < 0)
special = this.name.indexOf("|50");
if (special < 0)
special = this.name.indexOf("|90");
if (special < 0)
special = this.name.indexOf("|OFF");
return special >= 0;
}
isUnit(master, port, nodeLogicalAddress, unitLogicalAddress) {
if (master instanceof Unit) {
const unit = master;
return ((this.node.master.same(unit.node.master)) &&
(this.node.logicalAddress == unit.node.logicalAddress) &&
(this.logicalAddress == unit.logicalAddress));
}
else { /* if (typeof master === "string") */
return ((this.node.master.same(master, port)) &&
(this.node.logicalAddress == nodeLogicalAddress) &&
(this.logicalAddress == unitLogicalAddress));
}
}
sameValue(value) {
if (this.type === types_1.UnitType.kSwitchingMotor)
return (((this.value == types_1.UnitState.kOpening) && (value == 4)) ||
((this.value == types_1.UnitState.kClosing) && (value == 5)) ||
((this.value <= types_1.UnitState.kOpen) && (value == 3)));
else
return this.value == value;
}
typeName() {
switch (this.getType()) {
case types_1.UnitExtendedType.kDimmer: return 'Dimmer';
case types_1.UnitExtendedType.kSwitch: return 'Switch/Relay';
case types_1.UnitExtendedType.kLightbulb: return 'Lightbulb';
case types_1.UnitExtendedType.kInput: return 'Control input';
case types_1.UnitExtendedType.kTemperature: return 'Temperature sensor';
case types_1.UnitExtendedType.kPower: return 'Power meter';
case types_1.UnitExtendedType.kExtendedAudio: return 'Extended audio';
case types_1.UnitExtendedType.kMood: return 'Virtual mood';
case types_1.UnitExtendedType.kCondition: return 'Condition';
case types_1.UnitExtendedType.kSwitchingMotor: return 'Switch motor';
case types_1.UnitExtendedType.kGarageDoor: return 'Garagedoor';
case types_1.UnitExtendedType.kDoor: return 'Door';
case types_1.UnitExtendedType.kLock: return 'Lock';
case types_1.UnitExtendedType.kUnlocker: return 'Unlocker';
case types_1.UnitExtendedType.kAudio: return 'Basic audio';
case types_1.UnitExtendedType.kAV: return 'AV Matrix';
case types_1.UnitExtendedType.kIRTX: return 'IRTX';
case types_1.UnitExtendedType.kVideo: return 'Video multiplexer';
default: return 'Unknown unit type (' + this.type + ')';
}
}
getName() {
return this.name;
}
getDisplayName() {
return this.displayName;
}
getNumber() {
return this.node.getNumber() + ";" + (0, types_1.hex)(this.logicalAddress);
}
getSort() {
const name = this.getName().toLowerCase();
switch (this.type) {
case types_1.UnitType.kTemperature: return "01|" + name;
case types_1.UnitType.kSwitchingMotor: return "02|" + name;
case types_1.UnitType.kDimmer: return "03|" + name;
case types_1.UnitType.kSwitch: return "04|" + name;
case types_1.UnitType.kMood: return "09|" + name;
case types_1.UnitType.kInput: return "10|" + name;
// case UnitType.kPower: return "11|" + name;
case types_1.UnitType.kExtendedAudio: "12|" + name;
case types_1.UnitType.kAudio: return "13|" + name;
case types_1.UnitType.kAV: return "14|" + name;
case types_1.UnitType.kVideo: return "15|" + name;
case types_1.UnitType.kIRTX: return "19|" + name;
default: return "99|" + name;
}
}
getType() {
return this.extendedType;
}
calcExtendedType() {
// General idea extention on DuoTecno's types
//
// $ -> kind of lock -> needs authentication
// * -> toggle
//
// Extension on Duotecno's types
// updown =>
// if name contains $ => "garagedoor"
// if name contains * => "door"
// else => "window-covering"
// mood =>
// if name contains $ => "unlock", locks again after 1.2 sec
// if name contains * => permanent locked=on/unlocked=off
// else => "mood" (turns of 1.2 seconds after being turned on)
// switch =>
// if name contains $ => "lock"
// if name contains * => "switch" (also still works with "stc", "Stc", "STC", stk", "STK" and "Stk")
// else => "lightbulb"
//
////////////
// Switch //
////////////
// Switch -> with * or STK -> Switch
if ((this.type === types_1.UnitType.kSwitch) &&
((this.name.indexOf("STK") >= 0) || (this.name.indexOf("stk") >= 0) || (this.name.indexOf("Stk") >= 0) ||
(this.name.indexOf("STC") >= 0) || (this.name.indexOf("stc") >= 0) || (this.name.indexOf("Stc") >= 0) ||
(this.name.indexOf("*") >= 0)))
return types_1.UnitExtendedType.kSwitch;
// Switch -> with $ -> Door
if ((this.type === types_1.UnitType.kSwitch) &&
(this.name.indexOf("$") >= 0))
return types_1.UnitExtendedType.kLock;
// Switch -> default -> LightBulb
if (this.type === types_1.UnitType.kSwitch)
return types_1.UnitExtendedType.kLightbulb;
//////////////////
// Temp / Power //
//////////////////
//if ((this.type === UnitType.kTemperature) &&
// ((this.name[0] === "<") || (this.name[0] === ">")))
// return UnitExtendedType.kPower;
/////////////
// Up/Down //
/////////////
// UpDown -> with $ -> GarageDoor
if ((this.type === types_1.UnitType.kSwitchingMotor) &&
(this.name.indexOf("$") >= 0))
return types_1.UnitExtendedType.kGarageDoor;
// UpDown with * -> Door
if ((this.type === types_1.UnitType.kSwitchingMotor) &&
(this.name.indexOf("*") >= 0))
return types_1.UnitExtendedType.kDoor;
// UpDown -> default -> WindowCovering
if (this.type === types_1.UnitType.kSwitchingMotor)
return types_1.UnitExtendedType.kSwitchingMotor;
///////////
// Moods //
///////////
// Mood -> with $ -> Lock (re-closes after 1.2 secs)
if ((this.type === types_1.UnitType.kMood) &&
(this.name.indexOf("$") >= 0))
return types_1.UnitExtendedType.kUnlocker;
// Mood -> with * -> Mood with state
if ((this.type === types_1.UnitType.kMood) &&
(this.name.indexOf("*") >= 0))
return types_1.UnitExtendedType.kCondition;
// Mood -> default -> Mood (turn off after 1.2 secs)
if (this.type === types_1.UnitType.kMood)
return types_1.UnitExtendedType.kMood;
///////////////////////
// All other default //
///////////////////////
return this.type;
}
getSerialNr() {
if (this.inMultiNode)
return this.node.getName() + "-N" + this.logicalNodeAddress + "-U" + this.logicalAddress;
else
return "N" + this.logicalNodeAddress + "-U" + this.logicalAddress;
}
getModelName() {
return this.typeName() + " " + (0, types_1.hex)(this.node.logicalAddress) + ";" + (0, types_1.hex)(this.logicalAddress);
}
isCtrl() {
return this.isSwitch() || this.isDimmer() || this.isUpDown();
}
isSwitch() {
return (this.type === types_1.UnitType.kSwitch);
}
isMood() {
return (this.type === types_1.UnitType.kMood);
}
isInput() {
return (this.type === types_1.UnitType.kInput);
}
isTemperature() {
return (this.type === types_1.UnitType.kTemperature);
}
isDimmer() {
return (this.type === types_1.UnitType.kDimmer);
}
isUpDown() {
return (this.type === types_1.UnitType.kSwitchingMotor);
}
setPreset(preset, temp) {
return __awaiter(this, void 0, void 0, function* () {
yield this.node.master.setPreset(this, preset, temp);
});
}
selectPreset(preset) {
return __awaiter(this, void 0, void 0, function* () {
yield this.node.master.selectPreset(this, preset);
});
}
sensorOnOff(on) {
return __awaiter(this, void 0, void 0, function* () {
yield this.node.master.setTempOnOff(this, on);
});
}
doIncDecPreset(inc) {
return __awaiter(this, void 0, void 0, function* () {
yield this.node.master.doIncDecPreset(this, inc);
});
}
setSensorValue(temp) {
return __awaiter(this, void 0, void 0, function* () {
yield this.node.master.setSensorValue(this, temp);
});
}
setSensorStatus(value) {
return __awaiter(this, void 0, void 0, function* () {
yield this.node.master.setSensorStatus(this, value);
});
}
inMultiNode() {
return this.node.inMultiNode();
}
reqState(callback) {
return __awaiter(this, void 0, void 0, function* () {
yield this.node.master.requestUnitStatus(this);
if (callback)
exports.Protocol.addSubscriber(callback, this);
});
}
setState(value) {
return __awaiter(this, void 0, void 0, function* () {
yield this.node.master.setUnitStatus(this, value);
});
}
getDispayState() {
switch (this.getType()) {
case types_1.UnitExtendedType.kDimmer:
return ((this.status) ? 'on' : 'off') + ' (' + this.value + '%)';
case types_1.UnitExtendedType.kSwitch:
case types_1.UnitExtendedType.kLightbulb:
return (this.status) ? 'on' : 'off';
case types_1.UnitExtendedType.kInput:
return (this.status) ? 'on' : 'off';
case types_1.UnitExtendedType.kPower:
const aval = Math.abs(this.sun * 100 * 100 * 100 +
this.hsun * 100 * 100 +
this.moon * 100 +
this.hmoon);
return (isNaN(aval) ? "-" :
((aval < 10000) ?
(aval + " W") :
(Math.round(aval / 1000) + "." + (0, types_1.two)(Math.round(aval % 1000 / 100)) + " kW")));
case types_1.UnitExtendedType.kTemperature:
return isNaN(this.value) ? "-" : ((this.value / 10.0) + 'C');
case types_1.UnitExtendedType.kCondition:
case types_1.UnitExtendedType.kMood:
return (this.status) ? 'on' : 'off';
case types_1.UnitExtendedType.kLock:
return (this.status) ? 'locked' : 'unlocked';
case types_1.UnitExtendedType.kUnlocker:
return (this.status) ? 'unlocking' : 'locked';
case types_1.UnitExtendedType.kGarageDoor:
case types_1.UnitExtendedType.kDoor:
case types_1.UnitExtendedType.kSwitchingMotor:
if (this.status === types_1.UnitState.kOpening) {
return 'opening';
}
if (this.status === types_1.UnitState.kClosing) {
return 'closing';
}
if (this.status === types_1.UnitState.kOpen) {
return 'open';
}
if (this.status === types_1.UnitState.kClosed) {
return 'closed';
}
if (this.status === types_1.UnitState.kStopped) {
return 'stopped';
}
}
return (typeof this.status != "undefined") ? this.status.toString() : 'unknown';
}
getDescription() {
return this.getDisplayName() + ", active: " + this.active + ", type: " + this.typeName() + ", status: " + this.status + ", value: " + this.value + " -> " + this.getDispayState();
}
}
exports.Unit = Unit;
// callbacks, waiting to be called when a status for them arrives
const subscribers = [];
exports.Protocol = {
emitter: null,
setEmitter(emitter) {
this.emitter = emitter;
},
/////////////////
// Subscribers //
/////////////////
alertSubscriber(unit) {
const inx = subscribers.findIndex(vs => vs.unit.isUnit(unit));
if (inx >= 0) {
subscribers[inx].deliver(unit);
subscribers.splice(inx, 1);
}
},
addSubscriber(deliver, unit) {
subscribers.push({ deliver, unit });
},
////////////////////
// Helper methods //
////////////////////
getStr: function (arr, at) {
return arr.slice(at + 1, at + arr[at] + 1)
.map(val => String.fromCharCode(val))
.join("");
},
makeWord: function (arr, at) {
return arr[at + 0] * 256 + arr[at + 1];
},
makeSignedWord: function (arr, at) {
if (arr[at + 0] > 127)
return (arr[at + 0] - 255) * 256 +
(arr[at + 1] - 255) - 1;
else
return arr[at + 0] * 256 + arr[at + 1];
},
makeLong: function (arr, at) {
return arr[at + 0] * 256 * 256 * 256 +
arr[at + 1] * 256 * 256 +
arr[at + 2] * 256 +
arr[at + 3];
},
makeSigned: function (arr, at) {
if (arr[at + 0] > 127)
return (arr[at + 0] - 255) * 256 * 256 * 256 +
(arr[at + 1] - 255) * 256 * 256 +
(arr[at + 2] - 255) * 256 +
(arr[at + 3] - 255) - 1;
else
return arr[at + 0] * 256 * 256 * 256 +
arr[at + 1] * 256 * 256 +
arr[at + 2] * 256 +
arr[at + 3];
},
signedToArray: function (value) {
if (value < 0)
return [256 + Math.floor(value / 256 / 256 / 256),
256 + Math.floor(value / 256 / 256) % 256,
256 + Math.floor(value / 256) % 256,
256 + Math.floor(value) % 256];
else
return [Math.floor(value / 256 / 256 / 256),
Math.floor(value / 256 / 256) % 256,
Math.floor(value / 256) % 256,
Math.floor(value) % 256];
},
signedWordToArray: function (value) {
if (value < 0)
return [256 + Math.floor(value / 256) % 256, 256 + Math.floor(value) % 256];
else
return [Math.floor(value / 256) % 256, Math.floor(value) % 256];
},
wordToArray: function (value) {
return [Math.floor(value / 256) % 256, Math.floor(value) % 256];
},
isStatus: function (cmd) {
return (cmd === Rec.Mood) ||
(cmd === Rec.Dimmer) ||
(cmd === Rec.Switch) ||
(cmd === Rec.Sensor) ||
(cmd === Rec.Motor) ||
(cmd === Rec.Macro);
},
//////////////////////////
// Code to String stuff //
//////////////////////////
translateError: function (err) {
if (err[0] != Rec.ErrorMessage)
return "received unexpected data: " + err;
switch (err[1]) {
case 11: return "Wrong object method received for " + err[2] + "/" + err[3];
case 12: return "Wrong Message Code received: " + err[2];
case 18: return "This function can only be executed when this node is the master";
case 128: return "The node database is not ready";
case 129: return "Node " + err[2] + " could not be found in the database";
case 130: return "Wrong node index: " + err[2];
case 131: return "Unit " + err[2] + " with address " + err[3] + " could not be found in the database";
case 132: return "Wrong unit index " + err[3] + " for this node " + err[2];
case 133: return "Unit " + err[3] + " of node " + err[2] + " is of a different type";
case 140: return "The requested operation is not allowed";
case 141: return "The requested operation is not allowed because a wrong access code is used";
case 142: return "The requested operation is not implemented in this software version";
default:
return "Unknown error";
}
},
////////////////////
// Socket methods //
////////////////////
write: function (socket, data) {
let cmd = parseInt(data[0]);
if (isNaN(cmd))
cmd = data[0];
if (data instanceof Array) {
data = data.join(",");
}
if (typeof data === "string") {
// if no enclosing "[...]", add them
if (data[0] != "[")
data = "[" + data + "]";
(0, logger_1.log)("protocol", "sending: " + cmdName(cmd) + " - " + data);
try {
// append a LF char and send
socket.write(data + String.fromCharCode(10));
return types_1.WriteError.writeOK;
}
catch (e) {
(0, logger_1.err)("protocol", "error sending through socket " + e.message);
return types_1.WriteError.writeFatal;
}
}
else {
throw (new Error("wrong data type for sending"));
}
},
//////////////////////////////
// Handle incoming data //
// strip [] //
// convert to array //
// convert chars to ints //
//////////////////////////////
nextMessage: function (buffer) {
// pre return result
const nextRec = { rest: buffer, isStatus: false, message: null, cmd: 0 };
// no "start-of-data" -> discard buffer || else -> trim buffer
const begin = buffer.indexOf("[");
if (begin < 0) {
nextRec.rest = "";
}
else if (begin != 0) {
nextRec.rest = buffer.substring(begin);
}
// we either have valid start data or nothing
if (nextRec.rest.length > 0) {
// do we have an "end-of-data" in our buffer
let end = nextRec.rest.indexOf("]");
// if no end-of-data was found:
// leave it in the buffer and hope more data will arrive soon
//TODO: set up a timer that clears the buffer if nothing comes through
if (end >= 0) {
// fetch the first available message (discard the [ and ])
const msg = nextRec.rest.substring(1, end);
// delete the used message from the input buffer
// if there, also delete the trailing LF (0x0A)
if ((end <= nextRec.rest.length) && (nextRec.rest.charCodeAt(end + 1) === 0x0A))
end++;
nextRec.rest = nextRec.rest.substring(end + 1);
// convert to array and turn strings into numbers if IP command
nextRec.message = msg.split(",").map(c => parseInt(c));
// get the first byte to see what kind of incoming data
nextRec.cmd = nextRec.message[0];
nextRec.isStatus = this.isStatus(nextRec.cmd);
(0, logger_1.debug)("protocol", "processing: " + (nextRec.isStatus ? "status -> " : "") + msg);
}
}
return nextRec;
},
buildLogin: function (password) {
password = password || "";
return [Cmd.Login, reqConnect, password.length,
...password.split('').map(c => c.charCodeAt(0))];
},
buildDisconnect: function () {
return [Cmd.Login, reqDisconnect];
},
buildHeartbeat: function () {
return [Cmd.Heartbeat, 1];
},
/////////////////////////////////////
// Collect info of all found nodes //
/////////////////////////////////////
buildDBInfo: function () {
return [Cmd.DatabaseInfo, reqDBInfo];
},
buildNodeInfo: function (nodeInx) {
return [Cmd.DatabaseInfo, reqNodeInfo, nodeInx];
},
buildUnitInfo: function (node, unitInx) {
return [Cmd.DatabaseInfo, reqUnitInfo, node.logicalAddress, unitInx];
},
buildRequestUnitStatus: function (node, unit) {
return [Cmd.DatabaseInfo, reqUnitStatus, node.logicalAddress, unit.logicalAddress, unit.type];
},
buildRequestSchedule() {
return [Cmd.reqSchedule, 0];
},
buildRequestNodeMgt(method = reqNodeAttributes.nodeProtocol) {
return [Cmd.reqNodeManagement, method];
},
getCmdAndMethod: function (unit, value) {
switch (unit.type) {
case types_1.UnitType.kDimmer:
if (typeof value === "boolean")
return { cmd: Cmd.SetDimmer, method: (value) ? reqOn : reqOff };
else if (value <= 0)
return { cmd: Cmd.SetDimmer, method: reqOff };
else
return { cmd: Cmd.SetDimmer, method: reqDim, value: Math.max(Math.min(value, 99), 1) };
case types_1.UnitType.kSwitch:
return { cmd: Cmd.SetSwitch, method: (value) ? 3 : 2 };
case types_1.UnitType.kInput:
case types_1.UnitType.kMood:
if (value < 0)
return { cmd: Cmd.SetControl, method: 2 }; // short pulse
else
return { cmd: Cmd.SetControl, method: 3, value: (value) ? 1 : 0 }; // long event + 0/1
case types_1.UnitType.kSwitchingMotor:
return { cmd: Cmd.SetMotor, method: value }; // 5 close, 4 open, 3 is stop
case types_1.UnitType.kTemperature:
return { cmd: Cmd.SetSensor, method: 13 /* select preset */, value };
case types_1.UnitType.kExtendedAudio:
case types_1.UnitType.kAudio:
case types_1.UnitType.kAV:
case types_1.UnitType.kIRTX:
case types_1.UnitType.kVideo:
default: // "Unknown unit type (" + unit.type + ")";
this.err("setting " + unit.type + " not yet implemented");
return { cmd: 0, method: 0, value: 0 };
}
},
buildSetCmd: function (node, unit, value) {
let params = this.getCmdAndMethod(unit, value);
if (params.cmd) {
params.message = [params.cmd, params.method, node.logicalAddress, unit.logicalAddress];
if (typeof params.value != "undefined")
params.message.push(params.value);
// some need a requestStatus afterwards
params.reqStatus = ((params.cmd === Cmd.SetDimmer) && (params.method === reqDim));
}
return params;
},
/* Temperature / Presets */
buildSelectPreset(node, unit, preset) {
return [Cmd.SetSensor, 13, node.logicalAddress, unit.logicalAddress, preset];
},
buildSetSensor(node, unit, value) {
return [Cmd.SetSensorValue, 10, node.logicalAddress, unit.logicalAddress, types_1.UnitType.kTemperature, ...this.wordToArray(value)];
},
buildSetPreset(node, unit, preset, value) {
return [Cmd.SetSensor, 1, node.logicalAddress, unit.logicalAddress, preset, ...this.wordToArray(value)];
},
buildIncDecPreset(node, unit, inc) {
return [Cmd.SetSensor, (inc) ? 5 : 6, node.logicalAddress, unit.logicalAddress];
},
buildSensorOnOff(node, unit, on) {
return [Cmd.SetSensor, 3, node.logicalAddress, unit.logicalAddress, (on) ? 1 : 0];
},
/* Schedule commands */
buildSendSchedule(schedule) {
return [Cmd.SetSchedule, 0, Math.max(0, Math.min(3, schedule))];
},
buildRegister(register, value) {
if (typeof value != "undefined") {
// set register
return [Cmd.Register, 1, ...this.wordToArray(register), ...this.signedToArray(value)];
}
else {
// get register
return [Cmd.Register, 2, ...this.wordToArray(register)];
}
},
///////////////////
// Received info //
///////////////////
receiveStatus: function (next, unit) {
let kind = "-";
if (next.cmd === Rec.Sensor) {
// sensor -> value
unit.value = this.makeWord(next.message, 9); // 10x current temperature
unit.status = next.message[7]; // 0=idle, 1=heating, 2=cooling
unit.preset = (next.message[6]) ? next.message[8] : -1; // 0=sun, 1=half sun, 2=moon, 3=half moon, -1 = off
unit.sun = this.makeWord(next.message, 11); // 10x temperature
unit.hsun = this.makeWord(next.message, 13); // 10x temperature
unit.moon = this.makeWord(next.message, 15); // 10x temperature
unit.hmoon = this.makeWord(next.message, 17); // 10x temperature
(0, logger_1.debug)("protocol", "received status - " + unit.getDisplayName() + ", temperature = " + unit.value / 10.0 + ", sun = " + unit.sun);
kind = "S";
// Dimmers, switches and moods have
// - status (0=off,1=on,2=pir-on)
// = value (true/false for switches and moods, 1-99 for dimmers)
}
else if (next.cmd === Rec.Switch) {
// switch -> boolean
unit.status = next.message[6];
unit.value = (next.message[6] > 0);
(0, logger_1.debug)("protocol", "received status - switch = " + unit.value);
kind = "S";
}
else if (next.cmd === Rec.Dimmer) {
// dimmer -> 0 .. 99
unit.status = next.message[6];
unit.value = next.message[7];
(0, logger_1.debug)("protocol", "received status - dimmer -> value=" + unit.value + " / status=" + unit.status);
kind = "S";
}
else if (next.cmd === Rec.Mood) {
// control -> boolean
unit.status = next.message[6];
unit.value = (next.message[6] != 0);
(0, logger_1.debug)("protocol", "received status - mood = " + unit.value);
kind = "S";
}
else if (next.cmd === Rec.Motor) {
// motor -> boolean/status
// [38,0,252,104,8,0,0]
// 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
unit.status = next.message[6];
unit.value = next.message[6];
(0, logger_1.debug)("protocol", "received status - motor = " + unit.value);
kind = "S";
}
else if (next.cmd === Rec.Macro) {
// = EV_UNITMACROCOMMANDO
// examples: On 50%: [69,0,NodeAddress,UnitAddress,6,1,0,50]
// Off: [69,0,NodeAddress,UnitAddress,6,0,0,0] -> don't touch dimmer value
// unit_type_duoswitch -> Event = 32, state 0, 1, 2
// STATUS (Event: 7 = dimmer, 5 = switch, on = 1, off = 0)
if (((next.message[4] == 7) || (next.message[4] == 5)) && (next.message[5] == 1)) {
// PIR ON -- override status value with "2" (our PIR ON)
unit.status = 2;
}
else if ((next.message[4] < 8) || (next.message[4] == 10)) {
// ON/OFF messages, including sensor types (off/heating/cooling)
unit.status = next.message[5];
}
// VALUE
// only change dim value when state = 1 (ON, PIR ON, DIM STOP) for Dimmers and IRTX + for any setpoint event
if ((((next.message[4] === 1) || (next.message[4] === 6) || (next.message[4] === 7)) && (next.message[5] === 1)) ||
(next.message[4] === 11)) {
unit.value = this.makeWord(next.message, 6);
}
else if (next.message[4] === 32) {
// unit_type_duoswitch -> Event = 32
// [69,0,252,104,32,2,0,0]
// state = 0 -> stop, state = 1 -> up, state = 2 -> down
unit.value = 10 + next.message[5];
unit.status = next.message[5];
}
(0, logger_1.debug)("protocol", "received status - macro -> value=" + unit.value + " / status=" + unit.status);
kind = "M";
}
this.alertSubscriber(unit);
this.emitter.emit('update', unit, kind);
},
makeDBInfo(res) {
return { nrNodes: res[2] };
},
makeNodeInfo(res) {
let name = this.getStr(res, 8);
let offset = name.length;
return {
name: name,
index: res[2],
logicalAddress: res[3],
physicalAddress: this.makeLong(res, 4),
nrUnits: res[offset + 9],
type: res[offset + 10],
flags: res[offset + 11]
};
},
makeUnitInfo(res) {
let name = this.getStr(res, 6);
let offset = name.length;
return {
name,
index: res[3],
logicalNodeAddress: res[4],
logicalAddress: res[5],
type: res[offset + 7],
flags: res[offset + 8]
};
}
};
//# sourceMappingURL=protocol.js.map