Coverage for src / bluetooth_sig / gatt / characteristics / hid_information.py: 57%
37 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +0000
1"""HID Information characteristic implementation."""
3from __future__ import annotations
5from enum import IntFlag
7import msgspec
9from ..context import CharacteristicContext
10from .base import BaseCharacteristic
11from .utils import DataParser
13# Constants per Bluetooth HID specification
14BCD_HID_MAX = 0xFFFF # uint16 max for BCD HID version
15COUNTRY_CODE_MAX = 0xFF # uint8 max for country code
16HID_INFO_DATA_LENGTH = 4 # Fixed data length: bcdHID(2) + bCountryCode(1) + Flags(1)
19class HidInformationFlags(IntFlag):
20 """HID Information flags as per Bluetooth HID specification."""
22 REMOTE_WAKE = 0x01 # Bit 0: RemoteWake
23 NORMALLY_CONNECTABLE = 0x02 # Bit 1: NormallyConnectable
24 # Bits 2-7: Reserved
27class HidInformationData(msgspec.Struct, frozen=True, kw_only=True):
28 """Parsed data from HID Information characteristic.
30 Attributes:
31 bcd_hid: HID version in BCD format (uint16)
32 b_country_code: Country code (uint8)
33 flags: HID information flags
34 """
36 bcd_hid: int # uint16
37 b_country_code: int # uint8
38 flags: HidInformationFlags
40 def __post_init__(self) -> None:
41 """Validate HID information data."""
42 if not 0 <= self.bcd_hid <= BCD_HID_MAX:
43 raise ValueError(f"bcdHID must be 0-{BCD_HID_MAX:#x}, got {self.bcd_hid}")
44 if not 0 <= self.b_country_code <= COUNTRY_CODE_MAX:
45 raise ValueError(f"bCountryCode must be 0-{COUNTRY_CODE_MAX:#x}, got {self.b_country_code}")
48class HidInformationCharacteristic(BaseCharacteristic[HidInformationData]):
49 """HID Information characteristic (0x2A4A).
51 org.bluetooth.characteristic.hid_information
53 HID Information characteristic.
54 """
56 expected_length: int = 4 # bcdHID(2) + bCountryCode(1) + Flags(1)
58 def _decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> HidInformationData:
59 """Parse HID information data.
61 Format: bcdHID(2) + bCountryCode(1) + Flags(1)
63 Args:
64 data: Raw bytearray from BLE characteristic.
65 ctx: Optional context.
67 Returns:
68 HidInformationData containing parsed HID information.
69 """
70 if len(data) != HID_INFO_DATA_LENGTH:
71 raise ValueError(f"HID Information data must be exactly {HID_INFO_DATA_LENGTH} bytes, got {len(data)}")
73 bcd_hid = DataParser.parse_int16(data, 0, signed=False)
74 b_country_code = DataParser.parse_int8(data, 2, signed=False)
75 flags_value = DataParser.parse_int8(data, 3, signed=False)
76 flags = HidInformationFlags(flags_value)
78 return HidInformationData(
79 bcd_hid=bcd_hid,
80 b_country_code=b_country_code,
81 flags=flags,
82 )
84 def _encode_value(self, data: HidInformationData) -> bytearray:
85 """Encode HidInformationData back to bytes.
87 Args:
88 data: HidInformationData instance to encode
90 Returns:
91 Encoded bytes
92 """
93 result = bytearray()
94 result.extend(DataParser.encode_int16(data.bcd_hid, signed=False))
95 result.extend(DataParser.encode_int8(data.b_country_code, signed=False))
96 result.extend(DataParser.encode_int8(int(data.flags), signed=False))
97 return result