mirror of
https://github.com/esphome/esphome.git
synced 2024-11-24 16:08:10 +01:00
first commit of the status_indicator comp.
This commit is contained in:
parent
a9e3a8ae8c
commit
d8b42afdf2
3 changed files with 363 additions and 0 deletions
121
esphome/components/status_indicator/__init__.py
Normal file
121
esphome/components/status_indicator/__init__.py
Normal 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
|
153
esphome/components/status_indicator/status_indicator.cpp
Normal file
153
esphome/components/status_indicator/status_indicator.cpp
Normal 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
|
89
esphome/components/status_indicator/status_indicator.h
Normal file
89
esphome/components/status_indicator/status_indicator.h
Normal 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
|
Loading…
Reference in a new issue