Compare commits

...
Author SHA1 Message Date
J. Nick Koston 937b73c8f1 [ethernet] Use register_apply_action for ethernet.enable and ethernet.disable 2026-09-25 01:51:23 +01:00
J. Nick Koston 4a2f17bc8b [noise] Run the ESP8266 at 160 MHz during the handshake (#19233) 2026-09-25 00:24:34 +01:00
J. Nick Koston 253793c4df [web_server] Mask the value of a password text entity, not only its state (#19385) 2026-09-24 18:56:28 -04:00
Jeff Brown 3ef0acd4b2 [sgp30][sgp4x] Migrate CONF_HUMIDITY_SOURCE to const.py (#19436) 2026-09-24 17:54:58 -04:00
dependabot[bot] 73ca04579c Update pyparsing requirement from >=3.3.2 to >=3.3.3 (#19592)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-24 17:53:46 -04:00
dependabot[bot] 4ccc5fa37e Bump astral-sh/setup-uv from 10.1.0 to 10.2.0 in /.github/actions/restore-python (#19593)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-24 17:53:25 -04:00
dependabot[bot] f70880ce96 Bump astral-sh/setup-uv from 10.1.0 to 10.2.0 (#19594)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-24 17:53:09 -04:00
057bf88e4a [mcp3204] Read a conversion in one SPI transaction (#19423)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-24 14:06:07 -04:00
tomaszduda23 16757a84e6 [nrf52] add device power manager support to deep sleep component (#19494) 2026-09-24 13:57:16 -04:00
J. Nick Koston a9111a8993 [ota] Treat a client that leaves after the handshake as a clean close (#19401) 2026-09-24 18:36:45 +01:00
J. Nick Koston 211fd3ce54 [core] Dispatch controller updates through generated direct calls (#19588) 2026-09-24 17:24:10 +00:00
Janne Snabb 9df7e825d3 [core] Keep static task TCB in internal RAM (#19426) 2026-09-24 17:59:03 +01:00
Jonathan Swoboda e63428ddce [nextion] Keep add_waveform_data() available without a waveform sensor (#19590) 2026-09-24 12:31:16 -04:00
J. Nick Koston f8e8295088 [api] Decode without a vtable (#19029) 2026-09-24 16:14:25 +01:00
J. Nick Koston 9f5ac44db1 [api] Default response only string fields to a null StringRef (#19025) 2026-09-24 15:52:37 +01:00
dependabot[bot] 6f71b9102b Bump click from 8.3.3 to 8.5.0 (#18925)
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-24 15:49:07 +01:00
04da1827e3 [light] Add transition_state_publish_interval option to publish intermediate states during transitions (#11968)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-24 14:48:46 +00:00
d2fefe2299 [TAS2780] Add TAS2780 audio amplifier (#15708)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-24 15:45:51 +01:00
J. Nick Koston f1f83cda07 [api] Collapse the three protobuf decode virtuals into one (#19016) 2026-09-24 15:30:11 +01:00
Ivan KruglovandJ. Nick Koston 7dc98c683b [uart] clock_source option (#16486)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-24 15:16:40 +01:00
Bonne EgglestonandClaude 071fc08e32 [core] Silence -Wunused-function for user builds (#19571)
Co-authored-by: Claude <noreply@anthropic.com>
2026-09-24 14:15:52 +00:00
J. Nick Koston c27cbb132d [api] Make generated encode and size entry points type erased (#19015) 2026-09-24 15:13:52 +01:00
J. Nick Koston 7e308bb83d [tests] Define USE_WEBSERVER_PORT in the light unit test override (#19584) 2026-09-24 14:58:54 +01:00
J. Nick Koston 68235f34c5 [automation] Remove dead trigger classes from switch, button, binary_sensor and event (#19563) 2026-09-24 13:49:38 +00:00
J. Nick Koston b59518f6ab [remote_transmitter] Use register_apply_action for the digital_write action (#19550) 2026-09-24 13:28:29 +00:00
J. Nick Koston da148bedfe [api] Pass the encode cursor by value through the protobuf helpers (#19014) 2026-09-24 14:25:50 +01:00
Jesse Hills b0c9a7d5bd [core] Lint duplicate UNIT_/CONF_ constants against both const homes (#19422) 2026-09-24 14:21:32 +01:00
J. Nick Koston 22177680c4 [tests] Point the binary_sensor conditions at the renamed publish target (#19583) 2026-09-24 14:15:18 +01:00
J. Nick Koston e92b1e27ca [core] Let register_apply_action read the parent id from another schema key (#19548) 2026-09-24 09:12:04 -04:00
Ryan Ronnander fed5dd6a9d [light] Accept the top-level white key from Home Assistant's MQTT JSO… (#19570) 2026-09-24 13:11:40 +00:00
J. Nick Koston cbd985826c [speaker] Use register_apply_condition for speaker.is_playing and speaker.is_stopped (#19562) 2026-09-24 09:10:09 -04:00
J. Nick Koston 2ce2b7b25c [event] Remove the unused std::vector set_event_types overload (#19566) 2026-09-24 09:09:35 -04:00
J. Nick Koston acc9324fa8 [tests] Give the binary_sensor and sensor publish targets distinct ids (#19564) 2026-09-24 09:07:41 -04:00
J. Nick Koston cbb4c49011 [automation] Remove dead trigger classes from sensor, number and text_sensor (#19565) 2026-09-24 09:07:07 -04:00
J. Nick Koston afc5c29569 [event] Trigger events without building a temporary std::string (#19567) 2026-09-24 09:06:29 -04:00
J. Nick Koston 993c96875c [core][select] Pass the select.set option as a plain literal (#19568) 2026-09-24 09:05:24 -04:00
J. Nick Koston 75360a5d52 [core] Collapse ESP8266 flash string ifdefs onto the progmem helpers (#19578) 2026-09-24 13:05:21 +00:00
J. Nick Koston 8b11d3da39 [switch] Use register_apply_condition for switch.is_on and switch.is_off (#19553) 2026-09-24 08:57:58 -04:00
J. Nick Koston 5790181f44 [binary_sensor] Use register_apply_condition for binary_sensor.is_on and binary_sensor.is_off (#19554) 2026-09-24 08:57:34 -04:00
J. Nick Koston 4780936867 [fan] Use register_apply_condition for fan.is_on and fan.is_off (#19555) 2026-09-24 08:57:07 -04:00
J. Nick Koston 22959ea1cf [light] Use register_apply_condition for light.is_on and light.is_off (#19556) 2026-09-24 08:56:40 -04:00
Leonardo Rivera e9aecb5fe8 [climate_ir] Store the supported modes in one mask (#19580) 2026-09-24 12:56:11 +00:00
J. Nick Koston 6768cb818b [media_player] Use register_apply_condition for the state and muted conditions (#19557) 2026-09-24 08:55:39 -04:00
J. Nick Koston 1861d8282f [script] Use register_apply_condition for script.is_running (#19558) 2026-09-24 08:55:12 -04:00
J. Nick Koston c39ffd7099 [lock] Use register_apply_condition for lock.is_locked and lock.is_unlocked (#19559) 2026-09-24 08:54:42 -04:00
J. Nick Koston 1671bb6dd3 [time] Use register_apply_condition for time.has_time (#19560) 2026-09-24 08:54:03 -04:00
J. Nick Koston 0ceefd5f91 [mqtt] Use register_apply_condition for mqtt.connected (#19561) 2026-09-24 08:53:29 -04:00
J. Nick Koston 4477e35d66 [dfplayer] Use register_apply_action for the play, volume, eq, device and transport actions (#19549) 2026-09-24 08:52:34 -04:00
J. Nick Koston e98c4bd375 [online_image] Use register_apply_action for the set_url and release actions (#19547) 2026-09-24 08:51:30 -04:00
J. Nick Koston eae8ff7f91 [audio_dac] Use register_apply_action for the mute_off, mute_on and set_volume actions (#19546) 2026-09-24 08:51:04 -04:00
J. Nick Koston f24e1f31f2 [rotary_encoder] Use register_apply_action for the set_value action (#19545) 2026-09-24 08:50:33 -04:00
J. Nick Koston 4cbced407d [ha-addon] Announce the MCP server to Home Assistant (#19413) 2026-09-24 08:49:40 -04:00
J. Nick Koston 64127632f5 [core][logger] Keep ESP8266 log strings in flash and deprecate the RAM option (#19577) 2026-09-24 13:11:50 +01:00
189 changed files with 7612 additions and 5113 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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
+1
View File
@@ -549,6 +549,7 @@ esphome/components/sx127x/* @swoboda1337
esphome/components/sy6970/* @linkedupbits
esphome/components/syslog/* @clydebarrow
esphome/components/t6615/* @tylermenezes
esphome/components/tas2780/* @remcom
esphome/components/tc74/* @sethgirvan
esphome/components/tca9548a/* @andreashergert1984
esphome/components/tca9555/* @mobrembski
@@ -4,7 +4,6 @@
# Home Assistant Add-on: ESPHome
# Sends discovery information to Home Assistant.
# ==============================================================================
declare config
declare port
# We only disable it when disabled explicitly
@@ -19,14 +18,17 @@ port=$(bashio::addon.ingress_port)
# Wait for the ESPHome Device Builder to become available
bashio::net.wait_for "${port}" "127.0.0.1" 300
config=$(\
bashio::var.json \
host "127.0.0.1" \
port "^${port}" \
)
# Send one discovery message; the config is a JSON string built with bashio::var.json.
send_discovery() {
local service=$1
local config=$2
if bashio::discovery "${service}" "${config}" > /dev/null; then
bashio::log.info "Successfully send ${service} discovery information to Home Assistant."
else
bashio::log.error "${service} discovery message to Home Assistant failed!"
fi
}
if bashio::discovery "esphome" "${config}" > /dev/null; then
bashio::log.info "Successfully send discovery information to Home Assistant."
else
bashio::log.error "Discovery message to Home Assistant failed!"
fi
send_discovery "esphome" "$(bashio::var.json host "127.0.0.1" port "^${port}")"
# The Device Builder MCP server, consumed by Home Assistant's mcp integration.
send_discovery "mcp" "$(bashio::var.json url "http://127.0.0.1:${port}/api/mcp")"
+21 -8
View File
@@ -224,6 +224,15 @@ def flash_string(config: ConfigType, value: str) -> str:
return str(cg.safe_exp(value))
def literal_with_length(config: ConfigType, value: str) -> str:
"""Renderer for a ``(const char *, size_t)`` target: a plain literal plus its byte length.
The target compares or copies the bytes in place, so it needs the RAM literal rather than
the PROGMEM rendering on ESP8266, and the length saves a strlen.
"""
return f"{cg.safe_exp(value)}, {len(value.encode('utf-8'))}"
@dataclass(frozen=True)
class ApplyCall:
"""One statement from config keys, e.g. ``"set_range({}, {})"`` with ``((CONF_LOW, cg.float_), ...)``.
@@ -330,9 +339,9 @@ def _check_key_in_schema(
schema = schema.schema[markers[part]]
async def _apply_parent(config: ConfigType) -> str:
async def _apply_parent(config: ConfigType, id_key: str = CONF_ID) -> str:
# Global-scope qualified so a trigger arg named like the id cannot shadow it.
return f"::{await cg.get_variable(config[CONF_ID])}"
return f"::{await cg.get_variable(config[id_key])}"
def _apply_lambda_args(args: TemplateArgsType) -> TemplateArgsType:
@@ -387,12 +396,14 @@ def register_apply_action(
schema: cv.Schema,
*fields: ApplyField | ApplyCall,
call: str | None = None,
id_key: str = CONF_ID,
) -> None:
"""Register an action that only forwards config values to its parent, with no C++ class.
Generates one stateless function for ``ApplyAction<Ts...>``: parent and constants are baked
in, lambdas are called inline with the trigger args. With ``call`` every statement targets
the call object ``auto apply_call = parent->call()``, and ``apply_call.perform()`` is appended.
Generates one stateless function for ``ApplyAction<Ts...>``: the parent (read from
``id_key``) and constants are baked in, lambdas are called inline with the trigger args.
With ``call`` every statement targets the call object ``auto apply_call = parent->call()``,
and ``apply_call.perform()`` is appended.
"""
# An action stores the value, so a std::string constant stays in flash on ESP8266.
statements_spec = [
@@ -405,6 +416,7 @@ def register_apply_action(
)
for c in (f if isinstance(f, ApplyCall) else f.call() for f in fields)
]
_check_key_in_schema(name, schema, id_key)
for _, members in statements_spec:
for conf_key, _, _ in members:
_check_key_in_schema(name, schema, conf_key)
@@ -415,7 +427,7 @@ def register_apply_action(
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
parent = await _apply_parent(config)
parent = await _apply_parent(config, id_key)
lambda_args = _apply_lambda_args(args)
receiver = "apply_call." if call else f"{parent}->"
statements: list[str] = []
@@ -442,7 +454,7 @@ def register_apply_action(
def register_apply_condition(
name: str, schema: cv.Schema, check: str | ApplyCall
name: str, schema: cv.Schema, check: str | ApplyCall, id_key: str = CONF_ID
) -> None:
"""Register a condition that is one expression on its parent, with no C++ class.
@@ -454,6 +466,7 @@ def register_apply_condition(
"""
call = check if isinstance(check, ApplyCall) else ApplyCall(check)
members = call.members
_check_key_in_schema(name, schema, id_key)
for conf_key, _, _ in members:
_check_key_in_schema(name, schema, conf_key)
@@ -463,7 +476,7 @@ def register_apply_condition(
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
parent = await _apply_parent(config)
parent = await _apply_parent(config, id_key)
lambda_args = _apply_lambda_args(args)
exprs = await _render_values(
name,
-1
View File
@@ -64,7 +64,6 @@ from esphome.cpp_types import ( # noqa: F401
Application,
Component,
ComponentPtr,
Controller,
EntityBase,
EntityCategory,
ESPTime,
+2 -3
View File
@@ -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()
+7 -2
View File
@@ -600,7 +600,7 @@ bool APIConnection::send_light_state(light::LightState *light) {
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *light = static_cast<light::LightState *>(entity);
LightStateResponse resp;
auto values = light->remote_values;
auto values = light->get_reported_values();
auto color_mode = values.get_color_mode();
resp.state = values.is_on();
resp.color_mode = static_cast<enums::ColorMode>(color_mode);
@@ -2267,7 +2267,12 @@ bool APIConnection::send_message_(uint32_t payload_size, uint16_t message_type,
// Capacity reserved above, cannot fail
(void) shared_buf.resize(write_start + payload_size);
ProtoWriteBuffer buffer{&shared_buf, write_start};
encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
uint8_t *end = encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
#ifdef ESPHOME_DEBUG_API
proto_check_encode_end(end, shared_buf.data() + shared_buf.size());
#else
(void) end;
#endif
return this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type);
}
// encode_to_buffer is defined inline in api_connection.h (ESPHOME_ALWAYS_INLINE)
+6 -24
View File
@@ -346,11 +346,7 @@ class APIConnection final : public APIServerConnectionBase {
/// Returns false as soon as the TCP buffer is full. Marked nodiscard so we
/// have no silent failures: every caller must handle (or log) a refusal.
template<typename T> [[nodiscard]] bool send_message(const T &msg) {
if constexpr (T::ESTIMATED_SIZE == 0) {
return this->send_message_(0, T::MESSAGE_TYPE, &encode_msg_noop, &msg);
} else {
return this->send_message_(msg.calculate_size(), T::MESSAGE_TYPE, &proto_encode_msg<T>, &msg);
}
return this->send_message_(T::calc_size_msg(&msg), T::MESSAGE_TYPE, &T::encode_msg, &msg);
}
/// Clear the shared write buffer and reserve space for the first message.
@@ -406,16 +402,6 @@ class APIConnection final : public APIServerConnectionBase {
void process_state_subscriptions_();
#endif
// Size thunk — converts void* back to concrete type for direct calculate_size() call
template<typename T> static uint32_t calc_size(const void *msg) {
return static_cast<const T *>(msg)->calculate_size();
}
// Shared no-op encode thunk for empty messages (ESTIMATED_SIZE == 0)
static uint8_t *encode_msg_noop(const void *, ProtoWriteBuffer &buf PROTO_ENCODE_DEBUG_PARAM) {
return buf.get_pos();
}
// Non-template buffer management for send_message
bool send_message_(uint32_t payload_size, uint16_t message_type, MessageEncodeFn encode_fn, const void *msg);
@@ -434,11 +420,7 @@ class APIConnection final : public APIServerConnectionBase {
// Hot paths (state/info) go through fill_and_encode_entity_state/info instead.
// batch_message_type_ is already set by dispatch_message_ before reaching here.
template<typename T> static uint16_t encode_message_to_buffer(T &msg, APIConnection *conn, uint32_t remaining_size) {
if constexpr (T::ESTIMATED_SIZE == 0) {
return encode_to_buffer_slow(0, &encode_msg_noop, &msg, conn, remaining_size);
} else {
return encode_to_buffer_slow(msg.calculate_size(), &proto_encode_msg<T>, &msg, conn, remaining_size);
}
return encode_to_buffer_slow(T::calc_size_msg(&msg), &T::encode_msg, &msg, conn, remaining_size);
}
// Non-template core — fills state fields and encodes
@@ -450,7 +432,7 @@ class APIConnection final : public APIServerConnectionBase {
template<typename T>
static uint16_t fill_and_encode_entity_state(EntityBase *entity, T &msg, APIConnection *conn,
uint32_t remaining_size) {
return fill_and_encode_entity_state(entity, msg, &calc_size<T>, &proto_encode_msg<T>, conn, remaining_size);
return fill_and_encode_entity_state(entity, msg, &T::calc_size_msg, &T::encode_msg, conn, remaining_size);
}
// Non-template core — fills info fields, allocates buffers, and encodes
@@ -462,7 +444,7 @@ class APIConnection final : public APIServerConnectionBase {
template<typename T>
static uint16_t fill_and_encode_entity_info(EntityBase *entity, T &msg, APIConnection *conn,
uint32_t remaining_size) {
return fill_and_encode_entity_info(entity, msg, &calc_size<T>, &proto_encode_msg<T>, conn, remaining_size);
return fill_and_encode_entity_info(entity, msg, &T::calc_size_msg, &T::encode_msg, conn, remaining_size);
}
// Non-template core — fills device_class, then delegates to fill_and_encode_entity_info
@@ -476,8 +458,8 @@ class APIConnection final : public APIServerConnectionBase {
static uint16_t fill_and_encode_entity_info_with_device_class(EntityBase *entity, T &msg,
StringRef &device_class_field, APIConnection *conn,
uint32_t remaining_size) {
return fill_and_encode_entity_info_with_device_class(entity, msg, device_class_field, &calc_size<T>,
&proto_encode_msg<T>, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(entity, msg, device_class_field, &T::calc_size_msg,
&T::encode_msg, conn, remaining_size);
}
#ifdef USE_VOICE_ASSISTANT
@@ -46,7 +46,13 @@ inline uint16_t ESPHOME_ALWAYS_INLINE APIConnection::encode_to_buffer(uint32_t c
return 0;
}
ProtoWriteBuffer buffer{&shared_buf, shared_buf.size() - calculated_size};
encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
uint8_t *end = encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
#ifdef ESPHOME_DEBUG_API
// A body that writes fewer bytes than calculate_size() promised would ship stale buffer bytes
proto_check_encode_end(end, shared_buf.data() + shared_buf.size());
#else
(void) end;
#endif
return total_calculated_size;
}
@@ -5,16 +5,13 @@
#include "esphome/components/noise/noise.h"
#include "esphome/core/application.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "proto.h"
#include <cstring>
#include <cinttypes>
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
namespace esphome::api {
using noise::noise_err_to_logstr;
@@ -26,11 +23,7 @@ static_assert(MAX_HANDSHAKE_SIZE == noise::MAX_HANDSHAKE_SIZE,
"api and noise component handshake size limits must match");
static const char *const TAG = "api.noise";
#ifdef USE_ESP8266
static constexpr char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
#else
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
#endif
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
@@ -72,11 +65,7 @@ APIError APINoiseFrameHelper::init() {
state_ = State::FAILED;
return APIError::OUT_OF_MEMORY;
}
#ifdef USE_ESP8266
memcpy_P(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#else
std::memcpy(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#endif
progmem_memcpy(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
state_ = State::CLIENT_HELLO;
return APIError::OK;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -5
View File
@@ -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"));
}
+23 -24
View File
@@ -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);
+66 -59
View File
@@ -195,6 +195,12 @@ void proto_check_bounds_failed(const uint8_t *pos, size_t bytes, const uint8_t *
ESP_LOGE(TAG, "Proto encode bounds check failed in %s: need %zu bytes, %td available", caller, bytes, end - pos);
abort();
}
void proto_check_encode_end(const uint8_t *end, const uint8_t *expected) {
if (end == expected)
return;
ESP_LOGE(TAG, "Proto encode ended %td bytes off the calculated size", end - expected);
abort();
}
void ProtoWriteBuffer::debug_check_bounds_(size_t bytes, const char *caller) {
if (this->pos_ + bytes > this->buffer_->data() + this->buffer_->size()) {
ESP_LOGE(TAG, "ProtoWriteBuffer bounds check failed in %s: bytes=%zu offset=%td buf_size=%zu", caller, bytes,
@@ -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
View File
@@ -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);
-1
View File
@@ -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 -2
View File
@@ -1,5 +1,6 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
from esphome.components.const import UNIT_COUNTS
import esphome.config_validation as cv
from esphome.const import (
CONF_CLEAR,
@@ -33,8 +34,6 @@ CONF_F7 = "f7"
CONF_F8 = "f8"
CONF_NIR = "nir"
UNIT_COUNTS = "#"
AS7341_GAIN = as7341_ns.enum("AS7341Gain")
GAIN_OPTIONS = {
"X0.5": AS7341_GAIN.AS7341_GAIN_0_5X,
+11 -38
View File
@@ -2,9 +2,8 @@ from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_VOLUME
from esphome.core import ID, CoroPriority, coroutine_with_priority
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.const import CONF_VOLUME
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.types import ConfigType
CODEOWNERS = ["@kbx81"]
@@ -13,10 +12,6 @@ IS_PLATFORM_COMPONENT = True
audio_dac_ns = cg.esphome_ns.namespace("audio_dac")
AudioDac = audio_dac_ns.class_("AudioDac")
MuteOffAction = audio_dac_ns.class_("MuteOffAction", automation.Action)
MuteOnAction = audio_dac_ns.class_("MuteOnAction", automation.Action)
SetVolumeAction = audio_dac_ns.class_("SetVolumeAction", automation.Action)
MUTE_ACTION_SCHEMA = maybe_simple_id(
{
@@ -33,41 +28,19 @@ SET_VOLUME_ACTION_SCHEMA = cv.maybe_simple_value(
)
@automation.register_action(
"audio_dac.mute_off", MuteOffAction, MUTE_ACTION_SCHEMA, synchronous=True
)
@automation.register_action(
"audio_dac.mute_on", MuteOnAction, MUTE_ACTION_SCHEMA, synchronous=True
)
async def audio_dac_mute_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
for _name, _call in (
("audio_dac.mute_off", "set_mute_off()"),
("audio_dac.mute_on", "set_mute_on()"),
):
automation.register_apply_action(
_name, MUTE_ACTION_SCHEMA, automation.ApplyCall(_call)
)
@automation.register_action(
automation.register_apply_action(
"audio_dac.set_volume",
SetVolumeAction,
SET_VOLUME_ACTION_SCHEMA,
synchronous=True,
automation.ApplyField(CONF_VOLUME, "set_volume", cg.float_),
)
async def audio_dac_set_volume_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config.get(CONF_VOLUME), args, cg.float_)
cg.add(var.set_volume(template_))
return var
@coroutine_with_priority(CoroPriority.CORE)
-41
View File
@@ -1,41 +0,0 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "audio_dac.h"
namespace esphome::audio_dac {
template<typename... Ts> class MuteOffAction final : public Action<Ts...> {
public:
explicit MuteOffAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
void play(const Ts &...x) override { this->audio_dac_->set_mute_off(); }
protected:
AudioDac *audio_dac_;
};
template<typename... Ts> class MuteOnAction final : public Action<Ts...> {
public:
explicit MuteOnAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
void play(const Ts &...x) override { this->audio_dac_->set_mute_on(); }
protected:
AudioDac *audio_dac_;
};
template<typename... Ts> class SetVolumeAction final : public Action<Ts...> {
public:
explicit SetVolumeAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
TEMPLATABLE_VALUE(float, volume)
void play(const Ts &...x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); }
protected:
AudioDac *audio_dac_;
};
} // namespace esphome::audio_dac
+5 -16
View File
@@ -1,7 +1,7 @@
from logging import getLogger
from esphome import automation, core
from esphome.automation import Condition, maybe_simple_id
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server, zigbee
from esphome.components.const import CONF_ON_STATE_CHANGE
@@ -138,9 +138,6 @@ BinarySensorInvalidateAction = binary_sensor_ns.class_(
"BinarySensorInvalidateAction", automation.Action
)
# Condition
BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition)
# Filters
Filter = binary_sensor_ns.class_("Filter")
TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter)
@@ -645,20 +642,12 @@ BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
)
@automation.register_condition(
"binary_sensor.is_on", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
automation.register_apply_condition(
"binary_sensor.is_on", BINARY_SENSOR_CONDITION_SCHEMA, "state"
)
async def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, True)
@automation.register_condition(
"binary_sensor.is_off", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
automation.register_apply_condition(
"binary_sensor.is_off", BINARY_SENSOR_CONDITION_SCHEMA, "state == false"
)
async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, False)
@coroutine_with_priority(CoroPriority.CORE)
@@ -18,26 +18,6 @@ struct MultiClickTriggerEvent {
uint32_t max_length;
};
class PressTrigger final : public Trigger<> {
public:
explicit PressTrigger(BinarySensor *parent) {
parent->add_on_state_callback([this](bool state) {
if (state)
this->trigger();
});
}
};
class ReleaseTrigger final : public Trigger<> {
public:
explicit ReleaseTrigger(BinarySensor *parent) {
parent->add_on_state_callback([this](bool state) {
if (!state)
this->trigger();
});
}
};
bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length);
class ClickTrigger final : public Trigger<> {
@@ -140,31 +120,6 @@ template<size_t N> class MultiClickTrigger final : public MultiClickTriggerBase
std::array<MultiClickTriggerEvent, N> timing_storage_{};
};
class StateTrigger final : public Trigger<bool> {
public:
explicit StateTrigger(BinarySensor *parent) {
parent->add_on_state_callback([this](bool state) { this->trigger(state); });
}
};
class StateChangeTrigger final : public Trigger<optional<bool>, optional<bool> > {
public:
explicit StateChangeTrigger(BinarySensor *parent) {
parent->add_full_state_callback(
[this](optional<bool> old_state, optional<bool> state) { this->trigger(old_state, state); });
}
};
template<typename... Ts> class BinarySensorCondition final : public Condition<Ts...> {
public:
BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {}
bool check(const Ts &...x) override { return this->parent_->state == this->state_; }
protected:
BinarySensor *parent_;
bool state_;
};
template<typename... Ts> class BinarySensorInvalidateAction final : public Action<Ts...> {
public:
explicit BinarySensorInvalidateAction(BinarySensor *sensor) : sensor_(sensor) {}
-7
View File
@@ -16,11 +16,4 @@ template<typename... Ts> class PressAction final : public Action<Ts...> {
Button *button_;
};
class ButtonPressTrigger final : public Trigger<> {
public:
ButtonPressTrigger(Button *button) {
button->add_on_press_callback([this]() { this->trigger(); });
}
};
} // namespace esphome::button
+2 -8
View File
@@ -494,12 +494,6 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
)
def _literal_with_length(config: ConfigType, value: str) -> str:
# The (const char *, size_t) overload compares bytes in place, so it needs a plain
# literal rather than the default PROGMEM rendering on ESP8266, and skips a strlen.
return f"{cg.safe_exp(value)}, {len(value.encode('utf-8'))}"
automation.register_apply_action(
"climate.control",
CLIMATE_CONTROL_ACTION_SCHEMA,
@@ -517,14 +511,14 @@ automation.register_apply_action(
CONF_CUSTOM_FAN_MODE,
"set_fan_mode",
cg.std_string,
const_fn=_literal_with_length,
const_fn=automation.literal_with_length,
),
automation.ApplyField(CONF_PRESET, "set_preset", ClimatePreset),
automation.ApplyField(
CONF_CUSTOM_PRESET,
"set_preset",
cg.std_string,
const_fn=_literal_with_length,
const_fn=automation.literal_with_length,
),
automation.ApplyField(CONF_SWING_MODE, "set_swing_mode", ClimateSwingMode),
call="make_call",
+5 -13
View File
@@ -13,17 +13,7 @@ climate::ClimateTraits ClimateIR::traits() {
if (this->humidity_sensor_ != nullptr) {
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY);
}
traits.set_supported_modes({climate::CLIMATE_MODE_OFF});
if (this->supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
if (this->supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
if (this->supports_heat_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
if (this->supports_dry_)
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
if (this->supports_fan_only_)
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
traits.set_supported_modes(this->modes_);
traits.set_visual_min_temperature(this->minimum_temperature_);
traits.set_visual_max_temperature(this->maximum_temperature_);
@@ -98,8 +88,10 @@ void ClimateIR::dump_config() {
" Supports HEAT: %s\n"
" Supports COOL: %s\n"
" Supports HEAT_COOL: %s",
this->minimum_temperature_, this->maximum_temperature_, YESNO(this->supports_heat_),
YESNO(this->supports_cool_), YESNO(this->supports_heat_cool_));
this->minimum_temperature_, this->maximum_temperature_,
YESNO(this->modes_.count(climate::CLIMATE_MODE_HEAT)),
YESNO(this->modes_.count(climate::CLIMATE_MODE_COOL)),
YESNO(this->modes_.count(climate::CLIMATE_MODE_HEAT_COOL)));
}
} // namespace esphome::climate_ir
+21 -11
View File
@@ -30,8 +30,10 @@ class ClimateIR : public Component,
this->minimum_temperature_ = minimum_temperature;
this->maximum_temperature_ = maximum_temperature;
this->temperature_step_ = temperature_step;
this->supports_dry_ = supports_dry;
this->supports_fan_only_ = supports_fan_only;
if (supports_dry)
this->modes_.insert(climate::CLIMATE_MODE_DRY);
if (supports_fan_only)
this->modes_.insert(climate::CLIMATE_MODE_FAN_ONLY);
this->fan_modes_ = fan_modes;
this->swing_modes_ = swing_modes;
this->presets_ = presets;
@@ -39,9 +41,11 @@ class ClimateIR : public Component,
void setup() override;
void dump_config() override;
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
void set_supports_heat_cool(bool supports_heat_cool) { this->supports_heat_cool_ = supports_heat_cool; }
void set_supports_cool(bool supports_cool) { this->set_mode_supported_(climate::CLIMATE_MODE_COOL, supports_cool); }
void set_supports_heat(bool supports_heat) { this->set_mode_supported_(climate::CLIMATE_MODE_HEAT, supports_heat); }
void set_supports_heat_cool(bool supports_heat_cool) {
this->set_mode_supported_(climate::CLIMATE_MODE_HEAT_COOL, supports_heat_cool);
}
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; }
@@ -59,12 +63,18 @@ class ClimateIR : public Component,
// Dummy implement on_receive so implementation is optional for inheritors
bool on_receive(remote_base::RemoteReceiveData data) override { return false; };
bool supports_cool_{true};
bool supports_heat_{true};
// Default (supports_cool && supports_heat) is resolved during code generation.
bool supports_heat_cool_{true};
bool supports_dry_{false};
bool supports_fan_only_{false};
ESPHOME_ALWAYS_INLINE void set_mode_supported_(climate::ClimateMode mode, bool supported) {
if (supported) {
this->modes_.insert(mode);
} else {
this->modes_.erase(mode);
}
}
// The HEAT_COOL default (supports_cool && supports_heat) is resolved during code generation.
static constexpr climate::ClimateModeMask DEFAULT_MODES{climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_HEAT_COOL};
climate::ClimateModeMask modes_{DEFAULT_MODES};
climate::ClimateFanModeMask fan_modes_{};
climate::ClimateSwingModeMask swing_modes_{};
climate::ClimatePresetMask presets_{};
+2
View File
@@ -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 = "#"
+6 -2
View File
@@ -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")
+40 -245
View File
@@ -37,23 +37,6 @@ DEVICE = {
"TF_CARD": Device.TF_CARD,
}
NextAction = dfplayer_ns.class_("NextAction", automation.Action)
PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action)
PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action)
PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action)
PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action)
SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action)
VolumeUpAction = dfplayer_ns.class_("VolumeUpAction", automation.Action)
VolumeDownAction = dfplayer_ns.class_("VolumeDownAction", automation.Action)
SetEqAction = dfplayer_ns.class_("SetEqAction", automation.Action)
SleepAction = dfplayer_ns.class_("SleepAction", automation.Action)
ResetAction = dfplayer_ns.class_("ResetAction", automation.Action)
StartAction = dfplayer_ns.class_("StartAction", automation.Action)
PauseAction = dfplayer_ns.class_("PauseAction", automation.Action)
StopAction = dfplayer_ns.class_("StopAction", automation.Action)
RandomAction = dfplayer_ns.class_("RandomAction", automation.Action)
SetDeviceAction = dfplayer_ns.class_("SetDeviceAction", automation.Action)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -87,41 +70,30 @@ async def to_code(config):
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
@automation.register_action(
"dfplayer.play_next",
NextAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
DFPLAYER_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
)
async def dfplayer_next_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
for _name, _call in (
("dfplayer.play_next", "next()"),
("dfplayer.play_previous", "previous()"),
("dfplayer.volume_up", "volume_up()"),
("dfplayer.volume_down", "volume_down()"),
("dfplayer.sleep", "sleep()"),
("dfplayer.reset", "reset()"),
("dfplayer.start", "start()"),
("dfplayer.pause", "pause()"),
("dfplayer.stop", "stop()"),
("dfplayer.random", "random()"),
):
automation.register_apply_action(
_name, DFPLAYER_ACTION_SCHEMA, automation.ApplyCall(_call)
)
@automation.register_action(
"dfplayer.play_previous",
PreviousAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_previous_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
automation.register_apply_action(
"dfplayer.play_mp3",
PlayMp3Action,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
@@ -129,70 +101,43 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args):
},
key=CONF_FILE,
),
synchronous=True,
automation.ApplyField(CONF_FILE, "play_mp3", cg.uint16),
)
async def dfplayer_play_mp3_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16)
cg.add(var.set_file(template_))
return var
@automation.register_action(
# loop and file default to what the old action's unset templatable values evaluated to
automation.register_apply_action(
"dfplayer.play",
PlayFileAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
cv.Required(CONF_FILE): cv.templatable(cv.int_),
cv.Optional(CONF_LOOP): cv.templatable(cv.boolean),
cv.Optional(CONF_LOOP, default=False): cv.templatable(cv.boolean),
},
key=CONF_FILE,
),
synchronous=True,
automation.ApplyCall(
"play_file({}, {})", ((CONF_FILE, cg.uint16), (CONF_LOOP, cg.bool_))
),
)
async def dfplayer_play_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16)
cg.add(var.set_file(template_))
if CONF_LOOP in config:
template_ = await cg.templatable(config[CONF_LOOP], args, cg.bool_)
cg.add(var.set_loop(template_))
return var
@automation.register_action(
automation.register_apply_action(
"dfplayer.play_folder",
PlayFolderAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
cv.Required(CONF_FOLDER): cv.templatable(cv.int_),
cv.Optional(CONF_FILE): cv.templatable(cv.int_),
cv.Optional(CONF_LOOP): cv.templatable(cv.boolean),
cv.Optional(CONF_FILE, default=0): cv.templatable(cv.int_),
cv.Optional(CONF_LOOP, default=False): cv.templatable(cv.boolean),
}
),
synchronous=True,
automation.ApplyCall(
"play_folder({}, {}, {})",
((CONF_FOLDER, cg.uint16), (CONF_FILE, cg.uint16), (CONF_LOOP, cg.bool_)),
),
)
async def dfplayer_play_folder_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_FOLDER], args, cg.uint16)
cg.add(var.set_folder(template_))
if CONF_FILE in config:
template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16)
cg.add(var.set_file(template_))
if CONF_LOOP in config:
template_ = await cg.templatable(config[CONF_LOOP], args, cg.bool_)
cg.add(var.set_loop(template_))
return var
@automation.register_action(
automation.register_apply_action(
"dfplayer.set_device",
SetDeviceAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
@@ -200,19 +145,11 @@ async def dfplayer_play_folder_to_code(config, action_id, template_arg, args):
},
key=CONF_DEVICE,
),
synchronous=True,
automation.ApplyField(CONF_DEVICE, "set_device", Device),
)
async def dfplayer_set_device_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_DEVICE], args, Device)
cg.add(var.set_device(template_))
return var
@automation.register_action(
automation.register_apply_action(
"dfplayer.set_volume",
SetVolumeAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
@@ -220,51 +157,11 @@ async def dfplayer_set_device_to_code(config, action_id, template_arg, args):
},
key=CONF_VOLUME,
),
synchronous=True,
automation.ApplyField(CONF_VOLUME, "set_volume", cg.uint8),
)
async def dfplayer_set_volume_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.uint8)
cg.add(var.set_volume(template_))
return var
@automation.register_action(
"dfplayer.volume_up",
VolumeUpAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_volume_up_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"dfplayer.volume_down",
VolumeDownAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_volume_down_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
automation.register_apply_action(
"dfplayer.set_eq",
SetEqAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
@@ -272,110 +169,8 @@ async def dfplayer_volume_down_to_code(config, action_id, template_arg, args):
},
key=CONF_EQ_PRESET,
),
synchronous=True,
automation.ApplyField(CONF_EQ_PRESET, "set_eq", EqPreset),
)
async def dfplayer_set_eq_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_EQ_PRESET], args, EqPreset)
cg.add(var.set_eq(template_))
return var
@automation.register_action(
"dfplayer.sleep",
SleepAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_sleep_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"dfplayer.reset",
ResetAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_reset_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"dfplayer.start",
StartAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_start_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"dfplayer.pause",
PauseAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_pause_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"dfplayer.stop",
StopAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_stop_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"dfplayer.random",
RandomAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_random_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
def _default_enable(value: Any) -> Any:
+5 -94
View File
@@ -33,8 +33,13 @@ class DFPlayer final : public uart::UARTDevice, public Component {
void play_mp3(uint16_t file);
void play_file(uint16_t file);
void play_file_loop(uint16_t file);
void play_file(uint16_t file, bool loop) { loop ? this->play_file_loop(file) : this->play_file(file); }
void play_folder(uint16_t folder, uint16_t file);
void play_folder_loop(uint16_t folder);
// The loop command plays the whole folder, so file is ignored when loop is set.
void play_folder(uint16_t folder, uint16_t file, bool loop) {
loop ? this->play_folder_loop(folder) : this->play_folder(folder, file);
}
void volume_up();
void volume_down();
void set_device(Device device);
@@ -72,100 +77,6 @@ class DFPlayer final : public uart::UARTDevice, public Component {
CallbackManager<void()> on_finished_playback_callback_;
};
#define DFPLAYER_SIMPLE_ACTION(ACTION_CLASS, ACTION_METHOD) \
template<typename... Ts> \
class ACTION_CLASS : /* NOLINT */ \
public Action<Ts...>, \
public Parented<DFPlayer> { \
void play(const Ts &...x) override { this->parent_->ACTION_METHOD(); } \
};
DFPLAYER_SIMPLE_ACTION(NextAction, next)
DFPLAYER_SIMPLE_ACTION(PreviousAction, previous)
template<typename... Ts> class PlayMp3Action final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(uint16_t, file)
void play(const Ts &...x) override {
auto file = this->file_.value(x...);
this->parent_->play_mp3(file);
}
};
template<typename... Ts> class PlayFileAction final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(uint16_t, file)
TEMPLATABLE_VALUE(bool, loop)
void play(const Ts &...x) override {
auto file = this->file_.value(x...);
auto loop = this->loop_.value(x...);
if (loop) {
this->parent_->play_file_loop(file);
} else {
this->parent_->play_file(file);
}
}
};
template<typename... Ts> class PlayFolderAction final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(uint16_t, folder)
TEMPLATABLE_VALUE(uint16_t, file)
TEMPLATABLE_VALUE(bool, loop)
void play(const Ts &...x) override {
auto folder = this->folder_.value(x...);
auto file = this->file_.value(x...);
auto loop = this->loop_.value(x...);
if (loop) {
this->parent_->play_folder_loop(folder);
} else {
this->parent_->play_folder(folder, file);
}
}
};
template<typename... Ts> class SetDeviceAction final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(Device, device)
void play(const Ts &...x) override {
auto device = this->device_.value(x...);
this->parent_->set_device(device);
}
};
template<typename... Ts> class SetVolumeAction final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(uint8_t, volume)
void play(const Ts &...x) override {
auto volume = this->volume_.value(x...);
this->parent_->set_volume(volume);
}
};
template<typename... Ts> class SetEqAction final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(EqPreset, eq)
void play(const Ts &...x) override {
auto eq = this->eq_.value(x...);
this->parent_->set_eq(eq);
}
};
DFPLAYER_SIMPLE_ACTION(SleepAction, sleep)
DFPLAYER_SIMPLE_ACTION(ResetAction, reset)
DFPLAYER_SIMPLE_ACTION(StartAction, start)
DFPLAYER_SIMPLE_ACTION(PauseAction, pause)
DFPLAYER_SIMPLE_ACTION(StopAction, stop)
DFPLAYER_SIMPLE_ACTION(RandomAction, random)
DFPLAYER_SIMPLE_ACTION(VolumeUpAction, volume_up)
DFPLAYER_SIMPLE_ACTION(VolumeDownAction, volume_down)
template<typename... Ts> class DFPlayerIsPlayingCondition final : public Condition<Ts...>, public Parented<DFPlayer> {
public:
bool check(const Ts &...x) override { return this->parent_->is_playing(); }
@@ -3,7 +3,7 @@ import encodings
from esphome import automation
import esphome.codegen as cg
from esphome.components import esp32_ble
from esphome.components.const import CONF_MANUFACTURER
from esphome.components.const import CONF_DESCRIPTION, CONF_MANUFACTURER
from esphome.components.esp32 import request_bluetooth
from esphome.components.esp32_ble import BTLoggers, bt_uuid
import esphome.config_validation as cv
@@ -37,7 +37,6 @@ CONF_ADVERTISE = "advertise"
CONF_APPEARANCE = "appearance"
CONF_BROADCAST = "broadcast"
CONF_CHARACTERISTICS = "characteristics"
CONF_DESCRIPTION = "description"
CONF_DESCRIPTORS = "descriptors"
CONF_ENDIANNESS = "endianness"
CONF_FIRMWARE_VERSION = "firmware_version"
+29 -2
View File
@@ -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
+8 -7
View File
@@ -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)
)
-10
View File
@@ -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
+8 -1
View File
@@ -133,10 +133,17 @@ TRIGGER_EVENT_SCHEMA = cv.Schema(
)
def _event_type_literal(config: ConfigType, value: str) -> str:
"""A constant event type is a plain literal; trigger() only compares it, so no copy is needed."""
return str(cg.safe_exp(value))
automation.register_apply_action(
"event.trigger",
TRIGGER_EVENT_SCHEMA,
automation.ApplyField(CONF_EVENT_TYPE, "trigger", cg.std_string),
automation.ApplyField(
CONF_EVENT_TYPE, "trigger", cg.std_string, _event_type_literal
),
)
-16
View File
@@ -1,16 +0,0 @@
#pragma once
#include "esphome/components/event/event.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
namespace esphome::event {
class EventTrigger final : public Trigger<StringRef> {
public:
EventTrigger(Event *event) {
event->add_on_event_callback([this](StringRef event_type) { this->trigger(event_type); });
}
};
} // namespace esphome::event
+3 -11
View File
@@ -7,17 +7,17 @@ namespace esphome::event {
static const char *const TAG = "event";
void Event::trigger(const std::string &event_type) {
void Event::trigger(const char *event_type) {
// Linear search with strcmp - faster than std::set for small datasets (1-5 items typical)
const char *found = nullptr;
for (const char *type : this->types_) {
if (strcmp(type, event_type.c_str()) == 0) {
if (strcmp(type, event_type) == 0) {
found = type;
break;
}
}
if (found == nullptr) {
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type);
return;
}
this->last_event_type_ = found;
@@ -36,12 +36,4 @@ void Event::set_event_types(const FixedVector<const char *> &event_types) {
this->last_event_type_ = nullptr; // Reset when types change
}
void Event::set_event_types(const std::vector<const char *> &event_types) {
this->types_.init(event_types.size());
for (const char *type : event_types) {
this->types_.push_back(type);
}
this->last_event_type_ = nullptr; // Reset when types change
}
} // namespace esphome::event
+5 -7
View File
@@ -3,7 +3,6 @@
#include <cstring>
#include <limits>
#include <string>
#include <vector>
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
@@ -21,22 +20,21 @@ namespace esphome::event {
class Event : public EntityBase {
public:
void trigger(const std::string &event_type);
/// Trigger an event; the type is matched against the configured types by string compare.
void trigger(const char *event_type);
void trigger(const std::string &event_type) { this->trigger(event_type.c_str()); }
/// Set the event types supported by this event (from initializer list).
/// Set the event types supported by this event; called by generated code with string literals.
void set_event_types(std::initializer_list<const char *> event_types) {
this->types_ = event_types;
this->last_event_type_ = nullptr; // Reset when types change
}
/// Set the event types supported by this event (from FixedVector).
/// Copy the event types of another event, for components that wrap one.
void set_event_types(const FixedVector<const char *> &event_types);
/// Set the event types supported by this event (from vector).
void set_event_types(const std::vector<const char *> &event_types);
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
void set_event_types(std::initializer_list<std::string> event_types) = delete;
void set_event_types(const FixedVector<std::string> &event_types) = delete;
void set_event_types(const std::vector<std::string> &event_types) = delete;
/// Return the event types supported by this event.
const FixedVector<const char *> &get_event_types() const { return this->types_; }
+8 -22
View File
@@ -83,9 +83,6 @@ FanPresetSetTrigger = fan_ns.class_(
"FanPresetSetTrigger", automation.Trigger.template(cg.StringRef)
)
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template())
_FAN_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
@@ -367,27 +364,16 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args):
return var
@automation.register_condition(
"fan.is_on",
FanIsOnCondition,
automation.maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Fan),
}
),
FAN_CONDITION_SCHEMA = automation.maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Fan),
}
)
@automation.register_condition(
"fan.is_off",
FanIsOffCondition,
automation.maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Fan),
}
),
automation.register_apply_condition("fan.is_on", FAN_CONDITION_SCHEMA, "state")
automation.register_apply_condition(
"fan.is_off", FAN_CONDITION_SCHEMA, "state == false"
)
async def fan_is_on_off_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren)
@coroutine_with_priority(CoroPriority.CORE)
-17
View File
@@ -68,23 +68,6 @@ template<typename... Ts> class CycleSpeedAction final : public Action<Ts...> {
Fan *state_;
};
template<typename... Ts> class FanIsOnCondition final : public Condition<Ts...> {
public:
explicit FanIsOnCondition(Fan *state) : state_(state) {}
bool check(const Ts &...x) override { return this->state_->state; }
protected:
Fan *state_;
};
template<typename... Ts> class FanIsOffCondition final : public Condition<Ts...> {
public:
explicit FanIsOffCondition(Fan *state) : state_(state) {}
bool check(const Ts &...x) override { return !this->state_->state; }
protected:
Fan *state_;
};
class FanStateTrigger final : public Trigger<Fan *> {
public:
FanStateTrigger(Fan *state) : fan_(state) {
+1 -1
View File
@@ -22,6 +22,7 @@ from esphome.const import (
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_HERTZ,
UNIT_KILOWATT_HOURS,
UNIT_VOLT,
UNIT_WATT,
)
@@ -33,7 +34,6 @@ CONF_TOTAL_GENERATION_TIME = "total_generation_time"
CONF_TODAY_GENERATION_TIME = "today_generation_time"
CONF_PV1 = "pv1"
CONF_PV2 = "pv2"
UNIT_KILOWATT_HOURS = "kWh"
UNIT_HOURS = "h"
UNIT_KOHM = "kΩ"
UNIT_MILLIAMPERE = "mA"
+1 -1
View File
@@ -23,6 +23,7 @@ from esphome.const import (
UNIT_AMPERE,
UNIT_DEGREES,
UNIT_HERTZ,
UNIT_KILOWATT_HOURS,
UNIT_MINUTE,
UNIT_VOLT,
UNIT_VOLT_AMPS_REACTIVE,
@@ -36,7 +37,6 @@ CONF_TOTAL_GENERATION_TIME = "total_generation_time"
CONF_TODAY_GENERATION_TIME = "today_generation_time"
CONF_PV1 = "pv1"
CONF_PV2 = "pv2"
UNIT_KILOWATT_HOURS = "kWh"
UNIT_HOURS = "h"
UNIT_KOHM = "kΩ"
UNIT_MILLIAMPERE = "mA"
+1 -1
View File
@@ -24,6 +24,7 @@ from esphome.const import (
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_MILLIVOLT,
UNIT_VOLT,
UNIT_WATT,
UNIT_WATT_HOURS,
@@ -44,7 +45,6 @@ CONF_TEMPERATURE_COEFFICIENT = "temperature_coefficient"
CONF_RESET_ON_BOOT = "reset_on_boot"
UNIT_COULOMB = "C"
UNIT_JOULE = "J"
UNIT_MILLIVOLT = "mV"
ina2xx_base_ns = cg.esphome_ns.namespace("ina2xx_base")
INA2XX = ina2xx_base_ns.class_("INA2XX", cg.PollingComponent)
+15
View File
@@ -343,6 +343,7 @@ RESTORE_MODES = {
# Schema default that also matches the C++ initializer in light_state.h; codegen
# skips the setter when the config equals it.
DEFAULT_FLASH_TRANSITION_LENGTH = "0s"
CONF_TRANSITION_STATE_PUBLISH_INTERVAL = "transition_state_publish_interval"
LIGHT_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
@@ -393,6 +394,11 @@ BRIGHTNESS_ONLY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend(
cv.Optional(
CONF_FLASH_TRANSITION_LENGTH, default=DEFAULT_FLASH_TRANSITION_LENGTH
): cv.positive_time_period_milliseconds,
# Below 150ms a device cannot publish any faster and only spends CPU and traffic
cv.Optional(CONF_TRANSITION_STATE_PUBLISH_INTERVAL): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(min=cv.TimePeriod(milliseconds=150)),
),
cv.Optional(CONF_EFFECTS): validate_effects(MONOCHROMATIC_EFFECTS),
}
)
@@ -406,6 +412,11 @@ RGB_LIGHT_SCHEMA = BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(
ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(AddressableLightState),
# The addressable transformer writes the LED buffer directly, so there is no
# intermediate state to publish
cv.Optional(CONF_TRANSITION_STATE_PUBLISH_INTERVAL): cv.invalid(
"transition_state_publish_interval is not supported on addressable lights"
),
cv.Optional(CONF_EFFECTS): validate_effects(ADDRESSABLE_EFFECTS),
cv.Optional(CONF_COLOR_CORRECT): cv.All(
[cv.percentage], cv.Length(min=3, max=4)
@@ -513,6 +524,10 @@ async def setup_light_core_(light_var, config, output_var):
DEFAULT_FLASH_TRANSITION_LENGTH
):
cg.add(light_var.set_flash_transition_length(flash_transition_length))
# Setting an interval opts this light in and compiles the feature in
if (interval := config.get(CONF_TRANSITION_STATE_PUBLISH_INTERVAL)) is not None:
cg.add(light_var.set_transition_state_publish_interval(interval))
cg.add_define("USE_LIGHT_TRANSITION_PUBLISH_INTERVAL")
if (gamma_correct := config.get(CONF_GAMMA_CORRECT)) is not None:
cg.add(light_var.set_gamma_correct(gamma_correct))
fwd_arr = _get_or_create_gamma_table(gamma_correct)
-17
View File
@@ -117,23 +117,6 @@ template<bool Forward, typename... Ts> class LightEffectCycleAction final : publ
bool include_none_{false};
};
template<typename... Ts> class LightIsOnCondition final : public Condition<Ts...> {
public:
explicit LightIsOnCondition(LightState *state) : state_(state) {}
bool check(const Ts &...x) override { return this->state_->current_values.is_on(); }
protected:
LightState *state_;
};
template<typename... Ts> class LightIsOffCondition final : public Condition<Ts...> {
public:
explicit LightIsOffCondition(LightState *state) : state_(state) {}
bool check(const Ts &...x) override { return !this->state_->current_values.is_on(); }
protected:
LightState *state_;
};
class LightTurnOnTrigger final : public Trigger<>, public LightRemoteValuesListener {
public:
explicit LightTurnOnTrigger(LightState *a_light) : light_(a_light) {
+10 -21
View File
@@ -38,8 +38,6 @@ from .types import (
ColorMode,
DimRelativeAction,
LightEffectCycleAction,
LightIsOffCondition,
LightIsOnCondition,
LightState,
ToggleAction,
)
@@ -381,24 +379,15 @@ async def light_addressable_set_to_code(config, action_id, template_arg, args):
return var
@automation.register_condition(
"light.is_on",
LightIsOnCondition,
automation.maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(LightState),
}
),
LIGHT_CONDITION_SCHEMA = automation.maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(LightState),
}
)
@automation.register_condition(
"light.is_off",
LightIsOffCondition,
automation.maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(LightState),
}
),
automation.register_apply_condition(
"light.is_on", LIGHT_CONDITION_SCHEMA, "current_values.is_on()"
)
automation.register_apply_condition(
"light.is_off", LIGHT_CONDITION_SCHEMA, "current_values.is_on() == false"
)
async def light_is_on_off_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren)
@@ -32,7 +32,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
root[ESPHOME_F("effect_count")] = state.get_effect_count();
}
auto values = state.remote_values;
auto values = state.get_reported_values();
const auto color_mode = values.get_color_mode();
const auto *mode_str = get_color_mode_json_str(color_mode);
@@ -131,6 +131,13 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
call.set_white(float(root[ESPHOME_F("white_value")]) / 255.0f);
}
if (root[ESPHOME_F("white")].is<uint8_t>()) {
// White stays full because ESPHome multiplies brightness and white
call.set_color_mode_if_supported(ColorMode::WHITE);
call.set_brightness(float(root[ESPHOME_F("white")]) / 255.0f);
call.set_white(1.0f);
}
if (root[ESPHOME_F("color_temp")].is<uint16_t>()) {
call.set_color_temperature(float(root[ESPHOME_F("color_temp")]));
}
+46 -10
View File
@@ -1,6 +1,9 @@
#include "light_state.h"
#include "esp_color_correction.h"
#include "esphome/core/defines.h"
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
#include "esphome/core/application.h"
#endif
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
#include "light_output.h"
@@ -105,6 +108,13 @@ void LightState::dump_config() {
" Default Transition Length: %.1fs\n"
" Gamma Correct: %.2f",
this->default_transition_length_ / 1e3f, this->gamma_correct_);
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
// The define is build wide; only lights that set the option have an interval
if (this->transition_state_publish_interval_ != 0) {
ESP_LOGCONFIG(TAG, " Transition State Publish Interval: %" PRIu32 "ms",
this->transition_state_publish_interval_);
}
#endif
}
if (traits.supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) {
ESP_LOGCONFIG(TAG,
@@ -130,13 +140,30 @@ void LightState::loop() {
this->next_write_ = true;
}
if (this->transformer_->is_finished()) {
const bool finished = this->transformer_->is_finished();
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
if (this->transition_publish_enabled_ && !finished) {
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_transition_state_publish_ >= this->transition_state_publish_interval_) {
this->publish_state();
this->last_transition_state_publish_ = now;
}
}
#endif
if (finished) {
// if the transition has written directly to the output, current_values is outdated, so update it
this->current_values = this->transformer_->get_target_values();
this->transformer_->stop();
this->is_transformer_active_ = false;
this->transformer_ = nullptr;
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
if (this->transition_publish_enabled_) {
// Report the end state from remote_values; a flash's stop() left publishing to us
this->transition_publish_enabled_ = false;
this->publish_state();
}
#endif
if (this->target_state_reached_listeners_) {
for (auto *listener : *this->target_state_reached_listeners_) {
listener->on_light_target_state_reached();
@@ -348,10 +375,7 @@ void LightState::stop_effect_() {
void LightState::start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values) {
this->transformer_ = this->output_->create_default_transition();
this->transformer_->setup(this->current_values, target, length);
if (set_remote_values) {
this->remote_values = target;
}
this->set_transformer_remote_values_(target, set_remote_values);
// Enable loop while transition is active
this->enable_loop();
}
@@ -365,10 +389,7 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
this->transformer_ = make_unique<LightFlashTransformer>(*this);
this->transformer_->setup(end_colors, target, length);
if (set_remote_values) {
this->remote_values = target;
};
this->set_transformer_remote_values_(target, set_remote_values);
// Enable loop while flash is active
this->enable_loop();
}
@@ -376,6 +397,9 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
this->is_transformer_active_ = false;
this->transformer_ = nullptr;
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
this->transition_publish_enabled_ = false;
#endif
this->current_values = target;
if (set_remote_values) {
this->remote_values = target;
@@ -391,6 +415,18 @@ void LightState::disable_loop_if_idle_() {
}
}
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
void LightState::set_transformer_remote_values_(const LightColorValues &target, bool set_remote_values) {
this->transition_publish_enabled_ = set_remote_values && this->transition_state_publish_interval_ > 0;
if (this->transition_publish_enabled_) {
this->last_transition_state_publish_ = App.get_loop_component_start_time();
}
if (set_remote_values) {
this->remote_values = target;
}
}
#endif
void LightState::save_remote_values_() {
LightStateRTCState saved;
saved.color_mode = this->remote_values.get_color_mode();
+43
View File
@@ -100,6 +100,21 @@ class LightState : public EntityBase, public Component {
LightCall turn_on();
LightCall turn_off();
LightCall toggle();
/// The values reported to the frontend: current_values while a light publishes intermediate
/// states on an interval, otherwise remote_values. Each interval sample is a publish_state(),
/// so on_state automations run on every sample as well.
const LightColorValues &get_reported_values() const {
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
if (this->transition_publish_enabled_) {
return this->current_values;
}
#endif
return this->remote_values;
}
/// True from the call that starts a transition or flash until it reaches its target.
bool is_transitioning() const { return this->transformer_ != nullptr; }
LightCall make_call();
// ========== INTERNAL METHODS ==========
@@ -172,6 +187,13 @@ class LightState : public EntityBase, public Component {
void set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; }
float get_gamma_correct() const { return this->gamma_correct_; }
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
void set_transition_state_publish_interval(uint32_t transition_state_publish_interval) {
this->transition_state_publish_interval_ = transition_state_publish_interval;
}
uint32_t get_transition_state_publish_interval() const { return this->transition_state_publish_interval_; }
#endif
#ifdef USE_LIGHT_GAMMA_LUT
/// Set pre-computed gamma forward lookup table (256-entry uint16 PROGMEM array)
void set_gamma_table(const uint16_t *forward) { this->gamma_table_ = forward; }
@@ -291,6 +313,7 @@ class LightState : public EntityBase, public Component {
friend LightOutput;
friend LightCall;
friend class AddressableLight;
friend class LightFlashTransformer;
/// Internal method to start an effect with the given index
void start_effect_(uint32_t effect_index);
@@ -307,6 +330,18 @@ class LightState : public EntityBase, public Component {
/// Internal method to set the color values to target immediately (with no transition).
void set_immediately_(const LightColorValues &target, bool set_remote_values);
/// Point remote_values at the new transformer's target and, when this light publishes
/// intermediate states on an interval, start the interval clock.
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
void set_transformer_remote_values_(const LightColorValues &target, bool set_remote_values);
#else
void set_transformer_remote_values_(const LightColorValues &target, bool set_remote_values) {
if (set_remote_values) {
this->remote_values = target;
}
}
#endif
/// Internal method to save the current remote_values to the preferences
void save_remote_values_();
@@ -357,6 +392,10 @@ class LightState : public EntityBase, public Component {
uint32_t default_transition_length_{};
/// Transition length to use for flash transitions.
uint32_t flash_transition_length_{}; // Keep in sync with DEFAULT_FLASH_TRANSITION_LENGTH in __init__.py
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
uint32_t transition_state_publish_interval_{0};
uint32_t last_transition_state_publish_{0};
#endif
/// Gamma correction factor for the light.
float gamma_correct_{};
#ifdef USE_LIGHT_GAMMA_LUT
@@ -367,6 +406,10 @@ class LightState : public EntityBase, public Component {
bool next_write_{true};
// for effects, true if a transformer (transition) is active.
bool is_transformer_active_{false};
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
/// True while the active transformer publishes current_values on an interval from loop().
bool transition_publish_enabled_{false};
#endif
/// Restore mode of the light.
LightRestoreMode restore_mode_;
};
+5
View File
@@ -105,6 +105,11 @@ class LightFlashTransformer : public LightTransformer {
}
this->state_.current_values = this->get_start_values();
this->state_.remote_values = this->get_start_values();
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
// The light reports the end state itself once the transformer finishes
if (this->state_.transition_publish_enabled_)
return;
#endif
this->state_.publish_state();
}
-2
View File
@@ -44,8 +44,6 @@ ToggleAction = light_ns.class_("ToggleAction", automation.Action)
LightEffectCycleAction = light_ns.class_("LightEffectCycleAction", automation.Action)
DimRelativeAction = light_ns.class_("DimRelativeAction", automation.Action)
AddressableSet = light_ns.class_("AddressableSet", automation.Action)
LightIsOnCondition = light_ns.class_("LightIsOnCondition", automation.Condition)
LightIsOffCondition = light_ns.class_("LightIsOffCondition", automation.Condition)
# Triggers
LightTurnOnTrigger = light_ns.class_(
+9 -22
View File
@@ -1,5 +1,5 @@
from esphome import automation
from esphome.automation import Condition, maybe_simple_id
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
@@ -34,7 +34,6 @@ LockAction = lock_ns.class_("LockAction", automation.Action)
OpenAction = lock_ns.class_("OpenAction", automation.Action)
LockPublishAction = lock_ns.class_("LockPublishAction", automation.Action)
LockCondition = lock_ns.class_("LockCondition", Condition)
LockStateForwarder = lock_ns.class_("LockStateForwarder")
LockState = lock_ns.enum("LockState")
@@ -154,26 +153,14 @@ async def lock_action_to_code(
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_condition("lock.is_locked", LockCondition, LOCK_ACTION_SCHEMA)
async def lock_is_on_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, True)
@automation.register_condition("lock.is_unlocked", LockCondition, LOCK_ACTION_SCHEMA)
async def lock_is_off_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, False)
automation.register_apply_condition(
"lock.is_locked", LOCK_ACTION_SCHEMA, f"state == {LockState.LOCK_STATE_LOCKED}"
)
automation.register_apply_condition(
"lock.is_unlocked",
LOCK_ACTION_SCHEMA,
f"state == {LockState.LOCK_STATE_UNLOCKED}",
)
@coroutine_with_priority(CoroPriority.CORE)
-13
View File
@@ -36,19 +36,6 @@ template<typename... Ts> class OpenAction final : public Action<Ts...> {
Lock *lock_;
};
template<typename... Ts> class LockCondition final : public Condition<Ts...> {
public:
LockCondition(Lock *parent, bool state) : parent_(parent), state_(state) {}
bool check(const Ts &...x) override {
auto check_state = this->state_ ? LockState::LOCK_STATE_LOCKED : LockState::LOCK_STATE_UNLOCKED;
return this->parent_->state == check_state;
}
protected:
Lock *parent_;
bool state_;
};
/// Callback forwarder that triggers an Automation<> only when a specific lock state is entered.
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
template<LockState State> struct LockStateForwarder {
-3
View File
@@ -32,9 +32,6 @@ enum LockState : uint8_t {
};
const LogString *lock_state_to_string(LockState state);
/// Maximum length of lock state string (including null terminator): "UNLOCKING" = 10
static constexpr size_t LOCK_STATE_STR_SIZE = 10;
class LockTraits {
public:
LockTraits() = default;
+17 -4
View File
@@ -1,3 +1,4 @@
import logging
import re
from typing import Any
@@ -63,6 +64,8 @@ from esphome.core import CORE, ID, CoroPriority, Lambda, coroutine_with_priority
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"]
logger_ns = cg.esphome_ns.namespace("logger")
LOG_LEVELS = {
@@ -105,6 +108,7 @@ DEFAULT = "DEFAULT"
CONF_INITIAL_LEVEL = "initial_level"
CONF_LOGGER_ID = "logger_id"
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = "esp8266_store_log_strings_in_flash"
CONF_RUNTIME_TAG_LEVELS = "runtime_tag_levels"
CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size"
CONF_WAIT_FOR_CDC = "wait_for_cdc"
@@ -219,6 +223,18 @@ def validate_initial_no_higher_than_global(config: ConfigType) -> ConfigType:
return config
def warn_ram_log_strings(config: ConfigType) -> ConfigType:
# Remove before 2027.4.0
if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH) is False:
_LOGGER.warning(
"'%s: false' is ignored and will be rejected in 2027.4.0. Log format strings "
"always stay in flash now; copying them into RAM gave no speed gain and the "
"lost RAM caused crashes. Remove the option",
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH,
)
return config
def validate_wait_for_cdc(config: ConfigType) -> ConfigType:
if config.get(CONF_WAIT_FOR_CDC) and config.get(CONF_HARDWARE_UART) != USB_CDC:
raise cv.Invalid("wait_for_cdc requires hardware_uart: USB_CDC")
@@ -232,7 +248,6 @@ LoggerMessageTrigger = logger_ns.class_(
)
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = "esp8266_store_log_strings_in_flash"
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -332,6 +347,7 @@ CONFIG_SCHEMA = cv.All(
validate_local_no_higher_than_global,
validate_initial_no_higher_than_global,
validate_wait_for_cdc,
warn_ram_log_strings,
)
@@ -450,9 +466,6 @@ async def _late_logger_init(config: ConfigType) -> None:
cg.add_build_flag("-DCORE_DEBUG_LEVEL=5")
if CORE.is_esp32 and is_at_least_very_verbose:
cg.add_build_flag("-DENABLE_I2C_DEBUG_BUFFER")
if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH):
cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH")
if CORE.is_esp32:
if config[CONF_HARDWARE_UART] == USB_CDC:
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
+5 -5
View File
@@ -105,9 +105,9 @@ struct LogBuffer {
this->format_vsnprintf_(format, args);
this->finalize_();
}
#ifdef USE_STORE_LOG_STR_IN_FLASH
void HOT format_body_P(PGM_P format, va_list args) {
this->format_vsnprintf_P_(format, args);
#ifdef USE_ESP8266
void HOT format_body_p(PGM_P format, va_list args) {
this->format_vsnprintf_p_(format, args);
this->finalize_();
}
#endif
@@ -158,8 +158,8 @@ struct LogBuffer {
return;
this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args));
}
#ifdef USE_STORE_LOG_STR_IN_FLASH
void format_vsnprintf_P_(PGM_P format, va_list args) {
#ifdef USE_ESP8266
void format_vsnprintf_p_(PGM_P format, va_list args) {
if (this->full_())
return;
this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args));
+3 -4
View File
@@ -127,9 +127,8 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
}
#endif // USE_ESP32 || USE_HOST || USE_LIBRETINY || USE_ZEPHYR
#ifdef USE_STORE_LOG_STR_IN_FLASH
// Implementation for ESP8266 with flash string support.
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
#ifdef USE_ESP8266
// ESP8266 keeps log format strings in flash.
//
// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
// Uses vsnprintf_P to read the format string directly from flash without copying to RAM.
@@ -141,7 +140,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr);
}
#endif // USE_STORE_LOG_STR_IN_FLASH
#endif // USE_ESP8266
inline uint8_t Logger::level_for(const char *tag) {
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
+8 -8
View File
@@ -200,9 +200,9 @@ class Logger final : public Component {
float get_setup_priority() const override { return setup_priority::BUS + 500.0f; }
void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args); // NOLINT
#ifdef USE_STORE_LOG_STR_IN_FLASH
void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
va_list args); // NOLINT
#ifdef USE_ESP8266
// NOLINTNEXTLINE(readability-identifier-naming)
void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, va_list args);
#endif
protected:
@@ -244,14 +244,14 @@ class Logger final : public Component {
buf.format_body(format, args);
}
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
// Format a log message with flash string format and write it to a buffer with header, footer, and null terminator
// ESP8266-only (single-task), thread_name is always nullptr
inline void HOT format_log_to_buffer_with_terminator_P_(uint8_t level, const char *tag, int line,
inline void HOT format_log_to_buffer_with_terminator_p_(uint8_t level, const char *tag, int line,
const __FlashStringHelper *format, va_list args,
LogBuffer &buf) {
buf.write_header(level, tag, line, nullptr);
buf.format_body_P(reinterpret_cast<PGM_P>(format), args);
buf.format_body_p(reinterpret_cast<PGM_P>(format), args);
}
#endif
@@ -283,9 +283,9 @@ class Logger final : public Component {
FormatType format, va_list args, const char *thread_name) {
RecursionGuard guard(recursion_guard);
LogBuffer buf{this->tx_buffer_, ESPHOME_LOGGER_TX_BUFFER_SIZE};
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
if constexpr (std::is_same_v<FormatType, const __FlashStringHelper *>) {
this->format_log_to_buffer_with_terminator_P_(level, tag, line, format, args, buf);
this->format_log_to_buffer_with_terminator_p_(level, tag, line, format, args, buf);
} else
#endif
{
+1 -1
View File
@@ -1,5 +1,6 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
from esphome.components.const import UNIT_COUNTS
import esphome.config_validation as cv
from esphome.const import (
CONF_AMBIENT_LIGHT,
@@ -28,7 +29,6 @@ CONF_UV_INDEX = "uv_index"
CONF_UV = "uv"
CONF_WINDOW_CORRECTION_FACTOR = "window_correction_factor"
UNIT_COUNTS = "#"
UNIT_UVI = "UVI"
LTR390GAIN = ltr390_ns.enum("LTR390GAIN")
+1 -1
View File
@@ -3,6 +3,7 @@ from typing import Any
from esphome import automation
import esphome.codegen as cg
from esphome.components import i2c, sensor
from esphome.components.const import UNIT_COUNTS
import esphome.config_validation as cv
from esphome.const import (
CONF_ACTUAL_GAIN,
@@ -42,7 +43,6 @@ CONF_PS_LOW_THRESHOLD = "ps_low_threshold"
ICON_BRIGHTNESS_7 = "mdi:brightness-7"
ICON_GAIN = "mdi:multiplication"
ICON_PROXIMITY = "mdi:hand-wave-outline"
UNIT_COUNTS = "#"
ltr501_ns = cg.esphome_ns.namespace("ltr501")
+1 -1
View File
@@ -3,6 +3,7 @@ from typing import Any
from esphome import automation
import esphome.codegen as cg
from esphome.components import i2c, sensor
from esphome.components.const import UNIT_COUNTS
import esphome.config_validation as cv
from esphome.const import (
CONF_ACTUAL_GAIN,
@@ -41,7 +42,6 @@ CONF_PS_LOW_THRESHOLD = "ps_low_threshold"
ICON_BRIGHTNESS_7 = "mdi:brightness-7"
ICON_GAIN = "mdi:multiplication"
ICON_PROXIMITY = "mdi:hand-wave-outline"
UNIT_COUNTS = "#"
ltr_als_ps_ns = cg.esphome_ns.namespace("ltr_als_ps")
-2
View File
@@ -680,7 +680,6 @@ CONF_BODY = "body"
CONF_BUTTONS = "buttons"
CONF_CHANGE_RATE = "change_rate"
CONF_CLOSE_BUTTON = "close_button"
CONF_COLOR_DEPTH = "color_depth"
CONF_COLOR_END = "color_end"
CONF_COLOR_START = "color_start"
CONF_CONTAINER = "container"
@@ -767,7 +766,6 @@ CONF_RESUME_ON_INPUT = "resume_on_input"
CONF_RIGHT_BUTTON = "right_button"
CONF_ROLLOVER = "rollover"
CONF_ROOT_BACK_BTN = "root_back_btn"
CONF_ROWS = "rows"
CONF_SCALE = "scale"
CONF_SCALE_LINES = "scale_lines"
CONF_SCROLLBAR_MODE = "scrollbar_mode"
+8 -9
View File
@@ -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
}
+2 -6
View File
@@ -5,6 +5,7 @@
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/progmem.h"
// On ESP8266 and RP2040 the scheduler-backed MDNS.update() polling window is armed by
// IP state listener events on whichever network interface is configured.
#if (defined(USE_ESP8266) || defined(USE_RP2)) && \
@@ -39,12 +40,7 @@ struct MDNSString;
// Macro to cast string literals to MDNSString* (works on all platforms)
#define MDNS_STR(name) (reinterpret_cast<const esphome::mdns::MDNSString *>(name))
#ifdef USE_ESP8266
#include <pgmspace.h>
#define MDNS_STR_ARG(s) ((PGM_P) (s))
#else
#define MDNS_STR_ARG(s) (reinterpret_cast<const char *>(s))
#endif
#define MDNS_STR_ARG(s) (reinterpret_cast<ESPHOME_PGM_P>(s))
// Service count is calculated at compile time by Python codegen
// MDNS_SERVICE_COUNT will always be defined
+18 -30
View File
@@ -190,16 +190,15 @@ _STATE_TRIGGERS = (
(CONF_ON_TURN_OFF, MediaPlayerState.MEDIA_PLAYER_STATE_OFF),
)
# State conditions that all share the same schema and codegen handler
_STATE_CONDITIONS = [
"idle",
"paused",
"playing",
"announcing",
"on",
"off",
"muted",
]
# State conditions: (config_key suffix, checked state)
_STATE_CONDITIONS = (
("idle", MediaPlayerState.MEDIA_PLAYER_STATE_IDLE),
("paused", MediaPlayerState.MEDIA_PLAYER_STATE_PAUSED),
("playing", MediaPlayerState.MEDIA_PLAYER_STATE_PLAYING),
("announcing", MediaPlayerState.MEDIA_PLAYER_STATE_ANNOUNCING),
("on", MediaPlayerState.MEDIA_PLAYER_STATE_ON),
("off", MediaPlayerState.MEDIA_PLAYER_STATE_OFF),
)
MediaPlayerCommand = media_player_ns.enum("MediaPlayerCommand", is_class=True)
@@ -360,27 +359,16 @@ for _action_name in _COMMAND_ACTIONS:
)
def _snake_to_camel(name):
return "".join(word.capitalize() for word in name.split("_"))
for _condition_name, _state in _STATE_CONDITIONS:
automation.register_apply_condition(
f"media_player.is_{_condition_name}",
MEDIA_PLAYER_CONDITION_SCHEMA,
f"state == {_state}",
)
def _register_state_conditions():
async def handler(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
for condition_name in _STATE_CONDITIONS:
class_name = f"Is{_snake_to_camel(condition_name)}Condition"
condition_class = media_player_ns.class_(class_name, automation.Condition)
automation.register_condition(
f"media_player.is_{condition_name}",
condition_class,
MEDIA_PLAYER_CONDITION_SCHEMA,
)(handler)
_register_state_conditions()
automation.register_apply_condition(
"media_player.is_muted", MEDIA_PLAYER_CONDITION_SCHEMA, "is_muted()"
)
automation.register_apply_action(
@@ -27,41 +27,4 @@ static_assert(std::is_trivially_copyable_v<StateAnyForwarder>);
static_assert(sizeof(StateEnterForwarder<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<StateEnterForwarder<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>>);
template<typename... Ts> class IsIdleCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
public:
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_IDLE; }
};
template<typename... Ts> class IsPlayingCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
public:
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING; }
};
template<typename... Ts> class IsPausedCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
public:
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PAUSED; }
};
template<typename... Ts> class IsAnnouncingCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
public:
bool check(const Ts &...x) override {
return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
}
};
template<typename... Ts> class IsOnCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
public:
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ON; }
};
template<typename... Ts> class IsOffCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
public:
bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_OFF; }
};
template<typename... Ts> class IsMutedCondition final : public Condition<Ts...>, public Parented<MediaPlayer> {
public:
bool check(const Ts &...x) override { return this->parent_->is_muted(); }
};
} // namespace esphome::media_player
+1 -1
View File
@@ -102,7 +102,7 @@ void MitsubishiClimate::transmit_state() {
default:
remote_state[6] = MITSUBISHI_MODE_COOL;
remote_state[8] = MITSUBISHI_MODE_A_COOL;
if (this->supports_heat_) {
if (this->modes_.count(climate::CLIMATE_MODE_HEAT)) {
remote_state[6] = MITSUBISHI_MODE_HEAT;
remote_state[8] = MITSUBISHI_MODE_A_HEAT;
}
+4 -4
View File
@@ -51,10 +51,10 @@ class MitsubishiClimate final : public climate_ir::ClimateIR {
{climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_BOOST,
climate::CLIMATE_PRESET_SLEEP}) {}
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
void set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; }
void set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; }
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
void set_supports_dry(bool supports_dry) { this->set_mode_supported_(climate::CLIMATE_MODE_DRY, supports_dry); }
void set_supports_fan_only(bool supports_fan_only) {
this->set_mode_supported_(climate::CLIMATE_MODE_FAN_ONLY, supports_fan_only);
}
void set_fan_mode(SetFanMode fan_mode) {
this->fan_mode_ = fan_mode;
+2 -7
View File
@@ -1,5 +1,4 @@
from esphome import automation
from esphome.automation import Condition
import esphome.codegen as cg
from esphome.components import logger, socket
from esphome.components.esp32 import (
@@ -135,7 +134,6 @@ MQTTDisconnectTrigger = mqtt_ns.class_(
"MQTTDisconnectTrigger", automation.Trigger.template(MQTTClientDisconnectReason)
)
MQTTComponent = mqtt_ns.class_("MQTTComponent", cg.Component)
MQTTConnectedCondition = mqtt_ns.class_("MQTTConnectedCondition", Condition)
MQTTAlarmControlPanelComponent = mqtt_ns.class_(
"MQTTAlarmControlPanelComponent", MQTTComponent
@@ -599,18 +597,15 @@ async def register_mqtt_component(var, config):
)
@automation.register_condition(
automation.register_apply_condition(
"mqtt.connected",
MQTTConnectedCondition,
cv.Schema(
{
cv.GenerateID(): cv.use_id(MQTTClientComponent),
}
),
"is_connected()",
)
async def mqtt_connected_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren)
@automation.register_action(
-9
View File
@@ -417,15 +417,6 @@ template<typename... Ts> class MQTTPublishJsonAction final : public Action<Ts...
MQTTClientComponent *parent_;
};
template<typename... Ts> class MQTTConnectedCondition final : public Condition<Ts...> {
public:
MQTTConnectedCondition(MQTTClientComponent *parent) : parent_(parent) {}
bool check(const Ts &...x) override { return this->parent_->is_connected(); }
protected:
MQTTClientComponent *parent_;
};
template<typename... Ts> class MQTTEnableAction final : public Action<Ts...> {
public:
MQTTEnableAction(MQTTClientComponent *parent) : parent_(parent) {}
+1 -5
View File
@@ -312,11 +312,7 @@ bool MQTTComponent::send_discovery_() {
// Buffer sized for format string expansion: ~4 bytes net growth from format specifier to 8 hex digits, plus
// safety margin
char version_buf[sizeof(ver_fmt) + 8];
#ifdef USE_ESP8266
snprintf_P(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
#else
snprintf(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
#endif
ESPHOME_snprintf_P(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
device_info[MQTT_DEVICE_SW_VERSION] = version_buf;
device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
#if defined(USE_ESP8266) || defined(USE_ESP32)
+2 -8
View File
@@ -50,14 +50,8 @@ bool MQTTLockComponent::send_initial_state() { return this->publish_state(); }
bool MQTTLockComponent::publish_state() {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
#ifdef USE_STORE_LOG_STR_IN_FLASH
char buf[LOCK_STATE_STR_SIZE];
strncpy_P(buf, (PGM_P) lock_state_to_string(this->lock_->state), sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
return this->publish(this->get_state_topic_to_(topic_buf), buf);
#else
return this->publish(this->get_state_topic_to_(topic_buf), LOG_STR_ARG(lock_state_to_string(this->lock_->state)));
#endif
return this->publish(this->get_state_topic_to_(topic_buf),
reinterpret_cast<ProgmemStr>(lock_state_to_string(this->lock_->state)));
}
} // namespace esphome::mqtt
+2 -5
View File
@@ -1,6 +1,7 @@
#include <cinttypes>
#include "mqtt_sensor.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include "mqtt_const.h"
@@ -59,11 +60,7 @@ void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
root[MQTT_FORCE_UPDATE] = true;
if (this->sensor_->get_state_class() != STATE_CLASS_NONE) {
#ifdef USE_STORE_LOG_STR_IN_FLASH
root[MQTT_STATE_CLASS] = (const __FlashStringHelper *) state_class_to_string(this->sensor_->get_state_class());
#else
root[MQTT_STATE_CLASS] = LOG_STR_ARG(state_class_to_string(this->sensor_->get_state_class()));
#endif
root[MQTT_STATE_CLASS] = reinterpret_cast<ProgmemStr>(state_class_to_string(this->sensor_->get_state_class()));
}
config.command_topic = false;
-2
View File
@@ -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);
-7
View File
@@ -6,13 +6,6 @@
namespace esphome::number {
class NumberStateTrigger final : public Trigger<float> {
public:
explicit NumberStateTrigger(Number *parent) {
parent->add_on_state_callback([this](float value) { this->trigger(value); });
}
};
class ValueRangeTrigger final : public Trigger<float>, public Component {
public:
explicit ValueRangeTrigger(Number *parent) : parent_(parent) {}
+11 -33
View File
@@ -14,8 +14,7 @@ from esphome.const import (
CONF_TYPE,
CONF_URL,
)
from esphome.core import ID, Lambda
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.core import Lambda
from esphome.types import ConfigType
AUTO_LOAD = ["runtime_image"]
@@ -31,14 +30,6 @@ OnlineImage = online_image_ns.class_(
"OnlineImage", cg.PollingComponent, runtime_image.RuntimeImage
)
# Actions
SetUrlAction = online_image_ns.class_(
"OnlineImageSetUrlAction", automation.Action, cg.Parented.template(OnlineImage)
)
ReleaseImageAction = online_image_ns.class_(
"OnlineImageReleaseAction", automation.Action, cg.Parented.template(OnlineImage)
)
ONLINE_IMAGE_SCHEMA = (
runtime_image.runtime_image_schema(OnlineImage)
.extend(
@@ -90,31 +81,18 @@ RELEASE_IMAGE_SCHEMA = automation.maybe_simple_id(
)
@automation.register_action(
"online_image.set_url", SetUrlAction, SET_URL_SCHEMA, synchronous=True
automation.register_apply_action(
"online_image.set_url",
SET_URL_SCHEMA,
automation.ApplyField(CONF_URL, "set_url", cg.std_string),
automation.ApplyField(CONF_UPDATE, "update_if", cg.bool_),
)
@automation.register_action(
"online_image.release",
ReleaseImageAction,
RELEASE_IMAGE_SCHEMA,
synchronous=True,
)
async def online_image_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_URL in config:
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
cg.add(var.set_url(template_))
if CONF_UPDATE in config:
template_ = await cg.templatable(config[CONF_UPDATE], args, cg.bool_)
cg.add(var.set_update(template_))
return var
automation.register_apply_action(
"online_image.release",
RELEASE_IMAGE_SCHEMA,
automation.ApplyCall("release()"),
)
_CALLBACK_AUTOMATIONS = (
+5 -25
View File
@@ -43,6 +43,11 @@ class OnlineImage final : public PollingComponent,
bool is_big_endian = false);
void update() override;
/** Download now when `update` is true (the `update` flag of `online_image.set_url`). */
void update_if(bool update) {
if (update)
this->update();
}
void loop() override;
/** Set the URL to download the image from. */
@@ -104,29 +109,4 @@ class OnlineImage final : public PollingComponent,
uint32_t start_time_{0};
};
template<typename... Ts> class OnlineImageSetUrlAction final : public Action<Ts...> {
public:
OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(std::string, url)
TEMPLATABLE_VALUE(bool, update)
void play(const Ts &...x) override {
this->parent_->set_url(this->url_.value(x...));
if (this->update_.value(x...)) {
this->parent_->update();
}
}
protected:
OnlineImage *parent_;
};
template<typename... Ts> class OnlineImageReleaseAction final : public Action<Ts...> {
public:
OnlineImageReleaseAction(OnlineImage *parent) : parent_(parent) {}
void play(const Ts &...x) override { this->parent_->release(); }
protected:
OnlineImage *parent_;
};
} // namespace esphome::online_image
+1 -1
View File
@@ -1,5 +1,6 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components.const import CONF_ON_STATE_CHANGE
from esphome.config_helpers import (
filter_source_files_from_defines,
filter_source_files_from_platform,
@@ -38,7 +39,6 @@ CONF_ON_ABORT = "on_abort"
CONF_ON_BEGIN = "on_begin"
CONF_ON_END = "on_end"
CONF_ON_PROGRESS = "on_progress"
CONF_ON_STATE_CHANGE = "on_state_change"
ota_ns = cg.esphome_ns.namespace("ota")
@@ -144,14 +144,8 @@ void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, st
}
}
#ifdef USE_ESP8266
void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, const __FlashStringHelper *metric_name,
EntityBase *obj, std::string &area, std::string &node,
std::string &friendly_name) {
#else
void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, const char *metric_name, EntityBase *obj,
void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, ProgmemStr metric_name, EntityBase *obj,
std::string &area, std::string &node, std::string &friendly_name) {
#endif
stream->print(metric_name);
stream->print(ESPHOME_F("{id=\""));
stream->print(relabel_id_(obj).c_str());
@@ -328,7 +322,7 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat
// State
print_metric_labels_(stream, ESPHOME_F("esphome_light_state"), obj, area, node, friendly_name);
stream->print(ESPHOME_F("\"} "));
stream->print(obj->remote_values.is_on());
stream->print(obj->get_reported_values().is_on());
stream->print(ESPHOME_F("\n"));
// Brightness and RGBW
light::LightColorValues color = obj->current_values;
@@ -903,11 +897,7 @@ void PrometheusHandler::valve_row_(AsyncResponseStream *stream, valve::Valve *ob
stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str());
stream->print(ESPHOME_F("\",operation=\""));
#ifdef USE_STORE_LOG_STR_IN_FLASH
stream->print((const __FlashStringHelper *) valve::valve_operation_to_str(obj->current_operation));
#else
stream->print((const char *) valve::valve_operation_to_str(obj->current_operation));
#endif
stream->print(reinterpret_cast<ProgmemStr>(valve::valve_operation_to_str(obj->current_operation)));
stream->print(ESPHOME_F("\"} "));
stream->print(ESPHOME_F("1.0"));
stream->print(ESPHOME_F("\n"));
@@ -947,7 +937,11 @@ void PrometheusHandler::climate_setting_row_(AsyncResponseStream *stream, climat
stream->print(ESPHOME_F("\",category=\""));
stream->print(setting.c_str());
stream->print(ESPHOME_F("\",setting_value=\""));
#ifdef USE_ESP8266
stream->print((const __FlashStringHelper *) setting_value);
#else
stream->print(LOG_STR_ARG(setting_value));
#endif
stream->print(ESPHOME_F("\"} "));
stream->print(ESPHOME_F("1.0"));
stream->print(ESPHOME_F("\n"));
@@ -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
+2 -19
View File
@@ -15,8 +15,6 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_STEPS,
)
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
rotary_encoder_ns = cg.esphome_ns.namespace("rotary_encoder")
@@ -42,9 +40,6 @@ CONF_PUBLISH_INITIAL_VALUE = "publish_initial_value"
RotaryEncoderSensor = rotary_encoder_ns.class_(
"RotaryEncoderSensor", sensor.Sensor, cg.Component
)
RotaryEncoderSetValueAction = rotary_encoder_ns.class_(
"RotaryEncoderSetValueAction", automation.Action
)
def validate_min_max_value(config: ConfigType) -> ConfigType:
@@ -118,25 +113,13 @@ async def to_code(config: ConfigType) -> None:
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
@automation.register_action(
automation.register_apply_action(
"sensor.rotary_encoder.set_value",
RotaryEncoderSetValueAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(RotaryEncoderSensor),
cv.Required(CONF_VALUE): cv.templatable(cv.int_),
}
),
synchronous=True,
automation.ApplyField(CONF_VALUE, "set_value", cg.int_),
)
async def sensor_template_publish_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_VALUE], args, cg.int_)
cg.add(var.set_value(template_))
return var
+2 -7
View File
@@ -11,7 +11,6 @@ Script = script_ns.class_("Script", automation.Trigger.template())
ScriptExecuteAction = script_ns.class_("ScriptExecuteAction", automation.Action)
ScriptStopAction = script_ns.class_("ScriptStopAction", automation.Action)
ScriptWaitAction = script_ns.class_("ScriptWaitAction", automation.Action, cg.Component)
IsRunningCondition = script_ns.class_("IsRunningCondition", automation.Condition)
SingleScript = script_ns.class_("SingleScript", Script)
RestartScript = script_ns.class_("RestartScript", Script)
QueueingScript = script_ns.class_("QueueingScript", Script, cg.Component)
@@ -233,12 +232,8 @@ async def script_wait_action_to_code(config, action_id, template_arg, args):
return var
@automation.register_condition(
automation.register_apply_condition(
"script.is_running",
IsRunningCondition,
automation.maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
"is_running()",
)
async def script_is_running_to_code(config, condition_id, template_arg, args):
full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
return cg.new_Pvariable(condition_id, template_arg, paren)
+1 -7
View File
@@ -5,14 +5,8 @@ namespace esphome::script {
static const char *const TAG = "script";
#ifdef USE_STORE_LOG_STR_IN_FLASH
void ScriptLogger::esp_log_(int level, int line, const __FlashStringHelper *format, const char *param) {
void ScriptLogger::esp_log_(int level, int line, ProgmemStr format, const char *param) {
esp_log_printf_(level, TAG, line, format, param);
}
#else
void ScriptLogger::esp_log_(int level, int line, const char *format, const char *param) {
esp_log_printf_(level, TAG, line, format, param);
}
#endif
} // namespace esphome::script
+4 -23
View File
@@ -8,28 +8,19 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
namespace esphome::script {
class ScriptLogger {
protected:
#ifdef USE_STORE_LOG_STR_IN_FLASH
void esp_logw_(int line, const __FlashStringHelper *format, const char *param) {
void esp_logw_(int line, ProgmemStr format, const char *param) {
esp_log_(ESPHOME_LOG_LEVEL_WARN, line, format, param);
}
void esp_logd_(int line, const __FlashStringHelper *format, const char *param) {
void esp_logd_(int line, ProgmemStr format, const char *param) {
esp_log_(ESPHOME_LOG_LEVEL_DEBUG, line, format, param);
}
void esp_log_(int level, int line, const __FlashStringHelper *format, const char *param);
#else
void esp_logw_(int line, const char *format, const char *param) {
esp_log_(ESPHOME_LOG_LEVEL_WARN, line, format, param);
}
void esp_logd_(int line, const char *format, const char *param) {
esp_log_(ESPHOME_LOG_LEVEL_DEBUG, line, format, param);
}
void esp_log_(int level, int line, const char *format, const char *param);
#endif
void esp_log_(int level, int line, ProgmemStr format, const char *param);
};
/// The abstract base class for all script types.
@@ -276,16 +267,6 @@ template<class C, typename... Ts> class ScriptStopAction final : public Action<T
C *script_;
};
template<class C, typename... Ts> class IsRunningCondition final : public Condition<Ts...> {
public:
explicit IsRunningCondition(C *parent) : parent_(parent) {}
bool check(const Ts &...x) override { return this->parent_->is_running(); }
protected:
C *parent_;
};
/** Wait for a script to finish before continuing.
*
* Uses queue-based storage to safely handle concurrent executions.
+3 -3
View File
@@ -27,6 +27,9 @@ from esphome.const import (
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_HERTZ,
UNIT_KILOVOLT_AMPS_HOURS,
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
UNIT_KILOWATT_HOURS,
UNIT_VOLT,
UNIT_VOLT_AMPS,
UNIT_VOLT_AMPS_REACTIVE,
@@ -44,9 +47,6 @@ CONF_MAXIMUM_DEMAND_ACTIVE_POWER = "maximum_demand_active_power"
CONF_MAXIMUM_DEMAND_REACTIVE_POWER = "maximum_demand_reactive_power"
CONF_MAXIMUM_DEMAND_APPARENT_POWER = "maximum_demand_apparent_power"
UNIT_KILOWATT_HOURS = "kWh"
UNIT_KILOVOLT_AMPS_HOURS = "kVAh"
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh"
selec_meter_ns = cg.esphome_ns.namespace("selec_meter")
SelecMeter = selec_meter_ns.class_(
+6 -1
View File
@@ -142,7 +142,12 @@ automation.register_apply_action(
cv.Required(CONF_OPTION): cv.templatable(cv.string_strict),
}
),
automation.ApplyField(CONF_OPTION, "set_option", cg.std_string),
automation.ApplyField(
CONF_OPTION,
"set_option",
cg.std_string,
const_fn=automation.literal_with_length,
),
call="make_call",
)
-14
View File
@@ -6,20 +6,6 @@
namespace esphome::sensor {
class SensorStateTrigger final : public Trigger<float> {
public:
explicit SensorStateTrigger(Sensor *parent) {
parent->add_on_state_callback([this](float value) { this->trigger(value); });
}
};
class SensorRawStateTrigger final : public Trigger<float> {
public:
explicit SensorRawStateTrigger(Sensor *parent) {
parent->add_on_raw_state_callback([this](float value) { this->trigger(value); });
}
};
class ValueRangeTrigger final : public Trigger<float>, public Component {
public:
explicit ValueRangeTrigger(Sensor *parent) : parent_(parent) {}
+1 -1
View File
@@ -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 = (
+5 -3
View File
@@ -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:
+6 -17
View File
@@ -20,10 +20,6 @@ PlayAction = speaker_ns.class_(
)
IsPlayingCondition = speaker_ns.class_("IsPlayingCondition", automation.Condition)
IsStoppedCondition = speaker_ns.class_("IsStoppedCondition", automation.Condition)
async def setup_speaker_core_(var, config):
if audio_dac_config := config.get(CONF_AUDIO_DAC):
aud_dac = await cg.get_variable(audio_dac_config)
@@ -47,12 +43,6 @@ SPEAKER_AUTOMATION_SCHEMA = automation.maybe_simple_id(
)
async def speaker_action(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"speaker.play",
PlayAction,
@@ -91,13 +81,12 @@ for _name, _call in (
_name, SPEAKER_AUTOMATION_SCHEMA, automation.ApplyCall(_call)
)
automation.register_condition(
"speaker.is_playing", IsPlayingCondition, SPEAKER_AUTOMATION_SCHEMA
)(speaker_action)
automation.register_condition(
"speaker.is_stopped", IsStoppedCondition, SPEAKER_AUTOMATION_SCHEMA
)(speaker_action)
automation.register_apply_condition(
"speaker.is_playing", SPEAKER_AUTOMATION_SCHEMA, "is_running()"
)
automation.register_apply_condition(
"speaker.is_stopped", SPEAKER_AUTOMATION_SCHEMA, "is_stopped()"
)
automation.register_apply_action(
-10
View File
@@ -38,14 +38,4 @@ template<typename... Ts> class PlayAction final : public Action<Ts...>, public P
} data_;
};
template<typename... Ts> class IsPlayingCondition final : public Condition<Ts...>, public Parented<Speaker> {
public:
bool check(const Ts &...x) override { return this->parent_->is_running(); }
};
template<typename... Ts> class IsStoppedCondition final : public Condition<Ts...>, public Parented<Speaker> {
public:
bool check(const Ts &...x) override { return this->parent_->is_stopped(); }
};
} // namespace esphome::speaker

Some files were not shown because too many files have changed in this diff Show More