mirror of
https://github.com/esphome/esphome.git
synced 2026-09-29 16:00:23 +00:00
Merge remote-tracking branch 'upstream/dev' into integration
This commit is contained in:
@@ -549,6 +549,7 @@ esphome/components/sx127x/* @swoboda1337
|
||||
esphome/components/sy6970/* @linkedupbits
|
||||
esphome/components/syslog/* @clydebarrow
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tas2780/* @remcom
|
||||
esphome/components/tc74/* @sethgirvan
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
esphome/components/tca9555/* @mobrembski
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# Home Assistant Add-on: ESPHome
|
||||
# Sends discovery information to Home Assistant.
|
||||
# ==============================================================================
|
||||
declare config
|
||||
declare port
|
||||
|
||||
# We only disable it when disabled explicitly
|
||||
@@ -19,14 +18,17 @@ port=$(bashio::addon.ingress_port)
|
||||
# Wait for the ESPHome Device Builder to become available
|
||||
bashio::net.wait_for "${port}" "127.0.0.1" 300
|
||||
|
||||
config=$(\
|
||||
bashio::var.json \
|
||||
host "127.0.0.1" \
|
||||
port "^${port}" \
|
||||
)
|
||||
# Send one discovery message; the config is a JSON string built with bashio::var.json.
|
||||
send_discovery() {
|
||||
local service=$1
|
||||
local config=$2
|
||||
if bashio::discovery "${service}" "${config}" > /dev/null; then
|
||||
bashio::log.info "Successfully send ${service} discovery information to Home Assistant."
|
||||
else
|
||||
bashio::log.error "${service} discovery message to Home Assistant failed!"
|
||||
fi
|
||||
}
|
||||
|
||||
if bashio::discovery "esphome" "${config}" > /dev/null; then
|
||||
bashio::log.info "Successfully send discovery information to Home Assistant."
|
||||
else
|
||||
bashio::log.error "Discovery message to Home Assistant failed!"
|
||||
fi
|
||||
send_discovery "esphome" "$(bashio::var.json host "127.0.0.1" port "^${port}")"
|
||||
# The Device Builder MCP server, consumed by Home Assistant's mcp integration.
|
||||
send_discovery "mcp" "$(bashio::var.json url "http://127.0.0.1:${port}/api/mcp")"
|
||||
|
||||
+21
-8
@@ -224,6 +224,15 @@ def flash_string(config: ConfigType, value: str) -> str:
|
||||
return str(cg.safe_exp(value))
|
||||
|
||||
|
||||
def literal_with_length(config: ConfigType, value: str) -> str:
|
||||
"""Renderer for a ``(const char *, size_t)`` target: a plain literal plus its byte length.
|
||||
|
||||
The target compares or copies the bytes in place, so it needs the RAM literal rather than
|
||||
the PROGMEM rendering on ESP8266, and the length saves a strlen.
|
||||
"""
|
||||
return f"{cg.safe_exp(value)}, {len(value.encode('utf-8'))}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ApplyCall:
|
||||
"""One statement from config keys, e.g. ``"set_range({}, {})"`` with ``((CONF_LOW, cg.float_), ...)``.
|
||||
@@ -330,9 +339,9 @@ def _check_key_in_schema(
|
||||
schema = schema.schema[markers[part]]
|
||||
|
||||
|
||||
async def _apply_parent(config: ConfigType) -> str:
|
||||
async def _apply_parent(config: ConfigType, id_key: str = CONF_ID) -> str:
|
||||
# Global-scope qualified so a trigger arg named like the id cannot shadow it.
|
||||
return f"::{await cg.get_variable(config[CONF_ID])}"
|
||||
return f"::{await cg.get_variable(config[id_key])}"
|
||||
|
||||
|
||||
def _apply_lambda_args(args: TemplateArgsType) -> TemplateArgsType:
|
||||
@@ -387,12 +396,14 @@ def register_apply_action(
|
||||
schema: cv.Schema,
|
||||
*fields: ApplyField | ApplyCall,
|
||||
call: str | None = None,
|
||||
id_key: str = CONF_ID,
|
||||
) -> None:
|
||||
"""Register an action that only forwards config values to its parent, with no C++ class.
|
||||
|
||||
Generates one stateless function for ``ApplyAction<Ts...>``: parent and constants are baked
|
||||
in, lambdas are called inline with the trigger args. With ``call`` every statement targets
|
||||
the call object ``auto apply_call = parent->call()``, and ``apply_call.perform()`` is appended.
|
||||
Generates one stateless function for ``ApplyAction<Ts...>``: the parent (read from
|
||||
``id_key``) and constants are baked in, lambdas are called inline with the trigger args.
|
||||
With ``call`` every statement targets the call object ``auto apply_call = parent->call()``,
|
||||
and ``apply_call.perform()`` is appended.
|
||||
"""
|
||||
# An action stores the value, so a std::string constant stays in flash on ESP8266.
|
||||
statements_spec = [
|
||||
@@ -405,6 +416,7 @@ def register_apply_action(
|
||||
)
|
||||
for c in (f if isinstance(f, ApplyCall) else f.call() for f in fields)
|
||||
]
|
||||
_check_key_in_schema(name, schema, id_key)
|
||||
for _, members in statements_spec:
|
||||
for conf_key, _, _ in members:
|
||||
_check_key_in_schema(name, schema, conf_key)
|
||||
@@ -415,7 +427,7 @@ def register_apply_action(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
parent = await _apply_parent(config)
|
||||
parent = await _apply_parent(config, id_key)
|
||||
lambda_args = _apply_lambda_args(args)
|
||||
receiver = "apply_call." if call else f"{parent}->"
|
||||
statements: list[str] = []
|
||||
@@ -442,7 +454,7 @@ def register_apply_action(
|
||||
|
||||
|
||||
def register_apply_condition(
|
||||
name: str, schema: cv.Schema, check: str | ApplyCall
|
||||
name: str, schema: cv.Schema, check: str | ApplyCall, id_key: str = CONF_ID
|
||||
) -> None:
|
||||
"""Register a condition that is one expression on its parent, with no C++ class.
|
||||
|
||||
@@ -454,6 +466,7 @@ def register_apply_condition(
|
||||
"""
|
||||
call = check if isinstance(check, ApplyCall) else ApplyCall(check)
|
||||
members = call.members
|
||||
_check_key_in_schema(name, schema, id_key)
|
||||
for conf_key, _, _ in members:
|
||||
_check_key_in_schema(name, schema, conf_key)
|
||||
|
||||
@@ -463,7 +476,7 @@ def register_apply_condition(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
parent = await _apply_parent(config)
|
||||
parent = await _apply_parent(config, id_key)
|
||||
lambda_args = _apply_lambda_args(args)
|
||||
exprs = await _render_values(
|
||||
name,
|
||||
|
||||
@@ -600,7 +600,7 @@ bool APIConnection::send_light_state(light::LightState *light) {
|
||||
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
|
||||
auto *light = static_cast<light::LightState *>(entity);
|
||||
LightStateResponse resp;
|
||||
auto values = light->remote_values;
|
||||
auto values = light->get_reported_values();
|
||||
auto color_mode = values.get_color_mode();
|
||||
resp.state = values.is_on();
|
||||
resp.color_mode = static_cast<enums::ColorMode>(color_mode);
|
||||
@@ -2267,7 +2267,12 @@ bool APIConnection::send_message_(uint32_t payload_size, uint16_t message_type,
|
||||
// Capacity reserved above, cannot fail
|
||||
(void) shared_buf.resize(write_start + payload_size);
|
||||
ProtoWriteBuffer buffer{&shared_buf, write_start};
|
||||
encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
|
||||
uint8_t *end = encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
proto_check_encode_end(end, shared_buf.data() + shared_buf.size());
|
||||
#else
|
||||
(void) end;
|
||||
#endif
|
||||
return this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type);
|
||||
}
|
||||
// encode_to_buffer is defined inline in api_connection.h (ESPHOME_ALWAYS_INLINE)
|
||||
|
||||
@@ -346,11 +346,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
/// Returns false as soon as the TCP buffer is full. Marked nodiscard so we
|
||||
/// have no silent failures: every caller must handle (or log) a refusal.
|
||||
template<typename T> [[nodiscard]] bool send_message(const T &msg) {
|
||||
if constexpr (T::ESTIMATED_SIZE == 0) {
|
||||
return this->send_message_(0, T::MESSAGE_TYPE, &encode_msg_noop, &msg);
|
||||
} else {
|
||||
return this->send_message_(msg.calculate_size(), T::MESSAGE_TYPE, &proto_encode_msg<T>, &msg);
|
||||
}
|
||||
return this->send_message_(T::calc_size_msg(&msg), T::MESSAGE_TYPE, &T::encode_msg, &msg);
|
||||
}
|
||||
|
||||
/// Clear the shared write buffer and reserve space for the first message.
|
||||
@@ -406,16 +402,6 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
void process_state_subscriptions_();
|
||||
#endif
|
||||
|
||||
// Size thunk — converts void* back to concrete type for direct calculate_size() call
|
||||
template<typename T> static uint32_t calc_size(const void *msg) {
|
||||
return static_cast<const T *>(msg)->calculate_size();
|
||||
}
|
||||
|
||||
// Shared no-op encode thunk for empty messages (ESTIMATED_SIZE == 0)
|
||||
static uint8_t *encode_msg_noop(const void *, ProtoWriteBuffer &buf PROTO_ENCODE_DEBUG_PARAM) {
|
||||
return buf.get_pos();
|
||||
}
|
||||
|
||||
// Non-template buffer management for send_message
|
||||
bool send_message_(uint32_t payload_size, uint16_t message_type, MessageEncodeFn encode_fn, const void *msg);
|
||||
|
||||
@@ -434,11 +420,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
// Hot paths (state/info) go through fill_and_encode_entity_state/info instead.
|
||||
// batch_message_type_ is already set by dispatch_message_ before reaching here.
|
||||
template<typename T> static uint16_t encode_message_to_buffer(T &msg, APIConnection *conn, uint32_t remaining_size) {
|
||||
if constexpr (T::ESTIMATED_SIZE == 0) {
|
||||
return encode_to_buffer_slow(0, &encode_msg_noop, &msg, conn, remaining_size);
|
||||
} else {
|
||||
return encode_to_buffer_slow(msg.calculate_size(), &proto_encode_msg<T>, &msg, conn, remaining_size);
|
||||
}
|
||||
return encode_to_buffer_slow(T::calc_size_msg(&msg), &T::encode_msg, &msg, conn, remaining_size);
|
||||
}
|
||||
|
||||
// Non-template core — fills state fields and encodes
|
||||
@@ -450,7 +432,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
template<typename T>
|
||||
static uint16_t fill_and_encode_entity_state(EntityBase *entity, T &msg, APIConnection *conn,
|
||||
uint32_t remaining_size) {
|
||||
return fill_and_encode_entity_state(entity, msg, &calc_size<T>, &proto_encode_msg<T>, conn, remaining_size);
|
||||
return fill_and_encode_entity_state(entity, msg, &T::calc_size_msg, &T::encode_msg, conn, remaining_size);
|
||||
}
|
||||
|
||||
// Non-template core — fills info fields, allocates buffers, and encodes
|
||||
@@ -462,7 +444,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
template<typename T>
|
||||
static uint16_t fill_and_encode_entity_info(EntityBase *entity, T &msg, APIConnection *conn,
|
||||
uint32_t remaining_size) {
|
||||
return fill_and_encode_entity_info(entity, msg, &calc_size<T>, &proto_encode_msg<T>, conn, remaining_size);
|
||||
return fill_and_encode_entity_info(entity, msg, &T::calc_size_msg, &T::encode_msg, conn, remaining_size);
|
||||
}
|
||||
|
||||
// Non-template core — fills device_class, then delegates to fill_and_encode_entity_info
|
||||
@@ -476,8 +458,8 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
static uint16_t fill_and_encode_entity_info_with_device_class(EntityBase *entity, T &msg,
|
||||
StringRef &device_class_field, APIConnection *conn,
|
||||
uint32_t remaining_size) {
|
||||
return fill_and_encode_entity_info_with_device_class(entity, msg, device_class_field, &calc_size<T>,
|
||||
&proto_encode_msg<T>, conn, remaining_size);
|
||||
return fill_and_encode_entity_info_with_device_class(entity, msg, device_class_field, &T::calc_size_msg,
|
||||
&T::encode_msg, conn, remaining_size);
|
||||
}
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
|
||||
@@ -46,7 +46,13 @@ inline uint16_t ESPHOME_ALWAYS_INLINE APIConnection::encode_to_buffer(uint32_t c
|
||||
return 0;
|
||||
}
|
||||
ProtoWriteBuffer buffer{&shared_buf, shared_buf.size() - calculated_size};
|
||||
encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
|
||||
uint8_t *end = encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
// A body that writes fewer bytes than calculate_size() promised would ship stale buffer bytes
|
||||
proto_check_encode_end(end, shared_buf.data() + shared_buf.size());
|
||||
#else
|
||||
(void) end;
|
||||
#endif
|
||||
|
||||
return total_calculated_size;
|
||||
}
|
||||
|
||||
@@ -5,16 +5,13 @@
|
||||
#include "esphome/components/noise/noise.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "proto.h"
|
||||
#include <cstring>
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
using noise::noise_err_to_logstr;
|
||||
@@ -26,11 +23,7 @@ static_assert(MAX_HANDSHAKE_SIZE == noise::MAX_HANDSHAKE_SIZE,
|
||||
"api and noise component handshake size limits must match");
|
||||
|
||||
static const char *const TAG = "api.noise";
|
||||
#ifdef USE_ESP8266
|
||||
static constexpr char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
|
||||
#else
|
||||
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
||||
#endif
|
||||
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
||||
|
||||
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
|
||||
@@ -72,11 +65,7 @@ APIError APINoiseFrameHelper::init() {
|
||||
state_ = State::FAILED;
|
||||
return APIError::OUT_OF_MEMORY;
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
memcpy_P(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||
#else
|
||||
std::memcpy(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||
#endif
|
||||
progmem_memcpy(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||
|
||||
state_ = State::CLIENT_HELLO;
|
||||
return APIError::OK;
|
||||
|
||||
+2497
-2685
File diff suppressed because it is too large
Load Diff
+701
-352
File diff suppressed because it is too large
Load Diff
@@ -455,8 +455,9 @@ void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call
|
||||
// Home Assistant subscribes to actions shortly *after* authenticating, so actions
|
||||
// fired right at connection time (on_client_connected, on_time_sync, ...) can
|
||||
// arrive before the subscription and are lost - warn instead of failing silently.
|
||||
ESP_LOGW(TAG, "Home Assistant %s '%s' dropped; %s",
|
||||
call.is_event ? LOG_STR_LITERAL("event") : LOG_STR_LITERAL("action"), call.service.c_str(),
|
||||
ESP_LOGW(TAG, "Home Assistant %s '%.*s' dropped; %s",
|
||||
call.is_event ? LOG_STR_LITERAL("event") : LOG_STR_LITERAL("action"),
|
||||
static_cast<int>(call.service.size()), call.service.empty() ? "" : call.service.c_str(),
|
||||
this->is_connected() ? LOG_STR_LITERAL("client has not subscribed to actions (yet)")
|
||||
: LOG_STR_LITERAL("no client connected"));
|
||||
}
|
||||
|
||||
@@ -195,6 +195,12 @@ void proto_check_bounds_failed(const uint8_t *pos, size_t bytes, const uint8_t *
|
||||
ESP_LOGE(TAG, "Proto encode bounds check failed in %s: need %zu bytes, %td available", caller, bytes, end - pos);
|
||||
abort();
|
||||
}
|
||||
void proto_check_encode_end(const uint8_t *end, const uint8_t *expected) {
|
||||
if (end == expected)
|
||||
return;
|
||||
ESP_LOGE(TAG, "Proto encode ended %td bytes off the calculated size", end - expected);
|
||||
abort();
|
||||
}
|
||||
void ProtoWriteBuffer::debug_check_bounds_(size_t bytes, const char *caller) {
|
||||
if (this->pos_ + bytes > this->buffer_->data() + this->buffer_->size()) {
|
||||
ESP_LOGE(TAG, "ProtoWriteBuffer bounds check failed in %s: bytes=%zu offset=%td buf_size=%zu", caller, bytes,
|
||||
@@ -214,73 +220,74 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
const uint8_t *ptr = buffer;
|
||||
const uint8_t *end = buffer + length;
|
||||
|
||||
while (ptr < end) {
|
||||
// Parse field header - ptr < end guarantees len >= 1
|
||||
// Single-byte varints dominate, so that case advances the cursor inline.
|
||||
auto read_varint = [&](proto_varint_value_t &value) ESPHOME_ALWAYS_INLINE {
|
||||
if (ptr == end)
|
||||
return false;
|
||||
if (*ptr < 0x80) [[likely]] {
|
||||
value = *ptr++;
|
||||
return true;
|
||||
}
|
||||
auto res = ProtoVarInt::parse_non_empty(ptr, end - ptr);
|
||||
if (!res.has_value()) {
|
||||
if (!res.has_value())
|
||||
return false;
|
||||
value = res.value;
|
||||
ptr += res.consumed;
|
||||
return true;
|
||||
};
|
||||
|
||||
while (ptr < end) {
|
||||
proto_varint_value_t tag_value;
|
||||
if (!read_varint(tag_value)) {
|
||||
ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t tag = static_cast<uint32_t>(res.value);
|
||||
uint32_t tag = static_cast<uint32_t>(tag_value);
|
||||
uint32_t field_type = tag & WIRE_TYPE_MASK;
|
||||
uint32_t field_id = tag >> 3;
|
||||
ptr += res.consumed;
|
||||
// Length-delimited fields move this past the length prefix
|
||||
const uint8_t *data = ptr;
|
||||
proto_varint_value_t scalar;
|
||||
|
||||
switch (field_type) {
|
||||
case WIRE_TYPE_VARINT: { // VarInt
|
||||
res = ProtoVarInt::parse(ptr, end - ptr);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
if (!this->decode_varint(field_id, res.value)) {
|
||||
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu64 "!", field_id,
|
||||
static_cast<uint64_t>(res.value));
|
||||
}
|
||||
ptr += res.consumed;
|
||||
break;
|
||||
}
|
||||
case WIRE_TYPE_LENGTH_DELIMITED: { // Length-delimited
|
||||
res = ProtoVarInt::parse(ptr, end - ptr);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
uint32_t field_length = static_cast<uint32_t>(res.value);
|
||||
ptr += res.consumed;
|
||||
if (field_length > static_cast<size_t>(end - ptr)) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) {
|
||||
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
|
||||
}
|
||||
ptr += field_length;
|
||||
break;
|
||||
}
|
||||
case WIRE_TYPE_FIXED32: { // 32-bit
|
||||
if (end - ptr < 4) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
uint32_t val;
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
// Protobuf fixed32 is little-endian — direct load on LE platforms
|
||||
memcpy(&val, ptr, 4);
|
||||
#else
|
||||
val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
|
||||
#endif
|
||||
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
|
||||
}
|
||||
ptr += 4;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGV(TAG, "Invalid field type %" PRIu32 " at offset %ld", field_type, (long) (ptr - buffer));
|
||||
if (field_type == WIRE_TYPE_VARINT) [[likely]] {
|
||||
if (!read_varint(scalar)) {
|
||||
ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
switch (field_type) {
|
||||
case WIRE_TYPE_LENGTH_DELIMITED: {
|
||||
proto_varint_value_t length_value;
|
||||
if (!read_varint(length_value)) {
|
||||
ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
uint32_t field_length = static_cast<uint32_t>(length_value);
|
||||
if (field_length > static_cast<size_t>(end - ptr)) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
data = ptr;
|
||||
scalar = field_length;
|
||||
ptr += field_length;
|
||||
break;
|
||||
}
|
||||
case WIRE_TYPE_FIXED32: {
|
||||
if (end - ptr < 4) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
// Byte loads instead of memcpy: ESP-IDF passes -fno-builtin-memcpy, which made this a call
|
||||
scalar = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
|
||||
ptr += 4;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGV(TAG, "Invalid field type %" PRIu32 " at offset %ld", field_type, (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->decode_field(tag, data, scalar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+231
-166
@@ -170,40 +170,43 @@ class ProtoVarInt {
|
||||
class ProtoMessage;
|
||||
class ProtoSize;
|
||||
|
||||
class ProtoLengthDelimited {
|
||||
/// Case label for decode_field(): the wire tag of a field, so a field that arrives with another wire
|
||||
/// type matches no case.
|
||||
constexpr uint32_t proto_tag(uint32_t field_id, uint32_t wire_type) { return (field_id << 3) | wire_type; }
|
||||
|
||||
/// One decoded field: the payload pointer and a scalar holding the varint or fixed32 value, or the
|
||||
/// length of a length-delimited field. The wire type in the tag says which applies; accessors do not check.
|
||||
class ProtoFieldValue {
|
||||
public:
|
||||
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
|
||||
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
|
||||
ProtoFieldValue(const uint8_t *data, proto_varint_value_t scalar) : data_(data), scalar_(scalar) {}
|
||||
|
||||
// Direct access to raw data without string allocation
|
||||
const uint8_t *data() const { return this->value_; }
|
||||
size_t size() const { return this->length_; }
|
||||
proto_varint_value_t as_varint() const { return this->scalar_; }
|
||||
// A bool is sent as 0 or 1, so the low word is enough and saves a second compare with 64 bit varints
|
||||
bool as_bool() const { return static_cast<uint32_t>(this->scalar_) != 0; }
|
||||
|
||||
/// Decode the length-delimited data into a message instance.
|
||||
// Length-delimited accessors
|
||||
const uint8_t *data() const { return this->data_; }
|
||||
size_t size() const { return static_cast<size_t>(this->scalar_); }
|
||||
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->data_), this->size()); }
|
||||
/// Decode the length-delimited payload into a message instance.
|
||||
/// Template preserves concrete type so decode() resolves statically.
|
||||
template<typename T> void decode_to_message(T &msg) const;
|
||||
template<typename T> void decode_to_message(T &msg) const { msg.decode(this->data_, this->size()); }
|
||||
|
||||
protected:
|
||||
const uint8_t *const value_;
|
||||
const size_t length_;
|
||||
};
|
||||
|
||||
class Proto32Bit {
|
||||
public:
|
||||
explicit Proto32Bit(uint32_t value) : value_(value) {}
|
||||
uint32_t as_fixed32() const { return this->value_; }
|
||||
int32_t as_sfixed32() const { return static_cast<int32_t>(this->value_); }
|
||||
// Fixed32 accessors
|
||||
uint32_t as_fixed32() const { return static_cast<uint32_t>(this->scalar_); }
|
||||
int32_t as_sfixed32() const { return static_cast<int32_t>(this->as_fixed32()); }
|
||||
float as_float() const {
|
||||
union {
|
||||
uint32_t raw;
|
||||
float value;
|
||||
} s{};
|
||||
s.raw = this->value_;
|
||||
s.raw = this->as_fixed32();
|
||||
return s.value;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint32_t value_;
|
||||
private:
|
||||
const uint8_t *data_;
|
||||
proto_varint_value_t scalar_;
|
||||
};
|
||||
|
||||
// NOTE: Proto64Bit class removed - wire type 1 (64-bit fixed) not supported
|
||||
@@ -221,6 +224,9 @@ class Proto32Bit {
|
||||
proto_check_bounds_failed(pos, n, proto_debug_end_, __builtin_FUNCTION()); \
|
||||
} while (0)
|
||||
void proto_check_bounds_failed(const uint8_t *pos, size_t bytes, const uint8_t *end, const char *caller);
|
||||
/// Aborts unless an encode body ended exactly where calculate_size() promised. A plain check rather than
|
||||
/// assert(), so NDEBUG cannot switch it off.
|
||||
void proto_check_encode_end(const uint8_t *end, const uint8_t *expected);
|
||||
#else
|
||||
#define PROTO_ENCODE_DEBUG_PARAM
|
||||
#define PROTO_ENCODE_DEBUG_ARG
|
||||
@@ -252,7 +258,7 @@ class ProtoWriteBuffer {
|
||||
*
|
||||
* Following https://protobuf.dev/programming-guides/encoding/#structure
|
||||
*/
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw((field_id << 3) | type); }
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw(proto_tag(field_id, type)); }
|
||||
/// Single-pass encode for repeated submessage elements.
|
||||
/// Thin template wrapper; all buffer work is in the non-template core.
|
||||
template<typename T> void encode_sub_message(uint32_t field_id, const T &value);
|
||||
@@ -287,19 +293,31 @@ class ProtoWriteBuffer {
|
||||
uint8_t *pos_;
|
||||
};
|
||||
|
||||
// A four byte unaligned store is a memcpy call on ESP-IDF (-fno-builtin-memcpy) and on ARM cores without
|
||||
// unaligned access (Cortex-M0+, ARM9), so those targets share one outlined byte store helper per fixed32
|
||||
// field. Elsewhere the write inlines to a single store, or on ESP8266 to a few stores that measured
|
||||
// faster than a call, so it stays inline.
|
||||
#if defined(USE_ESP32) || (defined(__arm__) && !defined(__ARM_FEATURE_UNALIGNED))
|
||||
#define PROTO_OUTLINE_FOR_SIZE __attribute__((noinline))
|
||||
#define PROTO_FIXED32_BYTE_STORES true
|
||||
#else
|
||||
#define PROTO_OUTLINE_FOR_SIZE inline
|
||||
#define PROTO_FIXED32_BYTE_STORES false
|
||||
#endif
|
||||
|
||||
// Varint encoding thresholds — used by both proto_encode_* free functions and ProtoSize.
|
||||
constexpr uint32_t VARINT_MAX_1_BYTE = 1 << 7; // 128
|
||||
constexpr uint32_t VARINT_MAX_2_BYTE = 1 << 14; // 16384
|
||||
|
||||
/// Static encode helpers for generated encode() functions.
|
||||
/// Generated code hoists buffer.pos_ into a local uint8_t *__restrict__ pos,
|
||||
/// then calls these methods which take pos by reference. No struct, no overhead.
|
||||
/// For sub-messages, pos is synced back to buffer before the call and reloaded after.
|
||||
/// Static encode helpers for the generated encode bodies. Each takes the write cursor by value and
|
||||
/// returns it advanced, so outlined calls at -Os chain through the return register instead of a
|
||||
/// stack slot. Helpers without a _force suffix skip fields holding the proto3 default.
|
||||
class ProtoEncode {
|
||||
public:
|
||||
/// Write a multi-byte varint directly through a pos pointer.
|
||||
template<typename T>
|
||||
static inline void encode_varint_raw_loop(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, T value) {
|
||||
[[nodiscard]] static inline uint8_t *encode_varint_raw_loop(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
T value) {
|
||||
do {
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
|
||||
*pos++ = static_cast<uint8_t>(value | 0x80);
|
||||
@@ -307,48 +325,49 @@ class ProtoEncode {
|
||||
} while (value > 0x7F);
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
|
||||
*pos++ = static_cast<uint8_t>(value);
|
||||
return pos;
|
||||
}
|
||||
static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t value) {
|
||||
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
|
||||
encode_varint_raw(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t value) {
|
||||
if (value < VARINT_MAX_1_BYTE) [[likely]] {
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
|
||||
*pos++ = static_cast<uint8_t>(value);
|
||||
return;
|
||||
return pos;
|
||||
}
|
||||
encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
return encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
}
|
||||
/// Encode a varint that is expected to be 1-2 bytes (e.g. zigzag RSSI, small lengths).
|
||||
static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw_short(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t value) {
|
||||
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
|
||||
encode_varint_raw_short(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t value) {
|
||||
if (value < VARINT_MAX_1_BYTE) [[likely]] {
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
|
||||
*pos++ = static_cast<uint8_t>(value);
|
||||
return;
|
||||
return pos;
|
||||
}
|
||||
if (value < VARINT_MAX_2_BYTE) [[likely]] {
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 2);
|
||||
*pos++ = static_cast<uint8_t>(value | 0x80);
|
||||
*pos++ = static_cast<uint8_t>(value >> 7);
|
||||
return;
|
||||
return pos;
|
||||
}
|
||||
encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
return encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
}
|
||||
static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw_64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint64_t value) {
|
||||
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
|
||||
encode_varint_raw_64(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint64_t value) {
|
||||
if (value < VARINT_MAX_1_BYTE) [[likely]] {
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
|
||||
*pos++ = static_cast<uint8_t>(value);
|
||||
return;
|
||||
return pos;
|
||||
}
|
||||
encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
return encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
}
|
||||
/// Encode a 48-bit MAC address (stored in a uint64) as varint.
|
||||
/// Real MAC addresses occupy the full 48 bits (OUI in upper 24), so the
|
||||
/// fast path -- any non-zero bit in the top 6 of 48 -- emits exactly 7 bytes
|
||||
/// with no per-byte branch. Falls back to the general loop otherwise.
|
||||
/// Caller must guarantee value fits in 48 bits (checked in debug builds).
|
||||
static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw_48bit(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint64_t value) {
|
||||
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
|
||||
encode_varint_raw_48bit(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint64_t value) {
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
assert(value < (1ULL << (MAC_ADDRESS_SIZE * 8)) && "encode_varint_raw_48bit: value exceeds 48 bits");
|
||||
#endif
|
||||
@@ -363,38 +382,39 @@ class ProtoEncode {
|
||||
pos[4] = static_cast<uint8_t>((value >> 28) | 0x80);
|
||||
pos[5] = static_cast<uint8_t>((value >> 35) | 0x80);
|
||||
pos[6] = static_cast<uint8_t>(value >> 42);
|
||||
pos += 7;
|
||||
return;
|
||||
return pos + 7;
|
||||
}
|
||||
encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
return encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
}
|
||||
static inline void ESPHOME_ALWAYS_INLINE encode_field_raw(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, uint32_t type) {
|
||||
encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, (field_id << 3) | type);
|
||||
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
|
||||
encode_field_raw(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, uint32_t type) {
|
||||
return encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, proto_tag(field_id, type));
|
||||
}
|
||||
/// Write a single precomputed tag byte. Tag must be < 128.
|
||||
static inline void ESPHOME_ALWAYS_INLINE write_raw_byte(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint8_t b) {
|
||||
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
|
||||
write_raw_byte(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t b) {
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
|
||||
*pos++ = b;
|
||||
return pos;
|
||||
}
|
||||
/// Reserve one byte for later backpatch (e.g., sub-message length).
|
||||
/// Advances pos past the reserved byte without writing a value.
|
||||
static inline void ESPHOME_ALWAYS_INLINE reserve_byte(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
|
||||
reserve_byte(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
|
||||
pos++;
|
||||
return pos + 1;
|
||||
}
|
||||
/// Write raw bytes to the buffer (no tag, no length prefix).
|
||||
static inline void ESPHOME_ALWAYS_INLINE encode_raw(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
const void *data, size_t len) {
|
||||
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
|
||||
encode_raw(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, const void *data, size_t len) {
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, len);
|
||||
std::memcpy(pos, data, len);
|
||||
pos += len;
|
||||
return pos + len;
|
||||
}
|
||||
/// Encode tag + 1-byte length + raw string data. For strings with max_data_length < 128.
|
||||
/// Tag must be a single-byte varint (< 128). Always encodes (no zero check).
|
||||
static inline void encode_short_string_force(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag,
|
||||
const StringRef &ref) {
|
||||
[[nodiscard]] static inline uint8_t *encode_short_string_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint8_t tag, const StringRef &ref) {
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
assert(ref.size() < 128 && "encode_short_string_force: string exceeds max_data_length < 128");
|
||||
#endif
|
||||
@@ -402,137 +422,191 @@ class ProtoEncode {
|
||||
pos[0] = tag;
|
||||
pos[1] = static_cast<uint8_t>(ref.size());
|
||||
std::memcpy(pos + 2, ref.c_str(), ref.size());
|
||||
pos += 2 + ref.size();
|
||||
return pos + 2 + ref.size();
|
||||
}
|
||||
/// Write a precomputed tag byte + 32-bit value in one operation.
|
||||
static inline void ESPHOME_ALWAYS_INLINE write_tag_and_fixed32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint8_t tag, uint32_t value) {
|
||||
/// Write a precomputed tag byte + 32-bit value. Outlined on embedded: one copy beats inline stores per field.
|
||||
[[nodiscard]] static PROTO_OUTLINE_FOR_SIZE uint8_t *write_tag_and_fixed32(
|
||||
uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag, uint32_t value) {
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 5);
|
||||
pos[0] = tag;
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
std::memcpy(pos + 1, &value, 4);
|
||||
#else
|
||||
pos[1] = static_cast<uint8_t>(value & 0xFF);
|
||||
pos[2] = static_cast<uint8_t>((value >> 8) & 0xFF);
|
||||
pos[3] = static_cast<uint8_t>((value >> 16) & 0xFF);
|
||||
pos[4] = static_cast<uint8_t>((value >> 24) & 0xFF);
|
||||
#endif
|
||||
pos += 5;
|
||||
write_fixed32_le(pos + 1, value);
|
||||
return pos + 5;
|
||||
}
|
||||
static inline void encode_string(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
|
||||
const char *string, size_t len, bool force = false) {
|
||||
if (len == 0 && !force)
|
||||
return;
|
||||
encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 2); // type 2: Length-delimited string
|
||||
[[nodiscard]] static inline uint8_t *encode_string_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, const char *string, size_t len) {
|
||||
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 2); // type 2: Length-delimited string
|
||||
// NOLINTNEXTLINE(readability-inconsistent-ifelse-braces) -- false positive on [[likely]] attribute
|
||||
if (len < VARINT_MAX_1_BYTE) [[likely]] {
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 1 + len);
|
||||
*pos++ = static_cast<uint8_t>(len);
|
||||
} else {
|
||||
encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, len);
|
||||
pos = encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, len);
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, len);
|
||||
}
|
||||
std::memcpy(pos, string, len);
|
||||
pos += len;
|
||||
return pos + len;
|
||||
}
|
||||
static inline void encode_string(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
|
||||
const std::string &value, bool force = false) {
|
||||
encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, value.data(), value.size(), force);
|
||||
[[nodiscard]] static inline uint8_t *encode_string(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, const char *string, size_t len) {
|
||||
if (len == 0)
|
||||
return pos;
|
||||
return encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, string, len);
|
||||
}
|
||||
static inline void encode_string(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
|
||||
const StringRef &ref, bool force = false) {
|
||||
encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, ref.c_str(), ref.size(), force);
|
||||
[[nodiscard]] static inline uint8_t *encode_string_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, const std::string &value) {
|
||||
return encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value.data(), value.size());
|
||||
}
|
||||
static inline void encode_bytes(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
|
||||
const uint8_t *data, size_t len, bool force = false) {
|
||||
encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, reinterpret_cast<const char *>(data), len, force);
|
||||
[[nodiscard]] static inline uint8_t *encode_string(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, const StringRef &ref) {
|
||||
return encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, ref.c_str(), ref.size());
|
||||
}
|
||||
static inline void encode_uint32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
|
||||
uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
|
||||
encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
[[nodiscard]] static inline uint8_t *encode_string_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, const StringRef &ref) {
|
||||
return encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, ref.c_str(), ref.size());
|
||||
}
|
||||
static inline void encode_uint64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
|
||||
uint64_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
|
||||
encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
[[nodiscard]] static inline uint8_t *encode_bytes(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, const uint8_t *data, size_t len) {
|
||||
return encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, reinterpret_cast<const char *>(data), len);
|
||||
}
|
||||
static inline void encode_bool(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, bool value,
|
||||
bool force = false) {
|
||||
if (!value && !force)
|
||||
return;
|
||||
encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
|
||||
[[nodiscard]] static inline uint8_t *encode_bytes_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, const uint8_t *data, size_t len) {
|
||||
return encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, reinterpret_cast<const char *>(data), len);
|
||||
}
|
||||
[[nodiscard]] static inline uint8_t *encode_uint32_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, uint32_t value) {
|
||||
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
|
||||
return encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
}
|
||||
[[nodiscard]] static inline uint8_t *encode_uint32(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, uint32_t value) {
|
||||
if (value == 0)
|
||||
return pos;
|
||||
return encode_uint32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value);
|
||||
}
|
||||
[[nodiscard]] static inline uint8_t *encode_uint64_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, uint64_t value) {
|
||||
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
|
||||
return encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
}
|
||||
[[nodiscard]] static inline uint8_t *encode_uint64(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, uint64_t value) {
|
||||
if (value == 0)
|
||||
return pos;
|
||||
return encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value);
|
||||
}
|
||||
[[nodiscard]] static inline uint8_t *encode_bool_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, bool value) {
|
||||
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
|
||||
*pos++ = value ? 0x01 : 0x00;
|
||||
return pos;
|
||||
}
|
||||
static inline void encode_fixed32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
|
||||
uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 5);
|
||||
[[nodiscard]] static inline uint8_t *encode_bool(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, bool value) {
|
||||
if (!value)
|
||||
return pos;
|
||||
return encode_bool_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value);
|
||||
}
|
||||
/// Tag + fixed32 for multi-byte tags; single-byte tags use write_tag_and_fixed32.
|
||||
[[nodiscard]] static PROTO_OUTLINE_FOR_SIZE uint8_t *encode_fixed32_force(
|
||||
uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, uint32_t value) {
|
||||
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 5);
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 4);
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
std::memcpy(pos, &value, 4);
|
||||
pos += 4;
|
||||
#else
|
||||
*pos++ = (value >> 0) & 0xFF;
|
||||
*pos++ = (value >> 8) & 0xFF;
|
||||
*pos++ = (value >> 16) & 0xFF;
|
||||
*pos++ = (value >> 24) & 0xFF;
|
||||
#endif
|
||||
write_fixed32_le(pos, value);
|
||||
return pos + 4;
|
||||
}
|
||||
[[nodiscard]] static inline uint8_t *encode_fixed32(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, uint32_t value) {
|
||||
if (value == 0)
|
||||
return pos;
|
||||
return encode_fixed32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value);
|
||||
}
|
||||
// NOTE: Wire type 1 (64-bit fixed: double, fixed64, sfixed64) is intentionally
|
||||
// not supported to reduce overhead on embedded systems. All ESPHome devices are
|
||||
// 32-bit microcontrollers where 64-bit operations are expensive. If 64-bit support
|
||||
// is needed in the future, the necessary encoding/decoding functions must be added.
|
||||
static inline void encode_float(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, float value,
|
||||
bool force = false) {
|
||||
uint32_t raw = float_to_raw(value);
|
||||
if (raw == 0 && !force)
|
||||
return;
|
||||
encode_fixed32(pos PROTO_ENCODE_DEBUG_ARG, field_id, raw);
|
||||
[[nodiscard]] static inline uint8_t *encode_float(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, float value) {
|
||||
return encode_fixed32(pos PROTO_ENCODE_DEBUG_ARG, field_id, float_to_raw(value));
|
||||
}
|
||||
static inline void encode_int32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, int32_t value,
|
||||
bool force = false) {
|
||||
[[nodiscard]] static inline uint8_t *encode_float_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, float value) {
|
||||
return encode_fixed32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, float_to_raw(value));
|
||||
}
|
||||
[[nodiscard]] static inline uint8_t *encode_int32_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, int32_t value) {
|
||||
if (value < 0) {
|
||||
// negative int32 is always 10 byte long
|
||||
encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint64_t>(value), force);
|
||||
return;
|
||||
return encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint64_t>(value));
|
||||
}
|
||||
encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint32_t>(value), force);
|
||||
return encode_uint32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint32_t>(value));
|
||||
}
|
||||
static inline void encode_int64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, int64_t value,
|
||||
bool force = false) {
|
||||
encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint64_t>(value), force);
|
||||
[[nodiscard]] static inline uint8_t *encode_int32(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, int32_t value) {
|
||||
if (value == 0)
|
||||
return pos;
|
||||
return encode_int32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value);
|
||||
}
|
||||
static inline void encode_sint32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
|
||||
int32_t value, bool force = false) {
|
||||
encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag32(value), force);
|
||||
[[nodiscard]] static inline uint8_t *encode_int64(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, int64_t value) {
|
||||
return encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint64_t>(value));
|
||||
}
|
||||
static inline void encode_sint64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
|
||||
int64_t value, bool force = false) {
|
||||
encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag64(value), force);
|
||||
[[nodiscard]] static inline uint8_t *encode_int64_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, int64_t value) {
|
||||
return encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint64_t>(value));
|
||||
}
|
||||
/// Sub-message encoding: sync pos to buffer, delegate, get pos from return value.
|
||||
[[nodiscard]] static inline uint8_t *encode_sint32(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, int32_t value) {
|
||||
return encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag32(value));
|
||||
}
|
||||
[[nodiscard]] static inline uint8_t *encode_sint32_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, int32_t value) {
|
||||
return encode_uint32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag32(value));
|
||||
}
|
||||
[[nodiscard]] static inline uint8_t *encode_sint64(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, int64_t value) {
|
||||
return encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag64(value));
|
||||
}
|
||||
[[nodiscard]] static inline uint8_t *encode_sint64_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, int64_t value) {
|
||||
return encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag64(value));
|
||||
}
|
||||
/// Sub-message encoding: sync pos to buffer, delegate, read the cursor back.
|
||||
template<typename T>
|
||||
static inline void encode_sub_message(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, ProtoWriteBuffer &buffer,
|
||||
uint32_t field_id, const T &value) {
|
||||
[[nodiscard]] static inline uint8_t *encode_sub_message(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
ProtoWriteBuffer &buffer, uint32_t field_id, const T &value) {
|
||||
buffer.set_pos(pos);
|
||||
buffer.encode_sub_message(field_id, value);
|
||||
pos = buffer.get_pos();
|
||||
return buffer.get_pos();
|
||||
}
|
||||
template<typename T>
|
||||
static inline void encode_optional_sub_message(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
ProtoWriteBuffer &buffer, uint32_t field_id, const T &value) {
|
||||
[[nodiscard]] static inline uint8_t *encode_optional_sub_message(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
ProtoWriteBuffer &buffer, uint32_t field_id,
|
||||
const T &value) {
|
||||
buffer.set_pos(pos);
|
||||
buffer.encode_optional_sub_message(field_id, value);
|
||||
pos = buffer.get_pos();
|
||||
return buffer.get_pos();
|
||||
}
|
||||
|
||||
private:
|
||||
/// Unaligned little endian store of four bytes: byte stores where the outlined helper lives (ESP-IDF, ARM
|
||||
/// without unaligned access), otherwise a memcpy the compiler folds into one store. Callers bounds check
|
||||
/// and advance the cursor themselves.
|
||||
static inline void ESPHOME_ALWAYS_INLINE write_fixed32_le(uint8_t *__restrict__ pos, uint32_t value) {
|
||||
if constexpr (PROTO_FIXED32_BYTE_STORES) {
|
||||
// Spelled out so the outlined helper does not itself become a memcpy call
|
||||
pos[0] = static_cast<uint8_t>(value);
|
||||
pos[1] = static_cast<uint8_t>(value >> 8);
|
||||
pos[2] = static_cast<uint8_t>(value >> 16);
|
||||
pos[3] = static_cast<uint8_t>(value >> 24);
|
||||
} else {
|
||||
const uint32_t le = convert_little_endian(value);
|
||||
__builtin_memcpy(pos, &le, 4);
|
||||
}
|
||||
}
|
||||
};
|
||||
#undef PROTO_OUTLINE_FOR_SIZE
|
||||
#undef PROTO_FIXED32_BYTE_STORES
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
/**
|
||||
@@ -624,11 +698,12 @@ class DumpBuffer {
|
||||
|
||||
class ProtoMessage {
|
||||
public:
|
||||
// Non-virtual defaults for messages with no fields.
|
||||
// Concrete message classes hide these with their own implementations.
|
||||
// All call sites use templates to preserve the concrete type, so virtual
|
||||
// dispatch is not needed. This eliminates per-message vtable entries for
|
||||
// encode/calculate_size, saving ~1.3 KB of flash across all message types.
|
||||
// Non-virtual defaults for messages with no fields; generated classes hide all four. The
|
||||
// static encode_msg/calc_size_msg take const void * so &T::encode_msg needs no thunk.
|
||||
static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) {
|
||||
return buffer.get_pos();
|
||||
}
|
||||
static uint32_t calc_size_msg(const void *self) { return 0; }
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return buffer.get_pos(); }
|
||||
uint32_t calculate_size() const { return 0; }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -663,10 +738,10 @@ class ProtoDecodableMessage : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
~ProtoDecodableMessage() = default;
|
||||
virtual bool decode_varint(uint32_t field_id, proto_varint_value_t value) { return false; }
|
||||
virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; }
|
||||
virtual bool decode_32bit(uint32_t field_id, Proto32Bit value) { return false; }
|
||||
// NOTE: decode_64bit removed - wire type 1 not supported
|
||||
/// Store one decoded field; \p scalar is the varint or fixed32 value, or the length of the
|
||||
/// length-delimited payload at \p data. An unknown field or wrong wire type matches no case and is skipped.
|
||||
/// Three register arguments keep the decode loop free of spills.
|
||||
virtual void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) {}
|
||||
};
|
||||
|
||||
class ProtoSize {
|
||||
@@ -792,7 +867,7 @@ class ProtoSize {
|
||||
* @return The number of bytes needed to encode the field ID and wire type
|
||||
*/
|
||||
static constexpr uint32_t field(uint32_t field_id, uint32_t type) {
|
||||
uint32_t tag = (field_id << 3) | (type & WIRE_TYPE_MASK);
|
||||
uint32_t tag = proto_tag(field_id, type & WIRE_TYPE_MASK);
|
||||
return varint(tag);
|
||||
}
|
||||
|
||||
@@ -876,24 +951,14 @@ class ProtoSize {
|
||||
|
||||
// Implementation of methods that depend on ProtoSize being fully defined
|
||||
|
||||
// Encode thunk — converts void* back to concrete type for direct encode() call
|
||||
template<typename T> uint8_t *proto_encode_msg(const void *msg, ProtoWriteBuffer &buf PROTO_ENCODE_DEBUG_PARAM) {
|
||||
return static_cast<const T *>(msg)->encode(buf PROTO_ENCODE_DEBUG_ARG);
|
||||
}
|
||||
|
||||
// Thin template wrapper; delegates to non-template core in proto.cpp.
|
||||
template<typename T> inline void ProtoWriteBuffer::encode_sub_message(uint32_t field_id, const T &value) {
|
||||
this->encode_sub_message(field_id, &value, &proto_encode_msg<T>);
|
||||
this->encode_sub_message(field_id, &value, &T::encode_msg);
|
||||
}
|
||||
|
||||
// Thin template wrapper; delegates to non-template core.
|
||||
template<typename T> inline void ProtoWriteBuffer::encode_optional_sub_message(uint32_t field_id, const T &value) {
|
||||
this->encode_optional_sub_message(field_id, value.calculate_size(), &value, &proto_encode_msg<T>);
|
||||
}
|
||||
|
||||
// Template decode_to_message - preserves concrete type so decode() resolves statically
|
||||
template<typename T> void ProtoLengthDelimited::decode_to_message(T &msg) const {
|
||||
msg.decode(this->value_, this->length_);
|
||||
this->encode_optional_sub_message(field_id, T::calc_size_msg(&value), &value, &T::encode_msg);
|
||||
}
|
||||
|
||||
template<typename T> const char *proto_enum_to_string(T value);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components.const import UNIT_COUNTS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_CLEAR,
|
||||
@@ -33,8 +34,6 @@ CONF_F7 = "f7"
|
||||
CONF_F8 = "f8"
|
||||
CONF_NIR = "nir"
|
||||
|
||||
UNIT_COUNTS = "#"
|
||||
|
||||
AS7341_GAIN = as7341_ns.enum("AS7341Gain")
|
||||
GAIN_OPTIONS = {
|
||||
"X0.5": AS7341_GAIN.AS7341_GAIN_0_5X,
|
||||
|
||||
@@ -2,9 +2,8 @@ from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_VOLUME
|
||||
from esphome.core import ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.const import CONF_VOLUME
|
||||
from esphome.core import CoroPriority, coroutine_with_priority
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
@@ -13,10 +12,6 @@ IS_PLATFORM_COMPONENT = True
|
||||
audio_dac_ns = cg.esphome_ns.namespace("audio_dac")
|
||||
AudioDac = audio_dac_ns.class_("AudioDac")
|
||||
|
||||
MuteOffAction = audio_dac_ns.class_("MuteOffAction", automation.Action)
|
||||
MuteOnAction = audio_dac_ns.class_("MuteOnAction", automation.Action)
|
||||
SetVolumeAction = audio_dac_ns.class_("SetVolumeAction", automation.Action)
|
||||
|
||||
|
||||
MUTE_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
@@ -33,41 +28,19 @@ SET_VOLUME_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"audio_dac.mute_off", MuteOffAction, MUTE_ACTION_SCHEMA, synchronous=True
|
||||
)
|
||||
@automation.register_action(
|
||||
"audio_dac.mute_on", MuteOnAction, MUTE_ACTION_SCHEMA, synchronous=True
|
||||
)
|
||||
async def audio_dac_mute_action_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
for _name, _call in (
|
||||
("audio_dac.mute_off", "set_mute_off()"),
|
||||
("audio_dac.mute_on", "set_mute_on()"),
|
||||
):
|
||||
automation.register_apply_action(
|
||||
_name, MUTE_ACTION_SCHEMA, automation.ApplyCall(_call)
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"audio_dac.set_volume",
|
||||
SetVolumeAction,
|
||||
SET_VOLUME_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_VOLUME, "set_volume", cg.float_),
|
||||
)
|
||||
async def audio_dac_set_volume_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config.get(CONF_VOLUME), args, cg.float_)
|
||||
cg.add(var.set_volume(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "audio_dac.h"
|
||||
|
||||
namespace esphome::audio_dac {
|
||||
|
||||
template<typename... Ts> class MuteOffAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit MuteOffAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||
|
||||
void play(const Ts &...x) override { this->audio_dac_->set_mute_off(); }
|
||||
|
||||
protected:
|
||||
AudioDac *audio_dac_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class MuteOnAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit MuteOnAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||
|
||||
void play(const Ts &...x) override { this->audio_dac_->set_mute_on(); }
|
||||
|
||||
protected:
|
||||
AudioDac *audio_dac_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetVolumeAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetVolumeAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||
|
||||
TEMPLATABLE_VALUE(float, volume)
|
||||
|
||||
void play(const Ts &...x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); }
|
||||
|
||||
protected:
|
||||
AudioDac *audio_dac_;
|
||||
};
|
||||
|
||||
} // namespace esphome::audio_dac
|
||||
@@ -1,7 +1,7 @@
|
||||
from logging import getLogger
|
||||
|
||||
from esphome import automation, core
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server, zigbee
|
||||
from esphome.components.const import CONF_ON_STATE_CHANGE
|
||||
@@ -138,9 +138,6 @@ BinarySensorInvalidateAction = binary_sensor_ns.class_(
|
||||
"BinarySensorInvalidateAction", automation.Action
|
||||
)
|
||||
|
||||
# Condition
|
||||
BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition)
|
||||
|
||||
# Filters
|
||||
Filter = binary_sensor_ns.class_("Filter")
|
||||
TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter)
|
||||
@@ -645,20 +642,12 @@ BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"binary_sensor.is_on", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
|
||||
automation.register_apply_condition(
|
||||
"binary_sensor.is_on", BINARY_SENSOR_CONDITION_SCHEMA, "state"
|
||||
)
|
||||
async def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"binary_sensor.is_off", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
|
||||
automation.register_apply_condition(
|
||||
"binary_sensor.is_off", BINARY_SENSOR_CONDITION_SCHEMA, "state == false"
|
||||
)
|
||||
async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -18,26 +18,6 @@ struct MultiClickTriggerEvent {
|
||||
uint32_t max_length;
|
||||
};
|
||||
|
||||
class PressTrigger final : public Trigger<> {
|
||||
public:
|
||||
explicit PressTrigger(BinarySensor *parent) {
|
||||
parent->add_on_state_callback([this](bool state) {
|
||||
if (state)
|
||||
this->trigger();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class ReleaseTrigger final : public Trigger<> {
|
||||
public:
|
||||
explicit ReleaseTrigger(BinarySensor *parent) {
|
||||
parent->add_on_state_callback([this](bool state) {
|
||||
if (!state)
|
||||
this->trigger();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length);
|
||||
|
||||
class ClickTrigger final : public Trigger<> {
|
||||
@@ -140,31 +120,6 @@ template<size_t N> class MultiClickTrigger final : public MultiClickTriggerBase
|
||||
std::array<MultiClickTriggerEvent, N> timing_storage_{};
|
||||
};
|
||||
|
||||
class StateTrigger final : public Trigger<bool> {
|
||||
public:
|
||||
explicit StateTrigger(BinarySensor *parent) {
|
||||
parent->add_on_state_callback([this](bool state) { this->trigger(state); });
|
||||
}
|
||||
};
|
||||
|
||||
class StateChangeTrigger final : public Trigger<optional<bool>, optional<bool> > {
|
||||
public:
|
||||
explicit StateChangeTrigger(BinarySensor *parent) {
|
||||
parent->add_full_state_callback(
|
||||
[this](optional<bool> old_state, optional<bool> state) { this->trigger(old_state, state); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class BinarySensorCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {}
|
||||
bool check(const Ts &...x) override { return this->parent_->state == this->state_; }
|
||||
|
||||
protected:
|
||||
BinarySensor *parent_;
|
||||
bool state_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class BinarySensorInvalidateAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit BinarySensorInvalidateAction(BinarySensor *sensor) : sensor_(sensor) {}
|
||||
|
||||
@@ -16,11 +16,4 @@ template<typename... Ts> class PressAction final : public Action<Ts...> {
|
||||
Button *button_;
|
||||
};
|
||||
|
||||
class ButtonPressTrigger final : public Trigger<> {
|
||||
public:
|
||||
ButtonPressTrigger(Button *button) {
|
||||
button->add_on_press_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::button
|
||||
|
||||
@@ -494,12 +494,6 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
def _literal_with_length(config: ConfigType, value: str) -> str:
|
||||
# The (const char *, size_t) overload compares bytes in place, so it needs a plain
|
||||
# literal rather than the default PROGMEM rendering on ESP8266, and skips a strlen.
|
||||
return f"{cg.safe_exp(value)}, {len(value.encode('utf-8'))}"
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
"climate.control",
|
||||
CLIMATE_CONTROL_ACTION_SCHEMA,
|
||||
@@ -517,14 +511,14 @@ automation.register_apply_action(
|
||||
CONF_CUSTOM_FAN_MODE,
|
||||
"set_fan_mode",
|
||||
cg.std_string,
|
||||
const_fn=_literal_with_length,
|
||||
const_fn=automation.literal_with_length,
|
||||
),
|
||||
automation.ApplyField(CONF_PRESET, "set_preset", ClimatePreset),
|
||||
automation.ApplyField(
|
||||
CONF_CUSTOM_PRESET,
|
||||
"set_preset",
|
||||
cg.std_string,
|
||||
const_fn=_literal_with_length,
|
||||
const_fn=automation.literal_with_length,
|
||||
),
|
||||
automation.ApplyField(CONF_SWING_MODE, "set_swing_mode", ClimateSwingMode),
|
||||
call="make_call",
|
||||
|
||||
@@ -13,17 +13,7 @@ climate::ClimateTraits ClimateIR::traits() {
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY);
|
||||
}
|
||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF});
|
||||
if (this->supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (this->supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
if (this->supports_heat_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
|
||||
if (this->supports_dry_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||
if (this->supports_fan_only_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
traits.set_supported_modes(this->modes_);
|
||||
|
||||
traits.set_visual_min_temperature(this->minimum_temperature_);
|
||||
traits.set_visual_max_temperature(this->maximum_temperature_);
|
||||
@@ -98,8 +88,10 @@ void ClimateIR::dump_config() {
|
||||
" Supports HEAT: %s\n"
|
||||
" Supports COOL: %s\n"
|
||||
" Supports HEAT_COOL: %s",
|
||||
this->minimum_temperature_, this->maximum_temperature_, YESNO(this->supports_heat_),
|
||||
YESNO(this->supports_cool_), YESNO(this->supports_heat_cool_));
|
||||
this->minimum_temperature_, this->maximum_temperature_,
|
||||
YESNO(this->modes_.count(climate::CLIMATE_MODE_HEAT)),
|
||||
YESNO(this->modes_.count(climate::CLIMATE_MODE_COOL)),
|
||||
YESNO(this->modes_.count(climate::CLIMATE_MODE_HEAT_COOL)));
|
||||
}
|
||||
|
||||
} // namespace esphome::climate_ir
|
||||
|
||||
@@ -30,8 +30,10 @@ class ClimateIR : public Component,
|
||||
this->minimum_temperature_ = minimum_temperature;
|
||||
this->maximum_temperature_ = maximum_temperature;
|
||||
this->temperature_step_ = temperature_step;
|
||||
this->supports_dry_ = supports_dry;
|
||||
this->supports_fan_only_ = supports_fan_only;
|
||||
if (supports_dry)
|
||||
this->modes_.insert(climate::CLIMATE_MODE_DRY);
|
||||
if (supports_fan_only)
|
||||
this->modes_.insert(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
this->fan_modes_ = fan_modes;
|
||||
this->swing_modes_ = swing_modes;
|
||||
this->presets_ = presets;
|
||||
@@ -39,9 +41,11 @@ class ClimateIR : public Component,
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||
void set_supports_heat_cool(bool supports_heat_cool) { this->supports_heat_cool_ = supports_heat_cool; }
|
||||
void set_supports_cool(bool supports_cool) { this->set_mode_supported_(climate::CLIMATE_MODE_COOL, supports_cool); }
|
||||
void set_supports_heat(bool supports_heat) { this->set_mode_supported_(climate::CLIMATE_MODE_HEAT, supports_heat); }
|
||||
void set_supports_heat_cool(bool supports_heat_cool) {
|
||||
this->set_mode_supported_(climate::CLIMATE_MODE_HEAT_COOL, supports_heat_cool);
|
||||
}
|
||||
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; }
|
||||
|
||||
@@ -59,12 +63,18 @@ class ClimateIR : public Component,
|
||||
// Dummy implement on_receive so implementation is optional for inheritors
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override { return false; };
|
||||
|
||||
bool supports_cool_{true};
|
||||
bool supports_heat_{true};
|
||||
// Default (supports_cool && supports_heat) is resolved during code generation.
|
||||
bool supports_heat_cool_{true};
|
||||
bool supports_dry_{false};
|
||||
bool supports_fan_only_{false};
|
||||
ESPHOME_ALWAYS_INLINE void set_mode_supported_(climate::ClimateMode mode, bool supported) {
|
||||
if (supported) {
|
||||
this->modes_.insert(mode);
|
||||
} else {
|
||||
this->modes_.erase(mode);
|
||||
}
|
||||
}
|
||||
|
||||
// The HEAT_COOL default (supports_cool && supports_heat) is resolved during code generation.
|
||||
static constexpr climate::ClimateModeMask DEFAULT_MODES{climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
||||
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_HEAT_COOL};
|
||||
climate::ClimateModeMask modes_{DEFAULT_MODES};
|
||||
climate::ClimateFanModeMask fan_modes_{};
|
||||
climate::ClimateSwingModeMask swing_modes_{};
|
||||
climate::ClimatePresetMask presets_{};
|
||||
|
||||
@@ -61,3 +61,4 @@ ICON_SOLAR_POWER = "mdi:solar-power"
|
||||
KEY_METADATA = "metadata"
|
||||
|
||||
UNIT_AMPERE_HOUR = "Ah"
|
||||
UNIT_COUNTS = "#"
|
||||
|
||||
@@ -37,23 +37,6 @@ DEVICE = {
|
||||
"TF_CARD": Device.TF_CARD,
|
||||
}
|
||||
|
||||
NextAction = dfplayer_ns.class_("NextAction", automation.Action)
|
||||
PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action)
|
||||
PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action)
|
||||
PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action)
|
||||
PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action)
|
||||
SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action)
|
||||
VolumeUpAction = dfplayer_ns.class_("VolumeUpAction", automation.Action)
|
||||
VolumeDownAction = dfplayer_ns.class_("VolumeDownAction", automation.Action)
|
||||
SetEqAction = dfplayer_ns.class_("SetEqAction", automation.Action)
|
||||
SleepAction = dfplayer_ns.class_("SleepAction", automation.Action)
|
||||
ResetAction = dfplayer_ns.class_("ResetAction", automation.Action)
|
||||
StartAction = dfplayer_ns.class_("StartAction", automation.Action)
|
||||
PauseAction = dfplayer_ns.class_("PauseAction", automation.Action)
|
||||
StopAction = dfplayer_ns.class_("StopAction", automation.Action)
|
||||
RandomAction = dfplayer_ns.class_("RandomAction", automation.Action)
|
||||
SetDeviceAction = dfplayer_ns.class_("SetDeviceAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -87,41 +70,30 @@ async def to_code(config):
|
||||
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.play_next",
|
||||
NextAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
DFPLAYER_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
)
|
||||
async def dfplayer_next_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
for _name, _call in (
|
||||
("dfplayer.play_next", "next()"),
|
||||
("dfplayer.play_previous", "previous()"),
|
||||
("dfplayer.volume_up", "volume_up()"),
|
||||
("dfplayer.volume_down", "volume_down()"),
|
||||
("dfplayer.sleep", "sleep()"),
|
||||
("dfplayer.reset", "reset()"),
|
||||
("dfplayer.start", "start()"),
|
||||
("dfplayer.pause", "pause()"),
|
||||
("dfplayer.stop", "stop()"),
|
||||
("dfplayer.random", "random()"),
|
||||
):
|
||||
automation.register_apply_action(
|
||||
_name, DFPLAYER_ACTION_SCHEMA, automation.ApplyCall(_call)
|
||||
)
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.play_previous",
|
||||
PreviousAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_previous_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"dfplayer.play_mp3",
|
||||
PlayMp3Action,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
@@ -129,70 +101,43 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args):
|
||||
},
|
||||
key=CONF_FILE,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_FILE, "play_mp3", cg.uint16),
|
||||
)
|
||||
async def dfplayer_play_mp3_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16)
|
||||
cg.add(var.set_file(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
# loop and file default to what the old action's unset templatable values evaluated to
|
||||
automation.register_apply_action(
|
||||
"dfplayer.play",
|
||||
PlayFileAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
cv.Required(CONF_FILE): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_LOOP): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_LOOP, default=False): cv.templatable(cv.boolean),
|
||||
},
|
||||
key=CONF_FILE,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall(
|
||||
"play_file({}, {})", ((CONF_FILE, cg.uint16), (CONF_LOOP, cg.bool_))
|
||||
),
|
||||
)
|
||||
async def dfplayer_play_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16)
|
||||
cg.add(var.set_file(template_))
|
||||
if CONF_LOOP in config:
|
||||
template_ = await cg.templatable(config[CONF_LOOP], args, cg.bool_)
|
||||
cg.add(var.set_loop(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"dfplayer.play_folder",
|
||||
PlayFolderAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
cv.Required(CONF_FOLDER): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_FILE): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_LOOP): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_FILE, default=0): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_LOOP, default=False): cv.templatable(cv.boolean),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall(
|
||||
"play_folder({}, {}, {})",
|
||||
((CONF_FOLDER, cg.uint16), (CONF_FILE, cg.uint16), (CONF_LOOP, cg.bool_)),
|
||||
),
|
||||
)
|
||||
async def dfplayer_play_folder_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_FOLDER], args, cg.uint16)
|
||||
cg.add(var.set_folder(template_))
|
||||
if CONF_FILE in config:
|
||||
template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16)
|
||||
cg.add(var.set_file(template_))
|
||||
if CONF_LOOP in config:
|
||||
template_ = await cg.templatable(config[CONF_LOOP], args, cg.bool_)
|
||||
cg.add(var.set_loop(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"dfplayer.set_device",
|
||||
SetDeviceAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
@@ -200,19 +145,11 @@ async def dfplayer_play_folder_to_code(config, action_id, template_arg, args):
|
||||
},
|
||||
key=CONF_DEVICE,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_DEVICE, "set_device", Device),
|
||||
)
|
||||
async def dfplayer_set_device_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_DEVICE], args, Device)
|
||||
cg.add(var.set_device(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"dfplayer.set_volume",
|
||||
SetVolumeAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
@@ -220,51 +157,11 @@ async def dfplayer_set_device_to_code(config, action_id, template_arg, args):
|
||||
},
|
||||
key=CONF_VOLUME,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_VOLUME, "set_volume", cg.uint8),
|
||||
)
|
||||
async def dfplayer_set_volume_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.uint8)
|
||||
cg.add(var.set_volume(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.volume_up",
|
||||
VolumeUpAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_volume_up_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.volume_down",
|
||||
VolumeDownAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_volume_down_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"dfplayer.set_eq",
|
||||
SetEqAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
@@ -272,110 +169,8 @@ async def dfplayer_volume_down_to_code(config, action_id, template_arg, args):
|
||||
},
|
||||
key=CONF_EQ_PRESET,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_EQ_PRESET, "set_eq", EqPreset),
|
||||
)
|
||||
async def dfplayer_set_eq_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_EQ_PRESET], args, EqPreset)
|
||||
cg.add(var.set_eq(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.sleep",
|
||||
SleepAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_sleep_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.reset",
|
||||
ResetAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_reset_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.start",
|
||||
StartAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_start_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.pause",
|
||||
PauseAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_pause_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.stop",
|
||||
StopAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_stop_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.random",
|
||||
RandomAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_random_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
def _default_enable(value: Any) -> Any:
|
||||
|
||||
@@ -33,8 +33,13 @@ class DFPlayer final : public uart::UARTDevice, public Component {
|
||||
void play_mp3(uint16_t file);
|
||||
void play_file(uint16_t file);
|
||||
void play_file_loop(uint16_t file);
|
||||
void play_file(uint16_t file, bool loop) { loop ? this->play_file_loop(file) : this->play_file(file); }
|
||||
void play_folder(uint16_t folder, uint16_t file);
|
||||
void play_folder_loop(uint16_t folder);
|
||||
// The loop command plays the whole folder, so file is ignored when loop is set.
|
||||
void play_folder(uint16_t folder, uint16_t file, bool loop) {
|
||||
loop ? this->play_folder_loop(folder) : this->play_folder(folder, file);
|
||||
}
|
||||
void volume_up();
|
||||
void volume_down();
|
||||
void set_device(Device device);
|
||||
@@ -72,100 +77,6 @@ class DFPlayer final : public uart::UARTDevice, public Component {
|
||||
CallbackManager<void()> on_finished_playback_callback_;
|
||||
};
|
||||
|
||||
#define DFPLAYER_SIMPLE_ACTION(ACTION_CLASS, ACTION_METHOD) \
|
||||
template<typename... Ts> \
|
||||
class ACTION_CLASS : /* NOLINT */ \
|
||||
public Action<Ts...>, \
|
||||
public Parented<DFPlayer> { \
|
||||
void play(const Ts &...x) override { this->parent_->ACTION_METHOD(); } \
|
||||
};
|
||||
|
||||
DFPLAYER_SIMPLE_ACTION(NextAction, next)
|
||||
DFPLAYER_SIMPLE_ACTION(PreviousAction, previous)
|
||||
|
||||
template<typename... Ts> class PlayMp3Action final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto file = this->file_.value(x...);
|
||||
this->parent_->play_mp3(file);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class PlayFileAction final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
TEMPLATABLE_VALUE(bool, loop)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto file = this->file_.value(x...);
|
||||
auto loop = this->loop_.value(x...);
|
||||
if (loop) {
|
||||
this->parent_->play_file_loop(file);
|
||||
} else {
|
||||
this->parent_->play_file(file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class PlayFolderAction final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, folder)
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
TEMPLATABLE_VALUE(bool, loop)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto folder = this->folder_.value(x...);
|
||||
auto file = this->file_.value(x...);
|
||||
auto loop = this->loop_.value(x...);
|
||||
if (loop) {
|
||||
this->parent_->play_folder_loop(folder);
|
||||
} else {
|
||||
this->parent_->play_folder(folder, file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetDeviceAction final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(Device, device)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto device = this->device_.value(x...);
|
||||
this->parent_->set_device(device);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetVolumeAction final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, volume)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto volume = this->volume_.value(x...);
|
||||
this->parent_->set_volume(volume);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetEqAction final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(EqPreset, eq)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto eq = this->eq_.value(x...);
|
||||
this->parent_->set_eq(eq);
|
||||
}
|
||||
};
|
||||
|
||||
DFPLAYER_SIMPLE_ACTION(SleepAction, sleep)
|
||||
DFPLAYER_SIMPLE_ACTION(ResetAction, reset)
|
||||
DFPLAYER_SIMPLE_ACTION(StartAction, start)
|
||||
DFPLAYER_SIMPLE_ACTION(PauseAction, pause)
|
||||
DFPLAYER_SIMPLE_ACTION(StopAction, stop)
|
||||
DFPLAYER_SIMPLE_ACTION(RandomAction, random)
|
||||
DFPLAYER_SIMPLE_ACTION(VolumeUpAction, volume_up)
|
||||
DFPLAYER_SIMPLE_ACTION(VolumeDownAction, volume_down)
|
||||
|
||||
template<typename... Ts> class DFPlayerIsPlayingCondition final : public Condition<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_playing(); }
|
||||
|
||||
@@ -3,7 +3,7 @@ import encodings
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32_ble
|
||||
from esphome.components.const import CONF_MANUFACTURER
|
||||
from esphome.components.const import CONF_DESCRIPTION, CONF_MANUFACTURER
|
||||
from esphome.components.esp32 import request_bluetooth
|
||||
from esphome.components.esp32_ble import BTLoggers, bt_uuid
|
||||
import esphome.config_validation as cv
|
||||
@@ -37,7 +37,6 @@ CONF_ADVERTISE = "advertise"
|
||||
CONF_APPEARANCE = "appearance"
|
||||
CONF_BROADCAST = "broadcast"
|
||||
CONF_CHARACTERISTICS = "characteristics"
|
||||
CONF_DESCRIPTION = "description"
|
||||
CONF_DESCRIPTORS = "descriptors"
|
||||
CONF_ENDIANNESS = "endianness"
|
||||
CONF_FIRMWARE_VERSION = "firmware_version"
|
||||
|
||||
@@ -133,10 +133,17 @@ TRIGGER_EVENT_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
def _event_type_literal(config: ConfigType, value: str) -> str:
|
||||
"""A constant event type is a plain literal; trigger() only compares it, so no copy is needed."""
|
||||
return str(cg.safe_exp(value))
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
"event.trigger",
|
||||
TRIGGER_EVENT_SCHEMA,
|
||||
automation.ApplyField(CONF_EVENT_TYPE, "trigger", cg.std_string),
|
||||
automation.ApplyField(
|
||||
CONF_EVENT_TYPE, "trigger", cg.std_string, _event_type_literal
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/event/event.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome::event {
|
||||
|
||||
class EventTrigger final : public Trigger<StringRef> {
|
||||
public:
|
||||
EventTrigger(Event *event) {
|
||||
event->add_on_event_callback([this](StringRef event_type) { this->trigger(event_type); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::event
|
||||
@@ -7,17 +7,17 @@ namespace esphome::event {
|
||||
|
||||
static const char *const TAG = "event";
|
||||
|
||||
void Event::trigger(const std::string &event_type) {
|
||||
void Event::trigger(const char *event_type) {
|
||||
// Linear search with strcmp - faster than std::set for small datasets (1-5 items typical)
|
||||
const char *found = nullptr;
|
||||
for (const char *type : this->types_) {
|
||||
if (strcmp(type, event_type.c_str()) == 0) {
|
||||
if (strcmp(type, event_type) == 0) {
|
||||
found = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == nullptr) {
|
||||
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
|
||||
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type);
|
||||
return;
|
||||
}
|
||||
this->last_event_type_ = found;
|
||||
@@ -36,12 +36,4 @@ void Event::set_event_types(const FixedVector<const char *> &event_types) {
|
||||
this->last_event_type_ = nullptr; // Reset when types change
|
||||
}
|
||||
|
||||
void Event::set_event_types(const std::vector<const char *> &event_types) {
|
||||
this->types_.init(event_types.size());
|
||||
for (const char *type : event_types) {
|
||||
this->types_.push_back(type);
|
||||
}
|
||||
this->last_event_type_ = nullptr; // Reset when types change
|
||||
}
|
||||
|
||||
} // namespace esphome::event
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
@@ -21,22 +20,21 @@ namespace esphome::event {
|
||||
|
||||
class Event : public EntityBase {
|
||||
public:
|
||||
void trigger(const std::string &event_type);
|
||||
/// Trigger an event; the type is matched against the configured types by string compare.
|
||||
void trigger(const char *event_type);
|
||||
void trigger(const std::string &event_type) { this->trigger(event_type.c_str()); }
|
||||
|
||||
/// Set the event types supported by this event (from initializer list).
|
||||
/// Set the event types supported by this event; called by generated code with string literals.
|
||||
void set_event_types(std::initializer_list<const char *> event_types) {
|
||||
this->types_ = event_types;
|
||||
this->last_event_type_ = nullptr; // Reset when types change
|
||||
}
|
||||
/// Set the event types supported by this event (from FixedVector).
|
||||
/// Copy the event types of another event, for components that wrap one.
|
||||
void set_event_types(const FixedVector<const char *> &event_types);
|
||||
/// Set the event types supported by this event (from vector).
|
||||
void set_event_types(const std::vector<const char *> &event_types);
|
||||
|
||||
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
|
||||
void set_event_types(std::initializer_list<std::string> event_types) = delete;
|
||||
void set_event_types(const FixedVector<std::string> &event_types) = delete;
|
||||
void set_event_types(const std::vector<std::string> &event_types) = delete;
|
||||
|
||||
/// Return the event types supported by this event.
|
||||
const FixedVector<const char *> &get_event_types() const { return this->types_; }
|
||||
|
||||
@@ -83,9 +83,6 @@ FanPresetSetTrigger = fan_ns.class_(
|
||||
"FanPresetSetTrigger", automation.Trigger.template(cg.StringRef)
|
||||
)
|
||||
|
||||
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
|
||||
FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template())
|
||||
|
||||
_FAN_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
@@ -367,27 +364,16 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"fan.is_on",
|
||||
FanIsOnCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Fan),
|
||||
}
|
||||
),
|
||||
FAN_CONDITION_SCHEMA = automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Fan),
|
||||
}
|
||||
)
|
||||
@automation.register_condition(
|
||||
"fan.is_off",
|
||||
FanIsOffCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Fan),
|
||||
}
|
||||
),
|
||||
|
||||
automation.register_apply_condition("fan.is_on", FAN_CONDITION_SCHEMA, "state")
|
||||
automation.register_apply_condition(
|
||||
"fan.is_off", FAN_CONDITION_SCHEMA, "state == false"
|
||||
)
|
||||
async def fan_is_on_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -68,23 +68,6 @@ template<typename... Ts> class CycleSpeedAction final : public Action<Ts...> {
|
||||
Fan *state_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class FanIsOnCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
explicit FanIsOnCondition(Fan *state) : state_(state) {}
|
||||
bool check(const Ts &...x) override { return this->state_->state; }
|
||||
|
||||
protected:
|
||||
Fan *state_;
|
||||
};
|
||||
template<typename... Ts> class FanIsOffCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
explicit FanIsOffCondition(Fan *state) : state_(state) {}
|
||||
bool check(const Ts &...x) override { return !this->state_->state; }
|
||||
|
||||
protected:
|
||||
Fan *state_;
|
||||
};
|
||||
|
||||
class FanStateTrigger final : public Trigger<Fan *> {
|
||||
public:
|
||||
FanStateTrigger(Fan *state) : fan_(state) {
|
||||
|
||||
@@ -22,6 +22,7 @@ from esphome.const import (
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HERTZ,
|
||||
UNIT_KILOWATT_HOURS,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
)
|
||||
@@ -33,7 +34,6 @@ CONF_TOTAL_GENERATION_TIME = "total_generation_time"
|
||||
CONF_TODAY_GENERATION_TIME = "today_generation_time"
|
||||
CONF_PV1 = "pv1"
|
||||
CONF_PV2 = "pv2"
|
||||
UNIT_KILOWATT_HOURS = "kWh"
|
||||
UNIT_HOURS = "h"
|
||||
UNIT_KOHM = "kΩ"
|
||||
UNIT_MILLIAMPERE = "mA"
|
||||
|
||||
@@ -23,6 +23,7 @@ from esphome.const import (
|
||||
UNIT_AMPERE,
|
||||
UNIT_DEGREES,
|
||||
UNIT_HERTZ,
|
||||
UNIT_KILOWATT_HOURS,
|
||||
UNIT_MINUTE,
|
||||
UNIT_VOLT,
|
||||
UNIT_VOLT_AMPS_REACTIVE,
|
||||
@@ -36,7 +37,6 @@ CONF_TOTAL_GENERATION_TIME = "total_generation_time"
|
||||
CONF_TODAY_GENERATION_TIME = "today_generation_time"
|
||||
CONF_PV1 = "pv1"
|
||||
CONF_PV2 = "pv2"
|
||||
UNIT_KILOWATT_HOURS = "kWh"
|
||||
UNIT_HOURS = "h"
|
||||
UNIT_KOHM = "kΩ"
|
||||
UNIT_MILLIAMPERE = "mA"
|
||||
|
||||
@@ -24,6 +24,7 @@ from esphome.const import (
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_MILLIVOLT,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
@@ -44,7 +45,6 @@ CONF_TEMPERATURE_COEFFICIENT = "temperature_coefficient"
|
||||
CONF_RESET_ON_BOOT = "reset_on_boot"
|
||||
UNIT_COULOMB = "C"
|
||||
UNIT_JOULE = "J"
|
||||
UNIT_MILLIVOLT = "mV"
|
||||
|
||||
ina2xx_base_ns = cg.esphome_ns.namespace("ina2xx_base")
|
||||
INA2XX = ina2xx_base_ns.class_("INA2XX", cg.PollingComponent)
|
||||
|
||||
@@ -343,6 +343,7 @@ RESTORE_MODES = {
|
||||
# Schema default that also matches the C++ initializer in light_state.h; codegen
|
||||
# skips the setter when the config equals it.
|
||||
DEFAULT_FLASH_TRANSITION_LENGTH = "0s"
|
||||
CONF_TRANSITION_STATE_PUBLISH_INTERVAL = "transition_state_publish_interval"
|
||||
|
||||
LIGHT_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
@@ -393,6 +394,11 @@ BRIGHTNESS_ONLY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend(
|
||||
cv.Optional(
|
||||
CONF_FLASH_TRANSITION_LENGTH, default=DEFAULT_FLASH_TRANSITION_LENGTH
|
||||
): cv.positive_time_period_milliseconds,
|
||||
# Below 150ms a device cannot publish any faster and only spends CPU and traffic
|
||||
cv.Optional(CONF_TRANSITION_STATE_PUBLISH_INTERVAL): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(min=cv.TimePeriod(milliseconds=150)),
|
||||
),
|
||||
cv.Optional(CONF_EFFECTS): validate_effects(MONOCHROMATIC_EFFECTS),
|
||||
}
|
||||
)
|
||||
@@ -406,6 +412,11 @@ RGB_LIGHT_SCHEMA = BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(
|
||||
ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AddressableLightState),
|
||||
# The addressable transformer writes the LED buffer directly, so there is no
|
||||
# intermediate state to publish
|
||||
cv.Optional(CONF_TRANSITION_STATE_PUBLISH_INTERVAL): cv.invalid(
|
||||
"transition_state_publish_interval is not supported on addressable lights"
|
||||
),
|
||||
cv.Optional(CONF_EFFECTS): validate_effects(ADDRESSABLE_EFFECTS),
|
||||
cv.Optional(CONF_COLOR_CORRECT): cv.All(
|
||||
[cv.percentage], cv.Length(min=3, max=4)
|
||||
@@ -513,6 +524,10 @@ async def setup_light_core_(light_var, config, output_var):
|
||||
DEFAULT_FLASH_TRANSITION_LENGTH
|
||||
):
|
||||
cg.add(light_var.set_flash_transition_length(flash_transition_length))
|
||||
# Setting an interval opts this light in and compiles the feature in
|
||||
if (interval := config.get(CONF_TRANSITION_STATE_PUBLISH_INTERVAL)) is not None:
|
||||
cg.add(light_var.set_transition_state_publish_interval(interval))
|
||||
cg.add_define("USE_LIGHT_TRANSITION_PUBLISH_INTERVAL")
|
||||
if (gamma_correct := config.get(CONF_GAMMA_CORRECT)) is not None:
|
||||
cg.add(light_var.set_gamma_correct(gamma_correct))
|
||||
fwd_arr = _get_or_create_gamma_table(gamma_correct)
|
||||
|
||||
@@ -117,23 +117,6 @@ template<bool Forward, typename... Ts> class LightEffectCycleAction final : publ
|
||||
bool include_none_{false};
|
||||
};
|
||||
|
||||
template<typename... Ts> class LightIsOnCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
explicit LightIsOnCondition(LightState *state) : state_(state) {}
|
||||
bool check(const Ts &...x) override { return this->state_->current_values.is_on(); }
|
||||
|
||||
protected:
|
||||
LightState *state_;
|
||||
};
|
||||
template<typename... Ts> class LightIsOffCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
explicit LightIsOffCondition(LightState *state) : state_(state) {}
|
||||
bool check(const Ts &...x) override { return !this->state_->current_values.is_on(); }
|
||||
|
||||
protected:
|
||||
LightState *state_;
|
||||
};
|
||||
|
||||
class LightTurnOnTrigger final : public Trigger<>, public LightRemoteValuesListener {
|
||||
public:
|
||||
explicit LightTurnOnTrigger(LightState *a_light) : light_(a_light) {
|
||||
|
||||
@@ -38,8 +38,6 @@ from .types import (
|
||||
ColorMode,
|
||||
DimRelativeAction,
|
||||
LightEffectCycleAction,
|
||||
LightIsOffCondition,
|
||||
LightIsOnCondition,
|
||||
LightState,
|
||||
ToggleAction,
|
||||
)
|
||||
@@ -381,24 +379,15 @@ async def light_addressable_set_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"light.is_on",
|
||||
LightIsOnCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LightState),
|
||||
}
|
||||
),
|
||||
LIGHT_CONDITION_SCHEMA = automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LightState),
|
||||
}
|
||||
)
|
||||
@automation.register_condition(
|
||||
"light.is_off",
|
||||
LightIsOffCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LightState),
|
||||
}
|
||||
),
|
||||
|
||||
automation.register_apply_condition(
|
||||
"light.is_on", LIGHT_CONDITION_SCHEMA, "current_values.is_on()"
|
||||
)
|
||||
automation.register_apply_condition(
|
||||
"light.is_off", LIGHT_CONDITION_SCHEMA, "current_values.is_on() == false"
|
||||
)
|
||||
async def light_is_on_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
|
||||
@@ -32,7 +32,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
||||
root[ESPHOME_F("effect_count")] = state.get_effect_count();
|
||||
}
|
||||
|
||||
auto values = state.remote_values;
|
||||
auto values = state.get_reported_values();
|
||||
|
||||
const auto color_mode = values.get_color_mode();
|
||||
const auto *mode_str = get_color_mode_json_str(color_mode);
|
||||
@@ -131,6 +131,13 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
|
||||
call.set_white(float(root[ESPHOME_F("white_value")]) / 255.0f);
|
||||
}
|
||||
|
||||
if (root[ESPHOME_F("white")].is<uint8_t>()) {
|
||||
// White stays full because ESPHome multiplies brightness and white
|
||||
call.set_color_mode_if_supported(ColorMode::WHITE);
|
||||
call.set_brightness(float(root[ESPHOME_F("white")]) / 255.0f);
|
||||
call.set_white(1.0f);
|
||||
}
|
||||
|
||||
if (root[ESPHOME_F("color_temp")].is<uint16_t>()) {
|
||||
call.set_color_temperature(float(root[ESPHOME_F("color_temp")]));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "light_state.h"
|
||||
#include "esp_color_correction.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
#include "esphome/core/application.h"
|
||||
#endif
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "light_output.h"
|
||||
@@ -105,6 +108,13 @@ void LightState::dump_config() {
|
||||
" Default Transition Length: %.1fs\n"
|
||||
" Gamma Correct: %.2f",
|
||||
this->default_transition_length_ / 1e3f, this->gamma_correct_);
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
// The define is build wide; only lights that set the option have an interval
|
||||
if (this->transition_state_publish_interval_ != 0) {
|
||||
ESP_LOGCONFIG(TAG, " Transition State Publish Interval: %" PRIu32 "ms",
|
||||
this->transition_state_publish_interval_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (traits.supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
@@ -130,13 +140,30 @@ void LightState::loop() {
|
||||
this->next_write_ = true;
|
||||
}
|
||||
|
||||
if (this->transformer_->is_finished()) {
|
||||
const bool finished = this->transformer_->is_finished();
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
if (this->transition_publish_enabled_ && !finished) {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (now - this->last_transition_state_publish_ >= this->transition_state_publish_interval_) {
|
||||
this->publish_state();
|
||||
this->last_transition_state_publish_ = now;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (finished) {
|
||||
// if the transition has written directly to the output, current_values is outdated, so update it
|
||||
this->current_values = this->transformer_->get_target_values();
|
||||
|
||||
this->transformer_->stop();
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
if (this->transition_publish_enabled_) {
|
||||
// Report the end state from remote_values; a flash's stop() left publishing to us
|
||||
this->transition_publish_enabled_ = false;
|
||||
this->publish_state();
|
||||
}
|
||||
#endif
|
||||
if (this->target_state_reached_listeners_) {
|
||||
for (auto *listener : *this->target_state_reached_listeners_) {
|
||||
listener->on_light_target_state_reached();
|
||||
@@ -348,10 +375,7 @@ void LightState::stop_effect_() {
|
||||
void LightState::start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values) {
|
||||
this->transformer_ = this->output_->create_default_transition();
|
||||
this->transformer_->setup(this->current_values, target, length);
|
||||
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
}
|
||||
this->set_transformer_remote_values_(target, set_remote_values);
|
||||
// Enable loop while transition is active
|
||||
this->enable_loop();
|
||||
}
|
||||
@@ -365,10 +389,7 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
|
||||
|
||||
this->transformer_ = make_unique<LightFlashTransformer>(*this);
|
||||
this->transformer_->setup(end_colors, target, length);
|
||||
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
};
|
||||
this->set_transformer_remote_values_(target, set_remote_values);
|
||||
// Enable loop while flash is active
|
||||
this->enable_loop();
|
||||
}
|
||||
@@ -376,6 +397,9 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
|
||||
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
this->transition_publish_enabled_ = false;
|
||||
#endif
|
||||
this->current_values = target;
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
@@ -391,6 +415,18 @@ void LightState::disable_loop_if_idle_() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
void LightState::set_transformer_remote_values_(const LightColorValues &target, bool set_remote_values) {
|
||||
this->transition_publish_enabled_ = set_remote_values && this->transition_state_publish_interval_ > 0;
|
||||
if (this->transition_publish_enabled_) {
|
||||
this->last_transition_state_publish_ = App.get_loop_component_start_time();
|
||||
}
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void LightState::save_remote_values_() {
|
||||
LightStateRTCState saved;
|
||||
saved.color_mode = this->remote_values.get_color_mode();
|
||||
|
||||
@@ -100,6 +100,21 @@ class LightState : public EntityBase, public Component {
|
||||
LightCall turn_on();
|
||||
LightCall turn_off();
|
||||
LightCall toggle();
|
||||
|
||||
/// The values reported to the frontend: current_values while a light publishes intermediate
|
||||
/// states on an interval, otherwise remote_values. Each interval sample is a publish_state(),
|
||||
/// so on_state automations run on every sample as well.
|
||||
const LightColorValues &get_reported_values() const {
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
if (this->transition_publish_enabled_) {
|
||||
return this->current_values;
|
||||
}
|
||||
#endif
|
||||
return this->remote_values;
|
||||
}
|
||||
|
||||
/// True from the call that starts a transition or flash until it reaches its target.
|
||||
bool is_transitioning() const { return this->transformer_ != nullptr; }
|
||||
LightCall make_call();
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
@@ -172,6 +187,13 @@ class LightState : public EntityBase, public Component {
|
||||
void set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; }
|
||||
float get_gamma_correct() const { return this->gamma_correct_; }
|
||||
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
void set_transition_state_publish_interval(uint32_t transition_state_publish_interval) {
|
||||
this->transition_state_publish_interval_ = transition_state_publish_interval;
|
||||
}
|
||||
uint32_t get_transition_state_publish_interval() const { return this->transition_state_publish_interval_; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT_GAMMA_LUT
|
||||
/// Set pre-computed gamma forward lookup table (256-entry uint16 PROGMEM array)
|
||||
void set_gamma_table(const uint16_t *forward) { this->gamma_table_ = forward; }
|
||||
@@ -291,6 +313,7 @@ class LightState : public EntityBase, public Component {
|
||||
friend LightOutput;
|
||||
friend LightCall;
|
||||
friend class AddressableLight;
|
||||
friend class LightFlashTransformer;
|
||||
|
||||
/// Internal method to start an effect with the given index
|
||||
void start_effect_(uint32_t effect_index);
|
||||
@@ -307,6 +330,18 @@ class LightState : public EntityBase, public Component {
|
||||
/// Internal method to set the color values to target immediately (with no transition).
|
||||
void set_immediately_(const LightColorValues &target, bool set_remote_values);
|
||||
|
||||
/// Point remote_values at the new transformer's target and, when this light publishes
|
||||
/// intermediate states on an interval, start the interval clock.
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
void set_transformer_remote_values_(const LightColorValues &target, bool set_remote_values);
|
||||
#else
|
||||
void set_transformer_remote_values_(const LightColorValues &target, bool set_remote_values) {
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Internal method to save the current remote_values to the preferences
|
||||
void save_remote_values_();
|
||||
|
||||
@@ -357,6 +392,10 @@ class LightState : public EntityBase, public Component {
|
||||
uint32_t default_transition_length_{};
|
||||
/// Transition length to use for flash transitions.
|
||||
uint32_t flash_transition_length_{}; // Keep in sync with DEFAULT_FLASH_TRANSITION_LENGTH in __init__.py
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
uint32_t transition_state_publish_interval_{0};
|
||||
uint32_t last_transition_state_publish_{0};
|
||||
#endif
|
||||
/// Gamma correction factor for the light.
|
||||
float gamma_correct_{};
|
||||
#ifdef USE_LIGHT_GAMMA_LUT
|
||||
@@ -367,6 +406,10 @@ class LightState : public EntityBase, public Component {
|
||||
bool next_write_{true};
|
||||
// for effects, true if a transformer (transition) is active.
|
||||
bool is_transformer_active_{false};
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
/// True while the active transformer publishes current_values on an interval from loop().
|
||||
bool transition_publish_enabled_{false};
|
||||
#endif
|
||||
/// Restore mode of the light.
|
||||
LightRestoreMode restore_mode_;
|
||||
};
|
||||
|
||||
@@ -105,6 +105,11 @@ class LightFlashTransformer : public LightTransformer {
|
||||
}
|
||||
this->state_.current_values = this->get_start_values();
|
||||
this->state_.remote_values = this->get_start_values();
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
// The light reports the end state itself once the transformer finishes
|
||||
if (this->state_.transition_publish_enabled_)
|
||||
return;
|
||||
#endif
|
||||
this->state_.publish_state();
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,6 @@ ToggleAction = light_ns.class_("ToggleAction", automation.Action)
|
||||
LightEffectCycleAction = light_ns.class_("LightEffectCycleAction", automation.Action)
|
||||
DimRelativeAction = light_ns.class_("DimRelativeAction", automation.Action)
|
||||
AddressableSet = light_ns.class_("AddressableSet", automation.Action)
|
||||
LightIsOnCondition = light_ns.class_("LightIsOnCondition", automation.Condition)
|
||||
LightIsOffCondition = light_ns.class_("LightIsOffCondition", automation.Condition)
|
||||
|
||||
# Triggers
|
||||
LightTurnOnTrigger = light_ns.class_(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
import esphome.config_validation as cv
|
||||
@@ -34,7 +34,6 @@ LockAction = lock_ns.class_("LockAction", automation.Action)
|
||||
OpenAction = lock_ns.class_("OpenAction", automation.Action)
|
||||
LockPublishAction = lock_ns.class_("LockPublishAction", automation.Action)
|
||||
|
||||
LockCondition = lock_ns.class_("LockCondition", Condition)
|
||||
LockStateForwarder = lock_ns.class_("LockStateForwarder")
|
||||
|
||||
LockState = lock_ns.enum("LockState")
|
||||
@@ -154,26 +153,14 @@ async def lock_action_to_code(
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_condition("lock.is_locked", LockCondition, LOCK_ACTION_SCHEMA)
|
||||
async def lock_is_on_to_code(
|
||||
config: ConfigType,
|
||||
condition_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition("lock.is_unlocked", LockCondition, LOCK_ACTION_SCHEMA)
|
||||
async def lock_is_off_to_code(
|
||||
config: ConfigType,
|
||||
condition_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
automation.register_apply_condition(
|
||||
"lock.is_locked", LOCK_ACTION_SCHEMA, f"state == {LockState.LOCK_STATE_LOCKED}"
|
||||
)
|
||||
automation.register_apply_condition(
|
||||
"lock.is_unlocked",
|
||||
LOCK_ACTION_SCHEMA,
|
||||
f"state == {LockState.LOCK_STATE_UNLOCKED}",
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -36,19 +36,6 @@ template<typename... Ts> class OpenAction final : public Action<Ts...> {
|
||||
Lock *lock_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class LockCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
LockCondition(Lock *parent, bool state) : parent_(parent), state_(state) {}
|
||||
bool check(const Ts &...x) override {
|
||||
auto check_state = this->state_ ? LockState::LOCK_STATE_LOCKED : LockState::LOCK_STATE_UNLOCKED;
|
||||
return this->parent_->state == check_state;
|
||||
}
|
||||
|
||||
protected:
|
||||
Lock *parent_;
|
||||
bool state_;
|
||||
};
|
||||
|
||||
/// Callback forwarder that triggers an Automation<> only when a specific lock state is entered.
|
||||
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
|
||||
template<LockState State> struct LockStateForwarder {
|
||||
|
||||
@@ -32,9 +32,6 @@ enum LockState : uint8_t {
|
||||
};
|
||||
const LogString *lock_state_to_string(LockState state);
|
||||
|
||||
/// Maximum length of lock state string (including null terminator): "UNLOCKING" = 10
|
||||
static constexpr size_t LOCK_STATE_STR_SIZE = 10;
|
||||
|
||||
class LockTraits {
|
||||
public:
|
||||
LockTraits() = default;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
@@ -63,6 +64,8 @@ from esphome.core import CORE, ID, CoroPriority, Lambda, coroutine_with_priority
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
logger_ns = cg.esphome_ns.namespace("logger")
|
||||
LOG_LEVELS = {
|
||||
@@ -105,6 +108,7 @@ DEFAULT = "DEFAULT"
|
||||
|
||||
CONF_INITIAL_LEVEL = "initial_level"
|
||||
CONF_LOGGER_ID = "logger_id"
|
||||
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = "esp8266_store_log_strings_in_flash"
|
||||
CONF_RUNTIME_TAG_LEVELS = "runtime_tag_levels"
|
||||
CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size"
|
||||
CONF_WAIT_FOR_CDC = "wait_for_cdc"
|
||||
@@ -219,6 +223,18 @@ def validate_initial_no_higher_than_global(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def warn_ram_log_strings(config: ConfigType) -> ConfigType:
|
||||
# Remove before 2027.4.0
|
||||
if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH) is False:
|
||||
_LOGGER.warning(
|
||||
"'%s: false' is ignored and will be rejected in 2027.4.0. Log format strings "
|
||||
"always stay in flash now; copying them into RAM gave no speed gain and the "
|
||||
"lost RAM caused crashes. Remove the option",
|
||||
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH,
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def validate_wait_for_cdc(config: ConfigType) -> ConfigType:
|
||||
if config.get(CONF_WAIT_FOR_CDC) and config.get(CONF_HARDWARE_UART) != USB_CDC:
|
||||
raise cv.Invalid("wait_for_cdc requires hardware_uart: USB_CDC")
|
||||
@@ -232,7 +248,6 @@ LoggerMessageTrigger = logger_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = "esp8266_store_log_strings_in_flash"
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -332,6 +347,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
validate_local_no_higher_than_global,
|
||||
validate_initial_no_higher_than_global,
|
||||
validate_wait_for_cdc,
|
||||
warn_ram_log_strings,
|
||||
)
|
||||
|
||||
|
||||
@@ -450,9 +466,6 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
cg.add_build_flag("-DCORE_DEBUG_LEVEL=5")
|
||||
if CORE.is_esp32 and is_at_least_very_verbose:
|
||||
cg.add_build_flag("-DENABLE_I2C_DEBUG_BUFFER")
|
||||
if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH):
|
||||
cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH")
|
||||
|
||||
if CORE.is_esp32:
|
||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
|
||||
|
||||
@@ -105,9 +105,9 @@ struct LogBuffer {
|
||||
this->format_vsnprintf_(format, args);
|
||||
this->finalize_();
|
||||
}
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
void HOT format_body_P(PGM_P format, va_list args) {
|
||||
this->format_vsnprintf_P_(format, args);
|
||||
#ifdef USE_ESP8266
|
||||
void HOT format_body_p(PGM_P format, va_list args) {
|
||||
this->format_vsnprintf_p_(format, args);
|
||||
this->finalize_();
|
||||
}
|
||||
#endif
|
||||
@@ -158,8 +158,8 @@ struct LogBuffer {
|
||||
return;
|
||||
this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args));
|
||||
}
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
void format_vsnprintf_P_(PGM_P format, va_list args) {
|
||||
#ifdef USE_ESP8266
|
||||
void format_vsnprintf_p_(PGM_P format, va_list args) {
|
||||
if (this->full_())
|
||||
return;
|
||||
this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args));
|
||||
|
||||
@@ -127,9 +127,8 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
}
|
||||
#endif // USE_ESP32 || USE_HOST || USE_LIBRETINY || USE_ZEPHYR
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
// Implementation for ESP8266 with flash string support.
|
||||
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
|
||||
#ifdef USE_ESP8266
|
||||
// ESP8266 keeps log format strings in flash.
|
||||
//
|
||||
// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
|
||||
// Uses vsnprintf_P to read the format string directly from flash without copying to RAM.
|
||||
@@ -141,7 +140,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
|
||||
this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr);
|
||||
}
|
||||
#endif // USE_STORE_LOG_STR_IN_FLASH
|
||||
#endif // USE_ESP8266
|
||||
|
||||
inline uint8_t Logger::level_for(const char *tag) {
|
||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
|
||||
@@ -200,9 +200,9 @@ class Logger final : public Component {
|
||||
float get_setup_priority() const override { return setup_priority::BUS + 500.0f; }
|
||||
|
||||
void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args); // NOLINT
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
|
||||
va_list args); // NOLINT
|
||||
#ifdef USE_ESP8266
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, va_list args);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
@@ -244,14 +244,14 @@ class Logger final : public Component {
|
||||
buf.format_body(format, args);
|
||||
}
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
#ifdef USE_ESP8266
|
||||
// Format a log message with flash string format and write it to a buffer with header, footer, and null terminator
|
||||
// ESP8266-only (single-task), thread_name is always nullptr
|
||||
inline void HOT format_log_to_buffer_with_terminator_P_(uint8_t level, const char *tag, int line,
|
||||
inline void HOT format_log_to_buffer_with_terminator_p_(uint8_t level, const char *tag, int line,
|
||||
const __FlashStringHelper *format, va_list args,
|
||||
LogBuffer &buf) {
|
||||
buf.write_header(level, tag, line, nullptr);
|
||||
buf.format_body_P(reinterpret_cast<PGM_P>(format), args);
|
||||
buf.format_body_p(reinterpret_cast<PGM_P>(format), args);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -283,9 +283,9 @@ class Logger final : public Component {
|
||||
FormatType format, va_list args, const char *thread_name) {
|
||||
RecursionGuard guard(recursion_guard);
|
||||
LogBuffer buf{this->tx_buffer_, ESPHOME_LOGGER_TX_BUFFER_SIZE};
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
#ifdef USE_ESP8266
|
||||
if constexpr (std::is_same_v<FormatType, const __FlashStringHelper *>) {
|
||||
this->format_log_to_buffer_with_terminator_P_(level, tag, line, format, args, buf);
|
||||
this->format_log_to_buffer_with_terminator_p_(level, tag, line, format, args, buf);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components.const import UNIT_COUNTS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AMBIENT_LIGHT,
|
||||
@@ -28,7 +29,6 @@ CONF_UV_INDEX = "uv_index"
|
||||
CONF_UV = "uv"
|
||||
CONF_WINDOW_CORRECTION_FACTOR = "window_correction_factor"
|
||||
|
||||
UNIT_COUNTS = "#"
|
||||
UNIT_UVI = "UVI"
|
||||
|
||||
LTR390GAIN = ltr390_ns.enum("LTR390GAIN")
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components.const import UNIT_COUNTS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTUAL_GAIN,
|
||||
@@ -42,7 +43,6 @@ CONF_PS_LOW_THRESHOLD = "ps_low_threshold"
|
||||
ICON_BRIGHTNESS_7 = "mdi:brightness-7"
|
||||
ICON_GAIN = "mdi:multiplication"
|
||||
ICON_PROXIMITY = "mdi:hand-wave-outline"
|
||||
UNIT_COUNTS = "#"
|
||||
|
||||
ltr501_ns = cg.esphome_ns.namespace("ltr501")
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components.const import UNIT_COUNTS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTUAL_GAIN,
|
||||
@@ -41,7 +42,6 @@ CONF_PS_LOW_THRESHOLD = "ps_low_threshold"
|
||||
ICON_BRIGHTNESS_7 = "mdi:brightness-7"
|
||||
ICON_GAIN = "mdi:multiplication"
|
||||
ICON_PROXIMITY = "mdi:hand-wave-outline"
|
||||
UNIT_COUNTS = "#"
|
||||
|
||||
ltr_als_ps_ns = cg.esphome_ns.namespace("ltr_als_ps")
|
||||
|
||||
|
||||
@@ -680,7 +680,6 @@ CONF_BODY = "body"
|
||||
CONF_BUTTONS = "buttons"
|
||||
CONF_CHANGE_RATE = "change_rate"
|
||||
CONF_CLOSE_BUTTON = "close_button"
|
||||
CONF_COLOR_DEPTH = "color_depth"
|
||||
CONF_COLOR_END = "color_end"
|
||||
CONF_COLOR_START = "color_start"
|
||||
CONF_CONTAINER = "container"
|
||||
@@ -767,7 +766,6 @@ CONF_RESUME_ON_INPUT = "resume_on_input"
|
||||
CONF_RIGHT_BUTTON = "right_button"
|
||||
CONF_ROLLOVER = "rollover"
|
||||
CONF_ROOT_BACK_BTN = "root_back_btn"
|
||||
CONF_ROWS = "rows"
|
||||
CONF_SCALE = "scale"
|
||||
CONF_SCALE_LINES = "scale_lines"
|
||||
CONF_SCROLLBAR_MODE = "scrollbar_mode"
|
||||
|
||||
@@ -292,8 +292,8 @@ BASE_PROPS = {
|
||||
"LV_TEXT_DECOR_", "NONE", "UNDERLINE", "STRIKETHROUGH"
|
||||
).several_of,
|
||||
"text_font": lv_font,
|
||||
"text_letter_space": lvalid.lv_positive_int,
|
||||
"text_line_space": lvalid.lv_positive_int,
|
||||
"text_letter_space": lvalid.lv_int,
|
||||
"text_line_space": lvalid.lv_int,
|
||||
"text_opa": lvalid.opacity,
|
||||
"text_outline_stroke_color": lvalid.lv_color,
|
||||
"text_outline_stroke_opa": lvalid.opacity,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
// On ESP8266 and RP2040 the scheduler-backed MDNS.update() polling window is armed by
|
||||
// IP state listener events on whichever network interface is configured.
|
||||
#if (defined(USE_ESP8266) || defined(USE_RP2)) && \
|
||||
@@ -39,12 +40,7 @@ struct MDNSString;
|
||||
// Macro to cast string literals to MDNSString* (works on all platforms)
|
||||
#define MDNS_STR(name) (reinterpret_cast<const esphome::mdns::MDNSString *>(name))
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
#define MDNS_STR_ARG(s) ((PGM_P) (s))
|
||||
#else
|
||||
#define MDNS_STR_ARG(s) (reinterpret_cast<const char *>(s))
|
||||
#endif
|
||||
#define MDNS_STR_ARG(s) (reinterpret_cast<ESPHOME_PGM_P>(s))
|
||||
|
||||
// Service count is calculated at compile time by Python codegen
|
||||
// MDNS_SERVICE_COUNT will always be defined
|
||||
|
||||
@@ -190,16 +190,15 @@ _STATE_TRIGGERS = (
|
||||
(CONF_ON_TURN_OFF, MediaPlayerState.MEDIA_PLAYER_STATE_OFF),
|
||||
)
|
||||
|
||||
# State conditions that all share the same schema and codegen handler
|
||||
_STATE_CONDITIONS = [
|
||||
"idle",
|
||||
"paused",
|
||||
"playing",
|
||||
"announcing",
|
||||
"on",
|
||||
"off",
|
||||
"muted",
|
||||
]
|
||||
# State conditions: (config_key suffix, checked state)
|
||||
_STATE_CONDITIONS = (
|
||||
("idle", MediaPlayerState.MEDIA_PLAYER_STATE_IDLE),
|
||||
("paused", MediaPlayerState.MEDIA_PLAYER_STATE_PAUSED),
|
||||
("playing", MediaPlayerState.MEDIA_PLAYER_STATE_PLAYING),
|
||||
("announcing", MediaPlayerState.MEDIA_PLAYER_STATE_ANNOUNCING),
|
||||
("on", MediaPlayerState.MEDIA_PLAYER_STATE_ON),
|
||||
("off", MediaPlayerState.MEDIA_PLAYER_STATE_OFF),
|
||||
)
|
||||
|
||||
MediaPlayerCommand = media_player_ns.enum("MediaPlayerCommand", is_class=True)
|
||||
|
||||
@@ -360,27 +359,16 @@ for _action_name in _COMMAND_ACTIONS:
|
||||
)
|
||||
|
||||
|
||||
def _snake_to_camel(name):
|
||||
return "".join(word.capitalize() for word in name.split("_"))
|
||||
for _condition_name, _state in _STATE_CONDITIONS:
|
||||
automation.register_apply_condition(
|
||||
f"media_player.is_{_condition_name}",
|
||||
MEDIA_PLAYER_CONDITION_SCHEMA,
|
||||
f"state == {_state}",
|
||||
)
|
||||
|
||||
|
||||
def _register_state_conditions():
|
||||
async def handler(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
for condition_name in _STATE_CONDITIONS:
|
||||
class_name = f"Is{_snake_to_camel(condition_name)}Condition"
|
||||
condition_class = media_player_ns.class_(class_name, automation.Condition)
|
||||
automation.register_condition(
|
||||
f"media_player.is_{condition_name}",
|
||||
condition_class,
|
||||
MEDIA_PLAYER_CONDITION_SCHEMA,
|
||||
)(handler)
|
||||
|
||||
|
||||
_register_state_conditions()
|
||||
automation.register_apply_condition(
|
||||
"media_player.is_muted", MEDIA_PLAYER_CONDITION_SCHEMA, "is_muted()"
|
||||
)
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
|
||||
@@ -27,41 +27,4 @@ static_assert(std::is_trivially_copyable_v<StateAnyForwarder>);
|
||||
static_assert(sizeof(StateEnterForwarder<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>) <= sizeof(void *));
|
||||
static_assert(std::is_trivially_copyable_v<StateEnterForwarder<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>>);
|
||||
|
||||
template<typename... Ts> class IsIdleCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_IDLE; }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsPlayingCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING; }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsPausedCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PAUSED; }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsAnnouncingCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override {
|
||||
return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsOnCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ON; }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsOffCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_OFF; }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsMutedCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_muted(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::media_player
|
||||
|
||||
@@ -102,7 +102,7 @@ void MitsubishiClimate::transmit_state() {
|
||||
default:
|
||||
remote_state[6] = MITSUBISHI_MODE_COOL;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_COOL;
|
||||
if (this->supports_heat_) {
|
||||
if (this->modes_.count(climate::CLIMATE_MODE_HEAT)) {
|
||||
remote_state[6] = MITSUBISHI_MODE_HEAT;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_HEAT;
|
||||
}
|
||||
|
||||
@@ -51,10 +51,10 @@ class MitsubishiClimate final : public climate_ir::ClimateIR {
|
||||
{climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_BOOST,
|
||||
climate::CLIMATE_PRESET_SLEEP}) {}
|
||||
|
||||
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
void set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; }
|
||||
void set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; }
|
||||
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||
void set_supports_dry(bool supports_dry) { this->set_mode_supported_(climate::CLIMATE_MODE_DRY, supports_dry); }
|
||||
void set_supports_fan_only(bool supports_fan_only) {
|
||||
this->set_mode_supported_(climate::CLIMATE_MODE_FAN_ONLY, supports_fan_only);
|
||||
}
|
||||
|
||||
void set_fan_mode(SetFanMode fan_mode) {
|
||||
this->fan_mode_ = fan_mode;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import logger, socket
|
||||
from esphome.components.esp32 import (
|
||||
@@ -135,7 +134,6 @@ MQTTDisconnectTrigger = mqtt_ns.class_(
|
||||
"MQTTDisconnectTrigger", automation.Trigger.template(MQTTClientDisconnectReason)
|
||||
)
|
||||
MQTTComponent = mqtt_ns.class_("MQTTComponent", cg.Component)
|
||||
MQTTConnectedCondition = mqtt_ns.class_("MQTTConnectedCondition", Condition)
|
||||
|
||||
MQTTAlarmControlPanelComponent = mqtt_ns.class_(
|
||||
"MQTTAlarmControlPanelComponent", MQTTComponent
|
||||
@@ -599,18 +597,15 @@ async def register_mqtt_component(var, config):
|
||||
)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
automation.register_apply_condition(
|
||||
"mqtt.connected",
|
||||
MQTTConnectedCondition,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(MQTTClientComponent),
|
||||
}
|
||||
),
|
||||
"is_connected()",
|
||||
)
|
||||
async def mqtt_connected_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
@@ -417,15 +417,6 @@ template<typename... Ts> class MQTTPublishJsonAction final : public Action<Ts...
|
||||
MQTTClientComponent *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class MQTTConnectedCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
MQTTConnectedCondition(MQTTClientComponent *parent) : parent_(parent) {}
|
||||
bool check(const Ts &...x) override { return this->parent_->is_connected(); }
|
||||
|
||||
protected:
|
||||
MQTTClientComponent *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class MQTTEnableAction final : public Action<Ts...> {
|
||||
public:
|
||||
MQTTEnableAction(MQTTClientComponent *parent) : parent_(parent) {}
|
||||
|
||||
@@ -312,11 +312,7 @@ bool MQTTComponent::send_discovery_() {
|
||||
// Buffer sized for format string expansion: ~4 bytes net growth from format specifier to 8 hex digits, plus
|
||||
// safety margin
|
||||
char version_buf[sizeof(ver_fmt) + 8];
|
||||
#ifdef USE_ESP8266
|
||||
snprintf_P(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
#else
|
||||
snprintf(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
#endif
|
||||
ESPHOME_snprintf_P(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = version_buf;
|
||||
device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
|
||||
@@ -50,14 +50,8 @@ bool MQTTLockComponent::send_initial_state() { return this->publish_state(); }
|
||||
|
||||
bool MQTTLockComponent::publish_state() {
|
||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
char buf[LOCK_STATE_STR_SIZE];
|
||||
strncpy_P(buf, (PGM_P) lock_state_to_string(this->lock_->state), sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), buf);
|
||||
#else
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), LOG_STR_ARG(lock_state_to_string(this->lock_->state)));
|
||||
#endif
|
||||
return this->publish(this->get_state_topic_to_(topic_buf),
|
||||
reinterpret_cast<ProgmemStr>(lock_state_to_string(this->lock_->state)));
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <cinttypes>
|
||||
#include "mqtt_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
@@ -59,11 +60,7 @@ void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
|
||||
root[MQTT_FORCE_UPDATE] = true;
|
||||
|
||||
if (this->sensor_->get_state_class() != STATE_CLASS_NONE) {
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
root[MQTT_STATE_CLASS] = (const __FlashStringHelper *) state_class_to_string(this->sensor_->get_state_class());
|
||||
#else
|
||||
root[MQTT_STATE_CLASS] = LOG_STR_ARG(state_class_to_string(this->sensor_->get_state_class()));
|
||||
#endif
|
||||
root[MQTT_STATE_CLASS] = reinterpret_cast<ProgmemStr>(state_class_to_string(this->sensor_->get_state_class()));
|
||||
}
|
||||
|
||||
config.command_topic = false;
|
||||
|
||||
@@ -2,16 +2,13 @@
|
||||
#ifdef USE_NOISE
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <noise/protocol.h>
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::noise {
|
||||
|
||||
static const char *const TAG = "noise";
|
||||
@@ -74,22 +71,13 @@ size_t format_reject_payload(uint8_t *buf, size_t capacity, const LogString *rea
|
||||
return 0;
|
||||
}
|
||||
buf[0] = HANDSHAKE_STATUS_REJECT;
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
|
||||
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
|
||||
reason_len = std::min(reason_len, capacity - 1);
|
||||
if (reason_len > 0) {
|
||||
memcpy_P(buf + 1, reinterpret_cast<PGM_P>(reason), reason_len);
|
||||
}
|
||||
#else
|
||||
// The reason may live in PROGMEM on ESP8266; the progmem helpers read RAM and flash alike
|
||||
const char *reason_str = LOG_STR_ARG(reason);
|
||||
size_t reason_len = strlen(reason_str);
|
||||
reason_len = std::min(reason_len, capacity - 1);
|
||||
size_t reason_len = std::min(ESPHOME_strlen_P(reason_str), capacity - 1);
|
||||
if (reason_len > 0) {
|
||||
// NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary protocol, not a C string
|
||||
std::memcpy(buf + 1, reason_str, reason_len);
|
||||
progmem_memcpy(buf + 1, reason_str, reason_len);
|
||||
}
|
||||
#endif
|
||||
return reason_len + 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,6 @@
|
||||
|
||||
namespace esphome::number {
|
||||
|
||||
class NumberStateTrigger final : public Trigger<float> {
|
||||
public:
|
||||
explicit NumberStateTrigger(Number *parent) {
|
||||
parent->add_on_state_callback([this](float value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class ValueRangeTrigger final : public Trigger<float>, public Component {
|
||||
public:
|
||||
explicit ValueRangeTrigger(Number *parent) : parent_(parent) {}
|
||||
|
||||
@@ -14,8 +14,7 @@ from esphome.const import (
|
||||
CONF_TYPE,
|
||||
CONF_URL,
|
||||
)
|
||||
from esphome.core import ID, Lambda
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.core import Lambda
|
||||
from esphome.types import ConfigType
|
||||
|
||||
AUTO_LOAD = ["runtime_image"]
|
||||
@@ -31,14 +30,6 @@ OnlineImage = online_image_ns.class_(
|
||||
"OnlineImage", cg.PollingComponent, runtime_image.RuntimeImage
|
||||
)
|
||||
|
||||
# Actions
|
||||
SetUrlAction = online_image_ns.class_(
|
||||
"OnlineImageSetUrlAction", automation.Action, cg.Parented.template(OnlineImage)
|
||||
)
|
||||
ReleaseImageAction = online_image_ns.class_(
|
||||
"OnlineImageReleaseAction", automation.Action, cg.Parented.template(OnlineImage)
|
||||
)
|
||||
|
||||
ONLINE_IMAGE_SCHEMA = (
|
||||
runtime_image.runtime_image_schema(OnlineImage)
|
||||
.extend(
|
||||
@@ -90,31 +81,18 @@ RELEASE_IMAGE_SCHEMA = automation.maybe_simple_id(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"online_image.set_url", SetUrlAction, SET_URL_SCHEMA, synchronous=True
|
||||
automation.register_apply_action(
|
||||
"online_image.set_url",
|
||||
SET_URL_SCHEMA,
|
||||
automation.ApplyField(CONF_URL, "set_url", cg.std_string),
|
||||
automation.ApplyField(CONF_UPDATE, "update_if", cg.bool_),
|
||||
)
|
||||
@automation.register_action(
|
||||
"online_image.release",
|
||||
ReleaseImageAction,
|
||||
RELEASE_IMAGE_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
async def online_image_action_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
if CONF_URL in config:
|
||||
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
||||
cg.add(var.set_url(template_))
|
||||
if CONF_UPDATE in config:
|
||||
template_ = await cg.templatable(config[CONF_UPDATE], args, cg.bool_)
|
||||
cg.add(var.set_update(template_))
|
||||
return var
|
||||
automation.register_apply_action(
|
||||
"online_image.release",
|
||||
RELEASE_IMAGE_SCHEMA,
|
||||
automation.ApplyCall("release()"),
|
||||
)
|
||||
|
||||
|
||||
_CALLBACK_AUTOMATIONS = (
|
||||
|
||||
@@ -43,6 +43,11 @@ class OnlineImage final : public PollingComponent,
|
||||
bool is_big_endian = false);
|
||||
|
||||
void update() override;
|
||||
/** Download now when `update` is true (the `update` flag of `online_image.set_url`). */
|
||||
void update_if(bool update) {
|
||||
if (update)
|
||||
this->update();
|
||||
}
|
||||
void loop() override;
|
||||
|
||||
/** Set the URL to download the image from. */
|
||||
@@ -104,29 +109,4 @@ class OnlineImage final : public PollingComponent,
|
||||
uint32_t start_time_{0};
|
||||
};
|
||||
|
||||
template<typename... Ts> class OnlineImageSetUrlAction final : public Action<Ts...> {
|
||||
public:
|
||||
OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(std::string, url)
|
||||
TEMPLATABLE_VALUE(bool, update)
|
||||
void play(const Ts &...x) override {
|
||||
this->parent_->set_url(this->url_.value(x...));
|
||||
if (this->update_.value(x...)) {
|
||||
this->parent_->update();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
OnlineImage *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class OnlineImageReleaseAction final : public Action<Ts...> {
|
||||
public:
|
||||
OnlineImageReleaseAction(OnlineImage *parent) : parent_(parent) {}
|
||||
void play(const Ts &...x) override { this->parent_->release(); }
|
||||
|
||||
protected:
|
||||
OnlineImage *parent_;
|
||||
};
|
||||
|
||||
} // namespace esphome::online_image
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_ON_STATE_CHANGE
|
||||
from esphome.config_helpers import (
|
||||
filter_source_files_from_defines,
|
||||
filter_source_files_from_platform,
|
||||
@@ -38,7 +39,6 @@ CONF_ON_ABORT = "on_abort"
|
||||
CONF_ON_BEGIN = "on_begin"
|
||||
CONF_ON_END = "on_end"
|
||||
CONF_ON_PROGRESS = "on_progress"
|
||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||
|
||||
|
||||
ota_ns = cg.esphome_ns.namespace("ota")
|
||||
|
||||
@@ -144,14 +144,8 @@ void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, st
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, const __FlashStringHelper *metric_name,
|
||||
EntityBase *obj, std::string &area, std::string &node,
|
||||
std::string &friendly_name) {
|
||||
#else
|
||||
void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, const char *metric_name, EntityBase *obj,
|
||||
void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, ProgmemStr metric_name, EntityBase *obj,
|
||||
std::string &area, std::string &node, std::string &friendly_name) {
|
||||
#endif
|
||||
stream->print(metric_name);
|
||||
stream->print(ESPHOME_F("{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
@@ -328,7 +322,7 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat
|
||||
// State
|
||||
print_metric_labels_(stream, ESPHOME_F("esphome_light_state"), obj, area, node, friendly_name);
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(obj->remote_values.is_on());
|
||||
stream->print(obj->get_reported_values().is_on());
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
// Brightness and RGBW
|
||||
light::LightColorValues color = obj->current_values;
|
||||
@@ -903,11 +897,7 @@ void PrometheusHandler::valve_row_(AsyncResponseStream *stream, valve::Valve *ob
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",operation=\""));
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
stream->print((const __FlashStringHelper *) valve::valve_operation_to_str(obj->current_operation));
|
||||
#else
|
||||
stream->print((const char *) valve::valve_operation_to_str(obj->current_operation));
|
||||
#endif
|
||||
stream->print(reinterpret_cast<ProgmemStr>(valve::valve_operation_to_str(obj->current_operation)));
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(ESPHOME_F("1.0"));
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
@@ -947,7 +937,11 @@ void PrometheusHandler::climate_setting_row_(AsyncResponseStream *stream, climat
|
||||
stream->print(ESPHOME_F("\",category=\""));
|
||||
stream->print(setting.c_str());
|
||||
stream->print(ESPHOME_F("\",setting_value=\""));
|
||||
#ifdef USE_ESP8266
|
||||
stream->print((const __FlashStringHelper *) setting_value);
|
||||
#else
|
||||
stream->print(LOG_STR_ARG(setting_value));
|
||||
#endif
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(ESPHOME_F("1.0"));
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#ifdef USE_CLIMATE
|
||||
#include "esphome/core/log.h"
|
||||
#endif
|
||||
@@ -68,13 +69,8 @@ class PrometheusHandler final : public AsyncWebHandler, public Component {
|
||||
void add_node_label_(AsyncResponseStream *stream, std::string &node);
|
||||
void add_friendly_name_label_(AsyncResponseStream *stream, std::string &friendly_name);
|
||||
/// Print metric name and common labels (id, area, node, friendly_name, name)
|
||||
#ifdef USE_ESP8266
|
||||
void print_metric_labels_(AsyncResponseStream *stream, const __FlashStringHelper *metric_name, EntityBase *obj,
|
||||
std::string &area, std::string &node, std::string &friendly_name);
|
||||
#else
|
||||
void print_metric_labels_(AsyncResponseStream *stream, const char *metric_name, EntityBase *obj, std::string &area,
|
||||
void print_metric_labels_(AsyncResponseStream *stream, ProgmemStr metric_name, EntityBase *obj, std::string &area,
|
||||
std::string &node, std::string &friendly_name);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
/// Return the type for prometheus
|
||||
|
||||
@@ -20,8 +20,7 @@ from esphome.const import (
|
||||
CONF_VALUE,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.core import CORE
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -38,11 +37,6 @@ remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter")
|
||||
RemoteTransmitterComponent = remote_transmitter_ns.class_(
|
||||
"RemoteTransmitterComponent", remote_base.RemoteTransmitterBase, cg.Component
|
||||
)
|
||||
DigitalWriteAction = remote_transmitter_ns.class_(
|
||||
"DigitalWriteAction",
|
||||
automation.Action,
|
||||
cg.Parented.template(RemoteTransmitterComponent),
|
||||
)
|
||||
|
||||
|
||||
# Keep in sync with the USE_LIBRETINY_VARIANT_RTL8720C / REMOTE_TRANSMITTER_BK_PWM gates in
|
||||
@@ -138,23 +132,12 @@ DIGITAL_WRITE_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"remote_transmitter.digital_write",
|
||||
DigitalWriteAction,
|
||||
DIGITAL_WRITE_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_VALUE, "digital_write", cg.bool_),
|
||||
id_key=CONF_TRANSMITTER_ID,
|
||||
)
|
||||
async def digital_write_action_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_TRANSMITTER_ID])
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, cg.bool_)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/remote_transmitter/remote_transmitter.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::remote_transmitter {
|
||||
|
||||
template<typename... Ts>
|
||||
class DigitalWriteAction final : public Action<Ts...>, public Parented<RemoteTransmitterComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, value)
|
||||
void play(const Ts &...x) override { this->parent_->digital_write(this->value_.value(x...)); }
|
||||
};
|
||||
|
||||
} // namespace esphome::remote_transmitter
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome::rotary_encoder {
|
||||
@@ -106,15 +105,4 @@ class RotaryEncoderSensor final : public sensor::Sensor, public Component {
|
||||
CallbackManager<void(int32_t)> listeners_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class RotaryEncoderSetValueAction final : public Action<Ts...> {
|
||||
public:
|
||||
RotaryEncoderSetValueAction(RotaryEncoderSensor *encoder) : encoder_(encoder) {}
|
||||
TEMPLATABLE_VALUE(int, value)
|
||||
|
||||
void play(const Ts &...x) override { this->encoder_->set_value(this->value_.value(x...)); }
|
||||
|
||||
protected:
|
||||
RotaryEncoderSensor *encoder_;
|
||||
};
|
||||
|
||||
} // namespace esphome::rotary_encoder
|
||||
|
||||
@@ -15,8 +15,6 @@ from esphome.const import (
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_STEPS,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
rotary_encoder_ns = cg.esphome_ns.namespace("rotary_encoder")
|
||||
@@ -42,9 +40,6 @@ CONF_PUBLISH_INITIAL_VALUE = "publish_initial_value"
|
||||
RotaryEncoderSensor = rotary_encoder_ns.class_(
|
||||
"RotaryEncoderSensor", sensor.Sensor, cg.Component
|
||||
)
|
||||
RotaryEncoderSetValueAction = rotary_encoder_ns.class_(
|
||||
"RotaryEncoderSetValueAction", automation.Action
|
||||
)
|
||||
|
||||
|
||||
def validate_min_max_value(config: ConfigType) -> ConfigType:
|
||||
@@ -118,25 +113,13 @@ async def to_code(config: ConfigType) -> None:
|
||||
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"sensor.rotary_encoder.set_value",
|
||||
RotaryEncoderSetValueAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(RotaryEncoderSensor),
|
||||
cv.Required(CONF_VALUE): cv.templatable(cv.int_),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_VALUE, "set_value", cg.int_),
|
||||
)
|
||||
async def sensor_template_publish_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, cg.int_)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
@@ -11,7 +11,6 @@ Script = script_ns.class_("Script", automation.Trigger.template())
|
||||
ScriptExecuteAction = script_ns.class_("ScriptExecuteAction", automation.Action)
|
||||
ScriptStopAction = script_ns.class_("ScriptStopAction", automation.Action)
|
||||
ScriptWaitAction = script_ns.class_("ScriptWaitAction", automation.Action, cg.Component)
|
||||
IsRunningCondition = script_ns.class_("IsRunningCondition", automation.Condition)
|
||||
SingleScript = script_ns.class_("SingleScript", Script)
|
||||
RestartScript = script_ns.class_("RestartScript", Script)
|
||||
QueueingScript = script_ns.class_("QueueingScript", Script, cg.Component)
|
||||
@@ -233,12 +232,8 @@ async def script_wait_action_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
automation.register_apply_condition(
|
||||
"script.is_running",
|
||||
IsRunningCondition,
|
||||
automation.maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
|
||||
"is_running()",
|
||||
)
|
||||
async def script_is_running_to_code(config, condition_id, template_arg, args):
|
||||
full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
|
||||
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
|
||||
@@ -5,14 +5,8 @@ namespace esphome::script {
|
||||
|
||||
static const char *const TAG = "script";
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
void ScriptLogger::esp_log_(int level, int line, const __FlashStringHelper *format, const char *param) {
|
||||
void ScriptLogger::esp_log_(int level, int line, ProgmemStr format, const char *param) {
|
||||
esp_log_printf_(level, TAG, line, format, param);
|
||||
}
|
||||
#else
|
||||
void ScriptLogger::esp_log_(int level, int line, const char *format, const char *param) {
|
||||
esp_log_printf_(level, TAG, line, format, param);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace esphome::script
|
||||
|
||||
@@ -8,28 +8,19 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::script {
|
||||
|
||||
class ScriptLogger {
|
||||
protected:
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
void esp_logw_(int line, const __FlashStringHelper *format, const char *param) {
|
||||
void esp_logw_(int line, ProgmemStr format, const char *param) {
|
||||
esp_log_(ESPHOME_LOG_LEVEL_WARN, line, format, param);
|
||||
}
|
||||
void esp_logd_(int line, const __FlashStringHelper *format, const char *param) {
|
||||
void esp_logd_(int line, ProgmemStr format, const char *param) {
|
||||
esp_log_(ESPHOME_LOG_LEVEL_DEBUG, line, format, param);
|
||||
}
|
||||
void esp_log_(int level, int line, const __FlashStringHelper *format, const char *param);
|
||||
#else
|
||||
void esp_logw_(int line, const char *format, const char *param) {
|
||||
esp_log_(ESPHOME_LOG_LEVEL_WARN, line, format, param);
|
||||
}
|
||||
void esp_logd_(int line, const char *format, const char *param) {
|
||||
esp_log_(ESPHOME_LOG_LEVEL_DEBUG, line, format, param);
|
||||
}
|
||||
void esp_log_(int level, int line, const char *format, const char *param);
|
||||
#endif
|
||||
void esp_log_(int level, int line, ProgmemStr format, const char *param);
|
||||
};
|
||||
|
||||
/// The abstract base class for all script types.
|
||||
@@ -276,16 +267,6 @@ template<class C, typename... Ts> class ScriptStopAction final : public Action<T
|
||||
C *script_;
|
||||
};
|
||||
|
||||
template<class C, typename... Ts> class IsRunningCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
explicit IsRunningCondition(C *parent) : parent_(parent) {}
|
||||
|
||||
bool check(const Ts &...x) override { return this->parent_->is_running(); }
|
||||
|
||||
protected:
|
||||
C *parent_;
|
||||
};
|
||||
|
||||
/** Wait for a script to finish before continuing.
|
||||
*
|
||||
* Uses queue-based storage to safely handle concurrent executions.
|
||||
|
||||
@@ -27,6 +27,9 @@ from esphome.const import (
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_HERTZ,
|
||||
UNIT_KILOVOLT_AMPS_HOURS,
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
|
||||
UNIT_KILOWATT_HOURS,
|
||||
UNIT_VOLT,
|
||||
UNIT_VOLT_AMPS,
|
||||
UNIT_VOLT_AMPS_REACTIVE,
|
||||
@@ -44,9 +47,6 @@ CONF_MAXIMUM_DEMAND_ACTIVE_POWER = "maximum_demand_active_power"
|
||||
CONF_MAXIMUM_DEMAND_REACTIVE_POWER = "maximum_demand_reactive_power"
|
||||
CONF_MAXIMUM_DEMAND_APPARENT_POWER = "maximum_demand_apparent_power"
|
||||
|
||||
UNIT_KILOWATT_HOURS = "kWh"
|
||||
UNIT_KILOVOLT_AMPS_HOURS = "kVAh"
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh"
|
||||
|
||||
selec_meter_ns = cg.esphome_ns.namespace("selec_meter")
|
||||
SelecMeter = selec_meter_ns.class_(
|
||||
|
||||
@@ -142,7 +142,12 @@ automation.register_apply_action(
|
||||
cv.Required(CONF_OPTION): cv.templatable(cv.string_strict),
|
||||
}
|
||||
),
|
||||
automation.ApplyField(CONF_OPTION, "set_option", cg.std_string),
|
||||
automation.ApplyField(
|
||||
CONF_OPTION,
|
||||
"set_option",
|
||||
cg.std_string,
|
||||
const_fn=automation.literal_with_length,
|
||||
),
|
||||
call="make_call",
|
||||
)
|
||||
|
||||
|
||||
@@ -6,20 +6,6 @@
|
||||
|
||||
namespace esphome::sensor {
|
||||
|
||||
class SensorStateTrigger final : public Trigger<float> {
|
||||
public:
|
||||
explicit SensorStateTrigger(Sensor *parent) {
|
||||
parent->add_on_state_callback([this](float value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class SensorRawStateTrigger final : public Trigger<float> {
|
||||
public:
|
||||
explicit SensorRawStateTrigger(Sensor *parent) {
|
||||
parent->add_on_raw_state_callback([this](float value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class ValueRangeTrigger final : public Trigger<float>, public Component {
|
||||
public:
|
||||
explicit ValueRangeTrigger(Sensor *parent) : parent_(parent) {}
|
||||
|
||||
@@ -20,10 +20,6 @@ PlayAction = speaker_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
IsPlayingCondition = speaker_ns.class_("IsPlayingCondition", automation.Condition)
|
||||
IsStoppedCondition = speaker_ns.class_("IsStoppedCondition", automation.Condition)
|
||||
|
||||
|
||||
async def setup_speaker_core_(var, config):
|
||||
if audio_dac_config := config.get(CONF_AUDIO_DAC):
|
||||
aud_dac = await cg.get_variable(audio_dac_config)
|
||||
@@ -47,12 +43,6 @@ SPEAKER_AUTOMATION_SCHEMA = automation.maybe_simple_id(
|
||||
)
|
||||
|
||||
|
||||
async def speaker_action(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"speaker.play",
|
||||
PlayAction,
|
||||
@@ -91,13 +81,12 @@ for _name, _call in (
|
||||
_name, SPEAKER_AUTOMATION_SCHEMA, automation.ApplyCall(_call)
|
||||
)
|
||||
|
||||
automation.register_condition(
|
||||
"speaker.is_playing", IsPlayingCondition, SPEAKER_AUTOMATION_SCHEMA
|
||||
)(speaker_action)
|
||||
|
||||
automation.register_condition(
|
||||
"speaker.is_stopped", IsStoppedCondition, SPEAKER_AUTOMATION_SCHEMA
|
||||
)(speaker_action)
|
||||
automation.register_apply_condition(
|
||||
"speaker.is_playing", SPEAKER_AUTOMATION_SCHEMA, "is_running()"
|
||||
)
|
||||
automation.register_apply_condition(
|
||||
"speaker.is_stopped", SPEAKER_AUTOMATION_SCHEMA, "is_stopped()"
|
||||
)
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
|
||||
@@ -38,14 +38,4 @@ template<typename... Ts> class PlayAction final : public Action<Ts...>, public P
|
||||
} data_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsPlayingCondition final : public Condition<Ts...>, public Parented<Speaker> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_running(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsStoppedCondition final : public Condition<Ts...>, public Parented<Speaker> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_stopped(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::speaker
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server, zigbee
|
||||
import esphome.config_validation as cv
|
||||
@@ -54,7 +54,6 @@ RESTORE_MODES = {
|
||||
}
|
||||
|
||||
|
||||
SwitchCondition = switch_ns.class_("SwitchCondition", Condition)
|
||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True)
|
||||
|
||||
|
||||
@@ -201,17 +200,10 @@ for _name, _call in (
|
||||
_name, SWITCH_ACTION_SCHEMA, automation.ApplyCall(_call)
|
||||
)
|
||||
|
||||
|
||||
@automation.register_condition("switch.is_on", SwitchCondition, SWITCH_ACTION_SCHEMA)
|
||||
async def switch_is_on_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition("switch.is_off", SwitchCondition, SWITCH_ACTION_SCHEMA)
|
||||
async def switch_is_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
automation.register_apply_condition("switch.is_on", SWITCH_ACTION_SCHEMA, "state")
|
||||
automation.register_apply_condition(
|
||||
"switch.is_off", SWITCH_ACTION_SCHEMA, "state == false"
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
namespace esphome::switch_ {
|
||||
|
||||
template<typename... Ts> class SwitchCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
SwitchCondition(Switch *parent, bool state) : parent_(parent), state_(state) {}
|
||||
bool check(const Ts &...x) override { return this->parent_->state == this->state_; }
|
||||
|
||||
protected:
|
||||
Switch *parent_;
|
||||
bool state_;
|
||||
};
|
||||
|
||||
class SwitchStateTrigger final : public Trigger<bool> {
|
||||
public:
|
||||
SwitchStateTrigger(Switch *a_switch) {
|
||||
a_switch->add_on_state_callback([this](bool state) { this->trigger(state); });
|
||||
}
|
||||
};
|
||||
|
||||
class SwitchTurnOnTrigger final : public Trigger<> {
|
||||
public:
|
||||
SwitchTurnOnTrigger(Switch *a_switch) {
|
||||
a_switch->add_on_state_callback([this](bool state) {
|
||||
if (state) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class SwitchTurnOffTrigger final : public Trigger<> {
|
||||
public:
|
||||
SwitchTurnOffTrigger(Switch *a_switch) {
|
||||
a_switch->add_on_state_callback([this](bool state) {
|
||||
if (!state) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::switch_
|
||||
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@remcom"]
|
||||
@@ -0,0 +1,108 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
from esphome.components.audio_dac import AudioDac
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_POWER_MODE
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
tas2780_ns = cg.esphome_ns.namespace("tas2780")
|
||||
TAS2780 = tas2780_ns.class_("TAS2780", AudioDac, cg.PollingComponent, i2c.I2CDevice)
|
||||
ChannelSelect = tas2780_ns.enum("ChannelSelect")
|
||||
|
||||
CHANNELS = {
|
||||
"mono": ChannelSelect.MONO_DWN_MIX,
|
||||
"left": ChannelSelect.LEFT_CHANNEL,
|
||||
"right": ChannelSelect.RIGHT_CHANNEL,
|
||||
}
|
||||
|
||||
CONF_VOL_RANGE_MIN = "vol_range_min"
|
||||
CONF_VOL_RANGE_MAX = "vol_range_max"
|
||||
CONF_AMP_LEVEL = "amp_level"
|
||||
|
||||
_AMP_LEVEL = cv.int_range(min=0, max=20)
|
||||
_POWER_MODE = cv.int_range(min=0, max=3)
|
||||
|
||||
|
||||
def _validate_vol_range(config: ConfigType) -> ConfigType:
|
||||
if config[CONF_VOL_RANGE_MIN] >= config[CONF_VOL_RANGE_MAX]:
|
||||
raise cv.Invalid(f"{CONF_VOL_RANGE_MIN} must be less than {CONF_VOL_RANGE_MAX}")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TAS2780),
|
||||
cv.Optional(CONF_AMP_LEVEL, default=8): _AMP_LEVEL,
|
||||
cv.Optional(CONF_POWER_MODE, default=2): _POWER_MODE,
|
||||
cv.Optional(CONF_VOL_RANGE_MIN, default=0.3): cv.percentage,
|
||||
cv.Optional(CONF_VOL_RANGE_MAX, default=1.0): cv.percentage,
|
||||
cv.Optional(CONF_CHANNEL, default="mono"): cv.enum(CHANNELS),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5s"))
|
||||
.extend(i2c.i2c_device_schema(0x38)),
|
||||
_validate_vol_range,
|
||||
)
|
||||
|
||||
|
||||
TAS2780_ACTION_SCHEMA = maybe_simple_id({cv.GenerateID(): cv.use_id(TAS2780)})
|
||||
|
||||
for _name, _call in (
|
||||
("tas2780.deactivate", "deactivate()"),
|
||||
("tas2780.reset", "reset()"),
|
||||
):
|
||||
automation.register_apply_action(
|
||||
_name, TAS2780_ACTION_SCHEMA, automation.ApplyCall(_call)
|
||||
)
|
||||
|
||||
# Without power_mode the configured mode stays; activate() re-initializes only on a change.
|
||||
automation.register_apply_action(
|
||||
"tas2780.activate",
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(TAS2780),
|
||||
cv.Optional(CONF_POWER_MODE): cv.templatable(_POWER_MODE),
|
||||
}
|
||||
),
|
||||
automation.ApplyField(CONF_POWER_MODE, "set_power_mode", cg.uint8),
|
||||
automation.ApplyCall("activate()"),
|
||||
)
|
||||
|
||||
automation.register_apply_action(
|
||||
"tas2780.update_config",
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(TAS2780),
|
||||
cv.Optional(CONF_VOL_RANGE_MIN): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_VOL_RANGE_MAX): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_AMP_LEVEL): cv.templatable(_AMP_LEVEL),
|
||||
cv.Optional(CONF_CHANNEL): cv.templatable(cv.enum(CHANNELS)),
|
||||
}
|
||||
).add_extra(
|
||||
cv.has_at_least_one_key(
|
||||
CONF_VOL_RANGE_MIN, CONF_VOL_RANGE_MAX, CONF_AMP_LEVEL, CONF_CHANNEL
|
||||
)
|
||||
),
|
||||
automation.ApplyField(CONF_AMP_LEVEL, "set_amp_level", cg.uint8),
|
||||
automation.ApplyField(CONF_VOL_RANGE_MIN, "set_vol_range_min", cg.float_),
|
||||
automation.ApplyField(CONF_VOL_RANGE_MAX, "set_vol_range_max", cg.float_),
|
||||
automation.ApplyField(CONF_CHANNEL, "set_selected_channel", ChannelSelect),
|
||||
automation.ApplyCall("apply_config()"),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_amp_level(config[CONF_AMP_LEVEL]))
|
||||
cg.add(var.set_power_mode(config[CONF_POWER_MODE]))
|
||||
cg.add(var.set_vol_range_min(config[CONF_VOL_RANGE_MIN]))
|
||||
cg.add(var.set_vol_range_max(config[CONF_VOL_RANGE_MAX]))
|
||||
cg.add(var.set_selected_channel(config[CONF_CHANNEL]))
|
||||
@@ -0,0 +1,406 @@
|
||||
#include "tas2780.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::tas2780 {
|
||||
|
||||
static const char *const TAG = "tas2780";
|
||||
|
||||
static constexpr uint8_t TAS2780_PAGE_SELECT = 0x00; // Page Select
|
||||
static constexpr uint8_t TAS2780_PAGE_0 = 0x00; // Page 0
|
||||
static constexpr uint8_t TAS2780_PAGE_1 = 0x01; // Page 1
|
||||
static constexpr uint8_t TAS2780_PAGE_FD = 0xFD; // Page 0xFD
|
||||
|
||||
/* PAGE 0 */
|
||||
static constexpr uint8_t TAS2780_SW_RESET = 0x01; // Software Reset
|
||||
static constexpr uint8_t TAS2780_SW_RESET_CMD = 0x01; // Trigger software reset
|
||||
static constexpr uint8_t TAS2780_MODE_CTRL = 0x02; // Device operational mode
|
||||
static constexpr uint8_t TAS2780_MODE_CTRL_MODE_MASK = 0x07;
|
||||
static constexpr uint8_t TAS2780_MODE_CTRL_MODE_ACTIVE = 0x00;
|
||||
static constexpr uint8_t TAS2780_MODE_CTRL_MODE_ACTIVE_MUTED = 0x01;
|
||||
static constexpr uint8_t TAS2780_MODE_CTRL_MODE_SFTW_SHTDWN = 0x02;
|
||||
|
||||
static constexpr uint8_t TAS2780_CHNL_0 = 0x03; // Y Bridge and Channel settings
|
||||
static constexpr uint8_t TAS2780_CHNL_0_CDS_MODE_SHIFT = 6;
|
||||
static constexpr uint8_t TAS2780_CHNL_0_CDS_MODE_MASK = (0x03 << TAS2780_CHNL_0_CDS_MODE_SHIFT);
|
||||
static constexpr uint8_t TAS2780_CHNL_0_AMP_LEVEL_SHIFT = 1;
|
||||
static constexpr uint8_t TAS2780_CHNL_0_AMP_LEVEL_MASK = (0x1F) << TAS2780_CHNL_0_AMP_LEVEL_SHIFT;
|
||||
static constexpr uint8_t TAS2780_AMP_LEVEL_MAX = 0x14; // Codes above 20 are reserved
|
||||
|
||||
static constexpr uint8_t TAS2780_DC_BLK0 = 0x04; // SAR Filter and DC Path Blocker
|
||||
static constexpr uint8_t TAS2780_DC_BLK0_VBAT1S_MODE_MASK = (1 << 7);
|
||||
static constexpr uint8_t TAS2780_DC_BLK1 = 0x05; // Record DC Blocker
|
||||
static constexpr uint8_t TAS2780_DC_BLK1_RESET_VAL = 0x41; // Presence check, there is no WHO_AM_I register
|
||||
|
||||
static constexpr uint8_t TAS2780_TDM_CFG2 = 0x0A; // TDM Configuration 2
|
||||
static constexpr uint8_t TAS2780_TDM_CFG2_RX_SCFG_SHIFT = 4;
|
||||
static constexpr uint8_t TAS2780_TDM_CFG2_RX_SCFG_MASK = (3 << TAS2780_TDM_CFG2_RX_SCFG_SHIFT);
|
||||
static constexpr uint8_t TAS2780_TDM_CFG2_RX_WLEN_SHIFT = 2;
|
||||
static constexpr uint8_t TAS2780_TDM_CFG2_RX_WLEN_MASK = (3 << TAS2780_TDM_CFG2_RX_WLEN_SHIFT);
|
||||
static constexpr uint8_t TAS2780_TDM_CFG2_RX_WLEN_32BIT = (3 << TAS2780_TDM_CFG2_RX_WLEN_SHIFT);
|
||||
static constexpr uint8_t TAS2780_TDM_CFG2_RX_SLEN_MASK = (3 << 0);
|
||||
static constexpr uint8_t TAS2780_TDM_CFG2_RX_SLEN_32BIT = 2;
|
||||
|
||||
static constexpr uint8_t TAS2780_TDM_CFG5 = 0x0E; // TDM Configuration 5
|
||||
static constexpr uint8_t TAS2780_TDM_CFG5_TX_VSNS_EN_SLOT4 = 0x44; // vsns TX enable, slot 4
|
||||
static constexpr uint8_t TAS2780_TDM_CFG6 = 0x0F; // TDM Configuration 6
|
||||
static constexpr uint8_t TAS2780_TDM_CFG6_TX_ISNS_EN_SLOT0 = 0x40; // isns TX enable, slot 0
|
||||
|
||||
static constexpr uint8_t TAS2780_DVC = 0x1A; // Digital Volume Control
|
||||
static constexpr uint8_t TAS2780_DVC_MAX_ATTEN = 200; // 0 dB (0x00) to -100 dB (0xC8) in 0.5 dB steps
|
||||
|
||||
/* Interrupts */
|
||||
static constexpr uint8_t TAS2780_INT_MASK_ALL = 0xFF; // Mask all interrupts
|
||||
static constexpr uint8_t TAS2780_INT_MASK0 = 0x3B; // Interrupt Mask 0
|
||||
static constexpr uint8_t TAS2780_INT_MASK1 = 0x3C; // Interrupt Mask 1
|
||||
static constexpr uint8_t TAS2780_INT_MASK1_0 = 0x3D; // Interrupt Mask 1_0 (INT_LTCH1_0 group)
|
||||
static constexpr uint8_t TAS2780_INT_MASK2 = 0x40; // Interrupt Mask 2
|
||||
static constexpr uint8_t TAS2780_INT_MASK3 = 0x41; // Interrupt Mask 3
|
||||
static constexpr uint8_t TAS2780_INT_LTCH0 = 0x49; // Latched Interrupt Read-back 0, 1 and 1_0 follow
|
||||
static constexpr uint8_t TAS2780_INT_LTCH2 = 0x4F; // Latched Interrupt Read-back 2
|
||||
|
||||
static constexpr uint8_t TAS2780_INT_CLK_CFG = 0x5C; // Clock Setting and IRQZ
|
||||
static constexpr uint8_t TAS2780_INT_CLK_CFG_CLR_LATCH = (1 << 2); // Clear interrupt latches
|
||||
static constexpr uint8_t TAS2780_INT_CLK_CFG_MODE_MASK = 0x03; // Trigger mode field mask
|
||||
static constexpr uint8_t TAS2780_INT_CLK_CFG_MODE_LIVE = 0x00; // Trigger on any unmasked live interrupt
|
||||
static constexpr uint8_t TAS2780_PVDD_UVLO = 0x71; // UVLO Threshold
|
||||
static constexpr uint8_t TAS2780_PVDD_UVLO_2V76 = 0x03; // PVDD UVLO threshold = 2.76V
|
||||
|
||||
/* PAGE 0x01 */
|
||||
static constexpr uint8_t TAS2780_INIT_0 = 0x17; // Initialization
|
||||
static constexpr uint8_t TAS2780_INIT_0_VAL = 0xC8; // SARBurstMask=0, CMP_HYST_LP=1
|
||||
static constexpr uint8_t TAS2780_LSR = 0x19; // Modulation
|
||||
static constexpr uint8_t TAS2780_LSR_PWM_MODE = 0x00; // PWM modulation mode
|
||||
static constexpr uint8_t TAS2780_INIT_1 = 0x21; // Initialization
|
||||
static constexpr uint8_t TAS2780_INIT_1_VAL = 0x00; // Disable comparator hysteresis
|
||||
static constexpr uint8_t TAS2780_INIT_2 = 0x35; // Initialization
|
||||
static constexpr uint8_t TAS2780_INIT_2_VAL = 0x74; // Noise minimized
|
||||
|
||||
/* PAGE 0xFD */
|
||||
static constexpr uint8_t TAS2780_PAGE_FD_ACCESS = 0x0D; // Page 0xFD access unlock/lock register
|
||||
static constexpr uint8_t TAS2780_PAGE_FD_ACCESS_UNLOCK = 0x0D; // Unlock page 0xFD access
|
||||
static constexpr uint8_t TAS2780_PAGE_FD_ACCESS_LOCK = 0x00; // Lock page 0xFD access
|
||||
static constexpr uint8_t TAS2780_INIT_3 = 0x3E; // Initialization
|
||||
static constexpr uint8_t TAS2780_INIT_3_VAL = 0x4A; // Optimal Dmin
|
||||
|
||||
// CDS_MODE (2 bits) and VBAT1S_MODE (1 bit) per power mode 0..3, packed so nothing lands in RAM on ESP8266:
|
||||
// PWR_MODE0: 2/0, PWR_MODE1: 0/0, PWR_MODE2: 3/1, PWR_MODE3: 1/0
|
||||
static constexpr uint8_t POWER_MODE_CDS_MODES = 0x72;
|
||||
static constexpr uint8_t POWER_MODE_VBAT1S_MODES = 0x04;
|
||||
|
||||
// Latched interrupt bits per register (INT_LTCH0, 1, 1_0, 2, one byte each, low to high), split into faults
|
||||
// and informational events; bits outside both masks are undefined.
|
||||
static constexpr uint32_t TAS2780_INT_LTCH_ERROR_MASKS = 0x0FA058C7;
|
||||
static constexpr uint32_t TAS2780_INT_LTCH_INFO_MASKS = 0x00002138;
|
||||
|
||||
// An if chain rather than a switch: a switch table would land in rodata, which is RAM on ESP8266.
|
||||
static const LogString *fault_name(uint8_t reg, uint8_t bit) {
|
||||
const uint8_t key = (reg << 3) | bit;
|
||||
if (key == 0x00)
|
||||
return LOG_STR("Over temperature error");
|
||||
if (key == 0x01)
|
||||
return LOG_STR("Over current error");
|
||||
if (key == 0x02)
|
||||
return LOG_STR("TDM Clock Error");
|
||||
if (key == 0x03)
|
||||
return LOG_STR("Limiter active");
|
||||
if (key == 0x04)
|
||||
return LOG_STR("PVDD below limiter inflection point");
|
||||
if (key == 0x05)
|
||||
return LOG_STR("Limiter max attenuation");
|
||||
if (key == 0x06)
|
||||
return LOG_STR("BOP infinite hold");
|
||||
if (key == 0x07)
|
||||
return LOG_STR("BOP Mute");
|
||||
if (key == 0x08)
|
||||
return LOG_STR("Gain limiter active");
|
||||
if (key == 0x0B)
|
||||
return LOG_STR("Load Diagnostic mode fault status");
|
||||
if (key == 0x0D)
|
||||
return LOG_STR("Load diagnostic complete");
|
||||
if (key == 0x0E)
|
||||
return LOG_STR("OTP CRC error flag");
|
||||
if (key == 0x15)
|
||||
return LOG_STR("VBAT1S Under Voltage");
|
||||
if (key == 0x17)
|
||||
return LOG_STR("Internal PLL Clock Error");
|
||||
if (key == 0x18)
|
||||
return LOG_STR("PVDD UVLO");
|
||||
if (key == 0x19)
|
||||
return LOG_STR("Internal VBAT1S LDO Over Load");
|
||||
if (key == 0x1A)
|
||||
return LOG_STR("Internal VBAT1S LDO Over Voltage");
|
||||
if (key == 0x1B)
|
||||
return LOG_STR("Internal VBAT1S LDO Under Voltage");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TAS2780::setup() {
|
||||
if (!this->init_()) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->write_mode_ctrl_(TAS2780_MODE_CTRL_MODE_SFTW_SHTDWN);
|
||||
}
|
||||
|
||||
bool TAS2780::select_page_(uint8_t page) {
|
||||
if (this->current_page_ == page)
|
||||
return true;
|
||||
if (!this->write_byte(TAS2780_PAGE_SELECT, page)) {
|
||||
this->current_page_ = 0xFF;
|
||||
return false;
|
||||
}
|
||||
this->current_page_ = page;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TAS2780::update_bits_(uint8_t reg, uint8_t mask, uint8_t value) {
|
||||
uint8_t current;
|
||||
if (!this->select_page_(TAS2780_PAGE_0) || !this->read_byte(reg, ¤t)) {
|
||||
ESP_LOGE(TAG, "Failed to read register 0x%02X", reg);
|
||||
return false;
|
||||
}
|
||||
return this->write_byte(reg, (current & ~mask) | (value & mask));
|
||||
}
|
||||
|
||||
bool TAS2780::init_() {
|
||||
// Software reset (must select page 0 first; reset invalidates page cache)
|
||||
if (!this->select_page_(TAS2780_PAGE_0)) {
|
||||
ESP_LOGE(TAG, "I2C write failed during init");
|
||||
return false;
|
||||
}
|
||||
this->current_page_ = 0xFF;
|
||||
this->applied_power_mode_ = 0xFF; // only a complete init makes the chip trustworthy again
|
||||
this->reg(TAS2780_SW_RESET) = TAS2780_SW_RESET_CMD;
|
||||
delay(1);
|
||||
|
||||
uint8_t chd1;
|
||||
if (!this->read_byte(TAS2780_DC_BLK1, &chd1)) {
|
||||
ESP_LOGE(TAG, "I2C read failed during init");
|
||||
return false;
|
||||
}
|
||||
if (chd1 != TAS2780_DC_BLK1_RESET_VAL) {
|
||||
ESP_LOGE(TAG, "Init failed (DC_BLK1=0x%02X, expected 0x%02X)", chd1, TAS2780_DC_BLK1_RESET_VAL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->select_page_(TAS2780_PAGE_0)) {
|
||||
return false;
|
||||
}
|
||||
this->reg(TAS2780_TDM_CFG5) = TAS2780_TDM_CFG5_TX_VSNS_EN_SLOT4;
|
||||
this->reg(TAS2780_TDM_CFG6) = TAS2780_TDM_CFG6_TX_ISNS_EN_SLOT0;
|
||||
|
||||
if (!this->select_page_(TAS2780_PAGE_1)) {
|
||||
return false;
|
||||
}
|
||||
this->reg(TAS2780_LSR) = TAS2780_LSR_PWM_MODE;
|
||||
this->reg(TAS2780_INIT_0) = TAS2780_INIT_0_VAL;
|
||||
this->reg(TAS2780_INIT_1) = TAS2780_INIT_1_VAL;
|
||||
this->reg(TAS2780_INIT_2) = TAS2780_INIT_2_VAL;
|
||||
|
||||
if (!this->select_page_(TAS2780_PAGE_FD)) {
|
||||
return false;
|
||||
}
|
||||
this->reg(TAS2780_PAGE_FD_ACCESS) = TAS2780_PAGE_FD_ACCESS_UNLOCK;
|
||||
this->reg(TAS2780_INIT_3) = TAS2780_INIT_3_VAL;
|
||||
this->reg(TAS2780_PAGE_FD_ACCESS) = TAS2780_PAGE_FD_ACCESS_LOCK;
|
||||
|
||||
if (!this->select_page_(TAS2780_PAGE_0)) {
|
||||
return false;
|
||||
}
|
||||
if (!this->set_power_mode_(this->power_mode_))
|
||||
return false;
|
||||
|
||||
// When Y bridge is used (eg. PWR_MODE1) PVDD UVLO threshold needs to be set 2.5 V above VBAT1S level.
|
||||
// UVLO = 1.753V + val * 0.332V
|
||||
this->reg(TAS2780_PVDD_UVLO) = TAS2780_PVDD_UVLO_2V76;
|
||||
|
||||
// Mask all interrupt groups on the IRQZ pin, events are polled via update()
|
||||
this->reg(TAS2780_INT_MASK0) = TAS2780_INT_MASK_ALL;
|
||||
this->reg(TAS2780_INT_MASK1) = TAS2780_INT_MASK_ALL;
|
||||
this->reg(TAS2780_INT_MASK1_0) = TAS2780_INT_MASK_ALL;
|
||||
this->reg(TAS2780_INT_MASK2) = TAS2780_INT_MASK_ALL;
|
||||
this->reg(TAS2780_INT_MASK3) = TAS2780_INT_MASK_ALL;
|
||||
|
||||
if (!this->update_bits_(TAS2780_INT_CLK_CFG, TAS2780_INT_CLK_CFG_MODE_MASK, TAS2780_INT_CLK_CFG_MODE_LIVE))
|
||||
return false;
|
||||
|
||||
// Software reset sets DVC back to 0 dB (full volume)
|
||||
if (!this->apply_config())
|
||||
return false;
|
||||
this->applied_power_mode_ = this->power_mode_;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TAS2780::activate() {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
ESP_LOGD(TAG, "Activating (PWR_MODE:%u)", this->power_mode_);
|
||||
this->clear_latches_();
|
||||
if (this->power_mode_ != this->applied_power_mode_ && !this->reinit_())
|
||||
return;
|
||||
this->active_ = this->write_mode_ctrl_(this->active_mode_());
|
||||
}
|
||||
|
||||
void TAS2780::deactivate() {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
ESP_LOGD(TAG, "Deactivating");
|
||||
this->active_ = false;
|
||||
this->write_mode_ctrl_(TAS2780_MODE_CTRL_MODE_SFTW_SHTDWN);
|
||||
}
|
||||
|
||||
void TAS2780::reset() {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
if (this->reinit_() && this->active_)
|
||||
this->activate();
|
||||
}
|
||||
|
||||
bool TAS2780::reinit_() {
|
||||
if (!this->init_()) {
|
||||
ESP_LOGE(TAG, "Re-initialization failed");
|
||||
this->status_set_error(LOG_STR("Init failed"));
|
||||
return false;
|
||||
}
|
||||
this->status_clear_error();
|
||||
return true;
|
||||
}
|
||||
|
||||
void TAS2780::set_power_mode(uint8_t power_mode) {
|
||||
// Lambda-supplied values bypass schema validation; refuse before anything touches the chip
|
||||
if (power_mode >= 4) {
|
||||
ESP_LOGE(TAG, "Invalid power mode %u, must be 0-3", power_mode);
|
||||
return;
|
||||
}
|
||||
this->power_mode_ = power_mode;
|
||||
}
|
||||
|
||||
bool TAS2780::set_power_mode_(uint8_t power_mode) {
|
||||
uint8_t cds_mode = (POWER_MODE_CDS_MODES >> (power_mode * 2)) & 0x03;
|
||||
uint8_t vbat1s_mode = (POWER_MODE_VBAT1S_MODES >> power_mode) & 0x01;
|
||||
return this->update_bits_(TAS2780_CHNL_0, TAS2780_CHNL_0_CDS_MODE_MASK, cds_mode << TAS2780_CHNL_0_CDS_MODE_SHIFT) &&
|
||||
this->update_bits_(TAS2780_DC_BLK0, TAS2780_DC_BLK0_VBAT1S_MODE_MASK, vbat1s_mode ? 0xFF : 0);
|
||||
}
|
||||
|
||||
void TAS2780::clear_latches_() {
|
||||
this->update_bits_(TAS2780_INT_CLK_CFG, TAS2780_INT_CLK_CFG_CLR_LATCH, TAS2780_INT_CLK_CFG_CLR_LATCH);
|
||||
}
|
||||
|
||||
// Returns true if any latched interrupt flag is set
|
||||
bool TAS2780::log_error_states_() {
|
||||
uint8_t latched[4];
|
||||
if (!this->select_page_(TAS2780_PAGE_0) || !this->read_bytes(TAS2780_INT_LTCH0, latched, 3) ||
|
||||
!this->read_byte(TAS2780_INT_LTCH2, &latched[3])) {
|
||||
return false;
|
||||
}
|
||||
// LDMODE is a two-bit field in INT_LTCH1; report it once
|
||||
if (latched[1] & (1 << 4))
|
||||
latched[1] = (latched[1] & ~(1 << 4)) | (1 << 3);
|
||||
for (uint8_t reg = 0; reg < 4; reg++) {
|
||||
uint8_t errors = latched[reg] & (TAS2780_INT_LTCH_ERROR_MASKS >> (reg * 8));
|
||||
uint8_t infos = latched[reg] & (TAS2780_INT_LTCH_INFO_MASKS >> (reg * 8));
|
||||
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||
if (errors & (1 << bit)) {
|
||||
ESP_LOGE(TAG, "%s", LOG_STR_ARG(fault_name(reg, bit)));
|
||||
} else if (infos & (1 << bit)) {
|
||||
ESP_LOGD(TAG, "%s", LOG_STR_ARG(fault_name(reg, bit)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return (latched[0] | latched[1] | latched[2] | latched[3]) != 0;
|
||||
}
|
||||
|
||||
void TAS2780::update() {
|
||||
// Latches hold until cleared; without this the same events are logged on every update
|
||||
if (this->log_error_states_())
|
||||
this->clear_latches_();
|
||||
}
|
||||
|
||||
void TAS2780::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Audio Amplifier:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Power Mode: %u\n"
|
||||
" Amp Level: %u\n"
|
||||
" Volume Range: %.2f - %.2f\n"
|
||||
" Channel: %s",
|
||||
this->power_mode_, this->amp_level_, this->vol_range_min_, this->vol_range_max_,
|
||||
this->selected_channel_ == LEFT_CHANNEL ? LOG_STR_LITERAL("Left")
|
||||
: this->selected_channel_ == RIGHT_CHANNEL ? LOG_STR_LITERAL("Right")
|
||||
: LOG_STR_LITERAL("Mono Downmix"));
|
||||
}
|
||||
|
||||
bool TAS2780::write_mode_ctrl_(uint8_t mode) {
|
||||
return this->update_bits_(TAS2780_MODE_CTRL, TAS2780_MODE_CTRL_MODE_MASK, mode);
|
||||
}
|
||||
|
||||
uint8_t TAS2780::active_mode_() const {
|
||||
return this->is_muted_ ? TAS2780_MODE_CTRL_MODE_ACTIVE_MUTED : TAS2780_MODE_CTRL_MODE_ACTIVE;
|
||||
}
|
||||
|
||||
bool TAS2780::set_mute_(bool muted) {
|
||||
bool previous = this->is_muted_;
|
||||
this->is_muted_ = muted;
|
||||
uint8_t mode_ctrl;
|
||||
if (!this->select_page_(TAS2780_PAGE_0) || !this->read_byte(TAS2780_MODE_CTRL, &mode_ctrl)) {
|
||||
ESP_LOGE(TAG, "Failed to read MODE_CTRL");
|
||||
this->is_muted_ = previous;
|
||||
return false;
|
||||
}
|
||||
uint8_t current_mode = mode_ctrl & TAS2780_MODE_CTRL_MODE_MASK;
|
||||
// Only switch between active/muted if device is active; don't wake from shutdown
|
||||
if ((current_mode == TAS2780_MODE_CTRL_MODE_ACTIVE || current_mode == TAS2780_MODE_CTRL_MODE_ACTIVE_MUTED) &&
|
||||
!this->write_byte(TAS2780_MODE_CTRL, (mode_ctrl & ~TAS2780_MODE_CTRL_MODE_MASK) | this->active_mode_())) {
|
||||
ESP_LOGE(TAG, "Failed to write MODE_CTRL");
|
||||
this->is_muted_ = previous;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TAS2780::set_volume(float volume) {
|
||||
float previous = this->volume_;
|
||||
this->volume_ = clamp(volume, 0.0f, 1.0f);
|
||||
if (!this->write_volume_()) {
|
||||
this->volume_ = previous;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TAS2780::write_volume_() {
|
||||
// Lambda-supplied volume ranges are not bounded or ordered
|
||||
float range_min = std::min(this->vol_range_min_, this->vol_range_max_);
|
||||
float range_max = std::max(this->vol_range_min_, this->vol_range_max_);
|
||||
float volume = clamp(std::lerp(range_min, range_max, this->volume_), 0.0f, 1.0f);
|
||||
uint8_t dvc = remap<uint8_t, float>(volume, 0.0f, 1.0f, TAS2780_DVC_MAX_ATTEN, 0);
|
||||
ESP_LOGD(TAG, "Setting attenuation to: %u", dvc);
|
||||
return this->select_page_(TAS2780_PAGE_0) && this->write_byte(TAS2780_DVC, dvc);
|
||||
}
|
||||
|
||||
bool TAS2780::apply_config() { return this->apply_amp_and_channel_config_() && this->write_volume_(); }
|
||||
|
||||
bool TAS2780::apply_amp_and_channel_config_() {
|
||||
// Lambda-supplied values bypass schema validation
|
||||
if (this->amp_level_ > TAS2780_AMP_LEVEL_MAX) {
|
||||
ESP_LOGW(TAG, "Amp level %u out of range, using %u", this->amp_level_, TAS2780_AMP_LEVEL_MAX);
|
||||
this->amp_level_ = TAS2780_AMP_LEVEL_MAX;
|
||||
}
|
||||
ESP_LOGD(TAG, "Update amp to level idx: %u", this->amp_level_);
|
||||
if (!this->update_bits_(TAS2780_CHNL_0, TAS2780_CHNL_0_AMP_LEVEL_MASK,
|
||||
this->amp_level_ << TAS2780_CHNL_0_AMP_LEVEL_SHIFT)) {
|
||||
return false;
|
||||
}
|
||||
return this->update_bits_(
|
||||
TAS2780_TDM_CFG2, TAS2780_TDM_CFG2_RX_SCFG_MASK | TAS2780_TDM_CFG2_RX_WLEN_MASK | TAS2780_TDM_CFG2_RX_SLEN_MASK,
|
||||
(this->selected_channel_ << TAS2780_TDM_CFG2_RX_SCFG_SHIFT) | TAS2780_TDM_CFG2_RX_WLEN_32BIT |
|
||||
TAS2780_TDM_CFG2_RX_SLEN_32BIT);
|
||||
}
|
||||
|
||||
} // namespace esphome::tas2780
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/audio_dac/audio_dac.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome::tas2780 {
|
||||
|
||||
// Values are the TDM_CFG2 RX_SCFG field.
|
||||
enum ChannelSelect : uint8_t { LEFT_CHANNEL = 1, RIGHT_CHANNEL = 2, MONO_DWN_MIX = 3 };
|
||||
|
||||
class TAS2780 : public audio_dac::AudioDac, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::IO; }
|
||||
void update() override;
|
||||
|
||||
/// Software reset and re-initialization, leaving the amplifier on or off as last requested.
|
||||
void reset();
|
||||
/// Activate with the configured power mode; re-initializes first when the mode changed.
|
||||
void activate();
|
||||
void deactivate();
|
||||
/// Write the amp level, channel and volume range to the device.
|
||||
bool apply_config();
|
||||
|
||||
bool set_mute_off() override { return this->set_mute_(false); }
|
||||
bool set_mute_on() override { return this->set_mute_(true); }
|
||||
bool set_volume(float volume) override;
|
||||
|
||||
bool is_muted() override { return this->is_muted_; }
|
||||
float volume() override { return this->volume_; }
|
||||
|
||||
void set_amp_level(uint8_t amp_level) { this->amp_level_ = amp_level; }
|
||||
void set_power_mode(uint8_t power_mode);
|
||||
void set_vol_range_min(float min_val) { this->vol_range_min_ = min_val; }
|
||||
void set_vol_range_max(float max_val) { this->vol_range_max_ = max_val; }
|
||||
void set_selected_channel(ChannelSelect channel) { this->selected_channel_ = channel; }
|
||||
|
||||
protected:
|
||||
bool select_page_(uint8_t page);
|
||||
bool update_bits_(uint8_t reg, uint8_t mask, uint8_t value);
|
||||
bool init_();
|
||||
bool reinit_();
|
||||
bool set_power_mode_(uint8_t power_mode);
|
||||
bool apply_amp_and_channel_config_();
|
||||
bool write_mode_ctrl_(uint8_t mode);
|
||||
uint8_t active_mode_() const;
|
||||
bool set_mute_(bool muted);
|
||||
bool write_volume_();
|
||||
bool log_error_states_();
|
||||
void clear_latches_();
|
||||
|
||||
float volume_{0};
|
||||
float vol_range_min_{0.3f};
|
||||
float vol_range_max_{1.0f};
|
||||
uint8_t current_page_{0xFF};
|
||||
uint8_t power_mode_{2};
|
||||
uint8_t applied_power_mode_{0xFF};
|
||||
uint8_t amp_level_{8};
|
||||
ChannelSelect selected_channel_{MONO_DWN_MIX};
|
||||
bool active_{false};
|
||||
};
|
||||
|
||||
} // namespace esphome::tas2780
|
||||
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome::text_sensor {
|
||||
|
||||
class TextSensorStateTrigger final : public Trigger<std::string> {
|
||||
public:
|
||||
explicit TextSensorStateTrigger(TextSensor *parent) {
|
||||
parent->add_on_state_callback([this](const std::string &value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class TextSensorStateRawTrigger final : public Trigger<std::string> {
|
||||
public:
|
||||
explicit TextSensorStateRawTrigger(TextSensor *parent) {
|
||||
parent->add_on_raw_state_callback([this](const std::string &value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::text_sensor
|
||||
@@ -6,7 +6,6 @@ import logging
|
||||
import tzlocal
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
||||
from esphome.config_helpers import filter_source_files_from_defines
|
||||
@@ -18,7 +17,6 @@ from esphome.const import (
|
||||
CONF_DAYS_OF_WEEK,
|
||||
CONF_HOUR,
|
||||
CONF_HOURS,
|
||||
CONF_ID,
|
||||
CONF_MINUTE,
|
||||
CONF_MINUTES,
|
||||
CONF_MONTHS,
|
||||
@@ -49,7 +47,6 @@ time_ns = cg.esphome_ns.namespace("time")
|
||||
RealTimeClock = time_ns.class_("RealTimeClock", cg.PollingComponent)
|
||||
CronTrigger = time_ns.class_("CronTrigger", automation.Trigger.template(), cg.Component)
|
||||
SyncTrigger = time_ns.class_("SyncTrigger", automation.Trigger.template(), cg.Component)
|
||||
TimeHasTimeCondition = time_ns.class_("TimeHasTimeCondition", Condition)
|
||||
|
||||
# C++ types for pre-parsed timezone struct generation
|
||||
DSTRuleType_cpp = time_ns.enum("DSTRuleType", is_class=True)
|
||||
@@ -469,18 +466,15 @@ async def to_code(config):
|
||||
cg.add_global(time_ns.using)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
automation.register_apply_condition(
|
||||
"time.has_time",
|
||||
TimeHasTimeCondition,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(RealTimeClock),
|
||||
}
|
||||
),
|
||||
"now().is_valid()",
|
||||
)
|
||||
async def time_has_time_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
|
||||
|
||||
# posix_tz.cpp is fully #ifdef'd on USE_TIME_TIMEZONE, set only when a
|
||||
|
||||
@@ -44,13 +44,4 @@ class RealTimeClock : public PollingComponent {
|
||||
LazyCallbackManager<void()> time_sync_callback_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class TimeHasTimeCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
TimeHasTimeCondition(RealTimeClock *parent) : parent_(parent) {}
|
||||
bool check(const Ts &...x) override { return this->parent_->now().is_valid(); }
|
||||
|
||||
protected:
|
||||
RealTimeClock *parent_;
|
||||
};
|
||||
|
||||
} // namespace esphome::time
|
||||
|
||||
@@ -9,7 +9,7 @@ static const char *const TAG = "tuya.light";
|
||||
void TuyaLight::setup() {
|
||||
if (this->color_temperature_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->color_temperature_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
if (this->state_->current_values != this->state_->remote_values) {
|
||||
if (this->state_->is_transitioning()) {
|
||||
ESP_LOGD(TAG, "Light is transitioning, datapoint change ignored");
|
||||
return;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ void TuyaLight::setup() {
|
||||
}
|
||||
if (this->dimmer_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->dimmer_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
if (this->state_->current_values != this->state_->remote_values) {
|
||||
if (this->state_->is_transitioning()) {
|
||||
ESP_LOGD(TAG, "Light is transitioning, datapoint change ignored");
|
||||
return;
|
||||
}
|
||||
@@ -39,7 +39,7 @@ void TuyaLight::setup() {
|
||||
}
|
||||
if (switch_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->switch_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
if (this->state_->current_values != this->state_->remote_values) {
|
||||
if (this->state_->is_transitioning()) {
|
||||
ESP_LOGD(TAG, "Light is transitioning, datapoint change ignored");
|
||||
return;
|
||||
}
|
||||
@@ -51,7 +51,7 @@ void TuyaLight::setup() {
|
||||
}
|
||||
if (color_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->color_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
if (this->state_->current_values != this->state_->remote_values) {
|
||||
if (this->state_->is_transitioning()) {
|
||||
ESP_LOGD(TAG, "Light is transitioning, datapoint change ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,22 @@ import re
|
||||
from esphome import automation, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_DATA_BITS, CONF_PARITY, CONF_STOP_BITS
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32C61,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32H4,
|
||||
VARIANT_ESP32H21,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32S31,
|
||||
variant_filtered_enum,
|
||||
)
|
||||
from esphome.config_helpers import (
|
||||
filter_source_files_from_defines,
|
||||
filter_source_files_from_platform,
|
||||
@@ -179,6 +195,33 @@ UART_PARITY_OPTIONS = {
|
||||
CONF_FLUSH_TIMEOUT = "flush_timeout"
|
||||
CONF_RX_FULL_THRESHOLD = "rx_full_threshold"
|
||||
CONF_RX_TIMEOUT = "rx_timeout"
|
||||
CONF_CLOCK_SOURCE = "clock_source"
|
||||
|
||||
UARTClockSource = cg.global_ns.enum("uart_sclk_t")
|
||||
UART_CLOCK_SOURCES = {
|
||||
"DEFAULT": UARTClockSource.UART_SCLK_DEFAULT,
|
||||
"APB": UARTClockSource.UART_SCLK_APB,
|
||||
"XTAL": UARTClockSource.UART_SCLK_XTAL,
|
||||
"RTC": UARTClockSource.UART_SCLK_RTC,
|
||||
"REF_TICK": UARTClockSource.UART_SCLK_REF_TICK,
|
||||
}
|
||||
|
||||
# Keep in sync with SOC_UART_SUPPORT_* in ESP-IDF's per-variant soc_caps.h.
|
||||
UART_CLOCK_SOURCES_BY_VARIANT = {
|
||||
VARIANT_ESP32: ["DEFAULT", "APB", "REF_TICK"],
|
||||
VARIANT_ESP32C2: ["DEFAULT", "XTAL", "RTC"],
|
||||
VARIANT_ESP32C3: ["DEFAULT", "APB", "XTAL", "RTC"],
|
||||
VARIANT_ESP32C5: ["DEFAULT", "XTAL", "RTC"],
|
||||
VARIANT_ESP32C6: ["DEFAULT", "XTAL", "RTC"],
|
||||
VARIANT_ESP32C61: ["DEFAULT", "XTAL", "RTC"],
|
||||
VARIANT_ESP32H2: ["DEFAULT", "XTAL", "RTC"],
|
||||
VARIANT_ESP32H4: ["DEFAULT", "XTAL", "RTC"],
|
||||
VARIANT_ESP32H21: ["DEFAULT", "XTAL", "RTC"],
|
||||
VARIANT_ESP32P4: ["DEFAULT", "XTAL", "RTC"],
|
||||
VARIANT_ESP32S2: ["DEFAULT", "APB", "REF_TICK"],
|
||||
VARIANT_ESP32S3: ["DEFAULT", "APB", "XTAL", "RTC"],
|
||||
VARIANT_ESP32S31: ["DEFAULT", "XTAL", "RTC"],
|
||||
}
|
||||
|
||||
UARTDirection = uart_ns.enum("UARTDirection")
|
||||
UART_DIRECTIONS = {
|
||||
@@ -263,6 +306,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_FLUSH_TIMEOUT): cv.All(
|
||||
cv.only_on_esp32, cv.positive_time_period_milliseconds
|
||||
),
|
||||
cv.Optional(CONF_CLOCK_SOURCE): cv.All(
|
||||
cv.only_on_esp32,
|
||||
variant_filtered_enum(UART_CLOCK_SOURCES_BY_VARIANT, upper=True),
|
||||
),
|
||||
cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True),
|
||||
cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8),
|
||||
cv.Optional(CONF_PARITY, default="NONE"): cv.enum(
|
||||
@@ -344,6 +391,9 @@ async def to_code(config):
|
||||
cg.add(var.set_rx_timeout(config[CONF_RX_TIMEOUT]))
|
||||
if CONF_FLUSH_TIMEOUT in config:
|
||||
cg.add(var.set_flush_timeout(config[CONF_FLUSH_TIMEOUT]))
|
||||
# The member already defaults to UART_SCLK_DEFAULT, so only emit a real choice
|
||||
if (clock_source := config.get(CONF_CLOCK_SOURCE, "DEFAULT")) != "DEFAULT":
|
||||
cg.add(var.set_clock_source(UART_CLOCK_SOURCES[clock_source]))
|
||||
cg.add(var.set_stop_bits(config[CONF_STOP_BITS]))
|
||||
cg.add(var.set_data_bits(config[CONF_DATA_BITS]))
|
||||
cg.add(var.set_parity(config[CONF_PARITY]))
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_private/gpio.h"
|
||||
#include "soc/gpio_num.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/uart_pins.h"
|
||||
|
||||
#ifdef USE_UART_WAKE_LOOP_ON_RX
|
||||
@@ -37,6 +38,32 @@ static constexpr bool is_default_uart0_pin(int8_t pin_num) {
|
||||
return pin_num == U0TXD_GPIO_NUM || pin_num == U0RXD_GPIO_NUM;
|
||||
}
|
||||
|
||||
// clock_source_ is stored in a byte; every uart_sclk_t value is a soc_module_clk_t below SOC_MOD_CLK_INVALID
|
||||
static_assert(SOC_MOD_CLK_INVALID <= UINT8_MAX, "uart_sclk_t no longer fits in uint8_t clock_source_");
|
||||
|
||||
static const LogString *clock_source_to_str(uart_sclk_t clock_source) {
|
||||
switch (clock_source) {
|
||||
#if SOC_UART_SUPPORT_APB_CLK
|
||||
case UART_SCLK_APB:
|
||||
return LOG_STR("APB");
|
||||
#endif
|
||||
#if SOC_UART_SUPPORT_XTAL_CLK
|
||||
case UART_SCLK_XTAL:
|
||||
return LOG_STR("XTAL");
|
||||
#endif
|
||||
#if SOC_UART_SUPPORT_RTC_CLK
|
||||
case UART_SCLK_RTC:
|
||||
return LOG_STR("RTC");
|
||||
#endif
|
||||
#if SOC_UART_SUPPORT_REF_TICK
|
||||
case UART_SCLK_REF_TICK:
|
||||
return LOG_STR("REF_TICK");
|
||||
#endif
|
||||
default:
|
||||
return clock_source == UART_SCLK_DEFAULT ? LOG_STR("DEFAULT") : LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
uart_config_t IDFUARTComponent::get_config_() {
|
||||
uart_parity_t parity = UART_PARITY_DISABLE;
|
||||
if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
|
||||
@@ -70,7 +97,7 @@ uart_config_t IDFUARTComponent::get_config_() {
|
||||
uart_config.parity = parity;
|
||||
uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2;
|
||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
uart_config.source_clk = static_cast<uart_sclk_t>(this->clock_source_);
|
||||
uart_config.rx_flow_ctrl_thresh = 122;
|
||||
|
||||
return uart_config;
|
||||
@@ -336,12 +363,14 @@ void IDFUARTComponent::dump_config() {
|
||||
" Baud Rate: %" PRIu32 " baud\n"
|
||||
" Data Bits: %u\n"
|
||||
" Parity: %s\n"
|
||||
" Stop bits: %u"
|
||||
" Stop bits: %u\n"
|
||||
" Clock Source: %s"
|
||||
#ifdef USE_UART_WAKE_LOOP_ON_RX
|
||||
"\n Wake on data RX: ENABLED"
|
||||
#endif
|
||||
,
|
||||
this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_);
|
||||
this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_,
|
||||
LOG_STR_ARG(clock_source_to_str(static_cast<uart_sclk_t>(this->clock_source_))));
|
||||
this->check_logger_conflict();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ class IDFUARTComponent final : public UARTComponent, public Component {
|
||||
|
||||
void set_flush_timeout(uint32_t flush_timeout_ms) override { this->flush_timeout_ms_ = flush_timeout_ms; }
|
||||
|
||||
void set_clock_source(uart_sclk_t clock_source) { this->clock_source_ = static_cast<uint8_t>(clock_source); }
|
||||
|
||||
uint8_t get_hw_serial_number() { return this->uart_num_; }
|
||||
|
||||
/// Discard everything received so far: the peek cache and the driver's RX buffer.
|
||||
@@ -105,7 +107,8 @@ class IDFUARTComponent final : public UARTComponent, public Component {
|
||||
|
||||
bool has_peek_{false};
|
||||
uint8_t peek_byte_{0};
|
||||
uint32_t flush_timeout_ms_{0}; ///< 0 means wait indefinitely (portMAX_DELAY).
|
||||
uint8_t clock_source_{UART_SCLK_DEFAULT}; ///< uart_sclk_t stored in a byte; the IDF values are all small.
|
||||
uint32_t flush_timeout_ms_{0}; ///< 0 means wait indefinitely (portMAX_DELAY).
|
||||
|
||||
#ifdef USE_UART_WAKE_LOOP_ON_RX
|
||||
// ISR callback for UART RX data notification — wakes the main loop directly.
|
||||
|
||||
@@ -2,6 +2,7 @@ from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components.const import UNIT_COUNTS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTUAL_GAIN,
|
||||
@@ -31,7 +32,6 @@ from esphome.types import ConfigType
|
||||
CODEOWNERS = ["@latonita"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
UNIT_COUNTS = "#"
|
||||
ICON_MULTIPLICATION = "mdi:multiplication"
|
||||
ICON_BRIGHTNESS_7 = "mdi:brightness-7"
|
||||
|
||||
|
||||
@@ -1015,7 +1015,7 @@ json::SerializationBuffer<> WebServer::light_json_(light::LightState *obj, JsonD
|
||||
json::JsonBuilder builder;
|
||||
JsonObject root = builder.root();
|
||||
|
||||
set_json_value(root, obj, "light", obj->remote_values.is_on() ? "ON" : "OFF", start_config);
|
||||
set_json_value(root, obj, "light", obj->get_reported_values().is_on() ? "ON" : "OFF", start_config);
|
||||
|
||||
light::LightJSONSchema::dump_json(*obj, root);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
@@ -36,12 +37,8 @@ extern const size_t ESPHOME_WEBSERVER_JS_INCLUDE_SIZE;
|
||||
|
||||
namespace esphome::web_server {
|
||||
|
||||
// Type for parameter names that can be stored in flash on ESP8266
|
||||
#ifdef USE_ESP8266
|
||||
using ParamNameType = const __FlashStringHelper *;
|
||||
#else
|
||||
using ParamNameType = const char *;
|
||||
#endif
|
||||
// Parameter names live in flash on ESP8266
|
||||
using ParamNameType = ProgmemStr;
|
||||
|
||||
// All platforms need to defer actions to main loop thread.
|
||||
// Multi-core platforms need this for thread safety.
|
||||
|
||||
@@ -769,6 +769,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add_build_flag("-Wno-unused-variable")
|
||||
cg.add_build_flag("-Wno-unused-but-set-variable")
|
||||
cg.add_build_flag("-Wno-sign-compare")
|
||||
cg.add_build_flag("-Wno-unused-function")
|
||||
# C++20 deprecated ++/--, compound assignment, and chained assignment on
|
||||
# volatile lvalues; GCC warns via -Wvolatile, on by default at gnu++20.
|
||||
# C++23 (P2327R1) removed the deprecation for compound assignment, so the
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user