Coverage for src / bluetooth_sig / gatt / characteristics / bearer_list_current_calls.py: 98%

60 statements  

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

1"""Bearer List Current Calls characteristic (0x2BB9).""" 

2 

3from __future__ import annotations 

4 

5from enum import IntEnum, IntFlag 

6 

7import msgspec 

8 

9from ..context import CharacteristicContext 

10from .base import BaseCharacteristic 

11from .utils import DataParser 

12 

13 

14class CallState(IntEnum): 

15 """Call state values per TBS specification.""" 

16 

17 INCOMING = 0x00 

18 DIALING = 0x01 

19 ALERTING = 0x02 

20 ACTIVE = 0x03 

21 LOCALLY_HELD = 0x04 

22 REMOTELY_HELD = 0x05 

23 LOCALLY_AND_REMOTELY_HELD = 0x06 

24 

25 

26class CallFlags(IntFlag): 

27 """Call flags per TBS specification. 

28 

29 Bit 0: Incoming/Outgoing — 0 = incoming call, 1 = outgoing call (Table 3.7). 

30 """ 

31 

32 OUTGOING = 0x01 

33 WITHHELD = 0x02 

34 WITHHELD_BY_NETWORK = 0x04 

35 

36 

37class CallListItem(msgspec.Struct, frozen=True, kw_only=True): 

38 """A single call entry in the Bearer List Current Calls.""" 

39 

40 call_index: int 

41 state: CallState 

42 call_flags: CallFlags 

43 uri: str 

44 

45 

46class BearerListCurrentCallsData(msgspec.Struct, frozen=True, kw_only=True): 

47 """Parsed data from Bearer List Current Calls characteristic.""" 

48 

49 calls: tuple[CallListItem, ...] 

50 

51 

52_MIN_ITEM_LENGTH = 3 # call_index (uint8) + call_state (uint8) + call_flags (uint8) 

53 

54 

55class BearerListCurrentCallsCharacteristic(BaseCharacteristic[BearerListCurrentCallsData]): 

56 """Bearer List Current Calls characteristic (0x2BB9). 

57 

58 org.bluetooth.characteristic.bearer_list_current_calls 

59 

60 List of current calls on the telephone bearer. Each list item 

61 contains a length prefix, call index, state, flags, and URI. 

62 """ 

63 

64 min_length = 0 

65 allow_variable_length = True 

66 

67 def _decode_value( 

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

69 ) -> BearerListCurrentCallsData: 

70 calls: list[CallListItem] = [] 

71 offset = 0 

72 

73 while offset < len(data): 

74 if offset + 1 > len(data): 

75 break 

76 item_length = DataParser.parse_int8(data, offset, signed=False) 

77 offset += 1 

78 

79 if item_length < _MIN_ITEM_LENGTH or offset + item_length > len(data): 

80 break 

81 

82 call_index = DataParser.parse_int8(data, offset, signed=False) 

83 state = CallState(DataParser.parse_int8(data, offset + 1, signed=False)) 

84 call_flags = CallFlags(DataParser.parse_int8(data, offset + 2, signed=False)) 

85 

86 uri_length = item_length - 3 

87 uri = "" 

88 if uri_length > 0: 

89 uri = DataParser.parse_utf8_string(data[offset + 3 : offset + 3 + uri_length]) 

90 

91 calls.append( 

92 CallListItem( 

93 call_index=call_index, 

94 state=state, 

95 call_flags=call_flags, 

96 uri=uri, 

97 ) 

98 ) 

99 offset += item_length 

100 

101 return BearerListCurrentCallsData(calls=tuple(calls)) 

102 

103 def _encode_value(self, data: BearerListCurrentCallsData) -> bytearray: 

104 result = bytearray() 

105 for call in data.calls: 

106 uri_bytes = call.uri.encode("utf-8") 

107 item_length = 3 + len(uri_bytes) 

108 result.append(item_length) 

109 result.extend(DataParser.encode_int8(call.call_index, signed=False)) 

110 result.extend(DataParser.encode_int8(int(call.state), signed=False)) 

111 result.extend(DataParser.encode_int8(int(call.call_flags), signed=False)) 

112 result.extend(uri_bytes) 

113 return result