homebridge-smartsystem
Version:
SmartServer (Proxy TCP sockets to the cloud, Smappee MQTT, Duotecno IP Nodes, Homekit interface)
280 lines • 11.9 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.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