Coverage for src / bluetooth_sig / gatt / characteristics / glucose_feature.py: 89%
101 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +0000
1"""Glucose Feature characteristic implementation."""
3from __future__ import annotations
5from enum import IntFlag
7import msgspec
9from ..context import CharacteristicContext
10from .base import BaseCharacteristic
11from .utils import DataParser
14class GlucoseFeatures(IntFlag):
15 """Glucose Feature flags according to Bluetooth SIG specification."""
17 LOW_BATTERY_DETECTION = 0x0001
18 SENSOR_MALFUNCTION_DETECTION = 0x0002
19 SENSOR_SAMPLE_SIZE = 0x0004
20 SENSOR_STRIP_INSERTION_ERROR = 0x0008
21 SENSOR_STRIP_TYPE_ERROR = 0x0010
22 SENSOR_RESULT_HIGH_LOW = 0x0020
23 SENSOR_TEMPERATURE_HIGH_LOW = 0x0040
24 SENSOR_READ_INTERRUPT = 0x0080
25 GENERAL_DEVICE_FAULT = 0x0100
26 TIME_FAULT = 0x0200
27 MULTIPLE_BOND_SUPPORT = 0x0400
29 def __str__(self) -> str:
30 """Get human-readable description for a feature."""
31 descriptions = {
32 self.LOW_BATTERY_DETECTION.value: "Low Battery Detection During Measurement Supported",
33 self.SENSOR_MALFUNCTION_DETECTION.value: "Sensor Malfunction Detection Supported",
34 self.SENSOR_SAMPLE_SIZE.value: "Sensor Sample Size Supported",
35 self.SENSOR_STRIP_INSERTION_ERROR.value: "Sensor Strip Insertion Error Detection Supported",
36 self.SENSOR_STRIP_TYPE_ERROR.value: "Sensor Strip Type Error Detection Supported",
37 self.SENSOR_RESULT_HIGH_LOW.value: "Sensor Result High-Low Detection Supported",
38 self.SENSOR_TEMPERATURE_HIGH_LOW.value: "Sensor Temperature High-Low Detection Supported",
39 self.SENSOR_READ_INTERRUPT.value: "Sensor Read Interrupt Detection Supported",
40 self.GENERAL_DEVICE_FAULT.value: "General Device Fault Supported",
41 self.TIME_FAULT.value: "Time Fault Supported",
42 self.MULTIPLE_BOND_SUPPORT.value: "Multiple Bond Supported",
43 }
44 return descriptions.get(self.value, f"Reserved feature bit {self.value:04x}")
46 def get_enabled_features(self) -> list[GlucoseFeatures]:
47 """Get list of human-readable enabled features."""
48 enabled = list[GlucoseFeatures]()
49 for feature in GlucoseFeatures:
50 if self & feature:
51 enabled.append(feature)
52 return enabled
55class GlucoseFeatureData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods,too-many-instance-attributes
56 """Parsed data from Glucose Feature characteristic."""
58 features_bitmap: GlucoseFeatures
59 low_battery_detection: bool
60 sensor_malfunction_detection: bool
61 sensor_sample_size: bool
62 sensor_strip_insertion_error: bool
63 sensor_strip_type_error: bool
64 sensor_result_high_low: bool
65 sensor_temperature_high_low: bool
66 sensor_read_interrupt: bool
67 general_device_fault: bool
68 time_fault: bool
69 multiple_bond_support: bool
70 enabled_features: tuple[GlucoseFeatures, ...]
71 feature_count: int
74class GlucoseFeatureCharacteristic(BaseCharacteristic[GlucoseFeatureData]):
75 """Glucose Feature characteristic (0x2A51).
77 Used to expose the supported features of a glucose monitoring
78 device. Indicates which optional fields and capabilities are
79 available.
80 """
82 _characteristic_name: str = "Glucose Feature"
83 _manual_unit: str = "bitmap" # Feature bitmap
85 expected_length: int = 2
86 min_length: int = 2 # Features(2) fixed length
87 max_length: int = 2 # Features(2) fixed length
88 allow_variable_length: bool = False # Fixed length
90 def _decode_value( # pylint: disable=too-many-locals
91 self, data: bytearray, ctx: CharacteristicContext | None = None
92 ) -> GlucoseFeatureData:
93 """Parse glucose feature data according to Bluetooth specification.
95 Format: Features(2) - 16-bit bitmap indicating supported features
97 Args:
98 data: Raw bytearray from BLE characteristic
99 ctx: Optional context information
101 Returns:
102 GlucoseFeatureData containing parsed feature bitmap and details
104 Raises:
105 ValueError: If data format is invalid
107 """
108 features_bitmap = DataParser.parse_int16(data, 0, signed=False)
109 features = GlucoseFeatures(features_bitmap)
111 # Extract individual feature flags using enum
112 low_battery_detection = bool(features & GlucoseFeatures.LOW_BATTERY_DETECTION)
113 sensor_malfunction_detection = bool(features & GlucoseFeatures.SENSOR_MALFUNCTION_DETECTION)
114 sensor_sample_size = bool(features & GlucoseFeatures.SENSOR_SAMPLE_SIZE)
115 sensor_strip_insertion_error = bool(features & GlucoseFeatures.SENSOR_STRIP_INSERTION_ERROR)
116 sensor_strip_type_error = bool(features & GlucoseFeatures.SENSOR_STRIP_TYPE_ERROR)
117 sensor_result_high_low = bool(features & GlucoseFeatures.SENSOR_RESULT_HIGH_LOW)
118 sensor_temperature_high_low = bool(features & GlucoseFeatures.SENSOR_TEMPERATURE_HIGH_LOW)
119 sensor_read_interrupt = bool(features & GlucoseFeatures.SENSOR_READ_INTERRUPT)
120 general_device_fault = bool(features & GlucoseFeatures.GENERAL_DEVICE_FAULT)
121 time_fault = bool(features & GlucoseFeatures.TIME_FAULT)
122 multiple_bond_support = bool(features & GlucoseFeatures.MULTIPLE_BOND_SUPPORT)
124 # Get enabled features using the enum method
125 enabled_features = tuple(features.get_enabled_features())
127 return GlucoseFeatureData(
128 features_bitmap=features,
129 low_battery_detection=low_battery_detection,
130 sensor_malfunction_detection=sensor_malfunction_detection,
131 sensor_sample_size=sensor_sample_size,
132 sensor_strip_insertion_error=sensor_strip_insertion_error,
133 sensor_strip_type_error=sensor_strip_type_error,
134 sensor_result_high_low=sensor_result_high_low,
135 sensor_temperature_high_low=sensor_temperature_high_low,
136 sensor_read_interrupt=sensor_read_interrupt,
137 general_device_fault=general_device_fault,
138 time_fault=time_fault,
139 multiple_bond_support=multiple_bond_support,
140 enabled_features=enabled_features,
141 feature_count=len(enabled_features),
142 )
144 def _encode_value(self, data: GlucoseFeatureData) -> bytearray:
145 """Encode GlucoseFeatureData back to bytes.
147 Args:
148 data: GlucoseFeatureData instance to encode
150 Returns:
151 Encoded bytes representing the glucose features
153 """
154 # Reconstruct the features bitmap from individual flags using enum values
155 features_bitmap = 0
156 if data.low_battery_detection:
157 features_bitmap |= GlucoseFeatures.LOW_BATTERY_DETECTION
158 if data.sensor_malfunction_detection:
159 features_bitmap |= GlucoseFeatures.SENSOR_MALFUNCTION_DETECTION
160 if data.sensor_sample_size:
161 features_bitmap |= GlucoseFeatures.SENSOR_SAMPLE_SIZE
162 if data.sensor_strip_insertion_error:
163 features_bitmap |= GlucoseFeatures.SENSOR_STRIP_INSERTION_ERROR
164 if data.sensor_strip_type_error:
165 features_bitmap |= GlucoseFeatures.SENSOR_STRIP_TYPE_ERROR
166 if data.sensor_result_high_low:
167 features_bitmap |= GlucoseFeatures.SENSOR_RESULT_HIGH_LOW
168 if data.sensor_temperature_high_low:
169 features_bitmap |= GlucoseFeatures.SENSOR_TEMPERATURE_HIGH_LOW
170 if data.sensor_read_interrupt:
171 features_bitmap |= GlucoseFeatures.SENSOR_READ_INTERRUPT
172 if data.general_device_fault:
173 features_bitmap |= GlucoseFeatures.GENERAL_DEVICE_FAULT
174 if data.time_fault:
175 features_bitmap |= GlucoseFeatures.TIME_FAULT
176 if data.multiple_bond_support:
177 features_bitmap |= GlucoseFeatures.MULTIPLE_BOND_SUPPORT
179 # Pack as little-endian 16-bit integer
180 return DataParser.encode_int16(features_bitmap, signed=False)
182 def get_feature_description(self, feature_bit: int) -> str:
183 """Get description for a specific feature bit.
185 Args:
186 feature_bit: Bit position (0-15)
188 Returns:
189 Human-readable description of the feature
191 """
192 # Accept either a flag value (power-of-two) or a bit index
193 if feature_bit <= 0:
194 return f"Reserved feature bit {feature_bit}"
196 # If caller passed a power-of-two flag value (e.g., 0x0001), use it
197 if feature_bit & (feature_bit - 1) == 0:
198 feature_value = feature_bit
199 else:
200 # Otherwise treat as bit index (0..15)
201 feature_value = 1 << feature_bit
203 try:
204 feature = GlucoseFeatures(feature_value)
205 return str(feature)
206 except ValueError:
207 return f"Reserved feature bit {feature_bit}"