#[cfg(feature = "subrating")]
use bt_hci::cmd::le::LeSubrateRequest;
use bt_hci::cmd::le::{LeReadLocalSupportedFeatures, LeSetPhy, LeSetScanParams};
use bt_hci::controller::{ControllerCmdAsync, ControllerCmdSync};
use embassy_futures::select::{Either, Either3, select, select3};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::channel::Channel;
use embassy_sync::signal::Signal;
use embassy_time::{Duration, Timer, with_timeout};
use trouble_host::prelude::*;
use super::GattSplitMessage;
use crate::ble::adv::Adv;
use crate::ble::scan::{SPLIT_CENTRAL_SCAN_WINDOW, scan_config, start_scan};
use crate::ble::sleep::report_activity;
use crate::ble::{update_ble_phy, update_conn_params, wait_for_stack_started};
use crate::channel::FLASH_CHANNEL;
use crate::event::{EventSubscriber, SleepStateEvent, SubscribableEvent};
use crate::split::ble::PeerAddress;
use crate::split::driver::{PeripheralManager, SplitDriverError, SplitReader, SplitWriter, set_peripheral_connected};
use crate::split::{PeripheralMatrixConfig, SPLIT_MESSAGE_MAX_SIZE, SplitMessage};
use crate::storage::FlashOperationMessage;
static PERIPHERAL_FOUND: Signal<crate::RawMutex, (u8, BdAddr)> = Signal::new();
enum SlotState {
NoAddr,
Disconnected([u8; 6]),
Connected([u8; 6]),
}
const SPLIT_SERVICE_UUID: u128 = 0x4dd5fbaa_18e5_4b07_bf0a_353698659946;
const MESSAGE_TO_CENTRAL_UUID: u128 = 0x0e6313e3_bd0b_45c2_8d2e_37a2e8128bc3;
const MESSAGE_TO_PERIPHERAL_UUID: u128 = 0x4b3514fb_cae4_4d38_a097_3a2a3d1c3b9c;
pub(crate) async fn scan_and_connect_peripherals<'a, C: Controller + ControllerCmdSync<LeSetScanParams>>(
stack: &'a Stack<'_, C, DefaultPacketPool>,
conns: &[Channel<NoopRawMutex, Connection<'a, DefaultPacketPool>, 1>; crate::SPLIT_PERIPHERALS_NUM],
ended: &Channel<NoopRawMutex, usize, { crate::SPLIT_PERIPHERALS_NUM }>,
) {
let mut peripheral_slots: [SlotState; crate::SPLIT_PERIPHERALS_NUM] = core::array::from_fn(|_| SlotState::NoAddr);
for (id, slot) in peripheral_slots.iter_mut().enumerate() {
if let Some(peer) = crate::storage::read_peer_address(id as u8)
.await
.filter(|peer| peer.is_valid)
{
*slot = SlotState::Disconnected(peer.address);
}
}
let mut central = stack.central();
wait_for_stack_started().await;
loop {
while let Ok(id) = ended.try_receive() {
if let SlotState::Connected(addr) = peripheral_slots[id] {
peripheral_slots[id] = SlotState::Disconnected(addr);
}
}
let mut pending: heapless::Vec<(usize, [u8; 6]), { crate::SPLIT_PERIPHERALS_NUM }> = heapless::Vec::new();
for (id, slot) in peripheral_slots.iter().enumerate() {
if let SlotState::Disconnected(addr) = slot {
let _ = pending.push((id, *addr));
}
}
if !pending.is_empty() {
let targets: heapless::Vec<Address, { crate::SPLIT_PERIPHERALS_NUM }> =
pending.iter().map(|(_, addr)| Address::random(*addr)).collect();
let config = ConnectConfig {
connect_params: default_split_conn_params(),
scan_config: ScanConfig {
filter_accept_list: &targets,
..scan_config(SPLIT_CENTRAL_SCAN_WINDOW)
},
};
info!("Start connecting, {} peripheral(s) pending", pending.len());
let connected = match with_timeout(Duration::from_secs(15), central.connect(&config)).await {
Ok(Ok(conn)) => {
let peer = conn.peer_address();
if let Some(&(id, addr)) = pending.iter().find(|(_, addr)| Address::random(*addr) == peer) {
info!("Connected to peripheral {}", id);
peripheral_slots[id] = SlotState::Connected(addr);
conns[id].send(conn).await;
} else {
warn!("Connected peer {:?} matches no pending slot", peer.addr);
}
true
}
Ok(Err(e)) => {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
error!("Connect error: {:?}", e);
Timer::after_millis(500).await;
false
}
Err(_) => {
if crate::state::current_sleep_state() {
warn!("Connect timeout while asleep, keeping {} address(es)", pending.len());
} else {
warn!("Connect timeout, clearing {} address(es)", pending.len());
for &(id, _) in &pending {
peripheral_slots[id] = SlotState::NoAddr;
}
}
false
}
};
if !connected {
wait_until_wakeup(ended).await;
}
} else if peripheral_slots.iter().all(|s| matches!(s, SlotState::Connected(_))) {
ended.ready_to_receive().await;
} else if crate::state::current_sleep_state() {
wait_until_wakeup(ended).await;
} else {
info!("Start scanning peripherals");
let session = start_scan(stack, SPLIT_CENTRAL_SCAN_WINDOW, &[]).await;
let event = with_timeout(
Duration::from_secs(30),
select(PERIPHERAL_FOUND.wait(), ended.ready_to_receive()),
)
.await;
session.stop().await;
info!("Stop scanning");
if let Ok(Either::First((id, addr))) = event {
match peripheral_slots.get_mut(id as usize) {
Some(slot) if matches!(slot, SlotState::NoAddr) => {
let addr = addr.into_inner();
info!("Scanned new peripheral {:?}", addr);
*slot = SlotState::Disconnected(addr);
FLASH_CHANNEL
.send(FlashOperationMessage::PeerAddress(PeerAddress::new(id, true, addr)))
.await;
}
_ => {}
}
}
}
}
}
async fn wait_until_wakeup(ended: &Channel<NoopRawMutex, usize, { crate::SPLIT_PERIPHERALS_NUM }>) {
while crate::state::current_sleep_state() {
if with_timeout(Duration::from_secs(1), ended.ready_to_receive())
.await
.is_ok()
{
return;
}
}
}
pub(crate) struct ScanHandler;
impl EventHandler for ScanHandler {
fn on_adv_reports(&self, mut it: LeAdvReportsIter<'_>) {
while let Some(Ok(report)) = it.next() {
let Some(Adv::SplitPeripheral { id }) = Adv::decode(report.data) else {
continue;
};
info!("Found split peripheral: id={:?}, addr={:?}", id, report.addr);
PERIPHERAL_FOUND.signal((id, report.addr));
break;
}
}
}
pub(crate) async fn run_peripheral_session<
'a,
#[cfg(not(feature = "subrating"))] C: Controller
+ ControllerCmdSync<LeSetScanParams>
+ ControllerCmdAsync<LeSetPhy>
+ ControllerCmdSync<LeReadLocalSupportedFeatures>,
#[cfg(feature = "subrating")] C: Controller
+ ControllerCmdSync<LeSetScanParams>
+ ControllerCmdAsync<LeSetPhy>
+ ControllerCmdSync<LeReadLocalSupportedFeatures>
+ ControllerCmdAsync<LeSubrateRequest>,
>(
id: usize,
conns: &Channel<NoopRawMutex, Connection<'a, DefaultPacketPool>, 1>,
ended: &Channel<NoopRawMutex, usize, { crate::SPLIT_PERIPHERALS_NUM }>,
stack: &'a Stack<'_, C, DefaultPacketPool>,
matrix_config: PeripheralMatrixConfig,
) {
trace!("SPLIT_MESSAGE_MAX_SIZE: {}", SPLIT_MESSAGE_MAX_SIZE);
loop {
let conn = conns.receive().await;
set_peripheral_connected(id, true);
if let Err(e) = run_central_manager_task(id, stack, &conn, matrix_config).await {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
error!("BLE central error: {:?}", e);
}
set_peripheral_connected(id, false);
drop(conn);
Timer::after_millis(500).await;
ended.send(id).await;
}
}
fn default_split_conn_params() -> RequestedConnParams {
RequestedConnParams {
min_connection_interval: Duration::from_micros(7500),
max_connection_interval: Duration::from_micros(7500),
max_latency: 30, supervision_timeout: Duration::from_secs(6),
..Default::default()
}
}
fn sleep_split_conn_params() -> RequestedConnParams {
if crate::state::active_transport().is_some() {
RequestedConnParams {
min_connection_interval: Duration::from_millis(20),
max_connection_interval: Duration::from_millis(20),
max_latency: 200, supervision_timeout: Duration::from_secs(15),
..Default::default()
}
} else {
RequestedConnParams {
min_connection_interval: Duration::from_millis(200),
max_connection_interval: Duration::from_millis(200),
max_latency: 20, supervision_timeout: Duration::from_secs(15),
..Default::default()
}
}
}
#[cfg(feature = "subrating")]
pub(crate) mod subrating {
use bt_hci::cmd::le::{LeSubrateRequest, LeSubrateRequestParams};
use bt_hci::controller::ControllerCmdAsync;
use bt_hci::param::{ConnHandle, Duration, Error as HciError};
use trouble_host::prelude::*;
const SLEEP_HOST_CONN_SUBRATE: u16 = 30;
const SLEEP_NO_HOST_SUBRATE: u16 = 100;
const SLEEP_CONTINUATION_NUMBER: u16 = 2;
const fn calc_max_latency(subrate_max: u16) -> u16 {
(250 / subrate_max) - 1
}
pub(super) fn default_split_subrating_params(handle: ConnHandle) -> LeSubrateRequestParams {
LeSubrateRequestParams {
handle,
subrate_min: 1,
subrate_max: 1,
max_latency: 30,
continuation_number: 0,
supervision_timeout: Duration::from_secs(6),
}
}
pub(super) fn sleep_split_subrating_params(handle: ConnHandle) -> LeSubrateRequestParams {
let subrate = if crate::state::active_transport().is_some() {
SLEEP_HOST_CONN_SUBRATE
} else {
SLEEP_NO_HOST_SUBRATE
};
LeSubrateRequestParams {
handle,
subrate_min: subrate,
subrate_max: subrate,
max_latency: calc_max_latency(subrate), continuation_number: SLEEP_CONTINUATION_NUMBER,
supervision_timeout: Duration::from_millis(15_000),
}
}
pub(crate) async fn update_subrate_factor<C: Controller + ControllerCmdAsync<LeSubrateRequest>, P: PacketPool>(
stack: &Stack<'_, C, P>,
params: LeSubrateRequestParams,
) -> bool {
for _ in 0..10 {
let subrate_request = LeSubrateRequest::from(params);
match stack.async_command(subrate_request).await {
Ok(_) => {
debug!("[update_subrate_factor] requested {:?}", params);
return true;
}
Err(BleHostError::BleHost(Error::Hci(error))) => {
if error == HciError::CONTROLLER_BUSY || error == HciError::DIFFERENT_TRANSACTION_COLLISION {
info!("[update_subrate_factor] controller busy, retrying: {:?}", error);
embassy_time::Timer::after_millis(100).await;
continue;
}
error!("[update_subrate_factor] HCI error: {:?}", error);
return false;
}
Err(e) => {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
error!("[update_subrate_factor] BLE host error: {:?}", e);
return false;
}
}
}
warn!("[update_subrate_factor] controller stayed busy, giving up");
false
}
}
async fn run_central_manager_task<
'b,
's: 'b,
#[cfg(not(feature = "subrating"))] C: Controller + ControllerCmdAsync<LeSetPhy> + ControllerCmdSync<LeReadLocalSupportedFeatures>,
#[cfg(feature = "subrating")] C: Controller
+ ControllerCmdAsync<LeSetPhy>
+ ControllerCmdSync<LeReadLocalSupportedFeatures>
+ ControllerCmdAsync<LeSubrateRequest>,
P: PacketPool,
>(
id: usize,
stack: &'b Stack<'s, C, P>,
conn: &Connection<'b, P>,
matrix_config: PeripheralMatrixConfig,
) -> Result<(), BleHostError<C::Error>> {
let client = GattClient::<C, P, 10>::new(stack, conn).await?;
update_ble_phy(stack, conn, PhyKind::Le2M).await;
info!("Updating connection parameters for peripheral");
update_conn_params(stack, conn, &default_split_conn_params()).await;
let (Either3::First(e) | Either3::Second(e) | Either3::Third(e)) = select3(
ble_central_task(&client, conn),
discover_and_run_manager(id, &client, matrix_config),
update_conn_params_on_sleep_change(stack, conn),
)
.await;
e
}
async fn ble_central_task<'a, C: Controller + ControllerCmdAsync<LeSetPhy>, P: PacketPool>(
client: &GattClient<'a, C, P, 10>,
conn: &Connection<'a, P>,
) -> Result<(), BleHostError<C::Error>> {
let conn_events = async {
loop {
if let ConnectionEvent::Disconnected { reason } = conn.next().await {
info!("Connection lost: {:?}", reason);
break;
}
}
};
match select(client.task(), conn_events).await {
Either::First(e) => e,
Either::Second(()) => Ok(()),
}
}
async fn discover_and_run_manager<C: Controller + ControllerCmdAsync<LeSetPhy>, P: PacketPool>(
id: usize,
client: &GattClient<'_, C, P, 10>,
matrix_config: PeripheralMatrixConfig,
) -> Result<(), BleHostError<C::Error>> {
let services = client
.services_by_uuid(&Uuid::new_long(SPLIT_SERVICE_UUID.to_le_bytes()))
.await?;
info!("Services found");
let Some(service) = services.first() else {
return Ok(());
};
let message_to_central = client
.characteristic_by_uuid::<GattSplitMessage>(service, &Uuid::new_long(MESSAGE_TO_CENTRAL_UUID.to_le_bytes()))
.await?;
info!("Message to central found");
let message_to_peripheral = client
.characteristic_by_uuid::<GattSplitMessage>(service, &Uuid::new_long(MESSAGE_TO_PERIPHERAL_UUID.to_le_bytes()))
.await?;
info!("Subscribing notifications");
let listener = client.subscribe(&message_to_central, false).await?;
let split_ble_driver = BleSplitCentralDriver {
listener,
message_to_peripheral,
client,
};
PeripheralManager::new(split_ble_driver, id, matrix_config).run().await;
info!("Peripheral manager stopped");
Ok(())
}
struct BleSplitCentralDriver<'a, 'b, 'c, C: Controller + ControllerCmdAsync<LeSetPhy>, P: PacketPool> {
listener: NotificationListener<'b, 512>,
message_to_peripheral: Characteristic<GattSplitMessage>,
client: &'c GattClient<'a, C, P, 10>,
}
impl<'a, 'b, 'c, C: Controller + ControllerCmdAsync<LeSetPhy>, P: PacketPool> SplitReader
for BleSplitCentralDriver<'a, 'b, 'c, C, P>
{
async fn read(&mut self) -> Result<SplitMessage, SplitDriverError> {
let data = self.listener.next().await;
let message = postcard::from_bytes(data.as_ref()).map_err(|_| SplitDriverError::DeserializeError)?;
debug!("Received split message: {:?}", message);
if matches!(message, SplitMessage::Key(_) | SplitMessage::Pointing(_)) {
report_activity();
}
Ok(message)
}
}
impl<'a, 'b, 'c, C: Controller + ControllerCmdAsync<LeSetPhy>, P: PacketPool> SplitWriter
for BleSplitCentralDriver<'a, 'b, 'c, C, P>
{
async fn write(&mut self, message: &SplitMessage) -> Result<usize, SplitDriverError> {
let gatt_msg = GattSplitMessage::try_from(message)?;
if let Err(e) = self
.client
.write_characteristic_without_response(&self.message_to_peripheral, gatt_msg.as_gatt())
.await
{
if let BleHostError::BleHost(Error::NotFound) = e {
error!("Peripheral disconnected");
return Err(SplitDriverError::Disconnected);
}
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
error!("BLE message_to_peripheral_write error: {:?}", e);
}
Ok(gatt_msg.len)
}
}
async fn update_conn_params_on_sleep_change<
'b,
's: 'b,
#[cfg(not(feature = "subrating"))] C: Controller + ControllerCmdAsync<LeSetPhy> + ControllerCmdSync<LeReadLocalSupportedFeatures>,
#[cfg(feature = "subrating")] C: Controller + ControllerCmdAsync<LeSetPhy> + ControllerCmdAsync<LeSubrateRequest>,
P: PacketPool,
>(
stack: &'b Stack<'s, C, P>,
conn: &Connection<'b, P>,
) -> Result<(), BleHostError<C::Error>> {
let mut sleep_events = SleepStateEvent::subscriber();
let mut sleeping = crate::state::current_sleep_state();
if !sleeping {
report_activity();
}
let mut sleeping_conn_param_applied = false;
loop {
if sleeping != sleeping_conn_param_applied {
#[cfg(not(feature = "subrating"))]
let sent = {
let params = if sleeping {
sleep_split_conn_params()
} else {
default_split_conn_params()
};
update_conn_params(stack, conn, ¶ms).await
};
#[cfg(feature = "subrating")]
let sent = {
let params = if sleeping {
subrating::sleep_split_subrating_params(conn.handle())
} else {
subrating::default_split_subrating_params(conn.handle())
};
subrating::update_subrate_factor(stack, params).await
};
if sent {
sleeping_conn_param_applied = sleeping;
}
}
sleeping = sleep_events.next_event().await.0;
}
}