# How OpenLogi Communicates with Logitech Devices via Bolt, Unifying, Bluetooth, and Wired Connections

> Discover how OpenLogi connects with Logitech devices using Bolt Unifying Bluetooth and wired methods Explore the HID++ protocol and its transport channel and receiver layers for seamless integration

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-09-12

---

**OpenLogi communicates with Logitech peripherals through the HID++ protocol across three architectural layers: a transport layer that enumerates HID devices, a channel layer that wraps raw reports into structured messages, and receiver-specific implementations for Bolt and Unifying hardware, with direct USB and Bluetooth connections handled via the same protocol stack.**

OpenLogi is an open-source Rust implementation for managing Logitech peripherals without proprietary software. The project, hosted at `AprilNEA/OpenLogi`, implements the **HID++ protocol** to enable communication with devices connected via Bolt receivers, Unifying receivers, or directly through USB and Bluetooth Low Energy.

## The Three-Layer Communication Architecture

OpenLogi's communication stack is organized into three distinct layers that abstract the complexity of Logitech's proprietary protocols.

The **transport layer** resides in [`crates/openlogi-hid/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/transport.rs) and handles physical device detection. It filters system HID nodes to identify Logitech vendor ID `0x046D` and specific usage page combinations that indicate HID++ capability.

The **HID++ channel layer** in [`crates/openlogi-hidpp/src/channel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/channel.rs) wraps raw HID reports into structured request/response messages. This layer manages the protocol's short reports (`0x10`) and long reports (`0x11`), providing methods like `read_register` and `write_register` for device communication.

The **receiver abstraction layer** implements protocol-specific behavior for Bolt and Unifying hardware in [`crates/openlogi-hidpp/src/receiver/bolt.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/receiver/bolt.rs) and [`crates/openlogi-hidpp/src/receiver/unifying.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/receiver/unifying.rs).

## Transport Layer: Enumerating Logitech HID Devices

The transport layer begins communication by discovering available HID nodes through the `enumerate_devices` function defined in [`crates/openlogi-hid/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/transport.rs).

### Detecting HID++ Long Collections

The enumeration process filters for Logitech's specific **usage page and usage ID combinations** that indicate HID++ long-report support:

- **USB receivers**: Usage page `0xFF00`, usage ID `0x0002`
- **Bluetooth Low Energy**: Usage page `0xFF43`, usage ID `0x0202`
- **Wired keyboards**: Usage page `0xFF43`, usage ID `0x0602`

The function `is_hidpp_long_collection` validates these combinations to identify compatible devices.

```rust
let all: Vec<async_hid::Device> = HID_BACKEND.enumerate().await?;
for d in all.iter().filter(|d| d.vendor_id == LOGITECH_VENDOR_ID) {
    debug!(
        name = %d.name,
        pid = format_args!("{:04x}", d.product_id),
        usage_page = format_args!("{:#06x}", d.usage_page),
        usage_id = format_args!("{:#06x}", d.usage_id),
        matched = is_hidpp_long_collection(d.usage_page, d.usage_id),
        "logitech HID node"
    );
}

```

### Filtering Receiver Child Nodes

When Logitech receivers connect to Linux systems, the `hid-logitech-dj` kernel driver creates **per-device child nodes** under `/dev/hidraw`. The `is_receiver_child_node` function in [`transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/transport.rs) discards these child nodes because communication must occur through the **receiver node** itself, not individual device endpoints.

## Channel Layer: Wrapping HID Reports

Once a valid HID node is identified, the system constructs a `HidppChannel` from [`crates/openlogi-hidpp/src/channel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/channel.rs). This channel abstracts the raw byte-level communication into structured HID++ operations.

### Device Index Handling

The channel initialization accepts a **device index** parameter. For receiver-based communication, this uses `RECEIVER_DEVICE_INDEX = 0xff`. For direct connections, the same index value applies, indicating the device communicates without a receiver intermediary.

```rust
let chan = HidppChannel::new(device, RECEIVER_DEVICE_INDEX).await?;

```

### Register Operations

The `HidppChannel` implementation exposes three primary methods for device interaction:

- `read_register`: Reads short-form register values
- `write_register`: Writes data to device registers  
- `read_long_register`: Retrieves extended data using long-report format (`0x11`)

Additionally, the channel registers a message listener that decodes asynchronous HID++ notifications and forwards them to an `EventEmitter`, enabling real-time event handling for device connections and disconnections.

## Receiver Implementations: Bolt and Unifying

OpenLogi provides distinct receiver implementations that share a common trait interface but handle protocol-specific register layouts.

### Bolt Receiver Protocol

The **Bolt receiver** implementation in [`crates/openlogi-hidpp/src/receiver/bolt.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/receiver/bolt.rs) manages devices paired through Logitech's newer Bolt wireless protocol. The `Receiver` struct exposes several high-level methods:

- `get_notification_state`: Reads register `0x00` to determine enabled wireless notifications
- `count_pairings`: Reads register `0x02` to retrieve the number of occupied pairing slots
- `get_unique_id`: Reads register `0xFB` to obtain the receiver's unique identifier (distinct from serial number)
- `trigger_device_arrival`: Writes to register `0xC0` to force emission of arrival events for all paired slots, enabling device enumeration
- `discover_devices` / `cancel_device_discovery`: Manages BLE-based device discovery using register `0xC0`

The Bolt implementation follows the **HID++ 1.0** specification based on reverse-engineered data, as noted in the source comments.

### Unifying Receiver Protocol

The **Unifying receiver** in [`crates/openlogi-hidpp/src/receiver/unifying.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/receiver/unifying.rs) provides equivalent functionality but with register layout differences specific to legacy Unifying hardware. For example, device codenames store at register `0x40 + n-1` rather than the Bolt base of `0x60`.

Key methods mirror the Bolt implementation:

- `count_pairings` targets register `0x02`
- `set_wireless_notifications` manipulates register `0x00`
- `trigger_device_arrival` writes `[0x02, 0x00, 0x00]` to register `0x02`
- `get_receiver_info` reads long register `0xB5` with sub-register `0x03`

Both receivers implement the `Receiver` trait defined in [`crates/openlogi-hidpp/src/receiver.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/receiver.rs), allowing uniform treatment by higher-level application logic.

## Direct Connections: USB and Bluetooth

For devices communicating without a receiver, OpenLogi uses the **Direct** connection type defined in [`crates/openlogi-core/src/hid/route.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/hid/route.rs).

### The DeviceRoute Abstraction

The `DeviceRoute` enum distinguishes connection types:

```rust
pub enum DeviceRoute {
    Bolt { receiver_uid: String, slot: u8 },
    Unifying { receiver_uid: String, slot: u8 },
    Direct { /* no receiver */ },
}

```

Direct connections occur when the enumerated HID node is not a child of a known receiver, as determined by `is_hidpp_node` in [`transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/transport.rs). These devices—whether connected via USB cable or Bluetooth Low Energy—use the same `HidppChannel` implementation with device index `0xFF`, maintaining protocol consistency across all connection types.

### Bluetooth Low Energy Handling

BLE devices present usage page `0xFF43` and usage ID `0x0202`. On macOS, the system requires CoreBluetooth permissions handled in [`crates/openlogi-permissions/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-permissions/src/macos.rs), while Linux implementations filter receiver child nodes through sysfs examination.

## Practical Implementation Examples

### Opening a Bolt Receiver and Enumerating Devices

The following example demonstrates discovering a Bolt receiver, establishing a channel, and triggering device arrival notifications:

```rust
use openlogi_hidpp::receiver::bolt::Receiver;
use openlogi_hidpp::channel::HidppChannel;
use std::sync::Arc;

// Enumerate HID nodes and locate Bolt receiver
let devices = openlogi_hid::enumerate_devices().await?;
let bolt_dev = devices.into_iter()
    .find(|d| d.vendor_id == LOGITECH_VENDOR_ID && d.product_id == BOLT_PID)
    .expect("No Bolt receiver found");

// Establish HID++ channel with receiver device index
let chan = HidppChannel::new(bolt_dev, RECEIVER_DEVICE_INDEX).await?;

// Initialize Bolt receiver abstraction
let bolt = Receiver::new(Arc::new(chan)).expect("Failed to initialise Bolt");

// Query receiver information
let uid = bolt.get_unique_id().await?;
println!("Bolt UID: {}", uid);

let count = bolt.count_pairings().await?;
println!("Paired devices: {}", count);

// Trigger arrival notifications for enumeration
bolt.trigger_device_arrival().await?;

// Listen for device events
let mut events = bolt.listen();
while let Ok(event) = events.recv().await {
    println!("Device event: {:?}", event);
}

```

### Discovering New BLE Peripherals

To discover new devices for pairing:

```rust
// Initiate discovery mode
bolt.discover_devices().await?;

// Process discovery events
let mut ev = bolt.listen();
while let Ok(event) = ev.recv().await {
    match event {
        DeviceConnection::Discovered { address, name, .. } => {
            println!("Found device {} ({})", name, address);
        }
        DeviceConnection::PasskeyPress { .. } => {
            // Handle passkey confirmation
        }
        _ => {}
    }
}

```

### Connecting Direct Bluetooth Devices

For BLE devices without a receiver:

```rust
use openlogi_hidpp::channel::HidppChannel;
use openlogi_hid::enumerate_devices;

let devices = enumerate_devices().await?;
let bt_device = devices.into_iter()
    .find(|d| d.vendor_id == LOGITECH_VENDOR_ID && d.usage_page == 0xFF43 && d.usage_id == 0x0202)
    .expect("No BLE device found");

// Create direct channel with index 0xFF
let chan = HidppChannel::new(bt_device, 0xFF).await?;

// Read device registers directly
let uid = chan.read_long_register(0xFF, Register::UniqueId.into(), [0, 0, 0]).await?;
println!("Device UID: {}", hex::encode_upper(&uid[1..=4]));

```

## Summary

OpenLogi enables communication with Logitech devices through a layered architecture implementing the HID++ protocol:

- **Transport Layer** ([`crates/openlogi-hid/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/transport.rs)): Detects Logitech HID nodes by vendor ID `0x046D` and usage page combinations, filtering for long-report collections while excluding receiver child nodes
- **Channel Layer** ([`crates/openlogi-hidpp/src/channel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/channel.rs)): Wraps raw HID reports into structured messages, providing `read_register`, `write_register`, and `read_long_register` methods with asynchronous event handling
- **Receiver Layer** ([`crates/openlogi-hidpp/src/receiver/bolt.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/receiver/bolt.rs) and [`unifying.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/unifying.rs)): Implements protocol-specific register maps for Bolt and Unifying receivers, exposing high-level discovery and pairing methods
- **Direct Connections** ([`crates/openlogi-core/src/hid/route.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/hid/route.rs)): Handles USB and Bluetooth devices using the same HID++ channel with device index `0xFF`, abstracting connection differences through the `DeviceRoute` enum

## Frequently Asked Questions

### What is the HID++ protocol and how does OpenLogi implement it?

The **HID++ protocol** is Logitech's proprietary extension to standard HID for advanced device management. OpenLogi implements this protocol through the `HidppChannel` struct in [`crates/openlogi-hidpp/src/channel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/channel.rs), which wraps raw HID reports into structured request/response messages. The implementation supports both short reports (`0x10`) and long reports (`0x11`), enabling register reads, writes, and asynchronous event notifications across all connection types.

### How does OpenLogi distinguish between Bolt and Unifying receivers?

OpenLogi distinguishes receivers by **product ID and register layout**. During enumeration in [`transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/transport.rs), the system identifies specific product IDs associated with Bolt hardware. The `Receiver` implementations in [`crates/openlogi-hidpp/src/receiver/bolt.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/receiver/bolt.rs) and [`unifying.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/unifying.rs) then handle protocol differences: Bolt uses register base `0x60` for device codenames and register `0xC0` for discovery, while Unifying uses base `0x40` and register `0x02` for similar operations. Both implement the common `Receiver` trait for uniform API access.

### Can OpenLogi communicate with Logitech devices without a receiver?

**Yes.** OpenLogi communicates directly with USB-connected and Bluetooth Low Energy devices through the **Direct** variant of the `DeviceRoute` enum. These connections use the same `HidppChannel` infrastructure with device index `0xFF`, bypassing receiver-specific logic while maintaining full HID++ protocol compatibility for register operations and event handling.

### What are the specific usage page values for identifying Logitech HID++ devices?

OpenLogi identifies HID++ capable devices through specific **usage page and usage ID combinations** defined in [`crates/openlogi-hid/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/transport.rs):
- USB receivers: usage page `0xFF00`, usage ID `0x0002`
- Bluetooth Low Energy: usage page `0xFF43`, usage ID `0x0202`  
- Wired keyboards: usage page `0xFF43`, usage ID `0x0602`

The `is_hidpp_long_collection` function validates these combinations to filter compatible Logitech devices from system HID enumerations.