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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 16:41 +0000
1"""Search Control Point characteristic (0x2BA7)."""
3from __future__ import annotations
5from enum import IntEnum
7import msgspec
9from ...types.gatt_enums import CharacteristicRole
10from ..context import CharacteristicContext
11from .base import BaseCharacteristic
12from .utils import DataParser
15class SearchControlPointType(IntEnum):
16 """Search Control Point search types per MCS spec."""
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
29_PARAMETER_START_INDEX = 2
32class SearchControlPointData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
33 """Parsed data from Search Control Point characteristic.
35 Format: Length(uint8) + Type(uint8) + optional Parameter (UTF-8 string).
36 """
38 length: int
39 search_type: SearchControlPointType
40 parameter: str | None = None
43class SearchControlPointCharacteristic(BaseCharacteristic[SearchControlPointData]):
44 """Search Control Point characteristic (0x2BA7).
46 org.bluetooth.characteristic.search_control_point
48 Used for searching media in the Media Control Service.
49 """
51 _manual_role = CharacteristicRole.CONTROL
52 min_length = 2
53 allow_variable_length = True
55 def _decode_value(
56 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
57 ) -> SearchControlPointData:
58 """Parse Search Control Point data.
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))
65 parameter = None
66 if len(data) > _PARAMETER_START_INDEX:
67 parameter = data[_PARAMETER_START_INDEX:].decode("utf-8", errors="replace")
69 return SearchControlPointData(
70 length=length,
71 search_type=search_type,
72 parameter=parameter,
73 )
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