215 lines
8.6 KiB
Rust
215 lines
8.6 KiB
Rust
|
|
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<BLEAdvertising>) -> 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(())
|
|
}
|