Coverage for src / bluetooth_sig / gatt / characteristics / enhanced_intermediate_cuff_pressure.py: 100%
78 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
1"""Enhanced Intermediate Cuff Pressure characteristic implementation.
3Implements the Enhanced Intermediate Cuff Pressure characteristic (0x2B35).
4Reports a single intermediate cuff pressure reading (medfloat16) during
5an ongoing measurement, with enhanced optional fields matching the Enhanced
6Blood Pressure Measurement pattern.
8Flag-bit assignments (from GSS YAML):
9 Bit 0: Units (0=mmHg, 1=kPa)
10 Bit 1: Time Stamp present (uint32 seconds since epoch)
11 Bit 2: Pulse Rate present (medfloat16)
12 Bit 3: User ID present (uint8)
13 Bit 4: Measurement Status present (boolean[16])
14 Bit 5: User Facing Time present (uint32 seconds since epoch)
15 Bit 6: Epoch Start 2000 (0=1900, 1=2000)
16 Bit 7: Reserved
18References:
19 Bluetooth SIG Blood Pressure Service 1.1
20 org.bluetooth.characteristic.enhanced_intermediate_cuff_pressure (GSS YAML)
21"""
23from __future__ import annotations
25import msgspec
27from bluetooth_sig.types.units import PressureUnit
29from ..context import CharacteristicContext
30from .base import BaseCharacteristic
31from .blood_pressure_measurement import BloodPressureMeasurementStatus
32from .enhanced_blood_pressure_measurement import EnhancedBloodPressureFlags, EpochYear
33from .utils import DataParser, IEEE11073Parser
36class EnhancedIntermediateCuffPressureData(msgspec.Struct, frozen=True, kw_only=True):
37 """Parsed data from Enhanced Intermediate Cuff Pressure characteristic.
39 Attributes:
40 flags: Raw 8-bit flags field.
41 cuff_pressure: Current intermediate cuff pressure value.
42 unit: Pressure unit (mmHg or kPa).
43 timestamp: Seconds since epoch start. None if absent.
44 pulse_rate: Pulse rate in BPM. None if absent.
45 user_id: User ID (0-255). None if absent.
46 measurement_status: 16-bit measurement status flags. None if absent.
47 user_facing_time: User-facing time in seconds since epoch. None if absent.
48 epoch_year: Epoch start year (1900 or 2000).
50 """
52 flags: EnhancedBloodPressureFlags
53 cuff_pressure: float
54 unit: PressureUnit
55 timestamp: int | None = None
56 pulse_rate: float | None = None
57 user_id: int | None = None
58 measurement_status: BloodPressureMeasurementStatus | None = None
59 user_facing_time: int | None = None
60 epoch_year: EpochYear = EpochYear.EPOCH_1900
63class EnhancedIntermediateCuffPressureCharacteristic(
64 BaseCharacteristic[EnhancedIntermediateCuffPressureData],
65):
66 """Enhanced Intermediate Cuff Pressure characteristic (0x2B35).
68 Reports a single intermediate cuff pressure reading during an ongoing
69 blood pressure measurement, with enhanced timestamps and epoch flag.
70 """
72 expected_type = EnhancedIntermediateCuffPressureData
73 min_length: int = 3 # flags(1) + cuff_pressure(2)
74 allow_variable_length: bool = True
76 def _decode_value(
77 self,
78 data: bytearray,
79 ctx: CharacteristicContext | None = None,
80 *,
81 validate: bool = True,
82 ) -> EnhancedIntermediateCuffPressureData:
83 """Parse Enhanced Intermediate Cuff Pressure from raw BLE bytes.
85 Args:
86 data: Raw bytearray from BLE characteristic.
87 ctx: Optional context (unused).
88 validate: Whether to validate ranges.
90 Returns:
91 EnhancedIntermediateCuffPressureData with all present fields.
93 """
94 flags = EnhancedBloodPressureFlags(data[0])
95 unit = PressureUnit.KPA if flags & EnhancedBloodPressureFlags.UNITS_KPA else PressureUnit.MMHG
97 # Mandatory single cuff pressure value (medfloat16)
98 cuff_pressure = IEEE11073Parser.parse_sfloat(data, 1)
99 offset = 3
101 epoch_year = (
102 EpochYear.EPOCH_2000 if flags & EnhancedBloodPressureFlags.EPOCH_START_2000 else EpochYear.EPOCH_1900
103 )
105 timestamp: int | None = None
106 if flags & EnhancedBloodPressureFlags.TIMESTAMP_PRESENT:
107 timestamp = DataParser.parse_int32(data, offset, signed=False)
108 offset += 4
110 pulse_rate: float | None = None
111 if flags & EnhancedBloodPressureFlags.PULSE_RATE_PRESENT:
112 pulse_rate = IEEE11073Parser.parse_sfloat(data, offset)
113 offset += 2
115 user_id: int | None = None
116 if flags & EnhancedBloodPressureFlags.USER_ID_PRESENT:
117 user_id = data[offset]
118 offset += 1
120 measurement_status: BloodPressureMeasurementStatus | None = None
121 if flags & EnhancedBloodPressureFlags.MEASUREMENT_STATUS_PRESENT:
122 measurement_status = BloodPressureMeasurementStatus(DataParser.parse_int16(data, offset, signed=False))
123 offset += 2
125 user_facing_time: int | None = None
126 if flags & EnhancedBloodPressureFlags.USER_FACING_TIME_PRESENT:
127 user_facing_time = DataParser.parse_int32(data, offset, signed=False)
128 offset += 4
130 return EnhancedIntermediateCuffPressureData(
131 flags=flags,
132 cuff_pressure=cuff_pressure,
133 unit=unit,
134 timestamp=timestamp,
135 pulse_rate=pulse_rate,
136 user_id=user_id,
137 measurement_status=measurement_status,
138 user_facing_time=user_facing_time,
139 epoch_year=epoch_year,
140 )
142 def _encode_value(self, data: EnhancedIntermediateCuffPressureData) -> bytearray:
143 """Encode EnhancedIntermediateCuffPressureData back to BLE bytes.
145 Args:
146 data: EnhancedIntermediateCuffPressureData instance.
148 Returns:
149 Encoded bytearray matching the BLE wire format.
151 """
152 flags = EnhancedBloodPressureFlags(0)
153 if data.unit == PressureUnit.KPA:
154 flags |= EnhancedBloodPressureFlags.UNITS_KPA
155 if data.timestamp is not None:
156 flags |= EnhancedBloodPressureFlags.TIMESTAMP_PRESENT
157 if data.pulse_rate is not None:
158 flags |= EnhancedBloodPressureFlags.PULSE_RATE_PRESENT
159 if data.user_id is not None:
160 flags |= EnhancedBloodPressureFlags.USER_ID_PRESENT
161 if data.measurement_status is not None:
162 flags |= EnhancedBloodPressureFlags.MEASUREMENT_STATUS_PRESENT
163 if data.user_facing_time is not None:
164 flags |= EnhancedBloodPressureFlags.USER_FACING_TIME_PRESENT
165 if data.epoch_year == EpochYear.EPOCH_2000:
166 flags |= EnhancedBloodPressureFlags.EPOCH_START_2000
168 result = bytearray([int(flags)])
169 result.extend(IEEE11073Parser.encode_sfloat(data.cuff_pressure))
171 if data.timestamp is not None:
172 result.extend(DataParser.encode_int32(data.timestamp, signed=False))
174 if data.pulse_rate is not None:
175 result.extend(IEEE11073Parser.encode_sfloat(data.pulse_rate))
177 if data.user_id is not None:
178 result.append(data.user_id)
180 if data.measurement_status is not None:
181 result.extend(DataParser.encode_int16(int(data.measurement_status), signed=False))
183 if data.user_facing_time is not None:
184 result.extend(DataParser.encode_int32(data.user_facing_time, signed=False))
186 return result