Coverage for src / bluetooth_sig / types / advertising / channel_map_update.py: 100%

22 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-18 11:17 +0000

1"""Channel Map Update Indication (AD 0x28, Core Spec Vol 3, Part C §11). 

2 

3Decodes the Channel Map Update Indication AD type that carries a new 

4data-channel map and the connection-event instant at which it takes effect. 

5""" 

6 

7from __future__ import annotations 

8 

9import msgspec 

10 

11from bluetooth_sig.gatt.characteristics.utils import DataParser 

12 

13# Channel Map Update Indication layout sizes 

14CHANNEL_MAP_LENGTH = 5 # 5-byte bitmask of data channels 0-36 

15CHANNEL_MAP_INSTANT_OFFSET = CHANNEL_MAP_LENGTH # instant immediately follows map 

16 

17# BLE data channel range (Core Spec Vol 6, Part B §1.4.1) 

18MAX_DATA_CHANNEL = 36 

19 

20 

21class ChannelMapUpdateIndication(msgspec.Struct, frozen=True, kw_only=True): 

22 """Channel Map Update Indication (Core Spec Vol 3, Part C, §11). 

23 

24 Carries a new channel map and the connection-event instant at which 

25 it takes effect. 

26 

27 Format: channel_map (5 bytes) + instant (2 bytes LE uint16). 

28 

29 Attributes: 

30 channel_map: 5-byte bitmask of used data channels (channels 0-36). 

31 Bit *n* = 1 means channel *n* is in use. 

32 instant: Connection event count at which the new map takes effect. 

33 

34 """ 

35 

36 channel_map: bytes 

37 instant: int 

38 

39 @classmethod 

40 def decode(cls, data: bytes | bytearray) -> ChannelMapUpdateIndication: 

41 """Decode Channel Map Update Indication AD. 

42 

43 DataParser raises ``InsufficientDataError`` if the payload is 

44 shorter than the required 7 bytes. 

45 

46 Args: 

47 data: Raw AD data bytes (excluding length and AD type). 

48 

49 Returns: 

50 Parsed ChannelMapUpdateIndication. 

51 

52 """ 

53 channel_map = bytes(data[:CHANNEL_MAP_LENGTH]) 

54 instant = DataParser.parse_int16(data, CHANNEL_MAP_INSTANT_OFFSET, signed=False) 

55 

56 return cls(channel_map=channel_map, instant=instant) 

57 

58 def is_channel_used(self, channel: int) -> bool: 

59 """Check if a specific data channel is marked as used. 

60 

61 Args: 

62 channel: Channel number (0-36). 

63 

64 Returns: 

65 ``True`` if the channel is used in the new map. 

66 

67 Raises: 

68 ValueError: If channel is out of range. 

69 

70 """ 

71 if not 0 <= channel <= MAX_DATA_CHANNEL: 

72 msg = f"Channel must be 0-{MAX_DATA_CHANNEL}, got {channel}" 

73 raise ValueError(msg) 

74 

75 byte_index = channel // 8 

76 bit_index = channel % 8 

77 return bool(self.channel_map[byte_index] & (1 << bit_index)) 

78 

79 

80__all__ = [ 

81 "CHANNEL_MAP_LENGTH", 

82 "CHANNEL_MAP_INSTANT_OFFSET", 

83 "ChannelMapUpdateIndication", 

84 "MAX_DATA_CHANNEL", 

85]