Coverage for src / bluetooth_sig / gatt / characteristics / volume_offset_control_point.py: 100%
32 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"""Volume Offset Control Point characteristic (0x2B82)."""
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 VolumeOffsetControlPointOpCode(IntEnum):
16 """Volume Offset Control Point operation codes."""
18 SET_VOLUME_OFFSET = 0x01
21_OFFSET_MINIMUM_LENGTH = 4
24class VolumeOffsetControlPointData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
25 """Parsed data from Volume Offset Control Point characteristic.
27 Contains the opcode, change counter, and optional volume offset parameter.
28 """
30 op_code: VolumeOffsetControlPointOpCode
31 change_counter: int
32 volume_offset: int | None = None
35class VolumeOffsetControlPointCharacteristic(BaseCharacteristic[VolumeOffsetControlPointData]):
36 """Volume Offset Control Point characteristic (0x2B82).
38 org.bluetooth.characteristic.volume_offset_control_point
40 Used for controlling volume offset in the Volume Offset Control Service.
41 """
43 _manual_role = CharacteristicRole.CONTROL
44 min_length = 2
45 allow_variable_length = True
47 def _decode_value(
48 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
49 ) -> VolumeOffsetControlPointData:
50 """Parse Volume Offset Control Point data.
52 Format: opcode (uint8) + change_counter (uint8) + optional sint16 offset.
53 """
54 op_code = VolumeOffsetControlPointOpCode(DataParser.parse_int8(data, 0, signed=False))
55 change_counter = DataParser.parse_int8(data, 1, signed=False)
57 volume_offset = None
58 if op_code == VolumeOffsetControlPointOpCode.SET_VOLUME_OFFSET and len(data) >= _OFFSET_MINIMUM_LENGTH:
59 volume_offset = DataParser.parse_int16(data, 2, signed=True)
61 return VolumeOffsetControlPointData(
62 op_code=op_code,
63 change_counter=change_counter,
64 volume_offset=volume_offset,
65 )
67 def _encode_value(self, data: VolumeOffsetControlPointData) -> bytearray:
68 """Encode Volume Offset Control Point data to bytes."""
69 result = bytearray()
70 result += DataParser.encode_int8(int(data.op_code))
71 result += DataParser.encode_int8(data.change_counter)
72 if data.volume_offset is not None:
73 result += DataParser.encode_int16(data.volume_offset, signed=True)
74 return result