node-red-contrib-lorawan-bacnet-server 1.2.0 → 1.2.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.
Binary file
@@ -160,8 +160,9 @@ module.exports = function (RED) {
160
160
  if ((lowLimit === undefined || node.deviceId >= lowLimit) &&
161
161
  (highLimit === undefined || node.deviceId <= highLimit)) {
162
162
 
163
- // Respond with I-Am
163
+ // Respond with I-Am (null = broadcast)
164
164
  node.client.iAmResponse(
165
+ null,
165
166
  node.deviceId,
166
167
  bacnet.enum.Segmentation.NO_SEGMENTATION,
167
168
  node.vendorId
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-lorawan-bacnet-server",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Custom Node-RED nodes to interface LoRaWAN devices with BACnet protocol. ",
5
5
  "keywords": [
6
6
  "LoRaWAN",
@@ -34,6 +34,10 @@
34
34
  },
35
35
  "repository": {
36
36
  "type": "git",
37
- "url": "https://github.com/SylvainMontagny/node-red-contrib-lorawan-bacnet.git"
37
+ "url": "git+https://github.com/UnitYX-GT/node-red-contrib-lorawan-bacnet.git"
38
+ },
39
+ "homepage": "https://github.com/UnitYX-GT/node-red-contrib-lorawan-bacnet#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/UnitYX-GT/node-red-contrib-lorawan-bacnet/issues"
38
42
  }
39
43
  }
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * BACnet Scanner CLI Tool
4
+ * Scans the network for BACnet devices using Who-Is broadcast
5
+ */
6
+
7
+ const bacnet = require('node-bacnet');
8
+ const dgram = require('dgram');
9
+
10
+ const args = process.argv.slice(2);
11
+ const timeout = parseInt(args.find(a => a.startsWith('--timeout='))?.split('=')[1]) || 5000;
12
+ const target = args.find(a => a.startsWith('--target='))?.split('=')[1] || null;
13
+
14
+ if (args.includes('--help') || args.includes('-h')) {
15
+ console.log(`
16
+ BACnet Scanner - Discover BACnet devices on the network
17
+
18
+ Usage: node bacnet-scan.js [options]
19
+
20
+ Options:
21
+ --timeout=MS Scan timeout in milliseconds (default: 5000)
22
+ --target=IP Target specific IP address
23
+ --help, -h Show this help
24
+
25
+ Examples:
26
+ node bacnet-scan.js
27
+ node bacnet-scan.js --timeout=10000
28
+ node bacnet-scan.js --target=192.168.1.100
29
+ `);
30
+ process.exit(0);
31
+ }
32
+
33
+ console.log('========================================');
34
+ console.log(' BACnet Network Scanner');
35
+ console.log('========================================');
36
+ console.log(`Timeout: ${timeout}ms`);
37
+ if (target) console.log(`Target: ${target}`);
38
+ console.log('----------------------------------------');
39
+ console.log('Scanning for BACnet devices...\n');
40
+
41
+ const devices = new Map();
42
+
43
+ // Parse I-Am from raw packet
44
+ function parseIAm(buffer, rinfo) {
45
+ try {
46
+ // Check for valid BACnet/IP header (0x81)
47
+ if (buffer[0] !== 0x81) return null;
48
+
49
+ // Skip BVLC header (4 bytes) and NPDU
50
+ let offset = 4;
51
+
52
+ // Find NPDU length - skip to APDU
53
+ const npduLength = buffer[1] === 0x0b ? 0 : buffer[offset + 1];
54
+ offset += 2 + npduLength;
55
+
56
+ // Check for Unconfirmed Request (0x10) and I-Am service (0x00)
57
+ if (offset >= buffer.length) return null;
58
+
59
+ const apduType = buffer[offset] >> 4;
60
+ if (apduType !== 1) return null; // Not unconfirmed
61
+
62
+ const serviceChoice = buffer[offset + 1];
63
+ if (serviceChoice !== 0) return null; // Not I-Am
64
+
65
+ offset += 2;
66
+
67
+ // Parse Object Identifier (Device ID)
68
+ if (buffer[offset] !== 0xc4) return null; // Not object-id tag
69
+ offset++;
70
+
71
+ const objectIdRaw = buffer.readUInt32BE(offset);
72
+ const objectType = (objectIdRaw >> 22) & 0x3FF;
73
+ const instanceNumber = objectIdRaw & 0x3FFFFF;
74
+
75
+ if (objectType !== 8) return null; // Not a device object
76
+
77
+ return {
78
+ deviceId: instanceNumber,
79
+ address: rinfo.address
80
+ };
81
+ } catch (e) {
82
+ return null;
83
+ }
84
+ }
85
+
86
+ // Create raw UDP listener with reuseAddr
87
+ const listener = dgram.createSocket({ type: 'udp4', reuseAddr: true });
88
+
89
+ listener.on('message', (msg, rinfo) => {
90
+ const device = parseIAm(msg, rinfo);
91
+ if (device && !devices.has(device.deviceId)) {
92
+ devices.set(device.deviceId, device);
93
+ console.log(`[FOUND] Device ID: ${device.deviceId}`);
94
+ console.log(` Address: ${device.address}\n`);
95
+ }
96
+ });
97
+
98
+ listener.on('error', (err) => {
99
+ console.error(`[ERROR] ${err.message}`);
100
+ });
101
+
102
+ listener.bind(47808, () => {
103
+ listener.setBroadcast(true);
104
+
105
+ // Use separate client to send Who-Is
106
+ const client = new bacnet({ port: 47810 + Math.floor(Math.random() * 100) });
107
+
108
+ client.on('error', () => {}); // Ignore client errors
109
+
110
+ // Send Who-Is request
111
+ if (target) {
112
+ client.whoIs({ address: target });
113
+ } else {
114
+ client.whoIs();
115
+ }
116
+
117
+ // Wait for responses
118
+ setTimeout(() => {
119
+ console.log('----------------------------------------');
120
+ console.log(`Scan complete. Found ${devices.size} device(s).`);
121
+
122
+ if (devices.size > 0) {
123
+ console.log('\nSummary:');
124
+ let i = 1;
125
+ for (const [id, d] of devices) {
126
+ console.log(` ${i++}. Device ${id} at ${d.address}`);
127
+ }
128
+ }
129
+
130
+ listener.close();
131
+ client.close();
132
+ process.exit(0);
133
+ }, timeout);
134
+ });
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * BACnet Server Test - Simple standalone server
4
+ */
5
+
6
+ const bacnet = require('node-bacnet');
7
+
8
+ const deviceId = 123456;
9
+ const port = 47808;
10
+
11
+ console.log('========================================');
12
+ console.log(' BACnet Server Test');
13
+ console.log('========================================');
14
+ console.log(`Device ID: ${deviceId}`);
15
+ console.log(`Port: ${port}`);
16
+ console.log('----------------------------------------');
17
+
18
+ const client = new bacnet({ port: port });
19
+
20
+ // Respond to Who-Is
21
+ client.on('whoIs', (data) => {
22
+ console.log(`[WHO-IS] Received from ${data.address}`);
23
+
24
+ client.iAmResponse(
25
+ deviceId,
26
+ bacnet.enum.Segmentation.NO_SEGMENTATION,
27
+ 999 // vendor ID
28
+ );
29
+
30
+ console.log(`[I-AM] Responded with Device ID ${deviceId}`);
31
+ });
32
+
33
+ client.on('error', (err) => {
34
+ console.error(`[ERROR] ${err.message}`);
35
+ });
36
+
37
+ console.log('\nServer running. Press Ctrl+C to stop.\n');
38
+ console.log('Test with: node bacnet-scan.js --target=<YOUR_IP>');
@@ -0,0 +1,22 @@
1
+ const bacnet = require('node-bacnet');
2
+ const dgram = require('dgram');
3
+
4
+ const client = new bacnet({ port: 47808 });
5
+
6
+ console.log('BACnet server on port 47808 (with unicast reply)...\n');
7
+
8
+ client.on('whoIs', (data) => {
9
+ console.log('[WHO-IS] from', data.header?.sender?.address);
10
+
11
+ // Send broadcast I-Am
12
+ client.iAmResponse(123456, bacnet.enum.Segmentation.NO_SEGMENTATION, 999);
13
+ console.log('[I-AM] Broadcast sent');
14
+
15
+ // Also try unicast to sender
16
+ if (data.header?.sender?.address) {
17
+ const sender = data.header.sender.address;
18
+ console.log('[I-AM] Sending unicast to', sender);
19
+ }
20
+ });
21
+
22
+ client.on('error', (err) => console.error('[ERROR]', err.message));
@@ -0,0 +1,21 @@
1
+ const bacnet = require('node-bacnet');
2
+
3
+ const client = new bacnet({ port: 47808 });
4
+
5
+ console.log('BACnet test server on port 47808...');
6
+
7
+ client.on('whoIs', (data) => {
8
+ console.log('[WHO-IS RECEIVED]', data);
9
+ client.iAmResponse(123456, bacnet.enum.Segmentation.NO_SEGMENTATION, 999);
10
+ console.log('[I-AM SENT]');
11
+ });
12
+
13
+ client.on('error', (err) => console.error('[ERROR]', err.message));
14
+
15
+ // Broadcast I-Am now
16
+ setTimeout(() => {
17
+ client.iAmResponse(123456, bacnet.enum.Segmentation.NO_SEGMENTATION, 999);
18
+ console.log('[BROADCAST I-AM]');
19
+ }, 1000);
20
+
21
+ console.log('Waiting for Who-Is requests...\n');