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

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 # Mandatory single cuff pressure value (medfloat16) 

98 cuff_pressure = IEEE11073Parser.parse_sfloat(data, 1) 

99 offset = 3 

100 

101 epoch_year = ( 

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

103 ) 

104 

105 timestamp: int | None = None 

106 if flags & EnhancedBloodPressureFlags.TIMESTAMP_PRESENT: 

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

108 offset += 4 

109 

110 pulse_rate: float | None = None 

111 if flags & EnhancedBloodPressureFlags.PULSE_RATE_PRESENT: 

112 pulse_rate = IEEE11073Parser.parse_sfloat(data, offset) 

113 offset += 2 

114 

115 user_id: int | None = None 

116 if flags & EnhancedBloodPressureFlags.USER_ID_PRESENT: 

117 user_id = data[offset] 

118 offset += 1 

119 

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 

124 

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 

129 

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 ) 

141 

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

143 """Encode EnhancedIntermediateCuffPressureData back to BLE bytes. 

144 

145 Args: 

146 data: EnhancedIntermediateCuffPressureData instance. 

147 

148 Returns: 

149 Encoded bytearray matching the BLE wire format. 

150 

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 

167 

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

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

170 

171 if data.timestamp is not None: 

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

173 

174 if data.pulse_rate is not None: 

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

176 

177 if data.user_id is not None: 

178 result.append(data.user_id) 

179 

180 if data.measurement_status is not None: 

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

182 

183 if data.user_facing_time is not None: 

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

185 

186 return result