1
0
mirror of https://github.com/esphome/esphome.git synced 2025-03-19 00:58:13 +00:00

264 lines
8.5 KiB
C++

#include "he60r.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace he60r {
static const char *const TAG = "he60r.cover";
static const uint8_t QUERY_BYTE = 0x38;
static const uint8_t TOGGLE_BYTE = 0x30;
using namespace esphome::cover;
void HE60rCover::setup() {
auto restore = this->restore_state_();
if (restore.has_value()) {
restore->apply(this);
this->publish_state(false);
} else {
// if no other information, assume half open
this->position = 0.5f;
}
this->current_operation = COVER_OPERATION_IDLE;
this->last_recompute_time_ = this->start_dir_time_ = millis();
this->set_interval(300, [this]() { this->update_(); });
}
CoverTraits HE60rCover::get_traits() {
auto traits = CoverTraits();
traits.set_supports_stop(true);
traits.set_supports_position(true);
traits.set_supports_toggle(true);
traits.set_is_assumed_state(false);
return traits;
}
void HE60rCover::dump_config() {
LOG_COVER("", "HE60R Cover", this);
this->check_uart_settings(1200, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f);
ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f);
auto restore = this->restore_state_();
if (restore.has_value())
ESP_LOGCONFIG(TAG, " Saved position %d%%", (int) (restore->position * 100.f));
}
void HE60rCover::endstop_reached_(CoverOperation operation) {
const uint32_t now = millis();
this->set_current_operation_(COVER_OPERATION_IDLE);
auto new_position = operation == COVER_OPERATION_OPENING ? COVER_OPEN : COVER_CLOSED;
if (new_position != this->position || this->current_operation != COVER_OPERATION_IDLE) {
this->position = new_position;
this->current_operation = COVER_OPERATION_IDLE;
if (this->last_command_ == operation) {
float dur = (float) (now - this->start_dir_time_) / 1e3f;
ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(),
operation == COVER_OPERATION_OPENING ? "Open" : "Close", dur);
}
this->publish_state();
}
}
void HE60rCover::set_current_operation_(cover::CoverOperation operation) {
if (this->current_operation != operation) {
this->current_operation = operation;
if (operation != COVER_OPERATION_IDLE)
this->last_recompute_time_ = millis();
}
}
void HE60rCover::process_rx_(uint8_t data) {
ESP_LOGV(TAG, "Process RX data %X", data);
if (!this->query_seen_) {
this->query_seen_ = data == QUERY_BYTE;
if (!this->query_seen_)
ESP_LOGD(TAG, "RX Byte %02X", data);
return;
}
switch (data) {
case 0xB5: // at closed endstop, jammed?
case 0xF5: // at closed endstop, jammed?
case 0x55: // at closed endstop
this->next_direction_ = COVER_OPERATION_OPENING;
this->endstop_reached_(COVER_OPERATION_CLOSING);
break;
case 0x52: // at opened endstop
this->next_direction_ = COVER_OPERATION_CLOSING;
this->endstop_reached_(COVER_OPERATION_OPENING);
break;
case 0x51: // travelling up after encountering obstacle
case 0x01: // travelling up
case 0x11: // travelling up, triggered by remote
this->set_current_operation_(COVER_OPERATION_OPENING);
this->next_direction_ = COVER_OPERATION_IDLE;
break;
case 0x44: // travelling down
case 0x14: // travelling down, triggered by remote
this->next_direction_ = COVER_OPERATION_IDLE;
this->set_current_operation_(COVER_OPERATION_CLOSING);
break;
case 0x86: // Stopped, jammed?
case 0x16: // stopped midway while opening, by remote
case 0x06: // stopped midway while opening
this->next_direction_ = COVER_OPERATION_CLOSING;
this->set_current_operation_(COVER_OPERATION_IDLE);
break;
case 0x10: // stopped midway while closing, by remote
case 0x00: // stopped midway while closing
this->next_direction_ = COVER_OPERATION_OPENING;
this->set_current_operation_(COVER_OPERATION_IDLE);
break;
default:
break;
}
}
void HE60rCover::update_() {
if (this->toggles_needed_ != 0) {
if ((this->counter_++ & 0x3) == 0) {
this->toggles_needed_--;
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%u", this->toggles_needed_);
this->write_byte(TOGGLE_BYTE);
} else {
this->write_byte(QUERY_BYTE);
}
} else {
this->write_byte(QUERY_BYTE);
this->counter_ = 0;
}
if (this->current_operation != COVER_OPERATION_IDLE) {
this->recompute_position_();
// if we initiated the move, check if we reached the target position
if (this->last_command_ != COVER_OPERATION_IDLE) {
if (this->is_at_target_()) {
this->start_direction_(COVER_OPERATION_IDLE);
}
}
}
}
void HE60rCover::loop() {
uint8_t data;
while (this->available() > 0) {
if (this->read_byte(&data)) {
this->process_rx_(data);
}
}
}
void HE60rCover::control(const CoverCall &call) {
if (call.get_stop()) {
this->start_direction_(COVER_OPERATION_IDLE);
} else if (call.get_toggle().has_value()) {
// toggle action logic: OPEN - STOP - CLOSE
if (this->last_command_ != COVER_OPERATION_IDLE) {
this->start_direction_(COVER_OPERATION_IDLE);
} else {
this->toggles_needed_++;
}
} else if (call.get_position().has_value()) {
// go to position action
auto pos = *call.get_position();
// are we at the target?
if (pos == this->position) {
this->start_direction_(COVER_OPERATION_IDLE);
} else {
this->target_position_ = pos;
this->start_direction_(pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING);
}
}
}
/**
* Check if the cover has reached or passed the target position. This is used only
* for partial open/close requests - endstops are used for full open/close.
* @return True if the cover has reached or passed its target position. For full open/close target always return false.
*/
bool HE60rCover::is_at_target_() const {
// equality of floats is fraught with peril - this is reliable since the values are 0.0 or 1.0 which are
// exactly representable.
if (this->target_position_ == COVER_OPEN || this->target_position_ == COVER_CLOSED)
return false;
// aiming for an intermediate position - exact comparison here will not work and we need to allow for overshoot
switch (this->last_command_) {
case COVER_OPERATION_OPENING:
return this->position >= this->target_position_;
case COVER_OPERATION_CLOSING:
return this->position <= this->target_position_;
case COVER_OPERATION_IDLE:
return this->current_operation == COVER_OPERATION_IDLE;
default:
return true;
}
}
void HE60rCover::start_direction_(CoverOperation dir) {
this->last_command_ = dir;
if (this->current_operation == dir)
return;
ESP_LOGD(TAG, "'%s' - Direction '%s' requested.", this->name_.c_str(),
dir == COVER_OPERATION_OPENING ? "OPEN"
: dir == COVER_OPERATION_CLOSING ? "CLOSE"
: "STOP");
if (dir == this->next_direction_) {
// either moving and needs to stop, or stopped and will move correctly on one trigger
this->toggles_needed_ = 1;
} else {
if (this->current_operation == COVER_OPERATION_IDLE) {
// if stopped, but will go the wrong way, need 3 triggers.
this->toggles_needed_ = 3;
} else {
// just stop and reverse
this->toggles_needed_ = 2;
}
ESP_LOGD(TAG, "'%s' - Reversing direction.", this->name_.c_str());
}
this->start_dir_time_ = millis();
}
void HE60rCover::recompute_position_() {
if (this->current_operation == COVER_OPERATION_IDLE)
return;
const uint32_t now = millis();
if (now > this->last_recompute_time_) {
auto diff = (unsigned) (now - last_recompute_time_);
float delta;
switch (this->current_operation) {
case COVER_OPERATION_OPENING:
delta = (float) diff / (float) this->open_duration_;
break;
case COVER_OPERATION_CLOSING:
delta = -(float) diff / (float) this->close_duration_;
break;
default:
return;
}
// make sure our guesstimate never reaches full open or close.
auto new_position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
ESP_LOGD(TAG, "Recompute %ums, dir=%u, delta=%f, pos=%f", diff, this->current_operation, delta, new_position);
this->last_recompute_time_ = now;
if (this->position != new_position) {
this->position = new_position;
this->publish_state();
}
}
}
} // namespace he60r
} // namespace esphome