30const char *position_command_action(
const IoDevice &dev, uint8_t position) {
35 return "set position";
41 return active_state ?
"turn on" :
"turn off";
43 return active_state ?
"unlock" :
"lock";
50 return active_state ?
"open" :
"close";
59const char *operation_profile_name(
const IoDevice &dev) {
61 return "dimmable_light";
69const char *position_rejection_profile(
const IoDevice &dev, uint8_t position) {
84 bool warn_on_no_response, uint32_t retry_after_fail_ms) {
92 if (retry_after_fail_ms != 0)
95 if (warn_on_no_response) {
96 ESP_LOGW(
detail::TAG,
"Command 0x%02X failed for device %s: no valid response (stage=%s tries=%u)", request.
cmd,
97 device_id.c_str(), dbg.stage, dbg.tries);
111 }
else if (dev !=
nullptr) {
118 if (retry_after_fail_ms != 0)
123 if (retry_after_fail_ms != 0)
139 const char *action = position_command_action(*dev, position);
151 ESP_LOGI(
detail::TAG,
"Sending %s to device %s (profile=%s)", action, device_id.c_str(),
152 operation_profile_name(*dev));
165 this->poll_policy_.get_next_update(device_id) == 0)
183 operation_profile_name(*dev));
202 this->poll_policy_.get_next_update(device_id) == 0)
210 uint32_t
const existing = this->
poll_policy_.get_next_update(device_id);
211 if (existing == 0 || cap < existing)
229 ESP_LOGI(
detail::TAG,
"Sending tilt=%u%% to device %s (profile=%s)", tilt_percent, device_id.c_str(),
230 operation_profile_name(*dev));
242 if (this->
poll_policy_.get_interval(device_id) != 0 && this->poll_policy_.get_next_update(device_id) == 0)
248 uint8_t tilt_percent) {
260 ESP_LOGI(
detail::TAG,
"Sending position=%u%% tilt=%u%% to device %s (profile=%s)", position, tilt_percent,
261 device_id.c_str(), operation_profile_name(*dev));
273 if (this->
poll_policy_.get_interval(device_id) != 0 && this->poll_policy_.get_next_update(device_id) == 0)
296 uint32_t
const retry_after_fail_ms =
368 uint8_t pending_tilt = 0;
371 pending_tilt = op.position;
375 if (this->op_queue_.enqueue_set_position(device_id, position)) {
377 "Coalesced SET_POSITION (pos=%u) + pending SET_TILT (tilt=%u) → SET_POSITION_AND_TILT for "
379 position, pending_tilt, device_id.c_str());
393 this->
op_queue_.enqueue_device_command(device_id, cmd);
405 uint8_t pending_pos = 0;
408 pending_pos = op.position;
412 if (this->op_queue_.enqueue_set_tilt(device_id, tilt_percent)) {
414 "Coalesced pending SET_POSITION (pos=%u) + SET_TILT (tilt=%u) → "
415 "SET_POSITION_AND_TILT for device %s",
416 pending_pos, tilt_percent, device_id.c_str());
421 uint8_t tilt_percent) {
427 this->
op_queue_.enqueue_set_position_and_tilt(device_id, position, tilt_percent);
438 this->
op_queue_.enqueue_request_status(device_id);
444 this->
op_queue_.enqueue_request_name(device_id);
460 this->
op_queue_.enqueue_set_light_position(device_id, position);
473 this->
op_queue_.enqueue_set_lock_state(device_id, locked);
482 this->
op_queue_.enqueue_set_switch_state(device_id, on);
492 if (!opt.has_value())
496 switch (operation.
type) {
virtual bool set_lock_state(const std::string &device_id, bool locked)
Semantic lock helper for lock entities.
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.
bool send_and_receive_(const IoFrame &request, IoFrame &response, uint32_t freq)
Main request/response exchange with retry and automatic authentication.
virtual bool set_switch_state(const std::string &device_id, bool on)
Semantic binary helper for switch entities.
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.
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 process_pending_operation_()
Pop next pending operation from the queue and execute it (set position, request status,...
StatusPollPolicy poll_policy_
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 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().
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 log_exchange_debug_(const char *device_id) const
Log the last exchange debug snapshot (delegates to exchange_engine_).
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.
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().
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.
ExchangeEngine exchange_engine_
Owns all authenticated exchange and LBT/hop logic.
virtual bool set_light_state(const std::string &device_id, bool on)
Semantic binary helper for light entities.
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 notify_device_update_(const std::string &id)
Fire all registered device update callbacks for the given device ID.
virtual void queue_set_device_position(const std::string &device_id, uint8_t position)
Queue an async position update; returns immediately, executed in loop().
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.
virtual bool set_device_position(const std::string &device_id, uint8_t position)
Send a position command to a device.
uint8_t node_id_[NODE_ID_SIZE]
virtual bool request_device_name(const std::string &device_id)
Request the stored device name from a device.
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.
virtual void queue_discover_and_pair()
Queue a pairing operation; executed in loop() when radio idle.
virtual bool request_device_status(const std::string &device_id)
Request current status from a device.
virtual bool set_light_position(const std::string &device_id, uint8_t position)
Send an arbitrary IO position (0-100) to a light entity.
Internal helpers shared by the hub implementation .cpp files.
bool known_device_accepts_execute_tilt(const IoDevice &dev)
Can this device accept a tilt command?
bool known_device_matches_entity_class(const IoDevice &dev, DeviceCapabilityClass expected)
Does the device's type match the expected HA entity class?
void log_rejected_operation(const std::string &device_id, const IoDevice &dev, const char *operation, const char *expected)
Log a rejected operation with capability mismatch details.
bool known_device_accepts_execute_position(const IoDevice &dev, uint8_t position)
Can this device accept an execute (position) command?
constexpr const char * TAG
Shared log tag for hub-level messages.
bool known_device_supports_status_requests(const IoDevice &dev)
Does the device support status requests?
void record_exchange_timeout(IoDevice &dev, uint8_t tries)
Record that an outbound exchange to this device timed out (no valid response).
void update_link_health(IoDevice &dev, RadioDriver *radio)
Update per-device link-health stats from the radio's last capture.
bool is_binary_entity_position(uint8_t position)
Is the given position value an on/off binary encoding?
void log_frame_issue(IOHomeControlComponent *component, const char *direction, const char *reason, const IoFrame &frame, uint8_t len)
Log a frame‑level issue (unregistered endpoints, unsupported commands).
void log_command_result(const std::string &id, uint8_t result, uint8_t request_cmd=0, bool include_request_cmd=false)
Log a decoded CMD_ERROR_RESP result with optional request-command context.
void record_command_result(IoDevice &dev, const std::string &id, uint8_t result, uint8_t request_cmd=0, bool include_request_cmd=false)
Store a decoded CMD_ERROR_RESP result on the device and log it.
bool create_force_open(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t open_position)
Build a force-open execute frame (0x00): an ordinary position command to the device's wire-scale "ful...
const char * device_operation_profile_name(DeviceType type)
Human‑readable operation profile name for a device type.
bool create_get_name(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power)
Build a get-name request (0x50).
@ SET_LOCK_STATE
set_lock_state call (locked/unlocked).
@ SET_TILT
set_device_tilt call (tilt percentage 0–100).
@ SET_POSITION_AND_TILT
Combined set_device_position_and_tilt call.
@ SET_LIGHT_STATE
set_light_state call (binary on/off).
@ SET_SWITCH_STATE
set_switch_state call (binary on/off).
@ REQUEST_NAME
request_device_name call (poll for stored device name).
@ DISCOVER_AND_PAIR
discover_and_pair call (starts 3-phase pairing flow).
@ REQUEST_STATUS
request_device_status call (poll for current position).
@ DEVICE_COMMAND
Named device command (STOP, FAVORITE, VENT).
@ SET_POSITION
set_device_position call (position 0–100 or special values).
static constexpr uint8_t CMD_ERROR_RESP
Error response to any command.
bool create_get_status(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build a get-status request (0x03). The device responds with its current position.
DeviceCapabilityClass device_capability_class(DeviceType type)
Map a raw IO‑Homecontrol type to the closest ESPHome/Home Assistant entity family.
CoverCommand
Named device commands for cover-type actuators.
@ STOP
Stop movement immediately.
@ FORCE_OPEN
Move to fully open at elevated priority; intended to bypass soft locks and environmental limits (conf...
bool create_execute_position_and_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t position, uint8_t tilt_percent)
Build a combined position-and-tilt execute command (0x00) — setClosureAndOrientation.
uint8_t frame_length(const IoFrame &f)
Get total frame length from ctrl0.
bool create_execute_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t tilt_percent)
Build a tilt execute command (0x00) for devices that support slat angle control.
const char * cover_command_name(CoverCommand cmd)
Get a human-readable name for a CoverCommand.
static constexpr uint32_t STATUS_RETRY_AFTER_FAIL_MS
First retry after a silent failure.
static constexpr uint8_t BINARY_ENTITY_ON_POSITION
Position value written for binary ON commands (light on, switch on, lock unlock).
static constexpr uint8_t CMD_EXECUTE
Set position/open/close/stop — requires authentication.
static constexpr uint32_t FREQ_CH2
Channel 2: 868.95 MHz (1W and 2W, TX channel).
bool create_execute_position(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, uint8_t position)
Build a position execute command (0x00) to move a device to a numeric position.
@ CLIMATE
Climate device (heating/cooling).
@ SWITCH
Binary on/off switch.
@ UNKNOWN
Unknown capability.
@ COVER
Position‑controlled cover (shutter/blind/awning).
@ LIGHT
Binary on/off light.
static constexpr uint32_t STOP_SETTLE_POLL_CAP_MS
Upper bound on the settle-poll delay after a STOP command.
bool create_execute_command(IoFrame &f, const uint8_t *own, const uint8_t *dst, bool low_power, CoverCommand cmd)
Build a named-command execute frame (0x00) for STOP, FAVORITE, or VENT.
static constexpr uint8_t POS_STOP
Position values in the IO protocol.
static constexpr uint8_t BINARY_ENTITY_OFF_POSITION
Position value written for binary OFF commands (light off, switch off, lock lock).
bool create_get_status_tilt(IoFrame &f, const uint8_t *own, const uint8_t *dst)
Build a tilt-aware get-status request (0x03) that returns the extended 16-byte tilt payload.
bool device_supports_tilt(DeviceType type)
Does this device type support tilt (slat angle) control?
Command builders for the IO‑Homecontrol protocol.
Runtime state of a paired IO‑Homecontrol device.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
uint8_t data[FRAME_MAX_DATA_SIZE]
Command parameters (0–23 bytes).
uint8_t data_len
Actual length of data.
A single queued operation to be dispatched from loop().
CoverCommand command
Named command for DEVICE_COMMAND operations.
std::string device_id
Target device ID (hex string, e.g., "123ABC").
uint8_t tilt
Tilt value for SET_POSITION_AND_TILT (0–100).
uint8_t position
Position/tilt value (0–100) or binary state (ON=0, OFF=100).
PendingOperationType type
Operation type (determines which handler to invoke).