Coverage for src / bluetooth_sig / gatt / characteristics / cgm_session_start_time.py: 100%
44 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
1"""CGM Session Start Time characteristic implementation.
3Implements the CGM Session Start Time characteristic (0x2AAA).
5Structure (from GSS YAML):
6 Session Start Time (7 bytes) -- DateTime struct (year+month+day+h+m+s)
7 Time Zone (1 byte, uint8) -- offset from UTC in 15-minute increments
8 DST Offset (1 byte, uint8) -- DST adjustment code
9 E2E-CRC (2 bytes, uint16, optional)
11References:
12 Bluetooth SIG Continuous Glucose Monitoring Service
13 org.bluetooth.characteristic.cgm_session_start_time (GSS YAML)
14"""
16from __future__ import annotations
18from datetime import datetime
20import msgspec
22from ..context import CharacteristicContext
23from .base import BaseCharacteristic
24from .dst_offset import DSTOffset
25from .utils import DataParser
28class CGMSessionStartTimeData(msgspec.Struct, frozen=True, kw_only=True):
29 """Parsed data from CGM Session Start Time characteristic.
31 Attributes:
32 start_time: Session start date and time.
33 time_zone: Time zone offset from UTC in 15-minute increments.
34 dst_offset: DST adjustment code.
35 e2e_crc: E2E-CRC value. None if absent.
37 """
39 start_time: datetime
40 time_zone: int
41 dst_offset: DSTOffset
42 e2e_crc: int | None = None
45class CGMSessionStartTimeCharacteristic(BaseCharacteristic[CGMSessionStartTimeData]):
46 """CGM Session Start Time characteristic (0x2AAA).
48 Reports the session start time, time zone, and DST offset
49 for a CGM session.
50 """
52 expected_type = CGMSessionStartTimeData
53 min_length: int = 9 # datetime(7) + timezone(1) + dst(1)
54 allow_variable_length: bool = True # optional E2E-CRC
56 def _decode_value(
57 self,
58 data: bytearray,
59 ctx: CharacteristicContext | None = None,
60 *,
61 validate: bool = True,
62 ) -> CGMSessionStartTimeData:
63 """Parse CGM Session Start Time from raw BLE bytes.
65 Args:
66 data: Raw bytearray from BLE characteristic (9 or 11 bytes).
67 ctx: Optional context (unused).
68 validate: Whether to validate ranges.
70 Returns:
71 CGMSessionStartTimeData with parsed date/time and timezone info.
73 """
74 year = DataParser.parse_int16(data, 0, signed=False)
75 month = data[2]
76 day = data[3]
77 hour = data[4]
78 minute = data[5]
79 second = data[6]
80 start_time = datetime(year=year, month=month, day=day, hour=hour, minute=minute, second=second)
82 time_zone = data[7]
83 dst_offset = DSTOffset(data[8])
85 _min_length_with_crc = 11
86 e2e_crc: int | None = None
87 if len(data) >= _min_length_with_crc:
88 e2e_crc = DataParser.parse_int16(data, 9, signed=False)
90 return CGMSessionStartTimeData(
91 start_time=start_time,
92 time_zone=time_zone,
93 dst_offset=dst_offset,
94 e2e_crc=e2e_crc,
95 )
97 def _encode_value(self, data: CGMSessionStartTimeData) -> bytearray:
98 """Encode CGMSessionStartTimeData back to BLE bytes.
100 Args:
101 data: CGMSessionStartTimeData instance.
103 Returns:
104 Encoded bytearray (9 or 11 bytes).
106 """
107 result = bytearray()
108 result.extend(DataParser.encode_int16(data.start_time.year, signed=False))
109 result.append(data.start_time.month)
110 result.append(data.start_time.day)
111 result.append(data.start_time.hour)
112 result.append(data.start_time.minute)
113 result.append(data.start_time.second)
114 result.append(data.time_zone)
115 result.append(int(data.dst_offset))
117 if data.e2e_crc is not None:
118 result.extend(DataParser.encode_int16(data.e2e_crc, signed=False))
120 return result