Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/docs/main/docs/configuration/event.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ peripheral_battery.subs = 4
| `charging_state` | `ChargingStateEvent` | channel_size=2 |
| `battery_status` | `BatteryStatusEvent` | subs=4 |
| **Connection Events** | | |
| `connection_change` | `ConnectionChangeEvent` | |
| `ble_status_change` | `BleStatusChangeEvent` | pubs=2 |
| `connection_status_change` | `ConnectionStatusChangeEvent` | channel_size=2, pubs=2 |
| **Split Events** | | |
| `peripheral_connected` | `PeripheralConnectedEvent` | |
| `central_connected` | `CentralConnectedEvent` | |
Expand Down
3 changes: 1 addition & 2 deletions docs/docs/main/docs/features/event.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ RMK provides built-in event types organized by category:
- `BatteryStatusEvent` - Battery status changed (includes level and charging status)

**Connection Events** (`rmk::event::connection`):
- `ConnectionChangeEvent` - Connection type changed (USB/BLE)
- `BleStatusChangeEvent` - BLE status changed: profile, state (Advertising/Connected/Inactive)
- `ConnectionStatusChangeEvent` - Full `ConnectionStatus` snapshot (USB lifecycle, BLE profile/state, preferred transport); fires on every transition

**Split Keyboard Events** (`rmk::event::split`, when split is enabled):
- `PeripheralConnectedEvent` - Peripheral connection state changed
Expand Down
2 changes: 1 addition & 1 deletion examples/use_rust/sf32lb52x_ble/keyboard.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ subs = 5
[event.sleep_state]
subs = 5

[event.ble_status_change]
[event.connection_status_change]
subs = 5
10 changes: 2 additions & 8 deletions rmk-config/src/default_config/event_default.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
# Event channel default configurations
# Users can override any of these in their keyboard.toml under [event.xxx]

# BLE events
[event.ble_status_change]
channel_size = 2
pubs = 2
subs = 1

# Connection events
[event.connection_change]
channel_size = 1
[event.connection_status_change]
channel_size = 2
pubs = 2
subs = 1

Expand Down
10 changes: 10 additions & 0 deletions rmk-config/src/default_config/subscriber_default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ events = [
{ name = "battery_status" },
]

[[subscriber]]
features = ["display", "_ble"]
events = [
# display/mod.rs: DisplayProcessor subscribes when _ble is enabled
{ name = "connection_status_change" },
]

[[subscriber]]
features = ["display", "split"]
events = [
Expand Down Expand Up @@ -56,6 +63,9 @@ events = [
{ name = "led_indicator" },
# split/driver.rs: LayerChangeEvent::subscriber()
{ name = "layer_change" },
# split/driver.rs: ConnectionStatusChangeEvent::subscriber()
# Covers up to 2 peripherals; for 3+ peripherals override subs in keyboard.toml
{ name = "connection_status_change", count = 2 },
]

# --- Split + BLE-gated internal subscribers ---
Expand Down
4 changes: 1 addition & 3 deletions rmk-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,8 @@ macro_rules! define_event_config {
}

define_event_config!(
// BLE events
ble_status_change,
// Connection events
connection_change,
connection_status_change,
// Input events
modifier,
keyboard,
Expand Down
3 changes: 1 addition & 2 deletions rmk-config/src/resolved/build_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ impl crate::KeyboardTomlConfig {
}

let mut events = event_channels!(
ble_status_change,
connection_change,
connection_status_change,
modifier,
keyboard,
layer_change,
Expand Down
14 changes: 13 additions & 1 deletion rmk/src/ble/battery_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,20 @@ impl<P: PacketPool> Runnable for BleBatteryServer<'_, '_, '_, P> {
// Wait 2 seconds, ensure that gatt server has been started
Timer::after_secs(2).await;

// First report after connected
// First report after connected.
//
// Prefer the cached status from the processor — that way a host that
// connects after the level has already stabilized (battery clamped at
// 100%, no recent key activity, etc.) doesn't have to wait for a state
// change to learn the level. If the cache is empty, fall through to
// waiting on the event stream.
let first_report = async {
if let BatteryStatus::Available { level: Some(level), .. } =
crate::input_device::battery::current_battery_status()
&& self.battery_level.notify(self.conn, &level).await.is_ok()
{
return;
}
loop {
if let BatteryStatus::Available { level: Some(level), .. } = self.sub.next_message_pure().await.0 {
if let Err(e) = self.battery_level.notify(self.conn, &level).await {
Expand Down
8 changes: 4 additions & 4 deletions rmk/src/display/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub use ssd1306;

use crate::core_traits::Runnable;
#[cfg(feature = "_ble")]
use crate::event::BleStatusChangeEvent;
use crate::event::ConnectionStatusChangeEvent;
#[cfg(all(feature = "split", feature = "_ble"))]
use crate::event::PeripheralBatteryEvent;
use crate::event::{
Expand Down Expand Up @@ -224,7 +224,7 @@ pub trait DisplayRenderer<C: PixelColor> {
/// - `D` — display driver, must implement [`DisplayDriver`].
/// - `R` — the renderer, defaults to [`LogoRenderer`].
#[processor(subscribe = [KeyboardEvent, LayerChangeEvent, WpmUpdateEvent, LedIndicatorEvent, ModifierEvent, BatteryStatusEvent, SleepStateEvent])]
#[cfg_attr(feature = "_ble", processor(subscribe = [BleStatusChangeEvent]))]
#[cfg_attr(feature = "_ble", processor(subscribe = [ConnectionStatusChangeEvent]))]
#[cfg_attr(feature = "split", processor(subscribe = [PeripheralConnectedEvent, CentralConnectedEvent]))]
#[cfg_attr(all(feature = "split", feature = "_ble"), processor(subscribe = [PeripheralBatteryEvent]))]
#[::rmk::macros::runnable_generated]
Expand Down Expand Up @@ -381,8 +381,8 @@ where
}

#[cfg(feature = "_ble")]
async fn on_ble_status_change_event(&mut self, event: BleStatusChangeEvent) {
self.ctx.ble_status = event.0;
async fn on_connection_status_change_event(&mut self, event: ConnectionStatusChangeEvent) {
self.ctx.ble_status = event.0.ble;
self.render().await;
}

Expand Down
41 changes: 7 additions & 34 deletions rmk/src/event/connection.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,15 @@
//! Connection related events
//!
//! This module contains all connection-related events:
//! - Connection type change events (USB/BLE)
//! - BLE status change events
//! Single event published whenever the `ConnectionStatus` changes

use rmk_macro::event;
#[cfg(feature = "_ble")]
use rmk_types::ble::BleStatus;
pub use rmk_types::connection::ConnectionType;
pub use rmk_types::connection::{ConnectionStatus, ConnectionType};

// ============================================================================
// Connection Type Events
// ============================================================================

/// Connection type changed event
#[event(channel_size = crate::CONNECTION_CHANGE_EVENT_CHANNEL_SIZE, pubs = crate::CONNECTION_CHANGE_EVENT_PUB_SIZE, subs = crate::CONNECTION_CHANGE_EVENT_SUB_SIZE)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ConnectionChangeEvent(pub ConnectionType);

impl ConnectionChangeEvent {
pub fn new(connection_type: ConnectionType) -> Self {
Self(connection_type)
}
}

impl_payload_wrapper!(ConnectionChangeEvent, ConnectionType);

// ============================================================================
// BLE Connection Events
// ============================================================================

/// BLE status changed event
#[cfg(feature = "_ble")]
#[event(channel_size = crate::BLE_STATUS_CHANGE_EVENT_CHANNEL_SIZE, pubs = crate::BLE_STATUS_CHANGE_EVENT_PUB_SIZE, subs = crate::BLE_STATUS_CHANGE_EVENT_SUB_SIZE)]
/// `ConnectionStatus` changed event. Fires from `state::update_status` whenever
/// the connection status updates
#[event(channel_size = crate::CONNECTION_STATUS_CHANGE_EVENT_CHANNEL_SIZE, pubs = crate::CONNECTION_STATUS_CHANGE_EVENT_PUB_SIZE, subs = crate::CONNECTION_STATUS_CHANGE_EVENT_SUB_SIZE)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BleStatusChangeEvent(pub BleStatus);
pub struct ConnectionStatusChangeEvent(pub ConnectionStatus);

#[cfg(feature = "_ble")]
impl_payload_wrapper!(BleStatusChangeEvent, BleStatus);
impl_payload_wrapper!(ConnectionStatusChangeEvent, ConnectionStatus);
4 changes: 1 addition & 3 deletions rmk/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ mod state;

pub use action::ActionEvent;
pub use battery::{BatteryAdcEvent, BatteryStatusEvent, ChargingStateEvent};
#[cfg(feature = "_ble")]
pub use connection::BleStatusChangeEvent;
pub use connection::{ConnectionChangeEvent, ConnectionType};
pub use connection::{ConnectionStatus, ConnectionStatusChangeEvent, ConnectionType};
pub use input::{
Axis, AxisEvent, AxisValType, KeyPos, KeyboardEvent, KeyboardEventPos, ModifierEvent, PointingEvent,
PointingSetCpiEvent, RotaryEncoderPos,
Expand Down
8 changes: 5 additions & 3 deletions rmk/src/split/ble/peripheral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ use bt_hci::cmd::le::LeSetPhy;
use bt_hci::controller::ControllerCmdAsync;
use embassy_futures::join::join;
use embassy_time::{Duration, Timer, with_timeout};
use rmk_types::connection::ConnectionStatus;
use trouble_host::prelude::*;

#[cfg(feature = "storage")]
use super::PeerAddress;
use crate::event::{CentralConnectedEvent, KeyboardEvent, SubscribableEvent, publish_event};
use crate::split::driver::{SplitDriverError, SplitReader, SplitWriter};
use crate::split::peripheral::SplitPeripheral;
use crate::split::{CENTRAL_HOST_CONNECTED, SPLIT_MESSAGE_MAX_SIZE, SplitMessage};
use crate::split::{SPLIT_MESSAGE_MAX_SIZE, SplitMessage};
use crate::state::update_status;

/// Gatt service used in split peripheral to send split message to central
#[gatt_service(uuid = "4dd5fbaa-18e5-4b07-bf0a-353698659946")]
Expand Down Expand Up @@ -50,7 +52,7 @@ impl<'stack, 'server, 'c, P: PacketPool> SplitReader for BleSplitPeripheralDrive
match self.conn.next().await {
GattConnectionEvent::Disconnected { reason } => {
error!("Disconnected from central: {:?}", reason);
CENTRAL_HOST_CONNECTED.store(false, core::sync::atomic::Ordering::Release);
update_status(|c| *c = ConnectionStatus::new());
return Err(SplitDriverError::Disconnected);
}
GattConnectionEvent::Gatt { event: gatt_event } => {
Expand Down Expand Up @@ -143,7 +145,7 @@ pub async fn initialize_nrf_ble_split_peripheral_and_run<'b, 's: 'b, C: Controll
let peri_task = async {
let server = BleSplitPeripheralServer::new_default("rmk").unwrap();
loop {
CENTRAL_HOST_CONNECTED.store(false, core::sync::atomic::Ordering::Release);
update_status(|c| *c = ConnectionStatus::new());
publish_event(CentralConnectedEvent { connected: false });
match split_peripheral_advertise(id, central_addr, &mut peripheral, &server).await {
Ok(conn) => {
Expand Down
61 changes: 24 additions & 37 deletions rmk/src/split/driver.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! The abstracted driver layer of the split keyboard.
//!
use embassy_futures::select::{Either3, select3};
use embassy_time::{Instant, Timer};
use embassy_futures::select::{Either, select};
use futures::FutureExt;

use super::SplitMessage;
Expand Down Expand Up @@ -71,43 +70,45 @@ impl<const ROW: usize, const COL: usize, const ROW_OFFSET: usize, const COL_OFFS
/// Run the manager.
///
/// The manager receives from the peripheral and publishes input events.
/// It also syncs the central's host-connection state (`active_transport().is_some()`)
/// to the peripheral periodically as informational signal — peripheral-side
/// consumers (e.g. status display) read it from `CENTRAL_HOST_CONNECTED`.
/// It also syncs the central's `ConnectionStatus` to the peripheral on every
/// change as an informational signal
pub(crate) async fn run(mut self) {
use crate::event::EventSubscriber;

if self
.send(&SplitMessage::ConnectionState(
crate::state::active_transport().is_some(),
))
.await
.is_err()
{
return;
}
let mut last_sync_time = Instant::now();

let mut indicator_sub = crate::event::LedIndicatorEvent::subscriber();
let mut layer_sub = crate::event::LayerChangeEvent::subscriber();
// Subscribe before the initial send so any change racing past the
// snapshot is still delivered to us.
let mut connection_sub = crate::event::ConnectionStatusChangeEvent::subscriber();
#[cfg(feature = "_ble")]
let mut clear_peer_sub = crate::event::ClearPeerEvent::subscriber();

#[cfg(feature = "display")]
let mut wpm_sub = crate::event::WpmUpdateEvent::subscriber();
#[cfg(feature = "display")]
let mut modifier_sub = crate::event::ModifierEvent::subscriber();
#[cfg(feature = "display")]
let mut sleep_sub = crate::event::SleepStateEvent::subscriber();

loop {
let elapsed = last_sync_time.elapsed().as_millis();
let wait_time = if elapsed >= 3000 { 1 } else { 3000 - elapsed };
// Send the current state once on startup so the peripheral matches us
// even when no transition has happened since the central booted.
if self
.send(&SplitMessage::ConnectionStatus(
crate::state::current_connection_status(),
))
.await
.is_err()
{
return;
}

loop {
// Use select_biased_with_feature to handle feature-gated subscriber arms
let next_event_to_peri = async {
crate::select_biased_with_feature! {
e = indicator_sub.next_event().fuse() => SplitMessage::KeyboardIndicator(e.0.into_bits()),
e = layer_sub.next_event().fuse() => SplitMessage::Layer(e.0),
e = connection_sub.next_event().fuse() => SplitMessage::ConnectionStatus(e.0),
with_feature("_ble"): _ = clear_peer_sub.next_event().fuse() => {
#[cfg(feature = "storage")]
{
Expand All @@ -128,33 +129,19 @@ impl<const ROW: usize, const COL: usize, const ROW_OFFSET: usize, const COL_OFFS
}
};

match select3(
self.transceiver.read(),
next_event_to_peri,
Timer::after_millis(wait_time),
)
.await
{
Either3::First(read_result) => match read_result {
match select(self.transceiver.read(), next_event_to_peri).await {
Either::First(read_result) => match read_result {
Ok(split_message) => {
self.process_peripheral_message(split_message).await;
}
Err(e) => {
error!("Peripheral message read error: {:?}", e);
}
},
Either3::Second(message_to_peri) => {
if self.send(&message_to_peri).await.is_err() {
return;
}
}
Either3::Third(_) => {
let conn_state = crate::state::active_transport().is_some();
trace!("Syncing connection state to peripheral: {}", conn_state);
if self.send(&SplitMessage::ConnectionState(conn_state)).await.is_err() {
Either::Second(msg) => {
if self.send(&msg).await.is_err() {
return;
}
last_sync_time = Instant::now();
}
}
}
Expand Down
18 changes: 4 additions & 14 deletions rmk/src/split/mod.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
use core::sync::atomic::AtomicBool;

use postcard::experimental::max_size::MaxSize;
use rmk_types::connection::ConnectionStatus;
use serde::{Deserialize, Serialize};

#[cfg(feature = "_ble")]
use crate::event::BatteryStatusEvent;
use crate::event::{KeyboardEvent, PointingEvent};

/// Mirror of the central's `active_transport().is_some()` on the peripheral
/// MCU. The central syncs this over the split link via `SplitMessage::ConnectionState`
/// so peripheral-side consumers (e.g. a status display) can show whether the
/// central currently has a host transport. Read-only signal — nothing in the
/// input pipeline gates on it.
pub(crate) static CENTRAL_HOST_CONNECTED: AtomicBool = AtomicBool::new(false);

#[cfg(feature = "_ble")]
pub mod ble;
pub mod central;
Expand All @@ -39,11 +31,9 @@ pub(crate) enum SplitMessage {
Pointing(PointingEvent),
/// Led state, on/off, from central to peripheral
LedState(bool),
/// `true` when the central has an active host transport (USB Configured/
/// Suspended or BLE Connected). Synced central→peripheral periodically and
/// on change. Informational only — nothing in the input pipeline gates on
/// this; consumers are peripheral-side display/status code.
ConnectionState(bool),
/// `ConnectionStatus` snapshot of the central.
/// Synced central → peripheral on every change.
ConnectionStatus(ConnectionStatus),
/// BLE Address, used in syncing address between central and peripheral
Address([u8; 6]),
/// Clear the saved peer info
Expand Down
Loading
Loading