Compare commits

...

78 Commits

Author SHA1 Message Date
J. Nick Koston
744b8af2c7 disable loop when unused 2026-01-03 13:44:32 -10:00
J. Nick Koston
095f369ebd Merge branch 'dev' into wh_template 2026-01-03 13:30:51 -10:00
Clyde Stubbs
c29aa61e2a [image] Use alternative version of CairoSVG on Windows (#12811) 2026-01-04 10:08:47 +11:00
J. Nick Koston
cb3edfc654 [wifi] Use stack-based MAC formatting in ESP8266 and IDF event handlers (#12834) 2026-01-03 12:32:22 -10:00
J. Nick Koston
6685fa1da9 [core] Fix startup delay from setup timing logs when console connected (#12832) 2026-01-03 12:32:10 -10:00
J. Nick Koston
d505f0316b [wifi] Combine scan result log lines to reduce loop blocking with many matching APs (#12830) 2026-01-03 12:31:58 -10:00
J. Nick Koston
9781073f2a [espnow] Use stack-based MAC formatting and remove dead code (#12836) 2026-01-03 12:31:38 -10:00
John Hollowell
0a0501c140 Fix comment typos (#12828) 2026-01-03 17:11:48 -05:00
Jasper van der Neut - Stulen
a6e9aa7876 [mhz19] Refactor Actions to Parented (#12837) 2026-01-03 17:11:02 -05:00
Conrad Juhl Andersen
ede7391582 [wts01] Fix negative values for WTS01 sensor (#12835) 2026-01-03 17:06:33 -05:00
Jasper van der Neut - Stulen
5cfcf8d104 [mhz19] Make detection range configurable (#12677)
Co-authored-by: Fabio Pugliese Ornellas <fabio.ornellas@gmail.com>
2026-01-03 15:51:48 -05:00
J. Nick Koston
c34665f650 [api] Fix KeyError when running logs after password removal (#12831) 2026-01-03 19:13:07 +00:00
Mariusz Kryński
69867bf818 [nrf52, zephyr] move nrf52-specific code to nrf52 component (#12582)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-03 18:58:56 +00:00
J. Nick Koston
1d323c2d71 [api] Remove deprecated password authentication (#12819) 2026-01-03 07:14:48 -10:00
tomaszduda23
95a7356ea0 [uart] make sure that all variables are initialized (#12823) 2026-01-03 03:43:17 -06:00
J. Nick Koston
89b550b74a [tests] Remove reserved / character from entity names in component tests (#12820) 2026-01-03 01:00:46 -06:00
dependabot[bot]
538c6544a0 Bump ruamel-yaml from 0.18.17 to 0.19.1 (#12768)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-02 20:51:56 -10:00
dependabot[bot]
98e3695c89 Bump aioesphomeapi from 43.9.1 to 43.10.0 (#12821)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-03 06:45:17 +00:00
J. Nick Koston
00fd4f2fdd [esp8266] Exclude unused waveform code to save ~596 bytes RAM (#12690) 2026-01-02 19:51:07 -10:00
J. Nick Koston
2a5be725c8 [api] Enable zero-copy bytes SOURCE_BOTH messages (#12816) 2026-01-02 19:50:30 -10:00
Robert Klep
c4d339a4c9 [core] Add CONF_ON_START (#12439) (#12440) 2026-01-02 23:42:18 -05:00
J. Nick Koston
6409970f6e [uponor_smatrix] Use stack-based hex formatting in verbose logging (#12797)
Co-authored-by: Stefan Rado <628587+kroimon@users.noreply.github.com>
2026-01-02 16:41:02 -10:00
J. Nick Koston
bc1af007b4 [vbus] Use stack-based hex formatting in verbose logging (#12796) 2026-01-02 16:40:47 -10:00
Thomas Rupprecht
c3ffc1635d [gps] add icon for HDOP and use correct state_class for longitude and… (#12718) 2026-01-02 21:40:28 -05:00
J. Nick Koston
016eeef04a [tee501] Use stack-based hex formatting in verbose logging (#12795) 2026-01-02 16:40:06 -10:00
J. Nick Koston
ace48464a8 [addressable_light] Use StringRef to avoid allocation when saving effect name (#12759) 2026-01-02 16:39:44 -10:00
J. Nick Koston
64ba376330 [hte501] Use stack-based hex formatting in verbose logging (#12794) 2026-01-02 16:37:38 -10:00
J. Nick Koston
d946ddabfd [xiaomi_ble] Use stack-based hex formatting in verbose logging (#12793) 2026-01-02 16:37:16 -10:00
J. Nick Koston
a57011b50b [kuntze] Use stack buffer for hex formatting in verbose logging (#12775) 2026-01-02 16:36:57 -10:00
J. Nick Koston
1240e7907e [api] Use stack-based format_hex_pretty_to for packet logging macros (#12788) 2026-01-02 16:35:44 -10:00
J. Nick Koston
f0391f0213 [api] Remove object_id from API protocol - clients compute it from name #12698 (#12818) 2026-01-02 16:32:46 -10:00
J. Nick Koston
3cc6810be5 [core] Remove object_id RAM storage - no longer in hot path after #12627 (#12631) 2026-01-02 15:46:01 -10:00
J. Nick Koston
916370a943 [gpio] Avoid heap allocation in dump_summary (#12760) 2026-01-02 15:42:56 -10:00
J. Nick Koston
e2f45c590e [esp32_improv] Use stack buffer for hex formatting in verbose logging (#12737) 2026-01-02 14:28:38 -10:00
J. Nick Koston
7d21411ca4 [epaper_spi] Use stack buffer for hex formatting in command logging (#12734) 2026-01-02 14:27:00 -10:00
J. Nick Koston
56ed5af27d [nextion] Use stack buffers for hex formatting in upload logging (#12733) 2026-01-02 14:26:14 -10:00
J. Nick Koston
c8241b0122 [sonoff_d1] Use stack buffer for hex formatting in logging (#12730) 2026-01-02 14:25:02 -10:00
J. Nick Koston
30efd7fb07 [jsn_sr04t] Use stack buffer for hex formatting in error logging (#12729) 2026-01-02 14:24:47 -10:00
J. Nick Koston
1703343694 [a02yyuw] Use stack buffer for hex formatting in error logging (#12728) 2026-01-02 14:24:30 -10:00
J. Nick Koston
7fa04b6c25 [a01nyub] Use stack buffer for hex formatting in error logging (#12727) 2026-01-02 14:23:33 -10:00
J. Nick Koston
61b6476de4 [opentherm] Replace heap-allocating format calls with printf format specifiers in debug_error (#12726) 2026-01-02 14:23:18 -10:00
J. Nick Koston
b4e5e0bc9b [rc522] Use stack buffers for hex formatting in tag logging (#12725) 2026-01-02 14:22:58 -10:00
J. Nick Koston
f9b4e0e489 [remote_base] Use stack buffer for hex formatting in haier protocol logging (#12723) 2026-01-02 14:22:26 -10:00
J. Nick Koston
9ccb100cca [remote_base] Use stack buffer for hex formatting in mirage protocol logging (#12722) 2026-01-02 14:21:42 -10:00
J. Nick Koston
20b66cba23 [shelly_dimmer] Use stack buffer for hex formatting in command logging (#12721) 2026-01-02 14:21:23 -10:00
J. Nick Koston
b711172b33 [wifi] Use precision format specifier for SSID logging to avoid stack copy (#12704) 2026-01-02 14:21:09 -10:00
J. Nick Koston
0c4184b129 [cse7766] Use stack buffer for hex formatting in debug logging (#12732) 2026-01-02 14:20:17 -10:00
J. Nick Koston
0e108c2178 [esp32] Add minimum_chip_revision setting and log chip revision at startup (#12696) 2026-01-02 14:14:52 -10:00
J. Nick Koston
2230e56347 [wifi] Use stack buffers for IP address logging to avoid heap allocations (#12680) 2026-01-02 14:14:24 -10:00
J. Nick Koston
2ff9535f5f [esp32_improv] Use stack buffer for URL formatting to avoid heap allocation (#12682) 2026-01-02 14:14:12 -10:00
J. Nick Koston
ddb6c6cfd4 [captive_portal] Use stack buffer for IP address logging in DNS server (#12679) 2026-01-02 14:13:59 -10:00
J. Nick Koston
00ab64a3c7 [wifi] Use wifi_ssid_to() to avoid heap allocations in automation and connection checks (#12678) 2026-01-02 14:13:43 -10:00
J. Nick Koston
e732f8469e [udp] Avoid heap allocations when joining multicast groups (#12685) 2026-01-02 14:13:26 -10:00
J. Nick Koston
023be88a87 [tuya] Use stack buffers for hex logging to avoid heap allocations (#12689) 2026-01-02 14:13:08 -10:00
J. Nick Koston
25e60d62cf [mqtt] Avoid heap allocations when logging IP addresses (#12686) 2026-01-02 14:12:04 -10:00
J. Nick Koston
167a42aa27 [api] Use StringRef in send_action_response and send_execute_service_response (#12658) 2026-01-02 14:11:45 -10:00
J. Nick Koston
0ef49a8b73 [ld2410][ld2412][ld2450] Use stack buffers for hex logging (#12688) 2026-01-02 14:11:31 -10:00
J. Nick Koston
51259888bf [voice_assistant] Use zero-copy buffer access for audio data (#12656) 2026-01-02 14:10:21 -10:00
J. Nick Koston
0b7ff09657 [api] Use pointer to FixedVector for siren tones field (#12657) 2026-01-02 14:09:40 -10:00
J. Nick Koston
f394cf3f4d [packet_transport] Use stack-based format_hex_pretty_to for logging (#12791) 2026-01-02 14:06:03 -10:00
J. Nick Koston
4cb066bcbf [api] Use StringRef in handle_action_response to avoid temporary string (#12655) 2026-01-02 14:05:50 -10:00
J. Nick Koston
e7001c5eea [api] Auto-generate zero-copy pointer access for incoming API bytes fields (#12654) 2026-01-02 14:05:37 -10:00
esphomebot
5bb9ffa0cb Update webserver local assets to 20260102-230255 (#12817) 2026-01-02 23:14:11 +00:00
Douwe
90fa016b2b Merge branch 'dev' into wh_template 2025-12-28 11:46:41 +01:00
Douwe
c7f9ab2127 Merge branch 'dev' into wh_template 2025-12-22 10:45:13 +01:00
J. Nick Koston
2c7369614e compile tests 2025-12-21 12:04:28 -10:00
J. Nick Koston
75cb7e0095 typing 2025-12-21 11:57:02 -10:00
J. Nick Koston
f9eb2dab37 typing 2025-12-21 11:50:51 -10:00
J. Nick Koston
9376e904a6 tweaks 2025-12-21 11:49:17 -10:00
J. Nick Koston
2fcaed08b2 update schema 2025-12-21 11:47:22 -10:00
J. Nick Koston
8dae96617d realign with step 1 2025-12-21 11:46:37 -10:00
J. Nick Koston
1e8e917af6 Merge branch 'dev' into wh_template 2025-12-21 11:36:44 -10:00
dhoeben
2b8b42cbd7 Fix imports 2025-12-21 13:41:24 +01:00
dhoeben
3e9e0a93e2 Add supported_modes to available options 2025-12-21 13:34:39 +01:00
dhoeben
5602b39744 Removed duplicate min_/max_temperature 2025-12-21 12:13:46 +01:00
Douwe
8d5b49188c Merge branch 'dev' into wh_template 2025-12-21 11:06:43 +01:00
Douwe
c3f86054e8 Merge branch 'dev' into wh_template 2025-12-18 08:31:43 +01:00
dhoeben
ad3d7900f3 Add water_heater template 2025-12-16 09:27:25 +01:00
168 changed files with 7400 additions and 5591 deletions

View File

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

View File

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

View File

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

View File

@@ -226,32 +226,6 @@ 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
@@ -268,7 +242,17 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(APIServer),
cv.Optional(CONF_PORT, default=6053): cv.port,
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
# 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_REBOOT_TIMEOUT, default="15min"
): cv.positive_time_period_milliseconds,
@@ -330,7 +314,6 @@ CONFIG_SCHEMA = cv.All(
}
).extend(cv.COMPONENT_SCHEMA),
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
_validate_api_config,
_consume_api_sockets,
)
@@ -344,9 +327,6 @@ 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,10 +7,7 @@ service APIConnection {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
}
rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
}
// REMOVED in ESPHome 2026.1.0: rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse)
rpc disconnect (DisconnectRequest) returns (DisconnectResponse) {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
@@ -82,14 +79,13 @@ service APIConnection {
// * VarInt denoting the type of message.
// * The message object encoded as a ProtoBuf message
// The connection is established in 4 steps:
// The connection is established in 2 steps:
// * First, the client connects to the server and sends a "Hello Request" identifying itself
// * 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.
// * The server responds with a "Hello Response" and the connection is authenticated
// 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
@@ -130,25 +126,23 @@ message HelloResponse {
string name = 4;
}
// 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
// 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 AuthenticationRequest {
option (id) = 3;
option (source) = SOURCE_CLIENT;
option (no_delay) = true;
option (ifdef) = "USE_API_PASSWORD";
option deprecated = true;
// The password to log in with
string password = 1;
}
// 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 (ifdef) = "USE_API_PASSWORD";
option deprecated = true;
bool invalid_password = 1;
}
@@ -205,7 +199,9 @@ message DeviceInfoResponse {
option (id) = 10;
option (source) = SOURCE_SERVER;
bool uses_password = 1 [(field_ifdef) = "USE_API_PASSWORD"];
// 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];
// The name of the node, given by "App.set_name()"
string name = 2;
@@ -747,7 +743,7 @@ message NoiseEncryptionSetKeyRequest {
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_API_NOISE";
bytes key = 1 [(pointer_to_buffer) = true];
bytes key = 1;
}
message NoiseEncryptionSetKeyResponse {
@@ -796,7 +792,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 [(pointer_to_buffer) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
bytes response_data = 4 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
}
// ==================== IMPORT HOME ASSISTANT STATES ====================
@@ -1292,7 +1288,7 @@ message ListEntitiesSirenResponse {
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
bool disabled_by_default = 6;
repeated string tones = 7;
repeated string tones = 7 [(container_pointer_no_template) = "FixedVector<const char *>"];
bool supports_duration = 8;
bool supports_volume = 9;
EntityCategory entity_category = 10;
@@ -1692,7 +1688,7 @@ message BluetoothGATTWriteRequest {
uint32 handle = 2;
bool response = 3;
bytes data = 4 [(pointer_to_buffer) = true];
bytes data = 4;
}
message BluetoothGATTReadDescriptorRequest {
@@ -1712,7 +1708,7 @@ message BluetoothGATTWriteDescriptorRequest {
uint64 address = 1;
uint32 handle = 2;
bytes data = 3 [(pointer_to_buffer) = true];
bytes data = 3;
}
message BluetoothGATTNotifyRequest {
@@ -1937,7 +1933,7 @@ message VoiceAssistantAudio {
option (source) = SOURCE_BOTH;
option (ifdef) = "USE_VOICE_ASSISTANT";
bytes data = 1;
bytes data = 1 [(pointer_to_buffer) = true];
bool end = 2;
}
@@ -2425,7 +2421,7 @@ message ZWaveProxyFrame {
option (ifdef) = "USE_ZWAVE_PROXY";
option (no_delay) = true;
bytes data = 1 [(pointer_to_buffer) = true];
bytes data = 1;
}
enum ZWaveProxyRequestType {
@@ -2439,5 +2435,5 @@ message ZWaveProxyRequest {
option (ifdef) = "USE_ZWAVE_PROXY";
ZWaveProxyRequestType type = 1;
bytes data = 2 [(pointer_to_buffer) = true];
bytes data = 2;
}

View File

@@ -1530,32 +1530,16 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 13;
resp.api_version_minor = 14;
// 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()));
#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
// Auto-authenticate - password auth was removed in ESPHome 2026.1.0
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.byte(), msg.password.size());
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;
@@ -1564,9 +1548,6 @@ 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
@@ -1749,20 +1730,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, const std::string &error_message) {
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message) {
ExecuteServiceResponse resp;
resp.call_id = call_id;
resp.success = success;
resp.set_error_message(StringRef(error_message));
resp.set_error_message(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, const std::string &error_message,
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef 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(StringRef(error_message));
resp.set_error_message(error_message);
resp.response_data = response_data;
resp.response_data_len = response_data_len;
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
@@ -1845,12 +1826,6 @@ 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,9 +24,10 @@ 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
// 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;
// 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)
// 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
@@ -202,9 +203,6 @@ 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;
@@ -233,9 +231,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, const std::string &error_message);
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message,
void send_execute_service_response(uint32_t call_id, bool success, StringRef 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
@@ -260,9 +258,6 @@ 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
@@ -323,10 +318,16 @@ 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();
// Get object_id with zero heap allocation
// Static case returns direct reference, dynamic case uses buffer
// 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
char object_id_buf[OBJECT_ID_MAX_LEN];
msg.set_object_id(entity->get_object_id_to(object_id_buf));
if (!conn->client_supports_api_version(1, 14)) {
msg.set_object_id(entity->get_object_id_to(object_id_buf));
}
if (entity->has_own_name()) {
msg.set_name(entity->get_name());
@@ -349,16 +350,24 @@ 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();
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < MAX_INITIAL_PER_BATCH) {
size_t max_batch = this->get_max_batch_size_();
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_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_INITIAL_PER_BATCH) {
if (this->deferred_batch_.size() >= max_batch) {
this->process_batch_();
}
}

View File

@@ -13,12 +13,26 @@ 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) 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())
#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)
#else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0)

View File

@@ -24,12 +24,26 @@ 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) 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())
#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)
#else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0)

View File

@@ -18,12 +18,26 @@ 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) 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())
#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)
#else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0)

View File

@@ -43,21 +43,6 @@ 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: {
this->password = StringRef(reinterpret_cast<const char *>(value.data()), 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);
@@ -81,9 +66,6 @@ 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_);
@@ -139,9 +121,6 @@ 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());
@@ -1710,8 +1689,8 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon_ref_);
#endif
buffer.encode_bool(6, this->disabled_by_default);
for (auto &it : this->tones) {
buffer.encode_string(7, it, true);
for (const char *it : *this->tones) {
buffer.encode_string(7, it, strlen(it), true);
}
buffer.encode_bool(8, this->supports_duration);
buffer.encode_bool(9, this->supports_volume);
@@ -1728,9 +1707,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 auto &it : this->tones) {
size.add_length_force(1, it.size());
if (!this->tones->empty()) {
for (const char *it : *this->tones) {
size.add_length_force(1, strlen(it));
}
}
size.add_bool(1, this->supports_duration);
@@ -2527,20 +2506,22 @@ 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.as_string();
case 1: {
this->data = value.data();
this->data_len = value.size();
break;
}
default:
return false;
}
return true;
}
void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bytes(1, this->data_ptr_, this->data_len_);
buffer.encode_bytes(1, this->data, 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) {

View File

@@ -393,39 +393,6 @@ 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 = 9;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "authentication_request"; }
#endif
StringRef password{};
#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;
@@ -525,12 +492,9 @@ class DeviceInfo final : public ProtoMessage {
class DeviceInfoResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 10;
static constexpr uint16_t ESTIMATED_SIZE = 257;
static constexpr uint8_t ESTIMATED_SIZE = 255;
#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; }
@@ -1046,7 +1010,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 = 11;
static constexpr uint8_t ESTIMATED_SIZE = 21;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_logs_response"; }
#endif
@@ -1388,7 +1352,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 = 20;
static constexpr uint8_t ESTIMATED_SIZE = 30;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "camera_image_response"; }
#endif
@@ -1708,7 +1672,7 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_siren_response"; }
#endif
std::vector<std::string> tones{};
const FixedVector<const char *> *tones{};
bool supports_duration{false};
bool supports_volume{false};
void encode(ProtoWriteBuffer buffer) const override;
@@ -2123,7 +2087,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 = 17;
static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_read_response"; }
#endif
@@ -2218,7 +2182,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 = 17;
static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_notify_data_response"; }
#endif
@@ -2521,17 +2485,12 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage {
class VoiceAssistantAudio final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 106;
static constexpr uint8_t ESTIMATED_SIZE = 11;
static constexpr uint8_t ESTIMATED_SIZE = 21;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "voice_assistant_audio"; }
#endif
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;
}
const uint8_t *data{nullptr};
uint16_t data_len{0};
bool end{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;

View File

@@ -748,18 +748,6 @@ 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("'").append(this->password.c_str(), this->password.size()).append("'");
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 {}"); }
@@ -782,9 +770,6 @@ 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_);
@@ -1579,7 +1564,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);
@@ -1978,11 +1963,7 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
void VoiceAssistantAudio::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantAudio");
out.append(" data: ");
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(format_hex_pretty(this->data, this->data_len));
out.append("\n");
dump_field(out, "end", this->end);
}

View File

@@ -24,17 +24,6 @@ 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
@@ -643,13 +632,6 @@ 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();
@@ -841,10 +823,7 @@ 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
#ifdef USE_API_PASSWORD
case AuthenticationRequest::MESSAGE_TYPE: // No setup required
#endif
case HelloRequest::MESSAGE_TYPE: // No setup required
case DisconnectRequest::MESSAGE_TYPE: // No setup required
case PingRequest::MESSAGE_TYPE: // No setup required
break; // Skip all checks for these messages

View File

@@ -26,10 +26,6 @@ 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){};
@@ -228,9 +224,6 @@ 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;
@@ -357,9 +350,6 @@ 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,38 +224,6 @@ 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
@@ -377,10 +345,6 @@ 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
@@ -394,7 +358,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, const std::string &error_message) {
void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef 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);
@@ -406,7 +370,7 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std
}
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef 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) {
@@ -678,7 +642,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con
}
}
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message) {
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef 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);
@@ -688,7 +652,7 @@ void APIServer::send_action_response(uint32_t action_call_id, bool success, cons
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, const std::string &error_message,
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef 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,10 +59,6 @@ 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);
@@ -143,10 +139,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, const std::string &error_message);
void handle_action_response(uint32_t call_id, bool success, StringRef error_message);
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
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);
void handle_action_response(uint32_t call_id, bool success, StringRef 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
@@ -165,9 +161,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, const std::string &error_message);
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message,
void send_action_response(uint32_t action_call_id, bool success, StringRef 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
@@ -256,9 +252,6 @@ 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_PASSWORD, CONF_PORT, __version__
from esphome.const import CONF_KEY, CONF_PORT, __version__
from esphome.core import CORE
from . import CONF_ENCRYPTION
@@ -35,7 +35,6 @@ 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
@@ -50,7 +49,7 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
cli = APIClient(
addresses[0], # Primary address for compatibility
port,
password,
"", # Password auth removed in 2026.1.0
client_info=f"ESPHome Logs {__version__}",
noise_psk=noise_psk,
addresses=addresses, # Pass all addresses for automatic retry

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, const std::string &error_message) : success_(success), error_message_(error_message) {}
ActionResponse(bool success, StringRef error_message) : success_(success), error_message_(error_message) {}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
ActionResponse(bool success, const std::string &error_message, const uint8_t *data, size_t data_len)
ActionResponse(bool success, StringRef 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,9 +833,6 @@ 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.
@@ -873,20 +870,7 @@ class ProtoService {
return true;
}
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
}
inline bool check_authenticated_() { return this->check_connection_setup_(); }
};
} // 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, error_message);
this->parent_->send_action_response(call_id, success, StringRef(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, error_message,
this->parent_->send_action_response(call_id, success, StringRef(error_message),
reinterpret_cast<const uint8_t *>(json_str.data()), json_str.size());
return;
}
#endif
this->parent_->send_action_response(call_id, success, error_message);
this->parent_->send_action_response(call_id, success, StringRef(error_message));
}
protected:

View File

@@ -47,7 +47,10 @@ struct DNSAnswer {
void DNSServer::start(const network::IPAddress &ip) {
this->server_ip_ = ip;
ESP_LOGV(TAG, "Starting DNS server on %s", ip.str().c_str());
#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
// Create loop-monitored UDP socket
this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP);

View File

@@ -128,7 +128,9 @@ 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_); }
std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); }
size_t CH422GGPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "EXIO%u via CH422G", this->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;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void set_parent(CH422GComponent *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }

View File

@@ -1,11 +1,13 @@
#include "cse7766.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.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();
@@ -70,8 +72,8 @@ bool CSE7766Component::check_byte_() {
void CSE7766Component::parse_data_() {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{
std::string s = format_hex_pretty(this->raw_data_, sizeof(this->raw_data_));
ESP_LOGVV(TAG, "Raw data: %s", s.c_str());
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_)));
}
#endif

View File

@@ -7,6 +7,7 @@
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",
@@ -68,8 +69,11 @@ 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(ptr, length, '.', false).c_str());
format_hex_pretty_to(hex_buf, ptr, length, '.'));
#endif
this->dc_pin_->digital_write(false);
this->enable();

View File

@@ -85,6 +85,7 @@ 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 = [
@@ -109,6 +110,21 @@ 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
@@ -566,6 +582,16 @@ 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(
@@ -694,6 +720,9 @@ 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.
@@ -1017,6 +1046,16 @@ 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,10 +97,8 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
gpio_isr_handler_add(this->get_pin_num(), func, arg);
}
std::string ESP32InternalGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast<uint32_t>(this->pin_));
return buffer;
size_t ESP32InternalGPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "GPIO%" PRIu32, static_cast<uint32_t>(this->pin_));
}
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;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return this->pin_; }

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_STATE, CONF_TRIGGER_ID
from esphome.const import CONF_ID, CONF_ON_START, CONF_ON_STATE, CONF_TRIGGER_ID
AUTO_LOAD = ["esp32_ble_server", "improv_base"]
CODEOWNERS = ["@jesserockz"]
@@ -15,7 +15,6 @@ 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,6 +4,7 @@
#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
@@ -14,6 +15,7 @@ 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
@@ -314,7 +316,11 @@ void ESP32ImprovComponent::dump_config() {
void ESP32ImprovComponent::process_incoming_data_() {
uint8_t length = this->incoming_data_[1];
ESP_LOGV(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
#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
if (this->incoming_data_.size() - 3 == length) {
this->set_error_(improv::ERROR_NONE);
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
@@ -403,8 +409,12 @@ void ESP32ImprovComponent::check_wifi_connection_() {
#ifdef USE_WEBSERVER
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
if (ip.is_ip4()) {
char url_buffer[64];
snprintf(url_buffer, sizeof(url_buffer), "http://%s:%d", ip.str().c_str(), USE_WEBSERVER_PORT);
// "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);
url_strings[url_count++] = url_buffer;
break;
}

View File

@@ -28,6 +28,7 @@ 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
@@ -192,7 +193,12 @@ async def to_code(config):
cg.add_platformio_option(
"extra_scripts",
["pre:testing_mode.py", "pre:exclude_updater.py", "post:post_build.py"],
[
"pre:testing_mode.py",
"pre:exclude_updater.py",
"pre:exclude_waveform.py",
"post:post_build.py",
],
)
conf = config[CONF_FRAMEWORK]
@@ -264,10 +270,24 @@ 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():
def copy_files() -> None:
dir = Path(__file__).parent
post_build_file = dir / "post_build.py.script"
copy_file_if_changed(
@@ -284,3 +304,8 @@ def copy_files():
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,4 +1,5 @@
import esphome.codegen as cg
from esphome.core import CORE
KEY_ESP8266 = "esp8266"
KEY_BOARD = "board"
@@ -6,6 +7,25 @@ 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

@@ -0,0 +1,50 @@
# 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,10 +98,8 @@ void ESP8266GPIOPin::pin_mode(gpio::Flags flags) {
pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT
}
std::string ESP8266GPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
return buffer;
size_t ESP8266GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "GPIO%u", this->pin_);
}
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;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return pin_; }

View File

@@ -0,0 +1,34 @@
#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,6 +1,7 @@
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
@@ -34,7 +35,9 @@ CONFIG_SCHEMA = cv.All(
)
async def to_code(config):
async def to_code(config) -> None:
require_waveform()
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await output.register_output(var, config)

View File

@@ -64,18 +64,6 @@ 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
@@ -140,11 +128,13 @@ 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",
format_mac_address_pretty(this->own_address_).c_str(), version, this->wifi_channel_);
own_addr_buf, version, this->wifi_channel_);
#ifdef USE_WIFI
ESP_LOGCONFIG(TAG, " Wi-Fi enabled: %s", YESNO(this->is_wifi_enabled()));
#endif
@@ -300,9 +290,12 @@ 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)];
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_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));
#endif
if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
@@ -321,8 +314,9 @@ void ESPNowComponent::loop() {
}
case ESPNowPacket::SENT: {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
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)));
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)));
#endif
if (this->current_send_packet_ != nullptr) {
this->current_send_packet_->callback_(packet->packet_.sent.status);
@@ -409,8 +403,9 @@ 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) {
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)));
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)));
if (packet->callback_ != nullptr) {
packet->callback_(err);
}
@@ -439,8 +434,9 @@ esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) {
esp_err_t err = esp_now_add_peer(&peer_info);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to add peer %s - %s", format_mac_address_pretty(peer).c_str(),
LOG_STR_ARG(espnow_error_to_str(err)));
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)));
this->status_momentary_warning("peer-add-failed");
return err;
}
@@ -468,8 +464,9 @@ 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) {
ESP_LOGE(TAG, "Failed to delete peer %s - %s", format_mac_address_pretty(peer).c_str(),
LOG_STR_ARG(espnow_error_to_str(err)));
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)));
this->status_momentary_warning("peer-del-failed");
return err;
}

View File

@@ -11,6 +11,7 @@ from esphome.const import (
CONF_SPEED,
DEVICE_CLASS_SPEED,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_MEASUREMENT_ANGLE,
UNIT_DEGREES,
UNIT_KILOMETER_PER_HOUR,
UNIT_METER,
@@ -21,6 +22,7 @@ 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"
@@ -50,7 +52,7 @@ CONFIG_SCHEMA = cv.All(
unit_of_measurement=UNIT_DEGREES,
icon=ICON_LONGITUDE,
accuracy_decimals=6,
state_class=STATE_CLASS_MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT_ANGLE,
),
cv.Optional(CONF_SPEED): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOMETER_PER_HOUR,
@@ -63,7 +65,7 @@ CONFIG_SCHEMA = cv.All(
unit_of_measurement=UNIT_DEGREES,
icon=ICON_COMPASS,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT_ANGLE,
),
cv.Optional(CONF_ALTITUDE): sensor.sensor_schema(
unit_of_measurement=UNIT_METER,
@@ -72,11 +74,14 @@ 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

@@ -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

@@ -25,11 +25,7 @@ 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); }
std::string HostGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
return buffer;
}
size_t HostGPIOPin::dump_summary(char *buffer, size_t len) const { return snprintf(buffer, len, "GPIO%u", this->pin_); }
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;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return pin_; }

View File

@@ -7,6 +7,8 @@ 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];
@@ -16,7 +18,10 @@ void HTE501Component::setup() {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(identification + 0, 7).c_str());
#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));
}
void HTE501Component::dump_config() {

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 a 'yield' expression!
This is a coroutine, you need to await it with an 'await' expression!
"""
parent = await cg.get_variable(config[CONF_I2C_ID])
cg.add(var.set_i2c_bus(parent))

View File

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

View File

@@ -1,4 +1,5 @@
#include "kuntze.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
@@ -10,11 +11,17 @@ 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;
ESP_LOGV(TAG, "Data: %s", format_hex_pretty(data).c_str());
#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()));
float value = (float) get_16bit(0);
for (int i = 0; i < data[3]; i++)

View File

@@ -413,7 +413,8 @@ bool LD2410Component::handle_ack_data_() {
return true;
}
if (!ld2410::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
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));
return true;
}
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
@@ -597,11 +598,17 @@ 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])) {
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#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
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])) {
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#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
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {

View File

@@ -457,7 +457,8 @@ bool LD2412Component::handle_ack_data_() {
return true;
}
if (!ld2412::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
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));
return true;
}
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
@@ -670,11 +671,17 @@ 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])) {
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#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
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])) {
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#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
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {

View File

@@ -607,7 +607,8 @@ bool LD2450Component::handle_ack_data_() {
return true;
}
if (!ld2450::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
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));
return true;
}
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
@@ -758,11 +759,17 @@ 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]) {
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#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
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])) {
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#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
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {

View File

@@ -63,10 +63,8 @@ void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) {
pinMode(pin_, flags_to_mode(flags)); // NOLINT
}
std::string ArduinoInternalGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%u", pin_);
return buffer;
size_t ArduinoInternalGPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u", this->pin_);
}
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;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return pin_; }

View File

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

View File

@@ -161,10 +161,8 @@ 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_); }
std::string MAX6956GPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%u via Max6956", pin_);
return buffer;
size_t MAX6956GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via Max6956", this->pin_);
}
} // 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;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void set_parent(MAX6956 *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }

View File

@@ -99,10 +99,8 @@ 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_); }
std::string MCP23016GPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%u via MCP23016", pin_);
return buffer;
size_t MCP23016GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via MCP23016", this->pin_);
}
} // 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;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) 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> std::string MCP23XXXGPIOPin<N>::dump_summary() const {
return str_snprintf("%u via MCP23XXX", 15, pin_);
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 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;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void set_parent(MCP23XXXBase<N> *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }

View File

@@ -13,6 +13,9 @@ 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;
@@ -28,6 +31,8 @@ void MHZ19Component::setup() {
} else if (this->abc_boot_logic_ == MHZ19_ABC_DISABLED) {
this->abc_disable();
}
this->range_set(this->detection_range_);
}
void MHZ19Component::update() {
@@ -86,6 +91,26 @@ 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())
@@ -99,7 +124,9 @@ 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_);
@@ -113,6 +140,23 @@ 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,7 +8,18 @@
namespace esphome {
namespace mhz19 {
enum MHZ19ABCLogic { MHZ19_ABC_NONE = 0, MHZ19_ABC_ENABLED, MHZ19_ABC_DISABLED };
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,
};
class MHZ19Component : public PollingComponent, public uart::UARTDevice {
public:
@@ -21,11 +32,13 @@ 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);
@@ -33,37 +46,32 @@ 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...> {
template<typename... Ts> class MHZ19CalibrateZeroAction : public Action<Ts...>, public Parented<MHZ19Component> {
public:
MHZ19CalibrateZeroAction(MHZ19Component *mhz19) : mhz19_(mhz19) {}
void play(const Ts &...x) override { this->mhz19_->calibrate_zero(); }
protected:
MHZ19Component *mhz19_;
void play(const Ts &...x) override { this->parent_->calibrate_zero(); }
};
template<typename... Ts> class MHZ19ABCEnableAction : public Action<Ts...> {
template<typename... Ts> class MHZ19ABCEnableAction : public Action<Ts...>, public Parented<MHZ19Component> {
public:
MHZ19ABCEnableAction(MHZ19Component *mhz19) : mhz19_(mhz19) {}
void play(const Ts &...x) override { this->mhz19_->abc_enable(); }
protected:
MHZ19Component *mhz19_;
void play(const Ts &...x) override { this->parent_->abc_enable(); }
};
template<typename... Ts> class MHZ19ABCDisableAction : public Action<Ts...> {
template<typename... Ts> class MHZ19ABCDisableAction : public Action<Ts...>, public Parented<MHZ19Component> {
public:
MHZ19ABCDisableAction(MHZ19Component *mhz19) : mhz19_(mhz19) {}
void play(const Ts &...x) override { this->parent_->abc_disable(); }
};
void play(const Ts &...x) override { this->mhz19_->abc_disable(); }
template<typename... Ts> class MHZ19DetectionRangeSetAction : public Action<Ts...>, public Parented<MHZ19Component> {
public:
TEMPLATABLE_VALUE(MHZ19DetectionRange, detection_range)
protected:
MHZ19Component *mhz19_;
void play(const Ts &...x) override { this->parent_->range_set(this->detection_range_.value(x...)); }
};
} // namespace mhz19

View File

@@ -19,14 +19,33 @@ DEPENDENCIES = ["uart"]
CONF_AUTOMATIC_BASELINE_CALIBRATION = "automatic_baseline_calibration"
CONF_WARMUP_TIME = "warmup_time"
CONF_DETECTION_RANGE = "detection_range"
mhz19_ns = cg.esphome_ns.namespace("mhz19")
MHZ19Component = mhz19_ns.class_("MHZ19Component", cg.PollingComponent, uart.UARTDevice)
MHZ19CalibrateZeroAction = mhz19_ns.class_(
"MHZ19CalibrateZeroAction", automation.Action
"MHZ19CalibrateZeroAction", automation.Action, cg.Parented.template(MHZ19Component)
)
MHZ19ABCEnableAction = mhz19_ns.class_("MHZ19ABCEnableAction", automation.Action)
MHZ19ABCDisableAction = mhz19_ns.class_("MHZ19ABCDisableAction", automation.Action)
MHZ19ABCEnableAction = mhz19_ns.class_(
"MHZ19ABCEnableAction", automation.Action, cg.Parented.template(MHZ19Component)
)
MHZ19ABCDisableAction = mhz19_ns.class_(
"MHZ19ABCDisableAction", automation.Action, cg.Parented.template(MHZ19Component)
)
MHZ19DetectionRangeSetAction = mhz19_ns.class_(
"MHZ19DetectionRangeSetAction",
automation.Action,
cg.Parented.template(MHZ19Component),
)
mhz19_detection_range = mhz19_ns.enum("MHZ19DetectionRange")
MHZ19_DETECTION_RANGE_ENUM = {
2000: mhz19_detection_range.MHZ19_DETECTION_RANGE_0_2000PPM,
5000: mhz19_detection_range.MHZ19_DETECTION_RANGE_0_5000PPM,
10000: mhz19_detection_range.MHZ19_DETECTION_RANGE_0_10000PPM,
}
_validate_ppm = cv.float_with_unit("parts per million", "ppm")
CONFIG_SCHEMA = (
cv.Schema(
@@ -49,6 +68,9 @@ CONFIG_SCHEMA = (
cv.Optional(
CONF_WARMUP_TIME, default="75s"
): cv.positive_time_period_seconds,
cv.Optional(CONF_DETECTION_RANGE): cv.All(
_validate_ppm, cv.enum(MHZ19_DETECTION_RANGE_ENUM)
),
}
)
.extend(cv.polling_component_schema("60s"))
@@ -78,8 +100,11 @@ async def to_code(config):
cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME]))
if CONF_DETECTION_RANGE in config:
cg.add(var.set_detection_range(config[CONF_DETECTION_RANGE]))
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
NO_ARGS_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(MHZ19Component),
}
@@ -87,14 +112,37 @@ CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
@automation.register_action(
"mhz19.calibrate_zero", MHZ19CalibrateZeroAction, CALIBRATION_ACTION_SCHEMA
"mhz19.calibrate_zero", MHZ19CalibrateZeroAction, NO_ARGS_ACTION_SCHEMA
)
@automation.register_action(
"mhz19.abc_enable", MHZ19ABCEnableAction, CALIBRATION_ACTION_SCHEMA
"mhz19.abc_enable", MHZ19ABCEnableAction, NO_ARGS_ACTION_SCHEMA
)
@automation.register_action(
"mhz19.abc_disable", MHZ19ABCDisableAction, CALIBRATION_ACTION_SCHEMA
"mhz19.abc_disable", MHZ19ABCDisableAction, NO_ARGS_ACTION_SCHEMA
)
async def mhz19_calibration_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
async def mhz19_no_args_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
RANGE_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(MHZ19Component),
cv.Required(CONF_DETECTION_RANGE): cv.All(
_validate_ppm, cv.enum(MHZ19_DETECTION_RANGE_ENUM)
),
}
)
@automation.register_action(
"mhz19.detection_range_set", MHZ19DetectionRangeSetAction, RANGE_ACTION_SCHEMA
)
async def mhz19_detection_range_set_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
detection_range = config.get(CONF_DETECTION_RANGE)
template_ = await cg.templatable(detection_range, args, mhz19_detection_range)
cg.add(var.set_detection_range(template_))
return var

View File

@@ -153,10 +153,8 @@ void MPR121GPIOPin::digital_write(bool value) {
this->parent_->digital_write(this->pin_ - 4, value != this->inverted_);
}
std::string MPR121GPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "ELE%u on MPR121", this->pin_);
return buffer;
size_t MPR121GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "ELE%u on MPR121", this->pin_);
}
} // namespace mpr121

View File

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

View File

@@ -153,13 +153,14 @@ void MQTTClientComponent::on_log(uint8_t level, const char *tag, const char *mes
#endif
void MQTTClientComponent::dump_config() {
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGCONFIG(TAG,
"MQTT:\n"
" Server Address: %s:%u (%s)\n"
" Username: " LOG_SECRET("'%s'") "\n"
" Client ID: " LOG_SECRET("'%s'") "\n"
" Clean Session: %s",
this->credentials_.address.c_str(), this->credentials_.port, this->ip_.str().c_str(),
this->credentials_.address.c_str(), this->credentials_.port, this->ip_.str_to(ip_buf),
this->credentials_.username.c_str(), this->credentials_.client_id.c_str(),
YESNO(this->credentials_.clean_session));
if (this->is_discovery_ip_enabled()) {
@@ -246,7 +247,8 @@ void MQTTClientComponent::check_dnslookup_() {
return;
}
ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.str().c_str());
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.str_to(ip_buf));
this->start_connect_();
}
#if defined(USE_ESP8266) && LWIP_VERSION_MAJOR == 1

View File

@@ -7,12 +7,14 @@
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion.upload.arduino";
static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16;
// Followed guide
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
@@ -89,8 +91,10 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
EspClass::getFreeHeap());
upload_first_chunk_sent_ = true;
if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
ESP_LOGD(TAG, "Recv: [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGD(
TAG, "Recv: [%s]",
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()));
uint32_t result = 0;
for (int j = 0; j < 4; ++j) {
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
@@ -107,8 +111,10 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
buffer = nullptr;
return range_end + 1;
} else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok"
ESP_LOGE(TAG, "Invalid response: [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGE(
TAG, "Invalid response: [%s]",
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()));
// Deallocate buffer
allocator.deallocate(buffer, 4096);
buffer = nullptr;
@@ -274,8 +280,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
this->recv_ret_string_(response, 5000, true); // This can take some time to return
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGD(TAG, "Upload resp: [%s] %zu B",
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(response.data()), response.size()),
response.length());
ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap());

View File

@@ -9,12 +9,14 @@
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion.upload.esp32";
static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16;
// Followed guide
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
@@ -110,8 +112,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r
#endif
upload_first_chunk_sent_ = true;
if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
ESP_LOGD(TAG, "Recv: [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGD(
TAG, "Recv: [%s]",
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()));
uint32_t result = 0;
for (int j = 0; j < 4; ++j) {
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
@@ -128,8 +132,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r
buffer = nullptr;
return range_end + 1;
} else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok"
ESP_LOGE(TAG, "Invalid response: [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGE(
TAG, "Invalid response: [%s]",
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()));
// Deallocate buffer
allocator.deallocate(buffer, 4096);
buffer = nullptr;
@@ -287,8 +293,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
this->recv_ret_string_(response, 5000, true); // This can take some time to return
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGD(TAG, "Upload resp: [%s] %zu B",
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(response.data()), response.size()),
response.length());
ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());

View File

@@ -12,6 +12,7 @@ from esphome.components.zephyr import (
zephyr_add_prj_conf,
zephyr_data,
zephyr_set_core_data,
zephyr_setup_preferences,
zephyr_to_code,
)
from esphome.components.zephyr.const import (
@@ -49,7 +50,7 @@ from .const import (
from .gpio import nrf52_pin_to_code # noqa
CODEOWNERS = ["@tomaszduda23"]
AUTO_LOAD = ["zephyr"]
AUTO_LOAD = ["zephyr", "preferences"]
IS_TARGET_PLATFORM = True
_LOGGER = logging.getLogger(__name__)
@@ -194,6 +195,7 @@ async def to_code(config: ConfigType) -> None:
cg.add_platformio_option("board_upload.require_upload_port", "true")
cg.add_platformio_option("board_upload.wait_for_upload_port", "true")
zephyr_setup_preferences()
zephyr_to_code(config)
if dfu_config := config.get(CONF_DFU):
@@ -206,6 +208,18 @@ async def to_code(config: ConfigType) -> None:
if reg0_config[CONF_UICR_ERASE]:
cg.add_define("USE_NRF52_UICR_ERASE")
# c++ support
zephyr_add_prj_conf("CPLUSPLUS", True)
zephyr_add_prj_conf("LIB_CPLUSPLUS", True)
# watchdog
zephyr_add_prj_conf("WATCHDOG", True)
zephyr_add_prj_conf("WDT_DISABLE_AT_BOOT", False)
# disable console
zephyr_add_prj_conf("UART_CONSOLE", False)
zephyr_add_prj_conf("CONSOLE", False)
# use NFC pins as GPIO
zephyr_add_prj_conf("NFCT_PINS_AS_GPIOS", True)
@coroutine_with_priority(CoroPriority.DIAGNOSTICS)
async def _dfu_to_code(dfu_config):

View File

@@ -71,8 +71,15 @@ NRF52_PIN_SCHEMA = cv.All(
@pins.PIN_SCHEMA_REGISTRY.register(PLATFORM_NRF52, NRF52_PIN_SCHEMA)
async def nrf52_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER]
port = num // 32
pin_name_prefix = f"P{port}."
var = cg.new_Pvariable(
config[CONF_ID],
cg.RawExpression(f"DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpio{port}))"),
32,
pin_name_prefix,
)
cg.add(var.set_pin(num))
# Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)

View File

@@ -32,7 +32,7 @@ async def register_one_wire_device(var, config):
Sets the 1-wire bus to use and the 1-wire address.
This is a coroutine, you need to await it with a 'yield' expression!
This is a coroutine, you need to await it with an 'await' expression!
"""
parent = await cg.get_variable(config[CONF_ONE_WIRE_ID])
cg.add(var.set_one_wire_bus(parent))

View File

@@ -7,6 +7,7 @@
#include "opentherm.h"
#include "esphome/core/helpers.h"
#include <cinttypes>
#ifdef USE_ESP32
#include "driver/timer.h"
#include "esp_err.h"
@@ -569,8 +570,8 @@ void OpenTherm::debug_data(OpenthermData &data) {
to_string(data.f88()).c_str());
}
void OpenTherm::debug_error(OpenThermError &error) const {
ESP_LOGD(TAG, "data: %s; clock: %s; capture: %s; bit_pos: %s", format_hex(error.data).c_str(),
to_string(clock_).c_str(), format_bin(error.capture).c_str(), to_string(error.bit_pos).c_str());
ESP_LOGD(TAG, "data: 0x%08" PRIx32 "; clock: %u; capture: 0x%08" PRIx32 "; bit_pos: %u", error.data, this->clock_,
error.capture, error.bit_pos);
}
float OpenthermData::f88() { return ((float) this->s16()) / 256.0; }

View File

@@ -1,11 +1,15 @@
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "packet_transport.h"
#include "esphome/components/xxtea/xxtea.h"
namespace esphome {
namespace packet_transport {
// Maximum bytes to log in hex output (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t PACKET_MAX_LOG_BYTES = 168;
/**
* Structure of a data packet; everything is little-endian
*
@@ -263,7 +267,8 @@ void PacketTransport::flush_() {
xxtea::encrypt((uint32_t *) (encode_buffer.data() + header_len), len / 4,
(uint32_t *) this->encryption_key_.data());
}
ESP_LOGVV(TAG, "Sending packet %s", format_hex_pretty(encode_buffer.data(), encode_buffer.size()).c_str());
char hex_buf[format_hex_pretty_size(PACKET_MAX_LOG_BYTES)];
ESP_LOGVV(TAG, "Sending packet %s", format_hex_pretty_to(hex_buf, encode_buffer.data(), encode_buffer.size()));
this->send_packet(encode_buffer);
}
@@ -505,8 +510,9 @@ void PacketTransport::process_(const std::vector<uint8_t> &data) {
}
if (decoder.get(byte) == DECODE_OK) {
ESP_LOGW(TAG, "Unknown key %X", byte);
char hex_buf[format_hex_pretty_size(PACKET_MAX_LOG_BYTES)];
ESP_LOGD(TAG, "Buffer pos: %zu contents: %s", data.size() - decoder.get_remaining_size(),
format_hex_pretty(data).c_str());
format_hex_pretty_to(hex_buf, data.data(), data.size()));
}
break;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -164,7 +164,9 @@ bool PI4IOE5V6408GPIOPin::digital_read() { return this->parent_->digital_read(th
void PI4IOE5V6408GPIOPin::digital_write(bool value) {
this->parent_->digital_write(this->pin_, value != this->inverted_);
}
std::string PI4IOE5V6408GPIOPin::dump_summary() const { return str_sprintf("%u via PI4IOE5V6408", this->pin_); }
size_t PI4IOE5V6408GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via PI4IOE5V6408", this->pin_);
}
} // namespace pi4ioe5v6408
} // namespace esphome

View File

@@ -52,7 +52,7 @@ class PI4IOE5V6408GPIOPin : public GPIOPin, public Parented<PI4IOE5V6408Componen
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_inverted(bool inverted) { this->inverted_ = inverted; }

View File

@@ -12,6 +12,9 @@ static const uint8_t WAIT_I_RQ = 0x30; // RxIRq and IdleIRq
static const char *const TAG = "rc522";
// Max UID size for RFID tags (4, 7, or 10 bytes)
static constexpr size_t RC522_MAX_UID_SIZE = 10;
static const uint8_t RESET_COUNT = 5;
void RC522::setup() {
@@ -191,8 +194,9 @@ void RC522::loop() {
if (status == STATUS_TIMEOUT) {
ESP_LOGV(TAG, "STATE_READ_SERIAL_DONE -> TIMEOUT (no tag present) %d", status);
} else {
char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
ESP_LOGW(TAG, "Unexpected response. Read status is %d. Read bytes: %d (%s)", status, back_length_,
format_hex_pretty(buffer_, back_length_, '-', false).c_str());
format_hex_pretty_to(hex_buf, buffer_, back_length_, '-'));
}
state_ = STATE_DONE;
@@ -237,13 +241,18 @@ void RC522::loop() {
trigger->process(rfid_uid);
if (report) {
ESP_LOGD(TAG, "Found new tag '%s'", format_hex_pretty(rfid_uid, '-', false).c_str());
char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
ESP_LOGD(TAG, "Found new tag '%s'", format_hex_pretty_to(uid_buf, rfid_uid.data(), rfid_uid.size(), '-'));
}
break;
}
case STATE_DONE: {
if (!this->current_uid_.empty()) {
ESP_LOGV(TAG, "Tag '%s' removed", format_hex_pretty(this->current_uid_, '-', false).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
ESP_LOGV(TAG, "Tag '%s' removed",
format_hex_pretty_to(uid_buf, this->current_uid_.data(), this->current_uid_.size(), '-'));
#endif
for (auto *trigger : this->triggers_ontagremoved_)
trigger->process(this->current_uid_);
}
@@ -338,7 +347,10 @@ void RC522::pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
void RC522::pcd_transceive_data_(uint8_t send_len) {
ESP_LOGV(TAG, "PCD TRANSCEIVE: RX: %s", format_hex_pretty(buffer_, send_len, '-', false).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
ESP_LOGV(TAG, "PCD TRANSCEIVE: RX: %s", format_hex_pretty_to(hex_buf, buffer_, send_len, '-'));
#endif
delayMicroseconds(1000); // we need 1 ms delay between antenna on and those communication commands
send_len_ = send_len;
// Prepare values for BitFramingReg
@@ -412,8 +424,11 @@ RC522::StatusCode RC522::await_transceive_() {
error_reg_value); // TODO: is this always due to collissions?
return STATUS_ERROR;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
ESP_LOGV(TAG, "received %d bytes: %s", back_length_,
format_hex_pretty(buffer_ + send_len_, back_length_, '-', false).c_str());
format_hex_pretty_to(hex_buf, buffer_ + send_len_, back_length_, '-'));
#endif
return STATUS_OK;
}

View File

@@ -1,4 +1,5 @@
#include "haier_protocol.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -12,6 +13,8 @@ constexpr uint32_t BIT_MARK_US = 540;
constexpr uint32_t BIT_ONE_SPACE_US = 1650;
constexpr uint32_t BIT_ZERO_SPACE_US = 580;
constexpr unsigned int HAIER_IR_PACKET_BIT_SIZE = 112;
// Max data bytes in packet (excluding checksum)
constexpr size_t HAIER_MAX_DATA_BYTES = (HAIER_IR_PACKET_BIT_SIZE / 8);
void HaierProtocol::encode_byte_(RemoteTransmitData *dst, uint8_t item) {
for (uint8_t mask = 1 << 7; mask != 0; mask >>= 1) {
@@ -77,7 +80,8 @@ optional<HaierData> HaierProtocol::decode(RemoteReceiveData src) {
}
void HaierProtocol::dump(const HaierData &data) {
ESP_LOGI(TAG, "Received Haier: %s", format_hex_pretty(data.data).c_str());
char hex_buf[format_hex_pretty_size(HAIER_MAX_DATA_BYTES)];
ESP_LOGI(TAG, "Received Haier: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size()));
}
} // namespace remote_base

View File

@@ -1,4 +1,5 @@
#include "mirage_protocol.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -13,9 +14,12 @@ constexpr uint32_t BIT_ONE_SPACE_US = 1592;
constexpr uint32_t BIT_ZERO_SPACE_US = 545;
constexpr unsigned int MIRAGE_IR_PACKET_BIT_SIZE = 120;
// Max data bytes in packet (excluding checksum)
constexpr size_t MIRAGE_MAX_DATA_BYTES = (MIRAGE_IR_PACKET_BIT_SIZE / 8);
void MirageProtocol::encode(RemoteTransmitData *dst, const MirageData &data) {
ESP_LOGI(TAG, "Transive Mirage: %s", format_hex_pretty(data.data).c_str());
char hex_buf[format_hex_pretty_size(MIRAGE_MAX_DATA_BYTES)];
ESP_LOGI(TAG, "Transmit Mirage: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size()));
dst->set_carrier_frequency(38000);
dst->reserve(5 + ((data.data.size() + 1) * 2));
dst->mark(HEADER_MARK_US);
@@ -77,7 +81,8 @@ optional<MirageData> MirageProtocol::decode(RemoteReceiveData src) {
}
void MirageProtocol::dump(const MirageData &data) {
ESP_LOGI(TAG, "Received Mirage: %s", format_hex_pretty(data.data).c_str());
char hex_buf[format_hex_pretty_size(MIRAGE_MAX_DATA_BYTES)];
ESP_LOGI(TAG, "Received Mirage: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size()));
}
} // namespace remote_base

View File

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

View File

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

View File

@@ -270,7 +270,10 @@ void ShellyDimmer::send_settings_() {
}
bool ShellyDimmer::send_command_(uint8_t cmd, const uint8_t *const payload, uint8_t len) {
ESP_LOGD(TAG, "Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len, format_hex(payload, len).c_str());
// Buffer for hex formatting: max frame size * 2 + null (covers any payload)
char hex_buf[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE * 2 + 1];
ESP_LOGD(TAG, "Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len,
format_hex_to(hex_buf, sizeof(hex_buf), payload, len));
// Prepare a command frame.
uint8_t frame[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE];

View File

@@ -64,7 +64,9 @@ float SN74HC165Component::get_setup_priority() const { return setup_priority::IO
bool SN74HC165GPIOPin::digital_read() { return this->parent_->digital_read_(this->pin_) != this->inverted_; }
std::string SN74HC165GPIOPin::dump_summary() const { return str_snprintf("%u via SN74HC165", 18, pin_); }
size_t SN74HC165GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via SN74HC165", this->pin_);
}
} // namespace sn74hc165
} // namespace esphome

View File

@@ -47,7 +47,7 @@ class SN74HC165GPIOPin : public GPIOPin, public Parented<SN74HC165Component> {
void pin_mode(gpio::Flags flags) override {}
bool digital_read() override;
void digital_write(bool value) override{};
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void set_pin(uint16_t pin) { pin_ = pin; }
void set_inverted(bool inverted) { inverted_ = inverted; }

View File

@@ -93,7 +93,9 @@ float SN74HC595Component::get_setup_priority() const { return setup_priority::IO
void SN74HC595GPIOPin::digital_write(bool value) {
this->parent_->digital_write_(this->pin_, value != this->inverted_);
}
std::string SN74HC595GPIOPin::dump_summary() const { return str_snprintf("%u via SN74HC595", 18, pin_); }
size_t SN74HC595GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via SN74HC595", this->pin_);
}
} // namespace sn74hc595
} // namespace esphome

View File

@@ -54,7 +54,7 @@ class SN74HC595GPIOPin : public GPIOPin, public Parented<SN74HC595Component> {
void pin_mode(gpio::Flags flags) override {}
bool digital_read() override { return false; }
void digital_write(bool value) override;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void set_pin(uint16_t pin) { pin_ = pin; }
void set_inverted(bool inverted) { inverted_ = inverted; }

View File

@@ -42,12 +42,17 @@
* M 6C - CRC over bytes 2 to F (Addition)
\*********************************************************************************************/
#include "sonoff_d1.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace sonoff_d1 {
static const char *const TAG = "sonoff_d1";
// Protocol constants
static constexpr size_t SONOFF_D1_ACK_SIZE = 7;
static constexpr size_t SONOFF_D1_MAX_CMD_SIZE = 17;
uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) {
uint8_t crc = 0;
for (size_t i = 2; i < len - 1; i++) {
@@ -86,8 +91,11 @@ bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) {
// Read a minimal packet
if (this->read_array(cmd, 6)) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(6)];
ESP_LOGV(TAG, "[%04d] Reading from dimmer:", this->write_count_);
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, 6).c_str());
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, cmd, 6));
#endif
if (cmd[0] != 0xAA || cmd[1] != 0x55) {
ESP_LOGW(TAG, "[%04d] RX: wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]);
@@ -101,7 +109,10 @@ bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) {
return false;
}
if (this->read_array(&cmd[6], cmd[5] + 1 /*checksum suffix*/)) {
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(&cmd[6], cmd[5] + 1).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf2[format_hex_pretty_size(SONOFF_D1_MAX_CMD_SIZE)];
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf2, &cmd[6], cmd[5] + 1));
#endif
// Check the checksum
uint8_t valid_checksum = this->calc_checksum_(cmd, cmd[5] + 7);
@@ -145,9 +156,10 @@ bool SonoffD1Output::read_ack_(const uint8_t *cmd, const size_t len) {
ESP_LOGD(TAG, "[%04d] Acknowledge received", this->write_count_);
return true;
} else {
char hex_buf[format_hex_pretty_size(SONOFF_D1_ACK_SIZE)];
ESP_LOGW(TAG, "[%04d] Unexpected acknowledge received (possible clash of RF/HA commands), expected ack was:",
this->write_count_);
ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(ref_buffer, sizeof(ref_buffer)).c_str());
ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, ref_buffer, sizeof(ref_buffer)));
}
return false;
}
@@ -174,8 +186,11 @@ bool SonoffD1Output::write_command_(uint8_t *cmd, const size_t len, bool needs_a
// 2. UART command initiated by this component can clash with a command initiated by RF
uint32_t retries = 10;
do {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(SONOFF_D1_MAX_CMD_SIZE)];
ESP_LOGV(TAG, "[%04d] Writing to the dimmer:", this->write_count_);
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, len).c_str());
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, cmd, len));
#endif
this->write_array(cmd, len);
this->write_count_++;
if (!needs_ack)

View File

@@ -64,9 +64,9 @@ void SPIComponent::setup() {
void SPIComponent::dump_config() {
ESP_LOGCONFIG(TAG, "SPI bus:");
LOG_PIN(" CLK Pin: ", this->clk_pin_)
LOG_PIN(" SDI Pin: ", this->sdi_pin_)
LOG_PIN(" SDO Pin: ", this->sdo_pin_)
LOG_PIN(" CLK Pin: ", this->clk_pin_);
LOG_PIN(" SDI Pin: ", this->sdi_pin_);
LOG_PIN(" SDO Pin: ", this->sdo_pin_);
for (size_t i = 0; i != this->data_pins_.size(); i++) {
ESP_LOGCONFIG(TAG, " Data pin %u: GPIO%d", i, this->data_pins_[i]);
}

View File

@@ -120,7 +120,11 @@ class NullPin : public GPIOPin {
void digital_write(bool value) override {}
std::string dump_summary() const override { return std::string(); }
size_t dump_summary(char *buffer, size_t len) const override {
if (len > 0)
buffer[0] = '\0';
return 0;
}
protected:
static GPIOPin *const NULL_PIN; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

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

View File

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

View File

@@ -138,7 +138,9 @@ void TCA9555GPIOPin::setup() { this->pin_mode(this->flags_); }
void TCA9555GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
bool TCA9555GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void TCA9555GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
std::string TCA9555GPIOPin::dump_summary() const { return str_sprintf("%u via TCA9555", this->pin_); }
size_t TCA9555GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via TCA9555", this->pin_);
}
} // namespace tca9555
} // namespace esphome

View File

@@ -48,7 +48,7 @@ class TCA9555GPIOPin : public GPIOPin, public Parented<TCA9555Component> {
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_inverted(bool inverted) { this->inverted_ = inverted; }

View File

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

View File

@@ -0,0 +1,136 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import water_heater
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MODE,
CONF_OPTIMISTIC,
CONF_RESTORE_MODE,
CONF_SET_ACTION,
CONF_SUPPORTED_MODES,
CONF_TARGET_TEMPERATURE,
)
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
from .. import template_ns
CONF_CURRENT_TEMPERATURE = "current_temperature"
TemplateWaterHeater = template_ns.class_(
"TemplateWaterHeater", water_heater.WaterHeater
)
TemplateWaterHeaterPublishAction = template_ns.class_(
"TemplateWaterHeaterPublishAction",
automation.Action,
cg.Parented.template(TemplateWaterHeater),
)
TemplateWaterHeaterRestoreMode = template_ns.enum("TemplateWaterHeaterRestoreMode")
RESTORE_MODES = {
"NO_RESTORE": TemplateWaterHeaterRestoreMode.WATER_HEATER_NO_RESTORE,
"RESTORE": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE,
"RESTORE_AND_CALL": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE_AND_CALL,
}
CONFIG_SCHEMA = water_heater.water_heater_schema(TemplateWaterHeater).extend(
{
cv.Optional(CONF_OPTIMISTIC, default=True): cv.boolean,
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum(
RESTORE_MODES, upper=True
),
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_MODE): cv.templatable(water_heater.validate_water_heater_mode),
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
water_heater.validate_water_heater_mode
),
}
)
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await water_heater.register_water_heater(var, config)
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
if CONF_SET_ACTION in config:
await automation.build_automation(
var.get_set_trigger(), [], config[CONF_SET_ACTION]
)
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
if CONF_CURRENT_TEMPERATURE in config:
conf = config[CONF_CURRENT_TEMPERATURE]
if not isinstance(conf, cv.Lambda):
conf = cv.Lambda(f"return {conf};")
template_ = await cg.process_lambda(
conf,
[],
return_type=cg.optional.template(cg.float_),
)
cg.add(var.set_current_temperature_lambda(template_))
if CONF_MODE in config:
conf = config[CONF_MODE]
if not isinstance(conf, cv.Lambda):
enum_value_str = str(conf).rsplit("::", maxsplit=1)[-1]
conf = cv.Lambda(f"return water_heater::{enum_value_str};")
template_ = await cg.process_lambda(
conf,
[],
return_type=cg.optional.template(water_heater.WaterHeaterMode),
)
cg.add(var.set_mode_lambda(template_))
if CONF_SUPPORTED_MODES in config:
modes = [
water_heater.WATER_HEATER_MODES[mode]
for mode in config[CONF_SUPPORTED_MODES]
]
cg.add(var.set_supported_modes(modes))
@automation.register_action(
"water_heater.template.publish",
TemplateWaterHeaterPublishAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(TemplateWaterHeater),
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_MODE): cv.templatable(
water_heater.validate_water_heater_mode
),
}
),
)
async def water_heater_template_publish_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
if current_temp := config.get(CONF_CURRENT_TEMPERATURE):
template_ = await cg.templatable(current_temp, args, float)
cg.add(var.set_current_temperature(template_))
if target_temp := config.get(CONF_TARGET_TEMPERATURE):
template_ = await cg.templatable(target_temp, args, float)
cg.add(var.set_target_temperature(template_))
if mode := config.get(CONF_MODE):
template_ = await cg.templatable(mode, args, water_heater.WaterHeaterMode)
cg.add(var.set_mode(template_))
return var

View File

@@ -0,0 +1,35 @@
#pragma once
#include "template_water_heater.h"
#include "esphome/core/automation.h"
namespace esphome::template_ {
template<typename... Ts>
class TemplateWaterHeaterPublishAction : public Action<Ts...>, public Parented<TemplateWaterHeater> {
public:
TEMPLATABLE_VALUE(float, current_temperature)
TEMPLATABLE_VALUE(float, target_temperature)
TEMPLATABLE_VALUE(water_heater::WaterHeaterMode, mode)
void play(const Ts &...x) override {
if (this->current_temperature_.has_value()) {
this->parent_->set_current_temperature(this->current_temperature_.value(x...));
}
bool needs_call = this->target_temperature_.has_value() || this->mode_.has_value();
if (needs_call) {
auto call = this->parent_->make_call();
if (this->target_temperature_.has_value()) {
call.set_target_temperature(this->target_temperature_.value(x...));
}
if (this->mode_.has_value()) {
call.set_mode(this->mode_.value(x...));
}
call.perform();
} else {
this->parent_->publish_state();
}
}
};
} // namespace esphome::template_

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