BLE Codec PHY for better range and obstacle penetration#873
Conversation
Expose BLE 5 PHY selection as a per-interface config option so relay nodes can use Coded PHY for ~4× range through walls and rubble at reduced throughput. Defaults to 1M PHY for full backward compatibility. Components: - BleCodec enum (BlePowerSettings.kt): PHY_1M, PHY_2M, CODED_S2, CODED_S8 each with displayName and description; fromString() defaults to PHY_1M - bleCodec field on InterfaceConfig.AndroidBLE (default "PHY_1M"), serialised as "ble_codec" JSON key; absent key deserialises to PHY_1M - BleGattClient.preferredCodec var + applyPreferredPhy(): calls BluetoothGatt.setPreferredPhy() after GATT services are discovered; guarded on Build.VERSION_CODES.O — BT4 devices silently stay on 1M - KotlinBLEBridge.configureCodec(): propagates codec from InterfaceConfig down to the active BleGattClient before connection - AndroidBLEFields UI: four-segment button row (1M | 2M | S=2 | S=8) with per-selection description; S=8 shows an FEC note explaining the counter-intuitive power trade-off at poor link quality Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Call KotlinBLEBridge.configureCodec(config.bleCodec) in NativeInterfaceFactory.startBleInterface() so the PHY preference is applied before the first GATT connection attempt. - Correct S=8 note: FEC eliminates retransmissions at poor signal quality, so net battery draw at bad link budgets (rubble, walls) can be lower than 1M PHY — not higher. Changed copy and color accordingly. Also add "FEC" to CODED_S2 / CODED_S8 descriptions in BleCodec enum and UI. - Add BleCodecTest: fromString() resolution, PHY constant mapping, default / case-insensitive / unknown-value fallbacks, FEC description assertions. - Add InterfaceConfigExtTest: AndroidBLE ble_codec serialisation round-trips for all four codec values. - Add InterfaceRepositoryTest: AndroidBLE deserialises missing ble_codec key to PHY_1M (backward compat) and round-trips all four values. - Add InterfaceConfigDialogTest: PHY Codec section renders, all four buttons present, 1M default description, S=8 FEC note present/absent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR extends Confidence Score: 4/5Safe to merge; all findings are P2 quality/consistency issues with no runtime impact on the happy path. The BLE stack wiring is correct, serialization is backward-compatible, and API-level guards are in place. The only issues are duplicate description strings that have drifted from the enum, a misleading log when Bluetooth hardware is absent, and a tautological test assertion — none of these affect runtime behavior on supported devices. InterfaceConfigDialog.kt (hardcoded description strings diverge from BleCodec.description) and BleCodecTest.kt (tautological assertion). Important Files Changed
Sequence DiagramsequenceDiagram
participant UI as AndroidBLEFields (UI)
participant VM as InterfaceManagementViewModel
participant Repo as InterfaceRepository
participant DB as InterfaceEntity (JSON)
participant Factory as NativeInterfaceFactory
participant Bridge as KotlinBLEBridge
participant GATT as BleGattClient
UI->>VM: onConfigUpdate(bleCodec = "CODED_S8")
VM->>Repo: saveInterface(InterfaceConfig.AndroidBLE(bleCodec))
Repo->>DB: toJsonString() → {"ble_codec":"CODED_S8"}
Note over Factory,GATT: On interface start
Factory->>Bridge: configureCodec("CODED_S8")
Bridge->>GATT: preferredCodec = BleCodec.CODED_S8
Note over GATT: On GATT connection (API 26+)
GATT->>GATT: applyPreferredPhy()
GATT->>GATT: setPreferredPhy(PHY_LE_CODED_MASK, PHY_LE_CODED_MASK, PHY_OPTION_S8)
|
Add BLE PHY Codec Selector (PHY_1M / PHY_2M / CODED_S2 / CODED_S8)
Extends the AndroidBLE interface with a PHY codec preference that persists across app restarts and is applied at GATT connection time.
Why
Bluetooth 5 introduced LE Coded PHY, which trades throughput for range using forward error correction. Different deployments need different trade-offs: a dense urban mesh benefits from standard 1M PHY throughput, while disaster-response or rural scenarios need the ~4× range that S=8 provides. Previously there was no way to select PHY in the UI — the codec was always 1M regardless of the environment.
What Changed
Model & Persistence
BleCodecenum (PHY_1M,PHY_2M,CODED_S2,CODED_S8) inBlePowerSettings.ktwithdisplayNameanddescriptionstrings used directly by the UIbleCodec: Stringfield onInterfaceConfig.AndroidBLE, defaulting to"PHY_1M"ble_codecin the interface JSON config; absent key deserializes toPHY_1Mfor backward compatibilityBLE Stack Wiring
BleGattClient.applyPreferredPhy()callssetPreferredPhy(txPhy, rxPhy, phyOptions)after GATT connection, guarded byBuild.VERSION_CODES.O(API 26+) — BT4 devices skip it cleanlyKotlinBLEBridge.configureCodec(bleCodec)propagates the persisted selection to the GATT client before the interface startsNativeInterfaceFactory.startBleInterface()callsconfigureCodec()so the preference is in place when the first connection firesapplyPreferredPhy()UI
1M/2M/S=2/S=8) inAndroidBLEFieldsbleCodecin the config state immediatelyWhy the S=8 Note Is Neutral, Not a Warning
LE Coded PHY S=8 runs at 125 Kbps with FEC. The FEC overhead costs roughly 70% more TX power per packet, but at link budgets where 1M PHY would need more than 2 retransmissions per packet, the avoided retransmissions more than compensate. At poor signal (rubble, thick walls, disaster response) net battery draw can be lower than 1M PHY. The note reflects this accurately rather than discouraging a mode that is net-beneficial in the primary use case.
Test Coverage