-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add example of using trouble with a serial HCI adapter
- Loading branch information
Showing
3 changed files
with
347 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
[package] | ||
name = "serial-hci" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
serialport = "4.2.0" | ||
env_logger = "0.10.0" | ||
log = "0.4" | ||
crossterm = "0.27.0" | ||
rand_core = { version = "0.6.4", features = ["std"] } | ||
embassy-executor = { version = "0.5.0", features = ["task-arena-size-32768", "arch-std", "executor-thread", "log", "integrated-timers"] } | ||
embedded-io-adapters = { version = "0.6.1", features = ["futures-03"] } | ||
embedded-io-async = { version = "0.6.1" } | ||
embassy-time = { version = "0.3.0", features = ["log", "std", ] } | ||
embassy-sync = { version = "0.5.0", features = ["log"] } | ||
critical-section = { version = "1.1", features = ["std"] } | ||
embassy-futures = { version = "0.1" } | ||
nix = "0.26.2" | ||
async-io = "1.6.0" | ||
static_cell = "2" | ||
futures = { version = "0.3.17" } | ||
|
||
bt-hci = { version = "0.1.0", default-features = false } #features = ["log"] } | ||
trouble-host = { version = "0.1.0", path = "../../host" } #, features = ["log"] } | ||
|
||
[patch.crates-io] | ||
bt-hci = { git = "https://github.com/alexmoon/bt-hci.git", branch = "main" } | ||
embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", branch = "main" } | ||
embassy-time = { git = "https://github.com/embassy-rs/embassy.git", branch = "main" } | ||
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", branch = "main" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
// Use with any serial HCI | ||
use async_io::Async; | ||
use bt_hci::cmd::AsyncCmd; | ||
use bt_hci::cmd::SyncCmd; | ||
use bt_hci::data; | ||
use bt_hci::param; | ||
use bt_hci::Controller; | ||
use bt_hci::ControllerCmdAsync; | ||
use bt_hci::ControllerCmdSync; | ||
use bt_hci::ControllerToHostPacket; | ||
use bt_hci::ReadHci; | ||
use bt_hci::WithIndicator; | ||
use bt_hci::WriteHci; | ||
use core::future::Future; | ||
use core::ops::DerefMut; | ||
use embassy_executor::Executor; | ||
use embassy_futures::join::join3; | ||
use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
use embassy_sync::mutex::Mutex; | ||
use embassy_time as _; | ||
use embassy_time::{Duration, Timer}; | ||
use embedded_io_async::Read; | ||
use log::*; | ||
use nix::sys::termios; | ||
use static_cell::StaticCell; | ||
use trouble_host::{ | ||
adapter::{Adapter, HostResources}, | ||
advertise::{AdStructure, AdvertiseConfig, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE}, | ||
attribute::{AttributeTable, Characteristic, CharacteristicProp, Service, Uuid}, | ||
PacketQos, | ||
}; | ||
|
||
mod serial_port; | ||
use self::serial_port::SerialPort; | ||
|
||
static EXECUTOR: StaticCell<Executor> = StaticCell::new(); | ||
|
||
fn main() { | ||
env_logger::builder() | ||
.filter_level(log::LevelFilter::Debug) | ||
.filter_module("async_io", log::LevelFilter::Info) | ||
.format_timestamp_nanos() | ||
.init(); | ||
|
||
let executor = EXECUTOR.init(Executor::new()); | ||
executor.run(|spawner| { | ||
spawner.spawn(run()).unwrap(); | ||
}); | ||
} | ||
|
||
#[embassy_executor::task] | ||
async fn run() { | ||
let baudrate = termios::BaudRate::B115200; | ||
|
||
if std::env::args().len() != 2 { | ||
println!("Provide the serial port as the one and only command line argument."); | ||
return; | ||
} | ||
|
||
let args: Vec<String> = std::env::args().collect(); | ||
|
||
let port = SerialPort::new(args[1].as_str(), baudrate).unwrap(); | ||
let port = Async::new(port).unwrap(); | ||
let mut port = embedded_io_adapters::futures_03::FromFutures::new(port); | ||
|
||
println!("Reset the target"); | ||
let mut buffer = [0u8; 1]; | ||
|
||
loop { | ||
match port.read(&mut buffer).await { | ||
Ok(_len) => { | ||
if buffer[0] == 0xff { | ||
break; | ||
} | ||
} | ||
Err(_) => (), | ||
} | ||
} | ||
|
||
println!("Connected"); | ||
println!("Q to exit, N to notify, X force disconnect"); | ||
|
||
let controller = SerialController::new(port); | ||
static HOST_RESOURCES: StaticCell<HostResources<NoopRawMutex, 4, 32, 27>> = StaticCell::new(); | ||
let host_resources = HOST_RESOURCES.init(HostResources::new(PacketQos::None)); | ||
|
||
let adapter: Adapter<'_, NoopRawMutex, _, 2, 4, 1, 1> = Adapter::new(controller, host_resources); | ||
let config = AdvertiseConfig { | ||
params: None, | ||
data: &[ | ||
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), | ||
AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]), | ||
AdStructure::CompleteLocalName("Trouble"), | ||
], | ||
}; | ||
|
||
let mut table: AttributeTable<'_, NoopRawMutex, 10> = AttributeTable::new(); | ||
|
||
// Generic Access Service (mandatory) | ||
let mut id = [b'T', b'r', b'o', b'u', b'b', b'l', b'e']; | ||
let mut appearance = [0x80, 0x07]; | ||
let mut bat_level = [0; 1]; | ||
let handle = { | ||
let mut svc = table.add_service(Service::new(0x1800)); | ||
let _ = svc.add_characteristic(Characteristic::new(0x2a00, &[CharacteristicProp::Read], &mut id[..])); | ||
let _ = svc.add_characteristic(Characteristic::new( | ||
0x2a01, | ||
&[CharacteristicProp::Read], | ||
&mut appearance[..], | ||
)); | ||
drop(svc); | ||
|
||
// Generic attribute service (mandatory) | ||
table.add_service(Service::new(0x1801)); | ||
|
||
// Battery service | ||
let mut svc = table.add_service(Service::new(0x180f)); | ||
|
||
svc.add_characteristic(Characteristic::new( | ||
0x2a19, | ||
&[CharacteristicProp::Read, CharacteristicProp::Notify], | ||
&mut bat_level, | ||
)) | ||
}; | ||
|
||
let server = adapter.gatt_server(&table); | ||
|
||
info!("Starting advertising and GATT service"); | ||
let _ = join3( | ||
adapter.run(), | ||
async { | ||
loop { | ||
match server.next().await { | ||
Ok(event) => { | ||
info!("Gatt event: {:?}", event); | ||
} | ||
Err(e) => { | ||
error!("Error processing GATT events: {:?}", e); | ||
} | ||
} | ||
} | ||
}, | ||
async { | ||
let conn = adapter.advertise(&config).await.unwrap(); | ||
// Keep connection alive | ||
let mut tick: u8 = 0; | ||
loop { | ||
Timer::after(Duration::from_secs(10)).await; | ||
tick += 1; | ||
server.notify(handle, &conn, &[tick]).await.unwrap(); | ||
} | ||
}, | ||
) | ||
.await; | ||
} | ||
|
||
pub struct SerialController<T> | ||
where | ||
T: embedded_io_async::Read + embedded_io_async::Write, | ||
{ | ||
io: Mutex<NoopRawMutex, T>, | ||
} | ||
|
||
impl<T> SerialController<T> | ||
where | ||
T: embedded_io_async::Read + embedded_io_async::Write, | ||
{ | ||
pub fn new(io: T) -> Self { | ||
Self { io: Mutex::new(io) } | ||
} | ||
} | ||
|
||
impl<T> Controller for SerialController<T> | ||
where | ||
T: embedded_io_async::Read + embedded_io_async::Write, | ||
{ | ||
type Error = T::Error; | ||
fn write_acl_data(&self, packet: &data::AclPacket) -> impl Future<Output = Result<(), Self::Error>> { | ||
async { | ||
let mut io = self.io.lock().await; | ||
WithIndicator::new(packet) | ||
.write_hci_async(io.deref_mut()) | ||
.await | ||
.unwrap(); | ||
Ok(()) | ||
} | ||
} | ||
|
||
fn write_sync_data(&self, packet: &data::SyncPacket) -> impl Future<Output = Result<(), Self::Error>> { | ||
async { | ||
let mut io = self.io.lock().await; | ||
WithIndicator::new(packet) | ||
.write_hci_async(io.deref_mut()) | ||
.await | ||
.unwrap(); | ||
Ok(()) | ||
} | ||
} | ||
|
||
fn write_iso_data(&self, packet: &data::IsoPacket) -> impl Future<Output = Result<(), Self::Error>> { | ||
async { | ||
let mut io = self.io.lock().await; | ||
WithIndicator::new(packet) | ||
.write_hci_async(io.deref_mut()) | ||
.await | ||
.unwrap(); | ||
Ok(()) | ||
} | ||
} | ||
|
||
fn read<'a>(&self, buf: &'a mut [u8]) -> impl Future<Output = Result<ControllerToHostPacket<'a>, Self::Error>> { | ||
async { | ||
let mut io = self.io.lock().await; | ||
let value = ControllerToHostPacket::read_hci_async(io.deref_mut(), buf) | ||
.await | ||
.unwrap(); | ||
Ok(value) | ||
} | ||
} | ||
} | ||
|
||
impl<T, C> ControllerCmdSync<C> for SerialController<T> | ||
where | ||
T: embedded_io_async::Read + embedded_io_async::Write, | ||
C: SyncCmd, | ||
C::Return: bt_hci::FixedSizeValue, | ||
{ | ||
fn exec(&self, cmd: &C) -> impl Future<Output = Result<C::Return, param::Error>> { | ||
async { | ||
let mut buf = [0; 512]; | ||
let mut io = self.io.lock().await; | ||
WithIndicator::new(cmd).write_hci_async(io.deref_mut()).await.unwrap(); | ||
let value = C::Return::read_hci_async(io.deref_mut(), &mut buf[..]).await.unwrap(); | ||
Ok(value) | ||
} | ||
} | ||
} | ||
|
||
impl<T, C> ControllerCmdAsync<C> for SerialController<T> | ||
where | ||
T: embedded_io_async::Read + embedded_io_async::Write, | ||
C: AsyncCmd, | ||
{ | ||
fn exec(&self, cmd: &C) -> impl Future<Output = Result<(), param::Error>> { | ||
async { | ||
let mut io = self.io.lock().await; | ||
Ok(WithIndicator::new(cmd).write_hci_async(io.deref_mut()).await.unwrap()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
use std::io; | ||
use std::os::unix::io::{AsRawFd, RawFd}; | ||
|
||
use nix::errno::Errno; | ||
use nix::fcntl::OFlag; | ||
use nix::sys::termios; | ||
|
||
pub struct SerialPort { | ||
fd: RawFd, | ||
} | ||
|
||
impl SerialPort { | ||
pub fn new<P: ?Sized + nix::NixPath>(path: &P, baudrate: termios::BaudRate) -> io::Result<Self> { | ||
let fd = nix::fcntl::open( | ||
path, | ||
OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK, | ||
nix::sys::stat::Mode::empty(), | ||
) | ||
.map_err(to_io_error)?; | ||
|
||
let mut cfg = termios::tcgetattr(fd).map_err(to_io_error)?; | ||
cfg.input_flags = termios::InputFlags::empty(); | ||
cfg.output_flags = termios::OutputFlags::empty(); | ||
cfg.control_flags = termios::ControlFlags::empty(); | ||
cfg.local_flags = termios::LocalFlags::empty(); | ||
termios::cfmakeraw(&mut cfg); | ||
cfg.input_flags |= termios::InputFlags::IGNBRK; | ||
cfg.control_flags |= termios::ControlFlags::CREAD; | ||
//cfg.control_flags |= termios::ControlFlags::CRTSCTS; | ||
termios::cfsetospeed(&mut cfg, baudrate).map_err(to_io_error)?; | ||
termios::cfsetispeed(&mut cfg, baudrate).map_err(to_io_error)?; | ||
termios::cfsetspeed(&mut cfg, baudrate).map_err(to_io_error)?; | ||
// Set VMIN = 1 to block until at least one character is received. | ||
cfg.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; | ||
termios::tcsetattr(fd, termios::SetArg::TCSANOW, &cfg).map_err(to_io_error)?; | ||
termios::tcflush(fd, termios::FlushArg::TCIOFLUSH).map_err(to_io_error)?; | ||
|
||
Ok(Self { fd }) | ||
} | ||
} | ||
|
||
impl AsRawFd for SerialPort { | ||
fn as_raw_fd(&self) -> RawFd { | ||
self.fd | ||
} | ||
} | ||
|
||
impl io::Read for SerialPort { | ||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||
nix::unistd::read(self.fd, buf).map_err(to_io_error) | ||
} | ||
} | ||
|
||
impl io::Write for SerialPort { | ||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||
nix::unistd::write(self.fd, buf).map_err(to_io_error) | ||
} | ||
|
||
fn flush(&mut self) -> io::Result<()> { | ||
Ok(()) | ||
} | ||
} | ||
|
||
fn to_io_error(e: Errno) -> io::Error { | ||
e.into() | ||
} |