Coverage for src / bluetooth_sig / types / units.py: 92%

62 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 20:14 +0000

1"""Domain enums for measurement units in decoded characteristic data. 

2 

3These enums provide type-safe representations of measurement choices 

4(e.g., Celsius vs Fahrenheit, metric vs imperial) used in decoded 

5characteristic return values. They enable static type checking and 

6IDE autocompletion for unit-aware data. 

7 

8Note: These are distinct from UnitInfo in registry/uuids/units.py which 

9provides Bluetooth SIG metadata (UUID, name, symbol) loaded from YAML. 

10The symbol values here intentionally match the registry symbols for 

11consistency, but serve different purposes: 

12- Domain enums: Type hints for decoded data (e.g., `unit: PressureUnit`) 

13- Registry UnitInfo: UUID resolution and metadata lookup 

14 

15Reference: https://www.bipm.org/en/measurement-units 

16""" 

17 

18from __future__ import annotations 

19 

20from enum import Enum 

21 

22 

23class MeasurementSystem(Enum): 

24 """Measurement system for body composition and weight data.""" 

25 

26 METRIC = "metric" 

27 IMPERIAL = "imperial" 

28 

29 

30class WeightUnit(Enum): 

31 """Units for weight/mass measurements.""" 

32 

33 KG = "kg" 

34 LB = "lb" 

35 

36 

37class HeightUnit(Enum): 

38 """Units for height measurements.""" 

39 

40 METERS = "meters" 

41 INCHES = "inches" 

42 

43 

44class TemperatureUnit(Enum): 

45 """Units for temperature measurements.""" 

46 

47 CELSIUS = "°C" 

48 FAHRENHEIT = "°F" 

49 

50 

51class GlucoseConcentrationUnit(Enum): 

52 """Units for glucose concentration measurements.""" 

53 

54 MG_DL = "mg/dL" 

55 MMOL_L = "mmol/L" 

56 

57 

58class PressureUnit(Enum): 

59 """Units for pressure measurements.""" 

60 

61 KPA = "kPa" 

62 MMHG = "mmHg" 

63 

64 

65class ElectricalUnit(Enum): 

66 """Units for electrical measurements.""" 

67 

68 VOLTS = "V" 

69 AMPS = "A" 

70 HERTZ = "Hz" 

71 DBM = "dBm" 

72 

73 

74class ConcentrationUnit(Enum): 

75 """Units for concentration measurements.""" 

76 

77 MICROGRAMS_PER_CUBIC_METER = "µg/m³" 

78 PARTS_PER_MILLION = "ppm" 

79 PARTS_PER_BILLION = "ppb" 

80 KILOGRAMS_PER_CUBIC_METER = "kg/m³" 

81 GRAINS_PER_CUBIC_METER = "grains/m³" 

82 

83 

84class PercentageUnit(Enum): 

85 """Units for percentage measurements.""" 

86 

87 PERCENT = "%" 

88 

89 

90class AngleUnit(Enum): 

91 """Units for angle measurements.""" 

92 

93 DEGREES = "°" 

94 

95 

96class SoundUnit(Enum): 

97 """Units for sound measurements.""" 

98 

99 DECIBELS_SPL = "dB SPL" 

100 

101 

102class LengthUnit(Enum): 

103 """Units for length measurements.""" 

104 

105 MILLIMETERS = "mm" 

106 METERS = "m" 

107 INCHES = "'" 

108 

109 

110class PhysicalUnit(Enum): 

111 """Units for physical measurements.""" 

112 

113 TESLA = "T" 

114 

115 

116class SpecialValueType(Enum): 

117 """Standard Bluetooth SIG special value categories. 

118 

119 These represent sentinel values in characteristic data that indicate 

120 the measurement is not a normal reading. GSS YAML files define these 

121 using patterns like "value is not known" or "value is not valid". 

122 """ 

123 

124 UNKNOWN = "unknown" # Value not known/not available (most common) 

125 INVALID = "invalid" # Value not valid for current state 

126 OVERFLOW = "overflow" # Value exceeds maximum representable 

127 UNDERFLOW = "underflow" # Value below minimum representable 

128 OUT_OF_RANGE = "out_of_range" # Sensor out of measurement range 

129 

130 

131def classify_special_value(meaning: str) -> SpecialValueType: 

132 """Classify a GSS meaning string into a standard category. 

133 

134 Parses the human-readable meaning from GSS YAML special value definitions 

135 and maps it to a SpecialValueType enum. 

136 

137 Args: 

138 meaning: Human-readable meaning from GSS (e.g., "value is not known") 

139 

140 Returns: 

141 The appropriate SpecialValueType category. 

142 """ 

143 meaning_lower = meaning.lower() 

144 

145 if "not known" in meaning_lower or "unknown" in meaning_lower: 

146 return SpecialValueType.UNKNOWN 

147 if "not valid" in meaning_lower or "invalid" in meaning_lower: 

148 return SpecialValueType.INVALID 

149 if "or greater" in meaning_lower or "overflow" in meaning_lower: 

150 return SpecialValueType.OVERFLOW 

151 if "less than" in meaning_lower or "underflow" in meaning_lower: 

152 return SpecialValueType.UNDERFLOW 

153 if "out of range" in meaning_lower: 

154 return SpecialValueType.OUT_OF_RANGE 

155 

156 # Default fallback - most special values indicate unknown 

157 return SpecialValueType.UNKNOWN