usb-hid: Simplify API.

- Renamed structs to HidReaderWriter, HidReader, HidWriter.
- Removed unused const generics on `State`.
- Simplified generics on `HidReaderWriter`.
  The class type previously was `HidClass<D, Driver<'d, USBD>, ReportReader<'d, Driver<'d, USBD>, OUT_N>, IN_N>`
  It's now `HidClass<D, Driver<'d, USBD>, IN_N, OUT_N>`. Note that the driver type `Driver<'d, USBD>` is no longer repeated.
- Constructors are now: `HidWriter::new()` for IN-only, `HidReaderWriter::new()` for IN+OUT. No complicated bounds.
- HidReaderWriter has all the methods from HidReader, HidWriter.
This commit is contained in:
Dario Nieuwenhuis 2022-04-16 01:59:40 +02:00
parent 94090e068e
commit c0de54a341
3 changed files with 160 additions and 134 deletions

View File

@ -60,12 +60,12 @@ impl ReportId {
} }
} }
pub struct State<'a, const IN_N: usize, const OUT_N: usize> { pub struct State<'d> {
control: MaybeUninit<Control<'a>>, control: MaybeUninit<Control<'d>>,
out_report_offset: AtomicUsize, out_report_offset: AtomicUsize,
} }
impl<'a, const IN_N: usize, const OUT_N: usize> State<'a, IN_N, OUT_N> { impl<'d> State<'d> {
pub fn new() -> Self { pub fn new() -> Self {
State { State {
control: MaybeUninit::uninit(), control: MaybeUninit::uninit(),
@ -74,107 +74,146 @@ impl<'a, const IN_N: usize, const OUT_N: usize> State<'a, IN_N, OUT_N> {
} }
} }
pub struct HidClass<'d, D: Driver<'d>, T, const IN_N: usize> { pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> {
input: ReportWriter<'d, D, IN_N>, reader: HidReader<'d, D, READ_N>,
output: T, writer: HidWriter<'d, D, WRITE_N>,
} }
impl<'d, D: Driver<'d>, const IN_N: usize> HidClass<'d, D, (), IN_N> { fn build<'d, D: Driver<'d>>(
/// Creates a new HidClass.
///
/// poll_ms configures how frequently the host should poll for reading/writing
/// HID reports. A lower value means better throughput & latency, at the expense
/// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for
/// high performance uses, and a value of 255 is good for best-effort usecases.
///
/// This allocates an IN endpoint only.
pub fn new<const OUT_N: usize>(
builder: &mut UsbDeviceBuilder<'d, D>, builder: &mut UsbDeviceBuilder<'d, D>,
state: &'d mut State<'d, IN_N, OUT_N>, state: &'d mut State<'d>,
report_descriptor: &'static [u8], report_descriptor: &'static [u8],
request_handler: Option<&'d dyn RequestHandler>, request_handler: Option<&'d dyn RequestHandler>,
poll_ms: u8, poll_ms: u8,
max_packet_size: u16, max_packet_size: u16,
) -> Self { with_out_endpoint: bool,
let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms); ) -> (Option<D::EndpointOut>, D::EndpointIn, &'d AtomicUsize) {
let control = state.control.write(Control::new( let control = state.control.write(Control::new(
report_descriptor, report_descriptor,
request_handler, request_handler,
&state.out_report_offset, &state.out_report_offset,
)); ));
control.build(builder, None, &ep_in);
Self { let len = report_descriptor.len();
input: ReportWriter { ep_in }, let if_num = builder.alloc_interface_with_handler(control);
output: (), let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms);
} let ep_out = if with_out_endpoint {
Some(builder.alloc_interrupt_endpoint_out(max_packet_size, poll_ms))
} else {
None
};
builder.config_descriptor.interface(
if_num,
USB_CLASS_HID,
USB_SUBCLASS_NONE,
USB_PROTOCOL_NONE,
);
// HID descriptor
builder.config_descriptor.write(
HID_DESC_DESCTYPE_HID,
&[
// HID Class spec version
HID_DESC_SPEC_1_10[0],
HID_DESC_SPEC_1_10[1],
// Country code not supported
HID_DESC_COUNTRY_UNSPEC,
// Number of following descriptors
1,
// We have a HID report descriptor the host should read
HID_DESC_DESCTYPE_HID_REPORT,
// HID report descriptor size,
(len & 0xFF) as u8,
(len >> 8 & 0xFF) as u8,
],
);
builder.config_descriptor.endpoint(ep_in.info());
if let Some(ep_out) = &ep_out {
builder.config_descriptor.endpoint(ep_out.info());
} }
(ep_out, ep_in, &state.out_report_offset)
} }
impl<'d, D: Driver<'d>, T, const IN_N: usize> HidClass<'d, D, T, IN_N> { impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize>
/// Gets the [`ReportWriter`] for input reports. HidReaderWriter<'d, D, READ_N, WRITE_N>
///
/// **Note:** If the `HidClass` was created with [`new_ep_out()`](Self::new_ep_out)
/// this writer will be useless as no endpoint is availabe to send reports.
pub fn input(&mut self) -> &mut ReportWriter<'d, D, IN_N> {
&mut self.input
}
}
impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize>
HidClass<'d, D, ReportReader<'d, D, OUT_N>, IN_N>
{ {
/// Creates a new HidClass. /// Creates a new HidReaderWriter.
///
/// This will allocate one IN and one OUT endpoints. If you only need writing (sending)
/// HID reports, consider using [`HidWriter::new`] instead, which allocates an IN endpoint only.
/// ///
/// poll_ms configures how frequently the host should poll for reading/writing /// poll_ms configures how frequently the host should poll for reading/writing
/// HID reports. A lower value means better throughput & latency, at the expense /// HID reports. A lower value means better throughput & latency, at the expense
/// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for
/// high performance uses, and a value of 255 is good for best-effort usecases. /// high performance uses, and a value of 255 is good for best-effort usecases.
/// pub fn new(
/// This allocates two endpoints (IN and OUT).
pub fn with_output_ep(
builder: &mut UsbDeviceBuilder<'d, D>, builder: &mut UsbDeviceBuilder<'d, D>,
state: &'d mut State<'d, IN_N, OUT_N>, state: &'d mut State<'d>,
report_descriptor: &'static [u8], report_descriptor: &'static [u8],
request_handler: Option<&'d dyn RequestHandler>, request_handler: Option<&'d dyn RequestHandler>,
poll_ms: u8, poll_ms: u8,
max_packet_size: u16, max_packet_size: u16,
) -> Self { ) -> Self {
let ep_out = builder.alloc_interrupt_endpoint_out(max_packet_size, poll_ms); let (ep_out, ep_in, offset) = build(
let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms); builder,
state,
let control = state.control.write(Control::new(
report_descriptor, report_descriptor,
request_handler, request_handler,
&state.out_report_offset, poll_ms,
)); max_packet_size,
control.build(builder, Some(&ep_out), &ep_in); true,
);
Self { Self {
input: ReportWriter { ep_in }, reader: HidReader {
output: ReportReader { ep_out: ep_out.unwrap(),
ep_out, offset,
offset: &state.out_report_offset,
}, },
writer: HidWriter { ep_in },
} }
} }
/// Gets the [`ReportReader`] for output reports. /// Splits into seperate readers/writers for input and output reports.
pub fn output(&mut self) -> &mut ReportReader<'d, D, OUT_N> { pub fn split(self) -> (HidReader<'d, D, READ_N>, HidWriter<'d, D, WRITE_N>) {
&mut self.output (self.reader, self.writer)
} }
/// Splits this `HidClass` into seperate readers/writers for input and output reports. /// Waits for both IN and OUT endpoints to be enabled.
pub fn split(self) -> (ReportWriter<'d, D, IN_N>, ReportReader<'d, D, OUT_N>) { pub async fn ready(&mut self) -> () {
(self.input, self.output) self.reader.ready().await;
self.writer.ready().await;
}
/// Writes an input report by serializing the given report structure.
#[cfg(feature = "usbd-hid")]
pub async fn write_serialize<IR: AsInputReport>(
&mut self,
r: &IR,
) -> Result<(), EndpointError> {
self.writer.write_serialize(r).await
}
/// Writes `report` to its interrupt endpoint.
pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> {
self.writer.write(report).await
}
/// Reads an output report from the Interrupt Out pipe.
///
/// See [`HidReader::read`].
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, ReadError> {
self.reader.read(buf).await
} }
} }
pub struct ReportWriter<'d, D: Driver<'d>, const N: usize> { pub struct HidWriter<'d, D: Driver<'d>, const N: usize> {
ep_in: D::EndpointIn, ep_in: D::EndpointIn,
} }
pub struct ReportReader<'d, D: Driver<'d>, const N: usize> { pub struct HidReader<'d, D: Driver<'d>, const N: usize> {
ep_out: D::EndpointOut, ep_out: D::EndpointOut,
offset: &'d AtomicUsize, offset: &'d AtomicUsize,
} }
@ -197,17 +236,50 @@ impl From<embassy_usb::driver::EndpointError> for ReadError {
} }
} }
impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { impl<'d, D: Driver<'d>, const N: usize> HidWriter<'d, D, N> {
/// Creates a new HidWriter.
///
/// This will allocate one IN endpoint only, so the host won't be able to send
/// reports to us. If you need that, consider using [`HidReaderWriter::new`] instead.
///
/// poll_ms configures how frequently the host should poll for reading/writing
/// HID reports. A lower value means better throughput & latency, at the expense
/// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for
/// high performance uses, and a value of 255 is good for best-effort usecases.
pub fn new(
builder: &mut UsbDeviceBuilder<'d, D>,
state: &'d mut State<'d>,
report_descriptor: &'static [u8],
request_handler: Option<&'d dyn RequestHandler>,
poll_ms: u8,
max_packet_size: u16,
) -> Self {
let (ep_out, ep_in, _offset) = build(
builder,
state,
report_descriptor,
request_handler,
poll_ms,
max_packet_size,
false,
);
assert!(ep_out.is_none());
Self { ep_in }
}
/// Waits for the interrupt in endpoint to be enabled. /// Waits for the interrupt in endpoint to be enabled.
pub async fn ready(&mut self) -> () { pub async fn ready(&mut self) -> () {
self.ep_in.wait_enabled().await self.ep_in.wait_enabled().await
} }
/// Tries to write an input report by serializing the given report structure. /// Writes an input report by serializing the given report structure.
///
/// Panics if no endpoint is available.
#[cfg(feature = "usbd-hid")] #[cfg(feature = "usbd-hid")]
pub async fn serialize<IR: AsInputReport>(&mut self, r: &IR) -> Result<(), EndpointError> { pub async fn write_serialize<IR: AsInputReport>(
&mut self,
r: &IR,
) -> Result<(), EndpointError> {
let mut buf: [u8; N] = [0; N]; let mut buf: [u8; N] = [0; N];
let size = match serialize(&mut buf, r) { let size = match serialize(&mut buf, r) {
Ok(size) => size, Ok(size) => size,
@ -217,8 +289,6 @@ impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> {
} }
/// Writes `report` to its interrupt endpoint. /// Writes `report` to its interrupt endpoint.
///
/// Panics if no endpoint is available.
pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> {
assert!(report.len() <= N); assert!(report.len() <= N);
@ -236,16 +306,13 @@ impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> {
} }
} }
impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> { impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> {
/// Waits for the interrupt out endpoint to be enabled. /// Waits for the interrupt out endpoint to be enabled.
pub async fn ready(&mut self) -> () { pub async fn ready(&mut self) -> () {
self.ep_out.wait_enabled().await self.ep_out.wait_enabled().await
} }
/// Starts a task to deliver output reports from the Interrupt Out pipe to /// Delivers output reports from the Interrupt Out pipe to `handler`.
/// `handler`.
///
/// Terminates when the interface becomes disabled.
/// ///
/// If `use_report_ids` is true, the first byte of the report will be used as /// If `use_report_ids` is true, the first byte of the report will be used as
/// the `ReportId` value. Otherwise the `ReportId` value will be 0. /// the `ReportId` value. Otherwise the `ReportId` value will be 0.
@ -391,47 +458,6 @@ impl<'a> Control<'a> {
], ],
} }
} }
fn build<'d, D: Driver<'d>>(
&'d mut self,
builder: &mut UsbDeviceBuilder<'d, D>,
ep_out: Option<&D::EndpointOut>,
ep_in: &D::EndpointIn,
) {
let len = self.report_descriptor.len();
let if_num = builder.alloc_interface_with_handler(self);
builder.config_descriptor.interface(
if_num,
USB_CLASS_HID,
USB_SUBCLASS_NONE,
USB_PROTOCOL_NONE,
);
// HID descriptor
builder.config_descriptor.write(
HID_DESC_DESCTYPE_HID,
&[
// HID Class spec version
HID_DESC_SPEC_1_10[0],
HID_DESC_SPEC_1_10[1],
// Country code not supported
HID_DESC_COUNTRY_UNSPEC,
// Number of following descriptors
1,
// We have a HID report descriptor the host should read
HID_DESC_DESCTYPE_HID_REPORT,
// HID report descriptor size,
(len & 0xFF) as u8,
(len >> 8 & 0xFF) as u8,
],
);
builder.config_descriptor.endpoint(ep_in.info());
if let Some(ep) = ep_out {
builder.config_descriptor.endpoint(ep.info());
}
}
} }
impl<'d> ControlHandler for Control<'d> { impl<'d> ControlHandler for Control<'d> {

View File

@ -18,7 +18,7 @@ use embassy_nrf::usb::Driver;
use embassy_nrf::Peripherals; use embassy_nrf::Peripherals;
use embassy_usb::control::OutResponse; use embassy_usb::control::OutResponse;
use embassy_usb::{Config, DeviceStateHandler, UsbDeviceBuilder}; use embassy_usb::{Config, DeviceStateHandler, UsbDeviceBuilder};
use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; use embassy_usb_hid::{HidReaderWriter, ReportId, RequestHandler, State};
use futures::future::join; use futures::future::join;
use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor};
@ -75,7 +75,7 @@ async fn main(_spawner: Spawner, p: Peripherals) {
let request_handler = MyRequestHandler {}; let request_handler = MyRequestHandler {};
let device_state_handler = MyDeviceStateHandler::new(); let device_state_handler = MyDeviceStateHandler::new();
let mut state = State::<8, 1>::new(); let mut state = State::new();
let mut builder = UsbDeviceBuilder::new( let mut builder = UsbDeviceBuilder::new(
driver, driver,
@ -88,7 +88,7 @@ async fn main(_spawner: Spawner, p: Peripherals) {
); );
// Create classes on the builder. // Create classes on the builder.
let hid = HidClass::with_output_ep( let hid = HidReaderWriter::<_, 1, 8>::new(
&mut builder, &mut builder,
&mut state, &mut state,
KeyboardReport::desc(), KeyboardReport::desc(),
@ -135,7 +135,7 @@ async fn main(_spawner: Spawner, p: Peripherals) {
let mut button = Input::new(p.P0_11.degrade(), Pull::Up); let mut button = Input::new(p.P0_11.degrade(), Pull::Up);
let (mut hid_in, hid_out) = hid.split(); let (reader, mut writer) = hid.split();
// Do stuff with the class! // Do stuff with the class!
let in_fut = async { let in_fut = async {
@ -153,7 +153,7 @@ async fn main(_spawner: Spawner, p: Peripherals) {
modifier: 0, modifier: 0,
reserved: 0, reserved: 0,
}; };
match hid_in.serialize(&report).await { match writer.write_serialize(&report).await {
Ok(()) => {} Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e), Err(e) => warn!("Failed to send report: {:?}", e),
}; };
@ -167,7 +167,7 @@ async fn main(_spawner: Spawner, p: Peripherals) {
modifier: 0, modifier: 0,
reserved: 0, reserved: 0,
}; };
match hid_in.serialize(&report).await { match writer.write_serialize(&report).await {
Ok(()) => {} Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e), Err(e) => warn!("Failed to send report: {:?}", e),
}; };
@ -175,7 +175,7 @@ async fn main(_spawner: Spawner, p: Peripherals) {
}; };
let out_fut = async { let out_fut = async {
hid_out.run(false, &request_handler).await; reader.run(false, &request_handler).await;
}; };
let power_irq = interrupt::take!(POWER_CLOCK); let power_irq = interrupt::take!(POWER_CLOCK);

View File

@ -13,7 +13,7 @@ use embassy_nrf::usb::Driver;
use embassy_nrf::Peripherals; use embassy_nrf::Peripherals;
use embassy_usb::control::OutResponse; use embassy_usb::control::OutResponse;
use embassy_usb::{Config, UsbDeviceBuilder}; use embassy_usb::{Config, UsbDeviceBuilder};
use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; use embassy_usb_hid::{HidWriter, ReportId, RequestHandler, State};
use futures::future::join; use futures::future::join;
use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
@ -52,7 +52,7 @@ async fn main(_spawner: Spawner, p: Peripherals) {
let mut control_buf = [0; 16]; let mut control_buf = [0; 16];
let request_handler = MyRequestHandler {}; let request_handler = MyRequestHandler {};
let mut control = State::<5, 0>::new(); let mut control = State::new();
let mut builder = UsbDeviceBuilder::new( let mut builder = UsbDeviceBuilder::new(
driver, driver,
@ -65,7 +65,7 @@ async fn main(_spawner: Spawner, p: Peripherals) {
); );
// Create classes on the builder. // Create classes on the builder.
let mut hid = HidClass::new( let mut writer = HidWriter::<_, 5>::new(
&mut builder, &mut builder,
&mut control, &mut control,
MouseReport::desc(), MouseReport::desc(),
@ -94,7 +94,7 @@ async fn main(_spawner: Spawner, p: Peripherals) {
wheel: 0, wheel: 0,
pan: 0, pan: 0,
}; };
match hid.input().serialize(&report).await { match writer.write_serialize(&report).await {
Ok(()) => {} Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e), Err(e) => warn!("Failed to send report: {:?}", e),
} }