use log::*; //{trace, debug, info, warn, error} 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::{Button, Commands}; // TODO HARDWARE: test these values to see if they are suitable const BLE_MIN_INTERVAL: u16 = 24; // x 1.25ms 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 // 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". const UUID_SERVICE_PAIR: BleUuid = BleUuid::from_uuid128([0xB4, 0x09, 0x8D, 0xE0, 0x61, 0x08, 0x66, 0xBA, 0x61, 0x4B, 0x3C, 0xF1, 0x5A, 0xAD, 0x66, 0x99]); const UUID_SERVICE_LIFT: BleUuid = uuid128!("c1400000-8dda-45a3-959b-d23a0f8f53d7"); 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"); const BLE_BUTTON_RELEASE: u8 = 0; const BLE_BUTTON_PRESS: u8 = 1; pub struct BleServer { send_q: SendQ, recv_q: RecvQ, svc_flags: SvcFlags, dev_name: String, } impl BleServer { pub fn new(dp: &mut Dispatch) -> Self { let cmds = vec![ Commands::NotifyMotorDown { data: Button::Released }, Commands::NotifyMotorStop { data: Button::Released }, Commands::NotifyMotorUp { data: Button::Released }, 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(&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(&mut self) -> Result<()> { trace!("Entering BLE Run"); let ble_device = BLEDevice::take(); set_device_security(ble_device); let server = ble_device.get_server(); set_server_callbacks(server, self.send_q.clone()); let lift_service = server.create_service(UUID_SERVICE_LIFT); trace!("Setting up GATT"); //TODO: require authentication (bonding counts?) for these! let sender = self.send_q.clone(); // --- Button Up Bluetooth GATT ---------------------------------------------------------- let button_up = lift_service.lock().create_characteristic( UUID_BUTTON_UP, NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY, ); button_up.lock().set_value(&[0]) .on_write(closure!(clone sender, |args: &mut OnWriteArgs| { on_bluetooth_cmd(&sender, args, Commands::BluetoothUp {data: Button::Released}) })); // --- Button Down Bluetooth GATT -------------------------------------------------------- let button_down = lift_service.lock().create_characteristic( UUID_BUTTON_DOWN, NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY, ); button_down.lock().set_value(&[0]) .on_write(closure!(clone sender, |args: &mut OnWriteArgs| { on_bluetooth_cmd(&sender, args, Commands::BluetoothDown {data: Button::Released}) })); // --- Button Stop Bluetooth GATT -------------------------------------------------------- let button_stop = lift_service.lock().create_characteristic( UUID_BUTTON_STOP, NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY, ); button_stop.lock().set_value(&[1]) .on_write(closure!(clone sender, |args: &mut OnWriteArgs| { on_bluetooth_cmd(&sender, args, Commands::BluetoothStop {data: Button::Released}) })); // --- 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"); let cmd =self.recv_q.recv().await.expect("Bluetooth notification queue unexpectedly closed"); trace!("Received update to bluetooth variable {:?}", cmd); match cmd { // TODO DISCUSS: This logic (if one button is pressed others are released) could be done in app instead. Commands::NotifyMotorUp{data} => { button_up.lock().set_value(&button_to_ble_button(data)).notify(); } Commands::NotifyMotorDown{data} => { button_down.lock().set_value(&button_to_ble_button(data)).notify(); } Commands::NotifyMotorStop{data} => { button_up.lock().set_value(&button_to_ble_button(data)).notify(); } Commands::PairTimerExpired => { self.advertise_unpairable()?; self.send_q.send(Commands::PairTimerClear).await?; } Commands::AllowPairing => { 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. } } } } 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) { let v = args.recv_data(); // receiving incorrect data isn't fatal, but being unable to send events is. let attempt = match cmd { Commands::BluetoothUp { data: _ } => { sender.send_blocking(Commands::BluetoothUp {data: ble_to_button(v)} ) } Commands::BluetoothDown { data: _ } => { sender.send_blocking(Commands::BluetoothDown {data: ble_to_button(v)} ) } Commands::BluetoothStop { data: _ } => { sender.send_blocking(Commands::BluetoothStop {data: ble_to_button(v)} ) } 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 { Ok(_) => {} Err(_) => {panic!("Unable to send notifications of bluetooth events")} } } fn ble_to_button(val: &[u8]) -> Button { if val.len() > 0 { match val[0] { BLE_BUTTON_PRESS => {Button::Pressed} BLE_BUTTON_RELEASE => {Button::Released} _ => { error!("Received invalid bluetooth data {:?}", val); Button::Released } } } else { error!("Received zero-length bluetooth data when expecting a button press"); Button::Released } } fn set_device_security(dev: &mut BLEDevice) { dev.security() // Enable all security protections (including bond, so that bond info is saved) .set_auth(AuthReq::all()) // Options we support for putting in pairing info. // "NoInputOutput" means that we will have "just works" pairing .set_io_cap(SecurityIOCap::NoInputNoOutput) //Handle IOS's bluetooth address randomization .resolve_rpa(); } fn set_server_callbacks(server: &mut BLEServer, sender: SendQ) { server.on_connect(move |server, clntdesc| { // Print connected client data info!("client connected: {:?}", clntdesc); // Update connection parameters server .update_conn_params( clntdesc.conn_handle(), BLE_MIN_INTERVAL, BLE_MAX_INTERVAL, BLE_LATENCY, BLE_TIMEOUT, ).unwrap(); sender.send_blocking(Commands::PairTimerClear).unwrap(); // TODO: Cancel pairing mode timeout }); server.on_disconnect(|_desc, _reason| { info!("Disconnected, back to advertising"); }); } fn button_to_ble_button(but: Button) -> [u8; 1] { match but { Button::Released => {[BLE_BUTTON_RELEASE]} Button::Pressed => {[BLE_BUTTON_PRESS]} } } //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.