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

50 statements  

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

1"""Training Status characteristic (0x2AD3).""" 

2 

3from __future__ import annotations 

4 

5from enum import IntEnum, IntFlag 

6 

7import msgspec 

8 

9from ..context import CharacteristicContext 

10from .base import BaseCharacteristic 

11from .utils import DataParser 

12 

13_MIN_STRING_OFFSET = 2 # Flags(1) + TrainingStatus(1) 

14 

15 

16class TrainingStatusFlags(IntFlag): 

17 """Training Status flags (uint8).""" 

18 

19 TRAINING_STATUS_STRING_PRESENT = 0x01 

20 EXTENDED_STRING_PRESENT = 0x02 

21 

22 

23class TrainingStatusValue(IntEnum): 

24 """Training Status values per FTMS specification.""" 

25 

26 OTHER = 0x00 

27 IDLE = 0x01 

28 WARMING_UP = 0x02 

29 LOW_INTENSITY_INTERVAL = 0x03 

30 HIGH_INTENSITY_INTERVAL = 0x04 

31 RECOVERY_INTERVAL = 0x05 

32 ISOMETRIC = 0x06 

33 HEART_RATE_CONTROL = 0x07 

34 FITNESS_TEST = 0x08 

35 SPEED_OUTSIDE_OF_CONTROL_REGION_LOW = 0x09 

36 SPEED_OUTSIDE_OF_CONTROL_REGION_HIGH = 0x0A 

37 COOL_DOWN = 0x0B 

38 WATT_CONTROL = 0x0C 

39 MANUAL_MODE = 0x0D 

40 PRE_WORKOUT = 0x0E 

41 POST_WORKOUT = 0x0F 

42 

43 

44class TrainingStatusData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods 

45 """Parsed data from Training Status characteristic. 

46 

47 Contains flags, training status value, and an optional status string. 

48 """ 

49 

50 flags: TrainingStatusFlags 

51 training_status: TrainingStatusValue 

52 training_status_string: str | None = None 

53 

54 

55class TrainingStatusCharacteristic(BaseCharacteristic[TrainingStatusData]): 

56 """Training Status characteristic (0x2AD3). 

57 

58 org.bluetooth.characteristic.training_status 

59 

60 Reports the current training status of the fitness machine, 

61 including an optional descriptive string. 

62 """ 

63 

64 min_length = 2 

65 allow_variable_length = True 

66 

67 def _decode_value( 

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

69 ) -> TrainingStatusData: 

70 """Parse Training Status data. 

71 

72 Format: Flags (uint8) + Training Status (uint8) + optional string. 

73 

74 Args: 

75 data: Raw bytearray from BLE characteristic. 

76 ctx: Optional CharacteristicContext (may be None). 

77 validate: Whether to validate ranges (default True). 

78 

79 Returns: 

80 TrainingStatusData containing parsed training status. 

81 

82 """ 

83 flags_raw = DataParser.parse_int8(data, 0, signed=False) 

84 flags = TrainingStatusFlags(flags_raw) 

85 

86 status_raw = DataParser.parse_int8(data, 1, signed=False) 

87 training_status = TrainingStatusValue(status_raw) 

88 

89 training_status_string: str | None = None 

90 if flags & TrainingStatusFlags.TRAINING_STATUS_STRING_PRESENT and len(data) > _MIN_STRING_OFFSET: 

91 training_status_string = DataParser.parse_utf8_string(data[_MIN_STRING_OFFSET:]) 

92 

93 return TrainingStatusData( 

94 flags=flags, 

95 training_status=training_status, 

96 training_status_string=training_status_string, 

97 ) 

98 

99 def _encode_value(self, data: TrainingStatusData) -> bytearray: 

100 """Encode Training Status data to bytes. 

101 

102 Args: 

103 data: TrainingStatusData instance. 

104 

105 Returns: 

106 Encoded bytes representing the training status. 

107 

108 """ 

109 result = bytearray() 

110 result.extend(DataParser.encode_int8(int(data.flags), signed=False)) 

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

112 

113 if data.training_status_string is not None: 

114 result.extend(data.training_status_string.encode("utf-8")) 

115 

116 return result