How OpenLogi Uniquely Identifies and Keys Devices in Configuration
OpenLogi uses a stable identifier called DeviceStableId that combines transport route information with per-unit identity (serial number or unit ID) to create unique, persistent configuration keys for each physical device.
In AprilNEA/OpenLogi, every mouse, keyboard, or raw-HID device receives a deterministic identifier that survives disconnections, transport changes, and system restarts. This article explains the four route variants, three key-generation methods, and how they map to the TOML configuration structure.
DeviceStableId: The Core Identifier
The DeviceStableId enum in crates/openlogi-core/src/device_order.rs is OpenLogi's answer to reliable device identification. Rather than relying on volatile OS node names, it derives a stable ID from two inputs: the DeviceRoute (transport path) and DeviceIdentity (per-unit serial or unit ID)【/cache/repos/github.com/AprilNEA/OpenLogi/master/crates/openlogi-core/src/device_order.rs#L24-L33】.
The from_parts constructor assembles this identifier:
let stable_id = DeviceStableId::from_parts(
Some(&device_route), // transport route
slot, // receiver slot if applicable
serial, // optional serial string
unit_id, // 4-byte fallback identifier
);
Four Route Variants for Complete Device Coverage
DeviceStableId supports every possible connection method through four enum variants:
| Variant | Connection Type | Key Format |
|---|---|---|
| Bolt | Logitech receiver (Bolt/Unifying) | receiver:<uid>:slot:<slot> |
| Direct | USB or Bluetooth direct attachment | direct:<vendor_id>:<product_id> + identity |
| RawHid | Stand-alone raw-HID device | raw:<vendor_id>:<product_id>:<usage_page>:<usage_id>:<identity> |
| Unknown | Cached device without live route info | unknown:slot:<slot> + identity |
Each variant ensures the resulting key contains enough information to locate and identify the device if it reconnects.
DeviceIdentity: Per-Unit Serial vs. Unit ID
Inside DeviceStableId, the DeviceIdentity enum captures the actual hardware identifier. The from_parts helper implements a clear precedence rule: serial number preferred, unit ID as fallback【/cache/repos/github.com/AprilNEA/OpenLogi/master/crates/openlogi-core/src/device_order.rs#L68-L85】.
// Case-folded serial string (preferred)
DeviceIdentity::Serial("ABC12345".to_lowercase())
// 4-byte unit ID when serial unavailable
DeviceIdentity::UnitId([0x12, 0x34, 0x56, 0x78])
This design accommodates both premium devices with unique serials and mass-market hardware that only exposes a unit ID.
Three Key Methods for Different Use Cases
DeviceStableId exposes three distinct string representations, each serving a specific purpose in the codebase:
runtime_key(): Transient Session Key
Returns the full key including route-derived identity. Used for short-lived UI bookkeeping and internal device tracking. Not persisted to configuration files【/cache/repos/github.com/AprilNEA/OpenLogi/master/crates/openlogi-core/src/device_order.rs#L35-L45】.
route_key(): Configuration Link Key
Returns the path portion without device-specific identity. This string is the map key for the links table inside DeviceConfig.links【/cache/repos/github.com/AprilNEA/OpenLogi/master/crates/openlogi-core/src/device_order.rs#L67-L77】.
physical_key(): Persistent Device Identifier
Returns Option<PhysicalDeviceKey> — a stable, cross-session key only when the device has genuine physical identity (non-empty serial or non-zero unit ID). This becomes the top-level map key in per-device TOML configuration【/cache/repos/github.com/AprilNEA/OpenLogi/master/crates/openlogi-openlogi-core/src/device_order.rs#L88-L103】.
PhysicalDeviceKey can be round-tripped via PhysicalDeviceKey::parse.
Configuration Layout: How Keys Map to TOML
In crates/openlogi-core/src/config/device.rs, the DeviceConfig struct defines the persisted configuration:
pub struct DeviceConfig {
pub links: BTreeMap<String, LinkConfig>, // keyed by route_key()
// ... other fields
}
The links field uses route_key() as its map key【/cache/repos/github.com/AprilNEA/OpenLogi/master/crates/openlogi-core/src/config/device.rs#L71-L78】. Meanwhile, the entire DeviceConfig entry is keyed by PhysicalDeviceKey when the configuration file is serialized or deserialized.
This two-level key structure separates transport-specific link configuration from device identity, allowing a single physical device to maintain consistent settings across multiple connection methods.
Practical Examples
Bolt-Receiver Mouse
use openlogi_core::device_order::{DeviceStableId, DeviceIdentity};
use openlogi_core::hid::DeviceRoute;
let bolt_route = DeviceRoute::Bolt {
receiver_uid: "AABBCCDD".into(),
slot: 1,
};
let stable_id = DeviceStableId::from_parts(Some(&bolt_route), 1, None, [0; 4]);
assert_eq!(stable_id.runtime_key(), "receiver:aabbccdd:slot:1");
assert_eq!(stable_id.route_key(), "receiver:aabbccdd:slot:1");
assert!(stable_id.physical_key().is_some());
Direct-USB Keyboard with Serial
let direct_route = DeviceRoute::Direct {
vendor_id: 0x046d, // Logitech
product_id: 0xc08d, // G915 TKL
};
let stable_id = DeviceStableId::from_parts(
Some(&direct_route),
0xff, // unused slot for direct
Some("ABC12345"),
[0; 4],
);
assert_eq!(stable_id.runtime_key(), "direct:046d:c08d:serial:abc12345");
assert_eq!(stable_id.route_key(), "direct:046d:c08d");
assert_eq!(
stable_id.physical_key().map(|k| k.into_string()),
Some("direct:046d:c08d:serial:abc12345".to_string())
);
Using route_key in Configuration
let config_key = stable_id.route_key(); // "direct:046d:c08d"
// Insert into DeviceConfig.links map
device_config.links.insert(config_key, link_config);
Summary
DeviceStableIdcombines transport route and hardware identity to create deterministic device identifiers in OpenLogi.- Four route variants (Bolt, Direct, RawHid, Unknown) cover all connection scenarios.
- Three key methods serve distinct purposes:
runtime_key()for session tracking,route_key()for link configuration maps, andphysical_key()for persistent device identity. - Configuration uses a two-level key structure:
PhysicalDeviceKeyfor top-level device entries androute_key()for per-link settings insideDeviceConfig.links【/cache/repos/github.com/AprilNEA/OpenLogi/master/crates/openlogi-core/src/config/device.rs#L71-L78】. - The implementation guarantees configuration persistence across sleep/wake, transport changes, and system restarts.
Frequently Asked Questions
What happens if a device has no serial number?
OpenLogi falls back to the 4-byte unit ID. If both are absent, the device can still be managed at runtime but cannot produce a PhysicalDeviceKey, meaning its configuration won't persist across restarts. This is handled by DeviceIdentity::from_parts in device_order.rs.
Can the same physical device have multiple configuration entries?
No. The PhysicalDeviceKey is designed to be unique per physical device. However, a single device can have multiple link configurations keyed by different route_key() values if it supports multiple connection methods (e.g., both Bluetooth and USB receiver).
How does OpenLogi handle a device switching from USB to receiver?
The physical_key() remains stable because it incorporates the underlying device identity (serial or unit ID). Only the route_key() changes to reflect the new transport path, so link-specific settings may differ per connection method while core device settings persist.
Where is the configuration actually stored on disk?
Per-device TOML files use the PhysicalDeviceKey string as their identifier. The exact storage path is determined by the configuration backend, but the key format follows the direct:046d:c08d:serial:abc12345 pattern shown in the examples above.
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 →