Fit this lamp: 4 alarms, full data model, knock snooze, server use

Decode DP127 as 48 bytes (4 alarms), plus DP128, DP107 and DP126, from Tuya's
data model. Add day switches, alarm and sunset controls, a knock sensor on PA18,
display schedule, bedtime fade, radio timer and grouped web UI. Pin LibreTiny
v1.14.0 for the OTA restart fix. Ship the patched UART as an external component
so a server needs only a short YAML.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-11-05 12:27:54 +01:00
1 parent 3e3358af08
commit a8f6bb3ca5
36 files changed
+5132 -214

No files matched your search

+90
View 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
View 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
View 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 final : 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