Coverage for src / bluetooth_sig / gatt / descriptors / cccd.py: 81%
42 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"""Client Characteristic Configuration Descriptor implementation."""
3from __future__ import annotations
5from enum import IntFlag
7import msgspec
9from ..characteristics.utils import DataParser
10from .base import BaseDescriptor
13class CCCDFlags(IntFlag):
14 """CCCD (Client Characteristic Configuration Descriptor) flags."""
16 NOTIFICATIONS_ENABLED = 0x0001
17 INDICATIONS_ENABLED = 0x0002
20class CCCDData(msgspec.Struct, frozen=True, kw_only=True):
21 """CCCD (Client Characteristic Configuration Descriptor) data."""
23 notifications_enabled: bool
24 indications_enabled: bool
27class CCCDDescriptor(BaseDescriptor):
28 """Client Characteristic Configuration Descriptor (0x2902).
30 Controls notification and indication settings for a characteristic.
31 Critical for enabling BLE notifications and indications.
32 """
34 _descriptor_name = "Client Characteristic Configuration"
35 _writable = True # CCCD is always writable
37 def _has_structured_data(self) -> bool:
38 return True
40 def _get_data_format(self) -> str:
41 return "uint16"
43 def _parse_descriptor_value(self, data: bytes) -> CCCDData:
44 """Parse CCCD value into notification/indication flags.
46 Args:
47 data: Raw bytes (should be 2 bytes for uint16)
49 Returns:
50 CCCDData with notification/indication flags
52 Raises:
53 ValueError: If data is not exactly 2 bytes
54 """
55 # Parse as little-endian uint16
56 value = DataParser.parse_int16(data, endian="little")
58 return CCCDData(
59 notifications_enabled=bool(value & CCCDFlags.NOTIFICATIONS_ENABLED),
60 indications_enabled=bool(value & CCCDFlags.INDICATIONS_ENABLED),
61 )
63 @staticmethod
64 def create_enable_notifications_value() -> bytes:
65 """Create value to enable notifications."""
66 return CCCDFlags.NOTIFICATIONS_ENABLED.to_bytes(2, "little")
68 @staticmethod
69 def create_enable_indications_value() -> bytes:
70 """Create value to enable indications."""
71 return CCCDFlags.INDICATIONS_ENABLED.to_bytes(2, "little")
73 @staticmethod
74 def create_enable_both_value() -> bytes:
75 """Create value to enable both notifications and indications."""
76 return (CCCDFlags.NOTIFICATIONS_ENABLED | CCCDFlags.INDICATIONS_ENABLED).to_bytes(2, "little")
78 @staticmethod
79 def create_disable_value() -> bytes:
80 """Create value to disable notifications/indications."""
81 return (0).to_bytes(2, "little")
83 def is_notifications_enabled(self, data: bytes) -> bool:
84 """Check if notifications are enabled."""
85 parsed = self._parse_descriptor_value(data)
86 return parsed.notifications_enabled
88 def is_indications_enabled(self, data: bytes) -> bool:
89 """Check if indications are enabled."""
90 parsed = self._parse_descriptor_value(data)
91 return parsed.indications_enabled
93 def is_any_enabled(self, data: bytes) -> bool:
94 """Check if either notifications or indications are enabled."""
95 parsed = self._parse_descriptor_value(data)
96 return parsed.notifications_enabled or parsed.indications_enabled