Merge branch 'dev' into source_both_zero_copy

This commit is contained in:
J. Nick Koston
2026-01-02 16:45:45 -10:00
committed by GitHub
115 changed files with 1827 additions and 373 deletions

View File

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

View File

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

View File

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

View File

@@ -1530,7 +1530,7 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 13;
resp.api_version_minor = 14;
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
resp.set_server_info(ESPHOME_VERSION_REF);
resp.set_name(StringRef(App.get_name()));
@@ -1749,20 +1749,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
// the action list. This ensures async actions (delays, waits) complete first.
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message) {
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message) {
ExecuteServiceResponse resp;
resp.call_id = call_id;
resp.success = success;
resp.set_error_message(StringRef(error_message));
resp.set_error_message(error_message);
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message,
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len) {
ExecuteServiceResponse resp;
resp.call_id = call_id;
resp.success = success;
resp.set_error_message(StringRef(error_message));
resp.set_error_message(error_message);
resp.response_data = response_data;
resp.response_data_len = response_data_len;
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);

View File

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

View File

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

View File

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

View File

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

View File

@@ -678,7 +678,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con
}
}
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message) {
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message) {
for (auto &call : this->active_action_calls_) {
if (call.action_call_id == action_call_id) {
call.connection->send_execute_service_response(call.client_call_id, success, error_message);
@@ -688,7 +688,7 @@ void APIServer::send_action_response(uint32_t action_call_id, bool success, cons
ESP_LOGW(TAG, "Cannot send response: no active call found for action_call_id %u", action_call_id);
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message,
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len) {
for (auto &call : this->active_action_calls_) {
if (call.action_call_id == action_call_id) {

View File

@@ -165,9 +165,9 @@ class APIServer : public Component,
void unregister_active_action_call(uint32_t action_call_id);
void unregister_active_action_calls_for_connection(APIConnection *conn);
// Send response for a specific action call (uses action_call_id, sends client_call_id in response)
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message);
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message,
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len);
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,8 @@ static const char *const TAG = "tuya";
static const int COMMAND_DELAY = 10;
static const int RECEIVE_TIMEOUT = 300;
static const int MAX_RETRIES = 5;
// Max bytes to log for datapoint values (larger values are truncated)
static constexpr size_t MAX_DATAPOINT_LOG_BYTES = 16;
void Tuya::setup() {
this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
@@ -51,7 +53,9 @@ void Tuya::dump_config() {
}
for (auto &info : this->datapoints_) {
if (info.type == TuyaDatapointType::RAW) {
ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str());
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id,
format_hex_pretty_to(hex_buf, info.value_raw.data(), info.value_raw.size()));
} else if (info.type == TuyaDatapointType::BOOLEAN) {
ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool));
} else if (info.type == TuyaDatapointType::INTEGER) {
@@ -122,8 +126,11 @@ bool Tuya::validate_message_() {
// valid message
const uint8_t *message_data = data + 6;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version,
format_hex_pretty(message_data, length).c_str(), static_cast<uint8_t>(this->init_state_));
format_hex_pretty_to(hex_buf, message_data, length), static_cast<uint8_t>(this->init_state_));
#endif
this->handle_command_(command, version, message_data, length);
// return false to reset rx buffer
@@ -349,7 +356,11 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) {
switch (datapoint.type) {
case TuyaDatapointType::RAW:
datapoint.value_raw = std::vector<uint8_t>(data, data + data_size);
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
{
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id,
format_hex_pretty_to(hex_buf, datapoint.value_raw.data(), datapoint.value_raw.size()));
}
break;
case TuyaDatapointType::BOOLEAN:
if (data_size != 1) {
@@ -460,8 +471,12 @@ void Tuya::send_raw_command_(TuyaCommand command) {
break;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
version, format_hex_pretty(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
version, format_hex_pretty_to(hex_buf, command.payload.data(), command.payload.size()),
static_cast<uint8_t>(this->init_state_));
#endif
this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo});
if (!command.payload.empty())
@@ -675,7 +690,8 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType
}
void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector<uint8_t> &value, bool forced) {
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str());
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty_to(hex_buf, value.data(), value.size()));
optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
if (!datapoint.has_value()) {
ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);

View File

@@ -65,11 +65,14 @@ void UDPComponent::setup() {
server.sin_port = htons(this->listen_port_);
if (this->listen_address_.has_value()) {
// Only 16 bytes needed for IPv4, but use standard size for consistency
char addr_buf[network::IP_ADDRESS_BUFFER_SIZE];
this->listen_address_.value().str_to(addr_buf);
struct ip_mreq imreq = {};
imreq.imr_interface.s_addr = ESPHOME_INADDR_ANY;
inet_aton(this->listen_address_.value().str().c_str(), &imreq.imr_multiaddr);
inet_aton(addr_buf, &imreq.imr_multiaddr);
server.sin_addr.s_addr = imreq.imr_multiaddr.s_addr;
ESP_LOGD(TAG, "Join multicast %s", this->listen_address_.value().str().c_str());
ESP_LOGD(TAG, "Join multicast %s", addr_buf);
err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq));
if (err < 0) {
ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno);

View File

@@ -8,6 +8,9 @@ namespace uponor_smatrix {
static const char *const TAG = "uponor_smatrix";
// Maximum bytes to log in verbose hex output
static constexpr size_t UPONOR_MAX_LOG_BYTES = 36;
void UponorSmatrixComponent::setup() {
#ifdef USE_TIME
if (this->time_id_ != nullptr) {
@@ -97,8 +100,11 @@ bool UponorSmatrixComponent::parse_byte_(uint8_t byte) {
return false;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_size(UPONOR_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "Received packet: addr=%08X, data=%s, crc=%04X", device_address,
format_hex(&packet[4], packet_len - 6).c_str(), crc);
format_hex_to(hex_buf, &packet[4], packet_len - 6), crc);
// Handle packet
size_t data_len = (packet_len - 6) / 3;

View File

@@ -8,6 +8,9 @@ namespace vbus {
static const char *const TAG = "vbus";
// Maximum bytes to log in verbose hex output (16 frames * 4 bytes = 64 bytes typical)
static constexpr size_t VBUS_MAX_LOG_BYTES = 64;
void VBus::dump_config() {
ESP_LOGCONFIG(TAG, "VBus:");
check_uart_settings(9600);
@@ -101,8 +104,11 @@ void VBus::loop() {
this->buffer_.push_back(this->fbytes_[i]);
if (++this->cframe_ < this->frames_)
continue;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_size(VBUS_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "P2 C%04x %04x->%04x: %s", this->command_, this->source_, this->dest_,
format_hex(this->buffer_).c_str());
format_hex_to(hex_buf, this->buffer_.data(), this->buffer_.size()));
for (auto &listener : this->listeners_)
listener->on_message(this->command_, this->source_, this->dest_, this->buffer_);
this->state_ = 0;

View File

@@ -177,10 +177,10 @@ uint32_t WaveshareEPaper2P13InV3::idle_timeout_() { return 5000; }
void WaveshareEPaper2P13InV3::dump_config() {
LOG_DISPLAY("", "Waveshare E-Paper", this)
ESP_LOGCONFIG(TAG, " Model: 2.13inV3");
LOG_PIN(" CS Pin: ", this->cs_)
LOG_PIN(" Reset Pin: ", this->reset_pin_)
LOG_PIN(" DC Pin: ", this->dc_pin_)
LOG_PIN(" Busy Pin: ", this->busy_pin_)
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);
LOG_UPDATE_INTERVAL(this);
}

View File

@@ -245,10 +245,8 @@ void WeikaiGPIOPin::setup() {
this->pin_mode(this->flags_);
}
std::string WeikaiGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%u via WeiKai %s", this->pin_, this->parent_->get_name());
return buffer;
size_t WeikaiGPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via WeiKai %s", this->pin_, this->parent_->get_name());
}
///////////////////////////////////////////////////////////////////////////////

View File

@@ -278,7 +278,7 @@ class WeikaiGPIOPin : public GPIOPin {
gpio::Flags get_flags() const override { return this->flags_; }
void setup() override;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void pin_mode(gpio::Flags flags) override { this->parent_->set_pin_direction_(this->pin_, flags); }
bool digital_read() override { return this->parent_->read_pin_val_(this->pin_) != this->inverted_; }
void digital_write(bool value) override { this->parent_->write_pin_val_(this->pin_, value != this->inverted_); }

View File

@@ -45,7 +45,8 @@ template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, publi
if (this->connecting_)
return;
// If already connected to the same AP, do nothing
if (global_wifi_component->wifi_ssid() == ssid) {
char ssid_buf[SSID_BUFFER_SIZE];
if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), ssid.c_str()) == 0) {
// Callback to notify the user that the connection was successful
this->connect_trigger_->trigger();
return;
@@ -94,7 +95,8 @@ template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, publi
this->cancel_timeout("wifi-connect-timeout");
this->cancel_timeout("wifi-fallback-timeout");
this->connecting_ = false;
if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) {
char ssid_buf[SSID_BUFFER_SIZE];
if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), this->new_sta_.get_ssid().c_str()) == 0) {
// Callback to notify the user that the connection was successful
this->connect_trigger_->trigger();
} else {

View File

@@ -655,12 +655,15 @@ void WiFiComponent::setup_ap_config_() {
#ifdef USE_WIFI_MANUAL_IP
auto manual_ip = this->ap_.get_manual_ip();
if (manual_ip.has_value()) {
char static_ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGCONFIG(TAG,
" AP Static IP: '%s'\n"
" AP Gateway: '%s'\n"
" AP Subnet: '%s'",
manual_ip->static_ip.str().c_str(), manual_ip->gateway.str().c_str(),
manual_ip->subnet.str().c_str());
manual_ip->static_ip.str_to(static_ip_buf), manual_ip->gateway.str_to(gateway_buf),
manual_ip->subnet.str_to(subnet_buf));
}
#endif
@@ -816,8 +819,14 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) {
#ifdef USE_WIFI_MANUAL_IP
if (ap.get_manual_ip().has_value()) {
ManualIP m = *ap.get_manual_ip();
ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str().c_str(),
m.gateway.str().c_str(), m.subnet.str().c_str(), m.dns1.str().c_str(), m.dns2.str().c_str());
char static_ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str_to(static_ip_buf),
m.gateway.str_to(gateway_buf), m.subnet.str_to(subnet_buf), m.dns1.str_to(dns1_buf),
m.dns2.str_to(dns2_buf));
} else
#endif
{
@@ -1167,7 +1176,8 @@ void WiFiComponent::check_connecting_finished() {
auto status = this->wifi_sta_connect_status_();
if (status == WiFiSTAConnectStatus::CONNECTED) {
if (wifi_ssid().empty()) {
char ssid_buf[SSID_BUFFER_SIZE];
if (wifi_ssid_to(ssid_buf)[0] == '\0') {
ESP_LOGW(TAG, "Connection incomplete");
this->retry_connect();
return;

View File

@@ -518,15 +518,12 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
switch (event->event) {
case EVENT_STAMODE_CONNECTED: {
auto it = event->event_info.connected;
char buf[33];
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(),
it.channel);
ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=%s channel=%u", it.ssid_len, (const char *) it.ssid,
format_mac_address_pretty(it.bssid).c_str(), it.channel);
s_sta_connected = true;
#ifdef USE_WIFI_LISTENERS
for (auto *listener : global_wifi_component->connect_state_listeners_) {
listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid);
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
}
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#ifdef USE_WIFI_MANUAL_IP
@@ -543,17 +540,15 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
}
case EVENT_STAMODE_DISCONNECTED: {
auto it = event->event_info.disconnected;
char buf[33];
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
if (it.reason == REASON_NO_AP_FOUND) {
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len,
(const char *) it.ssid);
s_sta_connect_not_found = true;
} else {
char bssid_s[18];
format_mac_addr_upper(it.bssid, bssid_s);
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s,
LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len,
(const char *) it.ssid, bssid_s, LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
s_sta_connect_error = true;
}
s_sta_connected = false;
@@ -810,10 +805,13 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
network::IPAddress start_address = network::IPAddress(&info.ip);
start_address += 99;
lease.start_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
#endif
ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str_to(ip_buf));
start_address += 10;
lease.end_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str());
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str_to(ip_buf));
if (!wifi_softap_set_dhcps_lease(&lease)) {
ESP_LOGE(TAG, "Set SoftAP DHCP lease failed");
return false;

View File

@@ -734,16 +734,13 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_CONNECTED) {
const auto &it = data->data.sta_connected;
char buf[33];
assert(it.ssid_len <= 32);
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len,
(const char *) it.ssid, format_mac_address_pretty(it.bssid).c_str(), it.channel,
get_auth_mode_str(it.authmode));
s_sta_connected = true;
#ifdef USE_WIFI_LISTENERS
for (auto *listener : this->connect_state_listeners_) {
listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid);
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
}
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#ifdef USE_WIFI_MANUAL_IP
@@ -757,21 +754,18 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
const auto &it = data->data.sta_disconnected;
char buf[33];
assert(it.ssid_len <= 32);
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
if (it.reason == WIFI_REASON_NO_AP_FOUND) {
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len,
(const char *) it.ssid);
s_sta_connect_not_found = true;
} else if (it.reason == WIFI_REASON_ROAMING) {
ESP_LOGI(TAG, "Disconnected ssid='%s' reason='Station Roaming'", buf);
ESP_LOGI(TAG, "Disconnected ssid='%.*s' reason='Station Roaming'", it.ssid_len, (const char *) it.ssid);
return;
} else {
char bssid_s[18];
format_mac_addr_upper(it.bssid, bssid_s);
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s,
get_disconnect_reason_str(it.reason));
ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len,
(const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason));
s_sta_connect_error = true;
}
s_sta_connected = false;
@@ -969,10 +963,13 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
network::IPAddress start_address = network::IPAddress(&info.ip);
start_address += 99;
lease.start_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
#endif
ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str_to(ip_buf));
start_address += 10;
lease.end_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str());
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str_to(ip_buf));
err = esp_netif_dhcps_option(s_ap_netif, ESP_NETIF_OP_SET, ESP_NETIF_REQUESTED_IP_ADDRESS, &lease, sizeof(lease));
if (err != ESP_OK) {
@@ -984,8 +981,10 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
// Configure DHCP Option 114 (Captive Portal URI) if captive portal is enabled
// This provides a standards-compliant way for clients to discover the captive portal
if (captive_portal::global_captive_portal != nullptr) {
static char captive_portal_uri[32];
snprintf(captive_portal_uri, sizeof(captive_portal_uri), "http://%s", network::IPAddress(&info.ip).str().c_str());
// Buffer must be static - dhcps_set_option_info stores pointer, doesn't copy
static char captive_portal_uri[24]; // "http://" (7) + IPv4 max (15) + null
memcpy(captive_portal_uri, "http://", 7); // NOLINT(bugprone-not-null-terminated-result) - str_to null-terminates
network::IPAddress(&info.ip).str_to(captive_portal_uri + 7);
err = esp_netif_dhcps_option(s_ap_netif, ESP_NETIF_OP_SET, ESP_NETIF_CAPTIVEPORTAL_URI, captive_portal_uri,
strlen(captive_portal_uri));
if (err != ESP_OK) {

View File

@@ -296,14 +296,12 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: {
auto it = info.wifi_sta_connected;
char buf[33];
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len,
(const char *) it.ssid, format_mac_address_pretty(it.bssid).c_str(), it.channel,
get_auth_mode_str(it.authmode));
#ifdef USE_WIFI_LISTENERS
for (auto *listener : this->connect_state_listeners_) {
listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid);
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
}
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#ifdef USE_WIFI_MANUAL_IP
@@ -318,9 +316,6 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: {
auto it = info.wifi_sta_disconnected;
char buf[33];
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
// LibreTiny can send spurious disconnect events with empty ssid/bssid during connection.
// These are typically "Association Leave" events that don't indicate actual failures:
@@ -339,12 +334,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
if (it.reason == WIFI_REASON_NO_AP_FOUND) {
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len,
(const char *) it.ssid);
} else {
char bssid_s[18];
format_mac_addr_upper(it.bssid, bssid_s);
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s,
get_disconnect_reason_str(it.reason));
ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len,
(const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason));
}
uint8_t reason = it.reason;

View File

@@ -12,6 +12,9 @@ namespace xiaomi_ble {
static const char *const TAG = "xiaomi_ble";
// Maximum bytes to log in very verbose hex output (covers largest packet of ~24 bytes)
static constexpr size_t XIAOMI_MAX_LOG_BYTES = 32;
bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) {
// button pressed, 3 bytes, only byte 3 is used for supported devices so far
if ((value_type == 0x1001) && (value_length == 3)) {
@@ -263,7 +266,10 @@ optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::Service
bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, const uint64_t &address) {
if ((raw.size() != 19) && ((raw.size() < 22) || (raw.size() > 24))) {
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size());
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(XIAOMI_MAX_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty_to(hex_buf, raw.data(), raw.size()));
return false;
}
@@ -320,12 +326,17 @@ bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, c
memcpy(mac_address + 4, mac_reverse + 1, 1);
memcpy(mac_address + 5, mac_reverse, 1);
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed.");
ESP_LOGVV(TAG, " MAC address : %s", format_mac_address_pretty(mac_address).c_str());
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str());
ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str());
ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str());
ESP_LOGVV(TAG, " Cipher : %s", format_hex_pretty(vector.ciphertext, vector.datasize).c_str());
ESP_LOGVV(TAG, " Tag : %s", format_hex_pretty(vector.tag, vector.tagsize).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(mac_address, mac_buf);
char hex_buf[format_hex_pretty_size(XIAOMI_MAX_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, " MAC address : %s", mac_buf);
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty_to(hex_buf, raw.data(), raw.size()));
ESP_LOGVV(TAG, " Key : %s", format_hex_pretty_to(hex_buf, vector.key, vector.keysize));
ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty_to(hex_buf, vector.iv, vector.ivsize));
ESP_LOGVV(TAG, " Cipher : %s", format_hex_pretty_to(hex_buf, vector.ciphertext, vector.datasize));
ESP_LOGVV(TAG, " Tag : %s", format_hex_pretty_to(hex_buf, vector.tag, vector.tagsize));
mbedtls_ccm_free(&ctx);
return false;
}
@@ -341,8 +352,11 @@ bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, c
raw[0] &= ~0x08;
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption passed.");
ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", format_hex_pretty(raw.data() + cipher_pos, vector.datasize).c_str(),
static_cast<int>(raw[4]));
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(XIAOMI_MAX_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d",
format_hex_pretty_to(hex_buf, raw.data() + cipher_pos, vector.datasize), static_cast<int>(raw[4]));
mbedtls_ccm_free(&ctx);
return true;

View File

@@ -110,7 +110,9 @@ void XL9535Component::pin_mode(uint8_t pin, gpio::Flags mode) {
void XL9535GPIOPin::setup() { this->pin_mode(this->flags_); }
std::string XL9535GPIOPin::dump_summary() const { return str_snprintf("%u via XL9535", 15, this->pin_); }
size_t XL9535GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via XL9535", this->pin_);
}
void XL9535GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
bool XL9535GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }

View File

@@ -39,7 +39,7 @@ class XL9535GPIOPin : public GPIOPin {
gpio::Flags get_flags() const override { return this->flags_; }
void setup() override;
std::string dump_summary() const override;
size_t dump_summary(char *buffer, size_t len) const override;
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;

View File

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

View File

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

View File

@@ -7,6 +7,9 @@
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
#ifdef USE_ESP32
#include <esp_chip_info.h>
#endif
#include "esphome/core/version.h"
#include "esphome/core/hal.h"
#include <algorithm>
@@ -203,6 +206,19 @@ void Application::loop() {
ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", build_time_str);
#ifdef ESPHOME_PROJECT_NAME
ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION);
#endif
#ifdef USE_ESP32
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
ESP_LOGI(TAG, "ESP32 Chip: %s r%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100,
chip_info.revision % 100, chip_info.cores);
#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET)
// Suggest optimization for chips that don't need the PSRAM cache workaround
if (chip_info.revision >= 300) {
ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to reduce binary size", chip_info.revision / 100,
chip_info.revision % 100);
}
#endif
#endif
}

View File

@@ -166,6 +166,7 @@
#define USE_MQTT_IDF_ENQUEUE
#define USE_ESPHOME_TASK_LOG_BUFFER
#define USE_OTA_ROLLBACK
#define USE_ESP32_MIN_CHIP_REVISION_SET
#define USE_BLUETOOTH_PROXY
#define BLUETOOTH_PROXY_MAX_CONNECTIONS 3

View File

@@ -9,7 +9,8 @@ static const char *const TAG = "entity_base";
// Entity Name
const StringRef &EntityBase::get_name() const { return this->name_; }
void EntityBase::set_name(const char *name) {
void EntityBase::set_name(const char *name) { this->set_name(name, 0); }
void EntityBase::set_name(const char *name, uint32_t object_id_hash) {
this->name_ = StringRef(name);
if (this->name_.empty()) {
#ifdef USE_DEVICES
@@ -18,11 +19,29 @@ void EntityBase::set_name(const char *name) {
} else
#endif
{
this->name_ = StringRef(App.get_friendly_name());
// Bug-for-bug compatibility with OLD behavior:
// - With MAC suffix: OLD code used App.get_friendly_name() directly (no fallback)
// - Without MAC suffix: OLD code used pre-computed object_id with fallback to device name
const std::string &friendly = App.get_friendly_name();
if (App.is_name_add_mac_suffix_enabled()) {
// MAC suffix enabled - use friendly_name directly (even if empty) for compatibility
this->name_ = StringRef(friendly);
} else {
// No MAC suffix - fallback to device name if friendly_name is empty
this->name_ = StringRef(!friendly.empty() ? friendly : App.get_name());
}
}
this->flags_.has_own_name = false;
// Dynamic name - must calculate hash at runtime
this->calc_object_id_();
} else {
this->flags_.has_own_name = true;
// Static name - use pre-computed hash if provided
if (object_id_hash != 0) {
this->object_id_hash_ = object_id_hash;
} else {
this->calc_object_id_();
}
}
}
@@ -45,69 +64,30 @@ void EntityBase::set_icon(const char *icon) {
#endif
}
// Check if the object_id is dynamic (changes with MAC suffix)
bool EntityBase::is_object_id_dynamic_() const {
return !this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled();
}
// Entity Object ID
// Entity Object ID - computed on-demand from name
std::string EntityBase::get_object_id() const {
// Check if `App.get_friendly_name()` is constant or dynamic.
if (this->is_object_id_dynamic_()) {
// `App.get_friendly_name()` is dynamic.
return str_sanitize(str_snake_case(App.get_friendly_name()));
}
// `App.get_friendly_name()` is constant.
return this->object_id_c_str_ == nullptr ? "" : this->object_id_c_str_;
}
void EntityBase::set_object_id(const char *object_id) {
this->object_id_c_str_ = object_id;
this->calc_object_id_();
}
void EntityBase::set_name_and_object_id(const char *name, const char *object_id) {
this->set_name(name);
this->object_id_c_str_ = object_id;
this->calc_object_id_();
}
// Calculate Object ID Hash from Entity Name
void EntityBase::calc_object_id_() {
char buf[OBJECT_ID_MAX_LEN];
StringRef object_id = this->get_object_id_to(buf);
this->object_id_hash_ = fnv1_hash(object_id.c_str());
size_t len = this->write_object_id_to(buf, sizeof(buf));
return std::string(buf, len);
}
// Format dynamic object_id: sanitized snake_case of friendly_name
static size_t format_dynamic_object_id(char *buf, size_t buf_size) {
const std::string &name = App.get_friendly_name();
size_t len = std::min(name.size(), buf_size - 1);
for (size_t i = 0; i < len; i++) {
buf[i] = to_sanitized_char(to_snake_case_char(name[i]));
}
buf[len] = '\0';
return len;
// Calculate Object ID Hash directly from name using snake_case + sanitize
void EntityBase::calc_object_id_() {
this->object_id_hash_ = fnv1_hash_object_id(this->name_.c_str(), this->name_.size());
}
size_t EntityBase::write_object_id_to(char *buf, size_t buf_size) const {
if (this->is_object_id_dynamic_()) {
return format_dynamic_object_id(buf, buf_size);
size_t len = std::min(this->name_.size(), buf_size - 1);
for (size_t i = 0; i < len; i++) {
buf[i] = to_sanitized_char(to_snake_case_char(this->name_[i]));
}
const char *src = this->object_id_c_str_ == nullptr ? "" : this->object_id_c_str_;
size_t len = strlen(src);
if (len >= buf_size)
len = buf_size - 1;
memcpy(buf, src, len);
buf[len] = '\0';
return len;
}
StringRef EntityBase::get_object_id_to(std::span<char, OBJECT_ID_MAX_LEN> buf) const {
if (this->is_object_id_dynamic_()) {
size_t len = format_dynamic_object_id(buf.data(), buf.size());
return StringRef(buf.data(), len);
}
return this->object_id_c_str_ == nullptr ? StringRef() : StringRef(this->object_id_c_str_);
size_t len = this->write_object_id_to(buf.data(), buf.size());
return StringRef(buf.data(), len);
}
uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; }

View File

@@ -28,6 +28,9 @@ class EntityBase {
// Get/set the name of this Entity
const StringRef &get_name() const;
void set_name(const char *name);
/// Set name with pre-computed object_id hash (avoids runtime hash calculation)
/// Use hash=0 for dynamic names that need runtime calculation
void set_name(const char *name, uint32_t object_id_hash);
// Get whether this Entity has its own name or it should use the device friendly_name.
bool has_own_name() const { return this->flags_.has_own_name; }
@@ -43,10 +46,6 @@ class EntityBase {
"which will remain available longer. get_object_id() will be removed in 2026.7.0",
"2025.12.0")
std::string get_object_id() const;
void set_object_id(const char *object_id);
// Set both name and object_id in one call (reduces generated code size)
void set_name_and_object_id(const char *name, const char *object_id);
// Get the unique Object ID of this Entity
uint32_t get_object_id_hash();
@@ -142,11 +141,7 @@ class EntityBase {
protected:
void calc_object_id_();
/// Check if the object_id is dynamic (changes with MAC suffix)
bool is_object_id_dynamic_() const;
StringRef name_;
const char *object_id_c_str_{nullptr};
#ifdef USE_ENTITY_ICON
const char *icon_c_str_{nullptr};
#endif

View File

@@ -15,7 +15,7 @@ from esphome.const import (
from esphome.core import CORE, ID
from esphome.cpp_generator import MockObj, add, get_variable
import esphome.final_validate as fv
from esphome.helpers import sanitize, snake_case
from esphome.helpers import fnv1_hash_object_id, sanitize, snake_case
from esphome.types import ConfigType, EntityMetadata
_LOGGER = logging.getLogger(__name__)
@@ -75,34 +75,18 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None:
config: Configuration dictionary containing entity settings
platform: The platform name (e.g., "sensor", "binary_sensor")
"""
# Get device info
device_name: str | None = None
device_id_obj: ID | None
# Get device info if configured
if device_id_obj := config.get(CONF_DEVICE_ID):
device: MockObj = await get_variable(device_id_obj)
add(var.set_device(device))
# Get device name for object ID calculation
device_name = device_id_obj.id
# Calculate base object_id using the same logic as C++
# This must match the C++ behavior in esphome/core/entity_base.cpp
base_object_id = get_base_entity_object_id(
config[CONF_NAME], CORE.friendly_name, device_name
)
if not config[CONF_NAME]:
_LOGGER.debug(
"Entity has empty name, using '%s' as object_id base", base_object_id
)
# Set both name and object_id in one call to reduce generated code size
add(var.set_name_and_object_id(config[CONF_NAME], base_object_id))
_LOGGER.debug(
"Setting object_id '%s' for entity '%s' on platform '%s'",
base_object_id,
config[CONF_NAME],
platform,
)
# Set the entity name with pre-computed object_id hash
# For named entities: pre-compute hash from entity name
# For empty-name entities: pass 0, C++ calculates hash at runtime from
# device name, friendly_name, or app name (bug-for-bug compatibility)
entity_name = config[CONF_NAME]
object_id_hash = fnv1_hash_object_id(entity_name) if entity_name else 0
add(var.set_name(entity_name, object_id_hash))
# Only set disabled_by_default if True (default is False)
if config[CONF_DISABLED_BY_DEFAULT]:
add(var.set_disabled_by_default(True))

24
esphome/core/gpio.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "esphome/core/gpio.h"
#include "esphome/core/log.h"
namespace esphome {
#ifdef USE_ESP8266
void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin) {
if (pin == nullptr)
return;
static constexpr size_t LOG_PIN_PREFIX_MAX_LEN = 32;
char prefix_buf[LOG_PIN_PREFIX_MAX_LEN];
strncpy_P(prefix_buf, reinterpret_cast<const char *>(prefix), sizeof(prefix_buf) - 1);
prefix_buf[sizeof(prefix_buf) - 1] = '\0';
log_pin_with_prefix(tag, prefix_buf, pin);
}
#else
void log_pin(const char *tag, const char *prefix, GPIOPin *pin) {
if (pin == nullptr)
return;
log_pin_with_prefix(tag, prefix, pin);
}
#endif
} // namespace esphome

View File

@@ -1,13 +1,22 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <string>
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
#define LOG_PIN(prefix, pin) \
if ((pin) != nullptr) { \
ESP_LOGCONFIG(TAG, prefix "%s", (pin)->dump_summary().c_str()); \
}
/// Maximum buffer size for dump_summary output
inline constexpr size_t GPIO_SUMMARY_MAX_LEN = 48;
#ifdef USE_ESP8266
#define LOG_PIN(prefix, pin) log_pin(TAG, F(prefix), pin)
#else
#define LOG_PIN(prefix, pin) log_pin(TAG, prefix, pin)
#endif
// put GPIO flags in a namespace to not pollute esphome namespace
namespace gpio {
@@ -64,7 +73,17 @@ class GPIOPin {
virtual void digital_write(bool value) = 0;
virtual std::string dump_summary() const = 0;
/// Write a summary of this pin to the provided buffer.
/// @param buffer The buffer to write to
/// @param len The size of the buffer (must be > 0)
/// @return The number of characters that would be written (excluding null terminator),
/// which may exceed len-1 if truncation occurred (snprintf semantics)
virtual size_t dump_summary(char *buffer, size_t len) const;
/// Get a summary of this pin as a string.
/// @deprecated Use dump_summary(char*, size_t) instead. Will be removed in 2026.7.0.
ESPDEPRECATED("Override dump_summary(char*, size_t) instead. Will be removed in 2026.7.0.", "2026.1.0")
virtual std::string dump_summary() const;
virtual bool is_internal() { return false; }
};
@@ -103,4 +122,41 @@ class InternalGPIOPin : public GPIOPin {
virtual void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const = 0;
};
// Inline default implementations for GPIOPin virtual methods.
// These provide bridge functionality for backwards compatibility with external components.
// Default implementation bridges to old std::string method for backwards compatibility.
inline size_t GPIOPin::dump_summary(char *buffer, size_t len) const {
if (len == 0)
return 0;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
std::string s = this->dump_summary();
#pragma GCC diagnostic pop
size_t copy_len = std::min(s.size(), len - 1);
memcpy(buffer, s.c_str(), copy_len);
buffer[copy_len] = '\0';
return s.size(); // Return would-be length (snprintf semantics)
}
// Default implementation returns empty string.
// External components should override this if they haven't migrated to buffer-based version.
// Remove before 2026.7.0
inline std::string GPIOPin::dump_summary() const { return {}; }
// Inline helper for log_pin - allows compiler to inline into log_pin in gpio.cpp
inline void log_pin_with_prefix(const char *tag, const char *prefix, GPIOPin *pin) {
char buffer[GPIO_SUMMARY_MAX_LEN];
size_t len = pin->dump_summary(buffer, sizeof(buffer));
len = std::min(len, sizeof(buffer) - 1);
esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, "%s%.*s", prefix, (int) len, buffer);
}
// log_pin function declarations - implementation in gpio.cpp
#ifdef USE_ESP8266
void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin);
#else
void log_pin(const char *tag, const char *prefix, GPIOPin *pin);
#endif
} // namespace esphome

View File

@@ -529,6 +529,20 @@ constexpr char to_sanitized_char(char c) {
/// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores.
std::string str_sanitize(const std::string &str);
/// Calculate FNV-1 hash of a string while applying snake_case + sanitize transformations.
/// This computes object_id hashes directly from names without creating an intermediate buffer.
/// IMPORTANT: Must match Python fnv1_hash_object_id() in esphome/helpers.py.
/// If you modify this function, update the Python version and tests in both places.
inline uint32_t fnv1_hash_object_id(const char *str, size_t len) {
uint32_t hash = FNV1_OFFSET_BASIS;
for (size_t i = 0; i < len; i++) {
hash *= FNV1_PRIME;
// Apply snake_case (space->underscore, uppercase->lowercase) then sanitize
hash ^= static_cast<uint8_t>(to_sanitized_char(to_snake_case_char(str[i])));
}
return hash;
}
/// snprintf-like function returning std::string of maximum length \p len (excluding null terminator).
std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...);

View File

@@ -35,6 +35,10 @@ IS_MACOS = platform.system() == "Darwin"
IS_WINDOWS = platform.system() == "Windows"
IS_LINUX = platform.system() == "Linux"
# FNV-1 hash constants (must match C++ in esphome/core/helpers.h)
FNV1_OFFSET_BASIS = 2166136261
FNV1_PRIME = 16777619
def ensure_unique_string(preferred_string, current_strings):
test_string = preferred_string
@@ -49,8 +53,17 @@ def ensure_unique_string(preferred_string, current_strings):
return test_string
def fnv1_hash(string: str) -> int:
"""FNV-1 32-bit hash function (multiply then XOR)."""
hash_value = FNV1_OFFSET_BASIS
for char in string:
hash_value = (hash_value * FNV1_PRIME) & 0xFFFFFFFF
hash_value ^= ord(char)
return hash_value
def fnv1a_32bit_hash(string: str) -> int:
"""FNV-1a 32-bit hash function.
"""FNV-1a 32-bit hash function (XOR then multiply).
Note: This uses 32-bit hash instead of 64-bit for several reasons:
1. ESPHome targets 32-bit microcontrollers with limited RAM (often <320KB)
@@ -63,13 +76,22 @@ def fnv1a_32bit_hash(string: str) -> int:
a handful of area_ids and device_ids (typically <10 areas and <100
devices), making collisions virtually impossible.
"""
hash_value = 2166136261
hash_value = FNV1_OFFSET_BASIS
for char in string:
hash_value ^= ord(char)
hash_value = (hash_value * 16777619) & 0xFFFFFFFF
hash_value = (hash_value * FNV1_PRIME) & 0xFFFFFFFF
return hash_value
def fnv1_hash_object_id(name: str) -> int:
"""Compute FNV-1 hash of name with snake_case + sanitize transformations.
IMPORTANT: Must produce same result as C++ fnv1_hash_object_id() in helpers.h.
Used for pre-computing entity object_id hashes at code generation time.
"""
return fnv1_hash(sanitize(snake_case(name)))
def strip_accents(value: str) -> str:
"""Remove accents from a string."""
import unicodedata

View File

@@ -29,7 +29,7 @@ def test_binary_sensor_sets_mandatory_fields(generate_main):
)
# Then
assert 'bs_1->set_name_and_object_id("test bs1", "test_bs1");' in main_cpp
assert 'bs_1->set_name("test bs1",' in main_cpp
assert "bs_1->set_pin(" in main_cpp

View File

@@ -26,7 +26,7 @@ def test_button_sets_mandatory_fields(generate_main):
main_cpp = generate_main("tests/component_tests/button/test_button.yaml")
# Then
assert 'wol_1->set_name_and_object_id("wol_test_1", "wol_test_1");' in main_cpp
assert 'wol_1->set_name("wol_test_1",' in main_cpp
assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp

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