# HID++ Protocol Implementation Structure in the openlogi-hidpp Crate

> Explore the HID++ protocol implementation structure in the openlogi-hidpp Crate. Understand its layered architecture for HID++ 1.0 and 2.0 communication, separating protocol definitions, transport, and device management.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-09-13

---

**The openlogi-hidpp crate implements a layered architecture for HID++ 1.0 and 2.0 communication, separating protocol definitions in `src/protocol/`, transport channels in `src/channel/`, device management in [`src/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/device.rs) and `src/receiver/`, and feature-specific logic in over 40 `src/feature/` modules.**

The openlogi-hidpp crate in the AprilNEA/OpenLogi repository provides the low-level Rust driver for Logitech peripherals using the HID++ protocol. Understanding its structure reveals how raw HID bytes transform into type-safe feature interactions across platform-specific transports like `async-hid`.

## Protocol Definition Layer

This layer handles binary message formats. According to the source code, the implementation isolates version-specific parsing to maintain clean separation between HID++ 1.0 and 2.0 specifications while sharing common utilities.

### Version-Specific Message Layouts

- **[`src/protocol/v10.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/protocol/v10.rs)**: Defines the message header structure and payload layouts for HID++ 1.0 devices, handling the older short-header format with specific bit-field arrangements.
- **[`src/protocol/v20.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/protocol/v20.rs)**: Implements the extended header format required for HID++ 2.0, supporting longer payloads and additional protocol flags.
- **[`src/protocol.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/protocol.rs)**: Provides common protocol traits and shared helper functions utilized by both version implementations.
- **[`src/nibble.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/nibble.rs)**: Contains utilities for manipulating 4-bit integers (U4), essential for packing and unpacking the protocol's dense binary fields where nibbles frequently cross byte boundaries.

## Transport and Channel Abstraction

The channel layer abstracts raw HID I/O operations, decoupling the protocol logic from platform-specific backends. This enables the crate to function across macOS, Linux, and Windows without modifying higher-level device code.

### Raw Byte Streams

- **[`src/channel/raw.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/channel/raw.rs)**: Implements the raw byte-stream interface that reads from and writes to the underlying HID device file descriptors provided by the OS-specific backend.
- **[`src/channel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/channel.rs)**: Exposes the high-level `Channel` struct with `send` and `receive` methods, coordinating between the raw transport and protocol parsing layers.
- **[`src/channel/message.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/channel/message.rs)**: Defines the `Message` envelope that combines a parsed `MessageHeader` (originating from [`v10.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/v10.rs) or [`v20.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/v20.rs)) with its associated payload bytes for processing.

### Asynchronous Observation

- **[`src/channel/observation.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/channel/observation.rs)**: Manages the observation stream for unsolicited reports, such as battery level broadcasts or DPI change notifications initiated by the device rather than the host.

## Device and Receiver Management

This layer represents concrete hardware entities and manages their discovery lifecycle through receiver-specific implementations.

### The Device Struct

- **[`src/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/device.rs)**: Contains the `Device` struct, which holds a `Channel` instance and exposes high-level methods like `read_feature` and `write_feature`. Internally, these methods utilize the **Emitter** to construct commands and delegate response parsing to specific feature modules.

### Receiver Implementations

- **[`src/receiver.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/receiver.rs)**: Defines the generic `Receiver` trait for device discovery and pairing operations, establishing a consistent interface for different hardware connection methods.
- **[`src/receiver/unifying.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/receiver/unifying.rs)**: Implements the Unifying receiver logic for Logitech's wireless dongles, handling device enumeration and connection management protocols specific to the 2.4 GHz proprietary radio.
- **[`src/receiver/bolt.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/receiver/bolt.rs)**: Provides support for the newer Bolt receiver protocol, including Bluetooth and USB transport modes, with additional event handling logic located in [`src/receiver/bolt/event.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/receiver/bolt/event.rs).

## Feature Module System

Individual HID++ features (battery status, adjustable DPI, SmartShift, RGB effects) reside in dedicated modules under `src/feature/`. Each module implements the `Feature` trait, allowing the `Device` struct to communicate using strongly-typed Rust structs rather than raw byte buffers.

- **Feature Isolation**: Files such as [`src/feature/battery_status.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/feature/battery_status.rs), [`src/feature/adjustable_dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/feature/adjustable_dpi.rs), and [`src/feature/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/feature/smartshift.rs) encapsulate the specific HID++ feature ID, request/response data structures, and bitwise encoding logic.
- **Extensibility**: Adding support for new HID++ features requires only creating a new module implementing the `Feature` trait. The architecture supports over 40 distinct features without modifications to the core `Device` or `Channel` implementations.

## Message Construction and Synchronous Facade

### Packet Building

- **[`src/emitter.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/emitter.rs)**: Provides the `Emitter` type for constructing outbound HID++ packets. It handles low-level details of header construction, payload serialization, and checksum calculation required by the protocol specification.

### Blocking API

- **[`src/sync.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/sync.rs)**: Offers the `SyncDevice` wrapper around the asynchronous `Device`, enabling blocking `read_feature` and `write_feature` calls. This facade runs a small internal async runtime to accommodate callers that prefer synchronous APIs while maintaining the crate's async internals.

## Implementation Example

The following example demonstrates discovering a device via a Unifying receiver, reading battery status, and adjusting DPI settings using the crate's synchronous facade.

```rust
use openlogi_hidpp::{Device, FeatureSet, sync::SyncDevice};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Discover devices via the receiver (e.g., Unifying).
    let receiver = openlogi_hidpp::receiver::unifying::Receiver::new()?;
    let device = receiver
        .devices()
        .await?
        .into_iter()
        .next()
        .ok_or("no device found")?;

    // 2. Wrap the async device in a synchronous façade.
    let mut dev = SyncDevice::new(device);

    // 3. Read the battery status (feature ID 0x10).
    let battery = dev.read_feature::<openlogi_hidpp::feature::battery_status::BatteryInfo>()?;
    println!("Battery level: {} %", battery.level());

    // 4. Change DPI (feature ID 0x07) to 1200 DPI on X-axis.
    let mut dpi = dev.read_feature::<openlogi_hidpp::feature::adjustable_dpi::DpiSettings>()?;
    dpi.set_x(1200);
    dev.write_feature(&dpi)?;

    println!("DPI updated");
    Ok(())
}

```

**Key implementation details**:

- `Receiver::new()` instantiates a platform-specific receiver (Unifying in this case), while `devices()` returns an async stream of available `Device` instances.
- `SyncDevice::new()` wraps the async device to provide blocking operations.
- `BatteryInfo` and `DpiSettings` are zero-cost abstractions defined in [`src/feature/battery_status.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/feature/battery_status.rs) and [`src/feature/adjustable_dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/feature/adjustable_dpi.rs), implementing the `Feature` trait for type-safe serialization.

## Summary

- **Protocol versioning**: HID++ 1.0 and 2.0 implementations are isolated in [`src/protocol/v10.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/protocol/v10.rs) and [`src/protocol/v20.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/protocol/v20.rs) to prevent conflicting parsing logic, with shared utilities in [`src/nibble.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/nibble.rs).
- **Transport abstraction**: The `Channel` struct in [`src/channel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/channel.rs) and raw implementation in [`src/channel/raw.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/channel/raw.rs) decouple the protocol from platform-specific HID backends supported by `async-hid`.
- **Feature extensibility**: Individual modules in `src/feature/` implement the `Feature` trait for each HID++ capability, enabling granular support for device functions without core architectural changes.
- **Synchronous convenience**: [`src/sync.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/sync.rs) provides `SyncDevice` for blocking APIs while maintaining the crate's asynchronous internals.
- **Receiver support**: Both Unifying ([`src/receiver/unifying.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/receiver/unifying.rs)) and Bolt ([`src/receiver/bolt.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/receiver/bolt.rs)) receivers implement the `Receiver` trait from [`src/receiver.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/receiver.rs) for diverse connectivity options.

## Frequently Asked Questions

### How does openlogi-hidpp handle different HID++ protocol versions?

The crate maintains separate modules for each protocol version. [`src/protocol/v10.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/protocol/v10.rs) handles the short-header binary format used by older devices, while [`src/protocol/v20.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/protocol/v20.rs) manages the extended headers of modern devices. Both implement common traits defined in [`src/protocol.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/protocol.rs), allowing higher-level code in [`src/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/device.rs) to work with either version transparently through the same `Channel` abstraction.

### What is the role of the Feature trait in the crate architecture?

The `Feature` trait bridges raw HID++ bytes and Rust data structures. Each module in `src/feature/` (such as [`battery_status.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/battery_status.rs) or [`adjustable_dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/adjustable_dpi.rs)) implements this trait for a specific HID++ feature ID, defining how to encode requests and decode responses using the `Emitter` logic. This enables the `Device` struct to expose generic `read_feature` and `write_feature` methods that return strongly-typed results specific to the requested capability.

### Which Logitech receivers are supported by openlogi-hidpp?

The crate supports both legacy and modern receiver protocols. [`src/receiver/unifying.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/receiver/unifying.rs) implements the USB dongle protocol used by 2.4 GHz wireless devices, while [`src/receiver/bolt.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/receiver/bolt.rs) handles the newer Bolt receiver that operates over Bluetooth and USB. Both implement the `Receiver` trait from [`src/receiver.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/receiver.rs) for consistent device discovery across transport types.

### How are spontaneous device events (like battery notifications) handled?

Asynchronous events are captured through the observation channel implemented in [`src/channel/observation.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/channel/observation.rs). When the device sends unsolicited reports (such as battery level changes or DPI adjustments), the raw channel in [`src/channel/raw.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/channel/raw.rs) parses these into `Message` structures defined in [`src/channel/message.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/channel/message.rs) and forwards them to the observation stream, allowing the application to react to state changes without polling.