mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 18:22:48 +00:00
Compare commits
53
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
937b73c8f1 | ||
|
|
4a2f17bc8b | ||
|
|
253793c4df | ||
|
|
3ef0acd4b2 | ||
|
|
73ca04579c | ||
|
|
4ccc5fa37e | ||
|
|
f70880ce96 | ||
|
|
057bf88e4a | ||
|
|
16757a84e6 | ||
|
|
a9111a8993 | ||
|
|
211fd3ce54 | ||
|
|
9df7e825d3 | ||
|
|
e63428ddce | ||
|
|
f8e8295088 | ||
|
|
9f5ac44db1 | ||
|
|
6f71b9102b | ||
|
|
04da1827e3 | ||
|
|
d2fefe2299 | ||
|
|
f1f83cda07 | ||
|
|
7dc98c683b | ||
|
|
071fc08e32 | ||
|
|
c27cbb132d | ||
|
|
7e308bb83d | ||
|
|
68235f34c5 | ||
|
|
b59518f6ab | ||
|
|
da148bedfe | ||
|
|
b0c9a7d5bd | ||
|
|
22177680c4 | ||
|
|
e92b1e27ca | ||
|
|
fed5dd6a9d | ||
|
|
cbd985826c | ||
|
|
2ce2b7b25c | ||
|
|
acc9324fa8 | ||
|
|
cbb4c49011 | ||
|
|
afc5c29569 | ||
|
|
993c96875c | ||
|
|
75360a5d52 | ||
|
|
8b11d3da39 | ||
|
|
5790181f44 | ||
|
|
4780936867 | ||
|
|
22959ea1cf | ||
|
|
e9aecb5fe8 | ||
|
|
6768cb818b | ||
|
|
1861d8282f | ||
|
|
c39ffd7099 | ||
|
|
1671bb6dd3 | ||
|
|
0ceefd5f91 | ||
|
|
4477e35d66 | ||
|
|
e98c4bd375 | ||
|
|
eae8ff7f91 | ||
|
|
f24e1f31f2 | ||
|
|
4cbced407d | ||
|
|
64127632f5 |
@@ -32,7 +32,7 @@ runs:
|
||||
# detects the activated venv via ``VIRTUAL_ENV`` so the venv layout
|
||||
# downstream jobs rely on is preserved.
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: Set up uv
|
||||
# ``--system`` (below) installs into the setup-python interpreter;
|
||||
# no venv is created or restored by this workflow.
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull-request-only workflow: a save could never be shared and
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
# detects the activated venv via ``VIRTUAL_ENV`` so downstream jobs
|
||||
# that ``. venv/bin/activate`` see an identical layout.
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
@@ -413,7 +413,7 @@ jobs:
|
||||
- name: Set up uv
|
||||
# Only needed on cache miss to populate the venv.
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
@@ -1274,7 +1274,7 @@ jobs:
|
||||
# install step (order-of-magnitude faster on cold boots,
|
||||
# with its own wheel cache). actions/setup-python still
|
||||
# provides the interpreter.
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
# setup-python interpreter so subsequent ``prek`` /
|
||||
# ``script/run-in-env.py`` steps find the deps without a
|
||||
# ``uv run`` prefix.
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pin uv version so the action does not have to fetch the
|
||||
|
||||
@@ -549,6 +549,7 @@ esphome/components/sx127x/* @swoboda1337
|
||||
esphome/components/sy6970/* @linkedupbits
|
||||
esphome/components/syslog/* @clydebarrow
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tas2780/* @remcom
|
||||
esphome/components/tc74/* @sethgirvan
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
esphome/components/tca9555/* @mobrembski
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# Home Assistant Add-on: ESPHome
|
||||
# Sends discovery information to Home Assistant.
|
||||
# ==============================================================================
|
||||
declare config
|
||||
declare port
|
||||
|
||||
# We only disable it when disabled explicitly
|
||||
@@ -19,14 +18,17 @@ port=$(bashio::addon.ingress_port)
|
||||
# Wait for the ESPHome Device Builder to become available
|
||||
bashio::net.wait_for "${port}" "127.0.0.1" 300
|
||||
|
||||
config=$(\
|
||||
bashio::var.json \
|
||||
host "127.0.0.1" \
|
||||
port "^${port}" \
|
||||
)
|
||||
# Send one discovery message; the config is a JSON string built with bashio::var.json.
|
||||
send_discovery() {
|
||||
local service=$1
|
||||
local config=$2
|
||||
if bashio::discovery "${service}" "${config}" > /dev/null; then
|
||||
bashio::log.info "Successfully send ${service} discovery information to Home Assistant."
|
||||
else
|
||||
bashio::log.error "${service} discovery message to Home Assistant failed!"
|
||||
fi
|
||||
}
|
||||
|
||||
if bashio::discovery "esphome" "${config}" > /dev/null; then
|
||||
bashio::log.info "Successfully send discovery information to Home Assistant."
|
||||
else
|
||||
bashio::log.error "Discovery message to Home Assistant failed!"
|
||||
fi
|
||||
send_discovery "esphome" "$(bashio::var.json host "127.0.0.1" port "^${port}")"
|
||||
# The Device Builder MCP server, consumed by Home Assistant's mcp integration.
|
||||
send_discovery "mcp" "$(bashio::var.json url "http://127.0.0.1:${port}/api/mcp")"
|
||||
|
||||
+21
-8
@@ -224,6 +224,15 @@ def flash_string(config: ConfigType, value: str) -> str:
|
||||
return str(cg.safe_exp(value))
|
||||
|
||||
|
||||
def literal_with_length(config: ConfigType, value: str) -> str:
|
||||
"""Renderer for a ``(const char *, size_t)`` target: a plain literal plus its byte length.
|
||||
|
||||
The target compares or copies the bytes in place, so it needs the RAM literal rather than
|
||||
the PROGMEM rendering on ESP8266, and the length saves a strlen.
|
||||
"""
|
||||
return f"{cg.safe_exp(value)}, {len(value.encode('utf-8'))}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ApplyCall:
|
||||
"""One statement from config keys, e.g. ``"set_range({}, {})"`` with ``((CONF_LOW, cg.float_), ...)``.
|
||||
@@ -330,9 +339,9 @@ def _check_key_in_schema(
|
||||
schema = schema.schema[markers[part]]
|
||||
|
||||
|
||||
async def _apply_parent(config: ConfigType) -> str:
|
||||
async def _apply_parent(config: ConfigType, id_key: str = CONF_ID) -> str:
|
||||
# Global-scope qualified so a trigger arg named like the id cannot shadow it.
|
||||
return f"::{await cg.get_variable(config[CONF_ID])}"
|
||||
return f"::{await cg.get_variable(config[id_key])}"
|
||||
|
||||
|
||||
def _apply_lambda_args(args: TemplateArgsType) -> TemplateArgsType:
|
||||
@@ -387,12 +396,14 @@ def register_apply_action(
|
||||
schema: cv.Schema,
|
||||
*fields: ApplyField | ApplyCall,
|
||||
call: str | None = None,
|
||||
id_key: str = CONF_ID,
|
||||
) -> None:
|
||||
"""Register an action that only forwards config values to its parent, with no C++ class.
|
||||
|
||||
Generates one stateless function for ``ApplyAction<Ts...>``: parent and constants are baked
|
||||
in, lambdas are called inline with the trigger args. With ``call`` every statement targets
|
||||
the call object ``auto apply_call = parent->call()``, and ``apply_call.perform()`` is appended.
|
||||
Generates one stateless function for ``ApplyAction<Ts...>``: the parent (read from
|
||||
``id_key``) and constants are baked in, lambdas are called inline with the trigger args.
|
||||
With ``call`` every statement targets the call object ``auto apply_call = parent->call()``,
|
||||
and ``apply_call.perform()`` is appended.
|
||||
"""
|
||||
# An action stores the value, so a std::string constant stays in flash on ESP8266.
|
||||
statements_spec = [
|
||||
@@ -405,6 +416,7 @@ def register_apply_action(
|
||||
)
|
||||
for c in (f if isinstance(f, ApplyCall) else f.call() for f in fields)
|
||||
]
|
||||
_check_key_in_schema(name, schema, id_key)
|
||||
for _, members in statements_spec:
|
||||
for conf_key, _, _ in members:
|
||||
_check_key_in_schema(name, schema, conf_key)
|
||||
@@ -415,7 +427,7 @@ def register_apply_action(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
parent = await _apply_parent(config)
|
||||
parent = await _apply_parent(config, id_key)
|
||||
lambda_args = _apply_lambda_args(args)
|
||||
receiver = "apply_call." if call else f"{parent}->"
|
||||
statements: list[str] = []
|
||||
@@ -442,7 +454,7 @@ def register_apply_action(
|
||||
|
||||
|
||||
def register_apply_condition(
|
||||
name: str, schema: cv.Schema, check: str | ApplyCall
|
||||
name: str, schema: cv.Schema, check: str | ApplyCall, id_key: str = CONF_ID
|
||||
) -> None:
|
||||
"""Register a condition that is one expression on its parent, with no C++ class.
|
||||
|
||||
@@ -454,6 +466,7 @@ def register_apply_condition(
|
||||
"""
|
||||
call = check if isinstance(check, ApplyCall) else ApplyCall(check)
|
||||
members = call.members
|
||||
_check_key_in_schema(name, schema, id_key)
|
||||
for conf_key, _, _ in members:
|
||||
_check_key_in_schema(name, schema, conf_key)
|
||||
|
||||
@@ -463,7 +476,7 @@ def register_apply_condition(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
parent = await _apply_parent(config)
|
||||
parent = await _apply_parent(config, id_key)
|
||||
lambda_args = _apply_lambda_args(args)
|
||||
exprs = await _render_values(
|
||||
name,
|
||||
|
||||
@@ -64,7 +64,6 @@ from esphome.cpp_types import ( # noqa: F401
|
||||
Application,
|
||||
Component,
|
||||
ComponentPtr,
|
||||
Controller,
|
||||
EntityBase,
|
||||
EntityCategory,
|
||||
ESPTime,
|
||||
|
||||
@@ -81,7 +81,7 @@ def AUTO_LOAD(config: ConfigType) -> list[str]:
|
||||
|
||||
|
||||
api_ns = cg.esphome_ns.namespace("api")
|
||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||
APIServer = api_ns.class_("APIServer", cg.Component)
|
||||
HomeAssistantServiceCallAction = api_ns.class_(
|
||||
"HomeAssistantServiceCallAction", automation.Action
|
||||
)
|
||||
@@ -462,8 +462,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
# Track controller registration for StaticVector sizing
|
||||
CORE.register_controller()
|
||||
CORE.register_controller(var)
|
||||
|
||||
# Request a log listener slot for API log streaming
|
||||
request_log_listener()
|
||||
|
||||
@@ -600,7 +600,7 @@ bool APIConnection::send_light_state(light::LightState *light) {
|
||||
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
|
||||
auto *light = static_cast<light::LightState *>(entity);
|
||||
LightStateResponse resp;
|
||||
auto values = light->remote_values;
|
||||
auto values = light->get_reported_values();
|
||||
auto color_mode = values.get_color_mode();
|
||||
resp.state = values.is_on();
|
||||
resp.color_mode = static_cast<enums::ColorMode>(color_mode);
|
||||
@@ -2267,7 +2267,12 @@ bool APIConnection::send_message_(uint32_t payload_size, uint16_t message_type,
|
||||
// Capacity reserved above, cannot fail
|
||||
(void) shared_buf.resize(write_start + payload_size);
|
||||
ProtoWriteBuffer buffer{&shared_buf, write_start};
|
||||
encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
|
||||
uint8_t *end = encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
proto_check_encode_end(end, shared_buf.data() + shared_buf.size());
|
||||
#else
|
||||
(void) end;
|
||||
#endif
|
||||
return this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type);
|
||||
}
|
||||
// encode_to_buffer is defined inline in api_connection.h (ESPHOME_ALWAYS_INLINE)
|
||||
|
||||
@@ -346,11 +346,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
/// Returns false as soon as the TCP buffer is full. Marked nodiscard so we
|
||||
/// have no silent failures: every caller must handle (or log) a refusal.
|
||||
template<typename T> [[nodiscard]] bool send_message(const T &msg) {
|
||||
if constexpr (T::ESTIMATED_SIZE == 0) {
|
||||
return this->send_message_(0, T::MESSAGE_TYPE, &encode_msg_noop, &msg);
|
||||
} else {
|
||||
return this->send_message_(msg.calculate_size(), T::MESSAGE_TYPE, &proto_encode_msg<T>, &msg);
|
||||
}
|
||||
return this->send_message_(T::calc_size_msg(&msg), T::MESSAGE_TYPE, &T::encode_msg, &msg);
|
||||
}
|
||||
|
||||
/// Clear the shared write buffer and reserve space for the first message.
|
||||
@@ -406,16 +402,6 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
void process_state_subscriptions_();
|
||||
#endif
|
||||
|
||||
// Size thunk — converts void* back to concrete type for direct calculate_size() call
|
||||
template<typename T> static uint32_t calc_size(const void *msg) {
|
||||
return static_cast<const T *>(msg)->calculate_size();
|
||||
}
|
||||
|
||||
// Shared no-op encode thunk for empty messages (ESTIMATED_SIZE == 0)
|
||||
static uint8_t *encode_msg_noop(const void *, ProtoWriteBuffer &buf PROTO_ENCODE_DEBUG_PARAM) {
|
||||
return buf.get_pos();
|
||||
}
|
||||
|
||||
// Non-template buffer management for send_message
|
||||
bool send_message_(uint32_t payload_size, uint16_t message_type, MessageEncodeFn encode_fn, const void *msg);
|
||||
|
||||
@@ -434,11 +420,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
// Hot paths (state/info) go through fill_and_encode_entity_state/info instead.
|
||||
// batch_message_type_ is already set by dispatch_message_ before reaching here.
|
||||
template<typename T> static uint16_t encode_message_to_buffer(T &msg, APIConnection *conn, uint32_t remaining_size) {
|
||||
if constexpr (T::ESTIMATED_SIZE == 0) {
|
||||
return encode_to_buffer_slow(0, &encode_msg_noop, &msg, conn, remaining_size);
|
||||
} else {
|
||||
return encode_to_buffer_slow(msg.calculate_size(), &proto_encode_msg<T>, &msg, conn, remaining_size);
|
||||
}
|
||||
return encode_to_buffer_slow(T::calc_size_msg(&msg), &T::encode_msg, &msg, conn, remaining_size);
|
||||
}
|
||||
|
||||
// Non-template core — fills state fields and encodes
|
||||
@@ -450,7 +432,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
template<typename T>
|
||||
static uint16_t fill_and_encode_entity_state(EntityBase *entity, T &msg, APIConnection *conn,
|
||||
uint32_t remaining_size) {
|
||||
return fill_and_encode_entity_state(entity, msg, &calc_size<T>, &proto_encode_msg<T>, conn, remaining_size);
|
||||
return fill_and_encode_entity_state(entity, msg, &T::calc_size_msg, &T::encode_msg, conn, remaining_size);
|
||||
}
|
||||
|
||||
// Non-template core — fills info fields, allocates buffers, and encodes
|
||||
@@ -462,7 +444,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
template<typename T>
|
||||
static uint16_t fill_and_encode_entity_info(EntityBase *entity, T &msg, APIConnection *conn,
|
||||
uint32_t remaining_size) {
|
||||
return fill_and_encode_entity_info(entity, msg, &calc_size<T>, &proto_encode_msg<T>, conn, remaining_size);
|
||||
return fill_and_encode_entity_info(entity, msg, &T::calc_size_msg, &T::encode_msg, conn, remaining_size);
|
||||
}
|
||||
|
||||
// Non-template core — fills device_class, then delegates to fill_and_encode_entity_info
|
||||
@@ -476,8 +458,8 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
static uint16_t fill_and_encode_entity_info_with_device_class(EntityBase *entity, T &msg,
|
||||
StringRef &device_class_field, APIConnection *conn,
|
||||
uint32_t remaining_size) {
|
||||
return fill_and_encode_entity_info_with_device_class(entity, msg, device_class_field, &calc_size<T>,
|
||||
&proto_encode_msg<T>, conn, remaining_size);
|
||||
return fill_and_encode_entity_info_with_device_class(entity, msg, device_class_field, &T::calc_size_msg,
|
||||
&T::encode_msg, conn, remaining_size);
|
||||
}
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
|
||||
@@ -46,7 +46,13 @@ inline uint16_t ESPHOME_ALWAYS_INLINE APIConnection::encode_to_buffer(uint32_t c
|
||||
return 0;
|
||||
}
|
||||
ProtoWriteBuffer buffer{&shared_buf, shared_buf.size() - calculated_size};
|
||||
encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
|
||||
uint8_t *end = encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
// A body that writes fewer bytes than calculate_size() promised would ship stale buffer bytes
|
||||
proto_check_encode_end(end, shared_buf.data() + shared_buf.size());
|
||||
#else
|
||||
(void) end;
|
||||
#endif
|
||||
|
||||
return total_calculated_size;
|
||||
}
|
||||
|
||||
@@ -5,16 +5,13 @@
|
||||
#include "esphome/components/noise/noise.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "proto.h"
|
||||
#include <cstring>
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
using noise::noise_err_to_logstr;
|
||||
@@ -26,11 +23,7 @@ static_assert(MAX_HANDSHAKE_SIZE == noise::MAX_HANDSHAKE_SIZE,
|
||||
"api and noise component handshake size limits must match");
|
||||
|
||||
static const char *const TAG = "api.noise";
|
||||
#ifdef USE_ESP8266
|
||||
static constexpr char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
|
||||
#else
|
||||
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
||||
#endif
|
||||
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
||||
|
||||
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
|
||||
@@ -72,11 +65,7 @@ APIError APINoiseFrameHelper::init() {
|
||||
state_ = State::FAILED;
|
||||
return APIError::OUT_OF_MEMORY;
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
memcpy_P(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||
#else
|
||||
std::memcpy(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||
#endif
|
||||
progmem_memcpy(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||
|
||||
state_ = State::CLIENT_HELLO;
|
||||
return APIError::OK;
|
||||
|
||||
+2662
-2638
File diff suppressed because it is too large
Load Diff
+875
-352
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@
|
||||
#include "api_connection.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -61,8 +60,6 @@ void APIServer::socket_failed_(const LogString *msg) {
|
||||
}
|
||||
|
||||
void APIServer::setup() {
|
||||
ControllerRegistry::register_controller(this);
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
// Always reserve the slot: flash preferences are positional on esp8266, so
|
||||
// a yaml key build must keep the layout of a runtime key build
|
||||
@@ -455,8 +452,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"));
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/entity_includes.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
#ifdef USE_PROVISIONING
|
||||
@@ -49,8 +49,7 @@ struct SavedNoisePsk {
|
||||
bool load_saved_noise_psk(noise::psk_t &out);
|
||||
#endif
|
||||
|
||||
class APIServer final : public Component,
|
||||
public Controller
|
||||
class APIServer final : public Component
|
||||
#ifdef USE_CAMERA
|
||||
,
|
||||
public camera::CameraListener
|
||||
@@ -94,58 +93,58 @@ class APIServer final : public Component,
|
||||
|
||||
void handle_disconnect(APIConnection *conn);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
void on_cover_update(cover::Cover *obj) override;
|
||||
void on_cover_update(cover::Cover *obj);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void on_fan_update(fan::Fan *obj) override;
|
||||
void on_fan_update(fan::Fan *obj);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_update(light::LightState *obj) override;
|
||||
void on_light_update(light::LightState *obj);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
void on_sensor_update(sensor::Sensor *obj) override;
|
||||
void on_sensor_update(sensor::Sensor *obj);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_update(switch_::Switch *obj) override;
|
||||
void on_switch_update(switch_::Switch *obj);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void on_text_sensor_update(text_sensor::TextSensor *obj) override;
|
||||
void on_text_sensor_update(text_sensor::TextSensor *obj);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_update(climate::Climate *obj) override;
|
||||
void on_climate_update(climate::Climate *obj);
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void on_number_update(number::Number *obj) override;
|
||||
void on_number_update(number::Number *obj);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void on_date_update(datetime::DateEntity *obj) override;
|
||||
void on_date_update(datetime::DateEntity *obj);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
void on_time_update(datetime::TimeEntity *obj) override;
|
||||
void on_time_update(datetime::TimeEntity *obj);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
void on_datetime_update(datetime::DateTimeEntity *obj) override;
|
||||
void on_datetime_update(datetime::DateTimeEntity *obj);
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
void on_text_update(text::Text *obj) override;
|
||||
void on_text_update(text::Text *obj);
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void on_select_update(select::Select *obj) override;
|
||||
void on_select_update(select::Select *obj);
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_update(lock::Lock *obj) override;
|
||||
void on_lock_update(lock::Lock *obj);
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
void on_valve_update(valve::Valve *obj) override;
|
||||
void on_valve_update(valve::Valve *obj);
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
||||
void on_media_player_update(media_player::MediaPlayer *obj);
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
void on_water_heater_update(water_heater::WaterHeater *obj) override;
|
||||
void on_water_heater_update(water_heater::WaterHeater *obj);
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
||||
@@ -188,13 +187,13 @@ class APIServer final : public Component,
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
|
||||
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj);
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
void on_event(event::Event *obj) override;
|
||||
void on_event(event::Event *obj);
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
void on_update(update::UpdateEntity *obj) override;
|
||||
void on_update(update::UpdateEntity *obj);
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_zwave_proxy_request(const ZWaveProxyRequest &msg);
|
||||
|
||||
@@ -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,
|
||||
@@ -210,77 +216,78 @@ void ProtoWriteBuffer::debug_check_encode_size_(uint32_t field_id, uint32_t expe
|
||||
|
||||
#endif
|
||||
|
||||
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
void ProtoDecodableMessage::decode_fields(void *msg, const uint8_t *buffer, size_t length, DecodeFieldFn field) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
field(msg, tag, data, scalar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+243
-170
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
@@ -170,40 +171,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 +225,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 +259,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 +294,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 +326,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 +383,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 +423,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 +699,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
|
||||
@@ -648,7 +724,13 @@ class ProtoMessage {
|
||||
// Base class for messages that support decoding
|
||||
class ProtoDecodableMessage : public ProtoMessage {
|
||||
public:
|
||||
void decode(const uint8_t *buffer, size_t length);
|
||||
/// Stores one decoded field into \p msg; generated per message type. \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.
|
||||
using DecodeFieldFn = void (*)(void *msg, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar);
|
||||
/// Walk \p buffer and hand every field to \p field. The generated decode() passes the message's
|
||||
/// own decode_field, so decodable messages carry no vtable.
|
||||
static void decode_fields(void *msg, const uint8_t *buffer, size_t length, DecodeFieldFn field);
|
||||
|
||||
/**
|
||||
* Count occurrences of a repeated field in a protobuf buffer.
|
||||
@@ -660,14 +742,15 @@ class ProtoDecodableMessage : public ProtoMessage {
|
||||
* @return Number of times the field appears in the buffer
|
||||
*/
|
||||
static uint32_t count_repeated_field(const uint8_t *buffer, size_t length, uint32_t target_field_id);
|
||||
|
||||
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
|
||||
// The destructor stays accessible on purpose: the generated messages are aggregates that brace
|
||||
// initialise sub message members, which copies a base temporary. That trades away the compile time
|
||||
// guard against deleting through this type; messages are stack locals and never owned through a base
|
||||
// pointer. ProtoMessage keeps its guard for the dump builds.
|
||||
};
|
||||
#ifndef HAS_PROTO_MESSAGE_DUMP
|
||||
// decode() passes decode_field explicitly, so nothing here may add a vtable
|
||||
static_assert(!std::is_polymorphic_v<ProtoDecodableMessage>, "decodable messages carry no vtable");
|
||||
#endif
|
||||
|
||||
class ProtoSize {
|
||||
public:
|
||||
@@ -792,7 +875,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 +959,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);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#ifdef USE_API
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/component_iterator.h"
|
||||
#include "esphome/core/controller.h"
|
||||
namespace esphome::api {
|
||||
|
||||
class APIConnection;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components.const import UNIT_COUNTS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_CLEAR,
|
||||
@@ -33,8 +34,6 @@ CONF_F7 = "f7"
|
||||
CONF_F8 = "f8"
|
||||
CONF_NIR = "nir"
|
||||
|
||||
UNIT_COUNTS = "#"
|
||||
|
||||
AS7341_GAIN = as7341_ns.enum("AS7341Gain")
|
||||
GAIN_OPTIONS = {
|
||||
"X0.5": AS7341_GAIN.AS7341_GAIN_0_5X,
|
||||
|
||||
@@ -2,9 +2,8 @@ from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_VOLUME
|
||||
from esphome.core import ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.const import CONF_VOLUME
|
||||
from esphome.core import CoroPriority, coroutine_with_priority
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
@@ -13,10 +12,6 @@ IS_PLATFORM_COMPONENT = True
|
||||
audio_dac_ns = cg.esphome_ns.namespace("audio_dac")
|
||||
AudioDac = audio_dac_ns.class_("AudioDac")
|
||||
|
||||
MuteOffAction = audio_dac_ns.class_("MuteOffAction", automation.Action)
|
||||
MuteOnAction = audio_dac_ns.class_("MuteOnAction", automation.Action)
|
||||
SetVolumeAction = audio_dac_ns.class_("SetVolumeAction", automation.Action)
|
||||
|
||||
|
||||
MUTE_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
@@ -33,41 +28,19 @@ SET_VOLUME_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"audio_dac.mute_off", MuteOffAction, MUTE_ACTION_SCHEMA, synchronous=True
|
||||
)
|
||||
@automation.register_action(
|
||||
"audio_dac.mute_on", MuteOnAction, MUTE_ACTION_SCHEMA, synchronous=True
|
||||
)
|
||||
async def audio_dac_mute_action_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
for _name, _call in (
|
||||
("audio_dac.mute_off", "set_mute_off()"),
|
||||
("audio_dac.mute_on", "set_mute_on()"),
|
||||
):
|
||||
automation.register_apply_action(
|
||||
_name, MUTE_ACTION_SCHEMA, automation.ApplyCall(_call)
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"audio_dac.set_volume",
|
||||
SetVolumeAction,
|
||||
SET_VOLUME_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_VOLUME, "set_volume", cg.float_),
|
||||
)
|
||||
async def audio_dac_set_volume_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config.get(CONF_VOLUME), args, cg.float_)
|
||||
cg.add(var.set_volume(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "audio_dac.h"
|
||||
|
||||
namespace esphome::audio_dac {
|
||||
|
||||
template<typename... Ts> class MuteOffAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit MuteOffAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||
|
||||
void play(const Ts &...x) override { this->audio_dac_->set_mute_off(); }
|
||||
|
||||
protected:
|
||||
AudioDac *audio_dac_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class MuteOnAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit MuteOnAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||
|
||||
void play(const Ts &...x) override { this->audio_dac_->set_mute_on(); }
|
||||
|
||||
protected:
|
||||
AudioDac *audio_dac_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetVolumeAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetVolumeAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||
|
||||
TEMPLATABLE_VALUE(float, volume)
|
||||
|
||||
void play(const Ts &...x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); }
|
||||
|
||||
protected:
|
||||
AudioDac *audio_dac_;
|
||||
};
|
||||
|
||||
} // namespace esphome::audio_dac
|
||||
@@ -1,7 +1,7 @@
|
||||
from logging import getLogger
|
||||
|
||||
from esphome import automation, core
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server, zigbee
|
||||
from esphome.components.const import CONF_ON_STATE_CHANGE
|
||||
@@ -138,9 +138,6 @@ BinarySensorInvalidateAction = binary_sensor_ns.class_(
|
||||
"BinarySensorInvalidateAction", automation.Action
|
||||
)
|
||||
|
||||
# Condition
|
||||
BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition)
|
||||
|
||||
# Filters
|
||||
Filter = binary_sensor_ns.class_("Filter")
|
||||
TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter)
|
||||
@@ -645,20 +642,12 @@ BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"binary_sensor.is_on", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
|
||||
automation.register_apply_condition(
|
||||
"binary_sensor.is_on", BINARY_SENSOR_CONDITION_SCHEMA, "state"
|
||||
)
|
||||
async def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"binary_sensor.is_off", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
|
||||
automation.register_apply_condition(
|
||||
"binary_sensor.is_off", BINARY_SENSOR_CONDITION_SCHEMA, "state == false"
|
||||
)
|
||||
async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -18,26 +18,6 @@ struct MultiClickTriggerEvent {
|
||||
uint32_t max_length;
|
||||
};
|
||||
|
||||
class PressTrigger final : public Trigger<> {
|
||||
public:
|
||||
explicit PressTrigger(BinarySensor *parent) {
|
||||
parent->add_on_state_callback([this](bool state) {
|
||||
if (state)
|
||||
this->trigger();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class ReleaseTrigger final : public Trigger<> {
|
||||
public:
|
||||
explicit ReleaseTrigger(BinarySensor *parent) {
|
||||
parent->add_on_state_callback([this](bool state) {
|
||||
if (!state)
|
||||
this->trigger();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length);
|
||||
|
||||
class ClickTrigger final : public Trigger<> {
|
||||
@@ -140,31 +120,6 @@ template<size_t N> class MultiClickTrigger final : public MultiClickTriggerBase
|
||||
std::array<MultiClickTriggerEvent, N> timing_storage_{};
|
||||
};
|
||||
|
||||
class StateTrigger final : public Trigger<bool> {
|
||||
public:
|
||||
explicit StateTrigger(BinarySensor *parent) {
|
||||
parent->add_on_state_callback([this](bool state) { this->trigger(state); });
|
||||
}
|
||||
};
|
||||
|
||||
class StateChangeTrigger final : public Trigger<optional<bool>, optional<bool> > {
|
||||
public:
|
||||
explicit StateChangeTrigger(BinarySensor *parent) {
|
||||
parent->add_full_state_callback(
|
||||
[this](optional<bool> old_state, optional<bool> state) { this->trigger(old_state, state); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class BinarySensorCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {}
|
||||
bool check(const Ts &...x) override { return this->parent_->state == this->state_; }
|
||||
|
||||
protected:
|
||||
BinarySensor *parent_;
|
||||
bool state_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class BinarySensorInvalidateAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit BinarySensorInvalidateAction(BinarySensor *sensor) : sensor_(sensor) {}
|
||||
|
||||
@@ -16,11 +16,4 @@ template<typename... Ts> class PressAction final : public Action<Ts...> {
|
||||
Button *button_;
|
||||
};
|
||||
|
||||
class ButtonPressTrigger final : public Trigger<> {
|
||||
public:
|
||||
ButtonPressTrigger(Button *button) {
|
||||
button->add_on_press_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::button
|
||||
|
||||
@@ -494,12 +494,6 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
def _literal_with_length(config: ConfigType, value: str) -> str:
|
||||
# The (const char *, size_t) overload compares bytes in place, so it needs a plain
|
||||
# literal rather than the default PROGMEM rendering on ESP8266, and skips a strlen.
|
||||
return f"{cg.safe_exp(value)}, {len(value.encode('utf-8'))}"
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
"climate.control",
|
||||
CLIMATE_CONTROL_ACTION_SCHEMA,
|
||||
@@ -517,14 +511,14 @@ automation.register_apply_action(
|
||||
CONF_CUSTOM_FAN_MODE,
|
||||
"set_fan_mode",
|
||||
cg.std_string,
|
||||
const_fn=_literal_with_length,
|
||||
const_fn=automation.literal_with_length,
|
||||
),
|
||||
automation.ApplyField(CONF_PRESET, "set_preset", ClimatePreset),
|
||||
automation.ApplyField(
|
||||
CONF_CUSTOM_PRESET,
|
||||
"set_preset",
|
||||
cg.std_string,
|
||||
const_fn=_literal_with_length,
|
||||
const_fn=automation.literal_with_length,
|
||||
),
|
||||
automation.ApplyField(CONF_SWING_MODE, "set_swing_mode", ClimateSwingMode),
|
||||
call="make_call",
|
||||
|
||||
@@ -13,17 +13,7 @@ climate::ClimateTraits ClimateIR::traits() {
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY);
|
||||
}
|
||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF});
|
||||
if (this->supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (this->supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
if (this->supports_heat_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
|
||||
if (this->supports_dry_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||
if (this->supports_fan_only_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
traits.set_supported_modes(this->modes_);
|
||||
|
||||
traits.set_visual_min_temperature(this->minimum_temperature_);
|
||||
traits.set_visual_max_temperature(this->maximum_temperature_);
|
||||
@@ -98,8 +88,10 @@ void ClimateIR::dump_config() {
|
||||
" Supports HEAT: %s\n"
|
||||
" Supports COOL: %s\n"
|
||||
" Supports HEAT_COOL: %s",
|
||||
this->minimum_temperature_, this->maximum_temperature_, YESNO(this->supports_heat_),
|
||||
YESNO(this->supports_cool_), YESNO(this->supports_heat_cool_));
|
||||
this->minimum_temperature_, this->maximum_temperature_,
|
||||
YESNO(this->modes_.count(climate::CLIMATE_MODE_HEAT)),
|
||||
YESNO(this->modes_.count(climate::CLIMATE_MODE_COOL)),
|
||||
YESNO(this->modes_.count(climate::CLIMATE_MODE_HEAT_COOL)));
|
||||
}
|
||||
|
||||
} // namespace esphome::climate_ir
|
||||
|
||||
@@ -30,8 +30,10 @@ class ClimateIR : public Component,
|
||||
this->minimum_temperature_ = minimum_temperature;
|
||||
this->maximum_temperature_ = maximum_temperature;
|
||||
this->temperature_step_ = temperature_step;
|
||||
this->supports_dry_ = supports_dry;
|
||||
this->supports_fan_only_ = supports_fan_only;
|
||||
if (supports_dry)
|
||||
this->modes_.insert(climate::CLIMATE_MODE_DRY);
|
||||
if (supports_fan_only)
|
||||
this->modes_.insert(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
this->fan_modes_ = fan_modes;
|
||||
this->swing_modes_ = swing_modes;
|
||||
this->presets_ = presets;
|
||||
@@ -39,9 +41,11 @@ class ClimateIR : public Component,
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||
void set_supports_heat_cool(bool supports_heat_cool) { this->supports_heat_cool_ = supports_heat_cool; }
|
||||
void set_supports_cool(bool supports_cool) { this->set_mode_supported_(climate::CLIMATE_MODE_COOL, supports_cool); }
|
||||
void set_supports_heat(bool supports_heat) { this->set_mode_supported_(climate::CLIMATE_MODE_HEAT, supports_heat); }
|
||||
void set_supports_heat_cool(bool supports_heat_cool) {
|
||||
this->set_mode_supported_(climate::CLIMATE_MODE_HEAT_COOL, supports_heat_cool);
|
||||
}
|
||||
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; }
|
||||
|
||||
@@ -59,12 +63,18 @@ class ClimateIR : public Component,
|
||||
// Dummy implement on_receive so implementation is optional for inheritors
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override { return false; };
|
||||
|
||||
bool supports_cool_{true};
|
||||
bool supports_heat_{true};
|
||||
// Default (supports_cool && supports_heat) is resolved during code generation.
|
||||
bool supports_heat_cool_{true};
|
||||
bool supports_dry_{false};
|
||||
bool supports_fan_only_{false};
|
||||
ESPHOME_ALWAYS_INLINE void set_mode_supported_(climate::ClimateMode mode, bool supported) {
|
||||
if (supported) {
|
||||
this->modes_.insert(mode);
|
||||
} else {
|
||||
this->modes_.erase(mode);
|
||||
}
|
||||
}
|
||||
|
||||
// The HEAT_COOL default (supports_cool && supports_heat) is resolved during code generation.
|
||||
static constexpr climate::ClimateModeMask DEFAULT_MODES{climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
||||
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_HEAT_COOL};
|
||||
climate::ClimateModeMask modes_{DEFAULT_MODES};
|
||||
climate::ClimateFanModeMask fan_modes_{};
|
||||
climate::ClimateSwingModeMask swing_modes_{};
|
||||
climate::ClimatePresetMask presets_{};
|
||||
|
||||
@@ -23,6 +23,7 @@ CONF_ENABLE_OTA_DOWNGRADE_PROTECTION = "enable_ota_downgrade_protection"
|
||||
CONF_ENABLED = "enabled"
|
||||
CONF_GYROSCOPE_ODR = "gyroscope_odr"
|
||||
CONF_GYROSCOPE_RANGE = "gyroscope_range"
|
||||
CONF_HUMIDITY_SOURCE = "humidity_source"
|
||||
CONF_IAQ = "iaq"
|
||||
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
|
||||
CONF_IS_WRGB = "is_wrgb"
|
||||
@@ -61,3 +62,4 @@ ICON_SOLAR_POWER = "mdi:solar-power"
|
||||
KEY_METADATA = "metadata"
|
||||
|
||||
UNIT_AMPERE_HOUR = "Ah"
|
||||
UNIT_COUNTS = "#"
|
||||
|
||||
@@ -422,8 +422,12 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
if CONF_TOUCH_WAKEUP in config:
|
||||
cg.add(var.set_touch_wakeup(config[CONF_TOUCH_WAKEUP]))
|
||||
if CORE.using_zephyr and "zigbee" not in CORE.loaded_integrations:
|
||||
zephyr_add_prj_conf("POWEROFF", True)
|
||||
if CORE.using_zephyr:
|
||||
# Devices are suspended when CPU is entering a low power state
|
||||
# https://github.com/nrfconnect/sdk-zephyr/blob/v3.7.99-ncs2-2/doc/services/pm/device.rst#system-managed-device-power-management
|
||||
zephyr_add_prj_conf("PM_DEVICE", True)
|
||||
if "zigbee" not in CORE.loaded_integrations:
|
||||
zephyr_add_prj_conf("POWEROFF", True)
|
||||
|
||||
cg.add_define("USE_DEEP_SLEEP")
|
||||
|
||||
|
||||
@@ -37,23 +37,6 @@ DEVICE = {
|
||||
"TF_CARD": Device.TF_CARD,
|
||||
}
|
||||
|
||||
NextAction = dfplayer_ns.class_("NextAction", automation.Action)
|
||||
PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action)
|
||||
PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action)
|
||||
PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action)
|
||||
PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action)
|
||||
SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action)
|
||||
VolumeUpAction = dfplayer_ns.class_("VolumeUpAction", automation.Action)
|
||||
VolumeDownAction = dfplayer_ns.class_("VolumeDownAction", automation.Action)
|
||||
SetEqAction = dfplayer_ns.class_("SetEqAction", automation.Action)
|
||||
SleepAction = dfplayer_ns.class_("SleepAction", automation.Action)
|
||||
ResetAction = dfplayer_ns.class_("ResetAction", automation.Action)
|
||||
StartAction = dfplayer_ns.class_("StartAction", automation.Action)
|
||||
PauseAction = dfplayer_ns.class_("PauseAction", automation.Action)
|
||||
StopAction = dfplayer_ns.class_("StopAction", automation.Action)
|
||||
RandomAction = dfplayer_ns.class_("RandomAction", automation.Action)
|
||||
SetDeviceAction = dfplayer_ns.class_("SetDeviceAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -87,41 +70,30 @@ async def to_code(config):
|
||||
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.play_next",
|
||||
NextAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
DFPLAYER_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
)
|
||||
async def dfplayer_next_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
for _name, _call in (
|
||||
("dfplayer.play_next", "next()"),
|
||||
("dfplayer.play_previous", "previous()"),
|
||||
("dfplayer.volume_up", "volume_up()"),
|
||||
("dfplayer.volume_down", "volume_down()"),
|
||||
("dfplayer.sleep", "sleep()"),
|
||||
("dfplayer.reset", "reset()"),
|
||||
("dfplayer.start", "start()"),
|
||||
("dfplayer.pause", "pause()"),
|
||||
("dfplayer.stop", "stop()"),
|
||||
("dfplayer.random", "random()"),
|
||||
):
|
||||
automation.register_apply_action(
|
||||
_name, DFPLAYER_ACTION_SCHEMA, automation.ApplyCall(_call)
|
||||
)
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.play_previous",
|
||||
PreviousAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_previous_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"dfplayer.play_mp3",
|
||||
PlayMp3Action,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
@@ -129,70 +101,43 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args):
|
||||
},
|
||||
key=CONF_FILE,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_FILE, "play_mp3", cg.uint16),
|
||||
)
|
||||
async def dfplayer_play_mp3_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16)
|
||||
cg.add(var.set_file(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
# loop and file default to what the old action's unset templatable values evaluated to
|
||||
automation.register_apply_action(
|
||||
"dfplayer.play",
|
||||
PlayFileAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
cv.Required(CONF_FILE): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_LOOP): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_LOOP, default=False): cv.templatable(cv.boolean),
|
||||
},
|
||||
key=CONF_FILE,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall(
|
||||
"play_file({}, {})", ((CONF_FILE, cg.uint16), (CONF_LOOP, cg.bool_))
|
||||
),
|
||||
)
|
||||
async def dfplayer_play_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16)
|
||||
cg.add(var.set_file(template_))
|
||||
if CONF_LOOP in config:
|
||||
template_ = await cg.templatable(config[CONF_LOOP], args, cg.bool_)
|
||||
cg.add(var.set_loop(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"dfplayer.play_folder",
|
||||
PlayFolderAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
cv.Required(CONF_FOLDER): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_FILE): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_LOOP): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_FILE, default=0): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_LOOP, default=False): cv.templatable(cv.boolean),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall(
|
||||
"play_folder({}, {}, {})",
|
||||
((CONF_FOLDER, cg.uint16), (CONF_FILE, cg.uint16), (CONF_LOOP, cg.bool_)),
|
||||
),
|
||||
)
|
||||
async def dfplayer_play_folder_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_FOLDER], args, cg.uint16)
|
||||
cg.add(var.set_folder(template_))
|
||||
if CONF_FILE in config:
|
||||
template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16)
|
||||
cg.add(var.set_file(template_))
|
||||
if CONF_LOOP in config:
|
||||
template_ = await cg.templatable(config[CONF_LOOP], args, cg.bool_)
|
||||
cg.add(var.set_loop(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"dfplayer.set_device",
|
||||
SetDeviceAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
@@ -200,19 +145,11 @@ async def dfplayer_play_folder_to_code(config, action_id, template_arg, args):
|
||||
},
|
||||
key=CONF_DEVICE,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_DEVICE, "set_device", Device),
|
||||
)
|
||||
async def dfplayer_set_device_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_DEVICE], args, Device)
|
||||
cg.add(var.set_device(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"dfplayer.set_volume",
|
||||
SetVolumeAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
@@ -220,51 +157,11 @@ async def dfplayer_set_device_to_code(config, action_id, template_arg, args):
|
||||
},
|
||||
key=CONF_VOLUME,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_VOLUME, "set_volume", cg.uint8),
|
||||
)
|
||||
async def dfplayer_set_volume_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.uint8)
|
||||
cg.add(var.set_volume(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.volume_up",
|
||||
VolumeUpAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_volume_up_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.volume_down",
|
||||
VolumeDownAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_volume_down_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"dfplayer.set_eq",
|
||||
SetEqAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
@@ -272,110 +169,8 @@ async def dfplayer_volume_down_to_code(config, action_id, template_arg, args):
|
||||
},
|
||||
key=CONF_EQ_PRESET,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_EQ_PRESET, "set_eq", EqPreset),
|
||||
)
|
||||
async def dfplayer_set_eq_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_EQ_PRESET], args, EqPreset)
|
||||
cg.add(var.set_eq(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.sleep",
|
||||
SleepAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_sleep_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.reset",
|
||||
ResetAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_reset_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.start",
|
||||
StartAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_start_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.pause",
|
||||
PauseAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_pause_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.stop",
|
||||
StopAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_stop_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.random",
|
||||
RandomAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def dfplayer_random_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
def _default_enable(value: Any) -> Any:
|
||||
|
||||
@@ -33,8 +33,13 @@ class DFPlayer final : public uart::UARTDevice, public Component {
|
||||
void play_mp3(uint16_t file);
|
||||
void play_file(uint16_t file);
|
||||
void play_file_loop(uint16_t file);
|
||||
void play_file(uint16_t file, bool loop) { loop ? this->play_file_loop(file) : this->play_file(file); }
|
||||
void play_folder(uint16_t folder, uint16_t file);
|
||||
void play_folder_loop(uint16_t folder);
|
||||
// The loop command plays the whole folder, so file is ignored when loop is set.
|
||||
void play_folder(uint16_t folder, uint16_t file, bool loop) {
|
||||
loop ? this->play_folder_loop(folder) : this->play_folder(folder, file);
|
||||
}
|
||||
void volume_up();
|
||||
void volume_down();
|
||||
void set_device(Device device);
|
||||
@@ -72,100 +77,6 @@ class DFPlayer final : public uart::UARTDevice, public Component {
|
||||
CallbackManager<void()> on_finished_playback_callback_;
|
||||
};
|
||||
|
||||
#define DFPLAYER_SIMPLE_ACTION(ACTION_CLASS, ACTION_METHOD) \
|
||||
template<typename... Ts> \
|
||||
class ACTION_CLASS : /* NOLINT */ \
|
||||
public Action<Ts...>, \
|
||||
public Parented<DFPlayer> { \
|
||||
void play(const Ts &...x) override { this->parent_->ACTION_METHOD(); } \
|
||||
};
|
||||
|
||||
DFPLAYER_SIMPLE_ACTION(NextAction, next)
|
||||
DFPLAYER_SIMPLE_ACTION(PreviousAction, previous)
|
||||
|
||||
template<typename... Ts> class PlayMp3Action final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto file = this->file_.value(x...);
|
||||
this->parent_->play_mp3(file);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class PlayFileAction final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
TEMPLATABLE_VALUE(bool, loop)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto file = this->file_.value(x...);
|
||||
auto loop = this->loop_.value(x...);
|
||||
if (loop) {
|
||||
this->parent_->play_file_loop(file);
|
||||
} else {
|
||||
this->parent_->play_file(file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class PlayFolderAction final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, folder)
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
TEMPLATABLE_VALUE(bool, loop)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto folder = this->folder_.value(x...);
|
||||
auto file = this->file_.value(x...);
|
||||
auto loop = this->loop_.value(x...);
|
||||
if (loop) {
|
||||
this->parent_->play_folder_loop(folder);
|
||||
} else {
|
||||
this->parent_->play_folder(folder, file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetDeviceAction final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(Device, device)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto device = this->device_.value(x...);
|
||||
this->parent_->set_device(device);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetVolumeAction final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, volume)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto volume = this->volume_.value(x...);
|
||||
this->parent_->set_volume(volume);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetEqAction final : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(EqPreset, eq)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto eq = this->eq_.value(x...);
|
||||
this->parent_->set_eq(eq);
|
||||
}
|
||||
};
|
||||
|
||||
DFPLAYER_SIMPLE_ACTION(SleepAction, sleep)
|
||||
DFPLAYER_SIMPLE_ACTION(ResetAction, reset)
|
||||
DFPLAYER_SIMPLE_ACTION(StartAction, start)
|
||||
DFPLAYER_SIMPLE_ACTION(PauseAction, pause)
|
||||
DFPLAYER_SIMPLE_ACTION(StopAction, stop)
|
||||
DFPLAYER_SIMPLE_ACTION(RandomAction, random)
|
||||
DFPLAYER_SIMPLE_ACTION(VolumeUpAction, volume_up)
|
||||
DFPLAYER_SIMPLE_ACTION(VolumeDownAction, volume_down)
|
||||
|
||||
template<typename... Ts> class DFPlayerIsPlayingCondition final : public Condition<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_playing(); }
|
||||
|
||||
@@ -3,7 +3,7 @@ import encodings
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32_ble
|
||||
from esphome.components.const import CONF_MANUFACTURER
|
||||
from esphome.components.const import CONF_DESCRIPTION, CONF_MANUFACTURER
|
||||
from esphome.components.esp32 import request_bluetooth
|
||||
from esphome.components.esp32_ble import BTLoggers, bt_uuid
|
||||
import esphome.config_validation as cv
|
||||
@@ -37,7 +37,6 @@ CONF_ADVERTISE = "advertise"
|
||||
CONF_APPEARANCE = "appearance"
|
||||
CONF_BROADCAST = "broadcast"
|
||||
CONF_CHARACTERISTICS = "characteristics"
|
||||
CONF_DESCRIPTION = "description"
|
||||
CONF_DESCRIPTORS = "descriptors"
|
||||
CONF_ENDIANNESS = "endianness"
|
||||
CONF_FIRMWARE_VERSION = "firmware_version"
|
||||
|
||||
@@ -467,6 +467,8 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
if (this->extended_proto_()) {
|
||||
// Read ota type, 1 byte
|
||||
if (!this->data_readall_(buf, 1)) {
|
||||
if (this->client_left_before_start_())
|
||||
return;
|
||||
this->log_read_error_(LOG_STR("OTA type"));
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
@@ -476,6 +478,9 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
|
||||
// Read size, 4 bytes MSB first
|
||||
if (!this->data_readall_(buf, 4)) {
|
||||
// The first request byte is the type on the extended protocol; a close after it was a cut-off request
|
||||
if (!this->extended_proto_() && this->client_left_before_start_())
|
||||
return;
|
||||
this->log_read_error_(LOG_STR("size"));
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
@@ -542,6 +547,8 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
// there is no would-block retry here and failures are already logged.
|
||||
read = this->noise_read_data_(buf, requested);
|
||||
if (read <= 0) {
|
||||
if (this->remote_closed_)
|
||||
this->log_remote_closed_(LOG_STR("data"));
|
||||
error_code = ota::OTA_RESPONSE_ERROR_UNKNOWN;
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
@@ -666,7 +673,11 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) {
|
||||
return false;
|
||||
}
|
||||
} else if (read == 0) {
|
||||
ESP_LOGW(TAG, "Remote closed");
|
||||
// A partial message is a cut-off request, not a clean close; the caller reports the clean one
|
||||
this->remote_closed_ = at == 0;
|
||||
if (at > 0) {
|
||||
ESP_LOGW(TAG, "Remote closed after %u of %zu bytes", (unsigned) at, len);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
at += read;
|
||||
@@ -712,7 +723,22 @@ void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) {
|
||||
ESP_LOGW(TAG, "Socket %s: errno %d", LOG_STR_ARG(msg), errno);
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::log_read_error_(const LogString *what) { ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what)); }
|
||||
bool ESPHomeOTAComponent::client_left_before_start_() {
|
||||
// Key probes and scanners hang up right after the handshake; nothing started, so no error status or callback
|
||||
if (!this->remote_closed_)
|
||||
return false;
|
||||
ESP_LOGD(TAG, "Client left after the handshake");
|
||||
this->cleanup_connection_();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::log_read_error_(const LogString *what) {
|
||||
if (this->remote_closed_) {
|
||||
this->log_remote_closed_(what);
|
||||
return;
|
||||
}
|
||||
ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what));
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::log_start_(const LogString *phase) {
|
||||
char peername[socket::SOCKADDR_STR_LEN];
|
||||
@@ -793,6 +819,7 @@ void ESPHomeOTAComponent::cleanup_connection_() {
|
||||
this->handshake_buf_pos_ = 0;
|
||||
this->ota_state_ = OTAState::IDLE;
|
||||
this->ota_features_ = 0;
|
||||
this->remote_closed_ = false;
|
||||
this->backend_ = nullptr;
|
||||
#ifdef USE_OTA_PASSWORD
|
||||
this->cleanup_auth_();
|
||||
|
||||
@@ -134,6 +134,7 @@ class ESPHomeOTAComponent final : public ota::OTAComponent {
|
||||
void server_failed_(const LogString *msg);
|
||||
void log_socket_error_(const LogString *msg);
|
||||
void log_read_error_(const LogString *what);
|
||||
bool client_left_before_start_();
|
||||
void log_start_(const LogString *phase);
|
||||
void log_remote_closed_(const LogString *during);
|
||||
void cleanup_connection_();
|
||||
@@ -186,6 +187,7 @@ class ESPHomeOTAComponent final : public ota::OTAComponent {
|
||||
OTAState ota_state_{OTAState::IDLE};
|
||||
uint8_t handshake_buf_pos_{0};
|
||||
uint8_t ota_features_{0};
|
||||
bool remote_closed_{false}; // the peer hung up cleanly during a blocking read
|
||||
#ifdef USE_OTA_PASSWORD
|
||||
uint8_t auth_buf_pos_{0};
|
||||
uint8_t auth_type_{0}; // Store auth type to know which hasher to use
|
||||
|
||||
@@ -241,8 +241,6 @@ EthernetComponent = ethernet_ns.class_("EthernetComponent", cg.Component)
|
||||
ManualIP = ethernet_ns.struct("ManualIP")
|
||||
EthernetConnectedCondition = ethernet_ns.class_("EthernetConnectedCondition", Condition)
|
||||
EthernetEnabledCondition = ethernet_ns.class_("EthernetEnabledCondition", Condition)
|
||||
EthernetEnableAction = ethernet_ns.class_("EthernetEnableAction", automation.Action)
|
||||
EthernetDisableAction = ethernet_ns.class_("EthernetDisableAction", automation.Action)
|
||||
|
||||
|
||||
def _is_framework_spi_polling_mode_supported() -> bool:
|
||||
@@ -938,10 +936,13 @@ for _name, _cls in (
|
||||
("ethernet.enabled", EthernetEnabledCondition),
|
||||
):
|
||||
automation.register_condition(_name, _cls, cv.Schema({}))(_new_pvariable_to_code)
|
||||
for _name, _cls in (
|
||||
("ethernet.enable", EthernetEnableAction),
|
||||
("ethernet.disable", EthernetDisableAction),
|
||||
|
||||
ETHERNET_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(EthernetComponent)})
|
||||
|
||||
for _name, _method in (
|
||||
("ethernet.enable", "enable()"),
|
||||
("ethernet.disable", "disable()"),
|
||||
):
|
||||
automation.register_action(_name, _cls, cv.Schema({}), synchronous=True)(
|
||||
_new_pvariable_to_code
|
||||
automation.register_apply_action(
|
||||
_name, ETHERNET_ACTION_SCHEMA, automation.ApplyCall(_method)
|
||||
)
|
||||
|
||||
@@ -16,15 +16,5 @@ template<typename... Ts> class EthernetEnabledCondition final : public Condition
|
||||
bool check(const Ts &...x) override { return global_eth_component->is_enabled(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EthernetEnableAction final : public Action<Ts...> {
|
||||
public:
|
||||
void play(const Ts &...x) override { global_eth_component->enable(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EthernetDisableAction final : public Action<Ts...> {
|
||||
public:
|
||||
void play(const Ts &...x) override { global_eth_component->disable(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::ethernet
|
||||
#endif
|
||||
|
||||
@@ -133,10 +133,17 @@ TRIGGER_EVENT_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
def _event_type_literal(config: ConfigType, value: str) -> str:
|
||||
"""A constant event type is a plain literal; trigger() only compares it, so no copy is needed."""
|
||||
return str(cg.safe_exp(value))
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
"event.trigger",
|
||||
TRIGGER_EVENT_SCHEMA,
|
||||
automation.ApplyField(CONF_EVENT_TYPE, "trigger", cg.std_string),
|
||||
automation.ApplyField(
|
||||
CONF_EVENT_TYPE, "trigger", cg.std_string, _event_type_literal
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/event/event.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome::event {
|
||||
|
||||
class EventTrigger final : public Trigger<StringRef> {
|
||||
public:
|
||||
EventTrigger(Event *event) {
|
||||
event->add_on_event_callback([this](StringRef event_type) { this->trigger(event_type); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::event
|
||||
@@ -7,17 +7,17 @@ namespace esphome::event {
|
||||
|
||||
static const char *const TAG = "event";
|
||||
|
||||
void Event::trigger(const std::string &event_type) {
|
||||
void Event::trigger(const char *event_type) {
|
||||
// Linear search with strcmp - faster than std::set for small datasets (1-5 items typical)
|
||||
const char *found = nullptr;
|
||||
for (const char *type : this->types_) {
|
||||
if (strcmp(type, event_type.c_str()) == 0) {
|
||||
if (strcmp(type, event_type) == 0) {
|
||||
found = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == nullptr) {
|
||||
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
|
||||
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type);
|
||||
return;
|
||||
}
|
||||
this->last_event_type_ = found;
|
||||
@@ -36,12 +36,4 @@ void Event::set_event_types(const FixedVector<const char *> &event_types) {
|
||||
this->last_event_type_ = nullptr; // Reset when types change
|
||||
}
|
||||
|
||||
void Event::set_event_types(const std::vector<const char *> &event_types) {
|
||||
this->types_.init(event_types.size());
|
||||
for (const char *type : event_types) {
|
||||
this->types_.push_back(type);
|
||||
}
|
||||
this->last_event_type_ = nullptr; // Reset when types change
|
||||
}
|
||||
|
||||
} // namespace esphome::event
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
@@ -21,22 +20,21 @@ namespace esphome::event {
|
||||
|
||||
class Event : public EntityBase {
|
||||
public:
|
||||
void trigger(const std::string &event_type);
|
||||
/// Trigger an event; the type is matched against the configured types by string compare.
|
||||
void trigger(const char *event_type);
|
||||
void trigger(const std::string &event_type) { this->trigger(event_type.c_str()); }
|
||||
|
||||
/// Set the event types supported by this event (from initializer list).
|
||||
/// Set the event types supported by this event; called by generated code with string literals.
|
||||
void set_event_types(std::initializer_list<const char *> event_types) {
|
||||
this->types_ = event_types;
|
||||
this->last_event_type_ = nullptr; // Reset when types change
|
||||
}
|
||||
/// Set the event types supported by this event (from FixedVector).
|
||||
/// Copy the event types of another event, for components that wrap one.
|
||||
void set_event_types(const FixedVector<const char *> &event_types);
|
||||
/// Set the event types supported by this event (from vector).
|
||||
void set_event_types(const std::vector<const char *> &event_types);
|
||||
|
||||
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
|
||||
void set_event_types(std::initializer_list<std::string> event_types) = delete;
|
||||
void set_event_types(const FixedVector<std::string> &event_types) = delete;
|
||||
void set_event_types(const std::vector<std::string> &event_types) = delete;
|
||||
|
||||
/// Return the event types supported by this event.
|
||||
const FixedVector<const char *> &get_event_types() const { return this->types_; }
|
||||
|
||||
@@ -83,9 +83,6 @@ FanPresetSetTrigger = fan_ns.class_(
|
||||
"FanPresetSetTrigger", automation.Trigger.template(cg.StringRef)
|
||||
)
|
||||
|
||||
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
|
||||
FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template())
|
||||
|
||||
_FAN_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
@@ -367,27 +364,16 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"fan.is_on",
|
||||
FanIsOnCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Fan),
|
||||
}
|
||||
),
|
||||
FAN_CONDITION_SCHEMA = automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Fan),
|
||||
}
|
||||
)
|
||||
@automation.register_condition(
|
||||
"fan.is_off",
|
||||
FanIsOffCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Fan),
|
||||
}
|
||||
),
|
||||
|
||||
automation.register_apply_condition("fan.is_on", FAN_CONDITION_SCHEMA, "state")
|
||||
automation.register_apply_condition(
|
||||
"fan.is_off", FAN_CONDITION_SCHEMA, "state == false"
|
||||
)
|
||||
async def fan_is_on_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -68,23 +68,6 @@ template<typename... Ts> class CycleSpeedAction final : public Action<Ts...> {
|
||||
Fan *state_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class FanIsOnCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
explicit FanIsOnCondition(Fan *state) : state_(state) {}
|
||||
bool check(const Ts &...x) override { return this->state_->state; }
|
||||
|
||||
protected:
|
||||
Fan *state_;
|
||||
};
|
||||
template<typename... Ts> class FanIsOffCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
explicit FanIsOffCondition(Fan *state) : state_(state) {}
|
||||
bool check(const Ts &...x) override { return !this->state_->state; }
|
||||
|
||||
protected:
|
||||
Fan *state_;
|
||||
};
|
||||
|
||||
class FanStateTrigger final : public Trigger<Fan *> {
|
||||
public:
|
||||
FanStateTrigger(Fan *state) : fan_(state) {
|
||||
|
||||
@@ -22,6 +22,7 @@ from esphome.const import (
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HERTZ,
|
||||
UNIT_KILOWATT_HOURS,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
)
|
||||
@@ -33,7 +34,6 @@ CONF_TOTAL_GENERATION_TIME = "total_generation_time"
|
||||
CONF_TODAY_GENERATION_TIME = "today_generation_time"
|
||||
CONF_PV1 = "pv1"
|
||||
CONF_PV2 = "pv2"
|
||||
UNIT_KILOWATT_HOURS = "kWh"
|
||||
UNIT_HOURS = "h"
|
||||
UNIT_KOHM = "kΩ"
|
||||
UNIT_MILLIAMPERE = "mA"
|
||||
|
||||
@@ -23,6 +23,7 @@ from esphome.const import (
|
||||
UNIT_AMPERE,
|
||||
UNIT_DEGREES,
|
||||
UNIT_HERTZ,
|
||||
UNIT_KILOWATT_HOURS,
|
||||
UNIT_MINUTE,
|
||||
UNIT_VOLT,
|
||||
UNIT_VOLT_AMPS_REACTIVE,
|
||||
@@ -36,7 +37,6 @@ CONF_TOTAL_GENERATION_TIME = "total_generation_time"
|
||||
CONF_TODAY_GENERATION_TIME = "today_generation_time"
|
||||
CONF_PV1 = "pv1"
|
||||
CONF_PV2 = "pv2"
|
||||
UNIT_KILOWATT_HOURS = "kWh"
|
||||
UNIT_HOURS = "h"
|
||||
UNIT_KOHM = "kΩ"
|
||||
UNIT_MILLIAMPERE = "mA"
|
||||
|
||||
@@ -24,6 +24,7 @@ from esphome.const import (
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_MILLIVOLT,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
@@ -44,7 +45,6 @@ CONF_TEMPERATURE_COEFFICIENT = "temperature_coefficient"
|
||||
CONF_RESET_ON_BOOT = "reset_on_boot"
|
||||
UNIT_COULOMB = "C"
|
||||
UNIT_JOULE = "J"
|
||||
UNIT_MILLIVOLT = "mV"
|
||||
|
||||
ina2xx_base_ns = cg.esphome_ns.namespace("ina2xx_base")
|
||||
INA2XX = ina2xx_base_ns.class_("INA2XX", cg.PollingComponent)
|
||||
|
||||
@@ -343,6 +343,7 @@ RESTORE_MODES = {
|
||||
# Schema default that also matches the C++ initializer in light_state.h; codegen
|
||||
# skips the setter when the config equals it.
|
||||
DEFAULT_FLASH_TRANSITION_LENGTH = "0s"
|
||||
CONF_TRANSITION_STATE_PUBLISH_INTERVAL = "transition_state_publish_interval"
|
||||
|
||||
LIGHT_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
@@ -393,6 +394,11 @@ BRIGHTNESS_ONLY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend(
|
||||
cv.Optional(
|
||||
CONF_FLASH_TRANSITION_LENGTH, default=DEFAULT_FLASH_TRANSITION_LENGTH
|
||||
): cv.positive_time_period_milliseconds,
|
||||
# Below 150ms a device cannot publish any faster and only spends CPU and traffic
|
||||
cv.Optional(CONF_TRANSITION_STATE_PUBLISH_INTERVAL): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(min=cv.TimePeriod(milliseconds=150)),
|
||||
),
|
||||
cv.Optional(CONF_EFFECTS): validate_effects(MONOCHROMATIC_EFFECTS),
|
||||
}
|
||||
)
|
||||
@@ -406,6 +412,11 @@ RGB_LIGHT_SCHEMA = BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(
|
||||
ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AddressableLightState),
|
||||
# The addressable transformer writes the LED buffer directly, so there is no
|
||||
# intermediate state to publish
|
||||
cv.Optional(CONF_TRANSITION_STATE_PUBLISH_INTERVAL): cv.invalid(
|
||||
"transition_state_publish_interval is not supported on addressable lights"
|
||||
),
|
||||
cv.Optional(CONF_EFFECTS): validate_effects(ADDRESSABLE_EFFECTS),
|
||||
cv.Optional(CONF_COLOR_CORRECT): cv.All(
|
||||
[cv.percentage], cv.Length(min=3, max=4)
|
||||
@@ -513,6 +524,10 @@ async def setup_light_core_(light_var, config, output_var):
|
||||
DEFAULT_FLASH_TRANSITION_LENGTH
|
||||
):
|
||||
cg.add(light_var.set_flash_transition_length(flash_transition_length))
|
||||
# Setting an interval opts this light in and compiles the feature in
|
||||
if (interval := config.get(CONF_TRANSITION_STATE_PUBLISH_INTERVAL)) is not None:
|
||||
cg.add(light_var.set_transition_state_publish_interval(interval))
|
||||
cg.add_define("USE_LIGHT_TRANSITION_PUBLISH_INTERVAL")
|
||||
if (gamma_correct := config.get(CONF_GAMMA_CORRECT)) is not None:
|
||||
cg.add(light_var.set_gamma_correct(gamma_correct))
|
||||
fwd_arr = _get_or_create_gamma_table(gamma_correct)
|
||||
|
||||
@@ -117,23 +117,6 @@ template<bool Forward, typename... Ts> class LightEffectCycleAction final : publ
|
||||
bool include_none_{false};
|
||||
};
|
||||
|
||||
template<typename... Ts> class LightIsOnCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
explicit LightIsOnCondition(LightState *state) : state_(state) {}
|
||||
bool check(const Ts &...x) override { return this->state_->current_values.is_on(); }
|
||||
|
||||
protected:
|
||||
LightState *state_;
|
||||
};
|
||||
template<typename... Ts> class LightIsOffCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
explicit LightIsOffCondition(LightState *state) : state_(state) {}
|
||||
bool check(const Ts &...x) override { return !this->state_->current_values.is_on(); }
|
||||
|
||||
protected:
|
||||
LightState *state_;
|
||||
};
|
||||
|
||||
class LightTurnOnTrigger final : public Trigger<>, public LightRemoteValuesListener {
|
||||
public:
|
||||
explicit LightTurnOnTrigger(LightState *a_light) : light_(a_light) {
|
||||
|
||||
@@ -38,8 +38,6 @@ from .types import (
|
||||
ColorMode,
|
||||
DimRelativeAction,
|
||||
LightEffectCycleAction,
|
||||
LightIsOffCondition,
|
||||
LightIsOnCondition,
|
||||
LightState,
|
||||
ToggleAction,
|
||||
)
|
||||
@@ -381,24 +379,15 @@ async def light_addressable_set_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"light.is_on",
|
||||
LightIsOnCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LightState),
|
||||
}
|
||||
),
|
||||
LIGHT_CONDITION_SCHEMA = automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LightState),
|
||||
}
|
||||
)
|
||||
@automation.register_condition(
|
||||
"light.is_off",
|
||||
LightIsOffCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LightState),
|
||||
}
|
||||
),
|
||||
|
||||
automation.register_apply_condition(
|
||||
"light.is_on", LIGHT_CONDITION_SCHEMA, "current_values.is_on()"
|
||||
)
|
||||
automation.register_apply_condition(
|
||||
"light.is_off", LIGHT_CONDITION_SCHEMA, "current_values.is_on() == false"
|
||||
)
|
||||
async def light_is_on_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
|
||||
@@ -32,7 +32,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
||||
root[ESPHOME_F("effect_count")] = state.get_effect_count();
|
||||
}
|
||||
|
||||
auto values = state.remote_values;
|
||||
auto values = state.get_reported_values();
|
||||
|
||||
const auto color_mode = values.get_color_mode();
|
||||
const auto *mode_str = get_color_mode_json_str(color_mode);
|
||||
@@ -131,6 +131,13 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
|
||||
call.set_white(float(root[ESPHOME_F("white_value")]) / 255.0f);
|
||||
}
|
||||
|
||||
if (root[ESPHOME_F("white")].is<uint8_t>()) {
|
||||
// White stays full because ESPHome multiplies brightness and white
|
||||
call.set_color_mode_if_supported(ColorMode::WHITE);
|
||||
call.set_brightness(float(root[ESPHOME_F("white")]) / 255.0f);
|
||||
call.set_white(1.0f);
|
||||
}
|
||||
|
||||
if (root[ESPHOME_F("color_temp")].is<uint16_t>()) {
|
||||
call.set_color_temperature(float(root[ESPHOME_F("color_temp")]));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "light_state.h"
|
||||
#include "esp_color_correction.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
#include "esphome/core/application.h"
|
||||
#endif
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "light_output.h"
|
||||
@@ -105,6 +108,13 @@ void LightState::dump_config() {
|
||||
" Default Transition Length: %.1fs\n"
|
||||
" Gamma Correct: %.2f",
|
||||
this->default_transition_length_ / 1e3f, this->gamma_correct_);
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
// The define is build wide; only lights that set the option have an interval
|
||||
if (this->transition_state_publish_interval_ != 0) {
|
||||
ESP_LOGCONFIG(TAG, " Transition State Publish Interval: %" PRIu32 "ms",
|
||||
this->transition_state_publish_interval_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (traits.supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
@@ -130,13 +140,30 @@ void LightState::loop() {
|
||||
this->next_write_ = true;
|
||||
}
|
||||
|
||||
if (this->transformer_->is_finished()) {
|
||||
const bool finished = this->transformer_->is_finished();
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
if (this->transition_publish_enabled_ && !finished) {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (now - this->last_transition_state_publish_ >= this->transition_state_publish_interval_) {
|
||||
this->publish_state();
|
||||
this->last_transition_state_publish_ = now;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (finished) {
|
||||
// if the transition has written directly to the output, current_values is outdated, so update it
|
||||
this->current_values = this->transformer_->get_target_values();
|
||||
|
||||
this->transformer_->stop();
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
if (this->transition_publish_enabled_) {
|
||||
// Report the end state from remote_values; a flash's stop() left publishing to us
|
||||
this->transition_publish_enabled_ = false;
|
||||
this->publish_state();
|
||||
}
|
||||
#endif
|
||||
if (this->target_state_reached_listeners_) {
|
||||
for (auto *listener : *this->target_state_reached_listeners_) {
|
||||
listener->on_light_target_state_reached();
|
||||
@@ -348,10 +375,7 @@ void LightState::stop_effect_() {
|
||||
void LightState::start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values) {
|
||||
this->transformer_ = this->output_->create_default_transition();
|
||||
this->transformer_->setup(this->current_values, target, length);
|
||||
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
}
|
||||
this->set_transformer_remote_values_(target, set_remote_values);
|
||||
// Enable loop while transition is active
|
||||
this->enable_loop();
|
||||
}
|
||||
@@ -365,10 +389,7 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
|
||||
|
||||
this->transformer_ = make_unique<LightFlashTransformer>(*this);
|
||||
this->transformer_->setup(end_colors, target, length);
|
||||
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
};
|
||||
this->set_transformer_remote_values_(target, set_remote_values);
|
||||
// Enable loop while flash is active
|
||||
this->enable_loop();
|
||||
}
|
||||
@@ -376,6 +397,9 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
|
||||
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
this->transition_publish_enabled_ = false;
|
||||
#endif
|
||||
this->current_values = target;
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
@@ -391,6 +415,18 @@ void LightState::disable_loop_if_idle_() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
void LightState::set_transformer_remote_values_(const LightColorValues &target, bool set_remote_values) {
|
||||
this->transition_publish_enabled_ = set_remote_values && this->transition_state_publish_interval_ > 0;
|
||||
if (this->transition_publish_enabled_) {
|
||||
this->last_transition_state_publish_ = App.get_loop_component_start_time();
|
||||
}
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void LightState::save_remote_values_() {
|
||||
LightStateRTCState saved;
|
||||
saved.color_mode = this->remote_values.get_color_mode();
|
||||
|
||||
@@ -100,6 +100,21 @@ class LightState : public EntityBase, public Component {
|
||||
LightCall turn_on();
|
||||
LightCall turn_off();
|
||||
LightCall toggle();
|
||||
|
||||
/// The values reported to the frontend: current_values while a light publishes intermediate
|
||||
/// states on an interval, otherwise remote_values. Each interval sample is a publish_state(),
|
||||
/// so on_state automations run on every sample as well.
|
||||
const LightColorValues &get_reported_values() const {
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
if (this->transition_publish_enabled_) {
|
||||
return this->current_values;
|
||||
}
|
||||
#endif
|
||||
return this->remote_values;
|
||||
}
|
||||
|
||||
/// True from the call that starts a transition or flash until it reaches its target.
|
||||
bool is_transitioning() const { return this->transformer_ != nullptr; }
|
||||
LightCall make_call();
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
@@ -172,6 +187,13 @@ class LightState : public EntityBase, public Component {
|
||||
void set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; }
|
||||
float get_gamma_correct() const { return this->gamma_correct_; }
|
||||
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
void set_transition_state_publish_interval(uint32_t transition_state_publish_interval) {
|
||||
this->transition_state_publish_interval_ = transition_state_publish_interval;
|
||||
}
|
||||
uint32_t get_transition_state_publish_interval() const { return this->transition_state_publish_interval_; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT_GAMMA_LUT
|
||||
/// Set pre-computed gamma forward lookup table (256-entry uint16 PROGMEM array)
|
||||
void set_gamma_table(const uint16_t *forward) { this->gamma_table_ = forward; }
|
||||
@@ -291,6 +313,7 @@ class LightState : public EntityBase, public Component {
|
||||
friend LightOutput;
|
||||
friend LightCall;
|
||||
friend class AddressableLight;
|
||||
friend class LightFlashTransformer;
|
||||
|
||||
/// Internal method to start an effect with the given index
|
||||
void start_effect_(uint32_t effect_index);
|
||||
@@ -307,6 +330,18 @@ class LightState : public EntityBase, public Component {
|
||||
/// Internal method to set the color values to target immediately (with no transition).
|
||||
void set_immediately_(const LightColorValues &target, bool set_remote_values);
|
||||
|
||||
/// Point remote_values at the new transformer's target and, when this light publishes
|
||||
/// intermediate states on an interval, start the interval clock.
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
void set_transformer_remote_values_(const LightColorValues &target, bool set_remote_values);
|
||||
#else
|
||||
void set_transformer_remote_values_(const LightColorValues &target, bool set_remote_values) {
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Internal method to save the current remote_values to the preferences
|
||||
void save_remote_values_();
|
||||
|
||||
@@ -357,6 +392,10 @@ class LightState : public EntityBase, public Component {
|
||||
uint32_t default_transition_length_{};
|
||||
/// Transition length to use for flash transitions.
|
||||
uint32_t flash_transition_length_{}; // Keep in sync with DEFAULT_FLASH_TRANSITION_LENGTH in __init__.py
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
uint32_t transition_state_publish_interval_{0};
|
||||
uint32_t last_transition_state_publish_{0};
|
||||
#endif
|
||||
/// Gamma correction factor for the light.
|
||||
float gamma_correct_{};
|
||||
#ifdef USE_LIGHT_GAMMA_LUT
|
||||
@@ -367,6 +406,10 @@ class LightState : public EntityBase, public Component {
|
||||
bool next_write_{true};
|
||||
// for effects, true if a transformer (transition) is active.
|
||||
bool is_transformer_active_{false};
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
/// True while the active transformer publishes current_values on an interval from loop().
|
||||
bool transition_publish_enabled_{false};
|
||||
#endif
|
||||
/// Restore mode of the light.
|
||||
LightRestoreMode restore_mode_;
|
||||
};
|
||||
|
||||
@@ -105,6 +105,11 @@ class LightFlashTransformer : public LightTransformer {
|
||||
}
|
||||
this->state_.current_values = this->get_start_values();
|
||||
this->state_.remote_values = this->get_start_values();
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
// The light reports the end state itself once the transformer finishes
|
||||
if (this->state_.transition_publish_enabled_)
|
||||
return;
|
||||
#endif
|
||||
this->state_.publish_state();
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,6 @@ ToggleAction = light_ns.class_("ToggleAction", automation.Action)
|
||||
LightEffectCycleAction = light_ns.class_("LightEffectCycleAction", automation.Action)
|
||||
DimRelativeAction = light_ns.class_("DimRelativeAction", automation.Action)
|
||||
AddressableSet = light_ns.class_("AddressableSet", automation.Action)
|
||||
LightIsOnCondition = light_ns.class_("LightIsOnCondition", automation.Condition)
|
||||
LightIsOffCondition = light_ns.class_("LightIsOffCondition", automation.Condition)
|
||||
|
||||
# Triggers
|
||||
LightTurnOnTrigger = light_ns.class_(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
import esphome.config_validation as cv
|
||||
@@ -34,7 +34,6 @@ LockAction = lock_ns.class_("LockAction", automation.Action)
|
||||
OpenAction = lock_ns.class_("OpenAction", automation.Action)
|
||||
LockPublishAction = lock_ns.class_("LockPublishAction", automation.Action)
|
||||
|
||||
LockCondition = lock_ns.class_("LockCondition", Condition)
|
||||
LockStateForwarder = lock_ns.class_("LockStateForwarder")
|
||||
|
||||
LockState = lock_ns.enum("LockState")
|
||||
@@ -154,26 +153,14 @@ async def lock_action_to_code(
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_condition("lock.is_locked", LockCondition, LOCK_ACTION_SCHEMA)
|
||||
async def lock_is_on_to_code(
|
||||
config: ConfigType,
|
||||
condition_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition("lock.is_unlocked", LockCondition, LOCK_ACTION_SCHEMA)
|
||||
async def lock_is_off_to_code(
|
||||
config: ConfigType,
|
||||
condition_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
automation.register_apply_condition(
|
||||
"lock.is_locked", LOCK_ACTION_SCHEMA, f"state == {LockState.LOCK_STATE_LOCKED}"
|
||||
)
|
||||
automation.register_apply_condition(
|
||||
"lock.is_unlocked",
|
||||
LOCK_ACTION_SCHEMA,
|
||||
f"state == {LockState.LOCK_STATE_UNLOCKED}",
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -36,19 +36,6 @@ template<typename... Ts> class OpenAction final : public Action<Ts...> {
|
||||
Lock *lock_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class LockCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
LockCondition(Lock *parent, bool state) : parent_(parent), state_(state) {}
|
||||
bool check(const Ts &...x) override {
|
||||
auto check_state = this->state_ ? LockState::LOCK_STATE_LOCKED : LockState::LOCK_STATE_UNLOCKED;
|
||||
return this->parent_->state == check_state;
|
||||
}
|
||||
|
||||
protected:
|
||||
Lock *parent_;
|
||||
bool state_;
|
||||
};
|
||||
|
||||
/// Callback forwarder that triggers an Automation<> only when a specific lock state is entered.
|
||||
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
|
||||
template<LockState State> struct LockStateForwarder {
|
||||
|
||||
@@ -32,9 +32,6 @@ enum LockState : uint8_t {
|
||||
};
|
||||
const LogString *lock_state_to_string(LockState state);
|
||||
|
||||
/// Maximum length of lock state string (including null terminator): "UNLOCKING" = 10
|
||||
static constexpr size_t LOCK_STATE_STR_SIZE = 10;
|
||||
|
||||
class LockTraits {
|
||||
public:
|
||||
LockTraits() = default;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
@@ -63,6 +64,8 @@ from esphome.core import CORE, ID, CoroPriority, Lambda, coroutine_with_priority
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
logger_ns = cg.esphome_ns.namespace("logger")
|
||||
LOG_LEVELS = {
|
||||
@@ -105,6 +108,7 @@ DEFAULT = "DEFAULT"
|
||||
|
||||
CONF_INITIAL_LEVEL = "initial_level"
|
||||
CONF_LOGGER_ID = "logger_id"
|
||||
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = "esp8266_store_log_strings_in_flash"
|
||||
CONF_RUNTIME_TAG_LEVELS = "runtime_tag_levels"
|
||||
CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size"
|
||||
CONF_WAIT_FOR_CDC = "wait_for_cdc"
|
||||
@@ -219,6 +223,18 @@ def validate_initial_no_higher_than_global(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def warn_ram_log_strings(config: ConfigType) -> ConfigType:
|
||||
# Remove before 2027.4.0
|
||||
if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH) is False:
|
||||
_LOGGER.warning(
|
||||
"'%s: false' is ignored and will be rejected in 2027.4.0. Log format strings "
|
||||
"always stay in flash now; copying them into RAM gave no speed gain and the "
|
||||
"lost RAM caused crashes. Remove the option",
|
||||
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH,
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def validate_wait_for_cdc(config: ConfigType) -> ConfigType:
|
||||
if config.get(CONF_WAIT_FOR_CDC) and config.get(CONF_HARDWARE_UART) != USB_CDC:
|
||||
raise cv.Invalid("wait_for_cdc requires hardware_uart: USB_CDC")
|
||||
@@ -232,7 +248,6 @@ LoggerMessageTrigger = logger_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = "esp8266_store_log_strings_in_flash"
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -332,6 +347,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
validate_local_no_higher_than_global,
|
||||
validate_initial_no_higher_than_global,
|
||||
validate_wait_for_cdc,
|
||||
warn_ram_log_strings,
|
||||
)
|
||||
|
||||
|
||||
@@ -450,9 +466,6 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
cg.add_build_flag("-DCORE_DEBUG_LEVEL=5")
|
||||
if CORE.is_esp32 and is_at_least_very_verbose:
|
||||
cg.add_build_flag("-DENABLE_I2C_DEBUG_BUFFER")
|
||||
if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH):
|
||||
cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH")
|
||||
|
||||
if CORE.is_esp32:
|
||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
|
||||
|
||||
@@ -105,9 +105,9 @@ struct LogBuffer {
|
||||
this->format_vsnprintf_(format, args);
|
||||
this->finalize_();
|
||||
}
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
void HOT format_body_P(PGM_P format, va_list args) {
|
||||
this->format_vsnprintf_P_(format, args);
|
||||
#ifdef USE_ESP8266
|
||||
void HOT format_body_p(PGM_P format, va_list args) {
|
||||
this->format_vsnprintf_p_(format, args);
|
||||
this->finalize_();
|
||||
}
|
||||
#endif
|
||||
@@ -158,8 +158,8 @@ struct LogBuffer {
|
||||
return;
|
||||
this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args));
|
||||
}
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
void format_vsnprintf_P_(PGM_P format, va_list args) {
|
||||
#ifdef USE_ESP8266
|
||||
void format_vsnprintf_p_(PGM_P format, va_list args) {
|
||||
if (this->full_())
|
||||
return;
|
||||
this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args));
|
||||
|
||||
@@ -127,9 +127,8 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
}
|
||||
#endif // USE_ESP32 || USE_HOST || USE_LIBRETINY || USE_ZEPHYR
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
// Implementation for ESP8266 with flash string support.
|
||||
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
|
||||
#ifdef USE_ESP8266
|
||||
// ESP8266 keeps log format strings in flash.
|
||||
//
|
||||
// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
|
||||
// Uses vsnprintf_P to read the format string directly from flash without copying to RAM.
|
||||
@@ -141,7 +140,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
|
||||
this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr);
|
||||
}
|
||||
#endif // USE_STORE_LOG_STR_IN_FLASH
|
||||
#endif // USE_ESP8266
|
||||
|
||||
inline uint8_t Logger::level_for(const char *tag) {
|
||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
|
||||
@@ -200,9 +200,9 @@ class Logger final : public Component {
|
||||
float get_setup_priority() const override { return setup_priority::BUS + 500.0f; }
|
||||
|
||||
void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args); // NOLINT
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
|
||||
va_list args); // NOLINT
|
||||
#ifdef USE_ESP8266
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, va_list args);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
@@ -244,14 +244,14 @@ class Logger final : public Component {
|
||||
buf.format_body(format, args);
|
||||
}
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
#ifdef USE_ESP8266
|
||||
// Format a log message with flash string format and write it to a buffer with header, footer, and null terminator
|
||||
// ESP8266-only (single-task), thread_name is always nullptr
|
||||
inline void HOT format_log_to_buffer_with_terminator_P_(uint8_t level, const char *tag, int line,
|
||||
inline void HOT format_log_to_buffer_with_terminator_p_(uint8_t level, const char *tag, int line,
|
||||
const __FlashStringHelper *format, va_list args,
|
||||
LogBuffer &buf) {
|
||||
buf.write_header(level, tag, line, nullptr);
|
||||
buf.format_body_P(reinterpret_cast<PGM_P>(format), args);
|
||||
buf.format_body_p(reinterpret_cast<PGM_P>(format), args);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -283,9 +283,9 @@ class Logger final : public Component {
|
||||
FormatType format, va_list args, const char *thread_name) {
|
||||
RecursionGuard guard(recursion_guard);
|
||||
LogBuffer buf{this->tx_buffer_, ESPHOME_LOGGER_TX_BUFFER_SIZE};
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
#ifdef USE_ESP8266
|
||||
if constexpr (std::is_same_v<FormatType, const __FlashStringHelper *>) {
|
||||
this->format_log_to_buffer_with_terminator_P_(level, tag, line, format, args, buf);
|
||||
this->format_log_to_buffer_with_terminator_p_(level, tag, line, format, args, buf);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components.const import UNIT_COUNTS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AMBIENT_LIGHT,
|
||||
@@ -28,7 +29,6 @@ CONF_UV_INDEX = "uv_index"
|
||||
CONF_UV = "uv"
|
||||
CONF_WINDOW_CORRECTION_FACTOR = "window_correction_factor"
|
||||
|
||||
UNIT_COUNTS = "#"
|
||||
UNIT_UVI = "UVI"
|
||||
|
||||
LTR390GAIN = ltr390_ns.enum("LTR390GAIN")
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components.const import UNIT_COUNTS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTUAL_GAIN,
|
||||
@@ -42,7 +43,6 @@ CONF_PS_LOW_THRESHOLD = "ps_low_threshold"
|
||||
ICON_BRIGHTNESS_7 = "mdi:brightness-7"
|
||||
ICON_GAIN = "mdi:multiplication"
|
||||
ICON_PROXIMITY = "mdi:hand-wave-outline"
|
||||
UNIT_COUNTS = "#"
|
||||
|
||||
ltr501_ns = cg.esphome_ns.namespace("ltr501")
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components.const import UNIT_COUNTS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTUAL_GAIN,
|
||||
@@ -41,7 +42,6 @@ CONF_PS_LOW_THRESHOLD = "ps_low_threshold"
|
||||
ICON_BRIGHTNESS_7 = "mdi:brightness-7"
|
||||
ICON_GAIN = "mdi:multiplication"
|
||||
ICON_PROXIMITY = "mdi:hand-wave-outline"
|
||||
UNIT_COUNTS = "#"
|
||||
|
||||
ltr_als_ps_ns = cg.esphome_ns.namespace("ltr_als_ps")
|
||||
|
||||
|
||||
@@ -680,7 +680,6 @@ CONF_BODY = "body"
|
||||
CONF_BUTTONS = "buttons"
|
||||
CONF_CHANGE_RATE = "change_rate"
|
||||
CONF_CLOSE_BUTTON = "close_button"
|
||||
CONF_COLOR_DEPTH = "color_depth"
|
||||
CONF_COLOR_END = "color_end"
|
||||
CONF_COLOR_START = "color_start"
|
||||
CONF_CONTAINER = "container"
|
||||
@@ -767,7 +766,6 @@ CONF_RESUME_ON_INPUT = "resume_on_input"
|
||||
CONF_RIGHT_BUTTON = "right_button"
|
||||
CONF_ROLLOVER = "rollover"
|
||||
CONF_ROOT_BACK_BTN = "root_back_btn"
|
||||
CONF_ROWS = "rows"
|
||||
CONF_SCALE = "scale"
|
||||
CONF_SCALE_LINES = "scale_lines"
|
||||
CONF_SCROLLBAR_MODE = "scrollbar_mode"
|
||||
|
||||
@@ -18,19 +18,18 @@ void MCP3204::dump_config() {
|
||||
}
|
||||
|
||||
float MCP3204::read_data(uint8_t pin, bool differential) {
|
||||
uint8_t command, b0, b1;
|
||||
|
||||
command = (1 << 6) | // start bit
|
||||
((differential ? 0 : 1) << 5) | // single or differential bit
|
||||
((pin & 0x07) << 2); // pin
|
||||
const uint8_t command = (1 << 6) | // start bit
|
||||
((differential ? 0 : 1) << 5) | // single or differential bit
|
||||
((pin & 0x07) << 2); // pin
|
||||
// One full-duplex transaction: command out, 12-bit result back in bytes 1 and 2.
|
||||
// Word aligned so ESP-IDF DMA uses the buffer in place; only ESP32-P4 also checks the length and bounces.
|
||||
alignas(4) uint8_t buffer[3] = {command, 0x00, 0x00};
|
||||
|
||||
this->enable();
|
||||
this->transfer_byte(command);
|
||||
b0 = this->transfer_byte(0x00);
|
||||
b1 = this->transfer_byte(0x00);
|
||||
this->transfer_array(buffer, sizeof(buffer));
|
||||
this->disable();
|
||||
|
||||
uint16_t digital_value = encode_uint16(b0, b1) >> 4;
|
||||
uint16_t digital_value = encode_uint16(buffer[1], buffer[2]) >> 4;
|
||||
return float(digital_value) / 4096.000f * this->reference_voltage_; // in V
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
// On ESP8266 and RP2040 the scheduler-backed MDNS.update() polling window is armed by
|
||||
// IP state listener events on whichever network interface is configured.
|
||||
#if (defined(USE_ESP8266) || defined(USE_RP2)) && \
|
||||
@@ -39,12 +40,7 @@ struct MDNSString;
|
||||
// Macro to cast string literals to MDNSString* (works on all platforms)
|
||||
#define MDNS_STR(name) (reinterpret_cast<const esphome::mdns::MDNSString *>(name))
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
#define MDNS_STR_ARG(s) ((PGM_P) (s))
|
||||
#else
|
||||
#define MDNS_STR_ARG(s) (reinterpret_cast<const char *>(s))
|
||||
#endif
|
||||
#define MDNS_STR_ARG(s) (reinterpret_cast<ESPHOME_PGM_P>(s))
|
||||
|
||||
// Service count is calculated at compile time by Python codegen
|
||||
// MDNS_SERVICE_COUNT will always be defined
|
||||
|
||||
@@ -190,16 +190,15 @@ _STATE_TRIGGERS = (
|
||||
(CONF_ON_TURN_OFF, MediaPlayerState.MEDIA_PLAYER_STATE_OFF),
|
||||
)
|
||||
|
||||
# State conditions that all share the same schema and codegen handler
|
||||
_STATE_CONDITIONS = [
|
||||
"idle",
|
||||
"paused",
|
||||
"playing",
|
||||
"announcing",
|
||||
"on",
|
||||
"off",
|
||||
"muted",
|
||||
]
|
||||
# State conditions: (config_key suffix, checked state)
|
||||
_STATE_CONDITIONS = (
|
||||
("idle", MediaPlayerState.MEDIA_PLAYER_STATE_IDLE),
|
||||
("paused", MediaPlayerState.MEDIA_PLAYER_STATE_PAUSED),
|
||||
("playing", MediaPlayerState.MEDIA_PLAYER_STATE_PLAYING),
|
||||
("announcing", MediaPlayerState.MEDIA_PLAYER_STATE_ANNOUNCING),
|
||||
("on", MediaPlayerState.MEDIA_PLAYER_STATE_ON),
|
||||
("off", MediaPlayerState.MEDIA_PLAYER_STATE_OFF),
|
||||
)
|
||||
|
||||
MediaPlayerCommand = media_player_ns.enum("MediaPlayerCommand", is_class=True)
|
||||
|
||||
@@ -360,27 +359,16 @@ for _action_name in _COMMAND_ACTIONS:
|
||||
)
|
||||
|
||||
|
||||
def _snake_to_camel(name):
|
||||
return "".join(word.capitalize() for word in name.split("_"))
|
||||
for _condition_name, _state in _STATE_CONDITIONS:
|
||||
automation.register_apply_condition(
|
||||
f"media_player.is_{_condition_name}",
|
||||
MEDIA_PLAYER_CONDITION_SCHEMA,
|
||||
f"state == {_state}",
|
||||
)
|
||||
|
||||
|
||||
def _register_state_conditions():
|
||||
async def handler(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
for condition_name in _STATE_CONDITIONS:
|
||||
class_name = f"Is{_snake_to_camel(condition_name)}Condition"
|
||||
condition_class = media_player_ns.class_(class_name, automation.Condition)
|
||||
automation.register_condition(
|
||||
f"media_player.is_{condition_name}",
|
||||
condition_class,
|
||||
MEDIA_PLAYER_CONDITION_SCHEMA,
|
||||
)(handler)
|
||||
|
||||
|
||||
_register_state_conditions()
|
||||
automation.register_apply_condition(
|
||||
"media_player.is_muted", MEDIA_PLAYER_CONDITION_SCHEMA, "is_muted()"
|
||||
)
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
|
||||
@@ -27,41 +27,4 @@ static_assert(std::is_trivially_copyable_v<StateAnyForwarder>);
|
||||
static_assert(sizeof(StateEnterForwarder<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>) <= sizeof(void *));
|
||||
static_assert(std::is_trivially_copyable_v<StateEnterForwarder<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>>);
|
||||
|
||||
template<typename... Ts> class IsIdleCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_IDLE; }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsPlayingCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING; }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsPausedCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PAUSED; }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsAnnouncingCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override {
|
||||
return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsOnCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ON; }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsOffCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_OFF; }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsMutedCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_muted(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::media_player
|
||||
|
||||
@@ -102,7 +102,7 @@ void MitsubishiClimate::transmit_state() {
|
||||
default:
|
||||
remote_state[6] = MITSUBISHI_MODE_COOL;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_COOL;
|
||||
if (this->supports_heat_) {
|
||||
if (this->modes_.count(climate::CLIMATE_MODE_HEAT)) {
|
||||
remote_state[6] = MITSUBISHI_MODE_HEAT;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_HEAT;
|
||||
}
|
||||
|
||||
@@ -51,10 +51,10 @@ class MitsubishiClimate final : public climate_ir::ClimateIR {
|
||||
{climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_BOOST,
|
||||
climate::CLIMATE_PRESET_SLEEP}) {}
|
||||
|
||||
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
void set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; }
|
||||
void set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; }
|
||||
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||
void set_supports_dry(bool supports_dry) { this->set_mode_supported_(climate::CLIMATE_MODE_DRY, supports_dry); }
|
||||
void set_supports_fan_only(bool supports_fan_only) {
|
||||
this->set_mode_supported_(climate::CLIMATE_MODE_FAN_ONLY, supports_fan_only);
|
||||
}
|
||||
|
||||
void set_fan_mode(SetFanMode fan_mode) {
|
||||
this->fan_mode_ = fan_mode;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import logger, socket
|
||||
from esphome.components.esp32 import (
|
||||
@@ -135,7 +134,6 @@ MQTTDisconnectTrigger = mqtt_ns.class_(
|
||||
"MQTTDisconnectTrigger", automation.Trigger.template(MQTTClientDisconnectReason)
|
||||
)
|
||||
MQTTComponent = mqtt_ns.class_("MQTTComponent", cg.Component)
|
||||
MQTTConnectedCondition = mqtt_ns.class_("MQTTConnectedCondition", Condition)
|
||||
|
||||
MQTTAlarmControlPanelComponent = mqtt_ns.class_(
|
||||
"MQTTAlarmControlPanelComponent", MQTTComponent
|
||||
@@ -599,18 +597,15 @@ async def register_mqtt_component(var, config):
|
||||
)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
automation.register_apply_condition(
|
||||
"mqtt.connected",
|
||||
MQTTConnectedCondition,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(MQTTClientComponent),
|
||||
}
|
||||
),
|
||||
"is_connected()",
|
||||
)
|
||||
async def mqtt_connected_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
@@ -417,15 +417,6 @@ template<typename... Ts> class MQTTPublishJsonAction final : public Action<Ts...
|
||||
MQTTClientComponent *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class MQTTConnectedCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
MQTTConnectedCondition(MQTTClientComponent *parent) : parent_(parent) {}
|
||||
bool check(const Ts &...x) override { return this->parent_->is_connected(); }
|
||||
|
||||
protected:
|
||||
MQTTClientComponent *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class MQTTEnableAction final : public Action<Ts...> {
|
||||
public:
|
||||
MQTTEnableAction(MQTTClientComponent *parent) : parent_(parent) {}
|
||||
|
||||
@@ -312,11 +312,7 @@ bool MQTTComponent::send_discovery_() {
|
||||
// Buffer sized for format string expansion: ~4 bytes net growth from format specifier to 8 hex digits, plus
|
||||
// safety margin
|
||||
char version_buf[sizeof(ver_fmt) + 8];
|
||||
#ifdef USE_ESP8266
|
||||
snprintf_P(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
#else
|
||||
snprintf(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
#endif
|
||||
ESPHOME_snprintf_P(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = version_buf;
|
||||
device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
|
||||
@@ -50,14 +50,8 @@ bool MQTTLockComponent::send_initial_state() { return this->publish_state(); }
|
||||
|
||||
bool MQTTLockComponent::publish_state() {
|
||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
char buf[LOCK_STATE_STR_SIZE];
|
||||
strncpy_P(buf, (PGM_P) lock_state_to_string(this->lock_->state), sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), buf);
|
||||
#else
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), LOG_STR_ARG(lock_state_to_string(this->lock_->state)));
|
||||
#endif
|
||||
return this->publish(this->get_state_topic_to_(topic_buf),
|
||||
reinterpret_cast<ProgmemStr>(lock_state_to_string(this->lock_->state)));
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <cinttypes>
|
||||
#include "mqtt_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
@@ -59,11 +60,7 @@ void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
|
||||
root[MQTT_FORCE_UPDATE] = true;
|
||||
|
||||
if (this->sensor_->get_state_class() != STATE_CLASS_NONE) {
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
root[MQTT_STATE_CLASS] = (const __FlashStringHelper *) state_class_to_string(this->sensor_->get_state_class());
|
||||
#else
|
||||
root[MQTT_STATE_CLASS] = LOG_STR_ARG(state_class_to_string(this->sensor_->get_state_class()));
|
||||
#endif
|
||||
root[MQTT_STATE_CLASS] = reinterpret_cast<ProgmemStr>(state_class_to_string(this->sensor_->get_state_class()));
|
||||
}
|
||||
|
||||
config.command_topic = false;
|
||||
|
||||
@@ -614,7 +614,6 @@ class Nextion final : public NextionBase, public PollingComponent, public uart::
|
||||
*/
|
||||
void disable_component_touch(const char *component);
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
/**
|
||||
* Add waveform data to a waveform component
|
||||
* @param component_id The integer component id.
|
||||
@@ -624,7 +623,6 @@ class Nextion final : public NextionBase, public PollingComponent, public uart::
|
||||
void add_waveform_data(uint8_t component_id, uint8_t channel_number, uint8_t value);
|
||||
|
||||
void open_waveform_channel(uint8_t component_id, uint8_t channel_number, uint8_t value);
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
/**
|
||||
* Display a picture at coordinates.
|
||||
|
||||
@@ -217,7 +217,6 @@ void Nextion::set_component_value(const char *component, int32_t value) {
|
||||
this->add_no_result_to_queue_with_printf_(".val", "%s.val=%" PRId32, component, value);
|
||||
}
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
void Nextion::add_waveform_data(uint8_t component_id, uint8_t channel_number, uint8_t value) {
|
||||
this->add_no_result_to_queue_with_printf_("add", "add %" PRIu8 ",%" PRIu8 ",%" PRIu8, component_id, channel_number,
|
||||
value);
|
||||
@@ -227,7 +226,6 @@ void Nextion::open_waveform_channel(uint8_t component_id, uint8_t channel_number
|
||||
this->add_no_result_to_queue_with_printf_("addt", "addt %" PRIu8 ",%" PRIu8 ",%" PRIu8, component_id, channel_number,
|
||||
value);
|
||||
}
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
void Nextion::set_component_coordinates(const char *component, uint16_t x, uint16_t y) {
|
||||
this->add_no_result_to_queue_with_printf_(".xcen", "%s.xcen=%" PRIu16, component, x);
|
||||
|
||||
@@ -95,6 +95,7 @@ NoiseResponderHandshake::Action NoiseResponderHandshake::action() const {
|
||||
}
|
||||
|
||||
int NoiseResponderHandshake::read_message(uint8_t *data, size_t len) {
|
||||
CpuFrequencyBoost boost;
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_input(mbuf, data, len);
|
||||
@@ -103,6 +104,7 @@ int NoiseResponderHandshake::read_message(uint8_t *data, size_t len) {
|
||||
|
||||
int NoiseResponderHandshake::write_message(uint8_t *out, size_t capacity, size_t &out_len) {
|
||||
out_len = 0;
|
||||
CpuFrequencyBoost boost;
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_output(mbuf, out, capacity);
|
||||
|
||||
@@ -6,13 +6,6 @@
|
||||
|
||||
namespace esphome::number {
|
||||
|
||||
class NumberStateTrigger final : public Trigger<float> {
|
||||
public:
|
||||
explicit NumberStateTrigger(Number *parent) {
|
||||
parent->add_on_state_callback([this](float value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class ValueRangeTrigger final : public Trigger<float>, public Component {
|
||||
public:
|
||||
explicit ValueRangeTrigger(Number *parent) : parent_(parent) {}
|
||||
|
||||
@@ -14,8 +14,7 @@ from esphome.const import (
|
||||
CONF_TYPE,
|
||||
CONF_URL,
|
||||
)
|
||||
from esphome.core import ID, Lambda
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.core import Lambda
|
||||
from esphome.types import ConfigType
|
||||
|
||||
AUTO_LOAD = ["runtime_image"]
|
||||
@@ -31,14 +30,6 @@ OnlineImage = online_image_ns.class_(
|
||||
"OnlineImage", cg.PollingComponent, runtime_image.RuntimeImage
|
||||
)
|
||||
|
||||
# Actions
|
||||
SetUrlAction = online_image_ns.class_(
|
||||
"OnlineImageSetUrlAction", automation.Action, cg.Parented.template(OnlineImage)
|
||||
)
|
||||
ReleaseImageAction = online_image_ns.class_(
|
||||
"OnlineImageReleaseAction", automation.Action, cg.Parented.template(OnlineImage)
|
||||
)
|
||||
|
||||
ONLINE_IMAGE_SCHEMA = (
|
||||
runtime_image.runtime_image_schema(OnlineImage)
|
||||
.extend(
|
||||
@@ -90,31 +81,18 @@ RELEASE_IMAGE_SCHEMA = automation.maybe_simple_id(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"online_image.set_url", SetUrlAction, SET_URL_SCHEMA, synchronous=True
|
||||
automation.register_apply_action(
|
||||
"online_image.set_url",
|
||||
SET_URL_SCHEMA,
|
||||
automation.ApplyField(CONF_URL, "set_url", cg.std_string),
|
||||
automation.ApplyField(CONF_UPDATE, "update_if", cg.bool_),
|
||||
)
|
||||
@automation.register_action(
|
||||
"online_image.release",
|
||||
ReleaseImageAction,
|
||||
RELEASE_IMAGE_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
async def online_image_action_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
if CONF_URL in config:
|
||||
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
||||
cg.add(var.set_url(template_))
|
||||
if CONF_UPDATE in config:
|
||||
template_ = await cg.templatable(config[CONF_UPDATE], args, cg.bool_)
|
||||
cg.add(var.set_update(template_))
|
||||
return var
|
||||
automation.register_apply_action(
|
||||
"online_image.release",
|
||||
RELEASE_IMAGE_SCHEMA,
|
||||
automation.ApplyCall("release()"),
|
||||
)
|
||||
|
||||
|
||||
_CALLBACK_AUTOMATIONS = (
|
||||
|
||||
@@ -43,6 +43,11 @@ class OnlineImage final : public PollingComponent,
|
||||
bool is_big_endian = false);
|
||||
|
||||
void update() override;
|
||||
/** Download now when `update` is true (the `update` flag of `online_image.set_url`). */
|
||||
void update_if(bool update) {
|
||||
if (update)
|
||||
this->update();
|
||||
}
|
||||
void loop() override;
|
||||
|
||||
/** Set the URL to download the image from. */
|
||||
@@ -104,29 +109,4 @@ class OnlineImage final : public PollingComponent,
|
||||
uint32_t start_time_{0};
|
||||
};
|
||||
|
||||
template<typename... Ts> class OnlineImageSetUrlAction final : public Action<Ts...> {
|
||||
public:
|
||||
OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(std::string, url)
|
||||
TEMPLATABLE_VALUE(bool, update)
|
||||
void play(const Ts &...x) override {
|
||||
this->parent_->set_url(this->url_.value(x...));
|
||||
if (this->update_.value(x...)) {
|
||||
this->parent_->update();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
OnlineImage *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class OnlineImageReleaseAction final : public Action<Ts...> {
|
||||
public:
|
||||
OnlineImageReleaseAction(OnlineImage *parent) : parent_(parent) {}
|
||||
void play(const Ts &...x) override { this->parent_->release(); }
|
||||
|
||||
protected:
|
||||
OnlineImage *parent_;
|
||||
};
|
||||
|
||||
} // namespace esphome::online_image
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_ON_STATE_CHANGE
|
||||
from esphome.config_helpers import (
|
||||
filter_source_files_from_defines,
|
||||
filter_source_files_from_platform,
|
||||
@@ -38,7 +39,6 @@ CONF_ON_ABORT = "on_abort"
|
||||
CONF_ON_BEGIN = "on_begin"
|
||||
CONF_ON_END = "on_end"
|
||||
CONF_ON_PROGRESS = "on_progress"
|
||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||
|
||||
|
||||
ota_ns = cg.esphome_ns.namespace("ota")
|
||||
|
||||
@@ -144,14 +144,8 @@ void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, st
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, const __FlashStringHelper *metric_name,
|
||||
EntityBase *obj, std::string &area, std::string &node,
|
||||
std::string &friendly_name) {
|
||||
#else
|
||||
void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, const char *metric_name, EntityBase *obj,
|
||||
void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, ProgmemStr metric_name, EntityBase *obj,
|
||||
std::string &area, std::string &node, std::string &friendly_name) {
|
||||
#endif
|
||||
stream->print(metric_name);
|
||||
stream->print(ESPHOME_F("{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
@@ -328,7 +322,7 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat
|
||||
// State
|
||||
print_metric_labels_(stream, ESPHOME_F("esphome_light_state"), obj, area, node, friendly_name);
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(obj->remote_values.is_on());
|
||||
stream->print(obj->get_reported_values().is_on());
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
// Brightness and RGBW
|
||||
light::LightColorValues color = obj->current_values;
|
||||
@@ -903,11 +897,7 @@ void PrometheusHandler::valve_row_(AsyncResponseStream *stream, valve::Valve *ob
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",operation=\""));
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
stream->print((const __FlashStringHelper *) valve::valve_operation_to_str(obj->current_operation));
|
||||
#else
|
||||
stream->print((const char *) valve::valve_operation_to_str(obj->current_operation));
|
||||
#endif
|
||||
stream->print(reinterpret_cast<ProgmemStr>(valve::valve_operation_to_str(obj->current_operation)));
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(ESPHOME_F("1.0"));
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
@@ -947,7 +937,11 @@ void PrometheusHandler::climate_setting_row_(AsyncResponseStream *stream, climat
|
||||
stream->print(ESPHOME_F("\",category=\""));
|
||||
stream->print(setting.c_str());
|
||||
stream->print(ESPHOME_F("\",setting_value=\""));
|
||||
#ifdef USE_ESP8266
|
||||
stream->print((const __FlashStringHelper *) setting_value);
|
||||
#else
|
||||
stream->print(LOG_STR_ARG(setting_value));
|
||||
#endif
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(ESPHOME_F("1.0"));
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
|
||||
#include "esphome/components/web_server_base/web_server_base.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/entity_includes.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#ifdef USE_CLIMATE
|
||||
#include "esphome/core/log.h"
|
||||
#endif
|
||||
@@ -68,13 +69,8 @@ class PrometheusHandler final : public AsyncWebHandler, public Component {
|
||||
void add_node_label_(AsyncResponseStream *stream, std::string &node);
|
||||
void add_friendly_name_label_(AsyncResponseStream *stream, std::string &friendly_name);
|
||||
/// Print metric name and common labels (id, area, node, friendly_name, name)
|
||||
#ifdef USE_ESP8266
|
||||
void print_metric_labels_(AsyncResponseStream *stream, const __FlashStringHelper *metric_name, EntityBase *obj,
|
||||
std::string &area, std::string &node, std::string &friendly_name);
|
||||
#else
|
||||
void print_metric_labels_(AsyncResponseStream *stream, const char *metric_name, EntityBase *obj, std::string &area,
|
||||
void print_metric_labels_(AsyncResponseStream *stream, ProgmemStr metric_name, EntityBase *obj, std::string &area,
|
||||
std::string &node, std::string &friendly_name);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
/// Return the type for prometheus
|
||||
|
||||
@@ -20,8 +20,7 @@ from esphome.const import (
|
||||
CONF_VALUE,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.core import CORE
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -38,11 +37,6 @@ remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter")
|
||||
RemoteTransmitterComponent = remote_transmitter_ns.class_(
|
||||
"RemoteTransmitterComponent", remote_base.RemoteTransmitterBase, cg.Component
|
||||
)
|
||||
DigitalWriteAction = remote_transmitter_ns.class_(
|
||||
"DigitalWriteAction",
|
||||
automation.Action,
|
||||
cg.Parented.template(RemoteTransmitterComponent),
|
||||
)
|
||||
|
||||
|
||||
# Keep in sync with the USE_LIBRETINY_VARIANT_RTL8720C / REMOTE_TRANSMITTER_BK_PWM gates in
|
||||
@@ -138,23 +132,12 @@ DIGITAL_WRITE_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"remote_transmitter.digital_write",
|
||||
DigitalWriteAction,
|
||||
DIGITAL_WRITE_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_VALUE, "digital_write", cg.bool_),
|
||||
id_key=CONF_TRANSMITTER_ID,
|
||||
)
|
||||
async def digital_write_action_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_TRANSMITTER_ID])
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, cg.bool_)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/remote_transmitter/remote_transmitter.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::remote_transmitter {
|
||||
|
||||
template<typename... Ts>
|
||||
class DigitalWriteAction final : public Action<Ts...>, public Parented<RemoteTransmitterComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, value)
|
||||
void play(const Ts &...x) override { this->parent_->digital_write(this->value_.value(x...)); }
|
||||
};
|
||||
|
||||
} // namespace esphome::remote_transmitter
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome::rotary_encoder {
|
||||
@@ -106,15 +105,4 @@ class RotaryEncoderSensor final : public sensor::Sensor, public Component {
|
||||
CallbackManager<void(int32_t)> listeners_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class RotaryEncoderSetValueAction final : public Action<Ts...> {
|
||||
public:
|
||||
RotaryEncoderSetValueAction(RotaryEncoderSensor *encoder) : encoder_(encoder) {}
|
||||
TEMPLATABLE_VALUE(int, value)
|
||||
|
||||
void play(const Ts &...x) override { this->encoder_->set_value(this->value_.value(x...)); }
|
||||
|
||||
protected:
|
||||
RotaryEncoderSensor *encoder_;
|
||||
};
|
||||
|
||||
} // namespace esphome::rotary_encoder
|
||||
|
||||
@@ -15,8 +15,6 @@ from esphome.const import (
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_STEPS,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
rotary_encoder_ns = cg.esphome_ns.namespace("rotary_encoder")
|
||||
@@ -42,9 +40,6 @@ CONF_PUBLISH_INITIAL_VALUE = "publish_initial_value"
|
||||
RotaryEncoderSensor = rotary_encoder_ns.class_(
|
||||
"RotaryEncoderSensor", sensor.Sensor, cg.Component
|
||||
)
|
||||
RotaryEncoderSetValueAction = rotary_encoder_ns.class_(
|
||||
"RotaryEncoderSetValueAction", automation.Action
|
||||
)
|
||||
|
||||
|
||||
def validate_min_max_value(config: ConfigType) -> ConfigType:
|
||||
@@ -118,25 +113,13 @@ async def to_code(config: ConfigType) -> None:
|
||||
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"sensor.rotary_encoder.set_value",
|
||||
RotaryEncoderSetValueAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(RotaryEncoderSensor),
|
||||
cv.Required(CONF_VALUE): cv.templatable(cv.int_),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_VALUE, "set_value", cg.int_),
|
||||
)
|
||||
async def sensor_template_publish_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, cg.int_)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
@@ -11,7 +11,6 @@ Script = script_ns.class_("Script", automation.Trigger.template())
|
||||
ScriptExecuteAction = script_ns.class_("ScriptExecuteAction", automation.Action)
|
||||
ScriptStopAction = script_ns.class_("ScriptStopAction", automation.Action)
|
||||
ScriptWaitAction = script_ns.class_("ScriptWaitAction", automation.Action, cg.Component)
|
||||
IsRunningCondition = script_ns.class_("IsRunningCondition", automation.Condition)
|
||||
SingleScript = script_ns.class_("SingleScript", Script)
|
||||
RestartScript = script_ns.class_("RestartScript", Script)
|
||||
QueueingScript = script_ns.class_("QueueingScript", Script, cg.Component)
|
||||
@@ -233,12 +232,8 @@ async def script_wait_action_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
automation.register_apply_condition(
|
||||
"script.is_running",
|
||||
IsRunningCondition,
|
||||
automation.maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
|
||||
"is_running()",
|
||||
)
|
||||
async def script_is_running_to_code(config, condition_id, template_arg, args):
|
||||
full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
|
||||
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
|
||||
@@ -5,14 +5,8 @@ namespace esphome::script {
|
||||
|
||||
static const char *const TAG = "script";
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
void ScriptLogger::esp_log_(int level, int line, const __FlashStringHelper *format, const char *param) {
|
||||
void ScriptLogger::esp_log_(int level, int line, ProgmemStr format, const char *param) {
|
||||
esp_log_printf_(level, TAG, line, format, param);
|
||||
}
|
||||
#else
|
||||
void ScriptLogger::esp_log_(int level, int line, const char *format, const char *param) {
|
||||
esp_log_printf_(level, TAG, line, format, param);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace esphome::script
|
||||
|
||||
@@ -8,28 +8,19 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::script {
|
||||
|
||||
class ScriptLogger {
|
||||
protected:
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
void esp_logw_(int line, const __FlashStringHelper *format, const char *param) {
|
||||
void esp_logw_(int line, ProgmemStr format, const char *param) {
|
||||
esp_log_(ESPHOME_LOG_LEVEL_WARN, line, format, param);
|
||||
}
|
||||
void esp_logd_(int line, const __FlashStringHelper *format, const char *param) {
|
||||
void esp_logd_(int line, ProgmemStr format, const char *param) {
|
||||
esp_log_(ESPHOME_LOG_LEVEL_DEBUG, line, format, param);
|
||||
}
|
||||
void esp_log_(int level, int line, const __FlashStringHelper *format, const char *param);
|
||||
#else
|
||||
void esp_logw_(int line, const char *format, const char *param) {
|
||||
esp_log_(ESPHOME_LOG_LEVEL_WARN, line, format, param);
|
||||
}
|
||||
void esp_logd_(int line, const char *format, const char *param) {
|
||||
esp_log_(ESPHOME_LOG_LEVEL_DEBUG, line, format, param);
|
||||
}
|
||||
void esp_log_(int level, int line, const char *format, const char *param);
|
||||
#endif
|
||||
void esp_log_(int level, int line, ProgmemStr format, const char *param);
|
||||
};
|
||||
|
||||
/// The abstract base class for all script types.
|
||||
@@ -276,16 +267,6 @@ template<class C, typename... Ts> class ScriptStopAction final : public Action<T
|
||||
C *script_;
|
||||
};
|
||||
|
||||
template<class C, typename... Ts> class IsRunningCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
explicit IsRunningCondition(C *parent) : parent_(parent) {}
|
||||
|
||||
bool check(const Ts &...x) override { return this->parent_->is_running(); }
|
||||
|
||||
protected:
|
||||
C *parent_;
|
||||
};
|
||||
|
||||
/** Wait for a script to finish before continuing.
|
||||
*
|
||||
* Uses queue-based storage to safely handle concurrent executions.
|
||||
|
||||
@@ -27,6 +27,9 @@ from esphome.const import (
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_HERTZ,
|
||||
UNIT_KILOVOLT_AMPS_HOURS,
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
|
||||
UNIT_KILOWATT_HOURS,
|
||||
UNIT_VOLT,
|
||||
UNIT_VOLT_AMPS,
|
||||
UNIT_VOLT_AMPS_REACTIVE,
|
||||
@@ -44,9 +47,6 @@ CONF_MAXIMUM_DEMAND_ACTIVE_POWER = "maximum_demand_active_power"
|
||||
CONF_MAXIMUM_DEMAND_REACTIVE_POWER = "maximum_demand_reactive_power"
|
||||
CONF_MAXIMUM_DEMAND_APPARENT_POWER = "maximum_demand_apparent_power"
|
||||
|
||||
UNIT_KILOWATT_HOURS = "kWh"
|
||||
UNIT_KILOVOLT_AMPS_HOURS = "kVAh"
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh"
|
||||
|
||||
selec_meter_ns = cg.esphome_ns.namespace("selec_meter")
|
||||
SelecMeter = selec_meter_ns.class_(
|
||||
|
||||
@@ -142,7 +142,12 @@ automation.register_apply_action(
|
||||
cv.Required(CONF_OPTION): cv.templatable(cv.string_strict),
|
||||
}
|
||||
),
|
||||
automation.ApplyField(CONF_OPTION, "set_option", cg.std_string),
|
||||
automation.ApplyField(
|
||||
CONF_OPTION,
|
||||
"set_option",
|
||||
cg.std_string,
|
||||
const_fn=automation.literal_with_length,
|
||||
),
|
||||
call="make_call",
|
||||
)
|
||||
|
||||
|
||||
@@ -6,20 +6,6 @@
|
||||
|
||||
namespace esphome::sensor {
|
||||
|
||||
class SensorStateTrigger final : public Trigger<float> {
|
||||
public:
|
||||
explicit SensorStateTrigger(Sensor *parent) {
|
||||
parent->add_on_state_callback([this](float value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class SensorRawStateTrigger final : public Trigger<float> {
|
||||
public:
|
||||
explicit SensorRawStateTrigger(Sensor *parent) {
|
||||
parent->add_on_raw_state_callback([this](float value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class ValueRangeTrigger final : public Trigger<float>, public Component {
|
||||
public:
|
||||
explicit ValueRangeTrigger(Sensor *parent) : parent_(parent) {}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensirion_common, sensor
|
||||
from esphome.components.const import CONF_HUMIDITY_SOURCE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BASELINE,
|
||||
@@ -31,7 +32,6 @@ SGP30Component = sgp30_ns.class_(
|
||||
CONF_ECO2_BASELINE = "eco2_baseline"
|
||||
CONF_TVOC_BASELINE = "tvoc_baseline"
|
||||
CONF_UPTIME = "uptime"
|
||||
CONF_HUMIDITY_SOURCE = "humidity_source"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensirion_common, sensor
|
||||
from esphome.components.const import CONF_NOX_INDEX, CONF_VOC_INDEX
|
||||
from esphome.components.const import (
|
||||
CONF_HUMIDITY_SOURCE,
|
||||
CONF_NOX_INDEX,
|
||||
CONF_VOC_INDEX,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ALGORITHM_TUNING,
|
||||
@@ -33,8 +37,6 @@ SGP4xComponent = sgp4x_ns.class_(
|
||||
sensirion_common.SensirionI2CDevice,
|
||||
)
|
||||
|
||||
CONF_HUMIDITY_SOURCE = "humidity_source"
|
||||
|
||||
|
||||
def validate_sensors(config: ConfigType) -> ConfigType:
|
||||
if CONF_VOC_INDEX not in config and CONF_NOX_INDEX not in config:
|
||||
|
||||
@@ -20,10 +20,6 @@ PlayAction = speaker_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
IsPlayingCondition = speaker_ns.class_("IsPlayingCondition", automation.Condition)
|
||||
IsStoppedCondition = speaker_ns.class_("IsStoppedCondition", automation.Condition)
|
||||
|
||||
|
||||
async def setup_speaker_core_(var, config):
|
||||
if audio_dac_config := config.get(CONF_AUDIO_DAC):
|
||||
aud_dac = await cg.get_variable(audio_dac_config)
|
||||
@@ -47,12 +43,6 @@ SPEAKER_AUTOMATION_SCHEMA = automation.maybe_simple_id(
|
||||
)
|
||||
|
||||
|
||||
async def speaker_action(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"speaker.play",
|
||||
PlayAction,
|
||||
@@ -91,13 +81,12 @@ for _name, _call in (
|
||||
_name, SPEAKER_AUTOMATION_SCHEMA, automation.ApplyCall(_call)
|
||||
)
|
||||
|
||||
automation.register_condition(
|
||||
"speaker.is_playing", IsPlayingCondition, SPEAKER_AUTOMATION_SCHEMA
|
||||
)(speaker_action)
|
||||
|
||||
automation.register_condition(
|
||||
"speaker.is_stopped", IsStoppedCondition, SPEAKER_AUTOMATION_SCHEMA
|
||||
)(speaker_action)
|
||||
automation.register_apply_condition(
|
||||
"speaker.is_playing", SPEAKER_AUTOMATION_SCHEMA, "is_running()"
|
||||
)
|
||||
automation.register_apply_condition(
|
||||
"speaker.is_stopped", SPEAKER_AUTOMATION_SCHEMA, "is_stopped()"
|
||||
)
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
|
||||
@@ -38,14 +38,4 @@ template<typename... Ts> class PlayAction final : public Action<Ts...>, public P
|
||||
} data_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsPlayingCondition final : public Condition<Ts...>, public Parented<Speaker> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_running(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsStoppedCondition final : public Condition<Ts...>, public Parented<Speaker> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_stopped(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::speaker
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user