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

28 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-18 11:17 +0000

1"""PnP ID 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 VendorIdSource(IntEnum): 

16 """Vendor ID Source enumeration. 

17 

18 Defines the namespace for the Vendor ID field. 

19 """ 

20 

21 RESERVED_0 = 0 # Reserved for Future Use 

22 BLUETOOTH_SIG = 1 # Bluetooth SIG Assigned Company Identifier 

23 USB_IF = 2 # USB Implementer's Forum assigned Vendor ID 

24 

25 

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

27 """PnP ID data. 

28 

29 Attributes: 

30 vendor_id_source: Vendor ID source namespace 

31 vendor_id: Vendor ID from the specified namespace 

32 product_id: Manufacturer managed identifier 

33 product_version: Manufacturer managed version 

34 """ 

35 

36 vendor_id_source: VendorIdSource 

37 vendor_id: int 

38 product_id: int 

39 product_version: int 

40 

41 

42class PnpIdCharacteristic(BaseCharacteristic[PnpIdData]): 

43 """PnP ID characteristic (0x2A50). 

44 

45 org.bluetooth.characteristic.pnp_id 

46 

47 Contains PnP ID information (7 bytes). 

48 """ 

49 

50 _manual_role = CharacteristicRole.INFO 

51 expected_length = 7 

52 

53 def _decode_value( 

54 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True 

55 ) -> PnpIdData: 

56 """Parse PnP ID. 

57 

58 Args: 

59 data: Raw bytearray (7 bytes). 

60 ctx: Optional CharacteristicContext. 

61 validate: Whether to validate ranges (default True) 

62 

63 Returns: 

64 PnpIdData with vendor_id_source, vendor_id, product_id, product_version. 

65 """ 

66 return PnpIdData( 

67 vendor_id_source=VendorIdSource(data[0]), 

68 vendor_id=DataParser.parse_int16(data, 1, signed=False), 

69 product_id=DataParser.parse_int16(data, 3, signed=False), 

70 product_version=DataParser.parse_int16(data, 5, signed=False), 

71 ) 

72 

73 def _encode_value(self, data: PnpIdData) -> bytearray: 

74 """Encode PnP ID. 

75 

76 Args: 

77 data: PnpIdData to encode 

78 

79 Returns: 

80 Encoded bytes 

81 """ 

82 result = bytearray() 

83 result.append(int(data.vendor_id_source)) 

84 result.extend(DataParser.encode_int16(data.vendor_id, signed=False)) 

85 result.extend(DataParser.encode_int16(data.product_id, signed=False)) 

86 result.extend(DataParser.encode_int16(data.product_version, signed=False)) 

87 return result