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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 16:41 +0000
1"""Training Status characteristic (0x2AD3)."""
3from __future__ import annotations
5from enum import IntEnum, IntFlag
7import msgspec
9from ..context import CharacteristicContext
10from .base import BaseCharacteristic
11from .utils import DataParser
13_MIN_STRING_OFFSET = 2 # Flags(1) + TrainingStatus(1)
16class TrainingStatusFlags(IntFlag):
17 """Training Status flags (uint8)."""
19 TRAINING_STATUS_STRING_PRESENT = 0x01
20 EXTENDED_STRING_PRESENT = 0x02
23class TrainingStatusValue(IntEnum):
24 """Training Status values per FTMS specification."""
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
44class TrainingStatusData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
45 """Parsed data from Training Status characteristic.
47 Contains flags, training status value, and an optional status string.
48 """
50 flags: TrainingStatusFlags
51 training_status: TrainingStatusValue
52 training_status_string: str | None = None
55class TrainingStatusCharacteristic(BaseCharacteristic[TrainingStatusData]):
56 """Training Status characteristic (0x2AD3).
58 org.bluetooth.characteristic.training_status
60 Reports the current training status of the fitness machine,
61 including an optional descriptive string.
62 """
64 min_length = 2
65 allow_variable_length = True
67 def _decode_value(
68 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
69 ) -> TrainingStatusData:
70 """Parse Training Status data.
72 Format: Flags (uint8) + Training Status (uint8) + optional string.
74 Args:
75 data: Raw bytearray from BLE characteristic.
76 ctx: Optional CharacteristicContext (may be None).
77 validate: Whether to validate ranges (default True).
79 Returns:
80 TrainingStatusData containing parsed training status.
82 """
83 flags_raw = DataParser.parse_int8(data, 0, signed=False)
84 flags = TrainingStatusFlags(flags_raw)
86 status_raw = DataParser.parse_int8(data, 1, signed=False)
87 training_status = TrainingStatusValue(status_raw)
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:])
93 return TrainingStatusData(
94 flags=flags,
95 training_status=training_status,
96 training_status_string=training_status_string,
97 )
99 def _encode_value(self, data: TrainingStatusData) -> bytearray:
100 """Encode Training Status data to bytes.
102 Args:
103 data: TrainingStatusData instance.
105 Returns:
106 Encoded bytes representing the training status.
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))
113 if data.training_status_string is not None:
114 result.extend(data.training_status_string.encode("utf-8"))
116 return result