(data: ArrayBuffer)
| 223 | * Decode telemetry from 120-byte binary format. |
| 224 | */ |
| 225 | export function decodeTelemetry(data: ArrayBuffer): DecodedTelemetry | null { |
| 226 | const view = new DataView(data); |
| 227 | |
| 228 | // Expect exactly 120 bytes |
| 229 | if (data.byteLength < 120) { |
| 230 | return null; |
| 231 | } |
| 232 | |
| 233 | const msgType = view.getUint8(0); |
| 234 | if (msgType !== MSG_TELEMETRY) { |
| 235 | return null; |
| 236 | } |
| 237 | |
| 238 | // Parse header |
| 239 | const mode = view.getUint8(1) as Mode; |
| 240 | const sequence = view.getUint16(2, true); |
| 241 | // System metrics [4-6], reserved [7] |
| 242 | const cpu_percent = view.getUint8(4); |
| 243 | const mem_percent = view.getUint8(5); |
| 244 | const disk_percent = view.getUint8(6); |
| 245 | |
| 246 | // Pose (3x f64 = 24 bytes) [8-31] |
| 247 | const poseX = view.getFloat64(8, true); |
| 248 | const poseY = view.getFloat64(16, true); |
| 249 | const poseTheta = view.getFloat64(24, true); |
| 250 | |
| 251 | // Battery voltage + system current [32-39] |
| 252 | // New format: f32 voltage [32-35] + f32 current [36-39] |
| 253 | // Old format: f64 voltage [32-39] (no current) |
| 254 | // Detect by checking if f32 read produces a sane voltage (0-100V range) |
| 255 | const voltageF32 = view.getFloat32(32, true); |
| 256 | const isNewFormat = voltageF32 > 0 && voltageF32 < 100; |
| 257 | const batteryVoltage = isNewFormat ? voltageF32 : view.getFloat64(32, true); |
| 258 | const systemCurrent = isNewFormat ? view.getFloat32(36, true) : 0; |
| 259 | |
| 260 | // Timestamp [40-47] |
| 261 | const timestampLow = view.getUint32(40, true); |
| 262 | const timestampHigh = view.getUint32(44, true); |
| 263 | const timestamp_us = timestampLow + timestampHigh * 0x100000000; |
| 264 | |
| 265 | // Commanded velocity [48-55] |
| 266 | const cmdLinear = view.getFloat32(48, true); |
| 267 | const cmdAngular = view.getFloat32(52, true); |
| 268 | |
| 269 | // Measured velocity [56-63] |
| 270 | const measLinear = view.getFloat32(56, true); |
| 271 | const measAngular = view.getFloat32(60, true); |
| 272 | |
| 273 | // Acceleration [64-71] |
| 274 | const accelLinear = view.getFloat32(64, true); |
| 275 | const accelAngular = view.getFloat32(68, true); |
| 276 | |
| 277 | // Motor temps (4x f32 = 16 bytes) [72-87] |
| 278 | const motor_temps: [number, number, number, number] = [ |
| 279 | view.getFloat32(72, true), |
| 280 | view.getFloat32(76, true), |
| 281 | view.getFloat32(80, true), |
| 282 | view.getFloat32(84, true), |
no outgoing calls
no test coverage detected