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 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.4.10 or higher
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.2.0",
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, inverter) {
6
+ constructor(adapterInstance, device) {
5
7
  this.adapter = adapterInstance;
6
8
  this.log = this.adapter.logger;
7
- this.inverterInfo = inverter;
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(`Control: maximumChargePower is deprecated use "maximumChargingPower" instead`);
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(`Control: maximumDischargePower is deprecated use "maximumDischargingPower" instead`);
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
- //upgrade
556
- const tSOC = await this.adapter.getState(`${this.inverterInfo.path}.control.battery.targetSOC `);
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.log.info('Control: The default TOU setting are transferred');
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('Control: Service queue initialized');
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(`Control: Event - state: ${this.inverterInfo.path}.control.${id} changed: ${state.val} ack: ${state.ack}`);
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(`Control: Event is discarded because no battery has been detected. `);
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
- 'Control: The Adapter is not ready! Please check the value in the state sun2000.x.info.JSONhealth and the Log output.',
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
- `Control: Event is discarded because battery is not running. State: ${this.inverterInfo.path}.battery.runningStatus = ${BatStatus}. `,
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
- `Control: Event is discarded because the value ${event.value} is not a number. State: ${this.inverterInfo.path}.control.${event.id}`,
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(`Control: write state ${this.inverterInfo.path}.control.${event.id} : ${event.value} ack: true`);
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(`Control: Can not write state ${this.inverterInfo.path}.control.${event.id}`);
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(`Control: Event is discarded because it could not be processed. ${this.inverterInfo.path}.control.${event.id}`);
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; //Battery Charge control
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
- return this._regMap.get(startAddr, length, this.adapter.isReady);
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
- //0.4.x
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, inverter, options) {
6
- super(stateInstance, inverter, {
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
- //this.stateCache.set(`meter.derived.directionOfPowerFlow`, 1, { type: 'number' }); //Emma build-in energy sensor
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, inverter, options) {
768
- super(stateInstance, inverter, {
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/service_queue.js`);
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.adapter.logger.debug(`Modbus error: ${JSON.stringify(err)}`);
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
- if (unitId === 250 && device.instance?.modbusId === 0) {
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 === unitId) {
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.debug('getMultipleHolgingRegisters '+unitId+' '+startAddr+' len '+length+' '+this._isConnected);
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 sun2000 log.';
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: drid detecting';
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
- //v0.8.x
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.highInterval * 10) {
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.warn('watchdog: restart Adapter...');
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.inverter.0.config
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.2.0",
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.22",
31
+ "modbus-serial": "^8.0.23",
32
32
  "suncalc2": "^1.8.1",
33
33
  "tcp-port-used": "^1.0.2"
34
34
  },