var constants = { 'mTypeSpr': // MAC message type { 0: "Join Request", 1: "Join Accept", 2: "Unconfirmed Data Up", 3: "Unconfirmed Data Down", 4: "Confirmed Data Up", 5: "Confirmed Data Down", 6: "RFU", 7: "Proprietary" }, 'commonMessageHeaderSpr': { 'type': { 0: "Frame pending", 3: "Position message", 4: "Energy status message", 5: "Heartbeat message", 'h7': { 1: "Activity Status message", 2: "Configuration message" }, 9: "Shutdown message", 10: "Geolocation start message", 255: "Debug message" }, 'status7_5': { 0: "Standby", 1: "Motion tracking", 2: "Permanent tracking", 3: "Motion start/end tracking", 4: "Activity tracking", 5: "OFF" }, 'type_position_message': { 0: "GPS fix:", 1: "GPS timeout", 2: "No more used.", 3: "WIFI timeout", 4: "WIFI failure", 5: "LP-GPS data (encrypted, not described in this document)", 6: "LP-GPS data (encrypted, not described in this document)", 7: "BLE beacon scan", 8: "BLE beacon failure", 9: "WIFI BSSIDs" } }, 'param': { '00': "ul_period (sec: 60 - 86400 (min 30 for US))", '01': "lora_period (sec: 300 - 86400)", '02': "pw_stat_period (0, 300 - 604800)", '03': "periodic_pos_period (sec: 0, 900 - 604800)", '04': "rezerv04", 'geoloc_sensor': { 0: "WIFI only", 1: "GPS only", 2: "LP-GPS (AGPS/GPS)", 3: "Reserved (do not use)", 4: "Reserved (do not use)", 5: "Multimode (WIFI + low power-GPS + GPS) (with reset to WIFI on timeout). Superseded by mode 9.", 6: "WIFI-GPS only (WIFI then GPS if WIFI fails in one geolocation cycle)", 7: "WIFI-LPGPS only (WIFI then low power GPS if WIFI fails in one geolocation cycle)", 8: "Reserved (do not use)", 9: "WIFI-LPGPS first, the WIFI-GPS until timeout, then back to WIFI-LPGPS", 10: "BLE scan only" }, 'geoloc_method': { 0: "WIFI", 1: "GPS", 2: "LP-GPS (AGPS/GPS)", 3: "WIFI-GPS only (WIFI then GPS if WIFI fails in one geolocation cycle)", 4: "WIFI-LPGPS only (WIFI then low power GPS if WIFI fails in one geolocation cycle)", 5: "BLE scan only" }, '07': "rezerv07", '08': "motion_nb_pos (1-60)", '09': "gps_timeout (sec: 30-300)", '0a': "agps_timeout (sec: 30-250)", '0b': "gps_ehpe (meter: 0-100 )", '0c': "gps_convergence (sec: 0-300)", 'config_flags': { // 0d bit(0)==0... 0: "Frame pending mechanism", 1: "Activate long button press to switch to off mode", 2: "Double short press configuration: Alert (0) vs SOS (1)", 3: "Send a configuration uplink message in response to a configuration modification downlink. Used to confirm the action.", 'h4': { 0: "WIFI payload with Cypher", 1: "WIFI payload without Cypher" }, 5: "Activate BLE advertising", 6: "First WIFI scan when geolocation starts. If disabled (0), WIFI position is replaced by a \"geoloc start\" uplink message", 7: "Led blinks when a GPS fix is received. Set to enable the feature, reset to disable. FW V1-7-3 only \"config_flags\"", }, 'transmit_strat': { // 0e 0: "Single fixed. Single TX. Use provisioned data rate.", 1: "Single random: Single TX. Rate in [SF7..SF12].", 2: "Dual random: First TX with rate in [SF7..SF8], next TX with rate in [SF9..SF12].", 3: "Dual fixed: First TX in [SF7..SF8]. Next Use provisioned data rate. (not recommended)", 4: "Network ADR. The LoRa network controls the number of transmissions." }, '0f': "BLE_beacon_count (1-4)", '10': "BLE_beacon_timeout (sec: 1-5)", '11': "gps_standby_timeout (sec: 10-7200 pre V1.7-3, 0-28800 post V1.7-3)", '12': "confirmed_ul_bitmap (0x0 – 0xFFFF)", '13': "confirmed_ul_retry (0-8)", 'fd': "BLE version", 'fe': "Firmware version" }, 'wifi_err': { 0: "WIFI connection failure", 1: "Scan failure", 2: "Antenna unavailable", 3: "WIFI not supported on this device" }, 'ble_err': { 0: "BLE is not responding", 1: "Internal error", 2: "Shared antenna not available:", 3: "Scan already on going", 4: "BLE busy (should not occur)", 5: "No beacon detected", 6: "Hardware incompatibility" }, 'encoded': { 'battery': { 'lo': 2.8, 'hi': 4.2, 'nbits': 8, 'nresv': 2, 'step_size': 5.5 //mV Battery, WiFo time out }, 'temperature': { 'lo': -44, 'hi': 85, 'nbits': 8, 'nresv': 0, 'step_size': 0.5 //°C }, 'gps_satellite': { 'lo': 0, 'hi': 50, 'nbits': 8, 'nresv': 0, 'step_size': 0.2 // dBm. GPS timeout }, 'gps_age': { 'lo': 0, 'hi': 2040, 'nbits': 8, 'nresv': 0, 'step_size': 8 // seconds. Gps_fix, "BLE beacon scan, WIFI_BSSID_ }, 'gps_ehpe': { 'lo': 0, 'hi': 1000, 'nbits': 8, 'nresv': 0, 'step_size': 3.9 // meters. Gps_fix Encoded form using lo= 0, hi= 1000, nbits= 8, }, 'gps_wifi': { 'lo': 2.8, 'hi': 4.2, 'nbits': 8, 'nresv': 2, 'step_size': 5.5 // mV. lo=2.8, hi=4.2, nbits=8, nresv=2. It is expressed in volt with a step of 5.5mV } } }; /** * Start decoder * @type {any | undefined} */ var payLoadJson = decodeToJson(payload); var result = setPayload(); var payload_hex; var payloadByte = []; var telemetryC; var devEuiLink; /** Helper functions **/ /** * Parsing metadata and payload_hex * @param payLoadJson * @returns {null|{payload: {deviceType: string, telemetry: string, deviceName: string}}} */ function setPayload() { if (payLoadJson !== null) { if (payLoadJson !== null && payLoadJson.length !== 0) { if (payLoadJson.hasOwnProperty('DevEUI_uplink')) { devEuiLink = payLoadJson.DevEUI_uplink; } else if (payLoadJson.hasOwnProperty('DevEUI_downlink_Sent')) { devEuiLink = payLoadJson.DevEUI_downlink_Sent; } if (devEuiLink !== null && devEuiLink.length !== 0) { var payloadResult = getPayload(); if (devEuiLink.hasOwnProperty('payload_hex')) { payload_hex = devEuiLink['payload_hex']; if (payload_hex !== null && payload_hex.length !== 0) { payload_hex = payload_hex.trim(); payloadByte = hexStringToBytes(payload_hex); payloadResult.telemetry = (payLoadJson.hasOwnProperty('DevEUI_uplink')) ? getTelemetryUp() : getTelemetryDown(); } } else{ payloadResult.telemetry =getTelemetryDown(); } payloadResult.deviceName = devEuiLink['DevEUI']; // for the sent downLink telemetryC.values['sentPayloadHex'] = "00"; return payloadResult; } } } return null; } /** * Receiving telemetry by value message_type * @param payload_hex * @param payLoadJson * @returns {null|Telemetry } */ function getTelemetryUp() { var message_type = payloadByte[0]; if (message_type > 0) { telemetryC = getTelemetryCommonUp(); } switch (message_type) { case 0: // "Frame pending" return getTelemetryFramePending(); case 3: // Position message return getTelemetryPositionMessage(); case 4: // Energy status message return getTelemetryEnergyStatusMessage(); case 5: // Heartbeat message return getTelemetryHeartbeat(); case 7: // Activity Status message & Configuration message return getTelemetryActivityStatus_Configuration(); case 9: // Shutdown message return telemetryC; case 10: // Geolocation start message return telemetryC; case 255: // Debugg return telemetryC; default: // Нет таких значений return null; } } function getTelemetryDown() { telemetryC = getTelemetryCommonDown(); return telemetryC; } /** * Receiving telemetry by value message_type is equal to FramePending * @param payLoadJson * @returns Json {{ts: *, values: {*}}} */ function getTelemetryFramePending() { var mSecUtc = convertDateTimeISO8601_toMsUtc(payLoadJson.DevEUI_uplink["Time"]); ; return { 'ts': mSecUtc, 'values': { 'ph_type': constants.commonMessageHeaderSpr.type[0], 'acknowledge_token': payloadByte[1] } }; } /** * Receiving telemetry by value message_type is equal to EnergyStatusMessage * @param payload_hex * @param payloadByte * @param payLoadJson * @returns Json {{ts: *, values: {*}}} */ function getTelemetryEnergyStatusMessage() { var telemetryC = getTelemetryCommonUp(); // industrial only telemetryC.values['GPS_ON (sec)'] = bytesToInt(payloadByte.subarray(5, 9)); telemetryC.values['GPS_Standby (sec)'] = bytesToInt(payloadByte.subarray(9, 13)); telemetryC.values['WIFI-SCAN (number)'] = bytesToInt(payloadByte.subarray(13, 17)); return telemetryC; } /** * Receiving telemetry by value message_type is equal to PositionMessage * @param payload_hex * @param payloadByte * @param payLoadJson * @returns Json {{ts: *, values: {*}}} */ function getTelemetryPositionMessage() { var typePosMessageId = payloadByte[4] & 0xf; telemetryC.values['ph_typePosMessage'] = constants.commonMessageHeaderSpr.type_position_message[typePosMessageId]; switch (typePosMessageId) { case 0: // 0 – GPS fix getPosMesGPS_fix(); break; case 1: // 1: GPS timeout getPosMesGPS_timeout(); break; case 2: // "No more used." break; case 3: // "WIFI timeout" getPosMesWIFI_timeout(); break; case 4: // "WIFI failure" getPosMesWIFI_timeout(); telemetryC.values['pd_WIFI_err'] = constants.wifi_err[payloadByte[11]]; break; case 5: // "LP-GPS data (encrypted, not described in this document) break; case 6: // "LP-GPS data (encrypted, not described in this document)" break; case 7: // "BLE beacon scan" getPosMesBLE_beacon_scan(); break; case 8: // "BLE beacon failure" telemetryC.values['pd_BLE_err'] = constants.ble_err[payloadByte[5]]; break; case 9: // "WIFI BSSIDs" getPosMesWIFI_BSSIDs(); break; default: // Нет таких значений } return telemetryC; } function getPosMesGPS_fix() { getAge_0_7_9(); var latitude = payloadByte.subarray(6, 9); var longiture = payloadByte.subarray(9, 12); var ehpe = encodedFunction(payloadByte[12], constants.encoded.gps_ehpe['nresv'], constants.encoded.gps_ehpe['step_size'], constants.encoded.gps_ehpe['lo'], 1); var encrypted = payloadByte.subarray(13, 16); telemetryC.values['pd_latitude'] = convertCoordinate(latitude); telemetryC.values['pd_longiture'] = convertCoordinate(longiture); telemetryC.values['pd_EHPE'] = ehpe; telemetryC.values['pd_encrypted'] = bytesToInt(encrypted); } function getPosMesGPS_timeout() { telemetryC.values['timeout_cause'] = payloadByte[5] + '-User timeout cause'; var sat0 = encodedFunction(payloadByte[6], constants.encoded.gps_satellite['nresv'], constants.encoded.gps_satellite['step_size'], constants.encoded.gps_satellite['lo'], 1); var sat1 = encodedFunction(payloadByte[7], constants.encoded.gps_satellite['nresv'], constants.encoded.gps_satellite['step_size'], constants.encoded.gps_satellite['lo'], 1); var sat2 = encodedFunction(payloadByte[8], constants.encoded.gps_satellite['nresv'], constants.encoded.gps_satellite['step_size'], constants.encoded.gps_satellite['lo'], 1); var sat3 = encodedFunction(payloadByte[9], constants.encoded.gps_satellite['nresv'], constants.encoded.gps_satellite['step_size'], constants.encoded.gps_satellite['lo'], 1); telemetryC.values['C/N0'] = sat0; telemetryC.values['C/N1'] = sat1; telemetryC.values['C/N2'] = sat2; telemetryC.values['C/N3'] = sat3; } /** * Byte 5 Byte 6 Byte 7 Byte 8 Byte 9 Byte 10 v_bat1 v_bat2 v_bat3 v_bat4 v_bat5 v_bat6 v_bat1: encoded voltage at the start time (T0) of the WIFI scan. v_bat2: encoded voltage at T0 + 0.5 second. v_bat3: encoded voltage at T0 + 1 second. v_bat4: encoded voltage at T0 + 1.5 second. v_bat5: encoded voltage at T0 + 2 seconds. v_bat6: encoded voltage at T0 + 2.5 seconds. Notes: 1- Most of time a WIFI timeout occurs due to low battery. 2- v_bat encoding uses lo=2.8, hi=4.2, nbits=8, nresv=2. It is expressed in volt with a step of 5.5mV 3- Encoded form is detailed in the Encoded form section */ function getPosMesWIFI_timeout() { var bat_1 = encodedFunction(payloadByte[5], constants.encoded.gps_wifi['nresv'], constants.encoded.gps_wifi['step_size'], constants.encoded.gps_wifi['lo'], 1); var bat_2 = encodedFunction(payloadByte[6], constants.encoded.gps_wifi['nresv'], constants.encoded.gps_wifi['step_size'], constants.encoded.gps_wifi['lo'], 1); var bat_3 = encodedFunction(payloadByte[7], constants.encoded.gps_wifi['nresv'], constants.encoded.gps_wifi['step_size'], constants.encoded.gps_wifi['lo'], 1); var bat_4 = encodedFunction(payloadByte[8], constants.encoded.gps_wifi['nresv'], constants.encoded.gps_wifi['step_size'], constants.encoded.gps_wifi['lo'], 1); var bat_5 = encodedFunction(payloadByte[9], constants.encoded.gps_wifi['nresv'], constants.encoded.gps_wifi['step_size'], constants.encoded.gps_wifi['lo'], 1); var bat_6 = encodedFunction(payloadByte[10], constants.encoded.gps_wifi['nresv'], constants.encoded.gps_wifi['step_size'], constants.encoded.gps_wifi['lo'], 1); telemetryC.values['start time (T0)'] = bat_1; telemetryC.values['T0 + 0.5 sec'] = bat_2; telemetryC.values['T0 + 1 sec'] = bat_3; telemetryC.values['T0 + 1.5 sec'] = bat_4; telemetryC.values['T0 + 2 sec'] = bat_5; telemetryC.values['T0 + 2.5 sec'] = bat_6; } /** * Byte5 Byte6-11 Byte12 Byte13-18 Byte19 Byte20-25 Byte26 Byte27-33 Byte34 * Age MAC ADR0 RSSI0 MAC ADR1 RSSI1 MAC ADR2 RSSI2 MAC ADR3 RSSI3 */ function getPosMesBLE_beacon_scan() { getAge_0_7_9(); var data = payload_hex.substr(6 * 2, payload_hex.length); var array = data.match(/.{1,14}/g); var i = 0; array.forEach(function (e) { var adrid = getIdRssi(e); telemetryC['adr_' + i] = adrid["id"]; telemetryC['rssi_' + i] = adrid["rssi"]; i++; }); } /** * Byte5 B 6-11 Byte12 B 13-18 Byte19 B 20-25 Byte26 B 27-32 Byte 33 Age BSSID0 RSSI0 BSSID1 RSSI1 BSSID2 RSSI2 BSSID3 RSSI3 */ function getPosMesWIFI_BSSIDs() { getAge_0_7_9(); var data = payload_hex.substr(6 * 2, payload_hex.length); var array = data.match(/.{1,14}/g); var i = 0; array.forEach(function (e) { var bssid = getIdRssi(e); telemetryC['bssid_' + i] = bssid["id"]; telemetryC['rssi_' + i] = bssid["rssi"]; i++; }); } function getIdRssi(e) { var bssidStr = e.substr(0, 12); var id = (bssidStr.match(/.{1,2}/g)).join(':'); var rssiInt = parseInt(e.substr(12, e.length), 16); var rssi = (parseInt(rssiInt, 16) > 127) ? (128 - rssiInt) : rssiInt; return { "id": id, "rssi": rssi } } function getAge_0_7_9() { var age = encodedFunction(payloadByte[5], constants.encoded.gps_age['nresv'], constants.encoded.gps_age['step_size'], constants.encoded.gps_age['lo'], 1); telemetryC.values['pd_age'] = age; } /** * Receiving telemetry by value message_type is equal to Heartbeat * @param payload_hex * @param payloadByte * @param payLoadJson * @returns Json {{ts: *, values: {*}}} */ function getTelemetryHeartbeat() { telemetryC['last_reset_cause'] = payloadByte[5]; if (payloadByte.length > 6) { //1.7-5 telemetryC['fW_version'] = getVersion(payloadByte[6], payloadByte[7], payloadByte[8]); } return telemetryC; } /** * Receiving telemetry by value message_type is equal to ActivityStatus or Configuration * @param payload_hex * @param payloadByte * @param payLoadJson * @returns Json {{ts: *, values: {*}}} */ function getTelemetryActivityStatus_Configuration() { var tag = payloadByte[5]; if (tag === 1) { // Activity status messages byte 5 ==> tag =1; Byte 6-9 BigEndian telemetryC.values['Activity_counter'] = bytesToInt(payloadByte.subarray(6, 10)); } else if (tag === 2) { // Configuration messages byte 5 ==> tag =2 var data = payload_hex.substr(6 * 2, payload_hex.length - 6 * 2); var array = data.match(/.{1,10}/g); array.forEach(function (element) { var id = element.substr(0, 2); var param = constants.param[id]; var valId = parseInt(element.substr(2, element.length), 16); if (id === 'fe' || id === 'fd') { valId = getVersion(parseInt(element.substr(2, 4), 16), parseInt(element.substr(6, 2), 16), parseInt(element.substr(8, 2), 16)); } if (id === '05') { valId = constants.param.geoloc_sensor[valId]; telemetryC.values['geoloc_sensor'] = valId; } else if (id === '06') { valId = constants.param.geoloc_method[valId]; telemetryC.values['geoloc_method'] = valId; } else if (id === '0d') { if (valId > 0) { var byteStr = element.substr(element.length - 2, 2); telemetryC = getConfigFlags(telemetryC, byteStr, 'config_flags_bit_'); } } else if (id === '0e') { valId = constants.param.transmit_strat[valId]; telemetryC.values['transmit_strat'] = valId; } else { telemetryC.values[param] = valId; } }); } return telemetryC; } /** * Receiving common telemetry by any value message_type * @param payloadByte * @param payLoadJson * @returns Json {{ts: *, values: {*}}} */ function getTelemetryCommonUp() { if (payLoadJson.hasOwnProperty('DevEUI_uplink')) { var byteStatus = payloadByte[1]; var battery = encodedFunction(payloadByte[2], constants.encoded.battery['nresv'], constants.encoded.battery['step_size'], constants.encoded.battery['lo'], 100); var temperature = encodedFunction(payloadByte[3], constants.encoded.temperature['nresv'], constants.encoded.temperature['step_size'], constants.encoded.temperature['lo'], 1); var mSecUtc = convertDateTimeISO8601_toMsUtc(devEuiLink["Time"]); var phTypeId = payloadByte[0]; var phType; if (phTypeId === 7) { phType = constants.commonMessageHeaderSpr.type.h7[payloadByte[5]]; } else { phType = constants.commonMessageHeaderSpr.type[phTypeId]; } var ack = payloadByte[4] >> 4; } var rez = { "ts": mSecUtc, "values": { "batteryVoltage": battery, "temperature": temperature, "ph_type": phType, "ph_status": constants.commonMessageHeaderSpr.status7_5[byteStatus >> 5], "ph_alert_SOS_bit4": getBit(byteStatus, (4)), "ph_tracking/idle_state_bit3": getBit(byteStatus, (3)), "ph_tracker_is_moving_bit2": getBit(byteStatus, (2)), "ph_periodic_position_message_bit1": getBit(byteStatus, (1)), "ph_POD_message_bit0": getBit(byteStatus, (0)), "m_type": constants.mTypeSpr[devEuiLink['MType']], "m_port": devEuiLink['FPort'], "m_customerID": devEuiLink['CustomerID'], "m_LrrRSSI": devEuiLink["LrrRSSI"], "m_LrrSNR": devEuiLink["LrrSNR"], "m_Lrrid": devEuiLink["Lrrid"], "ack": ack } }; return rez; } /** * Receiving common telemetry by any value message_type * @param payloadByte * @param payLoadJson * @returns Json {{ts: *, values: {*}}} */ function getTelemetryCommonDown() { var mSecUtc = convertDateTimeISO8601_toMsUtc(devEuiLink["Time"]); var rez = { "ts": mSecUtc, "values": { "m_type": constants.mTypeSpr[devEuiLink['MType']], "m_port": devEuiLink['FPort'], "m_customerID": devEuiLink['CustomerID'], "m_LrrRSSI": devEuiLink["LrrRSSI"], "m_LrrSNR": devEuiLink["LrrSNR"], "m_Lrrid": devEuiLink["Lrrid"] } }; return rez; } /** * Metadata * @returns Headers payload: {deviceType: string, telemetry: string, deviceName: string} */ function getPayload() { return { 'deviceName': "", 'deviceType': "Abeeway Micro/Industrial Tracker", 'telemetry': "" } } /** * * @param value * @param nresv * @param step_size * @param lo * @param divider * @returns {number} */ function encodedFunction(value, nresv, step_size, lo, divider) { return (((value - nresv / 2) * (step_size * 10)) / 10 + lo) / divider; } function convertCoordinate(bytes) { bytes = bytesToInt(bytes); bytes = bytes << 8; if (bytes > 0x7FFFFFFF) { // 2147483647 bytes = bytes - 0x100000000; // 4294967296"deviceType": "tracker", } bytes = bytes / Math.pow(10, 7); return bytes; } /** * * @param telemetryC * @param byteStr * @param param[ '03', '4c', '00', '99', '61', '00', '4c', '47', '38', '00' ] * @returns telemetryC{*} with value of config_flags */ function getConfigFlags(telemetryC, byteStr, param) { var byte = parseInt(byteStr, 16); for (var i = 0; i < 8; i++) { if (i === 4) { telemetryC.values[param + i] = constants.param.config_flags.h4[getBit(byte, i)]; } else { if (getBit(byte, i) > 0) { telemetryC.values[param + i] = constants.param.config_flags[i]; } } } return telemetryC; } function getVersion(v, v0, vsub) { return "V" + v + "." + v0 + "-" + vsub; } function hexStringToBytes(str) { var array = str.match(/.{1,2}/g); var a = []; array.forEach(function (element) { a.push(parseInt(element, 16)); }); return new Uint8Array(a); } function convertDateTimeISO8601_toMsUtc(str) { return new Date(str).getTime(); } function bytesToInt(bytes) { var val = 0; for (var j = 0; j < bytes.length; j++) { val += bytes[j]; if (j < bytes.length - 1) { val = val << 8; } } return val; } function decodeToJson(payload) { try { return JSON.parse(String.fromCharCode.apply(String, payload)); } catch (e) { return JSON.parse(JSON.stringify(payload)); } } function getBit(byte, bitNumber) { return ((byte & (1 << bitNumber)) != 0) ? 1 : 0; } return result;