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 BluetoothBleRemote;
impl BluetoothBleRemote {
#[cfg(all(unix, feature = "bluetooth"))]
pub async fn spawn_with_llm_actions(
device_name: String,
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 Remote Control server: {}", device_name);
let remote_instruction = format!(
"Configure as a BLE HID remote control with HID Service (UUID: 0x1812). {} {}",
instruction,
"Add HID Report Map, HID Report Input, HID Information, and HID Control Point characteristics for consumer control."
);
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,
remote_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 HID remote control '{device_name}' could not start \
(decision=refused_adapter_unavailable): {err}. No HID service was published and \
nothing is advertising."
)
}
#[cfg(any(all(unix, not(feature = "bluetooth")), not(unix)))]
impl BluetoothBleRemote {
pub async fn spawn_with_llm_actions(
_device_name: String,
_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 remote support not enabled - compile with --features bluetooth-ble-remote"
)
}
}
pub mod consumer_control {
pub const PLAY_PAUSE: u16 = 0xCD;
pub const NEXT_TRACK: u16 = 0xB5;
pub const PREVIOUS_TRACK: u16 = 0xB6;
pub const STOP: u16 = 0xB7;
pub const FAST_FORWARD: u16 = 0xB3;
pub const REWIND: u16 = 0xB4;
pub const VOLUME_UP: u16 = 0xE9;
pub const VOLUME_DOWN: u16 = 0xEA;
pub const MUTE: u16 = 0xE2;
pub const POWER: u16 = 0x30;
pub const MENU: u16 = 0x40;
pub const HOME: u16 = 0x223;
}
pub const HID_REMOTE_INPUT_REPORT_LEN: usize = 2;
pub const HID_REMOTE_REPORT_DESCRIPTOR: &[u8] = &[
0x05, 0x0C, 0x09, 0x01, 0xA1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0C, 0x09, 0xCD, 0x09, 0xB5, 0x09, 0xB6, 0x09, 0xB7, 0x09, 0xB3, 0x09, 0xB4, 0x09, 0xE9, 0x09, 0xEA, 0x09, 0xE2, 0x09, 0x30, 0x09, 0x40, 0x0A, 0x23, 0x02, 0x81, 0x02, 0x95, 0x04, 0x81, 0x03, 0xC0, ];
pub fn build_remote_report(button: &str) -> Option<[u8; HID_REMOTE_INPUT_REPORT_LEN]> {
let bit_position: u32 = match button {
"play_pause" => 0,
"next_track" => 1,
"previous_track" => 2,
"stop" => 3,
"fast_forward" => 4,
"rewind" => 5,
"volume_up" => 6,
"volume_down" => 7,
"mute" => 8,
"power" => 9,
"menu" => 10,
"home" => 11,
_ => return None,
};
let mut report = [0u8; HID_REMOTE_INPUT_REPORT_LEN];
report[(bit_position / 8) as usize] = 1u8 << (bit_position % 8);
Some(report)
}