Skip to content

API Reference

Constructor

Rtl433Client(
    host,
    *,
    port=8433,
    path="/ws",
    secure=False,          # ws->wss / http->https for both the socket and /cmd
    session=None,          # inject one, or None to have the client own+close one
    skip_keys=None,        # measurement keys to drop from fields (time always dropped)
    on_event=None,         # sync or async callback receiving each NormalizedEvent
    on_hub_update=None,    # fires on connect/meta/stats/identity change
    clock=None,            # injectable now() -> datetime, for tests
)

Runtime snapshots

Read-only runtime snapshots are exposed as attributes: client.connected, client.meta, client.stats, client.dev_info, client.dev_query. Refresh them on demand with await client.refresh_meta() / refresh_stats() / refresh_dev_info().

client.time_precision reports the resolution of the server's event time stamps, observed from the frames themselves (get_meta carries no timestamp format, so there is nothing to ask). It is None until the first event frame, then one of:

Value Server config What it means for the consumer
TimePrecision.MICROSECOND report_meta time:...usec... Two transmissions from one device are always distinguishable.
TimePrecision.SECOND rtl_433's default Two transmissions from one device inside the same wall-clock second carry identical stamps and cannot be told apart by time alone.
TimePrecision.UNUSABLE report_meta time:off, or a form this parser does not accept No frame has a usable timestamp, so replay suppression is off entirely — the server's reconnect backlog re-fires in full.

It is latest-wins, so it clears on the next frame after an operator changes the server's config, and on_hub_update fires whenever it changes. The library only reports it; deciding whether to surface the server-side remedy (adding report_meta time:iso:usec:tz) is the consumer's call.

Module map

Module Responsibility
client.py Rtl433Client — the async WebSocket + /cmd transport: connect/reconnect loop, event dispatch, HTTP getters/setters, validate_connection.
normalizer.py Split a raw event into a deterministic device key + identity/measurement fields (normalize, device_key, NormalizedEvent).
naming.py Presentation helpers built on the device key: the safe_token builder it is made of, plus display_name and identity_suffix (the model-stripped id suffix).
replay.py Reconnect-replay classifier (classify_replay, ReplayVerdict), parse_event_time timestamp parsing, payload_identity — the per-decode-noise-free payload comparison that separates a repeated transmission from a new one sharing its timestamp — and time_precision / TimePrecision stamp-resolution reporting.
sdr.py Pure SDR /cmd command transforms: the command registry, value read/convert helpers, and gain_command_arg.
library/ The data-driven device library: YAML field mappings (library/data/*.yaml) loaded into a Registry of FieldDescriptors, plus lookup, should_skip, apply_transform, and the user-override merge. See the Device Library reference.
availability.py Event-driven device classification (is_event_driven, known_field_keys) — the pure half of an availability-timeout policy.
_urls.py WebSocket//cmd URL builders and the {"result": ...} getter-response unwrap.

Device library

load_library() parses the packaged YAML into (Registry, skip_keys); lookup(field_key, model, registry=...) resolves a FieldDescriptor (model-scoped first, then global) and apply_transform(descriptor, raw_value) converts a raw rtl_433 value into the state to store.

from pyrtl_433 import apply_transform, load_library, lookup, normalize

registry, skip_keys = load_library()
normalized = normalize(event, skip_keys)
for field_key, raw_value in normalized.fields.items():
    if descriptor := lookup(field_key, normalized.model, registry=registry):
        state = apply_transform(descriptor, raw_value)

FieldDescriptor, Registry, load_library, lookup and apply_transform are re-exported at the package top level; the rest of the surface (merge_overrides, validate_user_mappings, normalize_overrides, event_driven_field_keys, should_skip, USER_OVERRIDE_FILENAME) is imported from pyrtl_433.library. The YAML schema, the resolution order, and the override merge semantics are documented in Device Library.

Availability classification

pyrtl_433 ships the classifier, not the timeout policy — the consumer maps the boolean onto its own defaults (typically never-expire for event-driven devices, a finite default for periodic ones).

from pyrtl_433 import is_event_driven, known_field_keys
from pyrtl_433.library import event_driven_field_keys

event_keys = event_driven_field_keys(registry)
fields = known_field_keys(adopted_field_keys, latest_payload_field_keys)
never_expires = is_event_driven(fields, event_keys)

known_field_keys unions the persisted (restart-surviving) field keys with the latest live payload's, so a device that has been silent since a restart is still classified from what it reported before. An empty event_keys yields False, so a failed library load degrades to the periodic default.