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

1"""Cooking Step Status characteristic (0x2C28).""" 

2 

3from __future__ import annotations 

4 

5from enum import IntFlag 

6 

7import msgspec 

8 

9from bluetooth_sig.types import SpecialValueResult, SpecialValueType 

10 

11from ..context import CharacteristicContext 

12from ..exceptions import SpecialValueDetectedError 

13from .base import BaseCharacteristic 

14from .cooking_common import validate_flags 

15from .utils import DataParser 

16 

17_REMAINING_TIME_MAX_SECONDS = 0xFF00 

18_REMAINING_TIME_UNKNOWN = 0xFFFF 

19 

20 

21class CookingStepStatusFlags(IntFlag): 

22 """Bit flags for cooking step status.""" 

23 

24 USER_ACTION_REQUIRED = 1 << 0 

25 LAST_COOKING_STEP = 1 << 1 

26 COOKING_STEP_STARTED = 1 << 2 

27 

28 

29class CookingStepStatusData(msgspec.Struct, frozen=True, kw_only=True): 

30 """Decoded Cooking Step Status payload.""" 

31 

32 flags: CookingStepStatusFlags 

33 cooking_step_index: int 

34 remaining_time_seconds: int 

35 

36 

37class CookingStepStatusCharacteristic(BaseCharacteristic[CookingStepStatusData]): 

38 """Cooking Step Status characteristic (0x2C28). 

39 

40 org.bluetooth.characteristic.cooking_step_status 

41 """ 

42 

43 expected_length = 5 

44 

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 ) 

71 

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