From 8cc242f7c5ce3175bada26c82058a41943c7c62c Mon Sep 17 00:00:00 2001 From: Kezi Date: Mon, 9 Sep 2024 02:37:41 +0200 Subject: [PATCH] [software] add usb HID, implement samsung tv remote protocol for arrow keys, emulate them as a keyboard --- antani_sw/Cargo.lock | 19 ++---------- antani_sw/Cargo.toml | 5 ++-- antani_sw/src/main.rs | 66 +++++++++++++++++++++++++++++++++++++----- antani_sw/src/usb.rs | 67 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 124 insertions(+), 33 deletions(-) diff --git a/antani_sw/Cargo.lock b/antani_sw/Cargo.lock index 636b3c7..6c2afe3 100644 --- a/antani_sw/Cargo.lock +++ b/antani_sw/Cargo.lock @@ -62,6 +62,7 @@ dependencies = [ "smart-leds", "smart-leds-trait 0.2.1", "static_cell", + "usbd-hid", "ws2812-pio", ] @@ -1109,7 +1110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4047d9235d1423d66cc97da7d07eddb54d4f154d6c13805c6d0793956f4f25b0" dependencies = [ "cortex-m", - "rtt-target", + "defmt", ] [[package]] @@ -1418,16 +1419,6 @@ dependencies = [ "vcell", ] -[[package]] -name = "rtt-target" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b34c9e6832388e45f3c01f1bb60a016384a0a4ad80cdd7d34913bed25037f0" -dependencies = [ - "critical-section", - "ufmt-write", -] - [[package]] name = "rustc_version" version = "0.2.3" @@ -1650,12 +1641,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ufmt-write" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" - [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/antani_sw/Cargo.toml b/antani_sw/Cargo.toml index ed213d9..d250d92 100644 --- a/antani_sw/Cargo.toml +++ b/antani_sw/Cargo.toml @@ -14,7 +14,7 @@ embassy-sync = { version = "0.6.0", git = "https://github.com/embassy-rs/embassy embassy-executor = { version = "0.6.0", git = "https://github.com/embassy-rs/embassy.git", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", git = "https://github.com/embassy-rs/embassy.git", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.2.0", git = "https://github.com/embassy-rs/embassy.git", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } -embassy-usb = { version = "0.3.0", git = "https://github.com/embassy-rs/embassy.git", features = ["defmt","max-interface-count-6"] } +embassy-usb = { version = "0.3.0", git = "https://github.com/embassy-rs/embassy.git", features = ["defmt","max-interface-count-8"] } embassy-futures = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy.git" } embassy-usb-logger = { version = "0.2.0", git = "https://github.com/embassy-rs/embassy.git" } @@ -26,7 +26,7 @@ fixed-macro = "1.2" cortex-m = { version = "0.7.7", features = ["inline-asm"] } cortex-m-rt = "0.7.3" -panic-probe = { version = "0.3", features = ["print-rtt"] } +panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } heapless = "0.8" @@ -59,6 +59,7 @@ infrared = "0.14.2" num-traits = { version = "0.2", default-features = false, features = ["libm"] } capnp = { version = "0.19.6", default-features = false } +usbd-hid = "0.8.2" # cargo build/run diff --git a/antani_sw/src/main.rs b/antani_sw/src/main.rs index dcfff51..2d556fb 100644 --- a/antani_sw/src/main.rs +++ b/antani_sw/src/main.rs @@ -211,6 +211,7 @@ enum TaskCommand { SetBrightness(OutputPower), ResetTime, UsbActivity, + SendHidKeyboard(usbd_hid::descriptor::KeyboardUsage), Error, None, } @@ -257,9 +258,9 @@ impl OutputPower { } } -enum WhiteLedCommand{ +enum WhiteLedCommand { Communication, - Error + Error, } static WHITE_LED_SIGNAL: Signal = Signal::new(); @@ -317,11 +318,13 @@ fn main() -> ! { executor0.run(|spawner| { unwrap!(spawner.spawn(temperature(adc, ts, MEGA_CHANNEL.publisher().unwrap()))); - unwrap!(spawner.spawn(usb::usb_main(p.USB, MEGA_CHANNEL.publisher().unwrap()))); - unwrap!(spawner.spawn(button_tsk(user_btn, MEGA_CHANNEL.publisher().unwrap()))); - unwrap!(spawner.spawn(white_led_task( - white_led + unwrap!(spawner.spawn(usb::usb_main( + p.USB, + MEGA_CHANNEL.publisher().unwrap(), + MEGA_CHANNEL.subscriber().unwrap() ))); + unwrap!(spawner.spawn(button_tsk(user_btn, MEGA_CHANNEL.publisher().unwrap()))); + unwrap!(spawner.spawn(white_led_task(white_led))); unwrap!(spawner.spawn(ir_receiver( p.PIN_10.pin(), MEGA_CHANNEL.publisher().unwrap() @@ -460,6 +463,55 @@ async fn main_tsk(mut ws2812: Ws2812<'static, PIO0, 0, 9>, scenes: &'static Scen .await; } + // samsung tv remote, arrow right + (7, 98, false) => { + mega_publisher + .publish(TaskCommand::SendHidKeyboard( + usbd_hid::descriptor::KeyboardUsage::KeyboardRightArrow, + )) + .await; + } + // left + (7, 101, false) => { + mega_publisher + .publish(TaskCommand::SendHidKeyboard( + usbd_hid::descriptor::KeyboardUsage::KeyboardLeftArrow, + )) + .await; + } + // up + (7, 96, false) => { + mega_publisher + .publish(TaskCommand::SendHidKeyboard( + usbd_hid::descriptor::KeyboardUsage::KeyboardUpArrow, + )) + .await; + } + // down + (7, 97, false) => { + mega_publisher + .publish(TaskCommand::SendHidKeyboard( + usbd_hid::descriptor::KeyboardUsage::KeyboardDownArrow, + )) + .await; + } + // exit + (7, 102, false) => { + mega_publisher + .publish(TaskCommand::SendHidKeyboard( + usbd_hid::descriptor::KeyboardUsage::KeyboardEscape, + )) + .await; + } + // enter + (7, 104, false) => { + mega_publisher + .publish(TaskCommand::SendHidKeyboard( + usbd_hid::descriptor::KeyboardUsage::KeyboardEnter, + )) + .await; + } + _ => {} } WHITE_LED_SIGNAL.signal(WhiteLedCommand::Communication); @@ -552,7 +604,7 @@ async fn main_tsk(mut ws2812: Ws2812<'static, PIO0, 0, 9>, scenes: &'static Scen WHITE_LED_SIGNAL.signal(WhiteLedCommand::Error); } - TaskCommand::None => {} + TaskCommand::None | TaskCommand::SendHidKeyboard(_) => {} } } diff --git a/antani_sw/src/usb.rs b/antani_sw/src/usb.rs index 71c0b50..636bb59 100644 --- a/antani_sw/src/usb.rs +++ b/antani_sw/src/usb.rs @@ -3,12 +3,15 @@ use embassy_futures::join::join; use embassy_rp::bind_interrupts; use embassy_rp::peripherals::USB; use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_time::{Duration, Timer}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::class::hid::{self, HidWriter}; use heapless::Vec; use log::{error, info}; use static_cell::StaticCell; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; -use crate::MegaPublisher; +use crate::{MegaPublisher, MegaSubscriber, TaskCommand}; use embassy_usb::class::midi::MidiClass; use embassy_usb::driver::EndpointError; use embassy_usb::{Builder, Config}; @@ -21,12 +24,13 @@ bind_interrupts!(struct Irqs { static STATE: StaticCell = StaticCell::new(); static LOGGER_STATE: StaticCell = StaticCell::new(); -static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); +static HID_STATE: StaticCell = StaticCell::new(); +static CONFIG_DESCRIPTOR: StaticCell<[u8; 512]> = StaticCell::new(); static BOS_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new(); #[embassy_executor::task] -pub async fn usb_main(usb: USB, publisher: MegaPublisher) { +pub async fn usb_main(usb: USB, publisher: MegaPublisher, mut subscriber: MegaSubscriber) { // Create the driver, from the HAL. let driver = Driver::new(usb, Irqs); @@ -45,7 +49,7 @@ pub async fn usb_main(usb: USB, publisher: MegaPublisher) { config.device_protocol = 0x01; config.composite_with_iads = true; - let config_descriptor = CONFIG_DESCRIPTOR.init([0; 256]); + let config_descriptor = CONFIG_DESCRIPTOR.init([0; 512]); let bos_descriptor = BOS_DESCRIPTOR.init([0; 256]); let control_buf = CONTROL_BUF.init([0; 64]); @@ -62,6 +66,15 @@ pub async fn usb_main(usb: USB, publisher: MegaPublisher) { let state = STATE.init(State::new()); let logger_state = LOGGER_STATE.init(State::new()); + let hid_state = HID_STATE.init(hid::State::new()); + + let config = embassy_usb::class::hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 64, + }; + let mut hid_writer = HidWriter::<_, 8>::new(&mut builder, hid_state, config); let mut cdc_class = CdcAcmClass::new(&mut builder, state, 64); let logger_class = CdcAcmClass::new(&mut builder, logger_state, 64); @@ -81,6 +94,42 @@ pub async fn usb_main(usb: USB, publisher: MegaPublisher) { let usb_fut = usb.run(); + let hid_fut = async { + loop { + if let TaskCommand::SendHidKeyboard(cmd) = subscriber.next_message_pure().await { + let report = KeyboardReport { + keycodes: [cmd as u8, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + // Send the report. + match hid_writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => { + warn!("Failed to send report: {:?}", e); + publisher.publish(TaskCommand::Error).await; + } + }; + Timer::after(Duration::from_millis(100)).await; + + let report = KeyboardReport { + keycodes: [0, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match hid_writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => { + warn!("Failed to send report: {:?}", e); + publisher.publish(TaskCommand::Error).await; + } + }; + } + } + }; + let midi_fut = async { loop { midi_class.wait_connection().await; @@ -93,13 +142,17 @@ pub async fn usb_main(usb: USB, publisher: MegaPublisher) { let control_fut = async { loop { cdc_class.wait_connection().await; - log::info!("Connected"); + info!("Connected"); let _ = usb_control(&mut cdc_class, &publisher).await; - log::info!("Disconnected"); + info!("Disconnected"); } }; - join(usb_fut, join(control_fut, join(log_fut, midi_fut))).await; + join( + usb_fut, + join(control_fut, join(log_fut, join(hid_fut, midi_fut))), + ) + .await; } struct Disconnected {}