-
-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #192 from HaoboGu/feat/input_device
Add basic POC input device support and rotary encoder implementation as an example
- Loading branch information
Showing
28 changed files
with
993 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
# Input devices | ||
|
||
The definition of input devices varies, but for RMK, we focus on two categories: keys and sensors. | ||
|
||
- Keys are straightforward—they are essentially switches with two states. | ||
- Sensors, on the other hand, are more complex. Devices such as joysticks, mice, trackpads, and trackballs are all sensors. They produce different data and speak different protocols. | ||
|
||
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: | ||
|
||
![input_device_framework](../images/input_device_framework.svg) | ||
|
||
At first glance, the framework looks simple. However, as we delve into the implementation, intricacies emerge. In this documentation, we will focus on the left half of the diagram: `InputDevices` and `InputProcessors`. Key areas of complexity include: | ||
|
||
This design document outlines the current implementation, which aims to make RMK’s input device system clear, easy to use, and extensible. | ||
|
||
## Input device trait | ||
|
||
The input devices can be key matrix or sensors, which read the physical devices, send raw events to the input processors. All input devices in RMK should implement the `InputDevice` trait: | ||
|
||
```rust | ||
pub trait InputDevice { | ||
/// Event type that input device will send | ||
type EventType; | ||
|
||
/// Starts the input device task. | ||
/// | ||
/// This asynchronous method should contain the main logic for the input device. | ||
/// It will be executed concurrently with other input devices using the `run_devices` macro. | ||
fn run(&mut self) -> impl Future<Output = ()>; | ||
|
||
/// Get the event channel for the input device. All events should be send by this channel. | ||
fn get_channel(&self) -> &Channel<CriticalSectionRawMutex, Self::EventType, EVENT_CHANNEL_SIZE>; | ||
} | ||
``` | ||
|
||
This trait should be used with the `run_devices!` macro: | ||
|
||
```rust | ||
// Suppose that the d1 & d2 both implement `InputDevice`. `run()` will be called in `run_devices!` | ||
run_devices!(d1, d2).await; | ||
``` | ||
|
||
> Why `run_devices!`? | ||
> | ||
> Currently, embassy-rs does not support generic tasks. So the only option is to join all tasks(aka `run` function in `InputDevice`) together. That's what `run_devices!` does. | ||
|
||
Another thing that need to be mentioned is that every input device defines its own `EventType`. RMK provides a default Event enum, which is compatible with built-in InputProcessors. This design balances convenience and flexibility: | ||
|
||
- For common devices, developers can use the built-in `Event` and `InputProcessor` implementations to when implementing new `InputDevice` | ||
- For advanced use cases, developers can define custom events and processors to fully control the input logic. | ||
|
||
## Event | ||
|
||
The event is the output of input devices. It's defined as a non-exhaustive enum: | ||
|
||
```rust | ||
#[non_exhaustive] | ||
#[derive(Serialize, Deserialize, Clone, Debug)] | ||
pub enum Event { | ||
/// Keyboard event | ||
Key(KeyEvent), | ||
/// Rotary encoder, ec11 compatible models | ||
RotaryEncoder(RotaryEncoderEvent), | ||
/// Multi-touch touchpad | ||
Touchpad(TouchpadEvent), | ||
/// Joystick, suppose we have x,y,z axes for this joystick | ||
Joystick([AxisEvent; 3]), | ||
/// An AxisEvent in a stream of events. The receiver should keep receiving events until it receives [`Eos`] event. | ||
AxisEventStream(AxisEvent), | ||
/// End of the event sequence | ||
/// | ||
/// This is used with [`AxisEventStream`] to indicate the end of the event sequence. | ||
Eos, | ||
} | ||
``` | ||
|
||
The `Event` aims to include raw outputs of all commonly used input devices, such as mice, trackpads, joysticks, etc. It also provides a stream-like axis event representation `AxisEventStream`, which can be used for a multiple-axis device where the number of axes is not known. Note that when using this, the `Eos` should be sent the indicate the end of the event sequence, otherwise the `InputProcessor` would wait for the next event forever. | ||
|
||
## Input processor trait | ||
|
||
The input processors receive the event from input devices, process them and convert results to HID reports for USB/BLE transmission. All input processors should implement the `InputProcessor` trait: | ||
|
||
```rust | ||
pub trait InputProcessor { | ||
/// Event type that the input processor receives. | ||
type EventType; | ||
|
||
/// Process the incoming events, convert them to HID report [`KeyboardReportMessage`], | ||
/// then send the report to the USB/BLE. | ||
/// | ||
/// Note there might be mulitple HID reports are generated for one event, | ||
/// so the "sending report" operation should be done in the `process` method. | ||
/// The input processor implementor should be aware of this. | ||
fn process(&mut self, event: Self::EventType) -> impl Future<Output = ()>; | ||
|
||
/// Get the input event channel for the input processor. | ||
/// | ||
/// The input processor receives events from this channel, processes the event, | ||
/// then sends to the report channel. | ||
fn get_event_channel( | ||
&self, | ||
) -> &Channel<CriticalSectionRawMutex, Self::EventType, EVENT_CHANNEL_SIZE>; | ||
|
||
/// Get the output report channel for the input processor. | ||
/// | ||
/// The input processor sends keyboard reports to this channel. | ||
fn get_report_channel( | ||
&self, | ||
) -> &Channel<CriticalSectionRawMutex, KeyboardReportMessage, REPORT_CHANNEL_SIZE> { | ||
&KEYBOARD_REPORT_CHANNEL | ||
} | ||
|
||
/// Default implementation of the input processor. It wait for a new event from the event channel, | ||
/// then process the event. | ||
/// | ||
/// The report is sent to the USB/BLE in the `process` method. | ||
fn run(&mut self) -> impl Future<Output = ()> { | ||
async { | ||
loop { | ||
let event = self.get_event_channel().receive().await; | ||
self.process(event).await; | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
The `process` method is responsible for processing input events and sending HID report to the report channel, which is available by `get_report_channel`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<mxfile host="65bd71144e"> | ||
<diagram id="KHJxcRW501o7JvBhlY4s" name="第 1 页"> | ||
<mxGraphModel dx="1137" dy="862" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0"> | ||
<root> | ||
<mxCell id="0"/> | ||
<mxCell id="1" parent="0"/> | ||
<mxCell id="6" value="" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> | ||
<mxGeometry x="80" y="220" width="180" height="450" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="2" value="key matrix" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#f8cecc;strokeColor=#b85450;shadow=0;" parent="1" vertex="1"> | ||
<mxGeometry x="110" y="260" width="120" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="3" value="encoders" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#f8cecc;strokeColor=#b85450;shadow=0;" parent="1" vertex="1"> | ||
<mxGeometry x="110" y="360" width="120" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="4" value="mice" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#f8cecc;strokeColor=#b85450;shadow=0;" parent="1" vertex="1"> | ||
<mxGeometry x="110" y="460" width="120" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="5" value="trackpads" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#f8cecc;strokeColor=#b85450;shadow=0;" parent="1" vertex="1"> | ||
<mxGeometry x="110" y="560" width="120" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="7" value="Input devices" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;shadow=0;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;" parent="1" vertex="1"> | ||
<mxGeometry x="105" y="180" width="130" height="30" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="9" value="" style="shape=flexArrow;endArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="6" target="10" edge="1"> | ||
<mxGeometry width="50" height="50" relative="1" as="geometry"> | ||
<mxPoint x="580" y="510" as="sourcePoint"/> | ||
<mxPoint x="440" y="445" as="targetPoint"/> | ||
</mxGeometry> | ||
</mxCell> | ||
<mxCell id="10" value="" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> | ||
<mxGeometry x="440" y="220" width="180" height="450" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="11" value="key processor" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#f8cecc;strokeColor=#b85450;shadow=0;" parent="1" vertex="1"> | ||
<mxGeometry x="470" y="260" width="120" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="12" value="encoder processor" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#f8cecc;strokeColor=#b85450;shadow=0;" parent="1" vertex="1"> | ||
<mxGeometry x="470" y="360" width="120" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="13" value="mice processor" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#f8cecc;strokeColor=#b85450;shadow=0;" parent="1" vertex="1"> | ||
<mxGeometry x="470" y="460" width="120" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="14" value="axis processor" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#f8cecc;strokeColor=#b85450;shadow=0;" parent="1" vertex="1"> | ||
<mxGeometry x="470" y="560" width="120" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="15" value="Input processor" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;shadow=0;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;" parent="1" vertex="1"> | ||
<mxGeometry x="465" y="180" width="130" height="30" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="16" value="Event" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;shadow=0;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;" parent="1" vertex="1"> | ||
<mxGeometry x="320" y="410" width="60" height="30" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="17" value="" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> | ||
<mxGeometry x="770" y="345" width="180" height="200" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="20" value="USB" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#f8cecc;strokeColor=#b85450;shadow=0;" parent="1" vertex="1"> | ||
<mxGeometry x="800" y="370" width="120" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="21" value="BLE" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;fillColor=#f8cecc;strokeColor=#b85450;shadow=0;" parent="1" vertex="1"> | ||
<mxGeometry x="800" y="460" width="120" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="22" value="Reporter" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;shadow=0;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;" parent="1" vertex="1"> | ||
<mxGeometry x="795" y="300" width="130" height="30" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="23" value="" style="shape=flexArrow;endArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" edge="1"> | ||
<mxGeometry width="50" height="50" relative="1" as="geometry"> | ||
<mxPoint x="620" y="444.5" as="sourcePoint"/> | ||
<mxPoint x="770" y="445" as="targetPoint"/> | ||
</mxGeometry> | ||
</mxCell> | ||
<mxCell id="25" value="" style="shape=flexArrow;endArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;" parent="1" target="28" edge="1"> | ||
<mxGeometry width="50" height="50" relative="1" as="geometry"> | ||
<mxPoint x="960" y="445" as="sourcePoint"/> | ||
<mxPoint x="1060" y="445" as="targetPoint"/> | ||
</mxGeometry> | ||
</mxCell> | ||
<mxCell id="27" value="host" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;shadow=0;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;" parent="1" vertex="1"> | ||
<mxGeometry x="1065" y="370" width="130" height="30" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="28" value="" style="fontColor=#0066CC;verticalAlign=top;verticalLabelPosition=bottom;labelPosition=center;align=center;html=1;outlineConnect=0;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.laptop;rounded=1;shadow=0;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;" parent="1" vertex="1"> | ||
<mxGeometry x="1080" y="417.5" width="100" height="55" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="29" value="Report" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;shadow=0;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=16;" parent="1" vertex="1"> | ||
<mxGeometry x="660" y="410" width="60" height="30" as="geometry"/> | ||
</mxCell> | ||
</root> | ||
</mxGraphModel> | ||
</diagram> | ||
</mxfile> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.