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 anyhow::Result; use closure::closure; use crate::dispatch::{Dispatch, RecvQ, SendQ}; use crate::commands::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 const BUTTON_PRESSED: [u8; 1] = [0x1]; const BUTTON_RELEASED: [u8; 1] = [0x0]; const DEVICE_NAME: &str = "Gem Remotes"; const UUID_SERVICE_PAIR: BleUuid = uuid128!("9966ad5a-f13c-4b61-ba66-0861e08d09b4"); 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"); pub struct BleServer { send_q: SendQ, recv_q: RecvQ, } impl BleServer { pub fn new(dp: &mut Dispatch) -> Self { let cmds = vec![ // Switch to getting and updating notices; use the command version for sending commands only. //Commands::BluetoothUp { data: 0 }, //Commands::BluetoothDown { data: 0 }, //Commands::BluetoothStop { data: 0 }, Commands::NotifyMotorDown, Commands::NotifyMotorStopping, Commands::NotifyMotorUp, Commands::NotifyMotorStopped, ]; let r = dp.get_callback_channel(&cmds); let s = dp.get_cmd_channel(); BleServer { send_q: s.clone(), recv_q: r.clone(), } } 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); let _pairing_service = server.create_service(UUID_SERVICE_PAIR); 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(); //let mut cmd = Commands::BluetoothUp { data: 0 }; 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: 0}) })); 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: 0}) })); 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: 0}) })); let ble_advertiser = ble_device.get_advertising(); // TODO: we will need to enable / disable the ability to pair! advertise_pairing(ble_advertiser)?; 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 => { button_up.lock().set_value(&BUTTON_PRESSED).notify(); button_down.lock().set_value(&BUTTON_RELEASED).notify(); button_stop.lock().set_value(&BUTTON_RELEASED).notify(); } Commands::NotifyMotorDown => { button_up.lock().set_value(&BUTTON_RELEASED).notify(); button_down.lock().set_value(&BUTTON_PRESSED).notify(); button_stop.lock().set_value(&BUTTON_RELEASED).notify(); } Commands::NotifyMotorStopping => { button_up.lock().set_value(&BUTTON_RELEASED).notify(); button_down.lock().set_value(&BUTTON_RELEASED).notify(); button_stop.lock().set_value(&BUTTON_PRESSED).notify(); } Commands::NotifyMotorStopped => { button_up.lock().set_value(&BUTTON_RELEASED).notify(); button_down.lock().set_value(&BUTTON_RELEASED).notify(); button_stop.lock().set_value(&BUTTON_RELEASED).notify(); } _ => { error!("Invalid command received by bluetooth handler {:?}", cmd); // No need to reboot as state is recoverable. } } } } } 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: _ } => { if v.len() > 0 { sender.send_blocking(Commands::BluetoothUp {data: v[0]} ) } else {error!("Received zero-byte bluetooth characteristic update {:?}", cmd); Ok(())} } Commands::BluetoothDown { data: _ } => { if v.len() > 0 { sender.send_blocking(Commands::BluetoothDown {data: v[0]} ) } else {error!("Received zero-byte bluetooth characteristic update {:?}", cmd); Ok(())} } Commands::BluetoothStop { _data: _ } => { if v.len() > 0 { sender.send_blocking(Commands::BluetoothStop {_data: v[0]} ) } else {error!("Received zero-byte bluetooth characteristic update {:?}", cmd); Ok(())} } _ => {error!("Tried to handle an unknown bluetooth command: {:?}",cmd);Ok(())} }; match attempt { Ok(_) => {} Err(_) => {panic!("Unable to send notifications of bluetooth events")} } } fn set_device_security(dev: &mut BLEDevice) { dev.security() // Enable all security protections .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) { server.on_connect(|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(); }); server.on_disconnect(|_desc, _reason| { info!("Disconnected, back to advertising"); }); } fn advertise_pairing(advertiser: &Mutex) -> Result<()> { trace!("Setting up advertiser"); advertiser .lock() .set_data( BLEAdvertisementData::new() .name(DEVICE_NAME) .add_service_uuid(UUID_SERVICE_PAIR) )?; // TODO: this appears to run in its own thread; verify. // TODO: isn't there a restart? We'll need to switch between pairing and not. info!("Staring Bluetooth Server"); advertiser.lock().start()?; Ok(()) }