node-red-contrib-lorawan-bacnet-server 1.4.0 → 1.5.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.
@@ -7,10 +7,12 @@
7
7
  server: { value: '', type: 'bacnet-server', required: true },
8
8
  instanceOffset: { value: '1000' },
9
9
  autoDiscover: { value: true },
10
- outputOnWrite: { value: true }
10
+ outputOnWrite: { value: true },
11
+ writablePatterns: { value: 'setpoint,command,relay,valve,output,target,mode,enable,disable,on,off,speed,level,position' }
11
12
  },
12
13
  inputs: 1,
13
- outputs: 1,
14
+ outputs: 2,
15
+ outputLabels: ['uplink data', 'downlink commands'],
14
16
  icon: 'font-awesome/fa-exchange',
15
17
  label: function () {
16
18
  return this.name || 'lora-bacnet-bridge';
@@ -80,6 +82,12 @@
80
82
  <span>Emit message when BACnet client writes a value</span>
81
83
  </div>
82
84
 
85
+ <div class="form-row">
86
+ <label for="node-input-writablePatterns"><i class="fa fa-edit"></i> Writable Patterns</label>
87
+ <input type="text" id="node-input-writablePatterns" placeholder="setpoint,command,relay,valve,...">
88
+ <div class="form-tips">Comma-separated keywords. Points matching these patterns will be writable (for downlinks)</div>
89
+ </div>
90
+
83
91
  <div class="form-row">
84
92
  <label><i class="fa fa-list"></i> Discovered Devices</label>
85
93
  <button type="button" id="node-input-refresh-devices" class="red-ui-button">
@@ -131,15 +139,31 @@
131
139
  </dl>
132
140
 
133
141
  <h3>Outputs</h3>
142
+ <p><strong>Output 1 (Uplink Data):</strong></p>
134
143
  <dl class="message-properties">
135
144
  <dt>payload <span class="property-type">any</span></dt>
136
145
  <dd>Original payload passed through</dd>
137
146
 
138
147
  <dt>bacnetPoints <span class="property-type">array</span></dt>
139
148
  <dd>List of BACnet points created/updated</dd>
149
+ </dl>
140
150
 
151
+ <p><strong>Output 2 (Downlink Commands):</strong></p>
152
+ <dl class="message-properties">
141
153
  <dt>topic <span class="property-type">string</span></dt>
142
- <dd>For write events: bacnet/write/{deviceId}/{sensorKey}</dd>
154
+ <dd>lora/downlink/{deviceId}/{sensorKey}</dd>
155
+
156
+ <dt>payload <span class="property-type">any</span></dt>
157
+ <dd>Value written by BACnet client</dd>
158
+
159
+ <dt>deviceId <span class="property-type">string</span></dt>
160
+ <dd>Target LoRa device identifier</dd>
161
+
162
+ <dt>sensorKey <span class="property-type">string</span></dt>
163
+ <dd>Target sensor/actuator key</dd>
164
+
165
+ <dt>downlink <span class="property-type">object</span></dt>
166
+ <dd>Pre-formatted downlink data for Milesight/ChirpStack</dd>
143
167
  </dl>
144
168
 
145
169
  <h3>Details</h3>
@@ -168,12 +192,37 @@
168
192
  </ul>
169
193
  <p>Use this to trigger LoRa downlinks for actuators.</p>
170
194
 
171
- <h3>Example Flow</h3>
195
+ <h3>Writable Points (Downlinks)</h3>
196
+ <p>Points with names matching the "Writable Patterns" are automatically created as writable BACnet points. When a BACnet client writes to these points, a downlink message is emitted on Output 2.</p>
197
+ <p>Default writable patterns: setpoint, command, relay, valve, output, target, mode, enable, speed, level, position</p>
198
+
199
+ <h3>Example Flow - Bidirectional</h3>
172
200
  <pre>
173
- [MQTT LoRa] → [LoRaBAC] → [LoRa-BACnet Bridge]
174
-
175
- [BACnet Server]
176
-
177
- External BACnet clients
201
+ ┌─────────────────────────────────────────────────────────┐
202
+ │ UPLINK (sensors) │
203
+ [MQTT/LoRa] → [LoRa-BACnet Bridge] → [BACnet Server]
204
+ │ │ │
205
+ │ Output 1 │
206
+ └─────────────────────────────────────────────────────────┘
207
+
208
+ ┌─────────────────────────────────────────────────────────┐
209
+ │ DOWNLINK (actuators) │
210
+ │ [BACnet Client writes] → [LoRa-BACnet Bridge] │
211
+ │ │ │
212
+ │ Output 2 │
213
+ │ ↓ │
214
+ │ [MQTT Publish] → LoRa Device │
215
+ └─────────────────────────────────────────────────────────┘
216
+ </pre>
217
+
218
+ <h3>Use Case: HVAC Control</h3>
219
+ <pre>
220
+ Sensors (uplink):
221
+ - temperature → AV (read-only)
222
+ - humidity → AV (read-only)
223
+
224
+ Actuators (downlink):
225
+ - valve_setpoint → AV (writable) → LoRa downlink
226
+ - relay_command → BV (writable) → LoRa downlink
178
227
  </pre>
179
228
  </script>
@@ -43,6 +43,9 @@ module.exports = function (RED) {
43
43
  node.autoDiscover = config.autoDiscover !== false;
44
44
  node.outputOnWrite = config.outputOnWrite !== false;
45
45
 
46
+ // Patterns for writable (downlink) points
47
+ node.writablePatterns = (config.writablePatterns || 'setpoint,command,relay,valve,output,target,mode,enable,disable,on,off,speed,level,position').toLowerCase().split(',').map(p => p.trim());
48
+
46
49
  // Registry of discovered LoRa devices and their BACnet points
47
50
  // Key: deviceEUI or deviceName, Value: { points: Map, lastSeen: Date }
48
51
  node.loraDevices = new Map();
@@ -81,6 +84,12 @@ module.exports = function (RED) {
81
84
  return ObjectType.ANALOG_VALUE;
82
85
  }
83
86
 
87
+ // Determine if point should be writable (for downlink/actuators)
88
+ function isWritablePoint(key) {
89
+ const keyLower = key.toLowerCase();
90
+ return node.writablePatterns.some(pattern => keyLower.includes(pattern));
91
+ }
92
+
84
93
  // Determine units from key name
85
94
  function determineUnits(key) {
86
95
  const keyLower = key.toLowerCase();
@@ -119,6 +128,8 @@ module.exports = function (RED) {
119
128
 
120
129
  const objectName = `${deviceId}_${sensorKey}`.replace(/[^a-zA-Z0-9_-]/g, '_');
121
130
 
131
+ const writable = isWritablePoint(sensorKey);
132
+
122
133
  pointData = {
123
134
  objectType: objectType,
124
135
  instanceNumber: instanceNumber,
@@ -126,7 +137,7 @@ module.exports = function (RED) {
126
137
  sensorKey: sensorKey,
127
138
  deviceId: deviceId,
128
139
  units: determineUnits(sensorKey),
129
- writable: false, // LoRa uplink values are read-only
140
+ writable: writable,
130
141
  value: value
131
142
  };
132
143
 
@@ -147,19 +158,31 @@ module.exports = function (RED) {
147
158
  outputOnWrite: node.outputOnWrite,
148
159
  emitReadEvent: () => { },
149
160
  emitWriteEvent: (val, priority, sender) => {
150
- // Output write event
151
- node.send({
152
- topic: `bacnet/write/${deviceId}/${sensorKey}`,
161
+ // Output write event on second output (for downlink)
162
+ const downlinkMsg = {
163
+ topic: `lora/downlink/${deviceId}/${sensorKey}`,
153
164
  payload: val,
165
+ deviceId: deviceId,
166
+ sensorKey: sensorKey,
154
167
  bacnet: {
155
- deviceId: deviceId,
156
- sensorKey: sensorKey,
157
168
  objectType: objectType,
158
169
  instanceNumber: instanceNumber,
170
+ objectName: objectName,
159
171
  priority: priority,
160
172
  sender: sender
173
+ },
174
+ // For Milesight/ChirpStack compatibility
175
+ downlink: {
176
+ devEUI: deviceInfo?.devEUI || deviceId,
177
+ deviceName: deviceId,
178
+ confirmed: false,
179
+ fPort: 85,
180
+ data: val
161
181
  }
162
- });
182
+ };
183
+ // Send to second output [null, msg]
184
+ node.send([null, downlinkMsg]);
185
+ node.debug(`BACnet write -> LoRa downlink: ${deviceId}/${sensorKey} = ${val}`);
163
186
  }
164
187
  });
165
188
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-lorawan-bacnet-server",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Custom Node-RED nodes to interface LoRaWAN devices with BACnet protocol. ",
5
5
  "keywords": [
6
6
  "LoRaWAN",