@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
package/build/index.js ADDED
@@ -0,0 +1,1005 @@
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.tsv2registers = void 0;
7
+ const adapter_core_1 = require("@iobroker/adapter-core");
8
+ const node_path_1 = require("node:path");
9
+ const node_fs_1 = require("node:fs");
10
+ const convert_1 = __importDefault(require("./convert"));
11
+ exports.tsv2registers = convert_1.default;
12
+ const Master_1 = require("./lib/Master"); // Get common adapter utils
13
+ const Slave_1 = __importDefault(require("./lib/Slave")); // Get common adapter utils
14
+ let serialPortList = null;
15
+ function sortByAddress(a, b) {
16
+ const ad = parseFloat(a._address);
17
+ const bd = parseFloat(b._address);
18
+ return ad < bd ? -1 : ad > bd ? 1 : 0;
19
+ }
20
+ const defaultParams = {
21
+ type: 'tcp',
22
+ bind: '127.0.0.1',
23
+ port: 502,
24
+ comName: '',
25
+ baudRate: 9600,
26
+ dataBits: 8,
27
+ stopBits: 1,
28
+ parity: 'none',
29
+ deviceId: 1,
30
+ timeout: 5000,
31
+ slave: 0,
32
+ poll: 1000,
33
+ recon: 60000,
34
+ keepAliveInterval: 0,
35
+ maxBlock: 100,
36
+ maxBoolBlock: 128,
37
+ multiDeviceId: false,
38
+ pulseTime: 1000,
39
+ waitTime: 50,
40
+ disInputsOffset: 10001,
41
+ coilsOffset: 1,
42
+ inputRegsOffset: 30001,
43
+ holdingRegsOffset: 40001,
44
+ showAliases: true,
45
+ directAddresses: false,
46
+ doNotIncludeAdrInId: false,
47
+ preserveDotsInId: false,
48
+ round: 2,
49
+ doNotRoundAddressToWord: false,
50
+ doNotUseWriteMultipleRegisters: false,
51
+ onlyUseWriteMultipleRegisters: false,
52
+ writeInterval: 0,
53
+ readInterval: 0,
54
+ disableLogging: false,
55
+ sslEnabled: false,
56
+ sslCertPath: '',
57
+ sslKeyPath: '',
58
+ sslCaPath: '',
59
+ sslRejectUnauthorized: true,
60
+ };
61
+ class ModbusAdapter extends adapter_core_1.Adapter {
62
+ infoRegExp;
63
+ static _rmap = {
64
+ 0: 15,
65
+ 1: 14,
66
+ 2: 13,
67
+ 3: 12,
68
+ 4: 11,
69
+ 5: 10,
70
+ 6: 9,
71
+ 7: 8,
72
+ 8: 7,
73
+ 9: 6,
74
+ 10: 5,
75
+ 11: 4,
76
+ 12: 3,
77
+ 13: 2,
78
+ 14: 1,
79
+ 15: 0,
80
+ };
81
+ static _dmap = {
82
+ 0: 0,
83
+ 1: 1,
84
+ 2: 2,
85
+ 3: 3,
86
+ 4: 4,
87
+ 5: 5,
88
+ 6: 6,
89
+ 7: 7,
90
+ 8: 8,
91
+ 9: 9,
92
+ 10: 10,
93
+ 11: 11,
94
+ 12: 12,
95
+ 13: 13,
96
+ 14: 14,
97
+ 15: 15,
98
+ };
99
+ objects = {};
100
+ enumObjs = {};
101
+ static typeItemsLen = {
102
+ uint8be: 1,
103
+ uint8le: 1,
104
+ int8be: 1,
105
+ int8le: 1,
106
+ uint16be: 1,
107
+ uint16le: 1,
108
+ int16be: 1,
109
+ int16le: 1,
110
+ int16be1: 1,
111
+ int16le1: 1,
112
+ uint32be: 2,
113
+ uint32le: 2,
114
+ uint32sw: 2,
115
+ uint32sb: 2,
116
+ int32be: 2,
117
+ int32le: 2,
118
+ int32sw: 2,
119
+ int32sb: 2,
120
+ uint64be: 4,
121
+ uint64le: 4,
122
+ int64be: 4,
123
+ int64le: 4,
124
+ floatbe: 2,
125
+ floatle: 2,
126
+ floatsw: 2,
127
+ floatsb: 2,
128
+ doublebe: 4,
129
+ doublele: 4,
130
+ string: 0,
131
+ stringle: 0,
132
+ string16: 0,
133
+ string16le: 0,
134
+ rawhex: 0,
135
+ };
136
+ modbus = null;
137
+ constructor(adapterName, params, registers) {
138
+ super({
139
+ name: adapterName,
140
+ ready: () => {
141
+ // Merge configuration
142
+ this.config.params = {
143
+ ...defaultParams,
144
+ ...params,
145
+ ...this.config.params,
146
+ };
147
+ this.config.coils ||= registers.coils || [];
148
+ this.config.disInputs ||= registers.disInputs || [];
149
+ this.config.inputRegs ||= registers.inputRegs || [];
150
+ this.config.holdingRegs ||= registers.holdingRegs || [];
151
+ try {
152
+ import('serialport')
153
+ .then(s => {
154
+ serialPortList = s.SerialPort.list;
155
+ })
156
+ .catch(err => this.log.warn(`Serial is not available: ${err}`))
157
+ .finally(() => this.main());
158
+ }
159
+ catch (err) {
160
+ this.log.warn(`Serial is not available: ${err}`);
161
+ }
162
+ },
163
+ message: (obj) => this.processMessage(obj),
164
+ stateChange: async (id, state) => {
165
+ if (state && !state.ack && id && !this.infoRegExp.test(id)) {
166
+ if (!this.modbus) {
167
+ this.log.warn('No connection');
168
+ }
169
+ else {
170
+ this.log.debug(`state Changed ack=false: ${id}: ${JSON.stringify(state)}`);
171
+ if (!this.objects[id]) {
172
+ const obj = await this.getObjectAsync(id);
173
+ if (obj) {
174
+ this.objects[id] = obj;
175
+ }
176
+ }
177
+ if (this.objects[id]) {
178
+ this.modbus?.write(id, state).catch(err => this.log.error(err));
179
+ }
180
+ else {
181
+ this.log.warn(`State ${id} not found`);
182
+ }
183
+ }
184
+ }
185
+ },
186
+ unload: (callback) => this.stopAdapter(callback),
187
+ });
188
+ process.on('SIGINT', () => this.stopAdapter());
189
+ }
190
+ processMessage(obj) {
191
+ if (obj) {
192
+ switch (obj.command) {
193
+ case 'listUart':
194
+ if (obj.callback) {
195
+ if (serialPortList) {
196
+ // read all found serial ports
197
+ serialPortList()
198
+ .then(ports => {
199
+ const result = this.listSerial(ports);
200
+ this.log.info(`List of port: ${JSON.stringify(result)}`);
201
+ this.sendTo(obj.from, obj.command, result, obj.callback);
202
+ })
203
+ .catch((err) => {
204
+ this.log.warn(`Can not get Serial port list: ${err}`);
205
+ this.sendTo(obj.from, obj.command, [{ path: 'Not available' }], obj.callback);
206
+ });
207
+ }
208
+ else {
209
+ this.log.warn('Module serialport is not available');
210
+ this.sendTo(obj.from, obj.command, [{ path: 'Not available' }], obj.callback);
211
+ }
212
+ }
213
+ break;
214
+ }
215
+ }
216
+ }
217
+ stopAdapter(callback) {
218
+ if (this.modbus) {
219
+ this.modbus.close();
220
+ this.modbus = null;
221
+ }
222
+ if (this.setState && this.config?.params) {
223
+ void this.setState('info.connection', this.config.params.slave ? '' : false, true);
224
+ }
225
+ void this.getForeignStatesAsync(`${this.namespace}.info.clients.*`).then(async (allStates) => {
226
+ for (const id in allStates) {
227
+ if (allStates[id]?.val) {
228
+ await this.setStateAsync(id, false, true);
229
+ }
230
+ }
231
+ if (typeof callback === 'function') {
232
+ return void callback();
233
+ }
234
+ this.terminate ? this.terminate() : process.exit();
235
+ });
236
+ }
237
+ static filterSerialPorts(path) {
238
+ // get only serial port names
239
+ if (!/(tty(S|ACM|USB|AMA|MFD|XR)|rfcomm)/.test(path)) {
240
+ return false;
241
+ }
242
+ return (0, node_fs_1.statSync)(path).isCharacterDevice();
243
+ }
244
+ listSerial(ports) {
245
+ ports ||= [];
246
+ // Filter out the devices that aren't serial ports
247
+ const devDirName = '/dev';
248
+ let result;
249
+ try {
250
+ this.log.info(`Verify ${JSON.stringify(ports)}`);
251
+ result = (0, node_fs_1.readdirSync)(devDirName)
252
+ .map(file => (0, node_path_1.join)(devDirName, file))
253
+ .filter(path => ModbusAdapter.filterSerialPorts(path))
254
+ .map(port => {
255
+ if (!ports.find(p => p.path === port)) {
256
+ ports.push({ path: port });
257
+ }
258
+ return { path: port };
259
+ });
260
+ }
261
+ catch (e) {
262
+ if (require('node:os').platform() !== 'win32') {
263
+ this.log.error(`Cannot read "${devDirName}": ${e}`);
264
+ }
265
+ result = ports;
266
+ }
267
+ return result;
268
+ }
269
+ async addToEnum(enumName, id) {
270
+ const obj = await this.getForeignObjectAsync(enumName);
271
+ if (obj?.common?.members && !obj.common.members.includes(id)) {
272
+ obj.common.members.push(id);
273
+ obj.common.members.sort();
274
+ await this.setForeignObjectAsync(obj._id, obj);
275
+ }
276
+ }
277
+ async removeFromEnum(enumName, id) {
278
+ const obj = await this.getForeignObjectAsync(enumName);
279
+ if (obj?.common?.members) {
280
+ const pos = obj.common.members.indexOf(id);
281
+ if (pos !== -1) {
282
+ obj.common.members.splice(pos, 1);
283
+ await this.setForeignObjectAsync(obj._id, obj);
284
+ }
285
+ }
286
+ }
287
+ async syncEnums(enumGroup, id, newEnumName) {
288
+ if (!this.enumObjs[enumGroup]) {
289
+ const _enums = await this.getEnumAsync(enumGroup);
290
+ if (_enums) {
291
+ this.enumObjs[enumGroup] = _enums.result;
292
+ }
293
+ return;
294
+ }
295
+ // try to find this id in enums
296
+ let found = false;
297
+ for (const e in this.enumObjs[enumGroup]) {
298
+ if (Object.prototype.hasOwnProperty.call(this.enumObjs[enumGroup], e) &&
299
+ this.enumObjs[enumGroup][e].common?.members?.includes(id)) {
300
+ if (this.enumObjs[enumGroup][e]._id !== newEnumName) {
301
+ await this.removeFromEnum(this.enumObjs[enumGroup][e]._id, id);
302
+ }
303
+ else {
304
+ found = true;
305
+ }
306
+ }
307
+ }
308
+ if (!found && newEnumName) {
309
+ await this.addToEnum(newEnumName, id);
310
+ }
311
+ }
312
+ static address2alias(id, address, isDirect, offset) {
313
+ if (typeof address === 'string') {
314
+ address = parseInt(address, 10);
315
+ }
316
+ if (id === 'disInputs' || id === 'coils') {
317
+ address =
318
+ ((address >> 4) << 4) +
319
+ (isDirect ? ModbusAdapter._dmap[address % 16] : ModbusAdapter._rmap[address % 16]);
320
+ address += offset;
321
+ return address;
322
+ }
323
+ return address + offset;
324
+ }
325
+ async createExtendObject(id, objData) {
326
+ const oldObj = await this.getObjectAsync(id);
327
+ if (oldObj) {
328
+ await this.extendObjectAsync(id, objData);
329
+ }
330
+ else {
331
+ await this.setObjectNotExistsAsync(id, objData);
332
+ }
333
+ }
334
+ async processTasks(tasks) {
335
+ if (!tasks?.length) {
336
+ return;
337
+ }
338
+ for (const task of tasks) {
339
+ try {
340
+ if (task.name === 'add') {
341
+ await this.createExtendObject(task.id, task.obj);
342
+ }
343
+ else if (task.name === 'del') {
344
+ await this.delObjectAsync(task.id);
345
+ }
346
+ else if (task.name === 'syncEnums') {
347
+ await this.syncEnums('rooms', task.id, task.newName);
348
+ }
349
+ else {
350
+ this.log.error(`Unknown task: ${JSON.stringify(task)}`);
351
+ }
352
+ }
353
+ catch (err) {
354
+ this.log.info(`Can not execute task ${task.name} for ID ${task.id}: ${err.message}`);
355
+ }
356
+ }
357
+ }
358
+ async prepareConfig() {
359
+ const params = this.config.params;
360
+ const options = {
361
+ config: {
362
+ type: params.type || 'tcp',
363
+ slave: params.slave === '1',
364
+ alwaysUpdate: params.alwaysUpdate,
365
+ round: parseInt(params.round, 10) || 0,
366
+ timeout: parseInt(params.timeout, 10) || 5000,
367
+ defaultDeviceId: params.deviceId === undefined || params.deviceId === null
368
+ ? 1
369
+ : parseInt(params.deviceId, 10) || 0,
370
+ doNotIncludeAdrInId: params.doNotIncludeAdrInId === true || params.doNotIncludeAdrInId === 'true',
371
+ preserveDotsInId: params.preserveDotsInId === true || params.preserveDotsInId === 'true',
372
+ writeInterval: parseInt(params.writeInterval, 10) || 0,
373
+ doNotUseWriteMultipleRegisters: params.doNotUseWriteMultipleRegisters === true || params.doNotUseWriteMultipleRegisters === 'true',
374
+ onlyUseWriteMultipleRegisters: params.onlyUseWriteMultipleRegisters === true || params.onlyUseWriteMultipleRegisters === 'true',
375
+ },
376
+ devices: {},
377
+ objects: this.objects,
378
+ };
379
+ options.config.round = Math.pow(10, options.config.round);
380
+ if (!options.config.slave) {
381
+ options.config.multiDeviceId = params.multiDeviceId === true || params.multiDeviceId === 'true';
382
+ }
383
+ const deviceIds = [];
384
+ this.checkDeviceIds(options, this.config.disInputs, deviceIds);
385
+ this.checkDeviceIds(options, this.config.coils, deviceIds);
386
+ this.checkDeviceIds(options, this.config.inputRegs, deviceIds);
387
+ this.checkDeviceIds(options, this.config.holdingRegs, deviceIds);
388
+ deviceIds.sort((a, b) => a - b);
389
+ // settings for master
390
+ if (!options.config.slave) {
391
+ options.config.poll = parseInt(params.poll, 10) || 1000; // default is 1 second
392
+ options.config.recon = parseInt(params.recon, 10) || 60000;
393
+ if (options.config.recon < 1000) {
394
+ this.log.info(`Slave Reconnect time set to 1000ms because was too small (${options.config.recon})`);
395
+ options.config.recon = 1000;
396
+ }
397
+ options.config.maxBlock = parseInt(params.maxBlock, 10) || 100;
398
+ options.config.maxBoolBlock = parseInt(params.maxBoolBlock, 10) || 128;
399
+ options.config.pulseTime = parseInt(params.pulseTime) || 1000;
400
+ options.config.waitTime = params.waitTime === undefined ? 50 : parseInt(params.waitTime, 10) || 0;
401
+ options.config.readInterval = parseInt(params.readInterval, 10) || 0;
402
+ options.config.keepAliveInterval = parseInt(params.keepAliveInterval, 10) || 0;
403
+ }
404
+ options.config.disableLogging = params.disableLogging;
405
+ if (params.type === 'tcp' || params.type === 'tcprtu' || params.type === 'tcp-ssl') {
406
+ options.config.tcp = {
407
+ port: parseInt(params.port, 10) || 502,
408
+ bind: params.bind,
409
+ };
410
+ // Add SSL configuration for tcp-ssl type
411
+ if (params.type === 'tcp-ssl') {
412
+ try {
413
+ const [certificates] = await this.getCertificatesAsync(this.config.params.certPublic, this.config.params.certPrivate, this.config.params.certChained);
414
+ options.config.ssl = {
415
+ rejectUnauthorized: !params.sslAllowSelfSigned,
416
+ key: certificates.key,
417
+ cert: certificates.cert,
418
+ ca: certificates.ca,
419
+ };
420
+ }
421
+ catch (err) {
422
+ this.log.error(`Cannot get certificates: ${err}`);
423
+ }
424
+ }
425
+ }
426
+ else {
427
+ options.config.serial = {
428
+ comName: params.comName,
429
+ baudRate: params.baudRate,
430
+ dataBits: parseInt(params.dataBits, 10),
431
+ stopBits: parseInt(params.stopBits, 10),
432
+ parity: params.parity,
433
+ };
434
+ }
435
+ for (let d = 0; d < deviceIds.length; d++) {
436
+ const deviceId = deviceIds[d];
437
+ if (options.config.slave) {
438
+ options.devices[deviceId] = {
439
+ disInputs: {
440
+ fullIds: [],
441
+ changed: true,
442
+ addressLow: 0,
443
+ addressHigh: 0,
444
+ length: 0,
445
+ config: [],
446
+ values: [],
447
+ mapping: {},
448
+ offset: parseInt(params.disInputsOffset, 10),
449
+ },
450
+ coils: {
451
+ fullIds: [],
452
+ changed: true,
453
+ addressLow: 0,
454
+ addressHigh: 0,
455
+ length: 0,
456
+ config: [],
457
+ values: [],
458
+ mapping: {},
459
+ offset: parseInt(params.coilsOffset, 10),
460
+ },
461
+ inputRegs: {
462
+ fullIds: [],
463
+ config: [],
464
+ changed: true,
465
+ addressLow: 0,
466
+ addressHigh: 0,
467
+ length: 0,
468
+ values: [],
469
+ mapping: {},
470
+ offset: parseInt(params.inputRegsOffset, 10),
471
+ },
472
+ holdingRegs: {
473
+ fullIds: [],
474
+ config: [],
475
+ changed: true,
476
+ addressLow: 0,
477
+ addressHigh: 0,
478
+ length: 0,
479
+ values: [],
480
+ mapping: {},
481
+ offset: parseInt(params.holdingRegsOffset, 10),
482
+ },
483
+ };
484
+ }
485
+ else {
486
+ options.devices[deviceId] = {
487
+ disInputs: {
488
+ fullIds: [],
489
+ deviceId,
490
+ addressLow: 0,
491
+ addressHigh: 0,
492
+ length: 0,
493
+ config: [],
494
+ blocks: [],
495
+ offset: parseInt(params.disInputsOffset, 10),
496
+ },
497
+ coils: {
498
+ fullIds: [],
499
+ deviceId,
500
+ addressLow: 0,
501
+ addressHigh: 0,
502
+ length: 0,
503
+ config: [],
504
+ blocks: [],
505
+ cyclicWrite: [], // only holdingRegs and coils
506
+ offset: parseInt(params.coilsOffset, 10),
507
+ },
508
+ inputRegs: {
509
+ fullIds: [],
510
+ deviceId,
511
+ addressLow: 0,
512
+ addressHigh: 0,
513
+ length: 0,
514
+ config: [],
515
+ blocks: [],
516
+ offset: parseInt(params.inputRegsOffset, 10),
517
+ },
518
+ holdingRegs: {
519
+ fullIds: [],
520
+ deviceId,
521
+ addressLow: 0,
522
+ addressHigh: 0,
523
+ length: 0,
524
+ config: [],
525
+ blocks: [],
526
+ cyclicWrite: [], // only holdingRegs and coils
527
+ offset: parseInt(params.holdingRegsOffset, 10),
528
+ },
529
+ };
530
+ }
531
+ }
532
+ return options;
533
+ }
534
+ checkDeviceIds(options, config, deviceIds) {
535
+ for (let i = config.length - 1; i >= 0; i--) {
536
+ config[i].deviceId = !options.config.multiDeviceId
537
+ ? options.config.defaultDeviceId
538
+ : config[i].deviceId !== undefined
539
+ ? parseInt(config[i].deviceId, 10)
540
+ : options.config.defaultDeviceId;
541
+ if (isNaN(config[i].deviceId)) {
542
+ config[i].deviceId = options.config.defaultDeviceId;
543
+ }
544
+ if (!deviceIds.includes(config[i].deviceId)) {
545
+ deviceIds.push(config[i].deviceId);
546
+ }
547
+ }
548
+ }
549
+ checkObjects(regType, regName, regFullName, tasks, newObjects, deviceId) {
550
+ const regs = this.config[regType];
551
+ this.log.debug(`Initialize Objects for ${regType}: ${JSON.stringify(regs)}`);
552
+ for (let i = 0; regs.length > i; i++) {
553
+ if (regs[i].deviceId !== deviceId) {
554
+ continue;
555
+ }
556
+ const id = `${this.namespace}.${regs[i].id || i}`;
557
+ regs[i].fullId = id;
558
+ this.objects[id] = {
559
+ _id: regs[i].id,
560
+ type: 'state',
561
+ common: {
562
+ name: regs[i].description || '',
563
+ role: regs[i].role || '',
564
+ type: regType === 'coils' || regType === 'disInputs'
565
+ ? 'boolean'
566
+ : ['string', 'stringle', 'string16', 'string16le', 'rawhex'].includes(regs[i].type)
567
+ ? 'string'
568
+ : 'number',
569
+ read: true,
570
+ write: !!this.config.params.slave || regType === 'coils' || regType === 'holdingRegs',
571
+ def: regType === 'coils' || regType === 'disInputs'
572
+ ? false
573
+ : ['string', 'stringle', 'string16', 'string16le', 'rawhex'].includes(regs[i].type)
574
+ ? ''
575
+ : 0,
576
+ },
577
+ native: {
578
+ regType: regType,
579
+ address: regs[i].address,
580
+ deviceId: regs[i].deviceId,
581
+ },
582
+ };
583
+ if (this.objects[id]) {
584
+ if (regType === 'coils') {
585
+ this.objects[id].native.poll = regs[i].poll;
586
+ this.objects[id].common.read = !!regs[i].poll;
587
+ this.objects[id].native.wp = !!regs[i].wp;
588
+ }
589
+ else if (regType === 'inputRegs' || regType === 'holdingRegs') {
590
+ this.objects[id].common.unit = regs[i].unit || '';
591
+ this.objects[id].native.type = regs[i].type;
592
+ this.objects[id].native.len = regs[i].len;
593
+ this.objects[id].native.offset = regs[i].offset;
594
+ this.objects[id].native.factor = regs[i].factor;
595
+ if (regType === 'holdingRegs') {
596
+ this.objects[id].native.poll = regs[i].poll;
597
+ this.objects[id].common.read = !!regs[i].poll;
598
+ }
599
+ }
600
+ }
601
+ if (!regs[i].id) {
602
+ this.log.error(`Invalid data ${regName}/${i}: ${JSON.stringify(regs[i])}`);
603
+ this.log.error(`Invalid object: ${JSON.stringify(this.objects[id])}`);
604
+ }
605
+ tasks.push({
606
+ id: regs[i].id,
607
+ name: 'add',
608
+ obj: this.objects[id],
609
+ });
610
+ tasks.push({
611
+ id,
612
+ name: 'syncEnums',
613
+ newName: regs[i].room || '',
614
+ });
615
+ newObjects.push(id);
616
+ this.log.debug(`Add ${regs[i].id}: ${JSON.stringify(this.objects[id])}`);
617
+ }
618
+ if (regs.length) {
619
+ tasks.push({
620
+ id: regName,
621
+ name: 'add',
622
+ obj: {
623
+ type: 'channel',
624
+ common: {
625
+ name: regFullName,
626
+ },
627
+ native: {},
628
+ },
629
+ });
630
+ }
631
+ }
632
+ assignIds(deviceId, config, result, regName, regType, localOptions) {
633
+ for (let i = config.length - 1; i >= 0; i--) {
634
+ if (config[i].deviceId !== deviceId) {
635
+ continue;
636
+ }
637
+ if (config[i].address === undefined && config[i]._address !== undefined) {
638
+ if (localOptions.showAliases) {
639
+ if (config[i]._address >= result.offset) {
640
+ config[i].address = config[i]._address - result.offset;
641
+ if (localOptions.directAddresses && (regType === 'disInputs' || regType === 'coils')) {
642
+ const address = config[i].address;
643
+ config[i].address = ((address >> 4) << 4) + ModbusAdapter._dmap[address % 16];
644
+ }
645
+ }
646
+ }
647
+ else {
648
+ config[i].address = config[i]._address;
649
+ }
650
+ }
651
+ config[i].address = parseInt(config[i].address, 10);
652
+ const address = config[i].address;
653
+ if (address < 0) {
654
+ continue;
655
+ }
656
+ if (localOptions.multiDeviceId) {
657
+ config[i].id = `${regName}.${deviceId}.`;
658
+ }
659
+ else {
660
+ config[i].id = `${regName}.`;
661
+ }
662
+ if (localOptions.showAliases) {
663
+ config[i].id += ModbusAdapter.address2alias(regType, address, localOptions.directAddresses, result.offset);
664
+ }
665
+ else if (!localOptions.doNotIncludeAdrInId || !config[i].name) {
666
+ // add address if not disabled or name not empty
667
+ config[i].id += address;
668
+ if (localOptions.preserveDotsInId) {
669
+ config[i].id += '_';
670
+ }
671
+ }
672
+ if (localOptions.preserveDotsInId) {
673
+ // preserve dots in name and add to ID
674
+ config[i].id += config[i].name ? config[i].name.replace(/\s/g, '_') : '';
675
+ }
676
+ else {
677
+ // replace dots by underlines and add to ID
678
+ if (localOptions.doNotIncludeAdrInId) {
679
+ // It must be so, because of the bug https://github.com/ioBroker/ioBroker.modbus/issues/473
680
+ // config[i].id += config[i].name ? config[i].name.replace(/\./g, '_').replace(/\s/g, '_') : '';
681
+ // But because of breaking change
682
+ config[i].id += config[i].name ? `_${config[i].name.replace(/\./g, '_').replace(/\s/g, '_')}` : '';
683
+ }
684
+ else {
685
+ config[i].id += config[i].name ? `_${config[i].name.replace(/\./g, '_').replace(/\s/g, '_')}` : '';
686
+ }
687
+ }
688
+ if (config[i].id.endsWith('.')) {
689
+ config[i].id += config[i].id.substring(0, config[i].id.length - 1);
690
+ }
691
+ }
692
+ }
693
+ // localOptions = {
694
+ // multiDeviceId
695
+ // showAliases
696
+ // doNotRoundAddressToWord
697
+ // directAddresses
698
+ // isSlave
699
+ // maxBlock
700
+ // maxBoolBlock
701
+ // };
702
+ iterateAddresses(isBools, deviceId, result, regName, regType, localOptions) {
703
+ const config = result.config;
704
+ if (config?.length) {
705
+ result.addressLow = 0xffffffff;
706
+ result.addressHigh = 0;
707
+ for (let i = config.length - 1; i >= 0; i--) {
708
+ if (config[i].deviceId !== deviceId) {
709
+ continue;
710
+ }
711
+ config[i].address = parseInt(config[i].address, 10);
712
+ const address = config[i].address;
713
+ if (address < 0) {
714
+ this.log.error(`Invalid ${regName} address: ${address}`);
715
+ config.splice(i, 1);
716
+ continue;
717
+ }
718
+ if (!isBools) {
719
+ config[i].type ||= 'uint16be';
720
+ let offset = config[i].offset;
721
+ if (typeof offset === 'string') {
722
+ offset = offset.replace(',', '.');
723
+ config[i].offset = parseFloat(offset) || 0;
724
+ }
725
+ else if (typeof offset !== 'number') {
726
+ config[i].offset = 0;
727
+ }
728
+ else {
729
+ config[i].offset = offset || 0;
730
+ }
731
+ let factor = config[i].factor;
732
+ if (typeof factor === 'string') {
733
+ factor = factor.replace(',', '.');
734
+ config[i].factor = parseFloat(factor) || 1;
735
+ }
736
+ else if (typeof factor !== 'number') {
737
+ config[i].factor = 1;
738
+ }
739
+ else {
740
+ config[i].factor = factor || 1;
741
+ }
742
+ if (['string', 'stringle', 'string16', 'string16le', 'rawhex'].includes(config[i].type)) {
743
+ config[i].len = parseInt(config[i].len, 10) || 1;
744
+ }
745
+ else {
746
+ config[i].len = ModbusAdapter.typeItemsLen[config[i].type];
747
+ }
748
+ config[i].len ||= 1;
749
+ }
750
+ else {
751
+ config[i].len = 1;
752
+ }
753
+ // collect cyclic write registers
754
+ if (config[i].cw && Array.isArray(result.cyclicWrite)) {
755
+ result.cyclicWrite.push(`${this.namespace}.${config[i].id}`);
756
+ }
757
+ if (address < result.addressLow) {
758
+ result.addressLow = address;
759
+ }
760
+ if (address + config[i].len > result.addressHigh) {
761
+ result.addressHigh = address + config[i].len;
762
+ }
763
+ }
764
+ const maxBlock = isBools ? localOptions.maxBoolBlock : localOptions.maxBlock;
765
+ let lastAddress = null;
766
+ let startIndex = 0;
767
+ let blockStart = 0;
768
+ let i;
769
+ for (i = 0; i < config.length; i++) {
770
+ if (config[i].deviceId !== deviceId) {
771
+ continue;
772
+ }
773
+ if (lastAddress === null) {
774
+ startIndex = i;
775
+ blockStart = config[i].address;
776
+ lastAddress = blockStart + config[i].len;
777
+ }
778
+ // try to detect the next block
779
+ if (result.blocks) {
780
+ const blocks = result.blocks;
781
+ const wouldExceedLimit = config[i].address + config[i].len - blockStart > maxBlock;
782
+ const hasAddressGap = config[i].address - lastAddress > 10 && config[i].len < 10;
783
+ if (hasAddressGap || wouldExceedLimit) {
784
+ if (!blocks.map(obj => obj.start).includes(blockStart)) {
785
+ blocks.push({
786
+ start: blockStart,
787
+ count: lastAddress - blockStart,
788
+ startIndex: startIndex,
789
+ endIndex: i,
790
+ });
791
+ }
792
+ blockStart = config[i].address;
793
+ startIndex = i;
794
+ }
795
+ }
796
+ lastAddress = config[i].address + config[i].len;
797
+ }
798
+ if (lastAddress &&
799
+ lastAddress - blockStart &&
800
+ result.blocks &&
801
+ !result.blocks.map(obj => obj.start).includes(blockStart)) {
802
+ result.blocks.push({
803
+ start: blockStart,
804
+ count: lastAddress - blockStart,
805
+ startIndex: startIndex,
806
+ endIndex: i,
807
+ });
808
+ }
809
+ if (config.length) {
810
+ result.length = result.addressHigh - result.addressLow;
811
+ if (isBools && !localOptions.doNotRoundAddressToWord) {
812
+ const oldStart = result.addressLow;
813
+ // align addresses to 16 bit. E.g. 30 => 16, 31 => 16, 32 => 32
814
+ result.addressLow = (result.addressLow >> 4) << 4;
815
+ // increase the length on the alignment if any
816
+ result.length += oldStart - result.addressLow;
817
+ // If the length is not a multiple of 16
818
+ if (result.length % 16) {
819
+ // then round it up to the next multiple of 16
820
+ result.length = ((result.length >> 4) + 1) << 4;
821
+ }
822
+ if (result.blocks) {
823
+ const blocks = result.blocks;
824
+ for (let b = 0; b < blocks.length; b++) {
825
+ const _oldStart = blocks[b].start;
826
+ // align addresses to 16 bit. E.g 30 => 16, 31 => 16, 32 => 32
827
+ blocks[b].start = (blocks[b].start >> 4) << 4;
828
+ // increase the length on the alignment if any
829
+ blocks[b].count += _oldStart - blocks[b].start;
830
+ if (blocks[b].count % 16) {
831
+ blocks[b].count = ((blocks[b].count >> 4) + 1) << 4;
832
+ }
833
+ }
834
+ }
835
+ }
836
+ }
837
+ else {
838
+ result.length = 0;
839
+ }
840
+ if (result.mapping) {
841
+ for (let i = 0; i < config.length; i++) {
842
+ this.log.debug(`Iterate ${regType} ${regName}: ${config[i].address - result.addressLow} = ${config[i].id}`);
843
+ result.mapping[config[i].address - result.addressLow] =
844
+ `${this.namespace}.${config[i].id}`;
845
+ }
846
+ }
847
+ }
848
+ }
849
+ async parseConfig() {
850
+ const options = await this.prepareConfig();
851
+ const params = this.config.params;
852
+ // not for master or slave
853
+ const localOptions = {
854
+ multiDeviceId: options.config.multiDeviceId,
855
+ showAliases: params.showAliases === true || params.showAliases === 'true',
856
+ doNotRoundAddressToWord: params.doNotRoundAddressToWord === true || params.doNotRoundAddressToWord === 'true',
857
+ directAddresses: params.directAddresses === true || params.directAddresses === 'true',
858
+ maxBlock: options.config.maxBlock,
859
+ maxBoolBlock: options.config.maxBoolBlock,
860
+ doNotIncludeAdrInId: params.doNotIncludeAdrInId === true || params.doNotIncludeAdrInId === 'true',
861
+ preserveDotsInId: params.preserveDotsInId === true || params.preserveDotsInId === 'true',
862
+ };
863
+ const oldObjects = await this.getForeignObjects(`${this.namespace}.*`);
864
+ const newObjects = [];
865
+ this.config.disInputs.sort(sortByAddress);
866
+ this.config.coils.sort(sortByAddress);
867
+ this.config.inputRegs.sort(sortByAddress);
868
+ this.config.holdingRegs.sort(sortByAddress);
869
+ const tasks = [];
870
+ for (const _deviceId in options.devices) {
871
+ if (!Object.prototype.hasOwnProperty.call(options.devices, _deviceId)) {
872
+ continue;
873
+ }
874
+ const device = options.devices[_deviceId];
875
+ const deviceId = parseInt(_deviceId, 10);
876
+ // Discrete inputs
877
+ this.assignIds(deviceId, this.config.disInputs, device.disInputs, 'discreteInputs', 'disInputs', localOptions);
878
+ this.assignIds(deviceId, this.config.coils, device.coils, 'coils', 'coils', localOptions);
879
+ this.assignIds(deviceId, this.config.inputRegs, device.inputRegs, 'inputRegisters', 'inputRegs', localOptions);
880
+ this.assignIds(deviceId, this.config.holdingRegs, device.holdingRegs, 'holdingRegisters', 'holdingRegs', localOptions);
881
+ device.disInputs.config = this.config.disInputs.filter(e => e.deviceId === deviceId);
882
+ device.coils.config = this.config.coils.filter(e => e.poll && e.deviceId === deviceId);
883
+ device.inputRegs.config = this.config.inputRegs.filter(e => e.deviceId === deviceId);
884
+ device.holdingRegs.config = this.config.holdingRegs.filter(e => e.poll && e.deviceId === deviceId);
885
+ // ----------- remember poll values --------------------------
886
+ if (!options.config.slave) {
887
+ tasks.push({
888
+ id: 'info.pollTime',
889
+ name: 'add',
890
+ obj: {
891
+ type: 'state',
892
+ common: {
893
+ name: 'Poll time',
894
+ type: 'number',
895
+ role: '',
896
+ write: false,
897
+ read: true,
898
+ def: 0,
899
+ unit: 'ms',
900
+ },
901
+ native: {},
902
+ },
903
+ });
904
+ newObjects.push(`${this.namespace}.info.pollTime`);
905
+ }
906
+ // Discrete inputs
907
+ this.iterateAddresses(true, deviceId, device.disInputs, 'discreteInputs', 'disInputs', localOptions);
908
+ this.iterateAddresses(true, deviceId, device.coils, 'coils', 'coils', localOptions);
909
+ this.iterateAddresses(false, deviceId, device.inputRegs, 'inputRegisters', 'inputRegs', localOptions);
910
+ this.iterateAddresses(false, deviceId, device.holdingRegs, 'holdingRegisters', 'holdingRegs', localOptions);
911
+ // ------------- create states and objects ----------------------------
912
+ this.checkObjects('disInputs', 'discreteInputs', 'Discrete inputs', tasks, newObjects, deviceId);
913
+ this.checkObjects('coils', 'coils', 'Coils', tasks, newObjects, deviceId);
914
+ this.checkObjects('inputRegs', 'inputRegisters', 'Input registers', tasks, newObjects, deviceId);
915
+ this.checkObjects('holdingRegs', 'holdingRegisters', 'Holding registers', tasks, newObjects, deviceId);
916
+ if (options.config.slave) {
917
+ device.disInputs.fullIds = this.config.disInputs
918
+ .filter(e => e.deviceId === deviceId)
919
+ .map(e => e.fullId);
920
+ device.coils.fullIds = this.config.coils
921
+ .filter(e => e.deviceId === deviceId)
922
+ .map(e => e.fullId);
923
+ device.inputRegs.fullIds = this.config.inputRegs
924
+ .filter(e => e.deviceId === deviceId)
925
+ .map(e => e.fullId);
926
+ device.holdingRegs.fullIds = this.config.holdingRegs
927
+ .filter(e => e.deviceId === deviceId)
928
+ .map(e => e.fullId);
929
+ }
930
+ if (!options.config.multiDeviceId) {
931
+ break;
932
+ }
933
+ }
934
+ tasks.push({
935
+ id: 'info',
936
+ name: 'add',
937
+ obj: {
938
+ type: 'channel',
939
+ common: {
940
+ name: 'info',
941
+ },
942
+ native: {},
943
+ },
944
+ });
945
+ // create/ update 'info.connection' object
946
+ let obj = await this.getObjectAsync('info.connection');
947
+ if (!obj) {
948
+ obj = {
949
+ type: 'state',
950
+ common: {
951
+ name: options.config.slave ? 'IPs of connected partners' : 'If connected to slave',
952
+ role: 'indicator.connected',
953
+ write: false,
954
+ read: true,
955
+ type: options.config.slave ? 'string' : 'boolean',
956
+ def: options.config.slave ? '' : false,
957
+ },
958
+ native: {},
959
+ };
960
+ await this.setObjectAsync('info.connection', obj);
961
+ }
962
+ else if (options.config.slave && obj.common.type !== 'string') {
963
+ obj.common.type = 'string';
964
+ obj.common.name = 'Connected masters';
965
+ obj.common.def = '';
966
+ await this.setObjectAsync('info.connection', obj);
967
+ }
968
+ else if (!options.config.slave && obj.common.type !== 'boolean') {
969
+ obj.common.type = 'boolean';
970
+ obj.common.name = 'If connected to slave';
971
+ obj.common.def = false;
972
+ await this.setObjectAsync('info.connection', obj);
973
+ }
974
+ await this.setStateAsync('info.connection', this.config.params.slave ? '' : false, true);
975
+ newObjects.push(`${this.namespace}.info.connection`);
976
+ // clear unused states
977
+ for (const id_ in oldObjects) {
978
+ if (Object.prototype.hasOwnProperty.call(oldObjects, id_) &&
979
+ !newObjects.includes(id_) &&
980
+ !id_.startsWith(`${this.namespace}.info.clients.`)) {
981
+ this.log.debug(`Remove old object ${id_}`);
982
+ tasks.push({
983
+ id: id_,
984
+ name: 'del',
985
+ });
986
+ }
987
+ }
988
+ await this.processTasks(tasks);
989
+ this.subscribeStates('*');
990
+ return options;
991
+ }
992
+ async main() {
993
+ this.infoRegExp = new RegExp(`${this.namespace.replace('.', '\\.')}\\.info\\.`);
994
+ const options = await this.parseConfig();
995
+ if (options.config.slave) {
996
+ this.modbus = new Slave_1.default(options, this);
997
+ }
998
+ else {
999
+ this.modbus = new Master_1.Master(options, this);
1000
+ }
1001
+ this.modbus.start();
1002
+ }
1003
+ }
1004
+ exports.default = ModbusAdapter;
1005
+ //# sourceMappingURL=index.js.map