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

32 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-28 01:26 +0000

1"""Recipe Control characteristic (0x2C26).""" 

2 

3from __future__ import annotations 

4 

5from enum import IntEnum 

6 

7import msgspec 

8 

9from ..constants import SIZE_UINT8 

10from ..context import CharacteristicContext 

11from .base import BaseCharacteristic 

12from .utils import DataParser 

13 

14 

15class RecipeControlOpCode(IntEnum): 

16 """Recipe Control operation codes.""" 

17 

18 READ = 0x00 

19 START = 0x01 

20 STOP = 0x02 

21 DELETE = 0x03 

22 

23 

24class RecipeControlData(msgspec.Struct, frozen=True, kw_only=True): 

25 """Decoded Recipe Control payload.""" 

26 

27 op_code: RecipeControlOpCode 

28 cooking_step_index: int | None = None 

29 

30 

31class RecipeControlCharacteristic(BaseCharacteristic[RecipeControlData]): 

32 """Recipe Control characteristic (0x2C26). 

33 

34 org.bluetooth.characteristic.recipe_control 

35 """ 

36 

37 min_length = SIZE_UINT8 

38 max_length = 3 

39 allow_variable_length = True 

40 _FULL_PAYLOAD_LENGTH = 3 

41 

42 def _decode_value( 

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

44 ) -> RecipeControlData: 

45 if len(data) not in {SIZE_UINT8, self._FULL_PAYLOAD_LENGTH}: 

46 raise ValueError("Recipe Control payload must be 1 or 3 bytes") 

47 

48 op_code = RecipeControlOpCode(DataParser.parse_int8(data, 0, signed=False)) 

49 cooking_step_index = ( 

50 DataParser.parse_int16(data, SIZE_UINT8, signed=False) if len(data) == self._FULL_PAYLOAD_LENGTH else None 

51 ) 

52 return RecipeControlData(op_code=op_code, cooking_step_index=cooking_step_index) 

53 

54 def _encode_value(self, data: RecipeControlData) -> bytearray: 

55 result = bytearray() 

56 result.extend(DataParser.encode_int8(int(data.op_code), signed=False)) 

57 if data.cooking_step_index is not None: 

58 result.extend(DataParser.encode_int16(data.cooking_step_index, signed=False)) 

59 return result