1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-16 06:45:48 +00:00

Compare commits

..

18 Commits

Author SHA1 Message Date
J. Nick Koston
d60c358f48 preen 2025-11-15 18:43:39 -06:00
J. Nick Koston
26b820272a optimize esp8266 as well 2025-11-15 18:36:39 -06:00
J. Nick Koston
9557c90c20 comment 2025-11-15 18:31:07 -06:00
J. Nick Koston
d64bcf27b3 cleanup 2025-11-15 18:17:50 -06:00
J. Nick Koston
d5d61546e7 cleanup 2025-11-15 18:15:10 -06:00
J. Nick Koston
554cdbd5a4 bot is right 2025-11-15 18:03:08 -06:00
J. Nick Koston
0d147e5d10 missed one 2025-11-15 17:42:01 -06:00
J. Nick Koston
730a70ee8b missed header 2025-11-15 17:37:37 -06:00
J. Nick Koston
d096f1192d make bot happy 2025-11-15 17:35:41 -06:00
J. Nick Koston
88a23acc4b tweak 2025-11-15 17:19:37 -06:00
J. Nick Koston
950dff1a38 [logger] Reduce ESP32 UART mutex overhead by 50% 2025-11-15 16:54:18 -06:00
J. Nick Koston
61eddfdcda [logger] Reduce ESP32 UART mutex overhead by 50% 2025-11-15 16:47:35 -06:00
dependabot[bot]
1df996601d Bump ruff from 0.14.4 to 0.14.5 (#11910)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-11-14 19:14:07 +00:00
dependabot[bot]
c32891ec02 Bump github/codeql-action from 4.31.2 to 4.31.3 (#11911)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-14 13:09:59 -06:00
Jonathan Swoboda
2bf6d48fcf [uart] Improve error handling and validate buffer size (#11895)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-11-14 14:06:08 -05:00
Edward Firmo
e49a943cf7 [wifi] Allow use_psram with Arduino (#11902) 2025-11-14 09:13:48 -05:00
dependabot[bot]
67524e14ee Bump pylint from 4.0.2 to 4.0.3 (#11894)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-13 19:05:02 +00:00
Edward Firmo
2290eb0dd2 [light] Fix missing ColorMode::BRIGHTNESS case in logging (#11836) 2025-11-13 12:08:06 -06:00
21 changed files with 161 additions and 350 deletions

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
with:
category: "/language:${{matrix.language}}"

View File

@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.4
rev: v0.14.5
hooks:
# Run the linter.
- id: ruff

View File

@@ -52,8 +52,10 @@ static void log_invalid_parameter(const char *name, const LogString *message) {
}
static const LogString *color_mode_to_human(ColorMode color_mode) {
if (color_mode == ColorMode::UNKNOWN)
return LOG_STR("Unknown");
if (color_mode == ColorMode::ON_OFF)
return LOG_STR("On/Off");
if (color_mode == ColorMode::BRIGHTNESS)
return LOG_STR("Brightness");
if (color_mode == ColorMode::WHITE)
return LOG_STR("White");
if (color_mode == ColorMode::COLOR_TEMPERATURE)
@@ -68,7 +70,7 @@ static const LogString *color_mode_to_human(ColorMode color_mode) {
return LOG_STR("RGB + cold/warm white");
if (color_mode == ColorMode::RGB_COLOR_TEMPERATURE)
return LOG_STR("RGB + color temperature");
return LOG_STR("");
return LOG_STR("Unknown");
}
// Helper to log percentage values

View File

@@ -365,8 +365,10 @@ async def to_code(config):
if CORE.is_esp32:
if config[CONF_HARDWARE_UART] == USB_CDC:
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC")
elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG:
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True)
cg.add_define("USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG")
try:
uart_selection(USB_SERIAL_JTAG)
cg.add_define("USE_LOGGER_USB_SERIAL_JTAG")

View File

@@ -65,7 +65,9 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
uint16_t buffer_at = 0; // Initialize buffer position
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
MAX_CONSOLE_LOG_MSG_SIZE);
this->write_msg_(console_buffer);
// Add newline if platform needs it (ESP32 doesn't add via write_msg_)
this->add_newline_to_buffer_if_needed_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
this->write_msg_(console_buffer, buffer_at);
}
// Reset the recursion guard for this task
@@ -131,18 +133,19 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
// Save the offset before calling format_log_to_buffer_with_terminator_
// since it will increment tx_buffer_at_ to the end of the formatted string
uint32_t msg_start = this->tx_buffer_at_;
uint16_t msg_start = this->tx_buffer_at_;
this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
&this->tx_buffer_at_, this->tx_buffer_size_);
// Write to console and send callback starting at the msg_start
if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_ + msg_start);
}
size_t msg_length =
uint16_t msg_length =
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
// Callbacks get message first (before console write)
this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length);
// Write to console starting at the msg_start
this->write_tx_buffer_to_console_(msg_start, &msg_length);
global_recursion_guard_ = false;
}
#endif // USE_STORE_LOG_STR_IN_FLASH
@@ -209,9 +212,7 @@ void Logger::process_messages_() {
// This ensures all log messages appear on the console in a clean, serialized manner
// Note: Messages may appear slightly out of order due to async processing, but
// this is preferred over corrupted/interleaved console output
if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_);
}
this->write_tx_buffer_to_console_();
}
} else {
// No messages to process, disable loop if appropriate

View File

@@ -71,6 +71,17 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128;
// "0x" + 2 hex digits per byte + '\0'
static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1;
// Platform-specific: does write_msg_ add its own newline?
// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266)
// Allows single write call with newline included for efficiency
// true: write_msg_ adds newline itself via puts()/println() (other platforms)
// Newline should NOT be added to buffer
#if defined(USE_ESP32) || defined(USE_ESP8266)
static constexpr bool WRITE_MSG_ADDS_NEWLINE = false;
#else
static constexpr bool WRITE_MSG_ADDS_NEWLINE = true;
#endif
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
/** Enum for logging UART selection
*
@@ -173,7 +184,7 @@ class Logger : public Component {
protected:
void process_messages_();
void write_msg_(const char *msg);
void write_msg_(const char *msg, size_t len);
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
// It's the caller's responsibility to initialize buffer_at (typically to 0)
@@ -200,6 +211,35 @@ class Logger : public Component {
}
}
// Helper to add newline to buffer for platforms that need it
// Modifies buffer_at to include the newline
inline void HOT add_newline_to_buffer_if_needed_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
if constexpr (!WRITE_MSG_ADDS_NEWLINE) {
// Add newline - don't need to maintain null termination
// write_msg_ now always receives explicit length, so we can safely overwrite the null terminator
// This is safe because:
// 1. Callbacks already received the message (before we add newline)
// 2. write_msg_ receives the length explicitly (doesn't need null terminator)
if (*buffer_at < buffer_size) {
buffer[(*buffer_at)++] = '\n';
} else if (buffer_size > 0) {
// Buffer was full - replace last char with newline to ensure it's visible
buffer[buffer_size - 1] = '\n';
*buffer_at = buffer_size;
}
}
}
// Helper to write tx_buffer_ to console if logging is enabled
// INTERNAL USE ONLY - offset > 0 requires length parameter to be non-null
inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) {
if (this->baud_rate_ > 0) {
uint16_t *len_ptr = length ? length : &this->tx_buffer_at_;
this->add_newline_to_buffer_if_needed_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset);
this->write_msg_(this->tx_buffer_ + offset, *len_ptr);
}
}
// Helper to format and send a log message to both console and callbacks
inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format,
va_list args) {
@@ -208,10 +248,11 @@ class Logger : public Component {
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_,
this->tx_buffer_size_);
if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console
}
// Callbacks get message WITHOUT newline (for API/MQTT/syslog)
this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_);
// Console gets message WITH newline (if platform needs it)
this->write_tx_buffer_to_console_();
}
// Write the body of the log message to the buffer
@@ -425,7 +466,9 @@ class Logger : public Component {
}
// Update buffer_at with the formatted length (handle truncation)
uint16_t formatted_len = (ret >= remaining) ? remaining : ret;
// When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator
// When it doesn't truncate (ret < remaining), it writes ret chars + null terminator
uint16_t formatted_len = (ret >= remaining) ? (remaining - 1) : ret;
*buffer_at += formatted_len;
// Remove all trailing newlines right after formatting

View File

@@ -121,25 +121,23 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) {
if (
#if defined(USE_LOGGER_USB_CDC) && !defined(USE_LOGGER_USB_SERIAL_JTAG)
this->uart_ == UART_SELECTION_USB_CDC
#elif defined(USE_LOGGER_USB_SERIAL_JTAG) && !defined(USE_LOGGER_USB_CDC)
this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
#elif defined(USE_LOGGER_USB_CDC) && defined(USE_LOGGER_USB_SERIAL_JTAG)
this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
void HOT Logger::write_msg_(const char *msg, size_t len) {
// Length is now always passed explicitly - no strlen() fallback needed
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC) || defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG)
// USB CDC/JTAG - single write including newline (already in buffer)
// Use fwrite to stdout which goes through VFS to USB console
//
// Note: These defines indicate the user's YAML configuration choice (hardware_uart: USB_CDC/USB_SERIAL_JTAG).
// They are ONLY defined when the user explicitly selects USB as the logger output in their config.
// This is compile-time selection, not runtime detection - if USB is configured, it's always used.
// There is no fallback to regular UART if "USB isn't connected" - that's the user's responsibility
// to configure correctly for their hardware. This approach eliminates runtime overhead.
fwrite(msg, 1, len, stdout);
#else
/* DISABLES CODE */ (false) // NOLINT
// Regular UART - single write including newline (already in buffer)
uart_write_bytes(this->uart_num_, msg, len);
#endif
) {
puts(msg);
} else {
// Use tx_buffer_at_ if msg points to tx_buffer_, otherwise fall back to strlen
size_t len = (msg == this->tx_buffer_) ? this->tx_buffer_at_ : strlen(msg);
uart_write_bytes(this->uart_num_, msg, len);
uart_write_bytes(this->uart_num_, "\n", 1);
}
}
const LogString *Logger::get_uart_selection_() {

View File

@@ -33,7 +33,10 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
void HOT Logger::write_msg_(const char *msg, size_t len) {
// Single write with newline already in buffer (added by caller)
this->hw_serial_->write(msg, len);
}
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {

View File

@@ -3,7 +3,7 @@
namespace esphome::logger {
void HOT Logger::write_msg_(const char *msg) {
void HOT Logger::write_msg_(const char *msg, size_t) {
time_t rawtime;
struct tm *timeinfo;
char buffer[80];

View File

@@ -49,7 +49,7 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); }
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {

View File

@@ -27,7 +27,7 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); }
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {

View File

@@ -62,7 +62,7 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) {
void HOT Logger::write_msg_(const char *msg, size_t) {
#ifdef CONFIG_PRINTK
printk("%s\n", msg);
#endif

View File

@@ -137,11 +137,7 @@ async def to_code(config):
cg.add(var.set_arming_night_time(config[CONF_ARMING_NIGHT_TIME]))
supports_arm_night = True
if sensors := config.get(CONF_BINARY_SENSORS, []):
# Initialize FixedVector with the exact number of sensors
cg.add(var.init_sensors(len(sensors)))
for sensor in sensors:
for sensor in config.get(CONF_BINARY_SENSORS, []):
bs = await cg.get_variable(sensor[CONF_INPUT])
flags = BinarySensorFlags[FLAG_NORMAL]

View File

@@ -20,13 +20,10 @@ void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor,
// Save the flags and type. Assign a store index for the per sensor data type.
SensorDataStore sd;
sd.last_chime_state = false;
AlarmSensor alarm_sensor;
alarm_sensor.sensor = sensor;
alarm_sensor.info.flags = flags;
alarm_sensor.info.type = type;
alarm_sensor.info.store_index = this->next_store_index_++;
this->sensors_.push_back(alarm_sensor);
this->sensor_map_[sensor].flags = flags;
this->sensor_map_[sensor].type = type;
this->sensor_data_.push_back(sd);
this->sensor_map_[sensor].store_index = this->next_store_index_++;
};
static const LogString *sensor_type_to_string(AlarmSensorType type) {
@@ -48,7 +45,7 @@ void TemplateAlarmControlPanel::dump_config() {
ESP_LOGCONFIG(TAG,
"TemplateAlarmControlPanel:\n"
" Current State: %s\n"
" Number of Codes: %zu\n"
" Number of Codes: %u\n"
" Requires Code To Arm: %s\n"
" Arming Away Time: %" PRIu32 "s\n"
" Arming Home Time: %" PRIu32 "s\n"
@@ -61,8 +58,7 @@ void TemplateAlarmControlPanel::dump_config() {
(this->arming_home_time_ / 1000), (this->arming_night_time_ / 1000), (this->pending_time_ / 1000),
(this->trigger_time_ / 1000), this->get_supported_features());
#ifdef USE_BINARY_SENSOR
for (const auto &alarm_sensor : this->sensors_) {
const uint16_t flags = alarm_sensor.info.flags;
for (auto const &[sensor, info] : this->sensor_map_) {
ESP_LOGCONFIG(TAG,
" Binary Sensor:\n"
" Name: %s\n"
@@ -71,10 +67,11 @@ void TemplateAlarmControlPanel::dump_config() {
" Armed night bypass: %s\n"
" Auto bypass: %s\n"
" Chime mode: %s",
alarm_sensor.sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(alarm_sensor.info.type)),
TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME),
TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT),
TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_AUTO), TRUEFALSE(flags & BINARY_SENSOR_MODE_CHIME));
sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(info.type)),
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME),
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT),
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO),
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_CHIME));
}
#endif
}
@@ -124,9 +121,7 @@ void TemplateAlarmControlPanel::loop() {
#ifdef USE_BINARY_SENSOR
// Test all of the sensors regardless of the alarm panel state
for (const auto &alarm_sensor : this->sensors_) {
const auto &info = alarm_sensor.info;
auto *sensor = alarm_sensor.sensor;
for (auto const &[sensor, info] : this->sensor_map_) {
// Check for chime zones
if (info.flags & BINARY_SENSOR_MODE_CHIME) {
// Look for the transition from closed to open
@@ -247,11 +242,11 @@ void TemplateAlarmControlPanel::arm_(optional<std::string> code, alarm_control_p
void TemplateAlarmControlPanel::bypass_before_arming() {
#ifdef USE_BINARY_SENSOR
for (const auto &alarm_sensor : this->sensors_) {
for (auto const &[sensor, info] : this->sensor_map_) {
// Check for faulted bypass_auto sensors and remove them from monitoring
if ((alarm_sensor.info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (alarm_sensor.sensor->state)) {
ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", alarm_sensor.sensor->get_name().c_str());
this->bypassed_sensor_indicies_.push_back(alarm_sensor.info.store_index);
if ((info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor->state)) {
ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", sensor->get_name().c_str());
this->bypassed_sensor_indicies_.push_back(info.store_index);
}
}
#endif

View File

@@ -1,12 +1,11 @@
#pragma once
#include <cinttypes>
#include <vector>
#include <map>
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
@@ -50,13 +49,6 @@ struct SensorInfo {
uint8_t store_index;
};
#ifdef USE_BINARY_SENSOR
struct AlarmSensor {
binary_sensor::BinarySensor *sensor;
SensorInfo info;
};
#endif
class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControlPanel, public Component {
public:
TemplateAlarmControlPanel();
@@ -71,12 +63,6 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
void bypass_before_arming();
#ifdef USE_BINARY_SENSOR
/** Initialize the sensors vector with the specified capacity.
*
* @param capacity The number of sensors to allocate space for.
*/
void init_sensors(size_t capacity) { this->sensors_.init(capacity); }
/** Add a binary_sensor to the alarm_panel.
*
* @param sensor The BinarySensor instance.
@@ -136,8 +122,8 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
protected:
void control(const alarm_control_panel::AlarmControlPanelCall &call) override;
#ifdef USE_BINARY_SENSOR
// List of binary sensors with their alarm-specific info
FixedVector<AlarmSensor> sensors_;
// This maps a binary sensor to its alarm specific info
std::map<binary_sensor::BinarySensor *, SensorInfo> sensor_map_;
// a list of automatically bypassed sensors
std::vector<uint8_t> bypassed_sensor_indicies_;
#endif

View File

@@ -1,3 +1,4 @@
from logging import getLogger
import math
import re
@@ -35,6 +36,8 @@ from esphome.core import CORE, ID
import esphome.final_validate as fv
from esphome.yaml_util import make_data_base
_LOGGER = getLogger(__name__)
CODEOWNERS = ["@esphome/core"]
uart_ns = cg.esphome_ns.namespace("uart")
UARTComponent = uart_ns.class_("UARTComponent")
@@ -130,6 +133,21 @@ def validate_host_config(config):
return config
def validate_rx_buffer_size(config):
if CORE.is_esp32:
# ESP32 UART hardware FIFO is 128 bytes (LP UART is 16 bytes, but we use 128 as safe minimum)
# rx_buffer_size must be greater than the hardware FIFO length
min_buffer_size = 128
if config[CONF_RX_BUFFER_SIZE] <= min_buffer_size:
_LOGGER.warning(
"UART rx_buffer_size (%d bytes) is too small and must be greater than the hardware "
"FIFO size (%d bytes). The buffer size will be automatically adjusted at runtime.",
config[CONF_RX_BUFFER_SIZE],
min_buffer_size,
)
return config
def _uart_declare_type(value):
if CORE.is_esp8266:
return cv.declare_id(ESP8266UartComponent)(value)
@@ -247,6 +265,7 @@ CONFIG_SCHEMA = cv.All(
).extend(cv.COMPONENT_SCHEMA),
cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN, CONF_PORT),
validate_host_config,
validate_rx_buffer_size,
)

View File

@@ -91,6 +91,16 @@ void IDFUARTComponent::setup() {
this->uart_num_ = static_cast<uart_port_t>(next_uart_num++);
this->lock_ = xSemaphoreCreateMutex();
#if (SOC_UART_LP_NUM >= 1)
size_t fifo_len = ((this->uart_num_ < SOC_UART_HP_NUM) ? SOC_UART_FIFO_LEN : SOC_LP_UART_FIFO_LEN);
#else
size_t fifo_len = SOC_UART_FIFO_LEN;
#endif
if (this->rx_buffer_size_ <= fifo_len) {
ESP_LOGW(TAG, "rx_buffer_size is too small, must be greater than %zu", fifo_len);
this->rx_buffer_size_ = fifo_len * 2;
}
xSemaphoreTake(this->lock_, portMAX_DELAY);
this->load_settings(false);
@@ -237,8 +247,12 @@ void IDFUARTComponent::set_rx_timeout(size_t rx_timeout) {
void IDFUARTComponent::write_array(const uint8_t *data, size_t len) {
xSemaphoreTake(this->lock_, portMAX_DELAY);
uart_write_bytes(this->uart_num_, data, len);
int32_t write_len = uart_write_bytes(this->uart_num_, data, len);
xSemaphoreGive(this->lock_);
if (write_len != (int32_t) len) {
ESP_LOGW(TAG, "uart_write_bytes failed: %d != %zu", write_len, len);
this->mark_failed();
}
#ifdef USE_UART_DEBUGGER
for (size_t i = 0; i < len; i++) {
this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
@@ -267,6 +281,7 @@ bool IDFUARTComponent::peek_byte(uint8_t *data) {
bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
size_t length_to_read = len;
int32_t read_len = 0;
if (!this->check_read_timeout_(len))
return false;
xSemaphoreTake(this->lock_, portMAX_DELAY);
@@ -277,25 +292,31 @@ bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
this->has_peek_ = false;
}
if (length_to_read > 0)
uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_PERIOD_MS);
read_len = uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_PERIOD_MS);
xSemaphoreGive(this->lock_);
#ifdef USE_UART_DEBUGGER
for (size_t i = 0; i < len; i++) {
this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
}
#endif
return true;
return read_len == (int32_t) length_to_read;
}
int IDFUARTComponent::available() {
size_t available;
size_t available = 0;
esp_err_t err;
xSemaphoreTake(this->lock_, portMAX_DELAY);
uart_get_buffered_data_len(this->uart_num_, &available);
if (this->has_peek_)
available++;
err = uart_get_buffered_data_len(this->uart_num_, &available);
xSemaphoreGive(this->lock_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "uart_get_buffered_data_len failed: %s", esp_err_to_name(err));
this->mark_failed();
}
if (this->has_peek_) {
available++;
}
return available;
}

View File

@@ -12,7 +12,6 @@ from esphome.components.network import (
from esphome.components.psram import is_guaranteed as psram_is_guaranteed
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.config_validation import only_with_esp_idf
from esphome.const import (
CONF_AP,
CONF_BSSID,
@@ -352,7 +351,7 @@ CONFIG_SCHEMA = cv.All(
single=True
),
cv.Optional(CONF_USE_PSRAM): cv.All(
only_with_esp_idf, cv.requires_component("psram"), cv.boolean
cv.only_on_esp32, cv.requires_component("psram"), cv.boolean
),
}
),

View File

@@ -1,6 +1,6 @@
pylint==4.0.2
pylint==4.0.3
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
ruff==0.14.4 # also change in .pre-commit-config.yaml when updating
ruff==0.14.5 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.21.1 # also change in .pre-commit-config.yaml when updating
pre-commit

View File

@@ -1,136 +0,0 @@
esphome:
name: template-alarm-many-sensors
friendly_name: "Template Alarm Control Panel with Many Sensors"
logger:
host:
api:
binary_sensor:
- platform: template
id: sensor1
name: "Door 1"
- platform: template
id: sensor2
name: "Door 2"
- platform: template
id: sensor3
name: "Window 1"
- platform: template
id: sensor4
name: "Window 2"
- platform: template
id: sensor5
name: "Motion 1"
- platform: template
id: sensor6
name: "Motion 2"
- platform: template
id: sensor7
name: "Glass Break 1"
- platform: template
id: sensor8
name: "Glass Break 2"
- platform: template
id: sensor9
name: "Smoke Detector"
- platform: template
id: sensor10
name: "CO Detector"
alarm_control_panel:
- platform: template
id: test_alarm
name: "Test Alarm"
codes:
- "1234"
requires_code_to_arm: true
arming_away_time: 5s
arming_home_time: 3s
arming_night_time: 3s
pending_time: 10s
trigger_time: 300s
restore_mode: ALWAYS_DISARMED
binary_sensors:
- input: sensor1
bypass_armed_home: false
bypass_armed_night: false
bypass_auto: true
chime: true
trigger_mode: DELAYED
- input: sensor2
bypass_armed_home: false
bypass_armed_night: false
bypass_auto: true
chime: true
trigger_mode: DELAYED
- input: sensor3
bypass_armed_home: true
bypass_armed_night: false
bypass_auto: false
chime: false
trigger_mode: DELAYED
- input: sensor4
bypass_armed_home: true
bypass_armed_night: false
bypass_auto: false
chime: false
trigger_mode: DELAYED
- input: sensor5
bypass_armed_home: false
bypass_armed_night: true
bypass_auto: false
chime: false
trigger_mode: INSTANT
- input: sensor6
bypass_armed_home: false
bypass_armed_night: true
bypass_auto: false
chime: false
trigger_mode: INSTANT
- input: sensor7
bypass_armed_home: false
bypass_armed_night: false
bypass_auto: false
chime: false
trigger_mode: INSTANT
- input: sensor8
bypass_armed_home: false
bypass_armed_night: false
bypass_auto: false
chime: false
trigger_mode: INSTANT
- input: sensor9
bypass_armed_home: false
bypass_armed_night: false
bypass_auto: false
chime: false
trigger_mode: INSTANT_ALWAYS
- input: sensor10
bypass_armed_home: false
bypass_armed_night: false
bypass_auto: false
chime: false
trigger_mode: INSTANT_ALWAYS
on_disarmed:
- logger.log: "Alarm disarmed"
on_arming:
- logger.log: "Alarm arming"
on_armed_away:
- logger.log: "Alarm armed away"
on_armed_home:
- logger.log: "Alarm armed home"
on_armed_night:
- logger.log: "Alarm armed night"
on_pending:
- logger.log: "Alarm pending"
on_triggered:
- logger.log: "Alarm triggered"
on_cleared:
- logger.log: "Alarm cleared"
on_chime:
- logger.log: "Chime activated"
on_ready:
- logger.log: "Sensors ready state changed"

View File

@@ -1,118 +0,0 @@
"""Integration test for template alarm control panel with many sensors."""
from __future__ import annotations
import aioesphomeapi
from aioesphomeapi.model import APIIntEnum
import pytest
from .state_utils import InitialStateHelper
from .types import APIClientConnectedFactory, RunCompiledFunction
class EspHomeACPFeatures(APIIntEnum):
"""ESPHome AlarmControlPanel feature numbers."""
ARM_HOME = 1
ARM_AWAY = 2
ARM_NIGHT = 4
TRIGGER = 8
ARM_CUSTOM_BYPASS = 16
ARM_VACATION = 32
@pytest.mark.asyncio
async def test_template_alarm_control_panel_many_sensors(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test template alarm control panel with 10 binary sensors using FixedVector."""
async with run_compiled(yaml_config), api_client_connected() as client:
# Get entity info first
entities, _ = await client.list_entities_services()
# Find the alarm control panel and binary sensors
alarm_info: aioesphomeapi.AlarmControlPanelInfo | None = None
binary_sensors: list[aioesphomeapi.BinarySensorInfo] = []
for entity in entities:
if isinstance(entity, aioesphomeapi.AlarmControlPanelInfo):
alarm_info = entity
elif isinstance(entity, aioesphomeapi.BinarySensorInfo):
binary_sensors.append(entity)
assert alarm_info is not None, "Alarm control panel entity info not found"
assert alarm_info.name == "Test Alarm"
assert alarm_info.requires_code is True
assert alarm_info.requires_code_to_arm is True
# Verify we have 10 binary sensors
assert len(binary_sensors) == 10, (
f"Expected 10 binary sensors, got {len(binary_sensors)}"
)
# Verify sensor names
expected_sensor_names = {
"Door 1",
"Door 2",
"Window 1",
"Window 2",
"Motion 1",
"Motion 2",
"Glass Break 1",
"Glass Break 2",
"Smoke Detector",
"CO Detector",
}
actual_sensor_names = {sensor.name for sensor in binary_sensors}
assert actual_sensor_names == expected_sensor_names, (
f"Sensor names mismatch. Expected: {expected_sensor_names}, "
f"Got: {actual_sensor_names}"
)
# Use InitialStateHelper to wait for all initial states
state_helper = InitialStateHelper(entities)
def on_state(state: aioesphomeapi.EntityState) -> None:
# We'll receive subsequent states here after initial states
pass
client.subscribe_states(state_helper.on_state_wrapper(on_state))
# Wait for all initial states
await state_helper.wait_for_initial_states(timeout=5.0)
# Verify the alarm state is disarmed initially
alarm_state = state_helper.initial_states.get(alarm_info.key)
assert alarm_state is not None, "Alarm control panel initial state not received"
assert isinstance(alarm_state, aioesphomeapi.AlarmControlPanelEntityState)
assert alarm_state.state == aioesphomeapi.AlarmControlPanelState.DISARMED, (
f"Expected initial state DISARMED, got {alarm_state.state}"
)
# Verify all 10 binary sensors have initial states
binary_sensor_states = [
state_helper.initial_states.get(sensor.key) for sensor in binary_sensors
]
assert all(state is not None for state in binary_sensor_states), (
"Not all binary sensors have initial states"
)
# Verify all binary sensor states are BinarySensorState type
for i, state in enumerate(binary_sensor_states):
assert isinstance(state, aioesphomeapi.BinarySensorState), (
f"Binary sensor {i} state is not BinarySensorState: {type(state)}"
)
# Verify supported features
expected_features = (
EspHomeACPFeatures.ARM_HOME
| EspHomeACPFeatures.ARM_AWAY
| EspHomeACPFeatures.ARM_NIGHT
| EspHomeACPFeatures.TRIGGER
)
assert alarm_info.supported_features == expected_features, (
f"Expected supported_features={expected_features} (ARM_HOME|ARM_AWAY|ARM_NIGHT|TRIGGER), "
f"got {alarm_info.supported_features}"
)