Change name, basic pairing indicator flag

This commit is contained in:
2024-08-28 21:06:38 -04:00
parent f5cd9872bd
commit 326660ab43
5 changed files with 100 additions and 39 deletions

View File

@ -42,6 +42,7 @@ async-channel = "2.3.1"
strum = "0.26.3" strum = "0.26.3"
strum_macros = "0.26.4" strum_macros = "0.26.4"
closure = "0.3.0" closure = "0.3.0"
bitflags = "2.6.0"
[build-dependencies] [build-dependencies]
embuild = "0.32.0" embuild = "0.32.0"

View File

@ -1,9 +1,12 @@
use log::*; //{trace, debug, info, warn, error} use log::*; //{trace, debug, info, warn, error}
use esp32_nimble::{enums::*, uuid128, BLEAdvertisementData, BLEAdvertising, BLEDevice, BLEServer, NimbleProperties, OnWriteArgs}; use esp32_nimble::{enums::*, uuid128, BLEAdvertisementData, BLEDevice, BLEServer, NimbleProperties, OnWriteArgs};
use esp32_nimble::utilities::{mutex::Mutex, BleUuid}; use esp32_nimble::utilities::BleUuid;
use anyhow::Result; use anyhow::Result;
use closure::closure; use closure::closure;
use bitflags::bitflags;
use std::str::FromStr;
use std::sync::Arc;
use crate::dispatch::{Dispatch, RecvQ, SendQ}; use crate::dispatch::{Dispatch, RecvQ, SendQ};
use crate::commands::Commands; 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_LATENCY: u16 = 0; // Number of packets that can be missed, extending interval
const BLE_TIMEOUT: u16 = 500; // x10ms const BLE_TIMEOUT: u16 = 500; // x10ms
const SVC_DATA_PAIRING: [u8; 1] = [1]; // TODO: bitflag these // Bit flags in the custom service byte.
const SVC_DATA_NO_PAIRING: [u8; 1] = [0]; 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"; 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". // 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_UP: BleUuid = uuid128!("c1401121-8dda-45a3-959b-d23a0f8f53d7");
const UUID_BUTTON_DOWN: BleUuid = uuid128!("c1401122-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_BUTTON_STOP: BleUuid = uuid128!("c1401123-8dda-45a3-959b-d23a0f8f53d7");
const UUID_BLUETOOTH_NAME: BleUuid = uuid128!("c1401224-8dda-45a3-959b-d23a0f8f53d7");
pub struct BleServer { pub struct BleServer {
send_q: SendQ, send_q: SendQ,
recv_q: RecvQ, recv_q: RecvQ,
svc_flags: SvcFlags,
dev_name: String,
} }
impl BleServer { impl BleServer {
@ -42,23 +54,27 @@ impl BleServer {
Commands::PairTimerExpired, Commands::PairTimerExpired,
Commands::AllowPairing, Commands::AllowPairing,
Commands::EraseBleBonds, Commands::EraseBleBonds,
Commands::BluetoothName { data: Arc::new(String::new())},
]; ];
let r = dp.get_callback_channel(&cmds); let r = dp.get_callback_channel(&cmds);
let s = dp.get_cmd_channel(); let s = dp.get_cmd_channel();
let dev_name = DEVICE_NAME; //TODO: read this from NVS if it is present.
BleServer { BleServer {
send_q: s.clone(), send_q: s.clone(),
recv_q: r.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 { match self.do_run().await {
Ok(_) => {error!("Exited bluetooth server wait loop with no error.");panic!();} Ok(_) => {error!("Exited bluetooth server wait loop with no error.");panic!();}
Err(e) => {error!("Bluetooth task encountered error {}", e);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"); trace!("Entering BLE Run");
let ble_device = BLEDevice::take(); let ble_device = BLEDevice::take();
set_device_security(ble_device); set_device_security(ble_device);
@ -69,7 +85,7 @@ impl BleServer {
trace!("Setting up GATT"); trace!("Setting up GATT");
//TODO: require authentication (bonding counts?) for these! //TODO: require authentication (bonding counts?) for these!
let sender = self.send_q.clone(); 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( let button_up = lift_service.lock().create_characteristic(
UUID_BUTTON_UP, UUID_BUTTON_UP,
NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY, NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY,
@ -78,7 +94,7 @@ impl BleServer {
.on_write(closure!(clone sender, |args: &mut OnWriteArgs| { .on_write(closure!(clone sender, |args: &mut OnWriteArgs| {
on_bluetooth_cmd(&sender, args, Commands::BluetoothUp {data: 0}) on_bluetooth_cmd(&sender, args, Commands::BluetoothUp {data: 0})
})); }));
// --- Button Down Bluetooth GATT --------------------------------------------------------
let button_down = lift_service.lock().create_characteristic( let button_down = lift_service.lock().create_characteristic(
UUID_BUTTON_DOWN, UUID_BUTTON_DOWN,
NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY, NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY,
@ -87,7 +103,7 @@ impl BleServer {
.on_write(closure!(clone sender, |args: &mut OnWriteArgs| { .on_write(closure!(clone sender, |args: &mut OnWriteArgs| {
on_bluetooth_cmd(&sender, args, Commands::BluetoothDown {data: 0}) on_bluetooth_cmd(&sender, args, Commands::BluetoothDown {data: 0})
})); }));
// --- Button Stop Bluetooth GATT --------------------------------------------------------
let button_stop = lift_service.lock().create_characteristic( let button_stop = lift_service.lock().create_characteristic(
UUID_BUTTON_STOP, UUID_BUTTON_STOP,
NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY, NimbleProperties::READ | NimbleProperties::WRITE_NO_RSP | NimbleProperties::NOTIFY,
@ -96,12 +112,17 @@ impl BleServer {
.on_write(closure!(clone sender, |args: &mut OnWriteArgs| { .on_write(closure!(clone sender, |args: &mut OnWriteArgs| {
on_bluetooth_cmd(&sender, args, Commands::BluetoothStop {_data: 0}) on_bluetooth_cmd(&sender, args, Commands::BluetoothStop {_data: 0})
})); }));
// --- Device Name Bluetooth GATT --------------------------------------------------------
let device_name = lift_service.lock().create_characteristic(
let ble_advertiser = ble_device.get_advertising(); UUID_BLUETOOTH_NAME,
NimbleProperties::READ | NimbleProperties::WRITE,
// TODO: we will need to enable / disable the ability to pair! );
advertise(ble_advertiser)?; 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 { loop {
debug!("Waiting for updates that should be notified via bluetooth"); debug!("Waiting for updates that should be notified via bluetooth");
@ -119,17 +140,19 @@ impl BleServer {
button_up.lock().set_value(&[data]).notify(); button_up.lock().set_value(&[data]).notify();
} }
Commands::PairTimerExpired => { Commands::PairTimerExpired => {
//advertise_to_direct(ble_advertiser).expect("Failed to exit pairing mode"); self.advertise_unpairable()?;
debug!("pairing mode / non-pairing mode not currently supported");
self.send_q.send(Commands::PairTimerClear).await?; self.send_q.send(Commands::PairTimerClear).await?;
} }
Commands::AllowPairing => { 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"); debug!("pairing mode / non-pairing mode not currently supported");
} }
Commands::EraseBleBonds => { Commands::EraseBleBonds => {
ble_device.delete_all_bonds().expect("Failed trying to erase bluetooth bonding information"); 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); error!("Invalid command received by bluetooth handler {:?}", cmd);
// No need to reboot as state is recoverable. // 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) { 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]} ) sender.send_blocking(Commands::BluetoothStop {_data: v[0]} )
} else {error!("Received zero-byte bluetooth characteristic update {:?}", cmd); Ok(())} } 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(())} _ => {error!("Tried to handle an unknown bluetooth command: {:?}",cmd);Ok(())}
}; };
match attempt { match attempt {
@ -199,20 +272,5 @@ fn set_server_callbacks(server: &mut BLEServer, sender: SendQ) {
}); });
} }
fn advertise(advertiser: &Mutex<BLEAdvertising>) -> 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 set maximum pairs to remember?
//TODO after disconnect, it returns to scanning - will it return to directed scanning? Find out when directed is working. //TODO after disconnect, it returns to scanning - will it return to directed scanning? Find out when directed is working.

View File

@ -2,8 +2,9 @@
use strum_macros::EnumCount as EnumCountMacro; use strum_macros::EnumCount as EnumCountMacro;
use std::mem::discriminant; use std::mem::discriminant;
use std::sync::Arc;
#[derive(Clone, Copy, EnumCountMacro, Debug)] #[derive(Clone, EnumCountMacro, Debug)]
pub enum Commands { pub enum Commands {
// Inputs sent from the PIC microcontroller // Inputs sent from the PIC microcontroller
@ -15,6 +16,7 @@ pub enum Commands {
BluetoothUp {data: u8}, BluetoothUp {data: u8},
BluetoothDown {data: u8}, BluetoothDown {data: u8},
BluetoothStop {_data: u8}, // There is no state where releasing the stop button induces a change. BluetoothStop {_data: u8}, // There is no state where releasing the stop button induces a change.
BluetoothName { data: Arc<String>},
// Internal messages // Internal messages
StopTimerExpired, // Sent when the 2 second stop sequence is complete StopTimerExpired, // Sent when the 2 second stop sequence is complete

View File

@ -94,7 +94,7 @@ async fn main_loop() -> Result<()> {
Commands::PairTimerExpired, Commands::PairTimerExpired,
PAIR_TIME_MS, PAIR_TIME_MS,
&mut dp); &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 executor = Executor::new();
let mut tasks:Vec<_> = Vec::new(); let mut tasks:Vec<_> = Vec::new();

View File

@ -27,7 +27,7 @@ pub struct MessageTimer <Q, S> {
state: State, state: State,
} }
impl<Q: PartialEq, S: Copy> MessageTimer<Q, S> { impl<Q: PartialEq, S: Clone> MessageTimer<Q, S> {
pub fn new( pub fn new(
restart: Q, restart: Q,
cancel: Q, cancel: Q,
@ -54,7 +54,7 @@ impl<Q: PartialEq, S: Copy> MessageTimer<Q, S> {
ms: u64, ms: u64,
dp: &mut Dispatch, dp: &mut Dispatch,
) -> MessageTimer<Commands, Commands> { ) -> MessageTimer<Commands, Commands> {
let cmds = vec![restart, cancel]; let cmds = vec![restart.clone(), cancel.clone()];
let s = dp.get_cmd_channel(); let s = dp.get_cmd_channel();
let r = dp.get_callback_channel(&cmds); let r = dp.get_callback_channel(&cmds);
let duration = Duration::from_millis(ms); let duration = Duration::from_millis(ms);
@ -95,7 +95,7 @@ impl<Q: PartialEq, S: Copy> MessageTimer<Q, S> {
} }
Err(_) => { Err(_) => {
trace!("Timeout reached"); 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");
} }
} }
} }