Skip to main content

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}