Workout HR: log samples natively so recording survives screen-off
The JS 2s poll that stored workout HR samples is frozen by Android when the screen is off, so a sleep-long recording kept only the on-screen stretches (the strap and live display were fine — the persist path was the gap). The foreground service now appends every reading to ble_hr_log.jsonl; ble_read_hr_log exposes it; on stop the workout rebuilds its samples from that log (falling back to the poll array). ble_clear_hr truncates the log at start.
This commit is contained in:
parent
85d5e47972
commit
ee47af4479
6 changed files with 98 additions and 5 deletions
|
|
@ -59,6 +59,7 @@ class BleHrService : Service() {
|
|||
private var gatt: BluetoothGatt? = null
|
||||
private var deviceType = "hr_standard"
|
||||
private var dataFile: File? = null
|
||||
private var logFile: File? = null // append-only HR history (ble_hr_log.jsonl); survives screen-off
|
||||
private var lastBattery: Int = -1 // strap charge %, -1 = unknown
|
||||
private var scanResultsFile: File? = null
|
||||
private var fitproWriteChar: BluetoothGattCharacteristic? = null
|
||||
|
|
@ -115,6 +116,7 @@ class BleHrService : Service() {
|
|||
isRunning = true
|
||||
val appDataDir = filesDir.parentFile ?: filesDir
|
||||
dataFile = File(appDataDir, "ble_hr_data.json")
|
||||
logFile = File(appDataDir, "ble_hr_log.jsonl")
|
||||
connectDevice(address)
|
||||
}
|
||||
return START_NOT_STICKY // connect is user-initiated; no sticky restart
|
||||
|
|
@ -498,6 +500,12 @@ class BleHrService : Service() {
|
|||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Write data failed", e)
|
||||
}
|
||||
// Append real HR readings (not status lines) to the history log. This runs in the
|
||||
// foreground service, so it keeps recording with the screen off — the workout
|
||||
// recorder rebuilds its samples from here (ble_read_hr_log) instead of the JS poll.
|
||||
if (json.contains("\"hr\":")) {
|
||||
try { logFile?.appendText(json + "\n") } catch (e: Exception) { Log.e(TAG, "Append hr log failed", e) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun disconnect() {
|
||||
|
|
|
|||
|
|
@ -465,6 +465,18 @@ pub fn ble_get_hr(
|
|||
{ android::read_hr_data(&app) }
|
||||
}
|
||||
|
||||
// Full HR history for the current workout. The Kotlin foreground service appends every
|
||||
// reading to ble_hr_log.jsonl (one {"hr","ts"[,"bat"]} per line) — that keeps logging with
|
||||
// the screen off, unlike the JS poll which Android freezes in the background. The workout
|
||||
// recorder reads this at finalize to rebuild a gap-free sample series. Returns "" if none.
|
||||
#[tauri::command]
|
||||
pub fn ble_read_hr_log(app: tauri::AppHandle) -> Result<String, String> {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{ let _ = &app; Ok(String::new()) }
|
||||
#[cfg(target_os = "android")]
|
||||
{ Ok(android::read_file(&app, "ble_hr_log.jsonl").unwrap_or_default()) }
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn ble_is_connected(
|
||||
app: tauri::AppHandle,
|
||||
|
|
@ -488,10 +500,14 @@ pub fn ble_is_connected(
|
|||
pub fn ble_clear_hr(app: tauri::AppHandle) -> Result<(), String> {
|
||||
use tauri::Manager;
|
||||
let data_dir = app.path().app_data_dir().map_err(|e: tauri::Error| e.to_string())?;
|
||||
// Clear HR data file so stale values don't show on next session
|
||||
// Clear HR data file so stale values don't show on next session, and truncate the
|
||||
// native HR history log (the foreground service appends every reading there — see
|
||||
// ble_read_hr_log) so a new workout starts from an empty log.
|
||||
let _ = std::fs::write(data_dir.join("ble_hr_data.json"), "{}");
|
||||
let _ = std::fs::write(data_dir.join("ble_hr_log.jsonl"), "");
|
||||
if let Some(root) = data_dir.parent().and_then(|p| p.parent()) {
|
||||
let _ = std::fs::write(root.join("ble_hr_data.json"), "{}");
|
||||
let _ = std::fs::write(root.join("ble_hr_log.jsonl"), "");
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1162,7 +1162,7 @@ pub fn run() {
|
|||
.add_migrations("sqlite:baldav.db", migrations)
|
||||
.build(),
|
||||
)
|
||||
.invoke_handler(tauri::generate_handler![get_safe_insets, import_seed_products, import_tiles, download_file, get_points_filename, delete_points_file, gps_set_session_id, list_points_files, read_points_file, pick_file_open, get_picked_file, debug_native_paths, rebuild_roads, get_road_points, get_ride_graph, get_app_info, gps_service_command, gps_service_read_points, brouter_route, brouter_download_tile, brouter_download_status, brouter_list_tiles, save_file, export_image, read_file_base64, export_db, import_db, restart_app, get_update_info, http_request, chzn_http, ble::ble_scan, ble::ble_scan_results, ble::ble_connect, ble::ble_disconnect, ble::ble_get_hr, ble::ble_is_connected, ble::ble_check_bluetooth, ble::ble_save_device, ble::ble_clear_hr, ble::ble_request_enable, read_state_file])
|
||||
.invoke_handler(tauri::generate_handler![get_safe_insets, import_seed_products, import_tiles, download_file, get_points_filename, delete_points_file, gps_set_session_id, list_points_files, read_points_file, pick_file_open, get_picked_file, debug_native_paths, rebuild_roads, get_road_points, get_ride_graph, get_app_info, gps_service_command, gps_service_read_points, brouter_route, brouter_download_tile, brouter_download_status, brouter_list_tiles, save_file, export_image, read_file_base64, export_db, import_db, restart_app, get_update_info, http_request, chzn_http, ble::ble_scan, ble::ble_scan_results, ble::ble_connect, ble::ble_disconnect, ble::ble_get_hr, ble::ble_is_connected, ble::ble_check_bluetooth, ble::ble_save_device, ble::ble_clear_hr, ble::ble_read_hr_log, ble::ble_request_enable, read_state_file])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue