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

26 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-30 00:10 +0000

1"""VOC Concentration characteristic implementation.""" 

2 

3from __future__ import annotations 

4 

5from typing import cast 

6 

7from ..context import CharacteristicContext 

8from .base import BaseCharacteristic 

9from .templates import Uint16Template 

10 

11 

12# Special value constants for VOC Concentration characteristic 

13class VOCConcentrationValues: # pylint: disable=too-few-public-methods 

14 """Special values for VOC Concentration characteristic per Bluetooth SIG specification.""" 

15 

16 VALUE_65534_OR_GREATER = 0xFFFE # Indicates value is 65534 or greater 

17 VALUE_UNKNOWN = 0xFFFF # Indicates value is not known 

18 

19 

20class VOCConcentrationCharacteristic(BaseCharacteristic): 

21 """Volatile Organic Compounds concentration characteristic (0x2BE7). 

22 

23 Uses uint16 format as per SIG specification. 

24 Unit: ppb (parts per billion) 

25 Range: 0-65533 

26 (VOCConcentrationValues.VALUE_65534_OR_GREATER = ≥65534, 

27 VOCConcentrationValues.VALUE_UNKNOWN = unknown) 

28 """ 

29 

30 _template = Uint16Template() 

31 

32 _manual_unit: str | None = "ppb" # Unit as per SIG specification 

33 min_value: int | float | None = 0 

34 max_value: int | float | None = VOCConcentrationValues.VALUE_65534_OR_GREATER - 1 # 65533 

35 

36 def decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> int: 

37 """Parse VOC concentration value with special value handling.""" 

38 raw_value = cast(int, super().decode_value(data)) 

39 

40 # Handle special values per SIG specification 

41 if raw_value == VOCConcentrationValues.VALUE_65534_OR_GREATER: 

42 # Value is 65534 or greater - return a large value 

43 return 65534 

44 if raw_value == VOCConcentrationValues.VALUE_UNKNOWN: 

45 # Value is not known - could raise exception or return None 

46 # For now, return a sentinel value that tests can check 

47 raise ValueError("VOC concentration value is not known") 

48 

49 return raw_value 

50 

51 def encode_value(self, data: int) -> bytearray: 

52 """Encode VOC concentration with special value handling.""" 

53 if data < 0: 

54 raise ValueError("VOC concentration cannot be negative") 

55 if data >= VOCConcentrationValues.VALUE_65534_OR_GREATER: 

56 # Encode as "65534 or greater" per SIG specification 

57 return super().encode_value(VOCConcentrationValues.VALUE_65534_OR_GREATER) 

58 

59 return super().encode_value(data)