Базовые классы¶
BaseEntity¶
Абстрактный базовый класс для всех устройств Sber Smart Home.
Base entity class for Sber Smart Home device representations.
All device types (light, relay, climate, etc.) inherit from BaseEntity. It defines the contract for converting between HA states and Sber JSON protocol.
ALWAYS_PUBLISHED_FEATURES
module-attribute
¶
Feature keys that bypass the declared-features publish filter.
online is obligatory for every Sber category (see
_generated/obligatory_features.py — it appears in every entry), and
Sber drops a device whose publish lacks it. Filtering it out because a
buggy _create_features_list forgot to declare it, or because a user
removed it via sber_features_remove, would be strictly worse than
publishing an undeclared key, so it is always let through.
NO_SNAPSHOT
module-attribute
¶
NO_SNAPSHOT = NoSnapshot()
Singleton sentinel for :meth:BaseEntity.mark_state_published.
CommandResult
module-attribute
¶
CommandResult = ServiceCallResult | UpdateStateResult
Union type for all possible process_cmd return items.
ROLE_BATTERY
module-attribute
¶
ROLE_BATTERY = LinkableRole('battery', frozenset({'sensor'}), frozenset({'battery'}))
Battery percentage sensor (sensor domain, battery device_class).
ROLE_BATTERY_LOW
module-attribute
¶
ROLE_BATTERY_LOW = LinkableRole('battery_low', frozenset({'binary_sensor'}), frozenset({'battery'}))
Low-battery binary sensor (binary_sensor domain, battery device_class).
ROLE_SIGNAL
module-attribute
¶
ROLE_SIGNAL = LinkableRole('signal_strength', frozenset({'sensor'}), frozenset({'signal_strength'}))
Signal strength sensor (sensor domain, signal_strength device_class).
ROLE_TEMPERATURE
module-attribute
¶
ROLE_TEMPERATURE = LinkableRole('temperature', frozenset({'sensor'}), frozenset({'temperature'}))
Temperature sensor (sensor domain, temperature device_class).
ROLE_HUMIDITY
module-attribute
¶
ROLE_HUMIDITY = LinkableRole('humidity', frozenset({'sensor'}), frozenset({'humidity'}))
Humidity sensor (sensor domain, humidity device_class).
ROLE_CO2
module-attribute
¶
ROLE_CO2 = LinkableRole('co2', frozenset({'sensor'}), frozenset({'carbon_dioxide'}))
Carbon-dioxide concentration link (Sber sensor_air.co2, ppm).
ROLE_PM1
module-attribute
¶
ROLE_PM1 = LinkableRole('pm1', frozenset({'sensor'}), frozenset({'pm1'}))
PM1.0 particulate matter link (Sber sensor_air.pm1_0, µg/m³).
ROLE_PM25
module-attribute
¶
ROLE_PM25 = LinkableRole('pm25', frozenset({'sensor'}), frozenset({'pm25'}))
PM2.5 particulate matter link (Sber sensor_air.pm2_5, µg/m³).
ROLE_PM10
module-attribute
¶
ROLE_PM10 = LinkableRole('pm10', frozenset({'sensor'}), frozenset({'pm10'}))
PM10 particulate matter link (Sber sensor_air.pm10, µg/m³).
ROLE_TVOC
module-attribute
¶
ROLE_TVOC = LinkableRole('tvoc', frozenset({'sensor'}), frozenset({'volatile_organic_compounds'}))
TVOC concentration link (Sber sensor_air.tvoc_float, mg/m³).
ROLE_HCHO
module-attribute
¶
ROLE_HCHO = LinkableRole('hcho', frozenset({'sensor'}), frozenset({'volatile_organic_compounds_parts'}))
Formaldehyde link (Sber sensor_air.hcho_float, mg/m³). HA has no
dedicated device_class for formaldehyde; the closest match is
volatile_organic_compounds_parts. Users with a distinct HCHO sensor
will link it manually via the wizard.
ROLE_OPEN_STATE
module-attribute
¶
ROLE_OPEN_STATE = LinkableRole('open_state', frozenset({'binary_sensor'}), frozenset({'garage_door', 'door', 'opening'}))
Reed/contact sensor reporting the real position of an impulse-driven gate.
The primary entity of such a gate is the impulse relay (switch /
button / script), whose own HA state is only an echo of the last
written relay value and says nothing about the leaf. The position must
therefore come from a companion contact sensor linked in this role.
window is deliberately not accepted: window contacts are common
and would otherwise become gate candidates in the wizard.
SENSOR_LINK_ROLES
module-attribute
¶
SENSOR_LINK_ROLES = (ROLE_BATTERY, ROLE_BATTERY_LOW, ROLE_SIGNAL)
Common linkable roles for battery-powered devices (sensors, covers, valves).
GATE_LINK_ROLES
module-attribute
¶
GATE_LINK_ROLES = (ROLE_OPEN_STATE, ROLE_SIGNAL)
Linkable roles accepted by an impulse gate (the Sber gate spec has no battery).
ALL_LINKABLE_ROLES
module-attribute
¶
Global registry of all known linkable roles.
Derived automatically from every LinkableRole constant declared
above in this module. Device classes are expected to compose their
LINKABLE_ROLES from these constants; as long as they do, the wizard
path (resolve_link_role) and per-class matching (LINKABLE_ROLES)
stay in sync. Nothing in this module enforces that composition — a
class that builds its own LinkableRole inline still drifts, which is
why test_link_roles_registry walks every class in
CATEGORY_DOMAIN_MAP and fails on roles unknown to this registry.
NoSnapshot
¶
Sentinel type marking an omitted snapshot argument.
None is a meaningful snapshot value ("serialization failed, treat
as changed"), so :meth:BaseEntity.mark_state_published cannot use it
as its default. Public because it appears in that method's public
signature — callers reading the annotation must be able to name it.
ServiceCallUrl
¶
Bases: TypedDict
Descriptor for a single HA service call.
ServiceCallResult
¶
Bases: TypedDict
A process_cmd result instructing the bridge to call a HA service.
UpdateStateResult
¶
Bases: TypedDict
A process_cmd result instructing the bridge to re-publish current state.
AttrSpec
dataclass
¶
AttrSpec(field, attr_keys=(), parser=lambda v: v, default=None, preserve_on_missing=False, converter=None)
Declarative spec for parsing a single HA attribute into an instance field.
Subclasses of :class:BaseEntity can declare a class-level
ATTR_SPECS tuple and rely on
:meth:BaseEntity._apply_attr_specs to do the parsing in one line
instead of hand-rolling attrs.get(...) / try-except / int()
boilerplate for every attribute.
Attributes:
| Name | Type | Description |
|---|---|---|
field |
str
|
Instance attribute name to assign (e.g. |
attr_keys |
tuple[str, ...]
|
HA attribute key(s) to read in fallback order. First
non- |
parser |
Callable[[object], object]
|
Conversion function applied to the raw value. Defaults to
identity. Should raise |
default |
object
|
Value to assign when no key matched or parsing failed. |
preserve_on_missing |
bool
|
When |
converter
class-attribute
instance-attribute
¶
Full-attrs converter. When set, receives the entire HA attributes dict
instead of a single value looked up by attr_keys. parser and
attr_keys are ignored when converter is provided.
DeviceData
¶
Bases: TypedDict
Typed device registry data linked to an entity.
All keys are optional because linked device data may come from partial
HA device registry entries. Missing values fall back to sensible defaults
in BaseEntity.to_sber_state.
LinkableRole
dataclass
¶
Describes a linkable sensor role that a device class accepts.
Each role declares which HA domain + device_class combinations it matches.
Device classes declare which roles they accept via LINKABLE_ROLES.
This eliminates the need for separate mapping dicts and domain overrides.
Attributes:
| Name | Type | Description |
|---|---|---|
role |
str
|
Link role name (e.g. |
domains |
frozenset[str]
|
Accepted HA entity domains (e.g. |
device_classes |
frozenset[str]
|
Accepted HA device_class values (e.g. |
matches
¶
Check if an HA entity matches this role.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
domain
|
str
|
HA entity domain (e.g. |
required |
device_class
|
str
|
HA original_device_class (e.g. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if both domain and device_class match. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
BaseEntity
¶
Bases: ABC
Abstract base class for all Sber device entities.
Defines the interface that all device types must implement:
- fill_by_ha_state: Parse HA state into internal representation
- _create_features_list: Return Sber feature names
- to_sber_state: Build Sber device config JSON
- _build_current_state: Build Sber current state JSON (the public
to_sber_current_state wraps it with the declared-features filter)
- process_cmd: Handle Sber commands, return HA service calls
- process_state_change: Handle HA state change events
Initialize base entity from HA entity registry data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
category
|
str
|
Sber device category (e.g., 'light', 'relay', 'sensor_temp'). |
required |
entity_data
|
dict
|
Dict with HA entity registry fields. |
required |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
LINKABLE_ROLES
class-attribute
¶
Linkable roles this device class accepts. Override in subclasses.
REQUIRED_LINK_ROLES
class-attribute
¶
Role names this device class cannot work without.
Empty for every class whose Sber features are derivable from the
primary HA entity alone. A non-empty tuple means the device is
composite: without those links it would publish a fabricated state
(see :class:~devices.gate.ImpulseGateEntity, whose position exists
only in a linked contact sensor). The wizard refuses to add such a
device when a required role is unmapped
(websocket_api.devices_grouped.ws_add_ha_device →
missing_required_role).
ENTITY_OPTION_KEYS
class-attribute
¶
Per-entity user option keys this device class understands.
Empty for every class whose behaviour is fully derived from the HA
entity itself. A non-empty tuple opts the class into the generic
per-entity options mechanism: the values are persisted in
entry.options[CONF_ENTITY_OPTIONS] keyed by entity id, applied at
load time by :class:~entity_registry.SberEntityLoader, edited live
through the update_entity_options WebSocket command, reported to
the panel inside device_detail and carried by export / import.
The mechanism is deliberately class-driven: nothing outside the device class knows what an option means, so adding one to a new category needs no change in the loader, the bridge, the WS layer or the config round-trip.
ENTITY_OPTIONS_BLOCK
class-attribute
¶
Key under which device_detail reports :meth:entity_options_state.
Per class rather than global because the panel renders a different
form per option set, and because
:class:~devices.gate.ImpulseGateEntity shipped its block as
gate_options in v1.42 — renaming it would break every panel that
is still cached in a browser.
supports_entity_options
property
¶
Whether this device class accepts per-entity user options.
ATTR_SPECS
class-attribute
¶
Declarative HA-attribute parsing specs.
Subclasses can populate this tuple to drive
:meth:_apply_attr_specs instead of hand-rolling per-attribute
parsing inside fill_by_ha_state.
effective_room
property
¶
Return the best available room name.
Priority: entity area_id → device area_id → empty string.
is_online
property
¶
Public accessor for entity online status.
Returns:
| Type | Description |
|---|---|
bool
|
True if the entity state indicates it is reachable. |
apply_entity_options
¶
Apply persisted per-entity options (default: nothing to apply).
Implementations are deliberately lenient: unknown keys and
invalid values are ignored rather than raising, because this runs
on the entity-loading path where a hand-edited (or downgraded)
config must never take the whole integration down. Strict
checking of user input belongs to :meth:validate_entity_options.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
options
|
dict
|
Mapping of :attr: |
required |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
validate_entity_options
¶
Validate user-submitted option values (raises on bad input).
Called by the update_entity_options WebSocket command before
anything is persisted, so the user gets a readable message instead
of a silently ignored setting. Subclasses override to add
value-level checks and are expected to call super() first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
options
|
dict
|
Mapping submitted by the panel. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
With a human-readable message when the mapping contains a key this class does not accept. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
entity_options_state
¶
Return the option block the panel renders for this entity.
Returns:
| Type | Description |
|---|---|
dict[str, object]
|
Mapping of the current option values (plus any read-only |
dict[str, object]
|
context the form needs), or an empty dict for a class without |
dict[str, object]
|
options — |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
register_link
¶
Register a linked companion entity for the given role.
Public API for :class:SberEntityLoader — replaces direct mutation
of self._linked_entities to preserve encapsulation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
role
|
str
|
The role name (e.g. |
required |
linked_entity_id
|
str
|
HA entity_id of the linked companion. |
required |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
fill_by_ha_state
¶
Parse HA state dict and update internal state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ha_entity_state
|
dict
|
Dict with 'state' and 'attributes' keys from HA. |
required |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
is_group_state
¶
Check if this entity represents a group of other entities.
create_allowed_values_list
¶
Return allowed values map for Sber model descriptor.
Override in subclasses to provide allowed_values for features that require INTEGER ranges or ENUM value lists.
Returns:
| Type | Description |
|---|---|
dict[str, dict]
|
Dict mapping feature key to its allowed values descriptor, |
dict[str, dict]
|
or empty dict if no allowed values needed. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
create_dependencies
¶
Return feature dependencies map for Sber model descriptor.
Override in subclasses to declare feature dependencies (e.g., light_colour depends on light_mode == 'colour').
Returns:
| Type | Description |
|---|---|
dict[str, dict]
|
Dict mapping feature key to its dependency descriptor, |
dict[str, dict]
|
or empty dict if no dependencies needed. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
get_final_features_list
¶
Return features list with user overrides applied.
Removes features from removed_features and appends features
from extra_features. Duplicate-safe.
Returns:
| Type | Description |
|---|---|
list[str]
|
Final list of Sber feature names. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
update_linked_data
¶
Inject state from a linked companion HA entity (default: no-op).
Device classes that accept linked entities (e.g. a binary battery
sensor paired with a valve) override this to apply the foreign
state to their own fields. The default implementation does
nothing, which is correct for classes that don't advertise
:attr:LINKABLE_ROLES.
Providing a universal default also eliminates hasattr checks
at every call site -- callers may invoke it unconditionally.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
role
|
str
|
The link role (e.g. |
required |
ha_state
|
dict
|
HA state dict of the linked entity. |
required |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
link_device
¶
Link this entity to a HA device registry entry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
device_data
|
DeviceData
|
Device registry data dict. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If device_id does not match. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
to_sber_state
¶
Build Sber device config JSON for MQTT publish.
Handles both device_id is None (standalone HA entity) and
device_id is set (entity linked to a device registry entry)
cases through a unified source-resolver approach.
Returns:
| Type | Description |
|---|---|
dict
|
Dict with device descriptor for Sber (id, name, room, model, features). |
dict
|
Optionally includes nicknames, groups, parent_id, and partner_meta |
dict
|
when configured. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If fill_by_ha_state was not called first. |
RuntimeError
|
If device has device_id but linked_device is not set. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
to_sber_current_state
¶
Build the publish-ready Sber current state JSON.
Calls the subclass hook :meth:_build_current_state and then
drops every state whose key is not advertised in
:meth:get_final_features_list — see
:meth:_filter_undeclared_states.
Returns:
| Type | Description |
|---|---|
dict
|
Dict with entity_id key mapping to {'states': [...]}. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
get_entity_domain
¶
Extract HA domain from entity_id.
Returns:
| Type | Description |
|---|---|
str
|
Domain string (e.g., 'climate' from 'climate.living_room'). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If entity_id has invalid format. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
process_cmd
¶
Process a Sber command via the _cmd_handlers dispatch table.
Subclasses declare which Sber feature keys they handle by
overriding :attr:_cmd_handlers. The base implementation walks
cmd_data["states"], routes each entry to its handler, and
returns the concatenated service-call list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cmd_data
|
dict
|
Command payload with 'states' list. Always a dict —
the dispatcher rejects |
required |
Returns:
| Type | Description |
|---|---|
list[CommandResult]
|
List of :class: |
list[CommandResult]
|
items, or empty list if no action needed. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
process_state_change
¶
Handle a state change event from Home Assistant.
Default implementation refreshes internal state via fill_by_ha_state. Override in subclasses if additional processing is needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
_old_state
|
dict | None
|
Previous HA state dict (may be None). Reserved for subclass overrides that need to compare old and new state. |
required |
new_state
|
dict
|
New HA state dict. |
required |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
has_significant_change
¶
Check if current Sber state differs from last published state.
Used to avoid unnecessary MQTT publishes when only non-relevant HA attributes changed (e.g., last_updated, icon, etc.).
Returns:
| Type | Description |
|---|---|
bool
|
True if the state has changed and should be published. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
mark_state_published
¶
mark_state_published(*, snapshot=NO_SNAPSHOT)
Record the state that was just published, for value diffing.
Called after a successful MQTT publish so
:meth:has_significant_change can suppress redundant publishes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
snapshot
|
dict | NoSnapshot | None
|
The exact wire state that went out, captured
before the publish awaited. Passing it is the correct
form for the publish path: re-serializing here would
capture changes that raced in during the network
round-trip and silently swallow them (lost update).
|
NO_SNAPSHOT
|
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
resolve_link_role_for
¶
Resolve the link role of an HA entity against a specific role set.
Shared helper for link-role matching, used by the global
:func:resolve_link_role and available to per-class validation
(matching an entity against a primary's LINKABLE_ROLES) instead
of re-implementing the loop. Not yet a hard single source of truth:
websocket_api/links.py::ws_auto_link_all still runs its own
LinkableRole.matches loop. That is safe only because
ALL_LINKABLE_ROLES contains no two roles matching the same
(domain, device_class) pair — an invariant locked by
test_link_roles_registry.test_registry_has_no_ambiguous_matches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
accepted_roles
|
Iterable[LinkableRole]
|
Roles to match against (e.g. a device class's
|
required |
domain
|
str
|
HA entity domain. |
required |
device_class
|
str
|
HA original_device_class. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Role name string of the first match, or empty string if no match. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
resolve_link_role
¶
Determine the link role for an HA entity based on domain and device_class.
Iterates ALL_LINKABLE_ROLES and returns the role name of the first match.
Domain-aware disambiguation is built into the role definitions:
e.g. sensor + battery → battery, binary_sensor + battery
→ battery_low.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
domain
|
str
|
HA entity domain. |
required |
device_class
|
str
|
HA original_device_class. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Role name string, or empty string if no match. |
Source code in custom_components/sber_mqtt_bridge/devices/base_entity.py
OnOffEntity¶
Базовый класс для устройств с on/off состоянием (реле, розетки, клапаны).
Base class for Sber on/off entities (relay, socket).
Provides shared implementations of fill_by_ha_state, _create_features_list,
and _build_current_state for devices that expose a simple on/off state
via the Sber on_off feature.
Supports optional power, voltage, and current features when the
HA entity reports those values via attributes.
OnOffEntity
¶
Bases: BaseEntity
Base class for on/off entities that expose the Sber 'on_off' feature.
Subclasses must implement process_cmd to map Sber on/off commands
to the appropriate HA service calls (e.g., turn_on/turn_off
for relays).
Subclasses may override _ha_on_state if the HA 'on' state string
differs from the default "on".
Optionally reports power, voltage, and current when
the HA entity has those attributes.
Initialize on/off entity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
category
|
str
|
Sber device category string. |
required |
entity_data
|
dict
|
HA entity registry dict containing entity metadata. |
required |
Source code in custom_components/sber_mqtt_bridge/devices/on_off_entity.py
fill_by_ha_state
¶
Parse HA state and update on/off status, energy, and child_lock attributes.
Uses :class:BaseEntity.ATTR_SPECS for the declarative
attribute parsing of power / voltage / current / child_lock,
falling back to the current_state check which depends on
the subclass-overridable _ha_on_state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ha_state
|
dict
|
HA state dict with 'state' and 'attributes' keys. |
required |
Source code in custom_components/sber_mqtt_bridge/devices/on_off_entity.py
SimpleSensor¶
Базовый класс для read-only сенсоров.
Base class for read-only Sber sensors with a single value feature.
Provides shared implementations of process_cmd,
_create_features_list, and _build_current_state so that concrete
sensor subclasses only need to define how their value is extracted and
formatted for the Sber protocol.
Supports optional battery_percentage feature when the HA entity
reports battery level via attributes.
SimpleReadOnlySensor
¶
Bases: BatteryAndSignalLinkMixin, BaseEntity
Base class for read-only sensors that expose a single Sber feature.
Subclasses must define the class-level attributes _sber_value_key
and _sber_value_type, and implement _get_sber_value to return
the current sensor value in the appropriate Sber format.
Optionally reports battery_percentage when the HA entity has
a battery or battery_level attribute.
Initialize simple read-only sensor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
category
|
str
|
Sber device category string. |
required |
entity_data
|
dict
|
HA entity registry dict containing entity metadata. |
required |
Source code in custom_components/sber_mqtt_bridge/devices/simple_sensor.py
fill_by_ha_state
¶
Parse HA state and update internal state including battery and signal.
Battery (battery / battery_level) and signal strength
(signal_strength / rssi / linkquality) are parsed via
:class:AttrSpec with preserve_on_missing=True so that values
injected by linked companion sensors via update_linked_data
are not clobbered on every primary state refresh.
Sensor sensitivity (Aqara / Tuya) uses custom mapping logic that
is not expressible through AttrSpec — kept imperative.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ha_state
|
dict
|
HA state dict with 'state' and 'attributes' keys. |
required |