UNPKG

homebridge-smartsystem

Version:

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

280 lines 11.9 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.PowerBase = void 0; const types_1 = require("../duotecno/types"); const logger_1 = require("../duotecno/logger"); const base_1 = require("./base"); // Power base class // used by Smappee, Shelly PM, P1 Meter, ... // Johan Coppieters, Jul 2022. // const kPercentChange = 0.05; class PowerBase extends base_1.Base { constructor(type, system) { super(type); // called by Base: this.readConfig(); this.system = system; // will grow when we encounter one in the mqtt stream or if we receive a config message this.switches = {}; this.channels = {}; this.voltages = {}; this.realtime = {}; this.realtimeCounter = 0; this.rules = (this.config.rules || []).map(rule => types_1.Sanitizers.ruleConfig(rule)); this.bindings = (this.config.bindings || []).map(binding => types_1.Sanitizers.bindingConfig(binding)); this.init(true).then(() => { (0, logger_1.log)("power", "Power[" + type + "] created and started"); }); } // // start and restart services // init(firstTime) { return __awaiter(this, void 0, void 0, function* () { if (!firstTime) yield this.unsubscribe(); this.realtimeCounter = 0; return this.subscribe(); }); } subscribe() { return __awaiter(this, void 0, void 0, function* () { // overwrite }); } unsubscribe() { return __awaiter(this, void 0, void 0, function* () { // overwrite }); } // // return data for ejs pages // getData(context, message) { return __awaiter(this, void 0, void 0, function* () { return { config: this.config, bindings: this.bindings, rules: this.rules, realtimeCounter: this.realtimeCounter, message, realtime: this.realtime, voltages: this.voltages, switches: this.switches, channels: this.channels }; }); } // // config mgt // updateConfig(config) { return __awaiter(this, void 0, void 0, function* () { if (config.address) this.config.address = config.address; this.writeConfig(); return this.init(false); }); } evalPower(exp) { var _a, _b, _c, _d; let parsed = exp.split(/(\+|-)/); let result = (_b = (_a = this.channels[parseInt(parsed[0]) || 0]) === null || _a === void 0 ? void 0 : _a.power) !== null && _b !== void 0 ? _b : 0; for (let i = 1; i < parsed.length; i = i + 2) { const channelIndex = parseInt(parsed[i + 1]) || 0; const channelPower = (_d = (_c = this.channels[channelIndex]) === null || _c === void 0 ? void 0 : _c.power) !== null && _d !== void 0 ? _d : 0; if (parsed[i] === "-") result -= channelPower; else if (parsed[i] === "+") result += channelPower; } return result; } ////////////////////////////////////////////// // Bindings to Duotecno virtual temp sensor // ////////////////////////////////////////////// significant(current, previous) { // no values yet -> don't update if (isNaN(current)) return false; // no previous value -> always update if (isNaN(previous)) return true; // less than 1000W -> update on: difference > 5% previous if (current < 1000) return Math.abs(previous - current) > (previous * 0.05); // more than 1000W -> update on: difference > 2% previous if (current >= 1000) return Math.abs(previous - current) > (previous * 0.02); return false; } applyBindings() { if (this.realtimeCounter % 2 === 0) { // for all bindings this.bindings.forEach(b => { const power = this.evalPower(b.channel); (0, logger_1.debug)("power", `Binding channel=${b.channel}, register=${b.register}, current_value=${b.value}, calculated_power=${power}`); if (this.significant(power, b.value)) { (0, logger_1.debug)("power", "APPLY to " + b.channel + " = " + b.value + " -> " + power + " for " + b.register + "): difference: " + Math.abs(b.value - power) + " > threshold: 5% =" + Math.abs(b.value * 0.05) + " > threshold: 2% =" + Math.abs(b.value * 0.02)); this.applyBinding(b, power) .then(power => { b.value = power; (0, logger_1.debug)("power", "set register " + b.register + " to " + power); }) .catch((e) => (0, logger_1.err)(this.type, e.message || e)); } else { (0, logger_1.debug)("power", "NOT significant for " + b.channel + " = " + b.value + " -> " + power + " for register " + b.register + ": difference: " + Math.abs(b.value - power) + " <= threshold: 5% =" + Math.abs(b.value * 0.05) + " or 2% =" + Math.abs(b.value * 0.02)); } }); } } applyBinding(binding, value) { return __awaiter(this, void 0, void 0, function* () { const master = this.system.findMaster(binding.masterAddress, binding.masterPort); if (master) { yield master.setRegisterValue(binding.register, value); return value; } else if ((!binding.masterAddress) || (binding.register)) { throw new Error("***** MASTER NOT FOUND for binding for value " + value + " to " + binding.masterAddress + ":" + binding.masterPort + " - register = " + binding.register + " *****"); } // else empty binding -> no error }); } ///////////////////// // Rules execution // ///////////////////// applyRules() { if (this.realtimeCounter % 60 === 0) { (0, logger_1.log)("power", this.type + " > RealTime totals: Power = " + this.realtime.totalPower + ", Export = " + this.realtime.totalExportEnergy + ", Import = " + this.realtime.totalImportEnergy); } if (this.realtimeCounter % 5 === 0) { // for all rules this.rules.forEach(rule => { let power = 0; // if about power if (rule.type === "power") { // add all channels this rule refers to power = this.evalPower(rule.channel); } else if (rule.type === "sun") { // calc fake power usage power = this.production - this.consumption; } (0, logger_1.debug)("power", "checking " + rule.type + "-rule with channel = " + rule.channel + " = " + power + "W"); this.checkPowerRule(power, rule); }); } } checkPowerRule(power, rule) { let newCurrent; if (isNaN(power)) return; if (power < rule.low) { newCurrent = types_1.Boundaries.kLow; } else if (power > rule.high) { newCurrent = types_1.Boundaries.kHigh; } else { newCurrent = types_1.Boundaries.kMid; } rule.power = power; if (newCurrent != rule.current) { (0, logger_1.debug)("power", "triggered rule for channel: " + rule.channel + ", power = " + power + ", low: " + rule.low + ", high: " + rule.high + ", current: " + rule.current + " -> new: " + newCurrent); // should be done after successful applyCommand... rule.current = newCurrent; if (newCurrent < rule.actions.length) { this.applyCommand(rule.actions[newCurrent]) .then((val) => { (0, logger_1.debug)("power", "## sent command -> " + rule.type + ", power = " + power + ", channel = " + rule.channel + " -> current = " + newCurrent + " (unit: " + rule.actions[newCurrent].name + ", value: " + rule.actions[newCurrent].value + ")"); rule.current = newCurrent; }) .catch((e) => (0, logger_1.err)(this.type, e.message)); } } } applyCommand(action) { return __awaiter(this, void 0, void 0, function* () { const unit = this.system.findUnit(this.system.findMaster(action.masterAddress, action.masterPort), action.logicalNodeAddress, action.logicalAddress); if (unit) { yield unit.setState(action.value); } else if ((action.logicalNodeAddress != 0) || (action.logicalAddress != 0)) { throw new Error("***** UNIT NOT FOUND for action for value " + action.value + " to " + action.masterAddress + " - " + action.logicalNodeAddress + ";" + action.logicalAddress + " *****"); } }); } // // Rule mgt // updateRules() { // sort on channel. this.rules.sort((a, b) => parseInt(a.channel) - parseInt(b.channel)); // sanitize and copy all rules into the config this.config.rules = []; this.rules.forEach(r => this.config.rules.push(types_1.Sanitizers.ruleConfig(r))); (0, logger_1.debug)("power", "config.rules = " + JSON.stringify(this.config.rules)); this.writeConfig(); } newRule() { const rule = Object.assign({}, types_1.kEmptyRule); this.rules.push(rule); return rule; } updateRule(inx, rule) { if (inx < this.rules.length) { this.rules[inx] = rule; this.updateRules(); } } deleteRule(inx) { if (inx < this.rules.length) { this.rules.splice(inx, 1); this.updateRules(); } } // // Binding mgt // updateBindings() { // sort on channel. this.bindings.sort((a, b) => a.register - b.register); // sanitize and copy all rules into the config this.config.bindings = this.bindings.map(b => types_1.Sanitizers.bindingConfig(b)); (0, logger_1.debug)("power", "config.bindings = " + JSON.stringify(this.config.bindings)); this.writeConfig(); } newBinding() { const binding = Object.assign({}, types_1.kEmptyBinding); this.bindings.push(binding); return binding; } updateBinding(inx, binding) { if (inx < this.bindings.length) { this.bindings[inx] = binding; this.updateBindings(); } } deleteBinding(inx) { if (inx < this.bindings.length) { this.bindings.splice(inx, 1); this.updateBindings(); } } } exports.PowerBase = PowerBase; //# sourceMappingURL=powerbase.js.map