Basic bluetooth functionality
This commit is contained in:
@ -41,7 +41,7 @@ async-executor = "1.13.0"
|
|||||||
async-channel = "2.3.1"
|
async-channel = "2.3.1"
|
||||||
strum = "0.26.3"
|
strum = "0.26.3"
|
||||||
strum_macros = "0.26.4"
|
strum_macros = "0.26.4"
|
||||||
async-io = "2.3.4"
|
closure = "0.3.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
embuild = "0.32.0"
|
embuild = "0.32.0"
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
// Example for reference; not ready to use at all.
|
use crate::dispatch::{Dispatch, RecvQ, SendQ};
|
||||||
|
use crate::commands::Commands;
|
||||||
|
|
||||||
use esp32_nimble::{enums::*, uuid128, BLEAdvertisementData, BLEDevice, NimbleProperties};
|
// TODO: test these values to see if they are suitable
|
||||||
use esp_idf_svc::timer::EspTaskTimerService;
|
const BLE_MIN_INTERVAL: u16 = 24; // x 1.25ms
|
||||||
use core::time::Duration;
|
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
|
||||||
|
|
||||||
fn run_ble_server() {
|
const DEVICE_NAME: &str = "Gem Remotes";
|
||||||
// Take ownership of device
|
|
||||||
|
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();
|
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})
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
// Obtain handle for peripheral advertiser
|
|
||||||
let ble_advertiser = ble_device.get_advertising();
|
let ble_advertiser = ble_device.get_advertising();
|
||||||
|
|
||||||
// Configure Device Security
|
// TODO: we will need to enable / disable the ability to pair!
|
||||||
ble_device
|
advertise_pairing(ble_advertiser)?;
|
||||||
.security()
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Invalid command received by bluetooth handler {:?}", cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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);}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_device_security(dev: &mut BLEDevice) {
|
||||||
|
dev.security()
|
||||||
|
// Enable all security protections
|
||||||
.set_auth(AuthReq::all())
|
.set_auth(AuthReq::all())
|
||||||
.set_passkey(123456)
|
// Options we support for putting in pairing info.
|
||||||
.set_io_cap(SecurityIOCap::DisplayOnly)
|
// "NoInputOutput" means that we will have "just works" pairing
|
||||||
|
.set_io_cap(SecurityIOCap::NoInputNoOutput)
|
||||||
|
//Handle IOS's bluetooth address randomization
|
||||||
.resolve_rpa();
|
.resolve_rpa();
|
||||||
|
}
|
||||||
|
|
||||||
// Obtain handle for server
|
fn set_server_callbacks(server: &mut BLEServer) {
|
||||||
let server = ble_device.get_server();
|
|
||||||
|
|
||||||
// Define server connect behaviour
|
|
||||||
server.on_connect(|server, clntdesc| {
|
server.on_connect(|server, clntdesc| {
|
||||||
// Print connected client data
|
// Print connected client data
|
||||||
println!("{:?}", clntdesc);
|
info!("client connected: {:?}", clntdesc);
|
||||||
// Update connection parameters
|
// Update connection parameters
|
||||||
server
|
server
|
||||||
.update_conn_params(clntdesc.conn_handle(), 24, 48, 0, 60)
|
.update_conn_params(
|
||||||
.unwrap();
|
clntdesc.conn_handle(),
|
||||||
|
BLE_MIN_INTERVAL,
|
||||||
|
BLE_MAX_INTERVAL,
|
||||||
|
BLE_LATENCY,
|
||||||
|
BLE_TIMEOUT,
|
||||||
|
).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define server disconnect behaviour
|
|
||||||
server.on_disconnect(|_desc, _reason| {
|
server.on_disconnect(|_desc, _reason| {
|
||||||
println!("Disconnected, back to advertising");
|
info!("Disconnected, back to advertising");
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Create a service with custom UUID
|
fn advertise_pairing(advertiser: &Mutex<BLEAdvertising>) -> Result<()> {
|
||||||
let my_service = server.create_service(uuid128!("9b574847-f706-436c-bed7-fc01eb0965c1"));
|
trace!("Setting up advertiser");
|
||||||
|
advertiser
|
||||||
// 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
|
|
||||||
.lock()
|
.lock()
|
||||||
.set_data(
|
.set_data(
|
||||||
BLEAdvertisementData::new()
|
BLEAdvertisementData::new()
|
||||||
.name("ESP32 Server")
|
.name(DEVICE_NAME)
|
||||||
.add_service_uuid(uuid128!("9b574847-f706-436c-bed7-fc01eb0965c1")),
|
.add_service_uuid(UUID_SERVICE_PAIR)
|
||||||
)
|
)?;
|
||||||
.unwrap();
|
// 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.
|
||||||
// Start Advertising
|
info!("Staring Bluetooth Server");
|
||||||
ble_advertiser.lock().start().unwrap();
|
advertiser.lock().start()?;
|
||||||
|
Ok(())
|
||||||
// (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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ pub enum Commands {
|
|||||||
// Inputs from bluetooth
|
// Inputs from bluetooth
|
||||||
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.
|
||||||
|
|
||||||
// Internal messages
|
// Internal messages
|
||||||
StopTimerExpired, // Sent when the 2 second stop sequence is complete
|
StopTimerExpired, // Sent when the 2 second stop sequence is complete
|
||||||
|
|||||||
@ -22,6 +22,7 @@ mod dispatch;
|
|||||||
mod motor_controller;
|
mod motor_controller;
|
||||||
mod motor_driver;
|
mod motor_driver;
|
||||||
mod message_timer;
|
mod message_timer;
|
||||||
|
mod ble_server;
|
||||||
|
|
||||||
use crate::message_timer::MessageTimer;
|
use crate::message_timer::MessageTimer;
|
||||||
use crate::commands::Commands;
|
use crate::commands::Commands;
|
||||||
@ -33,7 +34,8 @@ fn main() {
|
|||||||
// Do basic initialization
|
// Do basic initialization
|
||||||
esp_idf_svc::sys::link_patches();
|
esp_idf_svc::sys::link_patches();
|
||||||
esp_idf_svc::log::EspLogger::initialize_default();
|
esp_idf_svc::log::EspLogger::initialize_default();
|
||||||
log::set_max_level(log::LevelFilter::Info);
|
log::set_max_level(log::LevelFilter::Trace);
|
||||||
|
//log::set_max_level(log::LevelFilter::Info);
|
||||||
|
|
||||||
match future::block_on(main_loop()) {
|
match future::block_on(main_loop()) {
|
||||||
Ok(_) => {error!("Exited main loop normally, but this should be impossible.")}
|
Ok(_) => {error!("Exited main loop normally, but this should be impossible.")}
|
||||||
@ -78,6 +80,7 @@ async fn main_loop() -> Result<()> {
|
|||||||
Commands::StopTimerExpired,
|
Commands::StopTimerExpired,
|
||||||
STOP_SAFETY_TIME_MS,
|
STOP_SAFETY_TIME_MS,
|
||||||
&mut dp);
|
&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();
|
||||||
@ -93,6 +96,7 @@ async fn main_loop() -> Result<()> {
|
|||||||
tasks.push(executor.spawn(motor_control.run()));
|
tasks.push(executor.spawn(motor_control.run()));
|
||||||
tasks.push(executor.spawn(button_timer.run()));
|
tasks.push(executor.spawn(button_timer.run()));
|
||||||
tasks.push(executor.spawn(stopping_timer.run()));
|
tasks.push(executor.spawn(stopping_timer.run()));
|
||||||
|
tasks.push(executor.spawn(ble_server.run()));
|
||||||
tasks.push(executor.spawn(dp.cmd_loop()));
|
tasks.push(executor.spawn(dp.cmd_loop()));
|
||||||
|
|
||||||
//Once we have all our tasks, await on them all to run them in parallel.
|
//Once we have all our tasks, await on them all to run them in parallel.
|
||||||
|
|||||||
@ -44,7 +44,7 @@ impl Controller {
|
|||||||
Commands::PicRecvStop,
|
Commands::PicRecvStop,
|
||||||
Commands::BluetoothUp{data: 0},
|
Commands::BluetoothUp{data: 0},
|
||||||
Commands::BluetoothDown{data: 0},
|
Commands::BluetoothDown{data: 0},
|
||||||
Commands::BluetoothStop{_data: 0},
|
Commands::BluetoothStop{data: 0},
|
||||||
Commands::StopTimerExpired,
|
Commands::StopTimerExpired,
|
||||||
Commands::ButtonTimerExpired,
|
Commands::ButtonTimerExpired,
|
||||||
];
|
];
|
||||||
@ -83,6 +83,7 @@ impl Controller {
|
|||||||
|
|
||||||
async fn exit_state(&mut self, old_s: &ControllerStates) -> Result <()> {
|
async fn exit_state(&mut self, old_s: &ControllerStates) -> Result <()> {
|
||||||
match old_s {
|
match old_s {
|
||||||
|
//TODO: We need to notify the BLE controller!
|
||||||
ControllerStates::Stopped => {}
|
ControllerStates::Stopped => {}
|
||||||
ControllerStates::Stopping => {
|
ControllerStates::Stopping => {
|
||||||
self.send.send(Commands::StopTimerClear).await?;
|
self.send.send(Commands::StopTimerClear).await?;
|
||||||
|
|||||||
@ -111,7 +111,7 @@ pub fn process_menu(
|
|||||||
Menu::BluetoothStop { data } => {
|
Menu::BluetoothStop { data } => {
|
||||||
cli.writer()
|
cli.writer()
|
||||||
.write_str("SendingBluetoothStop")?;
|
.write_str("SendingBluetoothStop")?;
|
||||||
let _ = dispatch.send_blocking(Commands::BluetoothStop { _data: data });
|
let _ = dispatch.send_blocking(Commands::BluetoothStop { data: data });
|
||||||
}
|
}
|
||||||
Menu::BluetoothLearn { data } => {
|
Menu::BluetoothLearn { data } => {
|
||||||
cli.writer()
|
cli.writer()
|
||||||
|
|||||||
Reference in New Issue
Block a user