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.
|
Binary file
|
|
@@ -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:
|
|
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>
|
|
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>
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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:
|
|
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
|
-
|
|
152
|
-
topic: `
|
|
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
|
Binary file
|