28#include "esphome/core/component.h"
29#include "esphome/core/hal.h"
30#include "esphome/components/api/custom_api_device.h"
31#include "esphome/components/spi/spi.h"
71 public api::CustomAPIDevice,
72 public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
73 spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_8MHZ>,
90 void setup()
override;
107 uint8_t
spi_transfer(uint8_t data)
override {
return this->transfer_byte(data); }
110 void spi_write(uint8_t data)
override { this->write_byte(data); }
113 uint8_t
spi_read()
override {
return this->read_byte(); }
173 this->
registry_.add_linked_remote(remote_id, device_id);
181 this->
registry_.add_linked_remote_class(type, device_id);
192 return this->
registry_.apply_optimistic_target(device_id, target_io_position);
201 return this->
registry_.clear_optimistic_target(device_id);
214 return this->
registry_.apply_optimistic_tilt(device_id, tilt_percent);
264 virtual void add_device(
const std::string &device_id);
298 virtual bool set_device_tilt(
const std::string &device_id, uint8_t tilt_percent);
365 virtual bool set_lock_state(
const std::string &device_id,
bool locked);
427#ifdef IOHOME_LR1121_FIRMWARE_UPDATE
434 void trigger_lr1121_firmware_update();
436#ifdef IOHOME_LR1121_BOOTLOADER_UPDATE
447 void set_bootloader_rewrite_allowed(
bool allowed) { this->bootloader_rewrite_allowed_ = allowed; }
457 bool transmit_frame_(
const IoFrame &frame, uint32_t freq, uint16_t preamble);
463 bool send_and_receive_(
const IoFrame &request, IoFrame &response, uint32_t freq);
563 const std::string &src_id)
const;
587 uint32_t retry_after_fail_ms = 0);
647#ifdef IOHOME_LR1121_FIRMWARE_UPDATE
657 void run_lr1121_boot_time_bootloader_read_();
665 void cache_lr1121_flash_verdict_();
668 void dump_lr1121_firmware_update_debug_()
const;
673 std::vector<std::string> lr1121_firmware_update_debug_lines_()
const;
678 std::string describe_lr1121_flash_verdict_()
const;
680#ifdef IOHOME_LR1121_BOOTLOADER_UPDATE
695 void arm_lr1121_flash_confirmation_();
704 void run_lr1121_flash_sequence_();
706#ifdef IOHOME_LR1121_BOOTLOADER_UPDATE
717 void run_lr1121_bootloader_upgrade_sequence_();
781#ifdef IOHOME_LR1121_FIRMWARE_UPDATE
785 Lr1121FirmwareUpdater *lr1121_firmware_updater_{
nullptr};
786 bool lr1121_bootloader_version_known_{
false};
787 uint8_t lr1121_bootloader_chip_type_{0};
788 uint16_t lr1121_bootloader_version_{0};
789 bool lr1121_flash_verdict_known_{
false};
791 uint16_t lr1121_installed_fw_{0};
797 uint8_t lr1121_installed_device_type_{0};
798 bool lr1121_flash_confirmation_armed_{
false};
799#ifdef IOHOME_LR1121_BOOTLOADER_UPDATE
802 bool bootloader_rewrite_allowed_{
false};
815 bool all_zero =
true;
818 all_zero = all_zero &&
id[i] == 0;
819 all_ff = all_ff &&
id[i] == UINT8_MAX;
821 return !all_zero && !all_ff;
832 snprintf(buf,
sizeof(buf),
"%.0f%%", pos);
Owns the per-hub device table, update callbacks, and linked-remote associations.
Authenticated exchange engine — outbound and inbound protocol flows.
InternalGPIOPin * fem_en_pin_
Front-end module enable.
InternalGPIOPin * fem_pa_pin_
Front-end module PA switch.
virtual ManagementActionResult force_open_device(const std::string &device_id)
Move a cover device to fully open at elevated priority, intended to bypass wind/rain soft locks.
virtual bool set_lock_state(const std::string &device_id, bool locked)
Semantic lock helper for lock entities.
void handle_key_extraction_discover_confirm_(const IoFrame &frame)
Handle an inbound CMD_DISCOVER_CONFIRM (0x2C) addressed to our throwaway node ID while armed.
virtual ManagementActionResult rename_device(const std::string &device_id, const std::string &new_name)
Rename a device and verify the result by reading the name back.
void log_key_extraction_result_()
Emit the security-sensitive "system key extracted" log block (see redaction.h — this is the one delib...
void set_tx_power(uint8_t power)
Set transmit power (dBm).
InternalGPIOPin * dio4_pin_
SX1276 DIO4 preamble detect (optional).
void add_exposed_sender(const std::string &sender_id)
Allow a 1W sender (identified by its node ID) to fire the esphome.home_io_control_sender_event event ...
void api_force_open_device_(const std::string &device_id)
Native API callback: force-open a registered cover device.
void maybe_fire_sender_event_(const OneWayFrameInfo &info, bool linked, const std::string &src_id)
Fire the sender HA event for a decoded 1W frame, if the sender is exposed.
virtual void set_key_extraction_armed(bool armed)
Arm or disarm the "Accept Foreign Pairing (Key Extraction)" responder.
void set_node_id(const std::string &id)
Set the controller's node ID (hex string).
virtual ManagementActionResult identify_device(const std::string &device_id)
Trigger a device's physical identify (brief jog/flash) so a user can confirm which physical motor a d...
virtual bool set_device_position_and_tilt(const std::string &device_id, uint8_t position, uint8_t tilt_percent)
Set both position and tilt of a tilt-capable cover in one atomic command.
virtual bool set_device_tilt(const std::string &device_id, uint8_t tilt_percent)
Send a tilt command to a tilt‑capable cover.
InternalGPIOPin * dio1_pin_
SX1262 DIO1 interrupt; also carries the LR1121's DIO9 IRQ line.
bool send_and_receive_(const IoFrame &request, IoFrame &response, uint32_t freq)
Main request/response exchange with retry and automatic authentication.
IOHomeControlComponent()
Initialize ExchangeEngine, PairingEngine, and ManagementActions with double-pointer/ reference indire...
uint32_t last_1w_activity_ms_
millis() of the most recent 1W frame of any kind, including ones dropped as duplicates — a repeat sti...
virtual bool set_switch_state(const std::string &device_id, bool on)
Semantic binary helper for switch entities.
void api_scan_paired_devices_()
Native API callback: broadcast a roll-call scan of already-paired devices.
void set_rst_pin(InternalGPIOPin *pin)
Set the radio reset pin.
virtual void queue_set_device_tilt(const std::string &device_id, uint8_t tilt_percent)
Queue an async tilt update; returns immediately, executed in loop().
void begin_status_poll_tracking_(const std::string &device_id, uint32_t initial_delay_ms)
Begin bounded follow-up polling for a device after a command or overheard remote activity.
void dump_config() override
Dump configuration and radio debug info to the log.
void update_tuning_select(const std::string &name, const std::string &value)
Receive a select tuning update from a HA select entity.
InternalGPIOPin * rst_pin_
virtual void register_device_callback(DeviceUpdateCallback cb)
Register a callback invoked when any device updates.
void set_pa_pin(uint8_t pa_pin)
Set PA boost pin configuration.
virtual IoDevice * get_device(const std::string &device_id)
Retrieve a device by ID; returns nullptr if not found.
virtual void queue_request_device_status(const std::string &device_id)
Queue an async status request; returns immediately, executed in loop().
void update_tuning_number(const std::string &name, float value)
Receive a numeric tuning update from a HA number entity.
void spi_enable() override
Enable the SPI bus.
virtual void add_device(const std::string &device_id)
Add a device to the registry by device ID only (undeclared/legacy path).
void set_fem_en_pin(InternalGPIOPin *pin)
Set the front‑end module enable pin.
InternalGPIOPin * vfem_pin_
Front-end module power.
TuningConfig tuning_
Runtime tuning overrides.
virtual void set_device_status_poll_interval(const std::string &device_id, uint32_t poll_interval_ms)
Configure the optional follow-up polling interval for a registered device.
std::vector< std::string > resolve_1w_target_devices_(const OneWayFrameInfo &info, const std::string &src_id) const
Resolve the set of devices a 1W frame should affect: devices linked to the sending remote by node ID,...
void process_pending_operation_()
Pop next pending operation from the queue and execute it (set position, request status,...
StatusPollPolicy poll_policy_
void schedule_status_poll_(const std::string &device_id, uint32_t delay_ms)
Schedule a delayed status poll for a registered device using the Component timeout API.
void hop_frequency_()
Delegate channel hop to ExchangeEngine (which owns last_hop_us_).
void add_linked_remote(const std::string &remote_id, const std::string &device_id)
Declare that a remote (identified by its node ID) controls a registered device.
virtual void set_device_dimmable(const std::string &device_id, bool dimmable)
Set a device's dimmable flag (see IoDevice::dimmable).
void handle_key_extraction_key_init_(const IoFrame &frame)
Handle an inbound CMD_KEY_INIT (0x31) addressed to our throwaway node ID while armed.
float get_tuning_number_value(const std::string &name) const
Current value of a numeric tuning parameter, used to seed a HA number entity on boot.
bool defer_background_poll_() const
Whether loop() should skip dispatching the queue this iteration because the pending work is a backgro...
bool execute_request_and_update_(const std::string &device_id, const IoFrame &request, bool warn_on_no_response, uint32_t retry_after_fail_ms=0)
Shared request/response helper for high-level operations.
void set_dio1_pin(InternalGPIOPin *pin)
Set the DIO1 interrupt pin (SX1262; also carries the LR1121's DIO9 IRQ line).
std::function< void(bool)> key_extraction_armed_callback_
Invoked whenever the key-extraction armed state changes; see set_key_extraction_armed_callback().
const PairingTelemetry & pairing_telemetry() const
void generate_key_extraction_throwaway_id_(uint8_t out[NODE_ID_SIZE])
Generate a random throwaway node ID for one key-extraction arm cycle, avoiding collisions with the br...
void update_device_status_(const IoFrame &frame, bool trust_position=true)
Extract supported position or metadata info from a response frame and merge it into the device record...
virtual void queue_request_device_name(const std::string &device_id)
Queue an async device-name request; returns immediately, executed in loop().
void handle_key_extraction_key_transfer_(const IoFrame &frame)
Handle an inbound CMD_KEY_TRANSFER (0x32) addressed to our throwaway node ID while armed.
void set_radio_test_mode(bool active)
Suspend the hub's normal loop (packet processing, hopping, polling).
void set_radio_type(const std::string &type)
Set radio type ("sx1276", "sx1262", or "lr1121"); required by the YAML schema.
virtual void queue_set_lock_state(const std::string &device_id, bool locked)
Async form of set_lock_state() that keeps radio work serialized on the main loop.
virtual void queue_set_light_position(const std::string &device_id, uint8_t position)
Async form of set_light_position() that keeps radio work serialized on the main loop.
void schedule_device_polls_(const std::vector< std::string > &device_ids, uint32_t delay_ms)
Schedule status polls for a fixed list of devices (shared by the id-linked and class-linked 1W paths,...
void loop() override
Main loop: process pending operations and drive radio state machine.
void log_exchange_debug_(const char *device_id) const
Log the last exchange debug snapshot (delegates to exchange_engine_).
void set_pairing_result_callback(std::function< void()> cb)
Register a callback invoked once, right after every discover_and_pair() attempt completes — used by t...
void schedule_background_poll_backoff_(const std::string &device_id, bool auth_like)
Apply backoff after a failed background status poll and log the result.
PairingTelemetry pairing_telemetry_
Per-attempt pairing telemetry, shared with ExchangeEngine/PairingEngine.
std::string radio_type_
"sx1276", "sx1262", or "lr1121"; required by the YAML schema.
virtual bool clear_optimistic_target(const std::string &device_id)
Clear a device's optimistic target (e.g.
void api_rename_device_(const std::string &device_id, const std::string &new_name)
Native API callback: rename a registered device.
uint8_t system_key_[AES_KEY_SIZE]
float get_setup_priority() const override
Get setup priority (HARDWARE to initialize early).
void record_1w_activity_(uint32_t now)
Record that a 1W frame just arrived, updating last_1w_activity_ms_ and — when this frame starts a new...
virtual void queue_set_device_position_and_tilt(const std::string &device_id, uint8_t position, uint8_t tilt_percent)
Queue an async combined position+tilt update; returns immediately, executed in loop().
void set_busy_pin(InternalGPIOPin *pin)
Set the BUSY pin (SX1262/LR1121).
virtual bool queue_device_command(const std::string &device_id, CoverCommand cmd)
Queue an async named command (STOP, FAVORITE, VENT, FORCE_OPEN); returns immediately,...
virtual bool discover_and_pair()
Discover and pair a device that is in pairing mode.
RadioDriver * select_and_construct_radio_(const char **chip_name_out)
Select and construct the radio driver named by the required radio_type config field.
uint8_t spi_read() override
Read one byte (MISO only).
void api_identify_device_(const std::string &device_id)
Native API callback: trigger a registered device's physical identify.
bool transmit_frame_(const IoFrame &frame, uint32_t freq, uint16_t preamble)
Transmit a raw IoFrame on the current frequency with given preamble length.
void set_dio0_pin(InternalGPIOPin *pin)
Set the DIO0 interrupt pin (SX1276).
void set_tuning_config(const TuningConfig &config)
Apply the tuning configuration generated from YAML / UI entities.
bool radio_test_mode_
When true, loop() is suspended for loopback testing.
void set_vfem_pin(InternalGPIOPin *pin)
Set the VFEM power pin.
ExchangeEngine exchange_engine_
Owns all authenticated exchange and LBT/hop logic.
virtual bool apply_optimistic_tilt(const std::string &device_id, float tilt_percent)
Set an optimistic slat angle ahead of a confirming status poll, and notify.
virtual bool set_light_state(const std::string &device_id, bool on)
Semantic binary helper for light entities.
esphome::home_io_control::ManagementActionResult ManagementActionResult
Result payload used by hub-level management actions such as rename.
PairingEngine pairing_engine_
Owns the three-phase device pairing flow.
void handle_key_extraction_discover_(const IoFrame &frame)
Handle an inbound CMD_DISCOVER_REQ (0x28) while the key-extraction responder is armed.
InternalGPIOPin * dio0_pin_
SX1276 DIO0 interrupt.
void set_system_key(const std::string &key)
Set the system key (hex string).
ManagementActions management_actions_
Owns rename, identify, force-open, scan_paired_devices, and other hub-level HA actions.
virtual void queue_set_light_state(const std::string &device_id, bool on)
Async form of set_light_state() that keeps radio work serialized on the main loop.
void schedule_linked_remote_polls_(const std::string &remote_id, uint32_t delay_ms=REMOTE_ACTIVITY_STATUS_POLL_DELAY_MS)
Schedule status polls for all devices associated with a linked remote.
void spi_write(uint8_t data) override
Write one byte (MOSI only).
void set_tcxo_voltage(uint8_t voltage)
Set TCXO voltage for SX1262/LR1121 (1.8V / 3.3V).
void notify_device_update_(const std::string &id)
Fire all registered device update callbacks for the given device ID.
std::string system_key_str_
virtual void queue_set_device_position(const std::string &device_id, uint8_t position)
Queue an async position update; returns immediately, executed in loop().
std::string get_tuning_select_value(const std::string &name) const
Current option string of a select tuning parameter, used to seed a HA select entity on boot.
virtual void queue_set_switch_state(const std::string &device_id, bool on)
Async form of set_switch_state() that keeps radio work serialized on the main loop.
void set_dio4_pin(InternalGPIOPin *pin)
Set the DIO4 preamble‑detect pin (SX1276, optional).
decisions::OneWayDedupState last_1w_logged_
Identity of the last processed 1W frame, for burst suppression; see decisions::is_duplicate_1w_frame(...
std::function< void()> pairing_result_callback_
Invoked once after every pairing attempt completes; see set_pairing_result_callback().
virtual bool set_device_position(const std::string &device_id, uint8_t position)
Send a position command to a device.
void setup() override
Initialize hardware (radio and device registry).
uint8_t node_id_[NODE_ID_SIZE]
uint8_t spi_transfer(uint8_t data) override
Transfer one byte full‑duplex.
virtual bool request_device_name(const std::string &device_id)
Request the stored device name from a device.
std::vector< std::string > exposed_senders_
1W sender node IDs (remotes or sensors) allowed to fire the sender HA event (add_exposed_sender).
pairing_responder::ResponderContext key_extraction_ctx_
State for the current "Accept Foreign Pairing" (key-extraction) arm cycle; DISARMED by default so a f...
uint8_t tcxo_voltage_
SX1262/LR1121 TCXO voltage setting (default 1.8 V).
virtual bool apply_optimistic_target(const std::string &device_id, float target_io_position)
Set an optimistic target position ahead of a confirming poll/response, and notify.
void process_received_packet_(const RadioRxPacket &packet)
Parse a received frame, merge supported device state or metadata, and notify callbacks.
bool execute_device_command_(const std::string &device_id, CoverCommand cmd)
Execute a named device command (STOP, FAVORITE, VENT, FORCE_OPEN) via the authenticated exchange.
bool authenticate_request_(const IoFrame &request, uint32_t freq)
Handle an inbound authenticated command from a device (status updates, etc.).
bool try_handle_key_extraction_frame_(const IoFrame &frame)
Dispatch a frame to the key-extraction responder if it's one of its 0x28/0x2C/0x31/0x32 frames and th...
void spi_disable() override
Disable the SPI bus.
InternalGPIOPin * busy_pin_
SX1262/LR1121 BUSY pin.
void register_management_actions_()
Register hub-level Home Assistant actions; called from setup().
void apply_tuning_to_radio_()
Apply the current tuning configuration to the active radio driver.
virtual void queue_discover_and_pair()
Queue a pairing operation; executed in loop() when radio idle.
void broadcast_key_extraction_reply_(const IoFrame &frame)
Transmit a key-extraction reply frame on all 3 IO-homecontrol channels, using the radio driver's resp...
uint32_t first_1w_activity_ms_
millis() of the first 1W frame in the current burst.
virtual bool request_device_status(const std::string &device_id)
Request current status from a device.
void add_linked_remote_class(DeviceType type, const std::string &device_id)
Declare that a device class's typed 1W broadcasts (e.g.
virtual ManagementActionResult scan_paired_devices()
Broadcast a roll-call and report every device that answers (see ManagementActions::scan_paired_device...
bool apply_optimistic_linked_state_(const OneWayFrameInfo &info, const std::vector< std::string > &device_ids)
Apply optimistic target state to every device in device_ids, when the decoded frame carries a resolva...
void set_key_extraction_armed_callback(std::function< void(bool)> cb)
Register a callback invoked whenever the key-extraction armed state changes — manual toggle,...
virtual bool set_light_position(const std::string &device_id, uint8_t position)
Send an arbitrary IO position (0-100) to a light entity.
void set_fem_pa_pin(InternalGPIOPin *pin)
Set the FEM PA switch pin.
RadioDriver * get_radio() const
Get the underlying radio driver (for diagnostics and test tooling).
Encapsulates hub-level management operations exposed as Home Assistant actions.
Serialized pending-operation queue with coalescing, deduplication, and two-band ordering.
static bool is_background_op(PendingOperationType t)
True for background poll types (REQUEST_STATUS, REQUEST_NAME) that yield to control operations.
Owns and drives all three phases of the IO-Homecontrol device pairing flow.
Fixed-size per-attempt telemetry recorder for the pairing flow.
Abstract radio driver for IO-Homecontrol.
Interface for SPI bus access.
Per-hub poll scheduling and failure-backoff policy.
Per-hub device table, update-callback fan-out, and linked-remote map.
Self-contained authenticated exchange engine for IO-Homecontrol 2W.
Pure transition helpers for hub-owned exchange and pairing frame decisions.
Internal exchange-state model for hub-owned authenticated non‑pairing flows.
Internal pairing-state model for hub‑owned discovery and key‑exchange flows.
Pure decision logic for the LR1121 transceiver-firmware-update feature.
Hub-level management operations exposed as Home Assistant actions.
bool defer_background_poll_for_1w_activity(bool next_op_is_background, uint32_t first_1w_activity_ms, uint32_t last_1w_activity_ms, uint32_t now, uint32_t quiet_ms, uint32_t max_defer_ms)
Decide whether to hold back a queued background poll because a 1W remote is still transmitting.
static constexpr uint32_t ONEWAY_QUIET_PERIOD_MS
Hold queued background polls back for this long after any 1W frame, so a poll the hub itself schedule...
static constexpr float UNKNOWN_POSITION
Sentinel value meaning "position is not known yet".
static constexpr uint8_t NODE_ID_SIZE
Device/node addresses are 3 bytes (e.g., "123ABC").
DeviceType
Device type identifiers reported by IO‑Homecontrol products.
constexpr size_t POSITION_TEXT_BUFFER_SIZE
Buffer for formatted position strings such as "100%".
std::function< void(const std::string &device_id, const IoDevice &device)> DeviceUpdateCallback
Callback type invoked when a device's state changes.
CoverCommand
Named device commands for cover-type actuators.
static constexpr uint32_t ONEWAY_POLL_DEFER_CAP_MS
Hard cap on how long sustained 1W traffic may hold a background poll back in total,...
std::string format_position(float pos)
Format a position float as a human‑readable string (e.g.
static constexpr uint32_t REMOTE_ACTIVITY_STATUS_POLL_DELAY_MS
Delay before polling after overheard remote traffic.
BootloaderUpgradePath
Whether the three-stage bootloader-rewrite sequence (ADR 0021) is applicable, and if not,...
bool stored_node_id_is_valid(const uint8_t id[NODE_ID_SIZE])
Check if a stored node ID is valid (not all-zero, not all-0xFF).
constexpr uint8_t DEFAULT_TCXO_VOLTAGE_SETTING_1P8V
SX1262 DIO3 setting value for a 1.8 V TCXO.
FlashDecision
Outcome of lr1121_flash_decision().
@ NEEDS_CONFIRMATION
Not unsafe, but not an unambiguous "yes" either — needs a second press.
static constexpr uint8_t AES_KEY_SIZE
AES-128 key size.
constexpr uint8_t DEFAULT_PA_PIN_PA_BOOST
SX1276 PA_CONFIG selector for the PA_BOOST output path.
constexpr uint8_t DEFAULT_TX_POWER_DBM
Default TX power used unless YAML overrides it.
Pending-operation queue with per-type coalescing and deduplication.
Device discovery and key-exchange engine for IO-Homecontrol pairing.
Pure decision logic for the device-role "Accept Foreign Pairing" (system-key extraction) responder.
Device-name, address-classification and 1W-frame codecs.
IO-Homecontrol 2W frame container: control bytes, IoFrame and (de)serialization.
Radio abstraction layer for IO-Homecontrol.
LR1121 bootloader-mode-*and*-loader-mode SPI transport, standalone from the running RadioDriver.
Per-device poll scheduling, failure backoff, and follow-up-poll state machine.
YAML-declared device metadata for registration; defaults match an undeclared device.
Runtime state of a paired IO‑Homecontrol device.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Result of a hub-level management action such as rename.
Decoded representation of a 1W remote frame.
All runtime tunable parameters for pairing and radio diagnostics.
Key fields of the last processed 1W frame, used to collapse a remote's repeat burst.
Context for one key-extraction arm cycle.
Runtime tuning configuration for pairing and radio diagnostics.