Basic bluetooth functionality

This commit is contained in:
2024-08-18 10:32:39 -04:00
parent 3185746213
commit cc72a23176
6 changed files with 185 additions and 65 deletions

View File

@ -1,81 +1,196 @@
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: 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 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![
Commands::BluetoothUp { data: 0 },
Commands::BluetoothDown { data: 0 },
Commands::BluetoothStop { data: 0 },
];
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(_) => {}
Err(e) => {error!("Bluetooth task encountered error {}", e);}
}
Ok(()) //TODO this is not ok; reboot the chip!
//TODO: we need a structure like this at each spawn point;; apparently functions don't pass errors up tasks.
}
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_single_byte(&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_single_byte(&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_single_byte(&sender, args, Commands::BluetoothStop {data: 0})
}));
// Example for reference; not ready to use at all.
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");
let cmd = self.recv_q.recv().await?;
trace!("Received update to bluetooth variable {:?}", cmd);
match cmd {
Commands::BluetoothUp { data } => {
//TODO: this sends a notify even if the command initially came from phone. Is this correct?
trace!("Updating BluetoothUp with {:?}", data);
button_up.lock().set_value(&[data]).notify();
}
Commands::BluetoothDown { data } => {
trace!("Updating BluetoothDown with {:?}", data);
button_down.lock().set_value(&[data]).notify();
}
Commands::BluetoothStop { data} => {
trace!("Updating BluetoothStop with {:?}", data);
button_stop.lock().set_value(&[data]).notify();
use esp32_nimble::{enums::*, uuid128, BLEAdvertisementData, BLEDevice, NimbleProperties};
use esp_idf_svc::timer::EspTaskTimerService;
use core::time::Duration;
}
_ => {
error!("Invalid command received by bluetooth handler {:?}", cmd);
}
}
}
}
fn run_ble_server() {
// Take ownership of device
let ble_device = BLEDevice::take();
}
// Obtain handle for peripheral advertiser
let ble_advertiser = ble_device.get_advertising();
fn on_single_byte(sender: &SendQ, args: &mut OnWriteArgs, cmd: Commands) {
let v = args.recv_data();
//TODO: add "update" versions of these commands, instead of the "got changes from bluetooth" versions, and handle those instead.
match cmd {
Commands::BluetoothUp { data: _ } => {
if v.len() > 0 {
sender.send_blocking(Commands::BluetoothUp {data: v[0]} ).ok();
} else {error!("Received zero-byte bluetooth characteristic update {:?}", cmd)}
}
Commands::BluetoothDown { data: _ } => {
if v.len() > 0 {
sender.send_blocking(Commands::BluetoothDown {data: v[0]} ).ok();
} else {error!("Received zero-byte bluetooth characteristic update {:?}", cmd)}
}
Commands::BluetoothStop { data: _ } => {
if v.len() > 0 {
sender.send_blocking(Commands::BluetoothStop {data: v[0]} ).ok();
} else {error!("Received zero-byte bluetooth characteristic update {:?}", cmd)}
}
_ => {error!("Tried to handle an unknown bluetooth command: {:?}",cmd);}
}
}
// Configure Device Security
ble_device
.security()
fn set_device_security(dev: &mut BLEDevice) {
dev.security()
// Enable all security protections
.set_auth(AuthReq::all())
.set_passkey(123456)
.set_io_cap(SecurityIOCap::DisplayOnly)
// 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();
}
// Obtain handle for server
let server = ble_device.get_server();
// Define server connect behaviour
fn set_server_callbacks(server: &mut BLEServer) {
server.on_connect(|server, clntdesc| {
// Print connected client data
println!("{:?}", clntdesc);
info!("client connected: {:?}", clntdesc);
// Update connection parameters
server
.update_conn_params(clntdesc.conn_handle(), 24, 48, 0, 60)
.unwrap();
.update_conn_params(
clntdesc.conn_handle(),
BLE_MIN_INTERVAL,
BLE_MAX_INTERVAL,
BLE_LATENCY,
BLE_TIMEOUT,
).unwrap();
});
// Define server disconnect behaviour
server.on_disconnect(|_desc, _reason| {
println!("Disconnected, back to advertising");
info!("Disconnected, back to advertising");
});
}
// Create a service with custom UUID
let my_service = server.create_service(uuid128!("9b574847-f706-436c-bed7-fc01eb0965c1"));
// Create a characteristic to associate with created service
let my_service_characteristic = my_service.lock().create_characteristic(
uuid128!("681285a6-247f-48c6-80ad-68c3dce18585"),
NimbleProperties::READ | NimbleProperties::READ_ENC,
);
// Modify characteristic value
my_service_characteristic.lock().set_value(b"Start Value");
// Configure Advertiser Data
ble_advertiser
fn advertise_pairing(advertiser: &Mutex<BLEAdvertising>) -> Result<()> {
trace!("Setting up advertiser");
advertiser
.lock()
.set_data(
BLEAdvertisementData::new()
.name("ESP32 Server")
.add_service_uuid(uuid128!("9b574847-f706-436c-bed7-fc01eb0965c1")),
)
.unwrap();
// Start Advertising
ble_advertiser.lock().start().unwrap();
// (Optional) Print dump of local GATT table
// server.ble_gatts_show_local();
// Init a value to pass to characteristic
let mut val = 0;
let timer_service = EspTaskTimerService::new()?;
let mut async_timer = timer_service.timer_async()?;
loop {
async_timer.after(Duration::from_secs(1)).await?;
my_service_characteristic.lock().set_value(&[val]).notify();
val = val.wrapping_add(1);
}
.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(())
}