Event entity support (#6451)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
David Friedland 2024-04-23 19:35:26 -07:00 committed by GitHub
parent b03d0f37a4
commit c531a528f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 792 additions and 0 deletions

View File

@ -119,6 +119,7 @@ esphome/components/esp32_rmt/* @jesserockz
esphome/components/esp32_rmt_led_strip/* @jesserockz esphome/components/esp32_rmt_led_strip/* @jesserockz
esphome/components/esp8266/* @esphome/core esphome/components/esp8266/* @esphome/core
esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/event/* @nohat
esphome/components/exposure_notifications/* @OttoWinter esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb esphome/components/ezo/* @ssieb
esphome/components/ezo_pmp/* @carlos-sarmiento esphome/components/ezo_pmp/* @carlos-sarmiento
@ -359,6 +360,7 @@ esphome/components/tee501/* @Stock-M
esphome/components/teleinfo/* @0hax esphome/components/teleinfo/* @0hax
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
esphome/components/template/datetime/* @rfdarter esphome/components/template/datetime/* @rfdarter
esphome/components/template/event/* @nohat
esphome/components/template/fan/* @ssieb esphome/components/template/fan/* @ssieb
esphome/components/text/* @mauritskorse esphome/components/text/* @mauritskorse
esphome/components/thermostat/* @kbx81 esphome/components/thermostat/* @kbx81

View File

@ -1702,6 +1702,32 @@ message TimeCommandRequest {
uint32 second = 4; uint32 second = 4;
} }
// ==================== EVENT ====================
message ListEntitiesEventResponse {
option (id) = 107;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_EVENT";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
repeated string event_types = 9;
}
message EventResponse {
option (id) = 108;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_EVENT";
fixed32 key = 1;
string event_type = 2;
}
// ==================== VALVE ==================== // ==================== VALVE ====================
message ListEntitiesValveResponse { message ListEntitiesValveResponse {

View File

@ -1209,6 +1209,30 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
} }
#endif #endif
#ifdef USE_EVENT
bool APIConnection::send_event(event::Event *event, std::string event_type) {
EventResponse resp{};
resp.key = event->get_object_id_hash();
resp.event_type = std::move(event_type);
return this->send_event_response(resp);
}
bool APIConnection::send_event_info(event::Event *event) {
ListEntitiesEventResponse msg;
msg.key = event->get_object_id_hash();
msg.object_id = event->get_object_id();
if (event->has_own_name())
msg.name = event->get_name();
msg.unique_id = get_default_unique_id("event", event);
msg.icon = event->get_icon();
msg.disabled_by_default = event->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(event->get_entity_category());
msg.device_class = event->get_device_class();
for (const auto &event_type : event->get_event_types())
msg.event_types.push_back(event_type);
return this->send_list_entities_event_response(msg);
}
#endif
bool APIConnection::send_log_message(int level, const char *tag, const char *line) { bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
if (this->log_subscription_ < level) if (this->log_subscription_ < level)
return false; return false;

View File

@ -153,6 +153,11 @@ class APIConnection : public APIServerConnection {
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
#endif #endif
#ifdef USE_EVENT
bool send_event(event::Event *event, std::string event_type);
bool send_event_info(event::Event *event);
#endif
void on_disconnect_response(const DisconnectResponse &value) override; void on_disconnect_response(const DisconnectResponse &value) override;
void on_ping_response(const PingResponse &value) override { void on_ping_response(const PingResponse &value) override {
// we initiated ping // we initiated ping

View File

@ -7709,6 +7709,157 @@ void TimeCommandRequest::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
}
bool ListEntitiesEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 8: {
this->device_class = value.as_string();
return true;
}
case 9: {
this->event_types.push_back(value.as_string());
return true;
}
default:
return false;
}
}
bool ListEntitiesEventResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class);
for (auto &it : this->event_types) {
buffer.encode_string(9, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesEventResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesEventResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
for (const auto &it : this->event_types) {
out.append(" event_types: ");
out.append("'").append(it).append("'");
out.append("\n");
}
out.append("}");
}
#endif
bool EventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->event_type = value.as_string();
return true;
}
default:
return false;
}
}
bool EventResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void EventResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_string(2, this->event_type);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void EventResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("EventResponse {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" event_type: ");
out.append("'").append(this->event_type).append("'");
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 6: { case 6: {

View File

@ -1974,6 +1974,40 @@ class TimeCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class ListEntitiesEventResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string device_class{};
std::vector<std::string> event_types{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class EventResponse : public ProtoMessage {
public:
uint32_t key{0};
std::string event_type{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ListEntitiesValveResponse : public ProtoMessage { class ListEntitiesValveResponse : public ProtoMessage {
public: public:
std::string object_id{}; std::string object_id{};

View File

@ -557,6 +557,22 @@ bool APIServerConnectionBase::send_time_state_response(const TimeStateResponse &
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
#endif #endif
#ifdef USE_EVENT
bool APIServerConnectionBase::send_list_entities_event_response(const ListEntitiesEventResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_event_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesEventResponse>(msg, 107);
}
#endif
#ifdef USE_EVENT
bool APIServerConnectionBase::send_event_response(const EventResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_event_response: %s", msg.dump().c_str());
#endif
return this->send_message_<EventResponse>(msg, 108);
}
#endif
#ifdef USE_VALVE #ifdef USE_VALVE
bool APIServerConnectionBase::send_list_entities_valve_response(const ListEntitiesValveResponse &msg) { bool APIServerConnectionBase::send_list_entities_valve_response(const ListEntitiesValveResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP

View File

@ -280,6 +280,12 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
virtual void on_time_command_request(const TimeCommandRequest &value){}; virtual void on_time_command_request(const TimeCommandRequest &value){};
#endif #endif
#ifdef USE_EVENT
bool send_list_entities_event_response(const ListEntitiesEventResponse &msg);
#endif
#ifdef USE_EVENT
bool send_event_response(const EventResponse &msg);
#endif
#ifdef USE_VALVE #ifdef USE_VALVE
bool send_list_entities_valve_response(const ListEntitiesValveResponse &msg); bool send_list_entities_valve_response(const ListEntitiesValveResponse &msg);
#endif #endif

View File

@ -318,6 +318,13 @@ void APIServer::on_media_player_update(media_player::MediaPlayer *obj) {
} }
#endif #endif
#ifdef USE_EVENT
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
for (auto &c : this->clients_)
c->send_event(obj, event_type);
}
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; } void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -96,6 +96,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override; void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
#endif #endif
#ifdef USE_EVENT
void on_event(event::Event *obj, const std::string &event_type) override;
#endif
bool is_connected() const; bool is_connected() const;

View File

@ -89,6 +89,9 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
return this->client_->send_alarm_control_panel_info(a_alarm_control_panel); return this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
} }
#endif #endif
#ifdef USE_EVENT
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); }
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View File

@ -69,6 +69,9 @@ class ListEntitiesIterator : public ComponentIterator {
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
#ifdef USE_EVENT
bool on_event(event::Event *event) override;
#endif #endif
bool on_end() override; bool on_end() override;

View File

@ -66,6 +66,9 @@ class InitialStateIterator : public ComponentIterator {
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
#ifdef USE_EVENT
bool on_event(event::Event *event) override { return true; };
#endif #endif
protected: protected:
APIConnection *client_; APIConnection *client_;

View File

@ -0,0 +1,134 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_ON_EVENT,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_EVENT_TYPE,
DEVICE_CLASS_BUTTON,
DEVICE_CLASS_DOORBELL,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_MOTION,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
from esphome.cpp_generator import MockObjClass
CODEOWNERS = ["@nohat"]
IS_PLATFORM_COMPONENT = True
DEVICE_CLASSES = [
DEVICE_CLASS_BUTTON,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_DOORBELL,
DEVICE_CLASS_MOTION,
]
event_ns = cg.esphome_ns.namespace("event")
Event = event_ns.class_("Event", cg.EntityBase)
EventPtr = Event.operator("ptr")
TriggerEventAction = event_ns.class_("TriggerEventAction", automation.Action)
EventTrigger = event_ns.class_("EventTrigger", automation.Trigger.template())
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
EVENT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent),
cv.GenerateID(): cv.declare_id(Event),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_EVENT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(EventTrigger),
}
),
}
)
_UNDEF = object()
def event_schema(
class_: MockObjClass = _UNDEF,
*,
icon: str = _UNDEF,
entity_category: str = _UNDEF,
device_class: str = _UNDEF,
) -> cv.Schema:
schema = {}
if class_ is not _UNDEF:
schema[cv.GenerateID()] = cv.declare_id(class_)
for key, default, validator in [
(CONF_ICON, icon, cv.icon),
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_DEVICE_CLASS, device_class, validate_device_class),
]:
if default is not _UNDEF:
schema[cv.Optional(key, default=default)] = validator
return EVENT_SCHEMA.extend(schema)
async def setup_event_core_(var, config, *, event_types: list[str]):
await setup_entity(var, config)
for conf in config.get(CONF_ON_EVENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.std_string, "event_type")], conf
)
cg.add(var.set_event_types(event_types))
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class))
if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_event(var, config, *, event_types: list[str]):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_event(var))
await setup_event_core_(var, config, event_types=event_types)
async def new_event(config, *, event_types: list[str]):
var = cg.new_Pvariable(config[CONF_ID])
await register_event(var, config, event_types=event_types)
return var
TRIGGER_EVENT_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(Event),
cv.Required(CONF_EVENT_TYPE): cv.templatable(cv.string_strict),
}
)
@automation.register_action("event.trigger", TriggerEventAction, TRIGGER_EVENT_SCHEMA)
async def event_fire_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
templ = await cg.templatable(config[CONF_EVENT_TYPE], args, cg.std_string)
cg.add(var.set_event_type(templ))
return var
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_EVENT")
cg.add_global(event_ns.using)

View File

@ -0,0 +1,25 @@
#pragma once
#include "esphome/components/event/event.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
namespace esphome {
namespace event {
template<typename... Ts> class TriggerEventAction : public Action<Ts...>, public Parented<Event> {
public:
TEMPLATABLE_VALUE(std::string, event_type)
void play(Ts... x) override { this->parent_->trigger(this->event_type_.value(x...)); }
};
class EventTrigger : public Trigger<std::string> {
public:
EventTrigger(Event *event) {
event->add_on_event_callback([this](const std::string &event_type) { this->trigger(event_type); });
}
};
} // namespace event
} // namespace esphome

View File

@ -0,0 +1,24 @@
#include "event.h"
#include "esphome/core/log.h"
namespace esphome {
namespace event {
static const char *const TAG = "event";
void Event::trigger(const std::string &event_type) {
if (types_.find(event_type) == types_.end()) {
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
return;
}
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), event_type.c_str());
this->event_callback_.call(event_type);
}
void Event::add_on_event_callback(std::function<void(const std::string &event_type)> &&callback) {
this->event_callback_.add(std::move(callback));
}
} // namespace event
} // namespace esphome

View File

@ -0,0 +1,37 @@
#pragma once
#include <set>
#include <string>
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace event {
#define LOG_EVENT(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
}
class Event : public EntityBase, public EntityBase_DeviceClass {
public:
void trigger(const std::string &event_type);
void set_event_types(const std::set<std::string> &event_types) { this->types_ = event_types; }
std::set<std::string> get_event_types() const { return this->types_; }
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
protected:
CallbackManager<void(const std::string &event_type)> event_callback_;
std::set<std::string> types_;
};
} // namespace event
} // namespace esphome

View File

@ -119,6 +119,7 @@ MQTTTextComponent = mqtt_ns.class_("MQTTTextComponent", MQTTComponent)
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent) MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent) MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent)
MQTTEventComponent = mqtt_ns.class_("MQTTEventComponent", MQTTComponent)
MQTTValveComponent = mqtt_ns.class_("MQTTValveComponent", MQTTComponent) MQTTValveComponent = mqtt_ns.class_("MQTTValveComponent", MQTTComponent)
MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator") MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")

View File

@ -73,6 +73,8 @@ constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en";
constexpr const char *const MQTT_ENTITY_CATEGORY = "ent_cat"; constexpr const char *const MQTT_ENTITY_CATEGORY = "ent_cat";
constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl"; constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl";
constexpr const char *const MQTT_ERROR_TOPIC = "err_t"; constexpr const char *const MQTT_ERROR_TOPIC = "err_t";
constexpr const char *const MQTT_EVENT_TYPE = "event_type";
constexpr const char *const MQTT_EVENT_TYPES = "evt_typ";
constexpr const char *const MQTT_EXPIRE_AFTER = "exp_aft"; constexpr const char *const MQTT_EXPIRE_AFTER = "exp_aft";
constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_cmd_tpl"; constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_cmd_tpl";
constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_cmd_t"; constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_cmd_t";
@ -330,6 +332,8 @@ constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default";
constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category";
constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template"; constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template";
constexpr const char *const MQTT_ERROR_TOPIC = "error_topic"; constexpr const char *const MQTT_ERROR_TOPIC = "error_topic";
constexpr const char *const MQTT_EVENT_TYPE = "event_type";
constexpr const char *const MQTT_EVENT_TYPES = "event_types";
constexpr const char *const MQTT_EXPIRE_AFTER = "expire_after"; constexpr const char *const MQTT_EXPIRE_AFTER = "expire_after";
constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_command_template"; constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_command_template";
constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic"; constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic";

View File

@ -0,0 +1,54 @@
#include "mqtt_event.h"
#include "esphome/core/log.h"
#include "mqtt_const.h"
#ifdef USE_MQTT
#ifdef USE_EVENT
namespace esphome {
namespace mqtt {
static const char *const TAG = "mqtt.event";
using namespace esphome::event;
MQTTEventComponent::MQTTEventComponent(event::Event *event) : event_(event) {}
void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
JsonArray event_types = root.createNestedArray(MQTT_EVENT_TYPES);
for (const auto &event_type : this->event_->get_event_types())
event_types.add(event_type);
if (!this->event_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->event_->get_device_class();
config.command_topic = false;
}
void MQTTEventComponent::setup() {
this->event_->add_on_event_callback([this](const std::string &event_type) { this->publish_event_(event_type); });
}
void MQTTEventComponent::dump_config() {
ESP_LOGCONFIG(TAG, "MQTT Event '%s': ", this->event_->get_name().c_str());
ESP_LOGCONFIG(TAG, "Event Types: ");
for (const auto &event_type : this->event_->get_event_types()) {
ESP_LOGCONFIG(TAG, "- %s", event_type.c_str());
}
LOG_MQTT_COMPONENT(true, true);
}
bool MQTTEventComponent::publish_event_(const std::string &event_type) {
return this->publish_json(this->get_state_topic_(),
[event_type](JsonObject root) { root[MQTT_EVENT_TYPE] = event_type; });
}
std::string MQTTEventComponent::component_type() const { return "event"; }
const EntityBase *MQTTEventComponent::get_entity() const { return this->event_; }
} // namespace mqtt
} // namespace esphome
#endif
#endif // USE_MQTT

View File

@ -0,0 +1,39 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_MQTT
#ifdef USE_EVENT
#include "esphome/components/event/event.h"
#include "mqtt_component.h"
namespace esphome {
namespace mqtt {
class MQTTEventComponent : public mqtt::MQTTComponent {
public:
explicit MQTTEventComponent(event::Event *event);
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
void setup() override;
void dump_config() override;
/// Events do not send a state so just return true.
bool send_initial_state() override { return true; }
protected:
bool publish_event_(const std::string &event_type);
std::string component_type() const override;
const EntityBase *get_entity() const override;
event::Event *event_;
};
} // namespace mqtt
} // namespace esphome
#endif
#endif // USE_MQTT

View File

@ -0,0 +1,24 @@
import esphome.config_validation as cv
from esphome.components import event
import esphome.codegen as cg
from esphome.const import CONF_EVENT_TYPES
from .. import template_ns
CODEOWNERS = ["@nohat"]
TemplateEvent = template_ns.class_("TemplateEvent", event.Event, cg.Component)
CONFIG_SCHEMA = event.event_schema(TemplateEvent).extend(
{
cv.Required(CONF_EVENT_TYPES): cv.ensure_list(cv.string_strict),
}
)
async def to_code(config):
var = await event.new_event(config, event_types=config[CONF_EVENT_TYPES])
await cg.register_component(var, config)

View File

@ -0,0 +1,12 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/event/event.h"
namespace esphome {
namespace template_ {
class TemplateEvent : public Component, public event::Event {};
} // namespace template_
} // namespace esphome

View File

@ -159,5 +159,14 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
} }
#endif #endif
#ifdef USE_EVENT
bool ListEntitiesIterator::on_event(event::Event *event) {
// Null event type, since we are just iterating over entities
const std::string null_event_type = "";
this->web_server_->events_.send(this->web_server_->event_json(event, null_event_type, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
} // namespace web_server } // namespace web_server
} // namespace esphome } // namespace esphome

View File

@ -62,6 +62,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif #endif
#ifdef USE_EVENT
bool on_event(event::Event *event) override;
#endif
protected: protected:
WebServer *web_server_; WebServer *web_server_;

View File

@ -1352,6 +1352,28 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
} }
#endif #endif
#ifdef USE_EVENT
void WebServer::on_event(event::Event *obj, const std::string &event_type) {
this->events_.send(this->event_json(obj, event_type, DETAIL_STATE).c_str(), "state");
}
std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) {
return json::build_json([obj, event_type, start_config](JsonObject root) {
set_json_id(root, obj, "event-" + obj->get_object_id(), start_config);
if (!event_type.empty()) {
root["event_type"] = event_type;
}
if (start_config == DETAIL_ALL) {
JsonArray event_types = root.createNestedArray("event_types");
for (auto const &event_type : obj->get_event_types()) {
event_types.add(event_type);
}
root["device_class"] = obj->get_device_class();
}
});
}
#endif
bool WebServer::canHandle(AsyncWebServerRequest *request) { bool WebServer::canHandle(AsyncWebServerRequest *request) {
if (request->url() == "/") if (request->url() == "/")
return true; return true;

View File

@ -297,6 +297,13 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config); alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config);
#endif #endif
#ifdef USE_EVENT
void on_event(event::Event *obj, const std::string &event_type) override;
/// Dump the event details with its value as a JSON string.
std::string event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config);
#endif
/// Override the web handler's canHandle method. /// Override the web handler's canHandle method.
bool canHandle(AsyncWebServerRequest *request) override; bool canHandle(AsyncWebServerRequest *request) override;
/// Override the web handler's handleRequest method. /// Override the web handler's handleRequest method.

View File

@ -251,6 +251,8 @@ CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support"
CONF_ESPHOME = "esphome" CONF_ESPHOME = "esphome"
CONF_ETHERNET = "ethernet" CONF_ETHERNET = "ethernet"
CONF_EVENT = "event" CONF_EVENT = "event"
CONF_EVENT_TYPE = "event_type"
CONF_EVENT_TYPES = "event_types"
CONF_EXPIRE_AFTER = "expire_after" CONF_EXPIRE_AFTER = "expire_after"
CONF_EXPORT_ACTIVE_ENERGY = "export_active_energy" CONF_EXPORT_ACTIVE_ENERGY = "export_active_energy"
CONF_EXPORT_REACTIVE_ENERGY = "export_reactive_energy" CONF_EXPORT_REACTIVE_ENERGY = "export_reactive_energy"
@ -517,6 +519,7 @@ CONF_ON_DOUBLE_CLICK = "on_double_click"
CONF_ON_ENROLLMENT_DONE = "on_enrollment_done" CONF_ON_ENROLLMENT_DONE = "on_enrollment_done"
CONF_ON_ENROLLMENT_FAILED = "on_enrollment_failed" CONF_ON_ENROLLMENT_FAILED = "on_enrollment_failed"
CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan" CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan"
CONF_ON_EVENT = "on_event"
CONF_ON_FINGER_SCAN_INVALID = "on_finger_scan_invalid" CONF_ON_FINGER_SCAN_INVALID = "on_finger_scan_invalid"
CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched" CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched"
CONF_ON_FINGER_SCAN_MISPLACED = "on_finger_scan_misplaced" CONF_ON_FINGER_SCAN_MISPLACED = "on_finger_scan_misplaced"
@ -1024,6 +1027,7 @@ DEVICE_CLASS_AWNING = "awning"
DEVICE_CLASS_BATTERY = "battery" DEVICE_CLASS_BATTERY = "battery"
DEVICE_CLASS_BATTERY_CHARGING = "battery_charging" DEVICE_CLASS_BATTERY_CHARGING = "battery_charging"
DEVICE_CLASS_BLIND = "blind" DEVICE_CLASS_BLIND = "blind"
DEVICE_CLASS_BUTTON = "button"
DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide" DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide"
DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide" DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide"
DEVICE_CLASS_COLD = "cold" DEVICE_CLASS_COLD = "cold"
@ -1036,6 +1040,7 @@ DEVICE_CLASS_DATA_SIZE = "data_size"
DEVICE_CLASS_DATE = "date" DEVICE_CLASS_DATE = "date"
DEVICE_CLASS_DISTANCE = "distance" DEVICE_CLASS_DISTANCE = "distance"
DEVICE_CLASS_DOOR = "door" DEVICE_CLASS_DOOR = "door"
DEVICE_CLASS_DOORBELL = "doorbell"
DEVICE_CLASS_DURATION = "duration" DEVICE_CLASS_DURATION = "duration"
DEVICE_CLASS_EMPTY = "" DEVICE_CLASS_EMPTY = ""
DEVICE_CLASS_ENERGY = "energy" DEVICE_CLASS_ENERGY = "energy"

View File

@ -63,6 +63,9 @@
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
#include "esphome/components/alarm_control_panel/alarm_control_panel.h" #include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#endif #endif
#ifdef USE_EVENT
#include "esphome/components/event/event.h"
#endif
namespace esphome { namespace esphome {
@ -164,6 +167,10 @@ class Application {
} }
#endif #endif
#ifdef USE_EVENT
void register_event(event::Event *event) { this->events_.push_back(event); }
#endif
/// Register the component in this Application instance. /// Register the component in this Application instance.
template<class C> C *register_component(C *c) { template<class C> C *register_component(C *c) {
static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered"); static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered");
@ -386,6 +393,16 @@ class Application {
} }
#endif #endif
#ifdef USE_EVENT
const std::vector<event::Event *> &get_events() { return this->events_; }
event::Event *get_event_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->events_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
Scheduler scheduler; Scheduler scheduler;
protected: protected:
@ -409,6 +426,9 @@ class Application {
#ifdef USE_BUTTON #ifdef USE_BUTTON
std::vector<button::Button *> buttons_{}; std::vector<button::Button *> buttons_{};
#endif #endif
#ifdef USE_EVENT
std::vector<event::Event *> events_{};
#endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
std::vector<sensor::Sensor *> sensors_{}; std::vector<sensor::Sensor *> sensors_{};
#endif #endif

View File

@ -321,6 +321,21 @@ void ComponentIterator::advance() {
} }
} }
break; break;
#endif
#ifdef USE_EVENT
case IteratorState::EVENT:
if (this->at_ >= App.get_events().size()) {
advance_platform = true;
} else {
auto *event = App.get_events()[this->at_];
if (event->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
success = this->on_event(event);
}
}
break;
#endif #endif
case IteratorState::MAX: case IteratorState::MAX:
if (this->on_end()) { if (this->on_end()) {

View File

@ -80,6 +80,9 @@ class ComponentIterator {
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
virtual bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) = 0; virtual bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) = 0;
#endif
#ifdef USE_EVENT
virtual bool on_event(event::Event *event) = 0;
#endif #endif
virtual bool on_end(); virtual bool on_end();
@ -146,6 +149,9 @@ class ComponentIterator {
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
ALARM_CONTROL_PANEL, ALARM_CONTROL_PANEL,
#endif
#ifdef USE_EVENT
EVENT,
#endif #endif
MAX, MAX,
} state_{IteratorState::NONE}; } state_{IteratorState::NONE};

View File

@ -109,6 +109,12 @@ void Controller::setup_controller(bool include_internal) {
obj->add_on_state_callback([this, obj]() { this->on_alarm_control_panel_update(obj); }); obj->add_on_state_callback([this, obj]() { this->on_alarm_control_panel_update(obj); });
} }
#endif #endif
#ifdef USE_EVENT
for (auto *obj : App.get_events()) {
if (include_internal || !obj->is_internal())
obj->add_on_event_callback([this, obj](const std::string &event_type) { this->on_event(obj, event_type); });
}
#endif
} }
} // namespace esphome } // namespace esphome

View File

@ -55,6 +55,9 @@
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
#include "esphome/components/alarm_control_panel/alarm_control_panel.h" #include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#endif #endif
#ifdef USE_EVENT
#include "esphome/components/event/event.h"
#endif
namespace esphome { namespace esphome {
@ -112,6 +115,9 @@ class Controller {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
virtual void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj){}; virtual void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj){};
#endif #endif
#ifdef USE_EVENT
virtual void on_event(event::Event *obj, const std::string &event_type){};
#endif
}; };
} // namespace esphome } // namespace esphome

View File

@ -24,6 +24,7 @@
#define USE_CLIMATE #define USE_CLIMATE
#define USE_COVER #define USE_COVER
#define USE_DEEP_SLEEP #define USE_DEEP_SLEEP
#define USE_EVENT
#define USE_FAN #define USE_FAN
#define USE_GRAPH #define USE_GRAPH
#define USE_HOMEASSISTANT_TIME #define USE_HOMEASSISTANT_TIME

View File

@ -624,6 +624,7 @@ def lint_trailing_whitespace(fname, match):
"esphome/components/datetime/date_entity.h", "esphome/components/datetime/date_entity.h",
"esphome/components/datetime/time_entity.h", "esphome/components/datetime/time_entity.h",
"esphome/components/display/display.h", "esphome/components/display/display.h",
"esphome/components/event/event.h",
"esphome/components/fan/fan.h", "esphome/components/fan/fan.h",
"esphome/components/i2c/i2c.h", "esphome/components/i2c/i2c.h",
"esphome/components/lock/lock.h", "esphome/components/lock/lock.h",

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired