UNPKG

homebridge-smartsystem

Version:

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

1,053 lines 56.4 kB
"use strict"; // SmartApp implementation with Webapp // Purpose: // - select IP nodes & units to include -> generate config file // - attach Smappee and rules -> update config file // - control units from the IP nodes as test // // Johan Coppieters, Feb 2019. // // v2.0: mar/apr 2020 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.SmartApp = void 0; const webapp_1 = require("./webapp"); const types_1 = require("../duotecno/types"); const logger_1 = require("../duotecno/logger"); const protocol_1 = require("../duotecno/protocol"); const somfy = require("./somfy"); const fs_1 = require("fs"); const config_1 = require("../duotecno/config"); const HB_1 = require("./HB"); const HA_API_1 = require("./HA-API"); const proxy_1 = require("./proxy"); const kMaster = { name: "master", type: "string", default: "0.0.0.0:5001" }; const kAddress = { name: "address", type: "string", default: "0.0.0.0" }; const kAddresses = { name: "addresses", type: "string", default: "[0.0.0.0]" }; const kAddressOld = { name: "addressX", type: "string", default: "0.0.0.0" }; const kPort = { name: "port", type: "integer", default: 80 }; const kPortOld = { name: "portX", type: "integer", default: 80 }; const kActive = { name: "active", type: "string", default: "N" }; const kUID = { name: "uid", type: "string", default: "" }; const kName = { name: "name", type: "string", default: "no name" }; const kPassword = { name: "password", type: "string", default: "no password" }; const kNode = { name: "node", type: "integer", default: 0 }; const kUnit = { name: "unit", type: "integer", default: 0 }; const kValue = { name: "value" }; const kIntValue = { name: "value", type: "integer", default: 0 }; const kPin = { name: "pin", type: "string", default: "577-03-001" }; // Mac file system -> write locally const isMac = (0, fs_1.existsSync)("/Volumes"); const kDHCPConfigFile = isMac ? "./config.dhcpcd" : "/etc/dhcpcd.conf"; class SmartApp extends webapp_1.WebApp { constructor(system, power, platform) { super("smartapp"); this.system = system; // get status change updates this.system.emitter.on('update', this.informChange.bind(this)); this.system.emitter.on('update', this.informLinks.bind(this)); this.system.emitter.on('plug', this.alertSwitch.bind(this)); // get some configurated params this.port = this.config.port || this.port || 80; this.password = platform.config.password || this.config.password || ""; this.user = platform.config.user || this.config.user || "pi"; this.system = system; this.power = power; this.platform = platform; // when all masters are loaded -> attach units to the switches this.system.emitter.on('ready', this.initSwitchUnits.bind(this)); this.system.emitter.on('ready', this.initLinks.bind(this)); this.addFile("unitList", __dirname + "/views/unit-list.ejs", "application/json"); this.addFile("masterList", __dirname + "/views/master-list.ejs", "text/html"); this.addFile("masterDetail", __dirname + "/views/master-detail.ejs", "text/html"); this.addFile("nodeDetail", __dirname + "/views/node-details.ejs", "text/html"); this.addFile("serviceList", __dirname + "/views/service-list.ejs", "text/html"); this.addFile("power", __dirname + "/views/power.ejs", "text/html"); this.addFile("login", __dirname + "/views/login.ejs", "text/html"); this.addFile("settings", __dirname + "/views/settings.ejs", "text/html"); this.addFile("switchDetail", __dirname + "/views/switch-details.ejs", "text/html"); this.addFile("switchList", __dirname + "/views/switch-list.ejs", "text/html"); this.addFile("linkDetail", __dirname + "/views/link-details.ejs", "text/html"); this.addFile("linkList", __dirname + "/views/link-list.ejs", "text/html"); this.addFile("powerRule", __dirname + "/views/power-rule.ejs", "text/html"); this.addFile("powerBinding", __dirname + "/views/power-binding.ejs", "text/html"); this.addFile("materializeCSS", __dirname + "/views/assets/materialize.min.css", "text/css"); this.addFile("materializeJS", __dirname + "/views/assets/materialize.min.js", "text/javascript"); this.addFile("favicon", __dirname + "/views/assets/favicon.ico", "image/x-icon"); this.addFile("proxy", __dirname + "/views/proxy.ejs", "text/html"); } writeConfig() { // copy switches into config, eliminate the runtime stuff (like unit) this.config.switches = this.switches.map(s => types_1.Sanitizers.makeSwitchConfig(s)); this.config.links = this.links.map(s => types_1.Sanitizers.makeLinkConfig(s)); super.writeConfig(); } readConfig() { super.readConfig(); // copy switches from config this.switches = this.config.switches.map(s => types_1.Sanitizers.switchConfig(s)); this.links = this.config.links.map(s => types_1.Sanitizers.linkConfig(s)); } checkReady(context) { if (this.platform && !this.platform.ready) { context["notReady"] = true; context["notReadyMessage"] = "=== waiting >> found " + this.system.allActiveUnits().length + " units out of " + this.system.activeUnitsConfig().length + " selected after " + this.platform.startWaiting + " sec ==="; (0, logger_1.log)("smartapp", "Platform not ready -> " + context["notReadyMessage"]); } else { context["notReady"] = false; context["notReadyMessage"] = ""; } } ////////////// // password // ////////////// pwOK(context, user, pw) { return __awaiter(this, void 0, void 0, function* () { // to bypass user/pw -> //return true; this.token = ""; (0, config_1.setToken)(""); try { const access_token = yield this.checkLogin(this.config.apiHost + ":" + this.config.apiPort, user, pw); if (access_token) { // save username / password in config this.config.username = user; this.config.password = pw; this.writeConfig(); this.token = access_token; (0, config_1.setToken)(this.token); return true; } } catch (e) { (0, logger_1.debug)("smartapp", "Login API failed " + JSON.stringify(e)); } return false; }); } ////////////////////////////// // Router // ////////////////////////////// needsLogin(context) { const noNeed = ((context.request === "units") || (context.request === "switches") || (context.request === "links")) && ((context.action == "press") || (context.action == "get") || (context.action == "set")); return super.needsLogin(context) && (context.request != "files") && (!noNeed); } doRequest(context) { const _super = Object.create(null, { doRequest: { get: () => super.doRequest } }); return __awaiter(this, void 0, void 0, function* () { this.checkReady(context); if (context.request === "") context.request = "masters"; context["hasSmappee"] = !!this.power.smappee; context["hasP1"] = !!this.power.p1; context["hasShelly"] = !!this.power.shelly; // try "[get]/set" url: node/unit[/status] const res = yield this.tryNumURL(context); if (res) { return res; } else if (context.request === "files") { return this.renderAssets(context); } else if (context.request === "images") { return this.renderImage(context); } else if (context.request === "masters") { return this.doMasters(context); } else if (context.request === "units") { return this.doUnits(context); } else if (context.request === "services") { return this.doServices(context); } else if (context.request === "power") { return this.doPower(context); } else if (context.request === "links") { return this.doLinks(context); } else if (context.request === "switches") { return this.doSwitches(context); } else if (context.request === "settings") { return this.doSettings(context); } else if (context.request === "proxy") { return this.doProxy(context); } else { return _super.doRequest.call(this, context); } }); } tryNumURL(context) { return __awaiter(this, void 0, void 0, function* () { const node = context.nums[0]; const master = this.system.masters[0]; if ((!node) || (!master)) return null; const unit = context.nums[1]; const state = context.nums[2]; if (typeof state === "undefined") return this.json(yield this.getState(master, node, unit)); else return this.json(yield this.setState(master, node, unit, state)); }); } scrapeUnit(context, boundary) { context.getMaster("action"); const master = this.system.findMaster(context["masterAddress"], context["masterPort"]); let unit = null; let logicalNodeAddress, logicalAddress; let name = context.getParam({ name: "unit" + boundary, type: "string", default: "--" }); const value = (0, types_1.actionValue)(context.getParam({ name: "value" + boundary, type: "string", default: "0" })); // hex addresses or name if ((name[0] === "0") && (name[1] === "x")) { ({ logicalNodeAddress, logicalAddress } = context.addr(name)); unit = this.system.findUnit(master, logicalNodeAddress, logicalAddress); name = (unit) ? unit.displayName : "--"; } else { unit = this.system.findUnitByName(master, name); logicalNodeAddress = (unit) ? unit.node.logicalAddress : 0; logicalAddress = (unit) ? unit.logicalAddress : 0; } return { name, value, masterAddress: context["masterAddress"], masterPort: context["masterPort"], logicalAddress, logicalNodeAddress }; } /////////// // Proxy // /////////// doProxy(context) { return __awaiter(this, void 0, void 0, function* () { let config = this.config.proxy || proxy_1.kEmptyProxy; if (context.action === "save") { // save the proxy config config.cloudServer = context.getParam({ name: "cloudServer", type: "string" }); config.cloudPort = context.getParam({ name: "cloudPort", type: "integer" }); config.masterAddress = context.getParam({ name: "masterAddress", type: "string" }); config.masterPort = context.getParam({ name: "masterPort", type: "integer" }); config.uniqueId = context.getParam({ name: "uniqueId", type: "string" }); this.write("proxy", config); (0, proxy_1.setProxyConfig)(config); } else if (context.action === "restart") { (0, proxy_1.cleanStart)(true); } else { // just show the proxy config } return this.ejs("proxy", context, { config }); }); } ////////////// // Settings // ////////////// doSettings(context) { return __awaiter(this, void 0, void 0, function* () { let config = this.read("settings"); if (context.action === "save") { config = this.scrapeSettings(context); this.write("settings", config); } else if (context.action === "restart") { context.request = "restart"; return this.doRestart(false); } else if (context.action === "install") { config = this.scrapeSettings(context); context.request = "install"; this.write("settings", config); this.writeDHCP(context, config); } else if (context.action === "reset") { config = Object.assign({}, types_1.kEmptySettings); this.resetDHCP(context); this.write("settings", config); return this.doReboot(context, false); } else if (context.action === "reboot") { return this.doReboot(context, false); } else { } return this.ejs("settings", context, { config }); }); } scrapeSettings(context) { return types_1.Sanitizers.settings({ network: { ip1: context.getParam({ name: "ip1", type: "string" }), gateway1: context.getParam({ name: "gateway1", type: "string" }), nameservers: context.getParam({ name: "nameservers", type: "string" }), ip2: context.getParam({ name: "ip2", type: "string" }), gateway2: context.getParam({ name: "gateway2", type: "string" }) } }); } writeDHCP(context, config) { try { this.resetDHCP(context); (0, fs_1.writeFileSync)(kDHCPConfigFile, this.DHCPdFile(config.network)); } catch (err) { context.message = JSON.stringify(err); } } resetDHCP(context) { try { if ((0, fs_1.existsSync)(kDHCPConfigFile)) (0, fs_1.truncateSync)(kDHCPConfigFile); } catch (err) { context.message = JSON.stringify(err); } } DHCPdFile(config) { const ip1 = "interface eth0\n" + "static ip_address=" + config.ip1 + "\n" + "static routers=" + config.gateway1 + "\n\n" + "static domain_name_servers=" + config.nameservers; if (config.ip2) { return ip1 + "\n\n" + "interface eth0:0\n" + "static ip_address=" + config.ip2 + "\n" + "static routers=" + config.gateway2; } else { return ip1; } } ////////////////////////////// // Power Stuff // ////////////////////////////// doPower(context) { return __awaiter(this, void 0, void 0, function* () { let message; const id = parseInt(context.id); const type = context.getParam({ name: "type", type: "string", default: "smappee" }); const power = this.power[type]; if (!power) { return this.error(context, "Power type not implemented", false); } // remember the power-type context["type"] = type; // if not found or action done -> drop through and list again the power attributes + all rules try { if (context.action === "add") { const rule = power.newRule(); return this.ejs("powerRule", context, { rule, id: power.rules.length - 1, masters: this.system.masters, type }); } else if (context.action === "rule") { if (id >= 0) return this.ejs("powerRule", context, { rule: power.rules[id], id, masters: this.system.masters, type }); } else if (context.action === "delete") { if (id >= 0) power.deleteRule(id); } else if (context.action === "change") { power.updateRule(id, this.scrapeRule(context)); } else if (context.action === "save") { power.updateConfig({ address: context.getParam(kAddress), uid: context.getParam(kUID), addresses: context.getParam(kAddresses) }); } else if (context.action === "badd") { const binding = power.newBinding(); return this.ejs("powerBinding", context, { binding, id: power.bindings.length - 1, masters: this.system.masters, type }); } else if (context.action === "binding") { if (id >= 0) return this.ejs("powerBinding", context, { binding: power.bindings[id], id, masters: this.system.masters, type }); } else if (context.action === "bdelete") { if (id >= 0) power.deleteBinding(id); } else if (context.action === "bchange") { power.updateBinding(id, this.scrapeBinding(context)); } else { return this.ejs("power", context, yield power.getData(context, "")); } } catch (e) { message = e.toString(); } return this.ejs("power", context, yield power.getData(context, "")); }); } scrapeBinding(context) { context.getMaster("action"); const register = context.getParam({ name: "register", type: "integer", default: 0 }); const channel = context.getParam({ name: "channel", type: "string", default: "0" }); return { masterAddress: context["masterAddress"], masterPort: context["masterPort"], register, channel }; } scrapeAction(context, boundary) { const { name, value, masterAddress, masterPort, logicalAddress, logicalNodeAddress } = this.scrapeUnit(context, boundary); return { name, value, masterAddress, masterPort, logicalAddress, logicalNodeAddress }; } scrapeRule(context) { // deep copy an empty rule let rule = Object.assign({}, types_1.kEmptyRule); // = shallow copy, re-assign actions later // get the form values rule.type = context.getParam({ name: "rtype", type: "string", default: rule.type }); rule.channel = context.getParam({ name: "channel", type: "string", default: rule.channel }); rule.low = context.getParam({ name: "low", type: "integer", default: rule.low }); rule.high = context.getParam({ name: "high", type: "integer", default: rule.high }); rule.actions = [this.scrapeUnit(context, "low"), this.scrapeUnit(context, "mid"), this.scrapeUnit(context, "high")]; return rule; } /////////// // Links // /////////// initLinks() { (0, logger_1.log)("smartapp", "Init " + this.links.length + " Links -> add units"); this.links.forEach(link => { link.unit || (link.unit = this.system.findUnit(this.system.findMaster(link.masterAddress, link.masterPort), link.logicalNodeAddress, link.logicalAddress)); if (link.unit) { link.dtValue = link.unit.value; } else { (0, logger_1.log)("smartapp", "** error ** missing unit: " + (0, types_1.hex)(link.logicalNodeAddress) + "/" + (0, types_1.hex)(link.logicalAddress) + " **"); } }); (0, HB_1.hbSubscribe)(() => __awaiter(this, void 0, void 0, function* () { // Get all values for subscribed accessories (links) if (this.links.length && (yield this.checkToken())) { for (const link of this.links) { if ((link.type === types_1.LinkType.kAccessory) && link.unit) { const hbValue = yield (0, HB_1.hbGetValue)(this.config.apiHost + ":" + this.config.apiPort, link.accId, link.accService, this.token); (0, logger_1.debug)("smartapp", "Received HB value for accessory: " + link.accName + " -> " + hbValue + " from accessory"); if (typeof hbValue != "undefined") { const { dtValue, dtTarget } = (0, HB_1.hb2dtvalue)(link.unit, hbValue); if (dtValue !== link.dtValue) { // || state !== link.status (0, logger_1.log)("smartapp", "-> Setting value of Duotecno unit: " + link.unit.getDisplayName() + " to " + dtTarget + " of HB accessory: " + link.accName + " with value " + hbValue); link.dtValue = dtValue; link.unit.setState(dtTarget); } else { (0, logger_1.debug)("smartapp", "-> No change for unit: " + link.unit.getDisplayName() + " to " + dtValue); } } } } } })); } informLinks(u, kind) { return __awaiter(this, void 0, void 0, function* () { (0, logger_1.debug)("smartapp", "Received DT value from unit: " + u.getDisplayName() + " -> " + u.value + " / " + u.status + " from unit"); for (const link of this.links) { if (u.isUnit(link.masterAddress, link.masterPort, link.logicalNodeAddress, link.logicalAddress)) { const hbValue = (0, HB_1.dt2hbvalue)(u, link.hbValue, link.min, link.max); if (typeof hbValue != "undefined") { if (hbValue === -1) { (0, logger_1.log)("smartapp", "-> Virtual unit send 'stop' for accessory: " + link.accName + " of Duotecno unit: " + u.getDisplayName()); const val = yield (0, HB_1.hbStop)(this.config.apiHost + ":" + this.config.apiPort, link.accId, link.accService, this.token); if (typeof val != "undefined") link.hbValue = val; } else if (hbValue !== link.hbValue) { (0, logger_1.log)("smartapp", "-> Setting HB value for accessory: " + link.accName + " to " + hbValue + " of Duotecno unit: " + u.getDisplayName() + " with value " + u.value); link.hbValue = hbValue; yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.accId, link.accService, hbValue, this.token); } else { (0, logger_1.debug)("smartapp", "-> No change for accessory: " + link.accName + " to " + u.value + " / " + u.status); } } } } ; }); } checkToken() { return __awaiter(this, void 0, void 0, function* () { if (!this.token) { const access_token = yield (0, HB_1.hbLogin)(this.config.apiHost + ":" + this.config.apiPort, this.config.username, this.config.password); if (!access_token) { (0, logger_1.err)("smartapp", "checkToken: failed to get token"); return false; } this.token = access_token; (0, config_1.setToken)(this.token); } return true; }); } getAccessories() { return __awaiter(this, void 0, void 0, function* () { if (yield this.checkToken()) return (0, HB_1.hbAccessories)(this.config.apiHost + ":" + this.config.apiPort, this.token); else return []; }); } doLinks(context) { return __awaiter(this, void 0, void 0, function* () { let inx = parseInt(context.id); const accessories = yield this.getAccessories(); const units = this.system.allUsedUnits(); let message; try { if (context.action === "add") { this.links.push(Object.assign(Object.assign({}, types_1.kEmptyLink), { type: types_1.LinkType.kAccessory })); inx = this.links.length - 1; return this.ejs("linkDetail", context, { link: this.links[inx], id: inx, masters: this.system.masters, accessories, units }); } else if (context.action === "edit") { return this.ejs("linkDetail", context, { link: this.links[inx], id: inx, masters: this.system.masters, accessories, units }); } else if (context.action === "delete") { this.deleteLink(inx); } else if (context.action === "change") { this.updateLink(inx, this.scrapeLink(context)); } else { // possible new IP Nodes, hence Units could be online this.initLinks(); } } catch (e) { message = e.toString(); } return this.ejs("linkList", context, { masters: this.system.masters, links: this.links, message }); }); } scrapeLink(context) { // don't take the name of the unit const { masterAddress, masterPort, logicalAddress, logicalNodeAddress } = this.scrapeUnit(context, ''); // for now only accessory links. const stype = context.getParam({ name: "type", type: "string", default: types_1.LinkType.kAccessory }); const name = context.getParam({ name: "name", type: "string", default: "--" }); // max & min values const min = context.getParam({ name: "min", type: "integer", default: 0 }); const max = context.getParam({ name: "max", type: "integer", default: 100 }); // uniqiue id for the accessory const accId = context.getParam({ name: "accId", type: "string", default: "" }); const accName = (0, HB_1.hbName)(accId); const accService = (0, HB_1.hbService)(accId); return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype, min, max }; ; } updateLink(inx, link) { if ((inx >= 0) && (inx < this.links.length)) { this.links[inx] = link; this.initLinks(); this.writeConfig(); } } deleteLink(inx) { if ((inx >= 0) && (inx < this.switches.length)) { this.links.splice(inx, 1); this.initLinks(); this.writeConfig(); } } setLink(inx, newstate, newvalue) { return __awaiter(this, void 0, void 0, function* () { // find the switch if an index is given let link = null; if (typeof inx === "number") { if ((inx >= 0) && (inx < this.links.length)) { link = this.links[inx]; } } else { // a Link was passed as first param link = inx; } // check if state is given if (typeof newstate != "undefined") { link.unit.status = !!newstate; } if (typeof newvalue != "undefined") { link.unit.value = +newvalue; } if (!link) { (0, logger_1.err)("smartapp", "Didn't find link with inx: " + inx); } else if (!link.unit) { (0, logger_1.err)("smartapp", "Don't have unit for link: " + link.unitname); } else { if (link.type == types_1.LinkType.kAccessory) { if (yield this.checkToken()) yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.id, link.service, link.unit.value, this.token); } else { (0, logger_1.err)("smartapp", "Don't know how to set a link of type " + link.type); } } }); } ////////////// // Switches // ////////////// initSwitchUnits() { (0, logger_1.log)("smartapp", "Init " + this.switches.length + " Switches -> add units"); const smappee = this.power.smappee; this.switches.forEach(swtch => { swtch.unit = swtch.unit || this.system.findUnit(this.system.findMaster(swtch.masterAddress, swtch.masterPort), swtch.logicalNodeAddress, swtch.logicalAddress); if ((smappee) && (swtch.type === types_1.SwitchType.kSmappee)) { for (let key in smappee.plugs) { // convert to numbers, better be safe then missing one... const p = (typeof swtch.plug === "string") ? parseInt(swtch.plug) : swtch.plug; const k = (typeof key === "string") ? parseInt(key) : key; if (k === p) swtch.value = smappee.plugs[key].value; } ; } else if ((swtch.type === types_1.SwitchType.kHTTPDimmer) || (swtch.type === types_1.SwitchType.kHTTPSwitch) || (swtch.type === types_1.SwitchType.kHTTPUpDown)) { if (swtch.unit) { swtch.value = swtch.unit.value; swtch.status = swtch.unit.status; } else { (0, logger_1.log)("smartapp", "** error ** missing unit: " + (0, types_1.hex)(swtch.logicalNodeAddress) + "/" + (0, types_1.hex)(swtch.logicalAddress) + " **"); } } }); } alertSwitch(type, plugNr, value) { (0, logger_1.debug)("smartapp", "Received " + type + " switch status change: " + plugNr + " -> " + value); if ((this.power.smappee) && (type === types_1.SwitchType.kSmappee)) { this.switches.forEach(swtch => { if ((swtch.type == types_1.SwitchType.kSmappee) && (swtch.plug == plugNr) && swtch.unit) { (0, logger_1.debug)("smartapp", " -> Switch was attached to unit = " + swtch.unit.getDisplayName() + " -> setting state to " + value); swtch.unit.setState(value); } }); } } doSwitches(context) { return __awaiter(this, void 0, void 0, function* () { let inx = parseInt(context.id); let message; try { if (context.action === "add") { this.switches.push(types_1.kEmptySwitch); inx = this.switches.length - 1; return this.ejs("switchDetail", context, { config: this.config, swtch: this.switches[inx], masters: this.system.masters, id: inx }); } else if (context.action === "edit") { return this.ejs("switchDetail", context, { rule: types_1.kEmptyRule, swtch: this.switches[inx], masters: this.system.masters }); } else if (context.action === "delete") { this.deleteSwitch(inx); } else if (context.action === "change") { this.updateSwitch(inx, this.scrapeSwitch(context)); } else if (context.action === "set") { const state = context.getParam({ name: "state", type: "string", default: "N" }); const value = context.getParam({ name: "value", type: "integer", default: 0 }); this.setSwitch(inx, (state === "Y"), value); return this.json({ switch: inx, state, value }); } else { // possible new IP Nodes, hence Units could be online this.initSwitchUnits(); } } catch (e) { message = e.toString(); } return this.ejs("switchList", context, { masters: this.system.masters, switches: this.switches, message }); }); } informChange(u, kind) { // kind should be "-", "S"tatus, "M"acro this.switches.forEach(swtch => { if (u.isUnit(swtch.masterAddress, swtch.masterPort, swtch.logicalNodeAddress, swtch.logicalAddress)) { if (!((kind === "M") && (swtch.nomacro === "Y"))) this.setSwitch(swtch); else (0, logger_1.debug)("smartapp", "*** macro changed blocked by switch"); } }); } scrapeSwitch(context) { const { name: unitName, masterAddress, masterPort, logicalAddress, logicalNodeAddress } = this.scrapeUnit(context, ''); const plug = context.getParam({ name: "plug", type: "string", default: "0" }); const nomacro = context.getParam({ name: "nomacro", type: "string", default: "N" }); const nostop = context.getParam({ name: "nostop", type: "string", default: "N" }); const stype = context.getParam({ name: "type", type: "string", default: types_1.SwitchType.kNoType }); const name = context.getParam({ name: "name", type: "string", default: "--" }); const data = context.getParam({ name: "data", type: "string", default: "" }); const header = context.getParam({ name: "header", type: "string", default: "" }); const method = context.getParam({ name: "method", type: "string", default: "GET" }); return { name, unitName, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype, plug, data, header, method, nomacro, nostop }; } updateSwitch(inx, swtch) { if ((inx >= 0) && (inx < this.switches.length)) { this.switches[inx] = swtch; this.initSwitchUnits(); this.writeConfig(); } } deleteSwitch(inx) { if ((inx >= 0) && (inx < this.switches.length)) { this.switches.splice(inx, 1); this.initSwitchUnits(); this.writeConfig(); } } setSwitch(inx, newstate, newvalue) { // find the switch if an index is given let swtch = null; if (typeof inx === "number") { if ((inx >= 0) && (inx < this.switches.length)) { swtch = this.switches[inx]; } } else { // a Switch was passed as first param swtch = inx; } // check if state is given if (typeof newstate != "undefined") { swtch.unit.status = !!newstate; } if (typeof newvalue != "undefined") { swtch.unit.value = +newvalue; } if (!swtch) { (0, logger_1.err)("smartapp", "Didn't find switch with inx: " + inx); } else if (!swtch.unit) { (0, logger_1.err)("smartapp", "Don't have unit for switch: " + swtch.unitname); } else { if ((swtch.type === types_1.SwitchType.kSmappee) && (this.power.smappee)) { this.power.smappee.setPlug(parseInt(swtch.plug), swtch.unit.value); } else if (swtch.type === types_1.SwitchType.kHTTPSwitch) { this.httpSwitch(swtch); } else if (swtch.type === types_1.SwitchType.kHTTPDimmer) { this.httpDimmer(swtch); } else if (swtch.type === types_1.SwitchType.kHTTPUpDown) { this.httpUpDown(swtch); } else if (swtch.type === types_1.SwitchType.kOhSwitch) { this.ohSwitch(swtch); } else if (swtch.type === types_1.SwitchType.kOhDimmer) { this.ohDimmer(swtch); } else if (swtch.type === types_1.SwitchType.kOhUpDown) { this.ohUpDown(swtch); } else if (swtch.type === types_1.SwitchType.kSomfy) { this.somfy(swtch); } else { (0, logger_1.err)("smartapp", "Don't know how to set a switch of type " + swtch.type); } } } /////////// // Somfy // /////////// somfy(swtch) { let nr = swtch.plug; if (typeof nr === "string") nr = parseInt(nr); nr = Math.max(0, Math.min(4, nr)); if (swtch.unit) { if (swtch.unit.status === 3) somfy.down(nr); // 3 = going down else if (swtch.unit.status === 4) somfy.up(nr); // 4 = going up else if (swtch.unit.status != 0) somfy.stop(nr); // 1 = stopped down, 2 = stopped up } } ////////////////////////// // http driven switches // ////////////////////////// makeVariableURL(url, state, value) { // support legacy on/off const parts = url.split("|"); let base = parts[0]; if (parts.length > 2) { base += parts[state ? 2 : 1]; } // do the dimmer value and on/off return base .replace("#B", state ? "true" : "false") .replace("#O", state ? "on" : "off") .replace("#1", state ? '1' : '0') .replace("#", state ? "on" : "off") .replace("$B", "" + Math.round(value / 100 * 256)) .replace("$T", "" + Math.round(value / 100 * 512)) .replace("$W", "" + Math.round(value / 100 * 256 * 256)) .replace("$1", "" + (value / 100)) .replace("$", "" + value); } httpSwitch(swtch) { const req = this.makeVariableURL(swtch.plug, !!swtch.unit.status, +swtch.unit.value); let data = ""; if (swtch.data) { // we have body data data = this.makeVariableURL(swtch.data, !!swtch.unit.status, +swtch.unit.value); } (0, logger_1.log)("smartapp", "HTTP-Switch(" + !!swtch.unit.status + ") -> " + req); (0, HA_API_1.httpRequest)(req, swtch.method, data, HA_API_1.ContentType.form, swtch.header); } httpUpDown(swtch) { let url = swtch.plug + ""; // support legacy on/off const parts = url.split("|"); let base = parts[0]; const val = 1 + swtch.unit.value; if (val < parts.length) { base += parts[val]; } let data = ""; if (swtch.data) { // we have body data data = this.makeVariableURL(swtch.data, !!swtch.unit.status, +swtch.unit.value); } if ((val === 1) && (swtch.nostop == 'Y')) { (0, logger_1.debug)("smartapp", "Blocked stop for HTTP-UpDown " + swtch.name); } else { (0, logger_1.log)("smartapp", "UpDown(" + val + ") -> " + base); (0, HA_API_1.httpRequest)(base, swtch.method, data, HA_API_1.ContentType.form, swtch.header); } } httpDimmer(swtch) { // do the possible on/off + value part let req = this.makeVariableURL(swtch.plug, !!swtch.unit.status, +swtch.unit.value); let data = ""; if (swtch.data) { // we have body data data = this.makeVariableURL(swtch.data, !!swtch.unit.status, +swtch.unit.value); } (0, logger_1.debug)("smartapp", "Dimmer(" + !!swtch.unit.status + "," + swtch.unit.value + ") -> " + req + " + " + data); (0, HA_API_1.httpRequest)(req, swtch.method, data, HA_API_1.ContentType.form, swtch.header); } //////////////////////// // http request stuff // //////////////////////// // overrides webapp.login -> login into HB checkLogin(url, username, password) { return __awaiter(this, void 0, void 0, function* () { return (0, HB_1.hbLogin)(url, username, password); }); } ////////////// // open HAB // ////////////// ohSwitch(swtch) { const req = this.makeVariableURL(swtch.plug, !!swtch.unit.status, +swtch.unit.value); (0, logger_1.debug)("smartapp", "OH-Switch(" + !!swtch.unit.status + ") -> " + req); (0, HA_API_1.httpRequest)(req, swtch.method, swtch.unit.status ? "ON" : "OFF", HA_API_1.ContentType.plain, swtch.header); } ohDimmer(swtch) { const req = this.makeVariableURL(swtch.plug, !!swtch.unit.status, +swtch.unit.value); const val = (swtch.unit.value == 1) ? "ON" : swtch.unit.value.toString(); (0, logger_1.debug)("smartapp", "OH-Dimmer(" + !!swtch.unit.status + ", " + val + ") -> " + req); (0, HA_API_1.httpRequest)(req, swtch.method, swtch.unit.status ? val : "OFF", HA_API_1.ContentType.plain, swtch.header); } ohUpDown(swtch) { let data; // 1=stopped, 2-closed, 3=opened, 4=closing, 5=opening if ((swtch.unit.status == 0) || (swtch.unit.status == 1) || (swtch.unit.status == 2)) { if (swtch.nostop == 'Y') (0, logger_1.debug)("smartapp", "Blocked stop for OH-UpDown " + swtch.name); else data = "STOP"; } else if (swtch.unit.status == 3) { data = "DOWN"; } else if (swtch.unit.status == 4) { data = "UP"; } if (data) { const req = this.makeVariableURL(swtch.plug, !!swtch.unit.status, +swtch.unit.value); (0, logger_1.debug)("smartapp", "OH-UpDown(" + swtch.unit.value + ", " + data + ") -> " + req); (0, HA_API_1.httpRequest)(req, swtch.method, data, HA_API_1.ContentType.plain, swtch.header); } } ////////////////////////////// // Services // ////////////////////////////// doServices(context) { return __awaiter(this, void 0, void 0, function* () { let units = this.system.allUsedUnits(); for (let u of units) { yield u.node.master.requestUnitStatus(u); } return this.ejs("serviceList", context, { units, nrActive: this.system.allActiveUnits().length }); }); } ////////////////////////////// // Masters // ////////////////////////////// doMasters(context) { return __awaiter(this, void 0, void 0, function* () { let message; try { if (context.action === "new") { return this.ejs("masterDetail", context, { config: types_1.Sanitizers.masterConfig(null), registers: [], nodes: [] }); } else if (context.action === "list") { return this.ejs("unitList", context, { masters: this.system.masters }); } else if (context.action === "services") { return this.serviceList(context); } else if (context.action === "edit") { context.getMaster("id"); const master = this.system.findMaster(context["masterAddress"], context["masterPort"]); if (master) return this.ejs("masterDetail", context, { nodes: master.nodes, config: master.getConfig(), registers: master.registers }); else message = "Error: Master not found"; } else if (context.action === "delete") { const master = this.system.findMaster(context.getParam(kAddress), context.getParam(kPort)); if (master) yield this.system.deleteMaster(master); else message = "Error: Master not found"; // drop through and list all masters } else if (context.action === "save") { const master = yield this.system.addMaster(context.getParam(kAddressOld), context.getParam(kPortOld), { address: context.getParam(kAddress), port: context.getParam(kPort), password: context.getParam(kPassword), name: context.getParam(kName), active: context.getParam(kActive) != "N", nodenames: {} }); this.updateNodes(master, context.params.nodes || "", context.params); this.system.writeConfig(); } else if (context.action === "close") { context.getMaster("id"); const master = this.system.findMaster(context["masterAddress"], context["masterPort"]); if (master) yield master.close(false); else message = "Error: Master not found"; } } catch (e) { message = e.toString(); } return this.ejs("masterList", context, { masters: this.system.masters, message }); }); } serviceList(context) { return __awaiter(this, void 0, void 0, function* () { let units = this.system.allActiveUnits(); for (let u of units) { yield u.node.master.requestUnitStatus(u); } return this.ejs("unitList", context, { services: this.system.allActiveUnits() }); }); } updateNodes(master, nodes, params) { return __awaiter(this, void 0, void 0, function* () { if (nodes) { let nodeArr = nodes.map(s => parseInt(s)); nodeArr.forEach(adr => { let node = this.system.findNode(master, adr); if (node) { node.active = (params["active_" + adr] === "Y"); this.system.setActiveState(node); } }); } }); } ////////////////////////////// // Units // ////////////////////////////// doUnits(context) { return __awaiter(this, void 0, void 0, function* () { // get masterAddress and Port context.getMaster("action", "node"); const master = this.system.findMaster(context["masterAddress"], context["masterPort"]); if (context.action === "save") { // store changes in units into master config this.updateUnits(master, context.getParam(kNode), context.params); this.system.writeConfig(); context.action = "cancel"; } if (context.action === "cancel") { // return to previous screen -> show master info + list of nodes. context.id = context["masterAddress"] + ":" + context["masterPort"]; context.request = "masters"; context.action = "edit"; return this.doRequest(context); } else if (context.action === "set") { if (!master) return this.error(context, "master not found", true); const { logicalNodeAddress, logicalAddress } = context.getUnit(); const response = yield this.setState(master, logicalNodeAddress, logicalAddress, context.getParam(kValue)); return this.json(response); } else if (context.action === "get") { if (!master) return this.error(context, "master not found", true); const { logicalNodeAddress, logicalAddress } = context.getUnit(); const response = yield this.getState(master, logicalNodeAddress, logicalAddress); return this.json(response); } else if (context.action === "press") { if (!master) return this.error(context, "master not found", true); const { logicalNodeAddress, logicalAddress } = context.getUnit(); const response = yield this.doPress(master, logicalNodeAddress, logicalAddress, context.getParam(kIntValue)); return this.json(response); } else { // context.action === "node" // reponding to /units/[master ip address:port]/[logical node address] const nodeLogicalAddress = parseInt(context.id); let response = yield this.getNodeInfo(master, nodeLogicalAddress, true); this.adjustUnitsToConfig(response.node); let N = this.sortCopy(response.node, context); return this.ejs("nodeDetail", context, { message: response.message, node: N }); } }); } adjustUnitsToConfig(node) { // check if units are still in config node.units.forEach(unit => { //console.log("Searching: ", unit.node.master.config.address, unit.node.master.config.port, unit.logicalNodeAddress, unit.logicalAddress) const config = this.system.config.cunits.find(u => { // console.log(u.masterAddress, u.masterPort