UNPKG

homebridge-smartsystem

Version:

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

439 lines (369 loc) 13.9 kB
import { Master } from "./master"; import { SystemConfig, MasterConfig, UnitConfig, YN, GroupConfig, SceneConfig } from "./types"; import { log, err, debug } from "./logger"; import { Node, Unit, Protocol } from "./protocol"; import { Base } from "../server/base"; import { EventEmitter } from "events"; export class System extends Base { public masters: Array<Master>; public config: SystemConfig; public isBrowser: boolean = true; public isSplitted: boolean = false; // rebuild active services (units) trigger: NodeJS.Timeout; emitter: EventEmitter; public moods: Array<Unit> = []; public controls: Array<Unit> = []; public temperatures: Array<Unit> = []; public stores: Array<Unit> = []; public scenes: Array<SceneConfig> = []; constructor() { super("system"); this.trigger = null; this.emitter = new EventEmitter(); Protocol.setEmitter(this.emitter); this.emitter.on('update', this.checkScenes.bind(this)); this.readScenes(); // open all masters listed in the config this.masters = []; } setBrowser(isB: boolean) { this.isBrowser = isB; } async openMasters(readDB: boolean = false) { for (let inx = 0; inx < this.config.cmasters.length; inx++) { try { if (this.config.cmasters[inx].active) await this.openMaster(this.config.cmasters[inx], readDB); } catch(err) { log("system", err); } } } async closeMasters() { for (let inx = 0; inx < this.masters.length; inx++) { await this.closeMaster(this.masters[inx]) } } async openMaster(config: MasterConfig, readDB: boolean = false): Promise<Master> { const master = new Master(this, config); this.masters.push(master); // check for old configs that don't contain the active flag if ((typeof master.config.active === "boolean") && (!master.config.active)) return; try { log("system", "opening master: " + master.getAddress()); await master.open(); if (! await master.login()) throw(new Error("Failed to log in")); log("system", "logged in on: " + master.getAddress()) await master.getDatabase(readDB); log("system", "master: " + master.getAddress() + " opened with " + master.nodes.length + " nodes."); this.triggerRebuild(); return master; } catch(e) { err("system", "failed to open master (" + e.toString() + ")"); return null; // throw(e); } } async closeMaster(master: Master) { // non-existing master -> do nothing if (! master) return; // find its index (we need it to delete it from the master list) let inx = this.findMasterInx(master); // close if open if (master.isOpen) { try { await master.close(); } catch(e) { err("system", "failed to close master on " + master.getAddress() + ":" + master.getPort()); } } // remove from list if (inx >= 0) this.masters.splice(inx, 1); } displayDatabases() { this.masters.forEach(m => m.displayDatabase()); } //////////////// // Setting up // //////////////// async addMaster(oldAddress: string, oldPort: number, cmaster: MasterConfig): Promise<Master> { if (!cmaster.address) return; // see if this master already exists let inx = this.findCMasterInx(oldAddress, oldPort); // store in config if not yet known if (inx < 0) { this.config.cmasters.push(cmaster); inx = this.masters.length-1; } else { // close to re-open (master is deleted from the master array) const master = this.findMaster(oldAddress, oldPort); await this.closeMaster(master); // update the config this.config.cmasters[inx] = cmaster; } this.writeConfig(); // master is openened and added to the master array return await this.openMaster(cmaster, true); } async deleteMaster(master: Master) { const masterAddress = master.getAddress(); const masterPort = master.getPort(); // remove from the config let inx = this.findCMasterInx(masterAddress, masterPort); if (inx >= 0) { // remove from the active masters await this.closeMaster(master); // remove the master from the config list this.config.cmasters.splice(inx, 1); // remove it's units from the config this.config.cunits = this.config.cunits.filter(unit => (unit.masterPort != masterPort) || (unit.masterAddress != masterAddress)); this.writeConfig(); } else { err("system", "didn't find the master " + master.getAddress() + ":" + master.getConfig().port + " in the config"); } } updateMasterConfig(master: Master) { let inx = this.findCMasterInx(master.getAddress(), master.getPort()); if (inx >= 0) { this.config.cmasters[inx] = master.getConfig(); } this.writeConfig(); } setActiveState(item: Node | Unit) { // for nodes if (item instanceof Node) { const masterAddress = item.master.getAddress(); const masterPort = item.master.getPort(); // check if at least 1 unit is used const unitConfig = this.config.cunits.find((cu: UnitConfig) => (cu.logicalNodeAddress === item.logicalAddress) && (cu.masterAddress === masterAddress) && (cu.masterPort == masterPort)); // if there is: set active to true of this node item.active = !! unitConfig; } // for units if (item instanceof Unit) { const masterAddress = item.node.master.getAddress(); const masterPort = item.node.master.getPort(); // see if there is a config record for this unit const unitConfig = this.config.cunits.find((cu: UnitConfig) => (cu.logicalAddress === item.logicalAddress) && (cu.logicalNodeAddress === item.logicalNodeAddress) && (cu.masterAddress === masterAddress) && (cu.masterPort == masterPort)); // if there is: copy active, used, displayName and group if (unitConfig) { item.active = (unitConfig.active === "Y"); item.used = (unitConfig.used === "Y"); item.displayName = unitConfig.displayName || unitConfig.name; item.group = unitConfig.group; log("system", "System.setActiveState(unit) -> config name: " + unitConfig.name + ", item name: " + item.name); } else { item.active = false; } } } ////////////////////////////////////// // Finding masters, nodes and units // ////////////////////////////////////// findMaster(master: Master | string, port?: number): Master { if (typeof master === "string") { if (typeof port === "undefined") { const parts = master.split(":"); if (parts.length > 1) { master = parts[0]; port = parseInt(parts[1]); } else { port = 5001; } } return this.masters.find((m: Master) => m && m.same(master, port)) } else { return this.masters.find((m: Master) => m && m.same(master)); } } findMasterInx(master: Master): number { return this.masters.findIndex((m: Master) => m && m.same(master)); } findCMasterInx(address: string, port: number): number { return this.config.cmasters.findIndex((m: MasterConfig) => (m.address == address) && (m.port == port)); } findNode(master: Master, logicalAddress: number) { if (master) return master.nodes.find((n: Node) => n && (n.logicalAddress === logicalAddress)); else return null; } findUnit(master: string, port: number, logicalNodeAddress: number, logicalAddress: number): Unit; findUnit(master: Master, logicalNodeAddress: number, logicalAddress: number): Unit; findUnit(master: Master | string, A: number, B: number, C?: number): Unit { let logicalNodeAddress = A; let logicalAddress = B; if (typeof master === "string") { master = this.findMaster(master, A); logicalNodeAddress = B; logicalAddress = C; } const node = this.findNode(master, logicalNodeAddress); if (node) return node.units.find((u: Unit) => u && (u.logicalAddress === logicalAddress)); else return null; } findUnitByAddress(logicalNodeAddress: number, logicalAddress: number): Unit { let unit: Unit = null; this.masters.forEach((m: Master) => { if (m) { const node = this.findNode(m, logicalNodeAddress); if (node) unit = node.units.find((u: Unit) => u && (u.logicalAddress === logicalAddress)); } }); return unit; } findUnitByName(master: string, port: number, name: string): Unit; findUnitByName(master: Master, name: string): Unit; findUnitByName(master: string | Master, A: number | string, name?: string): Unit { let unit: Unit = null; if (typeof master === "string") { master = this.findMaster(master, <number>A); } else { name = <string>A; } this.masters.forEach((m: Master) => { if (m && m.same(master)) { m.nodes.forEach((n: Node) => { if (n) { n.units.forEach(u => { if ((u.displayName === name) || (u.name === name)) unit = u; }); } }); } }); return unit; } activeUnitsConfig(): Array<UnitConfig> { return this.config.cunits.filter(u => u.active == "Y"); } allActiveUnits(): Array<Unit> { return this.masters .reduce((acc, m) => acc.concat(m.nodes), []) .reduce((acc, n) => acc.concat(n.units), []) .filter(u => u.active); } usedUnitsConfig(): Array<UnitConfig> { return this.config.cunits.filter(u => u.used == "Y"); } allUsedUnits(): Array<Unit> { return this.masters .reduce((acc, m) => acc.concat(m.nodes), []) .reduce((acc, n) => acc.concat(n.units), []) .filter(u => u.used); } allMasters(doToMaster: (m: Master) => void) { this.masters.forEach(m => doToMaster(m)); } ////////////////////////////////////////////////// // Getting the current state of units and nodes // ////////////////////////////////////////////////// updateSystem(dontTrigger: boolean = false) { this.config.cunits = this.allUsedUnits() .map((u: Unit) => { return { active: u.active ? "Y" : "N", used: <YN>"Y", group: u.group, name: u.name, displayName: u.displayName, type: u.type, extendedType: u.extendedType, masterAddress: u.node.master.getAddress(), masterPort: u.node.master.getPort(), logicalNodeAddress: u.node.logicalAddress, logicalAddress: u.logicalAddress}; }); this.writeConfig(); if (dontTrigger) this.triggerRebuild(); } triggerRebuild(immediate: boolean = false) { //log("system", "triggerRebuild requested") if (this.trigger) { //log("system", "killing pending rebuild") clearTimeout(this.trigger); this.trigger = null; } if (immediate) { this.rebuildServices(); } else { this.trigger = setTimeout(() => { this.trigger = null; this.rebuildServices(); }, 2000); } } rebuildServices() { function compare(a: Unit | Node | Master, b: Unit | Node | Master) { const an = a.getSort(); const bn = b.getSort(); if (an < bn) return -1; if (an > bn) return 1; return 0; } function compareN(a: Unit, b: Unit) { const aname = a.name.toLowerCase(); const bname = b.name.toLowerCase(); if (aname < bname) return -1; if (aname > bname) return 1; return 0; } // sort masters, nodes in masters, units in nodes. log("system", "rebuildMasters (" + this.masters.length + ") -> nodes -> units"); this.masters.sort(compare); this.masters.forEach((m: Master) => { m.nodes.sort(compare); m.nodes.forEach((n: Node) => n.units.sort(compare)); }); // sort selected controls, temperatures and moods. log("system", "rebuildServices") const services = this.allActiveUnits(); this.controls = services.filter(s => s.isCtrl()).sort(compareN); this.temperatures = services.filter(s => s.isTemperature()).sort(compareN); this.moods = services.filter(s => (s.isMood() || s.isInput())).sort(compareN); this.stores = this.controls.filter(s => s.isUpDown()); let complete = true; // should be of all active masters, nodes and units !! // this.allMasters(m => m.allNodes(n => {if (n.nrUnits != n.units.length) complete = false; })) if (complete) { log("system", "emitting READY"); this.emitter.emit('ready', this.masters.length); } } //////////// // Scenes // //////////// checkScenes(unit: Unit) { // called by updateState that is listening for status changes const scene = this.scenes.find(s => unit.isUnit(s.trigger.masterAddress, s.trigger.masterPort, s.trigger.logicalNodeAddress, s.trigger.logicalAddress)); if (scene) { debug("system", "scene found -> " + scene.name + ", value = " + scene.trigger.value + " unit = " + unit.value);console.log(scene); // if (unit.sameValue(scene.trigger.value)) { scene.units.forEach(u => { const unit = this.findUnit(u.masterAddress, u.masterPort, u.logicalNodeAddress, u.logicalAddress); if (unit) { debug("system", " - attached unit found -> " + unit.getDisplayName() + " -> " + u.value); unit.setState(u.value); } }); // } } } ////////////////// // Config stuff // ////////////////// readScenes() { this.scenes = <SceneConfig[]>this.read("scenes"); // order the scenes and reset the order indices this.scenes.sort((a,b) => a.order-b.order); this.scenes.forEach((g,i) => g.order = i); } writeScenes() { this.write("scenes", this.scenes); } }