API reference
Only names exported from ondotori_ble are considered public. Modules and
members beginning with an underscore may change without notice.
ondotori_ble
Read current T&D Ondotori sensor values from BLE advertisements.
T_AND_D_COMPANY_ID
module-attribute
Bluetooth SIG company identifier assigned to T&D.
__all__
module-attribute
__all__ = [
"T_AND_D_COMPANY_ID",
"AdvertisementFormat",
"DeviceModel",
"EvidenceLevel",
"MalformedAdvertisementError",
"Measurement",
"MeasurementKind",
"NotOndotoriAdvertisementError",
"OndotoriError",
"OndotoriScanner",
"Reading",
"ReadingTimeoutError",
"ScannerBackend",
"ScannerFactory",
"ScannerState",
"ScannerStats",
"ScannerUnavailableError",
"Unit",
"__version__",
"decode_advertisement",
"decode_manufacturer_data",
"read",
"read_all",
"read_all_async",
"read_async",
"scan",
"scan_async",
"stream_readings",
]
MalformedAdvertisementError
Bases: OndotoriError, ValueError
Raised when T&D manufacturer data is too short or structurally invalid.
NotOndotoriAdvertisementError
Bases: OndotoriError, ValueError
Raised when data explicitly carries a non-T&D Bluetooth company ID.
OndotoriError
ReadingTimeoutError
Bases: OndotoriError, TimeoutError
Raised when no matching BLE reading arrives before a requested timeout.
ScannerUnavailableError
Bases: OndotoriError, RuntimeError
Raised when the operating system cannot start a BLE scan.
AdvertisementFormat
DeviceModel
Bases: StrEnum
Ondotori model identified from verified packet evidence.
RTR500B L variants share their base model because the larger battery
enclosure does not change the measurement layout. Enum membership records
a known product name; it does not by itself mean that a decoder exists.
Source code in src/ondotori_ble/models.py
EvidenceLevel
Measurement
dataclass
One decoded sensor channel.
value is None when the device advertises an invalid/sensor-error
marker. raw_value is retained so callers can audit the conversion.
Source code in src/ondotori_ble/models.py
__post_init__
Validate invariants shared by decoded and user-created instances.
Source code in src/ondotori_ble/models.py
as_dict
Return a JSON-serializable representation.
Source code in src/ondotori_ble/models.py
MeasurementKind
Bases: StrEnum
Physical quantity represented by a measurement channel.
Source code in src/ondotori_ble/models.py
Reading
dataclass
A decoded T&D BLE advertisement.
A T&D packet from an unknown family is still returned, with
model=DeviceModel.UNKNOWN and no measurements. This makes protocol
changes observable instead of silently discarding them.
Source code in src/ondotori_ble/models.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | |
temperatures_c
property
Return temperature channels in channel order.
temperature_c
property
Return the first temperature channel, if present and valid.
humidities_percent
property
Return relative-humidity channels in channel order.
humidity_percent
property
Return the first relative-humidity channel, if present and valid.
__post_init__
Validate stable public invariants.
Source code in src/ondotori_ble/models.py
values
Return values of one measurement kind in channel order.
first_value
as_dict
Return a stable JSON-serializable representation.
Source code in src/ondotori_ble/models.py
Unit
OndotoriScanner
Continuously decode T&D advertisements without pairing or connecting.
The callback path performs only constant-time parsing and a non-blocking
queue write. Identical payloads are de-duplicated by default, while
:attr:latest is refreshed for every observation so its timestamp and RSSI
stay current.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queue_size
|
int
|
Maximum number of unread updates. When full, the oldest update is dropped so a slow consumer cannot stall the BLE backend. |
256
|
deduplicate
|
bool
|
Emit only when a serial's manufacturer payload changes. |
True
|
include_unknown
|
bool
|
Emit packets with T&D's company ID even when their serial family does not yet have a decoder. |
True
|
start_attempts
|
int
|
Number of attempts to start the operating-system scan. |
3
|
retry_delay
|
float
|
Initial delay between start attempts. It doubles after each failure up to 30 seconds. |
0.25
|
serial_numbers
|
str | Iterable[str] | None
|
Optional serial number or iterable of serial numbers to emit. Matching is case-insensitive. |
None
|
scanner_factory
|
ScannerFactory
|
Optional Bleak-compatible scanner factory. |
BleakScanner
|
Source code in src/ondotori_ble/scanner.py
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 | |
last_advertisement_at
property
Return when any BLE advertisement was most recently observed.
last_tandd_at
property
Return when a T&D advertisement was most recently observed.
last_matching_at
property
Return when a packet matching this scanner's filters was last observed.
latest
property
Return the newest reading per serial number, sorted by serial.
start
async
Start scanning, retrying transient backend failures.
Repeated calls while running are safe and do nothing.
Source code in src/ondotori_ble/scanner.py
stop
async
Stop scanning and wake any stream consumers.
Repeated calls are safe.
Source code in src/ondotori_ble/scanner.py
__aenter__
async
__aexit__
async
__aexit__(
exception_type: type[BaseException] | None,
exception: BaseException | None,
traceback: object | None,
) -> None
Stop scanning regardless of how the context exits.
Source code in src/ondotori_ble/scanner.py
get
async
Wait for one emitted reading.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_wait
|
float | None
|
Maximum seconds to wait. |
None
|
Raises:
| Type | Description |
|---|---|
TimeoutError
|
No update arrived before |
StopAsyncIteration
|
The scanner stopped and its queue is empty. |
Source code in src/ondotori_ble/scanner.py
stream
async
Yield updates until stopped or no update arrives before idle_timeout.
Source code in src/ondotori_ble/scanner.py
ScannerBackend
Bases: Protocol
Minimal async scanner lifecycle required by :class:OndotoriScanner.
Source code in src/ondotori_ble/scanner.py
start
async
ScannerFactory
Bases: Protocol
Create a scanner backend for a Bleak-compatible detection callback.
Source code in src/ondotori_ble/scanner.py
ScannerState
ScannerStats
dataclass
Point-in-time counters for the current scanner run.
Source code in src/ondotori_ble/scanner.py
decode_advertisement
decode_advertisement(
data: bytes | bytearray | memoryview,
*,
identifier: str = "",
rssi: int | None = None,
tx_power: int | None = None,
name: str | None = None,
observed_at: datetime | None = None,
company_id_included: bool = False,
) -> Reading
Decode one T&D manufacturer-specific data value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
bytes | bytearray | memoryview
|
Manufacturer payload. By default this is the value from
|
required |
identifier
|
str
|
Platform BLE identifier (MAC address on most systems, CoreBluetooth UUID on macOS). |
''
|
rssi
|
int | None
|
Received signal strength in dBm. |
None
|
tx_power
|
int | None
|
Advertised transmit power in dBm, when present. |
None
|
name
|
str | None
|
Local or cached BLE name. |
None
|
observed_at
|
datetime | None
|
Time of receipt. Defaults to the current UTC time. |
None
|
company_id_included
|
bool
|
Set to |
False
|
Returns:
| Type | Description |
|---|---|
Reading
|
An immutable :class: |
Reading
|
families are returned without measurements. |
Raises:
| Type | Description |
|---|---|
NotOndotoriAdvertisementError
|
The included company ID is not T&D's. |
MalformedAdvertisementError
|
The serial or expected values are truncated. |
Source code in src/ondotori_ble/parser.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | |
decode_manufacturer_data
decode_manufacturer_data(
manufacturer_data: Mapping[int, bytes],
*,
identifier: str = "",
rssi: int | None = None,
tx_power: int | None = None,
name: str | None = None,
observed_at: datetime | None = None,
) -> Reading | None
Decode T&D data from a Bleak-style manufacturer-data mapping.
None is returned when the mapping contains no T&D company entry.
The remaining keyword arguments attach receipt metadata to the reading.
Source code in src/ondotori_ble/parser.py
read
read(
serial_number: str | None = None,
*,
timeout: float = 75.0,
decoded_only: bool = False,
start_attempts: int = 3,
retry_delay: float = 0.25,
) -> Reading
Synchronous convenience wrapper around :func:read_async.
Async callers should use await read_async(...) instead.
Source code in src/ondotori_ble/scanner.py
read_all
read_all(
*,
duration: float = 75.0,
decoded_only: bool = False,
start_attempts: int = 3,
retry_delay: float = 0.25,
) -> tuple[Reading, ...]
Synchronous convenience wrapper around :func:read_all_async.
Async callers should use await read_all_async(...) instead.
Source code in src/ondotori_ble/scanner.py
read_all_async
async
read_all_async(
*,
duration: float = 75.0,
decoded_only: bool = False,
start_attempts: int = 3,
retry_delay: float = 0.25,
scanner_factory: ScannerFactory = BleakScanner,
) -> tuple[Reading, ...]
Collect the latest reading from every T&D BLE device in range.
Unlike :func:read_async, this function waits for the full collection
window so slower advertisers also have a chance to appear. An empty tuple is
returned when no matching devices are observed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
duration
|
float
|
Collection window in seconds. The default covers the roughly one-minute interval observed from an RTR500B unit. |
75.0
|
decoded_only
|
bool
|
Exclude packets whose measurement mode is not decoded. |
False
|
start_attempts
|
int
|
Number of attempts to start the BLE backend. |
3
|
retry_delay
|
float
|
Initial exponential-backoff delay between start attempts. |
0.25
|
scanner_factory
|
ScannerFactory
|
Optional Bleak-compatible scanner factory. |
BleakScanner
|
Source code in src/ondotori_ble/scanner.py
read_async
async
read_async(
serial_number: str | None = None,
*,
timeout: float = 75.0,
decoded_only: bool = False,
start_attempts: int = 3,
retry_delay: float = 0.25,
scanner_factory: ScannerFactory = BleakScanner,
) -> Reading
Return the first matching T&D reading observed before timeout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
serial_number
|
str | None
|
Optional eight-digit serial to wait for. When omitted, return the first T&D advertisement. |
None
|
timeout
|
float
|
Maximum scan time in seconds. The 75-second default covers the approximately one-minute interval observed from an RTR500B unit. |
75.0
|
decoded_only
|
bool
|
Ignore recognized-but-undecoded and unknown packets. |
False
|
start_attempts
|
int
|
Number of attempts to start the BLE backend. |
3
|
retry_delay
|
float
|
Initial exponential-backoff delay between start attempts. |
0.25
|
scanner_factory
|
ScannerFactory
|
Optional Bleak-compatible scanner factory. |
BleakScanner
|
Raises:
| Type | Description |
|---|---|
ReadingTimeoutError
|
No matching reading arrived before |
ValueError
|
A serial number or timeout is invalid. |
Source code in src/ondotori_ble/scanner.py
scan
scan(
duration: float = 10.0,
*,
include_unknown: bool = True,
start_attempts: int = 3,
retry_delay: float = 0.25,
serial_numbers: str | Iterable[str] | None = None,
) -> tuple[Reading, ...]
Synchronous convenience wrapper around :func:scan_async.
This function intentionally refuses to nest an event loop. Async callers
should use await scan_async(...) instead.
Source code in src/ondotori_ble/scanner.py
scan_async
async
scan_async(
duration: float = 10.0,
*,
include_unknown: bool = True,
start_attempts: int = 3,
retry_delay: float = 0.25,
serial_numbers: str | Iterable[str] | None = None,
scanner_factory: ScannerFactory = BleakScanner,
) -> tuple[Reading, ...]
Scan for a fixed duration and return the latest packet per serial.
This is the simplest API for async applications. Use
:class:OndotoriScanner when you need a continuous stream.
Source code in src/ondotori_ble/scanner.py
stream_readings
async
stream_readings(
*,
queue_size: int = 256,
deduplicate: bool = True,
include_unknown: bool = True,
serial_numbers: str | Iterable[str] | None = None,
start_attempts: int = 3,
retry_delay: float = 0.25,
silence_timeout: float | None = None,
restart_delay: float | None = 5.0,
scanner_factory: ScannerFactory = BleakScanner,
) -> AsyncGenerator[Reading]
Continuously yield readings while managing scanner lifecycle.
This is the simplest API for long-running services. A configured
silence_timeout restarts the backend when no filter-matching
advertisement arrives in that interval, including deduplicated packets.
Silence can also mean that no matching logger is nearby, so
the default is None and does not infer failure from radio quietness.
When scanner startup remains unavailable after start_attempts, a
non-None restart_delay waits and retries until the consumer cancels
iteration. Set it to None to propagate :class:ScannerUnavailableError.