# What Is the On-Disk Probe Cache in openlogi-hid?

> Discover the on-disk probe cache in openlogi-hid. Learn how it speeds up device probes by storing capability reports on disk, reducing redundant commands.

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

---

**The on-disk probe cache in openlogi-hid persists serialized HID++ device capability reports to disk, eliminating redundant probe commands on subsequent application starts while automatically invalidating entries when firmware versions change.**

The `openlogi-hid` crate in the AprilNEA/OpenLogi repository manages communication between the host operating system and Logitech HID++ devices. When the library discovers a device, it typically executes a **probe**—a sequence of commands that query hardware capabilities such as supported DPI steps, button layouts, and battery status. Running these probes on every startup introduces noticeable latency, especially on systems with multiple peripherals attached, so `openlogi-hid` implements an **on-disk probe cache** that stores capability data between application runs.

## How the On-Disk Probe Cache Works

The cache operates through a four-stage lifecycle implemented in [`crates/openlogi-hid/src/probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/probe_cache.rs):

### 1. Initial Probe Execution and Serialization

When a device is enumerated for the first time, the library executes the full HID++ probe sequence against the hardware. The resulting capability report is serialized into a compact binary format—rather than JSON or XML—to minimize both disk footprint and deserialization overhead.

### 2. Cache Storage Location and Naming

The serialized data is written to the user's platform-specific cache directory, typically `$XDG_CACHE_HOME/openlogi/probe_cache/` on Linux or the system equivalent on macOS. As implemented in `FileProbeCacheStore`, the filename is derived from the device's unique HID++ identifier, ensuring each physical device maintains an isolated cache entry.

### 3. Cache Retrieval on Subsequent Starts

On application startup, `openlogi-hid` checks for existing cache files matching discovered devices. If a valid entry exists and passes integrity checks, the stored binary data is deserialized directly into memory, bypassing the expensive USB/HID probe commands entirely. This retrieval happens transparently through the `ProbeCacheStore` trait interface.

### 4. Cache Invalidation Logic

The cache implements automatic invalidation triggered by two specific conditions:
- The device reports a **firmware version** that differs from the cached metadata.
- The cached file fails to deserialize (for example, after a library upgrade modifies the binary schema).

When either condition occurs, the library falls back to a fresh probe sequence and atomically overwrites the stale cache file with the new capability data.

## Implementation Architecture

The cache decouples storage logic from device enumeration through abstract interfaces defined in the source code:

**`ProbeCacheStore` Trait**: Defines the contract for cache read/write operations, allowing different storage backends without modifying enumeration logic.

**`FileProbeCacheStore`**: The concrete implementation located in [`crates/openlogi-hid/src/probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/probe_cache.rs) handles filesystem operations, directory creation, and binary serialization using the standard library's I/O primitives.

**Integration Points**: The host-side enumerator in [`crates/openlogi-hid/src/host.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/host.rs) creates an `Arc<FileProbeCacheStore>` and injects it via `enumerator.with_probe_cache(...)`. Additionally, [`crates/openlogi-device/src/inventory.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/inventory.rs) exposes this method on the device inventory builder, completing the integration pipeline while keeping storage details hidden from higher-level code.

## Enabling the On-Disk Probe Cache

To utilize caching in your application, instantiate `FileProbeCacheStore` and pass it to the enumerator using the pattern found in [`host.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/host.rs):

```rust
use std::sync::Arc;
use openlogi_hid::{Host, ProbeCacheStore, FileProbeCacheStore};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize the HID host
    let host = Host::new()?;

    // Create the on-disk cache store using the default cache directory
    let cache_store = FileProbeCacheStore::default();
    let cache_store = Arc::new(cache_store);

    // Attach the cache to the enumerator
    let enumerator = host
        .enumerator()
        .with_probe_cache(cache_store); // Integration point defined in inventory.rs

    // Enumerate devices; cached probes load instantly when available
    let devices = enumerator.enumerate()?;

    for dev in devices {
        println!("Found device: {}", dev.serial_number());
        // Capabilities are already populated from cache
    }

    Ok(())
}

```

This pattern significantly reduces HID traffic and improves startup performance, particularly when managing multiple Logitech peripherals simultaneously.

## Bypassing the Cache for Fresh Probes

For debugging scenarios or when you require guaranteed current hardware state, omit the `.with_probe_cache()` call to force live probing:

```rust
let enumerator = host.enumerator(); // No cache attached
// Every device will undergo full HID++ probing regardless of cache state

```

Without the cache store parameter, the enumerator executes the complete probe command sequence against every discovered device, ensuring you retrieve the actual hardware capabilities regardless of cached data.

## Summary

- The **on-disk probe cache** eliminates redundant HID++ probe commands by persisting serialized capability reports to disk between application runs.
- Cache files reside in the system cache directory (e.g., `$XDG_CACHE_HOME/openlogi/probe_cache/`) and use filenames derived from unique device HID++ identifiers.
- **Automatic invalidation** triggers when firmware versions change or deserialization fails, ensuring the cache never masks hardware updates.
- The implementation uses the `ProbeCacheStore` trait with `FileProbeCacheStore` as the concrete filesystem-backed provider in [`crates/openlogi-hid/src/probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/probe_cache.rs).
- Integration occurs via `enumerator.with_probe_cache(...)` in [`crates/openlogi-hid/src/host.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/host.rs) and [`crates/openlogi-device/src/inventory.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/inventory.rs), allowing transparent cache usage without higher-level code changes.

## Frequently Asked Questions

### Where does openlogi-hid store the probe cache files?

According to [`crates/openlogi-hid/src/probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/probe_cache.rs), the library stores cache files in the user's platform-specific cache directory. On Linux systems, this defaults to `$XDG_CACHE_HOME/openlogi/probe_cache/`, while macOS and Windows use their respective system cache locations.

### How does openlogi-hid know when to invalidate the cache?

The cache invalidates automatically when a device reports a firmware version that differs from the cached value, or when the cached file fails to deserialize (for example, after a library update changes the binary format). In both cases, `FileProbeCacheStore` triggers a fresh probe and updates the disk file with the new capability data.

### Can I use a custom storage backend instead of the filesystem?

Yes. The cache logic is decoupled from storage specifics through the `ProbeCacheStore` trait. While `FileProbeCacheStore` in [`probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/probe_cache.rs) provides the default filesystem implementation, you can implement `ProbeCacheStore` for any backend—such as an in-memory map for testing or a network store—and pass it to `with_probe_cache()` to override the default behavior.

### Does enabling the cache affect real-time device capability detection?

No. The cache only accelerates the initial enumeration phase; it does not interfere with runtime communication. If device capabilities change due to a firmware update, the invalidation logic detects the version mismatch during the next enumeration and performs a fresh probe, ensuring the application always reflects the current hardware state.