Coverage for src / bluetooth_sig / gatt / characteristics / boot_keyboard_input_report.py: 100%

35 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 20:14 +0000

1"""Boot Keyboard Input Report characteristic implementation.""" 

2 

3from __future__ import annotations 

4 

5from enum import IntFlag 

6 

7import msgspec 

8 

9from ..context import CharacteristicContext 

10from .base import BaseCharacteristic 

11 

12 

13class KeyboardModifiers(IntFlag): 

14 """Keyboard modifier keys bitmap.""" 

15 

16 LEFT_CTRL = 0x01 

17 LEFT_SHIFT = 0x02 

18 LEFT_ALT = 0x04 

19 LEFT_GUI = 0x08 

20 RIGHT_CTRL = 0x10 

21 RIGHT_SHIFT = 0x20 

22 RIGHT_ALT = 0x40 

23 RIGHT_GUI = 0x80 

24 

25 

26class BootKeyboardInputReportData(msgspec.Struct, frozen=True, kw_only=True): 

27 """Boot keyboard input report data. 

28 

29 Attributes: 

30 modifiers: Modifier keys state bitmap 

31 reserved: Reserved byte (always 0) 

32 keycodes: Up to 6 simultaneous key codes (HID usage IDs) 

33 """ 

34 

35 modifiers: KeyboardModifiers 

36 reserved: int 

37 keycodes: tuple[int, ...] 

38 

39 

40class BootKeyboardInputReportCharacteristic(BaseCharacteristic[BootKeyboardInputReportData]): 

41 """Boot Keyboard Input Report characteristic (0x2A22). 

42 

43 org.bluetooth.characteristic.boot_keyboard_input_report 

44 

45 Contains keyboard input report data in boot mode following USB HID boot protocol. 

46 Format: 1-8 bytes - modifier(1) + [reserved(1) + keycodes(0-6)]. 

47 

48 Spec Reference: 

49 USB HID Specification v1.11, Appendix B - Boot Interface Descriptors 

50 """ 

51 

52 _manual_value_type = "BootKeyboardInputReportData" 

53 

54 min_length = 1 

55 max_length = 8 

56 allow_variable_length = True 

57 

58 def _decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> BootKeyboardInputReportData: 

59 """Parse HID keyboard report. 

60 

61 Args: 

62 data: Raw bytearray from BLE characteristic (1-8 bytes). 

63 ctx: Optional CharacteristicContext. 

64 

65 Returns: 

66 BootKeyboardInputReportData with parsed keyboard state. 

67 """ 

68 modifiers = KeyboardModifiers(data[0]) 

69 reserved = data[1] if len(data) >= 2 else 0 

70 keycodes = tuple(data[i] for i in range(2, len(data))) 

71 

72 return BootKeyboardInputReportData( 

73 modifiers=modifiers, 

74 reserved=reserved, 

75 keycodes=keycodes, 

76 ) 

77 

78 def _encode_value(self, data: BootKeyboardInputReportData) -> bytearray: 

79 """Encode keyboard report to bytes. 

80 

81 Args: 

82 data: BootKeyboardInputReportData to encode 

83 

84 Returns: 

85 Encoded bytes (8 bytes total) 

86 """ 

87 result = bytearray(8) 

88 result[0] = int(data.modifiers) 

89 result[1] = data.reserved 

90 

91 # Encode up to 6 keycodes 

92 for i, keycode in enumerate(data.keycodes[:6]): 

93 result[2 + i] = keycode 

94 

95 return result