Skip to content

Commit 3b2cf13

Browse files
authored
Merge pull request #269 from HaoboGu/feat/device_api
WIP: Rework on input device trait(again), add input device API
2 parents 58e3485 + 29de86d commit 3b2cf13

File tree

127 files changed

+4411
-3302
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+4411
-3302
lines changed

docs/src/design_doc/input_device.md

Lines changed: 0 additions & 120 deletions
This file was deleted.

docs/src/device.md

Lines changed: 167 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,144 @@
1-
# Device
1+
# Input devices
22

3-
There are two types of device in RMK:
3+
The definition of input devices varies, but for RMK, we focus on two categories: keys and sensors.
44

5-
- input device: an external device which generates HID reports (keyboard/mouse/media), such as encoder, joystick, touchpad, etc.
5+
- Keys are straightforward—they are essentially switches with two states (pressed/released).
6+
- Sensors are more complex devices that can produce various types of data, such as joysticks, mice, trackpads, and trackballs.
67

7-
- output device: an external device which is triggered by RMK, to perform some functionalities, such as LED, RGB, screen, motor, etc
8+
## Usage
89

9-
## Input device
10+
RMK uses a standard pattern for running the entire system:
1011

11-
Here is a simple(but not exhaustive) list of input devices:
12+
```rust
13+
// Create a rotary encoder input device and a processor which processes encoder's event
14+
let mut encoder = RotaryEncoder::new(pin_a, pin_b, 0);
15+
let mut encoder_processor = RotaryEncoderProcessor::new(&keymap);
16+
// Start the system with three concurrent tasks
17+
join3(
18+
// Run all input devices and send events to EVENT_CHANNEL
19+
run_devices! (
20+
(matrix, encoder) => rmk::channel::EVENT_CHANNEL,
21+
),
22+
// Use `encoder_processor` to process the events.
23+
run_processor_chain! {
24+
rmk::channel::EVENT_CHANNEL => [encoder_processor],
25+
},
26+
// Run the keyboard processor
27+
keyboard.run(),
28+
// Run the remaining parts of RMK system
29+
run_rmk(&keymap, driver, storage, light_controller, rmk_config),
30+
)
31+
.await;
32+
```
33+
34+
Notes:
35+
- If the input devices emit only `KeyEvent`, use only `run_device!((matrix, dev) => EVENT_CHANNEL))` is enough, no `run_processor_chain` is needed. Because all `KeyEvent`s are automatically processed by `keyboard.run()`
36+
- `EVENT_CHANNEL` are built-in, you can also use your own local channels
37+
- The events are processed in a chained way, until the processor's `process()` returns `ProcessResult::Stop`. So the order of processors in the `run_processor_chain` matters
38+
- For advanced use cases, developers can define custom events and procesors to fully control the input logic
39+
- The keyboard is special -- it receives events only from `KEY_EVENT_CHANNEL` and processes `KeyEvent`s only. `KeyEvent` from ALL devices are handled by the `Keyboard` processor, then the other events are dispatched to binded processors
40+
41+
## Implementation new devices
42+
RMK's input device framework is designed to provide a simple yet extensible way to handle both keys and sensors. Below is an overview of the framework:
43+
44+
![input_device_framework](images/input_device_framework.excalidraw.svg)
45+
46+
To implement a new input device in RMK, you need to:
47+
48+
1. Create a struct for your device that implements the `InputDevice` trait
49+
2. Define how it generates events by implementing the `read_event()` method
50+
3. Use it with `run_devices!` to integrate into RMK's event system
51+
52+
## Input device trait
53+
54+
Input devices such as key matrices or sensors read physical devices and generate events. All input devices in RMK should implement the `InputDevice` trait:
55+
56+
```rust
57+
pub trait InputDevice {
58+
/// Read the raw input event
59+
async fn read_event(&mut self) -> Event;
60+
}
61+
```
62+
63+
This trait is used with the `run_devices!` macro to collect events from multiple input devices and send them to a specified channel:
64+
65+
```rust
66+
// Send events from matrix to EVENT_CHANNEL
67+
run_devices! (
68+
(matrix) => EVENT_CHANNEL,
69+
)
70+
```
71+
72+
> Why `run_devices!`?
73+
>
74+
> Currently, embassy-rs does not support generic tasks. The only option is to join all tasks together to handle multiple input devices concurrently. The `run_devices!` macro helps accomplish this efficiently.
75+
76+
## Runnable trait
77+
78+
For components that need to run continuously in a task, RMK provides the `Runnable` trait:
79+
80+
```rust
81+
pub trait Runnable {
82+
async fn run(&mut self);
83+
}
84+
```
1285

13-
- Keyboard itself
14-
- Rotary encoder
15-
- Touchpad
16-
- Trackball
17-
- Joystick
86+
The `Keyboard` type implements this trait to process events and generate reports.
1887

19-
Except keyboard and rotary encoder, the others protocol/implementation depend on the actual device. A driver like interface is what we need.
88+
89+
## Event Types
90+
91+
RMK provides a default `Event` enum that is compatible with built-in `InputProcessor`s:
92+
93+
```rust
94+
#[non_exhaustive]
95+
#[derive(Serialize, Deserialize, Clone, Debug)]
96+
pub enum Event {
97+
/// Keyboard event
98+
Key(KeyEvent),
99+
/// Rotary encoder, ec11 compatible models
100+
RotaryEncoder(RotaryEncoderEvent),
101+
/// Multi-touch touchpad
102+
Touchpad(TouchpadEvent),
103+
/// Joystick, suppose we have x,y,z axes for this joystick
104+
Joystick([AxisEvent; 3]),
105+
/// An AxisEvent in a stream of events. The receiver should keep receiving events until it receives [`Eos`] event.
106+
AxisEventStream(AxisEvent),
107+
/// End of the event sequence
108+
///
109+
/// This is used with [`AxisEventStream`] to indicate the end of the event sequence.
110+
Eos,
111+
}
112+
```
113+
114+
The `Event` enum aims to cover raw outputs from common input devices. It also provides a stream-like axis event representation via `AxisEventStream` for devices with a variable number of axes. When using `AxisEventStream`, the `Eos` event must be sent to indicate the end of the sequence.
115+
116+
## Input Processor Trait
117+
118+
Input processors receive events from input devices, process them, and convert the results into HID reports for USB/BLE transmission. All input processors must implement the `InputProcessor` trait:
119+
120+
```rust
121+
pub trait InputProcessor {
122+
/// Process the incoming events, convert them to HID report [`Report`],
123+
/// then send the report to the USB/BLE.
124+
///
125+
/// Note there might be multiple HID reports are generated for one event,
126+
/// so the "sending report" operation should be done in the `process` method.
127+
/// The input processor implementor should be aware of this.
128+
async fn process(&mut self, event: Event);
129+
130+
/// Send the processed report.
131+
async fn send_report(&self, report: Report) {
132+
KEYBOARD_REPORT_CHANNEL.send(report).await;
133+
}
134+
}
135+
```
136+
137+
The `process` method is responsible for processing input events and sending HID reports through the report channel. All processors share a common keymap state through `&'a RefCell<KeyMap<'a, ROW, COL, NUM_LAYER>>`.
20138

21139
### Rotary encoder
22140

141+
TODO:
23142
The encoder list is represented separately in vial, different from normal matrix. But layers still have effect on encoder. The behavior of rotary encoder could be changed by vial.
24143

25144
# Input Devices
@@ -30,29 +149,33 @@ RMK supports various input devices beyond just key matrices. The input system co
30149

31150
Each input device must implement the `InputDevice` trait, which requires:
32151

33-
- An associated `EventType` that defines what kind of events this device generates
34-
- A `run()` method containing the device's main logic
35-
- A `send_event()` method to send events to processors
152+
- A `read_event()` method to read raw input events
36153

37154
Example implementation:
38155

39156
```rust
40157
struct MyEncoder;
41158
impl InputDevice for MyEncoder {
42-
type EventType = Event;
43-
44-
async fn run(&mut self) {
45-
// Read encoder and send events
46-
let event = Event::RotaryEncoder(RotaryEncoderEvent::Clockwise);
47-
self.send_event(event).await;
48-
}
49-
50-
async fn send_event(&mut self, event: Self::EventType) {
51-
// Send event to processor
159+
async fn read_event(&mut self) -> Event {
160+
// Read encoder and return events
161+
embassy_time::Timer::after_secs(1).await;
162+
Event::RotaryEncoder(RotaryEncoderEvent::Clockwise)
52163
}
53164
}
54165
```
55166

167+
### Runnable Trait
168+
169+
For components that need to continuously run in the background, RMK provides the `Runnable` trait:
170+
171+
```rust
172+
pub trait Runnable {
173+
async fn run(&mut self);
174+
}
175+
```
176+
177+
The `Keyboard` type implements this trait to process events and generate reports.
178+
56179
## Input Processors
57180

58181
Input processors handle events from input devices and convert them into HID reports. A processor:
@@ -63,11 +186,8 @@ Input processors handle events from input devices and convert them into HID repo
63186

64187
The `InputProcessor` trait defines this behavior with:
65188

66-
- Associated types for events it handles (`EventType`) and reports it generates (`ReportType`)
67189
- A `process()` method to convert events to reports
68-
- A `read_event()` method to receive events
69190
- A `send_report()` method to send processed reports
70-
- A default `run()` implementation that handles the event processing loop
71191

72192
### Built-in Event Types
73193

@@ -79,21 +199,26 @@ RMK provides several built-in event types through the `Event` enum:
79199
- `Joystick` - Joystick axis events
80200
- `AxisEventStream` - Stream of axis events for complex input devices
81201

82-
### Running Multiple Devices
202+
### Running Devices, Processors and RMK
83203

84-
RMK provides macros to run multiple input devices and processors concurrently:
204+
RMK provides a standardized approach for running the entire system with multiple components:
85205

86206
```rust
87-
// Run multiple input devices
88-
let encoder = MyEncoder::new();
89-
let touchpad = MyTouchpad::new();
90-
91-
// Run multiple processors
92-
let encoder_proc = EncoderProcessor::new();
93-
let touchpad_proc = TouchpadProcessor::new();
94-
95-
join(
96-
run_processors!(encoder_proc, touchpad_proc),
97-
run_devices!(encoder, touchpad)
98-
).await;
207+
// Start the system with three concurrent tasks
208+
join3(
209+
// Task 1: Run all input devices and send events to EVENT_CHANNEL
210+
run_devices! (
211+
(matrix, encoder) => EVENT_CHANNEL,
212+
),
213+
// Task 2: Run the keyboard processor
214+
keyboard.run(),
215+
// Task 3: Run the main RMK system
216+
run_rmk(&keymap, driver, storage, light_controller, rmk_config),
217+
)
218+
.await;
99219
```
220+
221+
This pattern is used across all RMK examples and provides a clean way to:
222+
1. Read events from input devices and send them to a channel
223+
2. Process those events with a keyboard processor
224+
3. Handle all RMK system functionality in parallel

docs/src/keymap.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub(crate) const ROW: usize = 5;
2121
pub(crate) const NUM_LAYER: usize = 2;
2222

2323
#[rustfmt::skip]
24-
pub fn get_default_keymap() -> [[[KeyAction; COL]; ROW]; NUM_LAYER] {
24+
pub const fn get_default_keymap() -> [[[KeyAction; COL]; ROW]; NUM_LAYER] {
2525
[
2626
layer!([
2727
[k!(Grave), k!(Kc1), k!(Kc2), k!(Kc3), k!(Kc4), k!(Kc5), k!(Kc6), k!(Kc7), k!(Kc8), k!(Kc9), k!(Kc0), k!(Minus), k!(Equal), k!(Backspace)],
@@ -54,7 +54,7 @@ Then, the keymap is defined as a static 3-D matrix of `KeyAction`:
5454

5555
```rust
5656
// You should define a function that returns defualt keymap by yourself
57-
pub fn get_default_keymap() -> [[[KeyAction; COL]; ROW]; NUM_LAYER] {
57+
pub const fn get_default_keymap() -> [[[KeyAction; COL]; ROW]; NUM_LAYER] {
5858
...
5959
}
6060
```

0 commit comments

Comments
 (0)