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, Copy, Serialize)]
77pub struct DeviceModelInfo {
78 pub entity_count: u8,
79 pub unit_id: [u8; 4],
80 pub transports: DeviceTransports,
81 pub model_ids: [u16; 3],
82 pub extended_model_id: u8,
83}
84
85impl DeviceModelInfo {
86 /// Stable identifier used to key per-device configuration (button
87 /// bindings, etc.) and to look up assets in the OpenLogi asset registry.
88 ///
89 /// Format: `{extended_model_id:x}{model_ids[0]:04x}` — the same string
90 /// the depot `manifest.json` uses for its `modelId` field. Example: an
91 /// MX Master 4 with `extended_model_id = 0x02` and `model_ids[0] = 0xb042`
92 /// resolves to `"2b042"`.
93 #[must_use]
94 pub fn config_key(&self) -> String {
95 format!("{:x}{:04x}", self.extended_model_id, self.model_ids[0])
96 }
97}
98
99/// Mirror of hidpp's `DeviceTransport` bitfield — one bool per protocol the
100/// device firmware exposes. The shape is dictated by HID++ feature 0x0003;
101/// a state machine doesn't fit since a single device can announce multiple
102/// transports simultaneously.
103#[allow(
104 clippy::struct_excessive_bools,
105 reason = "bitfield mirroring HID++ DeviceInformation; transports are independent flags"
106)]
107#[derive(Debug, Clone, Copy, Default, Serialize)]
108pub struct DeviceTransports {
109 pub usb: bool,
110 pub equad: bool,
111 pub btle: bool,
112 pub bluetooth: bool,
113}
114
115#[derive(Debug, Clone, Serialize)]
116pub struct PairedDevice {
117 /// Receiver-assigned slot (1..=6 for Bolt).
118 pub slot: u8,
119 pub codename: Option<String>,
120 /// Wireless product ID. `None` for offline / unreachable devices on hidpp 0.2.
121 pub wpid: Option<u16>,
122 pub kind: DeviceKind,
123 pub online: bool,
124 pub battery: Option<BatteryInfo>,
125 /// Output of HID++ feature 0x0003 — populated for online devices that
126 /// expose the feature. Drives asset-registry lookups in the GUI.
127 pub model_info: Option<DeviceModelInfo>,
128}
129
130#[derive(Debug, Clone, Serialize)]
131pub struct DeviceInventory {
132 pub receiver: ReceiverInfo,
133 pub paired: Vec<PairedDevice>,
134}