Coverage for src/bluetooth_sig/gatt/characteristics/cooking_step_status.py: 98%
40 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-28 01:26 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-28 01:26 +0000
1"""Cooking Step Status characteristic (0x2C28)."""
3from __future__ import annotations
5from enum import IntFlag
7import msgspec
9from bluetooth_sig.types import SpecialValueResult, SpecialValueType
11from ..context import CharacteristicContext
12from ..exceptions import SpecialValueDetectedError
13from .base import BaseCharacteristic
14from .cooking_common import validate_flags
15from .utils import DataParser
17_REMAINING_TIME_MAX_SECONDS = 0xFF00
18_REMAINING_TIME_UNKNOWN = 0xFFFF
21class CookingStepStatusFlags(IntFlag):
22 """Bit flags for cooking step status."""
24 USER_ACTION_REQUIRED = 1 << 0
25 LAST_COOKING_STEP = 1 << 1
26 COOKING_STEP_STARTED = 1 << 2
29class CookingStepStatusData(msgspec.Struct, frozen=True, kw_only=True):
30 """Decoded Cooking Step Status payload."""
32 flags: CookingStepStatusFlags
33 cooking_step_index: int
34 remaining_time_seconds: int
37class CookingStepStatusCharacteristic(BaseCharacteristic[CookingStepStatusData]):
38 """Cooking Step Status characteristic (0x2C28).
40 org.bluetooth.characteristic.cooking_step_status
41 """
43 expected_length = 5
45 def _decode_value(
46 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
47 ) -> CookingStepStatusData:
48 flags = CookingStepStatusFlags(DataParser.parse_int8(data, 0, signed=False))
49 validate_flags(flags, CookingStepStatusFlags, "Cooking Step Status Flags")
50 cooking_step_index = DataParser.parse_int16(data, 1, signed=False)
51 remaining_time_raw = DataParser.parse_int16(data, 3, signed=False)
52 if remaining_time_raw == _REMAINING_TIME_UNKNOWN:
53 raise SpecialValueDetectedError(
54 special_value=SpecialValueResult(
55 raw_value=_REMAINING_TIME_UNKNOWN,
56 meaning="value is not known",
57 value_type=SpecialValueType.UNKNOWN,
58 ),
59 name=self.name,
60 uuid=self.uuid,
61 raw_data=bytes(data),
62 raw_int=_REMAINING_TIME_UNKNOWN,
63 )
64 if remaining_time_raw > _REMAINING_TIME_MAX_SECONDS:
65 raise ValueError("Remaining Time uses an RFU value")
66 return CookingStepStatusData(
67 flags=flags,
68 cooking_step_index=cooking_step_index,
69 remaining_time_seconds=remaining_time_raw,
70 )
72 def _encode_value(self, data: CookingStepStatusData) -> bytearray:
73 validate_flags(data.flags, CookingStepStatusFlags, "Cooking Step Status Flags")
74 result = bytearray()
75 result.extend(DataParser.encode_int8(int(data.flags), signed=False))
76 result.extend(DataParser.encode_int16(data.cooking_step_index, signed=False))
77 if 0 <= data.remaining_time_seconds <= _REMAINING_TIME_MAX_SECONDS:
78 result.extend(DataParser.encode_int16(data.remaining_time_seconds, signed=False))
79 else:
80 raise ValueError("Remaining Time must be 0x0000-0xFF00")
81 return result