|
| | _inject_hub_entity_id (config, *, flag_key, id_key, suffix, cls) |
| | _inject_accept_foreign_pairing_switch_id (config) |
| | _inject_recover_oneway_key_switch_id (config) |
| | _inject_scan_paired_devices_button_id (config) |
| | validate_lr1121_firmware_source (value, *, expect_loader=False) |
| | validate_checksum_md5 (value) |
| | to_code (config) |
| | _create_oneway_controller_entities (identity, var) |
| | _create_hub_arming_switch (config, var, *, cls, id_key, name) |
| | _create_scan_paired_devices_button (config, var) |
| | _cached_http_fetch (cache_dir) |
| | _render_lr1121_image_header (image, array_name, words_name, version_name) |
| | _render_lr1121_firmware_header (image) |
| | _render_lr1121_bootloader_loader_header (image) |
| | _create_lr1121_firmware_update (config, var) |
| | _create_lr1121_bootloader_update (bootloader_config, config, var, cache_dir) |
|
| | _LOGGER = logging.getLogger(__name__) |
| list | DEPENDENCIES = ["api", "spi"] |
| list | AUTO_LOAD = ["button", "climate", "cover", "light", "lock", "number", "select", "sensor", "switch", "text_sensor"] |
| bool | MULTI_CONF = False |
| str | CONF_HOME_IO_CONTROL_ID = "home_io_control_id" |
| str | CONF_RST_PIN = "rst_pin" |
| str | CONF_DIO0_PIN = "dio0_pin" |
| str | CONF_DIO4_PIN = "dio4_pin" |
| str | CONF_DIO1_PIN = "dio1_pin" |
| str | CONF_BUSY_PIN = "busy_pin" |
| str | CONF_NODE_ID = "node_id" |
| str | CONF_SYSTEM_KEY = "system_key" |
| str | CONF_TX_POWER = "tx_power" |
| str | CONF_PA_PIN = "pa_pin" |
| str | CONF_RADIO_TYPE = "radio_type" |
| str | CONF_FEM_EN_PIN = "fem_en_pin" |
| str | CONF_VFEM_PIN = "vfem_pin" |
| str | CONF_FEM_PA_PIN = "fem_pa_pin" |
| str | CONF_TCXO_VOLTAGE = "tcxo_voltage" |
| str | CONF_EXPOSED_SENDERS = "exposed_senders" |
| str | CONF_ACCEPT_FOREIGN_PAIRING = "accept_foreign_pairing" |
| str | CONF_RECOVER_ONEWAY_KEY = "recover_oneway_key" |
| str | CONF_SCAN_PAIRED_DEVICES_BUTTON = "scan_paired_devices_button" |
| str | CONF_ONEWAY_CONTROLLERS = "oneway_controllers" |
| str | CONF_NODE_ID_DERIVED = "_node_id_derived" |
| str | CONF_MANUFACTURER = "manufacturer" |
| str | CONF_IO_DEVICE_TYPE = "io_device_type" |
| str | CONF_INITIAL_SEQUENCE = "initial_sequence" |
| str | CONF_COMMANDS = "commands" |
| str | CONF_EXECUTE_ACEI = "execute_acei" |
| str | CONF_EXECUTE_BROADCAST = "execute_broadcast" |
| str | CONF_ENROLLMENT = "enrollment" |
| str | CONF_ENROLLMENT_WITH_MAC = "enrollment_with_mac" |
| str | CONF_ENROLLMENT_CLASSES = "enrollment_classes" |
| str | CONF_BUTTON_IDS = "button_ids" |
| str | CONF_LAST_COMMAND_SENSOR_ID = "last_command_sensor_id" |
| str | CONF_ENROLL_BUTTON_ID = "_enroll_button_id" |
| str | CONF_DIAGNOSTIC_PROBES = "diagnostic_probes" |
| str | CONF_LR1121_FIRMWARE_UPDATE = "lr1121_firmware_update" |
| str | CONF_LR1121_BOOTLOADER = "bootloader" |
| str | CONF_CHECKSUM_MD5 = "checksum_md5" |
| str | CONF_TARGET_VERSION = "target_version" |
| int | MIN_STATUS_POLL_INTERVAL_MS = 500 |
| str | CONF_ACCEPT_FOREIGN_PAIRING_SWITCH_ID = "_accept_foreign_pairing_switch_id" |
| str | CONF_RECOVER_ONEWAY_KEY_SWITCH_ID = "_recover_oneway_key_switch_id" |
| str | CONF_LR1121_FIRMWARE_UPDATE_BUTTON_ID = "_lr1121_firmware_update_button_id" |
| str | CONF_SCAN_PAIRED_DEVICES_BUTTON_ID = "_scan_paired_devices_button_id" |
| str | CONF_LR1121_BOOTLOADER_SWITCH_ID = "_lr1121_bootloader_switch_id" |
| | home_io_control_ns = cg.esphome_ns.namespace("home_io_control") |
| | IOHomeControlComponent |
| | IOHomeAcceptForeignPairingSwitch |
| | IOHomeRecoverOneWayKeySwitch |
| | IOHomeLr1121FirmwareUpdateButton |
| | IOHomeScanPairedDevicesButton |
| | IOHomeOneWayCommandButton |
| | IOHomeOneWayLastCommandTextSensor |
| | IOHomeOneWayEnrollButton |
| | OneWayButtonAction = home_io_control_ns.enum("OneWayButtonAction", is_class=True) |
| dict | ONEWAY_COMMANDS |
| | IOHomeLr1121BootloaderRewriteSwitch |
| | LR1121_BOOTLOADER_SCHEMA |
| | LR1121_FIRMWARE_UPDATE_SCHEMA |
| home_io_control._cached_http_fetch |
( |
| cache_dir | ) |
|
|
protected |
Build a `fetch(url, expected_hash=None) -> bytes` callable for
lr1121_firmware.fetch_and_verify(), backed by an on-disk cache so repeat and offline builds
don't re-download the same source.
The cache key incorporates `expected_hash` (the MD5 fetch_and_verify() already resolved from
the `.md5` sidecar or `checksum_md5:` before calling this for the `.bin`) rather than being
`sha256(url)` alone. With the default `ref: HEAD` the URL never changes, so a plain
url-only key means a corrupt/truncated download poisons the cache permanently -- no config
change can ever invalidate it, since nothing about the request changes on retry. Folding the
expected hash in means correcting a wrong `checksum_md5:` (or a fixed upstream sidecar) misses
the poisoned entry and forces a fresh download. The `.md5` sidecar fetch itself has no
expected_hash to key on (chicken-and-egg -- it's what supplies one for the .bin) and is cached
under the URL alone; a corrupted sidecar is a much smaller/rarer risk than a corrupted 64+ KB
binary, and the cache directory below is a manual escape hatch either way.
Data that fails its own hash check is deliberately never written to the cache (verify-before-store):
a transient network corruption then simply retries cleanly on the next build, with no
config change needed at all.
Definition at line 1309 of file __init__.py.
| home_io_control._create_hub_arming_switch |
( |
| config, |
|
|
| var, |
|
|
* | , |
|
|
| cls, |
|
|
| id_key, |
|
|
| name ) |
|
protected |
Create a hub-level arming switch (key extraction or key adoption).
Mirrors tuning.py's _create_number()/_create_select(): normalize a bare {id, name} dict
through switch_schema()+COMPONENT_SCHEMA so it carries the entity/component defaults
register_switch()/register_component() require, matching the neighboring pattern rather than
hand-assembling a config dict shape of its own.
ALWAYS_OFF is a security property, not a UX default: every switch built here arms a window
(foreign-key extraction or 1W key adoption) that must never come back armed after a reboot.
Definition at line 1260 of file __init__.py.
| home_io_control._create_lr1121_bootloader_update |
( |
| bootloader_config, |
|
|
| config, |
|
|
| var, |
|
|
| cache_dir ) |
|
protected |
Fetch/verify the configured loader image, generate its header, set the build flag that
gates the bootloader-rewrite feature, and create the arming switch.
Mirrors _create_lr1121_firmware_update() above -- same "block's presence is the build flag"
shape, one level down (ADR 0021). `target_version` is not passed to
fetch_and_verify(): the loader is not a "target" the way the transceiver image is, its version
is only ever compared for *equality* against the currently-running bootloader (Semtech's
rule), so there is nothing to override.
Definition at line 1448 of file __init__.py.
| home_io_control._create_lr1121_firmware_update |
( |
| config, |
|
|
| var ) |
|
protected |
Fetch/verify the configured firmware image, generate its header, set the build flag that
gates the whole feature, and create the "Flash LR1121 Radio Firmware" button.
The block's mere presence in YAML is the build flag (ADR 0020) — there is no
separate enable switch, so entering/leaving flash mode is a recompile + OTA each way.
Definition at line 1406 of file __init__.py.
| home_io_control._create_oneway_controller_entities |
( |
| identity, |
|
|
| var ) |
|
protected |
Create one identity's command buttons and its "Last 1W Command" diagnostic sensor.
Same normalization as the hub-level switches below: 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 1189 of file __init__.py.
| home_io_control._inject_hub_entity_id |
( |
| config, |
|
|
* | , |
|
|
| flag_key, |
|
|
| id_key, |
|
|
| suffix, |
|
|
| cls ) |
|
protected |
Shared body for the hub-level entities gated by a bare boolean flag in the
`home_io_control:` block (accept_foreign_pairing, recover_oneway_key,
scan_paired_devices_button): declare the entity's ID during validation, under the
`{hub_id}_{suffix}` name, only when its flag is set. See companion_id_base() in
platform_common.py for why this must happen at validation time rather than in to_code().
Definition at line 202 of file __init__.py.
| home_io_control._render_lr1121_image_header |
( |
| image, |
|
|
| array_name, |
|
|
| words_name, |
|
|
| version_name ) |
|
protected |
Render a verified firmware/loader image as a C++ header.
Each raw 4-byte chunk of the `.bin` is exactly one big-endian word as Semtech's own image
format already lays it out, so this only has to slice and format, not transform, the bytes.
`inline const` (not `constexpr`) for the array: it is never used in a constant expression, so
forcing constant-evaluation of up to ~61k elements would only cost compile time; `const` at
namespace scope still lands in `.rodata` (flash) on ESP32, not RAM. Shared by
_render_lr1121_firmware_header() (the transceiver image) and the bootloader loader image --
same shape, different symbol names so both headers can be included from the same translation
unit without colliding.
Definition at line 1352 of file __init__.py.
| home_io_control.validate_lr1121_firmware_source |
( |
| value, |
|
|
* | , |
|
|
| expect_loader = False ) |
Validate the lr1121_firmware_update `source:` shorthand at schema time.
Checks the shape (github://owner/repo/path[@ref]) and the image class (transceiver vs.
loader vs. modem, by filename -- see lr1121_firmware.validate_image_class()). The network
fetch and MD5/image-content verification happen later, in to_code(), where a failure is
still a build-time error but one that needs the network anyway.
@param expect_loader True for the bootloader sub-block's `source:` (must be a loader image),
False for the ordinary transceiver `source:` (must not be one).
Definition at line 247 of file __init__.py.