1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-21 04:32:23 +01:00

Merge remote-tracking branch 'upstream/logger_disable_loop' into integration

This commit is contained in:
J. Nick Koston
2025-06-23 09:03:16 +02:00
22 changed files with 284 additions and 148 deletions

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: cg.StructInitializer(
glyph_initializer.append( GlyphData,
cg.StructInitializer( (
GlyphData, "a_char",
( cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"),
"a_char", ),
cg.RawExpression( (
f"(const uint8_t *){cpp_string_escape(codepoint)}" "data",
), cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"),
), ),
( ("advance", x.advance),
"data", ("offset_x", x.offset_x),
cg.RawExpression( ("offset_y", x.offset_y),
f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}" ("width", x.width),
), ("height", x.height),
),
("advance", glyph_args[codepoint].advance),
("offset_x", glyph_args[codepoint].offset_x),
("offset_y", glyph_args[codepoint].offset_y),
("width", glyph_args[codepoint].width),
("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

@@ -46,8 +46,8 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
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);
if (message_sent) { if (message_sent) {
// Enable logger loop to process the buffered message // Enable logger loop to process the buffered message
// This is safe to call from any context including ISRs // This is safe to call from any context including ISRs
@@ -151,7 +151,7 @@ void Logger::init_log_buffer(size_t total_buffer_size) {
} }
#endif #endif
#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESPHOME_TASK_LOG_BUFFER) #if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32)
void Logger::loop() { void Logger::loop() {
#if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO) #if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO)
if (this->uart_ == UART_SELECTION_USB_CDC) { if (this->uart_ == UART_SELECTION_USB_CDC) {

View File

@@ -107,7 +107,7 @@ class Logger : public Component {
#ifdef USE_ESPHOME_TASK_LOG_BUFFER #ifdef USE_ESPHOME_TASK_LOG_BUFFER
void init_log_buffer(size_t total_buffer_size); void init_log_buffer(size_t total_buffer_size);
#endif #endif
#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESPHOME_TASK_LOG_BUFFER) #if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32)
void loop() override; void loop() override;
#endif #endif
/// Manually set the baud rate for serial, set to 0 to disable. /// Manually set the baud rate for serial, set to 0 to disable.
@@ -359,7 +359,7 @@ class Logger : public Component {
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);
} }
#ifdef USE_ESPHOME_TASK_LOG_BUFFER #ifdef USE_ESP32
// Disable loop when task buffer is empty (with USB CDC check) // Disable loop when task buffer is empty (with USB CDC check)
inline void disable_loop_when_buffer_empty_() { inline void disable_loop_when_buffer_empty_() {
// Thread safety note: This is safe even if another task calls enable_loop_soon_any_context() // Thread safety note: This is safe even if another task calls enable_loop_soon_any_context()

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

@@ -17,7 +17,6 @@
// logger // logger
#define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_VERY_VERBOSE #define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define USE_ESPHOME_TASK_LOG_BUFFER
// Feature flags // Feature flags
#define USE_ALARM_CONTROL_PANEL #define USE_ALARM_CONTROL_PANEL
@@ -131,6 +130,8 @@
// ESP32-specific feature flags // ESP32-specific feature flags
#ifdef USE_ESP32 #ifdef USE_ESP32
#define USE_ESPHOME_TASK_LOG_BUFFER
#define USE_BLUETOOTH_PROXY #define USE_BLUETOOTH_PROXY
#define USE_CAPTIVE_PORTAL #define USE_CAPTIVE_PORTAL
#define USE_ESP32_BLE #define USE_ESP32_BLE

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