(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: 1,// "boot_Version", 2: 2,// "program_Version", 3: 3,// "epaper_Version", 4: 4,// "mesh_version", 5: 5,// "bq27441_version", 6: 6,// "rtcmem_version", 7: 7,// "screen_version", 8: 8,// "message_parser_version", 9: 9,// "sht40_version", 10: 10,// "ssd1306_version", 11: 11,// "tmpxx_version", 12: 12,// "uqr_version", 13: 13,// "main_version", 14: 14,// "test_version", 15: 15,// "mc3470_version", 16: 16,// "settings_version", 17: 17,// "file_name_map_version", 18: 18,// "rv3032_version", 19: 19,// "bme280_version", 20: 20,// "walter_version", 21: 21,// "_walter_version", 22: 22,// "cellular_chunking_version", 23: 23,// "base_mase_version", 24: 24,// "unknown_file_version", 25: 25,// "unknown_file_version", 26: 26,// "unknown_file_version", 27: 27,// "unknown_file_version", 28: 28,// "unknown_file_version", 29: 29,// "unknown_file_version", 30: 30,// "unknown_file_version", 31: 31,// "unknown_file_version", 32: 32,// "unknown_file_version", 33: 33,// "unknown_file_version", 34: 34,// "unknown_file_version", 35: 35// "unknown_file_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, 22: arrayToDecimal, 23: arrayToDecimal, 24: arrayToDecimal, 25: arrayToDecimal, 26: arrayToDecimal, 27: arrayToDecimal, 28: arrayToDecimal, 29: arrayToDecimal, 30: arrayToDecimal, 31: arrayToDecimal, 32: arrayToDecimal, 33: arrayToDecimal, 34: arrayToDecimal, 35: 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 } })()