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

25 statements  

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

1"""Uncertainty characteristic implementation. 

2 

3IPS v1.0 §3.8: uint8 bitfield encoding stationary indicator, 

4update time, and position precision. 

5""" 

6 

7from __future__ import annotations 

8 

9import msgspec 

10 

11from ..context import CharacteristicContext 

12from .base import BaseCharacteristic 

13 

14# IPS v1.0 §3.8: Lookup table for update-time and precision sub-fields. 

15# Index = 3-bit value (0-7), value = round(e^(1.35 * index)). 

16_EXP_LOOKUP: tuple[int, ...] = (1, 4, 5, 7, 10, 14, 20, 28) 

17 

18 

19class UncertaintyData(msgspec.Struct, frozen=True, kw_only=True): 

20 """Parsed Uncertainty bitfield per IPS v1.0 §3.8. 

21 

22 Attributes: 

23 stationary: False if the transmitter is stationary, True if mobile. 

24 update_time: Estimated time between position updates (seconds). 

25 precision: Estimated position precision (decimetres). 

26 """ 

27 

28 stationary: bool 

29 update_time: int 

30 precision: int 

31 

32 

33class UncertaintyCharacteristic(BaseCharacteristic[UncertaintyData]): 

34 """Uncertainty characteristic (0x2AB4). 

35 

36 org.bluetooth.characteristic.uncertainty 

37 

38 IPS v1.0 §3.8: uint8 bitfield. 

39 Bit 0: Stationary (0 = stationary, 1 = mobile) 

40 Bits 1-3: Update Time - encoded as round(e^(1.35 * value)) seconds 

41 Bits 4-6: Precision - encoded as round(e^(1.35 * value)) decimetres 

42 Bit 7: RFU 

43 """ 

44 

45 expected_length = 1 

46 min_length = 1 

47 max_length = 1 

48 _is_bitfield = True 

49 

50 def _decode_value( 

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

52 ) -> UncertaintyData: 

53 """Decode Uncertainty bitfield per IPS v1.0 §3.8.""" 

54 raw = data[0] 

55 stationary = bool(raw & 0x01) 

56 update_time_idx = (raw >> 1) & 0x07 

57 precision_idx = (raw >> 4) & 0x07 

58 return UncertaintyData( 

59 stationary=stationary, 

60 update_time=_EXP_LOOKUP[update_time_idx], 

61 precision=_EXP_LOOKUP[precision_idx], 

62 ) 

63 

64 def _encode_value(self, value: UncertaintyData) -> bytearray: 

65 """Encode Uncertainty bitfield per IPS v1.0 §3.8.""" 

66 update_idx = _EXP_LOOKUP.index(value.update_time) 

67 precision_idx = _EXP_LOOKUP.index(value.precision) 

68 raw = (int(value.stationary) & 0x01) | ((update_idx & 0x07) << 1) | ((precision_idx & 0x07) << 4) 

69 return bytearray([raw])