# How OpenLogi Handles HID++ Device Communication: Architecture and Implementation

> Discover how OpenLogi uses a layered async architecture and the hidpp crate to abstract HID++ device communication into type-safe feature APIs. Explore its implementation.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: architecture
- Published: 2026-08-29

---

**OpenLogi handles HID++ device communication through a layered async architecture that abstracts raw HID transport into type-safe feature APIs using the `hidpp` protocol crate.**

In the AprilNEA/OpenLogi repository, HID++ communication is implemented across four specialized crates that separate transport mechanics from high-level device control. This design enables robust async communication with Logitech peripherals while maintaining clean separation between protocol-level operations and application logic.

## Architecture Overview

The codebase organizes HID++ communication into distinct layers, each with specific responsibilities and implementation files.

### Transport Layer

The transport layer handles raw device detection and channel initialization. In [`crates/openlogi-hid/src/transport/native.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/transport/native.rs), the system enumerates USB and BLE devices using `enumerate_devices`, filters for HID++ capability via `is_hidpp_node`, and establishes communication channels through `open_hidpp_channel`.

This layer creates a `HidppChannel` struct that wraps the raw HID device handle, providing the foundation for all higher-level operations.

### Channel Layer

Located in [`crates/openlogi-hidpp/src/channel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/channel.rs), the channel layer implements the `HidppChannel` struct with async request-response semantics. Key capabilities include:

- **Protocol adaptation**: Automatically detects short vs. long report support via `supports_short_long_hidpp`
- **Message sequencing**: Adds sequence numbers to outgoing reports
- **Error resilience**: Implements transparent retry logic for transient failures
- **Tracing integration**: Instruments all HID++ transactions for debugging

The channel exposes a unified `request` method that accepts feature-specific request objects and returns decoded responses.

### Feature API Layer

HID++ features are defined in `crates/openlogi-hidpp/src/feature/*.rs` as strongly-typed structs representing specific device capabilities like DPI control, backlighting, and SmartShift. The `#[derive(Feature)]` procedural macro in [`crates/openlogi-hidpp-derive/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp-derive/src/lib.rs) generates the encoding and decoding logic for each feature, mapping Rust types to HID++ function IDs and payload layouts.

### Device Layer

High-level device sessions reside in `crates/openlogi-device/src/`, where modules like [`write/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/write/dpi.rs) and [`session/keyboard.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/session/keyboard.rs) orchestrate feature calls. These modules instantiate feature structs, populate payloads, and forward commands through the channel abstraction.

## The Communication Flow

OpenLogi establishes HID++ communication through a five-step process:

1. **Device Enumeration**: The HID transport scans system devices and applies `is_hidpp_node` to filter for Logitech HID++ capable peripherals based on usage page and ID criteria.

2. **Channel Initialization**: For each valid device, `open_hidpp_channel` constructs a `RawHidChannel` and upgrades it to an async `HidppChannel`, querying the device to determine short or long report support.

3. **Request Construction**: Application code creates feature-specific requests using generated structs (e.g., `Dpi::set(800)`), which implement the `Feature` trait for automatic serialization.

4. **Protocol Transmission**: The channel's `request` method converts requests to HID++ reports, handles message length adaptation (short to long conversion when needed), writes to the device, and awaits the response.

5. **Response Handling**: Incoming reports are deserialized into strongly-typed response structs, with errors triggering the transport layer's retry mechanisms as implemented in [`crates/openlogi-hid/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/transport.rs).

## Implementing HID++ Features

The feature system uses Rust's procedural macros to eliminate boilerplate. When you apply `#[derive(Feature)]` to a struct in `crates/openlogi-hidpp/src/feature/`, the macro expands to implement:

- **Function ID mapping**: Associates the struct with its HID++ feature index and function number
- **Payload encoding**: Converts Rust fields into HID++ report bytes according to the protocol specification
- **Response parsing**: Deserializes device replies into Rust types

For example, the DPI feature in [`crates/openlogi-hidpp/src/feature/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/feature/dpi.rs) uses this macro to generate methods like `Dpi::get()` and `Dpi::set(dpi: u16)` that handle the binary protocol details automatically.

## Practical Examples

### Enumerating and Connecting to HID++ Devices

```rust
use openlogi_hid::transport::Transport;
use openlogi_hid::transport::enumerate_hidpp;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Scan all HID devices and filter for HID++ capable ones
    let hidpp_nodes = enumerate_hidpp().await?;
    
    for node in hidpp_nodes {
        println!("Found HID++ device: {:?}", node);
        
        // Open an async HID++ channel for this node
        let channel = node.open_hidpp().await?;
        
        // Query the device's feature set
        let features = channel.request(
            openlogi_hidpp::feature::FeatureSet::get()
        ).await?;
        
        println!("Supported features: {:?}", features);
    }
    Ok(())
}

```

### Setting Device DPI via the Feature API

```rust
use openlogi_hidpp::feature::Dpi;
use openlogi_hidpp::channel::HidppChannel;

async fn set_device_dpi(
    channel: &HidppChannel, 
    dpi: u16
) -> anyhow::Result<()> {
    // Build a DPI-set request using the generated Feature implementation
    let request = Dpi::set(dpi);
    
    // Send the request and ignore the empty response
    channel.request(request).await?;
    Ok(())
}

```

## Summary

- **Transport abstraction**: [`crates/openlogi-hid/src/transport/native.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/transport/native.rs) handles device discovery and raw channel creation via `enumerate_hidpp` and `open_hidpp_channel`.
- **Async protocol handling**: [`crates/openlogi-hidpp/src/channel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/channel.rs) implements `HidppChannel` with automatic short/long report detection and retry logic.
- **Type-safe features**: The `#[derive(Feature)]` macro in [`crates/openlogi-hidpp-derive/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp-derive/src/lib.rs) generates protocol code for feature structs defined in `crates/openlogi-hidpp/src/feature/*.rs`.
- **High-level API**: [`crates/openlogi-device/src/write/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write/dpi.rs) and session modules demonstrate ergonomic device control built atop the feature layer.

## Frequently Asked Questions

### How does OpenLogi detect if a device supports HID++?

OpenLogi detects HID++ devices in [`crates/openlogi-hid/src/transport/native.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/transport/native.rs) using the `is_hidpp_node` function, which inspects the USB or BLE usage page and usage ID to identify Logitech HID++ compatible peripherals during enumeration.

### What is the difference between short and long HID++ reports?

Short reports use a compact 7-byte format for simple commands, while long reports support 20-byte payloads for complex feature data. The `HidppChannel` in [`crates/openlogi-hidpp/src/channel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/channel.rs) automatically queries the device via `supports_short_long_hidpp` and adapts messages accordingly, converting short to long format when necessary.

### How does the Feature derive macro work?

The `#[derive(Feature)]` procedural macro defined in [`crates/openlogi-hidpp-derive/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp-derive/src/lib.rs) parses feature struct definitions and generates implementations for the `Feature` trait, including methods to encode requests into HID++ report bytes and decode device responses back into Rust types.

### Can OpenLogi handle multiple HID++ devices simultaneously?

Yes. The architecture supports concurrent device sessions through independent `HidppChannel` instances. Each device connection maintains its own async channel, and the transport layer uses `tokio` for non-blocking I/O across multiple peripherals.