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-04-03 16:41 +0000

1"""Enhanced Intermediate Cuff Pressure characteristic implementation. 

2 

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. 

7 

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 

17 

18References: 

19 Bluetooth SIG Blood Pressure Service 1.1 

20 org.bluetooth.characteristic.enhanced_intermediate_cuff_pressure (GSS YAML) 

21""" 

22 

23from __future__ import annotations 

24 

25import msgspec 

26 

27from bluetooth_sig.types.units import PressureUnit 

28 

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 

34 

35 

36class EnhancedIntermediateCuffPressureData(msgspec.Struct, frozen=True, kw_only=True): 

37 """Parsed data from Enhanced Intermediate Cuff Pressure characteristic. 

38 

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). 

49 

50 """ 

51 

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 

61 

62 

63class EnhancedIntermediateCuffPressureCharacteristic( 

64 BaseCharacteristic[EnhancedIntermediateCuffPressureData], 

65): 

66 """Enhanced Intermediate Cuff Pressure characteristic (0x2B35). 

67 

68 Reports a single intermediate cuff pressure reading during an ongoing 

69 blood pressure measurement, with enhanced timestamps and epoch flag. 

70 """ 

71 

72 expected_type = EnhancedIntermediateCuffPressureData 

73 min_length: int = 3 # flags(1) + cuff_pressure(2) 

74 allow_variable_length: bool = True 

75 

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. 

84 

85 Args: 

86 data: Raw bytearray from BLE characteristic. 

87 ctx: Optional context (unused). 

88 validate: Whether to validate ranges. 

89 

90 Returns: 

91 EnhancedIntermediateCuffPressureData with all present fields. 

92 

93 """ 

94 flags = EnhancedBloodPressureFlags(data[0]) 

95 unit = PressureUnit.KPA if flags & EnhancedBloodPressureFlags.UNITS_KPA else PressureUnit.MMHG 

96 

97 cuff_pressure = IEEE11073Parser.parse_sfloat(data, 1) 

98 offset = 3 

99 

100 epoch_year = ( 

101 EpochYear.EPOCH_2000 if flags & EnhancedBloodPressureFlags.EPOCH_START_2000 else EpochYear.EPOCH_1900 

102 ) 

103 

104 timestamp: int | None = None 

105 if flags & EnhancedBloodPressureFlags.TIMESTAMP_PRESENT: 

106 timestamp = DataParser.parse_int32(data, offset, signed=False) 

107 offset += 4 

108 

109 pulse_rate: float | None = None 

110 if flags & EnhancedBloodPressureFlags.PULSE_RATE_PRESENT: 

111 pulse_rate = IEEE11073Parser.parse_sfloat(data, offset) 

112 offset += 2 

113 

114 user_id: int | None = None 

115 if flags & EnhancedBloodPressureFlags.USER_ID_PRESENT: 

116 user_id = data[offset] 

117 offset += 1 

118 

119 measurement_status: BloodPressureMeasurementStatus | None = None 

120 if flags & EnhancedBloodPressureFlags.MEASUREMENT_STATUS_PRESENT: 

121 measurement_status = BloodPressureMeasurementStatus(DataParser.parse_int16(data, offset, signed=False)) 

122 offset += 2 

123 

124 user_facing_time: int | None = None 

125 if flags & EnhancedBloodPressureFlags.USER_FACING_TIME_PRESENT: 

126 user_facing_time = DataParser.parse_int32(data, offset, signed=False) 

127 offset += 4 

128 

129 return EnhancedIntermediateCuffPressureData( 

130 flags=flags, 

131 cuff_pressure=cuff_pressure, 

132 unit=unit, 

133 timestamp=timestamp, 

134 pulse_rate=pulse_rate, 

135 user_id=user_id, 

136 measurement_status=measurement_status, 

137 user_facing_time=user_facing_time, 

138 epoch_year=epoch_year, 

139 ) 

140 

141 def _encode_value(self, data: EnhancedIntermediateCuffPressureData) -> bytearray: 

142 """Encode EnhancedIntermediateCuffPressureData back to BLE bytes. 

143 

144 Args: 

145 data: EnhancedIntermediateCuffPressureData instance. 

146 

147 Returns: 

148 Encoded bytearray matching the BLE wire format. 

149 

150 """ 

151 flags = EnhancedBloodPressureFlags(0) 

152 if data.unit == PressureUnit.KPA: 

153 flags |= EnhancedBloodPressureFlags.UNITS_KPA 

154 if data.timestamp is not None: 

155 flags |= EnhancedBloodPressureFlags.TIMESTAMP_PRESENT 

156 if data.pulse_rate is not None: 

157 flags |= EnhancedBloodPressureFlags.PULSE_RATE_PRESENT 

158 if data.user_id is not None: 

159 flags |= EnhancedBloodPressureFlags.USER_ID_PRESENT 

160 if data.measurement_status is not None: 

161 flags |= EnhancedBloodPressureFlags.MEASUREMENT_STATUS_PRESENT 

162 if data.user_facing_time is not None: 

163 flags |= EnhancedBloodPressureFlags.USER_FACING_TIME_PRESENT 

164 if data.epoch_year == EpochYear.EPOCH_2000: 

165 flags |= EnhancedBloodPressureFlags.EPOCH_START_2000 

166 

167 result = bytearray([int(flags)]) 

168 result.extend(IEEE11073Parser.encode_sfloat(data.cuff_pressure)) 

169 

170 if data.timestamp is not None: 

171 result.extend(DataParser.encode_int32(data.timestamp, signed=False)) 

172 

173 if data.pulse_rate is not None: 

174 result.extend(IEEE11073Parser.encode_sfloat(data.pulse_rate)) 

175 

176 if data.user_id is not None: 

177 result.append(data.user_id) 

178 

179 if data.measurement_status is not None: 

180 result.extend(DataParser.encode_int16(int(data.measurement_status), signed=False)) 

181 

182 if data.user_facing_time is not None: 

183 result.extend(DataParser.encode_int32(data.user_facing_time, signed=False)) 

184 

185 return result