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, 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}