pub mod actions;
use anyhow::Result;
use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::info;
use crate::console_error;
use crate::llm::ollama_client::OllamaClient;
use crate::server::bluetooth_ble::BluetoothBle;
use crate::state::app_state::AppState;
pub struct BluetoothBleBattery;
impl BluetoothBleBattery {
#[cfg(all(unix, feature = "bluetooth"))]
pub async fn spawn_with_llm_actions(
device_name: String,
initial_level: u8,
llm_client: OllamaClient,
app_state: Arc<AppState>,
status_tx: mpsc::UnboundedSender<String>,
server_id: crate::state::ServerId,
instruction: String,
) -> Result<std::net::SocketAddr> {
info!(
"Starting BLE Battery Service: {} (initial level: {}%)",
device_name, initial_level
);
let sentence = format!(
"Configure as a BLE Battery Service (0x180F) with a Battery Level characteristic \
(0x2A19) starting at {initial_level}%."
);
let trimmed = instruction.trim().trim_end_matches('.').trim();
let instruction = if trimmed.is_empty() {
sentence
} else {
format!("{trimmed}. {sentence}")
};
let log_tx = status_tx.clone();
let log_name = device_name.clone();
match BluetoothBle::spawn_with_llm_actions(
device_name,
llm_client,
app_state,
status_tx,
server_id,
instruction,
)
.await
{
Ok(addr) => Ok(addr),
Err(e) => {
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 Battery Service '{device_name}' could not start \
(decision=refused_adapter_unavailable): {err}. No Battery Service was published and \
nothing is advertising."
)
}
#[cfg(any(all(unix, not(feature = "bluetooth")), not(unix)))]
impl BluetoothBleBattery {
pub async fn spawn_with_llm_actions(
_device_name: String,
_initial_level: u8,
_llm_client: OllamaClient,
_app_state: Arc<AppState>,
_status_tx: mpsc::UnboundedSender<String>,
_server_id: crate::state::ServerId,
_instruction: String,
) -> Result<std::net::SocketAddr> {
anyhow::bail!(
"BLE battery support not enabled - compile with --features bluetooth-ble-battery"
)
}
}
pub mod battery_uuids {
pub const BATTERY_SERVICE: u16 = 0x180F;
pub const BATTERY_LEVEL: u16 = 0x2A19;
}
pub fn encode_battery_level(level: u8) -> Result<[u8; 1]> {
if level > 100 {
anyhow::bail!(
"Battery Level (0x2A19) is a percentage and the Bluetooth SIG reserves every \
value above 100: {level} cannot be encoded. Refusing rather than clamping, \
because a clamped 100 is indistinguishable from a full battery."
);
}
Ok([level])
}