OTA (Over-The-Air) Packet: Difference between revisions
No edit summary |
No edit summary |
||
| Line 77: | Line 77: | ||
"timezone": 22} | "timezone": 22} | ||
</pre> | </pre> | ||
<html lang="en"> | |||
<head> | |||
<meta charset="UTF-8"> | |||
<title>OTA Packet Parser</title> | |||
<style> | |||
body { font-family: Arial, sans-serif; padding: 20px; } | |||
textarea { width: 100%; height: 100px; font-family: monospace; margin-bottom: 10px; } | |||
button { | |||
padding: 10px 20px; border-radius: 20px; | |||
font-weight: bold; background-color: #680022; color: white; | |||
border: none; cursor: pointer; | |||
} | |||
button:hover { background-color: #680022; } | |||
table { width: 100%; border-collapse: collapse; margin-top: 20px; } | |||
th, td { border: 1px solid #ccc; padding: 8px; } | |||
th { background-color: #680022; color: white; } | |||
</style> | |||
</head> | |||
<body> | |||
<h2>OTA Packet Parser</h2> | |||
<textarea id="hexInput" placeholder="Paste OTA packet hex here..."></textarea><br> | |||
<button onclick="parseOTAPacket()">Parse OTA Packet</button> | |||
<h3>Parsed Output:</h3> | |||
<table id="resultTable"> | |||
<thead><tr><th>Field</th><th>Value</th></tr></thead> | |||
<tbody></tbody> | |||
</table> | |||
<script> | |||
function hexToBits(hex) { | |||
return hex.match(/.{1,2}/g).map(b => | |||
parseInt(b, 16).toString(2).padStart(8, '0')).join(''); | |||
} | |||
function bitsToInt(bits) { | |||
return parseInt(bits, 2); | |||
} | |||
function bitsToSignedInt(bits) { | |||
let value = parseInt(bits, 2); | |||
const max = Math.pow(2, bits.length); | |||
return value >= max / 2 ? value - max : value; | |||
} | |||
function bigIntFromBits(bits) { | |||
return BigInt('0b' + bits).toString(); | |||
} | |||
function bitsToAscii(bits) { | |||
let ascii = ''; | |||
for (let i = 0; i < bits.length; i += 8) { | |||
const byte = bits.slice(i, i + 8); | |||
ascii += String.fromCharCode(parseInt(byte, 2)); | |||
} | |||
return ascii; | |||
} | |||
function parseOTAPacket() { | |||
const hex = document.getElementById("hexInput").value.trim().toLowerCase(); | |||
const resultBody = document.querySelector("#resultTable tbody"); | |||
resultBody.innerHTML = ""; | |||
if (!hex.startsWith("24")) { | |||
alert("Packet must start with hex '24'"); | |||
return; | |||
} | |||
const bits = hexToBits(hex); | |||
const getBits = (start, end) => bits.slice(start, end); | |||
const output = (label, value) => { | |||
const row = document.createElement("tr"); | |||
row.innerHTML = `<td>${label}</td><td>${value}</td>`; | |||
resultBody.appendChild(row); | |||
}; | |||
// Parse known fixed fields | |||
const imei = bigIntFromBits(getBits(25, 75)); // Correct IMEI parse | |||
output("IMEI", imei); | |||
output("Packet Type", bitsToInt(getBits(75, 80))); | |||
output("OTA Source", bitsToInt(getBits(80, 85))); | |||
output("Error Status", bitsToInt(getBits(85, 93))); | |||
const cellBits = getBits(93, 144); | |||
const isWithCountryCode = cellBits[0] === '1'; | |||
const cellNo = parseInt(cellBits.slice(1), 2); | |||
output("Cell Number", isWithCountryCode ? `+${cellNo}` : cellNo); | |||
const dt = bitsToInt(getBits(144, 176)); | |||
output("UTC Timestamp", new Date(dt * 1000).toISOString()); | |||
const timezoneRaw = bitsToSignedInt(getBits(176, 184)); | |||
const offsetMinutes = timezoneRaw * 15; | |||
const h = Math.floor(Math.abs(offsetMinutes) / 60); | |||
const m = Math.abs(offsetMinutes % 60); | |||
const tzSign = offsetMinutes >= 0 ? "+" : "-"; | |||
output("Timezone", `${offsetMinutes} mins = UTC${tzSign}${h}:${m.toString().padStart(2, '0')}`); | |||
const localTime = new Date((dt + offsetMinutes * 60) * 1000) | |||
.toISOString().replace("T", " ").replace(".000Z", ""); | |||
output("DateTime (Local)", localTime); | |||
const cmdLength = bitsToInt(getBits(184, 192)); | |||
output("Command Length", cmdLength); | |||
const cmdBits = getBits(192, 192 + cmdLength * 8); | |||
const command = bitsToAscii(cmdBits); | |||
output("Command", command); | |||
const respLenStart = 192 + cmdLength * 8; | |||
const responseLength = bitsToInt(getBits(respLenStart, respLenStart + 8)); | |||
output("Response Length", responseLength); | |||
const respBits = getBits(respLenStart + 8, respLenStart + 8 + responseLength * 8); | |||
const response = bitsToAscii(respBits); | |||
output("Response", response); | |||
} | |||
</script> | |||
Revision as of 09:20, 21 July 2025
| Field | Size (bits) | Bit Range | Description | Breakdown |
|---|---|---|---|---|
| Header | ||||
| Start byte | 8 | 0–7 | Starting character $ (ASCII value 36) | $ |
| Data length | 12 | 8–18 | 2-byte length of the data following the header | — |
| Num of data packets | 5 | 19–23 | Number of packets (0–32) | 0 - 32 |
| IMEI | 50 | 24–74 | Unique device identifier | Integer type: e.g., 887744556677882 |
| Packet Type | 5 | 75–79 | Type of packet |
00 - Device info |
| Data | ||||
| OTA Source | 5 | — | Source of OTA |
00 - OTA_SRC_SERIAL |
| Error status | 8 | — | Non-zero value indicates an error | 0 = No Error, Non-zero = Error |
| Cell Number | 51 | — | First bit (MSB) indicates presence of country code. Remaining 50 bits = mobile number |
Example: 400D6276ACF52 (+919784312658) |
| Date & Time | 32 | — | UTC Timestamp | UTC time in seconds |
| Timezone | 8 | — | Timezone in quarter-hours (e.g., 22 = +5:30) |
22 × 15 mins = 330 mins = +5:30 |
| Command Length | 8 | — | Length of command string | — |
| Command | up to 600 bits (75 bytes) | — | Command with arguments, null-terminated string |
Example: SET CUIOCFG:IO1-DI-IGN*IO2_AN_GEN*IO3-DI-PBN*IO4-DO-IMB* |
| Response Length | 8 | — | Length of response string | — |
| Response | up to 512 bits (64 bytes) | — | Response string from device |
Example: IOCFG:IO1-DI-IGN*IO2-AN_GEN*IO3-DI-PBN*IO4-DO-IMB* |
| Tail | ||||
| End Character | 8 | 0–7 | End character * (ASCII value 42) | * |
| CRC | 8 | 8–15 | 8-bit XOR CRC from $ to * (excluding both) | — |
Sample Packet
{"HEX:240340e1cabd09208ca210000000000000006824646f160947455420545355524c1c55524c3a5443503a3134312e3134372e3132382e3135303a383738382aa2",
"cell_no": 0,
"command": "GET TSURL",
"command_length": 9,
"dateTime": 1747215471,
"dateTime_tz": "2025-05-14 15:07:51.000",
"error_status": 0,
"imei": 860187062240357,
"insert_time": "Wed, 14 May 2025 09:38:03 GMT",
"ota_source": 2,
"packetType": 2,
"response": "URL:TCP:141.147.128.150:8788",
"response_length": 28,
"send_time": "2025-05-14 15:07:51",
"timezone": 22}
<html lang="en"> <head>
<meta charset="UTF-8">
<title>OTA Packet Parser</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
textarea { width: 100%; height: 100px; font-family: monospace; margin-bottom: 10px; }
button {
padding: 10px 20px; border-radius: 20px;
font-weight: bold; background-color: #680022; color: white;
border: none; cursor: pointer;
}
button:hover { background-color: #680022; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ccc; padding: 8px; }
th { background-color: #680022; color: white; }
</style>
</head> <body>
OTA Packet Parser
<textarea id="hexInput" placeholder="Paste OTA packet hex here..."></textarea>
<button onclick="parseOTAPacket()">Parse OTA Packet</button>
Parsed Output:
<thead></thead><tbody></tbody>
| Field | Value |
|---|
<script>
function hexToBits(hex) {
return hex.match(/.{1,2}/g).map(b =>
parseInt(b, 16).toString(2).padStart(8, '0')).join();
}
function bitsToInt(bits) {
return parseInt(bits, 2);
}
function bitsToSignedInt(bits) {
let value = parseInt(bits, 2); const max = Math.pow(2, bits.length); return value >= max / 2 ? value - max : value;
}
function bigIntFromBits(bits) {
return BigInt('0b' + bits).toString();
}
function bitsToAscii(bits) {
let ascii = ;
for (let i = 0; i < bits.length; i += 8) {
const byte = bits.slice(i, i + 8);
ascii += String.fromCharCode(parseInt(byte, 2));
}
return ascii;
}
function parseOTAPacket() {
const hex = document.getElementById("hexInput").value.trim().toLowerCase();
const resultBody = document.querySelector("#resultTable tbody");
resultBody.innerHTML = "";
if (!hex.startsWith("24")) {
alert("Packet must start with hex '24'");
return;
}
const bits = hexToBits(hex);
const getBits = (start, end) => bits.slice(start, end);
const output = (label, value) => {
const row = document.createElement("tr");
row.innerHTML = `${label}${value}`;
resultBody.appendChild(row); };
// Parse known fixed fields
const imei = bigIntFromBits(getBits(25, 75)); // Correct IMEI parse
output("IMEI", imei);
output("Packet Type", bitsToInt(getBits(75, 80)));
output("OTA Source", bitsToInt(getBits(80, 85)));
output("Error Status", bitsToInt(getBits(85, 93)));
const cellBits = getBits(93, 144);
const isWithCountryCode = cellBits[0] === '1';
const cellNo = parseInt(cellBits.slice(1), 2);
output("Cell Number", isWithCountryCode ? `+${cellNo}` : cellNo);
const dt = bitsToInt(getBits(144, 176));
output("UTC Timestamp", new Date(dt * 1000).toISOString());
const timezoneRaw = bitsToSignedInt(getBits(176, 184));
const offsetMinutes = timezoneRaw * 15;
const h = Math.floor(Math.abs(offsetMinutes) / 60);
const m = Math.abs(offsetMinutes % 60);
const tzSign = offsetMinutes >= 0 ? "+" : "-";
output("Timezone", `${offsetMinutes} mins = UTC${tzSign}${h}:${m.toString().padStart(2, '0')}`);
const localTime = new Date((dt + offsetMinutes * 60) * 1000)
.toISOString().replace("T", " ").replace(".000Z", "");
output("DateTime (Local)", localTime);
const cmdLength = bitsToInt(getBits(184, 192));
output("Command Length", cmdLength);
const cmdBits = getBits(192, 192 + cmdLength * 8);
const command = bitsToAscii(cmdBits);
output("Command", command);
const respLenStart = 192 + cmdLength * 8;
const responseLength = bitsToInt(getBits(respLenStart, respLenStart + 8));
output("Response Length", responseLength);
const respBits = getBits(respLenStart + 8, respLenStart + 8 + responseLength * 8);
const response = bitsToAscii(respBits);
output("Response", response);
} </script>