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

46 statements  

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

1"""Device Time Parameters characteristic (0x2B8F). 

2 

3Per DTS v1.0 Table 3.4, the DT Parameters characteristic is 2-12 octets 

4depending on which optional features are supported: 

5 

6 Field Condition Type Octets Unit 

7 E2E_CRC C.1 uint16 0 or 2 None 

8 RTC_Resolution M uint16 2 1/65,536 Second 

9 Max_RTC_Drift_Limit C.2 uint16 0 or 2 Seconds 

10 Max_Days_Until_Sync_Loss C.2 uint16 0 or 2 Days 

11 Non_Logged_Time_Adjustment_Limit C.3 uint16 0 or 2 Seconds 

12 Displayed_Formats C.4 uint16 0 or 2 N/A 

13 

14(C.1=E2E-CRC feature; C.2=RTC Drift Tracking feature; 

15 C.3=Time Change Logging feature; C.4=Displayed Formats feature) 

16 

17References: 

18 Bluetooth SIG Device Time Service v1.0, Table 3.4 

19""" 

20 

21from __future__ import annotations 

22 

23import msgspec 

24 

25from ..context import CharacteristicContext 

26from .base import BaseCharacteristic 

27from .utils import DataParser 

28 

29_MIN_LENGTH = 2 

30 

31 

32class DeviceTimeParametersData(msgspec.Struct, frozen=True, kw_only=True): 

33 """Parsed data from Device Time Parameters characteristic. 

34 

35 Attributes: 

36 rtc_resolution: RTC resolution in 1/65,536-second units (0=unknown). 

37 max_rtc_drift_limit: Max drift before sync loss, in seconds (optional). 

38 max_days_until_sync_loss: Max days without sync before loss (optional). 

39 non_logged_time_adjustment_limit: Non-logged adjustment limit in 

40 seconds; below this value Base_Time changes are not logged (optional). 

41 displayed_formats: Device displayed date/time format encoding (optional). 

42 """ 

43 

44 rtc_resolution: int 

45 max_rtc_drift_limit: int | None = None 

46 max_days_until_sync_loss: int | None = None 

47 non_logged_time_adjustment_limit: int | None = None 

48 displayed_formats: int | None = None 

49 

50 

51class DeviceTimeParametersCharacteristic(BaseCharacteristic[DeviceTimeParametersData]): 

52 """Device Time Parameters characteristic (0x2B8F). 

53 

54 org.bluetooth.characteristic.device_time_parameters 

55 

56 Reveals the Server's capabilities and behavioural thresholds for the 

57 Device Time Service. The characteristic is 2-12 octets in length 

58 depending on which optional features are supported. 

59 """ 

60 

61 min_length = _MIN_LENGTH 

62 allow_variable_length = True 

63 

64 def _decode_value( 

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

66 ) -> DeviceTimeParametersData: 

67 rtc_resolution = DataParser.parse_int16(data, 0, signed=False) 

68 

69 offset = 2 

70 max_rtc_drift_limit: int | None = None 

71 max_days_until_sync_loss: int | None = None 

72 non_logged_time_adjustment_limit: int | None = None 

73 displayed_formats: int | None = None 

74 

75 if len(data) >= offset + 2: 

76 max_rtc_drift_limit = DataParser.parse_int16(data, offset, signed=False) 

77 offset += 2 

78 

79 if len(data) >= offset + 2: 

80 max_days_until_sync_loss = DataParser.parse_int16(data, offset, signed=False) 

81 offset += 2 

82 

83 if len(data) >= offset + 2: 

84 non_logged_time_adjustment_limit = DataParser.parse_int16(data, offset, signed=False) 

85 offset += 2 

86 

87 if len(data) >= offset + 2: 

88 displayed_formats = DataParser.parse_int16(data, offset, signed=False) 

89 

90 return DeviceTimeParametersData( 

91 rtc_resolution=rtc_resolution, 

92 max_rtc_drift_limit=max_rtc_drift_limit, 

93 max_days_until_sync_loss=max_days_until_sync_loss, 

94 non_logged_time_adjustment_limit=non_logged_time_adjustment_limit, 

95 displayed_formats=displayed_formats, 

96 ) 

97 

98 def _encode_value(self, data: DeviceTimeParametersData) -> bytearray: 

99 result = bytearray() 

100 result.extend(DataParser.encode_int16(data.rtc_resolution, signed=False)) 

101 if data.max_rtc_drift_limit is not None: 

102 result.extend(DataParser.encode_int16(data.max_rtc_drift_limit, signed=False)) 

103 if data.max_days_until_sync_loss is not None: 

104 result.extend(DataParser.encode_int16(data.max_days_until_sync_loss, signed=False)) 

105 if data.non_logged_time_adjustment_limit is not None: 

106 result.extend(DataParser.encode_int16(data.non_logged_time_adjustment_limit, signed=False)) 

107 if data.displayed_formats is not None: 

108 result.extend(DataParser.encode_int16(data.displayed_formats, signed=False)) 

109 return result