How OpenLogi Handles Device Enumeration for Logitech Bolt and Unifying Receivers

OpenLogi enumerates paired peripherals by detecting HID++ capable USB receivers, instantiating protocol-specific handler objects for Bolt or Unifying hardware, and querying internal HID++ registers 0xB5/0x5N and 0xB5/0x6N to retrieve device metadata.

OpenLogi implements a Rust-based open source stack for managing Logitech input devices through the proprietary HID++ protocol. The OpenLogi device enumeration pipeline spans three primary crates—openlogi-hid, openlogi-hidpp, and openlogi-device—to discover receivers and list their paired peripherals.

HID Transport Layer Enumeration

The lowest level of the enumeration stack resides in crates/openlogi-hid/src/transport.rs. This module scans the host's USB bus for HID nodes belonging to the Logitech HID++ collection (0xFF00/0x0002). When a compatible node is found, the transport layer wraps it as an async_hid::Device object and returns it to the caller.

To optimize performance, crates/openlogi-hid/src/host.rs provides an enumerator() function that caches these probe results. This caching layer ensures that high-level code receives a stable snapshot of available receivers without repeatedly querying the USB subsystem.

Receiver Type Detection and Abstraction

Once the HID backend identifies a candidate node, control passes to crates/openlogi-hidpp/src/receiver.rs. This file defines the Receiver enum, which exposes two variants:

  • Receiver::Bolt
  • Receiver::Unifying

The Receiver::new() constructor inspects the USB/HID node's receiver protocol field to determine which variant to instantiate. This abstraction allows the rest of the codebase to treat Bolt and Unifying hardware through a unified interface while delegating protocol specifics to the appropriate implementation.

Bolt Receiver Enumeration

The Bolt-specific logic lives in crates/openlogi-hidpp/src/receiver/bolt.rs. This implementation communicates with the receiver's internal registers to discover paired devices.

Register-Based Device Discovery

The Bolt receiver stores pairing metadata in indexed registers. The enumeration routine reads the device-index registers 0xB5/0x5N and 0xB5/0x6N in a loop, where N represents the slot index. For each occupied slot, the code constructs a DeviceInfo struct containing the peripheral's metadata. Upon successfully parsing a device entry, the implementation emits a BoltEvent::DeviceConnection notification, as defined in crates/openlogi-hidpp/src/receiver/bolt/event.rs.

Unifying Receiver Enumeration

Unifying receiver support is implemented in crates/openlogi-hidpp/src/receiver/unifying.rs. This module follows the same register-reading pattern as the Bolt implementation, accessing 0xB5/0x5N and 0xB5/0x6N to list paired devices.

However, the Unifying protocol uses a distinct device-kind encoding scheme. While the register addresses match Bolt hardware, the interpretation of device type values diverges at indices 5 and above, requiring separate parsing logic to correctly identify peripheral categories.

Public Inventory API

The high-level entry point for device enumeration is openlogi_device::inventory::enumerate, located in crates/openlogi-device/src/lib.rs. This function orchestrates the entire pipeline:

  1. Invokes the HID backend to enumerate all HID++ compatible nodes.
  2. Instantiates a Receiver for each discovered node.
  3. Calls the receiver-specific enumerate_devices() method to query paired peripherals.
  4. Aggregates the results into DeviceInventory objects for transmission over IPC channels to GUI or overlay processes.

The following Rust example demonstrates the complete enumeration flow:

use openlogi_hidpp::receiver::Receiver;
use openlogi_hid::transport::enumerate_devices;

// Enumerate all HID++ nodes on the host
let nodes = enumerate_devices().await?;

// For each node, instantiate the appropriate receiver type
for node in nodes {
    if let Some(receiver) = Receiver::new(node).await? {
        match receiver {
            Receiver::Bolt(bolt) => {
                let devices = bolt.enumerate_devices().await?;
                println!("Bolt receiver has {} devices", devices.len());
            }
            Receiver::Unifying(unify) => {
                let devices = unify.enumerate_devices().await?;
                println!("Unifying receiver has {} devices", devices.len());
            }
        }
    }
}

Summary

  • Detection: openlogi-hid/src/transport.rs filters USB nodes by the HID++ collection ID 0xFF00/0x0002.
  • Abstraction: openlogi-hidpp/src/receiver.rs provides a unified Receiver enum that dispatches to Bolt or Unifying implementations based on protocol inspection.
  • Enumeration: Both receiver types read registers 0xB5/0x5N and 0xB5/0x6N, but interpret the device-kind field differently.
  • Events: The Bolt implementation emits BoltEvent::DeviceConnection notifications during the enumeration process.
  • API: openlogi-device/src/lib.rs exposes the public enumerate function that coordinates the entire discovery pipeline.

Frequently Asked Questions

What specific HID++ registers does OpenLogi read to enumerate paired devices?

OpenLogi reads the device-index register pair 0xB5/0x5N and 0xB5/0x6N, where N represents the device slot index. These registers contain the connection status and metadata for each paired peripheral on both Bolt and Unifying receivers.

How does OpenLogi distinguish between Bolt and Unifying receivers?

During initialization, Receiver::new() in crates/openlogi-hidpp/src/receiver.rs inspects the USB node's receiver protocol identifier. Based on this value, it instantiates either Receiver::Bolt or Receiver::Unifying, ensuring that subsequent register parsing uses the correct protocol interpretation.

Where does the low-level USB enumeration occur in the OpenLogi codebase?

The USB enumeration logic resides in crates/openlogi-hid/src/transport.rs, which scans for HID devices matching the Logitech HID++ collection. The enumerator() function in crates/openlogi-hid/src/host.rs adds a caching layer to stabilize results for higher-level consumption.

What events are emitted during Bolt device enumeration?

As the Bolt receiver implementation parses each slot from registers 0xB5/0x5N and 0xB5/0x6N, it emits BoltEvent::DeviceConnection events defined in crates/openlogi-hidpp/src/receiver/bolt/event.rs. These events signal the discovery of a paired device and carry the associated DeviceInfo metadata.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →