iobroker.sigenergy 1.3.3 → 1.3.5

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
@@ -156,6 +156,12 @@ MIT License — Copyright (c) 2025 ioBroker Community
156
156
  ---
157
157
 
158
158
  ## Changelog
159
+ ### 1.3.5 (2026-03-13)
160
+ * (ssbingo) Fix CI: use explicit mocha binary path to avoid PATH resolution issues with npm ci
161
+
162
+ ### 1.3.4 (2026-03-13)
163
+ * (ssbingo) Fix CI: add mocha to devDependencies so test:package script can run
164
+
159
165
  ### 1.3.3 (2026-03-13)
160
166
  * (ssbingo) Fix duplicate @param JSDoc warning caused by missing closing tag in modbus.js
161
167
 
package/io-package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "sigenergy",
4
- "version": "1.3.3",
4
+ "version": "1.3.5",
5
5
  "news": {
6
- "1.3.3": {
7
- "en": "Fix duplicate @param JSDoc warning caused by missing closing tag in modbus.js",
8
- "de": "Doppelten @param JSDoc-Hinweis durch fehlendes schließendes Tag in modbus.js behoben",
9
- "ru": "Исправлено дублирующее предупреждение @param JSDoc из-за отсутствующего закрывающего тега в modbus.js",
10
- "pt": "Corrigido aviso JSDoc @param duplicado causado por tag de fechamento ausente em modbus.js",
11
- "nl": "Dubbele @param JSDoc-waarschuwing verholpen door ontbrekend sluitend label in modbus.js",
12
- "fr": "Correction de l'avertissement JSDoc @param en double causé par une balise fermante manquante dans modbus.js",
13
- "it": "Corretto avviso JSDoc @param duplicato causato da tag di chiusura mancante in modbus.js",
14
- "es": "Corregida advertencia JSDoc @param duplicada causada por etiqueta de cierre faltante en modbus.js",
15
- "pl": "Poprawiono zduplikowane ostrzeżenie JSDoc @param spowodowane brakującym tagiem zamykającym w modbus.js",
16
- "uk": "Виправлено дубльоване попередження JSDoc @param через відсутній закриваючий тег у modbus.js",
17
- "zh-cn": "修复modbus.js中因缺少闭合标签导致的重复@param JSDoc警告"
6
+ "1.3.5": {
7
+ "en": "Fix CI: use explicit mocha binary path to avoid PATH resolution issues with npm ci",
8
+ "de": "CI-Fix: expliziten mocha-Binärpfad verwenden, um PATH-Auflösungsprobleme mit npm ci zu vermeiden",
9
+ "ru": "Исправление CI: использование явного пути к бинарному файлу mocha во избежание проблем с разрешением PATH при npm ci",
10
+ "pt": "Correção CI: usar caminho explícito do binário mocha para evitar problemas de resolução de PATH com npm ci",
11
+ "nl": "CI-fix: expliciet mocha-binair pad gebruiken om PATH-resolutieproblemen met npm ci te vermijden",
12
+ "fr": "Correction CI : utilisation du chemin binaire explicite de mocha pour éviter les problèmes de résolution PATH avec npm ci",
13
+ "it": "Fix CI: utilizzare il percorso binario esplicito di mocha per evitare problemi di risoluzione PATH con npm ci",
14
+ "es": "Fix CI: usar ruta binaria explícita de mocha para evitar problemas de resolución PATH con npm ci",
15
+ "pl": "Poprawka CI: użycie jawnej ścieżki binarnej mocha, aby uniknąć problemów z rozwiązywaniem PATH przy npm ci",
16
+ "uk": "Виправлення CI: використання явного шляху до бінарного файлу mocha для уникнення проблем із PATH при npm ci",
17
+ "zh-cn": "CI修复:使用mocha的显式二进制路径以避免npm ci的PATH解析问题"
18
18
  }
19
19
  },
20
20
  "titleLang": {
package/lib/modbus.js CHANGED
@@ -1,267 +1,246 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
- const ModbusRTU = require('modbus-serial');
3
+ const ModbusRTU = require("modbus-serial");
4
4
 
5
5
  /**
6
6
  * ModbusConnection - Handles Modbus TCP and Serial connections
7
7
  * for Sigenergy solar systems
8
8
  */
9
9
  class ModbusConnection {
10
- /**
11
- * @param {object} config - Connection configuration
12
- * @param {Function} logFn - Logging function
13
- */
14
- constructor(config, logFn) {
15
- this.config = config;
16
- this.log = logFn || console;
17
- this.client = new ModbusRTU();
18
- this.connected = false;
19
- this.connecting = false;
20
- this._reconnectTimer = null;
21
- this._requestQueue = [];
22
- this._processing = false;
23
- }
24
-
25
- /**
26
- * Connect to Modbus device
27
- *
28
- * @returns {Promise<boolean>} Resolves to true if connection succeeded, false otherwise
29
- */
30
- async connect() {
31
- if (this.connecting) {
32
- return false;
33
- }
34
- this.connecting = true;
35
-
36
- try {
37
- if (this.client.isOpen) {
38
- await this.client.close();
39
- }
40
-
41
- this.client = new ModbusRTU();
42
- this.client.setTimeout(this.config.timeout || 5000);
43
-
44
- if (this.config.connectionType === 'tcp') {
45
- this.log.debug(`Connecting Modbus TCP to ${this.config.tcpHost}:${this.config.tcpPort}`);
46
- const connectTimeout = this.config.timeout || 5000;
47
- await Promise.race([
48
- this.client.connectTCP(this.config.tcpHost, {
49
- port: this.config.tcpPort || 502,
50
- }),
51
- new Promise((_, reject) =>
52
- setTimeout(
53
- () => reject(new Error(`TCP connection timeout after ${connectTimeout}ms`)),
54
- connectTimeout,
55
- ),
56
- ),
57
- ]);
58
- } else {
59
- // Serial RTU
60
- this.log.debug(`Connecting Modbus RTU on ${this.config.serialPort}`);
61
- await this.client.connectRTUBuffered(this.config.serialPort, {
62
- baudRate: this.config.serialBaudRate || 9600,
63
- dataBits: this.config.serialDataBits || 8,
64
- parity: this.config.serialParity || 'none',
65
- stopBits: this.config.serialStopBits || 1,
66
- });
67
- }
68
-
69
- this.connected = true;
70
- this.connecting = false;
71
- this.log.info('Modbus connection established');
72
- return true;
73
- } catch (err) {
74
- this.connected = false;
75
- this.connecting = false;
76
- this.log.error(`Modbus connection failed: ${err.message}`);
77
- return false;
78
- }
79
- }
80
-
81
- /**
82
- * Disconnect from Modbus device
83
- */
84
- async disconnect() {
85
- if (this._reconnectTimer) {
86
- clearTimeout(this._reconnectTimer);
87
- this._reconnectTimer = null;
88
- }
89
- if (this.client && this.client.isOpen) {
90
- try {
91
- await this.client.close();
92
- } catch {
93
- // ignore
94
- }
95
- }
96
- this.connected = false;
97
- this.log.info('Modbus disconnected');
98
- }
99
-
100
- /**
101
- * Read input registers (function code 0x04)
102
- *
103
- * @param {number} slaveId - Slave address
104
- * @param {number} address - Register start address
105
- * @param {number} qty - Number of registers to read
106
- * @returns {Promise<number[]>} Resolves with an array of register values
107
- */
108
- async readInputRegisters(slaveId, address, qty) {
109
- if (!this.connected) {
110
- throw new Error('Not connected');
111
- }
112
- this.client.setID(slaveId);
113
- const result = await this.client.readInputRegisters(address, qty);
114
- return result.data;
115
- }
116
-
117
- /**
118
- * Read holding registers (function code 0x03)
119
- *
120
- * @param {number} slaveId - Slave address
121
- * @param {number} address - Register start address
122
- * @param {number} qty - Number of registers to read
123
- * @returns {Promise<number[]>} Resolves with an array of register values
124
- */
125
- async readHoldingRegisters(slaveId, address, qty) {
126
- if (!this.connected) {
127
- throw new Error('Not connected');
128
- }
129
- this.client.setID(slaveId);
130
- const result = await this.client.readHoldingRegisters(address, qty);
131
- return result.data;
132
- }
133
-
134
- /**
135
- * Write single register (function code 0x06)
136
- *
137
- * @param {number} slaveId - Slave address
138
- * @param {number} address - Register address
139
- * @param {number} value - Value to write
140
- * @returns {Promise<void>} Resolves when the register has been written
141
- */
142
- async writeSingleRegister(slaveId, address, value) {
143
- if (!this.connected) {
144
- throw new Error('Not connected');
145
- }
146
- this.client.setID(slaveId);
147
- await this.client.writeRegister(address, value);
148
- }
149
-
150
- /**
151
- * Write multiple registers (function code 0x10)
152
- *
153
- * @param {number} slaveId - Slave address
154
- * @param {number} address - Start address
155
- * @param {number[]} values - Values array
156
- * @returns {Promise<void>} Resolves when all registers have been written
157
- */
158
- async writeMultipleRegisters(slaveId, address, values) {
159
- if (!this.connected) {
160
- throw new Error('Not connected');
161
- }
162
- this.client.setID(slaveId);
163
- await this.client.writeRegisters(address, values);
164
- }
165
-
166
- /**
167
- * Test connection - used from admin
168
- *
169
- * @returns {Promise<{success: boolean, message: string}>} Result object with success flag and human-readable message
170
- */
171
- async testConnection() {
172
- try {
173
- const wasConnected = this.connected;
174
- if (!this.connected) {
175
- const ok = await this.connect();
176
- if (!ok) {
177
- return { success: false, message: 'Connection failed - check host/port settings' };
178
- }
179
- }
180
-
181
- // Try reading plant system time as connectivity check
182
- this.client.setID(247);
183
- await this.client.readInputRegisters(30000, 2);
184
-
185
- if (!wasConnected) {
186
- await this.disconnect();
187
- }
188
-
189
- return {
190
- success: true,
191
- message: `Connection OK - Plant system time register accessible`,
192
- };
193
- } catch (err) {
194
- this.connected = false;
195
- return {
196
- success: false,
197
- message: `Connection test failed: ${err.message}`,
198
- };
199
- }
200
- }
201
-
202
- /**
203
- * Parse register value based on data type
204
- *
205
- * @param {number[]} rawData - Raw register data
206
- * @param {string} type - Data type (U16, S16, U32, S32, U64, STRING)
207
- * @param {number|null} gain - Scaling factor
208
- * @returns {number|string} Parsed and optionally scaled register value; string for STRING type
209
- */
210
- static parseValue(rawData, type, gain) {
211
- let raw;
212
-
213
- switch (type) {
214
- case 'U16':
215
- raw = rawData[0];
216
- break;
217
- case 'S16':
218
- raw = rawData[0];
219
- if (raw > 32767) {
220
- raw -= 65536;
221
- }
222
- break;
223
- case 'U32':
224
- raw = (rawData[0] << 16) | rawData[1];
225
- raw = raw >>> 0; // ensure unsigned
226
- break;
227
- case 'S32':
228
- raw = (rawData[0] << 16) | rawData[1];
229
- if (raw > 2147483647) {
230
- raw -= 4294967296;
231
- }
232
- break;
233
- case 'U64':
234
- // JavaScript can't handle 64-bit integers precisely, use approximation
235
- raw =
236
- rawData[0] * Math.pow(2, 48) +
237
- rawData[1] * Math.pow(2, 32) +
238
- rawData[2] * Math.pow(2, 16) +
239
- rawData[3];
240
- break;
241
- case 'STRING': {
242
- // Each register holds 2 ASCII chars
243
- let str = '';
244
- for (let i = 0; i < rawData.length; i++) {
245
- const high = (rawData[i] >> 8) & 0xff;
246
- const low = rawData[i] & 0xff;
247
- if (high !== 0) {
248
- str += String.fromCharCode(high);
249
- }
250
- if (low !== 0) {
251
- str += String.fromCharCode(low);
252
- }
253
- }
254
- return str.trim();
255
- }
256
- default:
257
- raw = rawData[0];
258
- }
259
-
260
- if (gain && gain !== 1 && gain !== null) {
261
- return raw / gain;
262
- }
263
- return raw;
264
- }
10
+ /**
11
+ * @param {object} config - Connection configuration
12
+ * @param {function} logFn - Logging function
13
+ */
14
+ constructor(config, logFn) {
15
+ this.config = config;
16
+ this.log = logFn || console;
17
+ this.client = new ModbusRTU();
18
+ this.connected = false;
19
+ this.connecting = false;
20
+ this._reconnectTimer = null;
21
+ this._requestQueue = [];
22
+ this._processing = false;
23
+ }
24
+
25
+ /**
26
+ * Connect to Modbus device
27
+ * @returns {Promise<boolean>} Resolves to true if connection succeeded, false otherwise
28
+ */
29
+ async connect() {
30
+ if (this.connecting) return false;
31
+ this.connecting = true;
32
+
33
+ try {
34
+ if (this.client.isOpen) {
35
+ await this.client.close();
36
+ }
37
+
38
+ this.client = new ModbusRTU();
39
+ this.client.setTimeout(this.config.timeout || 5000);
40
+
41
+ if (this.config.connectionType === "tcp") {
42
+ this.log.debug(`Connecting Modbus TCP to ${this.config.tcpHost}:${this.config.tcpPort}`);
43
+ const connectTimeout = this.config.timeout || 5000;
44
+ await Promise.race([
45
+ this.client.connectTCP(this.config.tcpHost, {
46
+ port: this.config.tcpPort || 502,
47
+ }),
48
+ new Promise((_, reject) =>
49
+ setTimeout(() => reject(new Error(`TCP connection timeout after ${connectTimeout}ms`)), connectTimeout)
50
+ )
51
+ ]);
52
+ } else {
53
+ // Serial RTU
54
+ this.log.debug(`Connecting Modbus RTU on ${this.config.serialPort}`);
55
+ await this.client.connectRTUBuffered(this.config.serialPort, {
56
+ baudRate: this.config.serialBaudRate || 9600,
57
+ dataBits: this.config.serialDataBits || 8,
58
+ parity: this.config.serialParity || "none",
59
+ stopBits: this.config.serialStopBits || 1,
60
+ });
61
+ }
62
+
63
+ this.connected = true;
64
+ this.connecting = false;
65
+ this.log.info("Modbus connection established");
66
+ return true;
67
+ } catch (err) {
68
+ this.connected = false;
69
+ this.connecting = false;
70
+ this.log.error(`Modbus connection failed: ${err.message}`);
71
+ return false;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Disconnect from Modbus device
77
+ */
78
+ async disconnect() {
79
+ if (this._reconnectTimer) {
80
+ clearTimeout(this._reconnectTimer);
81
+ this._reconnectTimer = null;
82
+ }
83
+ if (this.client && this.client.isOpen) {
84
+ try {
85
+ await this.client.close();
86
+ } catch {
87
+ // ignore
88
+ }
89
+ }
90
+ this.connected = false;
91
+ this.log.info("Modbus disconnected");
92
+ }
93
+
94
+ /**
95
+ * Read input registers (function code 0x04)
96
+ * @param {number} slaveId - Slave address
97
+ * @param {number} address - Register start address
98
+ * @param {number} qty - Number of registers to read
99
+ * @returns {Promise<number[]>} Resolves with an array of register values
100
+ */
101
+ async readInputRegisters(slaveId, address, qty) {
102
+ if (!this.connected) {
103
+ throw new Error("Not connected");
104
+ }
105
+ this.client.setID(slaveId);
106
+ const result = await this.client.readInputRegisters(address, qty);
107
+ return result.data;
108
+ }
109
+
110
+ /**
111
+ * Read holding registers (function code 0x03)
112
+ * @param {number} slaveId - Slave address
113
+ * @param {number} address - Register start address
114
+ * @param {number} qty - Number of registers to read
115
+ * @returns {Promise<number[]>} Resolves with an array of register values
116
+ */
117
+ async readHoldingRegisters(slaveId, address, qty) {
118
+ if (!this.connected) {
119
+ throw new Error("Not connected");
120
+ }
121
+ this.client.setID(slaveId);
122
+ const result = await this.client.readHoldingRegisters(address, qty);
123
+ return result.data;
124
+ }
125
+
126
+ /**
127
+ * Write single register (function code 0x06)
128
+ * @param {number} slaveId - Slave address
129
+ * @param {number} address - Register address
130
+ * @param {number} value - Value to write
131
+ * @returns {Promise<void>} Resolves when the register has been written
132
+ */
133
+ async writeSingleRegister(slaveId, address, value) {
134
+ if (!this.connected) {
135
+ throw new Error("Not connected");
136
+ }
137
+ this.client.setID(slaveId);
138
+ await this.client.writeRegister(address, value);
139
+ }
140
+
141
+ /**
142
+ * Write multiple registers (function code 0x10)
143
+ * @param {number} slaveId - Slave address
144
+ * @param {number} address - Start address
145
+ * @param {number[]} values - Values array
146
+ * @returns {Promise<void>} Resolves when all registers have been written
147
+ */
148
+ async writeMultipleRegisters(slaveId, address, values) {
149
+ if (!this.connected) {
150
+ throw new Error("Not connected");
151
+ }
152
+ this.client.setID(slaveId);
153
+ await this.client.writeRegisters(address, values);
154
+ }
155
+
156
+ /**
157
+ * Test connection - used from admin
158
+ * @returns {Promise<{success: boolean, message: string}>} Result object with success flag and human-readable message
159
+ */
160
+ async testConnection() {
161
+ try {
162
+ const wasConnected = this.connected;
163
+ if (!this.connected) {
164
+ const ok = await this.connect();
165
+ if (!ok) {
166
+ return { success: false, message: "Connection failed - check host/port settings" };
167
+ }
168
+ }
169
+
170
+ // Try reading plant system time as connectivity check
171
+ this.client.setID(247);
172
+ await this.client.readInputRegisters(30000, 2);
173
+
174
+ if (!wasConnected) {
175
+ await this.disconnect();
176
+ }
177
+
178
+ return {
179
+ success: true,
180
+ message: `Connection OK - Plant system time register accessible`
181
+ };
182
+ } catch (err) {
183
+ this.connected = false;
184
+ return {
185
+ success: false,
186
+ message: `Connection test failed: ${err.message}`
187
+ };
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Parse register value based on data type
193
+ * @param {number[]} rawData - Raw register data
194
+ * @param {string} type - Data type (U16, S16, U32, S32, U64, STRING)
195
+ * @param {number|null} gain - Scaling factor
196
+ * @returns {number|string} Parsed and optionally scaled register value; string for STRING type
197
+ */
198
+ static parseValue(rawData, type, gain) {
199
+ let raw;
200
+
201
+ switch (type) {
202
+ case "U16":
203
+ raw = rawData[0];
204
+ break;
205
+ case "S16":
206
+ raw = rawData[0];
207
+ if (raw > 32767) raw -= 65536;
208
+ break;
209
+ case "U32":
210
+ raw = (rawData[0] << 16) | rawData[1];
211
+ raw = raw >>> 0; // ensure unsigned
212
+ break;
213
+ case "S32":
214
+ raw = (rawData[0] << 16) | rawData[1];
215
+ if (raw > 2147483647) raw -= 4294967296;
216
+ break;
217
+ case "U64":
218
+ // JavaScript can't handle 64-bit integers precisely, use approximation
219
+ raw = rawData[0] * Math.pow(2, 48) +
220
+ rawData[1] * Math.pow(2, 32) +
221
+ rawData[2] * Math.pow(2, 16) +
222
+ rawData[3];
223
+ break;
224
+ case "STRING": {
225
+ // Each register holds 2 ASCII chars
226
+ let str = "";
227
+ for (let i = 0; i < rawData.length; i++) {
228
+ const high = (rawData[i] >> 8) & 0xFF;
229
+ const low = rawData[i] & 0xFF;
230
+ if (high !== 0) str += String.fromCharCode(high);
231
+ if (low !== 0) str += String.fromCharCode(low);
232
+ }
233
+ return str.trim();
234
+ }
235
+ default:
236
+ raw = rawData[0];
237
+ }
238
+
239
+ if (gain && gain !== 1 && gain !== null) {
240
+ return raw / gain;
241
+ }
242
+ return raw;
243
+ }
265
244
  }
266
245
 
267
246
  module.exports = ModbusConnection;