Project

General

Profile

User Story #74 » androidApp-62-capture-uwb-rssi-into-signalquality.patch

knight8241, 08/07/2026 18:33

View differences:

androidApp/src/main/java/com/aether/mofe/platform/AndroidMofePlatformProvider.kt
// every bearing over ~3° as "out of range", so self-yaw never anchored.
azimuth = r.azimuthDeg?.toDouble()?.let { Math.toRadians(it) },
elevation = r.elevationDeg?.toDouble()?.let { Math.toRadians(it) },
signalQuality = 1.0,
rssi = 0.0,
// Per-link signal strength captured from the UWB ranging callback (#62).
// Previously both fields were dead constants (signalQuality≡1.0, rssi≡0.0),
// so MeasurementPreFilter's signal-quality gate (raw.signalQuality < 0.5)
// never fired and weak long-range / NLOS links were treated as pristine.
//
// Android UWB reports RSSI in dBm (when the stack exposes it), not a
// normalized [0,1] quality. Thread the real dBm into `rssi`, and DERIVE
// `signalQuality` from it with the SAME linear map the engine already applies
// to RSSI in MeasurementPreFilter.computeRawConfidence — ((rssi + 100)/50)
// clamped to [0,1] — so a link at ≤ -75 dBm crosses the 0.5 gate. When the
// stack reports no RSSI (r.rssiDbm == null) we fall back to the previous dead
// defaults, leaving behavior unchanged for hardware/versions that expose none.
// NOTE: the -100..-50 dBm scale is the engine's existing assumption; real UWB
// RSSI may need on-device recalibration of that map (see #62 follow-up).
signalQuality = r.rssiDbm?.let { ((it + 100.0) / 50.0).coerceIn(0.0, 1.0) } ?: 1.0,
rssi = r.rssiDbm?.toDouble() ?: 0.0,
)
}
androidApp/src/main/java/com/aether/mofe/platform/AndroidUwbService.kt
import android.util.Base64
import android.util.Log
import androidx.core.uwb.RangingParameters
import androidx.core.uwb.RangingPosition
import androidx.core.uwb.RangingResult
import androidx.core.uwb.UwbAddress
import androidx.core.uwb.UwbClientSessionScope
......
azimuthDeg = pos.azimuth?.value,
elevationDeg = pos.elevation?.value,
timestampMs = System.currentTimeMillis(),
// Capture per-link RSSI off this range sample (#62) so the
// engine can see weak links. Best-effort / null when the
// stack does not report it — see extractRssiDbm.
rssiDbm = extractRssiDbm(pos),
)
)
}
......
private fun b64(bytes: ByteArray): String = Base64.encodeToString(bytes, Base64.NO_WRAP)
private fun hex(bytes: ByteArray): String = bytes.joinToString("") { "%02x".format(it) }
/**
* Read per-link RSSI (dBm) off a UWB [RangingPosition] DEFENSIVELY (#62).
*
* WHY REFLECTION rather than a direct `position.rssiDbm`: receive-signal strength
* is not a stable part of the AndroidX UWB surface across the versions and vendor
* stacks this app runs against — `RangingPosition.rssiDbm` was added partway along
* the androidx.core.uwb 1.0.0 alpha→beta line, and some OEM builds still back it
* with the framework RSSI_UNKNOWN sentinel. A direct property reference would fail
* to compile/link where it is absent, and this Android module cannot be built in
* the offline dev sandbox to catch that. Reflection keeps the capture best-effort:
* real dBm when the stack reports it, null (→ the engine keeps its prior default)
* when it does not. First-path / CIR is not exposed on the AndroidX surface at all,
* so it is deliberately not attempted here.
*
* @return a plausible negative dBm reading, or null when unavailable / unknown.
*/
private fun extractRssiDbm(position: RangingPosition): Int? = runCatching {
val raw = (position.javaClass.getMethod("getRssiDbm").invoke(position) as? Number)?.toInt()
// Valid UWB RSSI is a negative dBm (~ -30 … -100). Reject the framework
// RSSI_UNKNOWN sentinel (-128) and any non-negative "unset" reading.
if (raw != null && raw in -127..-1) raw else null
}.getOrNull()
companion object {
// Fixed UWB channel so both peers agree without an OOB exchange. Channel 9 + BPRF
// preambles 9–12 are broadly supported (Pixel + Galaxy). UWB_PREAMBLE is the legacy
......
val azimuthDeg: Float?,
val elevationDeg: Float?,
val timestampMs: Long,
/** Per-link receive signal strength in dBm from the UWB ranging callback, or
* null when the platform/hardware does not report it (read defensively in
* AndroidUwbService.extractRssiDbm). Threaded to the engine's rssi /
* signalQuality fields in AndroidUwbRangingServiceAdapter. Defaulted so the
* non-radio emitExternalRange path stays source-compatible. (#62) */
val rssiDbm: Int? = null,
)
enum class SessionStatus {
    (1-1/1)