Jump to content

Telemetry Packet: Difference between revisions

From Transight Wiki
No edit summary
No edit summary
Line 168: Line 168:
   <h2>Telemetry Packet Parser</h2>
   <h2>Telemetry Packet Parser</h2>


   <textarea id="hexInput" placeholder="Paste Telemtry Hex Packet Here..."></textarea><br/>
   <textarea id="hexInput" placeholder="Paste Telemetry Hex Packet Here..."></textarea><br/>
   <button id="parseBtn">Parse Packet</button>
   <button id="parseBtn">Parse Packet</button>


Line 174: Line 174:


   <div class="section">
   <div class="section">
     <h3>Header </h3>
     <h3>Header</h3>
     <table id="headerTable">
     <table id="headerTable">
       <thead><tr><th>Field</th><th>Value</th></tr></thead>
       <thead><tr><th>Field</th><th>Value</th></tr></thead>
Line 192: Line 192:


<script>
<script>
function cleanHex(str) {
document.addEventListener('DOMContentLoaded', function () {
  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) --- */
  function cleanHex(str) {
const FULL_FIELDS = [
    return (str || "").toString().trim().replace(/[^0-9a-fA-F]/g, "").toLowerCase();
  ["Start Byte", 0, 8], ["Data Length", 8, 20], ["No. of Packets", 20, 25],
  }
  ["IMEI", 25, 75], ["Packet Type", 75, 80], ["Packet Status", 80, 81],
  function hexToBits(hex) {
  ["Frame Number", 81, 97], ["Alert ID", 97, 102], ["Operator", 102, 106],
    if (!hex) return "";
  ["Signal Strength", 106, 111], ["MCC", 111, 121], ["MNC", 121, 131],
    if (hex.length % 2 === 1) hex = "0" + hex; // pad odd length
  ["Cell ID", 131, 147], ["LAC", 147, 163], ["Fix Status", 163, 164],
    var bytes = hex.match(/.{1,2}/g) || [];
  ["Latitude", 164, 193], ["NS Indication", 193, 194], ["Longitude", 194, 223],
    return bytes.map(function (b) {
  ["EW Indication", 223, 224], ["HDOP", 224, 234], ["PDOP", 234, 244],
      return parseInt(b, 16).toString(2).padStart(8, "0");
  ["Speed", 244, 259], ["Altitude", 259, 274], ["Power Status", 274, 275],
    }).join("");
  ["Ignition Status", 275, 276], ["Immobilizer Status", 276, 277],
  }
  ["Tamper", 277, 278], ["Supply Voltage", 278, 288], ["Internal Battery Voltage", 288, 294],
  function bitsToInt(bits) { return bits ? parseInt(bits, 2) : 0; }
  ["Fuel Sensor Status 1", 294, 295], ["Fuel Percentage 1", 295, 305],
  function bitsToSignedInt(bits) {
  ["Fuel Sensor Value 1", 305, 321], ["Fuel Sensor Status 2", 321, 322],
    if (!bits) return 0;
  ["Fuel Percentage 2", 322, 332], ["Fuel Sensor Value 2", 332, 348],
    var v = parseInt(bits, 2);
  ["Fuel Sensor Status 3", 348, 349], ["Fuel Percentage 3", 349, 359],
    var max = 1 << bits.length;
  ["Fuel Sensor Value 3", 359, 375], ["Analog Input 1", 375, 385],
    return v >= max / 2 ? v - max : v;
  ["Analog Input 2", 385, 395], ["Digital Input 1", 395, 396],
  }
  ["Digital Input 2", 396, 397], ["Digital Output 1", 397, 398],
  function addRow(tbody, k, v) {
  ["Digital Output 2", 398, 399], ["Temperature Sensor Status 1", 399, 400],
    var tr = document.createElement("tr");
  ["Temperature 1", 400, 412], ["Temperature Sensor Status 2", 412, 413],
    tr.innerHTML = "<td>" + k + "</td><td>" + v + "</td>";
  ["Temperature 2", 413, 425], ["Temperature Sensor Status 3", 425, 426],
    tbody.appendChild(tr);
  ["Temperature 3", 426, 438], ["Humidity", 438, 445], ["Odometer", 445, 480],
  }
  ["DateTime UTC", 480, 512], ["TimeZone", 512, 520]
 
];
  /* --- Field Map (bit indices) --- */
  var 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) */
  var SUB_FIELDS = FULL_FIELDS
    .filter(function (x) { return x[1] >= 80 && x[2] <= 520; })
    .map(function (x) { return [x[0], x[1] - 80, x[2] - 80]; });


/* Sub-packet fields (strip first 80 header bits) */
  /* Packet type label fix */
const SUB_FIELDS = FULL_FIELDS
   var PACKET_TYPE_NAME = { 1: "TelemetryPacket" };
  .filter(([_, s, e]) => s >= 80 && e <= 520)
   .map(([name, s, e]) => [name, s - 80, e - 80]);


/* Packet type label fix */
  /* Render each packet as a 2-column table */
const PACKET_TYPE_NAME = { 1: "TelemetryPacket" };
  function renderPacketsAsKVTables(dataArray) {
    var container = document.getElementById("dataTables");
    container.innerHTML = "";
    dataArray.forEach(function (pkt, idx) {
      var title = document.createElement("div");
      title.className = "pkt-title";
      title.textContent = "Packet #" + (idx + 1);
      container.appendChild(title);


/* Render each packet as a 2-column table */
      var table = document.createElement("table");
function renderPacketsAsKVTables(dataArray) {
      var thead = document.createElement("thead");
  const container = document.getElementById("dataTables");
      thead.innerHTML = "<tr><th>Field</th><th>Value</th></tr>";
  container.innerHTML = "";
      var tbody = document.createElement("tbody");


  dataArray.forEach((pkt, idx) => {
      SUB_FIELDS.forEach(function (f) {
    const title = document.createElement("div");
        var name = f[0];
    title.className = "pkt-title";
        addRow(tbody, name, pkt[name.replace(/\s+/g, '')]);
    title.textContent = `Packet #${idx + 1}`;
      });
    container.appendChild(title);


    const table = document.createElement("table");
      table.appendChild(thead);
    const thead = document.createElement("thead");
      table.appendChild(tbody);
    thead.innerHTML = `<tr><th>Field</th><th>Value</th></tr>`;
      container.appendChild(table);
     const tbody = document.createElement("tbody");
     });
  }


    /* Keep the same field order as SUB_FIELDS */
  /* Parse button logic */
     for (const [name] of SUB_FIELDS) {
  var parseBtn = document.getElementById("parseBtn");
       addRow(tbody, name, pkt[name.replace(/\s+/g, '')]);
  if (parseBtn) {
    }
     parseBtn.addEventListener("click", function () {
       var hex = cleanHex(document.getElementById("hexInput").value);
      var errors = document.getElementById("errors");
      var headerBody = document.querySelector("#headerTable tbody");
      var dataTables = document.getElementById("dataTables");
      var jsonOut = document.getElementById("jsonOut");


    table.appendChild(thead);
      errors.textContent = "";
    table.appendChild(tbody);
      headerBody.innerHTML = "";
    container.appendChild(table);
      dataTables.innerHTML = "";
  });
      jsonOut.textContent = "";
}


/* Parse button logic */
      if (!hex) { errors.textContent = "No data provided."; return; }
document.getElementById("parseBtn").addEventListener("click", () => {
      if (!hex.startsWith("24")) { errors.textContent = "Invalid frame: Start Byte must be 0x24."; return; }
  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 = "";
      var bits = hexToBits(hex);
  headerBody.innerHTML = "";
      if (!bits) { errors.textContent = "Invalid hex payload."; return; }
  dataTables.innerHTML = "";
  jsonOut.textContent = "";


  if (!hex) { errors.textContent = "No data provided."; return; }
      var dataLenBytes = bitsToInt(bits.slice(8, 20));
  if (!hex.startsWith("24")) { errors.textContent = "Invalid frame: Start Byte must be 0x24."; return; }
      var numItems    = bitsToInt(bits.slice(20, 25));


  const bits = hexToBits(hex);
      // IMEI (BigInt fallback)
  const dataLenBits = bits.slice(8, 20);
      var imeiBits = bits.slice(25, 75);
  const countBits = bits.slice(20, 25);
      var imei;
      try { imei = BigInt("0b" + imeiBits).toString(); }
      catch (e) { imei = parseInt(imeiBits, 2).toString(); }


  const dataLenBytes = bitsToInt(dataLenBits);
      var pktTypeVal = bitsToInt(bits.slice(75, 80));
  const numItems = bitsToInt(countBits);
      var pktTypeName = PACKET_TYPE_NAME[pktTypeVal] || pktTypeVal.toString();
  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;
      var perItemBytes = numItems > 0 ? dataLenBytes / numItems : 0;
  const perItemBits = perItemBytes * 8;
      var perItemBits = perItemBytes * 8;
  const totalBits = bits.length;
      var totalBits   = bits.length;
  const subStart = 80;
      var subStart     = 80;


  if (!Number.isInteger(perItemBytes)) { errors.textContent = "Data Length not divisible by packet count."; return; }
      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; }
      if (totalBits < subStart + perItemBits * numItems) { errors.textContent = "Frame too short for given count."; return; }


  /* Header table (2-column) */
      /* Header table (2-column) */
  addRow(headerBody, "Start Byte", "$");
      addRow(headerBody, "Start Byte", "$");
  addRow(headerBody, "Length", dataLenBytes);
      addRow(headerBody, "Length", dataLenBytes);
  addRow(headerBody, "Data Items", numItems);
      addRow(headerBody, "Data Items", numItems);
  addRow(headerBody, "IMEI", imei);
      addRow(headerBody, "IMEI", imei);
  addRow(headerBody, "Packet Type", pktTypeName);
      addRow(headerBody, "Packet Type", pktTypeName);


  /* Build per-packet objects keyed by compact field name */
      /* Build per-packet objects keyed by compact field name */
  const dataArray = [];
      var dataArray = [];
  const KEY_MAP = new Map(FULL_FIELDS.map(([name]) => [name, name.replace(/\s+/g, '')]));
      var KEY_MAP = new Map(FULL_FIELDS.map(function (x) { return [x[0], x[0].replace(/\s+/g, '')]; }));


  for (let i = 0; i < numItems; i++) {
      for (var i = 0; i < numItems; i++) {
    const sbeg = subStart + i * perItemBits;
        var sbeg = subStart + i * perItemBits;
    const send = sbeg + perItemBits;
        var send = sbeg + perItemBits;
    const subBits = bits.slice(sbeg, send);
        var subBits = bits.slice(sbeg, send);
    const obj = {};
        var obj = {};


    for (const [name, s, e] of SUB_FIELDS) {
        SUB_FIELDS.forEach(function (f) {
      const b = subBits.slice(s, e);
          var name = f[0], s = f[1], e = f[2];
      let val;
          var b = subBits.slice(s, e);
          var val;


      if (name === "Temperature 1" || name === "Temperature 2" || name === "Temperature 3") {
          if (name === "Temperature 1" || name === "Temperature 2" || name === "Temperature 3") {
        val = bitsToSignedInt(b);
            val = bitsToSignedInt(b);
      } else if (name === "Supply Voltage" || name === "Internal Battery Voltage") {
          } else if (name === "Supply Voltage" || name === "Internal Battery Voltage") {
        val = bitsToInt(b) / 10;             // e.g., 123 -> 12.3 V
            val = bitsToInt(b) / 10; // 123 -> 12.3 V
      } else {
          } else {
        val = bitsToInt(b);
            val = bitsToInt(b);
          }
          obj[KEY_MAP.get(name)] = val;
        });
 
        dataArray.push(obj);
       }
       }
      obj[KEY_MAP.get(name)] = val;
    }
    dataArray.push(obj);
  }


  /* Render each packet as its own key/value table */
      /* Render each packet as its own key/value table */
  renderPacketsAsKVTables(dataArray);
      renderPacketsAsKVTables(dataArray);


  /* JSON */
      /* JSON */
  const out = { StartByte: "$", Length: dataLenBytes, DataItems: numItems, IMEI: imei, PacketType: pktTypeName, Data: dataArray };
      var out = { StartByte: "$", Length: dataLenBytes, DataItems: numItems, IMEI: imei, PacketType: pktTypeName, Data: dataArray };
  jsonOut.textContent = JSON.stringify(out, null, 2);
      jsonOut.textContent = JSON.stringify(out, null, 2);
});
    });
  }
}); // DOMContentLoaded
</script>
</script>
</body>
</body>
</html>
</html>

Revision as of 07:19, 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:
  • 00 - Device Info Packet
  • 01 - Telemetry Packet
  • 02 - OTA Packet
  • 03 - Error Packet
  • 04 - Device Configuration Packet
  • 05 - IP Configuration Packet
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 – Grouped Frames

Telemetry Packet Parser


Header

FieldValue

Data Packets

JSON Output