Coverage for src / bluetooth_sig / gatt / characteristics / idd_annunciation_status.py: 100%

78 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-03 16:41 +0000

1"""IDD Annunciation Status characteristic (0x2B22). 

2 

3Reports alarm/reminder/status-change annunciations from the 

4Insulin Delivery Device. 

5 

6References: 

7 Bluetooth SIG Insulin Delivery Service 1.0.1, Table 4.7 

8""" 

9 

10from __future__ import annotations 

11 

12from enum import IntEnum, IntFlag 

13 

14import msgspec 

15 

16from ..context import CharacteristicContext 

17from .base import BaseCharacteristic 

18from .utils import DataParser 

19 

20 

21class IDDAnnunciationFlags(IntFlag): 

22 """Flags indicating which fields are present.""" 

23 

24 ANNUNCIATION_PRESENT = 0x01 

25 AUX_INFO_1_PRESENT = 0x02 

26 AUX_INFO_2_PRESENT = 0x04 

27 AUX_INFO_3_PRESENT = 0x08 

28 AUX_INFO_4_PRESENT = 0x10 

29 AUX_INFO_5_PRESENT = 0x20 

30 

31 

32class IDDAnnunciationType(IntEnum): 

33 """IDD annunciation type (uint16 Hamming codes).""" 

34 

35 SYSTEM_ISSUE = 0x000F 

36 MECHANICAL_ISSUE = 0x0033 

37 OCCLUSION_DETECTED = 0x003C 

38 RESERVOIR_ISSUE = 0x0055 

39 RESERVOIR_EMPTY = 0x005A 

40 RESERVOIR_LOW = 0x0066 

41 PRIMING_ISSUE = 0x0069 

42 INFUSION_SET_INCOMPLETE = 0x0096 

43 INFUSION_SET_DETACHED = 0x0099 

44 POWER_SOURCE_INSUFFICIENT = 0x00A5 

45 BATTERY_EMPTY = 0x00AA 

46 BATTERY_LOW = 0x00C3 

47 BATTERY_MEDIUM = 0x00CC 

48 BATTERY_FULL = 0x00F0 

49 TEMPERATURE_OUT_OF_RANGE = 0x00FF 

50 AIR_PRESSURE_OUT_OF_RANGE = 0x0303 

51 BOLUS_CANCELED = 0x030C 

52 TBR_OVER = 0x0330 

53 TBR_CANCELED = 0x033F 

54 MAX_DELIVERY = 0x0356 

55 DATE_TIME_ISSUE = 0x0359 

56 TEMPERATURE = 0x0365 

57 

58 

59class IDDAnnunciationStatus(IntEnum): 

60 """IDD annunciation status (uint8 Hamming codes).""" 

61 

62 UNDETERMINED = 0x0F 

63 PENDING = 0x33 

64 SNOOZED = 0x3C 

65 CONFIRMED = 0x55 

66 

67 

68class IDDAnnunciationStatusData(msgspec.Struct, frozen=True, kw_only=True): 

69 """Parsed data from IDD Annunciation Status characteristic. 

70 

71 Attributes: 

72 flags: Bit field indicating which optional fields are present. 

73 annunciation_instance_id: Instance identifier for this annunciation. 

74 annunciation_type: Type of annunciation (Hamming-coded). 

75 annunciation_status: Current status of the annunciation. 

76 aux_info: Up to 5 auxiliary info uint16 values. 

77 

78 """ 

79 

80 flags: IDDAnnunciationFlags 

81 annunciation_instance_id: int | None = None 

82 annunciation_type: IDDAnnunciationType | None = None 

83 annunciation_status: IDDAnnunciationStatus | None = None 

84 aux_info: list[int] = [] 

85 

86 

87_AUX_FLAGS = [ 

88 IDDAnnunciationFlags.AUX_INFO_1_PRESENT, 

89 IDDAnnunciationFlags.AUX_INFO_2_PRESENT, 

90 IDDAnnunciationFlags.AUX_INFO_3_PRESENT, 

91 IDDAnnunciationFlags.AUX_INFO_4_PRESENT, 

92 IDDAnnunciationFlags.AUX_INFO_5_PRESENT, 

93] 

94 

95 

96class IDDAnnunciationStatusCharacteristic(BaseCharacteristic[IDDAnnunciationStatusData]): 

97 """IDD Annunciation Status characteristic (0x2B22). 

98 

99 org.bluetooth.characteristic.idd_annunciation_status 

100 

101 Reports annunciation (alert/reminder/status) information 

102 from the Insulin Delivery Device. 

103 """ 

104 

105 min_length = 1 # flags only 

106 allow_variable_length = True 

107 

108 def _decode_value( 

109 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True 

110 ) -> IDDAnnunciationStatusData: 

111 """Parse IDD Annunciation Status data. 

112 

113 Format: Flags(uint8) + [InstanceID(uint16) + Type(uint16) + Status(uint8)] 

114 + [AuxInfo1(uint16)] ... [AuxInfo5(uint16)] 

115 """ 

116 flags = IDDAnnunciationFlags(DataParser.parse_int8(data, 0, signed=False)) 

117 offset = 1 

118 

119 instance_id: int | None = None 

120 annunciation_type: IDDAnnunciationType | None = None 

121 annunciation_status: IDDAnnunciationStatus | None = None 

122 

123 if flags & IDDAnnunciationFlags.ANNUNCIATION_PRESENT: 

124 instance_id = DataParser.parse_int16(data, offset, signed=False) 

125 annunciation_type = IDDAnnunciationType(DataParser.parse_int16(data, offset + 2, signed=False)) 

126 annunciation_status = IDDAnnunciationStatus(DataParser.parse_int8(data, offset + 4, signed=False)) 

127 offset += 5 

128 

129 aux_info: list[int] = [] 

130 for aux_flag in _AUX_FLAGS: 

131 if flags & aux_flag: 

132 aux_info.append(DataParser.parse_int16(data, offset, signed=False)) 

133 offset += 2 

134 

135 return IDDAnnunciationStatusData( 

136 flags=flags, 

137 annunciation_instance_id=instance_id, 

138 annunciation_type=annunciation_type, 

139 annunciation_status=annunciation_status, 

140 aux_info=aux_info, 

141 ) 

142 

143 def _encode_value(self, data: IDDAnnunciationStatusData) -> bytearray: 

144 """Encode IDD Annunciation Status data.""" 

145 result = bytearray() 

146 result.extend(DataParser.encode_int8(int(data.flags), signed=False)) 

147 

148 if data.flags & IDDAnnunciationFlags.ANNUNCIATION_PRESENT: 

149 result.extend(DataParser.encode_int16(data.annunciation_instance_id or 0, signed=False)) 

150 result.extend(DataParser.encode_int16(int(data.annunciation_type or 0), signed=False)) 

151 result.extend(DataParser.encode_int8(int(data.annunciation_status or 0), signed=False)) 

152 

153 for value in data.aux_info: 

154 result.extend(DataParser.encode_int16(value, signed=False)) 

155 

156 return result