Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
platform_cover.cpp
Go to the documentation of this file.
1/// @file platform_cover.cpp
2/// @brief ESPHome cover entity for IO-Homecontrol devices.
3/// @ingroup hioc_platforms
4
5#include "platform_cover.h"
6#include "hub_internal.h"
7#include "esphome/core/log.h"
8
9namespace esphome {
10namespace home_io_control {
11
12static const char *const TAG = "home_io_control.cover";
13
15 // Register this device with the controller so it's included in the device map
16 const bool initial_invert = this->invert_explicit_ ? this->invert_ : default_inverted_for_type(this->device_type_);
17 this->parent_->add_device(this->device_id_, device_type_, subtype_, initial_invert);
18 this->parent_->set_device_status_poll_interval(this->device_id_, this->status_poll_interval_ms_);
19
20 // Subscribe to status updates from the controller
21 this->parent_->register_device_callback(
22 [this](const std::string &id, const IoDevice &dev) { this->on_device_update_(id, dev); });
23
24 // Request initial status after a short delay (give radio time to start hopping)
25 this->set_timeout("init_status", detail::INITIAL_STATUS_REQUEST_DELAY_MS,
26 [this]() { this->parent_->queue_request_device_status(this->device_id_); });
27}
28
29cover::CoverTraits IOHomeCover::get_traits() {
30 auto traits = cover::CoverTraits();
31 traits.set_supports_position(true); // Slider in HA UI
32 traits.set_supports_stop(true); // Stop button in HA UI
33 traits.set_supports_tilt(this->supports_tilt());
34 traits.set_is_assumed_state(false); // We hopefully get real feedback from the device
35 return traits;
36}
37
39 if (this->parent_ == nullptr)
40 return false;
41 const auto *dev = this->parent_->get_device(this->device_id_);
42 return dev != nullptr && device_supports_tilt(dev->type);
43}
44
46 if (this->invert_explicit_)
47 return this->invert_;
48 if (this->parent_ == nullptr)
49 return false;
50 const auto *dev = this->parent_->get_device(this->device_id_);
51 return dev != nullptr && dev->inverted;
52}
53
54cover::CoverOperation IOHomeCover::infer_operation_from_position_delta_(bool invert, float current_io_position) const {
55 if (this->last_io_position_ == UNKNOWN_POSITION || current_io_position == UNKNOWN_POSITION ||
56 current_io_position == this->last_io_position_) {
57 return cover::COVER_OPERATION_IDLE;
58 }
59
60 if (invert) {
61 return (current_io_position > this->last_io_position_) ? cover::COVER_OPERATION_OPENING
62 : cover::COVER_OPERATION_CLOSING;
63 }
64
65 return (current_io_position < this->last_io_position_) ? cover::COVER_OPERATION_OPENING
66 : cover::COVER_OPERATION_CLOSING;
67}
68
69void IOHomeCover::control(const cover::CoverCall &call) {
70 if (call.get_stop()) {
71 this->parent_->queue_set_device_position(this->device_id_, POS_STOP);
72 return;
73 }
74
75 const auto &tilt_opt = call.get_tilt();
76 if (tilt_opt.has_value()) {
77 auto const tilt = static_cast<uint8_t>(*tilt_opt * 100.0F);
78 this->parent_->queue_set_device_tilt(this->device_id_, tilt);
79 return;
80 }
81
82 const auto &position_opt = call.get_position();
83 if (position_opt.has_value()) {
84 float const ha_pos = *position_opt; // HA: 1.0 = fully open, 0.0 = fully closed
85 const bool invert = this->effective_invert_();
86
87 // Convert HA position (0.0-1.0) to IO position (0-100)
88 // Standard: HA 1.0 (open) → IO 0 (open), HA 0.0 (closed) → IO 100 (closed)
89 // Inverted: HA 1.0 (open) → IO 100, HA 0.0 (closed) → IO 0
90 // (used for devices like horizontal awnings where the IO convention is reversed)
91 uint8_t io_pos;
92 if (invert) {
93 io_pos = (uint8_t) (ha_pos * 100.0F);
94 } else {
95 io_pos = (uint8_t) ((1.0F - ha_pos) * 100.0F);
96 }
97
98 this->parent_->queue_set_device_position(this->device_id_, io_pos);
99 }
100}
101
102void IOHomeCover::on_device_update_(const std::string &id, const IoDevice &dev) {
103 if (id != this->device_id_)
104 return;
105
106 const bool invert = this->effective_invert_();
107 const float previous_io_position = this->last_io_position_;
108
109 if (dev.position != UNKNOWN_POSITION) {
110 // Convert IO position (0-100) back to HA position (0.0-1.0)
111 float ha_pos;
112 if (invert) {
113 ha_pos = dev.position / 100.0F;
114 } else {
115 ha_pos = 1.0F - (dev.position / 100.0F);
116 }
117
118 this->position = ha_pos;
119 }
120
121 if (this->supports_tilt() && dev.tilt != UNKNOWN_POSITION) {
122 this->tilt = dev.tilt / 100.0F;
123 }
124
125 // Determine movement direction for the HA UI animation
126 if (dev.is_stopped) {
127 this->current_operation = cover::COVER_OPERATION_IDLE;
128 } else if (dev.target != UNKNOWN_POSITION && dev.position != UNKNOWN_POSITION) {
129 // Figure out if we're opening or closing based on target vs current position
130 if (invert) {
131 this->current_operation =
132 (dev.target > dev.position) ? cover::COVER_OPERATION_OPENING : cover::COVER_OPERATION_CLOSING;
133 } else {
134 // Standard: lower IO position = more open, so target < current = opening
135 this->current_operation =
136 (dev.target < dev.position) ? cover::COVER_OPERATION_OPENING : cover::COVER_OPERATION_CLOSING;
137 }
138 } else if (dev.position != UNKNOWN_POSITION) {
139 this->current_operation = this->infer_operation_from_position_delta_(invert, dev.position);
140 }
141
142 if (dev.position != UNKNOWN_POSITION) {
143 this->last_io_position_ = dev.position;
144 } else {
145 this->last_io_position_ = previous_io_position;
146 }
147
148 this->publish_state();
149}
150
152 LOG_COVER("", "IO-Homecontrol Cover", this);
153 ESP_LOGCONFIG(TAG, " Device ID: %s", this->device_id_.c_str());
154 ESP_LOGCONFIG(TAG, " Invert Position Override: %s", this->invert_explicit_ ? YESNO(this->invert_) : "AUTO");
155 if (this->status_poll_interval_ms_ == 0) {
156 ESP_LOGCONFIG(TAG, " Status Poll Interval: default single settle poll");
157 } else {
158 ESP_LOGCONFIG(TAG, " Status Poll Interval: %u ms", this->status_poll_interval_ms_);
159 }
160 ESP_LOGCONFIG(TAG, " Supports Tilt: %s", YESNO(this->supports_tilt()));
161}
162
163} // namespace home_io_control
164} // namespace esphome
cover::CoverOperation infer_operation_from_position_delta_(bool invert, float current_io_position) const
Infer HA movement direction from successive IO positions when the protocol target is unknown.
bool supports_tilt() const
Query whether this device supports tilt (slat angle) control.
void on_device_update_(const std::string &id, const IoDevice &dev)
Callback invoked when the underlying device state changes.
bool effective_invert_() const
Resolve the current inversion mode.
cover::CoverTraits get_traits() override
Return the traits object describing this cover's capabilities.
void setup() override
Initialize the cover entity (register device, subscribe to updates, schedule initial status poll).
void dump_config() override
Dump configuration to log.
void control(const cover::CoverCall &call) override
Handle cover commands from Home Assistant (open/close/stop/set_position).
Internal helpers shared by the hub implementation .cpp files.
constexpr uint32_t INITIAL_STATUS_REQUEST_DELAY_MS
Delay before the first post-boot status request from an entity.
static constexpr float UNKNOWN_POSITION
Sentinel value meaning "position is not known yet".
bool default_inverted_for_type(DeviceType type)
Determine whether a device type has inverted position mapping by default.
static constexpr uint8_t POS_STOP
Position values in the IO protocol.
bool device_supports_tilt(DeviceType type)
Does this device type support tilt (slat angle) control?
static const char *const TAG
Definition hub_core.cpp:35
ESPHome cover entity for IO‑Homecontrol devices.
Runtime state of a paired IO‑Homecontrol device.
float target
Target position the device is moving toward.
float tilt
Current tilt: 0=closed, 100=open, or UNKNOWN_POSITION.
float position
Current position: 0=open, 100=closed, or UNKNOWN_POSITION.
bool is_stopped
True if device is not moving.