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

22 statements  

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

1"""Floor Number characteristic implementation.""" 

2 

3from __future__ import annotations 

4 

5from ...types.gatt_enums import CharacteristicRole 

6from ..context import CharacteristicContext 

7from .base import BaseCharacteristic 

8 

9# IPS spec §3.6: encoded value X = floor_number N + 20. 

10_FLOOR_OFFSET = 20 

11_FLOOR_NOT_CONFIGURED_RAW = 255 

12_FLOOR_MAX_ENCODED_RAW = 254 

13 

14 

15class FloorNumberCharacteristic(BaseCharacteristic[int]): 

16 """Floor Number characteristic (0x2AB2). 

17 

18 org.bluetooth.characteristic.floor_number 

19 

20 IPS spec §3.6: raw uint8 X = N + 20, where N is the floor number. 

21 Decoded floor number = X - 20. X = 255 means not configured. 

22 """ 

23 

24 _manual_role = CharacteristicRole.INFO 

25 

26 # IPS spec §3.6: unsigned 8-bit integer, fixed 1-byte payload. 

27 expected_length = 1 

28 min_length = 1 

29 max_length = 1 

30 

31 def _decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True) -> int: 

32 """Decode floor number: N = raw_uint8 - 20 (IPS spec §3.6).""" 

33 raw = data[0] 

34 if raw == _FLOOR_NOT_CONFIGURED_RAW: 

35 raise ValueError("Floor number is not configured (raw value 255)") 

36 return raw - _FLOOR_OFFSET 

37 

38 def _encode_value(self, data: int) -> bytearray: 

39 """Encode floor number: raw = N + 20 (IPS spec §3.6).""" 

40 raw = data + _FLOOR_OFFSET 

41 if not 0 <= raw <= _FLOOR_MAX_ENCODED_RAW: 

42 raise ValueError(f"Floor number {data} is outside encodable range [-20, 234]") 

43 return bytearray([raw])