#[cfg(feature = "subrating")]
use bt_hci::{cmd::le::LeSetHostFeature, controller::ControllerCmdSync};
use embassy_futures::join::join;
use embassy_time::{Duration, Timer};
use rmk_types::connection::ConnectionStatus;
use trouble_host::prelude::*;
#[cfg(feature = "storage")]
use super::PeerAddress;
use super::{GattSplitMessage, SplitMessage};
use crate::ble::adv::{Adv, advertise};
use crate::event::{CentralConnectedEvent, KeyboardEvent, SleepStateEvent, SubscribableEvent, publish_event};
use crate::split::driver::{SplitDriverError, SplitReader, SplitWriter};
use crate::split::peripheral::SplitPeripheral;
use crate::state::update_status;
#[gatt_service(uuid = "4dd5fbaa-18e5-4b07-bf0a-353698659946")]
pub(crate) struct SplitBleService {
#[characteristic(uuid = "0e6313e3-bd0b-45c2-8d2e-37a2e8128bc3", read, notify, indicate)]
pub(crate) message_to_central: GattSplitMessage,
#[characteristic(uuid = "4b3514fb-cae4-4d38-a097-3a2a3d1c3b9c", write_without_response, read, notify)]
pub(crate) message_to_peripheral: GattSplitMessage,
}
#[gatt_server]
pub(crate) struct BleSplitPeripheralServer {
pub(crate) service: SplitBleService,
}
pub(crate) struct BleSplitPeripheralDriver<'stack, 'server, 'c, P: PacketPool> {
message_to_peripheral: Characteristic<GattSplitMessage>,
message_to_central: Characteristic<GattSplitMessage>,
conn: &'c GattConnection<'stack, 'server, P>,
}
impl<'stack, 'server, 'c, P: PacketPool> BleSplitPeripheralDriver<'stack, 'server, 'c, P> {
pub(crate) fn new(server: &'server BleSplitPeripheralServer, conn: &'c GattConnection<'stack, 'server, P>) -> Self {
Self {
message_to_central: server.service.message_to_central.clone(),
message_to_peripheral: server.service.message_to_peripheral.clone(),
conn,
}
}
}
impl<'stack, 'server, 'c, P: PacketPool> SplitReader for BleSplitPeripheralDriver<'stack, 'server, 'c, P> {
async fn read(&mut self) -> Result<SplitMessage, SplitDriverError> {
let message = loop {
match self.conn.next().await {
GattConnectionEvent::Disconnected { reason } => {
error!("Disconnected from central: {:?}", reason);
update_status(|c| *c = ConnectionStatus::new());
return Err(SplitDriverError::Disconnected);
}
GattConnectionEvent::Gatt { event: gatt_event } => {
match &gatt_event {
GattEvent::Read(event) => {
info!("Gatt read event: {:?}", event.handle());
}
GattEvent::Write(event) => {
if event.handle() == self.message_to_peripheral.handle {
let parsed = event.with_data(|_, data| {
trace!("Got message from central: {:?}", data);
postcard::from_bytes::<SplitMessage>(data)
});
match parsed {
Ok(message) => {
trace!("Message from central: {:?}", message);
break message;
}
Err(e) => error!("Postcard deserialize split message error: {}", e),
}
} else {
info!("Gatt write other event: {:?}", event.handle());
}
}
_ => debug!("Other gatt event"),
};
match gatt_event.accept() {
Ok(r) => r.send().await,
Err(e) => warn!("[gatt] error sending response: {:?}", e),
}
}
GattConnectionEvent::ConnectionParamsUpdated {
conn_interval,
peripheral_latency,
supervision_timeout,
} => info!(
"[split] params updated: interval {:?}us, latency {:?}, timeout {:?}ms",
conn_interval.as_micros(),
peripheral_latency,
supervision_timeout.as_millis()
),
GattConnectionEvent::SubratingParamsUpdated {
subrate_factor,
peripheral_latency,
continuation_number,
supervision_timeout,
} => info!(
"[split] subrating updated: subrate {:?}, latency {:?}, continuation {:?}, timeout {:?}ms",
subrate_factor,
peripheral_latency,
continuation_number,
supervision_timeout.as_millis()
),
GattConnectionEvent::PhyUpdated { tx_phy, rx_phy } => {
info!("[split] PHY updated: {:?}, {:?}", tx_phy, rx_phy)
}
_ => (),
}
};
Ok(message)
}
}
impl<'stack, 'server, 'c, P: PacketPool> SplitWriter for BleSplitPeripheralDriver<'stack, 'server, 'c, P> {
async fn write(&mut self, message: &SplitMessage) -> Result<usize, SplitDriverError> {
let gatt_msg = GattSplitMessage::try_from(message)?;
debug!("Writing split message to central: {:?}", message);
self.message_to_central
.notify(self.conn, &gatt_msg, true)
.await
.map_err(|e| {
error!("BLE notify error: {:?}", e);
SplitDriverError::BleError(1)
})?;
Ok(gatt_msg.len)
}
}
#[cfg(feature = "subrating")]
async fn init_subrating_host_feature<C: Controller + ControllerCmdSync<LeSetHostFeature>>(
stack: &Stack<'_, C, impl PacketPool>,
) {
const CONN_SUBRATING_HOST_BIT: u8 = 38;
let cmd = LeSetHostFeature::new(CONN_SUBRATING_HOST_BIT, 1);
if let Err(e) = stack.command(cmd).await {
error!("[split_peri] error setting subrating host feature flag: {:?}", e);
}
}
pub async fn initialize_nrf_ble_split_peripheral_and_run<
'b,
's: 'b,
#[cfg(feature = "subrating")] C: Controller + ControllerCmdSync<LeSetHostFeature>,
#[cfg(not(feature = "subrating"))] C: Controller,
>(
id: usize,
stack: &'b Stack<'s, C, DefaultPacketPool>,
) {
publish_event(CentralConnectedEvent { connected: false });
let mut peripheral = stack.peripheral();
let runner = stack.runner();
let mut central_addr = crate::storage::read_peer_address(0)
.await
.filter(|a| a.is_valid)
.map(|a| a.address);
let peri_task = async {
#[cfg(feature = "subrating")]
init_subrating_host_feature(stack).await;
let server = BleSplitPeripheralServer::new_default("rmk").unwrap();
loop {
update_status(|c| *c = ConnectionStatus::new());
publish_event(CentralConnectedEvent { connected: false });
publish_event(SleepStateEvent::new(false));
match split_peripheral_advertise(id, central_addr, &mut peripheral, &server).await {
Ok(conn) => {
info!("Connected to the central");
publish_event(CentralConnectedEvent { connected: true });
let mut peripheral = SplitPeripheral::new(BleSplitPeripheralDriver::new(&server, &conn));
let new_addr = conn.raw().peer_address().addr.into_inner();
if central_addr != Some(new_addr) {
info!("Saving central address to storage");
if crate::storage::write_peer_address(PeerAddress {
peer_id: 0,
is_valid: true,
address: new_addr,
})
.await
{
central_addr = Some(new_addr);
}
}
peripheral.run().await;
info!("Disconnected from the central");
}
Err(BleHostError::BleHost(Error::Timeout)) => {
error!("Connect to central timeout");
publish_event(SleepStateEvent::new(true));
let mut sub = KeyboardEvent::subscriber();
sub.clear();
let _ = sub.next_message_pure().await;
continue;
}
Err(e) => {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
error!("Advertise error: {:?}", e);
Timer::after_millis(500).await;
continue;
}
};
}
};
join(crate::ble::ble_task(runner, &crate::ble::NoopHandler), peri_task).await;
}
async fn split_peripheral_advertise<'a, 'b, C: Controller>(
id: usize,
central_addr: Option<[u8; 6]>,
peripheral: &mut Peripheral<'a, C, DefaultPacketPool>,
server: &'b BleSplitPeripheralServer<'_>,
) -> Result<GattConnection<'a, 'b, DefaultPacketPool>, BleHostError<C::Error>> {
if let Some(addr) = central_addr {
let directed = Adv::Directed(Address::random(addr));
match advertise(peripheral, &server.server, directed, Duration::from_secs(10)).await {
Err(BleHostError::BleHost(Error::Timeout)) => warn!("[adv] Try update central_addr"),
result => return result,
}
}
let seeking = Adv::SplitPeripheral { id: id as u8 };
advertise(peripheral, &server.server, seeking, Duration::from_secs(300)).await
}