@ncd-io/node-red-enterprise-sensors
Version:
You can install this library through the Palette Manager in Node-Red's UI.
1,039 lines (1,019 loc) • 25 kB
JavaScript
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 22;
};
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"
]
},
"gyro_sample_rate": {
"read_index": 21,
"write_index": 10,
"descriptions": {
"title": "Set Gyroscope ODR",
"main_caption": ""
},
"default_value": 3,
"validator": {
"type": "uint8",
"min": 0,
"max": 3,
"generated": true
},
"options": {
"0": "125Hz",
"1": "250Hz",
"2": "500Hz",
"3": "1000Hz"
},
"html_id": "output_data_rate_103"
},
"acc_sample_rate": {
"read_index": 22,
"write_index": 11,
"descriptions": {
"title": "Set Accelerometer ODR",
"main_caption": ""
},
"default_value": 2,
"validator": {
"type": "uint8",
"min": 0,
"max": 4,
"generated": true
},
"options": {
"0": "8000Hz",
"1": "4000Hz",
"2": "1000Hz",
"3": "100Hz",
"4": "250Hz"
},
"html_id": "acc_output_data_rate_103"
},
"sampling_duration": {
"read_index": 23,
"write_index": 12,
"descriptions": {
"title": "Set Sampling Duration",
"main_caption": "Set the sampling duration in milliseconds, Example: a value of 1 = 50msec, 2 = 100msec, 100 = 5000msec"
},
"default_value": 1,
"validator": {
"type": "uint8",
"min": 1,
"max": 255,
"generated": true
},
"html_id": "sampling_duration_103"
},
"hpf_cutoff": {
"read_index": 24,
"write_index": 13,
"descriptions": {
"title": "Set HP Filter Cutoff",
"main_caption": "This setting will set the High Pass Filter freq to Sample Rate multiply by Selected Valu, Example: Sample Rate = 125 and Filter Coefficient = 0.00247 HPF freq (Hz) = 125 * 0.00247"
},
"default_value": 0,
"validator": {
"type": "uint8",
"min": 0,
"max": 6,
"generated": true
},
"options": {
"0": "Disabled",
"1": "0.00247",
"2": "0.00062084",
"3": "0.00015545",
"4": "0.00003862",
"5": "0.00000954",
"6": "0.00000238"
},
"html_id": "enable_hp_filter_cutoff_103"
},
"acc_fsr": {
"read_index": 25,
"write_index": 14,
"descriptions": {
"title": "Set Accelerometer FSR",
"main_caption": ""
},
"default_value": 0,
"validator": {
"type": "uint8",
"min": 0,
"max": 2,
"generated": true
},
"options": {
"0": "15g",
"1": "30g",
"2": "60g"
},
"html_id": "adxl_fsr_103"
},
"gyro_fsr": {
"read_index": 26,
"write_index": 15,
"descriptions": {
"title": "Set Gyroscope FSR",
"main_caption": ""
},
"default_value": 0,
"validator": {
"type": "uint8",
"min": 0,
"max": 3,
"generated": true
},
"options": {
"0": "250dsp",
"1": "500dps",
"2": "1000dps",
"3": "3000dps"
},
"html_id": "gyro_fsr_103"
},
"axis_enabled": {
"read_index": 27,
"write_index": 16,
"descriptions": {
"title": "Axes Enabled",
"main_caption": "New Command"
},
"validator": {
"type": "uint8"
},
"read_only": true,
},
"sampling_interval": {
"read_index": 28,
"write_index": 17,
"descriptions": {
"title": "Sampling Interval",
"main_caption": "Set how often will the sensor transmit measurement data."
},
"default_value": 1,
"validator": {
"type": "uint8",
"min": 0,
"max": 7,
"generated": true
},
"options": {
"0": "5 Minutes",
"1": "10 Minutes",
"2": "15 Minutes",
"3": "20 Minutes",
"4": "30 Minutes",
"5": "60 Minutes",
"6": "120 Minutes",
"7": "180 Minutes"
},
"html_id": "sampling_interval_101"
},
"accelerometer_threshold": {
"read_index": 29,
"write_index": 18,
"descriptions": {
"title": "Set Acceleration Threshold",
"main_caption": "Set a motion detection threshold for the sensor to trigger a data transmission. This is an interrupt-based configuration."
},
"default_value": 1,
"validator": {
"type": "uint8",
"min": 0,
"max": 255,
"generated": true
},
"html_id": "acc_threshold_103"
},
"enabled_sensors": {
"read_index": 30,
"write_index": 19,
"descriptions": {
"title": "Enable Sensor",
"main_caption": ""
},
"default_value": 2,
"validator": {
"type": "uint8",
"min": 0,
"max": 2,
"generated": true
},
"options": {
"0": "Accelerometer only",
"1": "Gyroscope only",
"2": "Both enabled"
},
"html_id": "enable_sensor_103"
},
"max_num_of_motion_tx_per_interval": {
"read_index": 31,
"write_index": 20,
"descriptions": {
"title": "Set Max Number Motion Tx Per Interval",
"main_caption": "Set Number of times it will send data due to motion triggers."
},
"default_value": 2,
"validator": {
"type": "uint8",
"min": 0,
"max": 255,
"generated": true
},
},
"send_raw_status": {
"read_index": 32,
"write_index": 21,
"descriptions": {
"title": "Set Raw On Motion Only",
"main_caption": ""
},
"validator": {
"type": "uint8",
"min": 0,
"max": 1,
"generated": true
},
"default_value": 0,
"html_id": "send_raw_on_motion_only_103"
},
"rtc": {
"read_index": 33,
"descriptions": {
"title": "Set RTC",
"main_caption": "Set the value for the Internal Real Time Clock."
},
"read_only": true,
}
};
};
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 firmware = frame[2];
if(firmware > 1){
let frame_data = {};
switch(frame[12]){
case 0:
frame_data.gyro_odr = 125;
break;
case 1:
frame_data.gyro_odr = 250;
break;
case 2:
frame_data.gyro_odr = 500;
break;
case 3:
frame_data.gyro_odr = 1000;
break;
}
switch(frame[13]){
case 0:
frame_data.acc_odr = 8000;
break;
case 1:
frame_data.acc_odr = 4000;
break;
case 2:
frame_data.acc_odr = 2000;
break;
case 3:
frame_data.acc_odr = 1000;
break;
case 4:
frame_data.acc_odr = 100;
break;
}
switch(frame[15]){
case 0:
frame_data.hpf_cutoff = false;
break;
case 1:
frame_data.hpf_cutoff = 0.00247;
break;
case 2:
frame_data.hpf_cutoff = 0.00062084;
break;
case 3:
frame_data.hpf_cutoff = 0.00015545;
break;
case 4:
frame_data.hpf_cutoff = 0.00003862;
break;
case 5:
frame_data.hpf_cutoff = 0.00000954;
break;
case 6:
frame_data.hpf_cutoff = 0.00000238;
break;
}
switch(frame[16]){
case 0:
frame_data.fsr_acc = "15g";
break;
case 1:
frame_data.fsr_acc = "30g";
break;
case 2:
frame_data.fsr_acc = "60g";
break;
}
switch(frame[17]){
case 0:
frame_data.fsr_gyro = "250dps";
break;
case 1:
frame_data.fsr_gyro = "500dps";
break;
case 2:
frame_data.fsr_gyro = "1000dps";
break;
case 3:
frame_data.fsr_gyro = "2000dps";
break;
}
switch(frame[18]){
case 1:
frame_data.en_axis = "X Axis";
break;
case 2:
frame_data.en_axis = "Y Axis";
break;
case 3:
frame_data.en_axis = "X-Y Axes";
break;
case 4:
frame_data.en_axis = "Z Axis";
break;
case 5:
frame_data.en_axis = "X-Z Axes";
break;
case 6:
frame_data.en_axis = "Y-Z Axes";
break;
case 7:
frame_data.en_axis = "All Axes";
break;
}
switch(frame[19]){
case 0:
frame_data.sampling_interval = 5;
break;
case 1:
frame_data.sampling_interval = 10;
break;
case 2:
frame_data.sampling_interval = 15;
break;
case 3:
frame_data.sampling_interval = 20;
break;
case 4:
frame_data.sampling_interval = 30;
break;
case 5:
frame_data.sampling_interval = 60;
break;
case 6:
frame_data.sampling_interval = 120;
break;
case 7:
frame_data.sampling_interval = 180;
break;
}
switch(frame[21]){
case 0:
frame_data.en_sensors = "acc_only";
break;
case 1:
frame_data.en_sensors = "gyro_only";
break;
case 2:
frame_data.en_sensors = "both_enabled";
break;
}
frame_data.hpf_cutoff = (frame_data.hpf_cutoff)?((frame_data.hpf_cutoff * frame_data.acc_odr).toFixed(2) + 'Hz'):'disabled';
return {
'firmware': firmware,
'gyro_sample_rate': frame_data.gyro_odr + 'Hz',
'acc_sample_rate': frame_data.acc_odr + 'Hz',
'sampling_duration': (frame[14]* 50) + 'msec',
'hpf_cutoff': frame_data.hpf_cutoff,
'acc_fsr': frame_data.fsr_acc,
'gyro_fsr': frame_data.fsr_gyro,
'axis_enabled': frame_data.en_axis,
'sampling_interval': frame_data.sampling_interval + 'min',
'accelerometer_threshold': (frame[20]* 32) + "mg",
'enabled_sensors': frame_data.en_sensors,
'max_num_of_motion_tx_per_interval': frame[22],
'rtc': [
String(frame[23]).padStart(2, '0'),
String(frame[24]).padStart(2, '0'),
String(frame[25]).padStart(2, '0')
].join(':'),
'hardware_id': frame.slice(26, 29),
'report_rate': frame.slice(29, 33).reduce(msbLsb),
'tx_life_counter': frame.slice(33, 37).reduce(msbLsb),
'machine_values': {
'firmware': frame[2],
'gyro_sample_rate': frame[12],
'acc_sample_rate': frame[13],
'sampling_duration': frame[14],
'hpf_cutoff': frame[15],
'acc_fsr': frame[16],
'gyro_fsr': frame[17],
'axis_enabled': frame[18],
'sampling_interval': frame[19],
'accelerometer_threshold': frame[20],
'enabled_sensors': frame[21],
'max_num_of_motion_tx_per_interval': frame[22],
'hour': frame[23],
'minute': frame[24],
'second': frame[25],
'hardware_id': frame.slice(26, 29),
'report_rate': frame.slice(29, 33),
'tx_life_counter': frame.slice(33, 37)
}
}
}else{
let frame_data = {};
switch(frame[12]){
case 0:
frame_data.odr = 125;
break;
case 1:
frame_data.odr = 250;
break;
case 2:
frame_data.odr = 500;
break;
case 3:
frame_data.odr = 1000;
break;
}
switch(frame[15]){
case 0:
frame_data.fsr_acc = "10g";
break;
case 1:
frame_data.fsr_acc = "20g";
break;
case 2:
frame_data.fsr_acc = "40g";
break;
}
switch(frame[16]){
case 0:
frame_data.fsr_gyro = "250dps";
break;
case 1:
frame_data.fsr_gyro = "500dps";
break;
case 2:
frame_data.fsr_gyro = "1000dps";
break;
case 3:
frame_data.fsr_gyro = "2000dps";
break;
}
switch(frame[17]){
case 7:
frame_data.en_axis = "all";
break;
}
switch(frame[20]){
case 1:
frame_data.en_sensors = "gyro_only";
break;
case 2:
frame_data.en_sensors = "accel_only";
break;
case 3:
frame_data.en_sensors = "all_enabled";
break;
}
switch(frame[18]){
case 0:
frame_data.sampling_interval = 5;
break;
case 1:
frame_data.sampling_interval = 10;
break;
case 2:
frame_data.sampling_interval = 15;
break;
case 3:
frame_data.sampling_interval = 20;
break;
case 4:
frame_data.sampling_interval = 30;
break;
case 5:
frame_data.sampling_interval = 60;
break;
case 6:
frame_data.sampling_interval = 120;
break;
case 7:
frame_data.sampling_interval = 180;
break;
}
switch(frame[14]){
case 0:
frame_data.hpf_cutoff = 0.00247;
break;
case 1:
frame_data.hpf_cutoff = 0.00062084;
break;
case 2:
frame_data.hpf_cutoff = 0.00015545;
break;
case 3:
frame_data.hpf_cutoff = 0.00003862;
break;
case 4:
frame_data.hpf_cutoff = 0.00000954;
break;
case 5:
frame_data.hpf_cutoff = 0.00000238;
break;
}
return {
'firmware': firmware,
'sample_rate': frame_data.odr + 'Hz',
'sampling_duration': (frame[13]* 50) + 'msec',
'hpf_cutoff': (frame_data.hpf_cutoff * frame_data.odr).toFixed(2) + 'Hz',
'acc_fsr': frame_data.fsr_acc,
'gyro_fsr': frame_data.fsr_gyro,
'axis_enabled': frame_data.en_axis,
'sampling_interval': frame_data.sampling_interval + 'min',
'accelerometer_threshold': (frame[19]* 32) + "mg",
'enabled_sensors': frame_data.en_sensors,
'rtc': [
String(frame[21]).padStart(2, '0'),
String(frame[22]).padStart(2, '0'),
String(frame[23]).padStart(2, '0')
].join(':'),
'hardware_id': frame.slice(24, 27),
'report_rate': frame.slice(27, 31).reduce(msbLsb),
'tx_life_counter': frame.slice(31, 35).reduce(msbLsb),
'machine_values': {
'firmware': frame[2],
'odr': frame[12],
'sampling_duration': frame[13],
'hpf_cutoff': frame[14],
'acc_fsr': frame[15],
'gyro_fsr': frame[16],
'axis_enabled': frame[17],
'sampling_interval': frame[18],
'accelerometer_threshold': frame[19],
'enabled_sensors': frame[20],
'hour': frame[21],
'minute': frame[22],
'second': frame[23],
'hardware_id': frame.slice(24, 27),
'report_rate': frame.slice(27, 31),
'tx_life_counter': frame.slice(31, 35)
}
}
}
};
const parse = (payload, parsed, mac) => {
if(payload[9] === 0){ // mode raw
var sensor_type = payload[8];
var deviceAddr = mac;
var data = {};
switch(sensor_type){
case 1:
data.sensor_type = 'Accel';
switch(payload[11]){ // for ADXL382
case 0:
// data.odr = '8000Hz';
data.odr = 8000;
break;
case 1:
// data.odr = '4000Hz';
data.odr = 4000;
break;
case 2:
// data.odr = '2000Hz';
data.odr = 1000;
break;
case 3:
// data.odr = '1000Hz';
data.odr = 100;
break;
case 4:
// data.odr = '100Hz';
data.odr = 250;
break;
}
break;
case 2:
data.sensor_type = 'gyro';
switch(payload[11]){
case 0:
data.odr = '125Hz';
break;
case 1:
data.odr = '250Hz';
break;
case 2:
data.odr = '500Hz';
break;
case 3:
data.odr = '1000Hz';
break;
}
break;
}
switch(payload[10]){
case 1:
data.event_type = 'report';
break;
case 2:
data.event_type = 'motion';
break;
}
var mode = payload[9];
var odr = data.odr;
var en_axis = payload[12] & 7;
var fsr = payload[12] >> 3;
var hour = payload[13];
var minute = payload[14];
var device_temp = payload.slice(15, 17).reduce(msbLsb) / 100;
var external_temperature = payload.slice(17, 19).reduce(msbLsb) / 100;
var expected_packets = payload.slice(19, 21).reduce(msbLsb);
var current_packet = payload.slice(21, 23).reduce(msbLsb);
var data_start = 23;
if(globalDevices.hasOwnProperty(deviceAddr) || expected_packets == 1){
if(expected_packets != 1){
// if current packet is equal to last one (duplicated data). This does not apply to the last package
if (globalDevices[deviceAddr].last_packet_counter == current_packet){
console.log('Duplicated message')
return;
}
// if current packet is equal to 1 or last packet counter is higher thant current packet
if(current_packet == 1 || (globalDevices[deviceAddr].last_packet_counter > current_packet)){
// clear stream
delete globalDevices[deviceAddr];
// create new stream
globalDevices[deviceAddr] = {
// stream_size: expected_packets,
data: {},
odr: odr,
mo: mode,
en_axis: en_axis,
fsr: fsr,
hour: hour,
minute: minute,
device_temp: device_temp,
external_temp: external_temperature
}
globalDevices[deviceAddr].last_packet_counter = current_packet;
globalDevices[deviceAddr].data[current_packet] = payload.slice(data_start);
return;
}
else{
globalDevices[deviceAddr].last_packet_counter = current_packet;
globalDevices[deviceAddr].data[current_packet] = payload.slice(data_start);
}
}
else{
globalDevices[deviceAddr] = {
// stream_size: expected_packets,
data: {},
odr: odr,
mo: mode,
en_axis: en_axis,
fsr: fsr,
hour: hour,
minute: minute,
device_temp: device_temp,
external_temp: external_temperature
}
globalDevices[deviceAddr].last_packet_counter = current_packet;
globalDevices[deviceAddr].data[current_packet] = payload.slice(data_start);
}
}
else{
globalDevices[deviceAddr] = {
data: {},
odr: odr,
mo: mode,
en_axis: en_axis,
fsr: fsr,
hour: hour,
minute: minute,
device_temp: device_temp,
external_temp: external_temperature
}
globalDevices[deviceAddr].last_packet_counter = current_packet;
globalDevices[deviceAddr].data[current_packet] = payload.slice(data_start);
}
if(current_packet == expected_packets){
var raw_data = new Array();
for(const packet in globalDevices[deviceAddr].data){
raw_data = raw_data.concat(globalDevices[deviceAddr].data[packet]);
}
var label = 0;
var fft = new Array();
var fft_concat = {};
var en_axis_data = {};
en_axis_data.x_offset = 0;
en_axis_data.y_offset = 2;
en_axis_data.z_offset = 4;
en_axis_data.increment = 6;
fft_concat = {x: [], y: [], z: []};
/* Evaluate sensor type */
if(payload[8] == 1){ // accelerometer
var fsr_mult = 0.00732;
var fsr_text = "";
switch(globalDevices[deviceAddr].fsr){
case 0:
fsr_mult = 0.00732;
break;
case 1:
fsr_mult = 0.01464;
break;
case 2:
fsr_mult = 0.02929;
break;
}
switch(globalDevices[deviceAddr].fsr){
case 0:
// fsr_text = "15g";
fsr_text = 15;
break;
case 1:
// fsr_text = "30g";
fsr_text = 30;
break;
case 2:
// fsr_text = "60g";
fsr_text = 60;
break;
}
}else{ // gyro
var fsr_mult = 0.0076;
var fsr_text = "";
switch(globalDevices[deviceAddr].fsr){
case 0:
fsr_mult = 0.0076;
break;
case 1:
fsr_mult = 0.015;
break;
case 2:
fsr_mult = 0.0305;
break;
case 3:
fsr_mult = 0.061;
break;
}
switch(globalDevices[deviceAddr].fsr){
case 0:
fsr_text = "250dps";
break;
case 1:
fsr_text = "500dps";
break;
case 2:
fsr_text = "1000dps";
break;
case 3:
fsr_text = "2000dps";
break;
}
}
for(var i = 0; i < raw_data.length; i+=en_axis_data.increment){
label++;
if('x_offset' in en_axis_data){
fft_concat.x.push(parseFloat((signInt(((raw_data[i+en_axis_data.x_offset]<<8)+(raw_data[i+en_axis_data.x_offset+1])), 16)*fsr_mult).toFixed(5)));
}
if('y_offset' in en_axis_data){
fft_concat.y.push(parseFloat((signInt(((raw_data[i+en_axis_data.y_offset]<<8)+(raw_data[i+en_axis_data.y_offset+1])), 16)*fsr_mult).toFixed(5)));
}
if('z_offset' in en_axis_data){
fft_concat.z.push(parseFloat((signInt(((raw_data[i+en_axis_data.z_offset]<<8)+(raw_data[i+en_axis_data.z_offset+1])), 16)*fsr_mult).toFixed(5)));
}
}
var fft_concat_obj = {
mode: mode,
sensor_type: 103,
probe_type: data.sensor_type,
msg_type: data.event_type,
time_id: globalDevices[deviceAddr].hour +':'+ globalDevices[deviceAddr].minute,
mac_address: deviceAddr,
en_axis: globalDevices[deviceAddr].en_axis,
fsr: fsr_text,
odr: globalDevices[deviceAddr].odr,
device_temp: globalDevices[deviceAddr].device_temp,
external_temp: globalDevices[deviceAddr].external_temp,
total_samples: label,
fft_confidence : ((Object.keys(globalDevices[deviceAddr].data).length / expected_packets) * 100).toFixed(2) + '%',
data: fft_concat,
raw_data: raw_data
};
sensor_data = fft_concat_obj;
delete globalDevices[deviceAddr];
return sensor_data;
}
else{
return;
}
}
};
// --- 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: 103,
name: 'Custom Wireless Accelerometer Sensor',
parse,
get_write_buffer_size,
get_config_map,
sync_parse,
parse_fly,
};
};