UNPKG

homebridge-smartsystem

Version:

SmartServer (Proxy TCP sockets to the cloud, Smappee MQTT, Duotecno IP Nodes, Homekit interface)

295 lines 12.6 kB
"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.Smappee = void 0; const mqtt = require("mqtt"); const types_1 = require("../duotecno/types"); const logger_1 = require("../duotecno/logger"); const powerbase_1 = require("./powerbase"); // Smappee MQTT implementation // Johan Coppieters, Jan 2019. // v2: rules can now add multiple channels, Jan 2021 // // Testing Raspberry: // mqtt sub -t '#' -h '192.168.99.75' -v => uid // mqtt sub -t 'servicelocation/57e3e0d8-bb05-4b04-8662-1a9871998f3f/#' -h '192.168.99.54' -v // // Testing Mac: // /Users/johan/.nvm/versions/node/v10.16.0/bin/mqtt sub -t 'servicelocation/57e3e0d8-bb05-4b04-8662-1a9871998f3f/#' -h '192.168.99.54' -v // // see smappee.json for output class Smappee extends powerbase_1.PowerBase { constructor(system) { super("smappee", system); this.client = null; // init is called by super } // // start and restart services // init(firstTime) { const _super = Object.create(null, { init: { get: () => super.init } }); return __awaiter(this, void 0, void 0, function* () { // will grow when we encounter one in the mqtt stream or if we receive a config message this.plugs = {}; return _super.init.call(this, firstTime); }); } // // return data for ejs pages // getData(context, message) { const _super = Object.create(null, { getData: { get: () => super.getData } }); return __awaiter(this, void 0, void 0, function* () { const data = yield _super.getData.call(this, context, message); // add plugs to context data["plugs"] = this.plugs; return data; }); } // // config mgt // updateConfig(config) { const _super = Object.create(null, { updateConfig: { get: () => super.updateConfig } }); return __awaiter(this, void 0, void 0, function* () { this.config.uid = config.uid; return _super.updateConfig.call(this, config); }); } // // subscribe to mqtt stream // subscribe() { return __awaiter(this, void 0, void 0, function* () { if (this.config.address && this.config.uid) return new Promise((resolve, reject) => { try { const client = mqtt.connect('mqtt:' + this.config.address); this.client = client; (0, logger_1.log)("smappee", "connecting to " + 'mqtt:' + this.config.address); client.on('connect', () => { client.subscribe('servicelocation/' + this.config.uid + '/#', (error) => { if (error) { (0, logger_1.err)("smappee", error.message); reject(); } else { (0, logger_1.log)("smappee", "subscribed to " + 'servicelocation/' + this.config.uid + "/# -- all messages"); resolve(); } }); }); client.on('message', (topic, buffer) => { // example topics: // servicelocation/57e3e0d8-bb05-4b04-8662-1a9871998f3f/plug/1/state // servicelocation/57e3e0d8-bb05-4b04-8662-1a9871998f3f/realtime try { const message = JSON.parse(buffer.toString()); const parts = topic.split("/"); if (parts.length > 2) { if (this.isRealTime(parts)) // && (message.utcTimeStamp % 5000 === 0)) this.processRealTime(message); else if (this.isPlug(parts)) this.processPlug(this.getPlugNr(parts), message); else if (this.isHomeControl(parts)) this.processHomeControl(message); else if (this.isChannelConfig(parts)) this.processChannelConfig(message); } } catch (error) { (0, logger_1.err)("smappee", "Error converting incoming : " + topic + " -> " + JSON.stringify(error) + " -> " + buffer.toString()); } }); } catch (err) { err("smappee", err); reject(); } }); }); } // // Alternative in above, more specific subscribes, needs also separate on-message-handlers // + don't forget also the home-control and channel-config messages. // // client.subscribe('servicelocation/' + uid + '/realtime', (error) => { // if (error) { // err("smappee", error.message); // } else { // log("smappee", "subscribed to " + 'servicelocation/' + uid + "/realtime"); // } // }); // client.subscribe('servicelocation/' + uid + '/plug/#', (error) => { // if (error) { // err("smappee", error.message); // } else { // log("smappee", "subscribed to " + 'servicelocation/' + uid + "/plug/#"); // } // }); unsubscribe() { return __awaiter(this, void 0, void 0, function* () { if (this.client) { return new Promise((resolve, reject) => { try { this.client.end(true, () => { (0, logger_1.log)("smappee", "unsubscribed from the MQTT stream"); resolve(); }); } catch (err) { err("smappee", err); reject(); } this.client = null; }); } }); } isRealTime(parts) { return (parts.length >= 3) && (parts[2] === "realtime"); } isPlug(parts) { return (parts.length >= 3) && (parts[2] === "plug"); } isHomeControl(parts) { return (parts.length >= 3) && (parts[2] === "homeControlConfig"); } isChannelConfig(parts) { return (parts.length >= 3) && (parts[2] === "channelConfigV2"); } getPlugNr(parts) { return (parts.length >= 3) ? parseInt(parts[3]) : 0; } processChannelConfig(message) { // console.log("ChannelConfig", message.dataProcessingSpecification.measurements); this.channels = {}; message.dataProcessingSpecification.measurements.forEach(m => { const inx = m.publishIndex; if (inx >= 0) { if (!this.channels[inx]) this.channels[inx] = {}; this.channels[inx].name = m.name; this.channels[inx].type = m.type; this.channels[inx].flow = m.flow; } }); } processHomeControl(message) { message.switchActuators.forEach(s => { const id = parseInt(s.nodeId); if (!this.switches[id]) this.switches[id] = { value: false, name: s.name }; else this.switches[id].name = s.name; }); message.smartplugActuators.forEach(s => { const id = parseInt(s.nodeId); if (!this.plugs[id]) this.plugs[id] = { value: false, name: s.name }; else this.plugs[id].name = s.name; }); } processRealTime(message) { this.realtimeCounter++; // update every 5 messages if (this.realtimeCounter % 5 === 0) { this.realtime = { totalPower: message.totalPower, totalReactivePower: message.totalReactivePower, totalExportEnergy: message.totalExportEnergy, totalImportEnergy: message.totalImportEnergy, monitorStatus: message.monitorStatus, utcTimeStamp: message.utcTimeStamp }; message.voltages.forEach(v => { this.voltages[v.phaseId] = v.voltage; }); message.channelPowers.forEach(p => { const inx = p.publishIndex; if (!this.channels[inx]) { // if we didn't receive a config message, give fake names and type. this.channels[inx] = { name: "CH-" + inx, flow: "-", type: "-" }; } this.channels[inx].power = p.power; this.channels[inx].exportEnergy = p.exportEnergy; this.channels[inx].importEnergy = p.importEnergy; this.channels[inx].phaseId = p.phaseId; this.channels[inx].voltage = this.voltages[p.phaseId] || 0; this.channels[inx].current = p.current; this.channels[inx].apparentPower = p.apparentPower; this.channels[inx].cosPhi = p.cosPhi; this.channels[inx].formula = p.formula; }); this.calcProducAndConsumption(message); } // check rules this.applyRules(); // check bindings this.applyBindings(); } calcProducAndConsumption(message) { this.production = 0; this.consumption = 0; message.channelPowers.forEach(p => { const channel = this.channels[p.publishIndex]; if (channel) { if (channel.flow == "PRODUCTION") { this.production += channel.power; } else { this.consumption += channel.power; } } }); // consumption seems to be wrong... ?? this.consumption = this.realtime.totalPower; } /////////// // Plugs // /////////// emptyPlug(nr) { return { value: null, name: "New plug - " + nr }; } processPlug(plugNr, message) { const newState = (message.value == "ON"); if (!this.plugs[plugNr]) this.plugs[plugNr] = this.emptyPlug(plugNr); if (this.plugs[plugNr].value != newState) { (0, logger_1.debug)("smappee", "doPlug, plugNr = " + plugNr + ", received: " + message.value + ", current: " + this.plugs[plugNr].value); this.plugs[plugNr].value = newState; // send status change to system this.system.emitter.emit('plug', types_1.SwitchType.kSmappee, plugNr, newState); } } setPlug(plugNr, state) { if (!this.plugs[plugNr]) this.plugs[plugNr] = this.emptyPlug(plugNr); const currState = this.plugs[plugNr].value; if ((typeof currState === "boolean") && (state != currState)) { // do we need to do this? do we get an mqtt status message ?? this.plugs[plugNr].value = currState; if (this.client) { const topic = 'servicelocation/' + this.config.uid + '/plug/' + plugNr + '/setstate'; const payload = '{"value": "' + ((state) ? "ON" : "OFF") + '", "since": "' + (new Date().getTime()) + '"}'; this.client.publish(topic, payload); } else { (0, logger_1.err)("smappee", "no open connection (yet?) to " + this.config.uid + " on " + this.config.address); } } } } exports.Smappee = Smappee; //# sourceMappingURL=smappee.js.map