@ncd-io/node-red-enterprise-sensors 2.0.2 → 2.0.3

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.
@@ -0,0 +1,478 @@
1
+ const { toMac, signInt, msbLsb } = require('../utils');
2
+
3
+ // --- 1. DEFINE LOCAL FUNCTIONS ---
4
+ // These are defined as local variables so they can call each other easily.
5
+ module.exports = (globalDevices) => {
6
+
7
+ const get_write_buffer_size = (firmware) => {
8
+ return 16;
9
+ };
10
+
11
+ const get_config_map = (firmware) => {
12
+ console.log('Generating sync map for firmware version', firmware);
13
+
14
+ return {
15
+ "core_version": {
16
+ "read_index": 3,
17
+ "descriptions": {
18
+ "title": "Core Version",
19
+ "main_caption": "The version of the core communication stack."
20
+ },
21
+ "validator": {
22
+ "type": "uint8"
23
+ },
24
+ "tags": [
25
+ "system"
26
+ ]
27
+ },
28
+ "firmware_version": {
29
+ "read_index": 4,
30
+ "descriptions": {
31
+ "title": "Firmware Version",
32
+ "main_caption": "The application-specific firmware version."
33
+ },
34
+ "validator": {
35
+ "type": "uint8"
36
+ },
37
+ "tags": [
38
+ "system"
39
+ ]
40
+ },
41
+ "sensor_type": {
42
+ "read_index": 5,
43
+ "descriptions": {
44
+ "title": "Sensor Type",
45
+ "main_caption": "The hardware identifier for the specific sensor model."
46
+ },
47
+ "validator": {
48
+ "type": "uint16be"
49
+ },
50
+ "tags": [
51
+ "system"
52
+ ]
53
+ },
54
+ "tx_lifetime_counter": {
55
+ "read_index": 7,
56
+ "descriptions": {
57
+ "title": "Sampling Interval",
58
+ "main_caption": "Set how often will the sensor transmit measurement data. Note: For this sensor, this value functions as the sampling interval rather than a traditional delay.",
59
+ "sub_caption": "Default value: 20 milliseconds."
60
+ },
61
+ "validator": {
62
+ "type": "uint32be"
63
+ },
64
+ "tags": [
65
+ "diagnostics"
66
+ ]
67
+ },
68
+ "hardware_id": {
69
+ "read_index": 11,
70
+ "length": 3,
71
+ "descriptions": {
72
+ "title": "Hardware ID",
73
+ "main_caption": "A unique 3-byte hardware identifier."
74
+ },
75
+ "validator": {
76
+ "type": "buffer"
77
+ },
78
+ "tags": [
79
+ "system"
80
+ ]
81
+ },
82
+ "network_id": {
83
+ "read_index": 14,
84
+ "write_index": 3,
85
+ "length": 2,
86
+ "descriptions": {
87
+ "title": "Network ID",
88
+ "main_caption": ""
89
+ },
90
+ "default_value": "7fff",
91
+ "validator": {
92
+ "type": "hex",
93
+ "length": 4
94
+ },
95
+ "html_id": "pan_id",
96
+ "tags": [
97
+ "communications"
98
+ ]
99
+ },
100
+ "destination_address": {
101
+ "read_index": 16,
102
+ "write_index": 5,
103
+ "length": 4,
104
+ "descriptions": {
105
+ "title": "Destination Address",
106
+ "main_caption": ""
107
+ },
108
+ "default_value": "0000ffff",
109
+ "validator": {
110
+ "type": "mac",
111
+ "length": 8
112
+ },
113
+ "html_id": "destination",
114
+ "tags": [
115
+ "communications"
116
+ ]
117
+ },
118
+ "node_id": {
119
+ "read_index": 20,
120
+ "write_index": 9,
121
+ "descriptions": {
122
+ "title": "Node ID",
123
+ "main_caption": ""
124
+ },
125
+ "default_value": "0",
126
+ "validator": {
127
+ "type": "uint8",
128
+ "min": 0,
129
+ "max": 255,
130
+ "generated": true
131
+ },
132
+ "html_id": "node_id",
133
+ "tags": [
134
+ "generic"
135
+ ]
136
+ },
137
+ "report_rate": {
138
+ "read_index": 21,
139
+ "write_index": 10,
140
+ "descriptions": {
141
+ "title": "Delay",
142
+ "main_caption": ""
143
+ },
144
+ "default_value": "600",
145
+ "validator": {
146
+ "type": "uint32be",
147
+ "min": 0,
148
+ "max": 65535,
149
+ "generated": true
150
+ },
151
+ "html_id": "delay",
152
+ "tags": [
153
+ "generic"
154
+ ]
155
+ },
156
+ "flow_unit": {
157
+ "read_index": 25,
158
+ "write_index": 14,
159
+ "descriptions": {
160
+ "title": "Set Flow Unit",
161
+ "main_caption": ""
162
+ },
163
+ "default_value": 0,
164
+ "validator": {
165
+ "type": "uint8",
166
+ "min": 0,
167
+ "max": 24,
168
+ "generated": true
169
+ },
170
+ "options": {
171
+ "0": "scf_m",
172
+ "1": "scf_h",
173
+ "2": "nm3_h",
174
+ "3": "nm3_m",
175
+ "4": "kg_h",
176
+ "5": "kg_m",
177
+ "6": "kg_s",
178
+ "7": "lbs_h",
179
+ "8": "lbs_m",
180
+ "9": "lbs_s",
181
+ "10": "nlp_h",
182
+ "11": "nlp_m",
183
+ "12": "mmscf_d",
184
+ "13": "lbs_d",
185
+ "14": "slp_m",
186
+ "15": "nlp_s",
187
+ "16": "mscf_d",
188
+ "17": "sm3_h",
189
+ "18": "mt_h",
190
+ "19": "nm3_d",
191
+ "20": "mmscf_m",
192
+ "21": "scf_d",
193
+ "22": "mcf_d",
194
+ "23": "sm3_m",
195
+ "24": "sm3_d"
196
+ },
197
+ "html_id": "flow_unit_545"
198
+ },
199
+ "temperature_unit": {
200
+ "read_index": 26,
201
+ "write_index": 15,
202
+ "descriptions": {
203
+ "title": "Set Temperature Unit",
204
+ "main_caption": ""
205
+ },
206
+ "default_value": 0,
207
+ "validator": {
208
+ "type": "uint8",
209
+ "min": 0,
210
+ "max": 1,
211
+ "generated": true
212
+ },
213
+ "options": {
214
+ "0": "Fahrenheit",
215
+ "1": "Celsius"
216
+ },
217
+ "html_id": "temperature_unit_545"
218
+ },
219
+ };
220
+ };
221
+
222
+ const sync_parse = (rep_buffer) => {
223
+ let response = {
224
+ 'human_readable': {},
225
+ 'machine_values': {}
226
+ };
227
+
228
+ // Get the map based on the sensor type byte
229
+ const sync_map = get_config_map(rep_buffer[4]);
230
+
231
+ for (const [key, config] of Object.entries(sync_map)) {
232
+ // Destructure 'type' from inside 'validator' and rename 'read_index' to 'idx'
233
+ const { read_index: idx, length, validator: { type } = {}, converter, options } = config;
234
+
235
+ // If for some reason a config doesn't have a validator/type, skip it
236
+ if (!type) continue;
237
+
238
+ switch (type) {
239
+ case 'uint8':
240
+ response.machine_values[key] = rep_buffer[idx];
241
+ break;
242
+ case 'uint16be':
243
+ response.machine_values[key] = rep_buffer.readUInt16BE(idx);
244
+ break;
245
+ case 'uint32be':
246
+ response.machine_values[key] = rep_buffer.readUInt32BE(idx);
247
+ break;
248
+ case 'buffer':
249
+ response.machine_values[key] = rep_buffer.subarray(idx, idx + length);
250
+ break;
251
+ case 'hex':
252
+ response.machine_values[key] = rep_buffer.subarray(idx, idx + length).toString('hex');
253
+ break;
254
+ case 'mac':
255
+ response.machine_values[key] = rep_buffer.subarray(idx, idx + length).toString('hex');
256
+ break;
257
+ }
258
+ let human_value = response.machine_values[key];
259
+ if(options && options[response.machine_values[key]]){
260
+ human_value = options[response.machine_values[key]];
261
+ }else{
262
+ if(converter && converter.multiplier){
263
+ human_value = human_value * converter.multiplier;
264
+ }
265
+ if(converter && converter.units){
266
+ human_value = human_value + converter.units;
267
+ }
268
+ }
269
+ response.human_readable[key] = human_value;
270
+ }
271
+ if (Object.hasOwn(response.machine_values, 'destination_address') && response.machine_values.destination_address.toLowerCase() === '00000000') {
272
+ console.log('##############################');
273
+ console.log('#########Dest Override########');
274
+ console.log('##############################');
275
+ response.destination_address = "0000ffff";
276
+ // response.auto_raw_destination_address = "0000ffff";
277
+ };
278
+ return response;
279
+ };
280
+
281
+ const parse_fly = (frame) => {
282
+ let frame_data = {};
283
+ switch(frame[12]){
284
+ case 0:
285
+ frame_data.flow_unit = 'scf_m';
286
+ break;
287
+ case 1:
288
+ frame_data.flow_unit = 'scf_h';
289
+ break;
290
+ case 2:
291
+ frame_data.flow_unit = 'nm3_h';
292
+ break;
293
+ case 3:
294
+ frame_data.flow_unit = 'nm3_m';
295
+ break;
296
+ case 4:
297
+ frame_data.flow_unit = 'kg_h';
298
+ break;
299
+ case 5:
300
+ frame_data.flow_unit = 'kg_m';
301
+ break;
302
+ case 6:
303
+ frame_data.flow_unit = 'kg_s';
304
+ break;
305
+ case 7:
306
+ frame_data.flow_unit = 'lbs_h';
307
+ break;
308
+ case 8:
309
+ frame_data.flow_unit = 'lbs_m';
310
+ break;
311
+ case 9:
312
+ frame_data.flow_unit = 'lbs_s';
313
+ break;
314
+ case 10:
315
+ frame_data.flow_unit = 'nlp_h';
316
+ break;
317
+ case 11:
318
+ frame_data.flow_unit = 'nlp_m';
319
+ break;
320
+ case 12:
321
+ frame_data.flow_unit = 'mmscf_d';
322
+ break;
323
+ case 13:
324
+ frame_data.flow_unit = 'lbs_d';
325
+ break;
326
+ case 14:
327
+ frame_data.flow_unit = 'slp_m';
328
+ break;
329
+ case 15:
330
+ frame_data.flow_unit = 'nlp_s';
331
+ break;
332
+ case 16:
333
+ frame_data.flow_unit = 'mscf_d';
334
+ break;
335
+ case 17:
336
+ frame_data.flow_unit = 'sm3_h';
337
+ break;
338
+ case 18:
339
+ frame_data.flow_unit = 'mt_h';
340
+ break;
341
+ case 19:
342
+ frame_data.flow_unit = 'nm3_d';
343
+ break;
344
+ case 20:
345
+ frame_data.flow_unit = 'mmscf_m';
346
+ break;
347
+ case 21:
348
+ frame_data.flow_unit = 'scf_d';
349
+ break;
350
+ case 22:
351
+ frame_data.flow_unit = 'mcf_d';
352
+ break;
353
+ case 23:
354
+ frame_data.flow_unit = 'sm3_m';
355
+ break;
356
+ case 24:
357
+ frame_data.flow_unit = 'sm3_d';
358
+ break;
359
+ }
360
+ switch(frame[13]){
361
+ case 0:
362
+ frame_data.temperature_unit = 'F';
363
+ break;
364
+ case 1:
365
+ frame_data.temperature_unit = 'C';
366
+ break;
367
+ }
368
+ return {
369
+ 'firmware': frame[2],
370
+ 'flow_unit': frame_data.flow_unit,
371
+ 'temperature_unit': frame_data.temperature_unit,
372
+ 'hardware_id': frame.slice(14, 17),
373
+ 'report_rate': frame.slice(17, 21).reduce(msbLsb),
374
+ 'tx_life_counter': frame.slice(21, 25).reduce(msbLsb),
375
+ 'machine_values': {
376
+ 'firmware': frame[2],
377
+ 'flow_unit': frame[12],
378
+ 'temperature_unit': frame[13],
379
+ 'hardware_id': frame.slice(14, 17),
380
+ 'report_rate': frame.slice(17, 21),
381
+ 'tx_life_counter': frame.slice(21, 25)
382
+ }
383
+ }
384
+ }
385
+
386
+ const parse = (payload, parsed, mac) => {
387
+ let firmware = payload[1];
388
+ let sensor_status = '';
389
+ const error_message = [];
390
+ if(payload[8] & 1){
391
+ error_message.push('pwr_up');
392
+ }
393
+ if(payload[8] & 2){
394
+ error_message.push('flw_hi_lim');
395
+ }
396
+ if(payload[8] & 4){
397
+ error_message.push('flw_lo_lim');
398
+ }
399
+ if(payload[8] & 8){
400
+ error_message.push('tmp_hi_lim');
401
+ }
402
+ if(payload[8] & 16){
403
+ error_message.push('tmp_lo_lim');
404
+ }
405
+ if(payload[8] & 32){
406
+ error_message.push('sensor_oor');
407
+ }
408
+ if(payload[8] & 64){
409
+ error_message.push('gas_mix_err');
410
+ }
411
+ if(payload[8] & 128){
412
+ error_message.push('inc_set');
413
+ }
414
+ if(error_message.length === 0){
415
+ sensor_status = 'ready';
416
+ } else {
417
+ sensor_status = error_message.join(', ');
418
+ }
419
+
420
+ const calv_value = payload.slice(9, 11).reduce(msbLsb) / 100;
421
+ let cal_val = '';
422
+ if(calv_value < 0.80){
423
+ cal_val = 'pass';
424
+ } else if(calv_value <= 1.0){
425
+ cal_val = 'warning';
426
+ } else {
427
+ cal_val = 'fail';
428
+ }
429
+
430
+ if(firmware > 1){
431
+ let read_status = payload[7] >> 1;
432
+ let sensor_read_status = '';
433
+ switch (read_status){
434
+ case 0: sensor_read_status = 'valid_data'; break;
435
+ case 2: sensor_read_status = 'invalid_data'; break;
436
+ }
437
+ return{
438
+ sensor_read: sensor_read_status,
439
+ sensor_status: sensor_status,
440
+ calcv_value: payload.slice(9, 11).reduce(msbLsb) / 100,
441
+ calcv_status: cal_val,
442
+ flow: payload.slice(11, 15).reduce(msbLsb)/100,
443
+ ghv: payload.slice(15, 19).reduce(msbLsb)/100,
444
+ total_flow: payload.slice(19, 27).reduce(msbLsb)/100,
445
+ temperature: payload.slice(27,29).reduce(msbLsb)/100,
446
+ density: payload.slice(29, 31).reduce(msbLsb)/100,
447
+ sensor_fw_ver: payload.slice(31, 33).reduce(msbLsb)/10,
448
+ model_status: payload.slice(33, 34).reduce(msbLsb),
449
+ calib_valid_res: payload.slice(35, 39).reduce(msbLsb)/100,
450
+ meter_serial_num: payload.slice(39, 43)
451
+ };
452
+ } else {
453
+ return{
454
+ sensor_status: sensor_status,
455
+ calcv_value: payload.slice(9, 11).reduce(msbLsb) / 100,
456
+ calcv_status: cal_val,
457
+ flow: payload.slice(11, 15).reduce(msbLsb)/100,
458
+ ghv: payload.slice(15, 19).reduce(msbLsb)/100,
459
+ total_flow: payload.slice(19, 27).reduce(msbLsb)/100,
460
+ temperature: payload.slice(27,29).reduce(msbLsb)/100,
461
+ density: payload.slice(29, 31).reduce(msbLsb)/100
462
+ };
463
+ }
464
+ };
465
+
466
+ // --- 2. EXPORT THE MODULE ---
467
+ // Export the module with all the necessary functions and properties
468
+ // that need to be called from outside the scrip
469
+ return {
470
+ type: 545,
471
+ name: 'Fox Thermal Flow Sensor',
472
+ parse,
473
+ get_write_buffer_size,
474
+ get_config_map,
475
+ sync_parse,
476
+ parse_fly
477
+ };
478
+ };