Coverage for src/bluetooth_sig/gatt/descriptors/characteristic_presentation_format.py: 100%

66 statements  

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

1"""Characteristic Presentation Format Descriptor implementation.""" 

2 

3from __future__ import annotations 

4 

5from enum import IntEnum 

6 

7import msgspec 

8 

9from ..characteristics.utils import DataParser 

10from .base import BaseDescriptor 

11 

12 

13class FormatNamespace(IntEnum): 

14 """Format namespace values for Characteristic Presentation Format.""" 

15 

16 BLUETOOTH_SIG_ASSIGNED_NUMBERS = 0x01 

17 RESERVED = 0x02 

18 

19 

20class FormatType(IntEnum): 

21 """Format type values for Characteristic Presentation Format.""" 

22 

23 # Common Bluetooth SIG format types 

24 BOOLEAN = 0x01 

25 UINT2 = 0x02 

26 UINT4 = 0x03 

27 UINT8 = 0x04 

28 UINT12 = 0x05 

29 UINT16 = 0x06 

30 UINT24 = 0x07 

31 UINT32 = 0x08 

32 UINT48 = 0x09 

33 UINT64 = 0x0A 

34 UINT128 = 0x0B 

35 SINT8 = 0x0C 

36 SINT12 = 0x0D 

37 SINT16 = 0x0E 

38 SINT24 = 0x0F 

39 SINT32 = 0x10 

40 SINT48 = 0x11 

41 SINT64 = 0x12 

42 SINT128 = 0x13 

43 FLOAT32 = 0x14 

44 FLOAT64 = 0x15 

45 SFLOAT = 0x16 

46 FLOAT = 0x17 

47 DUINT16 = 0x18 

48 UTF8S = 0x19 

49 UTF16S = 0x1A 

50 STRUCT = 0x1B 

51 

52 

53class CharacteristicPresentationFormatData(msgspec.Struct, frozen=True, kw_only=True): 

54 """Characteristic Presentation Format descriptor data.""" 

55 

56 format: int 

57 exponent: int 

58 unit: int 

59 namespace: int 

60 description: int 

61 

62 

63class CharacteristicPresentationFormatDescriptor(BaseDescriptor): 

64 """Characteristic Presentation Format Descriptor (0x2904). 

65 

66 Describes how characteristic values should be presented to users. 

67 Contains format, exponent, unit, namespace, and description information. 

68 """ 

69 

70 def _has_structured_data(self) -> bool: 

71 return True 

72 

73 def _get_data_format(self) -> str: 

74 return "struct" 

75 

76 def _parse_descriptor_value(self, data: bytes) -> CharacteristicPresentationFormatData: 

77 """Parse Characteristic Presentation Format value. 

78 

79 Format: 7 bytes 

80 - Format (1 byte): Data type format 

81 - Exponent (1 byte): Base 10 exponent (-128 to 127) 

82 - Unit (2 bytes): Unit of measurement (little-endian) 

83 - Namespace (1 byte): Namespace for description 

84 - Description (2 bytes): Description identifier (little-endian) 

85 

86 Args: 

87 data: Raw bytes (should be 7 bytes) 

88 

89 Returns: 

90 CharacteristicPresentationFormatData with format information 

91 

92 Raises: 

93 ValueError: If data is not exactly 7 bytes 

94 """ 

95 if len(data) != 7: 

96 raise ValueError(f"Characteristic Presentation Format data must be exactly 7 bytes, got {len(data)}") 

97 

98 return CharacteristicPresentationFormatData( 

99 format=DataParser.parse_int8(data, offset=0), 

100 exponent=DataParser.parse_int8(data, offset=1, signed=True), 

101 unit=DataParser.parse_int16(data, offset=2, endian="little"), 

102 namespace=DataParser.parse_int8(data, offset=4), 

103 description=DataParser.parse_int16(data, offset=5, endian="little"), 

104 ) 

105 

106 def get_format_type(self, data: bytes) -> int: 

107 """Get the format type.""" 

108 parsed = self._parse_descriptor_value(data) 

109 return parsed.format 

110 

111 def get_exponent(self, data: bytes) -> int: 

112 """Get the exponent for scaling.""" 

113 parsed = self._parse_descriptor_value(data) 

114 return parsed.exponent 

115 

116 def get_unit(self, data: bytes) -> int: 

117 """Get the unit identifier.""" 

118 parsed = self._parse_descriptor_value(data) 

119 return parsed.unit 

120 

121 def get_namespace(self, data: bytes) -> int: 

122 """Get the namespace identifier.""" 

123 parsed = self._parse_descriptor_value(data) 

124 return parsed.namespace 

125 

126 def get_description(self, data: bytes) -> int: 

127 """Get the description identifier.""" 

128 parsed = self._parse_descriptor_value(data) 

129 return parsed.description