Hello,
I’ll be grateful for assistance please.
I’ve created a custom cover for my blinds (shades) so that the slider is consistent with % closed (open is 0.0 0% and closed is 1.0 100%) and not % open as the cover component is currently (open is 1.0 100% and closed is 0.0 0%)
The custom cover works correctly when the stepper motor is anywhere in the 10% to 90% range (ie not at the endpoints) clicking the up arrow button decreases the % closed and clicking the down arrow button, % closed increases and the slider and ESPHome log follow correctly. However when the blinds are at the end points (0% and 100%) the incorrect indicator is shown (blinds open and closed icon) and the incorrect arrow button is enabled. At 0% the up arrow button is enabled and the stepper motor doesn’t move as it’s already at 0% and similarly at 100%.
How do I reverse the up/down buttons in the custom cover card as well as the open/closed indicator icon?
Here’s my test .yaml
substitutions:
devicename: roller_blind
upper_devicename: Roller Blind
mystepper: my_stepper # Name of the stepper motor (leave as is)
speed: 5000 steps/s # Set the speed of the motor
acc: inf #acceleration steps/s^2
dec: 2000 steps/s^2 #decceleration steps/s^2
dir: GPIO18 # Direction Pin
step: GPIO4 # Step Pin
sleep: GPIO23 # Sleep Pin
switch_pin: GPIO25 # Switch Pin
ssid: *********
password: *********
ap_ssid: Blind Single Fallback Hotspot
ap_password: KuLimqA7M2OQ
esphome:
name: blind_single
platform: ESP32
board: esp32dev
wifi:
ssid: ${ssid}
password: ${password}
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: ${ap_ssid}
password: ${ap_password}
web_server:
port: 80
stepper:
- platform: a4988
id: $mystepper
step_pin: $step
dir_pin: $dir
sleep_pin:
number: $sleep
inverted: True
max_speed: ${speed}
acceleration: ${acc}
deceleration: ${dec}
globals:
- id: ${mystepper}_global
type: int
restore_value: no
initial_value: '0'
- id: closed_position # Variable for storing end position
type: int
restore_value: no
initial_value: '10000'
- id: open_position # Variable for storing open position
type: int
restore_value: no
initial_value: '0'
sensor:
- platform: template
name: $upper_devicename Blind Position
id: blind_position
lambda: !lambda 'return (float(id($mystepper).current_position) /(id(closed_position) - (id(open_position))));'
internal: true
update_interval: 100ms
filters:
- or:
- throttle: 5s
- delta: 5.0
on_value:
then:
- cover.template.publish:
id: blind
state: !lambda 'return x;'
cover:
- platform: template
device_class: shade
name: $upper_devicename
id: blind
has_position: true
open_action:
- stepper.set_target:
id: $mystepper
target: !lambda 'return id(open_position);'
close_action:
- stepper.set_target:
id: $mystepper
target: !lambda 'return id(closed_position);'
stop_action:
- stepper.set_target:
id: $mystepper
target: !lambda 'return id($mystepper).current_position;'
position_action:
- stepper.set_target:
id: $mystepper
target: !lambda 'return (1.0f-pos) * id(closed_position);'
captive_portal:
# Enable logging
logger:
# Enable Home Assistant API
api:
ota:
and custom cover, the 3 lines which I have changed are annotated with // original:…
#include "cover.h"
#include "esphome/core/log.h"
namespace esphome {
namespace cover {
static const char *TAG = "cover";
const float COVER_OPEN = 0.0f; // original: 1.0f
const float COVER_CLOSED = 1.0f; // original: 0.0f
const char *cover_command_to_str(float pos) {
if (pos == COVER_OPEN) {
return "OPEN";
} else if (pos == COVER_CLOSED) {
return "CLOSED";
} else {
return "UNKNOWN";
}
}
const char *cover_operation_to_str(CoverOperation op) {
switch (op) {
case COVER_OPERATION_IDLE:
return "IDLE";
case COVER_OPERATION_OPENING:
return "OPENING";
case COVER_OPERATION_CLOSING:
return "CLOSING";
default:
return "UNKNOWN";
}
}
Cover::Cover(const std::string &name) : Nameable(name), position{COVER_OPEN} {}
uint32_t Cover::hash_base() { return 1727367479UL; }
CoverCall::CoverCall(Cover *parent) : parent_(parent) {}
CoverCall &CoverCall::set_command(const char *command) {
if (strcasecmp(command, "OPEN") == 0) {
this->set_command_open();
} else if (strcasecmp(command, "CLOSE") == 0) {
this->set_command_close();
} else if (strcasecmp(command, "STOP") == 0) {
this->set_command_stop();
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command);
}
return *this;
}
CoverCall &CoverCall::set_command_open() {
this->position_ = COVER_OPEN;
return *this;
}
CoverCall &CoverCall::set_command_close() {
this->position_ = COVER_CLOSED;
return *this;
}
CoverCall &CoverCall::set_command_stop() {
this->stop_ = true;
return *this;
}
CoverCall &CoverCall::set_position(float position) {
this->position_ = 1.0f - position; //original: this->position_ = position;
return *this;
}
CoverCall &CoverCall::set_tilt(float tilt) {
this->tilt_ = tilt;
return *this;
}
void CoverCall::perform() {
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
auto traits = this->parent_->get_traits();
this->validate_();
if (this->stop_) {
ESP_LOGD(TAG, " Command: STOP");
}
if (this->position_.has_value()) {
if (traits.get_supports_position()) {
ESP_LOGD(TAG, " Position: %.0f%%", *this->position_ * 100.0f);
} else {
ESP_LOGD(TAG, " Command: %s", cover_command_to_str(*this->position_));
}
}
if (this->tilt_.has_value()) {
ESP_LOGD(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f);
}
this->parent_->control(*this);
}
const optional<float> &CoverCall::get_position() const { return this->position_; }
const optional<float> &CoverCall::get_tilt() const { return this->tilt_; }
void CoverCall::validate_() {
auto traits = this->parent_->get_traits();
if (this->position_.has_value()) {
auto pos = *this->position_;
if (!traits.get_supports_position() && pos != COVER_OPEN && pos != COVER_CLOSED) {
ESP_LOGW(TAG, "'%s' - This cover device does not support setting position!", this->parent_->get_name().c_str());
this->position_.reset();
} else if (pos < 0.0f || pos > 1.0f) {
ESP_LOGW(TAG, "'%s' - Position %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), pos);
this->position_ = clamp(pos, 0.0f, 1.0f);
}
}
if (this->tilt_.has_value()) {
auto tilt = *this->tilt_;
if (!traits.get_supports_tilt()) {
ESP_LOGW(TAG, "'%s' - This cover device does not support tilt!", this->parent_->get_name().c_str());
this->tilt_.reset();
} else if (tilt < 0.0f || tilt > 1.0f) {
ESP_LOGW(TAG, "'%s' - Tilt %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), tilt);
this->tilt_ = clamp(tilt, 0.0f, 1.0f);
}
}
if (this->stop_) {
if (this->position_.has_value()) {
ESP_LOGW(TAG, "Cannot set position when stopping a cover!");
this->position_.reset();
}
if (this->tilt_.has_value()) {
ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!");
this->tilt_.reset();
}
}
}
CoverCall &CoverCall::set_stop(bool stop) {
this->stop_ = stop;
return *this;
}
bool CoverCall::get_stop() const { return this->stop_; }
void Cover::set_device_class(const std::string &device_class) { this->device_class_override_ = device_class; }
CoverCall Cover::make_call() { return {this}; }
void Cover::open() {
auto call = this->make_call();
call.set_command_open();
call.perform();
}
void Cover::close() {
auto call = this->make_call();
call.set_command_close();
call.perform();
}
void Cover::stop() {
auto call = this->make_call();
call.set_command_stop();
call.perform();
}
void Cover::add_on_state_callback(std::function<void()> &&f) { this->state_callback_.add(std::move(f)); }
void Cover::publish_state(bool save) {
this->position = clamp(this->position, 0.0f, 1.0f);
this->tilt = clamp(this->tilt, 0.0f, 1.0f);
ESP_LOGD(TAG, "'%s' - Publishing:", this->name_.c_str());
auto traits = this->get_traits();
if (traits.get_supports_position()) {
ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f);
} else {
if (this->position == COVER_OPEN) {
ESP_LOGD(TAG, " State: OPEN");
} else if (this->position == COVER_CLOSED) {
ESP_LOGD(TAG, " State: CLOSED");
} else {
ESP_LOGD(TAG, " State: UNKNOWN");
}
}
if (traits.get_supports_tilt()) {
ESP_LOGD(TAG, " Tilt: %.0f%%", this->tilt * 100.0f);
}
ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation));
this->state_callback_.call();
if (save) {
CoverRestoreState restore{};
memset(&restore, 0, sizeof(restore));
restore.position = this->position;
if (traits.get_supports_tilt()) {
restore.tilt = this->tilt;
}
this->rtc_.save(&restore);
}
}
optional<CoverRestoreState> Cover::restore_state_() {
this->rtc_ = global_preferences.make_preference<CoverRestoreState>(this->get_object_id_hash());
CoverRestoreState recovered{};
if (!this->rtc_.load(&recovered))
return {};
return recovered;
}
Cover::Cover() : Cover("") {}
std::string Cover::get_device_class() {
if (this->device_class_override_.has_value())
return *this->device_class_override_;
return this->device_class();
}
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }
std::string Cover::device_class() { return ""; }
CoverCall CoverRestoreState::to_call(Cover *cover) {
auto call = cover->make_call();
auto traits = cover->get_traits();
call.set_position(this->position);
if (traits.get_supports_tilt())
call.set_tilt(this->tilt);
return call;
}
void CoverRestoreState::apply(Cover *cover) {
cover->position = this->position;
cover->tilt = this->tilt;
cover->publish_state();
}
} // namespace cover
} // namespace esphome
I’ll be grateful for any help or suggestions please.
Thanks in advance