1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-26 07:02:21 +01:00

Merge remote-tracking branch 'upstream/dev' into api_reboot

This commit is contained in:
J. Nick Koston
2025-06-23 10:48:26 +02:00
28 changed files with 422 additions and 236 deletions

View File

@@ -28,6 +28,12 @@
namespace esphome { namespace esphome {
namespace api { namespace api {
// Read a maximum of 5 messages per loop iteration to prevent starving other components.
// This is a balance between API responsiveness and allowing other components to run.
// Since each message could contain multiple protobuf messages when using packet batching,
// this limits the number of messages processed, not the number of TCP packets.
static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5;
static const char *const TAG = "api.connection"; static const char *const TAG = "api.connection";
static const int ESP32_CAMERA_STOP_STREAM = 5000; static const int ESP32_CAMERA_STOP_STREAM = 5000;
@@ -109,12 +115,16 @@ void APIConnection::loop() {
return; return;
} }
const uint32_t now = App.get_loop_component_start_time();
// Check if socket has data ready before attempting to read // Check if socket has data ready before attempting to read
if (this->helper_->is_socket_ready()) { if (this->helper_->is_socket_ready()) {
// Read up to MAX_MESSAGES_PER_LOOP messages per loop to improve throughput
for (uint8_t message_count = 0; message_count < MAX_MESSAGES_PER_LOOP; message_count++) {
ReadPacketBuffer buffer; ReadPacketBuffer buffer;
err = this->helper_->read_packet(&buffer); err = this->helper_->read_packet(&buffer);
if (err == APIError::WOULD_BLOCK) { if (err == APIError::WOULD_BLOCK) {
// pass // No more data available
break;
} else if (err != APIError::OK) { } else if (err != APIError::OK) {
on_fatal_error(); on_fatal_error();
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
@@ -127,7 +137,7 @@ void APIConnection::loop() {
} }
return; return;
} else { } else {
this->last_traffic_ = App.get_loop_component_start_time(); this->last_traffic_ = now;
// read a packet // read a packet
if (buffer.data_len > 0) { if (buffer.data_len > 0) {
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
@@ -138,6 +148,7 @@ void APIConnection::loop() {
return; return;
} }
} }
}
// Process deferred batch if scheduled // Process deferred batch if scheduled
if (this->deferred_batch_.batch_scheduled && if (this->deferred_batch_.batch_scheduled &&
@@ -152,7 +163,6 @@ void APIConnection::loop() {
static uint8_t max_ping_retries = 60; static uint8_t max_ping_retries = 60;
static uint16_t ping_retry_interval = 1000; static uint16_t ping_retry_interval = 1000;
const uint32_t now = App.get_loop_component_start_time();
if (this->sent_ping_) { if (this->sent_ping_) {
// Disconnect if not responded within 2.5*keepalive // Disconnect if not responded within 2.5*keepalive
if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) { if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) {

View File

@@ -274,12 +274,21 @@ APIError APINoiseFrameHelper::init() {
} }
/// Run through handshake messages (if in that phase) /// Run through handshake messages (if in that phase)
APIError APINoiseFrameHelper::loop() { APIError APINoiseFrameHelper::loop() {
// During handshake phase, process as many actions as possible until we can't progress
// socket_->ready() stays true until next main loop, but state_action() will return
// WOULD_BLOCK when no more data is available to read
while (state_ != State::DATA && this->socket_->ready()) {
APIError err = state_action_(); APIError err = state_action_();
if (err != APIError::OK && err != APIError::WOULD_BLOCK) { if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err; return err;
} }
if (err == APIError::WOULD_BLOCK) {
break;
}
}
if (!this->tx_buf_.empty()) { if (!this->tx_buf_.empty()) {
err = try_send_tx_buf_(); APIError err = try_send_tx_buf_();
if (err != APIError::OK && err != APIError::WOULD_BLOCK) { if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err; return err;
} }

View File

@@ -522,6 +522,7 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData
} }
void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) { void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
this->scan_result_ = &scan_result;
for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++) for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
this->address_[i] = scan_result.bda[i]; this->address_[i] = scan_result.bda[i];
this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type); this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type);

View File

@@ -85,6 +85,9 @@ class ESPBTDevice {
const std::vector<ServiceData> &get_service_datas() const { return service_datas_; } const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
// Exposed through a function for use in lambdas
const BLEScanResult &get_scan_result() const { return *scan_result_; }
bool resolve_irk(const uint8_t *irk) const; bool resolve_irk(const uint8_t *irk) const;
optional<ESPBLEiBeacon> get_ibeacon() const { optional<ESPBLEiBeacon> get_ibeacon() const {
@@ -111,6 +114,7 @@ class ESPBTDevice {
std::vector<ESPBTUUID> service_uuids_{}; std::vector<ESPBTUUID> service_uuids_{};
std::vector<ServiceData> manufacturer_datas_{}; std::vector<ServiceData> manufacturer_datas_{};
std::vector<ServiceData> service_datas_{}; std::vector<ServiceData> service_datas_{};
const BLEScanResult *scan_result_{nullptr};
}; };
class ESP32BLETracker; class ESP32BLETracker;

View File

@@ -1,6 +1,7 @@
from collections.abc import MutableMapping from collections.abc import MutableMapping
import functools import functools
import hashlib import hashlib
from itertools import accumulate
import logging import logging
import os import os
from pathlib import Path from pathlib import Path
@@ -468,8 +469,9 @@ class EFont:
class GlyphInfo: class GlyphInfo:
def __init__(self, data_len, advance, offset_x, offset_y, width, height): def __init__(self, glyph, data, advance, offset_x, offset_y, width, height):
self.data_len = data_len self.glyph = glyph
self.bitmap_data = data
self.advance = advance self.advance = advance
self.offset_x = offset_x self.offset_x = offset_x
self.offset_y = offset_y self.offset_y = offset_y
@@ -477,6 +479,62 @@ class GlyphInfo:
self.height = height self.height = height
def glyph_to_glyphinfo(glyph, font, size, bpp):
scale = 256 // (1 << bpp)
if not font.is_scalable:
sizes = [pt_to_px(x.size) for x in font.available_sizes]
if size in sizes:
font.select_size(sizes.index(size))
else:
font.set_pixel_sizes(size, 0)
flags = FT_LOAD_RENDER
if bpp != 1:
flags |= FT_LOAD_NO_BITMAP
else:
flags |= FT_LOAD_TARGET_MONO
font.load_char(glyph, flags)
width = font.glyph.bitmap.width
height = font.glyph.bitmap.rows
buffer = font.glyph.bitmap.buffer
pitch = font.glyph.bitmap.pitch
glyph_data = [0] * ((height * width * bpp + 7) // 8)
src_mode = font.glyph.bitmap.pixel_mode
pos = 0
for y in range(height):
for x in range(width):
if src_mode == ft_pixel_mode_mono:
pixel = (
(1 << bpp) - 1
if buffer[y * pitch + x // 8] & (1 << (7 - x % 8))
else 0
)
else:
pixel = buffer[y * pitch + x] // scale
for bit_num in range(bpp):
if pixel & (1 << (bpp - bit_num - 1)):
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
pos += 1
ascender = pt_to_px(font.size.ascender)
if ascender == 0:
if not font.is_scalable:
ascender = size
else:
_LOGGER.error(
"Unable to determine ascender of font %s %s",
font.family_name,
font.style_name,
)
return GlyphInfo(
glyph,
glyph_data,
pt_to_px(font.glyph.metrics.horiAdvance),
font.glyph.bitmap_left,
ascender - font.glyph.bitmap_top,
width,
height,
)
async def to_code(config): async def to_code(config):
""" """
Collect all glyph codepoints, construct a map from a codepoint to a font file. Collect all glyph codepoints, construct a map from a codepoint to a font file.
@@ -506,98 +564,47 @@ async def to_code(config):
codepoints = list(point_set) codepoints = list(point_set)
codepoints.sort(key=functools.cmp_to_key(glyph_comparator)) codepoints.sort(key=functools.cmp_to_key(glyph_comparator))
glyph_args = {}
data = []
bpp = config[CONF_BPP] bpp = config[CONF_BPP]
scale = 256 // (1 << bpp)
size = config[CONF_SIZE] size = config[CONF_SIZE]
# create the data array for all glyphs # create the data array for all glyphs
for codepoint in codepoints: glyph_args = [
font = point_font_map[codepoint] glyph_to_glyphinfo(x, point_font_map[x], size, bpp) for x in codepoints
if not font.is_scalable: ]
sizes = [pt_to_px(x.size) for x in font.available_sizes] rhs = [HexInt(x) for x in flatten([x.bitmap_data for x in glyph_args])]
if size in sizes:
font.select_size(sizes.index(size))
else:
font.set_pixel_sizes(size, 0)
flags = FT_LOAD_RENDER
if bpp != 1:
flags |= FT_LOAD_NO_BITMAP
else:
flags |= FT_LOAD_TARGET_MONO
font.load_char(codepoint, flags)
width = font.glyph.bitmap.width
height = font.glyph.bitmap.rows
buffer = font.glyph.bitmap.buffer
pitch = font.glyph.bitmap.pitch
glyph_data = [0] * ((height * width * bpp + 7) // 8)
src_mode = font.glyph.bitmap.pixel_mode
pos = 0
for y in range(height):
for x in range(width):
if src_mode == ft_pixel_mode_mono:
pixel = (
(1 << bpp) - 1
if buffer[y * pitch + x // 8] & (1 << (7 - x % 8))
else 0
)
else:
pixel = buffer[y * pitch + x] // scale
for bit_num in range(bpp):
if pixel & (1 << (bpp - bit_num - 1)):
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
pos += 1
ascender = pt_to_px(font.size.ascender)
if ascender == 0:
if not font.is_scalable:
ascender = size
else:
_LOGGER.error(
"Unable to determine ascender of font %s", config[CONF_FILE]
)
glyph_args[codepoint] = GlyphInfo(
len(data),
pt_to_px(font.glyph.metrics.horiAdvance),
font.glyph.bitmap_left,
ascender - font.glyph.bitmap_top,
width,
height,
)
data += glyph_data
rhs = [HexInt(x) for x in data]
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
# Create the glyph table that points to data in the above array. # Create the glyph table that points to data in the above array.
glyph_initializer = [] glyph_initializer = [
for codepoint in codepoints:
glyph_initializer.append(
cg.StructInitializer( cg.StructInitializer(
GlyphData, GlyphData,
( (
"a_char", "a_char",
cg.RawExpression( cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"),
f"(const uint8_t *){cpp_string_escape(codepoint)}"
),
), ),
( (
"data", "data",
cg.RawExpression( cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"),
f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}"
), ),
), ("advance", x.advance),
("advance", glyph_args[codepoint].advance), ("offset_x", x.offset_x),
("offset_x", glyph_args[codepoint].offset_x), ("offset_y", x.offset_y),
("offset_y", glyph_args[codepoint].offset_y), ("width", x.width),
("width", glyph_args[codepoint].width), ("height", x.height),
("height", glyph_args[codepoint].height),
) )
for (x, y) in zip(
glyph_args, list(accumulate([len(x.bitmap_data) for x in glyph_args]))
) )
]
glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer) glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer)
font_height = pt_to_px(base_font.size.height) font_height = pt_to_px(base_font.size.height)
ascender = pt_to_px(base_font.size.ascender) ascender = pt_to_px(base_font.size.ascender)
descender = abs(pt_to_px(base_font.size.descender))
g = glyph_to_glyphinfo("x", base_font, size, bpp)
xheight = g.height if len(g.bitmap_data) > 1 else 0
g = glyph_to_glyphinfo("X", base_font, size, bpp)
capheight = g.height if len(g.bitmap_data) > 1 else 0
if font_height == 0: if font_height == 0:
if not base_font.is_scalable: if not base_font.is_scalable:
font_height = size font_height = size
@@ -610,5 +617,8 @@ async def to_code(config):
len(glyph_initializer), len(glyph_initializer),
ascender, ascender,
font_height, font_height,
descender,
xheight,
capheight,
bpp, bpp,
) )

View File

@@ -45,8 +45,15 @@ void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
*height = this->glyph_data_->height; *height = this->glyph_data_->height;
} }
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp) Font::Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
: baseline_(baseline), height_(height), bpp_(bpp) { uint8_t bpp)
: baseline_(baseline),
height_(height),
descender_(descender),
linegap_(height - baseline - descender),
xheight_(xheight),
capheight_(capheight),
bpp_(bpp) {
glyphs_.reserve(data_nr); glyphs_.reserve(data_nr);
for (int i = 0; i < data_nr; ++i) for (int i = 0; i < data_nr; ++i)
glyphs_.emplace_back(&data[i]); glyphs_.emplace_back(&data[i]);

View File

@@ -50,11 +50,17 @@ class Font
public: public:
/** Construct the font with the given glyphs. /** Construct the font with the given glyphs.
* *
* @param glyphs A vector of glyphs, must be sorted lexicographically. * @param data A vector of glyphs, must be sorted lexicographically.
* @param data_nr The number of glyphs in data.
* @param baseline The y-offset from the top of the text to the baseline. * @param baseline The y-offset from the top of the text to the baseline.
* @param bottom The y-offset from the top of the text to the bottom (i.e. height). * @param height The y-offset from the top of the text to the bottom.
* @param descender The y-offset from the baseline to the lowest stroke in the font (e.g. from letters like g or p).
* @param xheight The height of lowercase letters, usually measured at the "x" glyph.
* @param capheight The height of capital letters, usually measured at the "X" glyph.
* @param bpp The bits per pixel used for this font. Used to read data out of the glyph bitmaps.
*/ */
Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp = 1); Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
uint8_t bpp = 1);
int match_next_glyph(const uint8_t *str, int *match_length); int match_next_glyph(const uint8_t *str, int *match_length);
@@ -65,6 +71,11 @@ class Font
#endif #endif
inline int get_baseline() { return this->baseline_; } inline int get_baseline() { return this->baseline_; }
inline int get_height() { return this->height_; } inline int get_height() { return this->height_; }
inline int get_ascender() { return this->baseline_; }
inline int get_descender() { return this->descender_; }
inline int get_linegap() { return this->linegap_; }
inline int get_xheight() { return this->xheight_; }
inline int get_capheight() { return this->capheight_; }
inline int get_bpp() { return this->bpp_; } inline int get_bpp() { return this->bpp_; }
const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; } const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
@@ -73,6 +84,10 @@ class Font
std::vector<Glyph, RAMAllocator<Glyph>> glyphs_; std::vector<Glyph, RAMAllocator<Glyph>> glyphs_;
int baseline_; int baseline_;
int height_; int height_;
int descender_;
int linegap_;
int xheight_;
int capheight_;
uint8_t bpp_; // bits per pixel uint8_t bpp_; // bits per pixel
}; };

View File

@@ -184,7 +184,9 @@ CONFIG_SCHEMA = cv.All(
{ {
cv.GenerateID(): cv.declare_id(Logger), cv.GenerateID(): cv.declare_id(Logger),
cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int, cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes, cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.All(
cv.validate_bytes, cv.int_range(min=160, max=65535)
),
cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean, cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
cv.SplitDefault( cv.SplitDefault(
CONF_TASK_LOG_BUFFER_SIZE, CONF_TASK_LOG_BUFFER_SIZE,

View File

@@ -24,7 +24,7 @@ static const char *const TAG = "logger";
// - Messages are serialized through main loop for proper console output // - Messages are serialized through main loop for proper console output
// - Fallback to emergency console logging only if ring buffer is full // - Fallback to emergency console logging only if ring buffer is full
// - WITHOUT task log buffer: Only emergency console output, no callbacks // - WITHOUT task log buffer: Only emergency console output, no callbacks
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
if (level > this->level_for(tag)) if (level > this->level_for(tag))
return; return;
@@ -46,8 +46,8 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
bool message_sent = false; bool message_sent = false;
#ifdef USE_ESPHOME_TASK_LOG_BUFFER #ifdef USE_ESPHOME_TASK_LOG_BUFFER
// For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered // For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered
message_sent = this->log_buffer_->send_message_thread_safe(static_cast<uint8_t>(level), tag, message_sent =
static_cast<uint16_t>(line), current_task, format, args); this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), current_task, format, args);
#endif // USE_ESPHOME_TASK_LOG_BUFFER #endif // USE_ESPHOME_TASK_LOG_BUFFER
// Emergency console logging for non-main tasks when ring buffer is full or disabled // Emergency console logging for non-main tasks when ring buffer is full or disabled
@@ -58,7 +58,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
// Maximum size for console log messages (includes null terminator) // Maximum size for console log messages (includes null terminator)
static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144; static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety
int buffer_at = 0; // Initialize buffer position uint16_t buffer_at = 0; // Initialize buffer position
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at, this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
MAX_CONSOLE_LOG_MSG_SIZE); MAX_CONSOLE_LOG_MSG_SIZE);
this->write_msg_(console_buffer); this->write_msg_(console_buffer);
@@ -69,7 +69,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
} }
#else #else
// Implementation for all other platforms // Implementation for all other platforms
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
if (level > this->level_for(tag) || global_recursion_guard_) if (level > this->level_for(tag) || global_recursion_guard_)
return; return;
@@ -85,7 +85,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
#ifdef USE_STORE_LOG_STR_IN_FLASH #ifdef USE_STORE_LOG_STR_IN_FLASH
// Implementation for ESP8266 with flash string support. // Implementation for ESP8266 with flash string support.
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266. // Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
va_list args) { // NOLINT va_list args) { // NOLINT
if (level > this->level_for(tag) || global_recursion_guard_) if (level > this->level_for(tag) || global_recursion_guard_)
return; return;
@@ -122,7 +122,7 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr
} }
#endif // USE_STORE_LOG_STR_IN_FLASH #endif // USE_STORE_LOG_STR_IN_FLASH
inline int Logger::level_for(const char *tag) { inline uint8_t Logger::level_for(const char *tag) {
auto it = this->log_levels_.find(tag); auto it = this->log_levels_.find(tag);
if (it != this->log_levels_.end()) if (it != this->log_levels_.end())
return it->second; return it->second;
@@ -195,13 +195,13 @@ void Logger::loop() {
#endif #endif
void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
void Logger::set_log_level(const std::string &tag, int log_level) { this->log_levels_[tag] = log_level; } void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
UARTSelection Logger::get_uart() const { return this->uart_; } UARTSelection Logger::get_uart() const { return this->uart_; }
#endif #endif
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) { void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback) {
this->log_callback_.add(std::move(callback)); this->log_callback_.add(std::move(callback));
} }
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; } float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
@@ -230,7 +230,7 @@ void Logger::dump_config() {
} }
} }
void Logger::set_log_level(int level) { void Logger::set_log_level(uint8_t level) {
if (level > ESPHOME_LOG_LEVEL) { if (level > ESPHOME_LOG_LEVEL) {
level = ESPHOME_LOG_LEVEL; level = ESPHOME_LOG_LEVEL;
ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]); ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);

View File

@@ -61,7 +61,7 @@ static const char *const LOG_LEVEL_LETTERS[] = {
* *
* Advanced configuration (pin selection, etc) is not supported. * Advanced configuration (pin selection, etc) is not supported.
*/ */
enum UARTSelection { enum UARTSelection : uint8_t {
#ifdef USE_LIBRETINY #ifdef USE_LIBRETINY
UART_SELECTION_DEFAULT = 0, UART_SELECTION_DEFAULT = 0,
UART_SELECTION_UART0, UART_SELECTION_UART0,
@@ -129,10 +129,10 @@ class Logger : public Component {
#endif #endif
/// Set the default log level for this logger. /// Set the default log level for this logger.
void set_log_level(int level); void set_log_level(uint8_t level);
/// Set the log level of the specified tag. /// Set the log level of the specified tag.
void set_log_level(const std::string &tag, int log_level); void set_log_level(const std::string &tag, uint8_t log_level);
int get_log_level() { return this->current_level_; } uint8_t get_log_level() { return this->current_level_; }
// ========== INTERNAL METHODS ========== // ========== INTERNAL METHODS ==========
// (In most use cases you won't need these) // (In most use cases you won't need these)
@@ -140,19 +140,20 @@ class Logger : public Component {
void pre_setup(); void pre_setup();
void dump_config() override; void dump_config() override;
inline int level_for(const char *tag); inline uint8_t level_for(const char *tag);
/// Register a callback that will be called for every log message sent /// Register a callback that will be called for every log message sent
void add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback); void add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback);
// add a listener for log level changes // add a listener for log level changes
void add_listener(std::function<void(int)> &&callback) { this->level_callback_.add(std::move(callback)); } void add_listener(std::function<void(uint8_t)> &&callback) { this->level_callback_.add(std::move(callback)); }
float get_setup_priority() const override; float get_setup_priority() const override;
void log_vprintf_(int level, const char *tag, int line, const char *format, va_list args); // NOLINT void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args); // NOLINT
#ifdef USE_STORE_LOG_STR_IN_FLASH #ifdef USE_STORE_LOG_STR_IN_FLASH
void log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args); // NOLINT void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
va_list args); // NOLINT
#endif #endif
protected: protected:
@@ -160,8 +161,9 @@ class Logger : public Component {
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator // 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) // It's the caller's responsibility to initialize buffer_at (typically to 0)
inline void HOT format_log_to_buffer_with_terminator_(int level, const char *tag, int line, const char *format, inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format,
va_list args, char *buffer, int *buffer_at, int buffer_size) { va_list args, char *buffer, uint16_t *buffer_at,
uint16_t buffer_size) {
#if defined(USE_ESP32) || defined(USE_LIBRETINY) #if defined(USE_ESP32) || defined(USE_LIBRETINY)
this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size); this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size);
#else #else
@@ -180,7 +182,7 @@ class Logger : public Component {
} }
// Helper to format and send a log message to both console and callbacks // Helper to format and send a log message to both console and callbacks
inline void HOT log_message_to_buffer_and_send_(int level, const char *tag, int line, const char *format, inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format,
va_list args) { va_list args) {
// Format to tx_buffer and prepare for output // Format to tx_buffer and prepare for output
this->tx_buffer_at_ = 0; // Initialize buffer position this->tx_buffer_at_ = 0; // Initialize buffer position
@@ -194,11 +196,12 @@ class Logger : public Component {
} }
// Write the body of the log message to the buffer // Write the body of the log message to the buffer
inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, int *buffer_at, int buffer_size) { inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, uint16_t *buffer_at,
uint16_t buffer_size) {
// Calculate available space // Calculate available space
const int available = buffer_size - *buffer_at; if (*buffer_at >= buffer_size)
if (available <= 0)
return; return;
const uint16_t available = buffer_size - *buffer_at;
// Determine copy length (minimum of remaining capacity and string length) // Determine copy length (minimum of remaining capacity and string length)
const size_t copy_len = (length < static_cast<size_t>(available)) ? length : available; const size_t copy_len = (length < static_cast<size_t>(available)) ? length : available;
@@ -211,7 +214,7 @@ class Logger : public Component {
} }
// Format string to explicit buffer with varargs // Format string to explicit buffer with varargs
inline void printf_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format, ...) { inline void printf_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, ...) {
va_list arg; va_list arg;
va_start(arg, format); va_start(arg, format);
this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg); this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg);
@@ -222,41 +225,50 @@ class Logger : public Component {
const char *get_uart_selection_(); const char *get_uart_selection_();
#endif #endif
// Group 4-byte aligned members first
uint32_t baud_rate_; uint32_t baud_rate_;
char *tx_buffer_{nullptr}; char *tx_buffer_{nullptr};
int tx_buffer_at_{0}; #ifdef USE_ARDUINO
int tx_buffer_size_{0}; Stream *hw_serial_{nullptr};
#endif
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
void *main_task_ = nullptr; // Only used for thread name identification
#endif
#ifdef USE_ESP32
// Task-specific recursion guards:
// - Main task uses a dedicated member variable for efficiency
// - Other tasks use pthread TLS with a dynamically created key via pthread_key_create
pthread_key_t log_recursion_key_; // 4 bytes
#endif
#ifdef USE_ESP_IDF
uart_port_t uart_num_; // 4 bytes (enum defaults to int size)
#endif
// Large objects (internally aligned)
std::map<std::string, uint8_t> log_levels_{};
CallbackManager<void(uint8_t, const char *, const char *)> log_callback_{};
CallbackManager<void(uint8_t)> level_callback_{};
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
#endif
// Group smaller types together at the end
uint16_t tx_buffer_at_{0};
uint16_t tx_buffer_size_{0};
uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
UARTSelection uart_{UART_SELECTION_UART0}; UARTSelection uart_{UART_SELECTION_UART0};
#endif #endif
#ifdef USE_LIBRETINY #ifdef USE_LIBRETINY
UARTSelection uart_{UART_SELECTION_DEFAULT}; UARTSelection uart_{UART_SELECTION_DEFAULT};
#endif #endif
#ifdef USE_ARDUINO
Stream *hw_serial_{nullptr};
#endif
#ifdef USE_ESP_IDF
uart_port_t uart_num_;
#endif
std::map<std::string, int> log_levels_{};
CallbackManager<void(int, const char *, const char *)> log_callback_{};
int current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
#endif
#ifdef USE_ESP32 #ifdef USE_ESP32
// Task-specific recursion guards:
// - Main task uses a dedicated member variable for efficiency
// - Other tasks use pthread TLS with a dynamically created key via pthread_key_create
bool main_task_recursion_guard_{false}; bool main_task_recursion_guard_{false};
pthread_key_t log_recursion_key_;
#else #else
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
#endif #endif
CallbackManager<void(int)> level_callback_{};
#if defined(USE_ESP32) || defined(USE_LIBRETINY) #if defined(USE_ESP32) || defined(USE_LIBRETINY)
void *main_task_ = nullptr; // Only used for thread name identification
const char *HOT get_thread_name_() { const char *HOT get_thread_name_() {
TaskHandle_t current_task = xTaskGetCurrentTaskHandle(); TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
if (current_task == main_task_) { if (current_task == main_task_) {
@@ -297,11 +309,10 @@ class Logger : public Component {
} }
#endif #endif
inline void HOT write_header_to_buffer_(int level, const char *tag, int line, const char *thread_name, char *buffer, inline void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name,
int *buffer_at, int buffer_size) { char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
// Format header // Format header
if (level < 0) // uint8_t level is already bounded 0-255, just ensure it's <= 7
level = 0;
if (level > 7) if (level > 7)
level = 7; level = 7;
@@ -320,12 +331,12 @@ class Logger : public Component {
this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]: ", color, letter, tag, line); this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]: ", color, letter, tag, line);
} }
inline void HOT format_body_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format, inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format,
va_list args) { va_list args) {
// Get remaining capacity in the buffer // Get remaining capacity in the buffer
const int remaining = buffer_size - *buffer_at; if (*buffer_at >= buffer_size)
if (remaining <= 0)
return; return;
const uint16_t remaining = buffer_size - *buffer_at;
const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args); const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args);
@@ -334,7 +345,7 @@ class Logger : public Component {
} }
// Update buffer_at with the formatted length (handle truncation) // Update buffer_at with the formatted length (handle truncation)
int formatted_len = (ret >= remaining) ? remaining : ret; uint16_t formatted_len = (ret >= remaining) ? remaining : ret;
*buffer_at += formatted_len; *buffer_at += formatted_len;
// Remove all trailing newlines right after formatting // Remove all trailing newlines right after formatting
@@ -343,18 +354,18 @@ class Logger : public Component {
} }
} }
inline void HOT write_footer_to_buffer_(char *buffer, int *buffer_at, int buffer_size) { inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
static const int RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR); static const uint16_t RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR);
this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size); this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
} }
}; };
extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
class LoggerMessageTrigger : public Trigger<int, const char *, const char *> { class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *> {
public: public:
explicit LoggerMessageTrigger(Logger *parent, int level) { explicit LoggerMessageTrigger(Logger *parent, uint8_t level) {
this->level_ = level; this->level_ = level;
parent->add_on_log_callback([this](int level, const char *tag, const char *message) { parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message) {
if (level <= this->level_) { if (level <= this->level_) {
this->trigger(level, tag, message); this->trigger(level, tag, message);
} }
@@ -362,7 +373,7 @@ class LoggerMessageTrigger : public Trigger<int, const char *, const char *> {
} }
protected: protected:
int level_; uint8_t level_;
}; };
} // namespace logger } // namespace logger

View File

@@ -454,9 +454,13 @@ def container_validator(schema, widget_type: WidgetType):
""" """
def validator(value): def validator(value):
result = schema
if w_sch := widget_type.schema: if w_sch := widget_type.schema:
result = result.extend(w_sch) if isinstance(w_sch, dict):
w_sch = cv.Schema(w_sch)
# order is important here to preserve extras
result = w_sch.extend(schema)
else:
result = schema
ltype = df.TYPE_NONE ltype = df.TYPE_NONE
if value and (layout := value.get(df.CONF_LAYOUT)): if value and (layout := value.get(df.CONF_LAYOUT)):
if not isinstance(layout, dict): if not isinstance(layout, dict):

View File

@@ -3,7 +3,6 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID from esphome.const import CONF_ID
from esphome.core import ID from esphome.core import ID
from esphome.cpp_generator import MockObj
from .defines import ( from .defines import (
CONF_STYLE_DEFINITIONS, CONF_STYLE_DEFINITIONS,
@@ -13,12 +12,13 @@ from .defines import (
literal, literal,
) )
from .helpers import add_lv_use from .helpers import add_lv_use
from .lvcode import LambdaContext, LocalVariable, lv, lv_assign, lv_variable from .lvcode import LambdaContext, LocalVariable, lv
from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, STYLE_REMAP from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, STYLE_REMAP
from .types import ObjUpdateAction, lv_lambda_t, lv_obj_t, lv_obj_t_ptr, lv_style_t from .types import ObjUpdateAction, lv_obj_t, lv_style_t
from .widgets import ( from .widgets import (
Widget, Widget,
add_widgets, add_widgets,
collect_parts,
set_obj_properties, set_obj_properties,
theme_widget_map, theme_widget_map,
wait_for_widgets, wait_for_widgets,
@@ -37,12 +37,18 @@ async def style_set(svar, style):
lv.call(f"style_set_{remapped_prop}", svar, literal(value)) lv.call(f"style_set_{remapped_prop}", svar, literal(value))
async def create_style(style, id_name):
style_id = ID(id_name, True, lv_style_t)
svar = cg.new_Pvariable(style_id)
lv.style_init(svar)
await style_set(svar, style)
return svar
async def styles_to_code(config): async def styles_to_code(config):
"""Convert styles to C__ code.""" """Convert styles to C__ code."""
for style in config.get(CONF_STYLE_DEFINITIONS, ()): for style in config.get(CONF_STYLE_DEFINITIONS, ()):
svar = cg.new_Pvariable(style[CONF_ID]) await create_style(style, style[CONF_ID].id)
lv.style_init(svar)
await style_set(svar, style)
@automation.register_action( @automation.register_action(
@@ -68,16 +74,18 @@ async def theme_to_code(config):
if theme := config.get(CONF_THEME): if theme := config.get(CONF_THEME):
add_lv_use(CONF_THEME) add_lv_use(CONF_THEME)
for w_name, style in theme.items(): for w_name, style in theme.items():
if not isinstance(style, dict): # Work around Python 3.10 bug with nested async comprehensions
continue # With Python 3.11 this could be simplified
styles = {}
lname = "lv_theme_apply_" + w_name for part, states in collect_parts(style).items():
apply = lv_variable(lv_lambda_t, lname) styles[part] = {
theme_widget_map[w_name] = apply state: await create_style(
ow = Widget.create("obj", MockObj(ID("obj")), obj_spec) props,
async with LambdaContext([(lv_obj_t_ptr, "obj")], where=w_name) as context: "_lv_theme_style_" + w_name + "_" + part + "_" + state,
await set_obj_properties(ow, style) )
lv_assign(apply, await context.get_lambda()) for state, props in states.items()
}
theme_widget_map[w_name] = styles
async def add_top_layer(lv_component, config): async def add_top_layer(lv_component, config):

View File

@@ -6,7 +6,7 @@ from esphome.config_validation import Invalid
from esphome.const import CONF_DEFAULT, CONF_GROUP, CONF_ID, CONF_STATE, CONF_TYPE from esphome.const import CONF_DEFAULT, CONF_GROUP, CONF_ID, CONF_STATE, CONF_TYPE
from esphome.core import ID, TimePeriod from esphome.core import ID, TimePeriod
from esphome.coroutine import FakeAwaitable from esphome.coroutine import FakeAwaitable
from esphome.cpp_generator import CallExpression, MockObj from esphome.cpp_generator import MockObj
from ..defines import ( from ..defines import (
CONF_FLEX_ALIGN_CROSS, CONF_FLEX_ALIGN_CROSS,
@@ -453,7 +453,17 @@ async def widget_to_code(w_cnfig, w_type: WidgetType, parent):
w = Widget.create(wid, var, spec, w_cnfig) w = Widget.create(wid, var, spec, w_cnfig)
if theme := theme_widget_map.get(w_type): if theme := theme_widget_map.get(w_type):
lv_add(CallExpression(theme, w.obj)) for part, states in theme.items():
part = "LV_PART_" + part.upper()
for state, style in states.items():
state = "LV_STATE_" + state.upper()
if state == "LV_STATE_DEFAULT":
lv_state = literal(part)
elif part == "LV_PART_MAIN":
lv_state = literal(state)
else:
lv_state = join_enums((state, part))
lv.obj_add_style(w.obj, style, lv_state)
await set_obj_properties(w, w_cnfig) await set_obj_properties(w, w_cnfig)
await add_widgets(w, w_cnfig) await add_widgets(w, w_cnfig)
await spec.to_code(w, w_cnfig) await spec.to_code(w, w_cnfig)

View File

@@ -1,8 +1,15 @@
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_MODE, CONF_VALUE from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_MODE, CONF_VALUE
from ..defines import BAR_MODES, CONF_ANIMATED, CONF_INDICATOR, CONF_MAIN, literal from ..defines import (
from ..lv_validation import animated, get_start_value, lv_float BAR_MODES,
CONF_ANIMATED,
CONF_INDICATOR,
CONF_MAIN,
CONF_START_VALUE,
literal,
)
from ..lv_validation import animated, lv_int
from ..lvcode import lv from ..lvcode import lv
from ..types import LvNumber, NumberType from ..types import LvNumber, NumberType
from . import Widget from . import Widget
@@ -10,22 +17,30 @@ from . import Widget
# Note this file cannot be called "bar.py" because that name is disallowed. # Note this file cannot be called "bar.py" because that name is disallowed.
CONF_BAR = "bar" CONF_BAR = "bar"
BAR_MODIFY_SCHEMA = cv.Schema(
{
cv.Optional(CONF_VALUE): lv_float, def validate_bar(config):
cv.Optional(CONF_ANIMATED, default=True): animated, if config.get(CONF_MODE) != "LV_BAR_MODE_RANGE" and CONF_START_VALUE in config:
} raise cv.Invalid(
) f"{CONF_START_VALUE} is only allowed when {CONF_MODE} is set to 'RANGE'"
)
if (CONF_MIN_VALUE in config) != (CONF_MAX_VALUE in config):
raise cv.Invalid(
f"If either {CONF_MIN_VALUE} or {CONF_MAX_VALUE} is set, both must be set"
)
return config
BAR_SCHEMA = cv.Schema( BAR_SCHEMA = cv.Schema(
{ {
cv.Optional(CONF_VALUE): lv_float, cv.Optional(CONF_VALUE): lv_int,
cv.Optional(CONF_MIN_VALUE, default=0): cv.int_, cv.Optional(CONF_START_VALUE): lv_int,
cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, cv.Optional(CONF_MIN_VALUE): lv_int,
cv.Optional(CONF_MODE, default="NORMAL"): BAR_MODES.one_of, cv.Optional(CONF_MAX_VALUE): lv_int,
cv.Optional(CONF_MODE): BAR_MODES.one_of,
cv.Optional(CONF_ANIMATED, default=True): animated, cv.Optional(CONF_ANIMATED, default=True): animated,
} }
) ).add_extra(validate_bar)
class BarType(NumberType): class BarType(NumberType):
@@ -35,17 +50,23 @@ class BarType(NumberType):
LvNumber("lv_bar_t"), LvNumber("lv_bar_t"),
parts=(CONF_MAIN, CONF_INDICATOR), parts=(CONF_MAIN, CONF_INDICATOR),
schema=BAR_SCHEMA, schema=BAR_SCHEMA,
modify_schema=BAR_MODIFY_SCHEMA,
) )
async def to_code(self, w: Widget, config): async def to_code(self, w: Widget, config):
var = w.obj var = w.obj
if mode := config.get(CONF_MODE):
lv.bar_set_mode(var, literal(mode))
is_animated = literal(config[CONF_ANIMATED])
if CONF_MIN_VALUE in config: if CONF_MIN_VALUE in config:
lv.bar_set_range(var, config[CONF_MIN_VALUE], config[CONF_MAX_VALUE]) lv.bar_set_range(
lv.bar_set_mode(var, literal(config[CONF_MODE])) var,
value = await get_start_value(config) await lv_int.process(config[CONF_MIN_VALUE]),
if value is not None: await lv_int.process(config[CONF_MAX_VALUE]),
lv.bar_set_value(var, value, literal(config[CONF_ANIMATED])) )
if value := await lv_int.process(config.get(CONF_VALUE)):
lv.bar_set_value(var, value, is_animated)
if start_value := await lv_int.process(config.get(CONF_START_VALUE)):
lv.bar_set_start_value(var, start_value, is_animated)
@property @property
def animated(self): def animated(self):

View File

@@ -963,13 +963,12 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool
uint16_t ret = 0; uint16_t ret = 0;
uint8_t c = 0; uint8_t c = 0;
uint8_t nr_of_ff_bytes = 0; uint8_t nr_of_ff_bytes = 0;
uint64_t start;
bool exit_flag = false; bool exit_flag = false;
bool ff_flag = false; bool ff_flag = false;
start = App.get_loop_component_start_time(); const uint32_t start = millis();
while ((timeout == 0 && this->available()) || App.get_loop_component_start_time() - start <= timeout) { while ((timeout == 0 && this->available()) || millis() - start <= timeout) {
if (!this->available()) { if (!this->available()) {
App.feed_wdt(); App.feed_wdt();
delay(1); delay(1);
@@ -1038,7 +1037,7 @@ void Nextion::add_no_result_to_queue_(const std::string &variable_name) {
nextion_queue->component = new nextion::NextionComponentBase; nextion_queue->component = new nextion::NextionComponentBase;
nextion_queue->component->set_variable_name(variable_name); nextion_queue->component->set_variable_name(variable_name);
nextion_queue->queue_time = App.get_loop_component_start_time(); nextion_queue->queue_time = millis();
this->nextion_queue_.push_back(nextion_queue); this->nextion_queue_.push_back(nextion_queue);

View File

@@ -46,7 +46,7 @@ def set_sdkconfig_options(config):
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", config[CONF_PAN_ID]) add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", config[CONF_PAN_ID])
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", config[CONF_CHANNEL]) add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", config[CONF_CHANNEL])
add_idf_sdkconfig_option( add_idf_sdkconfig_option(
"CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}" "CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}".lower()
) )
if network_name := config.get(CONF_NETWORK_NAME): if network_name := config.get(CONF_NETWORK_NAME):
@@ -54,14 +54,14 @@ def set_sdkconfig_options(config):
if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None: if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None:
add_idf_sdkconfig_option( add_idf_sdkconfig_option(
"CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}" "CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}".lower()
) )
if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None: if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None:
add_idf_sdkconfig_option( add_idf_sdkconfig_option(
"CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix:X}" "CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix}".lower()
) )
if (pskc := config.get(CONF_PSKC)) is not None: if (pskc := config.get(CONF_PSKC)) is not None:
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}") add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}".lower())
if CONF_FORCE_DATASET in config: if CONF_FORCE_DATASET in config:
if config[CONF_FORCE_DATASET]: if config[CONF_FORCE_DATASET]:
@@ -98,7 +98,7 @@ _CONNECTION_SCHEMA = cv.Schema(
cv.Optional(CONF_EXT_PAN_ID): cv.hex_int, cv.Optional(CONF_EXT_PAN_ID): cv.hex_int,
cv.Optional(CONF_NETWORK_NAME): cv.string_strict, cv.Optional(CONF_NETWORK_NAME): cv.string_strict,
cv.Optional(CONF_PSKC): cv.hex_int, cv.Optional(CONF_PSKC): cv.hex_int,
cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.hex_int, cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.ipv6network,
} }
) )

View File

@@ -137,7 +137,7 @@ void OpenThreadSrpComponent::setup() {
// Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this // Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this
// component // component
this->mdns_services_ = this->mdns_->get_services(); this->mdns_services_ = this->mdns_->get_services();
ESP_LOGW(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size()); ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size());
for (const auto &service : this->mdns_services_) { for (const auto &service : this->mdns_services_) {
otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance); otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
if (!entry) { if (!entry) {
@@ -185,11 +185,11 @@ void OpenThreadSrpComponent::setup() {
if (error != OT_ERROR_NONE) { if (error != OT_ERROR_NONE) {
ESP_LOGW(TAG, "Failed to add service: %s", otThreadErrorToString(error)); ESP_LOGW(TAG, "Failed to add service: %s", otThreadErrorToString(error));
} }
ESP_LOGW(TAG, "Added service: %s", full_service.c_str()); ESP_LOGD(TAG, "Added service: %s", full_service.c_str());
} }
otSrpClientEnableAutoStartMode(instance, srp_start_callback, nullptr); otSrpClientEnableAutoStartMode(instance, srp_start_callback, nullptr);
ESP_LOGW(TAG, "Finished SRP setup"); ESP_LOGD(TAG, "Finished SRP setup");
} }
void *OpenThreadSrpComponent::pool_alloc_(size_t size) { void *OpenThreadSrpComponent::pool_alloc_(size_t size) {

View File

@@ -1,5 +1,6 @@
# Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9 # Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9
import binascii import binascii
import ipaddress
from esphome.const import CONF_CHANNEL from esphome.const import CONF_CHANNEL
@@ -37,6 +38,12 @@ def parse_tlv(tlv) -> dict:
if tag in TLV_TYPES: if tag in TLV_TYPES:
if tag == 3: if tag == 3:
output[TLV_TYPES[tag]] = val.decode("utf-8") output[TLV_TYPES[tag]] = val.decode("utf-8")
elif tag == 7:
mesh_local_prefix = binascii.hexlify(val).decode("utf-8")
mesh_local_prefix_str = f"{mesh_local_prefix}0000000000000000"
ipv6_bytes = bytes.fromhex(mesh_local_prefix_str)
ipv6_address = ipaddress.IPv6Address(ipv6_bytes)
output[TLV_TYPES[tag]] = f"{ipv6_address}/64"
else: else:
output[TLV_TYPES[tag]] = int.from_bytes(val) output[TLV_TYPES[tag]] = int.from_bytes(val)
return output return output

View File

@@ -6,7 +6,7 @@ from esphome.components.esp32 import (
only_on_variant, only_on_variant,
) )
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID from esphome.const import CONF_DEVICES, CONF_ID
from esphome.cpp_types import Component from esphome.cpp_types import Component
AUTO_LOAD = ["bytebuffer"] AUTO_LOAD = ["bytebuffer"]
@@ -16,7 +16,6 @@ usb_host_ns = cg.esphome_ns.namespace("usb_host")
USBHost = usb_host_ns.class_("USBHost", Component) USBHost = usb_host_ns.class_("USBHost", Component)
USBClient = usb_host_ns.class_("USBClient", Component) USBClient = usb_host_ns.class_("USBClient", Component)
CONF_DEVICES = "devices"
CONF_VID = "vid" CONF_VID = "vid"
CONF_PID = "pid" CONF_PID = "pid"
CONF_ENABLE_HUBS = "enable_hubs" CONF_ENABLE_HUBS = "enable_hubs"

View File

@@ -3,7 +3,15 @@
from contextlib import contextmanager from contextlib import contextmanager
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from ipaddress import AddressValueError, IPv4Address, ip_address from ipaddress import (
AddressValueError,
IPv4Address,
IPv4Network,
IPv6Address,
IPv6Network,
ip_address,
ip_network,
)
import logging import logging
import os import os
import re import re
@@ -1176,6 +1184,14 @@ def ipv4address(value):
return address return address
def ipv6address(value):
try:
address = IPv6Address(value)
except AddressValueError as exc:
raise Invalid(f"{value} is not a valid IPv6 address") from exc
return address
def ipv4address_multi_broadcast(value): def ipv4address_multi_broadcast(value):
address = ipv4address(value) address = ipv4address(value)
if not (address.is_multicast or (address == IPv4Address("255.255.255.255"))): if not (address.is_multicast or (address == IPv4Address("255.255.255.255"))):
@@ -1193,6 +1209,33 @@ def ipaddress(value):
return address return address
def ipv4network(value):
"""Validate that the value is a valid IPv4 network."""
try:
network = IPv4Network(value, strict=False)
except ValueError as exc:
raise Invalid(f"{value} is not a valid IPv4 network") from exc
return network
def ipv6network(value):
"""Validate that the value is a valid IPv6 network."""
try:
network = IPv6Network(value, strict=False)
except ValueError as exc:
raise Invalid(f"{value} is not a valid IPv6 network") from exc
return network
def ipnetwork(value):
"""Validate that the value is a valid IP network."""
try:
network = ip_network(value, strict=False)
except ValueError as exc:
raise Invalid(f"{value} is not a valid IP network") from exc
return network
def _valid_topic(value): def _valid_topic(value):
"""Validate that this is a valid topic name/filter.""" """Validate that this is a valid topic name/filter."""
if value is None: # Used to disable publishing and subscribing if value is None: # Used to disable publishing and subscribing

View File

@@ -217,6 +217,7 @@ CONF_DEST = "dest"
CONF_DEVICE = "device" CONF_DEVICE = "device"
CONF_DEVICE_CLASS = "device_class" CONF_DEVICE_CLASS = "device_class"
CONF_DEVICE_FACTOR = "device_factor" CONF_DEVICE_FACTOR = "device_factor"
CONF_DEVICES = "devices"
CONF_DIELECTRIC_CONSTANT = "dielectric_constant" CONF_DIELECTRIC_CONSTANT = "dielectric_constant"
CONF_DIMENSIONS = "dimensions" CONF_DIMENSIONS = "dimensions"
CONF_DIO_PIN = "dio_pin" CONF_DIO_PIN = "dio_pin"

View File

@@ -257,6 +257,17 @@ void Application::teardown_components(uint32_t timeout_ms) {
} }
void Application::calculate_looping_components_() { void Application::calculate_looping_components_() {
// Count total components that need looping
size_t total_looping = 0;
for (auto *obj : this->components_) {
if (obj->has_overridden_loop()) {
total_looping++;
}
}
// Pre-reserve vector to avoid reallocations
this->looping_components_.reserve(total_looping);
// First add all active components // First add all active components
for (auto *obj : this->components_) { for (auto *obj : this->components_) {
if (obj->has_overridden_loop() && if (obj->has_overridden_loop() &&

View File

@@ -29,7 +29,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const char *form
if (log == nullptr) if (log == nullptr)
return; return;
log->log_vprintf_(level, tag, line, format, args); log->log_vprintf_(static_cast<uint8_t>(level), tag, line, format, args);
#endif #endif
} }
@@ -41,7 +41,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const __FlashStr
if (log == nullptr) if (log == nullptr)
return; return;
log->log_vprintf_(level, tag, line, format, args); log->log_vprintf_(static_cast<uint8_t>(level), tag, line, format, args);
#endif #endif
} }
#endif #endif

View File

@@ -319,13 +319,17 @@ bool HOT Scheduler::cancel_item_(Component *component, const std::string &name,
return ret; return ret;
} }
uint64_t Scheduler::millis_() { uint64_t Scheduler::millis_() {
// Get the current 32-bit millis value
const uint32_t now = millis(); const uint32_t now = millis();
// Check for rollover by comparing with last value
if (now < this->last_millis_) { if (now < this->last_millis_) {
// Detected rollover (happens every ~49.7 days)
this->millis_major_++; this->millis_major_++;
ESP_LOGD(TAG, "Incrementing scheduler major at %" PRIu64 "ms", ESP_LOGD(TAG, "Incrementing scheduler major at %" PRIu64 "ms",
now + (static_cast<uint64_t>(this->millis_major_) << 32)); now + (static_cast<uint64_t>(this->millis_major_) << 32));
} }
this->last_millis_ = now; this->last_millis_ = now;
// Combine major (high 32 bits) and now (low 32 bits) into 64-bit time
return now + (static_cast<uint64_t>(this->millis_major_) << 32); return now + (static_cast<uint64_t>(this->millis_major_) << 32);
} }

View File

@@ -29,12 +29,16 @@ class Scheduler {
protected: protected:
struct SchedulerItem { struct SchedulerItem {
// Ordered by size to minimize padding
Component *component; Component *component;
std::string name;
enum Type { TIMEOUT, INTERVAL } type;
uint32_t interval; uint32_t interval;
// 64-bit time to handle millis() rollover. The scheduler combines the 32-bit millis()
// with a 16-bit rollover counter to create a 64-bit time that won't roll over for
// billions of years. This ensures correct scheduling even when devices run for months.
uint64_t next_execution_; uint64_t next_execution_;
std::string name;
std::function<void()> callback; std::function<void()> callback;
enum Type : uint8_t { TIMEOUT, INTERVAL } type;
bool remove; bool remove;
static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b); static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);

View File

@@ -5,7 +5,7 @@ import fnmatch
import functools import functools
import inspect import inspect
from io import BytesIO, TextIOBase, TextIOWrapper from io import BytesIO, TextIOBase, TextIOWrapper
from ipaddress import _BaseAddress from ipaddress import _BaseAddress, _BaseNetwork
import logging import logging
import math import math
import os import os
@@ -621,6 +621,7 @@ ESPHomeDumper.add_multi_representer(str, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(int, ESPHomeDumper.represent_int) ESPHomeDumper.add_multi_representer(int, ESPHomeDumper.represent_int)
ESPHomeDumper.add_multi_representer(float, ESPHomeDumper.represent_float) ESPHomeDumper.add_multi_representer(float, ESPHomeDumper.represent_float)
ESPHomeDumper.add_multi_representer(_BaseAddress, ESPHomeDumper.represent_stringify) ESPHomeDumper.add_multi_representer(_BaseAddress, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(_BaseNetwork, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(MACAddress, ESPHomeDumper.represent_stringify) ESPHomeDumper.add_multi_representer(MACAddress, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(TimePeriod, ESPHomeDumper.represent_stringify) ESPHomeDumper.add_multi_representer(TimePeriod, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(Lambda, ESPHomeDumper.represent_lambda) ESPHomeDumper.add_multi_representer(Lambda, ESPHomeDumper.represent_lambda)

View File

@@ -728,12 +728,15 @@ lvgl:
value: 30 value: 30
max_value: 100 max_value: 100
min_value: 10 min_value: 10
start_value: 20
mode: range mode: range
on_click: on_click:
then: then:
- lvgl.bar.update: - lvgl.bar.update:
id: bar_id id: bar_id
value: !lambda return (int)((float)rand() / RAND_MAX * 100); value: !lambda return (int)((float)rand() / RAND_MAX * 100);
start_value: !lambda return (int)((float)rand() / RAND_MAX * 100);
mode: symmetrical
- logger.log: - logger.log:
format: "bar value %f" format: "bar value %f"
args: [x] args: [x]

View File

@@ -8,4 +8,6 @@ openthread:
pan_id: 0x8f28 pan_id: 0x8f28
ext_pan_id: 0xd63e8e3e495ebbc3 ext_pan_id: 0xd63e8e3e495ebbc3
pskc: 0xc23a76e98f1a6483639b1ac1271e2e27 pskc: 0xc23a76e98f1a6483639b1ac1271e2e27
mesh_local_prefix: fd53:145f:ed22:ad81::/64
force_dataset: true force_dataset: true