fix: parse HAL two-anchor ranging format (8-byte int32×2)
The app's old parser expected a multi-anchor protocol: [count][anchorID+rangeMM+RSSI+age]×N (min 10 bytes) HAL's firmware sends a fixed 8-byte packet: [int32 front_mm LE][int32 back_mm LE] With the old parser data[0] was interpreted as anchor count (e.g. 0xF8 = 248 for a 4.6m reading), the loop guard failed immediately, and every notify returned [] — hence "waiting for ranging data" despite the tag showing live ranges on OLED. Changes: - BLEPackets: detect 8-byte HAL format by length; decode as anchor id=0 (Front) and id=1 (Back); legacy multi-anchor path retained for forward compatibility - AnchorInfo: rssiDBm is now Optional (nil when not reported); label maps id 0→"F", 1→"B" for the two-anchor HAL format - BLEStatusView: guard on optional rssiString before rendering Auto-reconnect confirmed correct (2s delay, bluetooth-central background mode declared in Info.plist). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7f9f159016
commit
12338f491e
@ -4,15 +4,22 @@ import Foundation
|
||||
struct AnchorInfo: Identifiable {
|
||||
let id: UInt8
|
||||
let rangeMetres: Double
|
||||
let rssiDBm: Double
|
||||
let ageMs: UInt16 // age reported by tag firmware
|
||||
let rssiDBm: Double? // nil when not reported (HAL two-anchor format)
|
||||
let ageMs: UInt16 // age reported by tag firmware; 0 when not reported
|
||||
let receivedAt: Date
|
||||
|
||||
/// True when the measurement is more than 3 seconds old (local wall-clock).
|
||||
var isStale: Bool { Date().timeIntervalSince(receivedAt) > 3.0 }
|
||||
|
||||
/// Display string for the anchor identifier.
|
||||
var label: String { "A\(id)" }
|
||||
/// IDs 0/1 map to Front/Back (HAL two-anchor format); others show "A<n>".
|
||||
var label: String {
|
||||
switch id {
|
||||
case 0: return "F"
|
||||
case 1: return "B"
|
||||
default: return "A\(id)"
|
||||
}
|
||||
}
|
||||
|
||||
/// Formatted range string matching the Flutter app style.
|
||||
var rangeString: String {
|
||||
@ -21,6 +28,9 @@ struct AnchorInfo: Identifiable {
|
||||
: String(format: "%.1f m", rangeMetres)
|
||||
}
|
||||
|
||||
/// Formatted RSSI string.
|
||||
var rssiString: String { "\(Int(rssiDBm.rounded())) dBm" }
|
||||
/// Formatted RSSI string, or nil when RSSI was not reported.
|
||||
var rssiString: String? {
|
||||
guard let r = rssiDBm else { return nil }
|
||||
return "\(Int(r.rounded())) dBm"
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,27 +87,46 @@ enum BLEPackets {
|
||||
|
||||
// MARK: - Ranging notification parser
|
||||
//
|
||||
// [0] Uint8 anchor count N
|
||||
// Per anchor (9 bytes, offset = 1 + i×9):
|
||||
// [+0] Uint8 anchor index
|
||||
// [+1-4] Int32 LE range mm
|
||||
// [+5-6] Int16 LE RSSI × 10 (dBm × 10)
|
||||
// [+7-8] Uint16LE age ms
|
||||
// HAL firmware format (8 bytes, 2 fixed anchors):
|
||||
// [0-3] Int32 LE front anchor range mm (id=0)
|
||||
// [4-7] Int32 LE back anchor range mm (id=1)
|
||||
//
|
||||
// Legacy multi-anchor format (future):
|
||||
// [0] Uint8 anchor count N
|
||||
// Per anchor (9 bytes):
|
||||
// [+0] Uint8 anchor index
|
||||
// [+1-4] Int32 LE range mm
|
||||
// [+5-6] Int16 LE RSSI × 10 (dBm × 10)
|
||||
// [+7-8] Uint16LE age ms
|
||||
|
||||
static func parseRanging(_ data: Data) -> [AnchorInfo] {
|
||||
let now = Date()
|
||||
|
||||
// HAL two-anchor format: exactly 8 bytes, two Int32 LE range values
|
||||
if data.count == 8 {
|
||||
let frontMM = data.readInt32LE(at: 0)
|
||||
let backMM = data.readInt32LE(at: 4)
|
||||
return [
|
||||
AnchorInfo(id: 0, rangeMetres: Double(frontMM) / 1000.0,
|
||||
rssiDBm: nil, ageMs: 0, receivedAt: now),
|
||||
AnchorInfo(id: 1, rangeMetres: Double(backMM) / 1000.0,
|
||||
rssiDBm: nil, ageMs: 0, receivedAt: now)
|
||||
]
|
||||
}
|
||||
|
||||
// Legacy multi-anchor format: [count][anchorID+rangeMM+RSSI+age] × N
|
||||
guard data.count >= 1 else { return [] }
|
||||
let count = Int(data[0])
|
||||
let now = Date()
|
||||
var result: [AnchorInfo] = []
|
||||
|
||||
for i in 0..<count {
|
||||
let base = 1 + i * 9
|
||||
guard base + 9 <= data.count else { break }
|
||||
|
||||
let anchorID = data[base]
|
||||
let rangeMM = data.readInt32LE(at: base + 1)
|
||||
let anchorID = data[base]
|
||||
let rangeMM = data.readInt32LE(at: base + 1)
|
||||
let rssiTimes10 = data.readInt16LE(at: base + 5)
|
||||
let ageMs = data.readUInt16LE(at: base + 7)
|
||||
let ageMs = data.readUInt16LE(at: base + 7)
|
||||
|
||||
result.append(AnchorInfo(
|
||||
id: anchorID,
|
||||
@ -116,6 +135,7 @@ enum BLEPackets {
|
||||
ageMs: ageMs,
|
||||
receivedAt: now
|
||||
))
|
||||
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@ -144,9 +144,11 @@ struct BLEStatusView: View {
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
Text(anchor.rssiString)
|
||||
.font(.system(size: 11))
|
||||
.foregroundStyle(.secondary)
|
||||
if let rssi = anchor.rssiString {
|
||||
Text(rssi)
|
||||
.font(.system(size: 11))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
if anchor.isStale {
|
||||
Text("STALE")
|
||||
.font(.system(size: 10, weight: .bold))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user