Coverage for src / bluetooth_sig / gatt / characteristics / intermediate_temperature.py: 90%
52 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"""Intermediate Temperature characteristic (0x2A1E).
3Reports the intermediate temperature measurement.
5References:
6 Bluetooth SIG Assigned Numbers / GATT Service Specifications
7"""
9from __future__ import annotations
11from datetime import datetime
12from enum import IntFlag
14import msgspec
16from bluetooth_sig.types.units import TemperatureUnit
18from ..context import CharacteristicContext
19from .base import BaseCharacteristic
20from .temperature_type import TemperatureType
21from .utils import IEEE11073Parser
24class IntermediateTemperatureFlags(IntFlag):
25 """Intermediate Temperature flags as per Bluetooth SIG specification."""
27 CELSIUS_UNIT = 0x00
28 FAHRENHEIT_UNIT = 0x01
29 TIMESTAMP_PRESENT = 0x02
30 TEMPERATURE_TYPE_PRESENT = 0x04
33class IntermediateTemperatureData(msgspec.Struct, frozen=True, kw_only=True):
34 """Parsed intermediate temperature data."""
36 temperature: float
37 unit: TemperatureUnit
38 flags: IntermediateTemperatureFlags
39 timestamp: datetime | None = None
40 temperature_type: TemperatureType | None = None
43class IntermediateTemperatureCharacteristic(BaseCharacteristic[IntermediateTemperatureData]):
44 """Intermediate Temperature characteristic (0x2A1E).
46 org.bluetooth.characteristic.intermediate_temperature
48 Same structure as Temperature Measurement: Flags(1) + Temperature(4) + [Timestamp(7)] + [Type(1)].
49 """
51 min_length: int | None = 5
52 max_length: int | None = 13
53 allow_variable_length: bool = True
55 def _decode_value(
56 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
57 ) -> IntermediateTemperatureData:
58 """Parse intermediate temperature data.
60 Format: Flags(1) + Temperature Value(4) + [Timestamp(7)] + [Temperature Type(1)].
61 """
62 flags = IntermediateTemperatureFlags(data[0])
63 temp_value = IEEE11073Parser.parse_float32(data, 1)
64 unit = (
65 TemperatureUnit.FAHRENHEIT
66 if IntermediateTemperatureFlags.FAHRENHEIT_UNIT in flags
67 else TemperatureUnit.CELSIUS
68 )
70 timestamp: datetime | None = None
71 temperature_type: TemperatureType | None = None
72 offset = 5
74 if IntermediateTemperatureFlags.TIMESTAMP_PRESENT in flags and len(data) >= offset + 7:
75 timestamp = IEEE11073Parser.parse_timestamp(data, offset)
76 offset += 7
78 if IntermediateTemperatureFlags.TEMPERATURE_TYPE_PRESENT in flags and len(data) >= offset + 1:
79 temperature_type = TemperatureType(data[offset])
81 return IntermediateTemperatureData(
82 temperature=temp_value,
83 unit=unit,
84 flags=flags,
85 timestamp=timestamp,
86 temperature_type=temperature_type,
87 )
89 def _encode_value(self, data: IntermediateTemperatureData) -> bytearray:
90 """Encode intermediate temperature value back to bytes."""
91 flags = IntermediateTemperatureFlags(0)
92 if data.unit == TemperatureUnit.FAHRENHEIT:
93 flags |= IntermediateTemperatureFlags.FAHRENHEIT_UNIT
94 if data.timestamp is not None:
95 flags |= IntermediateTemperatureFlags.TIMESTAMP_PRESENT
96 if data.temperature_type is not None:
97 flags |= IntermediateTemperatureFlags.TEMPERATURE_TYPE_PRESENT
99 result = bytearray([int(flags)])
100 result.extend(IEEE11073Parser.encode_float32(data.temperature))
102 if data.timestamp is not None:
103 result.extend(IEEE11073Parser.encode_timestamp(data.timestamp))
105 if data.temperature_type is not None:
106 result.append(int(data.temperature_type))
108 return result