Coverage for src/bluetooth_sig/gatt/descriptors/cccd.py: 86%
43 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 00:10 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 00:10 +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"
36 def _has_structured_data(self) -> bool:
37 return True
39 def _get_data_format(self) -> str:
40 return "uint16"
42 def _parse_descriptor_value(self, data: bytes) -> CCCDData:
43 """Parse CCCD value into notification/indication flags.
45 Args:
46 data: Raw bytes (should be 2 bytes for uint16)
48 Returns:
49 CCCDData with notification/indication flags
51 Raises:
52 ValueError: If data is not exactly 2 bytes
53 """
54 if len(data) != 2:
55 raise ValueError(f"CCCD data must be exactly 2 bytes, got {len(data)}")
57 # Parse as little-endian uint16
58 value = DataParser.parse_int16(data, endian="little")
60 return CCCDData(
61 notifications_enabled=bool(value & CCCDFlags.NOTIFICATIONS_ENABLED),
62 indications_enabled=bool(value & CCCDFlags.INDICATIONS_ENABLED),
63 )
65 @staticmethod
66 def create_enable_notifications_value() -> bytes:
67 """Create value to enable notifications."""
68 return CCCDFlags.NOTIFICATIONS_ENABLED.to_bytes(2, "little")
70 @staticmethod
71 def create_enable_indications_value() -> bytes:
72 """Create value to enable indications."""
73 return CCCDFlags.INDICATIONS_ENABLED.to_bytes(2, "little")
75 @staticmethod
76 def create_enable_both_value() -> bytes:
77 """Create value to enable both notifications and indications."""
78 return (CCCDFlags.NOTIFICATIONS_ENABLED | CCCDFlags.INDICATIONS_ENABLED).to_bytes(2, "little")
80 @staticmethod
81 def create_disable_value() -> bytes:
82 """Create value to disable notifications/indications."""
83 return (0).to_bytes(2, "little")
85 def is_notifications_enabled(self, data: bytes) -> bool:
86 """Check if notifications are enabled."""
87 parsed = self._parse_descriptor_value(data)
88 return parsed.notifications_enabled
90 def is_indications_enabled(self, data: bytes) -> bool:
91 """Check if indications are enabled."""
92 parsed = self._parse_descriptor_value(data)
93 return parsed.indications_enabled
95 def is_any_enabled(self, data: bytes) -> bool:
96 """Check if either notifications or indications are enabled."""
97 parsed = self._parse_descriptor_value(data)
98 return parsed.notifications_enabled or parsed.indications_enabled