UNPKG

@ncd-io/node-red-enterprise-sensors

Version:

You can install this library through the Palette Manager in Node-Red's UI.

478 lines (464 loc) 11.4 kB
const { toMac, signInt, msbLsb } = require('../utils'); // --- 1. DEFINE LOCAL FUNCTIONS --- // These are defined as local variables so they can call each other easily. module.exports = (globalDevices) => { const get_write_buffer_size = (firmware) => { return 16; }; const get_config_map = (firmware) => { console.log('Generating sync map for firmware version', firmware); return { "core_version": { "read_index": 3, "descriptions": { "title": "Core Version", "main_caption": "The version of the core communication stack." }, "validator": { "type": "uint8" }, "tags": [ "system" ] }, "firmware_version": { "read_index": 4, "descriptions": { "title": "Firmware Version", "main_caption": "The application-specific firmware version." }, "validator": { "type": "uint8" }, "tags": [ "system" ] }, "sensor_type": { "read_index": 5, "descriptions": { "title": "Sensor Type", "main_caption": "The hardware identifier for the specific sensor model." }, "validator": { "type": "uint16be" }, "tags": [ "system" ] }, "tx_lifetime_counter": { "read_index": 7, "descriptions": { "title": "Sampling Interval", "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.", "sub_caption": "Default value: 20 milliseconds." }, "validator": { "type": "uint32be" }, "tags": [ "diagnostics" ] }, "hardware_id": { "read_index": 11, "length": 3, "descriptions": { "title": "Hardware ID", "main_caption": "A unique 3-byte hardware identifier." }, "validator": { "type": "buffer" }, "tags": [ "system" ] }, "network_id": { "read_index": 14, "write_index": 3, "length": 2, "descriptions": { "title": "Network ID", "main_caption": "" }, "default_value": "7fff", "validator": { "type": "hex", "length": 4 }, "html_id": "pan_id", "tags": [ "communications" ] }, "destination_address": { "read_index": 16, "write_index": 5, "length": 4, "descriptions": { "title": "Destination Address", "main_caption": "" }, "default_value": "0000ffff", "validator": { "type": "mac", "length": 8 }, "html_id": "destination", "tags": [ "communications" ] }, "node_id": { "read_index": 20, "write_index": 9, "descriptions": { "title": "Node ID", "main_caption": "" }, "default_value": "0", "validator": { "type": "uint8", "min": 0, "max": 255, "generated": true }, "html_id": "node_id", "tags": [ "generic" ] }, "report_rate": { "read_index": 21, "write_index": 10, "descriptions": { "title": "Delay", "main_caption": "" }, "default_value": "600", "validator": { "type": "uint32be", "min": 0, "max": 65535, "generated": true }, "html_id": "delay", "tags": [ "generic" ] }, "flow_unit": { "read_index": 25, "write_index": 14, "descriptions": { "title": "Set Flow Unit", "main_caption": "" }, "default_value": 0, "validator": { "type": "uint8", "min": 0, "max": 24, "generated": true }, "options": { "0": "scf_m", "1": "scf_h", "2": "nm3_h", "3": "nm3_m", "4": "kg_h", "5": "kg_m", "6": "kg_s", "7": "lbs_h", "8": "lbs_m", "9": "lbs_s", "10": "nlp_h", "11": "nlp_m", "12": "mmscf_d", "13": "lbs_d", "14": "slp_m", "15": "nlp_s", "16": "mscf_d", "17": "sm3_h", "18": "mt_h", "19": "nm3_d", "20": "mmscf_m", "21": "scf_d", "22": "mcf_d", "23": "sm3_m", "24": "sm3_d" }, "html_id": "flow_unit_545" }, "temperature_unit": { "read_index": 26, "write_index": 15, "descriptions": { "title": "Set Temperature Unit", "main_caption": "" }, "default_value": 0, "validator": { "type": "uint8", "min": 0, "max": 1, "generated": true }, "options": { "0": "Fahrenheit", "1": "Celsius" }, "html_id": "temperature_unit_545" }, }; }; const sync_parse = (rep_buffer) => { let response = { 'human_readable': {}, 'machine_values': {} }; // Get the map based on the sensor type byte const sync_map = get_config_map(rep_buffer[4]); for (const [key, config] of Object.entries(sync_map)) { // Destructure 'type' from inside 'validator' and rename 'read_index' to 'idx' const { read_index: idx, length, validator: { type } = {}, converter, options } = config; // If for some reason a config doesn't have a validator/type, skip it if (!type) continue; switch (type) { case 'uint8': response.machine_values[key] = rep_buffer[idx]; break; case 'uint16be': response.machine_values[key] = rep_buffer.readUInt16BE(idx); break; case 'uint32be': response.machine_values[key] = rep_buffer.readUInt32BE(idx); break; case 'buffer': response.machine_values[key] = rep_buffer.subarray(idx, idx + length); break; case 'hex': response.machine_values[key] = rep_buffer.subarray(idx, idx + length).toString('hex'); break; case 'mac': response.machine_values[key] = rep_buffer.subarray(idx, idx + length).toString('hex'); break; } let human_value = response.machine_values[key]; if(options && options[response.machine_values[key]]){ human_value = options[response.machine_values[key]]; }else{ if(converter && converter.multiplier){ human_value = human_value * converter.multiplier; } if(converter && converter.units){ human_value = human_value + converter.units; } } response.human_readable[key] = human_value; } if (Object.hasOwn(response.machine_values, 'destination_address') && response.machine_values.destination_address.toLowerCase() === '00000000') { console.log('##############################'); console.log('#########Dest Override########'); console.log('##############################'); response.destination_address = "0000ffff"; // response.auto_raw_destination_address = "0000ffff"; }; return response; }; const parse_fly = (frame) => { let frame_data = {}; switch(frame[12]){ case 0: frame_data.flow_unit = 'scf_m'; break; case 1: frame_data.flow_unit = 'scf_h'; break; case 2: frame_data.flow_unit = 'nm3_h'; break; case 3: frame_data.flow_unit = 'nm3_m'; break; case 4: frame_data.flow_unit = 'kg_h'; break; case 5: frame_data.flow_unit = 'kg_m'; break; case 6: frame_data.flow_unit = 'kg_s'; break; case 7: frame_data.flow_unit = 'lbs_h'; break; case 8: frame_data.flow_unit = 'lbs_m'; break; case 9: frame_data.flow_unit = 'lbs_s'; break; case 10: frame_data.flow_unit = 'nlp_h'; break; case 11: frame_data.flow_unit = 'nlp_m'; break; case 12: frame_data.flow_unit = 'mmscf_d'; break; case 13: frame_data.flow_unit = 'lbs_d'; break; case 14: frame_data.flow_unit = 'slp_m'; break; case 15: frame_data.flow_unit = 'nlp_s'; break; case 16: frame_data.flow_unit = 'mscf_d'; break; case 17: frame_data.flow_unit = 'sm3_h'; break; case 18: frame_data.flow_unit = 'mt_h'; break; case 19: frame_data.flow_unit = 'nm3_d'; break; case 20: frame_data.flow_unit = 'mmscf_m'; break; case 21: frame_data.flow_unit = 'scf_d'; break; case 22: frame_data.flow_unit = 'mcf_d'; break; case 23: frame_data.flow_unit = 'sm3_m'; break; case 24: frame_data.flow_unit = 'sm3_d'; break; } switch(frame[13]){ case 0: frame_data.temperature_unit = 'F'; break; case 1: frame_data.temperature_unit = 'C'; break; } return { 'firmware': frame[2], 'flow_unit': frame_data.flow_unit, 'temperature_unit': frame_data.temperature_unit, 'hardware_id': frame.slice(14, 17), 'report_rate': frame.slice(17, 21).reduce(msbLsb), 'tx_life_counter': frame.slice(21, 25).reduce(msbLsb), 'machine_values': { 'firmware': frame[2], 'flow_unit': frame[12], 'temperature_unit': frame[13], 'hardware_id': frame.slice(14, 17), 'report_rate': frame.slice(17, 21), 'tx_life_counter': frame.slice(21, 25) } } } const parse = (payload, parsed, mac) => { let firmware = payload[1]; let sensor_status = ''; const error_message = []; if(payload[8] & 1){ error_message.push('pwr_up'); } if(payload[8] & 2){ error_message.push('flw_hi_lim'); } if(payload[8] & 4){ error_message.push('flw_lo_lim'); } if(payload[8] & 8){ error_message.push('tmp_hi_lim'); } if(payload[8] & 16){ error_message.push('tmp_lo_lim'); } if(payload[8] & 32){ error_message.push('sensor_oor'); } if(payload[8] & 64){ error_message.push('gas_mix_err'); } if(payload[8] & 128){ error_message.push('inc_set'); } if(error_message.length === 0){ sensor_status = 'ready'; } else { sensor_status = error_message.join(', '); } const calv_value = payload.slice(9, 11).reduce(msbLsb) / 100; let cal_val = ''; if(calv_value < 0.80){ cal_val = 'pass'; } else if(calv_value <= 1.0){ cal_val = 'warning'; } else { cal_val = 'fail'; } if(firmware > 1){ let read_status = payload[7] >> 1; let sensor_read_status = ''; switch (read_status){ case 0: sensor_read_status = 'valid_data'; break; case 2: sensor_read_status = 'invalid_data'; break; } return{ sensor_read: sensor_read_status, sensor_status: sensor_status, calcv_value: payload.slice(9, 11).reduce(msbLsb) / 100, calcv_status: cal_val, flow: payload.slice(11, 15).reduce(msbLsb)/100, ghv: payload.slice(15, 19).reduce(msbLsb)/100, total_flow: payload.slice(19, 27).reduce(msbLsb)/100, temperature: payload.slice(27,29).reduce(msbLsb)/100, density: payload.slice(29, 31).reduce(msbLsb)/100, sensor_fw_ver: payload.slice(31, 33).reduce(msbLsb)/10, model_status: payload.slice(33, 34).reduce(msbLsb), calib_valid_res: payload.slice(35, 39).reduce(msbLsb)/100, meter_serial_num: payload.slice(39, 43) }; } else { return{ sensor_status: sensor_status, calcv_value: payload.slice(9, 11).reduce(msbLsb) / 100, calcv_status: cal_val, flow: payload.slice(11, 15).reduce(msbLsb)/100, ghv: payload.slice(15, 19).reduce(msbLsb)/100, total_flow: payload.slice(19, 27).reduce(msbLsb)/100, temperature: payload.slice(27,29).reduce(msbLsb)/100, density: payload.slice(29, 31).reduce(msbLsb)/100 }; } }; // --- 2. EXPORT THE MODULE --- // Export the module with all the necessary functions and properties // that need to be called from outside the scrip return { type: 545, name: 'Fox Thermal Flow Sensor', parse, get_write_buffer_size, get_config_map, sync_parse, parse_fly }; };