Coverage for src/bluetooth_sig/gatt/characteristics/intermediate_cuff_pressure.py: 42%
38 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 00:10 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 00:10 +0000
1"""Intermediate Cuff Pressure characteristic implementation."""
3from __future__ import annotations
5import msgspec
7from bluetooth_sig.types.units import PressureUnit
9from ..context import CharacteristicContext
10from .blood_pressure_common import (
11 BLOOD_PRESSURE_MAX_KPA,
12 BLOOD_PRESSURE_MAX_MMHG,
13 BaseBloodPressureCharacteristic,
14 BloodPressureFlags,
15 BloodPressureOptionalFields,
16)
17from .utils import IEEE11073Parser
20class IntermediateCuffPressureData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods,too-many-instance-attributes
21 """Parsed data from Intermediate Cuff Pressure characteristic."""
23 current_cuff_pressure: float
24 unit: PressureUnit
25 optional_fields: BloodPressureOptionalFields = BloodPressureOptionalFields()
26 flags: BloodPressureFlags = BloodPressureFlags(0)
28 def __post_init__(self) -> None:
29 """Validate intermediate cuff pressure data."""
30 if self.unit not in (PressureUnit.MMHG, PressureUnit.KPA):
31 raise ValueError(f"Cuff pressure unit must be MMHG or KPA, got {self.unit}")
33 if self.unit == PressureUnit.MMHG:
34 valid_range = (0, BLOOD_PRESSURE_MAX_MMHG)
35 else: # kPa
36 valid_range = (0, BLOOD_PRESSURE_MAX_KPA)
38 if not valid_range[0] <= self.current_cuff_pressure <= valid_range[1]:
39 raise ValueError(
40 f"Current cuff pressure {self.current_cuff_pressure} {self.unit.value} is outside valid range "
41 f"({valid_range[0]}-{valid_range[1]} {self.unit.value})"
42 )
45class IntermediateCuffPressureCharacteristic(BaseBloodPressureCharacteristic):
46 """Intermediate Cuff Pressure characteristic (0x2A36).
48 Used to transmit intermediate cuff pressure values during a blood
49 pressure measurement process.
51 SIG Specification Pattern:
52 This characteristic can use Blood Pressure Feature (0x2A49) to interpret
53 which status flags are supported by the device.
54 """
56 _is_base_class = False # This is a concrete characteristic class
58 def decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> IntermediateCuffPressureData: # pylint: disable=too-many-locals
59 """Parse intermediate cuff pressure data according to Bluetooth specification.
61 Format: Flags(1) + Current Cuff Pressure(2) + Unused(2) + Unused(2) + [Timestamp(7)] +
62 [Pulse Rate(2)] + [User ID(1)] + [Measurement Status(2)].
63 All pressure values are IEEE-11073 16-bit SFLOAT. Unused fields are set to NaN.
65 Args:
66 data: Raw bytearray from BLE characteristic
67 ctx: Optional context providing access to Blood Pressure Feature characteristic
68 for validating which measurement status flags are supported
70 Returns:
71 IntermediateCuffPressureData containing parsed cuff pressure data with metadata
73 SIG Pattern:
74 When context is available, can validate that measurement status flags are
75 within the device's supported features as indicated by Blood Pressure Feature.
77 """
78 if len(data) < 7:
79 raise ValueError("Intermediate Cuff Pressure data must be at least 7 bytes")
81 flags = self._parse_blood_pressure_flags(data)
83 # Parse required fields
84 current_cuff_pressure = IEEE11073Parser.parse_sfloat(data, 1)
85 unit = self._parse_blood_pressure_unit(flags)
87 # Skip unused fields (bytes 3-6, should be NaN but we don't validate here)
89 # Parse optional fields
90 timestamp, pulse_rate, user_id, measurement_status = self._parse_optional_fields(data, flags)
92 # Create immutable struct with all values
93 return IntermediateCuffPressureData(
94 current_cuff_pressure=current_cuff_pressure,
95 unit=unit,
96 optional_fields=BloodPressureOptionalFields(
97 timestamp=timestamp,
98 pulse_rate=pulse_rate,
99 user_id=user_id,
100 measurement_status=measurement_status,
101 ),
102 flags=flags,
103 )
105 def encode_value(self, data: IntermediateCuffPressureData) -> bytearray:
106 """Encode IntermediateCuffPressureData back to bytes.
108 Args:
109 data: IntermediateCuffPressureData instance to encode
111 Returns:
112 Encoded bytes representing the intermediate cuff pressure
114 """
115 result = bytearray()
117 flags = self._encode_blood_pressure_flags(data, data.optional_fields)
118 result.append(flags)
120 result.extend(IEEE11073Parser.encode_sfloat(data.current_cuff_pressure))
121 # Add unused fields as NaN
122 result.extend(IEEE11073Parser.encode_sfloat(float("nan")))
123 result.extend(IEEE11073Parser.encode_sfloat(float("nan")))
125 self._encode_optional_fields(result, data.optional_fields)
127 return result