mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Add Current based cover (#1439)
* Adding first version of current_base cover. No Interlock yet. * simplifying code * Implementing malfunction protection * Adding test * Fixing too long lines * Fixing test sensor names * Adding missing id's in ade7953 tests * Adding code owners as requested * Fixing issue setting position when stop reached * Fixing issue setting position when stop reached * Black formatting * Fixing format issues * Fix for concurrent changes Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							1ba560dc9e
						
					
				
				
					commit
					e30f17f64f
				
			| @@ -39,6 +39,7 @@ esphome/components/coolix/* @glmnet | |||||||
| esphome/components/cover/* @esphome/core | esphome/components/cover/* @esphome/core | ||||||
| esphome/components/cs5460a/* @balrog-kun | esphome/components/cs5460a/* @balrog-kun | ||||||
| esphome/components/ct_clamp/* @jesserockz | esphome/components/ct_clamp/* @jesserockz | ||||||
|  | esphome/components/current_based/* @djwmarcx | ||||||
| esphome/components/daly_bms/* @s1lvi0 | esphome/components/daly_bms/* @s1lvi0 | ||||||
| esphome/components/dashboard_import/* @esphome/core | esphome/components/dashboard_import/* @esphome/core | ||||||
| esphome/components/debug/* @OttoWinter | esphome/components/debug/* @OttoWinter | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/current_based/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/current_based/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@djwmarcx"] | ||||||
							
								
								
									
										124
									
								
								esphome/components/current_based/cover.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								esphome/components/current_based/cover.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome import automation | ||||||
|  | from esphome.components import sensor, cover | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_CLOSE_ACTION, | ||||||
|  |     CONF_CLOSE_DURATION, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_OPEN_ACTION, | ||||||
|  |     CONF_OPEN_DURATION, | ||||||
|  |     CONF_STOP_ACTION, | ||||||
|  |     CONF_MAX_DURATION, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONF_OPEN_SENSOR = "open_sensor" | ||||||
|  | CONF_OPEN_MOVING_CURRENT_THRESHOLD = "open_moving_current_threshold" | ||||||
|  | CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD = "open_obstacle_current_threshold" | ||||||
|  |  | ||||||
|  | CONF_CLOSE_SENSOR = "close_sensor" | ||||||
|  | CONF_CLOSE_MOVING_CURRENT_THRESHOLD = "close_moving_current_threshold" | ||||||
|  | CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD = "close_obstacle_current_threshold" | ||||||
|  |  | ||||||
|  | CONF_OBSTACLE_ROLLBACK = "obstacle_rollback" | ||||||
|  | CONF_MALFUNCTION_DETECTION = "malfunction_detection" | ||||||
|  | CONF_MALFUNCTION_ACTION = "malfunction_action" | ||||||
|  | CONF_START_SENSING_DELAY = "start_sensing_delay" | ||||||
|  |  | ||||||
|  | current_based_ns = cg.esphome_ns.namespace("current_based") | ||||||
|  | CurrentBasedCover = current_based_ns.class_( | ||||||
|  |     "CurrentBasedCover", cover.Cover, cg.Component | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(CurrentBasedCover), | ||||||
|  |         cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True), | ||||||
|  |         cv.Required(CONF_OPEN_SENSOR): cv.use_id(sensor.Sensor), | ||||||
|  |         cv.Required(CONF_OPEN_MOVING_CURRENT_THRESHOLD): cv.float_range( | ||||||
|  |             min=0, min_included=False | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD): cv.float_range( | ||||||
|  |             min=0, min_included=False | ||||||
|  |         ), | ||||||
|  |         cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True), | ||||||
|  |         cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds, | ||||||
|  |         cv.Required(CONF_CLOSE_SENSOR): cv.use_id(sensor.Sensor), | ||||||
|  |         cv.Required(CONF_CLOSE_MOVING_CURRENT_THRESHOLD): cv.float_range( | ||||||
|  |             min=0, min_included=False | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD): cv.float_range( | ||||||
|  |             min=0, min_included=False | ||||||
|  |         ), | ||||||
|  |         cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True), | ||||||
|  |         cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds, | ||||||
|  |         cv.Optional(CONF_OBSTACLE_ROLLBACK, default="10%"): cv.percentage, | ||||||
|  |         cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds, | ||||||
|  |         cv.Optional(CONF_MALFUNCTION_DETECTION, default=True): cv.boolean, | ||||||
|  |         cv.Optional(CONF_MALFUNCTION_ACTION): automation.validate_automation( | ||||||
|  |             single=True | ||||||
|  |         ), | ||||||
|  |         cv.Optional( | ||||||
|  |             CONF_START_SENSING_DELAY, default="500ms" | ||||||
|  |         ): cv.positive_time_period_milliseconds, | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     yield cg.register_component(var, config) | ||||||
|  |     yield cover.register_cover(var, config) | ||||||
|  |  | ||||||
|  |     yield automation.build_automation( | ||||||
|  |         var.get_stop_trigger(), [], config[CONF_STOP_ACTION] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # OPEN | ||||||
|  |     bin = yield cg.get_variable(config[CONF_OPEN_SENSOR]) | ||||||
|  |     cg.add(var.set_open_sensor(bin)) | ||||||
|  |     cg.add( | ||||||
|  |         var.set_open_moving_current_threshold( | ||||||
|  |             config[CONF_OPEN_MOVING_CURRENT_THRESHOLD] | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  |     if CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD in config: | ||||||
|  |         cg.add( | ||||||
|  |             var.set_open_obstacle_current_threshold( | ||||||
|  |                 config[CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD] | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     cg.add(var.set_open_duration(config[CONF_OPEN_DURATION])) | ||||||
|  |     yield automation.build_automation( | ||||||
|  |         var.get_open_trigger(), [], config[CONF_OPEN_ACTION] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # CLOSE | ||||||
|  |     bin = yield cg.get_variable(config[CONF_CLOSE_SENSOR]) | ||||||
|  |     cg.add(var.set_close_sensor(bin)) | ||||||
|  |     cg.add( | ||||||
|  |         var.set_close_moving_current_threshold( | ||||||
|  |             config[CONF_CLOSE_MOVING_CURRENT_THRESHOLD] | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  |     if CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD in config: | ||||||
|  |         cg.add( | ||||||
|  |             var.set_close_obstacle_current_threshold( | ||||||
|  |                 config[CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD] | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION])) | ||||||
|  |     yield automation.build_automation( | ||||||
|  |         var.get_close_trigger(), [], config[CONF_CLOSE_ACTION] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_obstacle_rollback(config[CONF_OBSTACLE_ROLLBACK])) | ||||||
|  |     if CONF_MAX_DURATION in config: | ||||||
|  |         cg.add(var.set_max_duration(config[CONF_MAX_DURATION])) | ||||||
|  |     cg.add(var.set_malfunction_detection(config[CONF_MALFUNCTION_DETECTION])) | ||||||
|  |     if CONF_MALFUNCTION_ACTION in config: | ||||||
|  |         yield automation.build_automation( | ||||||
|  |             var.get_malfunction_trigger(), [], config[CONF_MALFUNCTION_ACTION] | ||||||
|  |         ) | ||||||
|  |     cg.add(var.set_start_sensing_delay(config[CONF_START_SENSING_DELAY])) | ||||||
							
								
								
									
										251
									
								
								esphome/components/current_based/current_based_cover.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								esphome/components/current_based/current_based_cover.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,251 @@ | |||||||
|  | #include "current_based_cover.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include <cfloat> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace current_based { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "current_based.cover"; | ||||||
|  |  | ||||||
|  | using namespace esphome::cover; | ||||||
|  |  | ||||||
|  | CoverTraits CurrentBasedCover::get_traits() { | ||||||
|  |   auto traits = CoverTraits(); | ||||||
|  |   traits.set_supports_position(true); | ||||||
|  |   traits.set_is_assumed_state(false); | ||||||
|  |   return traits; | ||||||
|  | } | ||||||
|  | void CurrentBasedCover::control(const CoverCall &call) { | ||||||
|  |   if (call.get_stop()) { | ||||||
|  |     this->direction_idle_(); | ||||||
|  |   } | ||||||
|  |   if (call.get_position().has_value()) { | ||||||
|  |     auto pos = *call.get_position(); | ||||||
|  |     if (pos == this->position) { | ||||||
|  |       // already at target | ||||||
|  |     } else { | ||||||
|  |       auto op = pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING; | ||||||
|  |       this->target_position_ = pos; | ||||||
|  |       this->start_direction_(op); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void CurrentBasedCover::setup() { | ||||||
|  |   auto restore = this->restore_state_(); | ||||||
|  |   if (restore.has_value()) { | ||||||
|  |     restore->apply(this); | ||||||
|  |   } else { | ||||||
|  |     this->position = 0.5f; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CurrentBasedCover::loop() { | ||||||
|  |   if (this->current_operation == COVER_OPERATION_IDLE) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   const uint32_t now = millis(); | ||||||
|  |  | ||||||
|  |   if (this->current_operation == COVER_OPERATION_OPENING) { | ||||||
|  |     if (this->malfunction_detection_ && this->is_closing_()) {  // Malfunction | ||||||
|  |       this->direction_idle_(); | ||||||
|  |       this->malfunction_trigger_->trigger(); | ||||||
|  |       ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit", | ||||||
|  |                this->name_.c_str()); | ||||||
|  |     } else if (this->is_opening_blocked_()) {  // Blocked | ||||||
|  |       ESP_LOGD(TAG, "'%s' - Obstacle detected during opening.", this->name_.c_str()); | ||||||
|  |       this->direction_idle_(); | ||||||
|  |       if (this->obstacle_rollback_ != 0) { | ||||||
|  |         this->set_timeout("rollback", 300, [this]() { | ||||||
|  |           ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str()); | ||||||
|  |           this->target_position_ = clamp(this->position - this->obstacle_rollback_, 0.0F, 1.0F); | ||||||
|  |           this->start_direction_(COVER_OPERATION_CLOSING); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } else if (this->is_initial_delay_finished_() && !this->is_opening_()) {  // End reached | ||||||
|  |       auto dur = (now - this->start_dir_time_) / 1e3f; | ||||||
|  |       ESP_LOGD(TAG, "'%s' - Open position reached. Took %.1fs.", this->name_.c_str(), dur); | ||||||
|  |       this->direction_idle_(COVER_OPEN); | ||||||
|  |     } | ||||||
|  |   } else if (this->current_operation == COVER_OPERATION_CLOSING) { | ||||||
|  |     if (this->malfunction_detection_ && this->is_opening_()) {  // Malfunction | ||||||
|  |       this->direction_idle_(); | ||||||
|  |       this->malfunction_trigger_->trigger(); | ||||||
|  |       ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit", | ||||||
|  |                this->name_.c_str()); | ||||||
|  |     } else if (this->is_closing_blocked_()) {  // Blocked | ||||||
|  |       ESP_LOGD(TAG, "'%s' - Obstacle detected during closing.", this->name_.c_str()); | ||||||
|  |       this->direction_idle_(); | ||||||
|  |       if (this->obstacle_rollback_ != 0) { | ||||||
|  |         this->set_timeout("rollback", 300, [this]() { | ||||||
|  |           ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str()); | ||||||
|  |           this->target_position_ = clamp(this->position + this->obstacle_rollback_, 0.0F, 1.0F); | ||||||
|  |           this->start_direction_(COVER_OPERATION_OPENING); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } else if (this->is_initial_delay_finished_() && !this->is_closing_()) {  // End reached | ||||||
|  |       auto dur = (now - this->start_dir_time_) / 1e3f; | ||||||
|  |       ESP_LOGD(TAG, "'%s' - Close position reached. Took %.1fs.", this->name_.c_str(), dur); | ||||||
|  |       this->direction_idle_(COVER_CLOSED); | ||||||
|  |     } | ||||||
|  |   } else if (now - this->start_dir_time_ > this->max_duration_) { | ||||||
|  |     ESP_LOGD(TAG, "'%s' - Max duration reached. Stopping cover.", this->name_.c_str()); | ||||||
|  |     this->direction_idle_(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Recompute position every loop cycle | ||||||
|  |   this->recompute_position_(); | ||||||
|  |  | ||||||
|  |   if (this->current_operation != COVER_OPERATION_IDLE && this->is_at_target_()) { | ||||||
|  |     this->direction_idle_(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Send current position every second | ||||||
|  |   if (this->current_operation != COVER_OPERATION_IDLE && now - this->last_publish_time_ > 1000) { | ||||||
|  |     this->publish_state(false); | ||||||
|  |     this->last_publish_time_ = now; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CurrentBasedCover::direction_idle_(float new_position) { | ||||||
|  |   this->start_direction_(COVER_OPERATION_IDLE); | ||||||
|  |   if (new_position != FLT_MAX) { | ||||||
|  |     this->position = new_position; | ||||||
|  |   } | ||||||
|  |   this->publish_state(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CurrentBasedCover::dump_config() { | ||||||
|  |   LOG_COVER("", "Endstop Cover", this); | ||||||
|  |   LOG_SENSOR("  ", "Open Sensor", this->open_sensor_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Open moving current threshold: %.11fA", this->open_moving_current_threshold_); | ||||||
|  |   if (this->open_obstacle_current_threshold_ != FLT_MAX) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Open obstacle current threshold: %.11fA", this->open_obstacle_current_threshold_); | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Open Duration: %.1fs", this->open_duration_ / 1e3f); | ||||||
|  |   LOG_SENSOR("  ", "Close Sensor", this->close_sensor_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Close moving current threshold: %.11fA", this->close_moving_current_threshold_); | ||||||
|  |   if (this->close_obstacle_current_threshold_ != FLT_MAX) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Close obstacle current threshold: %.11fA", this->close_obstacle_current_threshold_); | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Close Duration: %.1fs", this->close_duration_ / 1e3f); | ||||||
|  |   ESP_LOGCONFIG(TAG, "Obstacle Rollback: %.1f%%", this->obstacle_rollback_ * 100); | ||||||
|  |   if (this->max_duration_ != UINT32_MAX) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "Maximun duration: %.1fs", this->max_duration_ / 1e3f); | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "Start sensing delay: %.1fs", this->start_sensing_delay_ / 1e3f); | ||||||
|  |   ESP_LOGCONFIG(TAG, "Malfunction detection: %s", YESNO(this->malfunction_detection_)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float CurrentBasedCover::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  | void CurrentBasedCover::stop_prev_trigger_() { | ||||||
|  |   if (this->prev_command_trigger_ != nullptr) { | ||||||
|  |     this->prev_command_trigger_->stop_action(); | ||||||
|  |     this->prev_command_trigger_ = nullptr; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CurrentBasedCover::is_opening_() const { | ||||||
|  |   return this->open_sensor_->get_state() > this->open_moving_current_threshold_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CurrentBasedCover::is_opening_blocked_() const { | ||||||
|  |   if (this->open_obstacle_current_threshold_ == FLT_MAX) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CurrentBasedCover::is_closing_() const { | ||||||
|  |   return this->close_sensor_->get_state() > this->close_moving_current_threshold_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CurrentBasedCover::is_closing_blocked_() const { | ||||||
|  |   if (this->close_obstacle_current_threshold_ == FLT_MAX) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_; | ||||||
|  | } | ||||||
|  | bool CurrentBasedCover::is_initial_delay_finished_() const { | ||||||
|  |   return millis() - this->start_dir_time_ > this->start_sensing_delay_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CurrentBasedCover::is_at_target_() const { | ||||||
|  |   switch (this->current_operation) { | ||||||
|  |     case COVER_OPERATION_OPENING: | ||||||
|  |       if (this->target_position_ == COVER_OPEN) { | ||||||
|  |         if (!this->is_initial_delay_finished_())  // During initial delay, state is assumed | ||||||
|  |           return false; | ||||||
|  |         return !this->is_opening_(); | ||||||
|  |       } | ||||||
|  |       return this->position >= this->target_position_; | ||||||
|  |     case COVER_OPERATION_CLOSING: | ||||||
|  |       if (this->target_position_ == COVER_CLOSED) { | ||||||
|  |         if (!this->is_initial_delay_finished_())  // During initial delay, state is assumed | ||||||
|  |           return false; | ||||||
|  |         return !this->is_closing_(); | ||||||
|  |       } | ||||||
|  |       return this->position <= this->target_position_; | ||||||
|  |     case COVER_OPERATION_IDLE: | ||||||
|  |     default: | ||||||
|  |       return true; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void CurrentBasedCover::start_direction_(CoverOperation dir) { | ||||||
|  |   if (dir == this->current_operation) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   this->recompute_position_(); | ||||||
|  |   Trigger<> *trig; | ||||||
|  |   switch (dir) { | ||||||
|  |     case COVER_OPERATION_IDLE: | ||||||
|  |       trig = this->stop_trigger_; | ||||||
|  |       break; | ||||||
|  |     case COVER_OPERATION_OPENING: | ||||||
|  |       trig = this->open_trigger_; | ||||||
|  |       break; | ||||||
|  |     case COVER_OPERATION_CLOSING: | ||||||
|  |       trig = this->close_trigger_; | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->current_operation = dir; | ||||||
|  |  | ||||||
|  |   this->stop_prev_trigger_(); | ||||||
|  |   trig->trigger(); | ||||||
|  |   this->prev_command_trigger_ = trig; | ||||||
|  |  | ||||||
|  |   const auto now = millis(); | ||||||
|  |   this->start_dir_time_ = now; | ||||||
|  |   this->last_recompute_time_ = now; | ||||||
|  | } | ||||||
|  | void CurrentBasedCover::recompute_position_() { | ||||||
|  |   if (this->current_operation == COVER_OPERATION_IDLE) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   float dir; | ||||||
|  |   float action_dur; | ||||||
|  |   switch (this->current_operation) { | ||||||
|  |     case COVER_OPERATION_OPENING: | ||||||
|  |       dir = 1.0F; | ||||||
|  |       action_dur = this->open_duration_; | ||||||
|  |       break; | ||||||
|  |     case COVER_OPERATION_CLOSING: | ||||||
|  |       dir = -1.0F; | ||||||
|  |       action_dur = this->close_duration_; | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const auto now = millis(); | ||||||
|  |   this->position += dir * (now - this->last_recompute_time_) / action_dur; | ||||||
|  |   this->position = clamp(this->position, 0.0F, 1.0F); | ||||||
|  |  | ||||||
|  |   this->last_recompute_time_ = now; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace current_based | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										95
									
								
								esphome/components/current_based/current_based_cover.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								esphome/components/current_based/current_based_cover.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/cover/cover.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include <cfloat> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace current_based { | ||||||
|  |  | ||||||
|  | class CurrentBasedCover : public cover::Cover, public Component { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void loop() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |   Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } | ||||||
|  |  | ||||||
|  |   Trigger<> *get_open_trigger() const { return this->open_trigger_; } | ||||||
|  |   void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; } | ||||||
|  |   void set_open_moving_current_threshold(float open_moving_current_threshold) { | ||||||
|  |     this->open_moving_current_threshold_ = open_moving_current_threshold; | ||||||
|  |   } | ||||||
|  |   void set_open_obstacle_current_threshold(float open_obstacle_current_threshold) { | ||||||
|  |     this->open_obstacle_current_threshold_ = open_obstacle_current_threshold; | ||||||
|  |   } | ||||||
|  |   void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; } | ||||||
|  |  | ||||||
|  |   Trigger<> *get_close_trigger() const { return this->close_trigger_; } | ||||||
|  |   void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; } | ||||||
|  |   void set_close_moving_current_threshold(float close_moving_current_threshold) { | ||||||
|  |     this->close_moving_current_threshold_ = close_moving_current_threshold; | ||||||
|  |   } | ||||||
|  |   void set_close_obstacle_current_threshold(float close_obstacle_current_threshold) { | ||||||
|  |     this->close_obstacle_current_threshold_ = close_obstacle_current_threshold; | ||||||
|  |   } | ||||||
|  |   void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; } | ||||||
|  |  | ||||||
|  |   void set_max_duration(uint32_t max_duration) { this->max_duration_ = max_duration; } | ||||||
|  |   void set_obstacle_rollback(float obstacle_rollback) { this->obstacle_rollback_ = obstacle_rollback; } | ||||||
|  |  | ||||||
|  |   void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; } | ||||||
|  |   void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; } | ||||||
|  |  | ||||||
|  |   Trigger<> *get_malfunction_trigger() const { return this->malfunction_trigger_; } | ||||||
|  |  | ||||||
|  |   cover::CoverTraits get_traits() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void control(const cover::CoverCall &call) override; | ||||||
|  |   void stop_prev_trigger_(); | ||||||
|  |  | ||||||
|  |   bool is_at_target_() const; | ||||||
|  |   bool is_opening_() const; | ||||||
|  |   bool is_opening_blocked_() const; | ||||||
|  |   bool is_closing_() const; | ||||||
|  |   bool is_closing_blocked_() const; | ||||||
|  |   bool is_initial_delay_finished_() const; | ||||||
|  |  | ||||||
|  |   void direction_idle_(float new_position = FLT_MAX); | ||||||
|  |   void start_direction_(cover::CoverOperation dir); | ||||||
|  |  | ||||||
|  |   void recompute_position_(); | ||||||
|  |  | ||||||
|  |   Trigger<> *stop_trigger_{new Trigger<>()}; | ||||||
|  |  | ||||||
|  |   sensor::Sensor *open_sensor_{nullptr}; | ||||||
|  |   Trigger<> *open_trigger_{new Trigger<>()}; | ||||||
|  |   float open_moving_current_threshold_; | ||||||
|  |   float open_obstacle_current_threshold_{FLT_MAX}; | ||||||
|  |   uint32_t open_duration_; | ||||||
|  |  | ||||||
|  |   sensor::Sensor *close_sensor_{nullptr}; | ||||||
|  |   Trigger<> *close_trigger_{new Trigger<>()}; | ||||||
|  |   float close_moving_current_threshold_; | ||||||
|  |   float close_obstacle_current_threshold_{FLT_MAX}; | ||||||
|  |   uint32_t close_duration_; | ||||||
|  |  | ||||||
|  |   uint32_t max_duration_{UINT32_MAX}; | ||||||
|  |   bool malfunction_detection_{true}; | ||||||
|  |   Trigger<> *malfunction_trigger_{new Trigger<>()}; | ||||||
|  |   uint32_t start_sensing_delay_; | ||||||
|  |   float obstacle_rollback_; | ||||||
|  |  | ||||||
|  |   Trigger<> *prev_command_trigger_{nullptr}; | ||||||
|  |   uint32_t last_recompute_time_{0}; | ||||||
|  |   uint32_t start_dir_time_{0}; | ||||||
|  |   uint32_t last_publish_time_{0}; | ||||||
|  |   float target_position_{0}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace current_based | ||||||
|  | }  // namespace esphome | ||||||
| @@ -426,14 +426,19 @@ sensor: | |||||||
|     irq_pin: GPIO16 |     irq_pin: GPIO16 | ||||||
|     voltage: |     voltage: | ||||||
|       name: ADE7953 Voltage |       name: ADE7953 Voltage | ||||||
|  |       id: ade7953_voltage | ||||||
|     current_a: |     current_a: | ||||||
|       name: ADE7953 Current A |       name: ADE7953 Current A | ||||||
|  |       id: ade7953_current_a | ||||||
|     current_b: |     current_b: | ||||||
|       name: ADE7953 Current B |       name: ADE7953 Current B | ||||||
|  |       id: ade7953_current_b | ||||||
|     active_power_a: |     active_power_a: | ||||||
|       name: ADE7953 Active Power A |       name: ADE7953 Active Power A | ||||||
|  |       id: ade7953_active_power_a | ||||||
|     active_power_b: |     active_power_b: | ||||||
|       name: ADE7953 Active Power B |       name: ADE7953 Active Power B | ||||||
|  |       id: ade7953_active_power_b | ||||||
|   - platform: pzem004t |   - platform: pzem004t | ||||||
|     uart_id: uart3 |     uart_id: uart3 | ||||||
|     voltage: |     voltage: | ||||||
| @@ -1021,6 +1026,29 @@ cover: | |||||||
|     close_action: |     close_action: | ||||||
|       - switch.turn_on: gpio_switch2 |       - switch.turn_on: gpio_switch2 | ||||||
|     close_duration: 4.5min |     close_duration: 4.5min | ||||||
|  |   - platform: current_based | ||||||
|  |     name: "Current Based Cover" | ||||||
|  |     open_sensor: ade7953_current_a | ||||||
|  |     open_moving_current_threshold: 0.5 | ||||||
|  |     open_obstacle_current_threshold: 0.8 | ||||||
|  |     open_duration: 12s | ||||||
|  |     open_action: | ||||||
|  |       - switch.turn_on: gpio_switch1 | ||||||
|  |     close_sensor: ade7953_current_b | ||||||
|  |     close_moving_current_threshold: 0.5 | ||||||
|  |     close_obstacle_current_threshold: 0.8 | ||||||
|  |     close_duration: 10s | ||||||
|  |     close_action: | ||||||
|  |       - switch.turn_on: gpio_switch2 | ||||||
|  |     stop_action: | ||||||
|  |       - switch.turn_off: gpio_switch1 | ||||||
|  |       - switch.turn_off: gpio_switch2 | ||||||
|  |     obstacle_rollback: 30% | ||||||
|  |     start_sensing_delay: 0.8s | ||||||
|  |     malfunction_detection: true | ||||||
|  |     malfunction_action: | ||||||
|  |       then: | ||||||
|  |         - logger.log: "Malfunction Detected" | ||||||
|   - platform: template |   - platform: template | ||||||
|     name: Template Cover with Tilt |     name: Template Cover with Tilt | ||||||
|     tilt_lambda: 'return 0.5;' |     tilt_lambda: 'return 0.5;' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user