homebridge-smartsystem
Version:
SmartServer (Proxy TCP sockets to the cloud, Smappee MQTT, Duotecno IP Nodes, Homekit interface)
213 lines (182 loc) • 6.18 kB
text/typescript
// 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;
}
}