What Is the On-Disk Probe Cache in openlogi-hid?
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:
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 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 creates an Arc<FileProbeCacheStore> and injects it via enumerator.with_probe_cache(...). Additionally, 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:
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:
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
ProbeCacheStoretrait withFileProbeCacheStoreas the concrete filesystem-backed provider incrates/openlogi-hid/src/probe_cache.rs. - Integration occurs via
enumerator.with_probe_cache(...)incrates/openlogi-hid/src/host.rsandcrates/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, 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →