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

62 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-30 00:10 +0000

1"""LN Feature characteristic implementation.""" 

2 

3from __future__ import annotations 

4 

5from enum import IntFlag 

6 

7import msgspec 

8 

9from ...types.gatt_enums import ValueType 

10from ..context import CharacteristicContext 

11from .base import BaseCharacteristic 

12from .utils import DataParser 

13 

14 

15class LNFeatures(IntFlag): 

16 """LN Feature flags as per Bluetooth SIG specification.""" 

17 

18 INSTANTANEOUS_SPEED_SUPPORTED = 0x0001 

19 TOTAL_DISTANCE_SUPPORTED = 0x0002 

20 LOCATION_SUPPORTED = 0x0004 

21 ELEVATION_SUPPORTED = 0x0008 

22 HEADING_SUPPORTED = 0x0010 

23 ROLLING_TIME_SUPPORTED = 0x0020 

24 UTC_TIME_SUPPORTED = 0x0040 

25 REMAINING_DISTANCE_SUPPORTED = 0x0080 

26 REMAINING_VERTICAL_DISTANCE_SUPPORTED = 0x0100 

27 ESTIMATED_TIME_OF_ARRIVAL_SUPPORTED = 0x0200 

28 NUMBER_OF_BEACONS_IN_SOLUTION_SUPPORTED = 0x0400 

29 NUMBER_OF_BEACONS_IN_VIEW_SUPPORTED = 0x0800 

30 TIME_TO_FIRST_FIX_SUPPORTED = 0x1000 

31 ESTIMATED_HORIZONTAL_POSITION_ERROR_SUPPORTED = 0x2000 

32 ESTIMATED_VERTICAL_POSITION_ERROR_SUPPORTED = 0x4000 

33 HORIZONTAL_DILUTION_OF_PRECISION_SUPPORTED = 0x8000 

34 VERTICAL_DILUTION_OF_PRECISION_SUPPORTED = 0x00010000 

35 LOCATION_AND_SPEED_CHARACTERISTIC_CONTENT_MASKING_SUPPORTED = 0x00020000 

36 FIX_RATE_SETTING_SUPPORTED = 0x00040000 

37 ELEVATION_SETTING_SUPPORTED = 0x00080000 

38 POSITION_STATUS_SUPPORTED = 0x00100000 

39 

40 

41class LNFeatureData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods 

42 """Parsed data from LN Feature characteristic.""" 

43 

44 features_bitmap: int 

45 instantaneous_speed_supported: bool 

46 total_distance_supported: bool 

47 location_supported: bool 

48 elevation_supported: bool 

49 heading_supported: bool 

50 rolling_time_supported: bool 

51 utc_time_supported: bool 

52 remaining_distance_supported: bool 

53 remaining_vertical_distance_supported: bool 

54 estimated_time_of_arrival_supported: bool 

55 number_of_beacons_in_solution_supported: bool 

56 number_of_beacons_in_view_supported: bool 

57 time_to_first_fix_supported: bool 

58 estimated_horizontal_position_error_supported: bool 

59 estimated_vertical_position_error_supported: bool 

60 horizontal_dilution_of_precision_supported: bool 

61 vertical_dilution_of_precision_supported: bool 

62 location_and_speed_characteristic_content_masking_supported: bool 

63 fix_rate_setting_supported: bool 

64 elevation_setting_supported: bool 

65 position_status_supported: bool 

66 

67 

68class LNFeatureCharacteristic(BaseCharacteristic): 

69 """LN Feature characteristic. 

70 

71 Used to represent the supported features of a location and navigation sensor. 

72 """ 

73 

74 _manual_value_type: ValueType | str | None = ValueType.DICT # Override since decode_value returns dataclass 

75 

76 def decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> LNFeatureData: 

77 """Parse LN feature data according to Bluetooth specification. 

78 

79 Format: Features(4) - 32-bit bitmap indicating supported features. 

80 

81 Args: 

82 data: Raw bytearray from BLE characteristic. 

83 ctx: Optional CharacteristicContext providing surrounding context (may be None). 

84 

85 Returns: 

86 LNFeatureData containing parsed feature bitmap and capabilities. 

87 

88 """ 

89 if len(data) < 4: 

90 raise ValueError("LN Feature data must be at least 4 bytes") 

91 

92 features_bitmap = DataParser.parse_int32(data, 0, signed=False) 

93 

94 # Extract individual feature flags 

95 features = { 

96 "instantaneous_speed_supported": bool(features_bitmap & LNFeatures.INSTANTANEOUS_SPEED_SUPPORTED), 

97 "total_distance_supported": bool(features_bitmap & LNFeatures.TOTAL_DISTANCE_SUPPORTED), 

98 "location_supported": bool(features_bitmap & LNFeatures.LOCATION_SUPPORTED), 

99 "elevation_supported": bool(features_bitmap & LNFeatures.ELEVATION_SUPPORTED), 

100 "heading_supported": bool(features_bitmap & LNFeatures.HEADING_SUPPORTED), 

101 "rolling_time_supported": bool(features_bitmap & LNFeatures.ROLLING_TIME_SUPPORTED), 

102 "utc_time_supported": bool(features_bitmap & LNFeatures.UTC_TIME_SUPPORTED), 

103 "remaining_distance_supported": bool(features_bitmap & LNFeatures.REMAINING_DISTANCE_SUPPORTED), 

104 "remaining_vertical_distance_supported": bool( 

105 features_bitmap & LNFeatures.REMAINING_VERTICAL_DISTANCE_SUPPORTED 

106 ), 

107 "estimated_time_of_arrival_supported": bool( 

108 features_bitmap & LNFeatures.ESTIMATED_TIME_OF_ARRIVAL_SUPPORTED 

109 ), 

110 "number_of_beacons_in_solution_supported": bool( 

111 features_bitmap & LNFeatures.NUMBER_OF_BEACONS_IN_SOLUTION_SUPPORTED 

112 ), 

113 "number_of_beacons_in_view_supported": bool( 

114 features_bitmap & LNFeatures.NUMBER_OF_BEACONS_IN_VIEW_SUPPORTED 

115 ), 

116 "time_to_first_fix_supported": bool(features_bitmap & LNFeatures.TIME_TO_FIRST_FIX_SUPPORTED), 

117 "estimated_horizontal_position_error_supported": bool( 

118 features_bitmap & LNFeatures.ESTIMATED_HORIZONTAL_POSITION_ERROR_SUPPORTED 

119 ), 

120 "estimated_vertical_position_error_supported": bool( 

121 features_bitmap & LNFeatures.ESTIMATED_VERTICAL_POSITION_ERROR_SUPPORTED 

122 ), 

123 "horizontal_dilution_of_precision_supported": bool( 

124 features_bitmap & LNFeatures.HORIZONTAL_DILUTION_OF_PRECISION_SUPPORTED 

125 ), 

126 "vertical_dilution_of_precision_supported": bool( 

127 features_bitmap & LNFeatures.VERTICAL_DILUTION_OF_PRECISION_SUPPORTED 

128 ), 

129 "location_and_speed_characteristic_content_masking_supported": bool( 

130 features_bitmap & LNFeatures.LOCATION_AND_SPEED_CHARACTERISTIC_CONTENT_MASKING_SUPPORTED 

131 ), 

132 "fix_rate_setting_supported": bool(features_bitmap & LNFeatures.FIX_RATE_SETTING_SUPPORTED), 

133 "elevation_setting_supported": bool(features_bitmap & LNFeatures.ELEVATION_SETTING_SUPPORTED), 

134 "position_status_supported": bool(features_bitmap & LNFeatures.POSITION_STATUS_SUPPORTED), 

135 } 

136 

137 return LNFeatureData(features_bitmap=features_bitmap, **features) 

138 

139 def encode_value(self, data: LNFeatureData) -> bytearray: 

140 """Encode LNFeatureData back to bytes. 

141 

142 Args: 

143 data: LNFeatureData instance to encode 

144 

145 Returns: 

146 Encoded bytes representing the LN features 

147 

148 """ 

149 return DataParser.encode_int32(data.features_bitmap, signed=False)