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

60 statements  

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

1"""General Activity Summary Data characteristic (0x2B3D). 

2 

3Reports summary statistics for a general activity period. 

4 

5References: 

6 Bluetooth SIG Physical Activity Monitor Service 1.0 

7""" 

8 

9from __future__ import annotations 

10 

11from enum import IntFlag 

12 

13import msgspec 

14 

15from ..context import CharacteristicContext 

16from .base import BaseCharacteristic 

17from .utils import DataParser 

18 

19 

20class GeneralActivitySummaryFlags(IntFlag): 

21 """General Activity Summary Data flags (32-bit, 4 octets).""" 

22 

23 NORMAL_WALKING_EE_PRESENT = 0x00000001 

24 INTENSITY_EE_PRESENT = 0x00000002 

25 TOTAL_EE_PRESENT = 0x00000004 

26 FAT_BURNED_PRESENT = 0x00000008 

27 MIN_METABOLIC_EQUIVALENT_PRESENT = 0x00000010 

28 MAX_METABOLIC_EQUIVALENT_PRESENT = 0x00000020 

29 AVG_METABOLIC_EQUIVALENT_PRESENT = 0x00000040 

30 DISTANCE_PRESENT = 0x00000080 

31 MIN_SPEED_PRESENT = 0x00000100 

32 MAX_SPEED_PRESENT = 0x00000200 

33 AVG_SPEED_PRESENT = 0x00000400 

34 DURATION_NORMAL_WALKING_PRESENT = 0x00000800 

35 DURATION_INTENSITY_WALKING_PRESENT = 0x00001000 

36 MIN_MOTION_CADENCE_PRESENT = 0x00002000 

37 MAX_MOTION_CADENCE_PRESENT = 0x00004000 

38 AVG_MOTION_CADENCE_PRESENT = 0x00008000 

39 FLOORS_PRESENT = 0x00010000 

40 POSITIVE_ELEVATION_GAIN_PRESENT = 0x00020000 

41 NEGATIVE_ELEVATION_GAIN_PRESENT = 0x00040000 

42 ACTIVITY_COUNT_PRESENT = 0x00080000 

43 MIN_ACTIVITY_LEVEL_PRESENT = 0x00100000 

44 MAX_ACTIVITY_LEVEL_PRESENT = 0x00200000 

45 AVG_ACTIVITY_LEVEL_PRESENT = 0x00400000 

46 AVG_ACTIVITY_TYPE_PRESENT = 0x00800000 

47 WORN_DURATION_PRESENT = 0x01000000 

48 

49 

50class GeneralActivitySummaryData(msgspec.Struct, frozen=True, kw_only=True): 

51 """Parsed data from General Activity Summary Data characteristic. 

52 

53 Attributes: 

54 header: Segmentation header byte. 

55 flags: Presence flags for optional fields (32-bit). 

56 session_id: Session identifier (uint16). 

57 sub_session_id: Sub-session identifier (uint16). 

58 relative_timestamp: Relative timestamp in seconds (uint32). 

59 sequence_number: Sequence number (uint32). 

60 

61 """ 

62 

63 header: int 

64 flags: GeneralActivitySummaryFlags 

65 session_id: int 

66 sub_session_id: int 

67 relative_timestamp: int 

68 sequence_number: int 

69 

70 

71class GeneralActivitySummaryDataCharacteristic(BaseCharacteristic[GeneralActivitySummaryData]): 

72 """General Activity Summary Data characteristic (0x2B3D). 

73 

74 org.bluetooth.characteristic.general_activity_summary_data 

75 

76 Reports summary statistics for a general activity period. 

77 """ 

78 

79 min_length = 17 # header(1) + flags(4) + session(2) + subsession(2) + timestamp(4) + sequence(4) 

80 allow_variable_length = True 

81 

82 def _decode_value( 

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

84 ) -> GeneralActivitySummaryData: 

85 """Parse General Activity Summary Data. 

86 

87 Format: Header (uint8) + Flags (uint32) + SessionID (uint16) 

88 + SubSessionID (uint16) + RelativeTimestamp (uint32) 

89 + SequenceNumber (uint32) + [optional fields]. 

90 """ 

91 header = DataParser.parse_int8(data, 0, signed=False) 

92 flags_raw = DataParser.parse_int32(data, 1, signed=False) 

93 flags = GeneralActivitySummaryFlags(flags_raw) 

94 session_id = DataParser.parse_int16(data, 5, signed=False) 

95 sub_session_id = DataParser.parse_int16(data, 7, signed=False) 

96 relative_timestamp = DataParser.parse_int32(data, 9, signed=False) 

97 sequence_number = DataParser.parse_int32(data, 13, signed=False) 

98 

99 return GeneralActivitySummaryData( 

100 header=header, 

101 flags=flags, 

102 session_id=session_id, 

103 sub_session_id=sub_session_id, 

104 relative_timestamp=relative_timestamp, 

105 sequence_number=sequence_number, 

106 ) 

107 

108 def _encode_value(self, data: GeneralActivitySummaryData) -> bytearray: 

109 """Encode General Activity Summary data.""" 

110 result = bytearray() 

111 result.extend(DataParser.encode_int8(data.header, signed=False)) 

112 result.extend(DataParser.encode_int32(int(data.flags), signed=False)) 

113 result.extend(DataParser.encode_int16(data.session_id, signed=False)) 

114 result.extend(DataParser.encode_int16(data.sub_session_id, signed=False)) 

115 result.extend(DataParser.encode_int32(data.relative_timestamp, signed=False)) 

116 result.extend(DataParser.encode_int32(data.sequence_number, signed=False)) 

117 return result