Coverage for src / bluetooth_sig / types / bss.py: 100%

25 statements  

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

1"""Types for the Binary Sensor Service (BSS). 

2 

3References: 

4 Bluetooth SIG Binary Sensor Service 1.0, Sections 3.1.1.1 and 4.2.1 

5""" 

6 

7from __future__ import annotations 

8 

9from enum import IntEnum 

10 

11import msgspec 

12 

13 

14class BSSMessageID(IntEnum): 

15 """BSS Message IDs as per BSS v1.0 Table 4.3.""" 

16 

17 GET_SENSOR_STATUS_COMMAND = 0x00 

18 GET_SENSOR_STATUS_RESPONSE = 0x01 

19 SETTING_SENSOR_COMMAND = 0x02 

20 SETTING_SENSOR_RESPONSE = 0x03 

21 SENSOR_STATUS_EVENT = 0x04 

22 

23 

24class SplitHeader(msgspec.Struct, frozen=True, kw_only=True): 

25 """BSS Split Header as per BSS v1.0 Table 3.3. 

26 

27 Bit layout (uint8): 

28 Bit 0: Execute Flag (1 = final/non-split, 0 = split packet) 

29 Bits 1-5: Sequence Number (0-31) 

30 Bit 6: RFU 

31 Bit 7: Source Flag (0 = client→server, 1 = server→client) 

32 

33 Attributes: 

34 execute_flag: True if this is the final (or only) packet. 

35 sequence_number: Sequence order of split packets (0-31). 

36 source_flag: True if direction is server→client. 

37 

38 """ 

39 

40 execute_flag: bool 

41 sequence_number: int 

42 source_flag: bool 

43 

44 _EXECUTE_FLAG_MASK = 0x01 

45 _SEQUENCE_NUMBER_MASK = 0x1F 

46 _SEQUENCE_NUMBER_SHIFT = 1 

47 _SOURCE_FLAG_MASK = 0x80 

48 _SOURCE_FLAG_SHIFT = 7 

49 

50 @staticmethod 

51 def from_byte(value: int) -> SplitHeader: 

52 """Parse a Split Header from a raw uint8.""" 

53 return SplitHeader( 

54 execute_flag=bool(value & SplitHeader._EXECUTE_FLAG_MASK), 

55 sequence_number=(value >> SplitHeader._SEQUENCE_NUMBER_SHIFT) & SplitHeader._SEQUENCE_NUMBER_MASK, 

56 source_flag=bool(value & SplitHeader._SOURCE_FLAG_MASK), 

57 ) 

58 

59 def to_byte(self) -> int: 

60 """Encode the Split Header to a raw uint8.""" 

61 if not 0 <= self.sequence_number <= self._SEQUENCE_NUMBER_MASK: 

62 raise ValueError( 

63 f"sequence_number must be in range 0-{self._SEQUENCE_NUMBER_MASK}, got {self.sequence_number}" 

64 ) 

65 

66 return ( 

67 (int(self.execute_flag) & self._EXECUTE_FLAG_MASK) 

68 | ((self.sequence_number & self._SEQUENCE_NUMBER_MASK) << self._SEQUENCE_NUMBER_SHIFT) 

69 | (int(self.source_flag) << self._SOURCE_FLAG_SHIFT) 

70 )