iobroker.senec 1.6.16 → 2.0.0
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 +1 -3
- package/README.md +8 -1
- package/io-package.json +30 -30
- package/main.js +456 -197
- package/package.json +22 -29
package/LICENSE
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
Copyright (c)
|
|
1
|
+
Copyright (c) 2020-2026 NoBl <github@bluemle.org>
|
|
2
2
|
|
|
3
3
|
MIT License
|
|
4
4
|
|
|
5
|
-
Copyright (c) 2024 Author <author@mail.com>
|
|
6
|
-
|
|
7
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
9
7
|
in the Software without restriction, including without limitation the rights
|
package/README.md
CHANGED
|
@@ -299,6 +299,13 @@ This channel contains values polled from SENEC App-API.
|
|
|
299
299
|
|
|
300
300
|
## Changelog
|
|
301
301
|
|
|
302
|
+
### 2.0.0 (maett81, NoBl)
|
|
303
|
+
* Updated to use new SENEC API via mein-senec.de - Thanks to @maett81
|
|
304
|
+
* Some code and dependency housekeeping
|
|
305
|
+
|
|
306
|
+
### 1.6.17
|
|
307
|
+
* License update
|
|
308
|
+
|
|
302
309
|
### 1.6.16
|
|
303
310
|
* Moved Dashboard to ApiV2. This invalidates existing datapoints under /Dashboard/ and introduces "Dashboard/currently" and "Dashboard/today" due to changes in the API.
|
|
304
311
|
|
|
@@ -335,7 +342,7 @@ This channel contains values polled from SENEC App-API.
|
|
|
335
342
|
## License
|
|
336
343
|
MIT License
|
|
337
344
|
|
|
338
|
-
Copyright (c)
|
|
345
|
+
Copyright (c) 2020-2026 Norbert Bluemle <github@bluemle.org>
|
|
339
346
|
|
|
340
347
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
341
348
|
of this software and associated documentation files (the "Software"), to deal
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "senec",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.0.0": {
|
|
7
|
+
"en": "Update to use API via mein-senec.de (Thanks to maett81)",
|
|
8
|
+
"de": "Aktualisierung der API über mein-senec.de (Danke an maett81)",
|
|
9
|
+
"ru": "Использование API через mein-senec.de (перенаправлено с «Maett81»)",
|
|
10
|
+
"pt": "Atualizar para usar API via mein-senec.de (Graças a Maett81)",
|
|
11
|
+
"nl": "Update om API te gebruiken via mein-senec.de (Dankzij maett81)",
|
|
12
|
+
"fr": "Mise à jour pour utiliser l'API via mein-senec.de (Merci à maett81)",
|
|
13
|
+
"it": "Aggiornamento all'utilizzo API tramite mein-senec.de (Grazie a maett81)",
|
|
14
|
+
"es": "Actualización para utilizar API a través de mein-senec.de (Gracias a maett81)",
|
|
15
|
+
"pl": "Aktualizacja do użycia API poprzez mein- senec.de (Dzięki maett81)",
|
|
16
|
+
"uk": "Update to use API через mein-senec.de (Шанки до maett81)",
|
|
17
|
+
"zh-cn": "通过 mein-senec.de 使用 API 的更新 (谢谢Maett81)"
|
|
18
|
+
},
|
|
19
|
+
"1.6.17": {
|
|
20
|
+
"en": "License update",
|
|
21
|
+
"de": "Lizenz-Update",
|
|
22
|
+
"ru": "Обновление лицензии",
|
|
23
|
+
"pt": "Atualização da licença",
|
|
24
|
+
"nl": "Licentie-update",
|
|
25
|
+
"fr": "Mise à jour de licence",
|
|
26
|
+
"it": "Aggiornamento della licenza",
|
|
27
|
+
"es": "Actualización de la licencia",
|
|
28
|
+
"pl": "Aktualizacja licencji",
|
|
29
|
+
"uk": "Оновлення ліцензії",
|
|
30
|
+
"zh-cn": "许可证更新"
|
|
31
|
+
},
|
|
6
32
|
"1.6.16": {
|
|
7
33
|
"en": "Migrated Dashboard to ApiV2. This invalidates existing datapoints under /Dashboard/ and introduces \"Dashboard/currently\" and \"Dashboard/today\" due to changes in the API.",
|
|
8
34
|
"de": "Dashboard auf ApiV2 migriert. Dadurch werden bestehende Datenpunkte unter /Dashboard/ ungültig. Neu sind aufgrund von Änderungen in der API: \\\"Dashboard/current\\\" und \\\"Dashboard/today\\\".",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "Aktualizacja licencji",
|
|
68
94
|
"uk": "Оновлена ліцензія",
|
|
69
95
|
"zh-cn": "更新许可证"
|
|
70
|
-
},
|
|
71
|
-
"1.6.11": {
|
|
72
|
-
"en": "Moving from Senec App API 3.12.0 to 4.3.3 (thanks to oakdesign@github for providing the new API!). This WILL invalidate all current API datapoints in the Statistik branch. Easiest solution to this: Delete the Statistik branch. Remember to force a rebuild of historic data in adapter settings!",
|
|
73
|
-
"de": "Update der Senec App API 3.12.0 auf 4.3.3 (Dank an Oakdesign@github für die Analyse der neuen API.)! Dadurch werden leider alle aktuellen API-Datenpunkte im Statistik-Zweig ungültig. Einfachste Lösung hierfür: Den gesamten Statistik-Zweig löschen. Beachten: Die Gesamthistorie muss neu berechnet werden (Parameter in den Adaptereinstellungen)!",
|
|
74
|
-
"ru": "Переезд с Senec App API 3.12.0 до 4.3.3 (спасибо oakdesign@github для предоставления нового API.)! Это недействит все текущие точки API в филиале Statistik. Самое легкое решение для этого: Удалите филиал Statistik. Не забудьте заставить восстановить исторические данные в настройках адаптера!",
|
|
75
|
-
"pt": "Movendo de Senec App API 3.12.0 para 4.3.3 (graças para oakdesign@github para fornecer a nova API.)! Esta WILL invalida todos os datapoints API atuais no ramo Statistik. Solução mais fácil para isso: Excluir o ramo Statistik. Lembre-se de forçar uma reconstrução de dados históricos em configurações de adaptador!",
|
|
76
|
-
"nl": "Verhuizen van Senec App API 3.12.0 tot 4.3.3.3 uur tot Oakdesign @github voor de nieuwe API. Dit zal alle huidige API gegevens in de Statistik Branch invaliden. Verwijder de statistik tak. Vergeet niet een herbouw van historische gegevens te forceren in adapter settings!",
|
|
77
|
-
"fr": "Déplacement de Senec App API 3.12.0 à 4.3.3 (merci à Oakdesign@github pour fournir la nouvelle API.)! Ce WILL invalide tous les points de données API actuels dans la branche Statistik. Solution la plus facile à cela: Supprimer la branche Statistik. N'oubliez pas de forcer une reconstruction des données historiques dans les réglages d'adaptateur!",
|
|
78
|
-
"it": "Passando da Senec App API 3.12.0 a 4.3.3 (grazie a Oakdesign@github per fornire la nuova API.)! Questo WILL invalida tutti i datapoint API attuali nel ramo Statistik. Soluzione più semplice a questo: Elimina il ramo Statistik. Ricordatevi di forzare una ricostruzione dei dati storici nelle impostazioni dell'adattatore!",
|
|
79
|
-
"es": "Moving from Senec App API 3.12.0 to 4.3.3 (gracias a oakdesign@github para proporcionar la nueva API.)! Esto invalidará todos los puntos de datos actuales de la API en la rama Statistik. Solución más fácil: Eliminar la rama Statistik. Recuerde forzar una reconstrucción de datos históricos en la configuración del adaptador!",
|
|
80
|
-
"pl": "Przewidując się z interfejsu Senec App API 3,12.0 to 4.3.3 (dodatki do dębowego @github dla zapewnienia nowego API). WILL uszkodził wszystkie aktualne punkty danych API w oddziale Statistik. Najwięcej rozwiązań do tego: Dete the Statistik branch. Pamiętaj, aby odbudować historyczne dane w ustawieniach adapterów!",
|
|
81
|
-
"uk": "Переміщення від Senec App API 3.12.0 до 4.3.3 (завдяки до дубdesign@github для надання нового API.)! Це недійсне використання всіх поточних точок даних API у відділенні Statistik. Найсвіжіші рішення для цього: Видаліть статутне відділення. Пам'ятайте, що змусити перебудувати історичні дані в налаштуваннях адаптера!",
|
|
82
|
-
"zh-cn": "Moving from Senecapp API3.12.0 to 4.3.3 (as to oakdesign@github for the new API.!) 本协会在斯塔特斯科分处停止了目前所有阿联酋数据点。 最容易解决这一问题:删除斯塔特主义分支。 重新成员在适应环境中重建历史数据!"
|
|
83
|
-
},
|
|
84
|
-
"1.6.10": {
|
|
85
|
-
"en": "Bugfix for AllTimeHistory",
|
|
86
|
-
"de": "Bugfix für AllTimeHistory",
|
|
87
|
-
"ru": "Bugfix для AllTimeИстория",
|
|
88
|
-
"pt": "Bugfix para AllTimeHistory",
|
|
89
|
-
"nl": "Bugfix voor AllTimeHistory",
|
|
90
|
-
"fr": "Bugfix pour AllTimeHistory",
|
|
91
|
-
"it": "Bugfix per AllTimeHistory",
|
|
92
|
-
"es": "Bugfix para AllTimeHistory",
|
|
93
|
-
"pl": "Bugfix for AllTimeHistory (ang.)",
|
|
94
|
-
"uk": "Виправлення помилок для AllTimeHistory",
|
|
95
|
-
"zh-cn": "九. 一切制度"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"docs": {
|
|
@@ -153,12 +153,12 @@
|
|
|
153
153
|
},
|
|
154
154
|
"dependencies": [
|
|
155
155
|
{
|
|
156
|
-
"js-controller": ">=
|
|
156
|
+
"js-controller": ">=6.0.11"
|
|
157
157
|
}
|
|
158
158
|
],
|
|
159
159
|
"globalDependencies": [
|
|
160
160
|
{
|
|
161
|
-
"admin": ">=
|
|
161
|
+
"admin": ">=7.6.17"
|
|
162
162
|
}
|
|
163
163
|
]
|
|
164
164
|
},
|
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
"api_interval": 5,
|
|
198
198
|
"api_alltimeRebuild": false
|
|
199
199
|
},
|
|
200
|
-
"
|
|
200
|
+
"encryptedNative": {
|
|
201
201
|
"api_mail": "",
|
|
202
202
|
"api_pwd": ""
|
|
203
203
|
},
|
package/main.js
CHANGED
|
@@ -12,29 +12,29 @@ const utils = require("@iobroker/adapter-core");
|
|
|
12
12
|
const axios = require("axios").default;
|
|
13
13
|
axios.defaults.headers.post["Content-Type"] = "application/json";
|
|
14
14
|
|
|
15
|
+
const tough = require("tough-cookie");
|
|
16
|
+
const cheerio = require("cheerio");
|
|
17
|
+
let wrapper;
|
|
18
|
+
(async () => {
|
|
19
|
+
wrapper = (await import("axios-cookiejar-support")).wrapper;
|
|
20
|
+
})();
|
|
21
|
+
|
|
15
22
|
const state_attr = require(__dirname + "/lib/state_attr.js");
|
|
16
23
|
const state_trans = require(__dirname + "/lib/state_trans.js");
|
|
17
|
-
const api_trans = require(__dirname + "/lib/api_trans.js");
|
|
18
24
|
const kiloList = ["W", "Wh"];
|
|
19
25
|
|
|
20
|
-
const
|
|
21
|
-
const api2Url = "https://app-gateway.prod.senec.dev/v2/senec";
|
|
22
|
-
const apiLoginUrl = apiUrl + "/login";
|
|
23
|
-
const apiSystemsUrl = apiUrl + "/systems";
|
|
24
|
-
const api2SystemsUrl = api2Url + "/systems";
|
|
25
|
-
const apiMonitorUrl = apiUrl + "/monitor";
|
|
26
|
+
const apiLoginUrl = "https://mein-senec.de/endkunde/oauth2/authorization/endkunde-portal";
|
|
26
27
|
const apiKnownSystems = [];
|
|
27
28
|
|
|
28
29
|
const batteryOn =
|
|
29
30
|
'{"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":""}}';
|
|
30
31
|
const batteryOff =
|
|
31
32
|
'{"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":""}}';
|
|
32
|
-
//const blockDischargeOn
|
|
33
|
+
//const blockDischargeOn = '{"ENERGY":{"SAFE_CHARGE_FORCE":"","SAFE_CHARGE_PROHIBIT":"","SAFE_CHARGE_RUNNING":"","LI_STORAGE_MODE_START":"","LI_STORAGE_MODE_STOP":"","LI_STORAGE_MODE_RUNNING":"","STAT_STATE":""}}';
|
|
33
34
|
//const blockDischargeOff = '{"ENERGY":{"SAFE_CHARGE_FORCE":"","SAFE_CHARGE_PROHIBIT":"","SAFE_CHARGE_RUNNING":"","LI_STORAGE_MODE_START":"","LI_STORAGE_MODE_STOP":"","LI_STORAGE_MODE_RUNNING":"","STAT_STATE":""}}';
|
|
34
35
|
|
|
35
36
|
let apiConnected = false;
|
|
36
37
|
let lalaConnected = false;
|
|
37
|
-
let apiLoginToken = "";
|
|
38
38
|
let connectVia = "http://";
|
|
39
39
|
|
|
40
40
|
const allKnownObjects = new Set([
|
|
@@ -119,8 +119,8 @@ class Senec extends utils.Adapter {
|
|
|
119
119
|
this.log.info("Usage of SENEC App API configured.");
|
|
120
120
|
await this.initSenecAppApi();
|
|
121
121
|
if (apiConnected) {
|
|
122
|
-
await this.
|
|
123
|
-
await this.
|
|
122
|
+
await this.getWebApiSystems();
|
|
123
|
+
await this.pollSenecWebApi(0); // Web API
|
|
124
124
|
}
|
|
125
125
|
} else {
|
|
126
126
|
this.log.warn(
|
|
@@ -451,50 +451,135 @@ class Senec extends utils.Adapter {
|
|
|
451
451
|
this.log.info("Usage of SENEC App API not configured. Not using it");
|
|
452
452
|
return;
|
|
453
453
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
username: this.config.api_mail,
|
|
458
|
-
});
|
|
454
|
+
/** new for WEB API */
|
|
455
|
+
this.log.info("Connecting to SENEC Portal (mein-senec.de) login...");
|
|
456
|
+
|
|
459
457
|
try {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
458
|
+
// create cookie jar and a wrapped axios client that keeps cookies
|
|
459
|
+
const jar = new tough.CookieJar();
|
|
460
|
+
const webClient = wrapper(
|
|
461
|
+
axios.create({
|
|
462
|
+
jar,
|
|
463
|
+
withCredentials: true,
|
|
464
|
+
//httpsAgent: agent,
|
|
465
|
+
headers: {
|
|
466
|
+
"User-Agent": "Mozilla/5.0 (iobroker-senec-adapter)",
|
|
467
|
+
},
|
|
468
|
+
timeout: this.config.pollingTimeout || 5000,
|
|
469
|
+
maxRedirects: 5,
|
|
470
|
+
validateStatus: () => true,
|
|
471
|
+
}),
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
// STEP 1: fetch login page
|
|
475
|
+
this.log.info("STEP 1: fetch login portal page...");
|
|
476
|
+
const resp1 = await webClient.get(apiLoginUrl);
|
|
477
|
+
this.log.debug("Portal GET status: " + resp1.status);
|
|
478
|
+
this.log.silly(
|
|
479
|
+
"Portal GET response url: " + (resp1.request && resp1.request.res ? resp1.request.res.responseUrl : ""),
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
// parse form
|
|
483
|
+
const $ = cheerio.load(resp1.data);
|
|
484
|
+
const form = $("form#kc-form-login");
|
|
485
|
+
if (!form.length) {
|
|
486
|
+
throw new Error("Login form not found on portal page");
|
|
487
|
+
}
|
|
488
|
+
const actionAttr = form.attr("action");
|
|
489
|
+
const actionUrl = new URL(actionAttr, resp1.request.res.responseUrl).href;
|
|
490
|
+
|
|
491
|
+
// collect hidden inputs
|
|
492
|
+
const formData = {};
|
|
493
|
+
form.find("input").each((i, el) => {
|
|
494
|
+
const name = $(el).attr("name");
|
|
495
|
+
const value = $(el).attr("value") || "";
|
|
496
|
+
if (name) formData[name] = value;
|
|
497
|
+
});
|
|
498
|
+
formData.username = this.config.api_mail;
|
|
499
|
+
formData.password = this.config.api_pwd;
|
|
500
|
+
|
|
501
|
+
this.log.debug("Posting login to Keycloak action URL: " + actionUrl);
|
|
502
|
+
|
|
503
|
+
const resp2 = await webClient.post(actionUrl, new URLSearchParams(formData).toString(), {
|
|
504
|
+
headers: {
|
|
505
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
506
|
+
Referer: resp1.request.res.responseUrl,
|
|
507
|
+
},
|
|
508
|
+
maxRedirects: 5,
|
|
509
|
+
validateStatus: () => true,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
this.log.debug("Login POST status: " + resp2.status);
|
|
513
|
+
// check cookies in jar
|
|
514
|
+
const cookies = await jar.getCookies("https://mein-senec.de");
|
|
515
|
+
this.log.debug("Cookies after login: " + cookies.map((c) => c.cookieString()).join("; "));
|
|
516
|
+
|
|
517
|
+
if (resp2.status >= 400) {
|
|
518
|
+
throw new Error("Portal login failed: " + resp2.status);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// store web client for subsequent portal requests
|
|
522
|
+
this.senecWebClient = webClient;
|
|
523
|
+
this.senecWebJar = jar;
|
|
463
524
|
apiConnected = true;
|
|
464
|
-
|
|
525
|
+
this.log.info("Connected to SENEC Portal via web login.");
|
|
465
526
|
} catch (error) {
|
|
466
527
|
apiConnected = false;
|
|
467
|
-
|
|
528
|
+
this.log.error("Error connecting to SENEC Portal: " + error);
|
|
529
|
+
throw new Error("Error connecting to SENEC Portal. (" + error + ")");
|
|
468
530
|
}
|
|
531
|
+
/** end new WEB API */
|
|
469
532
|
}
|
|
470
533
|
|
|
471
534
|
/**
|
|
472
535
|
* Reads system data from senec app api
|
|
536
|
+
*
|
|
537
|
+
* Replaced: reads portal status overview and creates _api.Portal.Overview
|
|
473
538
|
*/
|
|
474
|
-
async
|
|
475
|
-
const pfx = "_api.
|
|
476
|
-
if (!this.config.api_use || !apiConnected) {
|
|
477
|
-
this.log.info("Usage of SENEC
|
|
539
|
+
async getWebApiSystems() {
|
|
540
|
+
const pfx = "_api.Portal.Profile.";
|
|
541
|
+
if (!this.config.api_use || !apiConnected || !this.senecWebClient) {
|
|
542
|
+
this.log.info("Usage of SENEC Portal not configured or not connected.");
|
|
478
543
|
return;
|
|
479
544
|
}
|
|
480
|
-
this.log.info("Reading Systems Information from
|
|
545
|
+
this.log.info("Reading Systems Information from SENEC Portal (status overview)");
|
|
546
|
+
|
|
547
|
+
const PROFILE_SETTINGS = "https://mein-senec.de/endkunde/api/settings/getProfileSettings";
|
|
548
|
+
|
|
481
549
|
try {
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
550
|
+
const resp = await this.senecWebClient.get(PROFILE_SETTINGS);
|
|
551
|
+
if (resp.status !== 200) {
|
|
552
|
+
throw new Error("Profile Settings returned HTTP " + resp.status);
|
|
553
|
+
}
|
|
554
|
+
const obj = resp.data;
|
|
555
|
+
|
|
556
|
+
// try to populate profile settings
|
|
557
|
+
apiKnownSystems.length = 0;
|
|
558
|
+
|
|
485
559
|
for (const [key, value] of Object.entries(obj)) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
560
|
+
if (key == "id") apiKnownSystems.push(value);
|
|
561
|
+
if (key == "land" || key == "sprache") {
|
|
562
|
+
for (const [key2, value2] of Object.entries(value)) {
|
|
563
|
+
this.log.debug("profileSetting: " + pfx + key + "." + key2 + ":" + value);
|
|
564
|
+
await this.doState(pfx + key + "." + key2, JSON.stringify(value2), "", "", false);
|
|
565
|
+
}
|
|
566
|
+
} else {
|
|
567
|
+
this.log.debug("profileSetting: " + pfx + key + ":" + value);
|
|
568
|
+
await this.doState(pfx + key, value, "", "", false);
|
|
493
569
|
}
|
|
494
570
|
}
|
|
495
|
-
|
|
571
|
+
|
|
572
|
+
/*
|
|
573
|
+
** ensure uniqueness, if there are multiple systems
|
|
574
|
+
const uniq = [...new Set(apiKnownSystems)];
|
|
575
|
+
apiKnownSystems.length = 0;
|
|
576
|
+
uniq.forEach((x) => apiKnownSystems.push(x));
|
|
577
|
+
*/
|
|
578
|
+
|
|
579
|
+
this.log.info("Detected systems from portal overview: " + JSON.stringify(apiKnownSystems));
|
|
580
|
+
//await this.doState(pfx + "IDs", JSON.stringify(apiKnownSystems), "Portal detected system IDs", "", false);
|
|
496
581
|
} catch (error) {
|
|
497
|
-
throw new Error("Error reading Systems Information from
|
|
582
|
+
throw new Error("Error reading Systems Information from SENEC Portal. (" + error + ").");
|
|
498
583
|
}
|
|
499
584
|
}
|
|
500
585
|
|
|
@@ -573,6 +658,7 @@ class Senec extends utils.Adapter {
|
|
|
573
658
|
this.log.info("(Poll) Double escapes autofixed! Body out: " + body);
|
|
574
659
|
}
|
|
575
660
|
const obj = JSON.parse(body, reviverNumParse);
|
|
661
|
+
this.log.debug("(Poll) Parsed object: " + JSON.stringify(obj));
|
|
576
662
|
await this.evalPoll(obj);
|
|
577
663
|
|
|
578
664
|
retry = 0;
|
|
@@ -618,61 +704,103 @@ class Senec extends utils.Adapter {
|
|
|
618
704
|
}
|
|
619
705
|
|
|
620
706
|
/**
|
|
621
|
-
* Read values from Senec App API
|
|
707
|
+
* Read values from Senec App API (replaced to query the mein-senec portal endpoints)
|
|
622
708
|
*/
|
|
623
|
-
async
|
|
624
|
-
if (!this.config.api_use || !apiConnected) {
|
|
625
|
-
this.log.info("Usage of SENEC
|
|
709
|
+
async pollSenecWebApi(retry) {
|
|
710
|
+
if (!this.config.api_use || !apiConnected || !this.senecWebClient) {
|
|
711
|
+
this.log.info("Usage of SENEC Portal not configured or not connected.");
|
|
626
712
|
return;
|
|
627
713
|
}
|
|
714
|
+
|
|
628
715
|
const interval = this.config.api_interval * 60000;
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
716
|
+
const base = "https://mein-senec.de/endkunde/api/status";
|
|
717
|
+
const endpoints = {
|
|
718
|
+
statusoverview: base + "/getstatusoverview.php?anlageNummer=0",
|
|
719
|
+
technischeDaten: base + "/technischeDaten?anlageNummer=0",
|
|
720
|
+
status24: base + "/getstatus24.php?anlageNummer=0",
|
|
721
|
+
autarky: base + "/getautarky.php?anlageNummer=0",
|
|
722
|
+
accustate: base + "/getaccustate.php?anlageNummer=0",
|
|
723
|
+
accusavings: base + "/getaccusavings.php?anlageNummer=0",
|
|
724
|
+
};
|
|
725
|
+
// also interesting endpoints:
|
|
726
|
+
|
|
727
|
+
// also prepare getstatus types
|
|
728
|
+
const statusTypes = ["accuexport", "accuimport", "gridexport", "gridimport", "powergenerated", "consumption"];
|
|
729
|
+
|
|
730
|
+
this.log.debug("Polling Portal API ...");
|
|
731
|
+
|
|
640
732
|
try {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
await this.
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
733
|
+
// statusoverview
|
|
734
|
+
let r = await this.senecWebClient.get(endpoints.statusoverview);
|
|
735
|
+
if (r.status === 200) {
|
|
736
|
+
await this.decodeStatusOverview(r.data);
|
|
737
|
+
} else {
|
|
738
|
+
this.log.warn("statusoverview returned HTTP " + r.status);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// technischeDaten
|
|
742
|
+
r = await this.senecWebClient.get(endpoints.technischeDaten);
|
|
743
|
+
if (r.status === 200) {
|
|
744
|
+
await this.decodeTechnischeDaten(r.data);
|
|
745
|
+
} else {
|
|
746
|
+
this.log.warn("technischeDaten returned HTTP " + r.status);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// status24
|
|
750
|
+
r = await this.senecWebClient.get(endpoints.status24);
|
|
751
|
+
if (r.status === 200) {
|
|
752
|
+
await this.decodeStatus24(r.data);
|
|
753
|
+
} else {
|
|
754
|
+
this.log.warn("stats24 returned HTTP " + r.status);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// autarky
|
|
758
|
+
r = await this.senecWebClient.get(endpoints.autarky);
|
|
759
|
+
if (r.status === 200) {
|
|
760
|
+
await this.decodeAutarky(r.data);
|
|
761
|
+
} else {
|
|
762
|
+
this.log.warn("autarky returned HTTP " + r.status);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// accustate
|
|
766
|
+
r = await this.senecWebClient.get(endpoints.accustate);
|
|
767
|
+
if (r.status === 200) {
|
|
768
|
+
await this.decodeAccuState(r.data);
|
|
769
|
+
} else {
|
|
770
|
+
this.log.warn("accustate returned HTTP " + r.status);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// accusavings
|
|
774
|
+
r = await this.senecWebClient.get(endpoints.accusavings);
|
|
775
|
+
if (r.status === 200) {
|
|
776
|
+
await this.decodeAccuSavings(r.data);
|
|
777
|
+
} else {
|
|
778
|
+
this.log.warn("accusavings returned HTTP " + r.status);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// getstatus with many types
|
|
782
|
+
for (let i = 0; i < statusTypes.length; i++) {
|
|
783
|
+
const t = statusTypes[i];
|
|
784
|
+
const url = base + "/getstatus.php?type=" + encodeURIComponent(t) + "&period=all&anlageNummer=0";
|
|
785
|
+
try {
|
|
786
|
+
const res = await this.senecWebClient.get(url);
|
|
787
|
+
if (res.status === 200) {
|
|
788
|
+
await this.decodeStatus(res.data, t);
|
|
789
|
+
} else {
|
|
790
|
+
this.log.warn("getstatus(" + t + ") returned HTTP " + res.status);
|
|
791
|
+
}
|
|
792
|
+
} catch (err) {
|
|
793
|
+
this.log.warn("Error fetching getstatus(" + t + "): " + err);
|
|
666
794
|
}
|
|
667
|
-
if (this.config.api_alltimeRebuild) await this.rebuildAllTimeHistory(apiKnownSystems[i]);
|
|
668
795
|
}
|
|
796
|
+
|
|
669
797
|
retry = 0;
|
|
670
798
|
if (unloaded) return;
|
|
671
|
-
this.timerAPI = setTimeout(() => this.
|
|
799
|
+
this.timerAPI = setTimeout(() => this.pollSenecWebApi(retry), interval);
|
|
672
800
|
} catch (error) {
|
|
673
801
|
if (retry == this.config.retries && this.config.retries < 999) {
|
|
674
802
|
this.log.error(
|
|
675
|
-
"Error reading from
|
|
803
|
+
"Error reading from SENEC Portal. Retried " +
|
|
676
804
|
retry +
|
|
677
805
|
" times. Giving up now. Check config and restart adapter. (" +
|
|
678
806
|
error +
|
|
@@ -682,101 +810,266 @@ class Senec extends utils.Adapter {
|
|
|
682
810
|
} else {
|
|
683
811
|
retry += 1;
|
|
684
812
|
this.log.warn(
|
|
685
|
-
"Error reading from
|
|
813
|
+
"Error reading from SENEC Portal. Retry " +
|
|
686
814
|
retry +
|
|
687
815
|
"/" +
|
|
688
816
|
this.config.retries +
|
|
689
817
|
" in " +
|
|
690
|
-
(
|
|
818
|
+
(this.config.api_interval * 60000 * this.config.retrymultiplier * retry) / 1000 +
|
|
691
819
|
" seconds! (" +
|
|
692
820
|
error +
|
|
693
821
|
")",
|
|
694
822
|
);
|
|
695
823
|
this.timerAPI = setTimeout(
|
|
696
|
-
() => this.
|
|
697
|
-
|
|
824
|
+
() => this.pollSenecWebApi(retry),
|
|
825
|
+
this.config.api_interval * 60000 * this.config.retrymultiplier * retry,
|
|
698
826
|
);
|
|
699
827
|
}
|
|
700
828
|
}
|
|
701
829
|
}
|
|
702
830
|
|
|
703
831
|
/**
|
|
704
|
-
* Decodes
|
|
832
|
+
* Decodes StatusOverview from WebAPI
|
|
705
833
|
*/
|
|
706
|
-
async
|
|
707
|
-
const pfx = "_api.
|
|
834
|
+
async decodeStatusOverview(obj) {
|
|
835
|
+
const pfx = "_api.Portal.StatusOverview.";
|
|
836
|
+
// store raw data
|
|
837
|
+
//await this.doState(pfx + "_json", JSON.stringify(obj), "Portal Status Overview", "", false);
|
|
708
838
|
for (const [key, value] of Object.entries(obj)) {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
839
|
+
if (
|
|
840
|
+
key == "wartungsplan" ||
|
|
841
|
+
key === "gridimport" ||
|
|
842
|
+
key === "gridexport" ||
|
|
843
|
+
key === "powergenerated" ||
|
|
844
|
+
key === "consumption" ||
|
|
845
|
+
key === "accuexport" ||
|
|
846
|
+
key === "accuimport" ||
|
|
847
|
+
key === "acculevel"
|
|
848
|
+
) {
|
|
713
849
|
for (const [key2, value2] of Object.entries(value)) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
await this.doState(
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
false,
|
|
724
|
-
);
|
|
725
|
-
}
|
|
850
|
+
if (key2 == "possibleMaintenanceTypes") continue; // skip this one
|
|
851
|
+
this.log.debug("decodeStatusOverview: " + pfx + key + "." + key2 + ":" + value);
|
|
852
|
+
await this.doState(
|
|
853
|
+
pfx + key + "." + key2,
|
|
854
|
+
ValueTyping(key2, JSON.stringify(value2)),
|
|
855
|
+
"",
|
|
856
|
+
"",
|
|
857
|
+
false,
|
|
858
|
+
);
|
|
726
859
|
}
|
|
860
|
+
} else if (key === "lastupdated") {
|
|
861
|
+
const date = new Date(value);
|
|
862
|
+
this.log.debug("decodeStatusOverview: " + pfx + key + ":" + date.toString());
|
|
863
|
+
await this.doState(pfx + key, date.toString(), "", "", false);
|
|
864
|
+
} else {
|
|
865
|
+
if (key === "suppressedNotificationIds") continue; // skip this one - empty array
|
|
866
|
+
this.log.debug("decodeStatusOverview: " + pfx + key + ":" + value);
|
|
867
|
+
await this.doState(pfx + key, ValueTyping(key, value), "", "", false);
|
|
727
868
|
}
|
|
728
869
|
}
|
|
729
870
|
}
|
|
730
871
|
|
|
731
872
|
/**
|
|
732
|
-
* Decodes
|
|
873
|
+
* Decodes technischeDaten from WebAPI
|
|
733
874
|
*/
|
|
734
|
-
async
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
875
|
+
async decodeTechnischeDaten(obj) {
|
|
876
|
+
const pfx = "_api.Portal.TechnischeDaten.";
|
|
877
|
+
// store raw data
|
|
878
|
+
//await this.doState(pfx + "_json", JSON.stringify(obj), "Portal Status Overview", "", false);
|
|
879
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
880
|
+
if (key == "installationsdatum") {
|
|
881
|
+
const date = new Date(value);
|
|
882
|
+
this.log.debug("decodeTechnischeDaten: " + pfx + key + ":" + date.toString());
|
|
883
|
+
await this.doState(pfx + key, date.toString(), "", "", false);
|
|
884
|
+
} else {
|
|
885
|
+
this.log.debug("decodeTechnischeDaten: " + pfx + key + ":" + value);
|
|
741
886
|
await this.doState(pfx + key, value, "", "", false);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Decodes Status24 from WebAPI
|
|
893
|
+
*/
|
|
894
|
+
async decodeStatus24(obj) {
|
|
895
|
+
const pfx = "_api.Portal.Status24.";
|
|
896
|
+
// store raw data
|
|
897
|
+
await this.doState(pfx + "json", JSON.stringify(obj), "Portal Status24", "", false);
|
|
898
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
899
|
+
if (key === "val") {
|
|
900
|
+
const accuExportArr = value[0];
|
|
901
|
+
const accuImportArr = value[1];
|
|
902
|
+
const gridExportArr = value[2];
|
|
903
|
+
const gridImportArr = value[3];
|
|
904
|
+
const powergeneratedArr = value[4];
|
|
905
|
+
const consumptionArr = value[5];
|
|
906
|
+
|
|
907
|
+
// AccuExport
|
|
908
|
+
let i = 0;
|
|
909
|
+
for (const [ts, val] of accuExportArr) {
|
|
910
|
+
i++;
|
|
911
|
+
const dateStr = new Date(ts).toString();
|
|
912
|
+
await this.doState(pfx + "AccuExport." + i + ".ts", dateStr, "Timestampe", "", false);
|
|
913
|
+
await this.doState(pfx + "AccuExport." + i + ".value", Number(val.toFixed(3)), "", "", false);
|
|
914
|
+
}
|
|
915
|
+
await this.doState(pfx + "AccuExport.json", JSON.stringify(accuExportArr), "", "", false);
|
|
916
|
+
|
|
917
|
+
// AccuImport
|
|
918
|
+
i = 0;
|
|
919
|
+
for (const [ts, val] of accuImportArr) {
|
|
920
|
+
i++;
|
|
921
|
+
const dateStr = new Date(ts).toString();
|
|
922
|
+
await this.doState(pfx + "AccuImport." + i + ".ts", dateStr, "Timestampe", "", false);
|
|
923
|
+
await this.doState(pfx + "AccuImport." + i + ".value", Number(val.toFixed(3)), "", "", false);
|
|
924
|
+
}
|
|
925
|
+
await this.doState(pfx + "AccuImport.json", JSON.stringify(accuImportArr), "", "", false);
|
|
926
|
+
|
|
927
|
+
// GridExport
|
|
928
|
+
i = 0;
|
|
929
|
+
for (const [ts, val] of gridExportArr) {
|
|
930
|
+
i++;
|
|
931
|
+
const dateStr = new Date(ts).toString();
|
|
932
|
+
await this.doState(pfx + "GridExport." + i + ".ts", dateStr, "Timestampe", "", false);
|
|
933
|
+
await this.doState(pfx + "GridExport." + i + ".value", Number(val.toFixed(3)), "", "", false);
|
|
934
|
+
}
|
|
935
|
+
await this.doState(pfx + "GridExport.json", JSON.stringify(gridExportArr), "", "", false);
|
|
936
|
+
|
|
937
|
+
// GridImport
|
|
938
|
+
i = 0;
|
|
939
|
+
for (const [ts, val] of gridImportArr) {
|
|
940
|
+
i++;
|
|
941
|
+
const dateStr = new Date(ts).toString();
|
|
942
|
+
await this.doState(pfx + "GridImport." + i + ".ts", dateStr, "Timestampe", "", false);
|
|
943
|
+
await this.doState(pfx + "GridImport." + i + ".value", Number(val.toFixed(3)), "", "", false);
|
|
944
|
+
}
|
|
945
|
+
await this.doState(pfx + "GridImport.json", JSON.stringify(gridImportArr), "", "", false);
|
|
946
|
+
|
|
947
|
+
// PowerGenerated
|
|
948
|
+
i = 0;
|
|
949
|
+
for (const [ts, val] of powergeneratedArr) {
|
|
950
|
+
i++;
|
|
951
|
+
const dateStr = new Date(ts).toString();
|
|
952
|
+
await this.doState(pfx + "PowerGenerated." + i + ".ts", dateStr, "Timestampe", "", false);
|
|
953
|
+
await this.doState(pfx + "PowerGenerated." + i + ".value", Number(val.toFixed(3)), "", "", false);
|
|
954
|
+
}
|
|
955
|
+
await this.doState(pfx + "PowerGenerated.json", JSON.stringify(powergeneratedArr), "", "", false);
|
|
956
|
+
|
|
957
|
+
// Consumption
|
|
958
|
+
i = 0;
|
|
959
|
+
for (const [ts, val] of consumptionArr) {
|
|
960
|
+
i++;
|
|
961
|
+
const dateStr = new Date(ts).toString();
|
|
962
|
+
await this.doState(pfx + "Consumption." + i + ".ts", dateStr, "Timestamp", "", false);
|
|
963
|
+
await this.doState(pfx + "Consumption." + i + ".value", Number(val.toFixed(3)), "", "", false);
|
|
964
|
+
}
|
|
965
|
+
await this.doState(pfx + "Consumption.json", JSON.stringify(consumptionArr), "", "", false);
|
|
742
966
|
} else {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
967
|
+
await this.doState(pfx + key, ValueTyping(key, value), "", "", false);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Decodes decodeAutarky from WebAPI *
|
|
974
|
+
*/
|
|
975
|
+
async decodeAutarky(obj) {
|
|
976
|
+
const pfx = "_api.Portal.Autarky.";
|
|
977
|
+
// store raw data
|
|
978
|
+
//await this.doState(pfx + "_json", JSON.stringify(obj), "Portal Autarky", "", false);
|
|
979
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
980
|
+
this.log.debug("decodeAutarky: " + pfx + key + ":" + value);
|
|
981
|
+
await this.doState(pfx + key, ValueTyping(key, value), "", "%", false);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Decodes AccuState from WebAPI
|
|
987
|
+
*/
|
|
988
|
+
async decodeAccuState(obj) {
|
|
989
|
+
const pfx = "_api.Portal.AccuState.";
|
|
990
|
+
// store raw json
|
|
991
|
+
//await this.doState(pfx + "_json", JSON.stringify(obj), "Portal Accu State", "", false);
|
|
992
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
993
|
+
if (key === "val") {
|
|
994
|
+
const voltageArr = value[0];
|
|
995
|
+
const currentArr = value[1];
|
|
996
|
+
|
|
997
|
+
// Voltage
|
|
998
|
+
let i = 0;
|
|
999
|
+
for (const [ts, val] of voltageArr) {
|
|
1000
|
+
i++;
|
|
1001
|
+
const dateStr = new Date(ts).toString();
|
|
1002
|
+
await this.doState(pfx + "Voltage." + i + ".ts", dateStr, "Timestamp", "", false);
|
|
1003
|
+
await this.doState(pfx + "Voltage." + i + ".value", Number(val.toFixed(3)), "Voltage", "V", false);
|
|
755
1004
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
);
|
|
1005
|
+
|
|
1006
|
+
// Current
|
|
1007
|
+
i = 0;
|
|
1008
|
+
for (const [ts, val] of currentArr) {
|
|
1009
|
+
i++;
|
|
1010
|
+
const dateStr = new Date(ts).toString();
|
|
1011
|
+
await this.doState(pfx + "Current." + i + ".ts", dateStr, "Timestampe", "", false);
|
|
1012
|
+
await this.doState(pfx + "Current." + i + ".value", Number(val.toFixed(3)), "Power", "A", false);
|
|
1013
|
+
}
|
|
1014
|
+
} else if (key === "lastupdated") {
|
|
1015
|
+
await this.doState(pfx + key, new Date(value).toString(), "Last updated", "", false);
|
|
1016
|
+
} else {
|
|
1017
|
+
await this.doState(pfx + key, ValueTyping(key, value), "", "", false);
|
|
764
1018
|
}
|
|
765
1019
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
)
|
|
777
|
-
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Decodes AccuSavings from WebAPI
|
|
1024
|
+
*/
|
|
1025
|
+
async decodeAccuSavings(obj) {
|
|
1026
|
+
const pfx = "_api.Portal.AccuSavings.";
|
|
1027
|
+
// store raw json
|
|
1028
|
+
//await this.doState(pfx + "_json", JSON.stringify(obj), "Portal Accu Savings", "", false);
|
|
1029
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1030
|
+
if (key == "lastupdated") {
|
|
1031
|
+
await this.doState(pfx + key, new Date(value).toString(), "Last Update", "", false);
|
|
1032
|
+
} else {
|
|
1033
|
+
await this.doState(pfx + key, ValueTyping(key, value), "", "", false);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Decodes Status from WebAPI
|
|
1040
|
+
*/
|
|
1041
|
+
async decodeStatus(obj, typ) {
|
|
1042
|
+
const pfx = "_api.Portal.Status." + typ + ".";
|
|
1043
|
+
// store raw json
|
|
1044
|
+
//await this.doState(pfx + "_json", JSON.stringify(obj), "Portal Status", "", false);
|
|
1045
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1046
|
+
if (key == "val") {
|
|
1047
|
+
// includes six arrays for the last 24 hours for different metrics (order? : accu import, accu export, grid import, grid export, power generated, consumption)
|
|
1048
|
+
const yearly = await this.decodeYearlyValues(value);
|
|
1049
|
+
for (const [year, aggregation] of Object.entries(yearly)) {
|
|
1050
|
+
await this.doState(pfx + year, Number(aggregation.toFixed(3)), "", "", false);
|
|
1051
|
+
}
|
|
1052
|
+
} else if (key == "lastupdated") {
|
|
1053
|
+
await this.doState(pfx + key, new Date(value).toString(), "Last Updated", "", false);
|
|
1054
|
+
} else {
|
|
1055
|
+
await this.doState(pfx + key, ValueTyping(key, value), "", "", false);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Converts SENEC yearly arrays into {year: value} object.
|
|
1062
|
+
*/
|
|
1063
|
+
async decodeYearlyValues(val) {
|
|
1064
|
+
if (!val || !Array.isArray(val)) return {};
|
|
1065
|
+
|
|
1066
|
+
const result = {};
|
|
1067
|
+
|
|
1068
|
+
for (const [ts, value] of val) {
|
|
1069
|
+
const year = new Date(ts).getFullYear(); // get year from timestamp
|
|
1070
|
+
result[year] = Number(value.toFixed(3)); // round to 3 decimal places
|
|
778
1071
|
}
|
|
779
|
-
|
|
1072
|
+
return result;
|
|
780
1073
|
}
|
|
781
1074
|
|
|
782
1075
|
/**
|
|
@@ -835,40 +1128,6 @@ class Senec extends utils.Adapter {
|
|
|
835
1128
|
}
|
|
836
1129
|
}
|
|
837
1130
|
|
|
838
|
-
/**
|
|
839
|
-
* Rebuilds AllTimeHistory from SENEC App API
|
|
840
|
-
*/
|
|
841
|
-
async rebuildAllTimeHistory(system) {
|
|
842
|
-
if (!this.config.api_use || !apiConnected) {
|
|
843
|
-
this.log.info("Usage of SENEC App API not configured or not connected.");
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
this.log.info("Rebuilding AllTime History ...");
|
|
848
|
-
let year = new Date(new Date().getFullYear() - 1, 1, 1).toISOString().split("T")[0]; // starting last year, because we already got current year covered
|
|
849
|
-
let body = "";
|
|
850
|
-
try {
|
|
851
|
-
while (new Date(year).getFullYear() > 2008) {
|
|
852
|
-
// senec was founded in 2009 by Mathias Hammer as Deutsche Energieversorgung GmbH (DEV) - so no way we have older data :)
|
|
853
|
-
this.log.info("Rebuilding AllTime History - Year: " + new Date(year).getFullYear());
|
|
854
|
-
const baseUrl = apiMonitorUrl + "/" + system;
|
|
855
|
-
let url = "";
|
|
856
|
-
const tzObj = await this.getStateAsync("_api.Anlagen." + system + ".zeitzone");
|
|
857
|
-
const tz = tzObj ? encodeURIComponent(tzObj.val) : encodeURIComponent("Europe/Berlin");
|
|
858
|
-
url = baseUrl + "/data?period=YEAR&date=" + year + "&locale=de_DE&timezone=" + tz;
|
|
859
|
-
this.log.debug("Polling: " + url);
|
|
860
|
-
body = await this.doGet(url, "", this, this.config.pollingTimeout, false);
|
|
861
|
-
await this.decodeStatistik(system, JSON.parse(body), api_trans["THIS_YEAR"].dp);
|
|
862
|
-
year = new Date(new Date(year).getFullYear() - 1, 1, 1).toISOString().split("T")[0];
|
|
863
|
-
if (unloaded) return;
|
|
864
|
-
}
|
|
865
|
-
} catch (error) {
|
|
866
|
-
this.log.info("Rebuild ended: " + error);
|
|
867
|
-
}
|
|
868
|
-
this.log.info("Restarting ...");
|
|
869
|
-
this.extendForeignObject(`system.adapter.${this.namespace}`, { native: { api_alltimeRebuild: false } });
|
|
870
|
-
}
|
|
871
|
-
|
|
872
1131
|
/**
|
|
873
1132
|
* sets a state's value and creates the state if it doesn't exist yet
|
|
874
1133
|
*/
|
|
@@ -1016,24 +1275,24 @@ const ValueTyping = (key, value) => {
|
|
|
1016
1275
|
}
|
|
1017
1276
|
};
|
|
1018
1277
|
|
|
1019
|
-
const ParseApi2KeyParts = (key) => {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
};
|
|
1278
|
+
// const ParseApi2KeyParts = (key) => {
|
|
1279
|
+
// //const match = key.match(/In([A-Za-z]+)$/);
|
|
1280
|
+
// //var unit = match ? match[1] : "";
|
|
1281
|
+
// //if (unit == "Percent") unit = "%";
|
|
1282
|
+
// //return unit;
|
|
1283
|
+
// const match = key.match(/^(.*)In([A-Za-z]+)$/);
|
|
1284
|
+
// if (match) {
|
|
1285
|
+
// return {
|
|
1286
|
+
// prefix: match[1], // part before "In"
|
|
1287
|
+
// unit: match[2] === "Percent" ? "%" : match[2], // replace "Percent" with "%"
|
|
1288
|
+
// };
|
|
1289
|
+
// }
|
|
1290
|
+
// return {
|
|
1291
|
+
// // default response for error
|
|
1292
|
+
// prefix: "unknownKey",
|
|
1293
|
+
// unit: "",
|
|
1294
|
+
// };
|
|
1295
|
+
// };
|
|
1037
1296
|
|
|
1038
1297
|
/**
|
|
1039
1298
|
* Converts float value in hex format to js float32.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.senec",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Senec Home",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "NoBl",
|
|
@@ -28,39 +28,32 @@
|
|
|
28
28
|
"url": "https://github.com/nobl/ioBroker.senec.git"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
"@iobroker/adapter-core": "^3.3.2",
|
|
32
|
+
"axios": "^1.13.4",
|
|
33
|
+
"axios-cookiejar-support": "^6.0.5",
|
|
34
|
+
"cheerio": "^1.2.0",
|
|
35
|
+
"tough-cookie": "^6.0.0"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
35
|
-
"@alcalzone/release-script": "^
|
|
36
|
-
"@alcalzone/release-script-plugin-iobroker": "^
|
|
37
|
-
"@alcalzone/release-script-plugin-license": "^
|
|
38
|
-
"@alcalzone/release-script-plugin-manual-review": "^
|
|
39
|
-
"@iobroker/adapter-dev": "^1.
|
|
40
|
-
"@iobroker/testing": "^5.
|
|
41
|
-
"@eslint/eslintrc": "^3.
|
|
42
|
-
"@eslint/js": "^9.
|
|
43
|
-
"@tsconfig/node20": "^20.1.
|
|
44
|
-
"@types/
|
|
45
|
-
"@types/chai-as-promised": "^8.0.1",
|
|
46
|
-
"@types/mocha": "^10.0.10",
|
|
47
|
-
"@types/node": "^22.10.7",
|
|
38
|
+
"@alcalzone/release-script": "^5.0.0",
|
|
39
|
+
"@alcalzone/release-script-plugin-iobroker": "^4.0.0",
|
|
40
|
+
"@alcalzone/release-script-plugin-license": "^4.0.0",
|
|
41
|
+
"@alcalzone/release-script-plugin-manual-review": "^4.0.0",
|
|
42
|
+
"@iobroker/adapter-dev": "^1.5.0",
|
|
43
|
+
"@iobroker/testing": "^5.2.2",
|
|
44
|
+
"@eslint/eslintrc": "^3.3.3",
|
|
45
|
+
"@eslint/js": "^9.39.2",
|
|
46
|
+
"@tsconfig/node20": "^20.1.8",
|
|
47
|
+
"@types/node": "^25.2.0",
|
|
48
48
|
"@types/proxyquire": "^1.3.31",
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
"eslint": "^9.17.0",
|
|
54
|
-
"eslint-config-prettier": "^10.0.1",
|
|
55
|
-
"eslint-plugin-prettier": "^5.2.1",
|
|
56
|
-
"globals": "^15.14.0",
|
|
57
|
-
"mocha": "^11.0.1",
|
|
49
|
+
"eslint": "^9.39.2",
|
|
50
|
+
"eslint-config-prettier": "^10.1.8",
|
|
51
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
52
|
+
"globals": "^17.3.0",
|
|
58
53
|
"mustache": "^4.2.0",
|
|
59
|
-
"prettier": "^3.
|
|
54
|
+
"prettier": "^3.8.1",
|
|
60
55
|
"proxyquire": "^2.1.3",
|
|
61
|
-
"
|
|
62
|
-
"sinon-chai": "^4.0.0",
|
|
63
|
-
"typescript": "~5.7.3"
|
|
56
|
+
"typescript": "~5.9.3"
|
|
64
57
|
},
|
|
65
58
|
"main": "main.js",
|
|
66
59
|
"files": [
|