Coverage for src / bluetooth_sig / gatt / characteristics / cgm_specific_ops_control_point.py: 98%

60 statements  

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

1"""CGM Specific Ops Control Point characteristic (0x2AAC). 

2 

3Control point for CGM-specific procedures: communication interval, 

4calibration, alert levels, session control. 

5 

6References: 

7 Bluetooth SIG Continuous Glucose Monitoring Service 

8 org.bluetooth.characteristic.cgm_specific_ops_control_point (GSS YAML) 

9""" 

10 

11from __future__ import annotations 

12 

13from enum import IntEnum 

14 

15import msgspec 

16 

17from ..context import CharacteristicContext 

18from .base import BaseCharacteristic 

19from .utils import DataParser 

20 

21 

22class CGMSpecificOpsOpCode(IntEnum): 

23 """CGM Specific Ops Control Point Op Codes.""" 

24 

25 SET_CGM_COMMUNICATION_INTERVAL = 0x01 

26 GET_CGM_COMMUNICATION_INTERVAL = 0x02 

27 CGM_COMMUNICATION_INTERVAL_RESPONSE = 0x03 

28 SET_GLUCOSE_CALIBRATION_VALUE = 0x04 

29 GET_GLUCOSE_CALIBRATION_VALUE = 0x05 

30 GLUCOSE_CALIBRATION_VALUE_RESPONSE = 0x06 

31 SET_PATIENT_HIGH_ALERT_LEVEL = 0x07 

32 GET_PATIENT_HIGH_ALERT_LEVEL = 0x08 

33 PATIENT_HIGH_ALERT_LEVEL_RESPONSE = 0x09 

34 SET_PATIENT_LOW_ALERT_LEVEL = 0x0A 

35 GET_PATIENT_LOW_ALERT_LEVEL = 0x0B 

36 PATIENT_LOW_ALERT_LEVEL_RESPONSE = 0x0C 

37 SET_HYPO_ALERT_LEVEL = 0x0D 

38 GET_HYPO_ALERT_LEVEL = 0x0E 

39 HYPO_ALERT_LEVEL_RESPONSE = 0x0F 

40 SET_HYPER_ALERT_LEVEL = 0x10 

41 GET_HYPER_ALERT_LEVEL = 0x11 

42 HYPER_ALERT_LEVEL_RESPONSE = 0x12 

43 SET_RATE_OF_DECREASE_ALERT_LEVEL = 0x13 

44 GET_RATE_OF_DECREASE_ALERT_LEVEL = 0x14 

45 RATE_OF_DECREASE_ALERT_LEVEL_RESPONSE = 0x15 

46 SET_RATE_OF_INCREASE_ALERT_LEVEL = 0x16 

47 GET_RATE_OF_INCREASE_ALERT_LEVEL = 0x17 

48 RATE_OF_INCREASE_ALERT_LEVEL_RESPONSE = 0x18 

49 RESET_DEVICE_SPECIFIC_ALERT = 0x19 

50 START_THE_SESSION = 0x1A 

51 STOP_THE_SESSION = 0x1B 

52 RESPONSE_CODE = 0x1C 

53 

54 

55class CGMSpecificOpsResponseCode(IntEnum): 

56 """CGM Specific Ops response codes.""" 

57 

58 SUCCESS = 0x01 

59 OP_CODE_NOT_SUPPORTED = 0x02 

60 INVALID_OPERAND = 0x03 

61 PROCEDURE_NOT_COMPLETED = 0x04 

62 PARAMETER_OUT_OF_RANGE = 0x05 

63 

64 

65class CGMSpecificOpsControlPointData(msgspec.Struct, frozen=True, kw_only=True): 

66 """Parsed data from CGM Specific Ops Control Point. 

67 

68 Attributes: 

69 opcode: The operation code. 

70 operand: Raw operand bytes (variable per opcode). Empty if none. 

71 e2e_crc: Optional E2E-CRC (present if CGM Feature indicates support). 

72 

73 """ 

74 

75 opcode: CGMSpecificOpsOpCode 

76 operand: bytes = b"" 

77 e2e_crc: int | None = None 

78 

79 

80class CGMSpecificOpsControlPointCharacteristic(BaseCharacteristic[CGMSpecificOpsControlPointData]): 

81 """CGM Specific Ops Control Point characteristic (0x2AAC). 

82 

83 org.bluetooth.characteristic.cgm_specific_ops_control_point 

84 

85 Used to enable procedures related to a continuous glucose monitor. 

86 ROLE: CONTROL 

87 """ 

88 

89 _manual_role = None # Let classifier infer CONTROL from name 

90 min_length = 1 

91 allow_variable_length = True 

92 

93 def _decode_value( 

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

95 ) -> CGMSpecificOpsControlPointData: 

96 """Parse CGM Specific Ops Control Point data. 

97 

98 Format: OpCode (uint8) + Operand (variable) + optional E2E-CRC (uint16). 

99 

100 The E2E-CRC, if present, occupies the last 2 bytes. Without external 

101 knowledge of CRC support we cannot distinguish a 2-byte operand tail 

102 from a CRC, so we always treat the last 2 bytes as CRC when >= 3 bytes 

103 total and the opcode is a response type or the data length suggests it. 

104 For simplicity, CRC is *not* split out here — consumers should use 

105 CGM Feature to decide whether CRC is present. 

106 """ 

107 opcode = CGMSpecificOpsOpCode(DataParser.parse_int8(data, 0, signed=False)) 

108 operand = bytes(data[1:]) 

109 

110 return CGMSpecificOpsControlPointData( 

111 opcode=opcode, 

112 operand=operand, 

113 ) 

114 

115 def _encode_value(self, data: CGMSpecificOpsControlPointData) -> bytearray: 

116 """Encode CGM Specific Ops Control Point data.""" 

117 result = bytearray() 

118 result.extend(DataParser.encode_int8(int(data.opcode), signed=False)) 

119 result.extend(data.operand) 

120 if data.e2e_crc is not None: 

121 result.extend(DataParser.encode_int16(data.e2e_crc, signed=False)) 

122 return result