first commit of the status_indicator comp.

This commit is contained in:
NP v/d Spek 2023-11-20 19:25:50 +01:00
parent a9e3a8ae8c
commit d8b42afdf2
3 changed files with 363 additions and 0 deletions

View file

@ -0,0 +1,121 @@
import esphome.config_validation as cv
import esphome.codegen as cg
import esphome.automation as auto
from esphome.const import (
CONF_ID,
CONF_PRIORITY,
CONF_GROUP,
CONF_TRIGGER_ID,
)
from esphome.core import coroutine_with_priority
CODEOWNERS = ["@nielsnl68"]
status_led_ns = cg.esphome_ns.namespace("status_indicator")
StatusLED = status_led_ns.class_("StatusIndicator", cg.Component)
StatusTrigger = status_led_ns.class_("StatusTrigger", auto.Trigger.template())
StatusAction = status_led_ns.class_("StatusAction", auto.Trigger.template())
CONF_TRIGGER_LIST = {
"on_app_error": True,
"on_clear_app_error": True,
"on_app_warning": True,
"on_clear_app_warning": True,
"on_network_connected": True,
"on_network_disconnected": True,
"on_wifi_ap_enabled": True,
"on_wifi_ap_disabled": True,
"on_api_connected": True,
"on_api_disconnected": True,
"on_mqtt_connected": True,
"on_mgtt_disconnected": True,
"on_custom_status": False,
}
def trigger_setup(Single):
return auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StatusTrigger),
cv.Optional(CONF_GROUP, default=""): cv.string,
cv.Optional(CONF_PRIORITY, default=0): cv.int_range(0, 10),
},
single=Single,
)
def add_default_triggers():
result = {}
for trigger, single in CONF_TRIGGER_LIST.items():
result[cv.Optional(trigger)] = trigger_setup(single)
return cv.Schema(result)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(StatusLED),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(add_default_triggers())
)
async def add_trigger(var, conf, key):
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID],
var,
conf[CONF_GROUP],
conf[CONF_PRIORITY],
)
await auto.build_automation(trigger, [], conf)
cg.add(var.set_trigger(key, trigger))
@coroutine_with_priority(80.0)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
for trigger_name, single in CONF_TRIGGER_LIST.items():
conf = config.get(trigger_name, None)
if conf is None:
continue
if single:
await add_trigger(var, conf, trigger_name)
else:
for conf in config.get(trigger_name, []):
await add_trigger(var, conf, conf[CONF_TRIGGER_ID].id)
@auto.register_action(
"status.push",
StatusAction,
cv.Schema(
{
cv.Required(CONF_TRIGGER_ID): cv.use_id(StatusTrigger),
}
),
)
async def status_action_push_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_TRIGGER_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
cg.add(var.set_state(True))
return var
@auto.register_action(
"status.pop",
StatusAction,
cv.Schema(
{
cv.Required(CONF_TRIGGER_ID): cv.use_id(StatusTrigger),
}
),
)
async def status_action_pop_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_TRIGGER_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
cg.add(var.set_state(False))
return var

View file

@ -0,0 +1,153 @@
#include "status_indicator.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/components/network/util.h"
#ifdef USE_WIFI
#include "esphome/components/wifi/wifi_component.h"
#endif
#ifdef USE_MQTT
#include "esphome/components/mqtt/mqtt_client.h"
#endif
#ifdef USE_API
#include "esphome/components/api/api_server.h"
#endif
namespace esphome {
namespace status_indicator {
static const char *const TAG = "status_indicator";
void StatusIndicator::dump_config() { ESP_LOGCONFIG(TAG, "Status Indicator:"); }
void StatusIndicator::loop() {
std::string status{""};
if ((App.get_app_state() & STATUS_LED_ERROR) != 0u) {
status = "on_app_error";
this->status_.on_error = 1;
} else if (this->status_.on_error == 1) {
status = "on_clear_app_error";
this->status_.on_error = 0;
} else if ((App.get_app_state() & STATUS_LED_WARNING) != 0u) {
status = "on_app_warning";
this->status_.on_warning = 1;
} else if (this->status_.on_warning == 1) {
status = "on_clear_app_warning";
this->status_.on_warning = 0;
}
if (this->current_trigger_ != nullptr) {
if (this->current_trigger_->is_action_running()) {
if (status == "") {
return;
}
this->current_trigger_->stop_action();
}
}
if (network::has_network()) {
#ifdef USE_WIFI
if (status == "" && wifi::global_wifi_component->is_ap_enabled()) {
status = "on_wifi_ap_enabled";
this->status_.on_wifi_ap = 1;
} else if (this->status_.on_wifi_ap == 1) {
status = "on_wifi_ap_disabled";
this->status_.on_wifi_ap = 0;
}
#endif
if (status == "" && not network::is_connected()) {
status = "on_network_disconnected";
this->status_.on_network = 1;
} else if (this->status_.on_network == 1) {
status = "on_network_connected";
this->status_.on_network = 0;
}
#ifdef USE_API
if (status == "" && api::global_api_server != nullptr && not api::global_api_server->is_connected()) {
status = "on_api_disconnected";
this->status_.on_api = 1;
} else if (this->status_.on_error == 1) {
status = "on_api_connected";
this->status_.on_api = 0;
}
#endif
#ifdef USE_MQTT
if (status == "" && mqtt::global_mqtt_client != nullptr && not mqtt::global_mqtt_client->is_connected()) {
status = "on_mqtt_disconnected";
this->status_.on_mqtt = 1;
} else if (this->status_.on_mqtt == 1) {
status = "on_mqtt_connected";
this->status_.on_mqtt = 0;
}
#endif
}
if (this->current_status_ != status) {
if (status != "") {
this->current_trigger_ = get_trigger(status);
if (this->current_trigger_ != nullptr) {
this->current_trigger_->trigger();
}
} else {
this->current_trigger_ = nullptr;
if (!this->custom_triggers_.empty()) {
this->custom_triggers_[0]->trigger();
}
}
this->current_status_ = status;
}
}
float StatusIndicator::get_setup_priority() const { return setup_priority::HARDWARE; }
float StatusIndicator::get_loop_priority() const { return 50.0f; }
StatusTrigger *StatusIndicator::get_trigger(std::string key) {
auto search = this->triggers_.find(key);
if (search != this->triggers_.end())
return search->second;
else {
return nullptr;
}
}
void StatusIndicator::set_trigger(std::string key, StatusTrigger *trigger) { this->triggers_[key] = trigger; }
void StatusIndicator::push_trigger(StatusTrigger *trigger) {
this->pop_trigger(trigger, true);
uint32_t x = 0;
while (this->custom_triggers_.size() > x) {
if (trigger->get_priority() <= this->custom_triggers_[x]->get_priority()) {
this->custom_triggers_.insert(this->custom_triggers_.begin() + x, trigger);
break;
} else {
x++;
}
}
}
void StatusIndicator::pop_trigger(StatusTrigger *trigger, bool incl_group) {
uint32_t x = 0;
while (this->custom_triggers_.size() > x) {
if (trigger == this->custom_triggers_[x]) {
this->custom_triggers_.erase(this->custom_triggers_.begin() + x);
} else if (incl_group && trigger->get_group() != "" && trigger->get_group() == this->custom_triggers_[x]->get_group()) {
this->custom_triggers_.erase(this->custom_triggers_.begin() + x);
} else {
x++;
}
}
}
void StatusIndicator::pop_trigger(std::string group) {
uint32_t x = 0;
while (this->custom_triggers_.size() > x) {
if ( group == this->custom_triggers_[x]->get_group()) {
this->custom_triggers_.erase(this->custom_triggers_.begin() + x);
} else {
x++;
}
}
}
} // namespace status_indicator
} // namespace esphome

View file

@ -0,0 +1,89 @@
#pragma once
#include <map>
#include <vector>
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/light/automation.h"
namespace esphome {
namespace status_indicator {
class StatusTrigger;
union StatusFlags {
struct {
int on_error : 1;
int on_warning : 1;
int on_network : 1;
int on_api : 1;
int on_mqtt : 1;
int on_wifi_ap : 1;
};
int setter = 0;
};
class StatusIndicator : public Component {
public:
void dump_config() override;
void loop() override;
float get_setup_priority() const override;
float get_loop_priority() const override;
StatusTrigger *get_trigger(std::string key);
void set_trigger(std::string key, StatusTrigger *trigger);
void push_trigger(StatusTrigger *trigger);
void pop_trigger(StatusTrigger *trigger, bool incl_group = false);
void pop_trigger(std::string group);
protected:
std::string current_status_{""};
StatusTrigger *current_trigger_{nullptr};
StatusFlags status_;
std::map<std::string, StatusTrigger *> triggers_{};
std::vector<StatusTrigger *> custom_triggers_{};
};
class StatusTrigger : public Trigger<> {
public:
explicit StatusTrigger(StatusIndicator *parent, std::string group, uint32_t priority)
: parent_(parent), group_(group), priority_(priority) {}
std::string get_group() { return this->group_; }
uint32 get_priority() { return this->priority_; }
void push_me() { parent_->push_trigger(this); }
void pop_me() { parent_->pop_trigger(this, false); }
protected:
StatusIndicator *parent_;
std::string group_; /// Minimum length of click. 0 means no minimum.
uint32_t priority_; /// Maximum length of click. 0 means no maximum.
};
template<typename... Ts> class StatusCondition : public Condition<Ts...> {
public:
StatusCondition(StatusIndicator *parent, bool state) : parent_(parent), state_(state) {}
bool check(Ts... x) override { return (this->parent_->status_.setter == 0) == this->state_; }
protected:
StatusIndicator *parent_;
bool state_;
};
template<typename... Ts> class StatusAction : public Action<Ts...> {
public:
explicit StatusAction(StatusTrigger *trigger) : trigger_(trigger) {}
TEMPLATABLE_VALUE(bool, state)
void play(Ts... x) override {
if (this->state_) {
} else {
}
}
protected:
StatusTrigger *trigger_;
};
} // namespace status_indicator
} // namespace esphome