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
« 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).
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"""
7from __future__ import annotations
9import msgspec
11from bluetooth_sig.gatt.characteristics.utils import DataParser
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
17# BLE data channel range (Core Spec Vol 6, Part B §1.4.1)
18MAX_DATA_CHANNEL = 36
21class ChannelMapUpdateIndication(msgspec.Struct, frozen=True, kw_only=True):
22 """Channel Map Update Indication (Core Spec Vol 3, Part C, §11).
24 Carries a new channel map and the connection-event instant at which
25 it takes effect.
27 Format: channel_map (5 bytes) + instant (2 bytes LE uint16).
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.
34 """
36 channel_map: bytes
37 instant: int
39 @classmethod
40 def decode(cls, data: bytes | bytearray) -> ChannelMapUpdateIndication:
41 """Decode Channel Map Update Indication AD.
43 DataParser raises ``InsufficientDataError`` if the payload is
44 shorter than the required 7 bytes.
46 Args:
47 data: Raw AD data bytes (excluding length and AD type).
49 Returns:
50 Parsed ChannelMapUpdateIndication.
52 """
53 channel_map = bytes(data[:CHANNEL_MAP_LENGTH])
54 instant = DataParser.parse_int16(data, CHANNEL_MAP_INSTANT_OFFSET, signed=False)
56 return cls(channel_map=channel_map, instant=instant)
58 def is_channel_used(self, channel: int) -> bool:
59 """Check if a specific data channel is marked as used.
61 Args:
62 channel: Channel number (0-36).
64 Returns:
65 ``True`` if the channel is used in the new map.
67 Raises:
68 ValueError: If channel is out of range.
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)
75 byte_index = channel // 8
76 bit_index = channel % 8
77 return bool(self.channel_map[byte_index] & (1 << bit_index))
80__all__ = [
81 "CHANNEL_MAP_LENGTH",
82 "CHANNEL_MAP_INSTANT_OFFSET",
83 "ChannelMapUpdateIndication",
84 "MAX_DATA_CHANNEL",
85]