mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Tuya Cover improvements (#2637)
This commit is contained in:
		| @@ -5,16 +5,27 @@ from esphome.const import ( | |||||||
|     CONF_OUTPUT_ID, |     CONF_OUTPUT_ID, | ||||||
|     CONF_MIN_VALUE, |     CONF_MIN_VALUE, | ||||||
|     CONF_MAX_VALUE, |     CONF_MAX_VALUE, | ||||||
|  |     CONF_RESTORE_MODE, | ||||||
| ) | ) | ||||||
| from .. import tuya_ns, CONF_TUYA_ID, Tuya | from .. import tuya_ns, CONF_TUYA_ID, Tuya | ||||||
|  |  | ||||||
| DEPENDENCIES = ["tuya"] | DEPENDENCIES = ["tuya"] | ||||||
|  |  | ||||||
|  | CONF_CONTROL_DATAPOINT = "control_datapoint" | ||||||
|  | CONF_DIRECTION_DATAPOINT = "direction_datapoint" | ||||||
| CONF_POSITION_DATAPOINT = "position_datapoint" | CONF_POSITION_DATAPOINT = "position_datapoint" | ||||||
|  | CONF_POSITION_REPORT_DATAPOINT = "position_report_datapoint" | ||||||
| CONF_INVERT_POSITION = "invert_position" | CONF_INVERT_POSITION = "invert_position" | ||||||
|  |  | ||||||
| TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) | 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): | def validate_range(config): | ||||||
|     if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: |     if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: | ||||||
| @@ -29,10 +40,16 @@ CONFIG_SCHEMA = cv.All( | |||||||
|         { |         { | ||||||
|             cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaCover), |             cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaCover), | ||||||
|             cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), |             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.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_MIN_VALUE, default=0): cv.int_, | ||||||
|             cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, |             cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, | ||||||
|             cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, |             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), |     ).extend(cv.COMPONENT_SCHEMA), | ||||||
|     validate_range, |     validate_range, | ||||||
| @@ -44,9 +61,16 @@ async def to_code(config): | |||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await cover.register_cover(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])) |     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_min_value(config[CONF_MIN_VALUE])) | ||||||
|     cg.add(var.set_max_value(config[CONF_MAX_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_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]) |     paren = await cg.get_variable(config[CONF_TUYA_ID]) | ||||||
|     cg.add(var.set_tuya_parent(paren)) |     cg.add(var.set_tuya_parent(paren)) | ||||||
|   | |||||||
| @@ -4,48 +4,122 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace tuya { | 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"; | static const char *const TAG = "tuya.cover"; | ||||||
|  |  | ||||||
| void TuyaCover::setup() { | void TuyaCover::setup() { | ||||||
|   this->value_range_ = this->max_value_ - this->min_value_; |   this->value_range_ = this->max_value_ - this->min_value_; | ||||||
|   if (this->position_id_.has_value()) { |  | ||||||
|     this->parent_->register_listener(*this->position_id_, [this](const TuyaDatapoint &datapoint) { |   this->parent_->add_on_initialized_callback([this]() { | ||||||
|       auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; |     // Set the direction (if configured/supported). | ||||||
|       if (this->invert_position_) |     this->set_direction_(this->invert_position_); | ||||||
|         pos = 1.0f - pos; |  | ||||||
|       this->position = pos; |     // Handle configured restore mode. | ||||||
|       this->publish_state(); |     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) { | void TuyaCover::control(const cover::CoverCall &call) { | ||||||
|   if (call.get_stop()) { |   if (call.get_stop()) { | ||||||
|     auto pos = this->position; |     if (this->control_id_.has_value()) { | ||||||
|     if (this->invert_position_) |       this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_STOP); | ||||||
|  |     } else { | ||||||
|  |       auto pos = this->position; | ||||||
|       pos = 1.0f - pos; |       pos = 1.0f - pos; | ||||||
|     auto position_int = static_cast<uint32_t>(pos * this->value_range_); |       auto position_int = static_cast<uint32_t>(pos * this->value_range_); | ||||||
|     position_int = position_int + this->min_value_; |       position_int = position_int + this->min_value_; | ||||||
|  |  | ||||||
|     parent_->set_integer_datapoint_value(*this->position_id_, position_int); |       parent_->set_integer_datapoint_value(*this->position_id_, position_int); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   if (call.get_position().has_value()) { |   if (call.get_position().has_value()) { | ||||||
|     auto pos = *call.get_position(); |     auto pos = *call.get_position(); | ||||||
|     if (this->invert_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; |       pos = 1.0f - pos; | ||||||
|     auto position_int = static_cast<uint32_t>(pos * this->value_range_); |       auto position_int = static_cast<uint32_t>(pos * this->value_range_); | ||||||
|     position_int = position_int + this->min_value_; |       position_int = position_int + this->min_value_; | ||||||
|  |  | ||||||
|     parent_->set_integer_datapoint_value(*this->position_id_, position_int); |       parent_->set_integer_datapoint_value(*this->position_id_, position_int); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->publish_state(); |   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() { | void TuyaCover::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "Tuya Cover:"); |   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()) |   if (this->position_id_.has_value()) | ||||||
|     ESP_LOGCONFIG(TAG, "   Position has datapoint ID %u", *this->position_id_); |     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() { | cover::CoverTraits TuyaCover::get_traits() { | ||||||
|   | |||||||
| @@ -7,22 +7,37 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace tuya { | namespace tuya { | ||||||
|  |  | ||||||
|  | enum TuyaCoverRestoreMode { | ||||||
|  |   COVER_NO_RESTORE, | ||||||
|  |   COVER_RESTORE, | ||||||
|  |   COVER_RESTORE_AND_CALL, | ||||||
|  | }; | ||||||
|  |  | ||||||
| class TuyaCover : public cover::Cover, public Component { | class TuyaCover : public cover::Cover, public Component { | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void set_position_id(uint8_t dimmer_id) { this->position_id_ = dimmer_id; } |   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_tuya_parent(Tuya *parent) { this->parent_ = parent; } | ||||||
|   void set_min_value(uint32_t min_value) { min_value_ = min_value; } |   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_max_value(uint32_t max_value) { max_value_ = max_value; } | ||||||
|   void set_invert_position(bool invert_position) { invert_position_ = invert_position; } |   void set_invert_position(bool invert_position) { invert_position_ = invert_position; } | ||||||
|  |   void set_restore_mode(TuyaCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void control(const cover::CoverCall &call) override; |   void control(const cover::CoverCall &call) override; | ||||||
|  |   void set_direction_(bool inverted); | ||||||
|   cover::CoverTraits get_traits() override; |   cover::CoverTraits get_traits() override; | ||||||
|  |  | ||||||
|   Tuya *parent_; |   Tuya *parent_; | ||||||
|  |   TuyaCoverRestoreMode restore_mode_{}; | ||||||
|  |   optional<uint8_t> control_id_{}; | ||||||
|  |   optional<uint8_t> direction_id_{}; | ||||||
|   optional<uint8_t> position_id_{}; |   optional<uint8_t> position_id_{}; | ||||||
|  |   optional<uint8_t> position_report_id_{}; | ||||||
|   uint32_t min_value_; |   uint32_t min_value_; | ||||||
|   uint32_t max_value_; |   uint32_t max_value_; | ||||||
|   uint32_t value_range_; |   uint32_t value_range_; | ||||||
|   | |||||||
| @@ -195,6 +195,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff | |||||||
|       if (this->init_state_ == TuyaInitState::INIT_DATAPOINT) { |       if (this->init_state_ == TuyaInitState::INIT_DATAPOINT) { | ||||||
|         this->init_state_ = TuyaInitState::INIT_DONE; |         this->init_state_ = TuyaInitState::INIT_DONE; | ||||||
|         this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); }); |         this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); }); | ||||||
|  |         this->initialized_callback_.call(); | ||||||
|       } |       } | ||||||
|       this->handle_datapoint_(buffer, len); |       this->handle_datapoint_(buffer, len); | ||||||
|       break; |       break; | ||||||
| @@ -439,53 +440,51 @@ void Tuya::send_local_time_() { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value) { | void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value) { | ||||||
|   ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); |   this->set_raw_datapoint_value_(datapoint_id, value, false); | ||||||
|   optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id); |  | ||||||
|   if (!datapoint.has_value()) { |  | ||||||
|     ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); |  | ||||||
|   } else if (datapoint->type != TuyaDatapointType::RAW) { |  | ||||||
|     ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); |  | ||||||
|     return; |  | ||||||
|   } else if (datapoint->value_raw == value) { |  | ||||||
|     ESP_LOGV(TAG, "Not sending unchanged value"); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { | void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { | ||||||
|   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1); |   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, false); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { | void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { | ||||||
|   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4); |   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, false); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { | void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { | ||||||
|   ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str()); |   this->set_string_datapoint_value_(datapoint_id, value, false); | ||||||
|   optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id); |  | ||||||
|   if (!datapoint.has_value()) { |  | ||||||
|     ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); |  | ||||||
|   } else if (datapoint->type != TuyaDatapointType::STRING) { |  | ||||||
|     ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); |  | ||||||
|     return; |  | ||||||
|   } else if (datapoint->value_string == value) { |  | ||||||
|     ESP_LOGV(TAG, "Not sending unchanged value"); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   std::vector<uint8_t> data; |  | ||||||
|   for (char const &c : value) { |  | ||||||
|     data.push_back(c); |  | ||||||
|   } |  | ||||||
|   this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { | void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { | ||||||
|   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1); |   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, false); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) { | void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) { | ||||||
|   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length); |   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Tuya::force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value) { | ||||||
|  |   this->set_raw_datapoint_value_(datapoint_id, value, true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Tuya::force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { | ||||||
|  |   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Tuya::force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { | ||||||
|  |   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Tuya::force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { | ||||||
|  |   this->set_string_datapoint_value_(datapoint_id, value, true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Tuya::force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { | ||||||
|  |   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Tuya::force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) { | ||||||
|  |   this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, true); | ||||||
| } | } | ||||||
|  |  | ||||||
| optional<TuyaDatapoint> Tuya::get_datapoint_(uint8_t datapoint_id) { | optional<TuyaDatapoint> Tuya::get_datapoint_(uint8_t datapoint_id) { | ||||||
| @@ -496,7 +495,7 @@ optional<TuyaDatapoint> Tuya::get_datapoint_(uint8_t datapoint_id) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value, | void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value, | ||||||
|                                         uint8_t length) { |                                         uint8_t length, bool forced) { | ||||||
|   ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value); |   ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value); | ||||||
|   optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id); |   optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id); | ||||||
|   if (!datapoint.has_value()) { |   if (!datapoint.has_value()) { | ||||||
| @@ -504,7 +503,7 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType | |||||||
|   } else if (datapoint->type != datapoint_type) { |   } else if (datapoint->type != datapoint_type) { | ||||||
|     ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); |     ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); | ||||||
|     return; |     return; | ||||||
|   } else if (datapoint->value_uint == value) { |   } else if (!forced && datapoint->value_uint == value) { | ||||||
|     ESP_LOGV(TAG, "Not sending unchanged value"); |     ESP_LOGV(TAG, "Not sending unchanged value"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -526,6 +525,40 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType | |||||||
|   this->send_datapoint_command_(datapoint_id, datapoint_type, data); |   this->send_datapoint_command_(datapoint_id, datapoint_type, data); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector<uint8_t> &value, bool forced) { | ||||||
|  |   ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); | ||||||
|  |   optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id); | ||||||
|  |   if (!datapoint.has_value()) { | ||||||
|  |     ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); | ||||||
|  |   } else if (datapoint->type != TuyaDatapointType::RAW) { | ||||||
|  |     ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); | ||||||
|  |     return; | ||||||
|  |   } else if (!forced && datapoint->value_raw == value) { | ||||||
|  |     ESP_LOGV(TAG, "Not sending unchanged value"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Tuya::set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced) { | ||||||
|  |   ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str()); | ||||||
|  |   optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id); | ||||||
|  |   if (!datapoint.has_value()) { | ||||||
|  |     ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); | ||||||
|  |   } else if (datapoint->type != TuyaDatapointType::STRING) { | ||||||
|  |     ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); | ||||||
|  |     return; | ||||||
|  |   } else if (!forced && datapoint->value_string == value) { | ||||||
|  |     ESP_LOGV(TAG, "Not sending unchanged value"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   std::vector<uint8_t> data; | ||||||
|  |   for (char const &c : value) { | ||||||
|  |     data.push_back(c); | ||||||
|  |   } | ||||||
|  |   this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); | ||||||
|  | } | ||||||
|  |  | ||||||
| void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data) { | void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data) { | ||||||
|   std::vector<uint8_t> buffer; |   std::vector<uint8_t> buffer; | ||||||
|   buffer.push_back(datapoint_id); |   buffer.push_back(datapoint_id); | ||||||
| @@ -550,5 +583,7 @@ void Tuya::register_listener(uint8_t datapoint_id, const std::function<void(Tuya | |||||||
|       func(datapoint); |       func(datapoint); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | TuyaInitState Tuya::get_init_state() { return this->init_state_; } | ||||||
|  |  | ||||||
| }  // namespace tuya | }  // namespace tuya | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
| #include "esphome/components/uart/uart.h" | #include "esphome/components/uart/uart.h" | ||||||
|  |  | ||||||
| #ifdef USE_TIME | #ifdef USE_TIME | ||||||
| @@ -81,12 +82,22 @@ class Tuya : public Component, public uart::UARTDevice { | |||||||
|   void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); |   void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); | ||||||
|   void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); |   void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); | ||||||
|   void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); |   void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); | ||||||
|  |   void force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value); | ||||||
|  |   void force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value); | ||||||
|  |   void force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value); | ||||||
|  |   void force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); | ||||||
|  |   void force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); | ||||||
|  |   void force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); | ||||||
|  |   TuyaInitState get_init_state(); | ||||||
| #ifdef USE_TIME | #ifdef USE_TIME | ||||||
|   void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } |   void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } | ||||||
| #endif | #endif | ||||||
|   void add_ignore_mcu_update_on_datapoints(uint8_t ignore_mcu_update_on_datapoints) { |   void add_ignore_mcu_update_on_datapoints(uint8_t ignore_mcu_update_on_datapoints) { | ||||||
|     this->ignore_mcu_update_on_datapoints_.push_back(ignore_mcu_update_on_datapoints); |     this->ignore_mcu_update_on_datapoints_.push_back(ignore_mcu_update_on_datapoints); | ||||||
|   } |   } | ||||||
|  |   void add_on_initialized_callback(std::function<void()> callback) { | ||||||
|  |     this->initialized_callback_.add(std::move(callback)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void handle_char_(uint8_t c); |   void handle_char_(uint8_t c); | ||||||
| @@ -100,7 +111,9 @@ class Tuya : public Component, public uart::UARTDevice { | |||||||
|   void send_command_(const TuyaCommand &command); |   void send_command_(const TuyaCommand &command); | ||||||
|   void send_empty_command_(TuyaCommandType command); |   void send_empty_command_(TuyaCommandType command); | ||||||
|   void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value, |   void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value, | ||||||
|                                     uint8_t length); |                                     uint8_t length, bool forced); | ||||||
|  |   void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced); | ||||||
|  |   void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector<uint8_t> &value, bool forced); | ||||||
|   void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data); |   void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data); | ||||||
|   void send_wifi_status_(); |   void send_wifi_status_(); | ||||||
|  |  | ||||||
| @@ -122,6 +135,7 @@ class Tuya : public Component, public uart::UARTDevice { | |||||||
|   std::vector<TuyaCommand> command_queue_; |   std::vector<TuyaCommand> command_queue_; | ||||||
|   optional<TuyaCommandType> expected_response_{}; |   optional<TuyaCommandType> expected_response_{}; | ||||||
|   uint8_t wifi_status_ = -1; |   uint8_t wifi_status_ = -1; | ||||||
|  |   CallbackManager<void()> initialized_callback_{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace tuya | }  // namespace tuya | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user