Bridge configuration and lifecycle¶
AferoBridgeV1 is the main entry point (from aioafero import v1).
Authentication¶
Login is handled by AferoAuth before the bridge is
constructed. The bridge only needs a refresh token (and optionally a still-valid
bearer token) — not a password. See Authentication for login, OTP, token persistence,
and refresh behavior.
Construction¶
Required:
username— Afero-backed account usernamerefresh_token— OAuth refresh token from login (or a saved token)session—aiohttp.ClientSessionfor API and auth traffic
Common optional arguments:
token— non-expired bearer token; skips the initial refresh if still validtoken_expiration— Unix timestamp whentokenexpires (omit to refresh on first use)afero_client—"hubspace"(default; only supported client)polling_interval— seconds between state polls (default30)discovery_interval— seconds between device discovery polls (default3600)temperature_unit—TemperatureUnit.CELSIUS(default) orFAHRENHEIThide_secrets— redact sensitive values from logs (defaultTrue)poll_version— periodically fetch firmware version metadata (defaultTrue)client_name— User-Agent token (default"aioafero")enable_conclave— open the Conclave push socket once the first discovery poll completes (Conclave push channel, defaultFalse)
After a running session, bridge.refresh_token reflects the current refresh token
(including any rotation). Restore tokens with bridge.set_token_data(token_data).
Lifecycle¶
Typical async flow:
Log in with
v1.AferoAuthand obtaintoken_data(Authentication)bridge = v1.AferoBridgeV1(username, token_data.refresh_token, session=session)await bridge.initialize()— starts controllers and background pollingawait bridge.async_block_until_done()— wait for the first poll / init tasksUse controllers to read state and send commands
await bridge.close()— stop polling. Does not closesessionwhen you passed it to__init__; callawait session.close()yourself (see Authentication and Examples). When usingopen()withoutsession,close()also closes the sessionopencreated.
Shorthand patterns¶
Async context manager — runs initialize on enter and close on exit. Call
async_block_until_done() inside the block when you need populated controllers:
async with v1.AferoBridgeV1(username, refresh_token, session=session) as bridge:
await bridge.async_block_until_done()
await bridge.lights.turn_on(device_id)
await session.close() # required when you passed session=
``AferoBridgeV1.open`` — construct with an optional session; when omitted,
open creates one and close() closes it. After open, async with bridge
is optional: __aenter__ is a no-op for initialization (already done); __aexit__
runs close():
bridge = await v1.AferoBridgeV1.open(username, refresh_token, session=session)
async with bridge:
await bridge.lights.turn_on(device_id)
await session.close() # when you passed session= to open()
Prefer explicit cleanup when you supplied session:
session = aiohttp.ClientSession()
bridge = await v1.AferoBridgeV1.open(username, refresh_token, session=session)
await bridge.lights.turn_on(device_id)
await bridge.close()
await session.close()
Or let open own the session (no session argument):
bridge = await v1.AferoBridgeV1.open(username, refresh_token)
await bridge.lights.turn_on(device_id)
await bridge.close() # also closes the session open created
Events¶
By default there is no live push channel from the cloud — only REST polling.
EventStream polls the REST API on a timer, merges changes into controller models,
then pushes in-process to any callbacks you register.
Polling loop (simplified):
Every
polling_intervalseconds,fetch_all_device_states()runs.For each parent metadevice, returned states are merged into the cached
AferoDevice(matched byfunctionClassandfunctionInstance) rather than replacing the fullstateslist. Partial poll payloads therefore do not drop other zones or split-light state rows.Changed devices are queued on
bridge.events.Each controller merges API data into its models (
update_elem).If something changed, registered callbacks run with
(event_type, item).
With enable_conclave=True (Conclave push channel), a background TLS client also merges
attr_change and status_change pushes into the same models and invokes the same
callbacks. Subscribers do not need a separate API. Conclave connection lifecycle events
(CONCLAVE_CONNECTING, CONCLAVE_CONNECTED, …) are emitted on bridge.events
alongside the REST poll status events.
item is the controller’s typed model (Fan, Light, etc.) after the merge.
Callbacks may be sync or async def; async handlers are scheduled as tasks on the
bridge event loop.
bridge.events—EventStream(polling, discovery, auth events)bridge.subscribe(callback)— same callback on every initialized controllercontroller.subscribe(callback, id_filter=..., event_filter=...)— scoped updates
See Examples for a full subscribe example.
Manual API access¶
Advanced callers can use:
await bridge.fetch_discovery_data()— full discovery payloadawait bridge.fetch_device_states(device_id)await bridge.fetch_all_device_states()— poll and merge states into the device cache (see polling loop above)await bridge.send_service_request(device_id, states)— low-level state write
See API reference for AferoBridgeV1 autodoc.