14#include "esphome/core/log.h"
33inline constexpr const char *
TAG =
"home_io_control";
55inline uint8_t
round_percent(
float fraction) {
return static_cast<uint8_t
>(std::lround(fraction * 100.0F)); }
122 const char *expected) {
123 ESP_LOGW(
TAG,
"Rejecting %s for device %s: type=%s (%u) class=%s profile=%s expected=%s", operation,
136 const IoFrame *frame =
nullptr) {
143 if (frame !=
nullptr) {
144 ESP_LOGD(
"io_capture",
145 "chip=%s phase=component stage=%s freq=%" PRIu32
" ts=%" PRIu32
146 " len=%u cmd=0x%02X src=%02X%02X%02X dst=%02X%02X%02X payload=%s",
148 frame->src[1], frame->src[2], frame->dst[0], frame->dst[1], frame->dst[2], payload_hex);
151 ESP_LOGD(
"io_capture",
"chip=%s phase=component stage=%s freq=%" PRIu32
" ts=%" PRIu32
" len=%u payload=%s",
162 const IoFrame &frame, uint8_t len) {
165 const bool src_registered = component->
get_device(src_id) !=
nullptr;
166 const bool dst_registered = component->
get_device(dst_id) !=
nullptr;
168 if (src_registered || dst_registered) {
169 ESP_LOGW(
TAG,
"%s issue=%s cmd=%s(0x%02X) src=%s%s dst=%s%s len=%u data_len=%u", direction, reason,
170 command_name(frame.
cmd), frame.
cmd, src_id.c_str(), src_registered ?
" (registered)" :
"", dst_id.c_str(),
171 dst_registered ?
" (registered)" :
"", len, frame.
data_len);
175 ESP_LOGD(
TAG,
"%s issue=%s cmd=%s(0x%02X) src=%s dst=%s len=%u data_len=%u", direction, reason,
195 const char *target_label =
200 if (linked_devices !=
nullptr && !linked_devices->empty()) {
201 suffix =
" (linked →";
202 for (
const auto &dev_id : *linked_devices) {
210 ESP_LOGD(
TAG,
"rx 1W remote %s targets %s: %s(0x%02X) %s originator=%s priority=%s%s", src_id.c_str(), target_label,
216 ESP_LOGD(
TAG,
"rx 1W remote %s targets %s: %s(0x%02X) data_len=%u%s", src_id.c_str(), target_label,
236inline bool is_exposed_sender(
const std::vector<std::string> &exposed_senders,
const std::string &sender_id) {
237 return std::find(exposed_senders.begin(), exposed_senders.end(), sender_id) != exposed_senders.end();
245 std::array<char, NAME_AND_HEX_BUFFER_SIZE> buffer{};
246 std::snprintf(buffer.data(), buffer.size(),
"%s(0x%02X)", name, value);
247 return std::string(buffer.data());
264 return std::string(
"io_device_type: \"") + name +
"\"";
265 std::array<char, LEARNED_DEVICE_TYPE_HEX_BUFFER_SIZE> buffer{};
266 std::snprintf(buffer.data(), buffer.size(),
"io_device_type: 0x%02X",
static_cast<uint8_t
>(type));
267 return std::string(buffer.data());
286 {
"linked", linked ?
"true" :
"false"},
321 if (radio ==
nullptr)
353 static_cast<uint16_t
>(std::min<uint32_t>(
static_cast<uint32_t
>(dev.
exchange_attempt_count) + tries, UINT16_MAX));
371 bool include_request_cmd =
false) {
373 if (include_request_cmd) {
374 ESP_LOGW(
TAG,
"Device %s: %s (0x%02X) returned %s result=0x%02X %s (%s)",
id.c_str(),
command_name(request_cmd),
379 ESP_LOGW(
TAG,
"Device %s: explicit %s result=0x%02X %s (%s)",
id.c_str(), kind, result,
command_result_name(result),
395 bool include_request_cmd =
false) {
The main IO-Homecontrol component.
virtual IoDevice * get_device(const std::string &device_id)
Retrieve a device by ID; returns nullptr if not found.
Abstract radio driver for IO-Homecontrol.
const RadioCaptureInfo & get_last_capture() const
Get the most recent radio capture info.
virtual const char * chip_name() const =0
Get a human‑readable chip name.
IO-Homecontrol ESPHome component — protocol controller.
Shared frame logging helpers for IO-Homecontrol.
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?
std::string format_name_and_hex(const char *name, uint8_t value)
Format a name/value pair as "name(0xXX)", e.g. "execute(0x00)".
std::map< std::string, std::string > build_sender_event_data(const OneWayFrameInfo &info, bool linked)
Build the Home Assistant event data map for a decoded 1W sender frame.
constexpr const char * TAG
Shared log tag for hub-level messages.
constexpr size_t NAME_AND_HEX_BUFFER_SIZE
Buffer size for format_name_and_hex(): longest command name plus "(0xXX)" and a margin.
std::string describe_learned_device_type(DeviceType type)
Build the YAML line to add once a device's type is learned at runtime.
constexpr float BINARY_ENTITY_ON_POSITION_THRESHOLD
Shared 0-100 cutoff: values below this mean binary "on".
bool known_device_supports_status_requests(const IoDevice &dev)
Does the device support status requests?
void log_1w_remote_frame(const OneWayFrameInfo &info, const std::vector< std::string > *linked_devices=nullptr)
Log an already-decoded 1W remote frame at DEBUG level.
void record_exchange_timeout(IoDevice &dev, uint8_t tries)
Record that an outbound exchange to this device timed out (no valid response).
void log_component_capture(const RadioDriver *radio, const char *stage, const uint8_t *buf, uint8_t len, const IoFrame *frame=nullptr)
Log a frame at the "io_capture" tag with structured fields.
bool is_exposed_sender(const std::vector< std::string > &exposed_senders, const std::string &sender_id)
Whether a 1W sender is on the exposed_senders allowlist for the sender HA event.
constexpr const char * ONEWAY_SENDER_EVENT
Home Assistant event fired when a decoded 1W frame carries a command intent from an exposed sender (a...
void update_link_health(IoDevice &dev, RadioDriver *radio)
Update per-device link-health stats from the radio's last capture.
uint8_t round_percent(float fraction)
Convert a 0.0-1.0 HA fraction (position, tilt, or brightness) to a 0-100 IO percent.
void normalize_stopped_state(IoDevice &dev)
Normalize stopped state: some devices briefly report stopped before target/current converge.
bool is_binary_entity_position(uint8_t position)
Is the given position value an on/off binary encoding?
constexpr uint32_t ONEWAY_DEDUP_WINDOW_MS
Suppress a repeated 1W log/poll for the same remote and the same intent within this window.
void log_status_update(const std::string &id, const IoDevice &dev, const char *suffix="")
Log a concise status‑update line used by inbound handlers.
constexpr size_t LEARNED_DEVICE_TYPE_HEX_BUFFER_SIZE
Buffer size for describe_learned_device_type()'s hex fallback: "io_device_type: 0xXX" plus margin.
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 clear_command_result(IoDevice &dev)
Clear a previously recorded CMD_ERROR_RESP result, if any.
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.
const char * device_operation_profile_name(DeviceType type)
Human‑readable operation profile name for a device type.
static constexpr float UNKNOWN_POSITION
Sentinel value meaning "position is not known yet".
DeviceType
Device type identifiers reported by IO‑Homecontrol products.
@ UNKNOWN
Unknown/unspecified device.
DeviceCapabilityClass device_capability_class(DeviceType type)
Map a raw IO‑Homecontrol type to the closest ESPHome/Home Assistant entity family.
bool device_supports_position_control(DeviceType type)
Does this device type support precise position control (0–100)?
const char * command_name(uint8_t cmd)
Get a human-readable name for any IO-Homecontrol command ID.
int16_t rssi_scaled_to_dbm(int16_t scaled)
Convert an rssi_ema_scaled fixed-point value to whole dBm (round half away from zero).
const char * device_type_name(DeviceType type)
Convert a DeviceType to a lowercase string identifier.
bool device_supports_binary_control(DeviceType type)
Does this device type support binary on/off control?
std::string format_position(float pos)
Format a position float as a human‑readable string (e.g.
bool device_supports_lock_control(DeviceType type)
Does this device type support binary lock/unlock control via execute commands?
const char * command_result_description(uint8_t result)
Return a human-readable explanation for a CMD_ERROR_RESP result code.
void render_frame_hex_redacted(const uint8_t *data, uint8_t len, char *out, size_t out_size)
Render a frame's bytes as spaced hex text, masking the payload when the command carries key material ...
const char * command_result_name(uint8_t result)
Return a stable symbolic name for a CMD_ERROR_RESP result code.
constexpr size_t FRAME_LOG_HEX_BUFFER_SIZE
Fits a full 32-byte frame rendered as spaced hex text.
static constexpr uint8_t BINARY_ENTITY_ON_POSITION
Position value written for binary ON commands (light on, switch on, lock unlock).
const char * device_capability_class_name(DeviceType type)
Get a human‑readable name for a capability class.
const char * acei_level_name(uint8_t level)
Get a human-readable name for an ACEI priority level (0–7).
std::string node_id_to_string(const uint8_t id[NODE_ID_SIZE])
Format a 3‑byte node ID as a 6‑character uppercase hex string.
static constexpr int16_t RSSI_EMA_SCALE
EMA weight denominator and fixed-point scale for IoDevice::rssi_ema_scaled.
@ BROADCAST_ALL
Broadcast to all devices of a type (address suffix 0x3F).
const char * address_class_name(AddressClass address_class)
Get a human-readable name for an address classification.
bool device_supports_status_requests(DeviceType type)
Does this device type support status request commands (0x03)?
bool has_reached_target_position(float target, float position)
Has the device reached its target within tolerance?
const char * yaml_device_type_name(DeviceType type)
Return the YAML-friendly device-type name for types exposed in the Python schema.
DeviceCapabilityClass
High‑level capability class derived from DeviceType.
@ LIGHT
Binary on/off light.
static constexpr int16_t RSSI_UNKNOWN_DBM
Sentinel value meaning "no RSSI sample recorded yet" for last_rssi_dbm/rssi_ema_scaled.
bool is_limitation_result(uint8_t result)
Check whether a result code represents an environmental or control limitation.
const char * originator_name(uint8_t originator)
Get a human-readable name for a command originator byte.
static constexpr uint8_t BINARY_ENTITY_OFF_POSITION
Position value written for binary OFF commands (light off, switch off, lock lock).
bool device_supports_tilt(DeviceType type)
Does this device type support tilt (slat angle) control?
Runtime state of a paired IO‑Homecontrol device.
uint32_t last_result_at_ms
millis() timestamp of last_result_code, 0 when none recorded.
float target
Target position the device is moving toward.
uint16_t exchange_attempt_count
Cumulative attempts (ExchangeEngine::DebugInfo::tries, 1-based per exchange) across those timed-out e...
uint8_t last_result_code
Last CMD_ERROR_RESP result byte (0 = none recorded).
int16_t last_rssi_dbm
Most recent raw RSSI sample (dBm), or RSSI_UNKNOWN_DBM.
float position
Current position: 0=open, 100=closed, or UNKNOWN_POSITION.
uint32_t last_seen_ms
millis() of the last frame received from this device (any command), 0 = never.
int16_t rssi_ema_scaled
Smoothed RSSI as fixed point in 1/RSSI_EMA_SCALE dBm — read through device_rssi_ema_dbm(),...
DeviceType type
Device type (shutter, awning, etc.).
uint16_t exchange_timeout_count
Cumulative count of outbound exchanges to this device with no valid response (see detail::record_exch...
bool is_stopped
True if device is not moving.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
uint8_t src[NODE_ID_SIZE]
Source node ID (3 bytes).
uint8_t dst[NODE_ID_SIZE]
Destination node ID (3 bytes).
uint8_t data_len
Actual length of data.
Decoded representation of a 1W remote frame.
bool has_intent
True if originator/ACEI/intent fields were decoded.
uint8_t originator
Command originator byte (e.g., ORIGINATOR_USER_REMOTE).
DeviceType target_type
Target device class from broadcast address.
uint8_t acei_level
ACEI priority level (0–7).
uint8_t cmd
Command ID (e.g., CMD_EXECUTE, CMD_ACTIVATE_MODE).
AddressClass address_class
Classification of the broadcast address.
char intent[ONEWAY_INTENT_BUFFER_SIZE]
Human-readable command intent (e.g., "CLOSE").
uint8_t src[NODE_ID_SIZE]
Remote source node ID (3 bytes).
uint8_t data_len
Raw data length (for commands without decoded intent).
Diagnostic capture from a radio operation.
uint32_t timestamp_ms
Timestamp of capture (millis).
bool valid
True if capture is valid.
uint32_t freq_hz
RF frequency of capture (Hz).
int16_t rssi_dbm
Received signal strength (dBm).