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

35 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-03 16:41 +0000

1"""ESL Control Point characteristic implementation.""" 

2 

3from __future__ import annotations 

4 

5from enum import IntEnum 

6 

7import msgspec 

8 

9from ...types.gatt_enums import CharacteristicRole 

10from ..context import CharacteristicContext 

11from .base import BaseCharacteristic 

12from .utils import DataParser 

13 

14 

15class ESLCPOpcode(IntEnum): 

16 """ESL Control Point opcodes per ESL specification.""" 

17 

18 PING = 0x00 

19 UNASSOCIATE = 0x01 

20 SERVICE_RESET = 0x02 

21 FACTORY_RESET = 0x03 

22 UPDATE_COMPLETE = 0x04 

23 READ_SENSOR_DATA = 0x10 

24 REFRESH_DISPLAY = 0x11 

25 DISPLAY_IMAGE = 0x20 

26 DISPLAY_TIMED_IMAGE = 0x21 

27 LED_CONTROL = 0x30 

28 LED_TIMED_CONTROL = 0x31 

29 VENDOR_SPECIFIC = 0xFF 

30 

31 

32class ESLCPData(msgspec.Struct, frozen=True, kw_only=True): 

33 """Parsed data from ESL Control Point characteristic. 

34 

35 Attributes: 

36 opcode: The ESL CP opcode. 

37 parameters: Raw parameter bytes (opcode-dependent). 

38 

39 """ 

40 

41 opcode: ESLCPOpcode 

42 parameters: bytes = b"" 

43 

44 

45class ESLControlPointCharacteristic(BaseCharacteristic[ESLCPData]): 

46 """ESL Control Point characteristic (0x2BFE). 

47 

48 org.bluetooth.characteristic.esl_control_point 

49 

50 Control point for Electronic Shelf Label operations. 

51 Complex multi-opcode characteristic supporting various ESL commands. 

52 """ 

53 

54 _manual_role = CharacteristicRole.CONTROL 

55 min_length: int = 1 # At minimum: opcode (1 byte) 

56 allow_variable_length: bool = True 

57 

58 def _decode_value( 

59 self, 

60 data: bytearray, 

61 ctx: CharacteristicContext | None = None, 

62 *, 

63 validate: bool = True, 

64 ) -> ESLCPData: 

65 """Parse ESL CP data. 

66 

67 Args: 

68 data: Raw bytes (1+ bytes). 

69 ctx: Optional CharacteristicContext. 

70 validate: Whether to validate ranges (default True). 

71 

72 Returns: 

73 ESLCPData with opcode and parameters. 

74 

75 """ 

76 opcode = ESLCPOpcode(DataParser.parse_int8(data, 0, signed=False)) 

77 parameters = bytes(data[1:]) 

78 return ESLCPData(opcode=opcode, parameters=parameters) 

79 

80 def _encode_value(self, data: ESLCPData) -> bytearray: 

81 """Encode ESL CP data to bytes. 

82 

83 Args: 

84 data: ESLCPData to encode. 

85 

86 Returns: 

87 Encoded bytes. 

88 

89 """ 

90 result = DataParser.encode_int8(int(data.opcode), signed=False) 

91 result.extend(data.parameters) 

92 return result