pub mod actions;
use anyhow::Result;
use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::info;
pub struct BluetoothBleHeartRate;
impl BluetoothBleHeartRate {
#[cfg(all(unix, feature = "bluetooth"))]
pub async fn spawn_with_llm_actions(
device_name: String,
llm_client: crate::llm::ollama_client::OllamaClient,
app_state: Arc<crate::state::app_state::AppState>,
status_tx: mpsc::UnboundedSender<String>,
server_id: crate::state::ServerId,
instruction: String,
) -> Result<std::net::SocketAddr> {
info!("Starting BLE Heart Rate Service: {}", device_name);
const SENTENCE: &str = "Configure as a BLE Heart Rate Service (0x180D) with a Heart \
Rate Measurement characteristic (0x2A37).";
let trimmed = instruction.trim().trim_end_matches('.').trim();
let hr_instruction = if trimmed.is_empty() {
SENTENCE.to_string()
} else {
format!("{trimmed}. {SENTENCE}")
};
let log_tx = status_tx.clone();
let log_name = device_name.clone();
match crate::server::bluetooth_ble::BluetoothBle::spawn_with_llm_actions(
device_name,
llm_client,
app_state,
status_tx,
server_id,
hr_instruction,
)
.await
{
Ok(addr) => Ok(addr),
Err(e) => {
crate::console_error!(log_tx, "{}", radio_start_failure(&log_name, &e));
Err(e)
}
}
}
}
pub fn radio_start_failure(device_name: &str, err: &anyhow::Error) -> String {
format!(
"BLE Heart Rate Service '{device_name}' could not start \
(decision=refused_adapter_unavailable): {err}. No Heart Rate Service was published \
and nothing is advertising."
)
}
#[cfg(any(all(unix, not(feature = "bluetooth")), not(unix)))]
impl BluetoothBleHeartRate {
pub async fn spawn_with_llm_actions(
_: String,
_: crate::llm::ollama_client::OllamaClient,
_: Arc<crate::state::app_state::AppState>,
_: mpsc::UnboundedSender<String>,
_: crate::state::ServerId,
) -> Result<std::net::SocketAddr> {
anyhow::bail!(
"BLE heart-rate not enabled - compile with --features bluetooth-ble-heart-rate"
)
}
}
pub const HEART_RATE_SERVICE: u16 = 0x180D;
pub const HEART_RATE_MEASUREMENT: u16 = 0x2A37;
pub const BODY_SENSOR_LOCATION: u16 = 0x2A38;
pub fn encode_heart_rate(bpm: u16) -> Vec<u8> {
if let Ok(small) = u8::try_from(bpm) {
vec![0x00, small]
} else {
let mut v = Vec::with_capacity(3);
v.push(0x01); v.extend_from_slice(&bpm.to_le_bytes()); v
}
}