Merge remote-tracking branch 'upstream/dev' into integration

This commit is contained in:
J. Nick Koston
2026-09-24 16:11:07 +01:00
152 changed files with 6796 additions and 4897 deletions
+1
View File
@@ -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
View File
@@ -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,
+7 -2
View File
@@ -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)
+6 -24
View File
@@ -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;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -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"));
}
+65 -58
View File
@@ -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
View File
@@ -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 -2
View File
@@ -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,
+11 -38
View File
@@ -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)
-41
View File
@@ -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
+5 -16
View File
@@ -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) {}
-7
View File
@@ -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
+2 -8
View File
@@ -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",
+5 -13
View File
@@ -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
+21 -11
View File
@@ -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_{};
+1
View File
@@ -61,3 +61,4 @@ ICON_SOLAR_POWER = "mdi:solar-power"
KEY_METADATA = "metadata"
UNIT_AMPERE_HOUR = "Ah"
UNIT_COUNTS = "#"
+40 -245
View File
@@ -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:
+5 -94
View File
@@ -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"
+8 -1
View File
@@ -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
),
)
-16
View File
@@ -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
+3 -11
View File
@@ -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
+5 -7
View File
@@ -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_; }
+8 -22
View File
@@ -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)
-17
View File
@@ -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) {
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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)
+15
View File
@@ -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)
-17
View File
@@ -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) {
+10 -21
View File
@@ -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")]));
}
+46 -10
View File
@@ -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();
+43
View File
@@ -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_;
};
+5
View File
@@ -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();
}
-2
View File
@@ -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_(
+9 -22
View File
@@ -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)
-13
View File
@@ -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 {
-3
View File
@@ -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;
+17 -4
View File
@@ -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)
+5 -5
View File
@@ -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));
+3 -4
View File
@@ -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
+8 -8
View File
@@ -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 -1
View File
@@ -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")
+1 -1
View File
@@ -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")
+1 -1
View File
@@ -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")
-2
View File
@@ -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"
+2 -2
View File
@@ -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,
+2 -6
View File
@@ -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
+18 -30
View File
@@ -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
+1 -1
View File
@@ -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;
}
+4 -4
View File
@@ -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;
+2 -7
View File
@@ -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(
-9
View File
@@ -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) {}
+1 -5
View File
@@ -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)
+2 -8
View File
@@ -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
+2 -5
View File
@@ -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;
+4 -16
View File
@@ -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;
}
-7
View File
@@ -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) {}
+11 -33
View File
@@ -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 = (
+5 -25
View File
@@ -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 -1
View File
@@ -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
+2 -19
View File
@@ -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
+2 -7
View File
@@ -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)
+1 -7
View File
@@ -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
+4 -23
View File
@@ -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.
+3 -3
View File
@@ -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_(
+6 -1
View File
@@ -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",
)
-14
View File
@@ -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) {}
+6 -17
View File
@@ -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(
-10
View File
@@ -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
+5 -13
View File
@@ -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)
-48
View File
@@ -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_
+1
View File
@@ -0,0 +1 @@
CODEOWNERS = ["@remcom"]
+108
View File
@@ -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]))
+406
View File
@@ -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, &current)) {
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
+65
View File
@@ -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
+2 -8
View File
@@ -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
+4 -4
View File
@@ -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;
}
+50
View File
@@ -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.
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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) {
+3 -6
View File
@@ -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.
+1
View File
@@ -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