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

31 statements  

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

1"""Shared typed sensor-value formats for Cookware Service sensor payloads.""" 

2 

3from __future__ import annotations 

4 

5from typing import ClassVar 

6 

7import msgspec 

8 

9from bluetooth_sig.types.uuid import BluetoothUUID 

10 

11from ..constants import SIZE_UINT16, SIZE_UINT32 

12from .base import BaseCharacteristic 

13from .cooking_common import COOKING_TEMPERATURE, HUMIDITY 

14from .pressure import PressureCharacteristic 

15 

16_PRESSURE = PressureCharacteristic() 

17 

18 

19class CookingSensorValue(msgspec.Struct, frozen=True, kw_only=True): 

20 """Typed sensor value selected by a Cooking Sensor Info UUID.""" 

21 

22 sensor_uuid: BluetoothUUID 

23 value: float 

24 

25 

26class CookingSensorDataFormat(msgspec.Struct, frozen=True, kw_only=True): 

27 """Permitted Cookware Sensor Data format from Assigned Numbers.""" 

28 

29 characteristic: BaseCharacteristic[float] 

30 value_size: int 

31 

32 

33class CookingSensorFormats: 

34 """Permitted sensor data formats for CWS.""" 

35 

36 formats: ClassVar[dict[BluetoothUUID, CookingSensorDataFormat]] = { 

37 COOKING_TEMPERATURE.uuid: CookingSensorDataFormat(characteristic=COOKING_TEMPERATURE, value_size=SIZE_UINT16), 

38 HUMIDITY.uuid: CookingSensorDataFormat(characteristic=HUMIDITY, value_size=SIZE_UINT16), 

39 _PRESSURE.uuid: CookingSensorDataFormat(characteristic=_PRESSURE, value_size=SIZE_UINT32), 

40 } 

41 

42 @classmethod 

43 def get(cls, sensor_uuid: BluetoothUUID) -> CookingSensorDataFormat: 

44 """Return the permitted format for a Cooking Sensor Info UUID.""" 

45 sensor_format = cls.formats.get(sensor_uuid) 

46 if sensor_format is None: 

47 raise ValueError("Cooking Sensor Info UUID is not permitted for Cookware Sensor Data") 

48 return sensor_format 

49 

50 

51def parse_cooking_sensor_value(sensor_uuid: BluetoothUUID, data: bytes | bytearray) -> CookingSensorValue: 

52 """Parse a CWS sensor value using the format selected by the sensor UUID.""" 

53 sensor_format = CookingSensorFormats.get(sensor_uuid) 

54 if len(data) != sensor_format.value_size: 

55 raise ValueError( 

56 f"Sensor Data for UUID {sensor_uuid.short_form} must be {sensor_format.value_size} octets, got {len(data)}" 

57 ) 

58 return CookingSensorValue( 

59 sensor_uuid=sensor_uuid, 

60 value=sensor_format.characteristic.parse_value(bytearray(data)), 

61 ) 

62 

63 

64def encode_cooking_sensor_value(sensor_value: CookingSensorValue) -> bytearray: 

65 """Encode a CWS sensor value using the format selected by its UUID.""" 

66 sensor_format = CookingSensorFormats.get(sensor_value.sensor_uuid) 

67 return sensor_format.characteristic.build_value(sensor_value.value)