diff --git a/gem-remotes-esp32/Cargo.toml b/gem-remotes-esp32/Cargo.toml index 60492c8..7d9128b 100644 --- a/gem-remotes-esp32/Cargo.toml +++ b/gem-remotes-esp32/Cargo.toml @@ -42,6 +42,7 @@ async-channel = "2.3.1" strum = "0.26.3" strum_macros = "0.26.4" closure = "0.3.0" +bitflags = "2.6.0" [build-dependencies] embuild = "0.32.0" diff --git a/gem-remotes-esp32/src/ble_server.rs b/gem-remotes-esp32/src/ble_server.rs index 1932da4..5e33301 100644 --- a/gem-remotes-esp32/src/ble_server.rs +++ b/gem-remotes-esp32/src/ble_server.rs @@ -1,9 +1,12 @@ use log::*; //{trace, debug, info, warn, error} -use esp32_nimble::{enums::*, uuid128, BLEAdvertisementData, BLEAdvertising, BLEDevice, BLEServer, NimbleProperties, OnWriteArgs}; -use esp32_nimble::utilities::{mutex::Mutex, BleUuid}; +use esp32_nimble::{enums::*, uuid128, BLEAdvertisementData, BLEDevice, BLEServer, NimbleProperties, OnWriteArgs}; +use esp32_nimble::utilities::BleUuid; use anyhow::Result; use closure::closure; +use bitflags::bitflags; +use std::str::FromStr; +use std::sync::Arc; use crate::dispatch::{Dispatch, RecvQ, SendQ}; use crate::commands::Commands; @@ -14,10 +17,15 @@ const BLE_MAX_INTERVAL: u16 = 48; // x 1.25ms const BLE_LATENCY: u16 = 0; // Number of packets that can be missed, extending interval const BLE_TIMEOUT: u16 = 500; // x10ms -const SVC_DATA_PAIRING: [u8; 1] = [1]; // TODO: bitflag these -const SVC_DATA_NO_PAIRING: [u8; 1] = [0]; - +// Bit flags in the custom service byte. +bitflags! { + #[derive(Debug, Clone, Copy)] + struct SvcFlags: u8 { + const PAIRING_MODE = 1 << 0; + } +} +// Names appear to truncate at 31 characters. const DEVICE_NAME: &str = "Gem Remotes"; // The [u8] version is, for whatever reason, little-endian. This should equate to the UUID "9966ad5a-f13c-4b61-ba66-0861e08d09b4". @@ -27,10 +35,14 @@ const UUID_SERVICE_LIFT: BleUuid = uuid128!("c1400000-8dda-45a3-959b-d23a0f8f53d const UUID_BUTTON_UP: BleUuid = uuid128!("c1401121-8dda-45a3-959b-d23a0f8f53d7"); const UUID_BUTTON_DOWN: BleUuid = uuid128!("c1401122-8dda-45a3-959b-d23a0f8f53d7"); const UUID_BUTTON_STOP: BleUuid = uuid128!("c1401123-8dda-45a3-959b-d23a0f8f53d7"); +const UUID_BLUETOOTH_NAME: BleUuid = uuid128!("c1401224-8dda-45a3-959b-d23a0f8f53d7"); pub struct BleServer { send_q: SendQ, recv_q: RecvQ, + svc_flags: SvcFlags, + dev_name: String, + } impl BleServer { @@ -42,23 +54,27 @@ impl BleServer { Commands::PairTimerExpired, Commands::AllowPairing, Commands::EraseBleBonds, + Commands::BluetoothName { data: Arc::new(String::new())}, ]; let r = dp.get_callback_channel(&cmds); let s = dp.get_cmd_channel(); + let dev_name = DEVICE_NAME; //TODO: read this from NVS if it is present. BleServer { send_q: s.clone(), recv_q: r.clone(), + svc_flags: SvcFlags::empty(), + dev_name: String::from_str(dev_name).unwrap(), //Unwrap should be fine here because we control this string } } - pub async fn run(&self) -> Result<()> { + pub async fn run(&mut self) -> Result<()> { match self.do_run().await { Ok(_) => {error!("Exited bluetooth server wait loop with no error.");panic!();} Err(e) => {error!("Bluetooth task encountered error {}", e);panic!();} } } - pub async fn do_run(&self) -> Result<()> { + pub async fn do_run(&mut self) -> Result<()> { trace!("Entering BLE Run"); let ble_device = BLEDevice::take(); set_device_security(ble_device); @@ -69,7 +85,7 @@ impl BleServer { trace!("Setting up GATT"); //TODO: require authentication (bonding counts?) for these! let sender = self.send_q.clone(); - //let mut cmd = Commands::BluetoothUp { data: 0 }; + // --- Button Up Bluetooth GATT ---------------------------------------------------------- let button_up = lift_service.lock().create_characteristic( UUID_BUTTON_UP, NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY, @@ -78,7 +94,7 @@ impl BleServer { .on_write(closure!(clone sender, |args: &mut OnWriteArgs| { on_bluetooth_cmd(&sender, args, Commands::BluetoothUp {data: 0}) })); - + // --- Button Down Bluetooth GATT -------------------------------------------------------- let button_down = lift_service.lock().create_characteristic( UUID_BUTTON_DOWN, NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY, @@ -87,7 +103,7 @@ impl BleServer { .on_write(closure!(clone sender, |args: &mut OnWriteArgs| { on_bluetooth_cmd(&sender, args, Commands::BluetoothDown {data: 0}) })); - + // --- Button Stop Bluetooth GATT -------------------------------------------------------- let button_stop = lift_service.lock().create_characteristic( UUID_BUTTON_STOP, NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY, @@ -96,12 +112,17 @@ impl BleServer { .on_write(closure!(clone sender, |args: &mut OnWriteArgs| { on_bluetooth_cmd(&sender, args, Commands::BluetoothStop {_data: 0}) })); - - - let ble_advertiser = ble_device.get_advertising(); - - // TODO: we will need to enable / disable the ability to pair! - advertise(ble_advertiser)?; + // --- Device Name Bluetooth GATT -------------------------------------------------------- + let device_name = lift_service.lock().create_characteristic( + UUID_BLUETOOTH_NAME, + NimbleProperties::READ | NimbleProperties::WRITE, + ); + device_name.lock().set_value(self.dev_name.as_bytes()) + .on_write(closure!(clone sender, |args: &mut OnWriteArgs| { + on_bluetooth_cmd(&sender, args, Commands::BluetoothName { data: Arc::new(String::new())}.clone()) + })); + // Default to not pairable + self.advertise_unpairable()?; loop { debug!("Waiting for updates that should be notified via bluetooth"); @@ -119,17 +140,19 @@ impl BleServer { button_up.lock().set_value(&[data]).notify(); } Commands::PairTimerExpired => { - //advertise_to_direct(ble_advertiser).expect("Failed to exit pairing mode"); - debug!("pairing mode / non-pairing mode not currently supported"); + self.advertise_unpairable()?; self.send_q.send(Commands::PairTimerClear).await?; } Commands::AllowPairing => { - //advertise_to_undirected(ble_advertiser).expect("Failed to enter pairing mode"); + self.advertise_pairable()?; debug!("pairing mode / non-pairing mode not currently supported"); } Commands::EraseBleBonds => { ble_device.delete_all_bonds().expect("Failed trying to erase bluetooth bonding information"); } + Commands::BluetoothName { data } => { + self.update_name(data.as_str())?; // TODO: consider not failing over trivial name change problem + } _ => { error!("Invalid command received by bluetooth handler {:?}", cmd); // No need to reboot as state is recoverable. @@ -138,6 +161,45 @@ impl BleServer { } } + fn update_name(&mut self, new_name: &str) -> Result<()> { + self.dev_name.clear(); + let mut name = new_name; + if name.len() > 31 {name = &new_name[..31];} + self.dev_name.push_str(name); + + self.advertise()?; + //TODO: save name to nvs + Ok(()) + } + + fn advertise(&self) -> Result<()> { + let ble_dev = BLEDevice::take(); + let ble_adv = ble_dev.get_advertising(); + let mut adv = ble_adv.lock(); + if adv.is_advertising() { + adv.stop()?; + } + let mut beacon_data = BLEAdvertisementData::new(); + beacon_data.name(self.dev_name.as_str()); + beacon_data.service_data(UUID_SERVICE_PAIR, &[self.svc_flags.bits()]); + adv + .advertisement_type(ConnMode::Und) + .set_data(&mut beacon_data)?; + info!("Staring Bluetooth Server"); + adv.start()?; + Ok(()) + } + + fn advertise_pairable(&mut self) -> Result<()> { + self.svc_flags = self.svc_flags | SvcFlags::PAIRING_MODE; + self.advertise() + } + + fn advertise_unpairable(&mut self) -> Result<()> { + self.svc_flags = self.svc_flags - SvcFlags::PAIRING_MODE; + self.advertise() + } + } fn on_bluetooth_cmd(sender: &SendQ, args: &mut OnWriteArgs, cmd: Commands) { @@ -159,6 +221,17 @@ fn on_bluetooth_cmd(sender: &SendQ, args: &mut OnWriteArgs, cmd: Commands) { sender.send_blocking(Commands::BluetoothStop {_data: v[0]} ) } else {error!("Received zero-byte bluetooth characteristic update {:?}", cmd); Ok(())} } + Commands::BluetoothName { data: _ } => { + if v.len() > 0 { + let name = String::from_utf8_lossy(v).into_owned(); + sender.send_blocking(Commands::BluetoothName { data: Arc::new(name) }) + } else { + // If the user clears the name, revert to the default. + // Unwrap is safe here because we control the string. + sender.send_blocking(Commands::BluetoothName { data: Arc::new(String::from_str(DEVICE_NAME).unwrap()) }) + } + } + //TODO when we get name changes, truncate to 31 chars, because that's what we have anyway. _ => {error!("Tried to handle an unknown bluetooth command: {:?}",cmd);Ok(())} }; match attempt { @@ -199,20 +272,5 @@ fn set_server_callbacks(server: &mut BLEServer, sender: SendQ) { }); } -fn advertise(advertiser: &Mutex) -> Result<()> { - trace!("Setting up advertiser"); - let mut beacon_data = BLEAdvertisementData::new(); - beacon_data.name(DEVICE_NAME); - //.add_service_uuid(UUID_SERVICE_PAIR); - beacon_data.service_data(UUID_SERVICE_PAIR, &SVC_DATA_PAIRING); - advertiser - .lock() - .advertisement_type(ConnMode::Und) - .set_data(&mut beacon_data)?; - info!("Staring Bluetooth Server"); - advertiser.lock().start()?; - Ok(()) -} - //TODO set maximum pairs to remember? //TODO after disconnect, it returns to scanning - will it return to directed scanning? Find out when directed is working. \ No newline at end of file diff --git a/gem-remotes-esp32/src/commands.rs b/gem-remotes-esp32/src/commands.rs index 56fc8b5..91a9ea6 100644 --- a/gem-remotes-esp32/src/commands.rs +++ b/gem-remotes-esp32/src/commands.rs @@ -2,8 +2,9 @@ use strum_macros::EnumCount as EnumCountMacro; use std::mem::discriminant; +use std::sync::Arc; -#[derive(Clone, Copy, EnumCountMacro, Debug)] +#[derive(Clone, EnumCountMacro, Debug)] pub enum Commands { // Inputs sent from the PIC microcontroller @@ -15,6 +16,7 @@ pub enum Commands { BluetoothUp {data: u8}, BluetoothDown {data: u8}, BluetoothStop {_data: u8}, // There is no state where releasing the stop button induces a change. + BluetoothName { data: Arc}, // Internal messages StopTimerExpired, // Sent when the 2 second stop sequence is complete diff --git a/gem-remotes-esp32/src/main.rs b/gem-remotes-esp32/src/main.rs index 75eec7c..853ab4a 100644 --- a/gem-remotes-esp32/src/main.rs +++ b/gem-remotes-esp32/src/main.rs @@ -94,7 +94,7 @@ async fn main_loop() -> Result<()> { Commands::PairTimerExpired, PAIR_TIME_MS, &mut dp); - let ble_server = ble_server::BleServer::new(&mut dp); + let mut ble_server = ble_server::BleServer::new(&mut dp); let executor = Executor::new(); let mut tasks:Vec<_> = Vec::new(); diff --git a/gem-remotes-esp32/src/message_timer.rs b/gem-remotes-esp32/src/message_timer.rs index f4f37eb..01e8a80 100644 --- a/gem-remotes-esp32/src/message_timer.rs +++ b/gem-remotes-esp32/src/message_timer.rs @@ -27,7 +27,7 @@ pub struct MessageTimer { state: State, } -impl MessageTimer { +impl MessageTimer { pub fn new( restart: Q, cancel: Q, @@ -54,7 +54,7 @@ impl MessageTimer { ms: u64, dp: &mut Dispatch, ) -> MessageTimer { - let cmds = vec![restart, cancel]; + let cmds = vec![restart.clone(), cancel.clone()]; let s = dp.get_cmd_channel(); let r = dp.get_callback_channel(&cmds); let duration = Duration::from_millis(ms); @@ -95,7 +95,7 @@ impl MessageTimer { } Err(_) => { trace!("Timeout reached"); - self.send_q.send(self.done).await.expect("Failed to send timeout"); + self.send_q.send(self.done.clone()).await.expect("Failed to send timeout"); } } }