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

50 statements  

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

1"""Media Control Point characteristic (0x2BA4).""" 

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

16 """Media Control Point operation codes per MCS spec.""" 

17 

18 PLAY = 0x01 

19 PAUSE = 0x02 

20 FAST_REWIND = 0x03 

21 FAST_FORWARD = 0x04 

22 STOP = 0x05 

23 MOVE_RELATIVE = 0x10 

24 PREVIOUS_SEGMENT = 0x20 

25 NEXT_SEGMENT = 0x21 

26 FIRST_SEGMENT = 0x22 

27 LAST_SEGMENT = 0x23 

28 GOTO_SEGMENT = 0x24 

29 PREVIOUS_TRACK = 0x30 

30 NEXT_TRACK = 0x31 

31 FIRST_TRACK = 0x32 

32 LAST_TRACK = 0x33 

33 GOTO_TRACK = 0x34 

34 PREVIOUS_GROUP = 0x40 

35 NEXT_GROUP = 0x41 

36 FIRST_GROUP = 0x42 

37 LAST_GROUP = 0x43 

38 GOTO_GROUP = 0x44 

39 

40 

41# Opcodes that carry a sint32 parameter 

42_OPCODES_WITH_SINT32 = frozenset( 

43 { 

44 MediaControlPointOpCode.MOVE_RELATIVE, 

45 MediaControlPointOpCode.GOTO_SEGMENT, 

46 MediaControlPointOpCode.GOTO_TRACK, 

47 MediaControlPointOpCode.GOTO_GROUP, 

48 } 

49) 

50 

51_SINT32_MINIMUM_LENGTH = 5 

52 

53 

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

55 """Parsed data from Media Control Point characteristic. 

56 

57 The parameter field is present only for opcodes that require a sint32 

58 operand (Move Relative, Goto Segment, Goto Track, Goto Group). 

59 """ 

60 

61 op_code: MediaControlPointOpCode 

62 parameter: int | None = None 

63 

64 

65class MediaControlPointCharacteristic(BaseCharacteristic[MediaControlPointData]): 

66 """Media Control Point characteristic (0x2BA4). 

67 

68 org.bluetooth.characteristic.media_control_point 

69 

70 Used for controlling media playback in the Media Control Service. 

71 """ 

72 

73 _manual_role = CharacteristicRole.CONTROL 

74 min_length = 1 

75 allow_variable_length = True 

76 

77 def _decode_value( 

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

79 ) -> MediaControlPointData: 

80 """Parse Media Control Point data. 

81 

82 Format: opcode (uint8) + optional sint32 parameter. 

83 """ 

84 op_code = MediaControlPointOpCode(DataParser.parse_int8(data, 0, signed=False)) 

85 

86 parameter = None 

87 if op_code in _OPCODES_WITH_SINT32 and len(data) >= _SINT32_MINIMUM_LENGTH: 

88 parameter = DataParser.parse_int32(data, 1, signed=True) 

89 

90 return MediaControlPointData(op_code=op_code, parameter=parameter) 

91 

92 def _encode_value(self, data: MediaControlPointData) -> bytearray: 

93 """Encode Media Control Point data to bytes.""" 

94 result = bytearray() 

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

96 if data.parameter is not None: 

97 result += DataParser.encode_int32(data.parameter, signed=True) 

98 return result