-
Notifications
You must be signed in to change notification settings - Fork 78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a dummy usb bus implementation #152
Add a dummy usb bus implementation #152
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I understand the utility but this crate has a whole bunch of traits and I am slightly worried about potentially adding a lot of dummy implementations.
Do we have many examples where this code would need to be duplicated?
Without a dummy implementation, a real HAL is needed to compile the code. So you can't just setup an example in your class crate that is checked for syntax correctness. A good example is the current A working version of that example using the dummy implementation could be like this: use usb_device::{
bus::UsbBusAllocator,
device::{UsbDeviceBuilder, UsbVidPid},
dummy_bus::DummyUsbBus,
prelude::*,
};
use usbd_serial::{SerialPort, USB_CLASS_CDC};
fn main() {
let usb_bus = UsbBusAllocator::new(DummyUsbBus::new());
let mut serial = SerialPort::new(&usb_bus);
let mut control_buffer = [0; 256];
let string_descriptors = [StringDescriptors::new(LangID::EN_US).product("Serial port")];
let mut usb_dev =
UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd), &mut control_buffer)
.strings(&string_descriptors)
.unwrap()
.device_class(USB_CLASS_CDC)
.build()
.unwrap();
loop {
if !usb_dev.poll(&mut [&mut serial]) {
continue;
}
let mut buf = [0u8; 64];
match serial.read(&mut buf[..]) {
Ok(count) => {
// count bytes were read to &buf[..count]
}
Err(UsbError::WouldBlock) => todo!(), // No data received
Err(err) => todo!(), // An error occurred
};
match serial.write(&[0x3a, 0x29]) {
Ok(count) => {
// count bytes were written
}
Err(UsbError::WouldBlock) => todo!(), // No data could be written (buffers full)
Err(err) => todo!(), // An error occurred
};
}
} |
Yes, I understood the motivation. My question is if it is worth providing dummy implementations inside this crate, instead of adding it in-place if we have a very limited number of examples (for the documentation, this dummy implementation could be added hidden so that it is actually compiled but not shown, like we do over at embedded-hal) use usb_device::{
bus::UsbBusAllocator,
device::{UsbDeviceBuilder, UsbVidPid},
dummy_bus::DummyUsbBus,
prelude::*,
};
use usbd_serial::{SerialPort, USB_CLASS_CDC};
fn main() {
let usb_bus = UsbBusAllocator::new(DummyUsbBus::new());
let mut serial = SerialPort::new(&usb_bus);
let mut control_buffer = [0; 256];
let string_descriptors = [StringDescriptors::new(LangID::EN_US).product("Serial port")];
let mut usb_dev =
UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd), &mut control_buffer)
.strings(&string_descriptors)
.unwrap()
.device_class(USB_CLASS_CDC)
.build()
.unwrap();
loop {
if !usb_dev.poll(&mut [&mut serial]) {
continue;
}
let mut buf = [0u8; 64];
match serial.read(&mut buf[..]) {
Ok(count) => {
// count bytes were read to &buf[..count]
}
Err(UsbError::WouldBlock) => todo!(), // No data received
Err(err) => todo!(), // An error occurred
};
match serial.write(&[0x3a, 0x29]) {
Ok(count) => {
// count bytes were written
}
Err(UsbError::WouldBlock) => todo!(), // No data could be written (buffers full)
Err(err) => todo!(), // An error occurred
};
}
}
// In the docs we can prepend all this with # to hide it
struct DummyUsbBus;
impl DummyUsbBus {
fn new() -> Self {
Self
}
}
impl UsbBus for DummyUsbBus {
fn alloc_ep(
&mut self,
ep_dir: crate::UsbDirection,
ep_addr: Option<crate::class_prelude::EndpointAddress>,
ep_type: crate::class_prelude::EndpointType,
max_packet_size: u16,
interval: u8,
) -> crate::Result<crate::class_prelude::EndpointAddress> { }
fn enable(&mut self) { }
fn force_reset(&self) -> crate::Result<()> { }
fn is_stalled(&self, ep_addr: crate::class_prelude::EndpointAddress) -> bool { }
fn poll(&self) -> crate::bus::PollResult { }
fn read(
&self,
ep_addr: crate::class_prelude::EndpointAddress,
buf: &mut [u8],
) -> crate::Result<usize> { }
fn reset(&self) { }
fn resume(&self) { }
fn set_device_address(&self, addr: u8) { }
} |
I understand that this dummy implementation feels a bit strange, but I have no better idea how to make testing easy for class crate authors. Rust in general motivates people to write examples that actually build, so this is just a convinient way to support this. I see it a bit similar to the test class that already exists in the crate. The alternatives are:
|
Hmm, I understand the situation better now, thanks. Doing away with this by providing a dummy implementation first-party is certainly alluring from an engineering perspective. Conversely, the examples will compile but they need to be translated to a real HAL impl, which is a non-trivial burden. If my recollection of the situation is correct, I am inclined to accept this (although maybe rename the module just |
My only request is that we gate the dummy implementation behind a feature to prevent people from using it in actual projects (or at least hide it). In the past, I've maintained these as separate crates (i.e. |
A good idea in general, but I'm not sure how the class crate authors should use it i.e. whether
|
One problem with features in general is though that |
Having a dummy bus would have been useful for one documentation example I had to write, but it's not a huge trait, and basically IDE does it automatically anyway (link). If would be significantly more useful if there were a lot of examples needing the bus, though. |
This PR adds
DummyUsbBus
which doesn't implement any functionality but can be used when writing examples (e.g. on class implementations) to ensure they compile. Note that any code using it cannot be run though.