mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	New script modes POC (#1168)
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
This commit is contained in:
		| @@ -108,7 +108,7 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->prev_trigger_ != nullptr) { |   if (this->prev_trigger_ != nullptr) { | ||||||
|     this->prev_trigger_->stop(); |     this->prev_trigger_->stop_action(); | ||||||
|     this->prev_trigger_ = nullptr; |     this->prev_trigger_ = nullptr; | ||||||
|   } |   } | ||||||
|   Trigger<> *trig; |   Trigger<> *trig; | ||||||
|   | |||||||
| @@ -94,7 +94,7 @@ void EndstopCover::dump_config() { | |||||||
| float EndstopCover::get_setup_priority() const { return setup_priority::DATA; } | float EndstopCover::get_setup_priority() const { return setup_priority::DATA; } | ||||||
| void EndstopCover::stop_prev_trigger_() { | void EndstopCover::stop_prev_trigger_() { | ||||||
|   if (this->prev_command_trigger_ != nullptr) { |   if (this->prev_command_trigger_ != nullptr) { | ||||||
|     this->prev_command_trigger_->stop(); |     this->prev_command_trigger_->stop_action(); | ||||||
|     this->prev_command_trigger_ = nullptr; |     this->prev_command_trigger_ = nullptr; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -67,9 +67,9 @@ class LambdaLightEffect : public LightEffect { | |||||||
| class AutomationLightEffect : public LightEffect { | class AutomationLightEffect : public LightEffect { | ||||||
|  public: |  public: | ||||||
|   AutomationLightEffect(const std::string &name) : LightEffect(name) {} |   AutomationLightEffect(const std::string &name) : LightEffect(name) {} | ||||||
|   void stop() override { this->trig_->stop(); } |   void stop() override { this->trig_->stop_action(); } | ||||||
|   void apply() override { |   void apply() override { | ||||||
|     if (!this->trig_->is_running()) { |     if (!this->trig_->is_action_running()) { | ||||||
|       this->trig_->trigger(); |       this->trig_->trigger(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import automation | from esphome import automation | ||||||
| from esphome.automation import maybe_simple_id | from esphome.automation import maybe_simple_id | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID, CONF_MODE | ||||||
|  |  | ||||||
| script_ns = cg.esphome_ns.namespace('script') | script_ns = cg.esphome_ns.namespace('script') | ||||||
| Script = script_ns.class_('Script', automation.Trigger.template()) | Script = script_ns.class_('Script', automation.Trigger.template()) | ||||||
| @@ -10,10 +10,47 @@ ScriptExecuteAction = script_ns.class_('ScriptExecuteAction', automation.Action) | |||||||
| ScriptStopAction = script_ns.class_('ScriptStopAction', automation.Action) | ScriptStopAction = script_ns.class_('ScriptStopAction', automation.Action) | ||||||
| ScriptWaitAction = script_ns.class_('ScriptWaitAction', automation.Action, cg.Component) | ScriptWaitAction = script_ns.class_('ScriptWaitAction', automation.Action, cg.Component) | ||||||
| IsRunningCondition = script_ns.class_('IsRunningCondition', automation.Condition) | IsRunningCondition = script_ns.class_('IsRunningCondition', automation.Condition) | ||||||
|  | SingleScript = script_ns.class_('SingleScript', Script) | ||||||
|  | RestartScript = script_ns.class_('RestartScript', Script) | ||||||
|  | QueueingScript = script_ns.class_('QueueingScript', Script, cg.Component) | ||||||
|  | ParallelScript = script_ns.class_('ParallelScript', Script) | ||||||
|  |  | ||||||
|  | CONF_SINGLE = 'single' | ||||||
|  | CONF_RESTART = 'restart' | ||||||
|  | CONF_QUEUE = 'queue' | ||||||
|  | CONF_PARALLEL = 'parallel' | ||||||
|  | CONF_MAX_RUNS = 'max_runs' | ||||||
|  |  | ||||||
|  | SCRIPT_MODES = { | ||||||
|  |     CONF_SINGLE: SingleScript, | ||||||
|  |     CONF_RESTART: RestartScript, | ||||||
|  |     CONF_QUEUE: QueueingScript, | ||||||
|  |     CONF_PARALLEL: ParallelScript, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def check_max_runs(value): | ||||||
|  |     if CONF_MAX_RUNS not in value: | ||||||
|  |         return value | ||||||
|  |     if value[CONF_MODE] not in [CONF_QUEUE, CONF_PARALLEL]: | ||||||
|  |         raise cv.Invalid("The option 'max_runs' is only valid in 'queue' and 'parallel' mode.", | ||||||
|  |                          path=[CONF_MAX_RUNS]) | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def assign_declare_id(value): | ||||||
|  |     value = value.copy() | ||||||
|  |     value[CONF_ID] = cv.declare_id(SCRIPT_MODES[value[CONF_MODE]])(value[CONF_ID]) | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = automation.validate_automation({ | CONFIG_SCHEMA = automation.validate_automation({ | ||||||
|     cv.Required(CONF_ID): cv.declare_id(Script), |     # Don't declare id as cv.declare_id yet, because the ID type | ||||||
| }) |     # dpeends on the mode. Will be checked later with assign_declare_id | ||||||
|  |     cv.Required(CONF_ID): cv.string_strict, | ||||||
|  |     cv.Optional(CONF_MODE, default=CONF_SINGLE): cv.one_of(*SCRIPT_MODES, lower=True), | ||||||
|  |     cv.Optional(CONF_MAX_RUNS): cv.positive_int, | ||||||
|  | }, extra_validators=cv.All(check_max_runs, assign_declare_id)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
| @@ -21,6 +58,15 @@ def to_code(config): | |||||||
|     triggers = [] |     triggers = [] | ||||||
|     for conf in config: |     for conf in config: | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_ID]) |         trigger = cg.new_Pvariable(conf[CONF_ID]) | ||||||
|  |         # Add a human-readable name to the script | ||||||
|  |         cg.add(trigger.set_name(conf[CONF_ID].id)) | ||||||
|  |  | ||||||
|  |         if CONF_MAX_RUNS in conf: | ||||||
|  |             cg.add(trigger.set_max_runs(conf[CONF_MAX_RUNS])) | ||||||
|  |  | ||||||
|  |         if conf[CONF_MODE] == CONF_QUEUE: | ||||||
|  |             yield cg.register_component(trigger, conf) | ||||||
|  |  | ||||||
|         triggers.append((trigger, conf)) |         triggers.append((trigger, conf)) | ||||||
|  |  | ||||||
|     for trigger, conf in triggers: |     for trigger, conf in triggers: | ||||||
|   | |||||||
							
								
								
									
										67
									
								
								esphome/components/script/script.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								esphome/components/script/script.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | #include "script.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace script { | ||||||
|  |  | ||||||
|  | static const char *TAG = "script"; | ||||||
|  |  | ||||||
|  | void SingleScript::execute() { | ||||||
|  |   if (this->is_action_running()) { | ||||||
|  |     ESP_LOGW(TAG, "Script '%s' is already running! (mode: single)", this->name_.c_str()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->trigger(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void RestartScript::execute() { | ||||||
|  |   if (this->is_action_running()) { | ||||||
|  |     ESP_LOGD(TAG, "Script '%s' restarting (mode: restart)", this->name_.c_str()); | ||||||
|  |     this->stop_action(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->trigger(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QueueingScript::execute() { | ||||||
|  |   if (this->is_action_running()) { | ||||||
|  |     // num_runs_ is the number of *queued* instances, so total number of instances is | ||||||
|  |     // num_runs_ + 1 | ||||||
|  |     if (this->max_runs_ != 0 && this->num_runs_ + 1 >= this->max_runs_) { | ||||||
|  |       ESP_LOGW(TAG, "Script '%s' maximum number of queued runs exceeded!", this->name_.c_str()); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ESP_LOGD(TAG, "Script '%s' queueing new instance (mode: queue)", this->name_.c_str()); | ||||||
|  |     this->num_runs_++; | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->trigger(); | ||||||
|  |   // Check if the trigger was immediate and we can continue right away. | ||||||
|  |   this->loop(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QueueingScript::stop() { | ||||||
|  |   this->num_runs_ = 0; | ||||||
|  |   Script::stop(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QueueingScript::loop() { | ||||||
|  |   if (this->num_runs_ != 0 && !this->is_action_running()) { | ||||||
|  |     this->num_runs_--; | ||||||
|  |     this->trigger(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ParallelScript::execute() { | ||||||
|  |   if (this->max_runs_ != 0 && this->automation_parent_->num_running() >= this->max_runs_) { | ||||||
|  |     ESP_LOGW(TAG, "Script '%s' maximum number of parallel runs exceeded!", this->name_.c_str()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->trigger(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace script | ||||||
|  | }  // namespace esphome | ||||||
| @@ -1,29 +1,86 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace script { | namespace script { | ||||||
|  |  | ||||||
|  | /// The abstract base class for all script types. | ||||||
| class Script : public Trigger<> { | class Script : public Trigger<> { | ||||||
|  public: |  public: | ||||||
|   void execute() { |   /** Execute a new instance of this script. | ||||||
|     bool prev = this->in_stack_; |    * | ||||||
|     this->in_stack_ = true; |    * The behavior of this function when a script is already running is defined by the subtypes | ||||||
|     this->trigger(); |    */ | ||||||
|     this->in_stack_ = prev; |   virtual void execute() = 0; | ||||||
|   } |   /// Check if any instance of this script is currently running. | ||||||
|   bool script_is_running() { return this->in_stack_ || this->is_running(); } |   virtual bool is_running() { return this->is_action_running(); } | ||||||
|  |   /// Stop all instances of this script. | ||||||
|  |   virtual void stop() { this->stop_action(); } | ||||||
|  |  | ||||||
|  |   // Internal function to give scripts readable names. | ||||||
|  |   void set_name(const std::string &name) { name_ = name; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool in_stack_{false}; |   std::string name_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** A script type for which only a single instance at a time is allowed. | ||||||
|  |  * | ||||||
|  |  * If a new instance is executed while the previous one hasn't finished yet, | ||||||
|  |  * a warning is printed and the new instance is discarded. | ||||||
|  |  */ | ||||||
|  | class SingleScript : public Script { | ||||||
|  |  public: | ||||||
|  |   void execute() override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** A script type that restarts scripts from the beginning when a new instance is started. | ||||||
|  |  * | ||||||
|  |  * If a new instance is started but another one is already running, the existing | ||||||
|  |  * script is stopped and the new instance starts from the beginning. | ||||||
|  |  */ | ||||||
|  | class RestartScript : public Script { | ||||||
|  |  public: | ||||||
|  |   void execute() override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** A script type that queues new instances that are created. | ||||||
|  |  * | ||||||
|  |  * Only one instance of the script can be active at a time. | ||||||
|  |  */ | ||||||
|  | class QueueingScript : public Script, public Component { | ||||||
|  |  public: | ||||||
|  |   void execute() override; | ||||||
|  |   void stop() override; | ||||||
|  |   void loop() override; | ||||||
|  |   void set_max_runs(int max_runs) { max_runs_ = max_runs; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   int num_runs_ = 0; | ||||||
|  |   int max_runs_ = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** A script type that executes new instances in parallel. | ||||||
|  |  * | ||||||
|  |  * If a new instance is started while previous ones haven't finished yet, | ||||||
|  |  * the new one is exeucted in parallel to the other instances. | ||||||
|  |  */ | ||||||
|  | class ParallelScript : public Script { | ||||||
|  |  public: | ||||||
|  |   void execute() override; | ||||||
|  |   void set_max_runs(int max_runs) { max_runs_ = max_runs; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   int max_runs_ = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| template<typename... Ts> class ScriptExecuteAction : public Action<Ts...> { | template<typename... Ts> class ScriptExecuteAction : public Action<Ts...> { | ||||||
|  public: |  public: | ||||||
|   ScriptExecuteAction(Script *script) : script_(script) {} |   ScriptExecuteAction(Script *script) : script_(script) {} | ||||||
|  |  | ||||||
|   void play(Ts... x) override { this->script_->trigger(); } |   void play(Ts... x) override { this->script_->execute(); } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   Script *script_; |   Script *script_; | ||||||
| @@ -43,7 +100,7 @@ template<typename... Ts> class IsRunningCondition : public Condition<Ts...> { | |||||||
|  public: |  public: | ||||||
|   explicit IsRunningCondition(Script *parent) : parent_(parent) {} |   explicit IsRunningCondition(Script *parent) : parent_(parent) {} | ||||||
|  |  | ||||||
|   bool check(Ts... x) override { return this->parent_->script_is_running(); } |   bool check(Ts... x) override { return this->parent_->is_running(); } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   Script *parent_; |   Script *parent_; | ||||||
|   | |||||||
| @@ -120,7 +120,7 @@ void TemplateCover::set_has_position(bool has_position) { this->has_position_ = | |||||||
| void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; } | void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; } | ||||||
| void TemplateCover::stop_prev_trigger_() { | void TemplateCover::stop_prev_trigger_() { | ||||||
|   if (this->prev_command_trigger_ != nullptr) { |   if (this->prev_command_trigger_ != nullptr) { | ||||||
|     this->prev_command_trigger_->stop(); |     this->prev_command_trigger_->stop_action(); | ||||||
|     this->prev_command_trigger_ = nullptr; |     this->prev_command_trigger_ = nullptr; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ void TemplateSwitch::loop() { | |||||||
| } | } | ||||||
| void TemplateSwitch::write_state(bool state) { | void TemplateSwitch::write_state(bool state) { | ||||||
|   if (this->prev_trigger_ != nullptr) { |   if (this->prev_trigger_ != nullptr) { | ||||||
|     this->prev_trigger_->stop(); |     this->prev_trigger_->stop_action(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (state) { |   if (state) { | ||||||
|   | |||||||
| @@ -223,7 +223,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->prev_action_trigger_ != nullptr) { |   if (this->prev_action_trigger_ != nullptr) { | ||||||
|     this->prev_action_trigger_->stop(); |     this->prev_action_trigger_->stop_action(); | ||||||
|     this->prev_action_trigger_ = nullptr; |     this->prev_action_trigger_ = nullptr; | ||||||
|   } |   } | ||||||
|   Trigger<> *trig = this->idle_action_trigger_; |   Trigger<> *trig = this->idle_action_trigger_; | ||||||
| @@ -262,7 +262,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { | |||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   if (this->prev_fan_mode_trigger_ != nullptr) { |   if (this->prev_fan_mode_trigger_ != nullptr) { | ||||||
|     this->prev_fan_mode_trigger_->stop(); |     this->prev_fan_mode_trigger_->stop_action(); | ||||||
|     this->prev_fan_mode_trigger_ = nullptr; |     this->prev_fan_mode_trigger_ = nullptr; | ||||||
|   } |   } | ||||||
|   Trigger<> *trig = this->fan_mode_auto_trigger_; |   Trigger<> *trig = this->fan_mode_auto_trigger_; | ||||||
| @@ -313,7 +313,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { | |||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   if (this->prev_mode_trigger_ != nullptr) { |   if (this->prev_mode_trigger_ != nullptr) { | ||||||
|     this->prev_mode_trigger_->stop(); |     this->prev_mode_trigger_->stop_action(); | ||||||
|     this->prev_mode_trigger_ = nullptr; |     this->prev_mode_trigger_ = nullptr; | ||||||
|   } |   } | ||||||
|   Trigger<> *trig = this->auto_mode_trigger_; |   Trigger<> *trig = this->auto_mode_trigger_; | ||||||
| @@ -355,7 +355,7 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo | |||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   if (this->prev_swing_mode_trigger_ != nullptr) { |   if (this->prev_swing_mode_trigger_ != nullptr) { | ||||||
|     this->prev_swing_mode_trigger_->stop(); |     this->prev_swing_mode_trigger_->stop_action(); | ||||||
|     this->prev_swing_mode_trigger_ = nullptr; |     this->prev_swing_mode_trigger_ = nullptr; | ||||||
|   } |   } | ||||||
|   Trigger<> *trig = this->swing_mode_off_trigger_; |   Trigger<> *trig = this->swing_mode_off_trigger_; | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ void TimeBasedCover::control(const CoverCall &call) { | |||||||
| } | } | ||||||
| void TimeBasedCover::stop_prev_trigger_() { | void TimeBasedCover::stop_prev_trigger_() { | ||||||
|   if (this->prev_command_trigger_ != nullptr) { |   if (this->prev_command_trigger_ != nullptr) { | ||||||
|     this->prev_command_trigger_->stop(); |     this->prev_command_trigger_->stop_action(); | ||||||
|     this->prev_command_trigger_ = nullptr; |     this->prev_command_trigger_ = nullptr; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -812,6 +812,7 @@ def mqtt_qos(value): | |||||||
| def requires_component(comp): | def requires_component(comp): | ||||||
|     """Validate that this option can only be specified when the component `comp` is loaded.""" |     """Validate that this option can only be specified when the component `comp` is loaded.""" | ||||||
|     def validator(value): |     def validator(value): | ||||||
|  |         # pylint: disable=unsupported-membership-test | ||||||
|         if comp not in CORE.raw_config: |         if comp not in CORE.raw_config: | ||||||
|             raise Invalid(f"This option requires component {comp}") |             raise Invalid(f"This option requires component {comp}") | ||||||
|         return value |         return value | ||||||
| @@ -1125,7 +1126,7 @@ def typed_schema(schemas, **kwargs): | |||||||
|     def validator(value): |     def validator(value): | ||||||
|         if not isinstance(value, dict): |         if not isinstance(value, dict): | ||||||
|             raise Invalid("Value must be dict") |             raise Invalid("Value must be dict") | ||||||
|         if CONF_TYPE not in value: |         if key not in value: | ||||||
|             raise Invalid("type not specified!") |             raise Invalid("type not specified!") | ||||||
|         value = value.copy() |         value = value.copy() | ||||||
|         key_v = key_validator(value.pop(key)) |         key_v = key_validator(value.pop(key)) | ||||||
| @@ -1175,6 +1176,7 @@ class OnlyWith(Optional): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def default(self): |     def default(self): | ||||||
|  |         # pylint: disable=unsupported-membership-test | ||||||
|         if self._component not in CORE.raw_config: |         if self._component not in CORE.raw_config: | ||||||
|             return vol.UNDEFINED |             return vol.UNDEFINED | ||||||
|         return self._default |         return self._default | ||||||
|   | |||||||
| @@ -50,18 +50,22 @@ template<typename... Ts> class Automation; | |||||||
|  |  | ||||||
| template<typename... Ts> class Trigger { | template<typename... Ts> class Trigger { | ||||||
|  public: |  public: | ||||||
|  |   /// Inform the parent automation that the event has triggered. | ||||||
|   void trigger(Ts... x) { |   void trigger(Ts... x) { | ||||||
|     if (this->automation_parent_ == nullptr) |     if (this->automation_parent_ == nullptr) | ||||||
|       return; |       return; | ||||||
|     this->automation_parent_->trigger(x...); |     this->automation_parent_->trigger(x...); | ||||||
|   } |   } | ||||||
|   void set_automation_parent(Automation<Ts...> *automation_parent) { this->automation_parent_ = automation_parent; } |   void set_automation_parent(Automation<Ts...> *automation_parent) { this->automation_parent_ = automation_parent; } | ||||||
|   void stop() { |  | ||||||
|  |   /// Stop any action connected to this trigger. | ||||||
|  |   void stop_action() { | ||||||
|     if (this->automation_parent_ == nullptr) |     if (this->automation_parent_ == nullptr) | ||||||
|       return; |       return; | ||||||
|     this->automation_parent_->stop(); |     this->automation_parent_->stop(); | ||||||
|   } |   } | ||||||
|   bool is_running() { |   /// Returns true if any action connected to this trigger is running. | ||||||
|  |   bool is_action_running() { | ||||||
|     if (this->automation_parent_ == nullptr) |     if (this->automation_parent_ == nullptr) | ||||||
|       return false; |       return false; | ||||||
|     return this->automation_parent_->is_running(); |     return this->automation_parent_->is_running(); | ||||||
| @@ -87,8 +91,18 @@ template<typename... Ts> class Action { | |||||||
|     } |     } | ||||||
|     this->stop_next_(); |     this->stop_next_(); | ||||||
|   } |   } | ||||||
|  |   /// Check if this or any of the following actions are currently running. | ||||||
|   virtual bool is_running() { return this->num_running_ > 0 || this->is_running_next_(); } |   virtual bool is_running() { return this->num_running_ > 0 || this->is_running_next_(); } | ||||||
|  |  | ||||||
|  |   /// The total number of actions that are currently running in this plus any of | ||||||
|  |   /// the following actions in the chain. | ||||||
|  |   int num_running_total() { | ||||||
|  |     int total = this->num_running_; | ||||||
|  |     if (this->next_ != nullptr) | ||||||
|  |       total += this->next_->num_running_total(); | ||||||
|  |     return total; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   friend ActionList<Ts...>; |   friend ActionList<Ts...>; | ||||||
|  |  | ||||||
| @@ -123,6 +137,8 @@ template<typename... Ts> class Action { | |||||||
|  |  | ||||||
|   Action<Ts...> *next_ = nullptr; |   Action<Ts...> *next_ = nullptr; | ||||||
|  |  | ||||||
|  |   /// The number of instances of this sequence in the list of actions | ||||||
|  |   /// that is currently being executed. | ||||||
|   int num_running_{0}; |   int num_running_{0}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -151,11 +167,19 @@ template<typename... Ts> class ActionList { | |||||||
|       this->actions_begin_->stop_complex(); |       this->actions_begin_->stop_complex(); | ||||||
|   } |   } | ||||||
|   bool empty() const { return this->actions_begin_ == nullptr; } |   bool empty() const { return this->actions_begin_ == nullptr; } | ||||||
|  |  | ||||||
|  |   /// Check if any action in this action list is currently running. | ||||||
|   bool is_running() { |   bool is_running() { | ||||||
|     if (this->actions_begin_ == nullptr) |     if (this->actions_begin_ == nullptr) | ||||||
|       return false; |       return false; | ||||||
|     return this->actions_begin_->is_running(); |     return this->actions_begin_->is_running(); | ||||||
|   } |   } | ||||||
|  |   /// Return the number of actions in this action list that are currently running. | ||||||
|  |   int num_running() { | ||||||
|  |     if (this->actions_begin_ == nullptr) | ||||||
|  |       return false; | ||||||
|  |     return this->actions_begin_->num_running_total(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   template<int... S> void play_tuple_(const std::tuple<Ts...> &tuple, seq<S...>) { this->play(std::get<S>(tuple)...); } |   template<int... S> void play_tuple_(const std::tuple<Ts...> &tuple, seq<S...>) { this->play(std::get<S>(tuple)...); } | ||||||
| @@ -177,6 +201,9 @@ template<typename... Ts> class Automation { | |||||||
|  |  | ||||||
|   bool is_running() { return this->actions_.is_running(); } |   bool is_running() { return this->actions_.is_running(); } | ||||||
|  |  | ||||||
|  |   /// Return the number of actions in the action part of this automation that are currently running. | ||||||
|  |   int num_running() { return this->actions_.num_running(); } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   Trigger<Ts...> *trigger_; |   Trigger<Ts...> *trigger_; | ||||||
|   ActionList<Ts...> actions_; |   ActionList<Ts...> actions_; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user