Compare commits

..

54 Commits

Author SHA1 Message Date
J. Nick Koston
36b5f0aaf0 [api] Eliminate heap allocations in Home Assistant state callbacks 2025-12-30 10:17:17 -10:00
Jonathan Swoboda
96c47f3b4d Merge branch 'release' into dev 2025-12-30 09:31:44 -05:00
Jonathan Swoboda
5b5cede5f9 Merge pull request #12752 from esphome/bump-2025.12.3
2025.12.3
2025-12-30 09:31:31 -05:00
Jonathan Swoboda
c737033cc4 Bump version to 2025.12.3 2025-12-30 09:22:03 -05:00
J. Nick Koston
0194bfd9ea [core] Fix incremental build failures when adding components on ESP32-Arduino (#12745) 2025-12-30 09:22:03 -05:00
J. Nick Koston
339399eb70 [lvgl] Fix lambdas in canvas actions called from outside LVGL context (#12671) 2025-12-30 09:22:03 -05:00
Samuel Sieb
a615b28ecf [bme68x_bsec2] add id: to allow extending (#12649) 2025-12-29 23:22:36 -08:00
bakroistvan
468bd7b04f [dallas_temp] higher precision for logged temperature (#12695) 2025-12-29 22:53:28 -08:00
Jonathan Swoboda
4c16afeacb [esp32] Add IDF framework source for Arduino builds (#12731)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-12-29 22:25:26 -05:00
J. Nick Koston
d86c05bfe6 [esp32] Breaking Change: Change default framework to ESP-IDF (#12746)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-30 03:23:41 +00:00
J. Nick Koston
63464a13c3 [core] Fix incremental build failures when adding components on ESP32-Arduino (#12745) 2025-12-29 16:57:22 -10:00
Clyde Stubbs
20e43398fa [cli] Report program path on host (#12743) 2025-12-30 13:21:30 +11:00
hsand
2e7cdad532 [pvvx_mithermometer] fix displaying negative numbers (#12735) 2025-12-29 13:58:38 -08:00
dependabot[bot]
636cccc6a3 Bump aioesphomeapi from 43.9.0 to 43.9.1 (#12724)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 09:55:26 -10:00
Thomas Rupprecht
93e2a1bd1a [tests] improve mipi_spi variable naming (#12716) 2025-12-29 14:21:07 -05:00
Thomas Rupprecht
dd3beb5841 [tests] fix typo mipi tests (#12715) 2025-12-29 14:20:38 -05:00
Thomas Rupprecht
97af01c5ed [usb_host] sort esp32 variants (#12720) 2025-12-29 14:19:36 -05:00
J. Nick Koston
7e362cdafc [ota] Use precision format specifier for auth logging (#12706)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-29 08:43:54 -10:00
Jonathan Swoboda
890d531cea [esp32] Bump to ESP-IDF 5.5.2, Arduino 3.3.5, platform 55.3.35 (#12681)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-12-29 11:35:54 -05:00
Swaptor
6a6c6b648f [internal_temperature] Add ESP32-C5 support (#12713)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-12-29 11:32:32 -05:00
dependabot[bot]
d0673122a8 Bump aioesphomeapi from 43.8.0 to 43.9.0 (#12702) 2025-12-28 18:15:06 -10:00
dependabot[bot]
5cbef3ef95 Bump aioesphomeapi from 43.7.0 to 43.8.0 (#12701) 2025-12-29 03:15:40 +00:00
dependabot[bot]
a1e0121330 Bump bleak from 2.0.0 to 2.1.0 (#12700)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-28 16:48:20 -10:00
dependabot[bot]
eb050ff13e Bump aioesphomeapi from 43.6.0 to 43.7.0 (#12699)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-28 16:48:08 -10:00
Jonathan Swoboda
45e61f100c [core] Replace USE_ESP_IDF with USE_ESP32 across components (#12673)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-27 11:59:55 -10:00
J. Nick Koston
5e99dd14ae [ethernet] Eliminate heap allocations in dump_config logging (#12665) 2025-12-27 08:36:35 -10:00
J. Nick Koston
a6097f4a0f [wifi] Eliminate heap allocations in dump_config logging (#12664)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-27 08:36:19 -10:00
J. Nick Koston
f243e609a5 [wifi] Use StringRef and std::span in WiFiConnectStateListener to avoid allocations (#12672) 2025-12-27 08:35:58 -10:00
J. Nick Koston
be0bf1e5b9 [lvgl] Fix lambdas in canvas actions called from outside LVGL context (#12671) 2025-12-27 08:35:36 -10:00
J. Nick Koston
a275f37135 [udp] Use stack buffer for listen address logging in dump_config (#12667) 2025-12-27 08:35:16 -10:00
J. Nick Koston
e9f2d75aab [core] Add format_hex_to helper for zero-allocation hex formatting (#12670) 2025-12-27 08:34:45 -10:00
J. Nick Koston
34067f8b15 [esp8266] Native OTA backend to reduce Arduino dependencies (#12675)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-27 08:29:15 -10:00
J. Nick Koston
bdc087148a [wifi_info] Reduce heap allocations in text sensor formatting (#12660) 2025-12-26 12:52:41 -10:00
J. Nick Koston
5a2e0612a8 [web_server] Use C++17 nested namespace syntax (#12663) 2025-12-26 08:44:34 -10:00
J. Nick Koston
f1fecd22e3 [web_server] Move HTTP header strings to flash on ESP8266 (#12668) 2025-12-26 08:44:17 -10:00
J. Nick Koston
0919017d49 [wifi] Avoid unnecessary string copy in failed connection logging (#12659) 2025-12-26 08:44:03 -10:00
J. Nick Koston
963f594c9e [text_sensor] Return state by const reference to avoid copies (#12661) 2025-12-26 07:58:46 -10:00
J. Nick Koston
4f70663658 [alarm_control_panel] Use C++17 nested namespace and remove unused include (#12662) 2025-12-26 07:57:33 -10:00
dependabot[bot]
958a35e262 Bump aioesphomeapi from 43.5.0 to 43.6.0 (#12644)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-24 14:17:52 -10:00
J. Nick Koston
0c566c6f00 [core] Deprecate get_object_id() and migrate remaining usages to get_object_id_to() (#12629) 2025-12-23 06:59:07 -10:00
Jonathan Swoboda
ba73289b28 Merge branch 'release' into dev 2025-12-23 11:17:15 -05:00
Jonathan Swoboda
99f7e9aeb7 Merge pull request #12632 from esphome/bump-2025.12.2
2025.12.2
2025-12-23 11:17:01 -05:00
Jonathan Swoboda
ebb6babb3d Fix hash 2025-12-23 09:26:38 -05:00
Jonathan Swoboda
0922f240e0 Bump version to 2025.12.2 2025-12-23 09:23:04 -05:00
Jonathan Swoboda
c8fb694dcb [cc1101] Fix packet mode RSSI/LQI (#12630)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 09:23:04 -05:00
J. Nick Koston
6054685dae [esp32_camera] Throttle frame logging to reduce overhead and improve throughput (#12586) 2025-12-23 09:23:04 -05:00
Anna Oake
61ec3508ed [cc1101] Fix option defaults and move them to YAML (#12608) 2025-12-23 09:23:04 -05:00
Leo Bergolth
086ec770ea send NIL ("-") as timestamp if time source is not valid (#12588) 2025-12-23 09:23:04 -05:00
Stuart Parmenter
b055f5b4bf [hub75] Bump esp-hub75 version to 0.1.7 (#12564) 2025-12-23 09:23:00 -05:00
Eduard Llull
726db746c8 [display_menu_base] Call on_value_ after updating the select (#12584) 2025-12-23 09:21:54 -05:00
Keith Burzinski
1922455fa7 [wifi] Fix for wifi_info when static IP is configured (#12576) 2025-12-23 09:21:54 -05:00
Thomas Rupprecht
dc943d7e7a [pca9685,sx126x,sx127x] Use frequency/float_range check (#12490)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-23 09:21:54 -05:00
Jonathan Swoboda
ffefa8929e [cc1101] Fix packet mode RSSI/LQI (#12630)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 09:05:48 -05:00
J. Nick Koston
7d5342bca5 [logger] Host: Use fwrite() with explicit length and remove platform branching (#12628) 2025-12-22 16:45:22 -10:00
184 changed files with 1694 additions and 2508 deletions

View File

@@ -1 +1 @@
4268ab0b5150f79ab1c317e8f3834c8bb0b4c8122da4f6b1fd67c49d0f2098c9
94557f94be073390342833aff12ef8676a8b597db5fa770a5a1232e9425cb48f

View File

@@ -780,13 +780,6 @@ def command_vscode(args: ArgsProtocol) -> int | None:
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
# Set memory analysis options in config
if args.analyze_memory:
config.setdefault(CONF_ESPHOME, {})["analyze_memory"] = True
if args.memory_report:
config.setdefault(CONF_ESPHOME, {})["memory_report_file"] = args.memory_report
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
@@ -796,7 +789,13 @@ def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
exit_code = compile_program(args, config)
if exit_code != 0:
return exit_code
_LOGGER.info("Successfully compiled program.")
if CORE.is_host:
from esphome.platformio_api import get_idedata
program_path = str(get_idedata(config).firmware_elf_path)
_LOGGER.info("Successfully compiled program to path '%s'", program_path)
else:
_LOGGER.info("Successfully compiled program.")
return 0
@@ -846,10 +845,8 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
if CORE.is_host:
from esphome.platformio_api import get_idedata
idedata = get_idedata(config)
if idedata is None:
return 1
program_path = idedata.raw["prog_path"]
program_path = str(get_idedata(config).firmware_elf_path)
_LOGGER.info("Running program from path '%s'", program_path)
return run_external_process(program_path)
# Get devices, resolving special identifiers like OTA
@@ -1266,17 +1263,6 @@ def parse_args(argv):
help="Only generate source code, do not compile.",
action="store_true",
)
parser_compile.add_argument(
"--analyze-memory",
help="Analyze and display memory usage by component after compilation.",
action="store_true",
)
parser_compile.add_argument(
"--memory-report",
help="Save memory analysis report to a file (supports .json or .txt).",
type=str,
metavar="FILE",
)
parser_upload = subparsers.add_parser(
"upload",

View File

@@ -1,7 +1,6 @@
"""CLI interface for memory analysis with report generation."""
from collections import defaultdict
import json
import sys
from . import (
@@ -298,28 +297,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
return "\n".join(lines)
def to_json(self) -> str:
"""Export analysis results as JSON."""
data = {
"components": {
name: {
"text": mem.text_size,
"rodata": mem.rodata_size,
"data": mem.data_size,
"bss": mem.bss_size,
"flash_total": mem.flash_total,
"ram_total": mem.ram_total,
"symbol_count": mem.symbol_count,
}
for name, mem in self.components.items()
},
"totals": {
"flash": sum(c.flash_total for c in self.components.values()),
"ram": sum(c.ram_total for c in self.components.values()),
},
}
return json.dumps(data, indent=2)
def dump_uncategorized_symbols(self, output_file: str | None = None) -> None:
"""Dump uncategorized symbols for analysis."""
# Sort by size descending

View File

@@ -8,8 +8,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
namespace esphome::alarm_control_panel {
static const char *const TAG = "alarm_control_panel";
@@ -115,5 +114,4 @@ void AlarmControlPanel::disarm(optional<std::string> code) {
call.perform();
}
} // namespace alarm_control_panel
} // namespace esphome
} // namespace esphome::alarm_control_panel

View File

@@ -1,7 +1,5 @@
#pragma once
#include <map>
#include "alarm_control_panel_call.h"
#include "alarm_control_panel_state.h"
@@ -9,8 +7,7 @@
#include "esphome/core/entity_base.h"
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
namespace esphome::alarm_control_panel {
enum AlarmControlPanelFeature : uint8_t {
// Matches Home Assistant values
@@ -141,5 +138,4 @@ class AlarmControlPanel : public EntityBase {
LazyCallbackManager<void()> ready_callback_{};
};
} // namespace alarm_control_panel
} // namespace esphome
} // namespace esphome::alarm_control_panel

View File

@@ -4,8 +4,7 @@
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
namespace esphome::alarm_control_panel {
static const char *const TAG = "alarm_control_panel";
@@ -99,5 +98,4 @@ void AlarmControlPanelCall::perform() {
}
}
} // namespace alarm_control_panel
} // namespace esphome
} // namespace esphome::alarm_control_panel

View File

@@ -6,8 +6,7 @@
#include "esphome/core/helpers.h"
namespace esphome {
namespace alarm_control_panel {
namespace esphome::alarm_control_panel {
class AlarmControlPanel;
@@ -36,5 +35,4 @@ class AlarmControlPanelCall {
void validate_();
};
} // namespace alarm_control_panel
} // namespace esphome
} // namespace esphome::alarm_control_panel

View File

@@ -1,7 +1,6 @@
#include "alarm_control_panel_state.h"
namespace esphome {
namespace alarm_control_panel {
namespace esphome::alarm_control_panel {
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) {
switch (state) {
@@ -30,5 +29,4 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat
}
}
} // namespace alarm_control_panel
} // namespace esphome
} // namespace esphome::alarm_control_panel

View File

@@ -3,8 +3,7 @@
#include <cstdint>
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
namespace esphome::alarm_control_panel {
enum AlarmControlPanelState : uint8_t {
ACP_STATE_DISARMED = 0,
@@ -25,5 +24,4 @@ enum AlarmControlPanelState : uint8_t {
*/
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state);
} // namespace alarm_control_panel
} // namespace esphome
} // namespace esphome::alarm_control_panel

View File

@@ -3,8 +3,7 @@
#include "esphome/core/automation.h"
#include "alarm_control_panel.h"
namespace esphome {
namespace alarm_control_panel {
namespace esphome::alarm_control_panel {
/// Trigger on any state change
class StateTrigger : public Trigger<> {
@@ -165,5 +164,4 @@ template<typename... Ts> class AlarmControlPanelCondition : public Condition<Ts.
AlarmControlPanel *parent_;
};
} // namespace alarm_control_panel
} // namespace esphome
} // namespace esphome::alarm_control_panel

View File

@@ -131,10 +131,8 @@ void APIConnection::start() {
this->fatal_error_with_log_(LOG_STR("Helper init failed"), err);
return;
}
// Initialize client name with peername (IP address) until Hello message provides actual name
char peername[socket::PEERNAME_MAX_LEN];
this->helper_->getpeername_to(peername);
this->client_info_.name = peername;
this->client_info_.peername = helper_->getpeername();
this->client_info_.name = this->client_info_.peername;
}
APIConnection::~APIConnection() {
@@ -254,7 +252,8 @@ void APIConnection::loop() {
// Disconnect if not responded within 2.5*keepalive
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
on_fatal_error();
this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("is unresponsive; disconnecting"));
ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(),
this->client_info_.peername.c_str());
}
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
// Only send ping if we're not disconnecting
@@ -288,7 +287,7 @@ bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
// remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response
// close will happen on next loop
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("disconnected"));
ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
this->flags_.next_close = true;
DisconnectResponse resp;
return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
@@ -1506,10 +1505,9 @@ void APIConnection::complete_authentication_() {
}
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected"));
ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
// Trigger expects std::string, get fresh peername from socket
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->helper_->getpeername());
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
#endif
#ifdef USE_HOMEASSISTANT_TIME
if (homeassistant::global_homeassistant_time != nullptr) {
@@ -1525,12 +1523,11 @@ void APIConnection::complete_authentication_() {
bool APIConnection::send_hello_response(const HelloRequest &msg) {
this->client_info_.name.assign(reinterpret_cast<const char *>(msg.client_info), msg.client_info_len);
this->client_info_.peername = this->helper_->getpeername();
this->client_api_version_major_ = msg.api_version_major;
this->client_api_version_minor_ = msg.api_version_minor;
char peername[socket::PEERNAME_MAX_LEN];
this->helper_->getpeername_to(peername);
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.name.c_str(),
peername, this->client_api_version_major_, this->client_api_version_minor_);
this->client_info_.peername.c_str(), this->client_api_version_major_, this->client_api_version_minor_);
HelloResponse resp;
resp.api_version_major = 1;
@@ -1714,9 +1711,8 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
continue;
}
// Create temporary string for callback (callback takes const std::string &)
// Handle empty state (nullptr with len=0)
std::string state(msg.state_len > 0 ? reinterpret_cast<const char *>(msg.state) : "", msg.state_len);
// Create StringRef directly from message data (zero allocation)
StringRef state(reinterpret_cast<const char *>(msg.state), msg.state_len);
it.callback(state);
}
}
@@ -1851,12 +1847,12 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
#ifdef USE_API_PASSWORD
void APIConnection::on_unauthenticated_access() {
this->on_fatal_error();
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no authentication"));
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();
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no connection setup"));
ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
}
void APIConnection::on_fatal_error() {
this->helper_->close();
@@ -2104,18 +2100,9 @@ void APIConnection::process_state_subscriptions_() {
}
#endif // USE_API_HOMEASSISTANT_STATES
void APIConnection::log_client_(int level, const LogString *message) {
char peername[socket::PEERNAME_MAX_LEN];
this->helper_->getpeername_to(peername);
esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->client_info_.name.c_str(), peername,
LOG_STR_ARG(message));
}
void APIConnection::log_warning_(const LogString *message, APIError err) {
char peername[socket::PEERNAME_MAX_LEN];
this->helper_->getpeername_to(peername);
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), peername, LOG_STR_ARG(message),
LOG_STR_ARG(api_error_to_logstr(err)), errno);
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(),
LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno);
}
} // namespace esphome::api

View File

@@ -17,9 +17,8 @@ namespace esphome::api {
// Client information structure
struct ClientInfo {
std::string name; // Client name from Hello message
// Note: peername (IP address) is not stored here to save memory.
// Use helper_->getpeername_to() or helper_->getpeername() when needed.
std::string name; // Client name from Hello message
std::string peername; // IP:port from socket
};
// Keepalive timeout in milliseconds
@@ -291,12 +290,7 @@ class APIConnection final : public APIServerConnection {
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
const std::string &get_name() const { return this->client_info_.name; }
/// Get peer name (IP address) into a stack buffer - avoids heap allocation
size_t get_peername_to(std::span<char, socket::PEERNAME_MAX_LEN> buf) const {
return this->helper_->getpeername_to(buf);
}
/// Get peer name as std::string - use sparingly, allocates on heap
std::string get_peername() const { return this->helper_->getpeername(); }
const std::string &get_peername() const { return this->client_info_.peername; }
protected:
// Helper function to handle authentication completion
@@ -758,8 +752,6 @@ class APIConnection final : public APIServerConnection {
return this->schedule_batch_();
}
// Helper function to log client messages with name and peername
void log_client_(int level, const LogString *message);
// Helper function to log API errors with errno
void log_warning_(const LogString *message, APIError err);
// Helper to handle fatal errors with logging

View File

@@ -13,16 +13,8 @@ namespace esphome::api {
static const char *const TAG = "api.frame_helper";
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define HELPER_LOG(msg, ...) \
do { \
char peername__[socket::PEERNAME_MAX_LEN]; \
this->socket_->getpeername_to(peername__); \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), peername__, ##__VA_ARGS__); \
} while (0)
#else
#define HELPER_LOG(msg, ...) ((void) 0)
#endif
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())

View File

@@ -91,7 +91,6 @@ class APIFrameHelper {
bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
std::string getpeername() { return socket_->getpeername(); }
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
size_t getpeername_to(std::span<char, socket::PEERNAME_MAX_LEN> buf) { return socket_->getpeername_to(buf); }
APIError close() {
state_ = State::CLOSED;
int err = this->socket_->close();

View File

@@ -24,16 +24,8 @@ static const char *const PROLOGUE_INIT = "NoiseAPIInit";
#endif
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define HELPER_LOG(msg, ...) \
do { \
char peername__[socket::PEERNAME_MAX_LEN]; \
this->socket_->getpeername_to(peername__); \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), peername__, ##__VA_ARGS__); \
} while (0)
#else
#define HELPER_LOG(msg, ...) ((void) 0)
#endif
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())

View File

@@ -18,16 +18,8 @@ namespace esphome::api {
static const char *const TAG = "api.plaintext";
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define HELPER_LOG(msg, ...) \
do { \
char peername__[socket::PEERNAME_MAX_LEN]; \
this->socket_->getpeername_to(peername__); \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), peername__, ##__VA_ARGS__); \
} while (0)
#else
#define HELPER_LOG(msg, ...) ((void) 0)
#endif
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())

View File

@@ -125,18 +125,15 @@ void APIServer::loop() {
if (!sock)
break;
char peername[socket::PEERNAME_MAX_LEN];
sock->getpeername_to(peername);
// Check if we're at the connection limit
if (this->clients_.size() >= this->max_connections_) {
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername);
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str());
// Immediately close - socket destructor will handle cleanup
sock.reset();
continue;
}
ESP_LOGD(TAG, "Accept %s", peername);
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this);
this->clients_.emplace_back(conn);
@@ -169,7 +166,8 @@ void APIServer::loop() {
// Network is down - disconnect all clients
for (auto &client : this->clients_) {
client->on_fatal_error();
client->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Network down; disconnect"));
ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(),
client->client_info_.peername.c_str());
}
// Continue to process and clean up the clients below
}
@@ -187,8 +185,7 @@ void APIServer::loop() {
// Rare case: handle disconnection
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
// Trigger expects std::string, get fresh peername from socket
this->client_disconnected_trigger_->trigger(client->client_info_.name, client->get_peername());
this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername);
#endif
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
this->unregister_active_action_calls_for_connection(client.get());
@@ -426,18 +423,18 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std
#endif // USE_API_HOMEASSISTANT_SERVICES
#ifdef USE_API_HOMEASSISTANT_STATES
// Helper to add subscription (reduces duplication)
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute,
std::function<void(const std::string &)> f, bool once) {
// Helper to add subscription (reduces duplication) - const char* version (zero allocation)
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
bool once) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once,
// entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation)
});
}
// Helper to add subscription with heap-allocated strings (reduces duplication)
// Helper to add subscription with heap-allocated strings and StringRef callback
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f, bool once) {
std::function<void(StringRef)> f, bool once) {
HomeAssistantStateSubscription sub;
// Allocate heap storage for the strings
sub.entity_id_dynamic_storage = std::make_unique<std::string>(std::move(entity_id));
@@ -455,18 +452,38 @@ void APIServer::add_state_subscription_(std::string entity_id, optional<std::str
this->state_subs_.push_back(std::move(sub));
}
// Legacy helper: wraps std::string callback and delegates to StringRef version
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f, bool once) {
// Wrap callback to convert StringRef -> std::string, then delegate
this->add_state_subscription_(std::move(entity_id), std::move(attribute),
std::function<void(StringRef)>([f = std::move(f)](StringRef state) { f(state.str()); }),
once);
}
// New const char* overload (for internal components - zero allocation)
void APIServer::subscribe_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(const std::string &)> f) {
std::function<void(StringRef)> f) {
this->add_state_subscription_(entity_id, attribute, std::move(f), false);
}
void APIServer::get_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(const std::string &)> f) {
std::function<void(StringRef)> f) {
this->add_state_subscription_(entity_id, attribute, std::move(f), true);
}
// Existing std::string overload (for custom_api_device.h - heap allocation)
// std::string overload with StringRef callback (zero-allocation callback)
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
}
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string)
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);

View File

@@ -195,7 +195,7 @@ class APIServer : public Component,
struct HomeAssistantStateSubscription {
const char *entity_id; // Pointer to flash (internal) or heap (external)
const char *attribute; // Pointer to flash or nullptr (nullptr means no attribute)
std::function<void(const std::string &)> callback;
std::function<void(StringRef)> callback;
bool once;
// Dynamic storage for external components using std::string API (custom_api_device.h)
@@ -205,12 +205,16 @@ class APIServer : public Component,
};
// New const char* overload (for internal components - zero allocation)
void subscribe_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(const std::string &)> f);
void get_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(const std::string &)> f);
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
// Existing std::string overload (for custom_api_device.h - heap allocation)
// std::string overload with StringRef callback (for custom_api_device.h with zero-allocation callback)
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f);
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string for callback)
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
@@ -238,7 +242,9 @@ class APIServer : public Component,
#endif // USE_API_NOISE
#ifdef USE_API_HOMEASSISTANT_STATES
// Helper methods to reduce code duplication
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(const std::string &)> f,
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
bool once);
void add_state_subscription_(std::string entity_id, optional<std::string> attribute, std::function<void(StringRef)> f,
bool once);
void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f, bool once);

View File

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

View File

@@ -1,6 +1,6 @@
#include "audio_reader.h"
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"

View File

@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include "audio.h"
#include "audio_transfer_buffer.h"

View File

@@ -1,8 +1,8 @@
#include "bh1750.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome::bh1750 {
namespace esphome {
namespace bh1750 {
static const char *const TAG = "bh1750.sensor";
@@ -13,31 +13,6 @@ static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011;
static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000;
static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001;
static constexpr uint32_t MEASUREMENT_TIMEOUT_MS = 2000;
static constexpr float HIGH_LIGHT_THRESHOLD_LX = 7000.0f;
// Measurement time constants (datasheet values)
static constexpr uint16_t MTREG_DEFAULT = 69;
static constexpr uint16_t MTREG_MIN = 31;
static constexpr uint16_t MTREG_MAX = 254;
static constexpr uint16_t MEAS_TIME_L_MS = 24; // L-resolution max measurement time @ mtreg=69
static constexpr uint16_t MEAS_TIME_H_MS = 180; // H/H2-resolution max measurement time @ mtreg=69
// Conversion constants (datasheet formulas)
static constexpr float RESOLUTION_DIVISOR = 1.2f; // counts to lux conversion divisor
static constexpr float MODE_H2_DIVISOR = 2.0f; // H2 mode has 2x higher resolution
// MTreg calculation constants
static constexpr int COUNTS_TARGET = 50000; // Target counts for optimal range (avoid saturation)
static constexpr int COUNTS_NUMERATOR = 10;
static constexpr int COUNTS_DENOMINATOR = 12;
// MTreg register bit manipulation constants
static constexpr uint8_t MTREG_HI_SHIFT = 5; // High 3 bits start at bit 5
static constexpr uint8_t MTREG_HI_MASK = 0b111; // 3-bit mask for high bits
static constexpr uint8_t MTREG_LO_SHIFT = 0; // Low 5 bits start at bit 0
static constexpr uint8_t MTREG_LO_MASK = 0b11111; // 5-bit mask for low bits
/*
bh1750 properties:
@@ -68,7 +43,74 @@ void BH1750Sensor::setup() {
this->mark_failed();
return;
}
this->state_ = IDLE;
}
void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f) {
// turn on (after one-shot sensor automatically powers down)
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Power on failed");
f(NAN);
return;
}
if (active_mtreg_ != mtreg) {
// set mtreg
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111);
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111);
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set measurement time failed");
active_mtreg_ = 0;
f(NAN);
return;
}
active_mtreg_ = mtreg;
}
uint8_t cmd;
uint16_t meas_time;
switch (mode) {
case BH1750_MODE_L:
cmd = BH1750_COMMAND_ONE_TIME_L;
meas_time = 24 * mtreg / 69;
break;
case BH1750_MODE_H:
cmd = BH1750_COMMAND_ONE_TIME_H;
meas_time = 180 * mtreg / 69;
break;
case BH1750_MODE_H2:
cmd = BH1750_COMMAND_ONE_TIME_H2;
meas_time = 180 * mtreg / 69;
break;
default:
f(NAN);
return;
}
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Start measurement failed");
f(NAN);
return;
}
// probably not needed, but adjust for rounding
meas_time++;
this->set_timeout("read", meas_time, [this, mode, mtreg, f]() {
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read data failed");
f(NAN);
return;
}
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / 1.2f;
lx *= 69.0f / mtreg;
if (mode == BH1750_MODE_H2)
lx /= 2.0f;
f(lx);
});
}
void BH1750Sensor::dump_config() {
@@ -82,189 +124,45 @@ void BH1750Sensor::dump_config() {
}
void BH1750Sensor::update() {
const uint32_t now = millis();
// Start coarse measurement to determine optimal mode/mtreg
if (this->state_ != IDLE) {
// Safety timeout: reset if stuck
if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) {
ESP_LOGW(TAG, "Measurement timeout, resetting state");
this->state_ = IDLE;
} else {
ESP_LOGW(TAG, "Previous measurement not complete, skipping update");
// first do a quick measurement in L-mode with full range
// to find right range
this->read_lx_(BH1750_MODE_L, 31, [this](float val) {
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
}
if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
this->state_ = WAITING_COARSE_MEASUREMENT;
this->enable_loop(); // Enable loop while measurement in progress
}
void BH1750Sensor::loop() {
const uint32_t now = App.get_loop_component_start_time();
switch (this->state_) {
case IDLE:
// Disable loop when idle to save cycles
this->disable_loop();
break;
case WAITING_COARSE_MEASUREMENT:
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
this->state_ = READING_COARSE_RESULT;
}
break;
case READING_COARSE_RESULT: {
float lx;
if (!this->read_measurement_(lx)) {
this->fail_and_reset_();
break;
}
this->process_coarse_result_(lx);
// Start fine measurement with optimal settings
// fetch millis() again since the read can take a bit
if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, millis())) {
this->fail_and_reset_();
break;
}
this->state_ = WAITING_FINE_MEASUREMENT;
break;
BH1750Mode use_mode;
uint8_t use_mtreg;
if (val <= 7000) {
use_mode = BH1750_MODE_H2;
use_mtreg = 254;
} else {
use_mode = BH1750_MODE_H;
// lx = counts / 1.2 * (69 / mtreg)
// -> mtreg = counts / 1.2 * (69 / lx)
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
// -> mtreg = 50000*(10/12)*(69/lx)
int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val);
use_mtreg = std::min(254, std::max(31, ideal_mtreg));
}
ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg);
case WAITING_FINE_MEASUREMENT:
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
this->state_ = READING_FINE_RESULT;
this->read_lx_(use_mode, use_mtreg, [this](float val) {
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
break;
case READING_FINE_RESULT: {
float lx;
if (!this->read_measurement_(lx)) {
this->fail_and_reset_();
break;
}
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val);
this->status_clear_warning();
this->publish_state(lx);
this->state_ = IDLE;
break;
}
}
}
bool BH1750Sensor::start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now) {
// Power on
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Power on failed");
return false;
}
// Set MTreg if changed
if (this->active_mtreg_ != mtreg) {
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK);
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK);
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set measurement time failed");
this->active_mtreg_ = 0;
return false;
}
this->active_mtreg_ = mtreg;
}
// Start measurement
uint8_t cmd;
uint16_t meas_time;
switch (mode) {
case BH1750_MODE_L:
cmd = BH1750_COMMAND_ONE_TIME_L;
meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT;
break;
case BH1750_MODE_H:
cmd = BH1750_COMMAND_ONE_TIME_H;
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
break;
case BH1750_MODE_H2:
cmd = BH1750_COMMAND_ONE_TIME_H2;
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
break;
default:
return false;
}
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Start measurement failed");
return false;
}
// Store current measurement parameters
this->current_mode_ = mode;
this->current_mtreg_ = mtreg;
this->measurement_start_time_ = now;
this->measurement_duration_ = meas_time + 1; // Add 1ms for safety
return true;
}
bool BH1750Sensor::read_measurement_(float &lx_out) {
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read data failed");
return false;
}
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / RESOLUTION_DIVISOR;
lx *= float(MTREG_DEFAULT) / this->current_mtreg_;
if (this->current_mode_ == BH1750_MODE_H2) {
lx /= MODE_H2_DIVISOR;
}
lx_out = lx;
return true;
}
void BH1750Sensor::process_coarse_result_(float lx) {
if (std::isnan(lx)) {
// Use defaults if coarse measurement failed
this->fine_mode_ = BH1750_MODE_H2;
this->fine_mtreg_ = MTREG_MAX;
return;
}
if (lx <= HIGH_LIGHT_THRESHOLD_LX) {
this->fine_mode_ = BH1750_MODE_H2;
this->fine_mtreg_ = MTREG_MAX;
} else {
this->fine_mode_ = BH1750_MODE_H;
// lx = counts / 1.2 * (69 / mtreg)
// -> mtreg = counts / 1.2 * (69 / lx)
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
// -> mtreg = 50000*(10/12)*(69/lx)
int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx);
this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg));
}
ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_);
}
void BH1750Sensor::fail_and_reset_() {
this->status_set_warning();
this->publish_state(NAN);
this->state_ = IDLE;
this->publish_state(val);
});
});
}
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace esphome::bh1750
} // namespace bh1750
} // namespace esphome

View File

@@ -4,9 +4,10 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome::bh1750 {
namespace esphome {
namespace bh1750 {
enum BH1750Mode : uint8_t {
enum BH1750Mode {
BH1750_MODE_L,
BH1750_MODE_H,
BH1750_MODE_H2,
@@ -20,36 +21,13 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
void setup() override;
void dump_config() override;
void update() override;
void loop() override;
float get_setup_priority() const override;
protected:
// State machine states
enum State : uint8_t {
IDLE,
WAITING_COARSE_MEASUREMENT,
READING_COARSE_RESULT,
WAITING_FINE_MEASUREMENT,
READING_FINE_RESULT,
};
void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f);
// 4-byte aligned members
uint32_t measurement_start_time_{0};
uint32_t measurement_duration_{0};
// 1-byte members grouped together to minimize padding
State state_{IDLE};
BH1750Mode current_mode_{BH1750_MODE_L};
uint8_t current_mtreg_{31};
BH1750Mode fine_mode_{BH1750_MODE_H2};
uint8_t fine_mtreg_{254};
uint8_t active_mtreg_{0};
// Helper methods
bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now);
bool read_measurement_(float &lx_out);
void process_coarse_result_(float lx);
void fail_and_reset_();
};
} // namespace esphome::bh1750
} // namespace bh1750
} // namespace esphome

View File

@@ -50,6 +50,7 @@ TYPES = [
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(cg.Component),
cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,

View File

@@ -97,10 +97,6 @@ async def to_code(config):
cg.add_define("USE_CAPTIVE_PORTAL")
if CORE.using_arduino:
if CORE.is_esp32:
cg.add_library("ESP32 Async UDP", None)
cg.add_library("DNSServer", None)
cg.add_library("WiFi", None)
if CORE.is_esp8266:
cg.add_library("DNSServer", None)
if CORE.is_libretiny:
@@ -110,6 +106,9 @@ async def to_code(config):
# Only compile the ESP-IDF DNS server when using ESP-IDF framework
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"dns_server_esp32_idf.cpp": {PlatformFramework.ESP32_IDF},
"dns_server_esp32_idf.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
}
)

View File

@@ -69,12 +69,11 @@ void CaptivePortal::start() {
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
#ifdef USE_ESP_IDF
#if defined(USE_ESP32)
// Create DNS server instance for ESP-IDF
this->dns_server_ = make_unique<DNSServer>();
this->dns_server_->start(ip);
#endif
#ifdef USE_ARDUINO
#elif defined(USE_ARDUINO)
this->dns_server_ = make_unique<DNSServer>();
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
this->dns_server_->start(53, ESPHOME_F("*"), ip);

View File

@@ -2,11 +2,10 @@
#include "esphome/core/defines.h"
#ifdef USE_CAPTIVE_PORTAL
#include <memory>
#ifdef USE_ARDUINO
#include <DNSServer.h>
#endif
#ifdef USE_ESP_IDF
#if defined(USE_ESP32)
#include "dns_server_esp32_idf.h"
#elif defined(USE_ARDUINO)
#include <DNSServer.h>
#endif
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
@@ -23,15 +22,14 @@ class CaptivePortal : public AsyncWebHandler, public Component {
void setup() override;
void dump_config() override;
void loop() override {
#ifdef USE_ARDUINO
if (this->dns_server_ != nullptr) {
this->dns_server_->processNextRequest();
}
#endif
#ifdef USE_ESP_IDF
#if defined(USE_ESP32)
if (this->dns_server_ != nullptr) {
this->dns_server_->process_next_request();
}
#elif defined(USE_ARDUINO)
if (this->dns_server_ != nullptr) {
this->dns_server_->processNextRequest();
}
#endif
}
float get_setup_priority() const override;
@@ -64,7 +62,7 @@ class CaptivePortal : public AsyncWebHandler, public Component {
web_server_base::WebServerBase *base_;
bool initialized_{false};
bool active_{false};
#if defined(USE_ARDUINO) || defined(USE_ESP_IDF)
#if defined(USE_ARDUINO) || defined(USE_ESP32)
std::unique_ptr<DNSServer> dns_server_{nullptr};
#endif
};

View File

@@ -1,5 +1,5 @@
#include "dns_server_esp32_idf.h"
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
@@ -202,4 +202,4 @@ void DNSServer::process_next_request() {
} // namespace esphome::captive_portal
#endif // USE_ESP_IDF
#endif // USE_ESP32

View File

@@ -1,5 +1,5 @@
#pragma once
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include <memory>
#include "esphome/core/helpers.h"
@@ -24,4 +24,4 @@ class DNSServer {
} // namespace esphome::captive_portal
#endif // USE_ESP_IDF
#endif // USE_ESP32

View File

@@ -169,14 +169,16 @@ void CC1101Component::loop() {
}
// Read packet
uint8_t payload_length;
uint8_t payload_length, expected_rx;
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
this->read_(Register::FIFO, &payload_length, 1);
expected_rx = payload_length + 1;
} else {
payload_length = this->state_.PKTLEN;
expected_rx = payload_length;
}
if (payload_length == 0 || payload_length > 64) {
ESP_LOGW(TAG, "Invalid payload length: %u", payload_length);
if (payload_length == 0 || payload_length > 64 || rx_bytes != expected_rx) {
ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length);
this->enter_idle_();
this->strobe_(Command::FRX);
this->strobe_(Command::RX);
@@ -186,13 +188,12 @@ void CC1101Component::loop() {
this->packet_.resize(payload_length);
this->read_(Register::FIFO, this->packet_.data(), payload_length);
// Read status and trigger
uint8_t status[2];
this->read_(Register::FIFO, status, 2);
int8_t rssi_raw = static_cast<int8_t>(status[0]);
float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET;
bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0;
uint8_t lqi = status[1] & STATUS_LQI_MASK;
// Read status from registers (more reliable than FIFO status bytes due to timing issues)
this->read_(Register::RSSI);
this->read_(Register::LQI);
float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET;
bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0;
uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK;
if (this->state_.CRC_EN == 0 || crc_ok) {
this->packet_trigger_->trigger(this->packet_, rssi, lqi);
}
@@ -616,12 +617,15 @@ void CC1101Component::set_packet_mode(bool value) {
this->state_.GDO0_CFG = 0x01;
// Set max RX FIFO threshold to ensure we only trigger on end-of-packet
this->state_.FIFO_THR = 15;
// Don't append status bytes to FIFO - we read from registers instead
this->state_.APPEND_STATUS = 0;
} else {
// Configure GDO0 for serial data (async serial mode)
this->state_.GDO0_CFG = 0x0D;
}
if (this->initialized_) {
this->write_(Register::PKTCTRL0);
this->write_(Register::PKTCTRL1);
this->write_(Register::IOCFG0);
this->write_(Register::FIFOTHR);
}

View File

@@ -369,7 +369,7 @@ optional<ClimateDeviceRestoreState> Climate::restore_state_() {
}
void Climate::save_state_() {
#if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \
#if (defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \
!defined(CLANG_TIDY)
#pragma GCC diagnostic ignored "-Wclass-memaccess"
#define TEMP_IGNORE_MEMACCESS

View File

@@ -51,7 +51,7 @@ void DallasTemperatureSensor::update() {
}
float tempc = this->get_temp_c_();
ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", this->get_name().c_str(), tempc);
ESP_LOGD(TAG, "'%s': Got Temperature=%f°C", this->get_name().c_str(), tempc);
this->publish_state(tempc);
});
}

View File

@@ -200,7 +200,7 @@ void DebugComponent::get_device_info_(std::string &device_info) {
#ifdef USE_ARDUINO
ESP_LOGD(TAG, "Framework: Arduino");
device_info += "Arduino";
#elif defined(USE_ESP_IDF)
#elif defined(USE_ESP32)
ESP_LOGD(TAG, "Framework: ESP-IDF");
device_info += "ESP-IDF";
#else

View File

@@ -63,13 +63,11 @@ def validate_auto_clear(value):
return cv.boolean(value)
def basic_display_schema(default_update_interval: str = "1s") -> cv.Schema:
"""Create a basic display schema with configurable default update interval."""
return cv.Schema(
{
cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_,
}
).extend(cv.polling_component_schema(default_update_interval))
BASIC_DISPLAY_SCHEMA = cv.Schema(
{
cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_,
}
).extend(cv.polling_component_schema("1s"))
def _validate_test_card(config):
@@ -83,41 +81,34 @@ def _validate_test_card(config):
return config
def full_display_schema(default_update_interval: str = "1s") -> cv.Schema:
"""Create a full display schema with configurable default update interval."""
schema = basic_display_schema(default_update_interval).extend(
{
cv.Optional(CONF_ROTATION): validate_rotation,
cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All(
cv.ensure_list(
{
cv.GenerateID(): cv.declare_id(DisplayPage),
cv.Required(CONF_LAMBDA): cv.lambda_,
}
),
cv.Length(min=1),
),
cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation(
FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
{
cv.Optional(CONF_ROTATION): validate_rotation,
cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All(
cv.ensure_list(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
DisplayOnPageChangeTrigger
),
cv.Optional(CONF_FROM): cv.use_id(DisplayPage),
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
cv.GenerateID(): cv.declare_id(DisplayPage),
cv.Required(CONF_LAMBDA): cv.lambda_,
}
),
cv.Optional(
CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED
): validate_auto_clear,
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
}
)
schema.add_extra(_validate_test_card)
return schema
BASIC_DISPLAY_SCHEMA = basic_display_schema("1s")
FULL_DISPLAY_SCHEMA = full_display_schema("1s")
cv.Length(min=1),
),
cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
DisplayOnPageChangeTrigger
),
cv.Optional(CONF_FROM): cv.use_id(DisplayPage),
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
}
),
cv.Optional(
CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED
): validate_auto_clear,
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
}
)
FULL_DISPLAY_SCHEMA.add_extra(_validate_test_card)
async def setup_display_core_(var, config):

View File

@@ -31,7 +31,6 @@ from esphome.const import (
CONF_TRANSFORM,
CONF_UPDATE_INTERVAL,
CONF_WIDTH,
SCHEDULER_DONT_RUN,
)
from esphome.cpp_generator import RawExpression
from esphome.final_validate import full_config
@@ -73,10 +72,12 @@ TRANSFORM_OPTIONS = {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY}
def model_schema(config):
model = MODELS[config[CONF_MODEL]]
class_name = epaper_spi_ns.class_(model.class_name, EPaperBase)
minimum_update_interval = update_interval(
model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s")
)
cv_dimensions = cv.Optional if model.get_default(CONF_WIDTH) else cv.Required
return (
display.full_display_schema("60s")
.extend(
display.FULL_DISPLAY_SCHEMA.extend(
spi.spi_device_schema(
cs_pin_required=False,
default_mode="MODE0",
@@ -93,6 +94,9 @@ def model_schema(config):
{
cv.Optional(CONF_ROTATION, default=0): validate_rotation,
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
cv.Optional(CONF_UPDATE_INTERVAL, default=cv.UNDEFINED): cv.All(
update_interval, cv.Range(min=minimum_update_interval)
),
cv.Optional(CONF_TRANSFORM): cv.Schema(
{
cv.Required(CONF_MIRROR_X): cv.boolean,
@@ -146,22 +150,15 @@ def _final_validate(config):
global_config = full_config.get()
from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
# If no drawing methods are configured, and LVGL is not enabled, show a test card
if (
CONF_LAMBDA not in config
and CONF_PAGES not in config
and LVGL_DOMAIN not in global_config
):
config[CONF_SHOW_TEST_CARD] = True
interval = config[CONF_UPDATE_INTERVAL]
if interval != SCHEDULER_DONT_RUN:
model = MODELS[config[CONF_MODEL]]
minimum = update_interval(model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s"))
if interval < minimum:
raise cv.Invalid(
f"update_interval must be at least {minimum} for {model.name}, got {interval}"
)
if CONF_LAMBDA not in config and CONF_PAGES not in config:
if LVGL_DOMAIN in global_config:
if CONF_UPDATE_INTERVAL not in config:
config[CONF_UPDATE_INTERVAL] = update_interval("never")
else:
# If no drawing methods are configured, and LVGL is not enabled, show a test card
config[CONF_SHOW_TEST_CARD] = True
elif CONF_UPDATE_INTERVAL not in config:
config[CONF_UPDATE_INTERVAL] = update_interval("1min")
return config

View File

@@ -357,11 +357,12 @@ def _is_framework_url(source: str) -> bool:
# The default/recommended arduino framework version
# - https://github.com/espressif/arduino-esp32/releases
ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
"recommended": cv.Version(3, 3, 2),
"latest": cv.Version(3, 3, 4),
"dev": cv.Version(3, 3, 4),
"recommended": cv.Version(3, 3, 5),
"latest": cv.Version(3, 3, 5),
"dev": cv.Version(3, 3, 5),
}
ARDUINO_PLATFORM_VERSION_LOOKUP = {
cv.Version(3, 3, 5): cv.Version(55, 3, 35),
cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"),
cv.Version(3, 3, 3): cv.Version(55, 3, 31, "2"),
cv.Version(3, 3, 2): cv.Version(55, 3, 31, "2"),
@@ -374,15 +375,33 @@ ARDUINO_PLATFORM_VERSION_LOOKUP = {
cv.Version(3, 1, 1): cv.Version(53, 3, 11),
cv.Version(3, 1, 0): cv.Version(53, 3, 10),
}
# Maps Arduino framework versions to a compatible ESP-IDF version
# These versions correspond to pioarduino/esp-idf releases
# See: https://github.com/pioarduino/esp-idf/releases
ARDUINO_IDF_VERSION_LOOKUP = {
cv.Version(3, 3, 5): cv.Version(5, 5, 2),
cv.Version(3, 3, 4): cv.Version(5, 5, 1),
cv.Version(3, 3, 3): cv.Version(5, 5, 1),
cv.Version(3, 3, 2): cv.Version(5, 5, 1),
cv.Version(3, 3, 1): cv.Version(5, 5, 1),
cv.Version(3, 3, 0): cv.Version(5, 5, 0),
cv.Version(3, 2, 1): cv.Version(5, 4, 2),
cv.Version(3, 2, 0): cv.Version(5, 4, 2),
cv.Version(3, 1, 3): cv.Version(5, 3, 2),
cv.Version(3, 1, 2): cv.Version(5, 3, 2),
cv.Version(3, 1, 1): cv.Version(5, 3, 1),
cv.Version(3, 1, 0): cv.Version(5, 3, 0),
}
# The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases
ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
"recommended": cv.Version(5, 5, 1),
"latest": cv.Version(5, 5, 1),
"dev": cv.Version(5, 5, 1),
"recommended": cv.Version(5, 5, 2),
"latest": cv.Version(5, 5, 2),
"dev": cv.Version(5, 5, 2),
}
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
cv.Version(5, 5, 2): cv.Version(55, 3, 35),
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"),
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"),
cv.Version(5, 4, 3): cv.Version(55, 3, 32),
@@ -399,9 +418,9 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = {
# The platform-espressif32 version
# - https://github.com/pioarduino/platform-espressif32/releases
PLATFORM_VERSION_LOOKUP = {
"recommended": cv.Version(55, 3, 31, "2"),
"latest": cv.Version(55, 3, 31, "2"),
"dev": cv.Version(55, 3, 31, "2"),
"recommended": cv.Version(55, 3, 35),
"latest": cv.Version(55, 3, 35),
"dev": cv.Version(55, 3, 35),
}
@@ -606,9 +625,6 @@ CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size"
KEY_VFS_SELECT_REQUIRED = "vfs_select_required"
KEY_VFS_DIR_REQUIRED = "vfs_dir_required"
# Ring buffer IRAM requirement tracking
KEY_RINGBUF_IN_IRAM = "ringbuf_in_iram"
def require_vfs_select() -> None:
"""Mark that VFS select support is required by a component.
@@ -628,17 +644,6 @@ def require_vfs_dir() -> None:
CORE.data[KEY_VFS_DIR_REQUIRED] = True
def enable_ringbuf_in_iram() -> None:
"""Keep ring buffer functions in IRAM instead of moving them to flash.
Call this from components that use esphome/core/ring_buffer.cpp and need
the ring buffer functions to remain in IRAM for performance reasons
(e.g., voice assistants, audio components).
This prevents CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH from being enabled.
"""
CORE.data[KEY_RINGBUF_IN_IRAM] = True
def _parse_idf_component(value: str) -> ConfigType:
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
# Match operator followed by version-like string (digit or *)
@@ -741,12 +746,14 @@ FRAMEWORK_SCHEMA = cv.Schema(
)
# Remove this class in 2026.7.0
class _FrameworkMigrationWarning:
shown = False
def _show_framework_migration_message(name: str, variant: str) -> None:
"""Show a friendly message about framework migration when defaulting to Arduino."""
"""Show a message about the framework default change and how to switch back to Arduino."""
# Remove this function in 2026.7.0
if _FrameworkMigrationWarning.shown:
return
_FrameworkMigrationWarning.shown = True
@@ -756,41 +763,27 @@ def _show_framework_migration_message(name: str, variant: str) -> None:
message = (
color(
AnsiFore.BOLD_CYAN,
f"💡 IMPORTANT: {name} doesn't have a framework specified!",
f"💡 NOTICE: {name} does not have a framework specified.",
)
+ "\n\n"
+ f"Currently, {variant} defaults to the Arduino framework.\n"
+ color(AnsiFore.YELLOW, "This will change to ESP-IDF in ESPHome 2026.1.0.\n")
+ f"Starting with ESPHome 2026.1.0, the default framework for {variant} is ESP-IDF.\n"
+ "(We've been warning about this change since ESPHome 2025.8.0)\n"
+ "\n"
+ "Note: Newer ESP32 variants (C6, H2, P4, etc.) already use ESP-IDF by default.\n"
+ "\n"
+ "Why change? ESP-IDF offers:\n"
+ color(AnsiFore.GREEN, " ✨ Up to 40% smaller binaries\n")
+ color(AnsiFore.GREEN, " 🚀 Better performance and optimization\n")
+ "Why we made this change:\n"
+ color(AnsiFore.GREEN, " ✨ Up to 40% smaller firmware binaries\n")
+ color(AnsiFore.GREEN, " ⚡ 2-3x faster compile times\n")
+ color(AnsiFore.GREEN, " 📦 Custom-built firmware for your exact needs\n")
+ color(
AnsiFore.GREEN,
" 🔧 Active development and testing by ESPHome developers\n",
)
+ color(AnsiFore.GREEN, " 🚀 Better performance and newer features\n")
+ color(AnsiFore.GREEN, " 🔧 More actively maintained by ESPHome\n")
+ "\n"
+ "Trade-offs:\n"
+ color(AnsiFore.YELLOW, " 🔄 Some components need migration\n")
+ "To continue using Arduino, add this to your YAML under 'esp32:':\n"
+ color(AnsiFore.WHITE, " framework:\n")
+ color(AnsiFore.WHITE, " type: arduino\n")
+ "\n"
+ "What should I do?\n"
+ color(AnsiFore.CYAN, " Option 1")
+ ": Migrate to ESP-IDF (recommended)\n"
+ " Add this to your YAML under 'esp32:':\n"
+ color(AnsiFore.WHITE, " framework:\n")
+ color(AnsiFore.WHITE, " type: esp-idf\n")
+ "To silence this message with ESP-IDF, explicitly set:\n"
+ color(AnsiFore.WHITE, " framework:\n")
+ color(AnsiFore.WHITE, " type: esp-idf\n")
+ "\n"
+ color(AnsiFore.CYAN, " Option 2")
+ ": Keep using Arduino (still supported)\n"
+ " Add this to your YAML under 'esp32:':\n"
+ color(AnsiFore.WHITE, " framework:\n")
+ color(AnsiFore.WHITE, " type: arduino\n")
+ "\n"
+ "Need help? Check out the migration guide:\n"
+ "Migration guide: "
+ color(
AnsiFore.BLUE,
"https://esphome.io/guides/esp32_arduino_to_idf/",
@@ -805,13 +798,13 @@ def _set_default_framework(config):
config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({})
if CONF_TYPE not in config[CONF_FRAMEWORK]:
variant = config[CONF_VARIANT]
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF
# Show migration message for variants that previously defaulted to Arduino
# Remove this message in 2026.7.0
if variant in ARDUINO_ALLOWED_VARIANTS:
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO
_show_framework_migration_message(
config.get(CONF_NAME, "This device"), variant
)
else:
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF
return config
@@ -1005,6 +998,13 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True)
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
# Add IDF framework source for Arduino builds to ensure it uses the same version as
# the ESP-IDF framework
if (idf_ver := ARDUINO_IDF_VERSION_LOOKUP.get(framework_ver)) is not None:
cg.add_platformio_option(
"platform_packages", [_format_framework_espidf_version(idf_ver, None)]
)
# ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency
if get_esp32_variant() == VARIANT_ESP32S2:
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=1")
@@ -1041,18 +1041,14 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH", True)
# Place ring buffer functions into flash instead of IRAM by default
# This saves IRAM but may impact performance for audio/voice components.
# Components that need ring buffer in IRAM call enable_ringbuf_in_iram().
# Users can also set ringbuf_in_iram: true to force IRAM placement.
# In ESP-IDF 6.0 flash placement becomes the default.
if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM] or CORE.data.get(
KEY_RINGBUF_IN_IRAM, False
):
# User config or component requires ring buffer in IRAM for performance
# This saves IRAM. In ESP-IDF 6.0 flash placement becomes the default.
# Users can set ringbuf_in_iram: true as an escape hatch if they encounter issues.
if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM]:
# User requests ring buffer in IRAM
# IDF 6.0+: will need CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=n
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH", False)
else:
# No component needs it - place in flash to save IRAM
# Place in flash to save IRAM (default)
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH", True)
# Setup watchdog

View File

@@ -1488,6 +1488,10 @@ BOARDS = {
"name": "Arduino Nano ESP32",
"variant": VARIANT_ESP32S3,
},
"arduino_nesso_n1": {
"name": "Arduino Nesso-N1",
"variant": VARIANT_ESP32C6,
},
"atd147_s3": {
"name": "ArtronShop ATD1.47-S3",
"variant": VARIANT_ESP32S3,
@@ -1656,6 +1660,10 @@ BOARDS = {
"name": "Espressif ESP32-C6-DevKitM-1",
"variant": VARIANT_ESP32C6,
},
"esp32-c61-devkitc1-n8r2": {
"name": "Espressif ESP32-C61-DevKitC-1 N8R2 (8 MB Flash Quad, 2 MB PSRAM Quad)",
"variant": VARIANT_ESP32C61,
},
"esp32-devkitlipo": {
"name": "OLIMEX ESP32-DevKit-LiPo",
"variant": VARIANT_ESP32,
@@ -1673,11 +1681,15 @@ BOARDS = {
"variant": VARIANT_ESP32H2,
},
"esp32-p4": {
"name": "Espressif ESP32-P4 generic",
"name": "Espressif ESP32-P4 ES (pre rev.300) generic",
"variant": VARIANT_ESP32P4,
},
"esp32-p4-evboard": {
"name": "Espressif ESP32-P4 Function EV Board",
"name": "Espressif ESP32-P4 Function EV Board (ES pre rev.300)",
"variant": VARIANT_ESP32P4,
},
"esp32-p4_r3": {
"name": "Espressif ESP32-P4 rev.300 generic",
"variant": VARIANT_ESP32P4,
},
"esp32-pico-devkitm-2": {
@@ -2093,7 +2105,7 @@ BOARDS = {
"variant": VARIANT_ESP32,
},
"m5stack-tab5-p4": {
"name": "M5STACK Tab5 esp32-p4 Board",
"name": "M5STACK Tab5 esp32-p4 Board (ES pre rev.300)",
"variant": VARIANT_ESP32P4,
},
"m5stack-timer-cam": {

View File

@@ -85,6 +85,7 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
break;
}
gpio_set_intr_type(this->get_pin_num(), idf_type);
gpio_intr_enable(this->get_pin_num());
if (!isr_service_installed) {
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
if (res != ESP_OK) {
@@ -94,7 +95,6 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
isr_service_installed = true;
}
gpio_isr_handler_add(this->get_pin_num(), func, arg);
gpio_intr_enable(this->get_pin_num());
}
std::string ESP32InternalGPIOPin::dump_summary() const {

View File

@@ -24,7 +24,9 @@ extern "C" {
#include <nvs_flash.h>
#ifdef USE_ARDUINO
#include <esp32-hal-bt.h>
// Prevent Arduino from releasing BT memory at startup (esp32-hal-misc.c).
// Without this, esp_bt_controller_init() fails with ESP_ERR_INVALID_STATE.
extern "C" bool btInUse() { return true; } // NOLINT(readability-identifier-naming)
#endif
namespace esphome::esp32_ble {
@@ -96,10 +98,6 @@ void ESP32BLE::advertising_set_service_data(const std::vector<uint8_t> &data) {
}
void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) {
this->advertising_set_manufacturer_data(std::span<const uint8_t>(data));
}
void ESP32BLE::advertising_set_manufacturer_data(std::span<const uint8_t> data) {
this->advertising_init_();
this->advertising_->set_manufacturer_data(data);
this->advertising_start();
@@ -169,12 +167,6 @@ void ESP32BLE::advertising_init_() {
bool ESP32BLE::ble_setup_() {
esp_err_t err;
#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
#ifdef USE_ARDUINO
if (!btStart()) {
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
return false;
}
#else
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
// start bt controller
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
@@ -199,7 +191,6 @@ bool ESP32BLE::ble_setup_() {
return false;
}
}
#endif
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
#else
@@ -338,12 +329,6 @@ bool ESP32BLE::ble_dismantle_() {
}
#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
#ifdef USE_ARDUINO
if (!btStop()) {
ESP_LOGE(TAG, "btStop failed: %d", esp_bt_controller_get_status());
return false;
}
#else
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
// stop bt controller
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) {
@@ -367,7 +352,6 @@ bool ESP32BLE::ble_dismantle_() {
return false;
}
}
#endif
#else
if (esp_hosted_bt_controller_disable() != ESP_OK) {
ESP_LOGW(TAG, "esp_hosted_bt_controller_disable failed");

View File

@@ -118,7 +118,6 @@ class ESP32BLE : public Component {
void advertising_start();
void advertising_set_service_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(std::span<const uint8_t> data);
void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; }
void advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name);
void advertising_add_service_uuid(ESPBTUUID uuid);

View File

@@ -59,10 +59,6 @@ void BLEAdvertising::set_service_data(const std::vector<uint8_t> &data) {
}
void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) {
this->set_manufacturer_data(std::span<const uint8_t>(data));
}
void BLEAdvertising::set_manufacturer_data(std::span<const uint8_t> data) {
delete[] this->advertising_data_.p_manufacturer_data;
this->advertising_data_.p_manufacturer_data = nullptr;
this->advertising_data_.manufacturer_len = data.size();

View File

@@ -37,7 +37,6 @@ class BLEAdvertising {
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
void set_manufacturer_data(const std::vector<uint8_t> &data);
void set_manufacturer_data(std::span<const uint8_t> data);
void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; }
void set_service_data(const std::vector<uint8_t> &data);
void set_service_data(std::span<const uint8_t> data);

View File

@@ -1,6 +1,5 @@
#include "esp32_ble_beacon.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP32

View File

@@ -15,10 +15,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
new Trigger<std::vector<uint8_t>, uint16_t>();
characteristic->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
// Convert span to vector for trigger - copy is necessary because:
// 1. Trigger stores the data for use in automation actions that execute later
// 2. The span is only valid during this callback (points to temporary BLE stack data)
// 3. User lambdas in automations need persistent data they can access asynchronously
// Convert span to vector for trigger
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
});
return on_write_trigger;
@@ -30,10 +27,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
new Trigger<std::vector<uint8_t>, uint16_t>();
descriptor->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
// Convert span to vector for trigger - copy is necessary because:
// 1. Trigger stores the data for use in automation actions that execute later
// 2. The span is only valid during this callback (points to temporary BLE stack data)
// 3. User lambdas in automations need persistent data they can access asynchronously
// Convert span to vector for trigger
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
});
return on_write_trigger;

View File

@@ -191,7 +191,8 @@ async def to_code(config):
cg.add_define(ThreadModel.SINGLE)
cg.add_platformio_option(
"extra_scripts", ["pre:testing_mode.py", "post:post_build.py"]
"extra_scripts",
["pre:testing_mode.py", "pre:exclude_updater.py", "post:post_build.py"],
)
conf = config[CONF_FRAMEWORK]
@@ -278,3 +279,8 @@ def copy_files():
testing_mode_file,
CORE.relative_build_path("testing_mode.py"),
)
exclude_updater_file = dir / "exclude_updater.py.script"
copy_file_if_changed(
exclude_updater_file,
CORE.relative_build_path("exclude_updater.py"),
)

View File

@@ -0,0 +1,21 @@
# pylint: disable=E0602
Import("env") # noqa
import os
# Filter out Updater.cpp from the Arduino core build
# This saves 228 bytes of .bss by not instantiating the global Update object
# ESPHome uses its own native OTA backend instead
def filter_updater_from_core(env, node):
"""Filter callback to exclude Updater.cpp from framework build."""
path = node.get_path()
if path.endswith("Updater.cpp"):
print(f"ESPHome: Excluding {os.path.basename(path)} from build (using native OTA backend)")
return None
return node
# Apply the filter to framework sources
env.AddBuildMiddleware(filter_updater_from_core, "**/cores/esp8266/Updater.cpp")

View File

@@ -31,7 +31,7 @@ CONFIG_SCHEMA = cv.All(
}
)
),
cv.only_with_esp_idf,
cv.only_on_esp32,
only_on_variant(supported=[VARIANT_ESP32P4]),
)

View File

@@ -10,7 +10,7 @@
#endif
#include "esphome/components/network/util.h"
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
#include "esphome/components/ota/ota_backend_esp8266.h"
#include "esphome/components/ota/ota_backend_arduino_libretiny.h"
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
#include "esphome/components/ota/ota_backend_esp_idf.h"
@@ -654,12 +654,7 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
this->auth_buf_[0] = this->auth_type_;
hasher->get_hex(buf);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too
memcpy(log_buf, buf, hex_size);
log_buf[hex_size] = '\0';
ESP_LOGV(TAG, "Auth: Nonce is %s", log_buf);
#endif
ESP_LOGV(TAG, "Auth: Nonce is %.*s", hex_size, buf);
}
// Try to write auth_type + nonce
@@ -739,23 +734,13 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
hasher->add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
hasher->calculate();
ESP_LOGV(TAG, "Auth: CNonce is %.*s", hex_size, cnonce);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too
// Log CNonce
memcpy(log_buf, cnonce, hex_size);
log_buf[hex_size] = '\0';
ESP_LOGV(TAG, "Auth: CNonce is %s", log_buf);
// Log computed hash
hasher->get_hex(log_buf);
log_buf[hex_size] = '\0';
ESP_LOGV(TAG, "Auth: Result is %s", log_buf);
// Log received response
memcpy(log_buf, response, hex_size);
log_buf[hex_size] = '\0';
ESP_LOGV(TAG, "Auth: Response is %s", log_buf);
char computed_hash[65]; // Buffer for hex-encoded hash (max expected length + null terminator)
hasher->get_hex(computed_hash);
ESP_LOGV(TAG, "Auth: Result is %.*s", hex_size, computed_hash);
#endif
ESP_LOGV(TAG, "Auth: Response is %.*s", hex_size, response);
// Compare response
bool matches = hasher->equals_hex(response);

View File

@@ -644,6 +644,12 @@ void EthernetComponent::dump_connect_params_() {
dns_ip2 = dns_getserver(1);
}
// Use stack buffers for IP address formatting to avoid heap allocations
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGCONFIG(TAG,
" IP Address: %s\n"
" Hostname: '%s'\n"
@@ -651,9 +657,9 @@ void EthernetComponent::dump_connect_params_() {
" Gateway: %s\n"
" DNS1: %s\n"
" DNS2: %s",
network::IPAddress(&ip.ip).str().c_str(), App.get_name().c_str(),
network::IPAddress(&ip.netmask).str().c_str(), network::IPAddress(&ip.gw).str().c_str(),
network::IPAddress(dns_ip1).str().c_str(), network::IPAddress(dns_ip2).str().c_str());
network::IPAddress(&ip.ip).str_to(ip_buf), App.get_name().c_str(),
network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf),
network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf));
#if USE_NETWORK_IPV6
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
@@ -665,12 +671,13 @@ void EthernetComponent::dump_connect_params_() {
}
#endif /* USE_NETWORK_IPV6 */
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGCONFIG(TAG,
" MAC Address: %s\n"
" Is Full Duplex: %s\n"
" Link Speed: %u",
this->get_eth_mac_address_pretty().c_str(), YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL),
this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
this->get_eth_mac_address_pretty_into_buffer(mac_buf),
YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
}
#ifdef USE_ETHERNET_SPI
@@ -711,11 +718,16 @@ void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
}
std::string EthernetComponent::get_eth_mac_address_pretty() {
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
return std::string(this->get_eth_mac_address_pretty_into_buffer(buf));
}
const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer(
std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) {
uint8_t mac[6];
get_eth_mac_address_raw(mac);
char buf[18];
format_mac_addr_upper(mac, buf);
return std::string(buf);
format_mac_addr_upper(mac, buf.data());
return buf.data();
}
eth_duplex_t EthernetComponent::get_duplex_mode() {

View File

@@ -3,6 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/components/network/ip_address.h"
#ifdef USE_ESP32
@@ -93,6 +94,7 @@ class EthernetComponent : public Component {
void set_use_address(const char *use_address);
void get_eth_mac_address_raw(uint8_t *mac);
std::string get_eth_mac_address_pretty();
const char *get_eth_mac_address_pretty_into_buffer(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf);
eth_duplex_t get_duplex_mode();
eth_speed_t get_link_speed();
bool powerdown();

View File

@@ -71,7 +71,7 @@ void FanCall::validate_() {
auto traits = this->parent_.get_traits();
if (this->speed_.has_value()) {
this->speed_ = clamp(*this->speed_, 1, static_cast<int>(traits.supported_speed_count()));
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
// https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes
// "Manually setting a speed must disable any set preset mode"

View File

@@ -11,7 +11,7 @@ namespace fan {
class FanTraits {
public:
FanTraits() = default;
FanTraits(bool oscillation, bool speed, bool direction, uint8_t speed_count)
FanTraits(bool oscillation, bool speed, bool direction, int speed_count)
: oscillation_(oscillation), speed_(speed), direction_(direction), speed_count_(speed_count) {}
/// Return if this fan supports oscillation.
@@ -23,9 +23,9 @@ class FanTraits {
/// Set whether this fan supports speed levels.
void set_speed(bool speed) { this->speed_ = speed; }
/// Return how many speed levels the fan has
uint8_t supported_speed_count() const { return this->speed_count_; }
int supported_speed_count() const { return this->speed_count_; }
/// Set how many speed levels this fan has.
void set_supported_speed_count(uint8_t speed_count) { this->speed_count_ = speed_count; }
void set_supported_speed_count(int speed_count) { this->speed_count_ = speed_count; }
/// Return if this fan supports changing direction
bool supports_direction() const { return this->direction_; }
/// Set whether this fan supports changing direction
@@ -64,7 +64,7 @@ class FanTraits {
bool oscillation_{false};
bool speed_{false};
bool direction_{false};
uint8_t speed_count_{};
int speed_count_{};
std::vector<const char *> preset_modes_{};
};

View File

@@ -39,7 +39,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum(
DECAY_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1, max=255),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput),
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
}

View File

@@ -15,7 +15,7 @@ enum DecayMode {
class HBridgeFan : public Component, public fan::Fan {
public:
HBridgeFan(uint8_t speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; }
void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; }
@@ -33,7 +33,7 @@ class HBridgeFan : public Component, public fan::Fan {
output::FloatOutput *pin_b_;
output::FloatOutput *enable_{nullptr};
output::BinaryOutput *oscillating_{nullptr};
uint8_t speed_count_{};
int speed_count_{};
DecayMode decay_mode_{DECAY_MODE_SLOW};
fan::FanTraits traits_;
std::vector<const char *> preset_modes_{};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
#include "esphome/components/md5/md5.h"
#include "esphome/components/watchdog/watchdog.h"
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
#include "esphome/components/ota/ota_backend_esp8266.h"
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
#include "esphome/components/ota/ota_backend_esp_idf.h"

View File

@@ -116,7 +116,7 @@ void IDFI2CBus::dump_config() {
if (s.second) {
ESP_LOGCONFIG(TAG, "Found device at address 0x%02X", s.first);
} else {
ESP_LOGCONFIG(TAG, "Unknown error at address 0x%02X", s.first);
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}

View File

@@ -1,11 +1,6 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components.esp32 import (
add_idf_sdkconfig_option,
enable_ringbuf_in_iram,
get_esp32_variant,
)
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
@@ -15,6 +10,8 @@ from esphome.components.esp32.const import (
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
add_idf_sdkconfig_option,
get_esp32_variant,
)
import esphome.config_validation as cv
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE
@@ -281,9 +278,6 @@ async def to_code(config):
# Helps avoid callbacks being skipped due to processor load
add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True)
# Keep ring buffer functions in IRAM for audio performance
enable_ringbuf_in_iram()
cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN]))
if CONF_I2S_BCLK_PIN in config:
cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN]))

View File

@@ -8,8 +8,9 @@ extern "C" {
uint8_t temprature_sens_read();
}
#elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || \
defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3)
#include "driver/temperature_sensor.h"
#endif // USE_ESP32_VARIANT
#endif // USE_ESP32
@@ -27,9 +28,9 @@ namespace internal_temperature {
static const char *const TAG = "internal_temperature";
#ifdef USE_ESP32
#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
static temperature_sensor_handle_t tsensNew = NULL;
#endif // USE_ESP32_VARIANT
#endif // USE_ESP32
@@ -44,8 +45,9 @@ void InternalTemperatureSensor::update() {
temperature = (raw - 32) / 1.8f;
success = (raw != 128);
#elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || \
defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3)
esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature);
success = (result == ESP_OK);
if (!success) {
@@ -81,9 +83,9 @@ void InternalTemperatureSensor::update() {
void InternalTemperatureSensor::setup() {
#ifdef USE_ESP32
#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
esp_err_t result = temperature_sensor_install(&tsens_config, &tsensNew);

View File

@@ -65,8 +65,8 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
uint16_t buffer_at = 0; // Initialize buffer position
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
MAX_CONSOLE_LOG_MSG_SIZE);
// Add newline if platform needs it (ESP32 doesn't add via write_msg_)
this->add_newline_to_buffer_if_needed_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
// Add newline before writing to console
this->add_newline_to_buffer_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
this->write_msg_(console_buffer, buffer_at);
}

View File

@@ -117,17 +117,6 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128;
// "0x" + 2 hex digits per byte + '\0'
static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1;
// Platform-specific: does write_msg_ add its own newline?
// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, RP2040, LibreTiny, Zephyr)
// Allows single write call with newline included for efficiency
// true: write_msg_ adds newline itself via puts()/println() (other platforms)
// Newline should NOT be added to buffer
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
static constexpr bool WRITE_MSG_ADDS_NEWLINE = false;
#else
static constexpr bool WRITE_MSG_ADDS_NEWLINE = true;
#endif
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
/** Enum for logging UART selection
*
@@ -259,22 +248,20 @@ class Logger : public Component {
}
}
// Helper to add newline to buffer for platforms that need it
// Helper to add newline to buffer before writing to console
// Modifies buffer_at to include the newline
inline void HOT add_newline_to_buffer_if_needed_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
if constexpr (!WRITE_MSG_ADDS_NEWLINE) {
// Add newline - don't need to maintain null termination
// write_msg_ now always receives explicit length, so we can safely overwrite the null terminator
// This is safe because:
// 1. Callbacks already received the message (before we add newline)
// 2. write_msg_ receives the length explicitly (doesn't need null terminator)
if (*buffer_at < buffer_size) {
buffer[(*buffer_at)++] = '\n';
} else if (buffer_size > 0) {
// Buffer was full - replace last char with newline to ensure it's visible
buffer[buffer_size - 1] = '\n';
*buffer_at = buffer_size;
}
inline void HOT add_newline_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
// Add newline - don't need to maintain null termination
// write_msg_ receives explicit length, so we can safely overwrite the null terminator
// This is safe because:
// 1. Callbacks already received the message (before we add newline)
// 2. write_msg_ receives the length explicitly (doesn't need null terminator)
if (*buffer_at < buffer_size) {
buffer[(*buffer_at)++] = '\n';
} else if (buffer_size > 0) {
// Buffer was full - replace last char with newline to ensure it's visible
buffer[buffer_size - 1] = '\n';
*buffer_at = buffer_size;
}
}
@@ -283,7 +270,7 @@ class Logger : public Component {
inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) {
if (this->baud_rate_ > 0) {
uint16_t *len_ptr = length ? length : &this->tx_buffer_at_;
this->add_newline_to_buffer_if_needed_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset);
this->add_newline_to_buffer_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset);
this->write_msg_(this->tx_buffer_ + offset, *len_ptr);
}
}

View File

@@ -3,16 +3,23 @@
namespace esphome::logger {
void HOT Logger::write_msg_(const char *msg, size_t) {
time_t rawtime;
struct tm *timeinfo;
char buffer[80];
void HOT Logger::write_msg_(const char *msg, size_t len) {
static constexpr size_t TIMESTAMP_LEN = 10; // "[HH:MM:SS]"
// tx_buffer_size_ defaults to 512, so 768 covers default + headroom
char buffer[TIMESTAMP_LEN + 768];
time_t rawtime;
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo);
fputs(buffer, stdout);
puts(msg);
struct tm *timeinfo = localtime(&rawtime);
size_t pos = strftime(buffer, TIMESTAMP_LEN + 1, "[%H:%M:%S]", timeinfo);
// Copy message (with newline already included by caller)
size_t copy_len = std::min(len, sizeof(buffer) - pos);
memcpy(buffer + pos, msg, copy_len);
pos += copy_len;
// Single write for everything
fwrite(buffer, 1, pos, stdout);
}
void Logger::pre_setup() { global_logger = this; }

View File

@@ -5,7 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i
"""
import logging
from typing import TYPE_CHECKING, Any
from typing import Any
from esphome import codegen as cg, config_validation as cv
from esphome.const import CONF_ITEMS
@@ -96,13 +96,9 @@ class LValidator:
return None
if isinstance(value, Lambda):
# Local import to avoid circular import
from .lvcode import CodeContext, LambdaContext
from .lvcode import get_lambda_context_args
if TYPE_CHECKING:
# CodeContext does not have get_automation_parameters
# so we need to assert the type here
assert isinstance(CodeContext.code_context, LambdaContext)
args = args or CodeContext.code_context.get_automation_parameters()
args = args or get_lambda_context_args()
return cg.RawExpression(
call_lambda(
await cg.process_lambda(value, args, return_type=self.rtype)

View File

@@ -1,5 +1,5 @@
import re
from typing import TYPE_CHECKING, Any
from typing import Any
import esphome.codegen as cg
from esphome.components import image
@@ -404,14 +404,9 @@ class TextValidator(LValidator):
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
) -> Expression:
# Local import to avoid circular import at module level
from .lvcode import get_lambda_context_args
from .lvcode import CodeContext, LambdaContext
if TYPE_CHECKING:
# CodeContext does not have get_automation_parameters
# so we need to assert the type here
assert isinstance(CodeContext.code_context, LambdaContext)
args = args or CodeContext.code_context.get_automation_parameters()
args = args or get_lambda_context_args()
if isinstance(value, dict):
if format_str := value.get(CONF_FORMAT):

View File

@@ -1,4 +1,5 @@
import abc
from typing import TYPE_CHECKING
from esphome import codegen as cg
from esphome.config import Config
@@ -200,6 +201,21 @@ class LvContext(LambdaContext):
return self.add(*args)
def get_lambda_context_args() -> list[tuple[SafeExpType, str]]:
"""Get automation parameters from the current lambda context if available.
When called from outside LVGL's context (e.g., from interval),
CodeContext.code_context will be None, so return empty args.
"""
if CodeContext.code_context is None:
return []
if TYPE_CHECKING:
# CodeContext base class doesn't define get_automation_parameters(),
# but LambdaContext and LvContext (the concrete implementations) do.
assert isinstance(CodeContext.code_context, LambdaContext)
return CodeContext.code_context.get_automation_parameters()
class LocalVariable(MockObj):
"""
Create a local variable and enclose the code using it within a block.

View File

@@ -56,7 +56,7 @@ void MCP23016::pin_mode(uint8_t pin, gpio::Flags flags) {
this->update_reg_(pin, false, iodir);
}
}
float MCP23016::get_setup_priority() const { return setup_priority::IO; }
float MCP23016::get_setup_priority() const { return setup_priority::HARDWARE; }
bool MCP23016::read_reg_(uint8_t reg, uint8_t *value) {
if (this->is_failed())
return false;

View File

@@ -368,7 +368,7 @@ CONFIG_SCHEMA = cv.All(
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_esp_idf,
cv.only_on_esp32,
)
@@ -448,9 +448,6 @@ async def to_code(config):
# The inference task queues detection events that need immediate processing
socket.require_wake_loop_threadsafe()
# Keep ring buffer functions in IRAM for audio performance
esp32.enable_ringbuf_in_iram()
mic_source = await microphone.microphone_source_to_code(config[CONF_MICROPHONE])
cg.add(var.set_microphone_source(mic_source))

View File

@@ -3,7 +3,7 @@
#include "micro_wake_word.h"
#include "streaming_model.h"
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
namespace esphome {
namespace micro_wake_word {

View File

@@ -1,6 +1,6 @@
#include "micro_wake_word.h"
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
@@ -473,4 +473,4 @@ bool MicroWakeWord::update_model_probabilities_(const int8_t audio_features[PREP
} // namespace micro_wake_word
} // namespace esphome
#endif // USE_ESP_IDF
#endif // USE_ESP32

View File

@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include "preprocessor_settings.h"
#include "streaming_model.h"
@@ -140,4 +140,4 @@ class MicroWakeWord : public Component
} // namespace micro_wake_word
} // namespace esphome
#endif // USE_ESP_IDF
#endif // USE_ESP32

View File

@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include <cstdint>

View File

@@ -1,6 +1,6 @@
#include "streaming_model.h"
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"

View File

@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include "preprocessor_settings.h"

View File

@@ -165,8 +165,8 @@ def model_schema(config):
)
return cv.All(
schema,
cv.only_on_esp32,
only_on_variant(supported=[VARIANT_ESP32P4]),
cv.only_with_esp_idf,
)

View File

@@ -224,8 +224,8 @@ def _config_schema(config):
schema = model_schema(config)
return cv.All(
schema,
cv.only_on_esp32,
only_on_variant(supported=[VARIANT_ESP32S3]),
cv.only_with_esp_idf,
)(config)

View File

@@ -224,7 +224,7 @@ def model_schema(config):
}
)
if bus_mode != TYPE_SINGLE:
return cv.All(schema, cv.only_with_esp_idf)
return cv.All(schema, cv.only_on_esp32)
return schema

View File

@@ -93,9 +93,7 @@ CONFIG_SCHEMA = cv.All(
),
cv.Optional(CONF_NUM_CHANNELS): cv.int_range(min=1, max=2),
cv.Optional(CONF_QUEUE_MODE, default=False): cv.boolean,
cv.SplitDefault(CONF_TASK_STACK_IN_PSRAM, esp32_idf=False): cv.All(
cv.boolean, cv.only_with_esp_idf
),
cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean,
}
),
cv.only_on([PLATFORM_ESP32]),

View File

@@ -232,16 +232,6 @@ void MQTTBackendESP32::esphome_mqtt_task(void *params) {
this_mqtt->mqtt_event_pool_.release(elem);
}
}
// Clean up any remaining items in the queue
struct QueueElement *elem;
while ((elem = this_mqtt->mqtt_queue_.pop()) != nullptr) {
this_mqtt->mqtt_event_pool_.release(elem);
}
// Note: EventPool destructor will clean up the pool itself
// Task will delete itself
vTaskDelete(nullptr);
}
bool MQTTBackendESP32::enqueue_(MqttQueueTypeT type, const char *topic, int qos, bool retain, const char *payload,

View File

@@ -8,7 +8,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/macros.h"
#if defined(USE_ESP_IDF) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0)
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0)
#include <lwip/ip_addr.h>
#endif
@@ -40,6 +40,9 @@ using ip4_addr_t = in_addr;
namespace esphome {
namespace network {
/// Buffer size for IP address string (IPv6 max: 39 chars + null)
static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40;
struct IPAddress {
public:
#ifdef USE_HOST
@@ -50,6 +53,10 @@ struct IPAddress {
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
std::string str() const { return str_lower_case(inet_ntoa(ip_addr_)); }
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
char *str_to(char *buf) const {
return const_cast<char *>(inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE));
}
#else
IPAddress() { ip_addr_set_zero(&ip_addr_); }
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
@@ -128,6 +135,8 @@ struct IPAddress {
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
char *str_to(char *buf) const { return ipaddr_ntoa_r(&ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE); }
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
IPAddress &operator+=(uint8_t increase) {

View File

@@ -49,7 +49,8 @@ void OneWireBus::search() {
break;
auto *address8 = reinterpret_cast<uint8_t *>(&address);
if (crc8(address8, 7) != address8[7]) {
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
char hex_buf[17];
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex_to(hex_buf, address));
} else {
this->devices_.push_back(address);
}
@@ -82,8 +83,9 @@ void OneWireBus::dump_devices_(const char *tag) {
ESP_LOGW(tag, " Found no devices!");
} else {
ESP_LOGCONFIG(tag, " Found devices:");
char hex_buf[17]; // uint64_t = 16 hex chars + null
for (auto &address : this->devices_) {
ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex(address).c_str(), LOG_STR_ARG(get_model_str(address & 0xff)));
ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex_to(hex_buf, address), LOG_STR_ARG(get_model_str(address & 0xff)));
}
}
}

View File

@@ -152,7 +152,6 @@ CONFIG_SCHEMA = cv.All(
}
).extend(_CONNECTION_SCHEMA),
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
cv.only_with_esp_idf,
only_on_variant(supported=[VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32H2]),
_validate,
_require_vfs_select,

View File

@@ -1,5 +1,5 @@
#include "esphome/core/defines.h"
#if defined(USE_OPENTHREAD) && defined(USE_ESP_IDF)
#if defined(USE_OPENTHREAD) && defined(USE_ESP32)
#include <openthread/logging.h>
#include "openthread.h"

View File

@@ -148,7 +148,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"ota_backend_arduino_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"ota_backend_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"ota_backend_arduino_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"ota_backend_arduino_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,

View File

@@ -1,89 +0,0 @@
#ifdef USE_ARDUINO
#ifdef USE_ESP8266
#include "ota_backend_arduino_esp8266.h"
#include "ota_backend.h"
#include "esphome/components/esp8266/preferences.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include <Updater.h>
namespace esphome {
namespace ota {
static const char *const TAG = "ota.arduino_esp8266";
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP8266OTABackend>(); }
OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) {
// Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space
if (image_size == 0) {
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
image_size = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
}
bool ret = Update.begin(image_size, U_FLASH);
if (ret) {
esp8266::preferences_prevent_write(true);
return OTA_RESPONSE_OK;
}
uint8_t error = Update.getError();
if (error == UPDATE_ERROR_BOOTSTRAP)
return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING;
if (error == UPDATE_ERROR_NEW_FLASH_CONFIG)
return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG;
if (error == UPDATE_ERROR_FLASH_CONFIG)
return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG;
if (error == UPDATE_ERROR_SPACE)
return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE;
ESP_LOGE(TAG, "Begin error: %d", error);
return OTA_RESPONSE_ERROR_UNKNOWN;
}
void ArduinoESP8266OTABackend::set_update_md5(const char *md5) {
Update.setMD5(md5);
this->md5_set_ = true;
}
OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) {
size_t written = Update.write(data, len);
if (written == len) {
return OTA_RESPONSE_OK;
}
uint8_t error = Update.getError();
ESP_LOGE(TAG, "Write error: %d", error);
return OTA_RESPONSE_ERROR_WRITING_FLASH;
}
OTAResponseTypes ArduinoESP8266OTABackend::end() {
// Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5
// This matches the behavior of the old web_server OTA implementation
bool success = Update.end(!this->md5_set_);
// On ESP8266, Update.end() might return false even with error code 0
// Check the actual error code to determine success
uint8_t error = Update.getError();
if (success || error == UPDATE_ERROR_OK) {
return OTA_RESPONSE_OK;
}
ESP_LOGE(TAG, "End error: %d", error);
return OTA_RESPONSE_ERROR_UPDATE_END;
}
void ArduinoESP8266OTABackend::abort() {
Update.end();
esp8266::preferences_prevent_write(false);
}
} // namespace ota
} // namespace esphome
#endif
#endif

View File

@@ -1,33 +0,0 @@
#pragma once
#ifdef USE_ARDUINO
#ifdef USE_ESP8266
#include "ota_backend.h"
#include "esphome/core/defines.h"
#include "esphome/core/macros.h"
namespace esphome {
namespace ota {
class ArduinoESP8266OTABackend : public OTABackend {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;
OTAResponseTypes write(uint8_t *data, size_t len) override;
OTAResponseTypes end() override;
void abort() override;
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
bool supports_compression() override { return true; }
#else
bool supports_compression() override { return false; }
#endif
private:
bool md5_set_{false};
};
} // namespace ota
} // namespace esphome
#endif
#endif

View File

@@ -0,0 +1,356 @@
#ifdef USE_ESP8266
#include "ota_backend_esp8266.h"
#include "ota_backend.h"
#include "esphome/components/esp8266/preferences.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <Esp.h>
#include <esp8266_peri.h>
#include <cinttypes>
extern "C" {
#include <c_types.h>
#include <eboot_command.h>
#include <flash_hal.h>
#include <spi_flash.h>
#include <user_interface.h>
}
// Note: FLASH_SECTOR_SIZE (0x1000) is already defined in spi_flash_geometry.h
// Flash header offsets
static constexpr uint8_t FLASH_MODE_OFFSET = 2;
// Firmware magic bytes
static constexpr uint8_t FIRMWARE_MAGIC = 0xE9;
static constexpr uint8_t GZIP_MAGIC_1 = 0x1F;
static constexpr uint8_t GZIP_MAGIC_2 = 0x8B;
// ESP8266 flash memory base address (memory-mapped flash starts here)
static constexpr uint32_t FLASH_BASE_ADDRESS = 0x40200000;
// Boot mode extraction from GPI register (bits 16-19 contain boot mode)
static constexpr int BOOT_MODE_SHIFT = 16;
static constexpr int BOOT_MODE_MASK = 0xf;
// Boot mode indicating UART download mode (OTA not possible)
static constexpr int BOOT_MODE_UART_DOWNLOAD = 1;
// Minimum buffer size when memory is constrained
static constexpr size_t MIN_BUFFER_SIZE = 256;
namespace esphome::ota {
static const char *const TAG = "ota.esp8266";
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ESP8266OTABackend>(); }
OTAResponseTypes ESP8266OTABackend::begin(size_t image_size) {
// Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space
if (image_size == 0) {
// Round down to sector boundary: subtract one sector, then mask to sector alignment
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
image_size = (ESP.getFreeSketchSpace() - FLASH_SECTOR_SIZE) & ~(FLASH_SECTOR_SIZE - 1);
}
// Check boot mode - if boot mode is UART download mode,
// we will not be able to reset into normal mode once update is done
int boot_mode = (GPI >> BOOT_MODE_SHIFT) & BOOT_MODE_MASK;
if (boot_mode == BOOT_MODE_UART_DOWNLOAD) {
return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING;
}
// Check flash configuration - real size must be >= configured size
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
if (!ESP.checkFlashConfig(false)) {
return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG;
}
// Get current sketch size
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
uint32_t sketch_size = ESP.getSketchSize();
// Size of current sketch rounded to sector boundary
uint32_t current_sketch_size = (sketch_size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
// Size of update rounded to sector boundary
uint32_t rounded_size = (image_size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
// End of available space for sketch and update (start of filesystem)
uint32_t update_end_address = FS_start - FLASH_BASE_ADDRESS;
// Calculate start address for the update (write from end backwards)
this->start_address_ = (update_end_address > rounded_size) ? (update_end_address - rounded_size) : 0;
// Check if there's enough space for both current sketch and update
if (this->start_address_ < current_sketch_size) {
return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE;
}
// Allocate buffer for sector writes (use smaller buffer if memory constrained)
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
this->buffer_size_ = (ESP.getFreeHeap() > 2 * FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : MIN_BUFFER_SIZE;
// ESP8266's umm_malloc guarantees 4-byte aligned allocations, which is required
// for spi_flash_write(). This is the same pattern used by Arduino's Updater class.
this->buffer_ = make_unique<uint8_t[]>(this->buffer_size_);
if (!this->buffer_) {
return OTA_RESPONSE_ERROR_UNKNOWN;
}
this->current_address_ = this->start_address_;
this->image_size_ = image_size;
this->buffer_len_ = 0;
this->md5_set_ = false;
// Disable WiFi sleep during update
wifi_set_sleep_type(NONE_SLEEP_T);
// Prevent preference writes during update
esp8266::preferences_prevent_write(true);
// Initialize MD5 computation
this->md5_.init();
ESP_LOGD(TAG, "OTA begin: start=0x%08" PRIX32 ", size=%zu", this->start_address_, image_size);
return OTA_RESPONSE_OK;
}
void ESP8266OTABackend::set_update_md5(const char *md5) {
// Parse hex string to bytes
if (parse_hex(md5, this->expected_md5_, 16)) {
this->md5_set_ = true;
}
}
OTAResponseTypes ESP8266OTABackend::write(uint8_t *data, size_t len) {
if (!this->buffer_) {
return OTA_RESPONSE_ERROR_UNKNOWN;
}
size_t written = 0;
while (written < len) {
// Calculate how much we can buffer
size_t to_buffer = std::min(len - written, this->buffer_size_ - this->buffer_len_);
memcpy(this->buffer_.get() + this->buffer_len_, data + written, to_buffer);
this->buffer_len_ += to_buffer;
written += to_buffer;
// If buffer is full, write to flash
if (this->buffer_len_ == this->buffer_size_ && !this->write_buffer_()) {
return OTA_RESPONSE_ERROR_WRITING_FLASH;
}
}
return OTA_RESPONSE_OK;
}
bool ESP8266OTABackend::erase_sector_if_needed_() {
if ((this->current_address_ % FLASH_SECTOR_SIZE) != 0) {
return true; // Not at sector boundary
}
App.feed_wdt();
if (spi_flash_erase_sector(this->current_address_ / FLASH_SECTOR_SIZE) != SPI_FLASH_RESULT_OK) {
ESP_LOGE(TAG, "Flash erase failed at 0x%08" PRIX32, this->current_address_);
return false;
}
return true;
}
bool ESP8266OTABackend::flash_write_() {
App.feed_wdt();
if (spi_flash_write(this->current_address_, reinterpret_cast<uint32_t *>(this->buffer_.get()), this->buffer_len_) !=
SPI_FLASH_RESULT_OK) {
ESP_LOGE(TAG, "Flash write failed at 0x%08" PRIX32, this->current_address_);
return false;
}
return true;
}
bool ESP8266OTABackend::write_buffer_() {
if (this->buffer_len_ == 0) {
return true;
}
if (!this->erase_sector_if_needed_()) {
return false;
}
// Patch flash mode in first sector if needed
// This is analogous to what esptool.py does when it receives a --flash_mode argument
bool is_first_sector = (this->current_address_ == this->start_address_);
uint8_t original_flash_mode = 0;
bool patched_flash_mode = false;
// Only patch if we have enough bytes to access flash mode offset and it's not GZIP
if (is_first_sector && this->buffer_len_ > FLASH_MODE_OFFSET && this->buffer_[0] != GZIP_MAGIC_1) {
// Not GZIP compressed - check and patch flash mode
uint8_t current_flash_mode = this->get_flash_chip_mode_();
uint8_t buffer_flash_mode = this->buffer_[FLASH_MODE_OFFSET];
if (buffer_flash_mode != current_flash_mode) {
original_flash_mode = buffer_flash_mode;
this->buffer_[FLASH_MODE_OFFSET] = current_flash_mode;
patched_flash_mode = true;
}
}
if (!this->flash_write_()) {
return false;
}
// Restore original flash mode for MD5 calculation
if (patched_flash_mode) {
this->buffer_[FLASH_MODE_OFFSET] = original_flash_mode;
}
// Update MD5 with original (unpatched) data
this->md5_.add(this->buffer_.get(), this->buffer_len_);
this->current_address_ += this->buffer_len_;
this->buffer_len_ = 0;
return true;
}
bool ESP8266OTABackend::write_buffer_final_() {
// Similar to write_buffer_(), but without flash mode patching or MD5 update (for final padded write)
if (this->buffer_len_ == 0) {
return true;
}
if (!this->erase_sector_if_needed_() || !this->flash_write_()) {
return false;
}
this->current_address_ += this->buffer_len_;
this->buffer_len_ = 0;
return true;
}
OTAResponseTypes ESP8266OTABackend::end() {
// Write any remaining buffered data
if (this->buffer_len_ > 0) {
// Add actual data to MD5 before padding
this->md5_.add(this->buffer_.get(), this->buffer_len_);
// Pad to 4-byte alignment for flash write
while (this->buffer_len_ % 4 != 0) {
this->buffer_[this->buffer_len_++] = 0xFF;
}
if (!this->write_buffer_final_()) {
this->abort();
return OTA_RESPONSE_ERROR_WRITING_FLASH;
}
}
// Calculate actual bytes written
size_t actual_size = this->current_address_ - this->start_address_;
// Check if any data was written
if (actual_size == 0) {
ESP_LOGE(TAG, "No data written");
this->abort();
return OTA_RESPONSE_ERROR_UPDATE_END;
}
// Verify MD5 if set (strict mode), otherwise use lenient mode
// In lenient mode (no MD5), we accept whatever was written
if (this->md5_set_) {
this->md5_.calculate();
if (!this->md5_.equals_bytes(this->expected_md5_)) {
ESP_LOGE(TAG, "MD5 mismatch");
this->abort();
return OTA_RESPONSE_ERROR_MD5_MISMATCH;
}
} else {
// Lenient mode: adjust size to what was actually written
// This matches Arduino's Update.end(true) behavior
this->image_size_ = actual_size;
}
// Verify firmware header
if (!this->verify_end_()) {
this->abort();
return OTA_RESPONSE_ERROR_UPDATE_END;
}
// Write eboot command to copy firmware on next boot
eboot_command ebcmd;
ebcmd.action = ACTION_COPY_RAW;
ebcmd.args[0] = this->start_address_;
ebcmd.args[1] = 0x00000; // Destination: start of flash
ebcmd.args[2] = this->image_size_;
eboot_command_write(&ebcmd);
ESP_LOGI(TAG, "OTA update staged: 0x%08" PRIX32 " -> 0x00000, size=%zu", this->start_address_, this->image_size_);
// Clean up
this->buffer_.reset();
esp8266::preferences_prevent_write(false);
return OTA_RESPONSE_OK;
}
void ESP8266OTABackend::abort() {
this->buffer_.reset();
this->buffer_len_ = 0;
this->image_size_ = 0;
esp8266::preferences_prevent_write(false);
}
bool ESP8266OTABackend::verify_end_() {
uint32_t buf;
if (spi_flash_read(this->start_address_, &buf, 4) != SPI_FLASH_RESULT_OK) {
ESP_LOGE(TAG, "Failed to read firmware header");
return false;
}
uint8_t *bytes = reinterpret_cast<uint8_t *>(&buf);
// Check for GZIP (compressed firmware)
if (bytes[0] == GZIP_MAGIC_1 && bytes[1] == GZIP_MAGIC_2) {
// GZIP compressed - can't verify further
return true;
}
// Check firmware magic byte
if (bytes[0] != FIRMWARE_MAGIC) {
ESP_LOGE(TAG, "Invalid firmware magic: 0x%02X (expected 0x%02X)", bytes[0], FIRMWARE_MAGIC);
return false;
}
#if !FLASH_MAP_SUPPORT
// Check if new firmware's flash size fits (only when auto-detection is disabled)
// With FLASH_MAP_SUPPORT (modern cores), flash size is auto-detected from chip
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
uint32_t bin_flash_size = ESP.magicFlashChipSize((bytes[3] & 0xf0) >> 4);
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
if (bin_flash_size > ESP.getFlashChipRealSize()) {
ESP_LOGE(TAG, "Firmware flash size (%" PRIu32 ") exceeds chip size (%" PRIu32 ")", bin_flash_size,
ESP.getFlashChipRealSize());
return false;
}
#endif
return true;
}
uint8_t ESP8266OTABackend::get_flash_chip_mode_() {
uint32_t data;
if (spi_flash_read(0x0000, &data, 4) != SPI_FLASH_RESULT_OK) {
return 0; // Default to QIO
}
return (reinterpret_cast<uint8_t *>(&data))[FLASH_MODE_OFFSET];
}
} // namespace esphome::ota
#endif // USE_ESP8266

View File

@@ -0,0 +1,58 @@
#pragma once
#ifdef USE_ESP8266
#include "ota_backend.h"
#include "esphome/components/md5/md5.h"
#include "esphome/core/defines.h"
#include <memory>
namespace esphome::ota {
/// OTA backend for ESP8266 using native SDK functions.
/// This implementation bypasses the Arduino Updater library to save ~228 bytes of RAM
/// by not having a global Update object in .bss.
class ESP8266OTABackend : public OTABackend {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;
OTAResponseTypes write(uint8_t *data, size_t len) override;
OTAResponseTypes end() override;
void abort() override;
// Compression supported in all ESP8266 Arduino versions ESPHome supports (>= 2.7.0)
bool supports_compression() override { return true; }
protected:
/// Erase flash sector if current address is at sector boundary
bool erase_sector_if_needed_();
/// Write buffer to flash (does not update address or clear buffer)
bool flash_write_();
/// Write buffered data to flash and update MD5
bool write_buffer_();
/// Write buffered data to flash without MD5 update (for final padded write)
bool write_buffer_final_();
/// Verify the firmware header is valid
bool verify_end_();
/// Get current flash chip mode from flash header
uint8_t get_flash_chip_mode_();
std::unique_ptr<uint8_t[]> buffer_;
size_t buffer_size_{0};
size_t buffer_len_{0};
uint32_t start_address_{0};
uint32_t current_address_{0};
size_t image_size_{0};
md5::MD5Digest md5_{};
uint8_t expected_md5_[16]; // Fixed-size buffer for 128-bit (16-byte) MD5 digest
bool md5_set_{false};
};
} // namespace esphome::ota
#endif // USE_ESP8266

View File

@@ -1,5 +1,4 @@
#include "pid_climate.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -163,16 +162,14 @@ void PIDClimate::start_autotune(std::unique_ptr<PIDAutotuner> &&autotune) {
float min_value = this->supports_cool_() ? -1.0f : 0.0f;
float max_value = this->supports_heat_() ? 1.0f : 0.0f;
this->autotuner_->config(min_value, max_value);
char object_id_buf[OBJECT_ID_MAX_LEN];
StringRef object_id = this->get_object_id_to(object_id_buf);
this->autotuner_->set_autotuner_id(std::string(object_id.c_str()));
this->autotuner_->set_autotuner_id(this->get_name());
ESP_LOGI(TAG,
"%s: Autotune has started. This can take a long time depending on the "
"responsiveness of your system. Your system "
"output will be altered to deliberately oscillate above and below the setpoint multiple times. "
"Until your sensor provides a reading, the autotuner may display \'nan\'",
object_id.c_str());
this->get_name().c_str());
this->set_interval("autotune-progress", 10000, [this]() {
if (this->autotuner_ != nullptr && !this->autotuner_->is_finished())
@@ -180,7 +177,8 @@ void PIDClimate::start_autotune(std::unique_ptr<PIDAutotuner> &&autotune) {
});
if (mode != climate::CLIMATE_MODE_HEAT_COOL) {
ESP_LOGW(TAG, "%s: !!! For PID autotuner you need to set AUTO (also called heat/cool) mode!", object_id.c_str());
ESP_LOGW(TAG, "%s: !!! For PID autotuner you need to set AUTO (also called heat/cool) mode!",
this->get_name().c_str());
}
}

View File

@@ -116,8 +116,7 @@ std::string PrometheusHandler::relabel_id_(EntityBase *obj) {
return item->second;
}
char object_id_buf[OBJECT_ID_MAX_LEN];
StringRef object_id = obj->get_object_id_to(object_id_buf);
return std::string(object_id.c_str());
return obj->get_object_id_to(object_id_buf).str();
}
std::string PrometheusHandler::relabel_name_(EntityBase *obj) {

View File

@@ -60,13 +60,13 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent {
* Valid values are from -99.5 to 1999.5. Smaller values are displayed as Lo, higher as Hi.
* It will printed as it fits in the screen.
*/
void print_bignum(float bignum) { this->bignum_ = bignum * 10; }
void print_bignum(float bignum) { this->bignum_ = static_cast<int16_t>(bignum * 10); }
/**
* Print the small number
*
* Valid values are from -9 to 99. Smaller values are displayed as Lo, higher as Hi.
*/
void print_smallnum(float smallnum) { this->smallnum_ = smallnum; }
void print_smallnum(float smallnum) { this->smallnum_ = static_cast<int16_t>(smallnum); }
/**
* Print a happy face
*
@@ -107,8 +107,8 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent {
bool auto_clear_enabled_{true};
uint32_t disconnect_delay_ms_ = 5000;
uint16_t validity_period_ = 300;
uint16_t bignum_ = 0;
uint16_t smallnum_ = 0;
int16_t bignum_ = 0;
int16_t smallnum_ = 0;
uint8_t cfg_ = 0;
void setcfgbit_(uint8_t bit, bool value);

View File

@@ -154,7 +154,7 @@ CONFIG_SCHEMA = cv.All(
upper=True,
key=CONF_MODEL,
),
cv.only_with_esp_idf,
cv.only_on_esp32,
)

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