Add Event Component to UART (#11765)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
This commit is contained in:
@@ -519,6 +519,7 @@ esphome/components/tuya/switch/* @jesserockz
|
||||
esphome/components/tuya/text_sensor/* @dentra
|
||||
esphome/components/uart/* @esphome/core
|
||||
esphome/components/uart/button/* @ssieb
|
||||
esphome/components/uart/event/* @eoasmxd
|
||||
esphome/components/uart/packet_transport/* @clydebarrow
|
||||
esphome/components/udp/* @clydebarrow
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
|
||||
90
esphome/components/uart/event/__init__.py
Normal file
90
esphome/components/uart/event/__init__.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import event, uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_EVENT_TYPES, CONF_ID
|
||||
from esphome.core import ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import uart_ns
|
||||
|
||||
CODEOWNERS = ["@eoasmxd"]
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
UARTEvent = uart_ns.class_("UARTEvent", event.Event, uart.UARTDevice, cg.Component)
|
||||
|
||||
|
||||
def validate_event_types(value) -> list[tuple[str, str | list[int]]]:
|
||||
if not isinstance(value, list):
|
||||
raise cv.Invalid("Event type must be a list of key-value mappings.")
|
||||
|
||||
processed: list[tuple[str, str | list[int]]] = []
|
||||
for item in value:
|
||||
if not isinstance(item, dict):
|
||||
raise cv.Invalid(f"Event type item must be a mapping (dictionary): {item}")
|
||||
if len(item) != 1:
|
||||
raise cv.Invalid(
|
||||
f"Event type item must be a single key-value mapping: {item}"
|
||||
)
|
||||
|
||||
# Get the single key-value pair
|
||||
event_name, match_data = next(iter(item.items()))
|
||||
|
||||
if not isinstance(event_name, str):
|
||||
raise cv.Invalid(f"Event name (key) must be a string: {event_name}")
|
||||
|
||||
try:
|
||||
# Try to validate as list of hex bytes
|
||||
match_data_bin = cv.ensure_list(cv.hex_uint8_t)(match_data)
|
||||
processed.append((event_name, match_data_bin))
|
||||
continue
|
||||
except cv.Invalid:
|
||||
pass # Not binary, try string
|
||||
|
||||
try:
|
||||
# Try to validate as string
|
||||
match_data_str = cv.string_strict(match_data)
|
||||
processed.append((event_name, match_data_str))
|
||||
continue
|
||||
except cv.Invalid:
|
||||
pass # Not string either
|
||||
|
||||
# If neither validation passed
|
||||
raise cv.Invalid(
|
||||
f"Event match data for '{event_name}' must be a string or a list of hex bytes. Invalid data: {match_data}"
|
||||
)
|
||||
|
||||
if not processed:
|
||||
raise cv.Invalid("event_types must contain at least one event mapping.")
|
||||
|
||||
return processed
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
event.event_schema(UARTEvent)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_EVENT_TYPES): validate_event_types,
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
event_names = [item[0] for item in config[CONF_EVENT_TYPES]]
|
||||
var = await event.new_event(config, event_types=event_names)
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
for i, (event_name, match_data) in enumerate(config[CONF_EVENT_TYPES]):
|
||||
if isinstance(match_data, str):
|
||||
match_data = [ord(c) for c in match_data]
|
||||
|
||||
match_data_var_id = ID(
|
||||
f"match_data_{config[CONF_ID]}_{i}", is_declaration=True, type=cg.uint8
|
||||
)
|
||||
match_data_var = cg.static_const_array(
|
||||
match_data_var_id, cg.ArrayInitializer(*match_data)
|
||||
)
|
||||
cg.add(var.add_event_matcher(event_name, match_data_var, len(match_data)))
|
||||
48
esphome/components/uart/event/uart_event.cpp
Normal file
48
esphome/components/uart/event/uart_event.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "uart_event.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome::uart {
|
||||
|
||||
static const char *const TAG = "uart.event";
|
||||
|
||||
void UARTEvent::setup() {}
|
||||
|
||||
void UARTEvent::dump_config() { LOG_EVENT("", "UART Event", this); }
|
||||
|
||||
void UARTEvent::loop() { this->read_data_(); }
|
||||
|
||||
void UARTEvent::add_event_matcher(const char *event_name, const uint8_t *match_data, size_t match_data_len) {
|
||||
this->matchers_.push_back({event_name, match_data, match_data_len});
|
||||
if (match_data_len > this->max_matcher_len_) {
|
||||
this->max_matcher_len_ = match_data_len;
|
||||
}
|
||||
}
|
||||
|
||||
void UARTEvent::read_data_() {
|
||||
while (this->available()) {
|
||||
uint8_t data;
|
||||
this->read_byte(&data);
|
||||
this->buffer_.push_back(data);
|
||||
|
||||
bool match_found = false;
|
||||
for (const auto &matcher : this->matchers_) {
|
||||
if (this->buffer_.size() < matcher.data_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::equal(matcher.data, matcher.data + matcher.data_len, this->buffer_.end() - matcher.data_len)) {
|
||||
this->trigger(matcher.event_name);
|
||||
this->buffer_.clear();
|
||||
match_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match_found && this->max_matcher_len_ > 0 && this->buffer_.size() > this->max_matcher_len_) {
|
||||
this->buffer_.erase(this->buffer_.begin());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::uart
|
||||
31
esphome/components/uart/event/uart_event.h
Normal file
31
esphome/components/uart/event/uart_event.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/event/event.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::uart {
|
||||
|
||||
class UARTEvent : public event::Event, public UARTDevice, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
void add_event_matcher(const char *event_name, const uint8_t *match_data, size_t match_data_len);
|
||||
|
||||
protected:
|
||||
struct EventMatcher {
|
||||
const char *event_name;
|
||||
const uint8_t *data;
|
||||
size_t data_len;
|
||||
};
|
||||
|
||||
void read_data_();
|
||||
std::vector<EventMatcher> matchers_;
|
||||
std::vector<uint8_t> buffer_;
|
||||
size_t max_matcher_len_ = 0;
|
||||
};
|
||||
|
||||
} // namespace esphome::uart
|
||||
@@ -75,3 +75,11 @@ button:
|
||||
- uart.write: !lambda |-
|
||||
std::string cmd = "VALUE=" + str_sprintf("%.0f", id(test_number).state) + "\r\n";
|
||||
return std::vector<uint8_t>(cmd.begin(), cmd.end());
|
||||
|
||||
event:
|
||||
- platform: uart
|
||||
uart_id: uart_uart
|
||||
name: "UART Event"
|
||||
event_types:
|
||||
- "string_event_A": "*A#"
|
||||
- "bytes_event_B": [0x2A, 0x42, 0x23]
|
||||
|
||||
@@ -31,3 +31,11 @@ button:
|
||||
name: "UART Button"
|
||||
uart_id: uart_uart
|
||||
data: [0xFF, 0xEE]
|
||||
|
||||
event:
|
||||
- platform: uart
|
||||
uart_id: uart_uart
|
||||
name: "UART Event"
|
||||
event_types:
|
||||
- "string_event_A": "*A#"
|
||||
- "bytes_event_B": [0x2A, 0x42, 0x23]
|
||||
|
||||
Reference in New Issue
Block a user