openlogi_core/device.rs
1//! Serializable device-model types.
2//!
3//! These mirror the HID++ types from the `hidpp` crate but live here so the
4//! CLI and any future GUI can depend on them without dragging in the protocol
5//! crate or its async transport.
6
7use serde::Serialize;
8
9/// What a paired peripheral is. Mirrors `hidpp::receiver::bolt::BoltDeviceKind`
10/// but is owned by us so consumers don't depend on `hidpp`.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
12#[serde(rename_all = "lowercase")]
13pub enum DeviceKind {
14 Mouse,
15 Keyboard,
16 Numpad,
17 Presenter,
18 Remote,
19 Trackball,
20 Touchpad,
21 Tablet,
22 Gamepad,
23 Joystick,
24 Headset,
25 Unknown,
26}
27
28/// Coarse battery bucket reported by the device firmware.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
30#[serde(rename_all = "lowercase")]
31pub enum BatteryLevel {
32 Critical,
33 Low,
34 Good,
35 Full,
36 Unknown,
37}
38
39/// Charging state. Mirrors `hidpp 0.2`'s `BatteryStatus` plus `Unknown` for
40/// values added in future protocol versions.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
42#[serde(rename_all = "snake_case")]
43pub enum BatteryStatus {
44 Discharging,
45 Charging,
46 ChargingSlow,
47 Full,
48 Error,
49 Unknown,
50}
51
52#[derive(Debug, Clone, Serialize)]
53pub struct BatteryInfo {
54 pub percentage: u8,
55 pub level: BatteryLevel,
56 pub status: BatteryStatus,
57}
58
59#[derive(Debug, Clone, Serialize)]
60pub struct ReceiverInfo {
61 pub name: String,
62 pub vendor_id: u16,
63 pub product_id: u16,
64 pub unique_id: Option<String>,
65}
66
67/// HID++ `DeviceInformation` (feature 0x0003) snapshot used to identify a
68/// device against external registries (e.g. the OpenLogi asset index).
69///
70/// `model_ids` is the per-transport PID array reported by the firmware,
71/// ordered to match the transports flagged in [`Self::transports`] (USB,
72/// eQuad, BTLE, Bluetooth) — slots that aren't enabled stay `0`. The Logi
73/// Options+ asset registry's `modelId` (e.g. `"6b023"`) is the concatenation
74/// of an extended-model byte and one of these PIDs, so callers usually want
75/// to format `extended_model_id` + `model_ids[N]` to match.
76#[derive(Debug, Clone, Serialize)]
77pub struct DeviceModelInfo {
78 pub entity_count: u8,
79 /// HID++ DeviceInformation serial number, when the device supports the
80 /// optional serial-number function.
81 pub serial_number: Option<String>,
82 pub unit_id: [u8; 4],
83 pub transports: DeviceTransports,
84 pub model_ids: [u16; 3],
85 pub extended_model_id: u8,
86}
87
88impl DeviceModelInfo {
89 /// Stable identifier used to key per-device configuration (button
90 /// bindings, etc.) and to look up assets in the OpenLogi asset registry.
91 ///
92 /// Format: `{extended_model_id:x}{model_ids[0]:04x}` — the same string
93 /// the depot `manifest.json` uses for its `modelId` field. Example: an
94 /// MX Master 4 with `extended_model_id = 0x02` and `model_ids[0] = 0xb042`
95 /// resolves to `"2b042"`.
96 #[must_use]
97 pub fn config_key(&self) -> String {
98 format!("{:x}{:04x}", self.extended_model_id, self.model_ids[0])
99 }
100}
101
102/// Mirror of hidpp's `DeviceTransport` bitfield — one bool per protocol the
103/// device firmware exposes. The shape is dictated by HID++ feature 0x0003;
104/// a state machine doesn't fit since a single device can announce multiple
105/// transports simultaneously.
106#[allow(
107 clippy::struct_excessive_bools,
108 reason = "bitfield mirroring HID++ DeviceInformation; transports are independent flags"
109)]
110#[derive(Debug, Clone, Copy, Default, Serialize)]
111pub struct DeviceTransports {
112 pub usb: bool,
113 pub equad: bool,
114 pub btle: bool,
115 pub bluetooth: bool,
116}
117
118#[derive(Debug, Clone, Serialize)]
119pub struct PairedDevice {
120 /// Receiver-assigned slot (1..=6 for Bolt).
121 pub slot: u8,
122 pub codename: Option<String>,
123 /// Wireless product ID. `None` for offline / unreachable devices on hidpp 0.2.
124 pub wpid: Option<u16>,
125 pub kind: DeviceKind,
126 pub online: bool,
127 pub battery: Option<BatteryInfo>,
128 /// Output of HID++ feature 0x0003 — populated for online devices that
129 /// expose the feature. Drives asset-registry lookups in the GUI.
130 pub model_info: Option<DeviceModelInfo>,
131}
132
133#[derive(Debug, Clone, Serialize)]
134pub struct DeviceInventory {
135 pub receiver: ReceiverInfo,
136 pub paired: Vec<PairedDevice>,
137}