Coverage for src / bluetooth_sig / gatt / characteristics / templates / domain.py: 72%
65 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
1"""Domain-specific templates that compose scaled templates.
3Covers TemperatureTemplate, ConcentrationTemplate, PressureTemplate.
4"""
6from __future__ import annotations
8from ...context import CharacteristicContext
9from ..utils.extractors import RawExtractor
10from ..utils.translators import ValueTranslator
11from .base import _RESOLUTION_HUNDREDTH, _RESOLUTION_INTEGER, _RESOLUTION_TENTH, CodingTemplate
12from .scaled import ScaledSint16Template, ScaledUint16Template, ScaledUint32Template
15class TemperatureTemplate(CodingTemplate[float]):
16 """Template for standard Bluetooth SIG temperature format (sint16, 0.01°C resolution)."""
18 def __init__(self) -> None:
19 """Initialize with standard temperature resolution."""
20 self._scaled_template = ScaledSint16Template.from_letter_method(1, -2, 0)
22 @property
23 def data_size(self) -> int:
24 """Size: 2 bytes."""
25 return 2
27 @property
28 def extractor(self) -> RawExtractor:
29 """Get extractor from underlying scaled template."""
30 return self._scaled_template.extractor
32 @property
33 def translator(self) -> ValueTranslator[float]:
34 """Return the linear translator from the underlying scaled template."""
35 return self._scaled_template.translator
37 def decode_value(
38 self, data: bytearray, offset: int = 0, ctx: CharacteristicContext | None = None, *, validate: bool = True
39 ) -> float:
40 """Parse temperature in 0.01°C resolution."""
41 return self._scaled_template.decode_value(data, offset, ctx, validate=validate) # pylint: disable=protected-access
43 def encode_value(self, value: float, *, validate: bool = True) -> bytearray:
44 """Encode temperature to bytes."""
45 return self._scaled_template.encode_value(value, validate=validate) # pylint: disable=protected-access
48class ConcentrationTemplate(CodingTemplate[float]):
49 """Template for concentration measurements with configurable resolution.
51 Used for environmental sensors like CO2, VOC, particulate matter, etc.
52 """
54 def __init__(self, resolution: float = 1.0) -> None:
55 """Initialize with resolution.
57 Args:
58 resolution: Measurement resolution (e.g., 1.0 for integer ppm, 0.1 for 0.1 ppm)
60 """
61 # Convert resolution to M, d, b parameters when it fits the pattern
62 # resolution = M * 10^d, so we find M and d such that M * 10^d = resolution
63 if resolution == _RESOLUTION_INTEGER:
64 # resolution = 1 * 10^0 # noqa: ERA001
65 self._scaled_template = ScaledUint16Template.from_letter_method(M=1, d=0, b=0)
66 elif resolution == _RESOLUTION_TENTH:
67 # resolution = 1 * 10^-1 # noqa: ERA001
68 self._scaled_template = ScaledUint16Template.from_letter_method(M=1, d=-1, b=0)
69 elif resolution == _RESOLUTION_HUNDREDTH:
70 # resolution = 1 * 10^-2 # noqa: ERA001
71 self._scaled_template = ScaledUint16Template.from_letter_method(M=1, d=-2, b=0)
72 else:
73 # Fallback to scale_factor for resolutions that don't fit M * 10^d pattern
74 self._scaled_template = ScaledUint16Template(scale_factor=resolution)
76 @classmethod
77 def from_letter_method(cls, M: int, d: int, b: int = 0) -> ConcentrationTemplate: # noqa: N803
78 """Create instance using Bluetooth SIG M, d, b parameters.
80 Args:
81 M: Multiplier factor
82 d: Decimal exponent (10^d)
83 b: Offset to add to raw value before scaling
85 Returns:
86 ConcentrationTemplate instance
88 """
89 instance = cls.__new__(cls)
90 instance._scaled_template = ScaledUint16Template.from_letter_method(M=M, d=d, b=b)
91 return instance
93 @property
94 def data_size(self) -> int:
95 """Size: 2 bytes."""
96 return 2
98 @property
99 def extractor(self) -> RawExtractor:
100 """Get extractor from underlying scaled template."""
101 return self._scaled_template.extractor
103 @property
104 def translator(self) -> ValueTranslator[float]:
105 """Return the linear translator from the underlying scaled template."""
106 return self._scaled_template.translator
108 def decode_value(
109 self, data: bytearray, offset: int = 0, ctx: CharacteristicContext | None = None, *, validate: bool = True
110 ) -> float:
111 """Parse concentration with resolution."""
112 return self._scaled_template.decode_value(data, offset, ctx, validate=validate) # pylint: disable=protected-access
114 def encode_value(self, value: float, *, validate: bool = True) -> bytearray:
115 """Encode concentration value to bytes."""
116 return self._scaled_template.encode_value(value, validate=validate) # pylint: disable=protected-access
119class PressureTemplate(CodingTemplate[float]):
120 """Template for pressure measurements (uint32, 0.1 Pa resolution)."""
122 def __init__(self) -> None:
123 """Initialize with standard pressure resolution (0.1 Pa)."""
124 self._scaled_template = ScaledUint32Template(scale_factor=0.1)
126 @property
127 def data_size(self) -> int:
128 """Size: 4 bytes."""
129 return 4
131 @property
132 def extractor(self) -> RawExtractor:
133 """Get extractor from underlying scaled template."""
134 return self._scaled_template.extractor
136 @property
137 def translator(self) -> ValueTranslator[float]:
138 """Return the linear translator from the underlying scaled template."""
139 return self._scaled_template.translator
141 def decode_value(
142 self, data: bytearray, offset: int = 0, ctx: CharacteristicContext | None = None, *, validate: bool = True
143 ) -> float:
144 """Parse pressure in 0.1 Pa resolution (returns Pa)."""
145 return self._scaled_template.decode_value(data, offset, ctx, validate=validate) # pylint: disable=protected-access
147 def encode_value(self, value: float, *, validate: bool = True) -> bytearray:
148 """Encode pressure to bytes."""
149 return self._scaled_template.encode_value(value, validate=validate) # pylint: disable=protected-access