node-red-contrib-lorawan-bacnet-server 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of node-red-contrib-lorawan-bacnet-server might be problematic. Click here for more details.

@@ -0,0 +1,652 @@
1
+ module.exports = function (RED) {
2
+ function LoRaBAC(config) {
3
+ RED.nodes.createNode(this, config);
4
+ var node = this;
5
+ const flow = node.context().flow;
6
+
7
+ node.warn(config.deviceList); // Display in debug
8
+
9
+ let status = verifyDeviceList(config.deviceList);
10
+ displayStatus(node, status);
11
+ if (status.ok) {
12
+ if (config.globalConfig.protocol === "restAPIBacnet") {
13
+ generateHttpAuthentication(config.deviceList);
14
+ }
15
+ setupGlobalVariables(this, config);
16
+ }
17
+
18
+ node.on("input", function (msg) {
19
+ let device = createObject(this, msg);
20
+ const result = {
21
+ "device": device,
22
+ }
23
+ node.send(result);
24
+ });
25
+
26
+ }
27
+
28
+ // ==================================================
29
+ // ============== UTIL FUNCTIONS ====================
30
+ // ==================================================
31
+
32
+ function generateHttpAuthentication(deviceList) {
33
+ for (let device in deviceList) {
34
+ const buffer = Buffer.from(deviceList[device].controller.login + ':' + deviceList[device].controller.password);
35
+ deviceList[device].controller.httpAuthentication = "Basic " + buffer.toString('base64');
36
+ }
37
+ }
38
+
39
+ function displayStatus(node, status){
40
+ if (status.ok) {
41
+ node.status({ fill: "green", shape: "dot", text: "Configuration OK" });
42
+ } else {
43
+ let message = status.message;
44
+ delete status.ok
45
+ delete status.message
46
+ node.status({
47
+ fill: "red",
48
+ shape: "dot",
49
+ text: message || "Invalid configuration"
50
+ });
51
+ node.error(`Error in device list : ${message}`, status);
52
+ }
53
+ }
54
+
55
+
56
+ function setupGlobalVariables(node, config) {
57
+ const flow = node.context().flow;
58
+ const global = node.context().global;
59
+
60
+ global.set('g_deviceList', config.deviceList);
61
+
62
+ flow.set('g_httpRequestTimeOut', 5000);
63
+ flow.set('g_tts_topicDownlinkSuffix', "/down");
64
+ flow.set('g_tts_topicUplinkSuffix', "/up");
65
+ flow.set('g_chirp_topicDownlinkSuffix', "/command/down");
66
+ flow.set('g_chirp_topicUplinkSuffix', "/event/up");
67
+ flow.set('g_actility_topicDownlinkSuffix', "/downlink");
68
+ flow.set('g_actility_topicUplinkSuffix', "/uplink");
69
+ if (flow.get("g_previousValues") === undefined) {
70
+ flow.set("g_previousValues", {});
71
+ }
72
+
73
+ const debug = function (device, debugType, debugText) {
74
+ if (debugType == "forceOn") {
75
+ node.warn(debugText);
76
+ }
77
+ else if (device.controller.debug?.some(element => element === "all" || element === debugType)) {
78
+ node.warn(debugText);
79
+ }
80
+ else {
81
+ return;
82
+ }
83
+ }
84
+ flow.set("g_debug", debug);
85
+ }
86
+
87
+ function createObject(node, msg){
88
+ const flow = node.context().flow;
89
+ const global = node.context().global;
90
+
91
+ let deviceList = global.get('g_deviceList');
92
+ let networkServer;
93
+ let deviceName, deviceType, deviceNum, devEUI, topicDownlink;
94
+ let devicePayload = {};
95
+ let previousValues = flow.get("g_previousValues");
96
+
97
+ let topicUp = msg.topic;
98
+
99
+ // Guess the NetworkServer from the received frame
100
+ if (msg.payload.hasOwnProperty('deviceInfo')) networkServer = "chirpstack";
101
+ if (msg.payload.hasOwnProperty('end_device_ids')) networkServer = "tts";
102
+ if (msg.payload.hasOwnProperty('DevEUI_uplink')) networkServer = "actility";
103
+
104
+ // Reject messages from Actility :
105
+ if ('DevEUI_notification' in msg.payload || 'DevEUI_notification' in msg.payload) return null;
106
+ if ('DevEUI_downlink_Rejected' in msg.payload) {
107
+ node.error("Actility : Downlink Message Rejected");
108
+ return null;
109
+ }
110
+
111
+ //////////////////////////////////////////////////////////////////////////
112
+ // The Things Stack Network Server
113
+ /////////////////////////////////////////////////////////////////////////
114
+
115
+ if (networkServer == "tts") {
116
+ deviceName = msg.payload.end_device_ids.device_id;
117
+ topicDownlink = topicUp.replace(flow.get('g_tts_topicUplinkSuffix'), "") + flow.get('g_tts_topicDownlinkSuffix');
118
+ devEUI = msg.payload.end_device_ids.dev_eui;
119
+ if (!Object.keys(msg.payload.uplink_message).some(element => element == "decoded_payload")) {
120
+ node.error(deviceName + " : No payload decoder configured on the Network Server");
121
+ return null;
122
+ }
123
+ devicePayload = msg.payload.uplink_message.decoded_payload;
124
+ }
125
+
126
+
127
+ //////////////////////////////////////////////////////////////////////////
128
+ // Chirpstack Network Server
129
+ /////////////////////////////////////////////////////////////////////////
130
+
131
+ if (networkServer == "chirpstack") {
132
+ if (msg.payload.fPort == 0) return 0;
133
+ deviceName = msg.payload.deviceInfo.deviceName;
134
+ topicDownlink = topicUp.replace(flow.get('g_chirp_topicUplinkSuffix'), "") + flow.get('g_chirp_topicDownlinkSuffix');
135
+ devEUI = msg.payload.deviceInfo.devEui;
136
+ if (!Object.keys(msg.payload).some(element => element == "object")) {
137
+ node.error(deviceName + " : No payload decoder configured on the Network Server");
138
+ return null;
139
+ }
140
+ devicePayload = msg.payload.object;
141
+ }
142
+
143
+ //////////////////////////////////////////////////////////////////////////
144
+ // Actility Network Server
145
+ /////////////////////////////////////////////////////////////////////////
146
+
147
+ if (networkServer == "actility") {
148
+ deviceName = msg.payload.DevEUI_uplink.CustomerData.name;
149
+ topicDownlink = topicUp.replace(flow.get('g_actility_topicUplinkSuffix'), "") + flow.get('g_actility_topicDownlinkSuffix');
150
+ devEUI = msg.payload.DevEUI_uplink.DevEUI;
151
+ if (!Object.keys(msg.payload.DevEUI_uplink).some(element => element == "payload")) {
152
+ node.error(deviceName + " : No payload decoder configured on the Network Server");
153
+ return null;
154
+ }
155
+ devicePayload = msg.payload.DevEUI_uplink.payload;
156
+ }
157
+
158
+ //////////////////////////////////////////////////////////////////////////
159
+ // Checks
160
+ /////////////////////////////////////////////////////////////////////////
161
+ const match = deviceName.match(/^(.*)-(\d+)$/);
162
+ if (match) {
163
+ deviceType = match[1]; // The part before the last dash
164
+ deviceNum = parseInt(match[2], 10); // The number at the end, converted to an integer
165
+ }
166
+ else {
167
+ node.error("Error: Device Name (" + deviceName + ") does not respect *xxx - num* format",
168
+ {
169
+ errorType: "deviceName",
170
+ value: deviceName,
171
+ });
172
+ return null;
173
+ }
174
+
175
+ if ((deviceNum == 0)) {
176
+ node.error("Error: Device Num is 0 is not allowed (" + deviceName + ")",
177
+ {
178
+ errorType: "deviceName",
179
+ value: deviceName,
180
+ });
181
+ return null;
182
+ }
183
+
184
+ if (deviceList[deviceType] == undefined) {
185
+ node.error("Error: Device Type does not belong to the Device List (" + deviceName + ")",
186
+ {
187
+ errorType: "deviceName",
188
+ value: deviceName,
189
+ });
190
+ return null;
191
+ }
192
+
193
+ // Check deviceNum overflow
194
+ if (deviceNum > deviceList[deviceType].identity.maxDevNum) {
195
+ node.error("Error: Device number is too high (" + deviceName + ")",
196
+ {
197
+ errorType: "deviceName",
198
+ value: deviceName,
199
+ });
200
+ return null;
201
+ }
202
+
203
+ //////////////////////////////////////////////////////////////////////////
204
+ // Create a copy of the "deviceType" object of the "deviceList" structure
205
+ /////////////////////////////////////////////////////////////////////////
206
+ let device = JSON.parse(JSON.stringify(deviceList[deviceType]));
207
+
208
+ device.identity.deviceName = deviceName;
209
+ device.identity.deviceType = deviceType;
210
+ device.identity.deviceNum = deviceNum;
211
+ device.identity.devEUI = devEUI;
212
+ device.mqtt.topicDownlink = topicDownlink;
213
+
214
+ for (let object in device.bacnet.objects) {
215
+ // Update instanceNum
216
+ switch (device.bacnet.objects[object].assignementMode) {
217
+ case "manual":
218
+
219
+ break;
220
+ case "auto":
221
+ switch (device.bacnet.objects[object].objectType) {
222
+ case "analogValue":
223
+ device.bacnet.objects[object].instanceNum += device.bacnet.offsetAV + (device.bacnet.instanceRangeAV * deviceNum);
224
+ break;
225
+ case "binaryValue":
226
+ device.bacnet.objects[object].instanceNum += device.bacnet.offsetBV + (device.bacnet.instanceRangeBV * deviceNum);
227
+ break;
228
+ default:
229
+ node.error("Object type of " + object + " is unknown : " + device.bacnet.objects[object].objectType);
230
+ return null;
231
+
232
+ }
233
+ break;
234
+ default:
235
+
236
+ }
237
+
238
+ // Update objectName
239
+ device.bacnet.objects[object].objectName = deviceName + '-' + object + '-' + device.bacnet.objects[object].instanceNum;
240
+ // Update value
241
+ if (device.bacnet.objects[object].dataDirection == "uplink") {
242
+ let lorawanPayloadName = device.bacnet.objects[object].lorawanPayloadName;
243
+ let keys = lorawanPayloadName.split(/[\.\[\]]/).filter(key => key !== "");
244
+ let value = keys.reduce((accumulator, currentValue) => accumulator[currentValue], devicePayload);
245
+ device.bacnet.objects[object].value = value;
246
+ }
247
+ // Check value
248
+ if (device.bacnet.objects[object].value == undefined || typeof device.bacnet.objects[object].value == "object") {
249
+ node.error(`Device : ${device.identity.deviceName} - Object : ${object} - Wrong Payload decoder or Wrong Device description`);
250
+ return null;
251
+ }
252
+
253
+ if (device.controller.protocol == "bacnet") {
254
+ // "restAPIBacnet" and "bacnet" compatibility
255
+ switch (device.bacnet.objects[object].objectType) {
256
+ case "analogValue": device.bacnet.objects[object].objectType = 2; break;
257
+ case "binaryValue": device.bacnet.objects[object].objectType = 5; break;
258
+ }
259
+ // Keep only uplink payload in a new object
260
+ device.bacnet.uplinkKeys = Object.entries(device.bacnet.objects)
261
+ .filter(([key, obj]) => obj.dataDirection === "uplink")
262
+ .map(([key, obj]) => key);
263
+ }
264
+ }
265
+
266
+ // For debug
267
+ device.transmitTime = Date.now();
268
+
269
+ // For InfluxDB support
270
+ device.influxdb = {
271
+ "source": "uplink"
272
+ };
273
+ // To save previous values
274
+ if (!previousValues.hasOwnProperty(device.identity.deviceName)) {
275
+
276
+ previousValues[device.identity.deviceName] = RED.util.cloneMessage(device);
277
+
278
+ }
279
+
280
+ return device;
281
+ }
282
+
283
+ function verifyDeviceList(deviceList) {
284
+ const regexIP = /^(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}$/;
285
+
286
+
287
+
288
+ let objectInstanceArrayAVManual = [], objectInstanceArrayBVManual = [];
289
+ let deviceNameArray = [];
290
+
291
+ //#region ///// IP Check /////
292
+ for (let device in deviceList) {
293
+ const dev = deviceList[device];
294
+
295
+ if (!regexIP.test(dev?.controller?.ipAddress)){
296
+ return {
297
+ ok: false,
298
+ message: "Indvalid IP address",
299
+ value: dev.controller.ipAddress
300
+ };
301
+ }
302
+ }
303
+ //#endregion
304
+
305
+ //#region ///// Range Check /////
306
+ for (let device in deviceList) {
307
+ const dev = deviceList[device];
308
+ for (let object in dev.bacnet.objects) {
309
+ const obj = dev.bacnet.objects[object];
310
+ if (obj.dataDirection === "downlink") {
311
+
312
+ if (obj.downlinkStrategy === "onChangeOfThisValueWithinRange" || obj.downlinkStrategy === "compareToUplinkObjectWithinRange" ) {
313
+
314
+ if (obj.range.length !== 2 || obj.range[1] < obj.range[0]) {
315
+ return {
316
+ ok: false,
317
+ errorType: "deviceListBACnetObjectConfiguration",
318
+ message: `Invalid range configuration for object ${object} of device ${device}`,
319
+ device,
320
+ object,
321
+ property: "range",
322
+ value: obj.range
323
+ };
324
+ }
325
+ }
326
+ }
327
+ }
328
+ }
329
+
330
+ //#endregion
331
+
332
+ //#region ///// uplinkObjectToCompareWith Check /////
333
+ for (let device in deviceList) {
334
+
335
+ const dev = deviceList[device];
336
+ let uplinkToCompareObjectArray = [], uplinkObjectArray = [];
337
+
338
+ for (let object in dev.bacnet.objects) {
339
+ const obj = dev.bacnet.objects[object];
340
+
341
+ if (obj.dataDirection === "downlink") {
342
+
343
+ if (obj.downlinkStrategy === "compareToUplinkObject" || obj.downlinkStrategy === "compareToUplinkObjectWithinRange") {
344
+ uplinkToCompareObjectArray.push(obj.uplinkToCompareWith)
345
+ }
346
+
347
+ }else{
348
+
349
+ uplinkObjectArray.push(object);
350
+ }
351
+ }
352
+
353
+ uplinkToCompareObjectArray.forEach(element => {
354
+ if (uplinkObjectArray.some(uplink => uplink === element)){
355
+ return {
356
+ ok: false,
357
+ errorType: "deviceListBACnetConfiguration",
358
+ message: `${element} is not an uplink object for device ${device}`,
359
+ device,
360
+ property: "instanceNum",
361
+ value: dev.bacnet.instanceRangeBV
362
+ };
363
+ }
364
+ })
365
+ }
366
+ //#endregion
367
+
368
+ //#region ///// AssignementMode Check /////
369
+ for (let device in deviceList) {
370
+ const dev = deviceList[device];
371
+
372
+ let assignementMode = null;
373
+
374
+ for (let object in dev.bacnet.objects) {
375
+ const obj = dev.bacnet.objects[object];
376
+
377
+ if (assignementMode === null) {
378
+ assignementMode = obj.assignementMode;
379
+ } else if (assignementMode !== obj.assignementMode) {
380
+ return {
381
+ ok: false,
382
+ errorType: "deviceListBACnetObjectConfiguration",
383
+ message: `Objects have different assignement modes for device ${device}`,
384
+ device,
385
+ object,
386
+ property: "assignementMode",
387
+ value: obj.assignementMode
388
+ };
389
+ }
390
+ }
391
+ }
392
+ //#endregion
393
+
394
+ //#region ///// AV InstanceNum Check /////
395
+ for (let device in deviceList) {
396
+
397
+ const dev = deviceList[device];
398
+
399
+ for (let object in dev.bacnet.objects) {
400
+ const obj = dev.bacnet.objects[object];
401
+
402
+ if (obj.objectType === "analogValue") {
403
+ if (obj.assignementMode !== "manual" && obj.instanceNum >= dev.bacnet.instanceRangeAV) {
404
+ return {
405
+ ok: false,
406
+ errorType: "deviceListBACnetObjectConfiguration",
407
+ message: `Analog instanceNum too high for object ${object} of device ${device}`,
408
+ device,
409
+ object,
410
+ property: "instanceNum",
411
+ value: obj.instanceNum
412
+ };
413
+ } else if (obj.assignementMode === "manual") {
414
+ objectInstanceArrayAVManual.push(obj.instanceNum);
415
+ }
416
+ }
417
+ }
418
+ }
419
+ //#endregion
420
+
421
+ //#region ///// BV InstanceNum Check /////
422
+ for (let device in deviceList) {
423
+
424
+ const dev = deviceList[device];
425
+
426
+ for (let object in dev.bacnet.objects) {
427
+ const obj = dev.bacnet.objects[object];
428
+
429
+ if (obj.objectType === "binaryValue") {
430
+ if (obj.assignementMode !== "manual" && obj.instanceNum >= dev.bacnet.instanceRangeBV) {
431
+ return {
432
+ ok: false,
433
+ errorType: "deviceListBACnetObjectConfiguration",
434
+ message: `Binary instanceNum too high for object ${object} of device ${device}`,
435
+ device,
436
+ object,
437
+ property: "instanceNum",
438
+ value: obj.instanceNum
439
+ };
440
+ } else if (obj.assignementMode === "manual") {
441
+ objectInstanceArrayBVManual.push(obj.instanceNum);
442
+ }
443
+ }
444
+ }
445
+ }
446
+ //#endregion
447
+
448
+ //#region ///// objects Name Check /////
449
+ for (let device in deviceList) {
450
+
451
+ const dev = deviceList[device];
452
+ let objectNameArray = []
453
+
454
+ for (let object in dev.bacnet.objects) {
455
+
456
+ if (objectNameArray.some( name => name === object)) {
457
+ return {
458
+ ok: false,
459
+ errorType: "deviceListBACnetConfiguration",
460
+ message: `Name already used: Object ${object} of device ${device}`,
461
+ device,
462
+ object,
463
+ value: object
464
+ };
465
+ }else{
466
+ objectNameArray.push(object);
467
+ }
468
+ }
469
+ }
470
+ //#endregion
471
+
472
+ //#region ///// device objects InstanceNum Check /////
473
+ for (let device in deviceList) {
474
+
475
+ const dev = deviceList[device];
476
+ let objectInstanceArrayAV = [], objectInstanceArrayBV = [];
477
+
478
+ for (let object in dev.bacnet.objects) {
479
+ const obj = dev.bacnet.objects[object];
480
+
481
+ if ((obj.objectType === "analogValue" && objectInstanceArrayAV.some( instanceNum => instanceNum === obj.instanceNum))||(obj.objectType === "binaryValue" && objectInstanceArrayBV.some( instanceNum => instanceNum === obj.instanceNum))) {
482
+ return {
483
+ ok: false,
484
+ errorType: "deviceListBACnetConfiguration",
485
+ message: `instanceNum already used: Object ${object} of device ${device}`,
486
+ device,
487
+ object,
488
+ property: "instanceNum",
489
+ value: dev.bacnet.instanceRangeBV
490
+ };
491
+ }else if (obj.objectType === "binaryValue") {
492
+ objectInstanceArrayBV.push(obj.instanceNum);
493
+ }else if (obj.objectType === "analogValue") {
494
+ objectInstanceArrayAV.push(obj.instanceNum);
495
+ }
496
+
497
+ }
498
+ }
499
+ //#endregion
500
+
501
+ //#region ///// device InstanceRangeAV Check /////
502
+ for (let device in deviceList) {
503
+
504
+ const dev = deviceList[device];
505
+ let instanceRangeAV = 0;
506
+
507
+ for (let object in dev.bacnet.objects) {
508
+ const obj = dev.bacnet.objects[object];
509
+
510
+ if (obj.objectType === "analogValue") {
511
+ instanceRangeAV++;
512
+ }
513
+ }
514
+
515
+ if (dev.bacnet.instanceRangeAV < instanceRangeAV) {
516
+ return {
517
+ ok: false,
518
+ errorType: "deviceListBACnetConfiguration",
519
+ message: `InstanceRangeAV too small for device ${device}`,
520
+ device,
521
+ property: "instanceRangeAV",
522
+ value: dev.bacnet.instanceRangeAV
523
+ };
524
+ }
525
+ }
526
+ //#endregion
527
+
528
+ //#region ///// device InstanceRangeBV Check /////
529
+ for (let device in deviceList) {
530
+
531
+ const dev = deviceList[device];
532
+ let instanceRangeBV = 0;
533
+
534
+ for (let object in dev.bacnet.objects) {
535
+ const obj = dev.bacnet.objects[object];
536
+
537
+ if (obj.objectType === "binaryValue") {
538
+ instanceRangeBV++;
539
+ }
540
+ }
541
+ if (dev.bacnet.instanceRangeBV < instanceRangeBV) {
542
+ return {
543
+ ok: false,
544
+ errorType: "deviceListBACnetConfiguration",
545
+ message: `InstanceRangeBV too small for device ${device}`,
546
+ device,
547
+ property: "instanceRangeBV",
548
+ value: dev.bacnet.instanceRangeBV
549
+ };
550
+ }
551
+ }
552
+ //#endregion
553
+
554
+ //#region ///// device Name Check /////
555
+ for (let device in deviceList) {
556
+
557
+ const dev = deviceList[device];
558
+
559
+ if (deviceNameArray.some( name => name === device)) {
560
+ return {
561
+ ok: false,
562
+ message: `name already used: Device ${device}`,
563
+ device,
564
+ value: device
565
+ };
566
+ }else{
567
+ deviceNameArray.push(device);
568
+ }
569
+ }
570
+ //#endregion
571
+
572
+ //#region ///// AV overlap Check /////
573
+ let objectInstanceArrayAV = []
574
+ for (let device in deviceList) {
575
+
576
+ const dev = deviceList[device];
577
+
578
+ objectInstanceArrayAV.push({ device, offset: dev.bacnet.offsetAV, instanceRange: dev.bacnet.instanceRangeAV, maxdevNum: dev.identity.maxDevNum });
579
+ }
580
+
581
+ objectInstanceArrayAV.sort((a, b) => a.offset - b.offset);
582
+
583
+ for (let i = 0; i < objectInstanceArrayAV.length - 1; i++) {
584
+ const current = objectInstanceArrayAV[i];
585
+ const next = objectInstanceArrayAV[i + 1];
586
+ if (current.offset + current.instanceRange * current.maxdevNum > next.offset) {
587
+ return {
588
+ ok: false,
589
+ errorType: "deviceListOverlap",
590
+ message: `Analog BACnet objects overlap for device ${current.device} and device ${next.device}`,
591
+ device1: current.device,
592
+ device2: next.device
593
+ };
594
+ }
595
+
596
+ for (let inst of objectInstanceArrayAVManual) {
597
+ if (inst >= current.offset && inst < current.offset + current.instanceRange * current.maxdevNum) {
598
+ return {
599
+ ok: false,
600
+ errorType: "deviceListOverlapManual",
601
+ message: `Manual analog object instance (${inst}) overlaps another device's range`,
602
+ device: current.device,
603
+ instanceNum: inst
604
+ };
605
+ }
606
+ }
607
+ }
608
+ //#endregion
609
+
610
+ //#region ///// BV overlap Check /////
611
+ let objectInstanceArrayBV = [];
612
+ for (let device in deviceList) {
613
+
614
+ const dev = deviceList[device];
615
+
616
+ objectInstanceArrayBV.push({ device, offset: dev.bacnet.offsetBV, instanceRange: dev.bacnet.instanceRangeBV, maxdevNum: dev.identity.maxDevNum });
617
+ }
618
+
619
+ objectInstanceArrayBV.sort((a, b) => a.offset - b.offset);
620
+
621
+ for (let i = 0; i < objectInstanceArrayBV.length - 1; i++) {
622
+ const current = objectInstanceArrayBV[i];
623
+ const next = objectInstanceArrayBV[i + 1];
624
+ if (current.offset + current.instanceRange * current.maxdevNum > next.offset) {
625
+ return {
626
+ ok: false,
627
+ errorType: "deviceListOverlap",
628
+ message: `Binary BACnet objects overlap for device ${current.device} and device ${next.device}`,
629
+ device1: current.device,
630
+ device2: next.device
631
+ };
632
+ }
633
+
634
+ for (let inst of objectInstanceArrayBVManual) {
635
+ if (inst >= current.offset && inst < current.offset + current.instanceRange * current.maxdevNum) {
636
+ return {
637
+ ok: false,
638
+ errorType: "deviceListOverlapManual",
639
+ message: "Manual binary object instance overlaps another device's range",
640
+ device: current.device,
641
+ instanceNum: inst
642
+ };
643
+ }
644
+ }
645
+ }
646
+ //#endregion
647
+ return { ok: true };
648
+ }
649
+
650
+
651
+ RED.nodes.registerType("LoRaBAC", LoRaBAC);
652
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "node-red-contrib-lorawan-bacnet-server",
3
+ "version": "1.2.0",
4
+ "description": "Custom Node-RED nodes to interface LoRaWAN devices with BACnet protocol. ",
5
+ "keywords": [
6
+ "LoRaWAN",
7
+ "LoRaBAC",
8
+ "LoRa",
9
+ "node-red",
10
+ "BACnet",
11
+ "BACnet Server",
12
+ "IoT"
13
+ ],
14
+ "license": "MIT",
15
+ "contributors": [
16
+ {
17
+ "name": "Elias CUZEAU",
18
+ "email": "elias.cuzeau@ikmail.com"
19
+ }
20
+ ],
21
+ "engines": {
22
+ "node": ">=16.0.0"
23
+ },
24
+ "dependencies": {
25
+ "node-bacnet": "^0.2.4"
26
+ },
27
+ "node-red": {
28
+ "version": ">=3.0.0",
29
+ "nodes": {
30
+ "lorabac": "nodes/lorabac/lorabac.js",
31
+ "bacnet-server": "nodes/bacnet-server/bacnet-server.js",
32
+ "bacnet-point": "nodes/bacnet-point/bacnet-point.js"
33
+ }
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/SylvainMontagny/node-red-contrib-lorawan-bacnet.git"
38
+ }
39
+ }