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

27 statements  

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

1"""PnP ID characteristic implementation.""" 

2 

3from __future__ import annotations 

4 

5from enum import IntEnum 

6 

7import msgspec 

8 

9from ..context import CharacteristicContext 

10from .base import BaseCharacteristic 

11from .utils import DataParser 

12 

13 

14class VendorIdSource(IntEnum): 

15 """Vendor ID Source enumeration. 

16 

17 Defines the namespace for the Vendor ID field. 

18 """ 

19 

20 RESERVED_0 = 0 # Reserved for Future Use 

21 BLUETOOTH_SIG = 1 # Bluetooth SIG Assigned Company Identifier 

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

23 

24 

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

26 """PnP ID data. 

27 

28 Attributes: 

29 vendor_id_source: Vendor ID source namespace 

30 vendor_id: Vendor ID from the specified namespace 

31 product_id: Manufacturer managed identifier 

32 product_version: Manufacturer managed version 

33 """ 

34 

35 vendor_id_source: VendorIdSource 

36 vendor_id: int 

37 product_id: int 

38 product_version: int 

39 

40 

41class PnpIdCharacteristic(BaseCharacteristic[PnpIdData]): 

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

43 

44 org.bluetooth.characteristic.pnp_id 

45 

46 Contains PnP ID information (7 bytes). 

47 """ 

48 

49 _manual_value_type = "PnpIdData" 

50 expected_length = 7 

51 

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

53 """Parse PnP ID. 

54 

55 Args: 

56 data: Raw bytearray (7 bytes). 

57 ctx: Optional CharacteristicContext. 

58 

59 Returns: 

60 PnpIdData with vendor_id_source, vendor_id, product_id, product_version. 

61 """ 

62 return PnpIdData( 

63 vendor_id_source=VendorIdSource(data[0]), 

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

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

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

67 ) 

68 

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

70 """Encode PnP ID. 

71 

72 Args: 

73 data: PnpIdData to encode 

74 

75 Returns: 

76 Encoded bytes 

77 """ 

78 result = bytearray() 

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

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

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

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

83 return result