Coverage for src / bluetooth_sig / gatt / characteristics / current_elapsed_time.py: 100%
47 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"""Current Elapsed Time characteristic implementation.
3Implements the Current Elapsed Time characteristic (0x2BF2).
5Structure (from GSS YAML - org.bluetooth.characteristic.elapsed_time):
6 Flags (uint8, 1 byte) -- interpretation flags
7 Time Value (uint48, 6 bytes) -- counter in time-resolution units
8 Time Sync Source Type (uint8, 1 byte) -- sync source enum
9 TZ/DST Offset (sint8, 1 byte) -- combined offset in 15-minute units
11Note: The GSS YAML identifier is ``elapsed_time`` but the UUID registry
12identifier is ``current_elapsed_time`` (0x2BF2). File is named to match
13the UUID registry for auto-discovery.
15Flag bits:
16 0: Tick counter (0=time of day, 1=relative counter)
17 1: UTC (0=local time, 1=UTC) — meaningless for tick counter
18 2-3: Time resolution (00=1s, 01=100ms, 10=1ms, 11=100µs)
19 4: TZ/DST offset used (0=not used, 1=used)
20 5: Current timeline (0=not current, 1=current)
21 6-7: Reserved
23References:
24 Bluetooth SIG Generic Sensor Service
25 org.bluetooth.characteristic.elapsed_time (GSS YAML)
26"""
28from __future__ import annotations
30from enum import IntEnum, IntFlag
32import msgspec
34from ..context import CharacteristicContext
35from .base import BaseCharacteristic
36from .reference_time_information import TimeSource
37from .utils import DataParser
39_TIME_RESOLUTION_MASK = 0x0C
40_TIME_RESOLUTION_SHIFT = 2
43class ElapsedTimeFlags(IntFlag):
44 """Flags for the Elapsed Time characteristic."""
46 TICK_COUNTER = 1 << 0
47 UTC = 1 << 1
48 TZ_DST_USED = 1 << 4
49 CURRENT_TIMELINE = 1 << 5
52class TimeResolution(IntEnum):
53 """Time resolution values (bits 2-3 of flags)."""
55 ONE_SECOND = 0
56 HUNDRED_MILLISECONDS = 1
57 ONE_MILLISECOND = 2
58 HUNDRED_MICROSECONDS = 3
61class CurrentElapsedTimeData(msgspec.Struct, frozen=True, kw_only=True):
62 """Parsed data from Current Elapsed Time characteristic.
64 Attributes:
65 flags: Interpretation flags.
66 time_value: Counter value in the resolution defined by flags.
67 time_resolution: Resolution of the time value.
68 is_tick_counter: True if time_value is a relative counter.
69 is_utc: True if time_value reports UTC (only meaningful if not tick counter).
70 tz_dst_used: True if tz_dst_offset is meaningful.
71 is_current_timeline: True if time stamp is from the current timeline.
72 sync_source_type: Time synchronisation source type.
73 tz_dst_offset: Combined TZ/DST offset from UTC in 15-minute units.
75 """
77 flags: ElapsedTimeFlags
78 time_value: int
79 time_resolution: TimeResolution
80 is_tick_counter: bool
81 is_utc: bool
82 tz_dst_used: bool
83 is_current_timeline: bool
84 sync_source_type: TimeSource
85 tz_dst_offset: int
88class CurrentElapsedTimeCharacteristic(BaseCharacteristic[CurrentElapsedTimeData]):
89 """Current Elapsed Time characteristic (0x2BF2).
91 Reports the current time of a clock or tick counter.
92 Fixed 9-byte structure.
93 """
95 expected_type = CurrentElapsedTimeData
96 min_length: int = 9
98 def _decode_value(
99 self,
100 data: bytearray,
101 ctx: CharacteristicContext | None = None,
102 *,
103 validate: bool = True,
104 ) -> CurrentElapsedTimeData:
105 """Parse Current Elapsed Time from raw BLE bytes.
107 Args:
108 data: Raw bytearray from BLE characteristic (9 bytes).
109 ctx: Optional context (unused).
110 validate: Whether to validate ranges.
112 Returns:
113 CurrentElapsedTimeData with parsed time information.
115 """
116 flags_raw = data[0]
117 flags = ElapsedTimeFlags(flags_raw & 0x33) # Mask out resolution bits + reserved
119 time_resolution = TimeResolution((flags_raw & _TIME_RESOLUTION_MASK) >> _TIME_RESOLUTION_SHIFT)
121 time_value = DataParser.parse_int48(data, 1, signed=False)
122 sync_source_type = TimeSource(data[7])
123 tz_dst_offset = DataParser.parse_int8(data, 8, signed=True)
125 return CurrentElapsedTimeData(
126 flags=flags,
127 time_value=time_value,
128 time_resolution=time_resolution,
129 is_tick_counter=bool(flags & ElapsedTimeFlags.TICK_COUNTER),
130 is_utc=bool(flags & ElapsedTimeFlags.UTC),
131 tz_dst_used=bool(flags & ElapsedTimeFlags.TZ_DST_USED),
132 is_current_timeline=bool(flags & ElapsedTimeFlags.CURRENT_TIMELINE),
133 sync_source_type=sync_source_type,
134 tz_dst_offset=tz_dst_offset,
135 )
137 def _encode_value(self, data: CurrentElapsedTimeData) -> bytearray:
138 """Encode CurrentElapsedTimeData back to BLE bytes.
140 Args:
141 data: CurrentElapsedTimeData instance.
143 Returns:
144 Encoded bytearray (9 bytes).
146 """
147 flags_raw = int(data.flags) | (data.time_resolution << _TIME_RESOLUTION_SHIFT)
148 result = bytearray([flags_raw])
149 result.extend(DataParser.encode_int48(data.time_value, signed=False))
150 result.append(int(data.sync_source_type))
151 result.extend(DataParser.encode_int8(data.tz_dst_offset, signed=True))
152 return result