SberBridge¶
Ядро интеграции: управление MQTT-соединением к Sber Cloud, диспетчеризация команд, отслеживание состояний HA-устройств.
Sber Smart Home MQTT Bridge - core bridge logic.
Manages: - Async MQTT connection to Sber cloud broker (aiomqtt) - HA state change listening and publishing to Sber - Sber command reception and forwarding to HA services - Connection health monitoring and device acknowledgment tracking
RECONNECT_GRACE_TIMEOUT
module-attribute
¶
Maximum seconds to wait for Sber acknowledgment after (re)connect.
After a reconnect, the bridge publishes HA states and waits for Sber to acknowledge them (via status_request or config_request) before accepting commands. This timeout is a fallback in case Sber never sends a request.
DEFERRED_CONFIRM_SLOT_SUFFIX
module-attribute
¶
Suffix of the _confirm_tasks slot holding an entity-requested republish.
The default confirm is keyed by the bare entity id (unchanged), so an
entity that asks for a second, later publish through
pending_confirm_delay gets its own slot instead of cancelling the
short one. # cannot occur in an HA entity id, so the two namespaces
can never collide.
LOG_PAYLOAD_MAX_CHARS
module-attribute
¶
Maximum characters of a payload stored in the DevTools message log.
Payloads may legally be up to max_payload_size (1 MB by default), but
the DevTools ring buffer keeps message_log_size entries and pushes
each one synchronously to every WebSocket subscriber. Storing full
payloads would bound memory at maxlen * max_payload_size (~50 MB with
defaults); truncating each stored copy to this limit bounds it at a few
hundred KB. Only the DevTools copy is truncated — real MQTT traffic and
command handling always see the full payload.
BridgeStats
dataclass
¶
BridgeStats(connected_since=None, messages_received=0, messages_sent=0, commands_received=0, config_requests=0, status_requests=0, errors_from_sber=0, publish_errors=0, reconnect_count=0, acknowledged_entities=set(), collectively_acked_entities=set(), last_error_detail='', validation_failures=list())
Connection statistics and health metrics for the Sber MQTT bridge.
connected_since
class-attribute
instance-attribute
¶
Timestamp when the current connection was established.
messages_received
class-attribute
instance-attribute
¶
Total MQTT messages received from Sber.
messages_sent
class-attribute
instance-attribute
¶
Total MQTT messages published to Sber.
commands_received
class-attribute
instance-attribute
¶
Total Sber commands processed.
config_requests
class-attribute
instance-attribute
¶
Total config requests received from Sber.
status_requests
class-attribute
instance-attribute
¶
Total status requests received from Sber.
errors_from_sber
class-attribute
instance-attribute
¶
Total error messages received from Sber.
publish_errors
class-attribute
instance-attribute
¶
Total failed publish attempts.
reconnect_count
class-attribute
instance-attribute
¶
Total number of reconnections since startup.
acknowledged_entities
class-attribute
instance-attribute
¶
Entity IDs that Sber has acknowledged (via status_request or command).
collectively_acked_entities
class-attribute
instance-attribute
¶
Subset of :attr:acknowledged_entities marked without being named.
A status_request carrying no device list means "send me the state
of everything you have". It is a real acknowledgement — the cloud is
talking to this hub — but it is a collective one: it names nobody,
so it cannot vouch for any individual device.
Keeping the two strengths apart is what lets the same signal answer two
different questions honestly. "Confirmed this session" (the panel
counter) legitimately counts a collective ack, while the
silent-rejection alarm
(:attr:~SberBridge.never_confirmed_entities) must not: a device Sber
silently rejected is still covered by "state of everything", so folding
the two together made the alarm unable to fire at all — the user saw
"confirmed: 36 / never confirmed: 0" on a bridge whose registry knew
nothing (issue #57).
An id leaves this set as soon as the cloud names it individually (a
command, or a status_request listing it): the weak mark is then
superseded by real per-device evidence.
last_error_detail
class-attribute
instance-attribute
¶
Human-readable detail of the last error message from Sber cloud.
validation_failures
class-attribute
instance-attribute
¶
Entity IDs that failed pydantic validation and were excluded from last config.
as_dict
¶
Return stats as a serializable dict.
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
SberBridge
¶
Bridge between Home Assistant and Sber Smart Home MQTT cloud.
Initialize the bridge.
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
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 | |
config_publish_context
property
¶
Return the descriptor context the next config publish will use.
Public so the DevTools "Raw config" preview can render exactly what
would go on the wire. Previously the preview called the payload
builder without these arguments and silently got the builder's own
defaults, so it always showed parent_id: "root" no matter how
hub_auto_parent_id was set — reported as "the setting is not
applied to the config" (issue #44).
is_connected
property
¶
Return True if connected to Sber MQTT (owned by MqttClientService).
connection_phase
property
¶
Return the current connection lifecycle phase.
Phases
starting — HA not fully loaded, waiting for integrations.
connecting — MQTT connection in progress.
awaiting_ack — connected, published config, waiting for Sber to acknowledge.
ready — fully operational, accepting commands.
disconnected — not connected to MQTT broker.
redefinitions
property
¶
Return a copy of the entity redefinitions mapping.
Values are per-entity dicts with optional name / room /
home keys (see :class:RedefinitionsStore).
ha_serial_prefix
property
¶
Return active per-HA serial prefix, or None when feature is off.
unacknowledged_entities
property
¶
Return entity IDs Sber has not spoken about in this session.
Note what this is not: evidence that the cloud rejected the
device. The acknowledgement mark is set when Sber sends a command
or a status_request for the entity, and it lives in memory, so
every restart empties it. The cloud has no idea we restarted and
no reason to speak up immediately — it will ask for state when the
user opens the Salute app, issues a voice command, or its own poll
comes round. Right after a restart this list therefore contains
everything, which says nothing about registration (issue #57).
Use :attr:never_confirmed_entities for "something is actually
wrong"; this property answers the narrower question of what has
been confirmed since the bridge came up.
cloud_known_entities
property
¶
Return exposed entity IDs the cloud is believed to hold.
Backed by :class:~cloud_device_registry.CloudDeviceRegistry,
which persists into ConfigEntry.options and therefore survives
a restart. This is the closest thing to "the cloud accepted it"
that the protocol allows: there is no way to ask Sber what it
holds — we only publish on up/config / up/status and learn
from the ids it names in down/status_request.
cloud_device_registry_state
property
¶
Return the raw cloud-device registry state, for diagnostics.
Deliberately unfiltered and paired with what is on disk: when the panel reports "known to Sber: 0" the question is which of the two is empty — the live set, the persisted key, or neither (in which case the panel is at fault). Answering that from a diagnostics download is the whole point; issue #57 was diagnosed by guesswork because none of this was in the dump.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Mapping with the in-memory set ( |
dict[str, Any]
|
in |
dict[str, Any]
|
the panel shows ( |
dict[str, Any]
|
publish has succeeded since this bridge came up. |
never_confirmed_entities
property
¶
Return exposed entities the cloud has never been seen to know.
Neither confirmed in this session nor remembered from an earlier
one. Unlike :attr:unacknowledged_entities this does not light up
after every restart, so it is the list worth alerting on: a device
published repeatedly that the cloud never once asks about is the
signature of a silent rejection.
Only named evidence counts here. A bare status_request
acknowledges every exposed entity collectively (see
:attr:SberStats.collectively_acked_entities), and counting that
as per-device confirmation disarmed the alarm permanently: a
silently rejected device is still covered by "send me the state of
everything", so it looked confirmed forever (issue #57).
entities_missing_required_links
property
¶
Return loaded composite entities whose required links are unmapped.
A class with a non-empty
:attr:~devices.base_entity.BaseEntity.REQUIRED_LINK_ROLES cannot
publish a truthful state without its companion — an impulse gate
without a reed contact reports close forever. The wizard
refuses to create such a device, but "add the entity, then set the
category by hand" bypasses that check, so the half-configured
device has to stay visible instead of silent: this property
feeds the HA repair issue, diagnostics and the panel's device
dialog.
Returns:
| Type | Description |
|---|---|
dict[str, list[str]]
|
|
dict[str, list[str]]
|
when every composite device is fully linked. |
message_log
property
¶
Return the DevTools outbound-message ring buffer (delegates to hub).
trace_collector
property
¶
Return the correlation-trace collector (delegates to hub).
validation_collector
property
¶
Return the schema-validation collector (delegates to hub).
schedule_confirm
¶
(Re)arm the delayed state confirm(s) for one commanded entity.
Always arms the short confirm that lets HA settle its async
attribute updates (:attr:_confirm_delay). An entity may ask for
a second, later republish through a pending_confirm_delay
attribute — the impulse gate uses it to replace its emulated
opening / closing value once the leaf's travel time is
over (see :class:~devices.gate.ImpulseGateEntity). Both go
through the very same :meth:_delayed_confirm machinery, just in
different slots, so there is exactly one timer mechanism to reason
about (and to cancel on :meth:async_stop).
Cancels a still-pending confirm in the same slot first, so a rapid
command sequence produces exactly one confirmation per slot. An
entity that no longer asks for a deferred republish gets its slot
cleared by :meth:_sync_deferred_confirm: a timer armed for a
movement that has since been cancelled (counter-command, contact
arrival, option switched off) would otherwise survive for the
whole travel time and fire a redundant forced publish long after
the fact.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_id
|
str
|
HA entity identifier that was just commanded. |
required |
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
forget_cloud_devices
¶
Drop entity ids from the persisted "cloud holds it" registry.
Called when the user un-exposes entities. Normally the next config
publish mirrors the shorter list on its own, but it cannot when the
shorter list is empty: a publish carrying no device is refused as
evidence (see
:meth:~cloud_device_registry.CloudDeviceRegistry.note_published),
so "remove everything" would otherwise leave the registry claiming
devices nobody exposes any more.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_ids
|
Iterable[str]
|
Entity ids the user removed from the bridge. |
required |
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
async_update_redefinition
async
¶
Merge redefinition fields for an entity and trigger config republish.
Public API for frontend / WebSocket handlers to update a device's
Sber-side name / room / home without reaching into private state.
Delegates data mutation and debounced persistence to
:meth:RedefinitionsStore.async_update.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_id
|
str
|
Target Sber entity identifier (must exist in the bridge). |
required |
fields
|
dict[str, str | None]
|
Partial mapping with any of |
required |
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Resulting redefinitions dict for the entity after merge. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
HomeAssistantError
|
If the follow-up config publish fails. |
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
async_update_entity_options
async
¶
Merge per-entity device options for one entity and apply them live.
Persists into entry.options[CONF_ENTITY_OPTIONS] and then
pushes the merged values straight into the loaded entity instead
of reloading the config entry: a reload tears the MQTT session
down and back up, and dropping the bridge for a couple of seconds
because someone flipped a checkbox is not a trade the user agreed
to. Same approach as :meth:async_update_redefinition.
The entity is re-seeded from HA afterwards because some options
change how existing readings are interpreted (a gate's
invert_contact flips the meaning of the contact's last value),
and both the config and this entity's state are republished
because the model descriptor may change too (travel_time /
auto_close_time add allowed_values.open_state). The
config publish covers every device on purpose: Sber reads each
config payload as the complete device list, so a one-device
payload would make the cloud drop and re-create everything else
(issue #44). Only the state publish is narrowed to the edited
entity.
Category-agnostic: which keys an entity accepts, what they mean
and whether a value is usable is decided by the device class
(BaseEntity.ENTITY_OPTION_KEYS /
validate_entity_options / apply_entity_options).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_id
|
str
|
HA entity identifier. |
required |
fields
|
dict[str, Any]
|
Partial option mapping; only the keys present are changed. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
The merged option dict stored for this entity. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
TypeError
|
If the entity's class accepts no options. |
ValueError
|
If the entity rejects one of the submitted values. |
HomeAssistantError
|
If the follow-up publish fails. |
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
async_update_gate_options
async
¶
Deprecated alias of :meth:async_update_entity_options.
Kept because the per-entity option store shipped for impulse gates first (v1.42) and this name is part of that public surface.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_id
|
str
|
HA entity identifier of the gate relay. |
required |
fields
|
dict[str, Any]
|
Partial gate-option mapping. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
The merged option dict stored for this entity. |
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
async_republish_config
async
¶
Public wrapper for forcing a device config republish to Sber.
Explicit user action — bypasses the coalescing gate so the panel's "Re-publish" button is immediate.
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
clear_message_log
¶
apply_settings
¶
Apply changed operational settings without full bridge restart.
Settings that take effect immediately: debounce_delay, max_mqtt_payload_size, message_log_size. Settings that take effect on next reconnect: reconnect_min, reconnect_max, verify_ssl.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
options
|
dict
|
Config entry options dict. |
required |
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
async_publish_raw
async
¶
Publish arbitrary JSON payload to Sber MQTT for debugging.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
str
|
Raw JSON string to publish. |
required |
target
|
str
|
Topic suffix — either "config" or "status". |
required |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not connected to MQTT broker. |
MqttError
|
Propagated on transport errors (counted in
|
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
async_inject_sber_message
async
¶
Feed a synthetic message into the dispatcher as if Sber sent it.
Used by DevTools Replay / Inject: takes a topic (full
sbdev/.../down/commands or a bare suffix like commands)
and runs it through the normal inbound pipeline —
:class:SberCommandDispatcher, correlation trace, state diff,
ack audit — without going through the MQTT broker. No network
round-trip means an injected command flows even when the bridge
is offline, which is exactly what users want when debugging.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
topic
|
str
|
Either the full MQTT topic as it would arrive from
Sber cloud, or just the last segment (suffix) which is
automatically expanded to |
required |
payload
|
str | bytes
|
Raw JSON body. Bytes pass through as-is; strings are UTF-8 encoded to match the real on-wire shape. |
required |
mark_replay
|
bool
|
When True (default), the DevTools message log
records the direction as |
True
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with |
dict[str, Any]
|
|
dict[str, Any]
|
for the given suffix (unknown topic). |
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
subscribe_messages
¶
Subscribe to new MQTT messages in real time (delegates to hub).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback_fn
|
Callable[[dict], None]
|
Called with each new message dict. |
required |
Returns:
| Type | Description |
|---|---|
Callable[[], None]
|
Unsubscribe callable. |
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
async_start
async
¶
Start the bridge: load entities, subscribe to HA events, connect MQTT.
HA state events are subscribed immediately (independent of MQTT connectivity) so that no state changes are lost while waiting for the first connection. MQTT connection is established in a background task with exponential backoff.
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
async_stop
async
¶
Stop the bridge: disconnect MQTT, unsubscribe from HA events.
Idempotent — safe to call multiple times. Cancels every timer and background task the bridge owns (state forwarder debounce, lifecycle listeners, ack-audit timer, delayed-confirm tasks, redefinitions debounce timer, MQTT connection loop) so nothing outlives the entry unload. A pending redefinitions snapshot is flushed synchronously before shutdown so user edits are not lost on reload.
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
refresh_repair_issues
¶
Recompute HA repair issues without awaiting.
Wraps :func:check_and_create_issues in a safe background task so
callers (notably the command dispatcher) can fire-and-forget after
an ack arrives. No-op when HA is not yet running so we don't fight
the early-startup grace window in :meth:_load_exposed_entities.
Source code in custom_components/sber_mqtt_bridge/sber_bridge.py
async_publish_entity_status
async
¶
Publish the current state of a single entity to Sber cloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_id
|
str
|
HA entity identifier. |
required |