ble: barometer on its own connection - simultaneous HR+baro, auto-connect, UI
Separate BleBaroService so the HR strap and the barometer each hold their own GATT link and work at the same time (one adapter, several LE connections). Verified on device against a PC BlueZ emulator of the sensor: BleHR and BleBaro deliver in parallel, no cross-talk. - BleBaroService: dedicated baro connection (scan-before-connect + direct-connect fallback, ESS subscribe, writes ble_baro_data.json). MainActivity routes baro connects to it and auto-connects both HR (ble_device.json) and baro (baro_device.json) at start. - Rust: ble_baro_disconnect, ble_save_baro_device. - Settings: a 'Датчики (BLE)' section - heart-rate and barometer as separate rows with their own slots, bind/connect/unbind, and a live pressure/temperature/altitude readout. - Tracking: altitude_baro_m stored per point, preferred in elevation stats (null without baro). - Record screen: 'Высота' from the barometer (1 Hz, dot marker), temperature from the sensor. - Fixes: direct-connect on scan timeout; Locale.US JSON (RU decimal comma broke the parse); parsing routed by characteristic (baro no longer written into the pulse); VR/HR stop returns to the tracks list instead of into details. - tools/baro-sensor/emulate_baro.py: PC BlueZ peripheral that emulates the sensor for testing. Emulator-verified only; real board still pending.
This commit is contained in:
parent
82f3dc9d4a
commit
9251c1dd90
12 changed files with 653 additions and 34 deletions
|
|
@ -48,6 +48,11 @@
|
|||
android:foregroundServiceType="connectedDevice"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".BleBaroService"
|
||||
android:foregroundServiceType="connectedDevice"
|
||||
android:exported="false" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,257 @@
|
|||
package dev.baldav.app
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.bluetooth.*
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import java.util.UUID
|
||||
|
||||
// Dedicated BLE connection for the DIY barometer "BaroBalDav" (ESP32-C3 + BMP280). Kept separate
|
||||
// from BleHrService so the heart-rate strap and the barometer each hold their OWN GATT link and work
|
||||
// at the same time (one adapter handles several LE connections). Reads the standard Environmental
|
||||
// Sensing service and writes the latest {pressure_pa, temp_c, ts} to ble_baro_data.json for Rust/JS.
|
||||
class BleBaroService : Service() {
|
||||
|
||||
companion object {
|
||||
const val TAG = "BleBaro"
|
||||
const val CHANNEL_ID = "baldav_ble_baro"
|
||||
const val NOTIFICATION_ID = 3
|
||||
const val ACTION_STOP = "dev.baldav.app.STOP_BARO"
|
||||
const val EXTRA_ADDRESS = "address"
|
||||
|
||||
val ESS_SERVICE: UUID = UUID.fromString("0000181a-0000-1000-8000-00805f9b34fb")
|
||||
val PRESSURE_CHAR: UUID = UUID.fromString("00002a6d-0000-1000-8000-00805f9b34fb") // uint32, 0.1 Pa
|
||||
val TEMP_CHAR: UUID = UUID.fromString("00002a6e-0000-1000-8000-00805f9b34fb") // int16, 0.01 C
|
||||
val CCCD_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
var isRunning = false
|
||||
private set
|
||||
}
|
||||
|
||||
private var gatt: BluetoothGatt? = null
|
||||
private var dataFile: File? = null
|
||||
private var logFile: File? = null
|
||||
private var lastAddress: String? = null
|
||||
private var connectedOnce = false
|
||||
private var stopping = false
|
||||
private var reconnectTries = 0
|
||||
private val maxReconnect = 3
|
||||
private var lastTempC: Double = Double.NaN
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
private val btStateReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||
if (intent?.action != BluetoothAdapter.ACTION_STATE_CHANGED) return
|
||||
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
|
||||
if (state == BluetoothAdapter.STATE_TURNING_OFF || state == BluetoothAdapter.STATE_OFF) {
|
||||
stopping = true
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
disconnect()
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun adapter(): BluetoothAdapter? =
|
||||
(getSystemService(BLUETOOTH_SERVICE) as? android.bluetooth.BluetoothManager)?.adapter
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
createNotificationChannel()
|
||||
try { registerReceiver(btStateReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)) }
|
||||
catch (e: Exception) { Log.e(TAG, "register BT receiver: ${e.message}") }
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent?.action == ACTION_STOP) {
|
||||
stopping = true
|
||||
disconnect()
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
val address = intent?.getStringExtra(EXTRA_ADDRESS)
|
||||
if (address != null) {
|
||||
startForeground(NOTIFICATION_ID, buildNotification("Барометр: подключение..."))
|
||||
isRunning = true
|
||||
val appDataDir = filesDir.parentFile ?: filesDir
|
||||
dataFile = File(appDataDir, "ble_baro_data.json")
|
||||
logFile = File(appDataDir, "ble_baro_log.jsonl")
|
||||
lastAddress = address; stopping = false; connectedOnce = false; reconnectTries = 0
|
||||
connectDevice(address)
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
// Quick scan to warm the BLE cache (avoids status=133), then connect. On scan timeout fall back
|
||||
// to a direct connect by address - a stationary sensor is reachable even if a 5 s scan misses it.
|
||||
private fun connectDevice(address: String) {
|
||||
val adapter = adapter()
|
||||
if (adapter == null || !adapter.isEnabled) { Log.e(TAG, "Bluetooth not available"); stopSelf(); return }
|
||||
val scanner = adapter.bluetoothLeScanner
|
||||
if (scanner == null) { directConnect(adapter, address); return }
|
||||
val scanCallback = object : android.bluetooth.le.ScanCallback() {
|
||||
override fun onScanResult(callbackType: Int, result: android.bluetooth.le.ScanResult) {
|
||||
if (result.device.address == address) {
|
||||
try { scanner.stopScan(this) } catch (e: Exception) {}
|
||||
try { gatt = result.device.connectGatt(this@BleBaroService, false, gattCallback, BluetoothDevice.TRANSPORT_LE) }
|
||||
catch (e: Exception) { Log.e(TAG, "connectGatt: ${e.message}") }
|
||||
}
|
||||
}
|
||||
override fun onScanFailed(errorCode: Int) { directConnect(adapter, address) }
|
||||
}
|
||||
try { scanner.startScan(scanCallback) } catch (e: Exception) { directConnect(adapter, address); return }
|
||||
handler.postDelayed({
|
||||
try { scanner.stopScan(scanCallback) } catch (e: Exception) {}
|
||||
if (gatt == null) { Log.d(TAG, "scan timeout - direct connect"); directConnect(adapter, address) }
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
private fun directConnect(adapter: BluetoothAdapter, address: String) {
|
||||
if (!adapter.isEnabled) { stopSelf(); return }
|
||||
try {
|
||||
gatt = adapter.getRemoteDevice(address).connectGatt(this, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
||||
} catch (e: Exception) { Log.e(TAG, "directConnect: ${e.message}"); stopSelf() }
|
||||
}
|
||||
|
||||
private val gattCallback = object : BluetoothGattCallback() {
|
||||
override fun onConnectionStateChange(g: BluetoothGatt, status: Int, newState: Int) {
|
||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
updateNotification("Барометр: поиск сервисов...")
|
||||
try { g.discoverServices() } catch (e: Exception) { Log.e(TAG, "discoverServices: ${e.message}") }
|
||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||
isRunning = false
|
||||
try { g.close() } catch (e: Exception) {}
|
||||
gatt = null
|
||||
if (!stopping && connectedOnce && reconnectTries < maxReconnect && adapter()?.isEnabled == true && lastAddress != null) {
|
||||
reconnectTries++
|
||||
updateNotification("Барометр: переподключение...")
|
||||
handler.postDelayed({ if (!stopping) connectDevice(lastAddress!!) }, 8000)
|
||||
} else stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServicesDiscovered(g: BluetoothGatt, status: Int) {
|
||||
if (status != BluetoothGatt.GATT_SUCCESS) { Log.e(TAG, "service discovery failed: $status"); return }
|
||||
connectedOnce = true; reconnectTries = 0; isRunning = true
|
||||
setupBaro(g)
|
||||
updateNotification("Барометр подключён")
|
||||
}
|
||||
|
||||
override fun onCharacteristicChanged(g: BluetoothGatt, char: BluetoothGattCharacteristic, value: ByteArray) {
|
||||
parseBaro(char, value)
|
||||
}
|
||||
@Deprecated("Deprecated in API 33")
|
||||
override fun onCharacteristicChanged(g: BluetoothGatt, char: BluetoothGattCharacteristic) {
|
||||
parseBaro(char, char.value ?: return)
|
||||
}
|
||||
override fun onDescriptorWrite(g: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
|
||||
onGattOperationComplete()
|
||||
}
|
||||
}
|
||||
|
||||
private val gattQueue = mutableListOf<() -> Unit>()
|
||||
private var gattBusy = false
|
||||
private fun enqueueGatt(op: () -> Unit) { gattQueue.add(op); processGattQueue() }
|
||||
private fun processGattQueue() {
|
||||
if (gattBusy || gattQueue.isEmpty()) return
|
||||
gattBusy = true
|
||||
gattQueue.removeAt(0).invoke()
|
||||
}
|
||||
private fun onGattOperationComplete() {
|
||||
gattBusy = false
|
||||
handler.postDelayed({ processGattQueue() }, 100)
|
||||
}
|
||||
|
||||
private fun setupBaro(g: BluetoothGatt) {
|
||||
val svc = g.getService(ESS_SERVICE)
|
||||
if (svc == null) { Log.e(TAG, "ESS service not found"); return }
|
||||
val pc = svc.getCharacteristic(PRESSURE_CHAR)
|
||||
if (pc == null) { Log.e(TAG, "pressure characteristic not found"); return }
|
||||
val tc = svc.getCharacteristic(TEMP_CHAR)
|
||||
enqueueGatt { subscribeNotify(g, pc) }
|
||||
if (tc != null) enqueueGatt { subscribeNotify(g, tc) }
|
||||
Log.d(TAG, "subscribed pressure${if (tc != null) " + temp" else ""}")
|
||||
}
|
||||
|
||||
private fun subscribeNotify(g: BluetoothGatt, char: BluetoothGattCharacteristic) {
|
||||
try {
|
||||
g.setCharacteristicNotification(char, true)
|
||||
val desc = char.getDescriptor(CCCD_UUID)
|
||||
if (desc != null) {
|
||||
desc.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||
g.writeDescriptor(desc)
|
||||
} else onGattOperationComplete()
|
||||
} catch (e: Exception) { Log.e(TAG, "subscribeNotify: ${e.message}"); onGattOperationComplete() }
|
||||
}
|
||||
|
||||
private fun parseBaro(char: BluetoothGattCharacteristic, value: ByteArray) {
|
||||
when (char.uuid) {
|
||||
TEMP_CHAR -> if (value.size >= 2) {
|
||||
val u = ((value[1].toInt() and 0xFF) shl 8) or (value[0].toInt() and 0xFF)
|
||||
lastTempC = u.toShort().toInt() / 100.0
|
||||
}
|
||||
PRESSURE_CHAR -> if (value.size >= 4) {
|
||||
val raw = (value[0].toLong() and 0xFF) or ((value[1].toLong() and 0xFF) shl 8) or
|
||||
((value[2].toLong() and 0xFF) shl 16) or ((value[3].toLong() and 0xFF) shl 24)
|
||||
val pa = raw / 10.0
|
||||
val ts = System.currentTimeMillis() / 1000
|
||||
val tj = if (!lastTempC.isNaN()) ",\"temp_c\":${String.format(Locale.US, "%.2f", lastTempC)}" else ""
|
||||
val json = "{\"pressure_pa\":${String.format(Locale.US, "%.1f", pa)}$tj,\"ts\":$ts}"
|
||||
try { dataFile?.writeText(json) } catch (e: Exception) { Log.e(TAG, "write: ${e.message}") }
|
||||
try { logFile?.appendText(json + "\n") } catch (e: Exception) {}
|
||||
Log.d(TAG, "Baro: $pa Pa T=$lastTempC")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun disconnect() {
|
||||
isRunning = false
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
try { gatt?.disconnect() } catch (e: Exception) {}
|
||||
try { gatt?.close() } catch (e: Exception) {}
|
||||
gatt = null
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(CHANNEL_ID, "BLE барометр", NotificationManager.IMPORTANCE_LOW)
|
||||
getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
private fun buildNotification(text: String): Notification {
|
||||
val intent = packageManager.getLaunchIntentForPackage(packageName)
|
||||
val pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
return Notification.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle("BalDav барометр")
|
||||
.setContentText(text)
|
||||
.setSmallIcon(android.R.drawable.ic_menu_compass)
|
||||
.setContentIntent(pi)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
}
|
||||
private fun updateNotification(text: String) {
|
||||
getSystemService(NotificationManager::class.java).notify(NOTIFICATION_ID, buildNotification(text))
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
stopping = true
|
||||
try { unregisterReceiver(btStateReceiver) } catch (e: Exception) {}
|
||||
disconnect()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import android.os.Looper
|
|||
import android.os.ParcelUuid
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import java.util.UUID
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
|
@ -260,12 +261,14 @@ class BleHrService : Service() {
|
|||
handler.postDelayed({
|
||||
try { scanner.stopScan(scanCallback) } catch (e: Exception) { Log.e(TAG, "stopScan: ${e.message}") }
|
||||
if (gatt == null) {
|
||||
// Not seen in the scan → the band isn't nearby/on. Do NOT hold a GATT connect to
|
||||
// an absent device (that keeps the radio busy and drains the battery). Give up;
|
||||
// the user re-taps "Подключить" once the band is on.
|
||||
Log.d(TAG, "Scan timeout, device $address not found - stopping")
|
||||
writeData("""{"status":"not_found","ts":${System.currentTimeMillis()/1000}}""")
|
||||
stopSelf()
|
||||
// Not re-found in the pre-connect scan within 5 s. A present-but-not-rescanned device
|
||||
// (a stationary sensor, or a BlueZ peripheral that the scanner just missed) still
|
||||
// connects by address, so fall back to a direct connect instead of giving up. The
|
||||
// address type is already cached from the earlier scan, so connectGatt uses it.
|
||||
Log.d(TAG, "Scan timeout, device $address not re-found - direct connect fallback")
|
||||
val a = adapter()
|
||||
if (a != null && a.isEnabled) directConnect(a, address)
|
||||
else { writeData("""{"status":"not_found","ts":${System.currentTimeMillis()/1000}}"""); stopSelf() }
|
||||
}
|
||||
}, 5000)
|
||||
}
|
||||
|
|
@ -335,6 +338,13 @@ class BleHrService : Service() {
|
|||
|
||||
override fun onCharacteristicChanged(g: BluetoothGatt, char: BluetoothGattCharacteristic, value: ByteArray) {
|
||||
Log.d(TAG, "Data received (${value.size} bytes): ${value.joinToString(" ") { "%02X".format(it) }}")
|
||||
// Route by WHAT the characteristic is, not the current deviceType: if an HR auto-connect
|
||||
// races in and flips deviceType while the baro link is still delivering, a pressure/temp
|
||||
// packet must NOT be parsed as HR. Standard GATT UUIDs are unambiguous.
|
||||
when (char.uuid) {
|
||||
PRESSURE_CHAR, TEMP_CHAR -> { parseBaro(char, value); return }
|
||||
HR_MEASUREMENT_UUID -> { parseStandardHr(value); return }
|
||||
}
|
||||
when (deviceType) {
|
||||
"hr_standard" -> parseStandardHr(value)
|
||||
"baro" -> parseBaro(char, value)
|
||||
|
|
@ -455,8 +465,10 @@ class BleHrService : Service() {
|
|||
((value[2].toLong() and 0xFF) shl 16) or ((value[3].toLong() and 0xFF) shl 24)
|
||||
val pa = raw / 10.0 // 0.1 Pa units -> Pa
|
||||
val ts = System.currentTimeMillis() / 1000
|
||||
val tj = if (!lastTempC.isNaN()) ",\"temp_c\":${"%.2f".format(lastTempC)}" else ""
|
||||
val json = "{\"pressure_pa\":${"%.1f".format(pa)}$tj,\"ts\":$ts}"
|
||||
// format with Locale.US - the phone's RU locale writes a decimal comma, which is
|
||||
// invalid JSON and fails the Rust parse
|
||||
val tj = if (!lastTempC.isNaN()) ",\"temp_c\":${String.format(Locale.US, "%.2f", lastTempC)}" else ""
|
||||
val json = "{\"pressure_pa\":${String.format(Locale.US, "%.1f", pa)}$tj,\"ts\":$ts}"
|
||||
try { baroDataFile?.writeText(json) } catch (e: Exception) { Log.e(TAG, "baro write: ${e.message}") }
|
||||
try { baroLogFile?.appendText(json + "\n") } catch (e: Exception) {}
|
||||
Log.d(TAG, "Baro: $pa Pa T=$lastTempC")
|
||||
|
|
|
|||
|
|
@ -565,9 +565,14 @@ class MainActivity : TauriActivity() {
|
|||
val type = if (lastColon > 0) payload.substring(lastColon + 1) else "hr_standard"
|
||||
Log.d("BalDav", "BLE connect: $address ($type)")
|
||||
if (ensureBlePermissions()) {
|
||||
val intent = Intent(this, BleHrService::class.java).apply {
|
||||
putExtra(BleHrService.EXTRA_ADDRESS, address)
|
||||
putExtra(BleHrService.EXTRA_DEVICE_TYPE, type)
|
||||
// barometer has its own service so it and the HR strap connect at the same time
|
||||
val intent = if (type == "baro") {
|
||||
Intent(this, BleBaroService::class.java).apply { putExtra(BleBaroService.EXTRA_ADDRESS, address) }
|
||||
} else {
|
||||
Intent(this, BleHrService::class.java).apply {
|
||||
putExtra(BleHrService.EXTRA_ADDRESS, address)
|
||||
putExtra(BleHrService.EXTRA_DEVICE_TYPE, type)
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(intent) else startService(intent)
|
||||
}
|
||||
|
|
@ -578,6 +583,12 @@ class MainActivity : TauriActivity() {
|
|||
intent.action = BleHrService.ACTION_STOP
|
||||
startService(intent)
|
||||
}
|
||||
cmd == "baro_disconnect" -> {
|
||||
Log.d("BalDav", "Baro disconnect")
|
||||
val intent = Intent(this, BleBaroService::class.java)
|
||||
intent.action = BleBaroService.ACTION_STOP
|
||||
startService(intent)
|
||||
}
|
||||
cmd == "ble_request_enable" -> {
|
||||
Log.d("BalDav", "Requesting Bluetooth enable")
|
||||
if (ensureBlePermissions()) {
|
||||
|
|
@ -711,6 +722,18 @@ class MainActivity : TauriActivity() {
|
|||
|
||||
private fun autoStartBle(appDataDir: File) {
|
||||
try {
|
||||
// Barometer: auto-connect its own device via its own service, so it runs alongside the HR strap
|
||||
val baroFile = File(appDataDir, "baro_device.json")
|
||||
if (baroFile.exists()) {
|
||||
val bjson = baroFile.readText().trim()
|
||||
val baddr = Regex(""""addr"\s*:\s*"([^"]+)"""").find(bjson)?.groupValues?.get(1)
|
||||
if (!baddr.isNullOrEmpty() && ensureBlePermissions()) {
|
||||
Log.d("BalDav", "Auto-starting baro: $baddr")
|
||||
val bi = Intent(this, BleBaroService::class.java).apply { putExtra(BleBaroService.EXTRA_ADDRESS, baddr) }
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(bi) else startService(bi)
|
||||
}
|
||||
}
|
||||
|
||||
// Read paired BLE device from profile in DB
|
||||
val bleAddr = File(appDataDir, "ble_device.txt")
|
||||
// JS saves ble_device_addr in profile (SQLite), but we can't read SQLite here easily.
|
||||
|
|
|
|||
|
|
@ -596,6 +596,29 @@ pub fn ble_save_device(app: tauri::AppHandle, address: String, device_type: Stri
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// Disconnect ONLY the barometer (its own service), leaving the HR strap connected.
|
||||
#[tauri::command]
|
||||
pub async fn ble_baro_disconnect(app: tauri::AppHandle) -> Result<String, String> {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{ let _ = &app; Ok("desktop: no-op".to_string()) }
|
||||
#[cfg(target_os = "android")]
|
||||
{ android::send_command(&app, "baro_disconnect")?; Ok("Disconnecting baro".to_string()) }
|
||||
}
|
||||
|
||||
// Persist the paired barometer so the Kotlin side auto-connects it at start (separate from the HR
|
||||
// device file). Empty address clears it.
|
||||
#[tauri::command]
|
||||
pub fn ble_save_baro_device(app: tauri::AppHandle, address: String) -> Result<(), String> {
|
||||
use tauri::Manager;
|
||||
let data_dir = app.path().app_data_dir().map_err(|e: tauri::Error| e.to_string())?;
|
||||
let json = format!(r#"{{"addr":"{}","type":"baro"}}"#, address);
|
||||
std::fs::write(data_dir.join("baro_device.json"), &json).ok();
|
||||
if let Some(root) = data_dir.parent().and_then(|p| p.parent()) {
|
||||
std::fs::write(root.join("baro_device.json"), &json).ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn ble_check_bluetooth(
|
||||
app: tauri::AppHandle,
|
||||
|
|
|
|||
|
|
@ -1315,7 +1315,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_elev_correction, get_app_info, gps_service_command, set_hydration_config, set_live_config, send_sms, pick_contact, get_contact_result, 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_get_baro, ble::ble_is_connected, ble::ble_check_bluetooth, ble::ble_save_device, ble::ble_clear_hr, ble::ble_read_hr_log, ble::ble_read_strap_uptime, ble::ble_reset_strap_uptime, 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_elev_correction, get_app_info, gps_service_command, set_hydration_config, set_live_config, send_sms, pick_contact, get_contact_result, 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_get_baro, ble::ble_baro_disconnect, ble::ble_save_baro_device, ble::ble_is_connected, ble::ble_check_bluetooth, ble::ble_save_device, ble::ble_clear_hr, ble::ble_read_hr_log, ble::ble_read_strap_uptime, ble::ble_reset_strap_uptime, 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