Compare commits

..

1 Commits

Author SHA1 Message Date
J. Nick Koston
36b5f0aaf0 [api] Eliminate heap allocations in Home Assistant state callbacks 2025-12-30 10:17:17 -10:00
265 changed files with 6494 additions and 9393 deletions

View File

@@ -91,7 +91,6 @@ esphome/components/bmp3xx_spi/* @latonita
esphome/components/bmp581/* @kahrendt
esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid
esphome/components/bthome_mithermometer/* @nagyrobi
esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @bdraco @DT-art1

View File

@@ -1,7 +1,6 @@
include LICENSE
include README.md
include requirements.txt
recursive-include esphome *.yaml
recursive-include esphome *.cpp *.h *.tcc *.c
recursive-include esphome *.py.script
recursive-include esphome LICENSE.txt

View File

@@ -11,16 +11,6 @@ FROM base-source-${BUILD_TYPE} AS base
RUN git config --system --add safe.directory "*"
# Install build tools for Python packages that require compilation
# (e.g., ruamel.yaml.clibz used by ESP-IDF's idf-component-manager)
RUN if command -v apk > /dev/null; then \
apk add --no-cache build-base; \
else \
apt-get update \
&& apt-get install -y --no-install-recommends build-essential \
&& rm -rf /var/lib/apt/lists/*; \
fi
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
RUN pip install --no-cache-dir -U pip uv==0.6.14

View File

@@ -30,9 +30,7 @@ void A01nyubComponent::check_buffer_() {
ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters);
this->publish_state(meters);
} else {
char hex_buf[format_hex_pretty_size(4)];
ESP_LOGW(TAG, "Invalid data read from sensor: %s",
format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size()));
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
}
} else {
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);

View File

@@ -29,9 +29,7 @@ void A02yyuwComponent::check_buffer_() {
ESP_LOGV(TAG, "Distance from sensor: %f mm", distance);
this->publish_state(distance);
} else {
char hex_buf[format_hex_pretty_size(4)];
ESP_LOGW(TAG, "Invalid data read from sensor: %s",
format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size()));
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
}
} else {
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);

View File

@@ -25,13 +25,11 @@ class AddressableLightDisplay : public display::DisplayBuffer {
if (enabled_ && !enabled) { // enabled -> disabled
// - Tell the parent light to refresh, effectively wiping the display. Also
// restores the previous effect (if any).
if (this->last_effect_index_.has_value()) {
light_state_->make_call().set_effect(*this->last_effect_index_).perform();
}
light_state_->make_call().set_effect(this->last_effect_).perform();
} else if (!enabled_ && enabled) { // disabled -> enabled
// - Save the current effect index.
this->last_effect_index_ = light_state_->get_current_effect_index();
// - Save the current effect.
this->last_effect_ = light_state_->get_effect_name();
// - Disable any current effect.
light_state_->make_call().set_effect(0).perform();
}
@@ -58,7 +56,7 @@ class AddressableLightDisplay : public display::DisplayBuffer {
int32_t width_;
int32_t height_;
std::vector<Color> addressable_light_buffer_;
optional<uint32_t> last_effect_index_;
optional<std::string> last_effect_;
optional<std::function<int(int, int)>> pixel_mapper_f_;
};
} // namespace addressable_light

View File

@@ -226,6 +226,32 @@ def _encryption_schema(config):
return ENCRYPTION_SCHEMA(config)
def _validate_api_config(config: ConfigType) -> ConfigType:
"""Validate API configuration with mutual exclusivity check and deprecation warning."""
# Check if both password and encryption are configured
has_password = CONF_PASSWORD in config and config[CONF_PASSWORD]
has_encryption = CONF_ENCRYPTION in config
if has_password and has_encryption:
raise cv.Invalid(
"The 'password' and 'encryption' options are mutually exclusive. "
"The API client only supports one authentication method at a time. "
"Please remove one of them. "
"Note: 'password' authentication is deprecated and will be removed in version 2026.1.0. "
"We strongly recommend using 'encryption' instead for better security."
)
# Warn about password deprecation
if has_password:
_LOGGER.warning(
"API 'password' authentication has been deprecated since May 2022 and will be removed in version 2026.1.0. "
"Please migrate to the 'encryption' configuration. "
"See https://esphome.io/components/api/#configuration-variables"
)
return config
def _consume_api_sockets(config: ConfigType) -> ConfigType:
"""Register socket needs for API component."""
from esphome.components import socket
@@ -242,17 +268,7 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(APIServer),
cv.Optional(CONF_PORT, default=6053): cv.port,
# Removed in 2026.1.0 - kept to provide helpful error message
cv.Optional(CONF_PASSWORD): cv.invalid(
"The 'password' option has been removed in ESPHome 2026.1.0.\n"
"Password authentication was deprecated in May 2022.\n"
"Please migrate to encryption for secure API communication:\n\n"
"api:\n"
" encryption:\n"
" key: !secret api_encryption_key\n\n"
"Generate a key with: openssl rand -base64 32\n"
"Or visit https://esphome.io/components/api/#configuration-variables"
),
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
cv.Optional(
CONF_REBOOT_TIMEOUT, default="15min"
): cv.positive_time_period_milliseconds,
@@ -314,6 +330,7 @@ CONFIG_SCHEMA = cv.All(
}
).extend(cv.COMPONENT_SCHEMA),
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
_validate_api_config,
_consume_api_sockets,
)
@@ -327,6 +344,9 @@ async def to_code(config: ConfigType) -> None:
CORE.register_controller()
cg.add(var.set_port(config[CONF_PORT]))
if config[CONF_PASSWORD]:
cg.add_define("USE_API_PASSWORD")
cg.add(var.set_password(config[CONF_PASSWORD]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
if CONF_LISTEN_BACKLOG in config:

View File

@@ -7,7 +7,10 @@ service APIConnection {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
}
// REMOVED in ESPHome 2026.1.0: rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse)
rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
}
rpc disconnect (DisconnectRequest) returns (DisconnectResponse) {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
@@ -79,13 +82,14 @@ service APIConnection {
// * VarInt denoting the type of message.
// * The message object encoded as a ProtoBuf message
// The connection is established in 2 steps:
// The connection is established in 4 steps:
// * First, the client connects to the server and sends a "Hello Request" identifying itself
// * The server responds with a "Hello Response" and the connection is authenticated
// * The server responds with a "Hello Response" and selects the protocol version
// * After receiving this message, the client attempts to authenticate itself using
// the password and a "Connect Request"
// * The server responds with a "Connect Response" and notifies of invalid password.
// If anything in this initial process fails, the connection must immediately closed
// by both sides and _no_ disconnection message is to be sent.
// Note: Password authentication via AuthenticationRequest/AuthenticationResponse (message IDs 3, 4)
// was removed in ESPHome 2026.1.0. Those message IDs are reserved and should not be reused.
// Message sent at the beginning of each connection
// Can only be sent by the client and only at the beginning of the connection
@@ -98,7 +102,7 @@ message HelloRequest {
// For example "Home Assistant"
// Not strictly necessary to send but nice for debugging
// purposes.
string client_info = 1;
string client_info = 1 [(pointer_to_buffer) = true];
uint32 api_version_major = 2;
uint32 api_version_minor = 3;
}
@@ -126,23 +130,25 @@ message HelloResponse {
string name = 4;
}
// DEPRECATED in ESPHome 2026.1.0 - Password authentication is no longer supported.
// These messages are kept for protocol documentation but are not processed by the server.
// Use noise encryption instead: https://esphome.io/components/api/#configuration-variables
// Message sent at the beginning of each connection to authenticate the client
// Can only be sent by the client and only at the beginning of the connection
message AuthenticationRequest {
option (id) = 3;
option (source) = SOURCE_CLIENT;
option (no_delay) = true;
option deprecated = true;
option (ifdef) = "USE_API_PASSWORD";
string password = 1;
// The password to log in with
string password = 1 [(pointer_to_buffer) = true];
}
// Confirmation of successful connection. After this the connection is available for all traffic.
// Can only be sent by the server and only at the beginning of the connection
message AuthenticationResponse {
option (id) = 4;
option (source) = SOURCE_SERVER;
option (no_delay) = true;
option deprecated = true;
option (ifdef) = "USE_API_PASSWORD";
bool invalid_password = 1;
}
@@ -199,9 +205,7 @@ message DeviceInfoResponse {
option (id) = 10;
option (source) = SOURCE_SERVER;
// Deprecated in ESPHome 2026.1.0, but kept for backward compatibility
// with older ESPHome versions that still send this field.
bool uses_password = 1 [deprecated = true];
bool uses_password = 1 [(field_ifdef) = "USE_API_PASSWORD"];
// The name of the node, given by "App.set_name()"
string name = 2;
@@ -473,7 +477,7 @@ message FanCommandRequest {
bool has_speed_level = 10;
int32 speed_level = 11;
bool has_preset_mode = 12;
string preset_mode = 13;
string preset_mode = 13 [(pointer_to_buffer) = true];
uint32 device_id = 14 [(field_ifdef) = "USE_DEVICES"];
}
@@ -575,7 +579,7 @@ message LightCommandRequest {
bool has_flash_length = 16;
uint32 flash_length = 17;
bool has_effect = 18;
string effect = 19;
string effect = 19 [(pointer_to_buffer) = true];
uint32 device_id = 28 [(field_ifdef) = "USE_DEVICES"];
}
@@ -743,7 +747,7 @@ message NoiseEncryptionSetKeyRequest {
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_API_NOISE";
bytes key = 1;
bytes key = 1 [(pointer_to_buffer) = true];
}
message NoiseEncryptionSetKeyResponse {
@@ -792,7 +796,7 @@ message HomeassistantActionResponse {
uint32 call_id = 1; // Matches the call_id from HomeassistantActionRequest
bool success = 2; // Whether the service call succeeded
string error_message = 3; // Error message if success = false
bytes response_data = 4 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
bytes response_data = 4 [(pointer_to_buffer) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
}
// ==================== IMPORT HOME ASSISTANT STATES ====================
@@ -820,9 +824,9 @@ message HomeAssistantStateResponse {
option (no_delay) = true;
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
string entity_id = 1;
string state = 2;
string attribute = 3;
string entity_id = 1 [(pointer_to_buffer) = true];
string state = 2 [(pointer_to_buffer) = true];
string attribute = 3 [(pointer_to_buffer) = true];
}
// ==================== IMPORT TIME ====================
@@ -837,7 +841,7 @@ message GetTimeResponse {
option (no_delay) = true;
fixed32 epoch_seconds = 1;
string timezone = 2;
string timezone = 2 [(pointer_to_buffer) = true];
}
// ==================== USER-DEFINES SERVICES ====================
@@ -1087,11 +1091,11 @@ message ClimateCommandRequest {
bool has_swing_mode = 14;
ClimateSwingMode swing_mode = 15;
bool has_custom_fan_mode = 16;
string custom_fan_mode = 17;
string custom_fan_mode = 17 [(pointer_to_buffer) = true];
bool has_preset = 18;
ClimatePreset preset = 19;
bool has_custom_preset = 20;
string custom_preset = 21;
string custom_preset = 21 [(pointer_to_buffer) = true];
bool has_target_humidity = 22;
float target_humidity = 23;
uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"];
@@ -1270,7 +1274,7 @@ message SelectCommandRequest {
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
string state = 2;
string state = 2 [(pointer_to_buffer) = true];
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -1288,7 +1292,7 @@ message ListEntitiesSirenResponse {
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
bool disabled_by_default = 6;
repeated string tones = 7 [(container_pointer_no_template) = "FixedVector<const char *>"];
repeated string tones = 7;
bool supports_duration = 8;
bool supports_volume = 9;
EntityCategory entity_category = 10;
@@ -1688,7 +1692,7 @@ message BluetoothGATTWriteRequest {
uint32 handle = 2;
bool response = 3;
bytes data = 4;
bytes data = 4 [(pointer_to_buffer) = true];
}
message BluetoothGATTReadDescriptorRequest {
@@ -1708,7 +1712,7 @@ message BluetoothGATTWriteDescriptorRequest {
uint64 address = 1;
uint32 handle = 2;
bytes data = 3;
bytes data = 3 [(pointer_to_buffer) = true];
}
message BluetoothGATTNotifyRequest {
@@ -1933,7 +1937,7 @@ message VoiceAssistantAudio {
option (source) = SOURCE_BOTH;
option (ifdef) = "USE_VOICE_ASSISTANT";
bytes data = 1 [(pointer_to_buffer) = true];
bytes data = 1;
bool end = 2;
}
@@ -2421,7 +2425,7 @@ message ZWaveProxyFrame {
option (ifdef) = "USE_ZWAVE_PROXY";
option (no_delay) = true;
bytes data = 1;
bytes data = 1 [(pointer_to_buffer) = true];
}
enum ZWaveProxyRequestType {
@@ -2435,5 +2439,5 @@ message ZWaveProxyRequest {
option (ifdef) = "USE_ZWAVE_PROXY";
ZWaveProxyRequestType type = 1;
bytes data = 2;
bytes data = 2 [(pointer_to_buffer) = true];
}

View File

@@ -473,7 +473,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
if (msg.has_direction)
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
if (msg.has_preset_mode)
call.set_preset_mode(msg.preset_mode.c_str(), msg.preset_mode.size());
call.set_preset_mode(reinterpret_cast<const char *>(msg.preset_mode), msg.preset_mode_len);
call.perform();
}
#endif
@@ -559,7 +559,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
if (msg.has_flash_length)
call.set_flash_length(msg.flash_length);
if (msg.has_effect)
call.set_effect(msg.effect.c_str(), msg.effect.size());
call.set_effect(reinterpret_cast<const char *>(msg.effect), msg.effect_len);
call.perform();
}
#endif
@@ -738,11 +738,11 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
if (msg.has_fan_mode)
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
if (msg.has_custom_fan_mode)
call.set_fan_mode(msg.custom_fan_mode.c_str(), msg.custom_fan_mode.size());
call.set_fan_mode(reinterpret_cast<const char *>(msg.custom_fan_mode), msg.custom_fan_mode_len);
if (msg.has_preset)
call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
if (msg.has_custom_preset)
call.set_preset(msg.custom_preset.c_str(), msg.custom_preset.size());
call.set_preset(reinterpret_cast<const char *>(msg.custom_preset), msg.custom_preset_len);
if (msg.has_swing_mode)
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
call.perform();
@@ -931,7 +931,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
}
void APIConnection::select_command(const SelectCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
call.set_option(msg.state.c_str(), msg.state.size());
call.set_option(reinterpret_cast<const char *>(msg.state), msg.state_len);
call.perform();
}
#endif
@@ -1153,8 +1153,9 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
if (homeassistant::global_homeassistant_time != nullptr) {
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
#ifdef USE_TIME_TIMEZONE
if (!value.timezone.empty()) {
homeassistant::global_homeassistant_time->set_timezone(value.timezone.c_str(), value.timezone.size());
if (value.timezone_len > 0) {
homeassistant::global_homeassistant_time->set_timezone(reinterpret_cast<const char *>(value.timezone),
value.timezone_len);
}
#endif
}
@@ -1521,7 +1522,7 @@ void APIConnection::complete_authentication_() {
}
bool APIConnection::send_hello_response(const HelloRequest &msg) {
this->client_info_.name.assign(msg.client_info.c_str(), msg.client_info.size());
this->client_info_.name.assign(reinterpret_cast<const char *>(msg.client_info), msg.client_info_len);
this->client_info_.peername = this->helper_->getpeername();
this->client_api_version_major_ = msg.api_version_major;
this->client_api_version_minor_ = msg.api_version_minor;
@@ -1530,16 +1531,32 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 14;
resp.api_version_minor = 13;
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
resp.set_server_info(ESPHOME_VERSION_REF);
resp.set_name(StringRef(App.get_name()));
// Auto-authenticate - password auth was removed in ESPHome 2026.1.0
#ifdef USE_API_PASSWORD
// Password required - wait for authentication
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::CONNECTED);
#else
// No password configured - auto-authenticate
this->complete_authentication_();
#endif
return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
}
#ifdef USE_API_PASSWORD
bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) {
AuthenticationResponse resp;
// bool invalid_password = 1;
resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len);
if (!resp.invalid_password) {
this->complete_authentication_();
}
return this->send_message(resp, AuthenticationResponse::MESSAGE_TYPE);
}
#endif // USE_API_PASSWORD
bool APIConnection::send_ping_response(const PingRequest &msg) {
PingResponse resp;
@@ -1548,6 +1565,9 @@ bool APIConnection::send_ping_response(const PingRequest &msg) {
bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
DeviceInfoResponse resp{};
#ifdef USE_API_PASSWORD
resp.uses_password = true;
#endif
resp.set_name(StringRef(App.get_name()));
resp.set_friendly_name(StringRef(App.get_friendly_name()));
#ifdef USE_AREAS
@@ -1673,28 +1693,26 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
#ifdef USE_API_HOMEASSISTANT_STATES
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
// Skip if entity_id is empty (invalid message)
if (msg.entity_id.empty()) {
if (msg.entity_id_len == 0) {
return;
}
for (auto &it : this->parent_->get_state_subs()) {
// Compare entity_id: check length matches and content matches
size_t entity_id_len = strlen(it.entity_id);
if (entity_id_len != msg.entity_id.size() ||
memcmp(it.entity_id, msg.entity_id.c_str(), msg.entity_id.size()) != 0) {
if (entity_id_len != msg.entity_id_len || memcmp(it.entity_id, msg.entity_id, msg.entity_id_len) != 0) {
continue;
}
// Compare attribute: either both have matching attribute, or both have none
size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0;
if (sub_attr_len != msg.attribute.size() ||
(sub_attr_len > 0 && memcmp(it.attribute, msg.attribute.c_str(), sub_attr_len) != 0)) {
if (sub_attr_len != msg.attribute_len ||
(sub_attr_len > 0 && memcmp(it.attribute, msg.attribute, sub_attr_len) != 0)) {
continue;
}
// Create temporary string for callback (callback takes const std::string &)
// Handle empty state
std::string state(!msg.state.empty() ? msg.state.c_str() : "", msg.state.size());
// Create StringRef directly from message data (zero allocation)
StringRef state(reinterpret_cast<const char *>(msg.state), msg.state_len);
it.callback(state);
}
}
@@ -1730,20 +1748,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
// the action list. This ensures async actions (delays, waits) complete first.
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message) {
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message) {
ExecuteServiceResponse resp;
resp.call_id = call_id;
resp.success = success;
resp.set_error_message(error_message);
resp.set_error_message(StringRef(error_message));
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message,
const uint8_t *response_data, size_t response_data_len) {
ExecuteServiceResponse resp;
resp.call_id = call_id;
resp.success = success;
resp.set_error_message(error_message);
resp.set_error_message(StringRef(error_message));
resp.response_data = response_data;
resp.response_data_len = response_data_len;
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
@@ -1826,6 +1844,12 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
// Do not set last_traffic_ on send
return true;
}
#ifdef USE_API_PASSWORD
void APIConnection::on_unauthenticated_access() {
this->on_fatal_error();
ESP_LOGD(TAG, "%s (%s) no authentication", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
}
#endif
void APIConnection::on_no_setup_connection() {
this->on_fatal_error();
ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str());

View File

@@ -24,10 +24,9 @@ struct ClientInfo {
// Keepalive timeout in milliseconds
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
// Maximum number of entities to process in a single batch during initial state/info sending
// API 1.14+ clients compute object_id client-side, so messages are smaller and we can fit more per batch
// TODO: Remove MAX_INITIAL_PER_BATCH_LEGACY before 2026.7.0 - all clients should support API 1.14 by then
static constexpr size_t MAX_INITIAL_PER_BATCH_LEGACY = 24; // For clients < API 1.14 (includes object_id)
static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= API 1.14 (no object_id)
// This was increased from 20 to 24 after removing the unique_id field from entity info messages,
// which reduced message sizes allowing more entities per batch without exceeding packet limits
static constexpr size_t MAX_INITIAL_PER_BATCH = 24;
// Maximum number of packets to process in a single batch (platform-dependent)
// This limit exists to prevent stack overflow from the PacketInfo array in process_batch_
// Each PacketInfo is 8 bytes, so 64 * 8 = 512 bytes, 32 * 8 = 256 bytes
@@ -203,6 +202,9 @@ class APIConnection final : public APIServerConnection {
void on_get_time_response(const GetTimeResponse &value) override;
#endif
bool send_hello_response(const HelloRequest &msg) override;
#ifdef USE_API_PASSWORD
bool send_authenticate_response(const AuthenticationRequest &msg) override;
#endif
bool send_disconnect_response(const DisconnectRequest &msg) override;
bool send_ping_response(const PingRequest &msg) override;
bool send_device_info_response(const DeviceInfoRequest &msg) override;
@@ -231,9 +233,9 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_API_USER_DEFINED_ACTIONS
void execute_service(const ExecuteServiceRequest &msg) override;
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message);
void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message,
const uint8_t *response_data, size_t response_data_len);
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
@@ -258,6 +260,9 @@ class APIConnection final : public APIServerConnection {
}
void on_fatal_error() override;
#ifdef USE_API_PASSWORD
void on_unauthenticated_access() override;
#endif
void on_no_setup_connection() override;
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
// FIXME: ensure no recursive writes can happen
@@ -318,16 +323,10 @@ class APIConnection final : public APIServerConnection {
APIConnection *conn, uint32_t remaining_size, bool is_single) {
// Set common fields that are shared by all entity types
msg.key = entity->get_object_id_hash();
// API 1.14+ clients compute object_id client-side from the entity name
// For older clients, we must send object_id for backward compatibility
// See: https://github.com/esphome/backlog/issues/76
// TODO: Remove this backward compat code before 2026.7.0 - all clients should support API 1.14 by then
// Buffer must remain in scope until encode_message_to_buffer is called
// Get object_id with zero heap allocation
// Static case returns direct reference, dynamic case uses buffer
char object_id_buf[OBJECT_ID_MAX_LEN];
if (!conn->client_supports_api_version(1, 14)) {
msg.set_object_id(entity->get_object_id_to(object_id_buf));
}
msg.set_object_id(entity->get_object_id_to(object_id_buf));
if (entity->has_own_name()) {
msg.set_name(entity->get_name());
@@ -350,24 +349,16 @@ class APIConnection final : public APIServerConnection {
inline bool check_voice_assistant_api_connection_() const;
#endif
// Get the max batch size based on client API version
// API 1.14+ clients don't receive object_id, so messages are smaller and more fit per batch
// TODO: Remove this method before 2026.7.0 and use MAX_INITIAL_PER_BATCH directly
size_t get_max_batch_size_() const {
return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY;
}
// Helper method to process multiple entities from an iterator in a batch
template<typename Iterator> void process_iterator_batch_(Iterator &iterator) {
size_t initial_size = this->deferred_batch_.size();
size_t max_batch = this->get_max_batch_size_();
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) {
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < MAX_INITIAL_PER_BATCH) {
iterator.advance();
}
// If the batch is full, process it immediately
// Note: iterator.advance() already calls schedule_batch_() via schedule_message_()
if (this->deferred_batch_.size() >= max_batch) {
if (this->deferred_batch_.size() >= MAX_INITIAL_PER_BATCH) {
this->process_batch_();
}
}

View File

@@ -13,26 +13,12 @@ namespace esphome::api {
static const char *const TAG = "api.frame_helper";
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Received frame: %s", \
format_hex_pretty_to(hex_buf_, (buffer).data(), \
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_SENDING(data, len) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Sending raw: %s", \
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
#else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0)

View File

@@ -24,26 +24,12 @@ 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)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Received frame: %s", \
format_hex_pretty_to(hex_buf_, (buffer).data(), \
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_SENDING(data, len) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Sending raw: %s", \
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
#else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0)

View File

@@ -18,26 +18,12 @@ namespace esphome::api {
static const char *const TAG = "api.plaintext";
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Received frame: %s", \
format_hex_pretty_to(hex_buf_, (buffer).data(), \
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_SENDING(data, len) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Sending raw: %s", \
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
#else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0)

View File

@@ -23,7 +23,9 @@ bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->client_info = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
// Use raw data directly to avoid allocation
this->client_info = value.data();
this->client_info_len = value.size();
break;
}
default:
@@ -43,6 +45,23 @@ void HelloResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->server_info_ref_.size());
size.add_length(1, this->name_ref_.size());
}
#ifdef USE_API_PASSWORD
bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
// Use raw data directly to avoid allocation
this->password = value.data();
this->password_len = value.size();
break;
}
default:
return false;
}
return true;
}
void AuthenticationResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
void AuthenticationResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); }
#endif
#ifdef USE_AREAS
void AreaInfo::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->area_id);
@@ -66,6 +85,9 @@ void DeviceInfo::calculate_size(ProtoSize &size) const {
}
#endif
void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
#ifdef USE_API_PASSWORD
buffer.encode_bool(1, this->uses_password);
#endif
buffer.encode_string(2, this->name_ref_);
buffer.encode_string(3, this->mac_address_ref_);
buffer.encode_string(4, this->esphome_version_ref_);
@@ -121,6 +143,9 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
#endif
}
void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
#ifdef USE_API_PASSWORD
size.add_bool(1, this->uses_password);
#endif
size.add_length(1, this->name_ref_.size());
size.add_length(1, this->mac_address_ref_.size());
size.add_length(1, this->esphome_version_ref_.size());
@@ -423,7 +448,9 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 13: {
this->preset_mode = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
// Use raw data directly to avoid allocation
this->preset_mode = value.data();
this->preset_mode_len = value.size();
break;
}
default:
@@ -588,7 +615,9 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 19: {
this->effect = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
// Use raw data directly to avoid allocation
this->effect = value.data();
this->effect_len = value.size();
break;
}
default:
@@ -830,6 +859,7 @@ void SubscribeLogsResponse::calculate_size(ProtoSize &size) const {
bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
// Use raw data directly to avoid allocation
this->key = value.data();
this->key_len = value.size();
break;
@@ -906,12 +936,12 @@ bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt v
}
bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3: {
this->error_message = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 3:
this->error_message = value.as_string();
break;
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
case 4: {
// Use raw data directly to avoid allocation
this->response_data = value.data();
this->response_data_len = value.size();
break;
@@ -937,15 +967,21 @@ void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const
bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->entity_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
// Use raw data directly to avoid allocation
this->entity_id = value.data();
this->entity_id_len = value.size();
break;
}
case 2: {
this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
// Use raw data directly to avoid allocation
this->state = value.data();
this->state_len = value.size();
break;
}
case 3: {
this->attribute = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
// Use raw data directly to avoid allocation
this->attribute = value.data();
this->attribute_len = value.size();
break;
}
default:
@@ -957,7 +993,9 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->timezone = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
// Use raw data directly to avoid allocation
this->timezone = value.data();
this->timezone_len = value.size();
break;
}
default:
@@ -1022,10 +1060,9 @@ bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value)
}
bool ExecuteServiceArgument::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 4: {
this->string_ = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 4:
this->string_ = value.as_string();
break;
}
case 9:
this->string_array.push_back(value.as_string());
break;
@@ -1116,7 +1153,7 @@ void ExecuteServiceResponse::calculate_size(ProtoSize &size) const {
size.add_bool(1, this->success);
size.add_length(1, this->error_message_ref_.size());
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
size.add_length(1, this->response_data_len);
size.add_length(4, this->response_data_len);
#endif
}
#endif
@@ -1371,11 +1408,15 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 17: {
this->custom_fan_mode = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
// Use raw data directly to avoid allocation
this->custom_fan_mode = value.data();
this->custom_fan_mode_len = value.size();
break;
}
case 21: {
this->custom_preset = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
// Use raw data directly to avoid allocation
this->custom_preset = value.data();
this->custom_preset_len = value.size();
break;
}
default:
@@ -1661,7 +1702,9 @@ bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
// Use raw data directly to avoid allocation
this->state = value.data();
this->state_len = value.size();
break;
}
default:
@@ -1689,8 +1732,8 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon_ref_);
#endif
buffer.encode_bool(6, this->disabled_by_default);
for (const char *it : *this->tones) {
buffer.encode_string(7, it, strlen(it), true);
for (auto &it : this->tones) {
buffer.encode_string(7, it, true);
}
buffer.encode_bool(8, this->supports_duration);
buffer.encode_bool(9, this->supports_volume);
@@ -1707,9 +1750,9 @@ void ListEntitiesSirenResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->icon_ref_.size());
#endif
size.add_bool(1, this->disabled_by_default);
if (!this->tones->empty()) {
for (const char *it : *this->tones) {
size.add_length_force(1, strlen(it));
if (!this->tones.empty()) {
for (const auto &it : this->tones) {
size.add_length_force(1, it.size());
}
}
size.add_bool(1, this->supports_duration);
@@ -1765,10 +1808,9 @@ bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
}
bool SirenCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 5: {
this->tone = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 5:
this->tone = value.as_string();
break;
}
default:
return false;
}
@@ -1857,10 +1899,9 @@ bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
}
bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 4: {
this->code = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 4:
this->code = value.as_string();
break;
}
default:
return false;
}
@@ -2028,10 +2069,9 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val
}
bool MediaPlayerCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 7: {
this->media_url = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 7:
this->media_url = value.as_string();
break;
}
default:
return false;
}
@@ -2239,6 +2279,7 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 4: {
// Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break;
@@ -2277,6 +2318,7 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3: {
// Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break;
@@ -2460,14 +2502,12 @@ bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value)
}
bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->name = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 1:
this->name = value.as_string();
break;
}
case 2: {
this->value = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 2:
this->value = value.as_string();
break;
}
default:
return false;
}
@@ -2506,22 +2546,20 @@ bool VoiceAssistantAudio::decode_varint(uint32_t field_id, ProtoVarInt value) {
}
bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->data = value.data();
this->data_len = value.size();
case 1:
this->data = value.as_string();
break;
}
default:
return false;
}
return true;
}
void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bytes(1, this->data, this->data_len);
buffer.encode_bytes(1, this->data_ptr_, this->data_len_);
buffer.encode_bool(2, this->end);
}
void VoiceAssistantAudio::calculate_size(ProtoSize &size) const {
size.add_length(1, this->data_len);
size.add_length(1, this->data_len_);
size.add_bool(1, this->end);
}
bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
@@ -2545,14 +2583,12 @@ bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVar
}
bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->timer_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 2:
this->timer_id = value.as_string();
break;
}
case 3: {
this->name = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 3:
this->name = value.as_string();
break;
}
default:
return false;
}
@@ -2570,18 +2606,15 @@ bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt
}
bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->media_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 1:
this->media_id = value.as_string();
break;
}
case 2: {
this->text = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 2:
this->text = value.as_string();
break;
}
case 3: {
this->preannounce_media_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 3:
this->preannounce_media_id = value.as_string();
break;
}
default:
return false;
}
@@ -2617,29 +2650,24 @@ bool VoiceAssistantExternalWakeWord::decode_varint(uint32_t field_id, ProtoVarIn
}
bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 1:
this->id = value.as_string();
break;
}
case 2: {
this->wake_word = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 2:
this->wake_word = value.as_string();
break;
}
case 3:
this->trained_languages.push_back(value.as_string());
break;
case 4: {
this->model_type = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 4:
this->model_type = value.as_string();
break;
}
case 6: {
this->model_hash = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 6:
this->model_hash = value.as_string();
break;
}
case 7: {
this->url = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 7:
this->url = value.as_string();
break;
}
default:
return false;
}
@@ -2749,10 +2777,9 @@ bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarI
}
bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3: {
this->code = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 3:
this->code = value.as_string();
break;
}
default:
return false;
}
@@ -2834,10 +2861,9 @@ bool TextCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
}
bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
case 2:
this->state = value.as_string();
break;
}
default:
return false;
}
@@ -3305,6 +3331,7 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
// Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break;
@@ -3329,6 +3356,7 @@ bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
// Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break;
@@ -3344,7 +3372,7 @@ void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const {
}
void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
size.add_uint32(1, static_cast<uint32_t>(this->type));
size.add_length(1, this->data_len);
size.add_length(2, this->data_len);
}
#endif

View File

@@ -357,11 +357,12 @@ class CommandProtoMessage : public ProtoDecodableMessage {
class HelloRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 1;
static constexpr uint8_t ESTIMATED_SIZE = 17;
static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "hello_request"; }
#endif
StringRef client_info{};
const uint8_t *client_info{nullptr};
uint16_t client_info_len{0};
uint32_t api_version_major{0};
uint32_t api_version_minor{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -393,6 +394,40 @@ class HelloResponse final : public ProtoMessage {
protected:
};
#ifdef USE_API_PASSWORD
class AuthenticationRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 3;
static constexpr uint8_t ESTIMATED_SIZE = 19;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "authentication_request"; }
#endif
const uint8_t *password{nullptr};
uint16_t password_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class AuthenticationResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 4;
static constexpr uint8_t ESTIMATED_SIZE = 2;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "authentication_response"; }
#endif
bool invalid_password{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
#endif
class DisconnectRequest final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 5;
@@ -492,9 +527,12 @@ class DeviceInfo final : public ProtoMessage {
class DeviceInfoResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 10;
static constexpr uint8_t ESTIMATED_SIZE = 255;
static constexpr uint16_t ESTIMATED_SIZE = 257;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "device_info_response"; }
#endif
#ifdef USE_API_PASSWORD
bool uses_password{false};
#endif
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
@@ -746,7 +784,7 @@ class FanStateResponse final : public StateResponseProtoMessage {
class FanCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 31;
static constexpr uint8_t ESTIMATED_SIZE = 38;
static constexpr uint8_t ESTIMATED_SIZE = 48;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "fan_command_request"; }
#endif
@@ -759,7 +797,8 @@ class FanCommandRequest final : public CommandProtoMessage {
bool has_speed_level{false};
int32_t speed_level{0};
bool has_preset_mode{false};
StringRef preset_mode{};
const uint8_t *preset_mode{nullptr};
uint16_t preset_mode_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -821,7 +860,7 @@ class LightStateResponse final : public StateResponseProtoMessage {
class LightCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 32;
static constexpr uint8_t ESTIMATED_SIZE = 112;
static constexpr uint8_t ESTIMATED_SIZE = 122;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "light_command_request"; }
#endif
@@ -850,7 +889,8 @@ class LightCommandRequest final : public CommandProtoMessage {
bool has_flash_length{false};
uint32_t flash_length{0};
bool has_effect{false};
StringRef effect{};
const uint8_t *effect{nullptr};
uint16_t effect_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1010,7 +1050,7 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage {
class SubscribeLogsResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 29;
static constexpr uint8_t ESTIMATED_SIZE = 21;
static constexpr uint8_t ESTIMATED_SIZE = 11;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_logs_response"; }
#endif
@@ -1131,7 +1171,7 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage {
#endif
uint32_t call_id{0};
bool success{false};
StringRef error_message{};
std::string error_message{};
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
const uint8_t *response_data{nullptr};
uint16_t response_data_len{0};
@@ -1182,13 +1222,16 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
class HomeAssistantStateResponse final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 40;
static constexpr uint8_t ESTIMATED_SIZE = 27;
static constexpr uint8_t ESTIMATED_SIZE = 57;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "home_assistant_state_response"; }
#endif
StringRef entity_id{};
StringRef state{};
StringRef attribute{};
const uint8_t *entity_id{nullptr};
uint16_t entity_id_len{0};
const uint8_t *state{nullptr};
uint16_t state_len{0};
const uint8_t *attribute{nullptr};
uint16_t attribute_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1213,12 +1256,13 @@ class GetTimeRequest final : public ProtoMessage {
class GetTimeResponse final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 37;
static constexpr uint8_t ESTIMATED_SIZE = 14;
static constexpr uint8_t ESTIMATED_SIZE = 24;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "get_time_response"; }
#endif
uint32_t epoch_seconds{0};
StringRef timezone{};
const uint8_t *timezone{nullptr};
uint16_t timezone_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1266,7 +1310,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage {
bool bool_{false};
int32_t legacy_int{0};
float float_{0.0f};
StringRef string_{};
std::string string_{};
int32_t int_{0};
FixedVector<bool> bool_array{};
FixedVector<int32_t> int_array{};
@@ -1352,7 +1396,7 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage {
class CameraImageResponse final : public StateResponseProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 44;
static constexpr uint8_t ESTIMATED_SIZE = 30;
static constexpr uint8_t ESTIMATED_SIZE = 20;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "camera_image_response"; }
#endif
@@ -1455,7 +1499,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage {
class ClimateCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 48;
static constexpr uint8_t ESTIMATED_SIZE = 84;
static constexpr uint8_t ESTIMATED_SIZE = 104;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "climate_command_request"; }
#endif
@@ -1472,11 +1516,13 @@ class ClimateCommandRequest final : public CommandProtoMessage {
bool has_swing_mode{false};
enums::ClimateSwingMode swing_mode{};
bool has_custom_fan_mode{false};
StringRef custom_fan_mode{};
const uint8_t *custom_fan_mode{nullptr};
uint16_t custom_fan_mode_len{0};
bool has_preset{false};
enums::ClimatePreset preset{};
bool has_custom_preset{false};
StringRef custom_preset{};
const uint8_t *custom_preset{nullptr};
uint16_t custom_preset_len{0};
bool has_target_humidity{false};
float target_humidity{0.0f};
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1649,11 +1695,12 @@ class SelectStateResponse final : public StateResponseProtoMessage {
class SelectCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 54;
static constexpr uint8_t ESTIMATED_SIZE = 18;
static constexpr uint8_t ESTIMATED_SIZE = 28;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "select_command_request"; }
#endif
StringRef state{};
const uint8_t *state{nullptr};
uint16_t state_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1672,7 +1719,7 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_siren_response"; }
#endif
const FixedVector<const char *> *tones{};
std::vector<std::string> tones{};
bool supports_duration{false};
bool supports_volume{false};
void encode(ProtoWriteBuffer buffer) const override;
@@ -1709,7 +1756,7 @@ class SirenCommandRequest final : public CommandProtoMessage {
bool has_state{false};
bool state{false};
bool has_tone{false};
StringRef tone{};
std::string tone{};
bool has_duration{false};
uint32_t duration{0};
bool has_volume{false};
@@ -1770,7 +1817,7 @@ class LockCommandRequest final : public CommandProtoMessage {
#endif
enums::LockCommand command{};
bool has_code{false};
StringRef code{};
std::string code{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1880,7 +1927,7 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage {
bool has_volume{false};
float volume{0.0f};
bool has_media_url{false};
StringRef media_url{};
std::string media_url{};
bool has_announcement{false};
bool announcement{false};
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2087,7 +2134,7 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage {
class BluetoothGATTReadResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 74;
static constexpr uint8_t ESTIMATED_SIZE = 27;
static constexpr uint8_t ESTIMATED_SIZE = 17;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_read_response"; }
#endif
@@ -2182,7 +2229,7 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage {
class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 79;
static constexpr uint8_t ESTIMATED_SIZE = 27;
static constexpr uint8_t ESTIMATED_SIZE = 17;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_notify_data_response"; }
#endif
@@ -2456,8 +2503,8 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage {
};
class VoiceAssistantEventData final : public ProtoDecodableMessage {
public:
StringRef name{};
StringRef value{};
std::string name{};
std::string value{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -2485,12 +2532,17 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage {
class VoiceAssistantAudio final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 106;
static constexpr uint8_t ESTIMATED_SIZE = 21;
static constexpr uint8_t ESTIMATED_SIZE = 11;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "voice_assistant_audio"; }
#endif
const uint8_t *data{nullptr};
uint16_t data_len{0};
std::string data{};
const uint8_t *data_ptr_{nullptr};
size_t data_len_{0};
void set_data(const uint8_t *data, size_t len) {
this->data_ptr_ = data;
this->data_len_ = len;
}
bool end{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2510,8 +2562,8 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage {
const char *message_name() const override { return "voice_assistant_timer_event_response"; }
#endif
enums::VoiceAssistantTimerEvent event_type{};
StringRef timer_id{};
StringRef name{};
std::string timer_id{};
std::string name{};
uint32_t total_seconds{0};
uint32_t seconds_left{0};
bool is_active{false};
@@ -2530,9 +2582,9 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "voice_assistant_announce_request"; }
#endif
StringRef media_id{};
StringRef text{};
StringRef preannounce_media_id{};
std::string media_id{};
std::string text{};
std::string preannounce_media_id{};
bool start_conversation{false};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -2575,13 +2627,13 @@ class VoiceAssistantWakeWord final : public ProtoMessage {
};
class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage {
public:
StringRef id{};
StringRef wake_word{};
std::string id{};
std::string wake_word{};
std::vector<std::string> trained_languages{};
StringRef model_type{};
std::string model_type{};
uint32_t model_size{0};
StringRef model_hash{};
StringRef url{};
std::string model_hash{};
std::string url{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -2682,7 +2734,7 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage {
const char *message_name() const override { return "alarm_control_panel_command_request"; }
#endif
enums::AlarmControlPanelStateCommand command{};
StringRef code{};
std::string code{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -2739,7 +2791,7 @@ class TextCommandRequest final : public CommandProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "text_command_request"; }
#endif
StringRef state{};
std::string state{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif

View File

@@ -736,7 +736,7 @@ template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums:
void HelloRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HelloRequest");
out.append(" client_info: ");
out.append("'").append(this->client_info.c_str(), this->client_info.size()).append("'");
out.append(format_hex_pretty(this->client_info, this->client_info_len));
out.append("\n");
dump_field(out, "api_version_major", this->api_version_major);
dump_field(out, "api_version_minor", this->api_version_minor);
@@ -748,6 +748,18 @@ void HelloResponse::dump_to(std::string &out) const {
dump_field(out, "server_info", this->server_info_ref_);
dump_field(out, "name", this->name_ref_);
}
#ifdef USE_API_PASSWORD
void AuthenticationRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AuthenticationRequest");
out.append(" password: ");
out.append(format_hex_pretty(this->password, this->password_len));
out.append("\n");
}
void AuthenticationResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AuthenticationResponse");
dump_field(out, "invalid_password", this->invalid_password);
}
#endif
void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
@@ -770,6 +782,9 @@ void DeviceInfo::dump_to(std::string &out) const {
#endif
void DeviceInfoResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "DeviceInfoResponse");
#ifdef USE_API_PASSWORD
dump_field(out, "uses_password", this->uses_password);
#endif
dump_field(out, "name", this->name_ref_);
dump_field(out, "mac_address", this->mac_address_ref_);
dump_field(out, "esphome_version", this->esphome_version_ref_);
@@ -950,7 +965,7 @@ void FanCommandRequest::dump_to(std::string &out) const {
dump_field(out, "speed_level", this->speed_level);
dump_field(out, "has_preset_mode", this->has_preset_mode);
out.append(" preset_mode: ");
out.append("'").append(this->preset_mode.c_str(), this->preset_mode.size()).append("'");
out.append(format_hex_pretty(this->preset_mode, this->preset_mode_len));
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -1028,7 +1043,7 @@ void LightCommandRequest::dump_to(std::string &out) const {
dump_field(out, "flash_length", this->flash_length);
dump_field(out, "has_effect", this->has_effect);
out.append(" effect: ");
out.append("'").append(this->effect.c_str(), this->effect.size()).append("'");
out.append(format_hex_pretty(this->effect, this->effect_len));
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -1190,9 +1205,7 @@ void HomeassistantActionResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantActionResponse");
dump_field(out, "call_id", this->call_id);
dump_field(out, "success", this->success);
out.append(" error_message: ");
out.append("'").append(this->error_message.c_str(), this->error_message.size()).append("'");
out.append("\n");
dump_field(out, "error_message", this->error_message);
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
out.append(" response_data: ");
out.append(format_hex_pretty(this->response_data, this->response_data_len));
@@ -1213,13 +1226,13 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
void HomeAssistantStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeAssistantStateResponse");
out.append(" entity_id: ");
out.append("'").append(this->entity_id.c_str(), this->entity_id.size()).append("'");
out.append(format_hex_pretty(this->entity_id, this->entity_id_len));
out.append("\n");
out.append(" state: ");
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append(format_hex_pretty(this->state, this->state_len));
out.append("\n");
out.append(" attribute: ");
out.append("'").append(this->attribute.c_str(), this->attribute.size()).append("'");
out.append(format_hex_pretty(this->attribute, this->attribute_len));
out.append("\n");
}
#endif
@@ -1228,7 +1241,7 @@ void GetTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "GetTimeResponse");
dump_field(out, "epoch_seconds", this->epoch_seconds);
out.append(" timezone: ");
out.append("'").append(this->timezone.c_str(), this->timezone.size()).append("'");
out.append(format_hex_pretty(this->timezone, this->timezone_len));
out.append("\n");
}
#ifdef USE_API_USER_DEFINED_ACTIONS
@@ -1253,9 +1266,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
dump_field(out, "bool_", this->bool_);
dump_field(out, "legacy_int", this->legacy_int);
dump_field(out, "float_", this->float_);
out.append(" string_: ");
out.append("'").append(this->string_.c_str(), this->string_.size()).append("'");
out.append("\n");
dump_field(out, "string_", this->string_);
dump_field(out, "int_", this->int_);
for (const auto it : this->bool_array) {
dump_field(out, "bool_array", static_cast<bool>(it), 4);
@@ -1413,13 +1424,13 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
dump_field(out, "swing_mode", static_cast<enums::ClimateSwingMode>(this->swing_mode));
dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode);
out.append(" custom_fan_mode: ");
out.append("'").append(this->custom_fan_mode.c_str(), this->custom_fan_mode.size()).append("'");
out.append(format_hex_pretty(this->custom_fan_mode, this->custom_fan_mode_len));
out.append("\n");
dump_field(out, "has_preset", this->has_preset);
dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset));
dump_field(out, "has_custom_preset", this->has_custom_preset);
out.append(" custom_preset: ");
out.append("'").append(this->custom_preset.c_str(), this->custom_preset.size()).append("'");
out.append(format_hex_pretty(this->custom_preset, this->custom_preset_len));
out.append("\n");
dump_field(out, "has_target_humidity", this->has_target_humidity);
dump_field(out, "target_humidity", this->target_humidity);
@@ -1547,7 +1558,7 @@ void SelectCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "SelectCommandRequest");
dump_field(out, "key", this->key);
out.append(" state: ");
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append(format_hex_pretty(this->state, this->state_len));
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -1564,7 +1575,7 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const {
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
for (const auto &it : *this->tones) {
for (const auto &it : this->tones) {
dump_field(out, "tones", it, 4);
}
dump_field(out, "supports_duration", this->supports_duration);
@@ -1588,9 +1599,7 @@ void SirenCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_state", this->has_state);
dump_field(out, "state", this->state);
dump_field(out, "has_tone", this->has_tone);
out.append(" tone: ");
out.append("'").append(this->tone.c_str(), this->tone.size()).append("'");
out.append("\n");
dump_field(out, "tone", this->tone);
dump_field(out, "has_duration", this->has_duration);
dump_field(out, "duration", this->duration);
dump_field(out, "has_volume", this->has_volume);
@@ -1632,9 +1641,7 @@ void LockCommandRequest::dump_to(std::string &out) const {
dump_field(out, "key", this->key);
dump_field(out, "command", static_cast<enums::LockCommand>(this->command));
dump_field(out, "has_code", this->has_code);
out.append(" code: ");
out.append("'").append(this->code.c_str(), this->code.size()).append("'");
out.append("\n");
dump_field(out, "code", this->code);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1712,9 +1719,7 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_volume", this->has_volume);
dump_field(out, "volume", this->volume);
dump_field(out, "has_media_url", this->has_media_url);
out.append(" media_url: ");
out.append("'").append(this->media_url.c_str(), this->media_url.size()).append("'");
out.append("\n");
dump_field(out, "media_url", this->media_url);
dump_field(out, "has_announcement", this->has_announcement);
dump_field(out, "announcement", this->announcement);
#ifdef USE_DEVICES
@@ -1944,12 +1949,8 @@ void VoiceAssistantResponse::dump_to(std::string &out) const {
}
void VoiceAssistantEventData::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantEventData");
out.append(" name: ");
out.append("'").append(this->name.c_str(), this->name.size()).append("'");
out.append("\n");
out.append(" value: ");
out.append("'").append(this->value.c_str(), this->value.size()).append("'");
out.append("\n");
dump_field(out, "name", this->name);
dump_field(out, "value", this->value);
}
void VoiceAssistantEventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantEventResponse");
@@ -1963,34 +1964,28 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
void VoiceAssistantAudio::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantAudio");
out.append(" data: ");
out.append(format_hex_pretty(this->data, this->data_len));
if (this->data_ptr_ != nullptr) {
out.append(format_hex_pretty(this->data_ptr_, this->data_len_));
} else {
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size()));
}
out.append("\n");
dump_field(out, "end", this->end);
}
void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantTimerEventResponse");
dump_field(out, "event_type", static_cast<enums::VoiceAssistantTimerEvent>(this->event_type));
out.append(" timer_id: ");
out.append("'").append(this->timer_id.c_str(), this->timer_id.size()).append("'");
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name.c_str(), this->name.size()).append("'");
out.append("\n");
dump_field(out, "timer_id", this->timer_id);
dump_field(out, "name", this->name);
dump_field(out, "total_seconds", this->total_seconds);
dump_field(out, "seconds_left", this->seconds_left);
dump_field(out, "is_active", this->is_active);
}
void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantAnnounceRequest");
out.append(" media_id: ");
out.append("'").append(this->media_id.c_str(), this->media_id.size()).append("'");
out.append("\n");
out.append(" text: ");
out.append("'").append(this->text.c_str(), this->text.size()).append("'");
out.append("\n");
out.append(" preannounce_media_id: ");
out.append("'").append(this->preannounce_media_id.c_str(), this->preannounce_media_id.size()).append("'");
out.append("\n");
dump_field(out, "media_id", this->media_id);
dump_field(out, "text", this->text);
dump_field(out, "preannounce_media_id", this->preannounce_media_id);
dump_field(out, "start_conversation", this->start_conversation);
}
void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
@@ -2004,25 +1999,15 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const {
}
void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord");
out.append(" id: ");
out.append("'").append(this->id.c_str(), this->id.size()).append("'");
out.append("\n");
out.append(" wake_word: ");
out.append("'").append(this->wake_word.c_str(), this->wake_word.size()).append("'");
out.append("\n");
dump_field(out, "id", this->id);
dump_field(out, "wake_word", this->wake_word);
for (const auto &it : this->trained_languages) {
dump_field(out, "trained_languages", it, 4);
}
out.append(" model_type: ");
out.append("'").append(this->model_type.c_str(), this->model_type.size()).append("'");
out.append("\n");
dump_field(out, "model_type", this->model_type);
dump_field(out, "model_size", this->model_size);
out.append(" model_hash: ");
out.append("'").append(this->model_hash.c_str(), this->model_hash.size()).append("'");
out.append("\n");
out.append(" url: ");
out.append("'").append(this->url.c_str(), this->url.size()).append("'");
out.append("\n");
dump_field(out, "model_hash", this->model_hash);
dump_field(out, "url", this->url);
}
void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest");
@@ -2081,9 +2066,7 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AlarmControlPanelCommandRequest");
dump_field(out, "key", this->key);
dump_field(out, "command", static_cast<enums::AlarmControlPanelStateCommand>(this->command));
out.append(" code: ");
out.append("'").append(this->code.c_str(), this->code.size()).append("'");
out.append("\n");
dump_field(out, "code", this->code);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -2120,9 +2103,7 @@ void TextStateResponse::dump_to(std::string &out) const {
void TextCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "TextCommandRequest");
dump_field(out, "key", this->key);
out.append(" state: ");
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append("\n");
dump_field(out, "state", this->state);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif

View File

@@ -24,6 +24,17 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_hello_request(msg);
break;
}
#ifdef USE_API_PASSWORD
case AuthenticationRequest::MESSAGE_TYPE: {
AuthenticationRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_authentication_request: %s", msg.dump().c_str());
#endif
this->on_authentication_request(msg);
break;
}
#endif
case DisconnectRequest::MESSAGE_TYPE: {
DisconnectRequest msg;
// Empty message: no decode needed
@@ -632,6 +643,13 @@ void APIServerConnection::on_hello_request(const HelloRequest &msg) {
this->on_fatal_error();
}
}
#ifdef USE_API_PASSWORD
void APIServerConnection::on_authentication_request(const AuthenticationRequest &msg) {
if (!this->send_authenticate_response(msg)) {
this->on_fatal_error();
}
}
#endif
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
if (!this->send_disconnect_response(msg)) {
this->on_fatal_error();
@@ -823,7 +841,10 @@ void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg)
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
// Check authentication/connection requirements for messages
switch (msg_type) {
case HelloRequest::MESSAGE_TYPE: // No setup required
case HelloRequest::MESSAGE_TYPE: // No setup required
#ifdef USE_API_PASSWORD
case AuthenticationRequest::MESSAGE_TYPE: // No setup required
#endif
case DisconnectRequest::MESSAGE_TYPE: // No setup required
case PingRequest::MESSAGE_TYPE: // No setup required
break; // Skip all checks for these messages

View File

@@ -26,6 +26,10 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_hello_request(const HelloRequest &value){};
#ifdef USE_API_PASSWORD
virtual void on_authentication_request(const AuthenticationRequest &value){};
#endif
virtual void on_disconnect_request(const DisconnectRequest &value){};
virtual void on_disconnect_response(const DisconnectResponse &value){};
virtual void on_ping_request(const PingRequest &value){};
@@ -224,6 +228,9 @@ class APIServerConnectionBase : public ProtoService {
class APIServerConnection : public APIServerConnectionBase {
public:
virtual bool send_hello_response(const HelloRequest &msg) = 0;
#ifdef USE_API_PASSWORD
virtual bool send_authenticate_response(const AuthenticationRequest &msg) = 0;
#endif
virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
virtual bool send_ping_response(const PingRequest &msg) = 0;
virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
@@ -350,6 +357,9 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
#ifdef USE_API_PASSWORD
void on_authentication_request(const AuthenticationRequest &msg) override;
#endif
void on_disconnect_request(const DisconnectRequest &msg) override;
void on_ping_request(const PingRequest &msg) override;
void on_device_info_request(const DeviceInfoRequest &msg) override;

View File

@@ -224,6 +224,38 @@ void APIServer::dump_config() {
#endif
}
#ifdef USE_API_PASSWORD
bool APIServer::check_password(const uint8_t *password_data, size_t password_len) const {
// depend only on input password length
const char *a = this->password_.c_str();
uint32_t len_a = this->password_.length();
const char *b = reinterpret_cast<const char *>(password_data);
uint32_t len_b = password_len;
// disable optimization with volatile
volatile uint32_t length = len_b;
volatile const char *left = nullptr;
volatile const char *right = b;
uint8_t result = 0;
if (len_a == length) {
left = *((volatile const char **) &a);
result = 0;
}
if (len_a != length) {
left = b;
result = 1;
}
for (size_t i = 0; i < length; i++) {
result |= *left++ ^ *right++; // NOLINT
}
return result == 0;
}
#endif
void APIServer::handle_disconnect(APIConnection *conn) {}
// Macro for controller update dispatch
@@ -345,6 +377,10 @@ float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI;
void APIServer::set_port(uint16_t port) { this->port_ = port; }
#ifdef USE_API_PASSWORD
void APIServer::set_password(const std::string &password) { this->password_ = password; }
#endif
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
#ifdef USE_API_HOMEASSISTANT_SERVICES
@@ -358,7 +394,7 @@ void APIServer::register_action_response_callback(uint32_t call_id, ActionRespon
this->action_response_callbacks_.push_back({call_id, std::move(callback)});
}
void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message) {
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message) {
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
if (it->call_id == call_id) {
auto callback = std::move(it->callback);
@@ -370,7 +406,7 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef
}
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message,
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
const uint8_t *response_data, size_t response_data_len) {
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
if (it->call_id == call_id) {
@@ -387,18 +423,18 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef
#endif // USE_API_HOMEASSISTANT_SERVICES
#ifdef USE_API_HOMEASSISTANT_STATES
// Helper to add subscription (reduces duplication)
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute,
std::function<void(std::string)> f, bool once) {
// Helper to add subscription (reduces duplication) - const char* version (zero allocation)
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
bool once) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once,
// entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation)
});
}
// Helper to add subscription with heap-allocated strings (reduces duplication)
// Helper to add subscription with heap-allocated strings and StringRef callback
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f, bool once) {
std::function<void(StringRef)> f, bool once) {
HomeAssistantStateSubscription sub;
// Allocate heap storage for the strings
sub.entity_id_dynamic_storage = std::make_unique<std::string>(std::move(entity_id));
@@ -416,25 +452,45 @@ void APIServer::add_state_subscription_(std::string entity_id, optional<std::str
this->state_subs_.push_back(std::move(sub));
}
// Legacy helper: wraps std::string callback and delegates to StringRef version
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f, bool once) {
// Wrap callback to convert StringRef -> std::string, then delegate
this->add_state_subscription_(std::move(entity_id), std::move(attribute),
std::function<void(StringRef)>([f = std::move(f)](StringRef state) { f(state.str()); }),
once);
}
// New const char* overload (for internal components - zero allocation)
void APIServer::subscribe_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(std::string)> f) {
std::function<void(StringRef)> f) {
this->add_state_subscription_(entity_id, attribute, std::move(f), false);
}
void APIServer::get_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(std::string)> f) {
std::function<void(StringRef)> f) {
this->add_state_subscription_(entity_id, attribute, std::move(f), true);
}
// Existing std::string overload (for custom_api_device.h - heap allocation)
// std::string overload with StringRef callback (zero-allocation callback)
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
std::function<void(StringRef)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
std::function<void(StringRef)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
}
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string)
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
}
@@ -642,7 +698,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con
}
}
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message) {
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message) {
for (auto &call : this->active_action_calls_) {
if (call.action_call_id == action_call_id) {
call.connection->send_execute_service_response(call.client_call_id, success, error_message);
@@ -652,7 +708,7 @@ void APIServer::send_action_response(uint32_t action_call_id, bool success, Stri
ESP_LOGW(TAG, "Cannot send response: no active call found for action_call_id %u", action_call_id);
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message,
const uint8_t *response_data, size_t response_data_len) {
for (auto &call : this->active_action_calls_) {
if (call.action_call_id == action_call_id) {

View File

@@ -59,6 +59,10 @@ class APIServer : public Component,
#endif
#ifdef USE_CAMERA
void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override;
#endif
#ifdef USE_API_PASSWORD
bool check_password(const uint8_t *password_data, size_t password_len) const;
void set_password(const std::string &password);
#endif
void set_port(uint16_t port);
void set_reboot_timeout(uint32_t reboot_timeout);
@@ -139,10 +143,10 @@ class APIServer : public Component,
// Action response handling
using ActionResponseCallback = std::function<void(const class ActionResponse &)>;
void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback);
void handle_action_response(uint32_t call_id, bool success, StringRef error_message);
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message);
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
void handle_action_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data,
size_t response_data_len);
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
const uint8_t *response_data, size_t response_data_len);
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif // USE_API_HOMEASSISTANT_SERVICES
@@ -161,9 +165,9 @@ class APIServer : public Component,
void unregister_active_action_call(uint32_t action_call_id);
void unregister_active_action_calls_for_connection(APIConnection *conn);
// Send response for a specific action call (uses action_call_id, sends client_call_id in response)
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message);
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message,
const uint8_t *response_data, size_t response_data_len);
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
@@ -191,7 +195,7 @@ class APIServer : public Component,
struct HomeAssistantStateSubscription {
const char *entity_id; // Pointer to flash (internal) or heap (external)
const char *attribute; // Pointer to flash or nullptr (nullptr means no attribute)
std::function<void(std::string)> callback;
std::function<void(StringRef)> callback;
bool once;
// Dynamic storage for external components using std::string API (custom_api_device.h)
@@ -201,14 +205,20 @@ class APIServer : public Component,
};
// New const char* overload (for internal components - zero allocation)
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(std::string)> f);
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(std::string)> f);
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
// Existing std::string overload (for custom_api_device.h - heap allocation)
// std::string overload with StringRef callback (for custom_api_device.h with zero-allocation callback)
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
std::function<void(StringRef)> f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
std::function<void(StringRef)> f);
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string for callback)
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
#endif
@@ -232,10 +242,12 @@ class APIServer : public Component,
#endif // USE_API_NOISE
#ifdef USE_API_HOMEASSISTANT_STATES
// Helper methods to reduce code duplication
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(std::string)> f,
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
bool once);
void add_state_subscription_(std::string entity_id, optional<std::string> attribute, std::function<void(StringRef)> f,
bool once);
void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f, bool once);
std::function<void(const std::string &)> f, bool once);
#endif // USE_API_HOMEASSISTANT_STATES
// Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr;
@@ -252,6 +264,9 @@ class APIServer : public Component,
// Vectors and strings (12 bytes each on 32-bit)
std::vector<std::unique_ptr<APIConnection>> clients_;
#ifdef USE_API_PASSWORD
std::string password_;
#endif
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
#ifdef USE_API_HOMEASSISTANT_STATES
std::vector<HomeAssistantStateSubscription> state_subs_;

View File

@@ -16,7 +16,7 @@ with warnings.catch_warnings():
import contextlib
from esphome.const import CONF_KEY, CONF_PORT, __version__
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__
from esphome.core import CORE
from . import CONF_ENCRYPTION
@@ -35,6 +35,7 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
conf = config["api"]
name = config["esphome"]["name"]
port: int = int(conf[CONF_PORT])
password: str = conf[CONF_PASSWORD]
noise_psk: str | None = None
if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)):
noise_psk = key
@@ -49,7 +50,7 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
cli = APIClient(
addresses[0], # Primary address for compatibility
port,
"", # Password auth removed in 2026.1.0
password,
client_info=f"ESPHome Logs {__version__}",
noise_psk=noise_psk,
addresses=addresses, # Pass all addresses for automatic retry

View File

@@ -122,17 +122,29 @@ class CustomAPIDevice {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "climate.kitchen", "current_temperature");
* }
*
* void on_state_changed(std::string state) {
* // State of sensor.weather_forecast is `state`
* void on_state_changed(StringRef state) {
* // State of climate.kitchen current_temperature is `state`
* // Use state.c_str() for C string, state.str() for std::string
* }
* ```
*
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes.
* @param callback The member function to call when the entity state changes (zero-allocation).
* @param entity_id The entity_id to track.
* @param attribute The entity state attribute to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
}
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
*
* @deprecated Use the StringRef overload for zero-allocation callbacks.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
@@ -148,23 +160,42 @@ class CustomAPIDevice {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
* }
*
* void on_state_changed(std::string entity_id, std::string state) {
* void on_state_changed(const std::string &entity_id, StringRef state) {
* // State of `entity_id` is `state`
* }
* ```
*
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes.
* @param callback The member function to call when the entity state changes (zero-allocation for state).
* @param entity_id The entity_id to track.
* @param attribute The entity state attribute to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
}
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
*
* @deprecated Use the StringRef overload for zero-allocation callbacks.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
}
#else
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id,
const std::string &attribute = "") {
static_assert(sizeof(T) == 0,
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
"of your YAML configuration");
}
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
const std::string &attribute = "") {
@@ -173,6 +204,14 @@ class CustomAPIDevice {
"of your YAML configuration");
}
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id,
const std::string &attribute = "") {
static_assert(sizeof(T) == 0,
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
"of your YAML configuration");
}
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
const std::string &attribute = "") {

View File

@@ -67,10 +67,10 @@ template<typename... Ts> class TemplatableKeyValuePair {
// the callback is invoked synchronously while the message is on the stack).
class ActionResponse {
public:
ActionResponse(bool success, StringRef error_message) : success_(success), error_message_(error_message) {}
ActionResponse(bool success, const std::string &error_message) : success_(success), error_message_(error_message) {}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
ActionResponse(bool success, StringRef error_message, const uint8_t *data, size_t data_len)
ActionResponse(bool success, const std::string &error_message, const uint8_t *data, size_t data_len)
: success_(success), error_message_(error_message) {
if (data == nullptr || data_len == 0)
return;

View File

@@ -833,6 +833,9 @@ class ProtoService {
virtual bool is_authenticated() = 0;
virtual bool is_connection_setup() = 0;
virtual void on_fatal_error() = 0;
#ifdef USE_API_PASSWORD
virtual void on_unauthenticated_access() = 0;
#endif
virtual void on_no_setup_connection() = 0;
/**
* Create a buffer with a reserved size.
@@ -870,7 +873,20 @@ class ProtoService {
return true;
}
inline bool check_authenticated_() { return this->check_connection_setup_(); }
inline bool check_authenticated_() {
#ifdef USE_API_PASSWORD
if (!this->check_connection_setup_()) {
return false;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return false;
}
return true;
#else
return this->check_connection_setup_();
#endif
}
};
} // namespace esphome::api

View File

@@ -255,7 +255,7 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
bool return_response = std::get<1>(args);
if (!return_response) {
// Client doesn't want response data, just send success/error
this->parent_->send_action_response(call_id, success, StringRef(error_message));
this->parent_->send_action_response(call_id, success, error_message);
return;
}
}
@@ -265,12 +265,12 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
json::JsonBuilder builder;
this->json_builder_(x..., builder.root());
std::string json_str = builder.serialize();
this->parent_->send_action_response(call_id, success, StringRef(error_message),
this->parent_->send_action_response(call_id, success, error_message,
reinterpret_cast<const uint8_t *>(json_str.data()), json_str.size());
return;
}
#endif
this->parent_->send_action_response(call_id, success, StringRef(error_message));
this->parent_->send_action_response(call_id, success, error_message);
}
protected:

View File

@@ -7,12 +7,8 @@
#include "esphome/core/automation.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
// Maximum bytes to log in hex format for BLE writes (many logging buffers are 256 chars)
static constexpr size_t BLE_WRITE_MAX_LOG_BYTES = 64;
namespace esphome::ble_client {
// placeholder class for static TAG .
@@ -155,10 +151,7 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
return false;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(BLE_WRITE_MAX_LOG_BYTES)];
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty_to(hex_buf, data, len));
#endif
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str());
esp_err_t err =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);

View File

@@ -1,36 +0,0 @@
import esphome.codegen as cg
from esphome.components import esp32_ble_tracker
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MAC_ADDRESS
CODEOWNERS = ["@nagyrobi"]
DEPENDENCIES = ["esp32_ble_tracker"]
BLE_DEVICE_SCHEMA = esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA
bthome_mithermometer_ns = cg.esphome_ns.namespace("bthome_mithermometer")
BTHomeMiThermometer = bthome_mithermometer_ns.class_(
"BTHomeMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
)
def bthome_mithermometer_base_schema(extra_schema=None):
if extra_schema is None:
extra_schema = {}
return (
cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(BTHomeMiThermometer),
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
}
)
.extend(BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
.extend(extra_schema)
)
async def setup_bthome_mithermometer(var, config):
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))

View File

@@ -1,298 +0,0 @@
#include "bthome_ble.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <array>
#ifdef USE_ESP32
namespace esphome {
namespace bthome_mithermometer {
static const char *const TAG = "bthome_mithermometer";
static std::string format_mac_address(uint64_t address) {
std::array<uint8_t, MAC_ADDRESS_SIZE> mac{};
for (size_t i = 0; i < MAC_ADDRESS_SIZE; i++) {
mac[i] = (address >> ((MAC_ADDRESS_SIZE - 1 - i) * 8)) & 0xFF;
}
char buffer[MAC_ADDRESS_SIZE * 3];
format_mac_addr_upper(mac.data(), buffer);
return buffer;
}
static bool get_bthome_value_length(uint8_t obj_type, size_t &value_length) {
switch (obj_type) {
case 0x00: // packet id
case 0x01: // battery
case 0x09: // count (uint8)
case 0x0F: // generic boolean
case 0x10: // power (bool)
case 0x11: // opening
case 0x15: // battery low
case 0x16: // battery charging
case 0x17: // carbon monoxide
case 0x18: // cold
case 0x19: // connectivity
case 0x1A: // door
case 0x1B: // garage door
case 0x1C: // gas
case 0x1D: // heat
case 0x1E: // light
case 0x1F: // lock
case 0x20: // moisture
case 0x21: // motion
case 0x22: // moving
case 0x23: // occupancy
case 0x24: // plug
case 0x25: // presence
case 0x26: // problem
case 0x27: // running
case 0x28: // safety
case 0x29: // smoke
case 0x2A: // sound
case 0x2B: // tamper
case 0x2C: // vibration
case 0x2D: // water leak
case 0x2E: // humidity (uint8)
case 0x2F: // moisture (uint8)
case 0x46: // UV index
case 0x57: // temperature (sint8)
case 0x58: // temperature (0.35C step)
case 0x59: // count (sint8)
case 0x60: // channel
value_length = 1;
return true;
case 0x02: // temperature (0.01C)
case 0x03: // humidity
case 0x06: // mass (kg)
case 0x07: // mass (lb)
case 0x08: // dewpoint
case 0x0C: // voltage (mV)
case 0x0D: // pm2.5
case 0x0E: // pm10
case 0x12: // CO2
case 0x13: // TVOC
case 0x14: // moisture
case 0x3D: // count (uint16)
case 0x3F: // rotation
case 0x40: // distance (mm)
case 0x41: // distance (m)
case 0x43: // current (A)
case 0x44: // speed
case 0x45: // temperature (0.1C)
case 0x47: // volume (L)
case 0x48: // volume (mL)
case 0x49: // volume flow rate
case 0x4A: // voltage (0.1V)
case 0x51: // acceleration
case 0x52: // gyroscope
case 0x56: // conductivity
case 0x5A: // count (sint16)
case 0x5D: // current (sint16)
case 0x5E: // direction
case 0x5F: // precipitation
case 0x61: // rotational speed
case 0xF0: // button event
value_length = 2;
return true;
case 0x04: // pressure
case 0x05: // illuminance
case 0x0A: // energy
case 0x0B: // power
case 0x42: // duration
case 0x4B: // gas (uint24)
case 0xF2: // firmware version (uint24)
value_length = 3;
return true;
case 0x3E: // count (uint32)
case 0x4C: // gas (uint32)
case 0x4D: // energy (uint32)
case 0x4E: // volume (uint32)
case 0x4F: // water (uint32)
case 0x50: // timestamp
case 0x55: // volume storage
case 0x5B: // count (sint32)
case 0x5C: // power (sint32)
case 0x62: // speed (sint32)
case 0x63: // acceleration (sint32)
case 0xF1: // firmware version (uint32)
value_length = 4;
return true;
default:
return false;
}
}
void BTHomeMiThermometer::dump_config() {
ESP_LOGCONFIG(TAG, "BTHome MiThermometer");
ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(this->address_).c_str());
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
LOG_SENSOR(" ", "Signal Strength", this->signal_strength_);
}
bool BTHomeMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
bool matched = false;
for (auto &service_data : device.get_service_datas()) {
if (this->handle_service_data_(service_data, device)) {
matched = true;
}
}
if (matched && this->signal_strength_ != nullptr) {
this->signal_strength_->publish_state(device.get_rssi());
}
return matched;
}
bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceData &service_data,
const esp32_ble_tracker::ESPBTDevice &device) {
if (!service_data.uuid.contains(0xD2, 0xFC)) {
return false;
}
const auto &data = service_data.data;
if (data.size() < 2) {
ESP_LOGVV(TAG, "BTHome data too short: %zu", data.size());
return false;
}
const uint8_t adv_info = data[0];
const bool is_encrypted = adv_info & 0x01;
const bool mac_included = adv_info & 0x02;
const bool is_trigger_based = adv_info & 0x04;
const uint8_t version = (adv_info >> 5) & 0x07;
if (version != 0x02) {
ESP_LOGVV(TAG, "Unsupported BTHome version %u", version);
return false;
}
if (is_encrypted) {
ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str().c_str());
return false;
}
size_t payload_index = 1;
uint64_t source_address = device.address_uint64();
if (mac_included) {
if (data.size() < 7) {
ESP_LOGVV(TAG, "BTHome payload missing MAC address");
return false;
}
source_address = 0;
for (int i = 5; i >= 0; i--) {
source_address = (source_address << 8) | data[1 + i];
}
payload_index = 7;
}
if (source_address != this->address_) {
ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(source_address).c_str());
return false;
}
if (payload_index >= data.size()) {
ESP_LOGVV(TAG, "BTHome payload empty after header");
return false;
}
bool reported = false;
size_t offset = payload_index;
uint8_t last_type = 0;
while (offset < data.size()) {
const uint8_t obj_type = data[offset++];
size_t value_length = 0;
bool has_length_byte = obj_type == 0x53; // text objects include explicit length
if (has_length_byte) {
if (offset >= data.size()) {
break;
}
value_length = data[offset++];
} else {
if (!get_bthome_value_length(obj_type, value_length)) {
ESP_LOGVV(TAG, "Unknown BTHome object 0x%02X", obj_type);
break;
}
}
if (value_length == 0) {
break;
}
if (offset + value_length > data.size()) {
ESP_LOGVV(TAG, "BTHome object length exceeds payload");
break;
}
const uint8_t *value = &data[offset];
offset += value_length;
if (obj_type < last_type) {
ESP_LOGVV(TAG, "BTHome objects not in ascending order");
}
last_type = obj_type;
switch (obj_type) {
case 0x00: { // packet id
const uint8_t packet_id = value[0];
if (this->last_packet_id_.has_value() && *this->last_packet_id_ == packet_id) {
return reported;
}
this->last_packet_id_ = packet_id;
break;
}
case 0x01: { // battery percentage
if (this->battery_level_ != nullptr) {
this->battery_level_->publish_state(value[0]);
reported = true;
}
break;
}
case 0x0C: { // battery voltage (mV)
if (this->battery_voltage_ != nullptr) {
const uint16_t raw = encode_uint16(value[1], value[0]);
this->battery_voltage_->publish_state(raw * 0.001f);
reported = true;
}
break;
}
case 0x02: { // temperature
if (this->temperature_ != nullptr) {
const int16_t raw = encode_uint16(value[1], value[0]);
this->temperature_->publish_state(raw * 0.01f);
reported = true;
}
break;
}
case 0x03: { // humidity
if (this->humidity_ != nullptr) {
const uint16_t raw = encode_uint16(value[1], value[0]);
this->humidity_->publish_state(raw * 0.01f);
reported = true;
}
break;
}
default:
break;
}
}
if (reported) {
ESP_LOGD(TAG, "BTHome data%sfrom %s", is_trigger_based ? " (triggered) " : " ", device.address_str().c_str());
}
return reported;
}
} // namespace bthome_mithermometer
} // namespace esphome
#endif

View File

@@ -1,44 +0,0 @@
#pragma once
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include <cstdint>
#ifdef USE_ESP32
namespace esphome {
namespace bthome_mithermometer {
class BTHomeMiThermometer : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
public:
void set_address(uint64_t address) { this->address_ = address; }
void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; }
void set_battery_level(sensor::Sensor *battery_level) { this->battery_level_ = battery_level; }
void set_battery_voltage(sensor::Sensor *battery_voltage) { this->battery_voltage_ = battery_voltage; }
void set_signal_strength(sensor::Sensor *signal_strength) { this->signal_strength_ = signal_strength; }
void dump_config() override;
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
protected:
bool handle_service_data_(const esp32_ble_tracker::ServiceData &service_data,
const esp32_ble_tracker::ESPBTDevice &device);
uint64_t address_{0};
optional<uint8_t> last_packet_id_{};
sensor::Sensor *temperature_{nullptr};
sensor::Sensor *humidity_{nullptr};
sensor::Sensor *battery_level_{nullptr};
sensor::Sensor *battery_voltage_{nullptr};
sensor::Sensor *signal_strength_{nullptr};
};
} // namespace bthome_mithermometer
} // namespace esphome
#endif

View File

@@ -1,88 +0,0 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_BATTERY_LEVEL,
CONF_BATTERY_VOLTAGE,
CONF_HUMIDITY,
CONF_ID,
CONF_SIGNAL_STRENGTH,
CONF_TEMPERATURE,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_DECIBEL_MILLIWATT,
UNIT_PERCENT,
UNIT_VOLT,
)
from . import bthome_mithermometer_base_schema, setup_bthome_mithermometer
CODEOWNERS = ["@nagyrobi"]
DEPENDENCIES = ["esp32_ble_tracker"]
CONFIG_SCHEMA = bthome_mithermometer_base_schema(
{
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
icon="mdi:battery-plus",
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema(
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await setup_bthome_mithermometer(var, config)
if temp_sens := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temp_sens)
cg.add(var.set_temperature(sens))
if humi_sens := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(humi_sens)
cg.add(var.set_humidity(sens))
if batl_sens := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(batl_sens)
cg.add(var.set_battery_level(sens))
if batv_sens := config.get(CONF_BATTERY_VOLTAGE):
sens = await sensor.new_sensor(batv_sens)
cg.add(var.set_battery_voltage(sens))
if sgnl_sens := config.get(CONF_SIGNAL_STRENGTH):
sens = await sensor.new_sensor(sgnl_sens)
cg.add(var.set_signal_strength(sens))

View File

@@ -47,10 +47,7 @@ struct DNSAnswer {
void DNSServer::start(const network::IPAddress &ip) {
this->server_ip_ = ip;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGV(TAG, "Starting DNS server on %s", ip.str_to(ip_buf));
#endif
ESP_LOGV(TAG, "Starting DNS server on %s", ip.str().c_str());
// Create loop-monitored UDP socket
this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP);

View File

@@ -128,9 +128,7 @@ void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->
bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; }
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); }
size_t CH422GGPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "EXIO%u via CH422G", this->pin_);
}
std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); }
void CH422GGPIOPin::set_flags(gpio::Flags flags) {
flags_ = flags;
this->parent_->pin_mode(this->pin_, flags);

View File

@@ -50,7 +50,7 @@ class CH422GGPIOPin : public GPIOPin {
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
size_t dump_summary(char *buffer, size_t len) const override;
std::string dump_summary() const override;
void set_parent(CH422GComponent *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }

View File

@@ -1,13 +1,11 @@
#include "cse7766.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace cse7766 {
static const char *const TAG = "cse7766";
static constexpr size_t CSE7766_RAW_DATA_SIZE = 24;
void CSE7766Component::loop() {
const uint32_t now = App.get_loop_component_start_time();
@@ -72,8 +70,8 @@ bool CSE7766Component::check_byte_() {
void CSE7766Component::parse_data_() {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{
char hex_buf[format_hex_pretty_size(CSE7766_RAW_DATA_SIZE)];
ESP_LOGVV(TAG, "Raw data: %s", format_hex_pretty_to(hex_buf, this->raw_data_, sizeof(this->raw_data_)));
std::string s = format_hex_pretty(this->raw_data_, sizeof(this->raw_data_));
ESP_LOGVV(TAG, "Raw data: %s", s.c_str());
}
#endif

View File

@@ -7,9 +7,6 @@ namespace ee895 {
static const char *const TAG = "ee895";
// Serial number is 16 bytes
static constexpr size_t EE895_SERIAL_NUMBER_SIZE = 16;
static const uint16_t CRC16_ONEWIRE_START = 0xFFFF;
static const uint8_t FUNCTION_CODE_READ = 0x03;
static const uint16_t SERIAL_NUMBER = 0x0000;
@@ -29,10 +26,7 @@ void EE895Component::setup() {
this->mark_failed();
return;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char serial_hex[format_hex_size(EE895_SERIAL_NUMBER_SIZE)];
#endif
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex_to(serial_hex, serial_number + 2, EE895_SERIAL_NUMBER_SIZE));
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(serial_number + 2, 16).c_str());
}
void EE895Component::dump_config() {

View File

@@ -7,7 +7,6 @@
namespace esphome::epaper_spi {
static const char *const TAG = "epaper_spi";
static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128;
static constexpr const char *const EPAPER_STATE_STRINGS[] = {
"IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
@@ -69,11 +68,8 @@ void EPaperBase::data(uint8_t value) {
// The command is the first byte, length is the length of data only in the second byte, followed by the data.
// [COMMAND, LENGTH, DATA...]
void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)];
ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
format_hex_pretty_to(hex_buf, ptr, length, '.'));
#endif
format_hex_pretty(ptr, length, '.', false).c_str());
this->dc_pin_->digital_write(false);
this->enable();

View File

@@ -76,12 +76,6 @@ class EPaperBase : public Display,
return 0;
}
void fill(Color color) override {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
auto pixel_color = color_to_bit(color) ? 0xFF : 0x00;
// We store 8 pixels per byte

View File

@@ -97,12 +97,6 @@ void EPaperSpectraE6::deep_sleep() {
}
void EPaperSpectraE6::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
EPaperBase::fill(color);
return;
}
auto pixel_color = color_to_hex(color);
// We store 2 pixels per byte

View File

@@ -85,7 +85,6 @@ CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback"
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
CONF_MINIMUM_CHIP_REVISION = "minimum_chip_revision"
CONF_RELEASE = "release"
LOG_LEVELS_IDF = [
@@ -110,21 +109,6 @@ COMPILER_OPTIMIZATIONS = {
"SIZE": "CONFIG_COMPILER_OPTIMIZATION_SIZE",
}
# ESP32 (original) chip revision options
# Setting minimum revision to 3.0 or higher:
# - Reduces flash size by excluding workaround code for older chip bugs
# - For PSRAM users: disables CONFIG_SPIRAM_CACHE_WORKAROUND, which saves significant
# IRAM by keeping C library functions in ROM instead of recompiling them
# See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/chip_revision.html
ESP32_CHIP_REVISIONS = {
"0.0": "CONFIG_ESP32_REV_MIN_0",
"1.0": "CONFIG_ESP32_REV_MIN_1",
"1.1": "CONFIG_ESP32_REV_MIN_1_1",
"2.0": "CONFIG_ESP32_REV_MIN_2",
"3.0": "CONFIG_ESP32_REV_MIN_3",
"3.1": "CONFIG_ESP32_REV_MIN_3_1",
}
# Socket limit configuration for ESP-IDF
# ESP-IDF CONFIG_LWIP_MAX_SOCKETS has range 1-253, default 10
DEFAULT_MAX_SOCKETS = 10 # ESP-IDF default
@@ -582,16 +566,6 @@ def final_validate(config):
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_IGNORE_EFUSE_MAC_CRC],
)
)
if (
config[CONF_VARIANT] != VARIANT_ESP32
and advanced.get(CONF_MINIMUM_CHIP_REVISION) is not None
):
errs.append(
cv.Invalid(
f"'{CONF_MINIMUM_CHIP_REVISION}' is only supported on {VARIANT_ESP32}",
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_MINIMUM_CHIP_REVISION],
)
)
if advanced[CONF_EXECUTE_FROM_PSRAM]:
if config[CONF_VARIANT] != VARIANT_ESP32S3:
errs.append(
@@ -720,9 +694,6 @@ FRAMEWORK_SCHEMA = cv.Schema(
cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean,
cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean,
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
cv.Optional(CONF_MINIMUM_CHIP_REVISION): cv.one_of(
*ESP32_CHIP_REVISIONS
),
# DHCP server is needed for WiFi AP mode. When WiFi component is used,
# it will handle disabling DHCP server when AP is not configured.
# Default to false (disabled) when WiFi is not used.
@@ -1046,16 +1017,6 @@ async def to_code(config):
add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
)
# Set minimum chip revision for ESP32 variant
# Setting this to 3.0 or higher reduces flash size by excluding workaround code,
# and for PSRAM users saves significant IRAM by keeping C library functions in ROM.
if variant == VARIANT_ESP32:
min_rev = conf[CONF_ADVANCED].get(CONF_MINIMUM_CHIP_REVISION)
if min_rev is not None:
for rev, flag in ESP32_CHIP_REVISIONS.items():
add_idf_sdkconfig_option(flag, rev == min_rev)
cg.add_define("USE_ESP32_MIN_CHIP_REVISION_SET")
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv")

View File

@@ -97,8 +97,10 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
gpio_isr_handler_add(this->get_pin_num(), func, arg);
}
size_t ESP32InternalGPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "GPIO%" PRIu32, static_cast<uint32_t>(this->pin_));
std::string ESP32InternalGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast<uint32_t>(this->pin_));
return buffer;
}
void ESP32InternalGPIOPin::setup() {

View File

@@ -24,7 +24,7 @@ class ESP32InternalGPIOPin : public InternalGPIOPin {
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
size_t dump_summary(char *buffer, size_t len) const override;
std::string dump_summary() const override;
void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return this->pin_; }

View File

@@ -37,9 +37,6 @@ namespace esphome::esp32_ble_tracker {
static const char *const TAG = "esp32_ble_tracker";
// BLE advertisement max: 31 bytes adv data + 31 bytes scan response
static constexpr size_t BLE_ADV_MAX_LOG_BYTES = 62;
ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
const char *client_state_to_string(ClientState state) {
@@ -448,7 +445,6 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
uuid.to_str(uuid_buf);
ESP_LOGVV(TAG, " Service UUID: %s", uuid_buf);
}
char hex_buf[format_hex_pretty_size(BLE_ADV_MAX_LOG_BYTES)];
for (auto &data : this->manufacturer_datas_) {
auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data);
if (ibeacon.has_value()) {
@@ -462,8 +458,7 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
} else {
char uuid_buf[esp32_ble::UUID_STR_LEN];
data.uuid.to_str(uuid_buf);
ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", uuid_buf,
format_hex_pretty_to(hex_buf, data.data.data(), data.data.size()));
ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", uuid_buf, format_hex_pretty(data.data).c_str());
}
}
for (auto &data : this->service_datas_) {
@@ -471,11 +466,11 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
char uuid_buf[esp32_ble::UUID_STR_LEN];
data.uuid.to_str(uuid_buf);
ESP_LOGVV(TAG, " UUID: %s", uuid_buf);
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size()));
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
}
ESP_LOGVV(TAG, " Adv data: %s",
format_hex_pretty_to(hex_buf, scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len));
format_hex_pretty(scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len).c_str());
#endif
}

View File

@@ -3,7 +3,7 @@ import esphome.codegen as cg
from esphome.components import binary_sensor, esp32_ble, improv_base, output
from esphome.components.esp32_ble import BTLoggers
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_ON_START, CONF_ON_STATE, CONF_TRIGGER_ID
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID
AUTO_LOAD = ["esp32_ble_server", "improv_base"]
CODEOWNERS = ["@jesserockz"]
@@ -15,6 +15,7 @@ CONF_BLE_SERVER_ID = "ble_server_id"
CONF_IDENTIFY_DURATION = "identify_duration"
CONF_ON_PROVISIONED = "on_provisioned"
CONF_ON_PROVISIONING = "on_provisioning"
CONF_ON_START = "on_start"
CONF_ON_STOP = "on_stop"
CONF_STATUS_INDICATOR = "status_indicator"
CONF_WIFI_TIMEOUT = "wifi_timeout"

View File

@@ -4,7 +4,6 @@
#include "esphome/components/esp32_ble/ble.h"
#include "esphome/components/esp32_ble_server/ble_2902.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@@ -15,7 +14,6 @@ namespace esp32_improv {
using namespace bytebuffer;
static const char *const TAG = "esp32_improv.component";
static constexpr size_t IMPROV_MAX_LOG_BYTES = 128;
static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
static constexpr uint16_t STOP_ADVERTISING_DELAY =
10000; // Delay (ms) before stopping service to allow BLE clients to read the final state
@@ -316,11 +314,7 @@ void ESP32ImprovComponent::dump_config() {
void ESP32ImprovComponent::process_incoming_data_() {
uint8_t length = this->incoming_data_[1];
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(IMPROV_MAX_LOG_BYTES)];
ESP_LOGV(TAG, "Processing bytes - %s",
format_hex_pretty_to(hex_buf, this->incoming_data_.data(), this->incoming_data_.size()));
#endif
ESP_LOGV(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
if (this->incoming_data_.size() - 3 == length) {
this->set_error_(improv::ERROR_NONE);
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
@@ -409,12 +403,8 @@ void ESP32ImprovComponent::check_wifi_connection_() {
#ifdef USE_WEBSERVER
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
if (ip.is_ip4()) {
// "http://" (7) + IPv4 max (15) + ":" (1) + port max (5) + null = 29
char url_buffer[32];
memcpy(url_buffer, "http://", 7); // NOLINT(bugprone-not-null-terminated-result) - str_to null-terminates
ip.str_to(url_buffer + 7);
size_t len = strlen(url_buffer);
snprintf(url_buffer + len, sizeof(url_buffer) - len, ":%d", USE_WEBSERVER_PORT);
char url_buffer[64];
snprintf(url_buffer, sizeof(url_buffer), "http://%s:%d", ip.str().c_str(), USE_WEBSERVER_PORT);
url_strings[url_count++] = url_buffer;
break;
}

View File

@@ -28,7 +28,6 @@ from .const import (
KEY_ESP8266,
KEY_FLASH_SIZE,
KEY_PIN_INITIAL_STATES,
KEY_WAVEFORM_REQUIRED,
esp8266_ns,
)
from .gpio import PinInitialState, add_pin_initial_states_array
@@ -193,12 +192,7 @@ async def to_code(config):
cg.add_platformio_option(
"extra_scripts",
[
"pre:testing_mode.py",
"pre:exclude_updater.py",
"pre:exclude_waveform.py",
"post:post_build.py",
],
["pre:testing_mode.py", "pre:exclude_updater.py", "post:post_build.py"],
)
conf = config[CONF_FRAMEWORK]
@@ -270,24 +264,10 @@ async def to_code(config):
cg.add_platformio_option("board_build.ldscript", ld_script)
CORE.add_job(add_pin_initial_states_array)
CORE.add_job(finalize_waveform_config)
@coroutine_with_priority(CoroPriority.WORKAROUNDS)
async def finalize_waveform_config() -> None:
"""Add waveform stubs define if waveform is not required.
This runs at WORKAROUNDS priority (-999) to ensure all components
have had a chance to call require_waveform() first.
"""
if not CORE.data.get(KEY_ESP8266, {}).get(KEY_WAVEFORM_REQUIRED, False):
# No component needs waveform - enable stubs and exclude Arduino waveform code
# Use build flag (visible to both C++ code and PlatformIO script)
cg.add_build_flag("-DUSE_ESP8266_WAVEFORM_STUBS")
# Called by writer.py
def copy_files() -> None:
def copy_files():
dir = Path(__file__).parent
post_build_file = dir / "post_build.py.script"
copy_file_if_changed(
@@ -304,8 +284,3 @@ def copy_files() -> None:
exclude_updater_file,
CORE.relative_build_path("exclude_updater.py"),
)
exclude_waveform_file = dir / "exclude_waveform.py.script"
copy_file_if_changed(
exclude_waveform_file,
CORE.relative_build_path("exclude_waveform.py"),
)

View File

@@ -1,5 +1,4 @@
import esphome.codegen as cg
from esphome.core import CORE
KEY_ESP8266 = "esp8266"
KEY_BOARD = "board"
@@ -7,25 +6,6 @@ KEY_PIN_INITIAL_STATES = "pin_initial_states"
CONF_RESTORE_FROM_FLASH = "restore_from_flash"
CONF_EARLY_PIN_INIT = "early_pin_init"
KEY_FLASH_SIZE = "flash_size"
KEY_WAVEFORM_REQUIRED = "waveform_required"
# esp8266 namespace is already defined by arduino, manually prefix esphome
esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266")
def require_waveform() -> None:
"""Mark that Arduino waveform/PWM support is required.
Call this from components that need the Arduino waveform generator
(startWaveform, stopWaveform, analogWrite, Tone, Servo).
If no component calls this, the waveform code is excluded from the build
to save ~596 bytes of RAM and 464 bytes of flash.
Example:
from esphome.components.esp8266.const import require_waveform
async def to_code(config):
require_waveform()
"""
CORE.data.setdefault(KEY_ESP8266, {})[KEY_WAVEFORM_REQUIRED] = True

View File

@@ -1,50 +0,0 @@
# pylint: disable=E0602
Import("env") # noqa
import os
# Filter out waveform/PWM code from the Arduino core build
# This saves ~596 bytes of RAM and 464 bytes of flash by not
# instantiating the waveform generator state structures (wvfState + pwmState).
#
# The waveform code is used by: analogWrite, Tone, Servo, and direct
# startWaveform/stopWaveform calls. ESPHome's esp8266_pwm component
# calls require_waveform() to keep this code when needed.
#
# When excluded, we provide stub implementations of stopWaveform() and
# _stopPWM() since digitalWrite() calls these unconditionally.
def has_define_flag(env, name):
"""Check if a define exists in the build flags."""
define_flag = f"-D{name}"
# Check BUILD_FLAGS (where ESPHome puts its defines)
for flag in env.get("BUILD_FLAGS", []):
if flag == define_flag or flag.startswith(f"{define_flag}="):
return True
# Also check CPPDEFINES list (parsed defines)
for define in env.get("CPPDEFINES", []):
if isinstance(define, tuple):
if define[0] == name:
return True
elif define == name:
return True
return False
# USE_ESP8266_WAVEFORM_STUBS is defined when no component needs waveform
if has_define_flag(env, "USE_ESP8266_WAVEFORM_STUBS"):
def filter_waveform_from_core(env, node):
"""Filter callback to exclude waveform files from framework build."""
path = node.get_path()
filename = os.path.basename(path)
if filename in (
"core_esp8266_waveform_pwm.cpp",
"core_esp8266_waveform_phase.cpp",
):
print(f"ESPHome: Excluding {filename} from build (waveform not required)")
return None
return node
# Apply the filter to framework sources
env.AddBuildMiddleware(filter_waveform_from_core, "**/cores/esp8266/*.cpp")

View File

@@ -98,8 +98,10 @@ void ESP8266GPIOPin::pin_mode(gpio::Flags flags) {
pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT
}
size_t ESP8266GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "GPIO%u", this->pin_);
std::string ESP8266GPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
return buffer;
}
bool ESP8266GPIOPin::digital_read() {

View File

@@ -17,7 +17,7 @@ class ESP8266GPIOPin : public InternalGPIOPin {
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
size_t dump_summary(char *buffer, size_t len) const override;
std::string dump_summary() const override;
void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return pin_; }

View File

@@ -1,34 +0,0 @@
#ifdef USE_ESP8266_WAVEFORM_STUBS
// Stub implementations for Arduino waveform/PWM functions.
//
// When the waveform generator is not needed (no esp8266_pwm component),
// we exclude core_esp8266_waveform_pwm.cpp from the build to save ~596 bytes
// of RAM and 464 bytes of flash.
//
// These stubs satisfy calls from the Arduino GPIO code when the real
// waveform implementation is excluded. They must be in the global namespace
// with C linkage to match the Arduino core function declarations.
#include <cstdint>
// Empty namespace to satisfy linter - actual stubs must be at global scope
namespace esphome::esp8266 {} // namespace esphome::esp8266
extern "C" {
// Called by Arduino GPIO code to stop any waveform on a pin
int stopWaveform(uint8_t pin) {
(void) pin;
return 1; // Success (no waveform to stop)
}
// Called by Arduino GPIO code to stop any PWM on a pin
bool _stopPWM(uint8_t pin) {
(void) pin;
return false; // No PWM was running
}
} // extern "C"
#endif // USE_ESP8266_WAVEFORM_STUBS

View File

@@ -1,7 +1,6 @@
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import output
from esphome.components.esp8266.const import require_waveform
import esphome.config_validation as cv
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_NUMBER, CONF_PIN
@@ -35,9 +34,7 @@ CONFIG_SCHEMA = cv.All(
)
async def to_code(config) -> None:
require_waveform()
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await output.register_output(var, config)

View File

@@ -16,7 +16,7 @@ from esphome.const import (
CONF_SAFE_MODE,
CONF_VERSION,
)
from esphome.core import coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.coroutine import CoroPriority
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -28,7 +28,17 @@ CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"]
AUTO_LOAD = ["sha256", "socket"]
def supports_sha256() -> bool:
"""Check if the current platform supports SHA256 for OTA authentication."""
return bool(CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny)
def AUTO_LOAD() -> list[str]:
"""Conditionally auto-load sha256 only on platforms that support it."""
base_components = ["md5", "socket"]
if supports_sha256():
return base_components + ["sha256"]
return base_components
esphome = cg.esphome_ns.namespace("esphome")
@@ -145,6 +155,11 @@ async def to_code(config: ConfigType) -> None:
if config.get(CONF_PASSWORD):
cg.add(var.set_auth_password(config[CONF_PASSWORD]))
cg.add_define("USE_OTA_PASSWORD")
# Only include hash algorithms when password is configured
cg.add_define("USE_OTA_MD5")
# Only include SHA256 support on platforms that have it
if supports_sha256():
cg.add_define("USE_OTA_SHA256")
cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
await cg.register_component(var, config)

View File

@@ -1,8 +1,13 @@
#include "ota_esphome.h"
#ifdef USE_OTA
#ifdef USE_OTA_PASSWORD
#ifdef USE_OTA_MD5
#include "esphome/components/md5/md5.h"
#endif
#ifdef USE_OTA_SHA256
#include "esphome/components/sha256/sha256.h"
#endif
#endif
#include "esphome/components/network/util.h"
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_esp8266.h"
@@ -26,6 +31,15 @@ static constexpr size_t OTA_BUFFER_SIZE = 1024; // buffer size
static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds for initial handshake
static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer
#ifdef USE_OTA_PASSWORD
#ifdef USE_OTA_MD5
static constexpr size_t MD5_HEX_SIZE = 32; // MD5 hash as hex string (16 bytes * 2)
#endif
#ifdef USE_OTA_SHA256
static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2)
#endif
#endif // USE_OTA_PASSWORD
void ESPHomeOTAComponent::setup() {
this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
if (this->server_ == nullptr) {
@@ -94,7 +108,15 @@ void ESPHomeOTAComponent::loop() {
}
static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
#ifdef USE_OTA_SHA256
static const uint8_t FEATURE_SUPPORTS_SHA256_AUTH = 0x02;
#endif
// Temporary flag to allow MD5 downgrade for ~3 versions (until 2026.1.0)
// This allows users to downgrade via OTA if they encounter issues after updating.
// Without this, users would need to do a serial flash to downgrade.
// TODO: Remove this flag and all associated code in 2026.1.0
#define ALLOW_OTA_DOWNGRADE_MD5
void ESPHomeOTAComponent::handle_handshake_() {
/// Handle the OTA handshake and authentication.
@@ -525,8 +547,26 @@ void ESPHomeOTAComponent::yield_and_feed_watchdog_() {
void ESPHomeOTAComponent::log_auth_warning_(const LogString *msg) { ESP_LOGW(TAG, "Auth: %s", LOG_STR_ARG(msg)); }
bool ESPHomeOTAComponent::select_auth_type_() {
#ifdef USE_OTA_SHA256
bool client_supports_sha256 = (this->ota_features_ & FEATURE_SUPPORTS_SHA256_AUTH) != 0;
#ifdef ALLOW_OTA_DOWNGRADE_MD5
// Allow fallback to MD5 if client doesn't support SHA256
if (client_supports_sha256) {
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH;
return true;
}
#ifdef USE_OTA_MD5
this->log_auth_warning_(LOG_STR("Using deprecated MD5"));
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH;
return true;
#else
this->log_auth_warning_(LOG_STR("SHA256 required"));
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
return false;
#endif // USE_OTA_MD5
#else // !ALLOW_OTA_DOWNGRADE_MD5
// Require SHA256
if (!client_supports_sha256) {
this->log_auth_warning_(LOG_STR("SHA256 required"));
@@ -535,6 +575,20 @@ bool ESPHomeOTAComponent::select_auth_type_() {
}
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH;
return true;
#endif // ALLOW_OTA_DOWNGRADE_MD5
#else // !USE_OTA_SHA256
#ifdef USE_OTA_MD5
// Only MD5 available
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH;
return true;
#else
// No auth methods available
this->log_auth_warning_(LOG_STR("No auth methods available"));
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
return false;
#endif // USE_OTA_MD5
#endif // USE_OTA_SHA256
}
bool ESPHomeOTAComponent::handle_auth_send_() {
@@ -558,12 +612,31 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
// [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
// [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// (no passing to other functions). All hash operations must happen in this function.
sha256::SHA256 hasher;
// Declare both hash objects in same stack frame, use pointer to select.
// NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3
// hardware SHA acceleration - the object must exist in this stack frame for all operations.
// Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope.
#ifdef USE_OTA_SHA256
sha256::SHA256 sha_hasher;
#endif
#ifdef USE_OTA_MD5
md5::MD5Digest md5_hasher;
#endif
HashBase *hasher = nullptr;
const size_t hex_size = hasher.get_size() * 2;
const size_t nonce_len = hasher.get_size() / 4;
#ifdef USE_OTA_SHA256
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
hasher = &sha_hasher;
}
#endif
#ifdef USE_OTA_MD5
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) {
hasher = &md5_hasher;
}
#endif
const size_t hex_size = hasher->get_size() * 2;
const size_t nonce_len = hasher->get_size() / 4;
const size_t auth_buf_size = 1 + 3 * hex_size;
this->auth_buf_ = std::make_unique<uint8_t[]>(auth_buf_size);
this->auth_buf_pos_ = 0;
@@ -575,17 +648,17 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
return false;
}
hasher.init();
hasher.add(buf, nonce_len);
hasher.calculate();
hasher->init();
hasher->add(buf, nonce_len);
hasher->calculate();
this->auth_buf_[0] = this->auth_type_;
hasher.get_hex(buf);
hasher->get_hex(buf);
ESP_LOGV(TAG, "Auth: Nonce is %.*s", hex_size, buf);
}
// Try to write auth_type + nonce
constexpr size_t hex_size = SHA256_HEX_SIZE;
size_t hex_size = this->get_auth_hex_size_();
const size_t to_write = 1 + hex_size;
size_t remaining = to_write - this->auth_buf_pos_;
@@ -607,7 +680,7 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
}
bool ESPHomeOTAComponent::handle_auth_read_() {
constexpr size_t hex_size = SHA256_HEX_SIZE;
size_t hex_size = this->get_auth_hex_size_();
const size_t to_read = hex_size * 2; // CNonce + Response
// Try to read remaining bytes (CNonce + Response)
@@ -632,25 +705,45 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
const char *cnonce = nonce + hex_size;
const char *response = cnonce + hex_size;
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// (no passing to other functions). All hash operations must happen in this function.
sha256::SHA256 hasher;
// CRITICAL ESP32-S3: Hash objects must stay in same stack frame (no passing to other functions).
// Declare both hash objects in same stack frame, use pointer to select.
// NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3
// hardware SHA acceleration - the object must exist in this stack frame for all operations.
// Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope.
#ifdef USE_OTA_SHA256
sha256::SHA256 sha_hasher;
#endif
#ifdef USE_OTA_MD5
md5::MD5Digest md5_hasher;
#endif
HashBase *hasher = nullptr;
hasher.init();
hasher.add(this->password_.c_str(), this->password_.length());
hasher.add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
hasher.calculate();
#ifdef USE_OTA_SHA256
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
hasher = &sha_hasher;
}
#endif
#ifdef USE_OTA_MD5
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) {
hasher = &md5_hasher;
}
#endif
hasher->init();
hasher->add(this->password_.c_str(), this->password_.length());
hasher->add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
hasher->calculate();
ESP_LOGV(TAG, "Auth: CNonce is %.*s", hex_size, cnonce);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char computed_hash[SHA256_HEX_SIZE + 1]; // Buffer for hex-encoded hash (max expected length + null terminator)
hasher.get_hex(computed_hash);
char computed_hash[65]; // Buffer for hex-encoded hash (max expected length + null terminator)
hasher->get_hex(computed_hash);
ESP_LOGV(TAG, "Auth: Result is %.*s", hex_size, computed_hash);
#endif
ESP_LOGV(TAG, "Auth: Response is %.*s", hex_size, response);
// Compare response
bool matches = hasher.equals_hex(response);
bool matches = hasher->equals_hex(response);
if (!matches) {
this->log_auth_warning_(LOG_STR("Password mismatch"));
@@ -664,6 +757,21 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
return true;
}
size_t ESPHomeOTAComponent::get_auth_hex_size_() const {
#ifdef USE_OTA_SHA256
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
return SHA256_HEX_SIZE;
}
#endif
#ifdef USE_OTA_MD5
return MD5_HEX_SIZE;
#else
#ifndef USE_OTA_SHA256
#error "Either USE_OTA_MD5 or USE_OTA_SHA256 must be defined when USE_OTA_PASSWORD is enabled"
#endif
#endif
}
void ESPHomeOTAComponent::cleanup_auth_() {
this->auth_buf_ = nullptr;
this->auth_buf_pos_ = 0;

View File

@@ -44,10 +44,10 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
void handle_handshake_();
void handle_data_();
#ifdef USE_OTA_PASSWORD
static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2)
bool handle_auth_send_();
bool handle_auth_read_();
bool select_auth_type_();
size_t get_auth_hex_size_() const;
void cleanup_auth_();
void log_auth_warning_(const LogString *msg);
#endif // USE_OTA_PASSWORD

View File

@@ -6,7 +6,6 @@
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <esp_event.h>
@@ -64,6 +63,18 @@ static const LogString *espnow_error_to_str(esp_err_t error) {
}
}
std::string peer_str(uint8_t *peer) {
if (peer == nullptr || peer[0] == 0) {
return "[Not Set]";
} else if (memcmp(peer, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
return "[Broadcast]";
} else if (memcmp(peer, ESPNOW_MULTICAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
return "[Multicast]";
} else {
return format_mac_address_pretty(peer);
}
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
void on_send_report(const esp_now_send_info_t *info, esp_now_send_status_t status)
#else
@@ -128,13 +139,11 @@ void ESPNowComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Disabled");
return;
}
char own_addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(this->own_address_, own_addr_buf);
ESP_LOGCONFIG(TAG,
" Own address: %s\n"
" Version: v%" PRIu32 "\n"
" Wi-Fi channel: %d",
own_addr_buf, version, this->wifi_channel_);
format_mac_address_pretty(this->own_address_).c_str(), version, this->wifi_channel_);
#ifdef USE_WIFI
ESP_LOGCONFIG(TAG, " Wi-Fi enabled: %s", YESNO(this->is_wifi_enabled()));
#endif
@@ -290,13 +299,9 @@ void ESPNowComponent::loop() {
// Intentionally left as if instead of else in case the peer is added above
if (esp_now_is_peer_exist(info.src_addr)) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char src_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
char dst_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
char hex_buf[format_hex_pretty_size(ESP_NOW_MAX_DATA_LEN)];
format_mac_addr_upper(info.src_addr, src_buf);
format_mac_addr_upper(info.des_addr, dst_buf);
ESP_LOGV(TAG, "<<< [%s -> %s] %s", src_buf, dst_buf,
format_hex_pretty_to(hex_buf, packet->packet_.receive.data, packet->packet_.receive.size));
ESP_LOGV(TAG, "<<< [%s -> %s] %s", format_mac_address_pretty(info.src_addr).c_str(),
format_mac_address_pretty(info.des_addr).c_str(),
format_hex_pretty(packet->packet_.receive.data, packet->packet_.receive.size).c_str());
#endif
if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
for (auto *handler : this->broadcasted_handlers_) {
@@ -314,9 +319,8 @@ void ESPNowComponent::loop() {
}
case ESPNowPacket::SENT: {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(packet->packet_.sent.address, addr_buf);
ESP_LOGV(TAG, ">>> [%s] %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(packet->packet_.sent.status)));
ESP_LOGV(TAG, ">>> [%s] %s", format_mac_address_pretty(packet->packet_.sent.address).c_str(),
LOG_STR_ARG(espnow_error_to_str(packet->packet_.sent.status)));
#endif
if (this->current_send_packet_ != nullptr) {
this->current_send_packet_->callback_(packet->packet_.sent.status);
@@ -403,9 +407,8 @@ void ESPNowComponent::send_() {
this->current_send_packet_ = packet;
esp_err_t err = esp_now_send(packet->address_, packet->data_, packet->size_);
if (err != ESP_OK) {
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(packet->address_, addr_buf);
ESP_LOGE(TAG, "Failed to send packet to %s - %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(err)));
ESP_LOGE(TAG, "Failed to send packet to %s - %s", format_mac_address_pretty(packet->address_).c_str(),
LOG_STR_ARG(espnow_error_to_str(err)));
if (packet->callback_ != nullptr) {
packet->callback_(err);
}
@@ -434,9 +437,8 @@ esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) {
esp_err_t err = esp_now_add_peer(&peer_info);
if (err != ESP_OK) {
char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(peer, peer_buf);
ESP_LOGE(TAG, "Failed to add peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err)));
ESP_LOGE(TAG, "Failed to add peer %s - %s", format_mac_address_pretty(peer).c_str(),
LOG_STR_ARG(espnow_error_to_str(err)));
this->status_momentary_warning("peer-add-failed");
return err;
}
@@ -464,9 +466,8 @@ esp_err_t ESPNowComponent::del_peer(const uint8_t *peer) {
if (esp_now_is_peer_exist(peer)) {
esp_err_t err = esp_now_del_peer(peer);
if (err != ESP_OK) {
char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(peer, peer_buf);
ESP_LOGE(TAG, "Failed to delete peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err)));
ESP_LOGE(TAG, "Failed to delete peer %s - %s", format_mac_address_pretty(peer).c_str(),
LOG_STR_ARG(espnow_error_to_str(err)));
this->status_momentary_warning("peer-del-failed");
return err;
}

View File

@@ -1,6 +1,5 @@
#include "ethernet_component.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
@@ -40,9 +39,6 @@ namespace ethernet {
static const char *const TAG = "ethernet";
// PHY register size for hex logging
static constexpr size_t PHY_REG_SIZE = 2;
EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *message) {
@@ -777,10 +773,7 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
uint32_t phy_control_2;
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)];
#endif
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str());
/*
* Bit 7 is `RMII Reference Clock Select`. Default is `0`.
@@ -797,8 +790,7 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed");
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s",
format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str());
}
}
#endif // USE_ETHERNET_KSZ8081

View File

@@ -3,7 +3,8 @@
#ifdef USE_ESP32
namespace esphome::ethernet_info {
namespace esphome {
namespace ethernet_info {
static const char *const TAG = "ethernet_info";
@@ -11,6 +12,7 @@ void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IP
void DNSAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo DNS Address", this); }
void MACAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo MAC Address", this); }
} // namespace esphome::ethernet_info
} // namespace ethernet_info
} // namespace esphome
#endif // USE_ESP32

View File

@@ -6,7 +6,8 @@
#ifdef USE_ESP32
namespace esphome::ethernet_info {
namespace esphome {
namespace ethernet_info {
class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor {
public:
@@ -39,27 +40,21 @@ class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextS
class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor {
public:
void update() override {
auto dns1 = ethernet::global_eth_component->get_dns_address(0);
auto dns2 = ethernet::global_eth_component->get_dns_address(1);
auto dns_one = ethernet::global_eth_component->get_dns_address(0);
auto dns_two = ethernet::global_eth_component->get_dns_address(1);
if (dns1 != this->last_dns1_ || dns2 != this->last_dns2_) {
this->last_dns1_ = dns1;
this->last_dns2_ = dns2;
// IP_ADDRESS_BUFFER_SIZE (40) = max IP (39) + null; space reuses first null's slot
char buf[network::IP_ADDRESS_BUFFER_SIZE * 2];
dns1.str_to(buf);
size_t len1 = strlen(buf);
buf[len1] = ' ';
dns2.str_to(buf + len1 + 1);
this->publish_state(buf);
std::string dns_results = dns_one.str() + " " + dns_two.str();
if (dns_results != this->last_results_) {
this->last_results_ = dns_results;
this->publish_state(dns_results);
}
}
float get_setup_priority() const override { return setup_priority::ETHERNET; }
void dump_config() override;
protected:
network::IPAddress last_dns1_;
network::IPAddress last_dns2_;
std::string last_results_;
};
class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor {
@@ -69,6 +64,7 @@ class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor
void dump_config() override;
};
} // namespace esphome::ethernet_info
} // namespace ethernet_info
} // namespace esphome
#endif // USE_ESP32

View File

@@ -11,7 +11,6 @@ from esphome.const import (
CONF_SPEED,
DEVICE_CLASS_SPEED,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_MEASUREMENT_ANGLE,
UNIT_DEGREES,
UNIT_KILOMETER_PER_HOUR,
UNIT_METER,
@@ -22,7 +21,6 @@ CONF_HDOP = "hdop"
ICON_ALTIMETER = "mdi:altimeter"
ICON_COMPASS = "mdi:compass"
ICON_CIRCLE_DOUBLE = "mdi:circle-double"
ICON_LATITUDE = "mdi:latitude"
ICON_LONGITUDE = "mdi:longitude"
ICON_SATELLITE = "mdi:satellite-variant"
@@ -52,7 +50,7 @@ CONFIG_SCHEMA = cv.All(
unit_of_measurement=UNIT_DEGREES,
icon=ICON_LONGITUDE,
accuracy_decimals=6,
state_class=STATE_CLASS_MEASUREMENT_ANGLE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_SPEED): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOMETER_PER_HOUR,
@@ -65,7 +63,7 @@ CONFIG_SCHEMA = cv.All(
unit_of_measurement=UNIT_DEGREES,
icon=ICON_COMPASS,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT_ANGLE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ALTITUDE): sensor.sensor_schema(
unit_of_measurement=UNIT_METER,
@@ -74,14 +72,11 @@ CONFIG_SCHEMA = cv.All(
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_SATELLITES): sensor.sensor_schema(
# no unit_of_measurement
icon=ICON_SATELLITE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HDOP): sensor.sensor_schema(
# no unit_of_measurement
icon=ICON_CIRCLE_DOUBLE,
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
),

View File

@@ -8,9 +8,6 @@ namespace esphome::hlk_fm22x {
static const char *const TAG = "hlk_fm22x";
// Maximum response size is 36 bytes (VERIFY reply: face_id + 32-byte name)
static constexpr size_t HLK_FM22X_MAX_RESPONSE_SIZE = 36;
void HlkFm22xComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up HLK-FM22X...");
this->set_enrolling_(false);
@@ -145,10 +142,7 @@ void HlkFm22xComponent::recv_command_() {
data.push_back(byte);
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(HLK_FM22X_MAX_RESPONSE_SIZE)];
ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty_to(hex_buf, data.data(), data.size()));
#endif
ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty(data).c_str());
byte = this->read();
if (byte != checksum) {

View File

@@ -34,9 +34,9 @@ void HLW8012Component::setup() {
}
void HLW8012Component::dump_config() {
ESP_LOGCONFIG(TAG, "HLW8012:");
LOG_PIN(" SEL Pin: ", this->sel_pin_);
LOG_PIN(" CF Pin: ", this->cf_pin_);
LOG_PIN(" CF1 Pin: ", this->cf1_pin_);
LOG_PIN(" SEL Pin: ", this->sel_pin_)
LOG_PIN(" CF Pin: ", this->cf_pin_)
LOG_PIN(" CF1 Pin: ", this->cf1_pin_)
ESP_LOGCONFIG(TAG,
" Change measurement mode every %" PRIu32 "\n"
" Current resistor: %.1f mΩ\n"

View File

@@ -30,7 +30,7 @@ class HmacMD5 {
void get_bytes(uint8_t *output);
/// Retrieve the HMAC-MD5 digest as hex characters.
/// The output must be able to hold 33 bytes or more (32 hex chars + null terminator).
/// The output must be able to hold 32 bytes or more.
void get_hex(char *output);
/// Compare the digest against a provided byte-encoded digest (16 bytes).

View File

@@ -35,7 +35,7 @@ class HmacSHA256 {
void get_bytes(uint8_t *output);
/// Retrieve the HMAC-SHA256 digest as hex characters.
/// The output must be able to hold 65 bytes or more (64 hex chars + null terminator).
/// The output must be able to hold 64 bytes or more.
void get_hex(char *output);
/// Compare the digest against a provided byte-encoded digest (32 bytes).

View File

@@ -2,37 +2,35 @@
#include "esphome/core/log.h"
#include "esphome/components/api/api_server.h"
namespace esphome {
namespace homeassistant {
namespace esphome::homeassistant {
static const char *const TAG = "homeassistant.binary_sensor";
void HomeassistantBinarySensor::setup() {
api::global_api_server->subscribe_home_assistant_state(
this->entity_id_, this->attribute_, [this](const std::string &state) {
auto val = parse_on_off(state.c_str());
switch (val) {
case PARSE_NONE:
case PARSE_TOGGLE:
ESP_LOGW(TAG, "Can't convert '%s' to binary state!", state.c_str());
break;
case PARSE_ON:
case PARSE_OFF:
bool new_state = val == PARSE_ON;
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_, this->attribute_, ONOFF(new_state));
} else {
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state));
}
if (this->initial_) {
this->publish_initial_state(new_state);
} else {
this->publish_state(new_state);
}
break;
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) {
auto val = parse_on_off(state.c_str());
switch (val) {
case PARSE_NONE:
case PARSE_TOGGLE:
ESP_LOGW(TAG, "Can't convert '%s' to binary state!", state.c_str());
break;
case PARSE_ON:
case PARSE_OFF:
bool new_state = val == PARSE_ON;
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_, this->attribute_, ONOFF(new_state));
} else {
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state));
}
this->initial_ = false;
});
if (this->initial_) {
this->publish_initial_state(new_state);
} else {
this->publish_state(new_state);
}
break;
}
this->initial_ = false;
});
}
void HomeassistantBinarySensor::dump_config() {
LOG_BINARY_SENSOR("", "Homeassistant Binary Sensor", this);
@@ -43,5 +41,4 @@ void HomeassistantBinarySensor::dump_config() {
}
float HomeassistantBinarySensor::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
} // namespace homeassistant
} // namespace esphome
} // namespace esphome::homeassistant

View File

@@ -3,8 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace homeassistant {
namespace esphome::homeassistant {
class HomeassistantBinarySensor : public binary_sensor::BinarySensor, public Component {
public:
@@ -20,5 +19,4 @@ class HomeassistantBinarySensor : public binary_sensor::BinarySensor, public Com
bool initial_{true};
};
} // namespace homeassistant
} // namespace esphome
} // namespace esphome::homeassistant

View File

@@ -4,13 +4,12 @@
#include "esphome/components/api/api_server.h"
#include "esphome/core/log.h"
namespace esphome {
namespace homeassistant {
namespace esphome::homeassistant {
static const char *const TAG = "homeassistant.number";
void HomeassistantNumber::state_changed_(const std::string &state) {
auto number_value = parse_number<float>(state);
void HomeassistantNumber::state_changed_(StringRef state) {
auto number_value = parse_number<float>(state.c_str());
if (!number_value.has_value()) {
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str());
this->publish_state(NAN);
@@ -23,8 +22,8 @@ void HomeassistantNumber::state_changed_(const std::string &state) {
this->publish_state(number_value.value());
}
void HomeassistantNumber::min_retrieved_(const std::string &min) {
auto min_value = parse_number<float>(min);
void HomeassistantNumber::min_retrieved_(StringRef min) {
auto min_value = parse_number<float>(min.c_str());
if (!min_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_, min.c_str());
return;
@@ -33,8 +32,8 @@ void HomeassistantNumber::min_retrieved_(const std::string &min) {
this->traits.set_min_value(min_value.value());
}
void HomeassistantNumber::max_retrieved_(const std::string &max) {
auto max_value = parse_number<float>(max);
void HomeassistantNumber::max_retrieved_(StringRef max) {
auto max_value = parse_number<float>(max.c_str());
if (!max_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_, max.c_str());
return;
@@ -43,8 +42,8 @@ void HomeassistantNumber::max_retrieved_(const std::string &max) {
this->traits.set_max_value(max_value.value());
}
void HomeassistantNumber::step_retrieved_(const std::string &step) {
auto step_value = parse_number<float>(step);
void HomeassistantNumber::step_retrieved_(StringRef step) {
auto step_value = parse_number<float>(step.c_str());
if (!step_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_, step.c_str());
return;
@@ -99,5 +98,4 @@ void HomeassistantNumber::control(float value) {
api::global_api_server->send_homeassistant_action(resp);
}
} // namespace homeassistant
} // namespace esphome
} // namespace esphome::homeassistant

View File

@@ -1,13 +1,10 @@
#pragma once
#include <map>
#include <string>
#include "esphome/components/number/number.h"
#include "esphome/core/component.h"
#include "esphome/core/string_ref.h"
namespace esphome {
namespace homeassistant {
namespace esphome::homeassistant {
class HomeassistantNumber : public number::Number, public Component {
public:
@@ -18,14 +15,14 @@ class HomeassistantNumber : public number::Number, public Component {
float get_setup_priority() const override;
protected:
void state_changed_(const std::string &state);
void min_retrieved_(const std::string &min);
void max_retrieved_(const std::string &max);
void step_retrieved_(const std::string &step);
void state_changed_(StringRef state);
void min_retrieved_(StringRef min);
void max_retrieved_(StringRef max);
void step_retrieved_(StringRef step);
void control(float value) override;
const char *entity_id_{nullptr};
};
} // namespace homeassistant
} // namespace esphome
} // namespace esphome::homeassistant

View File

@@ -2,28 +2,26 @@
#include "esphome/core/log.h"
#include "esphome/components/api/api_server.h"
namespace esphome {
namespace homeassistant {
namespace esphome::homeassistant {
static const char *const TAG = "homeassistant.sensor";
void HomeassistantSensor::setup() {
api::global_api_server->subscribe_home_assistant_state(
this->entity_id_, this->attribute_, [this](const std::string &state) {
auto val = parse_number<float>(state);
if (!val.has_value()) {
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str());
this->publish_state(NAN);
return;
}
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) {
auto val = parse_number<float>(state.c_str());
if (!val.has_value()) {
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str());
this->publish_state(NAN);
return;
}
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_, this->attribute_, *val);
} else {
ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_, *val);
}
this->publish_state(*val);
});
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_, this->attribute_, *val);
} else {
ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_, *val);
}
this->publish_state(*val);
});
}
void HomeassistantSensor::dump_config() {
LOG_SENSOR("", "Homeassistant Sensor", this);
@@ -34,5 +32,4 @@ void HomeassistantSensor::dump_config() {
}
float HomeassistantSensor::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
} // namespace homeassistant
} // namespace esphome
} // namespace esphome::homeassistant

View File

@@ -3,8 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace homeassistant {
namespace esphome::homeassistant {
class HomeassistantSensor : public sensor::Sensor, public Component {
public:
@@ -19,5 +18,4 @@ class HomeassistantSensor : public sensor::Sensor, public Component {
const char *attribute_{nullptr};
};
} // namespace homeassistant
} // namespace esphome
} // namespace esphome::homeassistant

View File

@@ -2,15 +2,14 @@
#include "esphome/components/api/api_server.h"
#include "esphome/core/log.h"
namespace esphome {
namespace homeassistant {
namespace esphome::homeassistant {
static const char *const TAG = "homeassistant.switch";
using namespace esphome::switch_;
void HomeassistantSwitch::setup() {
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, [this](const std::string &state) {
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, [this](StringRef state) {
auto val = parse_on_off(state.c_str());
switch (val) {
case PARSE_NONE:
@@ -59,5 +58,4 @@ void HomeassistantSwitch::write_state(bool state) {
api::global_api_server->send_homeassistant_action(resp);
}
} // namespace homeassistant
} // namespace esphome
} // namespace esphome::homeassistant

View File

@@ -3,8 +3,7 @@
#include "esphome/components/switch/switch.h"
#include "esphome/core/component.h"
namespace esphome {
namespace homeassistant {
namespace esphome::homeassistant {
class HomeassistantSwitch : public switch_::Switch, public Component {
public:
@@ -18,5 +17,4 @@ class HomeassistantSwitch : public switch_::Switch, public Component {
const char *entity_id_{nullptr};
};
} // namespace homeassistant
} // namespace esphome
} // namespace esphome::homeassistant

View File

@@ -2,21 +2,19 @@
#include "esphome/core/log.h"
#include "esphome/components/api/api_server.h"
namespace esphome {
namespace homeassistant {
namespace esphome::homeassistant {
static const char *const TAG = "homeassistant.text_sensor";
void HomeassistantTextSensor::setup() {
api::global_api_server->subscribe_home_assistant_state(
this->entity_id_, this->attribute_, [this](const std::string &state) {
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_, this->attribute_, state.c_str());
} else {
ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str());
}
this->publish_state(state);
});
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) {
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_, this->attribute_, state.c_str());
} else {
ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str());
}
this->publish_state(state.str());
});
}
void HomeassistantTextSensor::dump_config() {
LOG_TEXT_SENSOR("", "Homeassistant Text Sensor", this);
@@ -26,5 +24,5 @@ void HomeassistantTextSensor::dump_config() {
}
}
float HomeassistantTextSensor::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
} // namespace homeassistant
} // namespace esphome
} // namespace esphome::homeassistant

View File

@@ -3,8 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/components/text_sensor/text_sensor.h"
namespace esphome {
namespace homeassistant {
namespace esphome::homeassistant {
class HomeassistantTextSensor : public text_sensor::TextSensor, public Component {
public:
@@ -19,5 +18,4 @@ class HomeassistantTextSensor : public text_sensor::TextSensor, public Component
const char *attribute_{nullptr};
};
} // namespace homeassistant
} // namespace esphome
} // namespace esphome::homeassistant

View File

@@ -25,7 +25,11 @@ void HostGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Interr
}
void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); }
size_t HostGPIOPin::dump_summary(char *buffer, size_t len) const { return snprintf(buffer, len, "GPIO%u", this->pin_); }
std::string HostGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
return buffer;
}
bool HostGPIOPin::digital_read() { return inverted_; }
void HostGPIOPin::digital_write(bool value) {

View File

@@ -17,7 +17,7 @@ class HostGPIOPin : public InternalGPIOPin {
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
size_t dump_summary(char *buffer, size_t len) const override;
std::string dump_summary() const override;
void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return pin_; }

View File

@@ -7,8 +7,6 @@ namespace hte501 {
static const char *const TAG = "hte501";
static constexpr size_t HTE501_SERIAL_NUMBER_SIZE = 7;
void HTE501Component::setup() {
uint8_t address[] = {0x70, 0x29};
uint8_t identification[9];
@@ -18,10 +16,7 @@ void HTE501Component::setup() {
this->mark_failed();
return;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char serial_hex[format_hex_size(HTE501_SERIAL_NUMBER_SIZE)];
#endif
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex_to(serial_hex, identification, HTE501_SERIAL_NUMBER_SIZE));
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(identification + 0, 7).c_str());
}
void HTE501Component::dump_config() {

View File

@@ -111,9 +111,6 @@ void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) {
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) [[unlikely]]
return;
if (!this->get_clipping().inside(x, y))
return;
driver_->set_pixel(x, y, color.r, color.g, color.b);
App.feed_wdt();
}

View File

@@ -250,7 +250,7 @@ async def register_i2c_device(var, config):
Sets the i2c bus to use and the i2c address.
This is a coroutine, you need to await it with an 'await' expression!
This is a coroutine, you need to await it with a 'yield' expression!
"""
parent = await cg.get_variable(config[CONF_I2C_ID])
cg.add(var.set_i2c_bus(parent))

View File

@@ -12,9 +12,6 @@ namespace i2c {
static const char *const TAG = "i2c.arduino";
// Maximum bytes to log in hex format (truncates larger transfers)
static constexpr size_t I2C_MAX_LOG_BYTES = 32;
void ArduinoI2CBus::setup() {
recover_();
@@ -110,10 +107,7 @@ ErrorCode ArduinoI2CBus::write_readv(uint8_t address, const uint8_t *write_buffe
return ERROR_NOT_INITIALIZED;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(I2C_MAX_LOG_BYTES)];
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty_to(hex_buf, write_buffer, write_count));
#endif
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str());
uint8_t status = 0;
if (write_count != 0 || read_count == 0) {

View File

@@ -15,9 +15,6 @@ namespace i2c {
static const char *const TAG = "i2c.idf";
// Maximum bytes to log in hex format (truncates larger transfers)
static constexpr size_t I2C_MAX_LOG_BYTES = 32;
void IDFI2CBus::setup() {
static i2c_port_t next_hp_port = I2C_NUM_0;
#if SOC_LP_I2C_SUPPORTED
@@ -150,10 +147,7 @@ ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, s
jobs[num_jobs++].write.total_bytes = 1;
} else {
if (write_count != 0) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(I2C_MAX_LOG_BYTES)];
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty_to(hex_buf, write_buffer, write_count));
#endif
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str());
jobs[num_jobs++].command = I2C_MASTER_CMD_START;
jobs[num_jobs].command = I2C_MASTER_CMD_WRITE;
jobs[num_jobs].write.ack_check = true;

View File

@@ -131,13 +131,6 @@ float ILI9XXXDisplay::get_setup_priority() const { return setup_priority::HARDWA
void ILI9XXXDisplay::fill(Color color) {
if (!this->check_buffer_())
return;
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
uint16_t new_color = 0;
this->x_low_ = 0;
this->y_low_ = 0;

View File

@@ -293,13 +293,6 @@ void Inkplate::fill(Color color) {
ESP_LOGV(TAG, "Fill called");
uint32_t start_time = millis();
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time);
return;
}
if (this->greyscale_) {
uint8_t fill = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5;
memset(this->buffer_, (fill << 4) | fill, this->get_buffer_length_());

View File

@@ -39,9 +39,7 @@ void Jsnsr04tComponent::check_buffer_() {
ESP_LOGV(TAG, "Distance from sensor: %umm, %.3fm", distance, meters);
this->publish_state(meters);
} else {
char hex_buf[format_hex_pretty_size(4)];
ESP_LOGW(TAG, "Invalid data read from sensor: %s",
format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size()));
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
}
} else {
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);

View File

@@ -1,5 +1,4 @@
#include "kuntze.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
@@ -11,17 +10,11 @@ static const char *const TAG = "kuntze";
static const uint8_t CMD_READ_REG = 0x03;
static const uint16_t REGISTER[] = {4136, 4160, 4680, 6000, 4688, 4728, 5832};
// Maximum bytes to log for Modbus responses (2 registers = 4, plus count = 5)
static constexpr size_t KUNTZE_MAX_LOG_BYTES = 8;
void Kuntze::on_modbus_data(const std::vector<uint8_t> &data) {
auto get_16bit = [&](int i) -> uint16_t { return (uint16_t(data[i * 2]) << 8) | uint16_t(data[i * 2 + 1]); };
this->waiting_ = false;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(KUNTZE_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "Data: %s", format_hex_pretty_to(hex_buf, data.data(), data.size()));
ESP_LOGV(TAG, "Data: %s", format_hex_pretty(data).c_str());
float value = (float) get_16bit(0);
for (int i = 0; i < data[3]; i++)

View File

@@ -413,8 +413,7 @@ bool LD2410Component::handle_ack_data_() {
return true;
}
if (!ld2410::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)];
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE));
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
return true;
}
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
@@ -598,17 +597,11 @@ void LD2410Component::readline_(int readch) {
return; // Not enough data to process yet
}
if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next message
} else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {

View File

@@ -457,8 +457,7 @@ bool LD2412Component::handle_ack_data_() {
return true;
}
if (!ld2412::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)];
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE));
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
return true;
}
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
@@ -671,17 +670,11 @@ void LD2412Component::readline_(int readch) {
return; // Not enough data to process yet
}
if (ld2412::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next message
} else if (ld2412::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {

View File

@@ -607,8 +607,7 @@ bool LD2450Component::handle_ack_data_() {
return true;
}
if (!ld2450::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)];
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE));
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
return true;
}
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
@@ -759,17 +758,11 @@ void LD2450Component::readline_(int readch) {
}
if (this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[0] &&
this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[1]) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next frame
} else if (ld2450::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {

View File

@@ -63,8 +63,10 @@ void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) {
pinMode(pin_, flags_to_mode(flags)); // NOLINT
}
size_t ArduinoInternalGPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u", this->pin_);
std::string ArduinoInternalGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%u", pin_);
return buffer;
}
bool ArduinoInternalGPIOPin::digital_read() {

View File

@@ -16,7 +16,7 @@ class ArduinoInternalGPIOPin : public InternalGPIOPin {
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
size_t dump_summary(char *buffer, size_t len) const override;
std::string dump_summary() const override;
void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return pin_; }

View File

@@ -36,7 +36,7 @@ static const char *get_color_mode_json_str(ColorMode mode) {
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (state.supports_effects()) {
root[ESPHOME_F("effect")] = state.get_effect_name_ref();
root[ESPHOME_F("effect")] = state.get_effect_name();
root[ESPHOME_F("effect_index")] = state.get_current_effect_index();
root[ESPHOME_F("effect_count")] = state.get_effect_count();
}

View File

@@ -386,7 +386,7 @@ async def to_code(config):
except cv.Invalid:
pass
if CORE.is_nrf52:
if CORE.using_zephyr:
if config[CONF_HARDWARE_UART] == UART0:
zephyr_add_overlay("""&uart0 { status = "okay";};""")
if config[CONF_HARDWARE_UART] == UART1:

View File

@@ -85,11 +85,11 @@ class ArcType(NumberType):
lv.arc_set_range(w.obj, min_value, max_value)
await w.set_property(
"bg_start_angle",
CONF_START_ANGLE,
await lv_angle_degrees.process(config.get(CONF_START_ANGLE)),
)
await w.set_property(
"bg_end_angle", await lv_angle_degrees.process(config.get(CONF_END_ANGLE))
CONF_END_ANGLE, await lv_angle_degrees.process(config.get(CONF_END_ANGLE))
)
await w.set_property(
CONF_ROTATION, await lv_angle_degrees.process(config.get(CONF_ROTATION))

View File

@@ -161,8 +161,10 @@ void MAX6956GPIOPin::setup() { pin_mode(flags_); }
void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
size_t MAX6956GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via Max6956", this->pin_);
std::string MAX6956GPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%u via Max6956", pin_);
return buffer;
}
} // namespace max6956

View File

@@ -76,7 +76,7 @@ class MAX6956GPIOPin : public GPIOPin {
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
size_t dump_summary(char *buffer, size_t len) const override;
std::string dump_summary() const override;
void set_parent(MAX6956 *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }

View File

@@ -99,8 +99,10 @@ void MCP23016GPIOPin::setup() { pin_mode(flags_); }
void MCP23016GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
bool MCP23016GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void MCP23016GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
size_t MCP23016GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via MCP23016", this->pin_);
std::string MCP23016GPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%u via MCP23016", pin_);
return buffer;
}
} // namespace mcp23016

View File

@@ -60,7 +60,7 @@ class MCP23016GPIOPin : public GPIOPin {
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
size_t dump_summary(char *buffer, size_t len) const override;
std::string dump_summary() const override;
void set_parent(MCP23016 *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }

View File

@@ -16,8 +16,8 @@ template<uint8_t N> bool MCP23XXXGPIOPin<N>::digital_read() {
template<uint8_t N> void MCP23XXXGPIOPin<N>::digital_write(bool value) {
this->parent_->digital_write(this->pin_, value != this->inverted_);
}
template<uint8_t N> size_t MCP23XXXGPIOPin<N>::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via MCP23XXX", this->pin_);
template<uint8_t N> std::string MCP23XXXGPIOPin<N>::dump_summary() const {
return str_snprintf("%u via MCP23XXX", 15, pin_);
}
template class MCP23XXXGPIOPin<8>;

View File

@@ -36,7 +36,7 @@ template<uint8_t N> class MCP23XXXGPIOPin : public GPIOPin {
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
size_t dump_summary(char *buffer, size_t len) const override;
std::string dump_summary() const override;
void set_parent(MCP23XXXBase<N> *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }

View File

@@ -13,9 +13,6 @@ static const uint8_t MHZ19_COMMAND_GET_PPM[] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x
static const uint8_t MHZ19_COMMAND_ABC_ENABLE[] = {0xFF, 0x01, 0x79, 0xA0, 0x00, 0x00, 0x00, 0x00};
static const uint8_t MHZ19_COMMAND_ABC_DISABLE[] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00};
static const uint8_t MHZ19_COMMAND_CALIBRATE_ZERO[] = {0xFF, 0x01, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00};
static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_2000PPM[] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x07, 0xD0};
static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_5000PPM[] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x13, 0x88};
static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_10000PPM[] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x27, 0x10};
uint8_t mhz19_checksum(const uint8_t *command) {
uint8_t sum = 0;
@@ -31,8 +28,6 @@ void MHZ19Component::setup() {
} else if (this->abc_boot_logic_ == MHZ19_ABC_DISABLED) {
this->abc_disable();
}
this->range_set(this->detection_range_);
}
void MHZ19Component::update() {
@@ -91,26 +86,6 @@ void MHZ19Component::abc_disable() {
this->mhz19_write_command_(MHZ19_COMMAND_ABC_DISABLE, nullptr);
}
void MHZ19Component::range_set(MHZ19DetectionRange detection_ppm) {
switch (detection_ppm) {
case MHZ19_DETECTION_RANGE_DEFAULT:
ESP_LOGV(TAG, "Using previously set detection range (no change)");
break;
case MHZ19_DETECTION_RANGE_0_2000PPM:
ESP_LOGD(TAG, "Setting detection range to 0 to 2000ppm");
this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_2000PPM, nullptr);
break;
case MHZ19_DETECTION_RANGE_0_5000PPM:
ESP_LOGD(TAG, "Setting detection range to 0 to 5000ppm");
this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_5000PPM, nullptr);
break;
case MHZ19_DETECTION_RANGE_0_10000PPM:
ESP_LOGD(TAG, "Setting detection range to 0 to 10000ppm");
this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_10000PPM, nullptr);
break;
}
}
bool MHZ19Component::mhz19_write_command_(const uint8_t *command, uint8_t *response) {
// Empty RX Buffer
while (this->available())
@@ -124,9 +99,7 @@ bool MHZ19Component::mhz19_write_command_(const uint8_t *command, uint8_t *respo
return this->read_array(response, MHZ19_RESPONSE_LENGTH);
}
float MHZ19Component::get_setup_priority() const { return setup_priority::DATA; }
void MHZ19Component::dump_config() {
ESP_LOGCONFIG(TAG, "MH-Z19:");
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
@@ -140,23 +113,6 @@ void MHZ19Component::dump_config() {
}
ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_);
const char *range_str;
switch (this->detection_range_) {
case MHZ19_DETECTION_RANGE_DEFAULT:
range_str = "default";
break;
case MHZ19_DETECTION_RANGE_0_2000PPM:
range_str = "0 to 2000ppm";
break;
case MHZ19_DETECTION_RANGE_0_5000PPM:
range_str = "0 to 5000ppm";
break;
case MHZ19_DETECTION_RANGE_0_10000PPM:
range_str = "0 to 10000ppm";
break;
}
ESP_LOGCONFIG(TAG, " Detection range: %s", range_str);
}
} // namespace mhz19

View File

@@ -8,18 +8,7 @@
namespace esphome {
namespace mhz19 {
enum MHZ19ABCLogic {
MHZ19_ABC_NONE = 0,
MHZ19_ABC_ENABLED,
MHZ19_ABC_DISABLED,
};
enum MHZ19DetectionRange {
MHZ19_DETECTION_RANGE_DEFAULT = 0,
MHZ19_DETECTION_RANGE_0_2000PPM,
MHZ19_DETECTION_RANGE_0_5000PPM,
MHZ19_DETECTION_RANGE_0_10000PPM,
};
enum MHZ19ABCLogic { MHZ19_ABC_NONE = 0, MHZ19_ABC_ENABLED, MHZ19_ABC_DISABLED };
class MHZ19Component : public PollingComponent, public uart::UARTDevice {
public:
@@ -32,13 +21,11 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice {
void calibrate_zero();
void abc_enable();
void abc_disable();
void range_set(MHZ19DetectionRange detection_ppm);
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; }
void set_abc_enabled(bool abc_enabled) { abc_boot_logic_ = abc_enabled ? MHZ19_ABC_ENABLED : MHZ19_ABC_DISABLED; }
void set_warmup_seconds(uint32_t seconds) { warmup_seconds_ = seconds; }
void set_detection_range(MHZ19DetectionRange detection_range) { detection_range_ = detection_range; }
protected:
bool mhz19_write_command_(const uint8_t *command, uint8_t *response);
@@ -46,32 +33,37 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice {
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *co2_sensor_{nullptr};
MHZ19ABCLogic abc_boot_logic_{MHZ19_ABC_NONE};
uint32_t warmup_seconds_;
MHZ19DetectionRange detection_range_{MHZ19_DETECTION_RANGE_DEFAULT};
};
template<typename... Ts> class MHZ19CalibrateZeroAction : public Action<Ts...>, public Parented<MHZ19Component> {
template<typename... Ts> class MHZ19CalibrateZeroAction : public Action<Ts...> {
public:
void play(const Ts &...x) override { this->parent_->calibrate_zero(); }
MHZ19CalibrateZeroAction(MHZ19Component *mhz19) : mhz19_(mhz19) {}
void play(const Ts &...x) override { this->mhz19_->calibrate_zero(); }
protected:
MHZ19Component *mhz19_;
};
template<typename... Ts> class MHZ19ABCEnableAction : public Action<Ts...>, public Parented<MHZ19Component> {
template<typename... Ts> class MHZ19ABCEnableAction : public Action<Ts...> {
public:
void play(const Ts &...x) override { this->parent_->abc_enable(); }
MHZ19ABCEnableAction(MHZ19Component *mhz19) : mhz19_(mhz19) {}
void play(const Ts &...x) override { this->mhz19_->abc_enable(); }
protected:
MHZ19Component *mhz19_;
};
template<typename... Ts> class MHZ19ABCDisableAction : public Action<Ts...>, public Parented<MHZ19Component> {
template<typename... Ts> class MHZ19ABCDisableAction : public Action<Ts...> {
public:
void play(const Ts &...x) override { this->parent_->abc_disable(); }
};
MHZ19ABCDisableAction(MHZ19Component *mhz19) : mhz19_(mhz19) {}
template<typename... Ts> class MHZ19DetectionRangeSetAction : public Action<Ts...>, public Parented<MHZ19Component> {
public:
TEMPLATABLE_VALUE(MHZ19DetectionRange, detection_range)
void play(const Ts &...x) override { this->mhz19_->abc_disable(); }
void play(const Ts &...x) override { this->parent_->range_set(this->detection_range_.value(x...)); }
protected:
MHZ19Component *mhz19_;
};
} // namespace mhz19

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