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 +6 -0
- package/io-package.json +13 -13
- package/lib/modbus.js +236 -257
- package/lib/registers.js +185 -1607
- package/lib/statistics.js +236 -263
- package/main.js +592 -639
- package/package.json +3 -2
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.
|
|
4
|
+
"version": "1.3.5",
|
|
5
5
|
"news": {
|
|
6
|
-
"1.3.
|
|
7
|
-
"en": "Fix
|
|
8
|
-
"de": "
|
|
9
|
-
"ru": "
|
|
10
|
-
"pt": "
|
|
11
|
-
"nl": "
|
|
12
|
-
"fr": "Correction
|
|
13
|
-
"it": "
|
|
14
|
-
"es": "
|
|
15
|
-
"pl": "
|
|
16
|
-
"uk": "
|
|
17
|
-
"zh-cn": "
|
|
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
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const ModbusRTU = require(
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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;
|