Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
home_io_control.oneway_controllers Namespace Reference

Functions

 _no_duplicate_enrollment_classes (value)
 derive_oneway_node_id (hub_node_id, identity_id)
 _hex_byte_array (hex_string)
 _enrollment_classes_initialiser (identity)
 _power_class_override_expression (identity)
 oneway_controller_expression (identity, hub_node_id)
 _reject_node_id_collision (identity_id, node_id, seen_node_ids, derived)
 validate_oneway_controllers (config)
 _collect_declared_device_addresses (full_config)
 final_validate_oneway_controller_addresses (config)
 create_oneway_controller_entities (identity, var)

Variables

 _LOGGER = logging.getLogger(__name__)
dict ONEWAY_COMMANDS
 ONEWAY_CONTROLLER_SCHEMA
tuple _DEVICE_BOUND_DOMAINS = ("cover", "light", "lock", "switch")

Function Documentation

◆ _collect_declared_device_addresses()

_collect_declared_device_addresses ( full_config)
protected
Map every node ID declared as an `io_device_id:` or a bare `linked_remotes:` entry, across
every `home_io_control` entity in `full_config`, to a human-readable owner string.

Only entries with `platform: home_io_control` are considered — the domains in
_DEVICE_BOUND_DOMAINS are shared with every other component that provides a cover/light/
lock/switch platform, and those have nothing to do with this component's address space.
`class:` linked_remotes entries name a device *type*, not a node, so they carry no address to
collide with and are skipped.

CONF_IO_DEVICE_ID/CONF_LINKED_REMOTES are imported locally from platform_common rather than at
module level: platform_common imports from the package (`from . import ...`), whose
__init__.py imports this module, so a module-level import here would be circular. By the time
this function actually runs (final validation, after every used platform module has already
been imported), the cycle has already resolved and the import is a plain cache hit.

Definition at line 482 of file oneway_controllers.py.

◆ _enrollment_classes_initialiser()

_enrollment_classes_initialiser ( identity)
protected
Render `enrollment_classes:` as a 3-element std::array<DeviceType,3> brace-initialiser.

Unset, or fewer than 3, is padded with UNKNOWN (0x00) -- the sentinel effective_enrollment_classes()
(oneway_controller.h) reads as "not overridden here" / "skip this slot".

Definition at line 212 of file oneway_controllers.py.

◆ _hex_byte_array()

_hex_byte_array ( hex_string)
protected
Render a hex string as a C++ brace-initialiser list of bytes.

Definition at line 204 of file oneway_controllers.py.

◆ _no_duplicate_enrollment_classes()

_no_duplicate_enrollment_classes ( value)
protected
Reject a repeated class in `enrollment_classes:` -- each just retransmits the same 0x30.

Definition at line 87 of file oneway_controllers.py.

◆ _power_class_override_expression()

_power_class_override_expression ( identity)
protected
Map `low_power:` to the C++ `std::optional<OneWayPowerClass>` the identity stores.

Tri-state, and the three states are emitted as three distinct values rather than collapsed
here: unset -> `{}` (empty optional), `false` -> ALWAYS_ALIVE, `true` -> LOW_POWER.

An unset key deliberately does **not** pick a shape at codegen time. Resolving it needs the
manufacturer profile, which lives in C++ (`resolve_oneway_wire_profile()`), and splitting that
lookup across two languages is how the ACEI and enrollment-class defaults would drift from this
one. `effective_power_class()` is the single resolver. See ADR 0041, and ADR 0038 for the
shapes themselves.

Definition at line 226 of file oneway_controllers.py.

◆ _reject_node_id_collision()

_reject_node_id_collision ( identity_id,
node_id,
seen_node_ids,
derived )
protected
Raise if `node_id` is already claimed in `seen_node_ids`; no-op otherwise.

Shared between validate_oneway_controllers() (collisions against the hub's own node_id and
other oneway_controllers entries) and final_validate_oneway_controller_addresses()
(collisions against `linked_remotes:`/`io_device_id:` declared elsewhere in the same YAML),
so both raise identically-worded errors regardless of which side of the config the other
claimant lives on.

Definition at line 281 of file oneway_controllers.py.

◆ create_oneway_controller_entities()

create_oneway_controller_entities ( identity,
var )
Create one identity's command buttons and its "Last 1W Command" diagnostic sensor.

Same normalization as create_hub_arming_switch() (hub_entities.py): run a bare {id, name}
dict through the platform's own schema so it carries the entity/component defaults register_*() require.

Entity names derive from the identity handle and the command ("awning_remote" + "open" ->
"Awning Remote Open"), mirroring how the cover's favourite/vent companions derive theirs. The
*IDs* follow the documented `<identity_id>_<command>` rule instead, because those are what a
`time_based` cover composes against.

Definition at line 540 of file oneway_controllers.py.

◆ derive_oneway_node_id()

derive_oneway_node_id ( hub_node_id,
identity_id )
Derive a stable 3-byte 1W source address from the hub's node_id and an identity handle.

`node_id` is optional on a `oneway_controllers:` entry because asking a user to invent a
3-byte radio address is an unanswerable question — nothing tells them which addresses are
safe, and colliding with a real remote in range silently desyncs both transmitters' rolling
sequence counters. Deriving one removes the decision while leaving an explicit value
available to anyone who needs it.

The derivation is done here, at schema time, rather than at runtime on the device, so that a
derived address participates in the same collision checks as a configured one and a clash
fails the build instead of surfacing as a device that silently ignores commands. It is a
pure function of (hub node_id, identity id), so it is stable across builds and reproducible
from the YAML alone.

Uses BLAKE2b rather than Python's hash(), which is salted per-process and would produce a
different address on every compile.

Definition at line 180 of file oneway_controllers.py.

◆ final_validate_oneway_controller_addresses()

final_validate_oneway_controller_addresses ( config)
Extend the oneway_controllers address-collision check to addresses declared outside
`home_io_control:` — a `linked_remotes:` entry or an `io_device_id:` on some other entity in
this same YAML.

Runs as FINAL_VALIDATE_SCHEMA rather than inside validate_oneway_controllers() because only
final validation has access to the full cross-component config (`fv.full_config`) —
`cover:`/`light:`/`lock:`/`switch:` entries are validated independently of
`home_io_control:`'s own CONFIG_SCHEMA and are not visible to it. By this point
validate_oneway_controllers() has already run, so every identity's `node_id` (derived or
explicit) is resolved.

Definition at line 516 of file oneway_controllers.py.

Here is the call graph for this function:

◆ oneway_controller_expression()

oneway_controller_expression ( identity,
hub_node_id )
Generate the C++ OneWayControllerIdentity initialiser for one configured identity.

Emitted as a designated initialiser so the generated code reads like the YAML that produced
it, and so adding a field to the struct cannot silently shift an existing value.

Definition at line 244 of file oneway_controllers.py.

◆ validate_oneway_controllers()

validate_oneway_controllers ( config)
Resolve per-identity defaults and reject address/handle collisions at compile time.

Runs as a post-validator on the whole hub config because every rule here needs the hub's own
`node_id`/`system_key`, which a per-entry validator cannot see.

Only checks addresses visible within `home_io_control:` itself (its own `node_id` and every
configured `oneway_controllers` entry) — see final_validate_oneway_controller_addresses()
below for the matching check against `linked_remotes:`/`io_device_id:` declared elsewhere in
the same YAML, which needs the full cross-component config and so cannot run here. Even
together the two cannot see a real remote the user has never mentioned to this config at all;
see derive_oneway_node_id()'s own docstring for why that residual risk cannot be validated
away.

Definition at line 305 of file oneway_controllers.py.

Here is the call graph for this function:

Variable Documentation

◆ _DEVICE_BOUND_DOMAINS

tuple home_io_control.oneway_controllers._DEVICE_BOUND_DOMAINS = ("cover", "light", "lock", "switch")
protected

Definition at line 479 of file oneway_controllers.py.

◆ _LOGGER

home_io_control.oneway_controllers._LOGGER = logging.getLogger(__name__)
protected

Definition at line 62 of file oneway_controllers.py.

◆ ONEWAY_COMMANDS

dict home_io_control.oneway_controllers.ONEWAY_COMMANDS
Initial value:
= {
"open": OneWayButtonAction.OPEN,
"close": OneWayButtonAction.CLOSE,
"stop": OneWayButtonAction.STOP,
"vent": OneWayButtonAction.VENT,
"favorite": OneWayButtonAction.FAVORITE,
}

Definition at line 68 of file oneway_controllers.py.

◆ ONEWAY_CONTROLLER_SCHEMA

home_io_control.oneway_controllers.ONEWAY_CONTROLLER_SCHEMA

Definition at line 103 of file oneway_controllers.py.