roost/node/messages.js

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
}
})()