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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 16:41 +0000
1"""Uncertainty characteristic implementation.
3IPS v1.0 §3.8: uint8 bitfield encoding stationary indicator,
4update time, and position precision.
5"""
7from __future__ import annotations
9import msgspec
11from ..context import CharacteristicContext
12from .base import BaseCharacteristic
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)
19class UncertaintyData(msgspec.Struct, frozen=True, kw_only=True):
20 """Parsed Uncertainty bitfield per IPS v1.0 §3.8.
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 """
28 stationary: bool
29 update_time: int
30 precision: int
33class UncertaintyCharacteristic(BaseCharacteristic[UncertaintyData]):
34 """Uncertainty characteristic (0x2AB4).
36 org.bluetooth.characteristic.uncertainty
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 """
45 expected_length = 1
46 min_length = 1
47 max_length = 1
48 _is_bitfield = True
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 )
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])