Skip to content

Recording readings in a server

Run one scanner for the lifetime of a host process. Do not start a BLE scan for each HTTP request, and do not let every web worker open its own scanner. A dedicated collector process can write to the same database used by the API.

Managed collector

import asyncio
from collections.abc import Awaitable, Callable

from ondotori_ble import Reading, stream_readings


async def collect(store: Callable[[Reading], Awaitable[None]]) -> None:
    async for reading in stream_readings(
        deduplicate=False,
        silence_timeout=180,
        restart_delay=5,
    ):
        await store(reading)


async def main() -> None:
    await collect(repository.store)


if __name__ == "__main__":
    asyncio.run(main())

Cancellation automatically exits the generator and stops Bleak. An application framework should start collect() in its lifespan hook and cancel/await the task during shutdown.

Persistence choices

For a current-value table, upsert by serial_number and replace the row only when observed_at is newer. For an event table, store at least:

  • receiver timestamp (observed_at);
  • serial and family code;
  • identified model and evidence;
  • decoded measurements;
  • raw payload;
  • RSSI and receiver identifier; and
  • library version.

Advertisements do not provide exactly-once delivery. observed_at is the receiver's time rather than a device event ID. If duplicate events are not useful, use a database uniqueness policy based on the serial, raw payload, and an application-defined time bucket, or keep the library's default deduplicate=True.

Backpressure and health

Database work runs in the async consumer, never in the BLE callback. Increase queue_size only after measuring write latency, and monitor stats.dropped when using OndotoriScanner directly.

Expose these health fields through application metrics:

  • scanner state;
  • last advertisement time;
  • last T&D advertisement time;
  • last filter-matching advertisement time;
  • parsed, decoded, raw-only, malformed, duplicate, and dropped counts; and
  • the most recent backend error.

silence_timeout is deployment-specific. Leave it disabled when no logger may be present; enable it only when at least one device is expected to advertise within a known maximum interval.

Deployment boundary

The process needs host Bluetooth access and OS permission. Containers commonly need the host Bluetooth/DBus interface explicitly mounted or forwarded. Treat the collector as a singleton per adapter and use the database or a message broker to fan readings out to multiple API workers.