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}