diff --git a/esphome/components/fyrtur_motor/__init__.py b/esphome/components/fyrtur_motor/__init__.py index 631d3f3af9..c94ef0f28e 100644 --- a/esphome/components/fyrtur_motor/__init__.py +++ b/esphome/components/fyrtur_motor/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import uart +from esphome.components import uart, cover from esphome.const import CONF_ID DEPENDENCIES = ["uart"] @@ -13,25 +13,29 @@ CONF_FYRTUR_MOTOR_ID = "fyrtur_motor_id" fyrtur_motor_ns = cg.esphome_ns.namespace("fyrtur_motor") FyrturMotorComponent = fyrtur_motor_ns.class_( - "FyrturMotorComponent", cg.PollingComponent, uart.UARTDevice + "FyrturMotorComponent", + cg.PollingComponent, + uart.UARTDevice, + cover.Cover, ) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(FyrturMotorComponent), - } -) - -CONFIG_SCHEMA = cv.All( - CONFIG_SCHEMA.extend(uart.UART_DEVICE_SCHEMA).extend( - cv.polling_component_schema("1s") +CONFIG_SCHEMA = ( + cover.COVER_SCHEMA.extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("1s")) + .extend( + { + cv.GenerateID(): cv.declare_id(FyrturMotorComponent), + } ) ) FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( "fyrtur_motor", + baud_rate=2400, require_tx=True, require_rx=True, + data_bits=8, parity="NONE", stop_bits=1, ) @@ -41,3 +45,4 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) + await cover.register_cover(var, config) diff --git a/esphome/components/fyrtur_motor/cover/__init__.py b/esphome/components/fyrtur_motor/cover/__init__.py new file mode 100644 index 0000000000..09e4823e12 --- /dev/null +++ b/esphome/components/fyrtur_motor/cover/__init__.py @@ -0,0 +1,76 @@ +# from esphome.components import cover +# import esphome.config_validation as cv +# import esphome.codegen as cg +# from esphome.const import ( +# CONF_OUTPUT_ID, +# CONF_MIN_VALUE, +# CONF_MAX_VALUE, +# CONF_RESTORE_MODE, +# ) +# from .. import tuya_ns, CONF_TUYA_ID, Tuya + +# DEPENDENCIES = ["tuya"] + +# CONF_CONTROL_DATAPOINT = "control_datapoint" +# CONF_DIRECTION_DATAPOINT = "direction_datapoint" +# CONF_POSITION_DATAPOINT = "position_datapoint" +# CONF_POSITION_REPORT_DATAPOINT = "position_report_datapoint" +# CONF_INVERT_POSITION = "invert_position" + +# TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) + +# TuyaCoverRestoreMode = tuya_ns.enum("TuyaCoverRestoreMode") +# RESTORE_MODES = { +# "NO_RESTORE": TuyaCoverRestoreMode.COVER_NO_RESTORE, +# "RESTORE": TuyaCoverRestoreMode.COVER_RESTORE, +# "RESTORE_AND_CALL": TuyaCoverRestoreMode.COVER_RESTORE_AND_CALL, +# } + + +# def validate_range(config): +# if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: +# raise cv.Invalid( +# f"min_value ({config[CONF_MIN_VALUE]}) cannot be greater than max_value ({config[CONF_MAX_VALUE]})" +# ) +# return config + + +# CONFIG_SCHEMA = cv.All( +# cover.COVER_SCHEMA.extend( +# { +# cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaCover), +# cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), +# cv.Optional(CONF_CONTROL_DATAPOINT): cv.uint8_t, +# cv.Optional(CONF_DIRECTION_DATAPOINT): cv.uint8_t, +# cv.Required(CONF_POSITION_DATAPOINT): cv.uint8_t, +# cv.Optional(CONF_POSITION_REPORT_DATAPOINT): cv.uint8_t, +# cv.Optional(CONF_MIN_VALUE, default=0): cv.int_, +# cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, +# cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, +# cv.Optional(CONF_RESTORE_MODE, default="RESTORE"): cv.enum( +# RESTORE_MODES, upper=True +# ), +# }, +# ).extend(cv.COMPONENT_SCHEMA), +# validate_range, +# ) + + +# async def to_code(config): +# var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) +# await cg.register_component(var, config) +# await cover.register_cover(var, config) + +# if CONF_CONTROL_DATAPOINT in config: +# cg.add(var.set_control_id(config[CONF_CONTROL_DATAPOINT])) +# if CONF_DIRECTION_DATAPOINT in config: +# cg.add(var.set_direction_id(config[CONF_DIRECTION_DATAPOINT])) +# cg.add(var.set_position_id(config[CONF_POSITION_DATAPOINT])) +# if CONF_POSITION_REPORT_DATAPOINT in config: +# cg.add(var.set_position_report_id(config[CONF_POSITION_REPORT_DATAPOINT])) +# cg.add(var.set_min_value(config[CONF_MIN_VALUE])) +# cg.add(var.set_max_value(config[CONF_MAX_VALUE])) +# cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) +# cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) +# paren = await cg.get_variable(config[CONF_TUYA_ID]) +# cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/fyrtur_motor/cover/tuya_cover.cpp b/esphome/components/fyrtur_motor/cover/tuya_cover.cpp new file mode 100644 index 0000000000..fcb961f45e --- /dev/null +++ b/esphome/components/fyrtur_motor/cover/tuya_cover.cpp @@ -0,0 +1,137 @@ +#include "esphome/core/log.h" +#include "tuya_cover.h" + +namespace esphome { +namespace tuya { + +const uint8_t COMMAND_OPEN = 0x00; +const uint8_t COMMAND_CLOSE = 0x02; +const uint8_t COMMAND_STOP = 0x01; + +using namespace esphome::cover; + +static const char *const TAG = "tuya.cover"; + +void TuyaCover::setup() { + this->value_range_ = this->max_value_ - this->min_value_; + + this->parent_->add_on_initialized_callback([this]() { + // Set the direction (if configured/supported). + this->set_direction_(this->invert_position_); + + // Handle configured restore mode. + switch (this->restore_mode_) { + case COVER_NO_RESTORE: + break; + case COVER_RESTORE: { + auto restore = this->restore_state_(); + if (restore.has_value()) + restore->apply(this); + break; + } + case COVER_RESTORE_AND_CALL: { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->to_call(this).perform(); + } + break; + } + } + }); + + uint8_t report_id = *this->position_id_; + if (this->position_report_id_.has_value()) { + // A position report datapoint is configured; listen to that instead. + report_id = *this->position_report_id_; + } + + this->parent_->register_listener(report_id, [this](const TuyaDatapoint &datapoint) { + if (datapoint.value_int == 123) { + ESP_LOGD(TAG, "Ignoring MCU position report - not calibrated"); + return; + } + auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; + this->position = 1.0f - pos; + this->publish_state(); + }); +} + +void TuyaCover::control(const cover::CoverCall &call) { + if (call.get_stop()) { + if (this->control_id_.has_value()) { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_STOP); + } else { + auto pos = this->position; + pos = 1.0f - pos; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; + + parent_->force_set_integer_datapoint_value(*this->position_id_, position_int); + } + } + if (call.get_position().has_value()) { + auto pos = *call.get_position(); + if (this->control_id_.has_value() && (pos == COVER_OPEN || pos == COVER_CLOSED)) { + if (pos == COVER_OPEN) { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_OPEN); + } else { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_CLOSE); + } + } else { + pos = 1.0f - pos; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; + + parent_->force_set_integer_datapoint_value(*this->position_id_, position_int); + } + } + + this->publish_state(); +} + +void TuyaCover::set_direction_(bool inverted) { + if (!this->direction_id_.has_value()) { + return; + } + + if (inverted) { + ESP_LOGD(TAG, "Setting direction: inverted"); + } else { + ESP_LOGD(TAG, "Setting direction: normal"); + } + + this->parent_->set_boolean_datapoint_value(*this->direction_id_, inverted); +} + +void TuyaCover::dump_config() { + ESP_LOGCONFIG(TAG, "Tuya Cover:"); + if (this->invert_position_) { + if (this->direction_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Inverted"); + } else { + ESP_LOGCONFIG(TAG, " Configured as Inverted, but direction_datapoint isn't configured"); + } + } + if (this->control_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Control has datapoint ID %u", *this->control_id_); + } + if (this->direction_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Direction has datapoint ID %u", *this->direction_id_); + } + if (this->position_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Position has datapoint ID %u", *this->position_id_); + } + if (this->position_report_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Position Report has datapoint ID %u", *this->position_report_id_); + } +} + +cover::CoverTraits TuyaCover::get_traits() { + auto traits = cover::CoverTraits(); + traits.set_supports_stop(true); + traits.set_supports_position(true); + return traits; +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/fyrtur_motor/cover/tuya_cover.h b/esphome/components/fyrtur_motor/cover/tuya_cover.h new file mode 100644 index 0000000000..87c72b0e66 --- /dev/null +++ b/esphome/components/fyrtur_motor/cover/tuya_cover.h @@ -0,0 +1,48 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/cover/cover.h" + +namespace esphome { +namespace tuya { + +enum TuyaCoverRestoreMode { + COVER_NO_RESTORE, + COVER_RESTORE, + COVER_RESTORE_AND_CALL, +}; + +class TuyaCover : public cover::Cover, public Component { + public: + void setup() override; + void dump_config() override; + void set_control_id(uint8_t control_id) { this->control_id_ = control_id; } + void set_direction_id(uint8_t direction_id) { this->direction_id_ = direction_id; } + void set_position_id(uint8_t position_id) { this->position_id_ = position_id; } + void set_position_report_id(uint8_t position_report_id) { this->position_report_id_ = position_report_id; } + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + void set_min_value(uint32_t min_value) { min_value_ = min_value; } + void set_max_value(uint32_t max_value) { max_value_ = max_value; } + void set_invert_position(bool invert_position) { invert_position_ = invert_position; } + void set_restore_mode(TuyaCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } + + protected: + void control(const cover::CoverCall &call) override; + void set_direction_(bool inverted); + cover::CoverTraits get_traits() override; + + Tuya *parent_; + TuyaCoverRestoreMode restore_mode_{}; + optional control_id_{}; + optional direction_id_{}; + optional position_id_{}; + optional position_report_id_{}; + uint32_t min_value_; + uint32_t max_value_; + uint32_t value_range_; + bool invert_position_; +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/fyrtur_motor/fyrtur_motor.cpp b/esphome/components/fyrtur_motor/fyrtur_motor.cpp index ed96e8717f..f3cf085c7b 100644 --- a/esphome/components/fyrtur_motor/fyrtur_motor.cpp +++ b/esphome/components/fyrtur_motor/fyrtur_motor.cpp @@ -23,9 +23,13 @@ void FyrturMotorComponent::setup() { get_status(); } void FyrturMotorComponent::update() { get_status(); } -// void FyrturMotorComponent::loop(void) { -// // probably not needed -// } +void FyrturMotorComponent::loop(void) { + // while (this->available() > 0) { + // if (this->read_byte(&data)) { + // this->process_rx_(data); + // } + // } +} float FyrturMotorComponent::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/fyrtur_motor/fyrtur_motor.h b/esphome/components/fyrtur_motor/fyrtur_motor.h index 03f6127006..26052555bf 100644 --- a/esphome/components/fyrtur_motor/fyrtur_motor.h +++ b/esphome/components/fyrtur_motor/fyrtur_motor.h @@ -22,6 +22,7 @@ #ifdef USE_TEXT_SENSOR #include "esphome/components/text_sensor/text_sensor.h" #endif +#include "esphome/components/cover/cover.h" #include "esphome/components/uart/uart.h" #include "esphome/core/automation.h" #include "esphome/core/helpers.h" @@ -33,7 +34,7 @@ namespace fyrtur_motor { typedef enum RollingDirection { FORWARD, REVERSE } RollingDirection_t; -class FyrturMotorComponent : public PollingComponent, public uart::UARTDevice { +class FyrturMotorComponent : public PollingComponent, public uart::UARTDevice, public cover::Cover { public: FyrturMotorComponent() = default; @@ -98,7 +99,7 @@ class FyrturMotorComponent : public PollingComponent, public uart::UARTDevice { void setup() override; // void dump_config() override; void update() override; - // void loop() override; + void loop() override; float get_setup_priority() const override; #ifdef USE_SWITCH