homebridge-smartsystem 6.2.6 → 6.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/config.json +21 -194
- package/duotecno/logger.js +1 -0
- package/duotecno/logger.ts +1 -0
- package/duotecno/master.js +1 -1
- package/duotecno/master.ts +2 -1
- package/duotecno/system.js +7 -2
- package/duotecno/system.ts +11 -2
- package/duotecno/types.js +13 -4
- package/duotecno/types.ts +21 -9
- package/package.json +1 -1
- package/server/base.js +3 -1
- package/server/base.ts +3 -1
- package/server/p1.js +22 -22
- package/server/p1.ts +15 -13
- package/server/powerbase.js +7 -6
- package/server/powerbase.ts +10 -8
- package/server/shelly.js +144 -11
- package/server/shelly.ts +161 -15
- package/server/smappee.js +4 -3
- package/server/smappee.ts +6 -3
- package/server/smartapp.js +5 -5
- package/server/smartapp.ts +5 -5
- package/server/views/power.ejs +20 -11
package/server/p1.ts
CHANGED
|
@@ -182,43 +182,45 @@ export class P1 extends PowerBase {
|
|
|
182
182
|
|
|
183
183
|
consume(data: P1Data) {
|
|
184
184
|
if (! data['32.7.0']) return;
|
|
185
|
-
|
|
186
|
-
this.voltages = [data['32.7.0']?.value, data['52.7.0']?.value, data['72.7.0']?.value];
|
|
187
|
-
this.realtime = { totalPower: data['1.7.0']?.value * 1000,
|
|
188
|
-
totalReactivePower: data['2.7.0']?.value * 1000,
|
|
189
|
-
totalExportEnergy: data['2.8.1']?.value + data['2.8.2']?.value,
|
|
190
|
-
totalImportEnergy: data['1.8.1']?.value + data['1.8.2']?.value,
|
|
191
|
-
monitorStatus: 0, utcTimeStamp: new Date().getTime() };
|
|
192
185
|
|
|
193
186
|
this.channels = {
|
|
194
187
|
0: {
|
|
195
|
-
name: "Zonnepanelen L1", type: "CT", flow: "PRODUCTION",
|
|
188
|
+
name: "Zonnepanelen L1", type: "CT", flow: "PRODUCTION", voltage: data['32.7.0']?.value,
|
|
196
189
|
power: data['22.7.0']?.value * 1000, exportEnergy: 0, importEnergy: 0, phaseId: 0,
|
|
197
190
|
current: 0, apparentPower: 0, cosPhi: 0, formula: ""},
|
|
198
191
|
1: {
|
|
199
|
-
name: "Zonnepanelen L2", type: "CT", flow: "PRODUCTION",
|
|
192
|
+
name: "Zonnepanelen L2", type: "CT", flow: "PRODUCTION", voltage: data['52.7.0']?.value,
|
|
200
193
|
power: data['42.7.0']?.value * 1000, exportEnergy: 0, importEnergy: 0, phaseId: 1,
|
|
201
194
|
current: 0, apparentPower: 0, cosPhi: 0, formula: ""},
|
|
202
195
|
2: {
|
|
203
|
-
name: "Zonnepanelen L3", type: "CT", flow: "PRODUCTION",
|
|
196
|
+
name: "Zonnepanelen L3", type: "CT", flow: "PRODUCTION", voltage: data['72.7.0']?.value,
|
|
204
197
|
power: data['62.7.0']?.value * 1000, exportEnergy: 0, importEnergy: 0, phaseId: 2,
|
|
205
198
|
current: 0, apparentPower: 0, cosPhi: 0, formula: ""},
|
|
206
199
|
3: {
|
|
207
|
-
name: "Grid L1", type: "CT", flow: "CONSUMPTION",
|
|
200
|
+
name: "Grid L1", type: "CT", flow: "CONSUMPTION", voltage: data['32.7.0']?.value,
|
|
208
201
|
power: data['21.7.0']?.value * 1000, exportEnergy: 0, importEnergy: 0, phaseId: 0,
|
|
209
202
|
current: data['31.7.0']?.value, apparentPower: 0, cosPhi: 0, formula: ""},
|
|
210
203
|
4: {
|
|
211
|
-
name: "Grid L2", type: "CT", flow: "CONSUMPTION",
|
|
204
|
+
name: "Grid L2", type: "CT", flow: "CONSUMPTION", voltage: data['52.7.0']?.value,
|
|
212
205
|
power: data['41.7.0']?.value * 1000, exportEnergy: 0, importEnergy: 0, phaseId: 1,
|
|
213
206
|
current: data['51.7.0']?.value, apparentPower: 0, cosPhi: 0, formula: ""},
|
|
214
207
|
5: {
|
|
215
|
-
name: "Grid L3", type: "CT", flow: "CONSUMPTION",
|
|
208
|
+
name: "Grid L3", type: "CT", flow: "CONSUMPTION", voltage: data['72.7.0']?.value,
|
|
216
209
|
power: data['61.7.0']?.value * 1000, exportEnergy: 0, importEnergy: 0, phaseId: 2,
|
|
217
210
|
current: data['71.7.0']?.value, apparentPower: 0, cosPhi: 0, formula: ""}
|
|
218
211
|
};
|
|
219
212
|
// console.log(data);
|
|
220
213
|
|
|
214
|
+
this.voltages = [data['32.7.0']?.value, data['52.7.0']?.value, data['72.7.0']?.value];
|
|
215
|
+
this.realtime = { totalPower: data['1.7.0']?.value * 1000,
|
|
216
|
+
totalReactivePower: data['2.7.0']?.value * 1000,
|
|
217
|
+
totalExportEnergy: data['2.8.1']?.value + data['2.8.2']?.value,
|
|
218
|
+
totalImportEnergy: data['1.8.1']?.value + data['1.8.2']?.value,
|
|
219
|
+
monitorStatus: 0, utcTimeStamp: new Date().getTime() };
|
|
220
|
+
|
|
221
221
|
this.production = this.realtime.totalPower;
|
|
222
222
|
this.consumption = this.realtime.totalReactivePower;
|
|
223
|
+
|
|
224
|
+
|
|
223
225
|
}
|
|
224
226
|
}
|
package/server/powerbase.js
CHANGED
|
@@ -72,7 +72,8 @@ class PowerBase extends base_1.Base {
|
|
|
72
72
|
//
|
|
73
73
|
updateConfig(config) {
|
|
74
74
|
return __awaiter(this, void 0, void 0, function* () {
|
|
75
|
-
|
|
75
|
+
if (config.address)
|
|
76
|
+
this.config.address = config.address;
|
|
76
77
|
this.writeConfig();
|
|
77
78
|
return this.init(false);
|
|
78
79
|
});
|
|
@@ -105,7 +106,7 @@ class PowerBase extends base_1.Base {
|
|
|
105
106
|
b.power = value;
|
|
106
107
|
// debug("homebridge", "set unit " + b.name + " to " + value);
|
|
107
108
|
})
|
|
108
|
-
.catch((e) => (0, logger_1.err)(this.type, e));
|
|
109
|
+
.catch((e) => (0, logger_1.err)(this.type, e.message || e));
|
|
109
110
|
}
|
|
110
111
|
else {
|
|
111
112
|
(0, logger_1.debug)("power", "DONT to " + b.channel + " = " + b.power + " -> " + power + " for " + b.name + " (" + b.logicalNodeAddress + ";" + b.logicalAddress + "): difference: " + Math.abs(b.power - power) + " > threshold: " + Math.abs(b.power * kPercentChange));
|
|
@@ -130,10 +131,10 @@ class PowerBase extends base_1.Base {
|
|
|
130
131
|
// Rules execution //
|
|
131
132
|
/////////////////////
|
|
132
133
|
applyRules() {
|
|
133
|
-
if (this.realtimeCounter %
|
|
134
|
-
(0, logger_1.log)("power", this.type + " >
|
|
135
|
-
",
|
|
136
|
-
",
|
|
134
|
+
if (this.realtimeCounter % 60 === 0) {
|
|
135
|
+
(0, logger_1.log)("power", this.type + " > RealTime totals: Power = " + this.realtime.totalPower +
|
|
136
|
+
", Export = " + this.realtime.totalExportEnergy +
|
|
137
|
+
", Import = " + this.realtime.totalImportEnergy);
|
|
137
138
|
}
|
|
138
139
|
if (this.realtimeCounter % 5 === 0) {
|
|
139
140
|
// for all rules
|
package/server/powerbase.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { System } from "../duotecno/system";
|
|
2
2
|
import { PowerBaseConfig, Rule, Binding, Sanitizers, Boundaries, Action, kEmptyBinding, kEmptyRule } from "../duotecno/types";
|
|
3
|
-
import { err, debug, log } from "../duotecno/logger";
|
|
3
|
+
import { err, debug, log, logSettings, LogLevel } from "../duotecno/logger";
|
|
4
4
|
import { Base } from "./base";
|
|
5
5
|
import { Context } from "./webapp";
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ export class PowerBase extends Base {
|
|
|
28
28
|
totalExportEnergy: number, totalImportEnergy: number,
|
|
29
29
|
monitorStatus: number, utcTimeStamp: number }
|
|
30
30
|
channels: { [publishIndex: number]: {
|
|
31
|
-
name: string, type: string, flow: string,
|
|
31
|
+
name: string, type: string, flow: string, voltage: number,
|
|
32
32
|
power: number, exportEnergy: number, importEnergy: number, phaseId: number,
|
|
33
33
|
current: number, apparentPower: number, cosPhi: number, formula: string} };
|
|
34
34
|
|
|
@@ -87,7 +87,9 @@ export class PowerBase extends Base {
|
|
|
87
87
|
// config mgt
|
|
88
88
|
//
|
|
89
89
|
async updateConfig(config: any) {
|
|
90
|
-
|
|
90
|
+
if (config.address)
|
|
91
|
+
this.config.address = config.address;
|
|
92
|
+
|
|
91
93
|
this.writeConfig();
|
|
92
94
|
|
|
93
95
|
return this.init(false);
|
|
@@ -121,7 +123,7 @@ export class PowerBase extends Base {
|
|
|
121
123
|
b.power = value;
|
|
122
124
|
// debug("homebridge", "set unit " + b.name + " to " + value);
|
|
123
125
|
})
|
|
124
|
-
.catch((e) => err(this.type, e));
|
|
126
|
+
.catch((e) => err(this.type, e.message || e));
|
|
125
127
|
|
|
126
128
|
} else {
|
|
127
129
|
debug("power", "DONT to " + b.channel + " = " + b.power + " -> " + power + " for " + b.name + " (" + b.logicalNodeAddress + ";" + b.logicalAddress + "): difference: " + Math.abs(b.power - power) + " > threshold: " + Math.abs(b.power * kPercentChange))
|
|
@@ -147,10 +149,10 @@ export class PowerBase extends Base {
|
|
|
147
149
|
// Rules execution //
|
|
148
150
|
/////////////////////
|
|
149
151
|
applyRules() {
|
|
150
|
-
if (this.realtimeCounter %
|
|
151
|
-
log("power", this.type + " >
|
|
152
|
-
",
|
|
153
|
-
",
|
|
152
|
+
if (this.realtimeCounter % 60 === 0) {
|
|
153
|
+
log("power", this.type + " > RealTime totals: Power = " + this.realtime.totalPower +
|
|
154
|
+
", Export = " + this.realtime.totalExportEnergy +
|
|
155
|
+
", Import = " + this.realtime.totalImportEnergy);
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
if (this.realtimeCounter % 5 === 0) {
|
package/server/shelly.js
CHANGED
|
@@ -15,10 +15,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.Shelly = void 0;
|
|
16
16
|
const logger_1 = require("../duotecno/logger");
|
|
17
17
|
const powerbase_1 = require("./powerbase");
|
|
18
|
+
const api_1 = require("../duotecno/api");
|
|
19
|
+
const HTTPstatus = {
|
|
20
|
+
"wifi_sta": {
|
|
21
|
+
"connected": true,
|
|
22
|
+
"ssid": "Tele Coppieters",
|
|
23
|
+
"ip": "192.168.0.95",
|
|
24
|
+
"rssi": -73
|
|
25
|
+
},
|
|
26
|
+
"cloud": {
|
|
27
|
+
"enabled": true,
|
|
28
|
+
"connected": true
|
|
29
|
+
},
|
|
30
|
+
"mqtt": {
|
|
31
|
+
"connected": false
|
|
32
|
+
},
|
|
33
|
+
"time": "18:17",
|
|
34
|
+
"unixtime": 1663863464,
|
|
35
|
+
"serial": 7,
|
|
36
|
+
"has_update": true,
|
|
37
|
+
"mac": "C45BBE7928B9",
|
|
38
|
+
"cfg_changed_cnt": 1,
|
|
39
|
+
"actions_stats": { "skipped": 0 },
|
|
40
|
+
"relays": [{ "ison": true, "has_timer": false, "timer_started": 0, "timer_duration": 0, "timer_remaining": 0, "overpower": false, "is_valid": true, "source": "http" }],
|
|
41
|
+
"emeters": [
|
|
42
|
+
{ "power": 10.66, "pf": 1.00, "current": 0.08, "voltage": 232.06, "is_valid": true, "total": 5.3, "total_returned": 0.0 },
|
|
43
|
+
{ "power": 0.00, "pf": 0.00, "current": 0.01, "voltage": 232.06, "is_valid": true, "total": 0.0, "total_returned": 0.0 },
|
|
44
|
+
{ "power": 0.00, "pf": 0.00, "current": 0.01, "voltage": 232.05, "is_valid": true, "total": 0.0, "total_returned": 0.0 }
|
|
45
|
+
],
|
|
46
|
+
"total_power": 10.66,
|
|
47
|
+
"fs_mounted": true,
|
|
48
|
+
"update": { "status": "pending", "has_update": true, "new_version": "20220830-080542/v1.12-3EM-gcf4f7c2", "old_version": "20220209-094824/v1.11.8-g8c7bb8d" },
|
|
49
|
+
"ram_total": 49440,
|
|
50
|
+
"ram_free": 30960,
|
|
51
|
+
"fs_size": 233681,
|
|
52
|
+
"fs_free": 156875,
|
|
53
|
+
"uptime": 1364
|
|
54
|
+
};
|
|
55
|
+
const HttpEMeter0 = {
|
|
56
|
+
"power": 10.95,
|
|
57
|
+
"pf": 1.00,
|
|
58
|
+
"current": 0.08,
|
|
59
|
+
"voltage": 231.21,
|
|
60
|
+
"is_valid": true,
|
|
61
|
+
"total": 7.2,
|
|
62
|
+
"total_returned": 0.0
|
|
63
|
+
};
|
|
18
64
|
class Shelly extends powerbase_1.PowerBase {
|
|
19
65
|
constructor(system) {
|
|
20
|
-
super("
|
|
66
|
+
super("shelly", system);
|
|
21
67
|
this.timer = null;
|
|
68
|
+
this.meters = [];
|
|
69
|
+
this.nrPhases = 0;
|
|
22
70
|
// init is called by super
|
|
23
71
|
}
|
|
24
72
|
//
|
|
@@ -31,22 +79,43 @@ class Shelly extends powerbase_1.PowerBase {
|
|
|
31
79
|
//
|
|
32
80
|
// config mgt
|
|
33
81
|
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
82
|
+
updateConfig(config) {
|
|
83
|
+
const _super = Object.create(null, {
|
|
84
|
+
updateConfig: { get: () => super.updateConfig }
|
|
85
|
+
});
|
|
86
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
this.config.addresses = config.addresses;
|
|
88
|
+
return _super.updateConfig.call(this, config);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
38
91
|
subscribe() {
|
|
39
92
|
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
-
|
|
93
|
+
const kInterval = 5;
|
|
41
94
|
if (this.config.address) {
|
|
42
|
-
this.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
95
|
+
yield this.getDeviceConfig(this.config.address, true);
|
|
96
|
+
}
|
|
97
|
+
if (this.config.addresses) {
|
|
98
|
+
const list = this.config.addresses.split(",");
|
|
99
|
+
for (let inx in list) {
|
|
100
|
+
const phases = (this.config.address) ? false : (inx == "0");
|
|
101
|
+
yield this.getDeviceConfig(list[inx], phases);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
(0, logger_1.debug)("shelly", "meters: " + JSON.stringify(this.meters));
|
|
105
|
+
if (this.meters.length) {
|
|
106
|
+
this.timer = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
107
|
+
this.realtimeCounter += kInterval;
|
|
108
|
+
// fetch data
|
|
109
|
+
const ok = yield this.fetchData();
|
|
110
|
+
if (ok)
|
|
111
|
+
this.consume();
|
|
46
112
|
// call process/bindings/rules
|
|
47
113
|
this.applyBindings();
|
|
48
114
|
this.applyRules();
|
|
49
|
-
},
|
|
115
|
+
}), kInterval * 1000);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
(0, logger_1.log)("shelly", "No Shelly PM devices configured");
|
|
50
119
|
}
|
|
51
120
|
});
|
|
52
121
|
}
|
|
@@ -58,6 +127,70 @@ class Shelly extends powerbase_1.PowerBase {
|
|
|
58
127
|
}
|
|
59
128
|
});
|
|
60
129
|
}
|
|
130
|
+
getDeviceConfig(address, phases) {
|
|
131
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
try {
|
|
133
|
+
// debug("shelly", "fetching from " + address + "/status");
|
|
134
|
+
const res = yield (0, api_1.get)(address, 80, "/status", undefined);
|
|
135
|
+
(0, logger_1.debug)("shelly", "received status from " + address + " -> " + res);
|
|
136
|
+
const status = JSON.parse(res);
|
|
137
|
+
// enrich meter with its address and index + add to the global meter list
|
|
138
|
+
status.emeters.forEach((m, inx) => {
|
|
139
|
+
m.index = inx; // index with this PM device
|
|
140
|
+
m.phase = phases;
|
|
141
|
+
if (phases) {
|
|
142
|
+
m.phaseNr = this.nrPhases;
|
|
143
|
+
this.nrPhases++;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
m.phaseNr = -1;
|
|
147
|
+
}
|
|
148
|
+
m.address = address;
|
|
149
|
+
this.meters.push(m);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
(0, logger_1.err)("shelly", error.message);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
fetchData() {
|
|
158
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
159
|
+
try {
|
|
160
|
+
for (let meter in this.meters) {
|
|
161
|
+
// debug("shelly", "fetching from " + this.config.address + "/emeter/" + meter);
|
|
162
|
+
const res = yield (0, api_1.get)(this.meters[meter].address, 80, "/emeter/" + this.meters[meter].index, undefined);
|
|
163
|
+
(0, logger_1.debug)("shelly", "received meter: " + meter + " -> " + res);
|
|
164
|
+
this.meters[meter] = Object.assign(Object.assign({}, this.meters[meter]), JSON.parse(res));
|
|
165
|
+
}
|
|
166
|
+
;
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
(0, logger_1.err)("shelly", error.message || error);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
consume() {
|
|
176
|
+
// copy this.meters into the powerbase structures
|
|
177
|
+
this.meters.forEach((meter, inx) => {
|
|
178
|
+
this.channels[inx] = {
|
|
179
|
+
name: "Meter " + (inx + 1), type: "CT", flow: "PRODUCTION",
|
|
180
|
+
power: meter.power, exportEnergy: meter.total_returned, importEnergy: meter.total, phaseId: meter.phaseNr,
|
|
181
|
+
current: meter.power / meter.voltage, apparentPower: 0, cosPhi: 0, formula: "", voltage: meter.voltage
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
this.voltages = [0, 0, 0];
|
|
185
|
+
this.meters.filter(m => m.phase).forEach(m => this.voltages[m.phaseNr] = m.voltage);
|
|
186
|
+
this.realtime = { totalPower: this.meters.reduce((prev, curr) => curr.power + prev, 0),
|
|
187
|
+
totalReactivePower: 0,
|
|
188
|
+
totalExportEnergy: this.meters.reduce((prev, curr) => curr.total_returned + prev, 0),
|
|
189
|
+
totalImportEnergy: this.meters.reduce((prev, curr) => curr.total + prev, 0),
|
|
190
|
+
monitorStatus: 0, utcTimeStamp: new Date().getTime() };
|
|
191
|
+
this.production = this.realtime.totalPower;
|
|
192
|
+
this.consumption = this.realtime.totalReactivePower;
|
|
193
|
+
}
|
|
61
194
|
}
|
|
62
195
|
exports.Shelly = Shelly;
|
|
63
196
|
//# sourceMappingURL=shelly.js.map
|
package/server/shelly.ts
CHANGED
|
@@ -3,21 +3,86 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { System } from "../duotecno/system";
|
|
6
|
-
import {
|
|
7
|
-
import { debug, log } from "../duotecno/logger";
|
|
6
|
+
import { ShellyConfig } from "../duotecno/types";
|
|
7
|
+
import { debug, err, log } from "../duotecno/logger";
|
|
8
8
|
import { PowerBase } from "./powerbase";
|
|
9
|
+
import { get } from "../duotecno/api";
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
const HTTPstatus = {
|
|
13
|
+
"wifi_sta": {
|
|
14
|
+
"connected": true,
|
|
15
|
+
"ssid": "Tele Coppieters",
|
|
16
|
+
"ip": "192.168.0.95",
|
|
17
|
+
"rssi": -73
|
|
18
|
+
},
|
|
19
|
+
"cloud": {
|
|
20
|
+
"enabled": true,
|
|
21
|
+
"connected": true
|
|
22
|
+
},
|
|
23
|
+
"mqtt": {
|
|
24
|
+
"connected": false
|
|
25
|
+
},
|
|
26
|
+
"time": "18:17",
|
|
27
|
+
"unixtime": 1663863464,
|
|
28
|
+
"serial": 7,
|
|
29
|
+
"has_update": true,
|
|
30
|
+
"mac": "C45BBE7928B9",
|
|
31
|
+
"cfg_changed_cnt": 1,
|
|
32
|
+
"actions_stats": { "skipped": 0 },
|
|
33
|
+
"relays": [{ "ison": true, "has_timer": false, "timer_started": 0, "timer_duration": 0, "timer_remaining": 0, "overpower": false, "is_valid": true, "source": "http" }],
|
|
34
|
+
"emeters": [
|
|
35
|
+
{ "power": 10.66, "pf": 1.00, "current": 0.08, "voltage": 232.06, "is_valid": true, "total": 5.3, "total_returned": 0.0 },
|
|
36
|
+
{ "power": 0.00, "pf": 0.00, "current": 0.01, "voltage": 232.06, "is_valid": true, "total": 0.0, "total_returned": 0.0 },
|
|
37
|
+
{ "power": 0.00, "pf": 0.00, "current": 0.01, "voltage": 232.05, "is_valid": true, "total": 0.0, "total_returned": 0.0 }
|
|
38
|
+
],
|
|
39
|
+
"total_power": 10.66,
|
|
40
|
+
"fs_mounted": true,
|
|
41
|
+
"update": { "status": "pending", "has_update": true, "new_version": "20220830-080542/v1.12-3EM-gcf4f7c2", "old_version": "20220209-094824/v1.11.8-g8c7bb8d" },
|
|
42
|
+
"ram_total": 49440,
|
|
43
|
+
"ram_free": 30960,
|
|
44
|
+
"fs_size": 233681,
|
|
45
|
+
"fs_free": 156875,
|
|
46
|
+
"uptime": 1364
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const HttpEMeter0 = {
|
|
50
|
+
"power":10.95,
|
|
51
|
+
"pf":1.00,
|
|
52
|
+
"current":0.08,
|
|
53
|
+
"voltage":231.21,
|
|
54
|
+
"is_valid":true,
|
|
55
|
+
"total":7.2,
|
|
56
|
+
"total_returned":0.0
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
interface Meter {
|
|
60
|
+
address: string;
|
|
61
|
+
index: number;
|
|
62
|
+
phase: boolean;
|
|
63
|
+
phaseNr: number;
|
|
64
|
+
power: number;
|
|
65
|
+
pf: number;
|
|
66
|
+
current: number;
|
|
67
|
+
voltage: number;
|
|
68
|
+
is_valid: boolean;
|
|
69
|
+
total: number;
|
|
70
|
+
total_returned: number;
|
|
71
|
+
}
|
|
11
72
|
|
|
12
73
|
export class Shelly extends PowerBase {
|
|
13
|
-
config:
|
|
74
|
+
config: ShellyConfig; // redefine
|
|
14
75
|
timer: NodeJS.Timer;
|
|
15
76
|
|
|
77
|
+
meters: Array<Meter>;
|
|
78
|
+
nrPhases: number;
|
|
16
79
|
|
|
17
80
|
constructor(system: System) {
|
|
18
|
-
super("
|
|
81
|
+
super("shelly", system);
|
|
19
82
|
|
|
20
83
|
this.timer = null;
|
|
84
|
+
this.meters = [];
|
|
85
|
+
this.nrPhases = 0;
|
|
21
86
|
// init is called by super
|
|
22
87
|
}
|
|
23
88
|
|
|
@@ -33,26 +98,43 @@ export class Shelly extends PowerBase {
|
|
|
33
98
|
//
|
|
34
99
|
// config mgt
|
|
35
100
|
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
101
|
+
async updateConfig(config: any) {
|
|
102
|
+
this.config.addresses = config.addresses;
|
|
103
|
+
return super.updateConfig(config);
|
|
104
|
+
}
|
|
40
105
|
|
|
41
106
|
|
|
42
107
|
async subscribe() {
|
|
43
|
-
|
|
44
|
-
|
|
108
|
+
const kInterval = 5;
|
|
109
|
+
|
|
45
110
|
if (this.config.address) {
|
|
46
|
-
this.
|
|
47
|
-
|
|
111
|
+
await this.getDeviceConfig(this.config.address, true);
|
|
112
|
+
}
|
|
113
|
+
if (this.config.addresses) {
|
|
114
|
+
const list = this.config.addresses.split(",");
|
|
115
|
+
for (let inx in list) {
|
|
116
|
+
const phases = (this.config.address) ? false : (inx == "0");
|
|
117
|
+
await this.getDeviceConfig(list[inx], phases);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
debug("shelly", "meters: " + JSON.stringify(this.meters));
|
|
121
|
+
|
|
122
|
+
if (this.meters.length) {
|
|
123
|
+
this.timer = setInterval(async () => {
|
|
124
|
+
this.realtimeCounter += kInterval;
|
|
48
125
|
|
|
49
|
-
// fetch data
|
|
50
|
-
|
|
126
|
+
// fetch data
|
|
127
|
+
const ok = await this.fetchData();
|
|
128
|
+
if (ok)
|
|
129
|
+
this.consume();
|
|
51
130
|
|
|
52
131
|
// call process/bindings/rules
|
|
53
132
|
this.applyBindings();
|
|
54
133
|
this.applyRules();
|
|
55
|
-
},
|
|
134
|
+
}, kInterval * 1000);
|
|
135
|
+
|
|
136
|
+
} else {
|
|
137
|
+
log("shelly", "No Shelly PM devices configured");
|
|
56
138
|
}
|
|
57
139
|
}
|
|
58
140
|
|
|
@@ -63,4 +145,68 @@ export class Shelly extends PowerBase {
|
|
|
63
145
|
}
|
|
64
146
|
}
|
|
65
147
|
|
|
148
|
+
async getDeviceConfig(address: string, phases: boolean) {
|
|
149
|
+
try {
|
|
150
|
+
// debug("shelly", "fetching from " + address + "/status");
|
|
151
|
+
const res = await get(address, 80, "/status", undefined);
|
|
152
|
+
debug("shelly", "received status from " + address + " -> " + res);
|
|
153
|
+
const status = JSON.parse(res);
|
|
154
|
+
|
|
155
|
+
// enrich meter with its address and index + add to the global meter list
|
|
156
|
+
status.emeters.forEach((m, inx) => {
|
|
157
|
+
m.index = inx; // index with this PM device
|
|
158
|
+
m.phase = phases;
|
|
159
|
+
if (phases) {
|
|
160
|
+
m.phaseNr = this.nrPhases;
|
|
161
|
+
this.nrPhases++;
|
|
162
|
+
} else {
|
|
163
|
+
m.phaseNr = -1;
|
|
164
|
+
}
|
|
165
|
+
m.address = address;
|
|
166
|
+
this.meters.push(m);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
} catch(error) {
|
|
170
|
+
err("shelly", error.message)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async fetchData(): Promise<boolean> {
|
|
175
|
+
try {
|
|
176
|
+
for (let meter in this.meters) {
|
|
177
|
+
// debug("shelly", "fetching from " + this.config.address + "/emeter/" + meter);
|
|
178
|
+
const res = await get(this.meters[meter].address, 80, "/emeter/"+this.meters[meter].index, undefined);
|
|
179
|
+
debug("shelly", "received meter: " + meter + " -> " + res);
|
|
180
|
+
this.meters[meter] = {... this.meters[meter], ...JSON.parse(res)};
|
|
181
|
+
};
|
|
182
|
+
return true;
|
|
183
|
+
|
|
184
|
+
} catch(error) {
|
|
185
|
+
err("shelly", error.message || error);
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
consume() {
|
|
191
|
+
// copy this.meters into the powerbase structures
|
|
192
|
+
this.meters.forEach((meter, inx) => {
|
|
193
|
+
this.channels[inx] = {
|
|
194
|
+
name: "Meter " + (inx+1), type: "CT", flow: "PRODUCTION",
|
|
195
|
+
power: meter.power, exportEnergy: meter.total_returned, importEnergy: meter.total, phaseId: meter.phaseNr,
|
|
196
|
+
current: meter.power / meter.voltage, apparentPower: 0, cosPhi: 0, formula: "", voltage: meter.voltage}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
this.voltages = [0,0,0];
|
|
200
|
+
this.meters.filter(m => m.phase).forEach(m => this.voltages[m.phaseNr] = m.voltage);
|
|
201
|
+
|
|
202
|
+
this.realtime = { totalPower: this.meters.reduce((prev, curr) => curr.power + prev, 0),
|
|
203
|
+
totalReactivePower: 0,
|
|
204
|
+
totalExportEnergy: this.meters.reduce((prev, curr) => curr.total_returned + prev, 0),
|
|
205
|
+
totalImportEnergy: this.meters.reduce((prev, curr) => curr.total + prev, 0),
|
|
206
|
+
monitorStatus: 0, utcTimeStamp: new Date().getTime() };
|
|
207
|
+
|
|
208
|
+
this.production = this.realtime.totalPower;
|
|
209
|
+
this.consumption = this.realtime.totalReactivePower;
|
|
210
|
+
}
|
|
211
|
+
|
|
66
212
|
}
|
package/server/smappee.js
CHANGED
|
@@ -213,6 +213,9 @@ class Smappee extends powerbase_1.PowerBase {
|
|
|
213
213
|
this.realtime = { totalPower: message.totalPower, totalReactivePower: message.totalReactivePower,
|
|
214
214
|
totalExportEnergy: message.totalExportEnergy, totalImportEnergy: message.totalImportEnergy,
|
|
215
215
|
monitorStatus: message.monitorStatus, utcTimeStamp: message.utcTimeStamp };
|
|
216
|
+
message.voltages.forEach(v => {
|
|
217
|
+
this.voltages[v.phaseId] = v.voltage;
|
|
218
|
+
});
|
|
216
219
|
message.channelPowers.forEach(p => {
|
|
217
220
|
const inx = p.publishIndex;
|
|
218
221
|
if (!this.channels[inx]) {
|
|
@@ -223,14 +226,12 @@ class Smappee extends powerbase_1.PowerBase {
|
|
|
223
226
|
this.channels[inx].exportEnergy = p.exportEnergy;
|
|
224
227
|
this.channels[inx].importEnergy = p.importEnergy;
|
|
225
228
|
this.channels[inx].phaseId = p.phaseId;
|
|
229
|
+
this.channels[inx].voltage = this.voltages[p.phaseId] || 0;
|
|
226
230
|
this.channels[inx].current = p.current;
|
|
227
231
|
this.channels[inx].apparentPower = p.apparentPower;
|
|
228
232
|
this.channels[inx].cosPhi = p.cosPhi;
|
|
229
233
|
this.channels[inx].formula = p.formula;
|
|
230
234
|
});
|
|
231
|
-
message.voltages.forEach(v => {
|
|
232
|
-
this.voltages[v.phaseId] = v.voltage;
|
|
233
|
-
});
|
|
234
235
|
this.calcProducAndConsumption(message);
|
|
235
236
|
}
|
|
236
237
|
// check rules
|
package/server/smappee.ts
CHANGED
|
@@ -215,6 +215,10 @@ export class Smappee extends PowerBase {
|
|
|
215
215
|
totalExportEnergy: message.totalExportEnergy, totalImportEnergy: message.totalImportEnergy,
|
|
216
216
|
monitorStatus: message.monitorStatus, utcTimeStamp: message.utcTimeStamp };
|
|
217
217
|
|
|
218
|
+
message.voltages.forEach(v => {
|
|
219
|
+
this.voltages[v.phaseId] = v.voltage;
|
|
220
|
+
});
|
|
221
|
+
|
|
218
222
|
message.channelPowers.forEach(p => {
|
|
219
223
|
const inx = p.publishIndex;
|
|
220
224
|
if (! this.channels[inx]) {
|
|
@@ -225,14 +229,13 @@ export class Smappee extends PowerBase {
|
|
|
225
229
|
this.channels[inx].exportEnergy = p.exportEnergy;
|
|
226
230
|
this.channels[inx].importEnergy = p.importEnergy;
|
|
227
231
|
this.channels[inx].phaseId = p.phaseId;
|
|
232
|
+
this.channels[inx].voltage = this.voltages[p.phaseId] || 0;
|
|
228
233
|
this.channels[inx].current = p.current;
|
|
229
234
|
this.channels[inx].apparentPower = p.apparentPower;
|
|
230
235
|
this.channels[inx].cosPhi = p.cosPhi;
|
|
231
236
|
this.channels[inx].formula = p.formula;
|
|
232
237
|
});
|
|
233
|
-
|
|
234
|
-
this.voltages[v.phaseId] = v.voltage;
|
|
235
|
-
});
|
|
238
|
+
|
|
236
239
|
|
|
237
240
|
this.calcProducAndConsumption(message);
|
|
238
241
|
}
|
package/server/smartapp.js
CHANGED
|
@@ -30,6 +30,7 @@ const api_1 = require("../duotecno/api");
|
|
|
30
30
|
const config_1 = require("../duotecno/config");
|
|
31
31
|
const kMaster = { name: "master", type: "string", default: "0.0.0.0:5001" };
|
|
32
32
|
const kAddress = { name: "address", type: "string", default: "0.0.0.0" };
|
|
33
|
+
const kAddresses = { name: "addresses", type: "string", default: "[0.0.0.0]" };
|
|
33
34
|
const kAddressOld = { name: "addressX", type: "string", default: "0.0.0.0" };
|
|
34
35
|
const kPort = { name: "port", type: "integer", default: 80 };
|
|
35
36
|
const kPortOld = { name: "portX", type: "integer", default: 80 };
|
|
@@ -328,7 +329,7 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
328
329
|
power.updateRule(id, this.scrapeRule(context));
|
|
329
330
|
}
|
|
330
331
|
else if (context.action === "save") {
|
|
331
|
-
power.updateConfig({ address: context.getParam(kAddress), uid: context.getParam(kUID) });
|
|
332
|
+
power.updateConfig({ address: context.getParam(kAddress), uid: context.getParam(kUID), addresses: context.getParam(kAddresses), });
|
|
332
333
|
}
|
|
333
334
|
else if (context.action === "badd") {
|
|
334
335
|
const binding = power.newBinding();
|
|
@@ -818,7 +819,6 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
818
819
|
updateNodes(master, nodes, params) {
|
|
819
820
|
return __awaiter(this, void 0, void 0, function* () {
|
|
820
821
|
if (nodes) {
|
|
821
|
-
// let nodeArr = nodes.split(",").map(s => parseInt(s));
|
|
822
822
|
let nodeArr = nodes.map(s => parseInt(s));
|
|
823
823
|
nodeArr.forEach(adr => {
|
|
824
824
|
let node = this.system.findNode(master, adr);
|
|
@@ -875,7 +875,7 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
875
875
|
else { // context.action === "node"
|
|
876
876
|
// reponding to /units/[master ip address:port]/[logical node address]
|
|
877
877
|
const nodeLogicalAddress = parseInt(context.id);
|
|
878
|
-
let response = yield this.getNodeInfo(master, nodeLogicalAddress);
|
|
878
|
+
let response = yield this.getNodeInfo(master, nodeLogicalAddress, true);
|
|
879
879
|
let N = this.sortCopy(response.node, context);
|
|
880
880
|
return this.ejs("nodeDetail", context, { message: response.message, node: N });
|
|
881
881
|
}
|
|
@@ -908,7 +908,7 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
908
908
|
this.system.updateSystem(true);
|
|
909
909
|
}
|
|
910
910
|
}
|
|
911
|
-
getNodeInfo(master, nodeLogicalAddress) {
|
|
911
|
+
getNodeInfo(master, nodeLogicalAddress, doFetch = false) {
|
|
912
912
|
return __awaiter(this, void 0, void 0, function* () {
|
|
913
913
|
let node = this.system.findNode(master, nodeLogicalAddress);
|
|
914
914
|
if (!node) {
|
|
@@ -916,7 +916,7 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
916
916
|
return { node: new protocol_1.Node(this.system.masters[0], { name: "No node" }), message: "Node not found" };
|
|
917
917
|
}
|
|
918
918
|
else {
|
|
919
|
-
if (node.nrUnits != node.units.length)
|
|
919
|
+
if ((node.nrUnits != node.units.length) || doFetch)
|
|
920
920
|
yield master.fetchAllUnits(node);
|
|
921
921
|
yield node.master.requestNodeStatus(node);
|
|
922
922
|
return { node };
|