Skip to content

Commit

Permalink
replace POLLED_EVENTS_CHANNEL with trait function in `KeyboardLayou…
Browse files Browse the repository at this point in the history
…t` and `PeripheralDevice`
  • Loading branch information
Univa committed Mar 31, 2024
1 parent d4ad2b2 commit c1ec9ea
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 20 deletions.
6 changes: 5 additions & 1 deletion docs/src/content/docs/features/feature-split.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,13 @@ To set up the central device, you must add `split_central(driver = "<driver>")`
and your keyboard must implement the appropriate trait for the driver you're using. Check the [list of available split
keyboard drivers](#available-drivers) for this information.

Additionally, you must modify your `KeyboardLayout` implementation to include `type Layout = Self;`. This will
tell the rumcake to redirect matrix events to the layout, to be processed as keycodes.

For example, with the `ble` and an nRF5x chip selected, you must implement `NRFBLECentralDriverSettings`,
and `BluetoothDevice`:

```rust ins={6-8,20-36}
```rust ins={6-8,21-37}
// left.rs
use rumcake::keyboard;

Expand All @@ -66,6 +69,7 @@ struct MyKeyboardLeftHalf;
// KeyboardLayout should already be implemented
use rumcake::keyboard::KeyboardLayout;
impl KeyboardLayout for MyKeyboard {
type Layout = Self;
// ...
}

Expand Down
23 changes: 18 additions & 5 deletions docs/src/content/docs/getting-started/matrix-and-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ In the [templates](https://github.com/Univa/rumcake-templates), you will see tha
to implement a keyboard matrix, you need to implement the `KeyboardMatrix` trait
using one of the `build_<matrix_type>_matrix!` macros:

```rust ins={13-20}
```rust ins={13-21}
use rumcake::keyboard;

#[keyboard(usb)]
Expand All @@ -58,13 +58,18 @@ impl Keyboard for MyKeyboard {

use rumcake::keyboard::{build_standard_matrix, KeyboardMatrix};
impl KeyboardMatrix for MyKeyboard {
type Layout = Self; // Don't worry about the error here yet. It will be fixed once you implement `KeyboardLayout`

build_standard_matrix! {
{ PB2 PB10 PB11 PA3 } // Rows
{ PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 PB3 PB4 PA15 PB5 } // Columns
}
}
```

If you see an error about `Self` not implementing `KeyboardLayout`, don't worry. This will be fixed once you follow
the next section. Note that this associated type is used to redirect matrix events to the implemented layout.

The identifiers used for the matrix pins must match the identifiers used by the respective
HAL (hardware abstraction library) for your MCU. The linked sites below have a dropdown menu at
the top to allow you to select a chip. Choose your chip to see what pins are available:
Expand Down Expand Up @@ -92,7 +97,7 @@ Please follow `keyberon`'s macro instructions there to set up your keyboard layo

The following example shows a 3-layer keyboard layout, meant to be used with the matrix we defined previously:

```rust ins={22-44}
```rust ins={24-46}
use rumcake::keyboard;

#[keyboard(usb)]
Expand All @@ -107,6 +112,8 @@ impl Keyboard for MyKeyboard {

use rumcake::keyboard::{build_standard_matrix, KeyboardMatrix};
impl KeyboardMatrix for MyKeyboard {
type Layout = Self;

build_standard_matrix! {
{ PB2 PB10 PB11 PA3 } // Rows
{ PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 PB3 PB4 PA15 PB5 } // Columns
Expand Down Expand Up @@ -149,11 +156,13 @@ and flashing your firmware, or try implementing additional features in the "Feat
If your MCU pins are connected directly to a switch (as opposed to pins being connected to a row / column of switches),
then you can use the `build_direct_pin_matrix!` macro instead.

```rust ins={3-9}
```rust ins={3-11}
// rest of your config...

use rumcake::keyboard::{build_direct_pin_matrix, KeyboardMatrix};
impl KeyboardMatrix for MyKeyboard {
type Layout = Self;

build_direct_pin_matrix! {
[ PB2 PB10 PB11 PA3 ]
[ PB12 PB1 PB0 No ]
Expand Down Expand Up @@ -191,7 +200,7 @@ If your switch is powered by an analog-to-digital conversion peripheral (which i
then you can use the `build_analog_matrix!` macro. In addition, you will need to specify an ADC sampler configuration, using the `setup_adc_sampler!`
macro.

```rust ins={4-15,17-29}
```rust ins={4-15,17-31}
// rest of your config...

// Create an ADC sampler, where the pins of the MCU are either connected to a multiplexer, or directly to the analog source
Expand All @@ -210,6 +219,8 @@ setup_adc_sampler! {

use rumcake::keyboard::{build_analog_matrix, KeyboardMatrix};
impl KeyboardMatrix for MyKeyboard {
type Layout = Self;

build_analog_matrix! {
{
[ (1,0) (0,1) (0,4) (0,5) ]
Expand Down Expand Up @@ -277,7 +288,7 @@ you to configure something that would look like your matrix.

This can be useful for your keyboard layout config, or your backlight matrix config:

```rust del={50-63} ins={1-26,64-75}
```rust del={52-65} ins={1-26,66-77}
// This creates a `remap!` macro that you can use in other parts of your config.
remap_matrix! {
// This has the same number of rows and columns that you specified in your matrix.
Expand Down Expand Up @@ -319,6 +330,8 @@ impl Keyboard for MyKeyboard {

use rumcake::keyboard::{build_standard_matrix, KeyboardMatrix};
impl KeyboardMatrix for MyKeyboard {
type Layout = Self;

build_standard_matrix! {
{ PB3 PB4 PA15 PB5 PA0 PA1 PB2 PB10 PB11 PA3 } // Rows
{ PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 } // Columns
Expand Down
62 changes: 54 additions & 8 deletions rumcake/src/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ pub trait Keyboard {

/// A trait that must be implemented on a device that communicates with the host device.
pub trait KeyboardLayout {
/// Get a reference to a channel that can receive matrix events from other tasks to be
/// processed into keycodes.
fn get_matrix_events_channel() -> &'static Channel<RawMutex, Event, 1> {
static POLLED_EVENTS_CHANNEL: Channel<RawMutex, Event, 1> = Channel::new();

&POLLED_EVENTS_CHANNEL
}

const NUM_ENCODERS: usize = 0; // Only for VIA compatibility, no proper encoder support. This is the default if not set in QMK

/// Number of columns in the layout.
Expand Down Expand Up @@ -130,6 +138,14 @@ impl<const C: usize, const R: usize, const L: usize> Layout<C, R, L> {

/// A trait that must be implemented for any device that needs to poll a switch matrix.
pub trait KeyboardMatrix {
/// The layout to send matrix events to.
type Layout: private::MaybeKeyboardLayout = private::EmptyKeyboardLayout;

#[cfg(feature = "split-peripheral")]
/// The peripheral device in a split keyboard setup to send matrix events to.
type PeripheralDeviceType: crate::split::peripheral::private::MaybePeripheralDevice =
crate::split::peripheral::private::EmptyPeripheralDevice;

/// Debounce setting.
const DEBOUNCE_MS: u16 = 5;

Expand Down Expand Up @@ -350,15 +366,13 @@ where
}
}

/// Channel with keyboard events polled from the swtich matrix
///
/// The coordinates received will be remapped according to the implementation of
/// [`KeyboardMatrix::remap_to_layout`].
pub(crate) static POLLED_EVENTS_CHANNEL: Channel<RawMutex, Event, 1> = Channel::new();

#[rumcake_macros::task]
pub async fn matrix_poll<K: KeyboardMatrix + 'static>(_k: K) {
let matrix = K::get_matrix();
let layout_channel = <K::Layout as private::MaybeKeyboardLayout>::get_matrix_events_channel();

#[cfg(feature = "split-peripheral")]
let peripheral_channel = <K::PeripheralDeviceType as crate::split::peripheral::private::MaybePeripheralDevice>::get_matrix_events_channel();

loop {
{
Expand All @@ -380,7 +394,14 @@ pub async fn matrix_poll<K: KeyboardMatrix + 'static>(_k: K) {
Debug2Format(&remapped_event)
);

POLLED_EVENTS_CHANNEL.send(remapped_event).await;
if let Some(layout_channel) = layout_channel {
layout_channel.send(remapped_event).await
};

#[cfg(feature = "split-peripheral")]
if let Some(peripheral_channel) = peripheral_channel {
peripheral_channel.send(remapped_event).await
};
}
}
Timer::after(Duration::from_micros(500)).await;
Expand Down Expand Up @@ -426,12 +447,13 @@ where
let mut codes = [Consumer::Unassigned; 4];

let mut ticker = Ticker::every(Duration::from_millis(1));
let channel = K::get_matrix_events_channel();

loop {
let keys = {
let mut layout = layout.lock().await;

if let Ok(event) = POLLED_EVENTS_CHANNEL.try_receive() {
if let Ok(event) = channel.try_receive() {
layout.event(event);
MATRIX_EVENTS.publish_immediate(event); // Just immediately publish since we don't want to hold up any key events to be converted into keycodes.
};
Expand Down Expand Up @@ -551,3 +573,27 @@ where
ticker.next().await;
}
}

pub(crate) mod private {
use embassy_sync::channel::Channel;
use keyberon::layout::Event;

use crate::hw::platform::RawMutex;

use super::KeyboardLayout;

pub struct EmptyKeyboardLayout;
impl MaybeKeyboardLayout for EmptyKeyboardLayout {}

pub trait MaybeKeyboardLayout {
fn get_matrix_events_channel() -> Option<&'static Channel<RawMutex, Event, 1>> {
None
}
}

impl<T: KeyboardLayout> MaybeKeyboardLayout for T {
fn get_matrix_events_channel() -> Option<&'static Channel<RawMutex, Event, 1>> {
Some(T::get_matrix_events_channel())
}
}
}
17 changes: 13 additions & 4 deletions rumcake/src/split/central.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ use embassy_futures::select::{select, Either};
use embassy_sync::channel::Channel;

use crate::hw::platform::RawMutex;
use crate::keyboard::POLLED_EVENTS_CHANNEL;
use crate::keyboard::KeyboardLayout;
use crate::split::MessageToCentral;

use super::drivers::CentralDeviceDriver;
use super::MessageToPeripheral;

pub trait CentralDevice {
/// The layout to send matrix events (which were received by peripherals) to.
type Layout: KeyboardLayout;

/// Get a reference to a channel that can receive messages from other tasks to be sent to
/// peripherals.
fn get_message_to_peripheral_channel() -> &'static Channel<RawMutex, MessageToPeripheral, 4> {
Expand Down Expand Up @@ -58,14 +61,20 @@ pub(crate) mod private {

#[rumcake_macros::task]
pub async fn central_task<K: CentralDevice>(_k: K, mut driver: impl CentralDeviceDriver) {
let channel = K::get_message_to_peripheral_channel();
let message_to_peripherals_channel = K::get_message_to_peripheral_channel();
let matrix_events_channel = K::Layout::get_matrix_events_channel();

loop {
match select(driver.receive_message_from_peripherals(), channel.receive()).await {
match select(
driver.receive_message_from_peripherals(),
message_to_peripherals_channel.receive(),
)
.await
{
Either::First(message) => match message {
Ok(event) => match event {
MessageToCentral::KeyPress(_, _) | MessageToCentral::KeyRelease(_, _) => {
POLLED_EVENTS_CHANNEL.send(event.try_into().unwrap()).await;
matrix_events_channel.send(event.try_into().unwrap()).await;
}
},
Err(err) => {
Expand Down
41 changes: 39 additions & 2 deletions rumcake/src/split/peripheral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,26 @@

use defmt::{error, Debug2Format};
use embassy_futures::select::{select, Either};
use embassy_sync::channel::Channel;
use embassy_sync::pubsub::PubSubBehavior;
use keyberon::layout::Event;

use crate::keyboard::{MATRIX_EVENTS, POLLED_EVENTS_CHANNEL};
use crate::hw::platform::RawMutex;
use crate::keyboard::MATRIX_EVENTS;
use crate::split::MessageToPeripheral;

use super::drivers::PeripheralDeviceDriver;

// Trait that devices must implement to serve as a peripheral in a split keyboard setup.
pub trait PeripheralDevice {
/// Get a reference to a channel that can receive matrix events from other tasks to be
/// processed into keycodes.
fn get_matrix_events_channel() -> &'static Channel<RawMutex, Event, 1> {
static POLLED_EVENTS_CHANNEL: Channel<RawMutex, Event, 1> = Channel::new();

&POLLED_EVENTS_CHANNEL
}

#[cfg(feature = "underglow")]
type UnderglowDeviceType: crate::lighting::underglow::private::MaybeUnderglowDevice =
crate::lighting::private::EmptyLightingDevice;
Expand All @@ -32,13 +43,39 @@ pub trait PeripheralDevice {
type RGBBacklightMatrixDeviceType: crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice = crate::lighting::private::EmptyLightingDevice;
}

pub(crate) mod private {
use embassy_sync::channel::Channel;
use keyberon::layout::Event;

use crate::hw::platform::RawMutex;

use super::PeripheralDevice;

pub struct EmptyPeripheralDevice;
impl MaybePeripheralDevice for EmptyPeripheralDevice {}

pub trait MaybePeripheralDevice {
fn get_matrix_events_channel() -> Option<&'static Channel<RawMutex, Event, 1>> {
None
}
}

impl<T: PeripheralDevice> MaybePeripheralDevice for T {
fn get_matrix_events_channel() -> Option<&'static Channel<RawMutex, Event, 1>> {
Some(T::get_matrix_events_channel())
}
}
}

// This task replaces the `layout_collect` task, which is usually used on non-split keyboards for sending events to the keyboard layout
#[rumcake_macros::task]
pub async fn peripheral_task<K: PeripheralDevice>(_k: K, mut driver: impl PeripheralDeviceDriver) {
let channel = K::get_matrix_events_channel();

loop {
match select(
driver.receive_message_from_central(),
POLLED_EVENTS_CHANNEL.receive(),
channel.receive(),
)
.await
{
Expand Down

0 comments on commit c1ec9ea

Please sign in to comment.