Telemetry Packet: Difference between revisions
No edit summary |
|||
| Line 137: | Line 137: | ||
==Sample Packet== | ==Sample Packet== | ||
<pre> | <pre> | ||
HEX " | HEX "240370e21aeb7abfd5818011047aca0260081b780000000000000000000000000000307890000000000000000000000000000000000000000000000068a7f192162abf" | ||
{ | {"imei": 862942074896044, "packet_type": 1, "no_packets": 1, "packet_status": 1, "frame_number": 34, "alert_id": 1, "operator": 1, "signal_strength": 29, "mcc": 404, "mnc": 19, "cell_id": 64, "lac": 56256, "fix_status": 0, "latitude": 0.0, "latitude_dir": 0, "longitude": 0.0, "longitude_dir": 0, "hdop": 0.0, "pdop": 0.0, "speed": 0.0, "altitude": 0.0, "power": 1, "ignition": 1, "immobilizer": 0, "tamper": 0, "supply_volatge": 12.0, "internal_battery_volatge": 3.6, "fuel_sensor_status_1": 0, "fuel_percentage_1": 0.0, "fuel_value_sensor_1": 0, "fuel_sensor_status_2": 0, "fuel_percentage_2": 0.0, "fuel_value_sensor_2": 0, "fuel_sensor_status_3": 0, "fuel_percentage_3": 0.0, "fuel_value_sensor_3": 0, "analog_input1": 0.0, "analog_input2": 0.0, "digital_input1": 0, "digital_input2": 0, "digital_output1": 0, "digital_output2": 0, "temp_sensor_status_1": 0, "temperature_1": 0.0, "temp_sensor_status_2": 0, "temperature_2": 0.0, "temp_sensor_status_3": 0, "temperature_3": 0.0, "humidity": 0, "odometer": 0, "dateTime": 1755836818, "timezone": 22, "dateTime_tz": "2025-08-22 09:56:58", "error_code": 0 | ||
} | } | ||
</pre> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="UTF-8" /> | |||
<title>Telemetry Packet Parser – Grouped Frames</title> | |||
<style> | |||
body { font-family: Arial, sans-serif; margin: 24px; } | |||
textarea { width: 100%; height: 140px; font-family: monospace; } | |||
button { | |||
padding: 10px 20px; border-radius: 50px; font-weight: bold; | |||
background-color: #680022; color: white; border: none; cursor: pointer; | |||
} | |||
button:hover { background-color: #4c0019; } | |||
table { width: 100%; border-collapse: collapse; margin-top: 12px; table-layout: fixed; word-wrap: break-word; } | |||
th, td { border: 1px solid #ccc; padding: 6px; text-align: left; font-size: 13px; } | |||
th { background-color: #680022; color: white; } | |||
.error { color: #b00020; margin-top: 8px; font-weight: bold; } | |||
.section { margin-top: 30px; } | |||
.json-output { background: #f4f4f4; padding: 12px; font-family: monospace; font-size: 13px; overflow-x: auto; } | |||
.pkt-title { margin: 18px 0 6px; font-size: 16px; font-weight: bold; color: #333; } | |||
</style> | |||
</head> | |||
<body> | |||
<h2>Telemetry Packet Parser</h2> | |||
<textarea id="hexInput" placeholder="Paste Telemtry Hex Packet Here..."></textarea><br/> | |||
<button id="parseBtn">Parse Packet</button> | |||
<div id="errors" class="error"></div> | |||
<div class="section"> | |||
<h3>Header </h3> | |||
<table id="headerTable"> | |||
<thead><tr><th>Field</th><th>Value</th></tr></thead> | |||
<tbody></tbody> | |||
</table> | |||
</div> | |||
<div class="section"> | |||
<h3>Data Packets</h3> | |||
<div id="dataTables"></div> | |||
</div> | |||
<div class="section"> | |||
<h3>JSON Output</h3> | |||
<pre class="json-output" id="jsonOut"></pre> | |||
</div> | |||
<script> | |||
function cleanHex(str) { | |||
return (str || "").toString().trim().replace(/[^0-9a-fA-F]/g, "").toLowerCase(); | |||
} | |||
function hexToBits(hex) { | |||
return hex.match(/.{1,2}/g).map(b => parseInt(b, 16).toString(2).padStart(8, "0")).join(""); | |||
} | |||
function bitsToInt(bits) { return bits ? parseInt(bits, 2) : 0; } | |||
function bitsToSignedInt(bits) { | |||
if (!bits) return 0; | |||
const v = parseInt(bits, 2); | |||
const max = 1 << bits.length; | |||
return v >= max / 2 ? v - max : v; | |||
} | |||
function addRow(tbody, k, v) { | |||
const tr = document.createElement("tr"); | |||
tr.innerHTML = `<td>${k}</td><td>${v}</td>`; | |||
tbody.appendChild(tr); | |||
} | |||
/* --- Field Map (bit indices) --- */ | |||
const FULL_FIELDS = [ | |||
["Start Byte", 0, 8], ["Data Length", 8, 20], ["No. of Packets", 20, 25], | |||
["IMEI", 25, 75], ["Packet Type", 75, 80], ["Packet Status", 80, 81], | |||
["Frame Number", 81, 97], ["Alert ID", 97, 102], ["Operator", 102, 106], | |||
["Signal Strength", 106, 111], ["MCC", 111, 121], ["MNC", 121, 131], | |||
["Cell ID", 131, 147], ["LAC", 147, 163], ["Fix Status", 163, 164], | |||
["Latitude", 164, 193], ["NS Indication", 193, 194], ["Longitude", 194, 223], | |||
["EW Indication", 223, 224], ["HDOP", 224, 234], ["PDOP", 234, 244], | |||
["Speed", 244, 259], ["Altitude", 259, 274], ["Power Status", 274, 275], | |||
["Ignition Status", 275, 276], ["Immobilizer Status", 276, 277], | |||
["Tamper", 277, 278], ["Supply Voltage", 278, 288], ["Internal Battery Voltage", 288, 294], | |||
["Fuel Sensor Status 1", 294, 295], ["Fuel Percentage 1", 295, 305], | |||
["Fuel Sensor Value 1", 305, 321], ["Fuel Sensor Status 2", 321, 322], | |||
["Fuel Percentage 2", 322, 332], ["Fuel Sensor Value 2", 332, 348], | |||
["Fuel Sensor Status 3", 348, 349], ["Fuel Percentage 3", 349, 359], | |||
["Fuel Sensor Value 3", 359, 375], ["Analog Input 1", 375, 385], | |||
["Analog Input 2", 385, 395], ["Digital Input 1", 395, 396], | |||
["Digital Input 2", 396, 397], ["Digital Output 1", 397, 398], | |||
["Digital Output 2", 398, 399], ["Temperature Sensor Status 1", 399, 400], | |||
["Temperature 1", 400, 412], ["Temperature Sensor Status 2", 412, 413], | |||
["Temperature 2", 413, 425], ["Temperature Sensor Status 3", 425, 426], | |||
["Temperature 3", 426, 438], ["Humidity", 438, 445], ["Odometer", 445, 480], | |||
["DateTime UTC", 480, 512], ["TimeZone", 512, 520] | |||
]; | |||
/* Sub-packet fields (strip first 80 header bits) */ | |||
const SUB_FIELDS = FULL_FIELDS | |||
.filter(([_, s, e]) => s >= 80 && e <= 520) | |||
.map(([name, s, e]) => [name, s - 80, e - 80]); | |||
/* Packet type label fix */ | |||
const PACKET_TYPE_NAME = { 1: "TelemetryPacket" }; | |||
/* Render each packet as a 2-column table */ | |||
function renderPacketsAsKVTables(dataArray) { | |||
const container = document.getElementById("dataTables"); | |||
container.innerHTML = ""; | |||
dataArray.forEach((pkt, idx) => { | |||
const title = document.createElement("div"); | |||
title.className = "pkt-title"; | |||
title.textContent = `Packet #${idx + 1}`; | |||
container.appendChild(title); | |||
const table = document.createElement("table"); | |||
const thead = document.createElement("thead"); | |||
thead.innerHTML = `<tr><th>Field</th><th>Value</th></tr>`; | |||
const tbody = document.createElement("tbody"); | |||
/* Keep the same field order as SUB_FIELDS */ | |||
for (const [name] of SUB_FIELDS) { | |||
addRow(tbody, name, pkt[name.replace(/\s+/g, '')]); | |||
} | |||
table.appendChild(thead); | |||
table.appendChild(tbody); | |||
container.appendChild(table); | |||
}); | |||
} | } | ||
/* Parse button logic */ | |||
document.getElementById("parseBtn").addEventListener("click", () => { | |||
const hex = cleanHex(document.getElementById("hexInput").value); | |||
const errors = document.getElementById("errors"); | |||
const headerBody = document.querySelector("#headerTable tbody"); | |||
const dataTables = document.getElementById("dataTables"); | |||
const jsonOut = document.getElementById("jsonOut"); | |||
errors.textContent = ""; | |||
headerBody.innerHTML = ""; | |||
dataTables.innerHTML = ""; | |||
jsonOut.textContent = ""; | |||
if (!hex) { errors.textContent = "No data provided."; return; } | |||
if (!hex.startsWith("24")) { errors.textContent = "Invalid frame: Start Byte must be 0x24."; return; } | |||
const bits = hexToBits(hex); | |||
const dataLenBits = bits.slice(8, 20); | |||
const countBits = bits.slice(20, 25); | |||
const dataLenBytes = bitsToInt(dataLenBits); | |||
const numItems = bitsToInt(countBits); | |||
const imei = BigInt("0b" + bits.slice(25, 75)).toString(); | |||
const pktTypeVal = bitsToInt(bits.slice(75, 80)); | |||
const pktTypeName = PACKET_TYPE_NAME[pktTypeVal] || pktTypeVal.toString(); | |||
const perItemBytes = numItems > 0 ? dataLenBytes / numItems : 0; | |||
const perItemBits = perItemBytes * 8; | |||
const totalBits = bits.length; | |||
const subStart = 80; | |||
if (!Number.isInteger(perItemBytes)) { errors.textContent = "Data Length not divisible by packet count."; return; } | |||
if (totalBits < subStart + perItemBits * numItems) { errors.textContent = "Frame too short for given count."; return; } | |||
/* Header table (2-column) */ | |||
addRow(headerBody, "Start Byte", "$"); | |||
addRow(headerBody, "Length", dataLenBytes); | |||
addRow(headerBody, "Data Items", numItems); | |||
addRow(headerBody, "IMEI", imei); | |||
addRow(headerBody, "Packet Type", pktTypeName); | |||
/* Build per-packet objects keyed by compact field name */ | |||
const dataArray = []; | |||
const KEY_MAP = new Map(FULL_FIELDS.map(([name]) => [name, name.replace(/\s+/g, '')])); | |||
for (let i = 0; i < numItems; i++) { | |||
const sbeg = subStart + i * perItemBits; | |||
const send = sbeg + perItemBits; | |||
const subBits = bits.slice(sbeg, send); | |||
const obj = {}; | |||
for (const [name, s, e] of SUB_FIELDS) { | |||
const b = subBits.slice(s, e); | |||
let val; | |||
if (name === "Temperature 1" || name === "Temperature 2" || name === "Temperature 3") { | |||
val = bitsToSignedInt(b); | |||
} else if (name === "Supply Voltage" || name === "Internal Battery Voltage") { | |||
val = bitsToInt(b) / 10; // e.g., 123 -> 12.3 V | |||
} else { | |||
val = bitsToInt(b); | |||
} | |||
obj[KEY_MAP.get(name)] = val; | |||
} | |||
dataArray.push(obj); | |||
} | |||
/* Render each packet as its own key/value table */ | |||
renderPacketsAsKVTables(dataArray); | |||
/* JSON */ | |||
const out = { StartByte: "$", Length: dataLenBytes, DataItems: numItems, IMEI: imei, PacketType: pktTypeName, Data: dataArray }; | |||
jsonOut.textContent = JSON.stringify(out, null, 2); | |||
}); | |||
</script> | |||
</body> | |||
</html> | |||
<html lang="en"> | <html lang="en"> | ||
Revision as of 07:00, 22 August 2025
| Field | Size (bits) | Bit Range | Description | Breakdown |
|---|---|---|---|---|
| Header (10 bytes) | ||||
| Start byte | 8 | 0–7 | Starting character $ (ASCII value 36) | $ |
| Data length | 12 | 08–19 | 2-byte length of the data following the header | |
| Num of data packets | 5 | 20–24 | Number of packets (0–32) | 0–32 |
| IMEI | 50 | 25–74 | Unique device identifier | e.g., 887744556677882 |
| packet type | 5 | 75–79 | Integer type:
| |
| Data (= 55 bytes × number of packets) | ||||
| Packet Status | 1 | 80 | Type of packet | 0:History, 1:Live |
| Frame Number | 16 | 81-96 | Frame number | |
| AlertID | 5 | 97–101 | Alert identifier | |
| Operator | 4 | 102–105 | Network operator | 00-Airtel, 01-BSNL, 02-VI, 04-JIO |
| Signal Strength | 5 | 106–110 | Signal strength | Integer (0–31) |
| MCC | 10 | 111–120 | Mobile country code | Integer |
| MNC | 10 | 121–130 | Mobile network code | Integer |
| Cell Id | 16 | 131–146 | Cell tower ID | Integer |
| Location Area Code | 16 | 147–162 | Location area code | Integer |
| Fix_status | 1 | 163 | GPS fix status | 0: No fix, 1: Valid Fix |
| Latitude | 29 | 164–192 | Latitude coordinate | Divide by 1,000,000 for float value |
| NS_Indication | 1 | 193 | N or S | 0: N, 1: S |
| Longitude | 29 | 194–222 | Longitude coordinate | Divide by 1,000,000 for float value |
| EW_Indication | 1 | 223 | East/West Indication | 0: E, 1: W |
| HDOP | 10 | 224–233 | Horizontal dilution | Divide by 10 for float value |
| PDOP | 10 | 234–243 | Position dilution | Divide by 10 for float value |
| Speed | 15 | 244–258 | Speed in km/h | Divide by 10 for float value |
| Altitude | 15 | 259–273 | Altitude in meters | Divide by 10 for float value |
| Power Status | 1 | 274 | Power connection status | 0: Power disconnected, 1: Power connected |
| Ignition Status | 1 | 275 | Ignition status | 0: OFF, 1: ON |
| Immobilizer Status | 1 | 276 | Immobilizer status | 0: OFF, 1: ON |
| Tamper | 1 | 277 | Wire Tamper detection | 0: Tamper alert OFF, 1: Tamper alert ON |
| Supply Voltage | 10 | 278–287 | External battery voltage | Divide by 10 for float value |
| Internal Battery Voltage | 6 | 288–293 | Internal battery voltage | Divide by 10 for float value |
| FuelSensorstatus 1 | 1 | 294 | Status of fuel sensor 1 | 1 if connected, 0 if not connected |
| Fuel Percentage 1 | 10 | 295–304 | Fuel level percentage | Divide by 10 for float value |
| Fuel SensorValue 1 | 16 | 305–320 | Fuel sensor value | Divide by 10 if float |
| FuelSensorstatus 2 | 1 | 321 | Status of fuel sensor 2 | 1 if connected, 0 if not connected |
| Fuel Percentage 2 | 10 | 322–331 | Fuel level percentage | Divide by 10 for float value |
| Fuel SensorValue 2 | 16 | 332–347 | Fuel sensor value | Divide by 10 if float |
| FuelSensorstatus 3 | 1 | 348 | Status of fuel sensor 3 | 1 if connected, 0 if not connected |
| Fuel Percentage 3 | 16 | 349–358 | Fuel sensor value | Divide by 10 if float |
| Fuel SensorValue 3 | 16 | 359–374 | Fuel level percentage | Divide by 10 for float value |
| Analog Input 1 | 10 | 375–384 | Analog input | Divide by 10 for float value |
| Analog Input 2 | 10 | 385–394 | Analog input 1 | Divide by 10 for float value |
| Digital Input 1 | 1 | 395 | Digital input 1 | 0 or 1 |
| Digital Input 2 | 1 | 396 | Digital input 2 | 0 or 1 |
| Digital Output 1 | 1 | 397 | Digital output 1 | 0 or 1 |
| Digital Output 2 | 1 | 398 | Digital output 2 | 0 or 1 |
| Temperature sensor status 1 | 1 | 399 | Status of temperature sensor 1 | 1 if connected, 0 if not connected |
| Temperature 1 | 12 | 400–411 | Temperature in °C | Signed, divide by 10 |
| Temperature sensor status 2 | 1 | 412 | Status of temperature sensor 2 | 1 if connected, 0 if not connected |
| Temperature 2 | 12 | 413–424 | Temperature in °C | Signed, divide by 10 |
| Temperature sensor status 3 | 1 | 425 | Status of temperature sensor 3 | 1 if connected, 0 if not connected |
| Temperature 3 | 12 | 426–437 | Temperature in °C | Signed, divide by 10 |
| Humidity | 7 | 438–444 | Humidity percentage | |
| Odometer | 35 | 445–479 | Odometer value in meters | |
| DateTime UTC | 32 | 480–511 | Timestamp | UTC time in seconds |
| TimeZone | 8 | 512–519 |
Timezone in quarter-hours (e.g., 22 = +5:30). Each unit = 15 mins. Value 22 = 22 × 15 mins = 330 mins = +5:30 Range: -48 to 56, 2's complement || | |
| Tail | ||||
| End Character | 8 | 0–7 | Starting character * (ASCII value 42) | * |
| CRC | 8 | 8–15 | 8-bit XOR CRC of data starting from $ to * (excluding $ and *) | |
Sample Packet
HEX "240370e21aeb7abfd5818011047aca0260081b780000000000000000000000000000307890000000000000000000000000000000000000000000000068a7f192162abf"
{"imei": 862942074896044, "packet_type": 1, "no_packets": 1, "packet_status": 1, "frame_number": 34, "alert_id": 1, "operator": 1, "signal_strength": 29, "mcc": 404, "mnc": 19, "cell_id": 64, "lac": 56256, "fix_status": 0, "latitude": 0.0, "latitude_dir": 0, "longitude": 0.0, "longitude_dir": 0, "hdop": 0.0, "pdop": 0.0, "speed": 0.0, "altitude": 0.0, "power": 1, "ignition": 1, "immobilizer": 0, "tamper": 0, "supply_volatge": 12.0, "internal_battery_volatge": 3.6, "fuel_sensor_status_1": 0, "fuel_percentage_1": 0.0, "fuel_value_sensor_1": 0, "fuel_sensor_status_2": 0, "fuel_percentage_2": 0.0, "fuel_value_sensor_2": 0, "fuel_sensor_status_3": 0, "fuel_percentage_3": 0.0, "fuel_value_sensor_3": 0, "analog_input1": 0.0, "analog_input2": 0.0, "digital_input1": 0, "digital_input2": 0, "digital_output1": 0, "digital_output2": 0, "temp_sensor_status_1": 0, "temperature_1": 0.0, "temp_sensor_status_2": 0, "temperature_2": 0.0, "temp_sensor_status_3": 0, "temperature_3": 0.0, "humidity": 0, "odometer": 0, "dateTime": 1755836818, "timezone": 22, "dateTime_tz": "2025-08-22 09:56:58", "error_code": 0
}
Telemetry Packet Parser
Header
| Field | Value |
|---|
Data Packets
JSON Output
Telemetry Packet Parser
Parsed Output:
| Field | Value |
|---|