Coverage for src/bluetooth_sig/gatt/characteristics/cookware_sensor_data.py: 98%

42 statements  

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

1"""Cookware Sensor Data characteristic (0x2C2C).""" 

2 

3from __future__ import annotations 

4 

5from enum import IntFlag 

6 

7import msgspec 

8 

9from ..constants import SIZE_UINT8 

10from ..context import CharacteristicContext 

11from ..descriptor_utils import get_descriptor_from_context 

12from ..descriptors.cooking_sensor_info import CookingSensorInfoData, CookingSensorInfoDescriptor 

13from .base import BaseCharacteristic 

14from .cooking_common import validate_flags 

15from .cooking_sensor_common import CookingSensorValue, encode_cooking_sensor_value, parse_cooking_sensor_value 

16from .utils import DataParser 

17 

18 

19class CookwareSensorStatus(IntFlag): 

20 """Cookware Sensor Data status bits.""" 

21 

22 NO_ERROR = 0 

23 MEASURED_VALUE_OUT_OF_RANGE = 1 << 0 

24 SENSOR_INTERNAL_ERROR = 1 << 1 

25 

26 

27class CookwareSensorDataValue(msgspec.Struct, frozen=True, kw_only=True): 

28 """Decoded Cookware Sensor Data payload. 

29 

30 The sensor data field's type is determined by the Cooking Sensor Info 

31 descriptor UUID for the specific sensor instance. 

32 """ 

33 

34 sensor_status: CookwareSensorStatus 

35 sensor_data: CookingSensorValue 

36 

37 

38class CookwareSensorDataCharacteristic(BaseCharacteristic[CookwareSensorDataValue]): 

39 """Cookware Sensor Data characteristic (0x2C2C). 

40 

41 org.bluetooth.characteristic.cookware_sensor_data 

42 """ 

43 

44 min_length = SIZE_UINT8 

45 allow_variable_length = True 

46 

47 def _decode_value( 

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

49 ) -> CookwareSensorDataValue: 

50 sensor_status = CookwareSensorStatus(DataParser.parse_int8(data, 0, signed=False)) 

51 validate_flags(sensor_status, CookwareSensorStatus, "Sensor Status") 

52 sensor_data = bytearray(data[SIZE_UINT8:]) 

53 sensor_info = _get_sensor_info(ctx) 

54 if sensor_info is None: 

55 raise ValueError("Cooking Sensor Info descriptor is required to decode Sensor Data") 

56 return CookwareSensorDataValue( 

57 sensor_status=sensor_status, 

58 sensor_data=parse_cooking_sensor_value(sensor_info.sensor_uuid, sensor_data), 

59 ) 

60 

61 def _encode_value(self, data: CookwareSensorDataValue) -> bytearray: 

62 validate_flags(data.sensor_status, CookwareSensorStatus, "Sensor Status") 

63 result = bytearray() 

64 result.extend(DataParser.encode_int8(int(data.sensor_status), signed=False)) 

65 result.extend(encode_cooking_sensor_value(data.sensor_data)) 

66 return result 

67 

68 

69def _get_sensor_info(ctx: CharacteristicContext | None) -> CookingSensorInfoData | None: 

70 """Return Cooking Sensor Info descriptor data from parse context, if present.""" 

71 descriptor_data = get_descriptor_from_context(ctx, CookingSensorInfoDescriptor) 

72 if descriptor_data is None or not descriptor_data.parse_success: 

73 return None 

74 if isinstance(descriptor_data.value, CookingSensorInfoData): 

75 return descriptor_data.value 

76 raise ValueError("Cooking Sensor Info descriptor has unexpected value type")