773 lines
28 KiB
JavaScript
773 lines
28 KiB
JavaScript
(function() {
|
|
|
|
let message_id = 0
|
|
|
|
function increment_message_id() {
|
|
message_id = message_id + 1
|
|
}
|
|
|
|
const twoByteDataUnits = {
|
|
0: "",
|
|
1: "°C",
|
|
2: "°C",
|
|
3: "°C",
|
|
4: "hPa",
|
|
5: "%rh",
|
|
6: "",
|
|
7: "lux",
|
|
8: "",
|
|
9: "",
|
|
10: "mg",
|
|
11: "mg",
|
|
12: "mg",
|
|
13: "°/s",
|
|
14: "°/s",
|
|
15: "°/s",
|
|
16: "nT",
|
|
17: "nT",
|
|
18: "nT",
|
|
19: "nT",
|
|
20: "%",
|
|
21: "V",
|
|
22: "mA",
|
|
23: "m/s",
|
|
24: "",
|
|
25: "mm",
|
|
26: "mm/h",
|
|
27: "",
|
|
28: "",
|
|
29: "",
|
|
30: "",
|
|
31: "clicks",
|
|
32: "",
|
|
33: "",
|
|
34: "",
|
|
35: "",
|
|
36: "°",
|
|
37: "°",
|
|
38: "m",
|
|
39: "m/s",
|
|
40: "°",
|
|
41: "°",
|
|
42: "°",
|
|
43: "ppm",
|
|
44: "ppm",
|
|
45: "ppm",
|
|
46: "ppm",
|
|
47: "ppm",
|
|
48: "mAh",
|
|
49: "mA",
|
|
50: "mA",
|
|
51: "mA",
|
|
52: "mW",
|
|
53: "mAh",
|
|
54: "",
|
|
55: "",
|
|
56: "",
|
|
57: "",
|
|
58: "",
|
|
59: "",
|
|
60: "",
|
|
61: "s",
|
|
62: "",
|
|
63: "",
|
|
64: "",
|
|
65: "",
|
|
66: "",
|
|
67: "",
|
|
68: "",
|
|
69: "",
|
|
70: "",
|
|
71: "",
|
|
72: "",
|
|
73: "V",
|
|
74: "",
|
|
75: "h",
|
|
76: "",
|
|
77: "",
|
|
78: "s",
|
|
79: "",
|
|
80: "°C",
|
|
81: "°C",
|
|
82: "",
|
|
83: " nodes",
|
|
84: " nodes",
|
|
85: " nodes",
|
|
}
|
|
|
|
const twoByteDataTypesDict = {
|
|
0: "end", // end of measurement data
|
|
1: "probe_temperature", // temperature probe temperature
|
|
2: "ambient_temperature", // ambient air temperature
|
|
3: "device_temperature", // device controller temperature
|
|
4: "barometric_pressure", // barometric pressure
|
|
5: "relative_humidity", //relative humidity of the air
|
|
6: "soil_moisture", // soil moisture sensor
|
|
7: "lux", // ambient light intensity (measured in lux)
|
|
8: "visible", // visible light measurement (meassured in ??)
|
|
9: "ir", // ir light measurement (same as visible light, but ir)
|
|
10: "accelerometer_x",//accelerometer x-axis data
|
|
11: "accelerometer_y",//accelerometer y-axis data
|
|
12: "accelerometer_z", //accelerometer z-axis data
|
|
13: "gyroscope_x",//gyroscope x-axis data
|
|
14: "gyroscope_y",//gyroscope y-axis data
|
|
15: "gyroscope_z",//gyroscope z-axis data
|
|
16: "magnetometer_x",//magnetometer x-axis data
|
|
17: "magnetometer_y",//magnetometer y-axis data
|
|
18: "magnetometer_z",//magnetometer z-axis data
|
|
19: "magnetic_intensiy", //hall effect intensity data
|
|
20: "battery_charge_percent", //battery charge level (%)
|
|
21: "battery_voltage", //voltage
|
|
22: "current", //current
|
|
23: "wind_speed", //wind speed
|
|
24: "wind_direction", //wind direction
|
|
25: "absolute_rainfall", //absolute rainfall (i.e. XX inches)
|
|
26: "rainfall_rate", //rate of rainfall (i.e. XX inches/minute)
|
|
27: "rssi", //RSSI (wifi signal strength)
|
|
28: "number_of_peer", //number of known peers
|
|
29: "number_of_saved_measurements", //number of saved measurements
|
|
30: "rtc_data_used", //amount of RTC data storage used
|
|
31: "clicks", //click counter (so how many times a discrete on/off sensor has been triggered, like a hall effect switch)
|
|
32: "rot_m1", //motor1 rotation
|
|
33: "rot_m2", //motor2 rotation
|
|
34: "rot_m3", //motor3 rotation
|
|
35: "rot_m4", //motor4 rotation
|
|
36: "lat", //latitude
|
|
37: "lon", //longitude
|
|
38: "ele", //elevation
|
|
39: "speed", //speed
|
|
40: "heading", //tilt-adjusted compass heading (yaw)
|
|
41: "inclination",//inclination (pitch)
|
|
42: "roll",//roll (roll)
|
|
43: "ammonia",//ammonia/nh3
|
|
44: "co", //CO
|
|
45: "co2",//CO2
|
|
46: "h2s",//H2S
|
|
47: "ch4",//CH4
|
|
48: "remaining_battery_capacity", // Remaining Battery Capacity (mAh
|
|
49: "average_current", // Average Current
|
|
50: "standby_current", // Standby Current
|
|
51: "max_current", // Max Current
|
|
52: "average_power", // Average Power
|
|
53: "full_available_capacity", // Full Available Capacity
|
|
54: "boot_ver", // the verion of boot.py
|
|
55: "program_ver", // the program.py version
|
|
56: "epaper_ver", // the epaper version
|
|
57: "mesh_ver", // the mesh code version
|
|
58: "bq27441_ver", // the bq27441 code version
|
|
59: "rtc_mem_ver", // the RTCMem.py version
|
|
60: "request_type", // for control messages, things that want a response from the base station, or a forwarded message
|
|
61: "measurement_interval", // non-battery measured voltage,
|
|
62: "measurement_alignment",
|
|
63: "scheduled_time_1",
|
|
64: "scheduled_time_2",
|
|
65: "scheduled_time_3",
|
|
66: "scheduled_time_4",
|
|
67: "scheduled_time_5",
|
|
68: "scheduled_time_6",
|
|
69: "scheduled_time_7",
|
|
70: "scheduled_time_8",
|
|
71: "scheduled_time_9",
|
|
72: "scheduled_time_10",
|
|
73: "voltage",
|
|
74: "measurement_version",
|
|
75: "time_zone_offset",
|
|
76: "message_parser_version",
|
|
77: "settings_type",
|
|
78: "wake_window_length",
|
|
79: "hop_distance", // the number of hops between the node and the base station
|
|
80: "18_inch_temperature", // a temperature probe at 18 inch depth
|
|
81: "36_inch_temperature", // a temperature probe at 36 inch depth
|
|
82: "forwarder", // if the node is a forwarder during this interval
|
|
83: "closer_nodes",
|
|
84: "same_nodes",
|
|
85: "farther_nodes",
|
|
}
|
|
|
|
// handler funcitons for each type of data
|
|
const twoByteDataConversionFunctions = {
|
|
0: noop, //"end", // end of measurement data
|
|
1: arrayToSignedDividedDecimal, //"probe_temp", // temperature probe temperature
|
|
2: arrayToSignedDividedDecimal, //"ambient_temp", // ambient air temperature
|
|
3: arrayToSignedDividedDecimal, //"device_temp", // device controller temperature
|
|
4: arrayToDividedDecimal, //"bara_pressure", // barometric pressure
|
|
5: arrayToDividedDecimal, //"rh", //relative humidity of the air
|
|
6: arrayToDividedDecimal, //"soil_moisture", // soil moisture sensor
|
|
7: arrayToDecimal, //"lux", // ambient light intensity (measured in lux)
|
|
8: arrayToDecimal, //"visible", // visible light measurement (meassured in ??)
|
|
9: arrayToDecimal, //"ir", // ir light measurement (same as visible light, but ir)
|
|
10: arrayToDecimal, //"acc_x",//accelerometer x-axis data
|
|
11: arrayToDecimal, //"acc_y",//accelerometer y-axis data
|
|
12: arrayToDecimal, //"acc_z", //accelerometer z-axis data
|
|
13: arrayToDecimal, //"gyro_x",//gyroscope x-axis data
|
|
14: arrayToDecimal, //"gyro_y",//gyroscope y-axis data
|
|
15: arrayToDecimal, //"gyro_z",//gyroscope z-axis data
|
|
16: arrayToDecimal, //"mag_x",//magnetometer x-axis data
|
|
17: arrayToDecimal, //"mag_y",//magnetometer y-axis data
|
|
18: arrayToDecimal, //"mag_z",//magnetometer z-axis data
|
|
19: arrayToDecimal, //"hall", //hall effect intensity data
|
|
20: arrayToDecimal, //"batt", //battery charge level (%)
|
|
21: arrayToDividedDecimal, //"batt_volt", //voltage
|
|
22: arrayToDecimal, //"amps", //current
|
|
23: arrayToDividedDecimal, //"wind_s", //wind speed
|
|
24: arrayToDividedDecimal, //"wind_d", //wind direction
|
|
25: arrayToDividedDecimal, //"rain", //absolute rainfall (i.e. XX inches)
|
|
26: arrayToDividedDecimal, //"rain_rate", //rate of rainfall (i.e. XX inches/minute)
|
|
27: arrayToDividedDecimal, //"rssi", //RSSI (wifi signal strength)
|
|
28: arrayToDividedDecimal, //"num_peer", //number of known peers
|
|
29: arrayToDecimal, //"num_meas", //number of saved measurements
|
|
30: arrayToDecimal, //"rtc_dat", //amount of RTC data storage used
|
|
31: arrayToDecimal, //"clicks", //click counter (so how many times a discrete on/off sensor has been triggered, like a hall effect switch)
|
|
32: arrayToDecimal, //"rot_m1", //motor1 rotation
|
|
33: arrayToDecimal, //"rot_m2", //motor2 rotation
|
|
34: arrayToDecimal, //"rot_m3", //motor3 rotation
|
|
35: arrayToDecimal, //"rot_m4", //motor4 rotation
|
|
36: arrayToSignedDecimal, //"lat", //latitude
|
|
37: arrayToSignedDecimal, //"lon", //longitude
|
|
38: arrayToDividedDecimal, //"ele", //elevation
|
|
39: arrayToDividedDecimal, //"speed", //speed
|
|
40: arrayToDividedDecimal, //"heading", //tilt-adjusted compass heading (yaw)
|
|
41: arrayToDividedDecimal, //"pitch",//inclination (pitch)
|
|
42: arrayToDividedDecimal, //"roll",//roll (roll)
|
|
43: arrayToDividedDecimal, //"nh3",//ammonia
|
|
44: arrayToDividedDecimal, //"co", //CO
|
|
45: arrayToDividedDecimal, //"co2",//CO2
|
|
46: arrayToDividedDecimal, //"h2s",//H2S
|
|
47: arrayToDividedDecimal, //"ch4",//CH4
|
|
48: arrayToDecimal, //"remCap", // Remaining Battery Capacity (mAh
|
|
49: arrayToDividedDecimal, //"avgCur", // Average Current
|
|
50: arrayToDividedDecimal, //"sbc", // Standby Current
|
|
51: arrayToDividedDecimal, //"mc", // Max Current
|
|
52: arrayToDividedDecimal, //"ap", // Average Power
|
|
53: arrayToDividedDecimal, //"fac", // Full Available Capacity
|
|
54: arrayToDecimal, //"boot_ver", // the verion of boot.py
|
|
55: arrayToDecimal, //"prog_ver", // the program.py version
|
|
56: arrayToDecimal, //"ep_ver", // the epaper version
|
|
57: arrayToDecimal, //"mesh_ver", // the mesh code version
|
|
58: arrayToDecimal, //"bq_ver", // the bq27441 code version
|
|
59: arrayToDecimal, //"rtc_mem_ver", // the RTCMem.py version
|
|
60: noop, //"request_type", // for control messages, things that want a response from the base station, or a forwarded message
|
|
61: arrayToDecimal, //"measurement_interval" // time between measurements
|
|
62: arrayToTime, // measurement time alignment
|
|
63: arrayToTime, // measurement time 1
|
|
64: arrayToTime, // measurement time 2
|
|
65: arrayToTime, // measurement time 3
|
|
66: arrayToTime, // measurement time 4
|
|
67: arrayToTime, // measurement time 5
|
|
68: arrayToTime, // measurement time 6
|
|
69: arrayToTime, // measurement time 7
|
|
70: arrayToTime, // measurement time 8
|
|
71: arrayToTime, // measurement time 9
|
|
72: arrayToTime, // measurement time 10
|
|
73: arrayToDividedDecimal, // measured voltage
|
|
74: noop, // version (deprecated)
|
|
75: arrayToSignedDecimal, // time zone offset
|
|
76: arrayToDecimal, // message parser version
|
|
77: arrayToDecimal, // settings type
|
|
78: arrayToDecimal, // wake window length
|
|
79: arrayToDecimal, // distance in hops from node to base station
|
|
80: arrayToDividedDecimal, // 18 inch probe temperature
|
|
81: arrayToDividedDecimal, // 36 inch probe temperature
|
|
82: toBoolean, // forwarder node
|
|
83: arrayToDecimal, // closer nodes
|
|
84: arrayToDecimal, // same nodes
|
|
85: arrayToDecimal, // father nodes
|
|
}
|
|
|
|
const fourByteDataTypesDict = {
|
|
1: "time_drift", // the time error for the node
|
|
2: "sleep_duration",
|
|
3: "measurement_interval",
|
|
4: "measurement_offset",
|
|
5: "wake_window_length",
|
|
6: "previous_update_time", // the most recent time the device set its time
|
|
7: "base_station_rssi", // the RSSI at the skewer for a message from the base station
|
|
8: "last_successful_report", // the last time that the server received data from the base station or probe
|
|
}
|
|
|
|
const fourByteDataConversionFunctions = {
|
|
1: arrayToSignedDecimal,
|
|
2: arrayToDecimal,
|
|
3: arrayToDecimal,
|
|
4: arrayToDecimal,
|
|
5: arrayToDecimal,
|
|
6: arrayToDecimal,
|
|
7: arrayToDecimal,
|
|
8: arrayToSQLTimestamp,
|
|
}
|
|
|
|
const stringDataTypesDict = {
|
|
1: "site_name", // the human readable site name for the node
|
|
2: "node_name" // the human readable name for the node
|
|
}
|
|
|
|
const stringDataConversionFunctions = {
|
|
1: toTrimmedString,
|
|
2: toTrimmedString
|
|
}
|
|
|
|
const fileVersionManifestTypesDict = {
|
|
1: "boot_Version",
|
|
2: "program_Version",
|
|
3: "epaper_Version",
|
|
4: "mesh_version",
|
|
5: "bq27441_version",
|
|
6: "rtcmem_version",
|
|
7: "screen_version",
|
|
8: "message_parser_version",
|
|
9: "sht40_version",
|
|
10: "ssd1306_version",
|
|
11: "tmpxx_version",
|
|
12: "uqr_version",
|
|
13: "main_version",
|
|
14: "test_version",
|
|
15: "mc3470_version",
|
|
16: "settings_version",
|
|
17: "file_name_map_version",
|
|
18: "_walter_version",
|
|
19: "cellular_chunking_version",
|
|
20: "ds3231_version",
|
|
21: "walter_version",
|
|
}
|
|
|
|
const fileVersionManifestConversionFunctions = {
|
|
1: arrayToDecimal,
|
|
2: arrayToDecimal,
|
|
3: arrayToDecimal,
|
|
4: arrayToDecimal,
|
|
5: arrayToDecimal,
|
|
6: arrayToDecimal,
|
|
7: arrayToDecimal,
|
|
8: arrayToDecimal,
|
|
9: arrayToDecimal,
|
|
10: arrayToDecimal,
|
|
11: arrayToDecimal,
|
|
12: arrayToDecimal,
|
|
13: arrayToDecimal,
|
|
14: arrayToDecimal,
|
|
15: arrayToDecimal,
|
|
16: arrayToDecimal,
|
|
17: arrayToDecimal,
|
|
18: arrayToDecimal,
|
|
19: arrayToDecimal,
|
|
20: arrayToDecimal,
|
|
21: arrayToDecimal,
|
|
}
|
|
|
|
const rssiDataTypesDict = {
|
|
1: "rssi",
|
|
}
|
|
|
|
const rssiConversionFunctions = {
|
|
1: arrayToDecimal,
|
|
}
|
|
|
|
function noop(input) {
|
|
return input
|
|
}
|
|
|
|
function arrayToSQLTimestamp(input) {
|
|
const l = input.length
|
|
let out = 0
|
|
for(let i = 0; i < input.length; i++) {
|
|
out += input[i] * (2**(8*(l-i-1)))
|
|
}
|
|
try {
|
|
const theTimestamp = new Date(out*1000 + 946684800000).toISOString().slice(0, 19).replace('T', ' ')
|
|
return theTimestamp
|
|
} catch (e) {
|
|
console.log(out)
|
|
console.log(e)
|
|
return '00:00:00'
|
|
}
|
|
}
|
|
|
|
|
|
// input is a 2 byte array representing minutes since midnight, convert it into hh:mm:ss time
|
|
function arrayToTime(input) {
|
|
const minutesSinceMidnight = (input[0] << 8) + input[1]
|
|
minutes = minutesSinceMidnight % 60
|
|
hours = (Math.floor(minutesSinceMidnight - minutes) / 60)
|
|
|
|
while (hours > 23) {
|
|
hours = hours - 24
|
|
}
|
|
return hours + ":" + minutes + ":00"
|
|
}
|
|
|
|
function toBoolean(input) {
|
|
let tmp = 0
|
|
const l = input.length
|
|
for(let i = 0; i < input.length; i++) {
|
|
tmp += input[i] << (8*(l-i-1))
|
|
}
|
|
let out = false
|
|
if(tmp === 0) {
|
|
out = false
|
|
} else {
|
|
out = true
|
|
}
|
|
return out
|
|
}
|
|
|
|
// input is an array of bytes, trim off any leading 0x00 values and convert it to a string
|
|
function toTrimmedString(input) {
|
|
return input.toString().replaceAll('\x00', '')
|
|
}
|
|
|
|
function arrayToDividedDecimal(input) {
|
|
const l = input.length
|
|
let out = 0
|
|
for(let i = 0; i < input.length; i++) {
|
|
out += input[i] << (8*(l-i-1))
|
|
}
|
|
return out / 100
|
|
}
|
|
|
|
function arrayToHex(input) {
|
|
return input.map(x => ('00'+ x.toString(16)).slice(-2)).join('')
|
|
}
|
|
|
|
function arrayToDecimal(input) {
|
|
const l = input.length
|
|
let out = 0
|
|
for(let i = 0; i < input.length; i++) {
|
|
out += input[i] << (8*(l-i-1))
|
|
}
|
|
return out
|
|
}
|
|
|
|
function arrayToSignedDecimal(input) {
|
|
const l = input.length
|
|
let out = 0
|
|
for(let i = 0; i < input.length; i++) {
|
|
out += input[i] << (8*(l-i-1))
|
|
}
|
|
if (out > 2**(8*l-1)) {
|
|
out = out - 2**(8*l)
|
|
}
|
|
return out
|
|
}
|
|
|
|
function arrayToSignedDividedDecimal(input) {
|
|
const l = input.length
|
|
let out = 0
|
|
for(let i = 0; i < input.length; i++) {
|
|
out += input[i] << (8*(l-i-1))
|
|
}
|
|
if (out > 2**(8*l-1)) {
|
|
out = out - 2**(8*l)
|
|
}
|
|
return out / 100
|
|
}
|
|
|
|
function parse_message_header(theDataIn) {
|
|
const msg_id = arrayToHex([...theDataIn.slice(0,2)])
|
|
const msg_type = arrayToDecimal([...theDataIn.slice(2,3)])
|
|
const version = arrayToDecimal([...theDataIn.slice(3,4)])
|
|
if (version === 1) {
|
|
const source = arrayToHex([...theDataIn.slice(4,12)])
|
|
const target = arrayToHex([...theDataIn.slice(12,20)])
|
|
const timestamp = arrayToSQLTimestamp([...theDataIn.slice(20,24)])
|
|
return {
|
|
msg_id,
|
|
msg_type,
|
|
version,
|
|
source,
|
|
target,
|
|
timestamp,
|
|
device_id: source.slice(4),
|
|
header_length: 24
|
|
}
|
|
} else if (version === 2) {
|
|
const target_distance = arrayToDecimal([...theDataIn.slice(4,5)])
|
|
const source = arrayToHex([...theDataIn.slice(5,13)])
|
|
const target = arrayToHex([...theDataIn.slice(13,21)])
|
|
const timestamp = arrayToSQLTimestamp([...theDataIn.slice(21,25)])
|
|
return {
|
|
msg_id,
|
|
msg_type,
|
|
target_distance,
|
|
source,
|
|
target,
|
|
timestamp,
|
|
device_id: source.slice(4),
|
|
header_length: 25
|
|
}
|
|
} else { //else if (version === 3) {
|
|
const target_distance = arrayToDecimal([...theDataIn.slice(4,5)])
|
|
const source = arrayToHex([...theDataIn.slice(5,13)])
|
|
const target = arrayToHex([...theDataIn.slice(13,21)])
|
|
const t18 = arrayToDividedDecimal([...theDataIn.slice(21,23)])
|
|
const t36 = arrayToDividedDecimal([...theDataIn.slice(23,25)])
|
|
const timestamp = arrayToSQLTimestamp([...theDataIn.slice(25,29)])
|
|
return {
|
|
msg_id,
|
|
msg_type,
|
|
target_distance,
|
|
source,
|
|
target,
|
|
timestamp,
|
|
device_id: source.slice(4),
|
|
t18,
|
|
t36,
|
|
header_length: 29
|
|
}
|
|
}
|
|
}
|
|
|
|
const messageTypes = {
|
|
1: {
|
|
types: twoByteDataTypesDict,
|
|
fns: twoByteDataConversionFunctions
|
|
},
|
|
2: {
|
|
types: fourByteDataTypesDict,
|
|
fns: fourByteDataConversionFunctions
|
|
},
|
|
3: {
|
|
types: twoByteDataTypesDict,
|
|
fns: twoByteDataConversionFunctions
|
|
},
|
|
4: {
|
|
types: fourByteDataTypesDict,
|
|
fns: fourByteDataConversionFunctions
|
|
},
|
|
5: {
|
|
types: twoByteDataTypesDict,
|
|
fns: twoByteDataConversionFunctions
|
|
},
|
|
6: {
|
|
types: fourByteDataTypesDict,
|
|
fns: fourByteDataConversionFunctions
|
|
},
|
|
7: {
|
|
types: stringDataTypesDict,
|
|
fns: stringDataConversionFunctions
|
|
},
|
|
8: {
|
|
// I don't think we need anything here
|
|
},
|
|
9: {
|
|
// do we need anything here?
|
|
},
|
|
10: {
|
|
// I need to figure out what goes here
|
|
},
|
|
11: {
|
|
// TODO
|
|
},
|
|
12: {
|
|
// TODO
|
|
},
|
|
13: {
|
|
types: fileVersionManifestTypesDict,
|
|
fns: fileVersionManifestConversionFunctions
|
|
},
|
|
14: {
|
|
// nothing here
|
|
},
|
|
15: {
|
|
// nothing here
|
|
},
|
|
16: {
|
|
// nothing here
|
|
},
|
|
17: {
|
|
types: rssiDataTypesDict,
|
|
fns: rssiConversionFunctions
|
|
}
|
|
}
|
|
|
|
function get_type_and_fn(msg_type_id) {
|
|
msg_type = parsed["msg_type"]
|
|
theFn = noop
|
|
if (Object.keys(messageTypes).indexOf(String(parsed["msg_type"])) > -1) {
|
|
if( Object.keys(messageTypes[String(parsed["msg_type"])].types).indexOf(String(msg[ind])) > -1) {
|
|
msg_type = messageTypes[String(parsed["msg_type"])].types[String(msg[ind])]
|
|
}
|
|
if(Object.keys(messageTypes[String(parsed["msg_type"])].fns).indexOf(String(msg[ind])) > -1) {
|
|
theFn = messageTypes[String(parsed["msg_type"])].fns[String(msg[ind])]
|
|
}
|
|
}
|
|
return {type: msg_type, fn: theFn}
|
|
}
|
|
|
|
function parse_messages(full_msg) {
|
|
ind = -1
|
|
out = []
|
|
msg = Buffer.from(full_msg.data, 'hex')
|
|
while (ind < msg.length) {
|
|
if (ind == -1 || msg[ind] == 0) {
|
|
ind = ind + 1
|
|
if (msg.length > ind + 24) {
|
|
parsed = parse_message_header(msg.slice(ind))
|
|
parsed["data"] = {}
|
|
out.push(parsed)
|
|
ind = ind + parsed['header_length']
|
|
} else {
|
|
break
|
|
}
|
|
} else if ([1,3,5,13].indexOf(parsed["msg_type"]) > -1) { // data with 2 byte values
|
|
if (msg.length > ind + 3) {
|
|
type_info = get_type_and_fn(msg[ind])
|
|
parsed["data"][type_info.type] = type_info.fn(msg.slice(ind+1,ind+3))
|
|
ind = ind + 3
|
|
} else {
|
|
ind = ind + 1
|
|
}
|
|
} else if ([2,4,6].indexOf(parsed["msg_type"]) > -1) { // data with 4 byte values
|
|
if (msg.length > ind + 5) {
|
|
type_info = get_type_and_fn(msg[ind])
|
|
parsed["data"][type_info.type] = type_info.fn(msg.slice(ind+1,ind+5))
|
|
ind = ind + 5
|
|
} else {
|
|
ind = ind + 1
|
|
}
|
|
} else if (parsed["msg_type"] == 7) { // key-value pairs with byte keys and string values
|
|
types_value = msg[ind]
|
|
type_info = get_type_and_fn(msg[ind])
|
|
theKey = type_info.type
|
|
ind = ind + 1
|
|
valueEnd = 0
|
|
for (let i = ind; i < msg.length; i++) {
|
|
if (msg[i] == 255) {
|
|
valueEnd = i
|
|
break
|
|
}
|
|
}
|
|
if (!valueEnd) {
|
|
break
|
|
}
|
|
parsed["data"][theKey] = type_info.fn(msg.slice(ind,valueEnd))
|
|
ind = valueEnd + 1
|
|
} else if (parsed["msg_type"] == 8) {// # key-value pair with both key and value as strings
|
|
// read until the first 0xFF for the name
|
|
keyEnd = 0
|
|
for (let i = ind; i < msg.length; i++) {
|
|
if (msg[i] == 255) {
|
|
keyEnd = i
|
|
break
|
|
}
|
|
}
|
|
if (!keyEnd) {
|
|
// there is no terminator, the message isn't correctly formatted so we drop it
|
|
// if the first chararacter is the terminator it is also an error
|
|
break
|
|
}
|
|
theName = msg.slice(ind,keyEnd)
|
|
ind = keyEnd + 1
|
|
// read until the next 0xFF for the walue
|
|
valueEnd = 0
|
|
for (let i = ind; i < msg.length; i++) {
|
|
if (msg[i] == 255) {
|
|
valueEnd = i
|
|
break
|
|
}
|
|
}
|
|
if (!valueEnd) {
|
|
break
|
|
}
|
|
value = msg.slice(ind,valueEnd)
|
|
parsed["data"]["name"] = toTrimmedString(theName)
|
|
parsed["data"]["value"] = toTrimmedString(value)
|
|
ind = valueEnd + 1
|
|
} else if (parsed["msg_type"] == 10) {
|
|
parsed["data"]["distance"] = msg[ind]
|
|
ind = ind + 1
|
|
} else if (parsed["msg_type"] == 17) {
|
|
thisMac = msg.slice(ind,ind+6).toString('hex')
|
|
parsed["data"][thisMac] = msg[ind+6]
|
|
ind = ind + 7
|
|
} else {
|
|
// if we hit a message we don't know how to deal with return
|
|
break
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
function make_message_header(msg_type, version, source, target, this_message_id) {
|
|
target = ('0000000000000000' + target).slice(-16)
|
|
target_buf = Buffer.from(target, 'hex')
|
|
source = (source = '0000000000000000' + source).slice(-16)
|
|
source_buf = Buffer.from(source, 'hex')
|
|
if (!this_message_id) {
|
|
this_message_id = message_id
|
|
increment_message_id()
|
|
}
|
|
timestamp = Math.floor(Date.now() / 1000) - 946684800 // timestamp in the format that the ESP32 expects it, seconds since 2000-01-01
|
|
timestamp_buf = Buffer.alloc(4)
|
|
timestamp_buf.writeUInt32BE(timestamp)
|
|
buf = Buffer.alloc(4)
|
|
buf.writeUInt16BE(this_message_id, 0)
|
|
buf.writeUInt8(msg_type, 2)
|
|
buf.writeUInt8(version || 1, 3)
|
|
header = Buffer.concat([buf, source_buf, target_buf, timestamp_buf])
|
|
return header
|
|
}
|
|
|
|
function make_two_byte_data_message(msg_type, version, source, target, data, this_message_id) {
|
|
theHeader = make_message_header(msg_type, version, source, target, this_message_id)
|
|
theData = Buffer.from([])
|
|
Object.keys(data).forEach(function(thisKey) {
|
|
if(thisKey > 255) {
|
|
// if the key won't fit into 1 byte or the value won't fit into 2 bytes something is wrong so we skip this one
|
|
return
|
|
}
|
|
// make sure that thhe vaule is always 2 bytes long, zero pad if needed
|
|
const tmpBuffer = Buffer.alloc(2)
|
|
tmpBuffer.writeUInt16BE(data[thisKey])
|
|
this_buf = Buffer.concat([Buffer.from(thisKey), tmpBuffer])
|
|
theData = Buffer.concat([theData, this_buf])
|
|
})
|
|
return Buffer.concat([theHeader,theData,Buffer.from([0x00])])
|
|
}
|
|
|
|
function make_four_byte_data_message(msg_type, version, source, target, data, this_message_id) {
|
|
theHeader = make_message_header(msg_type, version, source, target, this_message_id)
|
|
theData = Buffer.from([])
|
|
Object.keys(data).forEach(function(thisKey) {
|
|
if(thisKey <= 255) {
|
|
// if the key won't fit into 1 byte or the value won't fit into 2 bytes something is wrong so we skip this one
|
|
return
|
|
}
|
|
const tmpBuffer = Buffer.alloc(4)
|
|
tmpBuffer.writeUInt32BE(data[thisKey])
|
|
this_buf = Buffer.concat([Buffer.from(thisKey), tmpBuffer])
|
|
theData = Buffer.concat([theData, this_buf])
|
|
})
|
|
return Buffer.concat([theHeader,theData,Buffer.from([0x00])])
|
|
|
|
}
|
|
|
|
function make_byte_string_message(msg_type, version, source, target, data, this_message_id) {
|
|
theHeader = make_message_header(msg_type, version, source, target, this_message_id)
|
|
theData = Buffer.from([])
|
|
Object.keys(data).forEach(function(thisKey) {
|
|
this_buf = Buffer.alloc(thisKey.length + data[thisKey].length)
|
|
this_buf.write(thisKey)
|
|
this_buf.write(data[thisKey],1)
|
|
theData = Buffer.concat([theData,this_buf, Buffer.from([255])])
|
|
})
|
|
return Buffer.concat([theHeader, theData, Buffer.from([0])])
|
|
}
|
|
|
|
function make_string_string_message(msg_type, version, source, target, key, value, this_message_id) {
|
|
theHeader = make_message_header(msg_type, version, source, target, this_message_id)
|
|
theData = Buffer.alloc(key.length + value.length + 3)
|
|
theData.write(key,0)
|
|
theData.writeUInt8(255, key.length)
|
|
theData.write(value, key.length+1)
|
|
theData.writeUInt8(255, key.length+value.length+2)
|
|
return Buffer.concat([theHeader, theData, Buffer.from([0])])
|
|
}
|
|
|
|
module.exports = {
|
|
parse_messages,
|
|
make_two_byte_data_message,
|
|
make_four_byte_data_message,
|
|
make_byte_string_message,
|
|
make_string_string_message
|
|
}
|
|
})() |