HR strap battery + low-charge voice/beep alerts at 10% and 5%
- BleHrService reads the standard Battery service (0x180F/0x2A19) 2.5s after the HR subscribe and every 60s; writes 'bat' into ble_hr_data.json. - HrReading gains battery: Option<u8> (serde alias 'bat'). - gps.js: the HR poll captures reading.battery; _checkBatteryAlert speaks (speechSynthesis ru-RU) and beeps once at 10% and 5% (beep is the guaranteed cue where TTS is absent), resetting above 15%. - Workout screen shows the strap charge in its status line.
This commit is contained in:
parent
f704498d11
commit
62434d6155
5 changed files with 95 additions and 4 deletions
|
|
@ -31,6 +31,9 @@ class BleHrService : Service() {
|
|||
val HR_SERVICE_UUID: UUID = UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb")
|
||||
val HR_MEASUREMENT_UUID: UUID = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")
|
||||
val CCCD_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
||||
// Standard Battery Service (strap charge)
|
||||
val BATTERY_SERVICE: UUID = UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb")
|
||||
val BATTERY_LEVEL: UUID = UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
// FitPro Nordic UART
|
||||
val FITPRO_SERVICE: UUID = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9d")
|
||||
|
|
@ -51,6 +54,7 @@ class BleHrService : Service() {
|
|||
private var gatt: BluetoothGatt? = null
|
||||
private var deviceType = "hr_standard"
|
||||
private var dataFile: File? = null
|
||||
private var lastBattery: Int = -1 // strap charge %, -1 = unknown
|
||||
private var scanResultsFile: File? = null
|
||||
private var fitproWriteChar: BluetoothGattCharacteristic? = null
|
||||
private var fitproGatt: BluetoothGatt? = null
|
||||
|
|
@ -260,6 +264,15 @@ class BleHrService : Service() {
|
|||
override fun onCharacteristicChanged(g: BluetoothGatt, char: BluetoothGattCharacteristic) {
|
||||
onCharacteristicChanged(g, char, char.value)
|
||||
}
|
||||
// Battery level read (0x2A19). Two overloads: API 33+ passes the value, older reads char.value.
|
||||
override fun onCharacteristicRead(g: BluetoothGatt, char: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
|
||||
if (char.uuid == BATTERY_LEVEL && value.isNotEmpty()) { lastBattery = value[0].toInt() and 0xFF; Log.d(TAG, "Battery: $lastBattery%") }
|
||||
}
|
||||
@Deprecated("Deprecated in API 33")
|
||||
override fun onCharacteristicRead(g: BluetoothGatt, char: BluetoothGattCharacteristic, status: Int) {
|
||||
val v = char.value
|
||||
if (char.uuid == BATTERY_LEVEL && v != null && v.isNotEmpty()) { lastBattery = v[0].toInt() and 0xFF; Log.d(TAG, "Battery: $lastBattery%") }
|
||||
}
|
||||
|
||||
override fun onDescriptorWrite(g: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
|
||||
Log.d(TAG, "onDescriptorWrite status=$status")
|
||||
|
|
@ -284,6 +297,25 @@ class BleHrService : Service() {
|
|||
g.writeDescriptor(desc)
|
||||
}
|
||||
Log.d(TAG, "Standard HR setup complete")
|
||||
// Strap battery (0x180F/0x2A19): read once after the HR subscribe settles, then every 60s.
|
||||
android.os.Handler(mainLooper).also { h ->
|
||||
val readBat = object : Runnable {
|
||||
override fun run() {
|
||||
if (!isRunning) return
|
||||
readBattery(g)
|
||||
h.postDelayed(this, 60000)
|
||||
}
|
||||
}
|
||||
h.postDelayed(readBat, 2500)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readBattery(g: BluetoothGatt) {
|
||||
try {
|
||||
val bs = g.getService(BATTERY_SERVICE) ?: return
|
||||
val bc = bs.getCharacteristic(BATTERY_LEVEL) ?: return
|
||||
g.readCharacteristic(bc)
|
||||
} catch (e: Exception) { Log.e(TAG, "readBattery: ${e.message}") }
|
||||
}
|
||||
|
||||
private val gattQueue = mutableListOf<() -> Unit>()
|
||||
|
|
@ -382,8 +414,9 @@ class BleHrService : Service() {
|
|||
}
|
||||
if (hr <= 0) return
|
||||
val ts = System.currentTimeMillis() / 1000
|
||||
writeData("""{"hr":$hr,"ts":$ts}""")
|
||||
Log.d(TAG, "HR: $hr")
|
||||
val batJson = if (lastBattery in 0..100) ""","bat":$lastBattery""" else ""
|
||||
writeData("""{"hr":$hr,"ts":$ts$batJson}""")
|
||||
Log.d(TAG, "HR: $hr bat: $lastBattery")
|
||||
}
|
||||
|
||||
private fun parseFitProData(data: ByteArray) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ pub struct HrReading {
|
|||
pub timestamp: u64,
|
||||
#[serde(default)]
|
||||
pub device: String,
|
||||
#[serde(alias = "bat", default)]
|
||||
pub battery: Option<u8>,
|
||||
}
|
||||
|
||||
// Shared state for BLE operations
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue