@iobroker/modbus 7.0.2

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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -0
  3. package/build/convert.d.ts +2 -0
  4. package/build/convert.js +102 -0
  5. package/build/convert.js.map +1 -0
  6. package/build/index.d.ts +88 -0
  7. package/build/index.js +1005 -0
  8. package/build/index.js.map +1 -0
  9. package/build/lib/Master.d.ts +27 -0
  10. package/build/lib/Master.js +811 -0
  11. package/build/lib/Master.js.map +1 -0
  12. package/build/lib/Put.d.ts +19 -0
  13. package/build/lib/Put.js +113 -0
  14. package/build/lib/Put.js.map +1 -0
  15. package/build/lib/Slave.d.ts +15 -0
  16. package/build/lib/Slave.js +545 -0
  17. package/build/lib/Slave.js.map +1 -0
  18. package/build/lib/common.d.ts +3 -0
  19. package/build/lib/common.js +371 -0
  20. package/build/lib/common.js.map +1 -0
  21. package/build/lib/crc16modbus.d.ts +1 -0
  22. package/build/lib/crc16modbus.js +33 -0
  23. package/build/lib/crc16modbus.js.map +1 -0
  24. package/build/lib/jsmodbus/modbus-client-core.d.ts +78 -0
  25. package/build/lib/jsmodbus/modbus-client-core.js +528 -0
  26. package/build/lib/jsmodbus/modbus-client-core.js.map +1 -0
  27. package/build/lib/jsmodbus/modbus-server-core.d.ts +33 -0
  28. package/build/lib/jsmodbus/modbus-server-core.js +363 -0
  29. package/build/lib/jsmodbus/modbus-server-core.js.map +1 -0
  30. package/build/lib/jsmodbus/transports/errors.d.ts +7 -0
  31. package/build/lib/jsmodbus/transports/errors.js +40 -0
  32. package/build/lib/jsmodbus/transports/errors.js.map +1 -0
  33. package/build/lib/jsmodbus/transports/modbus-client-serial.d.ts +23 -0
  34. package/build/lib/jsmodbus/transports/modbus-client-serial.js +154 -0
  35. package/build/lib/jsmodbus/transports/modbus-client-serial.js.map +1 -0
  36. package/build/lib/jsmodbus/transports/modbus-client-tcp-rtu.d.ts +24 -0
  37. package/build/lib/jsmodbus/transports/modbus-client-tcp-rtu.js +166 -0
  38. package/build/lib/jsmodbus/transports/modbus-client-tcp-rtu.js.map +1 -0
  39. package/build/lib/jsmodbus/transports/modbus-client-tcp-ssl.d.ts +34 -0
  40. package/build/lib/jsmodbus/transports/modbus-client-tcp-ssl.js +138 -0
  41. package/build/lib/jsmodbus/transports/modbus-client-tcp-ssl.js.map +1 -0
  42. package/build/lib/jsmodbus/transports/modbus-client-tcp.d.ts +27 -0
  43. package/build/lib/jsmodbus/transports/modbus-client-tcp.js +123 -0
  44. package/build/lib/jsmodbus/transports/modbus-client-tcp.js.map +1 -0
  45. package/build/lib/jsmodbus/transports/modbus-server-serial.d.ts +29 -0
  46. package/build/lib/jsmodbus/transports/modbus-server-serial.js +206 -0
  47. package/build/lib/jsmodbus/transports/modbus-server-serial.js.map +1 -0
  48. package/build/lib/jsmodbus/transports/modbus-server-tcp.d.ts +25 -0
  49. package/build/lib/jsmodbus/transports/modbus-server-tcp.js +112 -0
  50. package/build/lib/jsmodbus/transports/modbus-server-tcp.js.map +1 -0
  51. package/build/lib/loggingUtils.d.ts +11 -0
  52. package/build/lib/loggingUtils.js +37 -0
  53. package/build/lib/loggingUtils.js.map +1 -0
  54. package/package.json +68 -0
@@ -0,0 +1,811 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Master = void 0;
7
+ const common_1 = require("./common");
8
+ const modbus_client_serial_1 = __importDefault(require("./jsmodbus/transports/modbus-client-serial"));
9
+ const modbus_client_tcp_1 = __importDefault(require("./jsmodbus/transports/modbus-client-tcp"));
10
+ const modbus_client_tcp_rtu_1 = __importDefault(require("./jsmodbus/transports/modbus-client-tcp-rtu"));
11
+ const modbus_client_tcp_ssl_1 = __importDefault(require("./jsmodbus/transports/modbus-client-tcp-ssl"));
12
+ const loggingUtils_1 = require("./loggingUtils");
13
+ class Master {
14
+ modbusClient;
15
+ connected = false;
16
+ connectTimer;
17
+ nextPoll;
18
+ pollTime;
19
+ errorCount = 0;
20
+ ackObjects = {};
21
+ objects;
22
+ isStop = false;
23
+ pulseList = {};
24
+ sendBuffer = {};
25
+ devices;
26
+ deviceIds;
27
+ reconnectTimeout;
28
+ keepAliveTimeout;
29
+ adapter;
30
+ options;
31
+ scaleFactors = {};
32
+ showDebug;
33
+ constructor(options, adapter) {
34
+ this.adapter = adapter;
35
+ this.options = options;
36
+ this.devices = options.devices;
37
+ this.objects = options.objects;
38
+ this.deviceIds = Object.keys(options.devices).map(id => parseInt(id, 10));
39
+ this.showDebug = adapter.common?.loglevel === 'debug' || adapter.common?.loglevel === 'silly';
40
+ void adapter.setState('info.connection', false, true);
41
+ // delete all server connection information
42
+ void adapter.getStatesAsync('info.clients.*').then(async (states) => {
43
+ for (const id in states) {
44
+ await adapter.delForeignObjectAsync(id);
45
+ }
46
+ });
47
+ if (options.config.type === 'tcp') {
48
+ const tcp = options.config.tcp;
49
+ if (!tcp || !tcp.bind || tcp.bind === '0.0.0.0') {
50
+ adapter.log.error('IP address is not defined');
51
+ return;
52
+ }
53
+ try {
54
+ const logWrapper = (0, loggingUtils_1.createLoggingWrapper)(adapter.log, options.config.disableLogging);
55
+ this.modbusClient = new modbus_client_tcp_1.default({
56
+ tcp: {
57
+ host: tcp.bind,
58
+ port: tcp.port || 502,
59
+ autoReconnect: false,
60
+ },
61
+ logger: logWrapper,
62
+ timeout: options.config.timeout,
63
+ unitId: options.config.defaultDeviceId,
64
+ });
65
+ }
66
+ catch (e) {
67
+ adapter.log.error(`Cannot connect to "${tcp.bind}:${tcp.port || 502}": ${e}`);
68
+ }
69
+ }
70
+ else if (options.config.type === 'tcp-ssl') {
71
+ const tcp = options.config.tcp;
72
+ if (!tcp || !tcp.bind || tcp.bind === '0.0.0.0') {
73
+ adapter.log.error('IP address is not defined');
74
+ return;
75
+ }
76
+ try {
77
+ const logWrapper = (0, loggingUtils_1.createLoggingWrapper)(adapter.log, options.config.disableLogging);
78
+ if (!options.config.ssl?.cert || !options.config.ssl?.key) {
79
+ adapter.log.error('SSL certificate or key is not defined');
80
+ return;
81
+ }
82
+ // Prepare SSL configuration
83
+ const sslConfig = {
84
+ rejectUnauthorized: options.config.ssl?.rejectUnauthorized !== false,
85
+ cert: options.config.ssl.cert,
86
+ key: options.config.ssl.key,
87
+ ca: options.config.ssl.ca,
88
+ };
89
+ this.modbusClient = new modbus_client_tcp_ssl_1.default({
90
+ tcp: {
91
+ host: tcp.bind,
92
+ port: tcp.port || 502,
93
+ autoReconnect: false,
94
+ },
95
+ ssl: sslConfig,
96
+ logger: logWrapper,
97
+ timeout: options.config.timeout,
98
+ unitId: options.config.defaultDeviceId,
99
+ });
100
+ }
101
+ catch (e) {
102
+ adapter.log.error(`Cannot connect to SSL "${tcp.bind}:${tcp.port || 502}": ${e}`);
103
+ }
104
+ }
105
+ else if (options.config.type === 'tcprtu') {
106
+ const tcp = options.config.tcp;
107
+ if (!tcp || !tcp.bind || tcp.bind === '0.0.0.0') {
108
+ adapter.log.error('IP address is not defined');
109
+ return;
110
+ }
111
+ try {
112
+ const logWrapper = (0, loggingUtils_1.createLoggingWrapper)(adapter.log, options.config.disableLogging);
113
+ this.modbusClient = new modbus_client_tcp_rtu_1.default({
114
+ tcp: {
115
+ host: tcp.bind,
116
+ port: tcp.port || 502,
117
+ autoReconnect: false,
118
+ },
119
+ logger: logWrapper,
120
+ timeout: options.config.timeout,
121
+ unitId: options.config.defaultDeviceId,
122
+ });
123
+ }
124
+ catch (e) {
125
+ adapter.log.error(`Cannot connect to "${tcp.bind}:${tcp.port || 502}": ${e}`);
126
+ }
127
+ }
128
+ else if (options.config.type === 'serial') {
129
+ const serial = options.config.serial;
130
+ if (!serial || !serial.comName) {
131
+ adapter.log.error('Serial device name is not defined');
132
+ return;
133
+ }
134
+ try {
135
+ const logWrapper = (0, loggingUtils_1.createLoggingWrapper)(adapter.log, options.config.disableLogging);
136
+ this.modbusClient = new modbus_client_serial_1.default({
137
+ serial: {
138
+ portName: serial.comName,
139
+ baudRate: serial.baudRate || 9600,
140
+ dataBits: serial.dataBits || 8,
141
+ stopBits: serial.stopBits || 1,
142
+ parity: serial.parity || 'none',
143
+ },
144
+ logger: logWrapper,
145
+ timeout: options.config.timeout,
146
+ unitId: options.config.multiDeviceId ? undefined : options.config.defaultDeviceId,
147
+ });
148
+ }
149
+ catch (e) {
150
+ adapter.log.error(`Cannot open port "${serial.comName}" [${serial.baudRate || 9600}]: ${e}`);
151
+ }
152
+ }
153
+ else {
154
+ adapter.log.error(`Unsupported type ${options.config.type}"`);
155
+ return;
156
+ }
157
+ if (!this.modbusClient) {
158
+ adapter.log.error('Cannot create modbus master!');
159
+ return;
160
+ }
161
+ this.modbusClient
162
+ .on('connect', () => {
163
+ if (!this.connected) {
164
+ if (options.config.type === 'tcp') {
165
+ adapter.log.info(`Connected to slave ${options.config.tcp?.bind}`);
166
+ }
167
+ else {
168
+ adapter.log.info('Connected to slave');
169
+ }
170
+ this.connected = true;
171
+ void this.adapter.setState('info.connection', true, true);
172
+ }
173
+ if (this.nextPoll) {
174
+ adapter.clearTimeout(this.nextPoll);
175
+ this.nextPoll = null;
176
+ }
177
+ void this.#poll().catch(err => this.adapter.log.error(err));
178
+ this.keepAliveTimeout && adapter.clearTimeout(this.keepAliveTimeout);
179
+ this.keepAliveTimeout = adapter.setTimeout(() => {
180
+ this.keepAliveTimeout = undefined;
181
+ this.#pollBinariesBlockWithKeepAlive();
182
+ }, options.config.keepAliveInterval || 1000);
183
+ })
184
+ .on('disconnect', () => {
185
+ if (this.isStop) {
186
+ return;
187
+ }
188
+ this.reconnectTimeout ||= adapter.setTimeout(() => this.#reconnect(), 1000);
189
+ });
190
+ this.modbusClient.on('close', () => {
191
+ if (this.isStop) {
192
+ return;
193
+ }
194
+ this.reconnectTimeout = adapter.setTimeout(() => this.#reconnect(), 1000);
195
+ });
196
+ this.modbusClient.on('error', err => {
197
+ if (this.isStop) {
198
+ return;
199
+ }
200
+ if (err.code === 'ECONNREFUSED') {
201
+ adapter.log.warn(`Connection refused ${err.address}:${err.port}`);
202
+ }
203
+ else {
204
+ adapter.log.warn(`On error: ${JSON.stringify(err)}`);
205
+ }
206
+ this.reconnectTimeout = adapter.setTimeout(() => this.#reconnect(), 1000);
207
+ });
208
+ this.modbusClient.on('trashCurrentRequest', err => {
209
+ if (this.isStop) {
210
+ return;
211
+ }
212
+ adapter.log.warn(`Error: ${JSON.stringify(err)}`);
213
+ this.reconnectTimeout = adapter.setTimeout(() => this.#reconnect(), 1000);
214
+ });
215
+ }
216
+ #waitAsync(ms) {
217
+ if (!ms) {
218
+ return Promise.resolve();
219
+ }
220
+ return new Promise(resolve => this.adapter.setTimeout(resolve, ms));
221
+ }
222
+ #reconnect(isImmediately) {
223
+ if (this.reconnectTimeout) {
224
+ this.adapter.clearTimeout(this.reconnectTimeout);
225
+ this.reconnectTimeout = null;
226
+ }
227
+ if (this.nextPoll) {
228
+ this.adapter.clearTimeout(this.nextPoll);
229
+ this.nextPoll = null;
230
+ }
231
+ if (this.keepAliveTimeout) {
232
+ this.adapter.clearTimeout(this.keepAliveTimeout);
233
+ this.keepAliveTimeout = null;
234
+ }
235
+ try {
236
+ this.modbusClient?.close();
237
+ }
238
+ catch (e) {
239
+ this.adapter.log.error(`Cannot close master: ${e}`);
240
+ }
241
+ if (this.connected) {
242
+ if (this.options.config.tcp) {
243
+ this.adapter.log.info(`Disconnected from slave ${this.options.config.tcp?.bind}`);
244
+ }
245
+ else {
246
+ this.adapter.log.info('Disconnected from slave');
247
+ }
248
+ this.connected = false;
249
+ void this.adapter.setState('info.connection', false, true);
250
+ }
251
+ this.connectTimer ||= this.adapter.setTimeout(() => {
252
+ this.connectTimer = null;
253
+ if (typeof this.modbusClient?.connect === 'function') {
254
+ this.modbusClient.connect();
255
+ }
256
+ }, isImmediately ? 1000 : this.options.config.recon);
257
+ }
258
+ async #pollBinariesBlock(device, regType, block) {
259
+ const regs = device[regType];
260
+ const regBlock = regs.blocks[block];
261
+ if (regBlock.startIndex === regBlock.endIndex) {
262
+ regBlock.endIndex++;
263
+ }
264
+ if (this.showDebug) {
265
+ this.adapter.log.debug(`[DevID_${regs.deviceId}/${regType}] Poll address ${regBlock.start} - ${regBlock.count} bits`);
266
+ }
267
+ if (this.modbusClient) {
268
+ let response;
269
+ try {
270
+ if (regType === 'disInputs') {
271
+ response = await this.modbusClient.readDiscreteInputs(regs.deviceId, regBlock.start, regBlock.count);
272
+ }
273
+ else {
274
+ response = await this.modbusClient.readCoils(regs.deviceId, regBlock.start, regBlock.count);
275
+ }
276
+ }
277
+ catch (err) {
278
+ const errorMsg = `[DevID_${regs.deviceId}/${regType}] Block ${regBlock.start}-${regBlock.start + regBlock.count - 1}: ${JSON.stringify(err)}`;
279
+ this.adapter.log.warn(errorMsg);
280
+ return;
281
+ }
282
+ if (response.data?.length) {
283
+ for (let n = regBlock.startIndex; n < regBlock.endIndex; n++) {
284
+ const id = regs.config[n].id;
285
+ const val = response.data[regs.config[n].address - regBlock.start];
286
+ if (this.options.config.alwaysUpdate ||
287
+ this.ackObjects[id] === undefined ||
288
+ this.ackObjects[id].val !== val) {
289
+ this.ackObjects[id] = { val };
290
+ void this.adapter.setState(id, val, true, err => {
291
+ // analyze if the state could be set (because of permissions)
292
+ err && this.adapter.log.error(`Can not set state ${id}: ${err}`);
293
+ });
294
+ }
295
+ }
296
+ }
297
+ else {
298
+ this.adapter.log.warn(`Null buffer length for ${regType} ${regBlock.start}`);
299
+ }
300
+ }
301
+ else {
302
+ this.adapter.log.debug(`Poll canceled, because no connection`);
303
+ throw new Error('No connection');
304
+ }
305
+ }
306
+ #pollBinariesBlockWithKeepAlive() {
307
+ if (this.options.config.keepAliveInterval > 0) {
308
+ if (this.keepAliveTimeout) {
309
+ this.keepAliveTimeout && this.adapter.clearTimeout(this.keepAliveTimeout);
310
+ this.keepAliveTimeout = null;
311
+ }
312
+ if (this.modbusClient) {
313
+ this.modbusClient
314
+ .readDiscreteInputs(this.deviceIds[0], 0, 1)
315
+ .then(response => this.adapter.log.silly(`Keep alive response = ${JSON.stringify(response)}`))
316
+ .catch(() => { });
317
+ this.keepAliveTimeout = this.adapter.setTimeout(() => {
318
+ this.keepAliveTimeout = null;
319
+ this.#pollBinariesBlockWithKeepAlive();
320
+ }, this.options.config.keepAliveInterval);
321
+ }
322
+ else {
323
+ this.adapter.log.debug(`Poll canceled, because no connection`);
324
+ }
325
+ }
326
+ else {
327
+ this.adapter.log.silly('Keepalive is disabled!');
328
+ }
329
+ }
330
+ async #pollBinariesBlocks(device, regType) {
331
+ const regs = device[regType];
332
+ for (let n = 0; n < regs.length; n++) {
333
+ if (this.connected && !this.isStop) {
334
+ await this.#pollBinariesBlock(device, regType, n);
335
+ await this.#waitAsync(this.options.config.readInterval);
336
+ }
337
+ }
338
+ }
339
+ async #pollFloatBlock(device, regType, block) {
340
+ const regs = device[regType];
341
+ const regBlock = regs.blocks[block];
342
+ if (regBlock.startIndex === regBlock.endIndex) {
343
+ regBlock.endIndex++;
344
+ }
345
+ if (!this.scaleFactors[regs.deviceId]) {
346
+ this.adapter.log.debug('Initialization of scale factors done!');
347
+ this.scaleFactors[regs.deviceId] = {};
348
+ }
349
+ if (this.showDebug) {
350
+ this.adapter.log.debug(`[DevID_${regs.deviceId}/${regType}] Poll address ${regBlock.start} - ${regBlock.count} registers`);
351
+ }
352
+ if (this.modbusClient && this.connected && !this.isStop) {
353
+ let response;
354
+ try {
355
+ if (regType === 'inputRegs') {
356
+ response = await this.modbusClient.readInputRegisters(regs.deviceId, regBlock.start, regBlock.count);
357
+ }
358
+ else {
359
+ response = await this.modbusClient.readHoldingRegisters(regs.deviceId, regBlock.start, regBlock.count);
360
+ }
361
+ if (this.showDebug) {
362
+ this.adapter.log.debug(`[DevID_${regs.deviceId}/${regType}] Poll address ${regBlock.start} DONE`);
363
+ }
364
+ if (response.payload?.length) {
365
+ // first process all the scale factor values inside the block
366
+ for (let n = regBlock.startIndex; n < regBlock.endIndex; n++) {
367
+ if (regs.config[n].isScale) {
368
+ const prefixAddr = `DevID_${device.coils.deviceId}/${regType}/${regs.config[n]._address}`;
369
+ try {
370
+ let val = (0, common_1.extractValue)(regs.config[n].type, regs.config[n].len, response.payload, regs.config[n].address - regBlock.start);
371
+ const formula = regs.config[n].formula;
372
+ // If value must be calculated with formula
373
+ if (formula) {
374
+ if (this.showDebug) {
375
+ this.adapter.log.debug(`[${prefixAddr}] _Input Value = ${val}`);
376
+ this.adapter.log.debug(`[${prefixAddr}] _Formula = ${formula}`);
377
+ }
378
+ try {
379
+ const scaleAddress = parseInt(formula.substring(formula.indexOf('sf[') + 4));
380
+ if (scaleAddress !== null && !isNaN(scaleAddress)) {
381
+ this.adapter.log.warn(`[${prefixAddr}] Calculation of a scaleFactor which is based on another scaleFactor seems strange. Please check the config for address ${regs.config[n].address} !`);
382
+ }
383
+ // calculate value from formula or report an error
384
+ const func = new Function('x', 'sf', `return ${formula}`);
385
+ val = func(val, this.scaleFactors[regs.deviceId]);
386
+ if (typeof val === 'number') {
387
+ val =
388
+ Math.round(val * this.options.config.round) / this.options.config.round;
389
+ }
390
+ }
391
+ catch (e) {
392
+ this.adapter.log.warn(`[${prefixAddr}] Calculation: eval(${formula}) not possible: ${e}`);
393
+ }
394
+ }
395
+ else if (typeof val === 'number') {
396
+ // no formula used, so just scale with factor and offset
397
+ val = val * regs.config[n].factor + regs.config[n].offset;
398
+ val = Math.round(val * this.options.config.round) / this.options.config.round;
399
+ }
400
+ // store the finally calculated value as scaleFactor
401
+ this.scaleFactors[regs.deviceId][regs.config[n]._address] = val;
402
+ if (this.showDebug) {
403
+ this.adapter.log.debug(`[${prefixAddr}] Scale factor value stored from this address = ${val}`);
404
+ }
405
+ }
406
+ catch (err) {
407
+ this.adapter.log.error(`Can not set value for [DevID_${regs.deviceId}]: ${err.message}`);
408
+ }
409
+ }
410
+ }
411
+ // now process all values and store to the states
412
+ for (let n = regBlock.startIndex; n < regBlock.endIndex; n++) {
413
+ const id = regs.config[n].id;
414
+ try {
415
+ let val = (0, common_1.extractValue)(regs.config[n].type, regs.config[n].len, response.payload, regs.config[n].address - regBlock.start);
416
+ // If value must be calculated with formula
417
+ const prefixAddr = this.showDebug
418
+ ? `DevID_${device.coils.deviceId}/${regType}/${regs.config[n]._address}`
419
+ : '';
420
+ if (regs.config[n].formula) {
421
+ if (this.showDebug) {
422
+ this.adapter.log.debug(`[${prefixAddr}] Input Value = ${val}`);
423
+ this.adapter.log.debug(`[${prefixAddr}] Formula = ${regs.config[n].formula}`);
424
+ }
425
+ try {
426
+ if (this.showDebug) {
427
+ // scaleAddress is used only for debug output
428
+ const m = regs.config[n].formula.match(/sf\[(['"`\d]+)]/g);
429
+ m?.forEach(sf => {
430
+ const num = sf.match(/\d+/);
431
+ if (num) {
432
+ const scaleAddress = parseInt(num[1]);
433
+ if (scaleAddress !== null && !isNaN(scaleAddress)) {
434
+ // it seems that the current formula uses a scaleFactor, therefore check the validity
435
+ if (this.showDebug) {
436
+ this.adapter.log.debug(`[${prefixAddr}] Scale factor address is = ${scaleAddress}`);
437
+ this.adapter.log.debug(`[${prefixAddr}] Scale factor address is inside current read range = ${scaleAddress > regBlock.start && scaleAddress < regBlock.start + regBlock.count}`);
438
+ }
439
+ // check if the scaleFactor address is in the current read block or outside this block
440
+ if (scaleAddress < regBlock.start ||
441
+ scaleAddress > regBlock.start + regBlock.count) {
442
+ // the scaleFactor address is not in the current read block. So it cannot be ensured that the values are in sync / valid
443
+ this.adapter.log.warn(`[DevID_${device.coils.deviceId}/${regType}/${regs.config[n]._address}] The current range for reading the values was from address ${regBlock.start} up to address ${regBlock.start + regBlock.count}!`);
444
+ this.adapter.log.warn(`[DevID_${device.coils.deviceId}/${regType}/${regs.config[n]._address}] Please make sure to configure the read process that both adresses are read in the same block!`);
445
+ this.adapter.log.warn(`[DevID_${device.coils.deviceId}/${regType}/${regs.config[n]._address}] The used scaleFactor from address ${scaleAddress} is not inside the same read block as the parameter on address ${regs.config[n].address}`);
446
+ }
447
+ }
448
+ }
449
+ });
450
+ }
451
+ // calculate value from formula or report an error
452
+ const func = new Function('x', 'sf', `return ${regs.config[n].formula}`);
453
+ val = func(val, this.scaleFactors[regs.deviceId]);
454
+ if (this.showDebug) {
455
+ this.adapter.log.debug(`[${prefixAddr}] Calculation result = ${val}, type = ${typeof val}`);
456
+ }
457
+ // only do rounding in case the calculation result is a number
458
+ if (typeof val === 'number') {
459
+ val = Math.round(val * this.options.config.round) / this.options.config.round;
460
+ }
461
+ }
462
+ catch (e) {
463
+ this.adapter.log.warn(`[DevID_${device.coils.deviceId}/${regType}/${regs.config[n]._address}] Calculation: eval(${regs.config[n].formula}) not possible: ${e}`);
464
+ }
465
+ }
466
+ else if (typeof val === 'number') {
467
+ val = val * regs.config[n].factor + regs.config[n].offset;
468
+ val = Math.round(val * this.options.config.round) / this.options.config.round;
469
+ }
470
+ if (val !== null &&
471
+ (this.options.config.alwaysUpdate ||
472
+ this.ackObjects[id] === undefined ||
473
+ this.ackObjects[id].val !== val)) {
474
+ this.ackObjects[id] = { val };
475
+ void this.adapter.setState(id, val, true, err =>
476
+ // analyze if the state could be set (because of permissions)
477
+ err && this.adapter.log.error(`Can not set state ${id}: ${err}`));
478
+ }
479
+ }
480
+ catch (err) {
481
+ this.adapter.log.error(`Can not set value: ${err.message}`);
482
+ }
483
+ }
484
+ }
485
+ else {
486
+ this.adapter.log.warn(`Null buffer length for ${regType} ${regBlock.start}`);
487
+ }
488
+ // special case for cyclic write (cw)
489
+ if (this.options.config.maxBlock < 2 && regs.config[regBlock.startIndex].cw) {
490
+ // write immediately the current value
491
+ const fullId = regs.config[regBlock.startIndex].fullId;
492
+ await this.#writeFloatsReg(fullId);
493
+ }
494
+ }
495
+ catch (err) {
496
+ const errorMsg = `[DevID_${regs.deviceId}/${regType}] Block ${regBlock.start}-${regBlock.start + regBlock.count - 1}: ${JSON.stringify(err)}`;
497
+ this.adapter.log.warn(errorMsg);
498
+ return;
499
+ }
500
+ }
501
+ else {
502
+ this.adapter.log.debug(`Poll canceled, because no connection`);
503
+ throw new Error('No connection');
504
+ }
505
+ }
506
+ async #pollFloatsBlocks(device, regType) {
507
+ const regs = device[regType];
508
+ for (let n = 0; n < regs.blocks.length; n++) {
509
+ if (this.connected && !this.isStop) {
510
+ await this.#pollFloatBlock(device, regType, n);
511
+ await this.#waitAsync(this.options.config.readInterval);
512
+ }
513
+ }
514
+ }
515
+ async #writeFloatsReg(fullId) {
516
+ const obj = this.objects[fullId];
517
+ if (obj?.native?.len) {
518
+ const id = obj._id.substring(this.adapter.namespace.length + 1);
519
+ if (!this.modbusClient || !this.connected || this.isStop) {
520
+ throw new Error('client disconnected');
521
+ }
522
+ if (this.ackObjects[id]) {
523
+ await this.#writeValue(id, this.ackObjects[id].val);
524
+ }
525
+ }
526
+ }
527
+ async #writeFloatsRegs(device) {
528
+ const regs = device.holdingRegs;
529
+ if (regs.cyclicWrite) {
530
+ for (const fullId of regs.cyclicWrite) {
531
+ await this.#writeFloatsReg(fullId);
532
+ await this.#waitAsync(this.options.config.readInterval);
533
+ }
534
+ }
535
+ }
536
+ #pollResult(startTime, deviceId, err) {
537
+ if (err) {
538
+ this.errorCount++;
539
+ this.adapter.log.warn(`[DevID_${deviceId}] Poll error count: ${this.errorCount} code: ${JSON.stringify(err)}`);
540
+ void this.adapter.setState('info.connection', false, true);
541
+ if (this.errorCount > 12 * this.deviceIds.length) {
542
+ // 2 re-connects did not help, restart adapter
543
+ this.adapter.log.error('Reconnect did not help, restart adapter');
544
+ typeof this.adapter.terminate === 'function' ? this.adapter.terminate(156) : process.exit(156);
545
+ }
546
+ else if (this.errorCount < 6 * this.deviceIds.length && this.connected) {
547
+ // tolerate up to 6 errors per device
548
+ return;
549
+ }
550
+ else {
551
+ return new Error('disconnect');
552
+ }
553
+ }
554
+ else {
555
+ const currentPollTime = new Date().valueOf() - startTime;
556
+ if (this.pollTime !== undefined) {
557
+ if (Math.abs(this.pollTime - currentPollTime) > 100) {
558
+ this.pollTime = currentPollTime;
559
+ void this.adapter.setState('info.pollTime', currentPollTime, true);
560
+ }
561
+ }
562
+ else {
563
+ this.pollTime = currentPollTime;
564
+ void this.adapter.setState('info.pollTime', currentPollTime, true);
565
+ }
566
+ if (this.errorCount > 0) {
567
+ void this.adapter.setState('info.connection', true, true);
568
+ this.errorCount = 0;
569
+ }
570
+ }
571
+ }
572
+ async #pollDevice(device) {
573
+ this.adapter.log.debug(`[DevID_${device.coils.deviceId}] Poll start ---------------------`);
574
+ const startTime = new Date().valueOf();
575
+ // Track errors from each register type but continue polling
576
+ const pollErrors = [];
577
+ // Poll discrete inputs
578
+ try {
579
+ await this.#pollBinariesBlocks(device, 'disInputs');
580
+ }
581
+ catch (err) {
582
+ pollErrors.push(err);
583
+ }
584
+ // Poll coils
585
+ try {
586
+ await this.#pollBinariesBlocks(device, 'coils');
587
+ }
588
+ catch (err) {
589
+ pollErrors.push(err);
590
+ }
591
+ // Poll input registers
592
+ try {
593
+ await this.#pollFloatsBlocks(device, 'inputRegs');
594
+ }
595
+ catch (err) {
596
+ pollErrors.push(err);
597
+ }
598
+ // Poll holding registers
599
+ try {
600
+ await this.#pollFloatsBlocks(device, 'holdingRegs');
601
+ }
602
+ catch (err) {
603
+ pollErrors.push(err);
604
+ }
605
+ if (device.holdingRegs.cyclicWrite?.length && this.options.config.maxBlock >= 2) {
606
+ try {
607
+ await this.#writeFloatsRegs(device);
608
+ await this.#waitAsync(this.options.config.writeInterval);
609
+ }
610
+ catch (err) {
611
+ pollErrors.push(err);
612
+ }
613
+ }
614
+ if (this.connected && !this.isStop) {
615
+ // If all polls failed, report error, otherwise report success
616
+ const allFailed = pollErrors.length === 4;
617
+ const error = allFailed ? pollErrors[0] : null; // Report first error if all failed
618
+ if (pollErrors.length && pollErrors.length < 4) {
619
+ this.adapter.log.warn(`[DevID_${device.coils.deviceId}] Some register types failed but continuing: ${pollErrors.length}/4 errors`);
620
+ }
621
+ this.#pollResult(startTime, device.coils.deviceId, error);
622
+ }
623
+ }
624
+ async #poll() {
625
+ let anyError;
626
+ for (const id of this.deviceIds) {
627
+ try {
628
+ await this.#pollDevice(this.devices[id]);
629
+ }
630
+ catch (err) {
631
+ anyError = err;
632
+ }
633
+ await this.#waitAsync(this.options.config.waitTime);
634
+ }
635
+ if (anyError) {
636
+ if (!this.reconnectTimeout) {
637
+ this.#reconnect();
638
+ }
639
+ }
640
+ else {
641
+ this.nextPoll = this.adapter.setTimeout(() => {
642
+ this.nextPoll = null;
643
+ this.#poll().catch(e => this.adapter.log.error(`Cannot poll: ${e}`));
644
+ }, this.options.config.poll);
645
+ }
646
+ }
647
+ async #writeValue(id, val) {
648
+ const obj = this.objects[id];
649
+ if (!obj || !this.modbusClient) {
650
+ return;
651
+ }
652
+ const type = obj.native.regType;
653
+ try {
654
+ if (type === 'coils') {
655
+ if (val === 'true' || val === true) {
656
+ val = 1;
657
+ }
658
+ if (val === 'false' || val === false) {
659
+ val = 0;
660
+ }
661
+ val = parseFloat(val);
662
+ await this.modbusClient.writeSingleCoil(obj.native.deviceId, obj.native.address, !!val);
663
+ }
664
+ else if (type === 'holdingRegs') {
665
+ if (obj.native.float === undefined) {
666
+ obj.native.float =
667
+ obj.native.type === 'floatle' ||
668
+ obj.native.type === 'floatbe' ||
669
+ obj.native.type === 'floatsw' ||
670
+ obj.native.type === 'doublele' ||
671
+ obj.native.type === 'doublebe' ||
672
+ obj.native.type === 'floatsb';
673
+ }
674
+ if (!['string', 'stringle', 'string16', 'string16le', 'rawhex'].includes(obj.native.type)) {
675
+ val = parseFloat(val);
676
+ val = (val - obj.native.offset) / obj.native.factor;
677
+ if (!obj.native.float) {
678
+ val = Math.round(val);
679
+ }
680
+ }
681
+ if (!obj.native.type) {
682
+ this.adapter.log.error('No type defined for write.');
683
+ return;
684
+ }
685
+ // FC16
686
+ if (this.options.config.onlyUseWriteMultipleRegisters ||
687
+ (obj.native.len > 1 && !this.options.config.doNotUseWriteMultipleRegisters)) {
688
+ const hrBuffer = (0, common_1.writeValue)(obj.native.type, val, obj.native.len);
689
+ await this.modbusClient.writeMultipleRegisters(obj.native.deviceId, obj.native.address, hrBuffer);
690
+ }
691
+ else {
692
+ // FC06
693
+ const buffer = (0, common_1.writeValue)(obj.native.type, val, 1);
694
+ if (obj.native.len > 1) {
695
+ for (let r = 0; r < obj.native.len / 2; r++) {
696
+ const subBuffer = buffer.subarray(r * 2, r * 2 + 2);
697
+ await this.modbusClient.writeSingleRegister(obj.native.deviceId, obj.native.address + r, subBuffer);
698
+ await this.#waitAsync(this.options.config.writeInterval);
699
+ }
700
+ }
701
+ else {
702
+ await this.modbusClient.writeSingleRegister(obj.native.deviceId, obj.native.address, buffer);
703
+ }
704
+ }
705
+ }
706
+ if (this.showDebug) {
707
+ this.adapter.log.debug(`Write successfully [${obj.native.address}]: ${val}`);
708
+ }
709
+ }
710
+ catch (err) {
711
+ this.adapter.log.warn(`Can not write value ${val}: ${err}`);
712
+ if (!this.isStop && !this.reconnectTimeout) {
713
+ this.#reconnect(true);
714
+ }
715
+ }
716
+ }
717
+ async #send() {
718
+ if (!this.modbusClient) {
719
+ this.adapter.log.error('Client not connected');
720
+ return;
721
+ }
722
+ const id = Object.keys(this.sendBuffer)[0];
723
+ await this.#writeValue(id, this.sendBuffer[id]);
724
+ delete this.sendBuffer[id];
725
+ if (Object.keys(this.sendBuffer).length) {
726
+ this.adapter.setTimeout(() => this.#send(), this.options.config.writeInterval || 0);
727
+ }
728
+ }
729
+ #writeHelper(id, state) {
730
+ this.sendBuffer[id] = state.val;
731
+ if (Object.keys(this.sendBuffer).length === 1) {
732
+ this.#send().catch(e => this.adapter.log.error(`Cannot send: ${e}`));
733
+ }
734
+ }
735
+ async write(id, state) {
736
+ if (!this.objects[id]?.native) {
737
+ this.adapter.log.error(`Can not set state ${id}: unknown object`);
738
+ return;
739
+ }
740
+ if (this.objects[id].native.regType === 'coils' || this.objects[id].native.regType === 'holdingRegs') {
741
+ if (!this.objects[id].native.wp) {
742
+ this.#writeHelper(id, state);
743
+ // TODO: may be here we should calculate options.config.readInterval too
744
+ await this.#waitAsync(this.options.config.poll * 1.5);
745
+ const _id = id.substring(this.adapter.namespace.length + 1);
746
+ await this.adapter.setState(id, this.ackObjects[_id] ? this.ackObjects[_id].val : null, true);
747
+ }
748
+ else {
749
+ if (this.pulseList[id] === undefined) {
750
+ const _id = id.substring(this.adapter.namespace.length + 1);
751
+ this.pulseList[id] = this.ackObjects[_id] ? this.ackObjects[_id].val : !state.val;
752
+ this.#writeHelper(id, state);
753
+ await this.#waitAsync(this.options.config.pulseTime);
754
+ this.#writeHelper(id, { val: this.pulseList[id] });
755
+ await this.#waitAsync(this.options.config.poll * 1.5);
756
+ if (this.ackObjects[_id]) {
757
+ await this.adapter.setState(id, this.ackObjects[_id].val, true);
758
+ }
759
+ delete this.pulseList[id];
760
+ }
761
+ }
762
+ }
763
+ else {
764
+ this.adapter.setTimeout(() => {
765
+ const _id = id.substring(this.adapter.namespace.length + 1);
766
+ void this.adapter.setState(id, this.ackObjects[_id] ? this.ackObjects[_id].val : null, true, err =>
767
+ // analyse if the state could be set (because of permissions)
768
+ err && this.adapter.log.error(`Can not set state ${id}: ${err}`));
769
+ }, 0);
770
+ }
771
+ }
772
+ start() {
773
+ if (this.modbusClient && typeof this.modbusClient.connect === 'function') {
774
+ try {
775
+ this.modbusClient.connect();
776
+ }
777
+ catch (e) {
778
+ this.adapter.log.error(`Can not open Modbus connection: ${e} . Please check your settings`);
779
+ }
780
+ }
781
+ }
782
+ close() {
783
+ this.isStop = true;
784
+ if (this.reconnectTimeout) {
785
+ this.adapter.clearTimeout(this.reconnectTimeout);
786
+ this.reconnectTimeout = null;
787
+ }
788
+ if (this.connectTimer) {
789
+ this.adapter.clearTimeout(this.connectTimer);
790
+ this.connectTimer = null;
791
+ }
792
+ if (this.keepAliveTimeout) {
793
+ this.adapter.clearTimeout(this.keepAliveTimeout);
794
+ this.keepAliveTimeout = null;
795
+ }
796
+ if (this.nextPoll) {
797
+ this.adapter.clearTimeout(this.nextPoll);
798
+ this.nextPoll = null;
799
+ }
800
+ if (this.modbusClient) {
801
+ try {
802
+ this.modbusClient.close();
803
+ }
804
+ catch {
805
+ // ignore
806
+ }
807
+ }
808
+ }
809
+ }
810
+ exports.Master = Master;
811
+ //# sourceMappingURL=Master.js.map