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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 16:41 +0000
1"""Media Control Point characteristic (0x2BA4)."""
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 MediaControlPointOpCode(IntEnum):
16 """Media Control Point operation codes per MCS spec."""
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
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)
51_SINT32_MINIMUM_LENGTH = 5
54class MediaControlPointData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
55 """Parsed data from Media Control Point characteristic.
57 The parameter field is present only for opcodes that require a sint32
58 operand (Move Relative, Goto Segment, Goto Track, Goto Group).
59 """
61 op_code: MediaControlPointOpCode
62 parameter: int | None = None
65class MediaControlPointCharacteristic(BaseCharacteristic[MediaControlPointData]):
66 """Media Control Point characteristic (0x2BA4).
68 org.bluetooth.characteristic.media_control_point
70 Used for controlling media playback in the Media Control Service.
71 """
73 _manual_role = CharacteristicRole.CONTROL
74 min_length = 1
75 allow_variable_length = True
77 def _decode_value(
78 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
79 ) -> MediaControlPointData:
80 """Parse Media Control Point data.
82 Format: opcode (uint8) + optional sint32 parameter.
83 """
84 op_code = MediaControlPointOpCode(DataParser.parse_int8(data, 0, signed=False))
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)
90 return MediaControlPointData(op_code=op_code, parameter=parameter)
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