Skip to main content

hid_types/id/
unit.rs

1//! Types used in Unit items.
2
3use num_enum::{IntoPrimitive, TryFromPrimitive};
4
5use crate::Ident;
6
7/// Identifiers for some known Unit values.
8///
9/// Due to the complexity of the HID unit system, this enum does not attempt
10/// to represent all the possible unit values.
11///
12/// Unit values are 32 bits long, split into 8 nibbles (4-bit fields).
13///
14/// The lowest nibble (bits 3..0) specify a unit system:
15///
16/// - `1`: SI Linear
17/// - `2`: SI Rotation
18/// - `3`: English Linear
19/// - `4`: English Rotation
20///
21/// The other 5 nibbles are dedicated to individual unit categories:
22///
23/// - nibble 1 (bits 7..4): Length (Centimeter, Radian, Inch, or Degree)
24/// - nibble 2 (bits 11..8): Mass (Gram or Slug)
25/// - nibble 3 (bits ..12): Time (Seconds in all systems)
26/// - nibble 4 (bits ..16): Temperature (Kelvin or Fahrenheit)
27/// - nibble 5 (bits ..20): Current (Ampere in all systems)
28/// - nibble 6 (bits ..24): Luminous Intensity (Candela in all systems)
29/// - nibble 7 (bits 31..28): reserved
30///
31/// The value in each nibble is a signed integer from -8 to +7, and that integer
32/// is the exponent attached to each unit. For example:
33///
34/// - `0x23` is Length^2 in the English Linear system, or Inch^2.
35/// - `0x101` is Mass^1 in the SI Linear system, or Gram.
36/// - `0xF011` is Time^-1 * Length^1, or Centimeters/Second.
37///
38/// See [Device Class Definition for Human Interface Devices](https://www.usb.org/document-library/device-class-definition-hid-111),
39/// section 6.2.2.7 for more details.
40///
41#[derive(Clone, Copy, Debug, PartialEq, IntoPrimitive, TryFromPrimitive)]
42#[repr(u32)]
43#[non_exhaustive]
44pub enum KnownUnit {
45    /// No unit.
46    None = 0x0,
47    /// SI unit of length.
48    // Length^1 in SI Linear (1)
49    Centimeter = 0x1_1,
50    /// SI unit of rotation.
51    // Length^1 in SI Rotation (2)
52    Radian = 0x1_2,
53    /// English unit of length.
54    // Length^1 in English Linear (3)
55    Inch = 0x1_3,
56    /// English unit of rotation.
57    // Length^1 in English Rotation(4)
58    Degree = 0x1_4,
59    /// SI unit of mass.
60    // Mass^1 in SI Linear
61    Gram = 0x1_0_1,
62    /// SI unit of time.
63    // Time^1 in SI Linear
64    Seconds = 0x1_0_0_1,
65    // "Seconds" can be expressed in any unit system.
66    #[doc(hidden)]
67    _SecondsSystem2 = 0x1_0_0_2,
68    #[doc(hidden)]
69    _SecondsSystem3 = 0x1_0_0_3,
70    #[doc(hidden)]
71    _SecondsSystem4 = 0x1_0_0_4,
72    /// SI unit of frequency, Hertz aka second⁻¹)
73    // Time^-1 in SI Linear
74    Hertz = 0xF_0_0_1,
75    /// SI unit of velocity (centimeters/second).
76    // Length / Time in SI Linear
77    SiVelocity = 0xF_0_1_1,
78    /// SI unit of momentum (gram·centimeter/second).
79    // Mass * Length / Time in SI Linear
80    SiMomentum = 0xF_1_1_1,
81    /// SI unit of acceleration (centimeters/second²).
82    // Length / Time^2 in SI Linear
83    SiAcceleration = 0xE_0_1_1,
84    /// SI unit of force (gram·centimeter/second²).
85    ///
86    /// Divide by 10⁵ to get Newtons (kg·m/s²).
87    // Mass * Length / Time^2 in SI Linear
88    SiForce = 0xE_1_1_1,
89    /// SI unit of energy (gram·centimeter²/second²).
90    ///
91    /// Divide by 10⁷ to get Joules (kg·m²/s²).
92    // Mass * Length^2 / Time^2 in SI Linear
93    SiEnergy = 0xE_1_2_1,
94    /// SI unit of Angular Velocity (radians/second).
95    // Length / Time^2 in SI Rotation
96    SiAngularVelocity = 0xF_0_1_2,
97    /// English unit of Angular Acceleration (degrees/second).
98    // Length / Time^2 in SI Rotation
99    DegAngularVelocity = 0xF_0_1_4,
100    /// SI unit of Angular Acceleration (radians/second²).
101    // Length / Time^2 in SI Rotation
102    SiAngularAcceleration = 0xE_0_1_2,
103    /// English unit of Angular Acceleration (degrees/second²).
104    // Length / Time^2 in SI Rotation
105    DegAngularAcceleration = 0xE_0_1_4,
106    /// SI unit of temperature.
107    Kelvin = 0x1_0_0_0_1,
108    /// English unit of temperature.
109    DegreesFahrenheit = 0x1_0_0_0_3,
110    /// SI unit of current (amperes).
111    Current = 0x1_0_0_0_0_1,
112    /// SI unit for amp·seconds.
113    ///
114    /// This unit can be used as "battery capacity". See
115    /// [Usage Tables for HID Power Devices](https://www.usb.org/document-library/usage-tables-hid-power-devices-11).
116    AmpSeconds = 0x1_0_1_0_0_1,
117    /// SI unit of Voltage (gram·centimeter²/amp·seconds³).
118    ///
119    /// Divide by 10⁷ to get Volts (kg·m²/amp·seconds³).
120    // (Mass * Length^2) / (Time^3 * Current)
121    Voltage = 0xF_0_D_1_2_1,
122    /// SI unit of power (gram-centimeter²/seconds³).
123    ///
124    /// Divide by 10⁷ to get Watts or Volt-Amps (kg·m²/s³).
125    // Mass * Length^2 / Time^3
126    SiPower = 0xD_1_2_1,
127}
128
129/// A __Unit__ value.
130pub type Unit = Ident<KnownUnit, u32>;
131
132/// A unit exponent value.
133///
134/// The HID specification does not specify any limits on the range of this
135/// value. However, some developers have concluded that this value may only
136/// be 4 bits wide, leading to the confusing situation of a Unit item with
137/// a value of e.g. `0x0F`, which was meant to represent `-1`.
138///
139/// To work around this confusion, there is a method [`remap_as_i4()`][UnitExponent::remap_as_i4]
140/// that will attempt to undo the 4-bit-truncation, but may garble
141/// some values.
142///
143/// Note that this is encoded to a byte with zeros in the upper 4 bits,
144/// so `-1` will be stored as `0x0F`.
145#[derive(Clone, Copy, Debug, PartialEq)]
146pub struct UnitExponent(pub i32);
147
148impl core::ops::Deref for UnitExponent {
149    type Target = i32;
150
151    fn deref(&self) -> &Self::Target {
152        &self.0
153    }
154}
155
156impl From<i32> for UnitExponent {
157    fn from(value: i32) -> Self {
158        UnitExponent(value)
159    }
160}
161
162impl core::ops::DerefMut for UnitExponent {
163    fn deref_mut(&mut self) -> &mut Self::Target {
164        &mut self.0
165    }
166}
167
168impl UnitExponent {
169    /// The raw value stored in the Unit Exponent item.
170    ///
171    /// Due to odd encodings on some devices, the value returned by [`remap_as_i4()`][Self::remap_as_i4]
172    /// may be the intended value.
173    pub fn raw_value(self) -> i32 {
174        self.0
175    }
176
177    /// Attempt to remap the value, assuming it may have been truncated to 4 bits.
178    ///
179    /// This is always correct for inputs in the range `[i32::MIN, 7]` or `[16, i32::MAX]`.
180    /// It remaps the range `[8, 15]` to `[-8, -1]`. This could be wrong!
181    /// It depends on what the device manufacturer meant.
182    ///
183    /// If the upper bits are all zeros, then this will return `Some`
184    /// with a value that is in the range `[-8, +7]`.
185    ///
186    /// Otherwise, this will return the original value.
187    ///
188    /// See the [`UnitExponent`] docs for an explanation why this exists.
189    pub fn remap_as_i4(self) -> i32 {
190        let upper_bits = (self.0 as u32) & 0xFFFF_FFF0;
191        if upper_bits == 0 {
192            // 'as' truncates the upper 24 bits.
193            // shift-left, shift-right will cause sign
194            // extension from bit 3 to bits 4..31
195            self.0 << 28 >> 28
196        } else {
197            self.0
198        }
199    }
200}
201
202#[cfg(feature = "std")]
203mod std_impls {
204    use std::fmt::{self, Display};
205
206    use super::*;
207
208    impl Display for UnitExponent {
209        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210            let raw = self.raw_value();
211            let remap = self.remap_as_i4();
212            if remap == raw {
213                write!(f, "{raw}")
214            } else {
215                write!(f, "{raw} (possibly intended {remap})")
216            }
217        }
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn test_i4() {
227        assert_eq!(UnitExponent(0).remap_as_i4(), 0);
228        assert_eq!(UnitExponent(1).remap_as_i4(), 1);
229        assert_eq!(UnitExponent(7).remap_as_i4(), 7);
230        assert_eq!(UnitExponent(-1).remap_as_i4(), -1);
231        assert_eq!(UnitExponent(-8).remap_as_i4(), -8);
232        assert_eq!(UnitExponent(-9).remap_as_i4(), -9);
233
234        // These values are potentially wrong, but there's no way
235        // to tell.
236        assert_eq!(UnitExponent(8).remap_as_i4(), -8);
237        assert_eq!(UnitExponent(15).remap_as_i4(), -1);
238
239        // Large positive or negative values should always pass through.
240        assert_eq!(UnitExponent(16).remap_as_i4(), 16);
241        assert_eq!(UnitExponent(-16).remap_as_i4(), -16);
242        assert_eq!(UnitExponent(i32::MAX).remap_as_i4(), i32::MAX);
243        assert_eq!(UnitExponent(i32::MIN).remap_as_i4(), i32::MIN);
244    }
245}