aioafero.v1.conclave package

Submodules

aioafero.v1.conclave.access module

Mint a short-lived Conclave access token via REST.

The Hubspace app calls POST .../accounts/{accountId}/conclaveAccess before opening the Conclave TLS socket. The OAuth bearer is only used to mint this short-lived token — the socket itself authenticates with tokens[0].token from this response, not the JWT (using the JWT was the historic cause of “hello works, login rejected”).

class aioafero.v1.conclave.access.ConclaveAccess(host: str, port: int, ssl: bool, compression: bool, token: str, channel_id: str, expires_timestamp: int | None = None)[source]

Bases: object

Parsed response from POST .../conclaveAccess.

Parameters:
  • host (str) – TLS host for the Conclave socket (e.g. conclave-stream1.afero.net).

  • port (int) – TLS port for the Conclave socket.

  • ssl (bool) – True when the server advertises TLS (always True in observed captures).

  • compression (bool) – True when the server announces zlib compression on the wire (the initial hello is sent as a single zlib blob).

  • token (str) – Short-lived single-use Conclave socket access token. Not the OAuth bearer — pass this as login.accessToken.

  • channel_id (str) – Conclave channel ID (the account UUID).

  • expires_timestamp (int | None) – Optional epoch milliseconds at which token expires; observed tokens may also be single-use, so callers should always mint a fresh token on reconnect rather than trust this clock.

host: str
port: int
ssl: bool
compression: bool
token: str
channel_id: str
expires_timestamp: int | None = None
__init__(host: str, port: int, ssl: bool, compression: bool, token: str, channel_id: str, expires_timestamp: int | None = None) → None
Parameters:
Return type:

None

async aioafero.v1.conclave.access.request_conclave_access(bridge: AferoBridgeV1, *, user: bool = True, soft_hub: bool = False) → ConclaveAccess[source]

Mint a fresh Conclave access token.

Always call this on first connect and on every reconnect — observed tokens are short-lived and may be single-use.

Parameters:
  • bridge (AferoBridgeV1) – Initialized AferoBridgeV1; the bearer token and account ID are read from the bridge.

  • user (bool) – Set True for end-user clients (the only mode observed in captures). Always True for HA / app-style consumers.

  • soft_hub (bool) – Set True only for soft-hub clients. Token shape and channel routing for softHub=true are not yet captured — leave False unless you have verified the response shape.

Returns:

Parsed ConclaveAccess.

Raises:

AferoError – If the response is missing the conclave host or an account-scoped token.

Return type:

ConclaveAccess

aioafero.v1.conclave.client module

TLS client for the Conclave push channel.

class aioafero.v1.conclave.client.ConclaveStatus(*values)[source]

Bases: Enum

Connection status of the Conclave push client.

DISCONNECTED = 'disconnected'
CONNECTING = 'connecting'
CONNECTED = 'connected'
exception aioafero.v1.conclave.client.ConclavePushStaleError[source]

Bases: ConnectionError

Raised when the socket stays up but no private pushes arrive in time.

class aioafero.v1.conclave.client.ConclaveConnection(reader: StreamReader, writer: StreamWriter, *, access: ConclaveAccess, read_timeout: float = 75.0)[source]

Bases: object

Single attempt at a Conclave socket session.

Parameters:
__init__(reader: StreamReader, writer: StreamWriter, *, access: ConclaveAccess, read_timeout: float = 75.0) → None[source]

Bind a Conclave TLS stream to access credentials.

Parameters:
Return type:

None

property socket_closed: bool

True when the underlying stream has reached EOF.

set_read_timeout(timeout: float) → None[source]

Update the per-read idle timeout (seconds).

Parameters:

timeout (float)

Return type:

None

async open_session() → dict[source]

Send the opening {} and return the handshake envelope.

Return type:

dict

async login() → dict[source]

Send the login frame and return the welcome envelope.

Return type:

dict

async iter_frames() → AsyncIterator[dict][source]

Yield frames from the connection until it closes.

Return type:

AsyncIterator[dict]

async send_heartbeat() → None[source]

Send a bare \n heartbeat ack.

Return type:

None

async close() → None[source]

Close the underlying writer (idempotent).

Return type:

None

class aioafero.v1.conclave.client.ConclaveClient(bridge: AferoBridgeV1, *, connect: ConnectCallable | None = None, read_timeout: float = 75.0, initial_backoff: float = 1.0, max_backoff: float = 60.0, push_idle_timeout: float | None = None, reconcile_on_reconnect: bool = True)[source]

Bases: object

Long-running Conclave push consumer for an AferoBridgeV1.

Parameters:
  • bridge (AferoBridgeV1)

  • connect (ConnectCallable | None)

  • read_timeout (float)

  • initial_backoff (float)

  • max_backoff (float)

  • push_idle_timeout (float | None)

  • reconcile_on_reconnect (bool)

__init__(bridge: AferoBridgeV1, *, connect: ConnectCallable | None = None, read_timeout: float = 75.0, initial_backoff: float = 1.0, max_backoff: float = 60.0, push_idle_timeout: float | None = None, reconcile_on_reconnect: bool = True) → None[source]

Attach a reconnecting Conclave consumer to bridge.

Parameters:
  • push_idle_timeout (float | None) – When set, end the session if no private push is successfully handled within this many seconds after login while the socket is still receiving heartbeats (idle clock starts at welcome). Successful public inventory handlers do not reset the clock. None disables (default). Use for zombie sessions where welcome succeeds but state pushes never arrive or stop.

  • reconcile_on_reconnect (bool) – After a failed session, run one REST state poll once login succeeds again (in the background so the TLS dispatch loop can keep acking heartbeats).

  • bridge (AferoBridgeV1)

  • connect (ConnectCallable | None)

  • read_timeout (float)

  • initial_backoff (float)

  • max_backoff (float)

Return type:

None

property status: ConclaveStatus

Return the current Conclave connection status.

property connected: bool

True when the push session has completed login.

Prefer logged_in for socket-session liveness; connected reflects the emitted lifecycle state used by bridge.events.

property running: bool

True while the background run loop has not been stopped.

property logged_in: bool

True after welcome until the socket session ends.

Cleared before connected during teardown; use status for lifecycle callbacks on events.

property seconds_since_last_push: float | None

Seconds since the last handled private push, when logged in.

property push_stale: bool

True when logged in and push idle exceeds push_idle_timeout.

async wait_until_logged_in(timeout: float = 60.0) → bool[source]

Wait until a Conclave session completes login.

Parameters:

timeout (float)

Return type:

bool

async start() → None[source]

Schedule the background reconnect loop (idempotent).

The loop is owned by this client (stop() / bridge.close()), not tracked as a bridge ad-hoc job — otherwise async_block_until_done() would hang while Conclave is running.

Return type:

None

async stop() → None[source]

Stop the loop and close any open connection (idempotent).

Return type:

None

aioafero.v1.conclave.events module

Apply Conclave private and public events to the cached bridge state.

aioafero.v1.conclave.events.translate_attr_change(device: AferoDevice, attribute: Mapping[str, Any]) → AferoState | None[source]

Map one Conclave attribute block to an AferoState.

Parameters:
Return type:

AferoState | None

aioafero.v1.conclave.events.translate_status_change(status: Mapping[str, Any]) → list[AferoState][source]

Map a Conclave status block to AferoState rows.

Parameters:

status (Mapping[str, Any])

Return type:

list[AferoState]

async aioafero.v1.conclave.events.apply_attr_change(bridge: AferoBridgeV1, payload: dict) → bool[source]

Apply a private / attr_change event to the cached device.

Parameters:
Return type:

bool

async aioafero.v1.conclave.events.apply_status_change(bridge: AferoBridgeV1, payload: dict) → bool[source]

Apply a private / status_change event to the cached device.

Parameters:
Return type:

bool

async aioafero.v1.conclave.events.apply_invalidate_remove(bridge: AferoBridgeV1, payload: dict) → bool[source]

Apply a public / invalidate kind=remove inventory event.

Parameters:
Return type:

bool

async aioafero.v1.conclave.events.apply_invalidate_add(bridge: AferoBridgeV1, payload: dict) → bool[source]

Apply a public / invalidate kind=add inventory event.

target=metadevices fetches the new metadevice over REST (Conclave only sends the id) and fans out RESOURCE_ADDED / RESOURCE_UPDATED like a discovery poll for that payload alone.

target=devices is ignored when the physical device is already cached — the matching metadevices add carries the semantics we need. When the physical id is unknown, a full discovery poll reconciles inventory.

Parameters:
Return type:

bool

async aioafero.v1.conclave.events.apply_public_invalidate(bridge: AferoBridgeV1, payload: dict) → bool[source]

Dispatch a public / invalidate inventory frame by kind.

Parameters:
Return type:

bool

aioafero.v1.conclave.frame_capture module

Buffered NDJSON logging.Handler for Conclave application frames.

Attach to FRAME_LOGGER while Conclave runs; call flush() (or close()) to harvest. Home Assistant can do the same and anonymize on Generate Debug later.

class aioafero.v1.conclave.frame_capture.ConclaveFrameFileHandler(filename: str | Path, *, capacity: int = 32, encoding: str = 'utf-8')[source]

Bases: Handler

Buffer log records in memory and append them to an NDJSON file.

Each emitted record’s formatted message is one JSON object (as produced by the Conclave frame logger). When the buffer reaches capacity, or when flush() / close() is called, lines are appended to filename.

Parameters:
  • filename (str | Path)

  • capacity (int)

  • encoding (str)

__init__(filename: str | Path, *, capacity: int = 32, encoding: str = 'utf-8') → None[source]

Create a handler that writes buffered frame lines to filename.

Parameters:
Return type:

None

property path: Path

Destination NDJSON file.

property buffered_count: int

Frames waiting in memory (not yet flushed).

emit(record: LogRecord) → None[source]

Buffer record; spill to disk when the buffer is full.

Parameters:

record (LogRecord)

Return type:

None

flush() → None[source]

Append buffered lines to the capture file and clear the buffer.

Return type:

None

close() → None[source]

Flush remaining frames and close the handler.

Return type:

None

read_frames() → list[dict[str, Any]][source]

Flush, then load all NDJSON objects from path.

Return type:

list[dict[str, Any]]

aioafero.v1.conclave.frame_capture.attach_frame_capture(path: str | Path, *, buffer_size: int = 32, propagate: bool = False) → ConclaveFrameFileHandler[source]

Add a ConclaveFrameFileHandler to FRAME_LOGGER.

Raises the frame logger to DEBUG and sets propagate. Caller should flush() to harvest, then FRAME_LOGGER.removeHandler(handler) and handler.close().

Parameters:
  • path (str | Path) – Destination NDJSON file.

  • buffer_size (int) – Frames held before a disk spill.

  • propagate (bool) – When false (default), frame lines stay off parent loggers.

Return type:

ConclaveFrameFileHandler

aioafero.v1.conclave.frames module

Conclave wire-frame decoder.

Server frames arrive as a zlib hello blob, then newline-delimited or concatenated JSON objects, with bare \n heartbeats mixed in.

Pure helpers (try_parse_zlib_prefix(), index_json_object_start()) are module-level for unit tests; ConclaveFrameDecoder is the incremental buffer used by the TLS client.

class aioafero.v1.conclave.frames.ZlibPrefixResult(frame: dict | None, consumed: int, decided: bool)[source]

Bases: object

Result of try_parse_zlib_prefix().

Parameters:
frame: dict | None
consumed: int
decided: bool
__init__(frame: dict | None, consumed: int, decided: bool) → None
Parameters:
Return type:

None

aioafero.v1.conclave.frames.index_json_object_start(data: bytes) → int | None[source]

Return the byte offset of the first { in data, if any.

Parameters:

data (bytes)

Return type:

int | None

aioafero.v1.conclave.frames.try_parse_zlib_prefix(data: bytes) → ZlibPrefixResult[source]

Attempt to decompress a leading zlib envelope from data.

Parameters:

data (bytes)

Return type:

ZlibPrefixResult

class aioafero.v1.conclave.frames.ConclaveFrameDecoder[source]

Bases: object

Incremental decoder for the Conclave server-to-client stream.

__init__() → None[source]

Start decoding a fresh Conclave connection.

Return type:

None

feed(chunk: bytes) → Iterator[dict][source]

Feed bytes and yield each complete frame.

Parameters:

chunk (bytes)

Return type:

Iterator[dict]

aioafero.v1.conclave.frames.encode_frame(payload: dict) → bytes[source]

Encode a client-to-server JSON frame.

Parameters:

payload (dict)

Return type:

bytes

aioafero.v1.conclave.frames.encode_heartbeat() → bytes[source]

Return the client heartbeat byte sequence.

Return type:

bytes

aioafero.v1.conclave.protocol module

Conclave login and private/public-envelope helpers (no I/O).

class aioafero.v1.conclave.protocol.PrivateEvent(event: str, data: dict[str, Any])[source]

Bases: object

Parsed private envelope from a Conclave push frame.

Parameters:
event: str
data: dict[str, Any]
__init__(event: str, data: dict[str, Any]) → None
Parameters:
Return type:

None

class aioafero.v1.conclave.protocol.PublicEvent(event: str, data: dict[str, Any])[source]

Bases: object

Parsed public envelope from a Conclave push frame.

Inventory changes arrive as event="invalidate" with a data block carrying kind (add / remove / update / …) and usually a target (metadevices / devices).

Parameters:
event: str
data: dict[str, Any]
__init__(event: str, data: dict[str, Any]) → None
Parameters:
Return type:

None

aioafero.v1.conclave.protocol.build_login_frame(access: ConclaveAccess, *, client_type: str = 'aioafero', version: str = '1.0.0', protocol: int = 2, trace: bool = False) → dict[str, Any][source]

Build the client login JSON frame for a ConclaveAccess.

Parameters:
Return type:

dict[str, Any]

aioafero.v1.conclave.protocol.server_heartbeat_seconds(handshake: dict[str, Any], welcome: dict[str, Any]) → float[source]

Return the server-advertised heartbeat interval in seconds.

Parameters:
Return type:

float

aioafero.v1.conclave.protocol.parse_private_frame(frame: dict[str, Any]) → PrivateEvent | None[source]

Extract a private event from a decoded Conclave frame.

Returns:

PrivateEvent when frame carries a well-formed private block; otherwise None.

Parameters:

frame (dict[str, Any])

Return type:

PrivateEvent | None

aioafero.v1.conclave.protocol.parse_public_frame(frame: dict[str, Any]) → PublicEvent | None[source]

Extract a public event from a decoded Conclave frame.

Returns:

PublicEvent when frame carries a well-formed public block; otherwise None.

Parameters:

frame (dict[str, Any])

Return type:

PublicEvent | None

aioafero.v1.conclave.semantics module

Build a per-device attribute index from metadevice semantics.

Conclave attribute.id values map to deviceValues[].key in each device’s description.functions[] semantics — per-device-profile, never a global enum.

This module turns the cached AferoDevice.functions into a lookup index keyed by str(attribute.id). The decoded (functionClass, functionInstance, value) triple feeds straight back into the existing AferoState cache that REST polls already populate.

class aioafero.v1.conclave.semantics.SemanticBinding(function_class: str, function_instance: str | None, value_name: str | None, device_value: str | None)[source]

Bases: object

One deviceValues[] entry from a metadevice’s semantics.

Parameters:
  • function_class (str) – functionClass for the parent function (e.g. power, brightness, color-mode).

  • function_instance (str | None) – Optional functionInstance (e.g. light-power, fan-power).

  • value_name (str | None) – Name of the functions[].values[] entry (e.g. on, off, sequence, hanukkah-fade).

  • device_value (str | None) – Optional encoded value from deviceValues[].value — set for enum mappings (e.g. color modes) and blob mappings (e.g. custom effect sequences); None for numeric/free-form values.

function_class: str
function_instance: str | None
value_name: str | None
device_value: str | None
__init__(function_class: str, function_instance: str | None, value_name: str | None, device_value: str | None) → None
Parameters:
  • function_class (str)

  • function_instance (str | None)

  • value_name (str | None)

  • device_value (str | None)

Return type:

None

aioafero.v1.conclave.semantics.build_attribute_index(device: AferoDevice) → AttributeIndex[source]

Walk device.functions and index attribute key → binding(s).

Multiple bindings per key are normal (e.g. key 1 → power on/off with distinct deviceValue; key 5 → multiple color modes).

Parameters:

device (AferoDevice) – Discovered AferoDevice with its functions populated (post REST discovery).

Returns:

Dict mapping str(attribute.id) to a list of SemanticBinding entries in declaration order.

Return type:

AttributeIndex

aioafero.v1.conclave.semantics.resolve_binding(index: dict[str, list[SemanticBinding]], attribute_id: object, attribute_value: object, attribute_data: object | None = None) → SemanticBinding | None[source]

Resolve a single push attribute to one SemanticBinding.

Resolution order:

  1. Look up bindings by str(attribute_id); return None if unknown.

  2. If exactly one binding is registered, use it (numeric / free-form attribute — value carries through unchanged).

  3. Otherwise, prefer a binding whose device_value matches the attribute’s decoded value (enum keys like power, color-mode).

  4. Fall back to matching device_value against the raw data blob (used for color-sequence/custom on key 300 where Conclave pushes the encoded blob and the friendly name lives in semantics).

Parameters:
Return type:

SemanticBinding | None

aioafero.v1.conclave.semantics.coerce_rest_state_value(binding: SemanticBinding, attribute_value: object, attribute_data: object | None = None) → object[source]

Translate a Conclave attribute payload into REST-shaped AferoState.value.

Conclave pushes decoded strings such as "0" / "1" for booleans and "4000" for color temperature. REST discovery and controllers expect semantic names ("on" / "off") and suffixed units ("4000K").

Parameters:
Return type:

object

Module contents

Conclave push-event client for the Afero IoT API.

Conclave is Afero’s account-scoped TLS push channel. After a short-lived conclaveAccess token is minted via REST, the client opens a long-lived TLS connection to conclave-stream1.afero.net:443 and receives private (attr_change / status_change) and public (invalidate inventory) frames whenever devices on the account change — regardless of which client (app, hub, automation) triggered it.

The Conclave socket is subscribe-only: writes still go through REST. See docs/user/conclave.rst for setup, lifecycle, and limitations.

class aioafero.v1.conclave.ConclaveAccess(host: str, port: int, ssl: bool, compression: bool, token: str, channel_id: str, expires_timestamp: int | None = None)[source]

Bases: object

Parsed response from POST .../conclaveAccess.

Parameters:
  • host (str) – TLS host for the Conclave socket (e.g. conclave-stream1.afero.net).

  • port (int) – TLS port for the Conclave socket.

  • ssl (bool) – True when the server advertises TLS (always True in observed captures).

  • compression (bool) – True when the server announces zlib compression on the wire (the initial hello is sent as a single zlib blob).

  • token (str) – Short-lived single-use Conclave socket access token. Not the OAuth bearer — pass this as login.accessToken.

  • channel_id (str) – Conclave channel ID (the account UUID).

  • expires_timestamp (int | None) – Optional epoch milliseconds at which token expires; observed tokens may also be single-use, so callers should always mint a fresh token on reconnect rather than trust this clock.

host: str
port: int
ssl: bool
compression: bool
token: str
channel_id: str
expires_timestamp: int | None = None
__init__(host: str, port: int, ssl: bool, compression: bool, token: str, channel_id: str, expires_timestamp: int | None = None) → None
Parameters:
Return type:

None

class aioafero.v1.conclave.ConclaveClient(bridge: AferoBridgeV1, *, connect: ConnectCallable | None = None, read_timeout: float = 75.0, initial_backoff: float = 1.0, max_backoff: float = 60.0, push_idle_timeout: float | None = None, reconcile_on_reconnect: bool = True)[source]

Bases: object

Long-running Conclave push consumer for an AferoBridgeV1.

Parameters:
  • bridge (AferoBridgeV1)

  • connect (ConnectCallable | None)

  • read_timeout (float)

  • initial_backoff (float)

  • max_backoff (float)

  • push_idle_timeout (float | None)

  • reconcile_on_reconnect (bool)

__init__(bridge: AferoBridgeV1, *, connect: ConnectCallable | None = None, read_timeout: float = 75.0, initial_backoff: float = 1.0, max_backoff: float = 60.0, push_idle_timeout: float | None = None, reconcile_on_reconnect: bool = True) → None[source]

Attach a reconnecting Conclave consumer to bridge.

Parameters:
  • push_idle_timeout (float | None) – When set, end the session if no private push is successfully handled within this many seconds after login while the socket is still receiving heartbeats (idle clock starts at welcome). Successful public inventory handlers do not reset the clock. None disables (default). Use for zombie sessions where welcome succeeds but state pushes never arrive or stop.

  • reconcile_on_reconnect (bool) – After a failed session, run one REST state poll once login succeeds again (in the background so the TLS dispatch loop can keep acking heartbeats).

  • bridge (AferoBridgeV1)

  • connect (ConnectCallable | None)

  • read_timeout (float)

  • initial_backoff (float)

  • max_backoff (float)

Return type:

None

property status: ConclaveStatus

Return the current Conclave connection status.

property connected: bool

True when the push session has completed login.

Prefer logged_in for socket-session liveness; connected reflects the emitted lifecycle state used by bridge.events.

property running: bool

True while the background run loop has not been stopped.

property logged_in: bool

True after welcome until the socket session ends.

Cleared before connected during teardown; use status for lifecycle callbacks on events.

property seconds_since_last_push: float | None

Seconds since the last handled private push, when logged in.

property push_stale: bool

True when logged in and push idle exceeds push_idle_timeout.

async wait_until_logged_in(timeout: float = 60.0) → bool[source]

Wait until a Conclave session completes login.

Parameters:

timeout (float)

Return type:

bool

async start() → None[source]

Schedule the background reconnect loop (idempotent).

The loop is owned by this client (stop() / bridge.close()), not tracked as a bridge ad-hoc job — otherwise async_block_until_done() would hang while Conclave is running.

Return type:

None

async stop() → None[source]

Stop the loop and close any open connection (idempotent).

Return type:

None

class aioafero.v1.conclave.ConclaveFrameDecoder[source]

Bases: object

Incremental decoder for the Conclave server-to-client stream.

__init__() → None[source]

Start decoding a fresh Conclave connection.

Return type:

None

feed(chunk: bytes) → Iterator[dict][source]

Feed bytes and yield each complete frame.

Parameters:

chunk (bytes)

Return type:

Iterator[dict]

class aioafero.v1.conclave.ConclaveFrameFileHandler(filename: str | Path, *, capacity: int = 32, encoding: str = 'utf-8')[source]

Bases: Handler

Buffer log records in memory and append them to an NDJSON file.

Each emitted record’s formatted message is one JSON object (as produced by the Conclave frame logger). When the buffer reaches capacity, or when flush() / close() is called, lines are appended to filename.

Parameters:
  • filename (str | Path)

  • capacity (int)

  • encoding (str)

__init__(filename: str | Path, *, capacity: int = 32, encoding: str = 'utf-8') → None[source]

Create a handler that writes buffered frame lines to filename.

Parameters:
Return type:

None

property path: Path

Destination NDJSON file.

property buffered_count: int

Frames waiting in memory (not yet flushed).

emit(record: LogRecord) → None[source]

Buffer record; spill to disk when the buffer is full.

Parameters:

record (LogRecord)

Return type:

None

flush() → None[source]

Append buffered lines to the capture file and clear the buffer.

Return type:

None

close() → None[source]

Flush remaining frames and close the handler.

Return type:

None

read_frames() → list[dict[str, Any]][source]

Flush, then load all NDJSON objects from path.

Return type:

list[dict[str, Any]]

class aioafero.v1.conclave.ConclaveStatus(*values)[source]

Bases: Enum

Connection status of the Conclave push client.

DISCONNECTED = 'disconnected'
CONNECTING = 'connecting'
CONNECTED = 'connected'
async aioafero.v1.conclave.apply_attr_change(bridge: AferoBridgeV1, payload: dict) → bool[source]

Apply a private / attr_change event to the cached device.

Parameters:
Return type:

bool

async aioafero.v1.conclave.apply_invalidate_add(bridge: AferoBridgeV1, payload: dict) → bool[source]

Apply a public / invalidate kind=add inventory event.

target=metadevices fetches the new metadevice over REST (Conclave only sends the id) and fans out RESOURCE_ADDED / RESOURCE_UPDATED like a discovery poll for that payload alone.

target=devices is ignored when the physical device is already cached — the matching metadevices add carries the semantics we need. When the physical id is unknown, a full discovery poll reconciles inventory.

Parameters:
Return type:

bool

async aioafero.v1.conclave.apply_invalidate_remove(bridge: AferoBridgeV1, payload: dict) → bool[source]

Apply a public / invalidate kind=remove inventory event.

Parameters:
Return type:

bool

async aioafero.v1.conclave.apply_public_invalidate(bridge: AferoBridgeV1, payload: dict) → bool[source]

Dispatch a public / invalidate inventory frame by kind.

Parameters:
Return type:

bool

async aioafero.v1.conclave.apply_status_change(bridge: AferoBridgeV1, payload: dict) → bool[source]

Apply a private / status_change event to the cached device.

Parameters:
Return type:

bool

aioafero.v1.conclave.attach_frame_capture(path: str | Path, *, buffer_size: int = 32, propagate: bool = False) → ConclaveFrameFileHandler[source]

Add a ConclaveFrameFileHandler to FRAME_LOGGER.

Raises the frame logger to DEBUG and sets propagate. Caller should flush() to harvest, then FRAME_LOGGER.removeHandler(handler) and handler.close().

Parameters:
  • path (str | Path) – Destination NDJSON file.

  • buffer_size (int) – Frames held before a disk spill.

  • propagate (bool) – When false (default), frame lines stay off parent loggers.

Return type:

ConclaveFrameFileHandler

aioafero.v1.conclave.build_attribute_index(device: AferoDevice) → AttributeIndex[source]

Walk device.functions and index attribute key → binding(s).

Multiple bindings per key are normal (e.g. key 1 → power on/off with distinct deviceValue; key 5 → multiple color modes).

Parameters:

device (AferoDevice) – Discovered AferoDevice with its functions populated (post REST discovery).

Returns:

Dict mapping str(attribute.id) to a list of SemanticBinding entries in declaration order.

Return type:

AttributeIndex

async aioafero.v1.conclave.request_conclave_access(bridge: AferoBridgeV1, *, user: bool = True, soft_hub: bool = False) → ConclaveAccess[source]

Mint a fresh Conclave access token.

Always call this on first connect and on every reconnect — observed tokens are short-lived and may be single-use.

Parameters:
  • bridge (AferoBridgeV1) – Initialized AferoBridgeV1; the bearer token and account ID are read from the bridge.

  • user (bool) – Set True for end-user clients (the only mode observed in captures). Always True for HA / app-style consumers.

  • soft_hub (bool) – Set True only for soft-hub clients. Token shape and channel routing for softHub=true are not yet captured — leave False unless you have verified the response shape.

Returns:

Parsed ConclaveAccess.

Raises:

AferoError – If the response is missing the conclave host or an account-scoped token.

Return type:

ConclaveAccess