iobroker.senec 1.6.11 → 1.6.13
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/LICENSE +2 -2
- package/README.md +9 -11
- package/admin/jsonConfig.json +1 -1
- package/io-package.json +27 -40
- package/lib/api_trans.js +20 -21
- package/lib/state_attr.js +5606 -5606
- package/lib/state_trans.js +473 -468
- package/lib/tools.js +50 -52
- package/main.js +722 -484
- package/package.json +31 -24
package/main.js
CHANGED
|
@@ -1,41 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
//process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; // not cool, not nice - but well ... just a last option if everything else fails
|
|
3
3
|
|
|
4
|
-
const https = require(
|
|
5
|
-
const agent = new https.Agent({
|
|
4
|
+
const https = require("https");
|
|
5
|
+
const agent = new https.Agent({
|
|
6
6
|
requestCert: true,
|
|
7
|
-
rejectUnauthorized: false
|
|
7
|
+
rejectUnauthorized: false,
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
-
const utils = require(
|
|
10
|
+
const utils = require("@iobroker/adapter-core");
|
|
11
11
|
|
|
12
|
-
const axios = require(
|
|
13
|
-
axios.defaults.headers.post[
|
|
12
|
+
const axios = require("axios").default;
|
|
13
|
+
axios.defaults.headers.post["Content-Type"] = "application/json";
|
|
14
14
|
|
|
15
|
-
const state_attr = require(__dirname +
|
|
16
|
-
const state_trans = require(__dirname +
|
|
17
|
-
const api_trans = require(__dirname +
|
|
15
|
+
const state_attr = require(__dirname + "/lib/state_attr.js");
|
|
16
|
+
const state_trans = require(__dirname + "/lib/state_trans.js");
|
|
17
|
+
const api_trans = require(__dirname + "/lib/api_trans.js");
|
|
18
18
|
const kiloList = ["W", "Wh"];
|
|
19
19
|
|
|
20
20
|
const apiUrl = "https://app-gateway.prod.senec.dev/v1/senec";
|
|
21
21
|
const apiLoginUrl = apiUrl + "/login";
|
|
22
22
|
const apiSystemsUrl = apiUrl + "/systems";
|
|
23
23
|
const apiMonitorUrl = apiUrl + "/monitor";
|
|
24
|
-
const apiKnownSystems = []
|
|
24
|
+
const apiKnownSystems = [];
|
|
25
25
|
|
|
26
|
-
const batteryOn =
|
|
27
|
-
|
|
26
|
+
const batteryOn =
|
|
27
|
+
'{"ENERGY":{"SAFE_CHARGE_FORCE":"u8_01","SAFE_CHARGE_PROHIBIT":"","SAFE_CHARGE_RUNNING":"","LI_STORAGE_MODE_START":"","LI_STORAGE_MODE_STOP":"","LI_STORAGE_MODE_RUNNING":"","STAT_STATE":""}}';
|
|
28
|
+
const batteryOff =
|
|
29
|
+
'{"ENERGY":{"SAFE_CHARGE_FORCE":"","SAFE_CHARGE_PROHIBIT":"u8_01","SAFE_CHARGE_RUNNING":"","LI_STORAGE_MODE_START":"","LI_STORAGE_MODE_STOP":"","LI_STORAGE_MODE_RUNNING":"","STAT_STATE":""}}';
|
|
28
30
|
|
|
29
31
|
let apiConnected = false;
|
|
30
32
|
let lalaConnected = false;
|
|
31
33
|
let apiLoginToken = "";
|
|
32
|
-
let retry = 0; // retry-counter
|
|
33
|
-
let retryLowPrio = 0; // retry-counter
|
|
34
34
|
let connectVia = "http://";
|
|
35
35
|
|
|
36
|
-
const allKnownObjects = new Set([
|
|
36
|
+
const allKnownObjects = new Set([
|
|
37
|
+
"BAT1",
|
|
38
|
+
"BAT1OBJ1",
|
|
39
|
+
"BMS",
|
|
40
|
+
"BMS_PARA",
|
|
41
|
+
"BMZ_CURRENT_LIMITS",
|
|
42
|
+
"CASC",
|
|
43
|
+
"CELL_DEVIATION_ROC",
|
|
44
|
+
"CURRENT_IMBALANCE_CONTROL",
|
|
45
|
+
"DEBUG",
|
|
46
|
+
"ENERGY",
|
|
47
|
+
"FACTORY",
|
|
48
|
+
"FEATURES",
|
|
49
|
+
"GRIDCONFIG",
|
|
50
|
+
"ISKRA",
|
|
51
|
+
"LOG",
|
|
52
|
+
"PM1",
|
|
53
|
+
"PM1OBJ1",
|
|
54
|
+
"PM1OBJ2",
|
|
55
|
+
"PV1",
|
|
56
|
+
"PWR_UNIT",
|
|
57
|
+
"RTC",
|
|
58
|
+
"SENEC_IO_INPUT",
|
|
59
|
+
"SENEC_IO_OUTPUT",
|
|
60
|
+
"SELFTEST_RESULTS",
|
|
61
|
+
"SOCKETS",
|
|
62
|
+
"STECA",
|
|
63
|
+
"SYS_UPDATE",
|
|
64
|
+
"TEMPMEASURE",
|
|
65
|
+
"TEST",
|
|
66
|
+
"UPDATE",
|
|
67
|
+
"WALLBOX",
|
|
68
|
+
"WIZARD",
|
|
69
|
+
]);
|
|
37
70
|
|
|
38
|
-
const highPrioObjects = new Map;
|
|
71
|
+
const highPrioObjects = new Map();
|
|
39
72
|
let lowPrioForm = "";
|
|
40
73
|
let highPrioForm = "";
|
|
41
74
|
|
|
@@ -44,30 +77,29 @@ let unloaded = false;
|
|
|
44
77
|
const knownObjects = {};
|
|
45
78
|
|
|
46
79
|
class Senec extends utils.Adapter {
|
|
80
|
+
/**
|
|
81
|
+
* @param {Partial<ioBroker.AdapterOptions>} [options={}]
|
|
82
|
+
*/
|
|
83
|
+
constructor(options) {
|
|
84
|
+
super({
|
|
85
|
+
...options,
|
|
86
|
+
name: "senec",
|
|
87
|
+
});
|
|
88
|
+
this.on("ready", this.onReady.bind(this));
|
|
89
|
+
this.on("stateChange", this.onStateChange.bind(this));
|
|
90
|
+
this.on("unload", this.onUnload.bind(this));
|
|
91
|
+
}
|
|
47
92
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this.on('unload', this.onUnload.bind(this));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Is called when databases are connected and adapter received configuration.
|
|
63
|
-
*/
|
|
64
|
-
async onReady() {
|
|
65
|
-
// Initialize your adapter here
|
|
66
|
-
|
|
67
|
-
// Reset the connection indicator during startup
|
|
68
|
-
this.setState('info.connection', false, true);
|
|
69
|
-
try {
|
|
70
|
-
await this.checkConfig();
|
|
93
|
+
/**
|
|
94
|
+
* Is called when databases are connected and adapter received configuration.
|
|
95
|
+
*/
|
|
96
|
+
async onReady() {
|
|
97
|
+
// Initialize your adapter here
|
|
98
|
+
|
|
99
|
+
// Reset the connection indicator during startup
|
|
100
|
+
this.setState("info.connection", false, true);
|
|
101
|
+
try {
|
|
102
|
+
await this.checkConfig();
|
|
71
103
|
if (this.config.lala_use) {
|
|
72
104
|
this.log.info("Usage of lala.cgi configured.");
|
|
73
105
|
await this.initPollSettings();
|
|
@@ -87,43 +119,54 @@ class Senec extends utils.Adapter {
|
|
|
87
119
|
await this.pollSenecAppApi(0); // App API
|
|
88
120
|
}
|
|
89
121
|
} else {
|
|
90
|
-
this.log.warn(
|
|
122
|
+
this.log.warn(
|
|
123
|
+
"Usage of SENEC App API not configured. Only polling appliance via local network if configured.",
|
|
124
|
+
);
|
|
91
125
|
}
|
|
92
|
-
|
|
93
126
|
if (lalaConnected || apiConnected) {
|
|
94
|
-
this.setState(
|
|
127
|
+
this.setState("info.connection", true, true);
|
|
95
128
|
} else {
|
|
96
129
|
this.log.error("Neither local connection nor API connection configured. Please check config!");
|
|
97
130
|
}
|
|
98
131
|
if (this.config.control_active) {
|
|
99
132
|
this.log.info("Active appliance control activated!");
|
|
100
133
|
await this.subscribeStatesAsync("control.*"); // subscribe on all state changes in control.
|
|
101
|
-
await this.subscribeStatesAsync("ENERGY.STAT_STATE");
|
|
134
|
+
await this.subscribeStatesAsync("ENERGY.STAT_STATE");
|
|
102
135
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
136
|
+
} catch (error) {
|
|
137
|
+
this.log.error(error);
|
|
138
|
+
this.setState("info.connection", false, true);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
109
142
|
/**
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
143
|
+
* @param {string} id
|
|
144
|
+
* @param {ioBroker.State | null | undefined} state
|
|
145
|
+
*/
|
|
146
|
+
async onStateChange(id, state) {
|
|
147
|
+
if (state && !state.ack) {
|
|
115
148
|
this.log.debug("State changed: " + id + " ( " + JSON.stringify(state) + " )");
|
|
116
|
-
|
|
117
|
-
|
|
149
|
+
if (this.config.control_active) {
|
|
150
|
+
// All state-changes for .control.* need active config value
|
|
118
151
|
if (id === this.namespace + ".control.ForceLoadBattery" && lalaConnected) {
|
|
119
152
|
const url = connectVia + this.config.senecip + "/lala.cgi";
|
|
120
153
|
try {
|
|
121
154
|
if (state.val) {
|
|
122
|
-
this.log.info("Enable force battery charging ...");
|
|
123
|
-
this.evalPoll(
|
|
155
|
+
this.log.info("Enable force battery charging ...");
|
|
156
|
+
this.evalPoll(
|
|
157
|
+
JSON.parse(
|
|
158
|
+
await this.doGet(url, batteryOn, this, this.config.pollingTimeout, true),
|
|
159
|
+
reviverNumParse,
|
|
160
|
+
),
|
|
161
|
+
);
|
|
124
162
|
} else {
|
|
125
163
|
this.log.info("Disable force battery charging ...");
|
|
126
|
-
this.evalPoll(
|
|
164
|
+
this.evalPoll(
|
|
165
|
+
JSON.parse(
|
|
166
|
+
await this.doGet(url, batteryOff, this, this.config.pollingTimeout, true),
|
|
167
|
+
reviverNumParse,
|
|
168
|
+
),
|
|
169
|
+
);
|
|
127
170
|
}
|
|
128
171
|
} catch (error) {
|
|
129
172
|
this.log.error(error);
|
|
@@ -132,266 +175,353 @@ class Senec extends utils.Adapter {
|
|
|
132
175
|
}
|
|
133
176
|
}
|
|
134
177
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
} else if (state && id === this.namespace + ".ENERGY.STAT_STATE") { // states that do have state.ack already
|
|
178
|
+
this.setStateAsync(id, { val: state.val, ack: true }); // Verarbeitung bestätigen
|
|
179
|
+
} else if (state && id === this.namespace + ".ENERGY.STAT_STATE") {
|
|
180
|
+
// states that do have state.ack already
|
|
139
181
|
this.log.debug("State changed: " + id + " ( " + JSON.stringify(state) + " )");
|
|
140
182
|
const forceLoad = await this.getStateAsync(this.namespace + ".control.ForceLoadBattery");
|
|
141
183
|
if (state.val == 8 || state.val == 9) {
|
|
142
184
|
if (state.val == 9) this.log.info("Battery forced loading completed (battery full).");
|
|
143
185
|
if (!forceLoad.val) {
|
|
144
|
-
this.log.info(
|
|
186
|
+
this.log.info(
|
|
187
|
+
"Battery forced loading activated (from outside or just lag). Syncing control-state.",
|
|
188
|
+
);
|
|
145
189
|
this.setStateChangedAsync(this.namespace + ".control.ForceLoadBattery", { val: true, ack: true });
|
|
146
190
|
}
|
|
147
191
|
} else {
|
|
148
192
|
if (forceLoad.val) {
|
|
149
|
-
this.log.info(
|
|
193
|
+
this.log.info(
|
|
194
|
+
"Battery forced loading deactivated (from outside or just lag). Syncing control-state.",
|
|
195
|
+
);
|
|
150
196
|
this.setStateChangedAsync(this.namespace + ".control.ForceLoadBattery", { val: false, ack: true });
|
|
151
197
|
}
|
|
152
198
|
}
|
|
153
199
|
}
|
|
154
|
-
|
|
200
|
+
}
|
|
155
201
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
202
|
+
/**
|
|
203
|
+
* Is called when adapter shuts down - callback has to be called under any circumstances!
|
|
204
|
+
* @param {() => void} callback
|
|
205
|
+
*/
|
|
206
|
+
onUnload(callback) {
|
|
207
|
+
try {
|
|
162
208
|
unloaded = true;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
209
|
+
if (this.timer) {
|
|
210
|
+
clearTimeout(this.timer);
|
|
211
|
+
}
|
|
212
|
+
if (this.timerAPI) {
|
|
213
|
+
clearTimeout(this.timerAPI);
|
|
214
|
+
}
|
|
215
|
+
this.log.info("cleaned everything up...");
|
|
216
|
+
this.setState("info.connection", false, true);
|
|
217
|
+
callback();
|
|
218
|
+
} catch (e) {
|
|
219
|
+
callback(e);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
177
223
|
async initPollSettings() {
|
|
178
224
|
// creating form for low priority pulling (which means pulling everything we know)
|
|
179
225
|
// we can do this while preparing values for high prio
|
|
180
|
-
lowPrioForm =
|
|
226
|
+
lowPrioForm = "{";
|
|
181
227
|
for (const value of allKnownObjects) {
|
|
182
228
|
lowPrioForm += '"' + value + '":{},';
|
|
183
229
|
const objectsSet = new Set();
|
|
184
230
|
switch (value) {
|
|
185
231
|
case "BMS":
|
|
186
|
-
[
|
|
187
|
-
|
|
188
|
-
|
|
232
|
+
[
|
|
233
|
+
"CELL_TEMPERATURES_MODULE_A",
|
|
234
|
+
"CELL_TEMPERATURES_MODULE_B",
|
|
235
|
+
"CELL_TEMPERATURES_MODULE_C",
|
|
236
|
+
"CELL_TEMPERATURES_MODULE_D",
|
|
237
|
+
"CELL_VOLTAGES_MODULE_A",
|
|
238
|
+
"CELL_VOLTAGES_MODULE_B",
|
|
239
|
+
"CELL_VOLTAGES_MODULE_C",
|
|
240
|
+
"CELL_VOLTAGES_MODULE_D",
|
|
241
|
+
"CURRENT",
|
|
242
|
+
"SOC",
|
|
243
|
+
"SYSTEM_SOC",
|
|
244
|
+
"TEMP_MAX",
|
|
245
|
+
"TEMP_MIN",
|
|
246
|
+
"VOLTAGE",
|
|
247
|
+
].forEach((item) => objectsSet.add(item));
|
|
248
|
+
if (this.config.disclaimer && this.config.highPrio_BMS_active)
|
|
249
|
+
this.addUserDps(value, objectsSet, this.config.highPrio_BMS);
|
|
250
|
+
break;
|
|
189
251
|
case "ENERGY":
|
|
190
|
-
[
|
|
191
|
-
|
|
192
|
-
|
|
252
|
+
[
|
|
253
|
+
"STAT_STATE",
|
|
254
|
+
"GUI_BAT_DATA_POWER",
|
|
255
|
+
"GUI_INVERTER_POWER",
|
|
256
|
+
"GUI_HOUSE_POW",
|
|
257
|
+
"GUI_GRID_POW",
|
|
258
|
+
"GUI_BAT_DATA_FUEL_CHARGE",
|
|
259
|
+
"GUI_CHARGING_INFO",
|
|
260
|
+
"GUI_BOOSTING_INFO",
|
|
261
|
+
"GUI_BAT_DATA_POWER",
|
|
262
|
+
"GUI_BAT_DATA_VOLTAGE",
|
|
263
|
+
"GUI_BAT_DATA_CURRENT",
|
|
264
|
+
"GUI_BAT_DATA_FUEL_CHARGE",
|
|
265
|
+
"GUI_BAT_DATA_OA_CHARGING",
|
|
266
|
+
"STAT_LIMITED_NET_SKEW",
|
|
267
|
+
"SAFE_CHARGE_FORCE",
|
|
268
|
+
"SAFE_CHARGE_PROHIBIT",
|
|
269
|
+
"SAFE_CHARGE_RUNNING",
|
|
270
|
+
].forEach((item) => objectsSet.add(item));
|
|
271
|
+
if (this.config.disclaimer && this.config.highPrio_ENERGY_active)
|
|
272
|
+
this.addUserDps(value, objectsSet, this.config.highPrio_ENERGY);
|
|
273
|
+
break;
|
|
193
274
|
case "PV1":
|
|
194
|
-
["POWER_RATIO","MPP_POWER"].forEach(item => objectsSet.add(item));
|
|
195
|
-
if (this.config.disclaimer && this.config.highPrio_PV1_active)
|
|
196
|
-
|
|
275
|
+
["POWER_RATIO", "MPP_POWER"].forEach((item) => objectsSet.add(item));
|
|
276
|
+
if (this.config.disclaimer && this.config.highPrio_PV1_active)
|
|
277
|
+
this.addUserDps(value, objectsSet, this.config.highPrio_PV1);
|
|
278
|
+
break;
|
|
197
279
|
case "PWR_UNIT":
|
|
198
|
-
["POWER_L1","POWER_L2","POWER_L3"].forEach(item => objectsSet.add(item));
|
|
199
|
-
if (this.config.disclaimer && this.config.highPrio_PWR_UNIT_active)
|
|
200
|
-
|
|
280
|
+
["POWER_L1", "POWER_L2", "POWER_L3"].forEach((item) => objectsSet.add(item));
|
|
281
|
+
if (this.config.disclaimer && this.config.highPrio_PWR_UNIT_active)
|
|
282
|
+
this.addUserDps(value, objectsSet, this.config.highPrio_PWR_UNIT);
|
|
283
|
+
break;
|
|
201
284
|
case "PM1OBJ1":
|
|
202
|
-
["FREQ","U_AC","I_AC","P_AC","P_TOTAL"].forEach(item => objectsSet.add(item));
|
|
203
|
-
if (this.config.disclaimer && this.config.highPrio_PM1OBJ1_active)
|
|
204
|
-
|
|
285
|
+
["FREQ", "U_AC", "I_AC", "P_AC", "P_TOTAL"].forEach((item) => objectsSet.add(item));
|
|
286
|
+
if (this.config.disclaimer && this.config.highPrio_PM1OBJ1_active)
|
|
287
|
+
this.addUserDps(value, objectsSet, this.config.highPrio_PM1OBJ1);
|
|
288
|
+
break;
|
|
205
289
|
case "PM1OBJ2":
|
|
206
|
-
["FREQ","U_AC","I_AC","P_AC","P_TOTAL"].forEach(item => objectsSet.add(item));
|
|
207
|
-
if (this.config.disclaimer && this.config.highPrio_PM1OBJ2_active)
|
|
208
|
-
|
|
290
|
+
["FREQ", "U_AC", "I_AC", "P_AC", "P_TOTAL"].forEach((item) => objectsSet.add(item));
|
|
291
|
+
if (this.config.disclaimer && this.config.highPrio_PM1OBJ2_active)
|
|
292
|
+
this.addUserDps(value, objectsSet, this.config.highPrio_PM1OBJ2);
|
|
293
|
+
break;
|
|
209
294
|
case "WALLBOX":
|
|
210
|
-
if (this.config.disclaimer && this.config.highPrio_WALLBOX_active)
|
|
211
|
-
|
|
295
|
+
if (this.config.disclaimer && this.config.highPrio_WALLBOX_active)
|
|
296
|
+
this.addUserDps(value, objectsSet, this.config.highPrio_WALLBOX);
|
|
297
|
+
break;
|
|
212
298
|
case "BAT1":
|
|
213
|
-
if (this.config.disclaimer && this.config.highPrio_BAT1_active)
|
|
214
|
-
|
|
299
|
+
if (this.config.disclaimer && this.config.highPrio_BAT1_active)
|
|
300
|
+
this.addUserDps(value, objectsSet, this.config.highPrio_BAT1);
|
|
301
|
+
break;
|
|
215
302
|
case "BAT1OBJ1":
|
|
216
|
-
if (this.config.disclaimer && this.config.highPrio_BAT1OBJ1_active)
|
|
217
|
-
|
|
303
|
+
if (this.config.disclaimer && this.config.highPrio_BAT1OBJ1_active)
|
|
304
|
+
this.addUserDps(value, objectsSet, this.config.highPrio_BAT1OBJ1);
|
|
305
|
+
break;
|
|
218
306
|
case "TEMPMEASURE":
|
|
219
|
-
if (this.config.disclaimer && this.config.highPrio_TEMPMEASURE_active)
|
|
220
|
-
|
|
307
|
+
if (this.config.disclaimer && this.config.highPrio_TEMPMEASURE_active)
|
|
308
|
+
this.addUserDps(value, objectsSet, this.config.highPrio_TEMPMEASURE);
|
|
309
|
+
break;
|
|
221
310
|
default:
|
|
222
311
|
// nothing to do here
|
|
223
|
-
|
|
312
|
+
break;
|
|
224
313
|
}
|
|
225
314
|
if (objectsSet.size > 0) {
|
|
226
315
|
highPrioObjects.set(value, objectsSet);
|
|
227
316
|
}
|
|
228
317
|
}
|
|
229
|
-
|
|
230
|
-
lowPrioForm = lowPrioForm.slice(0, -1) +
|
|
318
|
+
|
|
319
|
+
lowPrioForm = lowPrioForm.slice(0, -1) + "}";
|
|
231
320
|
this.log.info("(initPollSettings) lowPrio: " + lowPrioForm);
|
|
232
|
-
|
|
321
|
+
|
|
233
322
|
// creating form for high priority pulling
|
|
234
|
-
highPrioForm =
|
|
235
|
-
highPrioObjects.forEach(
|
|
323
|
+
highPrioForm = "{";
|
|
324
|
+
//highPrioObjects.forEach(function (mapValue, key, map) {
|
|
325
|
+
highPrioObjects.forEach(function (mapValue, key) {
|
|
236
326
|
highPrioForm += '"' + key + '":{';
|
|
237
|
-
mapValue.forEach
|
|
327
|
+
mapValue.forEach(function (setValue) {
|
|
238
328
|
highPrioForm += '"' + setValue + '":"",';
|
|
239
|
-
})
|
|
240
|
-
highPrioForm = highPrioForm.slice(0, -1) +
|
|
241
|
-
})
|
|
242
|
-
highPrioForm = highPrioForm.slice(0, -1) +
|
|
329
|
+
});
|
|
330
|
+
highPrioForm = highPrioForm.slice(0, -1) + "},";
|
|
331
|
+
});
|
|
332
|
+
highPrioForm = highPrioForm.slice(0, -1) + "}";
|
|
243
333
|
this.log.info("(initPollSettings) highPrio: " + highPrioForm);
|
|
244
334
|
}
|
|
245
|
-
|
|
335
|
+
|
|
246
336
|
addUserDps(value, objectsSet, dpToAdd) {
|
|
247
|
-
if (dpToAdd.trim().length < 1 || !/^[A-Z0-9_,]*$/.test(dpToAdd.toUpperCase().trim())) {
|
|
248
|
-
|
|
249
|
-
|
|
337
|
+
if (dpToAdd.trim().length < 1 || !/^[A-Z0-9_,]*$/.test(dpToAdd.toUpperCase().trim())) {
|
|
338
|
+
// don't accept anything but entries like DP_1,DP2,dp3
|
|
339
|
+
this.log.warn(
|
|
340
|
+
"(addUserDps) Datapoints config for " +
|
|
341
|
+
value +
|
|
342
|
+
" doesn't follow [A-Z0-9_,] (no blanks allowed!) - Ignoring: " +
|
|
343
|
+
dpToAdd.toUpperCase().trim(),
|
|
344
|
+
);
|
|
345
|
+
return;
|
|
250
346
|
}
|
|
251
|
-
dpToAdd
|
|
347
|
+
dpToAdd
|
|
348
|
+
.toUpperCase()
|
|
349
|
+
.trim()
|
|
350
|
+
.split(",")
|
|
351
|
+
.forEach((item) => objectsSet.add(item));
|
|
252
352
|
this.log.info("(addUserDps) Datapoints config changed for " + value + ": " + dpToAdd.toUpperCase().trim());
|
|
253
353
|
}
|
|
254
354
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
355
|
+
/**
|
|
356
|
+
* checks config paramaters
|
|
357
|
+
* Fallback to default values in case they are out of scope
|
|
358
|
+
*/
|
|
359
|
+
async checkConfig() {
|
|
360
|
+
this.log.debug("(checkConf) Configured polling interval high priority: " + this.config.interval);
|
|
361
|
+
if (this.config.interval < 1 || this.config.interval > 3600) {
|
|
362
|
+
this.log.warn(
|
|
363
|
+
"(checkConf) Config interval high priority " +
|
|
364
|
+
this.config.interval +
|
|
365
|
+
" not [1..3600] seconds. Using default: 10",
|
|
366
|
+
);
|
|
367
|
+
this.config.interval = 10;
|
|
368
|
+
}
|
|
369
|
+
this.log.debug("(checkConf) Configured polling interval low priority: " + this.config.intervalLow);
|
|
370
|
+
if (this.config.intervalLow < 10 || this.config.intervalLow > 3600) {
|
|
371
|
+
this.log.warn(
|
|
372
|
+
"(checkConf) Config interval low priority " +
|
|
373
|
+
this.config.intervalLow +
|
|
374
|
+
" not [10..3600] minutes. Using default: 60",
|
|
375
|
+
);
|
|
376
|
+
this.config.intervalLow = 60;
|
|
377
|
+
}
|
|
378
|
+
this.log.debug("(checkConf) Configured polling timeout: " + this.config.pollingTimeout);
|
|
379
|
+
if (this.config.pollingTimeout < 1000 || this.config.pollingTimeout > 10000) {
|
|
380
|
+
this.log.warn(
|
|
381
|
+
"(checkConf) Config timeout " +
|
|
382
|
+
this.config.pollingTimeout +
|
|
383
|
+
" not [1000..10000] ms. Using default: 5000",
|
|
384
|
+
);
|
|
385
|
+
this.config.pollingTimeout = 5000;
|
|
386
|
+
}
|
|
387
|
+
this.log.debug("(checkConf) Configured num of retries: " + this.config.retries);
|
|
388
|
+
if (this.config.retries < 0 || this.config.retries > 999) {
|
|
389
|
+
this.log.warn(
|
|
390
|
+
"(checkConf) Config num of retries " + this.config.retries + " not [0..999] seconds. Using default: 10",
|
|
391
|
+
);
|
|
392
|
+
this.config.retries = 10;
|
|
393
|
+
}
|
|
394
|
+
this.log.debug("(checkConf) Configured retry multiplier: " + this.config.retrymultiplier);
|
|
395
|
+
if (this.config.retrymultiplier < 1 || this.config.retrymultiplier > 10) {
|
|
396
|
+
this.log.warn(
|
|
397
|
+
"(checkConf) Config retry multiplier " +
|
|
398
|
+
this.config.retrymultiplier +
|
|
399
|
+
" not [1..10] seconds. Using default: 2",
|
|
400
|
+
);
|
|
401
|
+
this.config.retrymultiplier = 2;
|
|
402
|
+
}
|
|
285
403
|
this.log.debug("(checkConf) Configured https-usage: " + this.config.useHttps);
|
|
286
404
|
if (this.config.useHttps) {
|
|
287
405
|
connectVia = "https://";
|
|
288
406
|
this.log.debug("(checkConf) Switching to https ... " + this.config.useHttps);
|
|
289
407
|
}
|
|
290
408
|
this.log.debug("(checkConf) Configured api polling interval: " + this.config.api_interval);
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
409
|
+
if (this.config.api_interval < 3 || this.config.api_interval > 1440) {
|
|
410
|
+
this.log.warn(
|
|
411
|
+
"(checkConf) Config api polling interval " +
|
|
412
|
+
this.config.api_interval +
|
|
413
|
+
" not [3..1440] seconds. Using default: 5",
|
|
414
|
+
);
|
|
415
|
+
this.config.api_interval = 5;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* checks connection to senec service
|
|
421
|
+
*/
|
|
422
|
+
async checkConnection() {
|
|
423
|
+
const url = connectVia + this.config.senecip + "/lala.cgi";
|
|
424
|
+
const form = '{"ENERGY":{"STAT_STATE":""}}';
|
|
425
|
+
try {
|
|
426
|
+
this.log.info("connecting to Senec: " + url);
|
|
427
|
+
await this.doGet(url, form, this, this.config.pollingTimeout, true);
|
|
428
|
+
this.log.info("connected to Senec: " + url);
|
|
307
429
|
lalaConnected = true;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
430
|
+
} catch (error) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
"Error connecting to Senec (IP: " +
|
|
433
|
+
connectVia +
|
|
434
|
+
this.config.senecip +
|
|
435
|
+
"). Exiting! (" +
|
|
436
|
+
error +
|
|
437
|
+
"). Try to toggle https-mode in settings and check FQDN of SENEC appliance.",
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
313
442
|
/**
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
443
|
+
* Inits connection to senec app api
|
|
444
|
+
*/
|
|
445
|
+
async initSenecAppApi() {
|
|
317
446
|
if (!this.config.api_use) {
|
|
318
|
-
this.log.info(
|
|
447
|
+
this.log.info("Usage of SENEC App API not configured. Not using it");
|
|
319
448
|
return;
|
|
320
449
|
}
|
|
321
|
-
|
|
450
|
+
this.log.info("connecting to Senec App API: " + apiLoginUrl);
|
|
322
451
|
const loginData = JSON.stringify({
|
|
323
452
|
password: this.config.api_pwd,
|
|
324
|
-
username: this.config.api_mail
|
|
453
|
+
username: this.config.api_mail,
|
|
325
454
|
});
|
|
326
455
|
try {
|
|
327
|
-
|
|
328
|
-
|
|
456
|
+
const body = await this.doGet(apiLoginUrl, loginData, this, this.config.pollingTimeout, true);
|
|
457
|
+
this.log.info("connected to Senec AppAPI.");
|
|
329
458
|
apiLoginToken = JSON.parse(body).token;
|
|
330
459
|
apiConnected = true;
|
|
331
|
-
axios.defaults.headers.get[
|
|
332
|
-
|
|
460
|
+
axios.defaults.headers.get["authorization"] = apiLoginToken;
|
|
461
|
+
} catch (error) {
|
|
333
462
|
apiConnected = false;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
463
|
+
throw new Error("Error connecting to Senec AppAPI. Exiting! (" + error + ").");
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
338
467
|
/**
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
468
|
+
* Reads system data from senec app api
|
|
469
|
+
*/
|
|
470
|
+
async getApiSystems() {
|
|
342
471
|
const pfx = "_api.Anlagen.";
|
|
343
472
|
if (!this.config.api_use || !apiConnected) {
|
|
344
|
-
this.log.info(
|
|
473
|
+
this.log.info("Usage of SENEC App API not configured or not connected.");
|
|
345
474
|
return;
|
|
346
475
|
}
|
|
347
|
-
|
|
476
|
+
this.log.info("Reading Systems Information from Senec App API " + apiSystemsUrl);
|
|
348
477
|
try {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
478
|
+
const body = await this.doGet(apiSystemsUrl, "", this, this.config.pollingTimeout, false);
|
|
479
|
+
this.log.info("Read Systems Information from Senec AppAPI.");
|
|
480
|
+
const obj = JSON.parse(body);
|
|
481
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
482
|
+
this.log.debug("ApiPull: " + key + ":" + JSON.stringify(value));
|
|
354
483
|
const systemId = value.id;
|
|
355
484
|
apiKnownSystems.push(systemId);
|
|
356
|
-
for (const[key2, value2] of Object.entries(value)) {
|
|
357
|
-
if (typeof value2 === "object")
|
|
485
|
+
for (const [key2, value2] of Object.entries(value)) {
|
|
486
|
+
if (typeof value2 === "object")
|
|
358
487
|
await this.doState(pfx + systemId + "." + key2, JSON.stringify(value2), "", "", false);
|
|
359
|
-
else
|
|
360
|
-
await this.doState(pfx + systemId + "." + key2, value2, "", "", false);
|
|
488
|
+
else await this.doState(pfx + systemId + "." + key2, value2, "", "", false);
|
|
361
489
|
}
|
|
362
490
|
}
|
|
363
|
-
await this.doState(pfx +
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
491
|
+
await this.doState(pfx + "IDs", JSON.stringify(apiKnownSystems), "Anlagen IDs", "", false);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
throw new Error("Error reading Systems Information from Senec AppAPI. (" + error + ").");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Read from url via axios
|
|
499
|
+
* @param url to read from
|
|
500
|
+
* @param form to post
|
|
501
|
+
*/
|
|
374
502
|
doGet(pUrl, pForm, caller, pollingTimeout, isPost) {
|
|
375
503
|
return new Promise(function (resolve, reject) {
|
|
376
504
|
axios({
|
|
377
|
-
method: isPost ?
|
|
505
|
+
method: isPost ? "post" : "get",
|
|
378
506
|
httpsAgent: agent,
|
|
379
507
|
url: pUrl,
|
|
380
508
|
data: pForm,
|
|
381
|
-
timeout: pollingTimeout
|
|
509
|
+
timeout: pollingTimeout,
|
|
382
510
|
})
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
)
|
|
390
|
-
.catch(
|
|
391
|
-
(error) => {
|
|
511
|
+
.then(async (response) => {
|
|
512
|
+
const content = response.data;
|
|
513
|
+
caller.log.debug("(Poll) received data (" + response.status + "): " + JSON.stringify(content));
|
|
514
|
+
resolve(JSON.stringify(content));
|
|
515
|
+
})
|
|
516
|
+
.catch((error) => {
|
|
392
517
|
if (error.response) {
|
|
393
518
|
// The request was made and the server responded with a status code
|
|
394
|
-
caller.log.warn(
|
|
519
|
+
caller.log.warn(
|
|
520
|
+
"(Poll) received error " +
|
|
521
|
+
error.response.status +
|
|
522
|
+
" response from SENEC with content: " +
|
|
523
|
+
JSON.stringify(error.response.data),
|
|
524
|
+
);
|
|
395
525
|
if (error.response.status == 403 && apiConnected) {
|
|
396
526
|
apiConnected = false; // apparently the api is inaccessible
|
|
397
527
|
this.initSenecAppApi();
|
|
@@ -407,162 +537,252 @@ class Senec extends utils.Adapter {
|
|
|
407
537
|
caller.log.info(error.message);
|
|
408
538
|
reject(error.status);
|
|
409
539
|
}
|
|
410
|
-
}
|
|
411
|
-
);
|
|
540
|
+
});
|
|
412
541
|
});
|
|
413
542
|
}
|
|
414
543
|
|
|
415
|
-
|
|
416
544
|
/**
|
|
417
|
-
|
|
545
|
+
* Read values from Senec Home V2.1
|
|
418
546
|
* Careful with the amount and interval of HighPrio values polled because this causes high demand on the SENEC machine so it shouldn't run too often. Adverse effects: No sync with Senec possible if called too often.
|
|
419
|
-
|
|
547
|
+
*/
|
|
420
548
|
async pollSenec(isHighPrio, retry) {
|
|
421
|
-
const url = connectVia + this.config.senecip +
|
|
422
|
-
|
|
423
|
-
if (!isHighPrio) {
|
|
424
|
-
this.log.info(
|
|
425
|
-
interval = this.config.intervalLow * 1000 * 60
|
|
549
|
+
const url = connectVia + this.config.senecip + "/lala.cgi";
|
|
550
|
+
let interval = this.config.interval * 1000;
|
|
551
|
+
if (!isHighPrio) {
|
|
552
|
+
this.log.info("LowPrio polling ...");
|
|
553
|
+
interval = this.config.intervalLow * 1000 * 60;
|
|
426
554
|
}
|
|
427
|
-
|
|
555
|
+
|
|
428
556
|
try {
|
|
429
|
-
|
|
430
|
-
|
|
557
|
+
let body = await this.doGet(
|
|
558
|
+
url,
|
|
559
|
+
isHighPrio ? highPrioForm : lowPrioForm,
|
|
560
|
+
this,
|
|
561
|
+
this.config.pollingTimeout,
|
|
562
|
+
true,
|
|
563
|
+
);
|
|
564
|
+
if (body.includes('\\"')) {
|
|
431
565
|
// in rare cases senec reports back extra escape sequences on some machines ...
|
|
432
566
|
this.log.info("(Poll) Double escapes detected! Body inc: " + body);
|
|
433
567
|
body = body.replace(/\\"/g, '"');
|
|
434
568
|
this.log.info("(Poll) Double escapes autofixed! Body out: " + body);
|
|
435
569
|
}
|
|
436
|
-
|
|
437
|
-
|
|
570
|
+
const obj = JSON.parse(body, reviverNumParse);
|
|
571
|
+
await this.evalPoll(obj);
|
|
438
572
|
|
|
439
|
-
|
|
573
|
+
retry = 0;
|
|
440
574
|
if (unloaded) return;
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
575
|
+
this.timer = setTimeout(() => this.pollSenec(isHighPrio, retry), interval);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
if (retry == this.config.retries && this.config.retries < 999) {
|
|
578
|
+
this.log.error(
|
|
579
|
+
"Error reading from Senec " +
|
|
580
|
+
(isHighPrio ? "high" : "low") +
|
|
581
|
+
"Prio (" +
|
|
582
|
+
this.config.senecip +
|
|
583
|
+
"). Retried " +
|
|
584
|
+
retry +
|
|
585
|
+
" times. Giving up now. Check config and restart adapter. (" +
|
|
586
|
+
error +
|
|
587
|
+
")",
|
|
588
|
+
);
|
|
589
|
+
this.setState("info.connection", false, true);
|
|
590
|
+
} else {
|
|
591
|
+
retry += 1;
|
|
592
|
+
this.log.warn(
|
|
593
|
+
"Error reading from Senec " +
|
|
594
|
+
(isHighPrio ? "high" : "low") +
|
|
595
|
+
"Prio (" +
|
|
596
|
+
this.config.senecip +
|
|
597
|
+
"). Retry " +
|
|
598
|
+
retry +
|
|
599
|
+
"/" +
|
|
600
|
+
this.config.retries +
|
|
601
|
+
" in " +
|
|
602
|
+
(interval * this.config.retrymultiplier * retry) / 1000 +
|
|
603
|
+
" seconds! (" +
|
|
604
|
+
error +
|
|
605
|
+
")",
|
|
606
|
+
);
|
|
607
|
+
this.timer = setTimeout(
|
|
608
|
+
() => this.pollSenec(isHighPrio, retry),
|
|
609
|
+
interval * this.config.retrymultiplier * retry,
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
452
613
|
}
|
|
453
|
-
|
|
614
|
+
|
|
454
615
|
/**
|
|
455
|
-
|
|
456
|
-
|
|
616
|
+
* Read values from Senec App API
|
|
617
|
+
*/
|
|
457
618
|
async pollSenecAppApi(retry) {
|
|
458
619
|
if (!this.config.api_use || !apiConnected) {
|
|
459
|
-
this.log.info(
|
|
620
|
+
this.log.info("Usage of SENEC App API not configured or not connected.");
|
|
460
621
|
return;
|
|
461
622
|
}
|
|
462
623
|
const interval = this.config.api_interval * 60000;
|
|
463
624
|
const dates = new Map([
|
|
464
|
-
["THIS_DAY", new Date().toISOString().split(
|
|
465
|
-
["LAST_DAY", new Date(new Date().setDate(new Date().getDate()-1)).toISOString().split(
|
|
466
|
-
["THIS_MONTH", new Date().toISOString().split(
|
|
467
|
-
["LAST_MONTH", new Date(new Date().setDate(0)).toISOString().split(
|
|
468
|
-
["THIS_YEAR", new Date().toISOString().split(
|
|
469
|
-
["LAST_YEAR", new Date(new Date().getFullYear() - 1, 1, 1).toISOString().split(
|
|
625
|
+
["THIS_DAY", new Date().toISOString().split("T")[0]],
|
|
626
|
+
["LAST_DAY", new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().split("T")[0]],
|
|
627
|
+
["THIS_MONTH", new Date().toISOString().split("T")[0]],
|
|
628
|
+
["LAST_MONTH", new Date(new Date().setDate(0)).toISOString().split("T")[0]],
|
|
629
|
+
["THIS_YEAR", new Date().toISOString().split("T")[0]],
|
|
630
|
+
["LAST_YEAR", new Date(new Date().getFullYear() - 1, 1, 1).toISOString().split("T")[0]],
|
|
470
631
|
]);
|
|
471
|
-
|
|
632
|
+
|
|
472
633
|
this.log.debug("Polling API ...");
|
|
473
|
-
|
|
634
|
+
let body = "";
|
|
474
635
|
try {
|
|
475
636
|
for (let i = 0; i < apiKnownSystems.length; i++) {
|
|
476
637
|
const baseUrl = apiSystemsUrl + "/" + apiKnownSystems[i];
|
|
477
638
|
const baseUrlMonitor = apiMonitorUrl + "/" + apiKnownSystems[i];
|
|
478
|
-
|
|
639
|
+
let url = "";
|
|
479
640
|
const tzObj = await this.getStateAsync("_api.Anlagen." + apiKnownSystems[i] + ".zeitzone");
|
|
480
641
|
const tz = tzObj ? encodeURIComponent(tzObj.val) : encodeURIComponent("Europe/Berlin");
|
|
481
|
-
|
|
642
|
+
|
|
482
643
|
// dashboard
|
|
483
644
|
url = baseUrl + "/dashboard";
|
|
484
645
|
body = await this.doGet(url, "", this, this.config.pollingTimeout, false);
|
|
485
646
|
await this.decodeDashboard(apiKnownSystems[i], JSON.parse(body));
|
|
486
|
-
|
|
487
|
-
for (
|
|
647
|
+
|
|
648
|
+
for (const [key, value] of dates.entries()) {
|
|
488
649
|
// statistik for period
|
|
489
|
-
url =
|
|
650
|
+
url =
|
|
651
|
+
baseUrlMonitor +
|
|
652
|
+
"/data?period=" +
|
|
653
|
+
api_trans[key].api +
|
|
654
|
+
"&date=" +
|
|
655
|
+
value +
|
|
656
|
+
"&locale=de_DE&timezone=" +
|
|
657
|
+
tz;
|
|
490
658
|
this.log.debug("Calling: " + url);
|
|
491
659
|
body = await this.doGet(url, "", this, this.config.pollingTimeout, false);
|
|
492
660
|
await this.decodeStatistik(apiKnownSystems[i], JSON.parse(body), api_trans[key].dp);
|
|
493
661
|
}
|
|
494
|
-
|
|
495
662
|
if (this.config.api_alltimeRebuild) await this.rebuildAllTimeHistory(apiKnownSystems[i]);
|
|
496
|
-
|
|
497
663
|
}
|
|
498
664
|
retry = 0;
|
|
499
665
|
if (unloaded) return;
|
|
500
666
|
this.timerAPI = setTimeout(() => this.pollSenecAppApi(retry), interval);
|
|
501
667
|
} catch (error) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
668
|
+
if (retry == this.config.retries && this.config.retries < 999) {
|
|
669
|
+
this.log.error(
|
|
670
|
+
"Error reading from Senec AppAPI. Retried " +
|
|
671
|
+
retry +
|
|
672
|
+
" times. Giving up now. Check config and restart adapter. (" +
|
|
673
|
+
error +
|
|
674
|
+
")",
|
|
675
|
+
);
|
|
676
|
+
this.setState("info.connection", false, true);
|
|
677
|
+
} else {
|
|
678
|
+
retry += 1;
|
|
679
|
+
this.log.warn(
|
|
680
|
+
"Error reading from Senec AppAPI. Retry " +
|
|
681
|
+
retry +
|
|
682
|
+
"/" +
|
|
683
|
+
this.config.retries +
|
|
684
|
+
" in " +
|
|
685
|
+
(interval * this.config.retrymultiplier * retry) / 1000 +
|
|
686
|
+
" seconds! (" +
|
|
687
|
+
error +
|
|
688
|
+
")",
|
|
689
|
+
);
|
|
690
|
+
this.timerAPI = setTimeout(
|
|
691
|
+
() => this.pollSenecAppApi(retry),
|
|
692
|
+
interval * this.config.retrymultiplier * retry,
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
511
696
|
}
|
|
512
|
-
|
|
697
|
+
|
|
513
698
|
/**
|
|
514
699
|
* Decodes Dashboard information from SENEC App API
|
|
515
700
|
*/
|
|
516
701
|
async decodeDashboard(system, obj) {
|
|
517
702
|
const pfx = "_api.Anlagen." + system + ".Dashboard.";
|
|
518
|
-
for (const[key, value] of Object.entries(obj)) {
|
|
703
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
519
704
|
if (key == "zeitstempel" || key == "electricVehicleConnected") {
|
|
520
705
|
await this.doState(pfx + key, value, "", "", false);
|
|
521
706
|
} else {
|
|
522
|
-
for (const[key2, value2] of Object.entries(value)) {
|
|
523
|
-
await this.doState(
|
|
707
|
+
for (const [key2, value2] of Object.entries(value)) {
|
|
708
|
+
await this.doState(
|
|
709
|
+
pfx + key + "." + key2,
|
|
710
|
+
Number(value2.wert.toFixed(2)),
|
|
711
|
+
"",
|
|
712
|
+
value2.einheit,
|
|
713
|
+
false,
|
|
714
|
+
);
|
|
524
715
|
if (kiloList.includes(value2.einheit)) {
|
|
525
|
-
await this.doState(
|
|
716
|
+
await this.doState(
|
|
717
|
+
pfx + key + "." + key2 + " (k" + value2.einheit + ")",
|
|
718
|
+
Number((value2.wert / 1000).toFixed(2)),
|
|
719
|
+
"",
|
|
720
|
+
"k" + value2.einheit,
|
|
721
|
+
false,
|
|
722
|
+
);
|
|
526
723
|
}
|
|
527
724
|
}
|
|
528
725
|
}
|
|
529
726
|
}
|
|
530
|
-
|
|
531
727
|
}
|
|
532
|
-
|
|
728
|
+
|
|
533
729
|
/**
|
|
534
730
|
* Decodes Statistik information from SENEC App API
|
|
535
731
|
*/
|
|
536
732
|
async decodeStatistik(system, obj, period) {
|
|
537
733
|
if (obj == null || obj == undefined || obj.aggregation == null || obj.aggregation == undefined) return; // could happen (e.g.) if we pull information for "last year" when the appliance isn't that old yet
|
|
538
734
|
const pfx = "_api.Anlagen." + system + ".Statistik." + period + ".";
|
|
539
|
-
for (const[key, value] of Object.entries(obj.aggregation)) {
|
|
735
|
+
for (const [key, value] of Object.entries(obj.aggregation)) {
|
|
736
|
+
this.log.debug("decodeStatistic: " + pfx + key + ":" + value);
|
|
540
737
|
// only reading 'aggregation' - no interest in fine granular information
|
|
541
738
|
if (key == "startDate") {
|
|
542
739
|
await this.doState(pfx + key, value, "", "", false);
|
|
543
740
|
} else {
|
|
544
|
-
if (!this.config.api_alltimeRebuild) {
|
|
545
|
-
|
|
741
|
+
if (!this.config.api_alltimeRebuild) {
|
|
742
|
+
// don't update DPs if we are AllTime-Rebuild-Process
|
|
743
|
+
await this.doState(pfx + key, Number(value.value.toFixed(2)), "", value.unit, false);
|
|
546
744
|
if (kiloList.includes(value.unit)) {
|
|
547
|
-
await this.doState(
|
|
745
|
+
await this.doState(
|
|
746
|
+
pfx + key + " (k" + value.unit + ")",
|
|
747
|
+
Number((value.value / 1000).toFixed(2)),
|
|
748
|
+
"",
|
|
749
|
+
"k" + value.unit,
|
|
750
|
+
false,
|
|
751
|
+
);
|
|
548
752
|
}
|
|
549
753
|
}
|
|
550
|
-
if (period == api_trans["THIS_YEAR"].dp)
|
|
754
|
+
if (period == api_trans["THIS_YEAR"].dp)
|
|
755
|
+
await this.insertAllTimeHistory(
|
|
756
|
+
system,
|
|
757
|
+
key,
|
|
758
|
+
new Date(obj.aggregation.startDate).getFullYear(),
|
|
759
|
+
Number(value.value.toFixed(0)),
|
|
760
|
+
value.unit,
|
|
761
|
+
);
|
|
551
762
|
}
|
|
552
763
|
}
|
|
553
764
|
if (obj.aggregation.totalUsage.value != 0) {
|
|
554
|
-
const autarky = Number(
|
|
765
|
+
const autarky = Number(
|
|
766
|
+
(
|
|
767
|
+
((obj.aggregation.generation.value -
|
|
768
|
+
obj.aggregation.gridFeedIn.value -
|
|
769
|
+
obj.aggregation.storageLoad.value +
|
|
770
|
+
obj.aggregation.storageConsumption.value) /
|
|
771
|
+
obj.aggregation.totalUsage.value) *
|
|
772
|
+
100
|
|
773
|
+
).toFixed(2),
|
|
774
|
+
);
|
|
555
775
|
await this.doState(pfx + "Autarkie", autarky, "", "%", false);
|
|
556
776
|
}
|
|
557
777
|
await this.updateAllTimeHistory(system);
|
|
558
778
|
}
|
|
559
|
-
|
|
779
|
+
|
|
560
780
|
/**
|
|
561
781
|
* inserts a value for a given key and year into AllTimeValueStore
|
|
562
782
|
*/
|
|
563
783
|
async insertAllTimeHistory(system, key, year, value, einheit) {
|
|
564
|
-
this.log.debug("Insert AllTimeHistory: " + system + "/" +
|
|
565
|
-
if (key ===
|
|
784
|
+
this.log.debug("Insert AllTimeHistory: " + system + "/" + key + "/" + year + "/" + value + "/" + einheit);
|
|
785
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") return; // Security fix
|
|
566
786
|
if (isNaN(year) || isNaN(value)) return; // Security fix
|
|
567
787
|
const pfx = "_api.Anlagen." + system + ".Statistik.AllTime.";
|
|
568
788
|
const valueStore = pfx + "valueStore";
|
|
@@ -574,7 +794,7 @@ class Senec extends utils.Adapter {
|
|
|
574
794
|
stats[key]["einheit"] = einheit;
|
|
575
795
|
await this.doState(valueStore, JSON.stringify(stats), "", "", false);
|
|
576
796
|
}
|
|
577
|
-
|
|
797
|
+
|
|
578
798
|
/**
|
|
579
799
|
* Updated AllTimeHistory based on what we have in our AllTimeValueStore
|
|
580
800
|
*/
|
|
@@ -584,10 +804,10 @@ class Senec extends utils.Adapter {
|
|
|
584
804
|
const statsObj = await this.getStateAsync(valueStore);
|
|
585
805
|
const stats = statsObj ? JSON.parse(statsObj.val) : {};
|
|
586
806
|
const sums = {};
|
|
587
|
-
for (const[key, value] of Object.entries(stats)) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
for (const[key2, value2] of Object.entries(value)) {
|
|
807
|
+
for (const [key, value] of Object.entries(stats)) {
|
|
808
|
+
let einheit = "";
|
|
809
|
+
let sum = 0.0;
|
|
810
|
+
for (const [key2, value2] of Object.entries(value)) {
|
|
591
811
|
if (key2 == "einheit") {
|
|
592
812
|
einheit = value2;
|
|
593
813
|
} else {
|
|
@@ -602,67 +822,78 @@ class Senec extends utils.Adapter {
|
|
|
602
822
|
}
|
|
603
823
|
}
|
|
604
824
|
if (sums.totalUsage != 0) {
|
|
605
|
-
const autarky = Number(
|
|
825
|
+
const autarky = Number(
|
|
826
|
+
(
|
|
827
|
+
((sums.generation - sums.gridFeedIn - sums.storageLoad + sums.storageConsumption) /
|
|
828
|
+
sums.totalUsage) *
|
|
829
|
+
100
|
|
830
|
+
).toFixed(0),
|
|
831
|
+
);
|
|
606
832
|
await this.doState(pfx + "Autarkie", autarky, "", "%", false);
|
|
607
833
|
}
|
|
608
834
|
}
|
|
609
|
-
|
|
835
|
+
|
|
610
836
|
/**
|
|
611
837
|
* Rebuilds AllTimeHistory from SENEC App API
|
|
612
838
|
*/
|
|
613
839
|
async rebuildAllTimeHistory(system) {
|
|
614
840
|
if (!this.config.api_use || !apiConnected) {
|
|
615
|
-
this.log.info(
|
|
841
|
+
this.log.info("Usage of SENEC App API not configured or not connected.");
|
|
616
842
|
return;
|
|
617
843
|
}
|
|
618
|
-
|
|
844
|
+
|
|
619
845
|
this.log.info("Rebuilding AllTime History ...");
|
|
620
|
-
|
|
621
|
-
|
|
846
|
+
let year = new Date(new Date().getFullYear() - 1, 1, 1).toISOString().split("T")[0]; // starting last year, because we already got current year covered
|
|
847
|
+
let body = "";
|
|
622
848
|
try {
|
|
623
|
-
while (new Date(year).getFullYear() > 2008) {
|
|
849
|
+
while (new Date(year).getFullYear() > 2008) {
|
|
850
|
+
// senec was founded in 2009 by Mathias Hammer as Deutsche Energieversorgung GmbH (DEV) - so no way we have older data :)
|
|
624
851
|
this.log.info("Rebuilding AllTime History - Year: " + new Date(year).getFullYear());
|
|
625
852
|
const baseUrl = apiMonitorUrl + "/" + system;
|
|
626
|
-
|
|
853
|
+
let url = "";
|
|
627
854
|
const tzObj = await this.getStateAsync("_api.Anlagen." + system + ".zeitzone");
|
|
628
855
|
const tz = tzObj ? encodeURIComponent(tzObj.val) : encodeURIComponent("Europe/Berlin");
|
|
629
856
|
url = baseUrl + "/data?period=YEAR&date=" + year + "&locale=de_DE&timezone=" + tz;
|
|
630
857
|
this.log.debug("Polling: " + url);
|
|
631
858
|
body = await this.doGet(url, "", this, this.config.pollingTimeout, false);
|
|
632
859
|
await this.decodeStatistik(system, JSON.parse(body), api_trans["THIS_YEAR"].dp);
|
|
633
|
-
year = new Date(new Date(year).getFullYear() - 1, 1, 1).toISOString().split(
|
|
860
|
+
year = new Date(new Date(year).getFullYear() - 1, 1, 1).toISOString().split("T")[0];
|
|
634
861
|
if (unloaded) return;
|
|
635
862
|
}
|
|
636
863
|
} catch (error) {
|
|
637
|
-
this.log.info("Rebuild ended
|
|
638
|
-
|
|
864
|
+
this.log.info("Rebuild ended: " + error);
|
|
865
|
+
}
|
|
639
866
|
this.log.info("Restarting ...");
|
|
640
|
-
this.extendForeignObject(`system.adapter.${this.namespace}`, {native: {api_alltimeRebuild: false}});
|
|
867
|
+
this.extendForeignObject(`system.adapter.${this.namespace}`, { native: { api_alltimeRebuild: false } });
|
|
641
868
|
}
|
|
642
869
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
870
|
+
/**
|
|
871
|
+
* sets a state's value and creates the state if it doesn't exist yet
|
|
872
|
+
*/
|
|
873
|
+
async doState(name, value, description, unit, write) {
|
|
647
874
|
if (!isNaN(name.substring(0, 1))) {
|
|
648
875
|
// keys cannot start with digits! Possibly SENEC delivering erraneous data
|
|
649
|
-
this.log.debug(
|
|
876
|
+
this.log.debug("(doState) Invalid datapoint: " + name + ": " + value);
|
|
650
877
|
return;
|
|
651
878
|
}
|
|
652
|
-
this.log.silly(
|
|
653
|
-
|
|
879
|
+
this.log.silly("(doState) Update: " + name + ": " + value);
|
|
880
|
+
|
|
654
881
|
const valueType = value !== null && value !== undefined ? typeof value : "mixed";
|
|
655
|
-
|
|
882
|
+
|
|
656
883
|
// Check object for changes:
|
|
657
884
|
const obj = knownObjects[name] ? knownObjects[name] : await this.getObjectAsync(name);
|
|
658
885
|
if (obj) {
|
|
659
886
|
const newCommon = {};
|
|
660
887
|
if (obj.common.name !== description) {
|
|
661
|
-
this.log.debug(
|
|
888
|
+
this.log.debug(
|
|
889
|
+
"(doState) Updating object: " + name + " (desc): " + obj.common.name + " -> " + description,
|
|
890
|
+
);
|
|
662
891
|
newCommon.name = description;
|
|
663
892
|
}
|
|
664
893
|
if (obj.common.type !== valueType) {
|
|
665
|
-
this.log.debug(
|
|
894
|
+
this.log.debug(
|
|
895
|
+
"(doState) Updating object: " + name + " (type): " + obj.common.type + " -> " + typeof value,
|
|
896
|
+
);
|
|
666
897
|
newCommon.type = valueType;
|
|
667
898
|
}
|
|
668
899
|
if (obj.common.unit !== unit) {
|
|
@@ -685,70 +916,76 @@ class Senec extends utils.Adapter {
|
|
|
685
916
|
role: "value",
|
|
686
917
|
unit: unit,
|
|
687
918
|
read: true,
|
|
688
|
-
write: write
|
|
919
|
+
write: write,
|
|
689
920
|
},
|
|
690
|
-
native: {}
|
|
921
|
+
native: {},
|
|
691
922
|
};
|
|
692
923
|
await this.setObjectNotExistsAsync(name, knownObjects[name]);
|
|
693
924
|
}
|
|
694
925
|
await this.setStateChangedAsync(name, {
|
|
695
926
|
val: value,
|
|
696
|
-
ack: true
|
|
927
|
+
ack: true,
|
|
697
928
|
});
|
|
698
929
|
await this.doDecode(name, value);
|
|
699
930
|
}
|
|
700
|
-
|
|
931
|
+
|
|
701
932
|
/**
|
|
702
933
|
* Checks if there is decoding possible for a given value and creates/updates a decoded state
|
|
703
934
|
* Language used for translations is the language of the SENEC appliance
|
|
704
935
|
*/
|
|
705
936
|
async doDecode(name, value) {
|
|
706
937
|
// Lang: WIZARD.GUI_LANG 0=German, 1=English, 2=Italian
|
|
707
|
-
|
|
708
|
-
|
|
938
|
+
let lang = 1; // fallback to english
|
|
939
|
+
const langState = await this.getStateAsync("WIZARD.GUI_LANG");
|
|
709
940
|
if (langState) lang = langState.val;
|
|
710
941
|
this.log.silly("(Decode) Senec language: " + lang);
|
|
711
|
-
|
|
712
|
-
if (!isNaN(name.substring(name.lastIndexOf(
|
|
942
|
+
let key = name;
|
|
943
|
+
if (!isNaN(name.substring(name.lastIndexOf(".")) + 1)) key = name.substring(0, name.lastIndexOf("."));
|
|
713
944
|
this.log.silly("(Decode) Checking: " + name + " -> " + key);
|
|
714
|
-
|
|
945
|
+
|
|
715
946
|
if (state_trans[key + "." + lang] !== undefined) {
|
|
716
947
|
this.log.silly("(Decode) Trans found for: " + key + "." + lang);
|
|
717
|
-
const trans =
|
|
948
|
+
const trans =
|
|
949
|
+
state_trans[key + "." + lang] !== undefined
|
|
950
|
+
? state_trans[key + "." + lang][value] !== undefined
|
|
951
|
+
? state_trans[key + "." + lang][value]
|
|
952
|
+
: "(unknown)"
|
|
953
|
+
: "(unknown)";
|
|
718
954
|
this.log.silly("(Decode) Trans " + key + ":" + value + " = " + trans);
|
|
719
|
-
const desc =
|
|
955
|
+
const desc = state_attr[key + "_Text"] !== undefined ? state_attr[key + "_Text"].name : key;
|
|
720
956
|
await this.doState(name + "_Text", trans, desc, "", true);
|
|
721
957
|
}
|
|
722
958
|
}
|
|
723
|
-
|
|
959
|
+
|
|
724
960
|
/**
|
|
725
961
|
* evaluates data polled from SENEC system.
|
|
726
962
|
* creates / updates the state.
|
|
727
963
|
*/
|
|
728
|
-
|
|
964
|
+
async evalPoll(obj) {
|
|
729
965
|
if (unloaded) return;
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
for (var i = 0; i < value2.length; i++) {
|
|
742
|
-
this.doState(key + '.' + i, ValueTyping(key, value2[i]), desc + '[' + i + ']', unit, false);
|
|
743
|
-
}
|
|
744
|
-
} else {
|
|
745
|
-
this.doState(key, ValueTyping(key, value2), desc, unit, false);
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
}
|
|
966
|
+
for (const [key1, value1] of Object.entries(obj)) {
|
|
967
|
+
for (const [key2, value2] of Object.entries(value1)) {
|
|
968
|
+
if (value2 !== "VARIABLE_NOT_FOUND" && key2 !== "OBJECT_NOT_FOUND") {
|
|
969
|
+
const key = key1 + "." + key2;
|
|
970
|
+
if (state_attr[key] === undefined) {
|
|
971
|
+
this.log.debug(
|
|
972
|
+
"REPORT_TO_DEV: State attribute definition missing for: " + key + ", Val: " + value2,
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
const desc = state_attr[key] !== undefined ? state_attr[key].name : key2;
|
|
976
|
+
const unit = state_attr[key] !== undefined ? state_attr[key].unit : "";
|
|
751
977
|
|
|
978
|
+
if (Array.isArray(value2)) {
|
|
979
|
+
for (let i = 0; i < value2.length; i++) {
|
|
980
|
+
this.doState(key + "." + i, ValueTyping(key, value2[i]), desc + "[" + i + "]", unit, false);
|
|
981
|
+
}
|
|
982
|
+
} else {
|
|
983
|
+
this.doState(key, ValueTyping(key, value2), desc, unit, false);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
752
989
|
}
|
|
753
990
|
|
|
754
991
|
/**
|
|
@@ -757,25 +994,25 @@ class Senec extends utils.Adapter {
|
|
|
757
994
|
*/
|
|
758
995
|
const ValueTyping = (key, value) => {
|
|
759
996
|
if (!isNaN(value)) value = Number(value); // otherwise iobroker will note it as string
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
const isBool =
|
|
764
|
-
const isDate =
|
|
765
|
-
const isIP =
|
|
766
|
-
const multiply =
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
997
|
+
if (state_attr[key] === undefined) {
|
|
998
|
+
return value;
|
|
999
|
+
}
|
|
1000
|
+
const isBool = state_attr[key] !== undefined && state_attr[key].booltype ? state_attr[key].booltype : false;
|
|
1001
|
+
const isDate = state_attr[key] !== undefined && state_attr[key].datetype ? state_attr[key].datetype : false;
|
|
1002
|
+
const isIP = state_attr[key] !== undefined && state_attr[key].iptype ? state_attr[key].iptype : false;
|
|
1003
|
+
const multiply = state_attr[key] !== undefined && state_attr[key].multiply ? state_attr[key].multiply : 1;
|
|
1004
|
+
if (isBool) {
|
|
1005
|
+
return value === 0 ? false : true;
|
|
1006
|
+
} else if (isDate) {
|
|
1007
|
+
return new Date(value * 1000).toString();
|
|
1008
|
+
} else if (isIP) {
|
|
1009
|
+
return DecToIP(value);
|
|
1010
|
+
} else if (multiply !== 1) {
|
|
1011
|
+
return parseFloat((value * multiply).toFixed(2));
|
|
1012
|
+
} else {
|
|
1013
|
+
return value;
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
779
1016
|
|
|
780
1017
|
/**
|
|
781
1018
|
* Converts float value in hex format to js float32.
|
|
@@ -783,22 +1020,22 @@ const ValueTyping = (key, value) => {
|
|
|
783
1020
|
* @param string with hex value
|
|
784
1021
|
*/
|
|
785
1022
|
const HexToFloat32 = (str) => {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
}
|
|
1023
|
+
const int = parseInt(str, 16);
|
|
1024
|
+
if (int > 0 || int < 0) {
|
|
1025
|
+
// var sign = (int >>> 31) ? -1 : 1;
|
|
1026
|
+
const sign = int & 0x80000000 ? -1 : 1;
|
|
1027
|
+
let exp = ((int >>> 23) & 0xff) - 127;
|
|
1028
|
+
const mantissa = (int & (0x7fffff + 0x800000)).toString(2);
|
|
1029
|
+
let float32 = 0;
|
|
1030
|
+
for (let i = 0; i < mantissa.length; i++) {
|
|
1031
|
+
float32 += parseInt(mantissa[i]) ? Math.pow(2, exp) : 0;
|
|
1032
|
+
exp--;
|
|
1033
|
+
}
|
|
1034
|
+
return (float32 * sign).toFixed(2);
|
|
1035
|
+
} else {
|
|
1036
|
+
return 0;
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
802
1039
|
|
|
803
1040
|
/**
|
|
804
1041
|
* Converts a given decimal to a properly formatted IP address.
|
|
@@ -807,16 +1044,16 @@ const HexToFloat32 = (str) => {
|
|
|
807
1044
|
* for proper human reading.
|
|
808
1045
|
*/
|
|
809
1046
|
const DecToIP = (str) => {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
}
|
|
1047
|
+
let ipHex = str.toString(16);
|
|
1048
|
+
while (ipHex.length < 8) {
|
|
1049
|
+
ipHex = "0" + ipHex;
|
|
1050
|
+
}
|
|
1051
|
+
const fourth = ipHex.substring(0, 2);
|
|
1052
|
+
const third = ipHex.substring(2, 4);
|
|
1053
|
+
const second = ipHex.substring(4, 6);
|
|
1054
|
+
const first = ipHex.substring(6);
|
|
1055
|
+
return parseInt(first, 16) + "." + parseInt(second, 16) + "." + parseInt(third, 16) + "." + parseInt(fourth, 16);
|
|
1056
|
+
};
|
|
820
1057
|
|
|
821
1058
|
/**
|
|
822
1059
|
* Reviver function to convert numeric values to float or int.
|
|
@@ -824,100 +1061,101 @@ const DecToIP = (str) => {
|
|
|
824
1061
|
* @param key value pair as defined in reviver option
|
|
825
1062
|
*/
|
|
826
1063
|
const reviverNumParse = (key, value) => {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1064
|
+
// prepare values for output using reviver function
|
|
1065
|
+
if (typeof value === "string") {
|
|
1066
|
+
if (value.startsWith("fl_")) {
|
|
1067
|
+
// float in hex IEEE754
|
|
1068
|
+
return HexToFloat32(value.substring(3));
|
|
1069
|
+
} else if (value.startsWith("u")) {
|
|
1070
|
+
// unsigned int in hex
|
|
1071
|
+
return parseInt(value.substring(3), 16);
|
|
1072
|
+
} else if (value.startsWith("st_")) {
|
|
1073
|
+
// string?
|
|
1074
|
+
return value.substring(3);
|
|
1075
|
+
} else if (value.startsWith("i1")) {
|
|
1076
|
+
// int
|
|
1077
|
+
let val = parseInt(value.substring(3), 16);
|
|
1078
|
+
if (!isNaN(val)) {
|
|
1079
|
+
if ((val & 0x8000) > 0) {
|
|
1080
|
+
val = val - 0x10000;
|
|
1081
|
+
}
|
|
1082
|
+
return val;
|
|
1083
|
+
} else return 0;
|
|
1084
|
+
} else if (value.startsWith("i3")) {
|
|
1085
|
+
// int
|
|
1086
|
+
let val = parseInt(value.substring(3), 16);
|
|
1087
|
+
if (!isNaN(val)) {
|
|
1088
|
+
if (Math.abs(value & 0x80000000) > 0) {
|
|
1089
|
+
val = val - 0x100000000;
|
|
1090
|
+
}
|
|
1091
|
+
return val;
|
|
1092
|
+
} else return 0;
|
|
1093
|
+
} else if (value.startsWith("i8")) {
|
|
1094
|
+
// int
|
|
1095
|
+
let val = parseInt(value.substring(3), 16);
|
|
1096
|
+
if (!isNaN(val)) {
|
|
1097
|
+
if ((value & 0x80) > 0) {
|
|
1098
|
+
val = val - 0x100;
|
|
1099
|
+
}
|
|
1100
|
+
return val;
|
|
1101
|
+
} else return 0;
|
|
1102
|
+
} else if (value.startsWith("VARIABLE_NOT_FOUND")) {
|
|
1103
|
+
return "VARIABLE_NOT_FOUND";
|
|
866
1104
|
} else if (value.startsWith("FILE_VARIABLE_NOT_READABLE")) {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
}
|
|
1105
|
+
return "";
|
|
1106
|
+
} else {
|
|
1107
|
+
return "REPORT TO DEV: " + key + ":" + value;
|
|
1108
|
+
//throw new Error("Unknown value in JSON: " + key + ":" + value);
|
|
1109
|
+
}
|
|
1110
|
+
} else {
|
|
1111
|
+
return value;
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
876
1114
|
|
|
877
1115
|
/**
|
|
878
1116
|
* Returns the current day of the year
|
|
879
1117
|
*/
|
|
880
|
-
const getCurDay = () => {
|
|
881
|
-
return (Math.round((new Date().setHours(23) - new Date(new Date().getYear()+1900, 0, 1, 0, 0, 0))/1000/60/60/24));
|
|
882
|
-
}
|
|
1118
|
+
//const getCurDay = () => {
|
|
1119
|
+
// return (Math.round((new Date().setHours(23) - new Date(new Date().getYear()+1900, 0, 1, 0, 0, 0))/1000/60/60/24));
|
|
1120
|
+
//}
|
|
883
1121
|
|
|
884
1122
|
/**
|
|
885
1123
|
* Returns the current month of the year
|
|
886
1124
|
*/
|
|
887
|
-
const getCurMonth = () => {
|
|
888
|
-
return (new Date().getMonth());
|
|
889
|
-
}
|
|
1125
|
+
//const getCurMonth = () => {
|
|
1126
|
+
// return (new Date().getMonth());
|
|
1127
|
+
//}
|
|
890
1128
|
|
|
891
1129
|
/**
|
|
892
1130
|
* Returns the current year
|
|
893
1131
|
*/
|
|
894
|
-
const getCurYear = () => {
|
|
895
|
-
return (new Date().getFullYear());
|
|
896
|
-
}
|
|
1132
|
+
//const getCurYear = () => {
|
|
1133
|
+
// return (new Date().getFullYear());
|
|
1134
|
+
//}
|
|
897
1135
|
|
|
898
1136
|
/**
|
|
899
1137
|
* Returns the current week of the year
|
|
900
1138
|
* Using Standard ISO8601
|
|
901
1139
|
*/
|
|
902
|
-
const getCurWeek = () => {
|
|
903
|
-
var tdt = new Date();
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
}
|
|
1140
|
+
//const getCurWeek = () => {
|
|
1141
|
+
// var tdt = new Date();
|
|
1142
|
+
// var dayn = (tdt.getDay() + 6) % 7;
|
|
1143
|
+
// tdt.setDate(tdt.getDate() - dayn + 3);
|
|
1144
|
+
// var firstThursday = tdt.valueOf();
|
|
1145
|
+
// tdt.setMonth(0, 1);
|
|
1146
|
+
// if (tdt.getDay() !== 4) {
|
|
1147
|
+
// tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
|
|
1148
|
+
// }
|
|
1149
|
+
// return 1 + Math.ceil((firstThursday - tdt) / 604800000);
|
|
1150
|
+
//};
|
|
913
1151
|
|
|
914
1152
|
if (require.main !== module) {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1153
|
+
// Export the constructor in compact mode
|
|
1154
|
+
/**
|
|
1155
|
+
* @param {Partial<ioBroker.AdapterOptions>} [options={}]
|
|
1156
|
+
*/
|
|
1157
|
+
module.exports = (options) => new Senec(options);
|
|
920
1158
|
} else {
|
|
921
|
-
|
|
922
|
-
|
|
1159
|
+
// otherwise start the instance directly
|
|
1160
|
+
new Senec();
|
|
923
1161
|
}
|