mirror of
https://github.com/esphome/esphome.git
synced 2025-10-05 19:33:47 +01:00
New script modes POC (#1168)
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
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 = 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)
|
||||
ScriptWaitAction = script_ns.class_('ScriptWaitAction', automation.Action, cg.Component)
|
||||
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({
|
||||
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):
|
||||
@@ -21,6 +58,15 @@ def to_code(config):
|
||||
triggers = []
|
||||
for conf in config:
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace script {
|
||||
|
||||
/// The abstract base class for all script types.
|
||||
class Script : public Trigger<> {
|
||||
public:
|
||||
void execute() {
|
||||
bool prev = this->in_stack_;
|
||||
this->in_stack_ = true;
|
||||
this->trigger();
|
||||
this->in_stack_ = prev;
|
||||
}
|
||||
bool script_is_running() { return this->in_stack_ || this->is_running(); }
|
||||
/** Execute a new instance of this script.
|
||||
*
|
||||
* The behavior of this function when a script is already running is defined by the subtypes
|
||||
*/
|
||||
virtual void execute() = 0;
|
||||
/// Check if any instance of this script is currently 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:
|
||||
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...> {
|
||||
public:
|
||||
ScriptExecuteAction(Script *script) : script_(script) {}
|
||||
|
||||
void play(Ts... x) override { this->script_->trigger(); }
|
||||
void play(Ts... x) override { this->script_->execute(); }
|
||||
|
||||
protected:
|
||||
Script *script_;
|
||||
@@ -43,7 +100,7 @@ template<typename... Ts> class IsRunningCondition : public Condition<Ts...> {
|
||||
public:
|
||||
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:
|
||||
Script *parent_;
|
||||
|
Reference in New Issue
Block a user