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

36 statements  

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

1"""ASE Control Point characteristic (0x2BC6).""" 

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 ASEControlPointOpCode(IntEnum): 

16 """ASE Control Point operation codes.""" 

17 

18 CONFIG_CODEC = 0x01 

19 CONFIG_QOS = 0x02 

20 ENABLE = 0x03 

21 RECEIVER_START_READY = 0x04 

22 DISABLE = 0x05 

23 RECEIVER_STOP_READY = 0x06 

24 UPDATE_METADATA = 0x07 

25 RELEASE = 0x08 

26 

27 

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

29 """Parsed data from ASE Control Point characteristic. 

30 

31 Contains the opcode, number of ASEs, and any additional 

32 ASE-specific parameters as raw bytes. 

33 """ 

34 

35 op_code: ASEControlPointOpCode 

36 number_of_ases: int 

37 parameter_data: bytes = b"" 

38 

39 

40_HEADER_SIZE = 2 # opcode (uint8) + number_of_ases (uint8) 

41 

42 

43class ASEControlPointCharacteristic(BaseCharacteristic[ASEControlPointData]): 

44 """ASE Control Point characteristic (0x2BC6). 

45 

46 org.bluetooth.characteristic.ase_control_point 

47 

48 Used for controlling Audio Stream Endpoints in the Audio 

49 Stream Control Service. 

50 """ 

51 

52 _manual_role = CharacteristicRole.CONTROL 

53 min_length = 2 

54 allow_variable_length = True 

55 

56 def _decode_value( 

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

58 ) -> ASEControlPointData: 

59 """Parse ASE Control Point data. 

60 

61 Format: opcode (uint8) + number_of_ases (uint8) + ASE-specific params. 

62 """ 

63 op_code = ASEControlPointOpCode(DataParser.parse_int8(data, 0, signed=False)) 

64 number_of_ases = DataParser.parse_int8(data, 1, signed=False) 

65 parameter_data = bytes(data[_HEADER_SIZE:]) if len(data) > _HEADER_SIZE else b"" 

66 

67 return ASEControlPointData( 

68 op_code=op_code, 

69 number_of_ases=number_of_ases, 

70 parameter_data=parameter_data, 

71 ) 

72 

73 def _encode_value(self, data: ASEControlPointData) -> bytearray: 

74 """Encode ASE Control Point data to bytes.""" 

75 result = bytearray() 

76 result += DataParser.encode_int8(int(data.op_code)) 

77 result += DataParser.encode_int8(data.number_of_ases) 

78 result += bytearray(data.parameter_data) 

79 return result