7个版本
0.1.6 | 2023年9月7日 |
---|---|
0.1.5 | 2023年6月5日 |
0.1.4 | 2022年11月29日 |
0.1.2 | 2022年6月12日 |
0.1.0 | 2021年11月12日 |
#2081 在 嵌入式开发
38 每月下载次数
用于 6 个crate(直接使用3个)
175KB
3.5K SLoC
hid-io协议
HID-IO服务器和设备协议实现
此库可以使用device
和server
功能标志集成到嵌入式和完整用户空间应用程序中。
hid-io协议库处理以下功能
- 缓冲区分包和重组
- 所有不同的数据包类型(数据、ACK、NAK、无ACK数据、同步以及继续的变体)
- HID-IO命令处理(发送和接收)
- 16位和32位命令ID
规范
API文档
请参阅docs.rs。
构建
服务器库
cargo build
cargo build --release
设备库
cargo build --no-default-features --features device
cargo build --no-default-features --features device --release
使用方法
可以使用两种不同的方式将hid-io-protocol作为服务器库使用。
- 接收字节,组装缓冲区,处理消息,创建响应缓冲区,序列化缓冲区,发送字节
- 接收消息缓冲区,处理消息,创建响应缓冲区,发送缓冲区
在每种方式中,都会创建一个CommandInterface结构,并实现Commands trait。CommandInterface的实现是两种选项之间的区别。
选项1更简单,因为hid-io-protocol可以处理从hidraw接口的所有处理。设备库通常选择这种方式。
const BufChunk: usize = U64;
const IdLen: usize = U10;
const MessageLen: usize = U256;
const RxBuf: usize = U8;
const SerializationLen: usize = U276;
const TxBuf: usize = U8;
let ids = [
HidIoCommandID::SupportedIDs,
/* Add supported ids */
/* This is the master list, if it's not listed here comamnds will not work */
];
let intf = CommandInterface::<TxBuf, RxBuf, BufChunk, MessageLen, {MessageLen - 1}, {MessageLen - 4}, SerializationLen, IdLen>::new(&ids).unwrap();
}
// The max length must equal BufChunk (e.g. 64 bytes)
// This may not be 64 bytes depending on your use-case and situation
// 63 bytes is common when you need to use a hid report id
let hidraw_buffer = read_hidraw();
// Enqueue bytes into buffer
intf.rx_bytebuf.enqueue(match Vec::from_slice(slice) {
Ok(vec) => vec,
Err(_) => {
return HidioStatus::ErrorBufSizeTooSmall;
}
}).unwrap();
// Process messages
// If any responses are created, they'll be sent out with intf.tx_bytebuf
intf.process_rx();
// Copy a single chunk from the tx_buffer
// You'll likely want to do this repeatedly until the buffer is empty
match intf.tx_bytebuf.dequeue() {
Some(chunk) => {
// Write to hidraw output buffer
// Same size restrictions apply as above
write_hidraw(chunk);
}
None => {}
}
struct CommandInterface<
const TX: usize,
const RX: usize,
const N: usize,
const H: usize,
const HSUB1: usize,
const HSUB4: usize,
const S: usize,
const ID: usize,
> {
ids: Vec<HidIoCommandID, ID>,
rx_bytebuf: buffer::Buffer<RX, N>,
rx_packetbuf: HidIoPacketBuffer<H>,
tx_bytebuf: buffer::Buffer<TX, N>,
serial_buf: Vec<u8, S>,
}
impl<
const TX: usize,
const RX: usize,
const N: usize,
const H: usize,
const HSUB1: usize,
const HSUB4: usize,
const S: usize,
const ID: usize,
> CommandInterface<TX, RX, N, H, HSUB1, HSUB4, S, ID>
{
fn new(
ids: &[HidIoCommandID],
) -> Result<CommandInterface<TX, RX, N, H, HSUB1, HSUB4, S, ID>, CommandError> {
// Make sure we have a large enough id vec
let ids = match Vec::from_slice(ids) {
Ok(ids) => ids,
Err(_) => {
return Err(CommandError::IdVecTooSmall);
}
};
let tx_bytebuf = buffer::Buffer::new();
let rx_bytebuf = buffer::Buffer::new();
let rx_packetbuf = HidIoPacketBuffer::new();
let serial_buf = Vec::new();
Ok(CommandInterface {
ids,
rx_bytebuf,
rx_packetbuf,
tx_bytebuf,
serial_buf,
})
}
/// Decode rx_bytebuf into a HidIoPacketBuffer
/// Returns true if buffer ready, false if not
fn rx_packetbuffer_decode(&mut self) -> Result<bool, CommandError> {
loop {
// Retrieve vec chunk
if let Some(buf) = self.rx_bytebuf.dequeue() {
// Decode chunk
match self.rx_packetbuf.decode_packet(&buf) {
Ok(_recv) => {
// Only handle buffer if ready
if self.rx_packetbuf.done {
// Handle sync packet type
match self.rx_packetbuf.ptype {
HidIoPacketType::Sync => {
// Clear buffer, packet missing
self.rx_packetbuf.clear();
}
_ => {
return Ok(true);
}
}
}
}
Err(e) => {
return Err(CommandError::PacketDecodeError(e));
}
}
} else {
return Ok(false);
}
}
}
/// Process rx buffer until empty
/// Handles flushing tx->rx, decoding, then processing buffers
/// Returns the number of buffers processed
pub fn process_rx(&mut self) -> Result<u8, CommandError> {
// Decode bytes into buffer
while (self.rx_packetbuffer_decode()? {
// Process rx buffer
self.rx_message_handling(self.rx_packetbuf.clone())?;
// Clear buffer
self.rx_packetbuf.clear();
}
Ok(cur)
}
}
/// CommandInterface for Commands
/// TX - tx byte buffer size (in multiples of N)
/// RX - tx byte buffer size (in multiples of N)
/// N - Max payload length (HidIoPacketBuffer), used for default values
/// H - Max data payload length (HidIoPacketBuffer)
/// S - Serialization buffer size
/// ID - Max number of HidIoCommandIDs
impl<
const TX: usize,
const RX: usize,
const N: usize,
const H: usize,
const HSUB1: usize,
const HSUB4: usize,
const S: usize,
const ID: usize,
> Commands<H, ID> for CommandInterface<TX, RX, N, H, HSUB1, HSUB4, S, ID> {
fn default_packet_chunk(&self) -> u32 {
N as u32
}
fn tx_packetbuffer_send(&mut self, buf: &mut HidIoPacketBuffer<H>) -> Result<(), CommandError> {
let size = buf.serialized_len() as usize;
if self.serial_buf.resize_default(size).is_err() {
return Err(CommandError::SerializationVecTooSmall);
}
match buf.serialize_buffer(&mut self.serial_buf) {
Ok(data) => data,
Err(err) => {
return Err(CommandError::SerializationFailed(err));
}
};
// Add serialized data to buffer
// May need to enqueue multiple packets depending how much
// was serialized
// The first byte is a serde type and is dropped
let data = &self.serial_buf;
for pos in (1..data.len()).step_by(N) {
let len = core::cmp::min(N, data.len() - pos);
match self
.tx_bytebuf
.enqueue(match Vec::from_slice(&data[pos..len + pos]) {
Ok(vec) => vec,
Err(_) => {
return Err(CommandError::TxBufferVecTooSmall);
}
}) {
Ok(_) => {}
Err(_) => {
return Err(CommandError::TxBufferSendFailed);
}
}
}
Ok(())
}
fn supported_id(&self, id: HidIoCommandID) -> bool {
/* Your implementation */
}
fn h0000_supported_ids_cmd(&mut self, _data: h0000::Cmd) -> Result<h0000::Ack<ID>, h0000::Nak> {
/* Message specific commands are optional to implement */
}
}
hid-io-core使用选项2,因为不同的线程处理字节接收和消息处理。您需要自己处理缓冲区的组装/解组装。
struct CommandInterface {}
impl Commands<mailbox::HidIoPacketBufferDataSize, 0> for CommandInterface {
fn tx_packetbuffer_send(
&mut self,
buf: &mut mailbox::HidIoPacketBuffer,
) -> Result<(), CommandError> {
/* Send command and wait for a reply */
/* If sending an ACK/NAK or NA Data then there won't be a reply */
if let Some(rcvmsg) = /* send buffer */ {
// Handle ack/nak
self.rx_message_handling(rcvmsg.data)?;
}
Ok(())
}
fn h0016_flashmode_ack(
&mut self,
data: h0016::Ack,
) -> Result<(), CommandError> {
/* ACK */
Ok(())
}
fn h0016_flashmode_nak(
&mut self,
data: h0016::Nak,
) -> Result<(), CommandError> {
/* NAK */
Ok(())
}
}
let mut intf = CommandInterface {};
// Send command
intf.h0016_flashmode(h0016::Cmd {}).unwrap();
hidraw设置
当使用hid-io-protocol与设备固件一起使用时,您需要设置一个hidraw接口。对于USB、蓝牙(以及其他支持USB HID规范的设备),这应该是一样的。使用示例中的选项1有存根,指示hidraw rx和tx发生的位置。
HID描述符
以下是使用64字节数据包的USB 2.0 FS的hidraw HID描述符示例。
0x06, 0x1C, 0xFF, // Usage Page (Vendor Defined) 0xFF1C
0x0A, 0x00, 0x11, // Usage 0x1100
0xA1, 0x01, // Collection (Application)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x95, 0x40, // Report Count (64)
0x09, 0x01, // Usage (Output)
0x91, 0x02, // Output (Data,Var,Abs)
0x95, 0x40, // Report Count (64)
0x09, 0x02, // Usage (Input)
0x81, 0x02, // Input (Data,Var,Abs)
0xC0, // End Collection
也可以使用报告ID;但是报告计数应该调整,以确保您不会为单个hidraw报告发送多个USB数据包。相应地调整报告计数
字段。
USB端点设置
以下是如何为上述hid描述符设置USB端点的示例。
// --- Vendor Specific / RAW I/O ---
// - 9 bytes -
// interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12
9, // bLength
4, // bDescriptorType
RAWIO_INTERFACE, // bInterfaceNumber
0, // bAlternateSetting
2, // bNumEndpoints
0x03, // bInterfaceClass (0x03)
0x00, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0, // iInterface (can point to a string name if desired)
// - 9 bytes -
// HID interface descriptor, HID 1.11 spec, section 6.2.1
9, // bLength
0x21, // bDescriptorType
0x11, 0x01, // bcdHID
0, // bCountryCode
1, // bNumDescriptors
0x22, // bDescriptorType
LSB(sizeof(rawio_report_desc)), // wDescriptorLength
MSB(sizeof(rawio_report_desc)),
// - 7 bytes -
// endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
7, // bLength
5, // bDescriptorType
RAWIO_TX_ENDPOINT | 0x80, // bEndpointAddress
0x03, // bmAttributes (0x03=intr)
0x40, 0, // wMaxPacketSize (64 bytes)
1, // bInterval
// - 7 bytes -
// endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
7, // bLength
5, // bDescriptorType
RAWIO_RX_ENDPOINT, // bEndpointAddress
0x03, // bmAttributes (0x03=intr)
0x40, 0, // wMaxPacketSize (64 bytes)
1, // bInterval
然后您只需向指定的端点发送和接收数据RAWIO_TX_ENDPOINT
和RAWIO_RX_ENDPOINT
。
C固件使用
测试
cargo test
一些测试使用额外的日志记录,因此您也可以这样做
RUST_LOG=info cargo test
依赖项
- Rust nightly(可能随着时间的推移而放宽)
- 注意:bincode-core还很新,还没有正式发布,可能随时会出错。
支持的服务器应用程序
支持的设备固件
- kiibohd(KLL)- 进行中
贡献
- 拉取请求运行各种测试
- 在添加新消息时,请确保添加单元测试验证
- 一些推荐测试
cargotest
cargobuild
cargobuild --no-default-features --featuresdevice
依赖项
~2.5MB
~58K SLoC