Compare commits
6 Commits
f8ee319fbe
...
189a8c463f
| Author | SHA1 | Date | |
|---|---|---|---|
| 189a8c463f | |||
|
ea01fe2071
|
|||
| 2b99a56a4a | |||
|
681c93f558
|
|||
| 0dc8e721b2 | |||
|
e49e5b1311
|
152
.github/workflows/ci.yml
vendored
Normal file
152
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
# .github/workflows/ci.yml
|
||||
|
||||
name: Rust CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
schedule:
|
||||
- cron: '0 0 * * 1' # Weekly Sunday midnight UTC
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-Dwarnings"
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.17.0
|
||||
with:
|
||||
target: thumbv6m-none-eabi
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "."
|
||||
|
||||
- name: Run cargo check
|
||||
run: cargo check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.17.0
|
||||
with:
|
||||
components: clippy
|
||||
target: thumbv6m-none-eabi
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "."
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy -- -D warnings
|
||||
|
||||
fmt:
|
||||
name: Format
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.17.0
|
||||
with:
|
||||
components: rustfmt
|
||||
target: thumbv6m-none-eabi
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check, clippy, fmt]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.17.0
|
||||
with:
|
||||
target: thumbv6m-none-eabi
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "."
|
||||
|
||||
- name: Build debug
|
||||
run: cargo build --verbose
|
||||
|
||||
- name: Build release
|
||||
run: cargo build --release --verbose
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: target/
|
||||
retention-days: 7
|
||||
|
||||
docs:
|
||||
name: Documentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.17.0
|
||||
with:
|
||||
components: rust-docs
|
||||
target: thumbv6m-none-eabi
|
||||
|
||||
- name: Build documentation
|
||||
run: cargo doc --no-deps --document-private-items
|
||||
|
||||
- name: Upload documentation
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: documentation
|
||||
path: target/thumbv6m-none-eabi/doc/
|
||||
retention-days: 7
|
||||
|
||||
pages:
|
||||
name: Deploy Docs
|
||||
runs-on: ubuntu-latest
|
||||
needs: [docs]
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v3
|
||||
|
||||
- name: Build documentation
|
||||
run: cargo doc --no-deps
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v2
|
||||
with:
|
||||
path: target/doc/
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::usb::KeyboardEvent;
|
||||
use alloc::vec::Vec;
|
||||
use embassy_rp::gpio::Input;
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::channel::Sender;
|
||||
@@ -21,6 +21,7 @@ pub struct Keyboard<'d, const KEY_N: usize> {
|
||||
num_keys: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, defmt::Format)]
|
||||
pub enum KeyboardError {
|
||||
MaxKeys,
|
||||
}
|
||||
@@ -45,7 +46,7 @@ impl<'d, const KEY_N: usize> Keyboard<'d, KEY_N> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn process<'ch, M, const N: usize>(mut self, sender: Sender<'ch, M, KeyboardEvent, N>)
|
||||
pub async fn process<'ch, M, const N: usize>(mut self, sender: Sender<'ch, M, Vec<char>, N>)
|
||||
where
|
||||
M: RawMutex,
|
||||
{
|
||||
@@ -56,14 +57,14 @@ impl<'d, const KEY_N: usize> Keyboard<'d, KEY_N> {
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.next()
|
||||
.await;
|
||||
|
||||
let mut pressed: Vec<char> = Vec::new();
|
||||
for key in self.keys.iter_mut().filter_map(|opt| opt.as_mut()) {
|
||||
sender
|
||||
.send(KeyboardEvent {
|
||||
key: key.value,
|
||||
action: key.button.get_level().into(),
|
||||
})
|
||||
.await;
|
||||
if key.button.is_low() {
|
||||
pressed.push(key.value);
|
||||
}
|
||||
}
|
||||
sender.send(pressed).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
56
src/main.rs
56
src/main.rs
@@ -5,8 +5,10 @@ mod heartbeat;
|
||||
mod keyboard;
|
||||
mod usb;
|
||||
|
||||
extern crate alloc;
|
||||
use crate::heartbeat::heartbeat;
|
||||
use crate::keyboard::Keyboard;
|
||||
use alloc::vec::Vec;
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_futures::join::join;
|
||||
@@ -37,7 +39,7 @@ async fn main(_spawner: Spawner) {
|
||||
);
|
||||
}
|
||||
|
||||
let keyboard_events: Channel<ThreadModeRawMutex, usb::KeyboardEvent, 64> = Channel::new();
|
||||
let keyboard_events: Channel<ThreadModeRawMutex, Vec<char>, 64> = Channel::new();
|
||||
|
||||
let p = embassy_rp::init(Default::default());
|
||||
|
||||
@@ -45,18 +47,54 @@ async fn main(_spawner: Spawner) {
|
||||
let driver = Driver::new(p.USB, Irqs);
|
||||
let mut usb_buf = usb::UsbKeyboardBuf::new();
|
||||
let usbkey = usb::UsbKeyboard::create_usb(&mut usb_buf, driver);
|
||||
let mut keyboard = Keyboard::<6>::new();
|
||||
|
||||
// Set up the signal pin that will be used to trigger the keyboard.
|
||||
let mut signal_pin = Input::new(p.PIN_16, Pull::None);
|
||||
|
||||
// Enable the schmitt trigger to slightly debounce.
|
||||
let mut signal_pin = Input::new(p.PIN_15, Pull::None);
|
||||
signal_pin.set_schmitt(true);
|
||||
|
||||
let mut keyboard = Keyboard::<1>::new();
|
||||
match keyboard.add_key(signal_pin, 'a') {
|
||||
Ok(()) => info!("Key 'a' registered"),
|
||||
Err(_) => {
|
||||
defmt::panic!("Failed to register key!");
|
||||
Err(e) => {
|
||||
defmt::panic!("Failed to register key! {:?}", e);
|
||||
}
|
||||
}
|
||||
let mut signal_pin = Input::new(p.PIN_16, Pull::None);
|
||||
signal_pin.set_schmitt(true);
|
||||
match keyboard.add_key(signal_pin, 's') {
|
||||
Ok(()) => info!("Key 's' registered"),
|
||||
Err(e) => {
|
||||
defmt::panic!("Failed to register key! {:?}", e);
|
||||
}
|
||||
}
|
||||
let mut signal_pin = Input::new(p.PIN_17, Pull::None);
|
||||
signal_pin.set_schmitt(true);
|
||||
match keyboard.add_key(signal_pin, 'x') {
|
||||
Ok(()) => info!("Key 'x' registered"),
|
||||
Err(e) => {
|
||||
defmt::panic!("Failed to register key! {:?}", e);
|
||||
}
|
||||
}
|
||||
let mut signal_pin = Input::new(p.PIN_18, Pull::None);
|
||||
signal_pin.set_schmitt(true);
|
||||
match keyboard.add_key(signal_pin, 'd') {
|
||||
Ok(()) => info!("Key 'd' registered"),
|
||||
Err(e) => {
|
||||
defmt::panic!("Failed to register key! {:?}", e);
|
||||
}
|
||||
}
|
||||
let mut signal_pin = Input::new(p.PIN_19, Pull::None);
|
||||
signal_pin.set_schmitt(true);
|
||||
match keyboard.add_key(signal_pin, 'c') {
|
||||
Ok(()) => info!("Key 'c' registered"),
|
||||
Err(e) => {
|
||||
defmt::panic!("Failed to register key! {:?}", e);
|
||||
}
|
||||
}
|
||||
let mut signal_pin = Input::new(p.PIN_20, Pull::None);
|
||||
signal_pin.set_schmitt(true);
|
||||
match keyboard.add_key(signal_pin, 'w') {
|
||||
Ok(()) => info!("Key 'w' registered"),
|
||||
Err(e) => {
|
||||
defmt::panic!("Failed to register key! {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
110
src/usb.rs
110
src/usb.rs
@@ -1,8 +1,8 @@
|
||||
use alloc::vec::Vec;
|
||||
use core::cell::OnceCell;
|
||||
use core::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
use defmt::*;
|
||||
use embassy_futures::join::join;
|
||||
use embassy_rp::gpio::Level;
|
||||
use embassy_rp::peripherals::USB;
|
||||
use embassy_rp::usb::Driver;
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
@@ -18,27 +18,6 @@ use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor};
|
||||
|
||||
pub static HID_PROTOCOL_MODE: AtomicU8 = AtomicU8::new(HidProtocolMode::Boot as u8);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum KeyboardAction {
|
||||
Press,
|
||||
Depress,
|
||||
}
|
||||
|
||||
impl From<Level> for KeyboardAction {
|
||||
fn from(value: Level) -> Self {
|
||||
match value {
|
||||
Level::Low => KeyboardAction::Depress,
|
||||
Level::High => KeyboardAction::Press,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct KeyboardEvent {
|
||||
pub key: char,
|
||||
pub action: KeyboardAction,
|
||||
}
|
||||
|
||||
pub struct UsbKeyboardBuf<'d> {
|
||||
// Create embassy-usb DeviceBuilder using the driver and config.
|
||||
// It needs some buffers for building the descriptors.
|
||||
@@ -128,7 +107,7 @@ impl<'d> UsbKeyboard<'d> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run<'ch, M, const N: usize>(self, receiver: Receiver<'ch, M, KeyboardEvent, N>)
|
||||
pub async fn run<'ch, M, const N: usize>(self, receiver: Receiver<'ch, M, Vec<char>, N>)
|
||||
where
|
||||
M: RawMutex,
|
||||
{
|
||||
@@ -148,58 +127,39 @@ impl<'d> UsbKeyboard<'d> {
|
||||
loop {
|
||||
let event = receiver.receive().await;
|
||||
|
||||
match event {
|
||||
KeyboardEvent {
|
||||
key: 'a',
|
||||
action: KeyboardAction::Press,
|
||||
} => {
|
||||
if HID_PROTOCOL_MODE.load(Ordering::Relaxed) == HidProtocolMode::Boot as u8
|
||||
{
|
||||
match writer.write(&[0, 0, 4, 0, 0, 0, 0, 0]).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => warn!("Failed to send boot report: {:?}", e),
|
||||
};
|
||||
} else {
|
||||
// Create a report with the A key pressed. (no shift modifier)
|
||||
let report = KeyboardReport {
|
||||
keycodes: [4, 0, 0, 0, 0, 0],
|
||||
leds: 0,
|
||||
modifier: 0,
|
||||
reserved: 0,
|
||||
};
|
||||
// Send the report.
|
||||
match writer.write_serialize(&report).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => warn!("Failed to send report: {:?}", e),
|
||||
};
|
||||
}
|
||||
}
|
||||
KeyboardEvent {
|
||||
key: 'a',
|
||||
action: KeyboardAction::Depress,
|
||||
} => {
|
||||
if HID_PROTOCOL_MODE.load(Ordering::Relaxed) == HidProtocolMode::Boot as u8
|
||||
{
|
||||
match writer.write(&[0, 0, 0, 0, 0, 0, 0, 0]).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => warn!("Failed to send boot report: {:?}", e),
|
||||
};
|
||||
} else {
|
||||
let report = KeyboardReport {
|
||||
keycodes: [0, 0, 0, 0, 0, 0],
|
||||
leds: 0,
|
||||
modifier: 0,
|
||||
reserved: 0,
|
||||
};
|
||||
match writer.write_serialize(&report).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => warn!("Failed to send report: {:?}", e),
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
defmt::panic!("Unexpected event!");
|
||||
}
|
||||
let mut boot_keys = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
for (&key, idx) in event.iter().zip(2usize..) {
|
||||
let boot_key = match key {
|
||||
'd' => 79,
|
||||
'a' => 80,
|
||||
's' => 81,
|
||||
'w' => 82,
|
||||
('a'..='z') => (key as u8) - b'a' + 4,
|
||||
_ => defmt::panic!("Not supported key: {}", key),
|
||||
};
|
||||
boot_keys[idx] = boot_key;
|
||||
}
|
||||
|
||||
if HID_PROTOCOL_MODE.load(Ordering::Relaxed) == HidProtocolMode::Boot as u8 {
|
||||
match writer.write(&boot_keys).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => warn!("Failed to send boot report: {:?}", e),
|
||||
};
|
||||
} else {
|
||||
defmt::unimplemented!("Only supports boot mode keyboards!");
|
||||
// // Create a report with the A key pressed. (no shift modifier)
|
||||
// let report = KeyboardReport {
|
||||
// keycodes: [4, 0, 0, 0, 0, 0],
|
||||
// leds: 0,
|
||||
// modifier: 0,
|
||||
// reserved: 0,
|
||||
// };
|
||||
// // Send the report.
|
||||
// match writer.write_serialize(&report).await {
|
||||
// Ok(()) => {}
|
||||
// Err(e) => warn!("Failed to send report: {:?}", e),
|
||||
// };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user