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

40 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-03 16:41 +0000

1"""Search Control Point characteristic (0x2BA7).""" 

2 

3from __future__ import annotations 

4 

5from enum import IntEnum 

6 

7import msgspec 

8 

9from ...types.gatt_enums import CharacteristicRole 

10from ..context import CharacteristicContext 

11from .base import BaseCharacteristic 

12from .utils import DataParser 

13 

14 

15class SearchControlPointType(IntEnum): 

16 """Search Control Point search types per MCS spec.""" 

17 

18 TRACK_NAME = 0x01 

19 ARTIST_NAME = 0x02 

20 ALBUM_NAME = 0x03 

21 GROUP_NAME = 0x04 

22 EARLIEST_YEAR = 0x05 

23 LATEST_YEAR = 0x06 

24 GENRE = 0x07 

25 ONLY_TRACKS = 0x08 

26 ONLY_GROUPS = 0x09 

27 

28 

29_PARAMETER_START_INDEX = 2 

30 

31 

32class SearchControlPointData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods 

33 """Parsed data from Search Control Point characteristic. 

34 

35 Format: Length(uint8) + Type(uint8) + optional Parameter (UTF-8 string). 

36 """ 

37 

38 length: int 

39 search_type: SearchControlPointType 

40 parameter: str | None = None 

41 

42 

43class SearchControlPointCharacteristic(BaseCharacteristic[SearchControlPointData]): 

44 """Search Control Point characteristic (0x2BA7). 

45 

46 org.bluetooth.characteristic.search_control_point 

47 

48 Used for searching media in the Media Control Service. 

49 """ 

50 

51 _manual_role = CharacteristicRole.CONTROL 

52 min_length = 2 

53 allow_variable_length = True 

54 

55 def _decode_value( 

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

57 ) -> SearchControlPointData: 

58 """Parse Search Control Point data. 

59 

60 Format: length (uint8) + type (uint8) + optional parameter string. 

61 """ 

62 length = DataParser.parse_int8(data, 0, signed=False) 

63 search_type = SearchControlPointType(DataParser.parse_int8(data, 1, signed=False)) 

64 

65 parameter = None 

66 if len(data) > _PARAMETER_START_INDEX: 

67 parameter = data[_PARAMETER_START_INDEX:].decode("utf-8", errors="replace") 

68 

69 return SearchControlPointData( 

70 length=length, 

71 search_type=search_type, 

72 parameter=parameter, 

73 ) 

74 

75 def _encode_value(self, data: SearchControlPointData) -> bytearray: 

76 """Encode Search Control Point data to bytes.""" 

77 result = bytearray() 

78 result += DataParser.encode_int8(data.length) 

79 result += DataParser.encode_int8(int(data.search_type)) 

80 if data.parameter is not None: 

81 result += data.parameter.encode("utf-8") 

82 return result