Coverage for src / bluetooth_sig / gatt / characteristics / call_control_point.py: 91%
43 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"""Call Control Point characteristic (0x2BBE)."""
3from __future__ import annotations
5from enum import IntEnum
7import msgspec
9from ..context import CharacteristicContext
10from .base import BaseCharacteristic
11from .utils import DataParser
14class CallControlPointOpCode(IntEnum):
15 """Call Control Point operation codes per TBS specification."""
17 ACCEPT = 0x00
18 TERMINATE = 0x01
19 LOCAL_HOLD = 0x02
20 LOCAL_RETRIEVE = 0x03
21 ORIGINATE = 0x04
22 JOIN = 0x05
25class CallControlPointData(msgspec.Struct, frozen=True, kw_only=True):
26 """Parsed data from Call Control Point characteristic.
28 For ACCEPT/TERMINATE/LOCAL_HOLD/LOCAL_RETRIEVE: call_index is set.
29 For ORIGINATE: uri is set.
30 For JOIN: call_indexes is set.
31 """
33 op_code: CallControlPointOpCode
34 call_index: int | None = None
35 uri: str | None = None
36 call_indexes: tuple[int, ...] | None = None
39class CallControlPointCharacteristic(BaseCharacteristic[CallControlPointData]):
40 """Call Control Point characteristic (0x2BBE).
42 org.bluetooth.characteristic.call_control_point
44 Used to control calls on the telephone bearer.
45 """
47 min_length = 1
48 allow_variable_length = True
50 def _decode_value(
51 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
52 ) -> CallControlPointData:
53 op_code = CallControlPointOpCode(DataParser.parse_int8(data, 0, signed=False))
55 if op_code in (
56 CallControlPointOpCode.ACCEPT,
57 CallControlPointOpCode.TERMINATE,
58 CallControlPointOpCode.LOCAL_HOLD,
59 CallControlPointOpCode.LOCAL_RETRIEVE,
60 ):
61 call_index = DataParser.parse_int8(data, 1, signed=False) if len(data) > 1 else None
62 return CallControlPointData(op_code=op_code, call_index=call_index)
64 if op_code == CallControlPointOpCode.ORIGINATE:
65 uri = DataParser.parse_utf8_string(data[1:]) if len(data) > 1 else None
66 return CallControlPointData(op_code=op_code, uri=uri)
68 if op_code == CallControlPointOpCode.JOIN:
69 indexes = tuple(data[i] for i in range(1, len(data)))
70 return CallControlPointData(op_code=op_code, call_indexes=indexes)
72 return CallControlPointData(op_code=op_code)
74 def _encode_value(self, data: CallControlPointData) -> bytearray:
75 result = bytearray([int(data.op_code)])
77 if data.call_index is not None:
78 result.extend(DataParser.encode_int8(data.call_index, signed=False))
79 elif data.uri is not None:
80 result.extend(data.uri.encode("utf-8"))
81 elif data.call_indexes is not None:
82 for idx in data.call_indexes:
83 result.append(idx)
85 return result