iobroker.sun2000 2.2.0 → 2.3.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/README.md +13 -1
- package/io-package.json +27 -27
- package/lib/controls/emma_service_queue.js +304 -0
- package/lib/controls/{service_queue.js → inverter_service_queue.js} +117 -43
- package/lib/drivers/driver_base.js +44 -4
- package/lib/drivers/driver_emma.js +24 -5
- package/lib/drivers/driver_inverter.js +1 -1
- package/lib/modbus/modbus_connect.js +9 -2
- package/lib/modbus/modbus_server.js +11 -7
- package/lib/register.js +1 -1
- package/lib/tools.js +21 -0
- package/lib/types.js +21 -1
- package/main.js +17 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Feel free to follow the discussions in the german [iobroker forum](https://forum
|
|
|
30
30
|
## Requirements
|
|
31
31
|
* Node.js 20 or higher
|
|
32
32
|
* ioBroker host (js-controller) 6.0.11 or higher
|
|
33
|
-
* ioBroker admin 7.
|
|
33
|
+
* ioBroker admin 7.6.17 or higher
|
|
34
34
|
|
|
35
35
|
## Documentation
|
|
36
36
|
|
|
@@ -65,6 +65,18 @@ browse in the [wiki](https://github.com/bolliy/ioBroker.sun2000/wiki)
|
|
|
65
65
|
Placeholder for the next version (at the beginning of the line):
|
|
66
66
|
### **WORK IN PROGRESS**
|
|
67
67
|
-->
|
|
68
|
+
### 2.3.0 (2025-10-21)
|
|
69
|
+
* new release for npm migrates to Trusted Publishing
|
|
70
|
+
|
|
71
|
+
### 2.2.1-alpha.0 (2025-10-21)
|
|
72
|
+
* inverter control: add same state for startup and shutdown an inverter [#199](https://github.com/bolliy/ioBroker.sun2000/issues/199)
|
|
73
|
+
* fix: Device status name has been corrected [#202](https://github.com/bolliy/ioBroker.sun2000/pull/202)
|
|
74
|
+
* add undocumented device status `Shutdown: End of the ESS discharge process`
|
|
75
|
+
* emma control: new state ` emma.control.battery.ESSControlMode`. You can now configure EMMA with TOU-mode (Time of Use) to charge the battery from grid. [#200](https://github.com/bolliy/ioBroker.sun2000/discussions/200)
|
|
76
|
+
* if an Emma is installed, some control states of the inverter are deactivated (read only). Mainly for the grid settings.
|
|
77
|
+
* deprecated control states have been removed.
|
|
78
|
+
* a workaround for issue [#582](https://github.com/yaacov/node-modbus-serial/issues/582) of node-modbus-serial has been implemented.
|
|
79
|
+
|
|
68
80
|
### 2.2.0 (2025-10-05)
|
|
69
81
|
* dependency and configuration updates
|
|
70
82
|
* new state `meter.derived.signConventionForPowerFeed-in` sign of meter.activePower that is currently being fed into the power grid
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "sun2000",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.3.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.3.0": {
|
|
7
|
+
"en": "new release for npm migrates to Trusted Publishing",
|
|
8
|
+
"de": "neue Veröffentlichung für npm migriert Trusted Publishing",
|
|
9
|
+
"ru": "новый релиз для npm перенесен в Trusted Publishing",
|
|
10
|
+
"pt": "nova versão para npm migra para a publicação confiável",
|
|
11
|
+
"nl": "nieuwe release voor npm-migraties naar Trusted Publishing",
|
|
12
|
+
"fr": "nouvelle version pour npm migre vers Trusted Publishing",
|
|
13
|
+
"it": "nuovo rilascio per npm migra a Trusted Publishing",
|
|
14
|
+
"es": "nueva versión para npm migrates a Trusted Publishing",
|
|
15
|
+
"pl": "nowa wersja dla migratów npm do zaufanej publikacji",
|
|
16
|
+
"uk": "новий реліз для npm migrats для Trusted Publishing",
|
|
17
|
+
"zh-cn": "npm 的新版本迁移到信任出版"
|
|
18
|
+
},
|
|
19
|
+
"2.2.1-alpha.0": {
|
|
20
|
+
"en": "inverter control: add same state for startup and shutdown an inverter [#199](https://github.com/bolliy/ioBroker.sun2000/issues/199)\nfix: Device status name has been corrected [#202](https://github.com/bolliy/ioBroker.sun2000/pull/202)\nadd undocumented device status `Shutdown: End of the ESS discharge process` \nemma control: new state ` emma.control.battery.ESSControlMode`. You can now configure EMMA with TOU-mode (Time of Use) to charge the battery from grid. [#200](https://github.com/bolliy/ioBroker.sun2000/discussions/200)\nif an Emma is installed, some control states of the inverter are deactivated (read only). Mainly for the grid settings.\ndeprecated control states have been removed.\na workaround for issue [#582](https://github.com/yaacov/node-modbus-serial/issues/582) of node-modbus-serial has been implemented.",
|
|
21
|
+
"de": "inverter-Steuerung: Fügen Sie den gleichen Zustand für Start und Abschaltung eines Inverters [#199](https://github.com/bolliy/ioBroker.sun2000/issues/199) hinzu\nbehoben: Gerätestatusname wurde korrigiert [#202](https://github.com/bolliy/ioBroker.sun2000/pull/202)\n`Shutdown: Ende des ESS-Entladungsvorgangs`\nemma control: new state ` emma.control.battery.ESSControlMode`. Sie können nun EMMA mit TOU-Modus (Time of Use) konfigurieren, um den Akku aus dem Netz zu laden. [#200](https://github.com/bolliy/ioBroker.sun2000/discussions/200)\nwenn eine Emma installiert ist, werden einige Steuerzustände des Wechselrichters deaktiviert (nur lesen). Hauptsächlich für die Netzeinstellungen.\ndeprekierte steuerzustände wurden entfernt.\nein workaround für ausgabe [#582](https://github.com/yaacov/node-modbus-serial/issues/582) von node-modbus-serial wurde implementiert.",
|
|
22
|
+
"ru": "инверторное управление: добавьте то же состояние для запуска и отключения инвертора [#199] (https://github.com/bolliy/ioBroker.sun2000/issues/199)\nисправление: имя статуса устройства было исправлено [#202] (https://github.com/bolliy/ioBroker.sun2000/pull/202)\nдобавить незарегистрированный статус устройства «Shutdown: End of the ESS discharge process»\nemma control: emma.control.battery.ESSControlMode. Теперь вы можете настроить EMMA с режимом TOU (Время использования) для зарядки батареи из сетки. [#200] (https://github.com/bolliy/ioBroker.sun2000/discussions/200)\nесли установлена Эмма, некоторые контрольные состояния инвертора деактивируются (только чтение). В основном для настроек сетки.\nустаревшие контрольные штаты были удалены.\nбыл реализован обходной путь для выпуска [#582] (https://github.com/yaacov/node-modbus-serial/issues/582) node-modbus-serial.",
|
|
23
|
+
"pt": "controle do inversor: adicione o mesmo estado para iniciar e desligar um inversor [#199](https://github.com/bolliy/ioBroker.sun2000/issues/199)\ncorreção: o nome do estado do dispositivo foi corrigido [#202] (https://github.com/bolliy/ioBroker.sun2000/pull/202)\nadicionar status do dispositivo não documentado `Shutdown: Fim do processo de descarga do ESS'\nemma control: novo estado `emma.control.battery.ESSControlMode`. Agora você pode configurar o EMMA com o modo TOU (Time of Use) para carregar a bateria da grade. [#200](https://github.com/bolliy/ioBroker.sun2000/discussions/200)\nse uma Emma estiver instalada, alguns estados de controle do inversor são desativados (apenas lidos). Principalmente para as configurações da grade.\nos estados de controlo desactualizados foram removidos.\numa solução alternativa para o problema [#582](https://github.com/yaacov/node-modbus-serial/issues/582) do nó-modbus-serial foi implementada.",
|
|
24
|
+
"nl": "inverter control: voeg dezelfde status toe voor het opstarten en afsluiten van een inverter [#199](https://github.com/bolliy/ioBroker.sun2000/issues/199)\nfix: De status van het apparaat is gecorrigeerd [#202](https://github.com/bolliy/ioBroker.sun2000/pull/202)\nstatus van niet-gedocumenteerd apparaat toevoegen: Afsluiten: Einde van het ESS-ontladingsproces\nemma control: nieuwe staat U kunt nu EMMA configureren met de TOU-modus (Time of Use) om de batterij op te laden van het net. [#200](https://github.com/bolliy/ioBroker.sun2000/discussions/200)\nals een Emma is geïnstalleerd, worden sommige controletoestanden van de omvormer gedeactiveerd (alleen lezen). Vooral voor de rasterinstellingen.\nverouderde controlestaten zijn verwijderd.\ner is een oplossing gevonden voor nummer [#582](https://github.com/yaacov/node-modbus-serial/issues/582) van node-modbus-serial.",
|
|
25
|
+
"fr": "contrôle de l'onduleur: ajouter le même état pour le démarrage et l'arrêt d'un onduleur [#199](https://github.com/bolliy/ioBroker.sun2000/issues/199)\ncorrection : le nom de l'état du périphérique a été corrigé [#202](https://github.com/bolliy/ioBroker.sun2000/pull/202)\najouter l'état de l'appareil sans papiers `Shutdown: Fin du processus de décharge ESS'\ncontrôle emma: nouvel état ` emma.control.battery.ESSControlMode`. Vous pouvez maintenant configurer EMMA en mode TOU (Time of Use) pour charger la batterie à partir de la grille. [#200](https://github.com/bolliy/ioBroker.sun2000/discussions/200)\nsi une Emma est installée, certains états de contrôle de l'onduleur sont désactivés (lisez seulement). Principalement pour les paramètres de la grille.\nles états de contrôle dépréciés ont été supprimés.\nune solution de rechange pour l'émission [#582](https://github.com/yaacov/node-modbus-serial/issues/582) de nœud-modbus-serial a été mise en œuvre.",
|
|
26
|
+
"it": "controllo inverter: aggiungere lo stesso stato per l'avvio e l'arresto di un inverter [#199](https://github.com/bolliy/ioBroker.sun2000/issues/199)\nfix: Il nome dello stato del dispositivo è stato corretto [#202](https://github.com/bolliy/ioBroker.sun2000/pull/202)\naggiungere lo stato del dispositivo non documentato `Shutdown: Fine del processo di scarico ESS`\ncontrollo emma: nuovo stato ` emma.control.battery.ESSControlMode`. È ora possibile configurare EMMA con TOU-mode (Time of Use) per caricare la batteria dalla rete. [#200](https://github.com/bolliy/ioBroker.sun2000/discussions/200)\nse viene installata una Emma, alcuni stati di controllo dell'inverter sono disattivati (leggi solo). Principalmente per le impostazioni della griglia.\nstati di controllo deprecati sono stati rimossi.\nè stato implementato un workaround per il numero [#582](https://github.com/yaacov/node-modbus-serial/issues/582) di node-modbus-serial.",
|
|
27
|
+
"es": "control inverter: añadir el mismo estado para iniciar y cerrar un inverter [#199](https://github.com/bolliy/ioBroker.sun2000/issues/199)\nfijado: Se ha corregido el nombre del estado de los dispositivos [#202](https://github.com/bolliy/ioBroker.sun2000/pull/202)\nañadir estado de dispositivo no documentado `Shutdown: Final del proceso de descarga ESS`\nemma control: nuevo estado ` emma.control.battery.ESSControlMode`. Ahora puede configurar EMMA con TOU-mode (Time of Use) para cargar la batería de la red. [#200](https://github.com/bolliy/ioBroker.sun2000/discussions/200)\nsi se instala una Emma, se desactivan algunos estados de control del inversor (sólo lectura). Principalmente para la configuración de la red.\nestados de control deprecatados han sido eliminados.\na workaround for issue [#582](https://github.com/yaacov/node-modbus-serial/issues/582) of node-modbus-serial has been implemented.",
|
|
28
|
+
"pl": "inverter control: add same state for startup and shutdown an inverter [# 199] (https: / / github.com / bolliy / ioBroker.sun2000 / issues / 199)\nfix: Nazwa statusu urządzenia została poprawiona [# 202] (https: / / github.com / bolliy / ioBroker.sun2000 / pull / 202)\ndodać nieudokumentowany status urządzenia \"Wyłączenie: koniec procesu rozładowania ESS\"\nemma control: New state 'emma.control.battery.ESSControlMode'. Można teraz skonfigurować EMMA z TOU- mode (Time of Use), aby naładować baterię z siatki. [# 200] (https: / / github.com / bolliy / ioBroker.sun2000 / discussions / 200)\njeśli zainstalowana jest Emma, niektóre stany sterujące inwertera są wyłączone (tylko do odczytu). Głównie dla ustawień siatki.\nusunięto zdepregatowane stany kontrolne.\na workaround for issue [# 582] (https: / / github.com / yaacov / node- modbus- serial / issues / 582) of node- modbus- serial został wdrożony.",
|
|
29
|
+
"uk": "контроль інвертора: додати однаковий стан для запуску та відключення інвертора [#199](https://github.com/bolliy/ioBroker.sun2000/issues/199)\nвиправлено: Назва стану пристрою було виправлено [#202] (https://github.com/bolliy/ioBroker.sun2000/pull/202)\nдодати недокументований статус пристрою `Пошук: кінець процесу розряду ESS\nемма управління: новий стан ` emma.control.battery.ESSControlMode`. Ви можете налаштувати EMMA з TOU-mode (Time of Use) для зарядки акумулятора з сітки. [#200](https://github.com/bolliy/ioBroker.sun2000/discussions/200)\nякщо встановлений Емма, деактивуються деякі стани управління інвертором (прочитати тільки). В основному для налаштування сітки.\nвилучені стани керування.\n[#582](https://github.com/yaacov/node-modbus-serial/issues/582) вузла-modbus-serial.",
|
|
30
|
+
"zh-cn": "反转器控制:为启动和关闭添加相同状态的反转器[199](https://github.com/bolliy/ioBroker.sun2000/issues/199)\n固定: 设备状态名称已更正 [# 202] (https://github.com/bolliy/ioBroker.sun2000/pul/202)\n添加无文件设备状态“关闭:ESS放电过程结束”\nemma控制:新状态`emma.control.battery.ESS Control Mode'. 您现在可以用 TOU- mode( 使用时间) 配置 EMMA 从电网中充电 。 [# 200] (https://github.com/bolliy/ioBroker.sun2000/discussions/200)\n如果安装了Emma,反转器的某些控制状态就会失效(只读). 主要用于网格设置.\n贬值的控制状态已被取消.\n已落实了节点-modbus-serial[#582](https://github.com/yaacov/node-modbus-serial/issues/582)的工作."
|
|
31
|
+
},
|
|
6
32
|
"2.2.0": {
|
|
7
33
|
"en": "dependency and configuration updates\nnew state `meter.derived.signConventionForPowerFeed-in` sign of meter.activePower that is currently being fed into the power grid\nnew state `meter.derived.feed-inPower` electric power that is supplied to a grid (\"fed in\")",
|
|
8
34
|
"de": "abhängigkeits- und konfigurationsupdates\nneuer Zustand `meter.derived.signConventionForPowerFeed-in` Zeichen von meter.active Strom, der derzeit in das Stromnetz eingespeist wird\nneue Zustand `meter.derived.feed-inPower` elektrische Leistung, die einem Netz zugeführt wird (\"fed in\")",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "aktualizacji zależności\nfix: nadwyżka mocy podczas rozładowania akumulatora\ncontrol: nowa wartość kontrolna 'sun2000.0.control.usableSurplus.buverHysteresis'",
|
|
68
94
|
"uk": "оновлення залежності\nвиправити: надлишок живлення при розряді акумулятора\n`sun2000.0.control.usableSurplus.bufferHysteresis`",
|
|
69
95
|
"zh-cn": "更新依赖关系\n固定:电池放电时的剩余功率\n控制: 新的控制值 `sun2000.control.ulySurplus.bufferHysteresis'"
|
|
70
|
-
},
|
|
71
|
-
"1.4.0": {
|
|
72
|
-
"en": "control: new control state `sun2000.0.control.externalPower` #76\nfixed issue detected by repository checker #166",
|
|
73
|
-
"de": "steuerung: neuer Steuerzustand `sun2000.0.control.externalPower` #76\nbehobenes problem erkannt von repository checker #166",
|
|
74
|
-
"ru": "новый контрольный режим «sun2000.0.control.externalPower» #76\nфиксированная проблема, обнаруженная проверкой репозитория #166",
|
|
75
|
-
"pt": "controle: novo estado de controle `sun2000.0.control.externalPower` #76\nproblema fixo detectado pelo verificador de repositório #166",
|
|
76
|
-
"nl": "controle: nieuwe controle staat #76\nvaste uitgifte gedetecteerd door repository checker #166",
|
|
77
|
-
"fr": "contrôle: nouvel état de contrôle `sun2000.0.control.externalPower` #76\nproblème corrigé détecté par le vérificateur de dépôt #166",
|
|
78
|
-
"it": "controllo: nuovo stato di controllo `sun2000.0.control.externalPower` #76\nproblema fisso rilevato da repository checker #166",
|
|
79
|
-
"es": "control: nuevo estado de control `sun2000.0.control.externalPower` #76\nnúmero fijo detectado por el repositorio #166",
|
|
80
|
-
"pl": "control: nowy stan kontroli 'sun2000.0.control.externalPower' # 76\nstała emisja wykryta przez kontroler repozytorium # 166",
|
|
81
|
-
"uk": "контроль: новий стан управління `sun2000.0.control.externalPower` #76\nвиправлено проблему, виявлену репозиторійною перевіркою #166",
|
|
82
|
-
"zh-cn": "控制:新的控制状态`sun 2000.0.control.external Power ' 76号\n仓库检查器检测到的固定问题 # 166"
|
|
83
|
-
},
|
|
84
|
-
"1.3.0": {
|
|
85
|
-
"en": "usableSurplusPower: new control state `sun2000.0.control.usableSurplus.allowNegativeValue`",
|
|
86
|
-
"de": "usableSurplusPower: neuer Steuerzustand `sun2000.0.control.usableSurplus.allowNegativeValue `",
|
|
87
|
-
"ru": "usableSurplusPower: новое состояние управления 'sun2000.0.control.usableSurplus.allowНегативная ценность пункт",
|
|
88
|
-
"pt": "usableSurplusPower: novo estado de controle `sun2000.0.control.usableSurplus.allowValue negativo \"",
|
|
89
|
-
"nl": "bruikbareSurplusPower: nieuwe controlestatus Wat",
|
|
90
|
-
"fr": "utilisableSurplusPower: nouvel état de contrôle `sun2000.0.control.usableSurplus.allowNegativeValue \"",
|
|
91
|
-
"it": "usableSurplusPower: nuovo stato di controllo `sun2000.0.control.usableSurplus.allowValore negativo #",
|
|
92
|
-
"es": "usableSurplusPower: nuevo estado de control `sun2000.0.control.usableSurplus.allowNegativeValue `",
|
|
93
|
-
"pl": "usableSurplusPower: nowy stan kontroli 'Sun2000.0.control.usableSurplus.allowNegativeValue'",
|
|
94
|
-
"uk": "english, Українська, Français... й",
|
|
95
|
-
"zh-cn": "可用SurpusPower: 新的控制状态 `sun 2000.control.usurplus.allow NegativeValue ' `"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { deviceType, dataType } = require(`${__dirname}/../types.js`);
|
|
3
|
+
class ServiceQueueMap {
|
|
4
|
+
constructor(adapterInstance, emma) {
|
|
5
|
+
this.adapter = adapterInstance;
|
|
6
|
+
this.log = this.adapter.logger;
|
|
7
|
+
this.inverterInfo = emma;
|
|
8
|
+
this._modbusClient = null;
|
|
9
|
+
this._serviceMap = new Map();
|
|
10
|
+
this._eventMap = new Map();
|
|
11
|
+
this._initialized = false;
|
|
12
|
+
this._name = 'emma control';
|
|
13
|
+
|
|
14
|
+
this.serviceFields = [
|
|
15
|
+
{
|
|
16
|
+
state: { id: 'battery.ESSControlMode', name: 'ESS control mode', type: 'number', unit: '', role: 'level', desc: 'reg:40000, len:1' },
|
|
17
|
+
type: deviceType.battery,
|
|
18
|
+
fn: async event => {
|
|
19
|
+
let ret = false;
|
|
20
|
+
if (event.value > 6) {
|
|
21
|
+
event.value = 2;
|
|
22
|
+
}
|
|
23
|
+
if (event.value < 2 || event.value === 3) {
|
|
24
|
+
event.value = 2;
|
|
25
|
+
}
|
|
26
|
+
if (this.isTestMode()) {
|
|
27
|
+
this.log.info(`${this._name}: the test mode is active, so the ESS control mode is always written to register 47086`);
|
|
28
|
+
ret = await this._writeRegisters(47086, dataType.numToArray(event.value, dataType.uint16));
|
|
29
|
+
} else {
|
|
30
|
+
ret = await this._writeRegisters(40000, dataType.numToArray(event.value, dataType.uint16));
|
|
31
|
+
/*
|
|
32
|
+
if (ret) {
|
|
33
|
+
this.inverterInfo.instance.stateCache.set(`emma.${event.id}`, event.value, { type: 'number' });
|
|
34
|
+
}
|
|
35
|
+
*/
|
|
36
|
+
}
|
|
37
|
+
return ret;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
state: {
|
|
42
|
+
id: 'battery.tou.preferredUseOfSurplusPvPower',
|
|
43
|
+
name: '[Time of Use mode] Preferred use of surplus PV power',
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
unit: '',
|
|
46
|
+
role: 'switch.enable',
|
|
47
|
+
desc: 'reg: 40001, len: 1',
|
|
48
|
+
},
|
|
49
|
+
type: deviceType.battery,
|
|
50
|
+
fn: async event => {
|
|
51
|
+
let ret = false;
|
|
52
|
+
|
|
53
|
+
if (this.isTestMode()) {
|
|
54
|
+
this.log.info(`${this._name}: the test mode is active, so the maximum power for charging batteries from grid not transferred`);
|
|
55
|
+
ret = true;
|
|
56
|
+
} else {
|
|
57
|
+
ret = await this._writeRegisters(40001, event.value === true ? [1] : [0]);
|
|
58
|
+
}
|
|
59
|
+
/*
|
|
60
|
+
if (ret) {
|
|
61
|
+
this.inverterInfo.instance.stateCache.set(`emma.${event.id}`, event.value);
|
|
62
|
+
}
|
|
63
|
+
*/
|
|
64
|
+
return ret;
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
state: {
|
|
69
|
+
id: 'battery.tou.maximumPowerForChargingFromGrid',
|
|
70
|
+
name: '[Time of Use mode] Maximum power for charging batteries from grid',
|
|
71
|
+
type: 'number',
|
|
72
|
+
unit: 'kW',
|
|
73
|
+
role: 'level.power',
|
|
74
|
+
desc: 'reg: 40002, len: 2',
|
|
75
|
+
},
|
|
76
|
+
type: deviceType.battery,
|
|
77
|
+
fn: async event => {
|
|
78
|
+
let ret = false;
|
|
79
|
+
if (event.value > 50) {
|
|
80
|
+
event.value = 50;
|
|
81
|
+
}
|
|
82
|
+
if (event.value < 0) {
|
|
83
|
+
event.value = 0;
|
|
84
|
+
}
|
|
85
|
+
if (this.isTestMode()) {
|
|
86
|
+
this.log.info(`${this._name}: the test mode is active, so the maximum power for charging batteries from grid not transferred`);
|
|
87
|
+
ret = true;
|
|
88
|
+
} else {
|
|
89
|
+
ret = await this._writeRegisters(40002, dataType.numToArray(event.value * 1000, dataType.uint32));
|
|
90
|
+
}
|
|
91
|
+
/*
|
|
92
|
+
if (ret) {
|
|
93
|
+
this.inverterInfo.instance.stateCache.set(`emma.${event.id}`, event.value, { type: 'number' });
|
|
94
|
+
}
|
|
95
|
+
*/
|
|
96
|
+
return ret;
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async _init() {
|
|
103
|
+
if (this.inverterInfo.instance) {
|
|
104
|
+
for (const item of this.serviceFields) {
|
|
105
|
+
if (item?.state) {
|
|
106
|
+
this._serviceMap.set(item.state.id, item);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const entry of this._serviceMap.values()) {
|
|
111
|
+
//await this._initState('emma.control.',entry.state);
|
|
112
|
+
const path = `emma.control.`;
|
|
113
|
+
await this._initState(path, entry.state);
|
|
114
|
+
const state = await this.adapter.getState(path + entry.state.id);
|
|
115
|
+
if (state && state.ack === false) {
|
|
116
|
+
this.set(entry.state.id, state);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//subscribe all control states
|
|
120
|
+
this.adapter.subscribeStates(`emma.control*`);
|
|
121
|
+
this._initialized = true;
|
|
122
|
+
|
|
123
|
+
if (this.adapter.settings?.cb.tou && !this.isTestMode()) {
|
|
124
|
+
const essControlMode = await this._readHoldingRegisters(40000, 1);
|
|
125
|
+
//const tou = await this._readHoldingRegisters(40004,43); //first periode
|
|
126
|
+
if (essControlMode && essControlMode[0] !== 5) {
|
|
127
|
+
/*
|
|
128
|
+
127 - Working mode settings
|
|
129
|
+
2 : Maximise self consumptions (default)
|
|
130
|
+
5 : Time Of Use(Luna) - hilfreich bei dynamischem Stromtarif (z.B Tibber)
|
|
131
|
+
|
|
132
|
+
Time of Using charging and discharging periodes (siehe Table 5-6)
|
|
133
|
+
tCDP[3] = 127 - Working mode settings - load from grid (charge)
|
|
134
|
+
tCDP[3] = 383 - Working mode settings - self-consumption (discharge)
|
|
135
|
+
*/
|
|
136
|
+
const tCDP = [
|
|
137
|
+
1, 0, 1440, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
138
|
+
];
|
|
139
|
+
if (await this._writeRegisters(40004, tCDP)) {
|
|
140
|
+
this.inverterInfo.instance.addHoldingRegisters((40004, tCDP));
|
|
141
|
+
this.log.info(`${this._name}: The default TOU setting are transferred`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (this._initialized) {
|
|
148
|
+
this.log.info(`${this._name}: service queue initialized`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Checks if the device is in test mode
|
|
154
|
+
* @returns {boolean} True if the device is in test mode, false otherwise
|
|
155
|
+
*/
|
|
156
|
+
isTestMode() {
|
|
157
|
+
return this.inverterInfo.instance?.isTestMode() || false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @description Check if the value of the event is a number and optionally round it to the nearest integer.
|
|
162
|
+
* @param {object} event The event object
|
|
163
|
+
* @param {boolean} [round] If true, the value is rounded to the nearest integer
|
|
164
|
+
* @returns {boolean} True if the value is a number, false otherwise
|
|
165
|
+
*/
|
|
166
|
+
isNumber(event, round = true) {
|
|
167
|
+
if (isNaN(event.value)) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
if (round) event.value = Math.round(event.value);
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
get(id) {
|
|
175
|
+
return this._eventMap.get(id);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
set(id, state) {
|
|
179
|
+
const service = this._serviceMap.get(id);
|
|
180
|
+
if (state && service) {
|
|
181
|
+
if (state.val !== null && !state.ack) {
|
|
182
|
+
this.log.info(`${this._name}: Event - state: emma.control.${id} changed: ${state.val} ack: ${state.ack}`);
|
|
183
|
+
const event = this._eventMap.get(id);
|
|
184
|
+
if (event) {
|
|
185
|
+
event.value = state.val;
|
|
186
|
+
event.ack = false;
|
|
187
|
+
} else {
|
|
188
|
+
this._eventMap.set(id, { id: id, value: state.val, ack: false });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Processes pending events in the service queue and attempts to execute their associated functions.
|
|
196
|
+
*
|
|
197
|
+
* @async
|
|
198
|
+
* @param {object} modbusClient - The modbus client instance used for communication.
|
|
199
|
+
*
|
|
200
|
+
* @description This function iterates over the events in the service queue, checking whether each event
|
|
201
|
+
* can be processed. It verifies conditions such as battery presence and running status, and checks if the
|
|
202
|
+
* event value is a number when required. If the event is successfully processed, it acknowledges the event
|
|
203
|
+
* by setting its state in the adapter and removes it from the event map. If the event cannot be processed
|
|
204
|
+
* after multiple attempts, it is discarded. The function initializes the service queue if it is not already
|
|
205
|
+
* initialized and the adapter is connected.
|
|
206
|
+
*/
|
|
207
|
+
|
|
208
|
+
async process(modbusClient) {
|
|
209
|
+
this._modbusClient = modbusClient;
|
|
210
|
+
|
|
211
|
+
if (this._initialized) {
|
|
212
|
+
let count = 0;
|
|
213
|
+
for (const event of this._eventMap.values()) {
|
|
214
|
+
if (event.ack) {
|
|
215
|
+
continue;
|
|
216
|
+
} //allready done
|
|
217
|
+
const service = this._serviceMap.get(event.id);
|
|
218
|
+
if (!service.errorCount) {
|
|
219
|
+
service.errorCount = 0;
|
|
220
|
+
}
|
|
221
|
+
if (event.value !== null && service.fn) {
|
|
222
|
+
//check if battery is present and running
|
|
223
|
+
if (service.state.type === 'number') {
|
|
224
|
+
if (!this.isNumber(event)) {
|
|
225
|
+
this.log.warn(
|
|
226
|
+
`${this._name}: Event is discarded because the value ${event.value} is not a number. State: emma.control.${event.id}`,
|
|
227
|
+
);
|
|
228
|
+
this._eventMap.delete(event.id); //forget the event
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
count++;
|
|
233
|
+
if (await service.fn(event)) {
|
|
234
|
+
service.errorCount = 0;
|
|
235
|
+
try {
|
|
236
|
+
event.ack = true;
|
|
237
|
+
await this.adapter.setState(`emma.control.${event.id}`, { val: event.value, ack: true });
|
|
238
|
+
this._eventMap.delete(event.id);
|
|
239
|
+
this.log.info(`${this._name}: write state emma.control.${event.id} : ${event.value} ack: true`);
|
|
240
|
+
} catch {
|
|
241
|
+
this.log.warn(`${this._name}: Can not write state emma.control.${event.id}`);
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
service.errorCount++;
|
|
245
|
+
if (service.errorCount > 1) {
|
|
246
|
+
this._eventMap.delete(event.id); //forget it
|
|
247
|
+
this.log.info(`${this._name}: Event is discarded because it could not be processed. State: emma.control.${event.id}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (count > 1) {
|
|
252
|
+
break;
|
|
253
|
+
} //max 2 Events
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (!this._initialized && this.adapter.isConnected) {
|
|
257
|
+
await this._init();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async _writeRegisters(address, data) {
|
|
262
|
+
try {
|
|
263
|
+
this.log.debug(`Try to write data to id/address/length ${this._modbusClient.id}/${address}/${data.length}`);
|
|
264
|
+
await this._modbusClient.writeRegisters(address, data);
|
|
265
|
+
this.inverterInfo.instance.addHoldingRegisters(address, data); //write also to the modbus read cache
|
|
266
|
+
return true;
|
|
267
|
+
} catch (err) {
|
|
268
|
+
this.log.warn(
|
|
269
|
+
`Error while writing to ${this._modbusClient.ipAddress} [Reg: ${address}, Len: ${data.length}, modbusID: ${this._modbusClient.id}] with: ${err.message}`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async _readHoldingRegisters(address, length) {
|
|
275
|
+
try {
|
|
276
|
+
this.log.debug(`Try to read data to id/address/length ${this._modbusClient.id}/${address}/${length}`);
|
|
277
|
+
const data = await this._modbusClient.readHoldingRegisters(address, length);
|
|
278
|
+
return data;
|
|
279
|
+
} catch (err) {
|
|
280
|
+
this.log.warn(
|
|
281
|
+
`Error while reading from ${this._modbusClient.ipAddress} [Reg: ${address}, Len: ${length}, modbusID: ${this._modbusClient.id}] with: ${err.message}`,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
//state
|
|
287
|
+
async _initState(path, state) {
|
|
288
|
+
await this.adapter.extendObject(path + state.id, {
|
|
289
|
+
type: 'state',
|
|
290
|
+
common: {
|
|
291
|
+
name: state.name,
|
|
292
|
+
type: state.type,
|
|
293
|
+
role: state.role,
|
|
294
|
+
unit: state.unit,
|
|
295
|
+
desc: state.desc,
|
|
296
|
+
read: true,
|
|
297
|
+
write: true,
|
|
298
|
+
},
|
|
299
|
+
native: {},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
module.exports = ServiceQueueMap;
|
|
@@ -1,19 +1,62 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
const { deviceType, dataType } = require(`${__dirname}/../types.js`);
|
|
2
|
+
const { deviceType, driverClasses, dataType } = require(`${__dirname}/../types.js`);
|
|
3
|
+
const tools = require(`${__dirname}/../tools.js`);
|
|
4
|
+
|
|
3
5
|
class ServiceQueueMap {
|
|
4
|
-
constructor(adapterInstance,
|
|
6
|
+
constructor(adapterInstance, device) {
|
|
5
7
|
this.adapter = adapterInstance;
|
|
6
8
|
this.log = this.adapter.logger;
|
|
7
|
-
this.inverterInfo =
|
|
9
|
+
this.inverterInfo = device;
|
|
8
10
|
this._modbusClient = null;
|
|
9
11
|
this._serviceMap = new Map();
|
|
10
12
|
this._eventMap = new Map();
|
|
11
13
|
this._initialized = false;
|
|
14
|
+
this._name = 'inverter control';
|
|
15
|
+
this._emmaAvailable = this.adapter.devices.find(d => d.driverClass == driverClasses.emma);
|
|
12
16
|
|
|
13
17
|
this.serviceFields = [
|
|
18
|
+
{
|
|
19
|
+
state: {
|
|
20
|
+
id: 'startup',
|
|
21
|
+
name: 'startup inverter',
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
role: 'switch.enable',
|
|
24
|
+
desc: 'reg: 40200 , len: 1',
|
|
25
|
+
},
|
|
26
|
+
type: deviceType.inverter,
|
|
27
|
+
deactiveIfEmmaAvailable: true,
|
|
28
|
+
fn: async event => {
|
|
29
|
+
let ret = false;
|
|
30
|
+
if (event.value === true) {
|
|
31
|
+
ret = await this._writeRegisters(40200, [0]);
|
|
32
|
+
event.value = false;
|
|
33
|
+
}
|
|
34
|
+
return ret;
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
state: {
|
|
39
|
+
id: 'shutdown',
|
|
40
|
+
name: 'shutdown inverter',
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
role: 'switch.enable',
|
|
43
|
+
desc: 'reg: 40201, len: 1',
|
|
44
|
+
},
|
|
45
|
+
deactiveIfEmmaAvailable: true,
|
|
46
|
+
type: deviceType.inverter,
|
|
47
|
+
fn: async event => {
|
|
48
|
+
let ret = false;
|
|
49
|
+
if (event.value === true) {
|
|
50
|
+
ret = await this._writeRegisters(40201, [0]);
|
|
51
|
+
event.value = false;
|
|
52
|
+
}
|
|
53
|
+
return ret;
|
|
54
|
+
},
|
|
55
|
+
},
|
|
14
56
|
{
|
|
15
57
|
state: { id: 'battery.chargeFromGridFunction', name: 'Charge from grid', type: 'boolean', role: 'switch.enable', desc: 'reg: 47087, len: 1' },
|
|
16
58
|
type: deviceType.battery,
|
|
59
|
+
deactiveIfEmmaAvailable: true,
|
|
17
60
|
fn: async event => {
|
|
18
61
|
const ret = await this._writeRegisters(47087, event.value === true ? [1] : [0]);
|
|
19
62
|
if (ret) {
|
|
@@ -72,8 +115,8 @@ class ServiceQueueMap {
|
|
|
72
115
|
return ret;
|
|
73
116
|
},
|
|
74
117
|
},
|
|
118
|
+
/* @deprecated
|
|
75
119
|
{
|
|
76
|
-
//@deprecated
|
|
77
120
|
state: {
|
|
78
121
|
id: 'battery.maximumChargePower',
|
|
79
122
|
name: 'MaximumChargePower',
|
|
@@ -91,12 +134,11 @@ class ServiceQueueMap {
|
|
|
91
134
|
if (event.value < 0) {
|
|
92
135
|
event.value = 0;
|
|
93
136
|
}
|
|
94
|
-
this.log.warn(
|
|
137
|
+
this.log.warn(`${this._name}: maximumChargePower is deprecated use "maximumChargingPower" instead`);
|
|
95
138
|
return await this._writeRegisters(47075, dataType.numToArray(event.value, dataType.uint32));
|
|
96
139
|
},
|
|
97
140
|
},
|
|
98
141
|
{
|
|
99
|
-
//@deprecated
|
|
100
142
|
state: {
|
|
101
143
|
id: 'battery.maximumDischargePower',
|
|
102
144
|
name: 'MaximumDischargePower',
|
|
@@ -114,10 +156,11 @@ class ServiceQueueMap {
|
|
|
114
156
|
if (event.value < 0) {
|
|
115
157
|
event.value = 0;
|
|
116
158
|
}
|
|
117
|
-
this.log.warn(
|
|
159
|
+
this.log.warn(`${this._name}: maximumDischargePower is deprecated use "maximumDischargingPower" instead`);
|
|
118
160
|
return await this._writeRegisters(47077, dataType.numToArray(event.value, dataType.uint32));
|
|
119
161
|
},
|
|
120
162
|
},
|
|
163
|
+
*/
|
|
121
164
|
{
|
|
122
165
|
state: {
|
|
123
166
|
id: 'battery.chargingCutoffCapacity',
|
|
@@ -200,6 +243,7 @@ class ServiceQueueMap {
|
|
|
200
243
|
if (event.value < 0) {
|
|
201
244
|
event.value = 2;
|
|
202
245
|
}
|
|
246
|
+
|
|
203
247
|
const ret = await this._writeRegisters(47086, dataType.numToArray(event.value, dataType.uint16));
|
|
204
248
|
if (ret) {
|
|
205
249
|
this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
|
|
@@ -528,41 +572,49 @@ class ServiceQueueMap {
|
|
|
528
572
|
}
|
|
529
573
|
|
|
530
574
|
async _init() {
|
|
575
|
+
const emmaAvailable = this.adapter.devices.find(d => d.driverClass == driverClasses.emma);
|
|
576
|
+
|
|
531
577
|
if (this.inverterInfo.instance) {
|
|
532
578
|
for (const item of this.serviceFields) {
|
|
533
|
-
//no battery - no controls
|
|
534
|
-
//if (item.type == deviceType.battery && this.inverterInfo.instance.numberBatteryUnits() === 0) continue;
|
|
535
|
-
if (item.type == deviceType.meter && !this.inverterInfo?.meter) {
|
|
536
|
-
continue;
|
|
537
|
-
}
|
|
538
|
-
if (item.type == deviceType.gridPowerControl && !this.inverterInfo?.meter) {
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
579
|
if (item?.state) {
|
|
580
|
+
if (item.type == deviceType.meter && !this.inverterInfo?.meter) {
|
|
581
|
+
//no meter - delete states if existing
|
|
582
|
+
item.state.write = false;
|
|
583
|
+
/*
|
|
584
|
+
await this._delState(`${this.inverterInfo.path}.control.${item.state.id}`);
|
|
585
|
+
continue;
|
|
586
|
+
*/
|
|
587
|
+
}
|
|
588
|
+
if (item.type == deviceType.gridPowerControl && !this.inverterInfo?.meter) {
|
|
589
|
+
//not meter - delete states if existing
|
|
590
|
+
item.state.write = false;
|
|
591
|
+
/*
|
|
592
|
+
await this._delState(`${this.inverterInfo.path}.control.${item.state.id}`);
|
|
593
|
+
continue;
|
|
594
|
+
*/
|
|
595
|
+
}
|
|
596
|
+
if (emmaAvailable && item?.deactiveIfEmmaAvailable) {
|
|
597
|
+
item.state.write = false;
|
|
598
|
+
}
|
|
599
|
+
|
|
542
600
|
this._serviceMap.set(item.state.id, item);
|
|
543
601
|
}
|
|
544
602
|
}
|
|
545
603
|
|
|
546
604
|
for (const entry of this._serviceMap.values()) {
|
|
547
|
-
//await this._initState(this.inverterInfo.path+'.control.',entry.state);
|
|
548
605
|
const path = `${this.inverterInfo.path}.control.`;
|
|
606
|
+
|
|
607
|
+
//create state
|
|
549
608
|
await this._initState(path, entry.state);
|
|
609
|
+
|
|
610
|
+
//get previous unacknowledged state
|
|
550
611
|
const state = await this.adapter.getState(path + entry.state.id);
|
|
551
612
|
if (state && state.ack === false) {
|
|
552
613
|
this.set(entry.state.id, state);
|
|
553
614
|
}
|
|
554
615
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (tSOC) {
|
|
558
|
-
await this.adapter.delObject(`${this.inverterInfo.path}.control.battery.targetSOC `, { recursive: false });
|
|
559
|
-
if (tSOC.val !== null) {
|
|
560
|
-
await this.adapter.setState(`${this.inverterInfo.path}.control.battery.targetSOC`, { val: tSOC.val, ack: tSOC.ack });
|
|
561
|
-
if (tSOC.ack === false) {
|
|
562
|
-
this.set('battery.targetSOC', tSOC);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
616
|
+
|
|
617
|
+
await this._upgrade(); //upgrade old states
|
|
566
618
|
|
|
567
619
|
this.adapter.subscribeStates(`${this.inverterInfo.path}.control*`);
|
|
568
620
|
this._initialized = true;
|
|
@@ -584,14 +636,40 @@ class ServiceQueueMap {
|
|
|
584
636
|
1, 0, 1440, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
585
637
|
];
|
|
586
638
|
if (await this._writeRegisters(47255, tCDP)) {
|
|
587
|
-
this.
|
|
639
|
+
this.inverterInfo.instance.addHoldingRegisters(47255, tCDP);
|
|
640
|
+
this.log.info(`${this._name}: The default TOU setting are transferred to the inverter.`);
|
|
588
641
|
}
|
|
589
642
|
}
|
|
590
643
|
}
|
|
591
644
|
}
|
|
592
645
|
|
|
593
646
|
if (this._initialized) {
|
|
594
|
-
this.log.info(
|
|
647
|
+
this.log.info(`${this._name}: Service queue initialized`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Upgrades deprecated states.
|
|
653
|
+
* Checks if the old states exist and if so, deletes them and replaces them with the new ones.
|
|
654
|
+
* @returns {Promise<void>}
|
|
655
|
+
*/
|
|
656
|
+
async _upgrade() {
|
|
657
|
+
if (await tools.existsState(this.adapter, `${this.inverterInfo.path}.control.battery.targetSOC `)) {
|
|
658
|
+
const tSOC = await this.adapter.getState(`${this.inverterInfo.path}.control.battery.targetSOC `);
|
|
659
|
+
await tools.deleteState(this.adapter, `${this.inverterInfo.path}.control.battery.targetSOC `);
|
|
660
|
+
if (tSOC && tSOC?.val !== null) {
|
|
661
|
+
await this.adapter.setState(`${this.inverterInfo.path}.control.battery.targetSOC`, { val: tSOC.val, ack: tSOC.ack });
|
|
662
|
+
if (tSOC?.ack === false) {
|
|
663
|
+
this.set('battery.targetSOC', tSOC);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (await tools.existsState(this.adapter, `${this.inverterInfo.path}.control.battery.maximumChargePower`)) {
|
|
669
|
+
await tools.deleteState(this.adapter, `${this.inverterInfo.path}.control.battery.maximumChargePower`);
|
|
670
|
+
}
|
|
671
|
+
if (await tools.existsState(this.adapter, `${this.inverterInfo.path}.control.battery.maximumDischargePower`)) {
|
|
672
|
+
await tools.deleteState(this.adapter, `${this.inverterInfo.path}.control.battery.maximumDischargePower`);
|
|
595
673
|
}
|
|
596
674
|
}
|
|
597
675
|
|
|
@@ -617,7 +695,7 @@ class ServiceQueueMap {
|
|
|
617
695
|
const service = this._serviceMap.get(id);
|
|
618
696
|
if (state && service) {
|
|
619
697
|
if (state.val !== null && !state.ack) {
|
|
620
|
-
this.log.info(
|
|
698
|
+
this.log.info(`${this._name}: Event - state: ${this.inverterInfo.path}.control.${id} changed: ${state.val} ack: ${state.ack}`);
|
|
621
699
|
const event = this._eventMap.get(id);
|
|
622
700
|
if (event) {
|
|
623
701
|
event.value = state.val;
|
|
@@ -628,11 +706,7 @@ class ServiceQueueMap {
|
|
|
628
706
|
}
|
|
629
707
|
}
|
|
630
708
|
}
|
|
631
|
-
|
|
632
|
-
values() {
|
|
633
|
-
return this._map.values();
|
|
634
|
-
}
|
|
635
|
-
*/
|
|
709
|
+
|
|
636
710
|
/**
|
|
637
711
|
* Processes pending events in the service queue and attempts to execute their associated functions.
|
|
638
712
|
*
|
|
@@ -665,10 +739,10 @@ class ServiceQueueMap {
|
|
|
665
739
|
//check if battery is present and running
|
|
666
740
|
if (service.type == deviceType.battery) {
|
|
667
741
|
if (this.inverterInfo.instance.numberBatteryUnits() === 0) {
|
|
668
|
-
this.log.warn(
|
|
742
|
+
this.log.warn(`${this._name}: Event is discarded because no battery has been detected. `);
|
|
669
743
|
if (!this.adapter.isReady) {
|
|
670
744
|
this.log.warn(
|
|
671
|
-
|
|
745
|
+
`${this._name}: The Adapter is not ready! Please check the value in the state sun2000.x.info.JSONhealth and the Log output.`,
|
|
672
746
|
);
|
|
673
747
|
}
|
|
674
748
|
this._eventMap.delete(event.id); //forget the event
|
|
@@ -677,7 +751,7 @@ class ServiceQueueMap {
|
|
|
677
751
|
const BatStatus = this.inverterInfo.instance.stateCache.get(`${this.inverterInfo.path}.battery.runningStatus`)?.value ?? -1;
|
|
678
752
|
if (BatStatus !== 2 && BatStatus !== 1 && BatStatus !== -1) {
|
|
679
753
|
this.log.warn(
|
|
680
|
-
|
|
754
|
+
`${this._name}: Event is discarded because battery is not running. State: ${this.inverterInfo.path}.battery.runningStatus = ${BatStatus}. `,
|
|
681
755
|
);
|
|
682
756
|
this._eventMap.delete(event.id); //forget the event
|
|
683
757
|
continue;
|
|
@@ -686,7 +760,7 @@ class ServiceQueueMap {
|
|
|
686
760
|
if (service.state.type === 'number') {
|
|
687
761
|
if (!this.isNumber(event)) {
|
|
688
762
|
this.log.warn(
|
|
689
|
-
|
|
763
|
+
`${this._name}: Event is discarded because the value ${event.value} is not a number. State: ${this.inverterInfo.path}.control.${event.id}`,
|
|
690
764
|
);
|
|
691
765
|
this._eventMap.delete(event.id); //forget the event
|
|
692
766
|
continue;
|
|
@@ -699,15 +773,15 @@ class ServiceQueueMap {
|
|
|
699
773
|
event.ack = true;
|
|
700
774
|
await this.adapter.setState(`${this.inverterInfo.path}.control.${event.id}`, { val: event.value, ack: true });
|
|
701
775
|
this._eventMap.delete(event.id);
|
|
702
|
-
this.log.info(
|
|
776
|
+
this.log.info(`${this._name}: write state ${this.inverterInfo.path}.control.${event.id} : ${event.value} ack: true`);
|
|
703
777
|
} catch {
|
|
704
|
-
this.log.warn(
|
|
778
|
+
this.log.warn(`${this._name}: Can not write state ${this.inverterInfo.path}.control.${event.id}`);
|
|
705
779
|
}
|
|
706
780
|
} else {
|
|
707
781
|
service.errorCount++;
|
|
708
782
|
if (service.errorCount > 1) {
|
|
709
783
|
this._eventMap.delete(event.id); //forget it
|
|
710
|
-
this.log.info(
|
|
784
|
+
this.log.info(`${this._name}: Event is discarded because it could not be processed. ${this.inverterInfo.path}.control.${event.id}`);
|
|
711
785
|
}
|
|
712
786
|
}
|
|
713
787
|
}
|
|
@@ -757,7 +831,7 @@ class ServiceQueueMap {
|
|
|
757
831
|
unit: state.unit,
|
|
758
832
|
desc: state.desc,
|
|
759
833
|
read: true,
|
|
760
|
-
write: true,
|
|
834
|
+
write: state.write ?? true,
|
|
761
835
|
},
|
|
762
836
|
native: {},
|
|
763
837
|
});
|
|
@@ -21,7 +21,7 @@ class DriverBase {
|
|
|
21
21
|
this._deviceStatus = -1; //device shutdown or standby
|
|
22
22
|
this._regMap = new RegisterMap();
|
|
23
23
|
|
|
24
|
-
this.control = null; //
|
|
24
|
+
this.control = null; //Sdongle Service Queue, Emma Service Queue
|
|
25
25
|
this.log = new Logging(this.adapter); //my own Logger
|
|
26
26
|
|
|
27
27
|
this.registerFields = [];
|
|
@@ -65,7 +65,26 @@ class DriverBase {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
getHoldingRegisters(startAddr, length) {
|
|
68
|
-
|
|
68
|
+
const data = this._regMap.get(startAddr, length, this.adapter.isReady);
|
|
69
|
+
if (this.isTestMode() && startAddr === 30302) {
|
|
70
|
+
this.log.info(`### TestMode ### get raw data from device/id/address/data ${this._name}/${this._modbusId}/${startAddr}/${data}`);
|
|
71
|
+
}
|
|
72
|
+
return data;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Checks if the device is in test mode
|
|
77
|
+
* @returns {boolean} True if the device is in test mode, false otherwise
|
|
78
|
+
*/
|
|
79
|
+
isTestMode() {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
logHoldingRegisters(startAddr, length) {
|
|
84
|
+
if (startAddr === 30302) {
|
|
85
|
+
const data = this.getHoldingRegisters(startAddr, length);
|
|
86
|
+
this.log.info(`### TestMode ### read data from device/id/address/data ${this._name}/${this._modbusId}/${startAddr}/${data}`);
|
|
87
|
+
}
|
|
69
88
|
}
|
|
70
89
|
|
|
71
90
|
_fromArray(data, address, field) {
|
|
@@ -126,8 +145,7 @@ class DriverBase {
|
|
|
126
145
|
}
|
|
127
146
|
|
|
128
147
|
async _processRegister(reg, data) {
|
|
129
|
-
|
|
130
|
-
this.addHoldingRegisters(reg.address, data);
|
|
148
|
+
this.addHoldingRegisters(reg.address, data); //store row Data of modbus registers
|
|
131
149
|
|
|
132
150
|
const path = this._getStatePath(reg.type);
|
|
133
151
|
//pre hook
|
|
@@ -257,6 +275,28 @@ class DriverBase {
|
|
|
257
275
|
return chargers;
|
|
258
276
|
}
|
|
259
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Updates the states of the device.
|
|
280
|
+
*
|
|
281
|
+
* This function sets the Modbus client, then calls the battery control and active power control
|
|
282
|
+
* if the refresh rate is not high. It then iterates over the register fields and calls
|
|
283
|
+
* the readHoldingRegisters function on the Modbus client to read the data from the registers.
|
|
284
|
+
* For each register, it checks if the device is allowed to communicate with the Modbus network,
|
|
285
|
+
* if the refresh rate is equal to the register's refresh rate, and if the register's checkIfActive
|
|
286
|
+
* function returns true. If all conditions are met, it calls the _processRegister function to process
|
|
287
|
+
* the data from the register.
|
|
288
|
+
* If the refresh rate is low or empty, it checks if the last read time is older than the specified
|
|
289
|
+
* interval. If so, it calls the readHoldingRegisters function to read the data from the register.
|
|
290
|
+
* If any errors occur during the read operation, it catches the error and logs a warning message.
|
|
291
|
+
* If the error is a connection error, it stops the update loop.
|
|
292
|
+
* Finally, it calls the _runPostUpdateHooks function to run any post update hooks and stores the states.
|
|
293
|
+
*
|
|
294
|
+
* @param {ModbusClient} modbusClient - The Modbus client to use for communication.
|
|
295
|
+
* @param {dataRefreshRate} refreshRate - The refresh rate to use for updating the states.
|
|
296
|
+
* @param {number} [duration] - The duration in milliseconds for which the states should be updated.
|
|
297
|
+
* @returns {Promise<number>} - A promise that resolves to the number of registers read.
|
|
298
|
+
*/
|
|
299
|
+
|
|
260
300
|
async updateStates(modbusClient, refreshRate, duration) {
|
|
261
301
|
this._modbusClient = modbusClient;
|
|
262
302
|
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
const { deviceType, driverClasses, dataRefreshRate, dataType } = require(`${__dirname}/../types.js`);
|
|
2
2
|
const DriverBase = require(`${__dirname}/driver_base.js`);
|
|
3
|
+
const ServiceQueueMap = require(`${__dirname}/../controls/emma_service_queue.js`);
|
|
3
4
|
|
|
4
5
|
class Emma extends DriverBase {
|
|
5
|
-
constructor(stateInstance,
|
|
6
|
-
super(stateInstance,
|
|
6
|
+
constructor(stateInstance, device, options) {
|
|
7
|
+
super(stateInstance, device, {
|
|
7
8
|
name: 'emma',
|
|
8
9
|
driverClass: driverClasses.emma,
|
|
9
10
|
...options,
|
|
10
11
|
});
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
this.control = new ServiceQueueMap(this.adapter, this.deviceInfo); //emma service queue
|
|
13
14
|
|
|
14
15
|
//https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/stateroles.md
|
|
15
16
|
const newFields = [
|
|
@@ -271,6 +272,14 @@ class Emma extends DriverBase {
|
|
|
271
272
|
register: { reg: 30348, type: dataType.uint64, gain: 100 },
|
|
272
273
|
},
|
|
273
274
|
],
|
|
275
|
+
postHook: path => {
|
|
276
|
+
if (this.isTestMode()) {
|
|
277
|
+
const value = 18000099;
|
|
278
|
+
this.log.info(`### TestMode ### set Emma inverterTotalAbsorbedEnergy 30302 - set to ${value} kWh`);
|
|
279
|
+
this.stateCache.set(`${path}emma.inverterTotalAbsorbedEnergy`, value, { type: 'number' });
|
|
280
|
+
this.addHoldingRegisters(30302, dataType.numToArray(value * 100, dataType.uint64)); //write also to the modbus read cache
|
|
281
|
+
}
|
|
282
|
+
},
|
|
274
283
|
},
|
|
275
284
|
{
|
|
276
285
|
address: 30354,
|
|
@@ -761,11 +770,21 @@ class Emma extends DriverBase {
|
|
|
761
770
|
|
|
762
771
|
this.registerFields.push.apply(this.registerFields, newFields);
|
|
763
772
|
}
|
|
773
|
+
|
|
774
|
+
// Override
|
|
775
|
+
/**
|
|
776
|
+
* Checks if the device is in test mode
|
|
777
|
+
* @returns {boolean} True if the device is in test mode, false otherwise
|
|
778
|
+
* A device is considered in test mode if its modbus ID is not equal to 0.
|
|
779
|
+
*/
|
|
780
|
+
isTestMode() {
|
|
781
|
+
return this.modbusId !== 0;
|
|
782
|
+
}
|
|
764
783
|
}
|
|
765
784
|
|
|
766
785
|
class EmmaCharger extends DriverBase {
|
|
767
|
-
constructor(stateInstance,
|
|
768
|
-
super(stateInstance,
|
|
786
|
+
constructor(stateInstance, device, options) {
|
|
787
|
+
super(stateInstance, device, {
|
|
769
788
|
name: 'emmaCharger',
|
|
770
789
|
driverClass: driverClasses.emmaCharger,
|
|
771
790
|
...options,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { deviceType, driverClasses, storeType, getDeviceStatusInfo, batteryStatus, dataRefreshRate, dataType } = require(`${__dirname}/../types.js`);
|
|
4
4
|
const { RiemannSum, isSunshine } = require(`${__dirname}/../tools.js`);
|
|
5
5
|
const DriverBase = require(`${__dirname}/driver_base.js`);
|
|
6
|
-
const ServiceQueueMap = require(`${__dirname}/../controls/
|
|
6
|
+
const ServiceQueueMap = require(`${__dirname}/../controls/inverter_service_queue.js`);
|
|
7
7
|
|
|
8
8
|
class InverterInfo extends DriverBase {
|
|
9
9
|
constructor(stateInstance, device) {
|
|
@@ -100,6 +100,8 @@ class ModbusConnect extends DeviceInterface {
|
|
|
100
100
|
this.client.close(() => {
|
|
101
101
|
resolve({});
|
|
102
102
|
});
|
|
103
|
+
//workaround for issue https://github.com/yaacov/node-modbus-serial/issues/582 with node-modbus-serial
|
|
104
|
+
resolve({});
|
|
103
105
|
} else {
|
|
104
106
|
resolve({});
|
|
105
107
|
}
|
|
@@ -134,10 +136,12 @@ class ModbusConnect extends DeviceInterface {
|
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
async _checkError(err) {
|
|
137
|
-
this.
|
|
139
|
+
this.log.debug(`Modbus error: ${JSON.stringify(err)}`);
|
|
138
140
|
if (err.modbusCode === undefined) {
|
|
139
141
|
this._adjustDelay(err, false);
|
|
140
142
|
await this.close();
|
|
143
|
+
//await this._destroy();
|
|
144
|
+
//await this.closing();
|
|
141
145
|
await this._create();
|
|
142
146
|
if (err.errno === 'ECONNREFUSED') {
|
|
143
147
|
this.log.warn('Has another device interrupted the modbus connection?');
|
|
@@ -146,6 +150,8 @@ class ModbusConnect extends DeviceInterface {
|
|
|
146
150
|
} else {
|
|
147
151
|
if (err.modbusCode === 0) {
|
|
148
152
|
await this.close();
|
|
153
|
+
//await this._destroy();
|
|
154
|
+
//await this.closing();
|
|
149
155
|
this._adjustDelay(err, false);
|
|
150
156
|
await this._create();
|
|
151
157
|
}
|
|
@@ -165,9 +171,10 @@ class ModbusConnect extends DeviceInterface {
|
|
|
165
171
|
} catch (err) {
|
|
166
172
|
this.log.warn(`Couldnt connect Modbus TCP to ${this.ipAddress}:${this.port} ${err.message}`);
|
|
167
173
|
await this._checkError(err);
|
|
174
|
+
let delay = 4000;
|
|
168
175
|
if (repeatCounter > 0) throw err;
|
|
169
|
-
let delay = 2000;
|
|
170
176
|
if (err.code == 'EHOSTUNREACH') delay *= 10;
|
|
177
|
+
this.log.debug(`Retry to connect Modbus TCP to ${this.ipAddress}:${this.port} in ${delay} ms`);
|
|
171
178
|
await this.wait(delay);
|
|
172
179
|
await this.connect(repeatCounter + 1);
|
|
173
180
|
}
|
|
@@ -23,6 +23,7 @@ class ModbusServer {
|
|
|
23
23
|
await this._handleGetReg('getMultipleInputRegisters', startAddr, length, unitId, callback);
|
|
24
24
|
},
|
|
25
25
|
getMultipleHoldingRegisters: async (startAddr, length, unitId, callback) => {
|
|
26
|
+
//this.adapter.log.info(`getMultipleHolgingRegisters ${unitId} ${startAddr} len ${length}`);
|
|
26
27
|
await this._handleGetReg('getMultipleHoldingRegisters', startAddr, length, unitId, callback);
|
|
27
28
|
},
|
|
28
29
|
getCoil: (addr, unitId, callback) => {
|
|
@@ -141,12 +142,19 @@ class ModbusServer {
|
|
|
141
142
|
* Special case: unitId 250 is mapped to the device with modbusId 0.
|
|
142
143
|
*/
|
|
143
144
|
getDeviceInstance(unitId) {
|
|
145
|
+
const realUnitId = unitId === 250 ? 0 : unitId;
|
|
144
146
|
for (const device of this.adapter.devices) {
|
|
145
147
|
if (device.instance) {
|
|
146
|
-
|
|
148
|
+
//for TestMode
|
|
149
|
+
if (realUnitId === 0 && device.instance.info?.name === 'emma') {
|
|
150
|
+
//if (this.adapter.settings.ms.log) {
|
|
151
|
+
this.log.info(
|
|
152
|
+
`### Modbus-proxy ###: check device ${device.instance._name} with id ${device.instance.modbusId} for unitId ${realUnitId} (TestMode active)`,
|
|
153
|
+
);
|
|
154
|
+
//}
|
|
147
155
|
return device.instance;
|
|
148
156
|
}
|
|
149
|
-
if (device.instance?.modbusId ===
|
|
157
|
+
if (device.instance?.modbusId === realUnitId) {
|
|
150
158
|
return device.instance;
|
|
151
159
|
}
|
|
152
160
|
}
|
|
@@ -164,15 +172,11 @@ class ModbusServer {
|
|
|
164
172
|
*/
|
|
165
173
|
|
|
166
174
|
async _handleGetReg(fnName, startAddr, length, unitId, callback) {
|
|
167
|
-
//this.adapter.log.
|
|
175
|
+
//this.adapter.log.info(`getMultipleHolgingRegisters ${unitId} ${startAddr} len ${length} ${this._isConnected}`);
|
|
168
176
|
try {
|
|
169
177
|
const device = this.getDeviceInstance(unitId);
|
|
170
178
|
if (device) {
|
|
171
|
-
//this.adapter.log.debug('Device Info '+JSON.stringify(device?.info));
|
|
172
179
|
const values = device.getHoldingRegisters(startAddr, length);
|
|
173
|
-
if (this.adapter.settings.ms.log) {
|
|
174
|
-
this.log.info(`Modbus-proxy: read data from id/address/data ${unitId}/${startAddr}/${values}`);
|
|
175
|
-
}
|
|
176
180
|
|
|
177
181
|
if (!this.adapter.isConnected) {
|
|
178
182
|
//this._addInfoStat('#WaitForConnected',startAddr, length, unitId);
|
package/lib/register.js
CHANGED
|
@@ -458,7 +458,7 @@ class Registers {
|
|
|
458
458
|
ret.message = "Can't read data from device! Please check the configuration.";
|
|
459
459
|
} else {
|
|
460
460
|
ret.errno = 102;
|
|
461
|
-
ret.message = 'Not all data can be read! Please inspect the
|
|
461
|
+
ret.message = 'Not all data can be read! Please inspect the adapter log.';
|
|
462
462
|
}
|
|
463
463
|
return ret;
|
|
464
464
|
}
|
package/lib/tools.js
CHANGED
|
@@ -314,6 +314,25 @@ function contains(r, val) {
|
|
|
314
314
|
return -1;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
async function existsState(adapter, id, callback) {
|
|
318
|
+
if (typeof callback === 'function') {
|
|
319
|
+
adapter.getObject(id, (err, obj) => callback(err, obj && obj.type === 'state'));
|
|
320
|
+
} else {
|
|
321
|
+
const obj = await adapter.getObjectAsync(id);
|
|
322
|
+
if (obj) {
|
|
323
|
+
return obj.type === 'state';
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function deleteState(adapter, id, callback) {
|
|
329
|
+
if (typeof callback === 'function') {
|
|
330
|
+
adapter.delObject(id, { recursive: false }, callback);
|
|
331
|
+
} else {
|
|
332
|
+
return await adapter.delObjectAsync(id, { recursive: false });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
317
336
|
module.exports = {
|
|
318
337
|
Logging,
|
|
319
338
|
StateMap,
|
|
@@ -324,4 +343,6 @@ module.exports = {
|
|
|
324
343
|
getAstroDate,
|
|
325
344
|
isSunshine,
|
|
326
345
|
contains,
|
|
346
|
+
existsState,
|
|
347
|
+
deleteState,
|
|
327
348
|
};
|
package/lib/types.js
CHANGED
|
@@ -17,7 +17,7 @@ function getDeviceStatusInfo(value) {
|
|
|
17
17
|
case 0x0002:
|
|
18
18
|
return 'Standby: detecting irradiation';
|
|
19
19
|
case 0x0003:
|
|
20
|
-
return 'Standby:
|
|
20
|
+
return 'Standby: grid detecting';
|
|
21
21
|
case 0x0100:
|
|
22
22
|
return 'Starting';
|
|
23
23
|
case 0x0200:
|
|
@@ -46,6 +46,8 @@ function getDeviceStatusInfo(value) {
|
|
|
46
46
|
return 'Shutdown: rapid cutoff';
|
|
47
47
|
case 0x0308:
|
|
48
48
|
return 'Shutdown: input underpower';
|
|
49
|
+
case 0x030c:
|
|
50
|
+
return 'Shutdown: End of the ESS discharge process';
|
|
49
51
|
case 0x0401:
|
|
50
52
|
return 'Grid scheduling: cosPhi-P curve';
|
|
51
53
|
case 0x0402:
|
|
@@ -143,6 +145,14 @@ const dataType = {
|
|
|
143
145
|
return undefined;
|
|
144
146
|
}
|
|
145
147
|
},
|
|
148
|
+
/**
|
|
149
|
+
* Converts a number to an array of type given.
|
|
150
|
+
* It uses the size of the type to determine how to convert the number.
|
|
151
|
+
* @param {number} num - The number to convert.
|
|
152
|
+
* @param {symbol} type - The type to convert the number to.
|
|
153
|
+
* @returns {Uint16Array} An array containing the converted number.
|
|
154
|
+
* @throws {Error} If the type is not supported.
|
|
155
|
+
*/
|
|
146
156
|
numToArray(num, type) {
|
|
147
157
|
switch (this.size(type)) {
|
|
148
158
|
case 1: {
|
|
@@ -153,6 +163,16 @@ const dataType = {
|
|
|
153
163
|
const int16Array = new Uint16Array([(num & 0xffff0000) >> 16, num & 0xffff]);
|
|
154
164
|
return int16Array;
|
|
155
165
|
}
|
|
166
|
+
case 4: {
|
|
167
|
+
if (typeof num !== 'bigint') {
|
|
168
|
+
num = BigInt(num);
|
|
169
|
+
}
|
|
170
|
+
const parts = new Uint16Array(4);
|
|
171
|
+
for (let i = 0; i < 4; i++) {
|
|
172
|
+
parts[3 - i] = Number((num >> BigInt(i * 16)) & 0xffffn);
|
|
173
|
+
}
|
|
174
|
+
return parts;
|
|
175
|
+
}
|
|
156
176
|
}
|
|
157
177
|
throw false;
|
|
158
178
|
},
|
package/main.js
CHANGED
|
@@ -63,10 +63,8 @@ class Sun2000 extends utils.Adapter {
|
|
|
63
63
|
},
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
//v0.6.
|
|
67
66
|
this.logger = new Logging(this); //only for adapter
|
|
68
67
|
|
|
69
|
-
//1.1.0
|
|
70
68
|
this.control = new ConfigMap(this);
|
|
71
69
|
|
|
72
70
|
this.on('ready', this.onReady.bind(this));
|
|
@@ -470,7 +468,7 @@ class Sun2000 extends utils.Adapter {
|
|
|
470
468
|
this.devices.push({
|
|
471
469
|
index: 0,
|
|
472
470
|
duration: 0,
|
|
473
|
-
//modbusId: 1,
|
|
471
|
+
//modbusId: 1, --> testMode
|
|
474
472
|
modbusId: 0,
|
|
475
473
|
meter: true,
|
|
476
474
|
driverClass: driverClasses.emma,
|
|
@@ -551,7 +549,7 @@ class Sun2000 extends utils.Adapter {
|
|
|
551
549
|
}
|
|
552
550
|
const ret = this.state.CheckReadError(this.settings.lowInterval * 2);
|
|
553
551
|
this.logger.debug(JSON.stringify(this.modbusClient.info));
|
|
554
|
-
|
|
552
|
+
|
|
555
553
|
if (!this.isReady) {
|
|
556
554
|
this.isReady = this.isConnected && !ret.errno;
|
|
557
555
|
}
|
|
@@ -578,9 +576,9 @@ class Sun2000 extends utils.Adapter {
|
|
|
578
576
|
this.lastStateUpdatedLow = 0;
|
|
579
577
|
this.lastStateUpdatedHigh = 0;
|
|
580
578
|
|
|
581
|
-
if (sinceLastUpdate > this.settings.
|
|
579
|
+
if (sinceLastUpdate > this.settings.lowInterval * 10) {
|
|
582
580
|
this.setState('info.JSONhealth', { val: '{errno:2, message: "Internal loop error"}', ack: true });
|
|
583
|
-
this.logger.
|
|
581
|
+
this.logger.error('watchdog: Internal loop error! Restart adapter...');
|
|
584
582
|
this.restart();
|
|
585
583
|
}
|
|
586
584
|
}, this.settings.lowInterval);
|
|
@@ -638,7 +636,7 @@ class Sun2000 extends utils.Adapter {
|
|
|
638
636
|
}
|
|
639
637
|
//this.log.info(`### state ${id} changed: ${state.val} (ack = ${state.ack})`);
|
|
640
638
|
}
|
|
641
|
-
// sun2000.0.
|
|
639
|
+
// sun2000.0.control
|
|
642
640
|
if (idArray[2] == 'control') {
|
|
643
641
|
let serviceId = idArray[3];
|
|
644
642
|
for (let i = 4; i < idArray.length; i++) {
|
|
@@ -647,6 +645,18 @@ class Sun2000 extends utils.Adapter {
|
|
|
647
645
|
//this.log.info(`### id: ${serviceId} state ${id} changed: ${state.val} (ack = ${state.ack})`);
|
|
648
646
|
this.control.set(serviceId, state);
|
|
649
647
|
}
|
|
648
|
+
// sun2000.0.emma.control
|
|
649
|
+
if (idArray[2] == 'emma') {
|
|
650
|
+
const emma = this.devices.find(d => d.driverClass == driverClasses.emma);
|
|
651
|
+
if (emma && emma.instance && emma.instance.control) {
|
|
652
|
+
let serviceId = idArray[4];
|
|
653
|
+
for (let i = 5; i < idArray.length; i++) {
|
|
654
|
+
serviceId += `.${idArray[i]}`;
|
|
655
|
+
}
|
|
656
|
+
//this.log.info(`### state ${id} changed: ${state.val} (ack = ${state.ack})`);
|
|
657
|
+
emma.instance.control.set(serviceId, state);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
650
660
|
} else {
|
|
651
661
|
// The state was deleted
|
|
652
662
|
this.logger.info(`state ${id} deleted`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.sun2000",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "sun2000",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "bolliy",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@iobroker/adapter-core": "^3.3.2",
|
|
31
|
-
"modbus-serial": "^8.0.
|
|
31
|
+
"modbus-serial": "^8.0.23",
|
|
32
32
|
"suncalc2": "^1.8.1",
|
|
33
33
|
"tcp-port-used": "^1.0.2"
|
|
34
34
|
},
|