UNPKG

homebridge-smartsystem

Version:

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

213 lines (182 loc) 6.18 kB
// Shelly PM implementation // Johan Coppieters, Jul 2022. // import { System } from "../duotecno/system"; import { ShellyConfig } from "../duotecno/types"; import { debug, err, log } from "../duotecno/logger"; import { PowerBase } from "./powerbase"; import { get } from "../duotecno/api"; const HTTPstatus = { "wifi_sta": { "connected": true, "ssid": "Tele Coppieters", "ip": "192.168.0.95", "rssi": -73 }, "cloud": { "enabled": true, "connected": true }, "mqtt": { "connected": false }, "time": "18:17", "unixtime": 1663863464, "serial": 7, "has_update": true, "mac": "C45BBE7928B9", "cfg_changed_cnt": 1, "actions_stats": { "skipped": 0 }, "relays": [{ "ison": true, "has_timer": false, "timer_started": 0, "timer_duration": 0, "timer_remaining": 0, "overpower": false, "is_valid": true, "source": "http" }], "emeters": [ { "power": 10.66, "pf": 1.00, "current": 0.08, "voltage": 232.06, "is_valid": true, "total": 5.3, "total_returned": 0.0 }, { "power": 0.00, "pf": 0.00, "current": 0.01, "voltage": 232.06, "is_valid": true, "total": 0.0, "total_returned": 0.0 }, { "power": 0.00, "pf": 0.00, "current": 0.01, "voltage": 232.05, "is_valid": true, "total": 0.0, "total_returned": 0.0 } ], "total_power": 10.66, "fs_mounted": true, "update": { "status": "pending", "has_update": true, "new_version": "20220830-080542/v1.12-3EM-gcf4f7c2", "old_version": "20220209-094824/v1.11.8-g8c7bb8d" }, "ram_total": 49440, "ram_free": 30960, "fs_size": 233681, "fs_free": 156875, "uptime": 1364 }; const HttpEMeter0 = { "power":10.95, "pf":1.00, "current":0.08, "voltage":231.21, "is_valid":true, "total":7.2, "total_returned":0.0 }; interface Meter { address: string; index: number; phase: boolean; phaseNr: number; power: number; pf: number; current: number; voltage: number; is_valid: boolean; total: number; total_returned: number; } export class Shelly extends PowerBase { config: ShellyConfig; // redefine timer: NodeJS.Timer; meters: Array<Meter>; nrPhases: number; constructor(system: System) { super("shelly", system); this.timer = null; this.meters = []; this.nrPhases = 0; // init is called by super } // // return data for ejs pages // // async getData(context: Context, message: String): Promise<any> { // // nothing special... for now // return super.getData(context, message); // } // // config mgt // async updateConfig(config: any) { this.config.addresses = config.addresses; return super.updateConfig(config); } async subscribe() { const kInterval = 5; if (this.config.address) { await this.getDeviceConfig(this.config.address, true); } if (this.config.addresses) { const list = this.config.addresses.split(","); for (let inx in list) { const phases = (this.config.address) ? false : (inx == "0"); await this.getDeviceConfig(list[inx], phases); } } debug("shelly", "meters: " + JSON.stringify(this.meters)); if (this.meters.length) { this.timer = setInterval(async () => { this.realtimeCounter += kInterval; // fetch data const ok = await this.fetchData(); if (ok) this.consume(); // call process/bindings/rules this.applyBindings(); this.applyRules(); }, kInterval * 1000); } else { log("shelly", "No Shelly PM devices configured"); } } async unsubscribe() { if (this.timer) { clearInterval(this.timer); this.timer = null; } } async getDeviceConfig(address: string, phases: boolean) { try { // debug("shelly", "fetching from " + address + "/status"); const res = await get(address, 80, "/status", undefined); debug("shelly", "received status from " + address + " -> " + res); const status = JSON.parse(res); // enrich meter with its address and index + add to the global meter list status.emeters.forEach((m, inx) => { m.index = inx; // index with this PM device m.phase = phases; if (phases) { m.phaseNr = this.nrPhases; this.nrPhases++; } else { m.phaseNr = -1; } m.address = address; this.meters.push(m); }); } catch(error) { err("shelly", error.message) } } async fetchData(): Promise<boolean> { try { for (let meter in this.meters) { // debug("shelly", "fetching from " + this.config.address + "/emeter/" + meter); const res = await get(this.meters[meter].address, 80, "/emeter/"+this.meters[meter].index, undefined); debug("shelly", "received meter: " + meter + " -> " + res); this.meters[meter] = {... this.meters[meter], ...JSON.parse(res)}; }; return true; } catch(error) { err("shelly", error.message || error); return false; } } consume() { // copy this.meters into the powerbase structures this.meters.forEach((meter, inx) => { this.channels[inx] = { name: "Meter " + (inx+1), type: "CT", flow: "PRODUCTION", power: meter.power, exportEnergy: meter.total_returned, importEnergy: meter.total, phaseId: meter.phaseNr, current: meter.power / meter.voltage, apparentPower: 0, cosPhi: 0, formula: "", voltage: meter.voltage} }); this.voltages = [0,0,0]; this.meters.filter(m => m.phase).forEach(m => this.voltages[m.phaseNr] = m.voltage); this.realtime = { totalPower: this.meters.reduce((prev, curr) => curr.power + prev, 0), totalReactivePower: 0, totalExportEnergy: this.meters.reduce((prev, curr) => curr.total_returned + prev, 0), totalImportEnergy: this.meters.reduce((prev, curr) => curr.total + prev, 0), monitorStatus: 0, utcTimeStamp: new Date().getTime() }; this.production = this.realtime.totalPower; this.consumption = this.realtime.totalReactivePower; } }