esphome/esphome/core/automation.h
Andrew Zaborowski 2f07225984
Fix: Component script not stopped in certain situations (#1004)
* Move stop/is_running implementation to Action base class

Try to fix issue #1105.  Until now if an UpdateComponentAction, a
LambdaAction, or any action that can contain those inside their
"else" or "then" action lists, resulted in a call to script.stop on
the script that contains them, the script would continue running,
because they didn't implement a stop() method.  Basically only
the asynchronous ones did: DelayAction, WaitUntilAction and
ScriptWaitAction.

With this change num_running_ in Action replaces
DelayAction::num_running_ and WaitUntilAction::triggered_ to provide
the same is_running logic to other actions.

* Make some Action methods protected

Apparently play()/stop() etc. are not meant to be called directly by
users of the class and if they're called directly that would not give
the expected result for the classes that have an empty play().

Make all methods except play_complex, stop_comples and is_running
protected.  While there also make RemoteTransmitterActionBase::encode
protected.

* lint

* format

Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
2020-06-28 19:44:15 -03:00

185 lines
5 KiB
C++

#pragma once
#include <vector>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/defines.h"
#include "esphome/core/preferences.h"
namespace esphome {
#define TEMPLATABLE_VALUE_(type, name) \
protected: \
TemplatableValue<type, Ts...> name##_{}; \
\
public: \
template<typename V> void set_##name(V name) { this->name##_ = name; }
#define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name)
#define TEMPLATABLE_STRING_VALUE_(name) \
protected: \
TemplatableStringValue<Ts...> name##_{}; \
\
public: \
template<typename V> void set_##name(V name) { this->name##_ = name; }
#define TEMPLATABLE_STRING_VALUE(name) TEMPLATABLE_STRING_VALUE_(name)
/** Base class for all automation conditions.
*
* @tparam Ts The template parameters to pass when executing.
*/
template<typename... Ts> class Condition {
public:
/// Check whether this condition passes. This condition check must be instant, and not cause any delays.
virtual bool check(Ts... x) = 0;
/// Call check with a tuple of values as parameter.
bool check_tuple(const std::tuple<Ts...> &tuple) {
return this->check_tuple_(tuple, typename gens<sizeof...(Ts)>::type());
}
protected:
template<int... S> bool check_tuple_(const std::tuple<Ts...> &tuple, seq<S...>) {
return this->check(std::get<S>(tuple)...);
}
};
template<typename... Ts> class Automation;
template<typename... Ts> class Trigger {
public:
void trigger(Ts... x) {
if (this->automation_parent_ == nullptr)
return;
this->automation_parent_->trigger(x...);
}
void set_automation_parent(Automation<Ts...> *automation_parent) { this->automation_parent_ = automation_parent; }
void stop() {
if (this->automation_parent_ == nullptr)
return;
this->automation_parent_->stop();
}
bool is_running() {
if (this->automation_parent_ == nullptr)
return false;
return this->automation_parent_->is_running();
}
protected:
Automation<Ts...> *automation_parent_{nullptr};
};
template<typename... Ts> class ActionList;
template<typename... Ts> class Action {
public:
virtual void play_complex(Ts... x) {
this->num_running_++;
this->play(x...);
this->play_next_(x...);
}
virtual void stop_complex() {
if (num_running_) {
this->stop();
this->num_running_ = 0;
}
this->stop_next_();
}
virtual bool is_running() { return this->num_running_ > 0 || this->is_running_next_(); }
protected:
friend ActionList<Ts...>;
virtual void play(Ts... x) = 0;
void play_next_(Ts... x) {
if (this->num_running_ > 0) {
this->num_running_--;
if (this->next_ != nullptr) {
this->next_->play_complex(x...);
}
}
}
template<int... S> void play_next_tuple_(const std::tuple<Ts...> &tuple, seq<S...>) {
this->play_next_(std::get<S>(tuple)...);
}
void play_next_tuple_(const std::tuple<Ts...> &tuple) {
this->play_next_tuple_(tuple, typename gens<sizeof...(Ts)>::type());
}
virtual void stop() {}
void stop_next_() {
if (this->next_ != nullptr) {
this->next_->stop_complex();
}
}
bool is_running_next_() {
if (this->next_ == nullptr)
return false;
return this->next_->is_running();
}
Action<Ts...> *next_ = nullptr;
int num_running_{0};
};
template<typename... Ts> class ActionList {
public:
void add_action(Action<Ts...> *action) {
if (this->actions_end_ == nullptr) {
this->actions_begin_ = action;
} else {
this->actions_end_->next_ = action;
}
this->actions_end_ = action;
}
void add_actions(const std::vector<Action<Ts...> *> &actions) {
for (auto *action : actions) {
this->add_action(action);
}
}
void play(Ts... x) {
if (this->actions_begin_ != nullptr)
this->actions_begin_->play_complex(x...);
}
void play_tuple(const std::tuple<Ts...> &tuple) { this->play_tuple_(tuple, typename gens<sizeof...(Ts)>::type()); }
void stop() {
if (this->actions_begin_ != nullptr)
this->actions_begin_->stop_complex();
}
bool empty() const { return this->actions_begin_ == nullptr; }
bool is_running() {
if (this->actions_begin_ == nullptr)
return false;
return this->actions_begin_->is_running();
}
protected:
template<int... S> void play_tuple_(const std::tuple<Ts...> &tuple, seq<S...>) { this->play(std::get<S>(tuple)...); }
Action<Ts...> *actions_begin_{nullptr};
Action<Ts...> *actions_end_{nullptr};
};
template<typename... Ts> class Automation {
public:
explicit Automation(Trigger<Ts...> *trigger) : trigger_(trigger) { this->trigger_->set_automation_parent(this); }
Action<Ts...> *add_action(Action<Ts...> *action) { this->actions_.add_action(action); }
void add_actions(const std::vector<Action<Ts...> *> &actions) { this->actions_.add_actions(actions); }
void stop() { this->actions_.stop(); }
void trigger(Ts... x) { this->actions_.play(x...); }
bool is_running() { return this->actions_.is_running(); }
protected:
Trigger<Ts...> *trigger_;
ActionList<Ts...> actions_;
};
} // namespace esphome