mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 12:27:46 +00:00
Merge branch 'dev' into multi_device
This commit is contained in:
@@ -323,6 +323,7 @@ esphome/components/one_wire/* @ssieb
|
||||
esphome/components/online_image/* @clydebarrow @guillempages
|
||||
esphome/components/opentherm/* @olegtarasov
|
||||
esphome/components/openthread/* @mrene
|
||||
esphome/components/opt3001/* @ccutrer
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/packet_transport/* @clydebarrow
|
||||
|
@@ -28,6 +28,12 @@
|
||||
namespace esphome {
|
||||
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 int ESP32_CAMERA_STOP_STREAM = 5000;
|
||||
|
||||
@@ -109,33 +115,38 @@ void APIConnection::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
// Check if socket has data ready before attempting to read
|
||||
if (this->helper_->is_socket_ready()) {
|
||||
ReadPacketBuffer buffer;
|
||||
err = this->helper_->read_packet(&buffer);
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
// pass
|
||||
} else if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
|
||||
ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str());
|
||||
} else if (err == APIError::CONNECTION_CLOSED) {
|
||||
ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(),
|
||||
api_error_to_str(err), errno);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
this->last_traffic_ = App.get_loop_component_start_time();
|
||||
// read a packet
|
||||
if (buffer.data_len > 0) {
|
||||
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
|
||||
} else {
|
||||
this->read_message(0, buffer.type, nullptr);
|
||||
}
|
||||
if (this->remove_)
|
||||
// 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;
|
||||
err = this->helper_->read_packet(&buffer);
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
// No more data available
|
||||
break;
|
||||
} else if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
|
||||
ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str());
|
||||
} else if (err == APIError::CONNECTION_CLOSED) {
|
||||
ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(),
|
||||
api_error_to_str(err), errno);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
this->last_traffic_ = now;
|
||||
// read a packet
|
||||
if (buffer.data_len > 0) {
|
||||
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
|
||||
} else {
|
||||
this->read_message(0, buffer.type, nullptr);
|
||||
}
|
||||
if (this->remove_)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +163,6 @@ void APIConnection::loop() {
|
||||
|
||||
static uint8_t max_ping_retries = 60;
|
||||
static uint16_t ping_retry_interval = 1000;
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (this->sent_ping_) {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) {
|
||||
|
@@ -274,12 +274,21 @@ APIError APINoiseFrameHelper::init() {
|
||||
}
|
||||
/// Run through handshake messages (if in that phase)
|
||||
APIError APINoiseFrameHelper::loop() {
|
||||
APIError err = state_action_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
// 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_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->tx_buf_.empty()) {
|
||||
err = try_send_tx_buf_();
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
|
@@ -522,6 +522,7 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData
|
||||
}
|
||||
|
||||
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++)
|
||||
this->address_[i] = scan_result.bda[i];
|
||||
this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type);
|
||||
|
@@ -85,6 +85,9 @@ class ESPBTDevice {
|
||||
|
||||
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;
|
||||
|
||||
optional<ESPBLEiBeacon> get_ibeacon() const {
|
||||
@@ -111,6 +114,7 @@ class ESPBTDevice {
|
||||
std::vector<ESPBTUUID> service_uuids_{};
|
||||
std::vector<ServiceData> manufacturer_datas_{};
|
||||
std::vector<ServiceData> service_datas_{};
|
||||
const BLEScanResult *scan_result_{nullptr};
|
||||
};
|
||||
|
||||
class ESP32BLETracker;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
from collections.abc import MutableMapping
|
||||
import functools
|
||||
import hashlib
|
||||
from itertools import accumulate
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -468,8 +469,9 @@ class EFont:
|
||||
|
||||
|
||||
class GlyphInfo:
|
||||
def __init__(self, data_len, advance, offset_x, offset_y, width, height):
|
||||
self.data_len = data_len
|
||||
def __init__(self, glyph, data, advance, offset_x, offset_y, width, height):
|
||||
self.glyph = glyph
|
||||
self.bitmap_data = data
|
||||
self.advance = advance
|
||||
self.offset_x = offset_x
|
||||
self.offset_y = offset_y
|
||||
@@ -477,6 +479,62 @@ class GlyphInfo:
|
||||
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):
|
||||
"""
|
||||
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.sort(key=functools.cmp_to_key(glyph_comparator))
|
||||
glyph_args = {}
|
||||
data = []
|
||||
bpp = config[CONF_BPP]
|
||||
scale = 256 // (1 << bpp)
|
||||
size = config[CONF_SIZE]
|
||||
# create the data array for all glyphs
|
||||
for codepoint in codepoints:
|
||||
font = point_font_map[codepoint]
|
||||
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(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]
|
||||
glyph_args = [
|
||||
glyph_to_glyphinfo(x, point_font_map[x], size, bpp) for x in codepoints
|
||||
]
|
||||
rhs = [HexInt(x) for x in flatten([x.bitmap_data for x in glyph_args])]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
|
||||
# Create the glyph table that points to data in the above array.
|
||||
glyph_initializer = []
|
||||
for codepoint in codepoints:
|
||||
glyph_initializer.append(
|
||||
cg.StructInitializer(
|
||||
GlyphData,
|
||||
(
|
||||
"a_char",
|
||||
cg.RawExpression(
|
||||
f"(const uint8_t *){cpp_string_escape(codepoint)}"
|
||||
),
|
||||
),
|
||||
(
|
||||
"data",
|
||||
cg.RawExpression(
|
||||
f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}"
|
||||
),
|
||||
),
|
||||
("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),
|
||||
)
|
||||
glyph_initializer = [
|
||||
cg.StructInitializer(
|
||||
GlyphData,
|
||||
(
|
||||
"a_char",
|
||||
cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"),
|
||||
),
|
||||
(
|
||||
"data",
|
||||
cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"),
|
||||
),
|
||||
("advance", x.advance),
|
||||
("offset_x", x.offset_x),
|
||||
("offset_y", x.offset_y),
|
||||
("width", x.width),
|
||||
("height", x.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)
|
||||
|
||||
font_height = pt_to_px(base_font.size.height)
|
||||
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 not base_font.is_scalable:
|
||||
font_height = size
|
||||
@@ -610,5 +617,8 @@ async def to_code(config):
|
||||
len(glyph_initializer),
|
||||
ascender,
|
||||
font_height,
|
||||
descender,
|
||||
xheight,
|
||||
capheight,
|
||||
bpp,
|
||||
)
|
||||
|
@@ -45,8 +45,15 @@ void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
|
||||
*height = this->glyph_data_->height;
|
||||
}
|
||||
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp)
|
||||
: baseline_(baseline), height_(height), bpp_(bpp) {
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
|
||||
uint8_t bpp)
|
||||
: baseline_(baseline),
|
||||
height_(height),
|
||||
descender_(descender),
|
||||
linegap_(height - baseline - descender),
|
||||
xheight_(xheight),
|
||||
capheight_(capheight),
|
||||
bpp_(bpp) {
|
||||
glyphs_.reserve(data_nr);
|
||||
for (int i = 0; i < data_nr; ++i)
|
||||
glyphs_.emplace_back(&data[i]);
|
||||
|
@@ -50,11 +50,17 @@ class Font
|
||||
public:
|
||||
/** 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 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);
|
||||
|
||||
@@ -65,6 +71,11 @@ class Font
|
||||
#endif
|
||||
inline int get_baseline() { return this->baseline_; }
|
||||
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_; }
|
||||
|
||||
const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
|
||||
@@ -73,6 +84,10 @@ class Font
|
||||
std::vector<Glyph, RAMAllocator<Glyph>> glyphs_;
|
||||
int baseline_;
|
||||
int height_;
|
||||
int descender_;
|
||||
int linegap_;
|
||||
int xheight_;
|
||||
int capheight_;
|
||||
uint8_t bpp_; // bits per pixel
|
||||
};
|
||||
|
||||
|
@@ -184,7 +184,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Logger),
|
||||
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.SplitDefault(
|
||||
CONF_TASK_LOG_BUFFER_SIZE,
|
||||
|
@@ -24,7 +24,7 @@ static const char *const TAG = "logger";
|
||||
// - Messages are serialized through main loop for proper console output
|
||||
// - Fallback to emergency console logging only if ring buffer is full
|
||||
// - 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))
|
||||
return;
|
||||
|
||||
@@ -46,8 +46,8 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
|
||||
bool message_sent = false;
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
// 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,
|
||||
static_cast<uint16_t>(line), current_task, format, args);
|
||||
message_sent =
|
||||
this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), current_task, format, args);
|
||||
#endif // USE_ESPHOME_TASK_LOG_BUFFER
|
||||
|
||||
// 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)
|
||||
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
|
||||
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,
|
||||
MAX_CONSOLE_LOG_MSG_SIZE);
|
||||
this->write_msg_(console_buffer);
|
||||
@@ -69,7 +69,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
|
||||
}
|
||||
#else
|
||||
// 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_)
|
||||
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
|
||||
// Implementation for ESP8266 with flash string support.
|
||||
// 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
|
||||
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||
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
|
||||
|
||||
inline int Logger::level_for(const char *tag) {
|
||||
inline uint8_t Logger::level_for(const char *tag) {
|
||||
auto it = this->log_levels_.find(tag);
|
||||
if (it != this->log_levels_.end())
|
||||
return it->second;
|
||||
@@ -195,13 +195,13 @@ void Logger::loop() {
|
||||
#endif
|
||||
|
||||
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)
|
||||
UARTSelection Logger::get_uart() const { return this->uart_; }
|
||||
#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));
|
||||
}
|
||||
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) {
|
||||
level = ESPHOME_LOG_LEVEL;
|
||||
ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
|
||||
|
@@ -61,7 +61,7 @@ static const char *const LOG_LEVEL_LETTERS[] = {
|
||||
*
|
||||
* Advanced configuration (pin selection, etc) is not supported.
|
||||
*/
|
||||
enum UARTSelection {
|
||||
enum UARTSelection : uint8_t {
|
||||
#ifdef USE_LIBRETINY
|
||||
UART_SELECTION_DEFAULT = 0,
|
||||
UART_SELECTION_UART0,
|
||||
@@ -129,10 +129,10 @@ class Logger : public Component {
|
||||
#endif
|
||||
|
||||
/// 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.
|
||||
void set_log_level(const std::string &tag, int log_level);
|
||||
int get_log_level() { return this->current_level_; }
|
||||
void set_log_level(const std::string &tag, uint8_t log_level);
|
||||
uint8_t get_log_level() { return this->current_level_; }
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
@@ -140,19 +140,20 @@ class Logger : public Component {
|
||||
void pre_setup();
|
||||
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
|
||||
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
|
||||
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;
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
// 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,
|
||||
va_list args, char *buffer, int *buffer_at, int buffer_size) {
|
||||
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, uint16_t *buffer_at,
|
||||
uint16_t buffer_size) {
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size);
|
||||
#else
|
||||
@@ -180,7 +182,7 @@ class Logger : public Component {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Format to tx_buffer and prepare for output
|
||||
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
|
||||
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
|
||||
const int available = buffer_size - *buffer_at;
|
||||
if (available <= 0)
|
||||
if (*buffer_at >= buffer_size)
|
||||
return;
|
||||
const uint16_t available = buffer_size - *buffer_at;
|
||||
|
||||
// Determine copy length (minimum of remaining capacity and string length)
|
||||
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
|
||||
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_start(arg, format);
|
||||
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_();
|
||||
#endif
|
||||
|
||||
// Group 4-byte aligned members first
|
||||
uint32_t baud_rate_;
|
||||
char *tx_buffer_{nullptr};
|
||||
int tx_buffer_at_{0};
|
||||
int tx_buffer_size_{0};
|
||||
#ifdef USE_ARDUINO
|
||||
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)
|
||||
UARTSelection uart_{UART_SELECTION_UART0};
|
||||
#endif
|
||||
#ifdef USE_LIBRETINY
|
||||
UARTSelection uart_{UART_SELECTION_DEFAULT};
|
||||
#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
|
||||
// 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};
|
||||
pthread_key_t log_recursion_key_;
|
||||
#else
|
||||
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
|
||||
#endif
|
||||
CallbackManager<void(int)> level_callback_{};
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
void *main_task_ = nullptr; // Only used for thread name identification
|
||||
const char *HOT get_thread_name_() {
|
||||
TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
|
||||
if (current_task == main_task_) {
|
||||
@@ -297,11 +309,10 @@ class Logger : public Component {
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void HOT write_header_to_buffer_(int level, const char *tag, int line, const char *thread_name, char *buffer,
|
||||
int *buffer_at, int buffer_size) {
|
||||
inline void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name,
|
||||
char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
|
||||
// Format header
|
||||
if (level < 0)
|
||||
level = 0;
|
||||
// uint8_t level is already bounded 0-255, just ensure it's <= 7
|
||||
if (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);
|
||||
}
|
||||
|
||||
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) {
|
||||
// Get remaining capacity in the buffer
|
||||
const int remaining = buffer_size - *buffer_at;
|
||||
if (remaining <= 0)
|
||||
if (*buffer_at >= buffer_size)
|
||||
return;
|
||||
const uint16_t remaining = buffer_size - *buffer_at;
|
||||
|
||||
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)
|
||||
int formatted_len = (ret >= remaining) ? remaining : ret;
|
||||
uint16_t formatted_len = (ret >= remaining) ? remaining : ret;
|
||||
*buffer_at += formatted_len;
|
||||
|
||||
// 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) {
|
||||
static const int RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR);
|
||||
inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
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:
|
||||
explicit LoggerMessageTrigger(Logger *parent, int level) {
|
||||
explicit LoggerMessageTrigger(Logger *parent, uint8_t 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_) {
|
||||
this->trigger(level, tag, message);
|
||||
}
|
||||
@@ -362,7 +373,7 @@ class LoggerMessageTrigger : public Trigger<int, const char *, const char *> {
|
||||
}
|
||||
|
||||
protected:
|
||||
int level_;
|
||||
uint8_t level_;
|
||||
};
|
||||
|
||||
} // namespace logger
|
||||
|
@@ -454,9 +454,13 @@ def container_validator(schema, widget_type: WidgetType):
|
||||
"""
|
||||
|
||||
def validator(value):
|
||||
result = 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
|
||||
if value and (layout := value.get(df.CONF_LAYOUT)):
|
||||
if not isinstance(layout, dict):
|
||||
|
@@ -3,7 +3,6 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from .defines import (
|
||||
CONF_STYLE_DEFINITIONS,
|
||||
@@ -13,12 +12,13 @@ from .defines import (
|
||||
literal,
|
||||
)
|
||||
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 .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 (
|
||||
Widget,
|
||||
add_widgets,
|
||||
collect_parts,
|
||||
set_obj_properties,
|
||||
theme_widget_map,
|
||||
wait_for_widgets,
|
||||
@@ -37,12 +37,18 @@ async def style_set(svar, style):
|
||||
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):
|
||||
"""Convert styles to C__ code."""
|
||||
for style in config.get(CONF_STYLE_DEFINITIONS, ()):
|
||||
svar = cg.new_Pvariable(style[CONF_ID])
|
||||
lv.style_init(svar)
|
||||
await style_set(svar, style)
|
||||
await create_style(style, style[CONF_ID].id)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
@@ -68,16 +74,18 @@ async def theme_to_code(config):
|
||||
if theme := config.get(CONF_THEME):
|
||||
add_lv_use(CONF_THEME)
|
||||
for w_name, style in theme.items():
|
||||
if not isinstance(style, dict):
|
||||
continue
|
||||
|
||||
lname = "lv_theme_apply_" + w_name
|
||||
apply = lv_variable(lv_lambda_t, lname)
|
||||
theme_widget_map[w_name] = apply
|
||||
ow = Widget.create("obj", MockObj(ID("obj")), obj_spec)
|
||||
async with LambdaContext([(lv_obj_t_ptr, "obj")], where=w_name) as context:
|
||||
await set_obj_properties(ow, style)
|
||||
lv_assign(apply, await context.get_lambda())
|
||||
# Work around Python 3.10 bug with nested async comprehensions
|
||||
# With Python 3.11 this could be simplified
|
||||
styles = {}
|
||||
for part, states in collect_parts(style).items():
|
||||
styles[part] = {
|
||||
state: await create_style(
|
||||
props,
|
||||
"_lv_theme_style_" + w_name + "_" + part + "_" + state,
|
||||
)
|
||||
for state, props in states.items()
|
||||
}
|
||||
theme_widget_map[w_name] = styles
|
||||
|
||||
|
||||
async def add_top_layer(lv_component, config):
|
||||
|
@@ -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.core import ID, TimePeriod
|
||||
from esphome.coroutine import FakeAwaitable
|
||||
from esphome.cpp_generator import CallExpression, MockObj
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from ..defines import (
|
||||
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)
|
||||
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 add_widgets(w, w_cnfig)
|
||||
await spec.to_code(w, w_cnfig)
|
||||
|
@@ -1,8 +1,15 @@
|
||||
import esphome.config_validation as cv
|
||||
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 ..lv_validation import animated, get_start_value, lv_float
|
||||
from ..defines import (
|
||||
BAR_MODES,
|
||||
CONF_ANIMATED,
|
||||
CONF_INDICATOR,
|
||||
CONF_MAIN,
|
||||
CONF_START_VALUE,
|
||||
literal,
|
||||
)
|
||||
from ..lv_validation import animated, lv_int
|
||||
from ..lvcode import lv
|
||||
from ..types import LvNumber, NumberType
|
||||
from . import Widget
|
||||
@@ -10,22 +17,30 @@ from . import Widget
|
||||
# Note this file cannot be called "bar.py" because that name is disallowed.
|
||||
|
||||
CONF_BAR = "bar"
|
||||
BAR_MODIFY_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VALUE): lv_float,
|
||||
cv.Optional(CONF_ANIMATED, default=True): animated,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def validate_bar(config):
|
||||
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(
|
||||
{
|
||||
cv.Optional(CONF_VALUE): lv_float,
|
||||
cv.Optional(CONF_MIN_VALUE, default=0): cv.int_,
|
||||
cv.Optional(CONF_MAX_VALUE, default=100): cv.int_,
|
||||
cv.Optional(CONF_MODE, default="NORMAL"): BAR_MODES.one_of,
|
||||
cv.Optional(CONF_VALUE): lv_int,
|
||||
cv.Optional(CONF_START_VALUE): lv_int,
|
||||
cv.Optional(CONF_MIN_VALUE): lv_int,
|
||||
cv.Optional(CONF_MAX_VALUE): lv_int,
|
||||
cv.Optional(CONF_MODE): BAR_MODES.one_of,
|
||||
cv.Optional(CONF_ANIMATED, default=True): animated,
|
||||
}
|
||||
)
|
||||
).add_extra(validate_bar)
|
||||
|
||||
|
||||
class BarType(NumberType):
|
||||
@@ -35,17 +50,23 @@ class BarType(NumberType):
|
||||
LvNumber("lv_bar_t"),
|
||||
parts=(CONF_MAIN, CONF_INDICATOR),
|
||||
schema=BAR_SCHEMA,
|
||||
modify_schema=BAR_MODIFY_SCHEMA,
|
||||
)
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
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:
|
||||
lv.bar_set_range(var, config[CONF_MIN_VALUE], config[CONF_MAX_VALUE])
|
||||
lv.bar_set_mode(var, literal(config[CONF_MODE]))
|
||||
value = await get_start_value(config)
|
||||
if value is not None:
|
||||
lv.bar_set_value(var, value, literal(config[CONF_ANIMATED]))
|
||||
lv.bar_set_range(
|
||||
var,
|
||||
await lv_int.process(config[CONF_MIN_VALUE]),
|
||||
await lv_int.process(config[CONF_MAX_VALUE]),
|
||||
)
|
||||
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
|
||||
def animated(self):
|
||||
|
@@ -34,6 +34,7 @@ MULTI_CONF = True
|
||||
|
||||
CONF_ON_DOWNLOAD_FINISHED = "on_download_finished"
|
||||
CONF_PLACEHOLDER = "placeholder"
|
||||
CONF_UPDATE = "update"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -167,6 +168,7 @@ SET_URL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(OnlineImage),
|
||||
cv.Required(CONF_URL): cv.templatable(cv.url),
|
||||
cv.Optional(CONF_UPDATE, default=True): cv.templatable(bool),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -188,6 +190,9 @@ async def online_image_action_to_code(config, action_id, template_arg, args):
|
||||
if CONF_URL in config:
|
||||
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
||||
cg.add(var.set_url(template_))
|
||||
if CONF_UPDATE in config:
|
||||
template_ = await cg.templatable(config[CONF_UPDATE], args, bool)
|
||||
cg.add(var.set_update(template_))
|
||||
return var
|
||||
|
||||
|
||||
|
@@ -201,9 +201,12 @@ template<typename... Ts> class OnlineImageSetUrlAction : public Action<Ts...> {
|
||||
public:
|
||||
OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(std::string, url)
|
||||
TEMPLATABLE_VALUE(bool, update)
|
||||
void play(Ts... x) override {
|
||||
this->parent_->set_url(this->url_.value(x...));
|
||||
this->parent_->update();
|
||||
if (this->update_.value(x...)) {
|
||||
this->parent_->update();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@@ -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_CHANNEL", config[CONF_CHANNEL])
|
||||
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):
|
||||
@@ -54,14 +54,14 @@ def set_sdkconfig_options(config):
|
||||
|
||||
if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None:
|
||||
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:
|
||||
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:
|
||||
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 config[CONF_FORCE_DATASET]:
|
||||
@@ -98,7 +98,7 @@ _CONNECTION_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_EXT_PAN_ID): cv.hex_int,
|
||||
cv.Optional(CONF_NETWORK_NAME): cv.string_strict,
|
||||
cv.Optional(CONF_PSKC): cv.hex_int,
|
||||
cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.hex_int,
|
||||
cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.ipv6network,
|
||||
}
|
||||
)
|
||||
|
||||
|
@@ -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
|
||||
// component
|
||||
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_) {
|
||||
otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
|
||||
if (!entry) {
|
||||
@@ -185,11 +185,11 @@ void OpenThreadSrpComponent::setup() {
|
||||
if (error != OT_ERROR_NONE) {
|
||||
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);
|
||||
ESP_LOGW(TAG, "Finished SRP setup");
|
||||
ESP_LOGD(TAG, "Finished SRP setup");
|
||||
}
|
||||
|
||||
void *OpenThreadSrpComponent::pool_alloc_(size_t size) {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
# Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9
|
||||
import binascii
|
||||
import ipaddress
|
||||
|
||||
from esphome.const import CONF_CHANNEL
|
||||
|
||||
@@ -37,6 +38,12 @@ def parse_tlv(tlv) -> dict:
|
||||
if tag in TLV_TYPES:
|
||||
if tag == 3:
|
||||
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:
|
||||
output[TLV_TYPES[tag]] = int.from_bytes(val)
|
||||
return output
|
||||
|
0
esphome/components/opt3001/__init__.py
Normal file
0
esphome/components/opt3001/__init__.py
Normal file
122
esphome/components/opt3001/opt3001.cpp
Normal file
122
esphome/components/opt3001/opt3001.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "opt3001.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace opt3001 {
|
||||
|
||||
static const char *const TAG = "opt3001.sensor";
|
||||
|
||||
static const uint8_t OPT3001_REG_RESULT = 0x00;
|
||||
static const uint8_t OPT3001_REG_CONFIGURATION = 0x01;
|
||||
// See datasheet for full description of each bit.
|
||||
static const uint16_t OPT3001_CONFIGURATION_RANGE_FULL = 0b1100000000000000;
|
||||
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_TIME_800 = 0b100000000000;
|
||||
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_MASK = 0b11000000000;
|
||||
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_SINGLE_SHOT = 0b01000000000;
|
||||
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_SHUTDOWN = 0b00000000000;
|
||||
// tl;dr: Configure an automatic-ranged, 800ms single shot reading,
|
||||
// with INT processing disabled
|
||||
static const uint16_t OPT3001_CONFIGURATION_FULL_RANGE_ONE_SHOT = OPT3001_CONFIGURATION_RANGE_FULL |
|
||||
OPT3001_CONFIGURATION_CONVERSION_TIME_800 |
|
||||
OPT3001_CONFIGURATION_CONVERSION_MODE_SINGLE_SHOT;
|
||||
static const uint16_t OPT3001_CONVERSION_TIME_800 = 825; // give it 25 extra ms; it seems to not be ready quite often
|
||||
|
||||
/*
|
||||
opt3001 properties:
|
||||
|
||||
- e (exponent) = high 4 bits of result register
|
||||
- m (mantissa) = low 12 bits of result register
|
||||
- formula: (0.01 * 2^e) * m lx
|
||||
|
||||
*/
|
||||
|
||||
void OPT3001Sensor::read_result_(const std::function<void(float)> &f) {
|
||||
// ensure the single shot flag is clear, indicating it's done
|
||||
uint16_t raw_value;
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Reading configuration register failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
raw_value = i2c::i2ctohs(raw_value);
|
||||
|
||||
if ((raw_value & OPT3001_CONFIGURATION_CONVERSION_MODE_MASK) != OPT3001_CONFIGURATION_CONVERSION_MODE_SHUTDOWN) {
|
||||
// not ready; wait 10ms and try again
|
||||
ESP_LOGW(TAG, "Data not ready; waiting 10ms");
|
||||
this->set_timeout("opt3001_wait", 10, [this, f]() { read_result_(f); });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->read_register(OPT3001_REG_RESULT, reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Reading result register failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
raw_value = i2c::i2ctohs(raw_value);
|
||||
|
||||
uint8_t exponent = raw_value >> 12;
|
||||
uint16_t mantissa = raw_value & 0b111111111111;
|
||||
|
||||
double lx = 0.01 * pow(2.0, double(exponent)) * double(mantissa);
|
||||
f(float(lx));
|
||||
}
|
||||
|
||||
void OPT3001Sensor::read_lx_(const std::function<void(float)> &f) {
|
||||
// turn on (after one-shot sensor automatically powers down)
|
||||
uint16_t start_measurement = i2c::htoi2cs(OPT3001_CONFIGURATION_FULL_RANGE_ONE_SHOT);
|
||||
if (this->write_register(OPT3001_REG_CONFIGURATION, reinterpret_cast<uint8_t *>(&start_measurement), 2) !=
|
||||
i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Triggering one shot measurement failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_timeout("read", OPT3001_CONVERSION_TIME_800, [this, f]() {
|
||||
if (this->write(&OPT3001_REG_CONFIGURATION, 1, true) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Starting configuration register read failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
this->read_result_(f);
|
||||
});
|
||||
}
|
||||
|
||||
void OPT3001Sensor::dump_config() {
|
||||
LOG_SENSOR("", "OPT3001", this);
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void OPT3001Sensor::update() {
|
||||
// Set a flag and skip just in case the sensor isn't responding,
|
||||
// and we just keep waiting for it in read_result_.
|
||||
// This way we don't end up with potentially boundless "threads"
|
||||
// using up memory and eventually crashing the device
|
||||
if (this->updating_) {
|
||||
return;
|
||||
}
|
||||
this->updating_ = true;
|
||||
|
||||
this->read_lx_([this](float val) {
|
||||
this->updating_ = false;
|
||||
|
||||
if (std::isnan(val)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val);
|
||||
this->status_clear_warning();
|
||||
this->publish_state(val);
|
||||
});
|
||||
}
|
||||
|
||||
float OPT3001Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace opt3001
|
||||
} // namespace esphome
|
27
esphome/components/opt3001/opt3001.h
Normal file
27
esphome/components/opt3001/opt3001.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace opt3001 {
|
||||
|
||||
/// This class implements support for the i2c-based OPT3001 ambient light sensor.
|
||||
class OPT3001Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
// checks if one-shot is complete before reading the result and returning it
|
||||
void read_result_(const std::function<void(float)> &f);
|
||||
// begins a one-shot measurement
|
||||
void read_lx_(const std::function<void(float)> &f);
|
||||
|
||||
bool updating_{false};
|
||||
};
|
||||
|
||||
} // namespace opt3001
|
||||
} // namespace esphome
|
35
esphome/components/opt3001/sensor.py
Normal file
35
esphome/components/opt3001/sensor.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_LUX,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
CODEOWNERS = ["@ccutrer"]
|
||||
|
||||
opt3001_ns = cg.esphome_ns.namespace("opt3001")
|
||||
|
||||
OPT3001Sensor = opt3001_ns.class_(
|
||||
"OPT3001Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
OPT3001Sensor,
|
||||
unit_of_measurement=UNIT_LUX,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x44))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
@@ -5,7 +5,15 @@ from __future__ import annotations
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
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 os
|
||||
import re
|
||||
@@ -1186,6 +1194,14 @@ def ipv4address(value):
|
||||
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):
|
||||
address = ipv4address(value)
|
||||
if not (address.is_multicast or (address == IPv4Address("255.255.255.255"))):
|
||||
@@ -1203,6 +1219,33 @@ def ipaddress(value):
|
||||
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):
|
||||
"""Validate that this is a valid topic name/filter."""
|
||||
if value is None: # Used to disable publishing and subscribing
|
||||
|
@@ -257,6 +257,17 @@ void Application::teardown_components(uint32_t timeout_ms) {
|
||||
}
|
||||
|
||||
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
|
||||
for (auto *obj : this->components_) {
|
||||
if (obj->has_overridden_loop() &&
|
||||
|
@@ -29,7 +29,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const char *form
|
||||
if (log == nullptr)
|
||||
return;
|
||||
|
||||
log->log_vprintf_(level, tag, line, format, args);
|
||||
log->log_vprintf_(static_cast<uint8_t>(level), tag, line, format, args);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const __FlashStr
|
||||
if (log == nullptr)
|
||||
return;
|
||||
|
||||
log->log_vprintf_(level, tag, line, format, args);
|
||||
log->log_vprintf_(static_cast<uint8_t>(level), tag, line, format, args);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
@@ -319,13 +319,17 @@ bool HOT Scheduler::cancel_item_(Component *component, const std::string &name,
|
||||
return ret;
|
||||
}
|
||||
uint64_t Scheduler::millis_() {
|
||||
// Get the current 32-bit millis value
|
||||
const uint32_t now = millis();
|
||||
// Check for rollover by comparing with last value
|
||||
if (now < this->last_millis_) {
|
||||
// Detected rollover (happens every ~49.7 days)
|
||||
this->millis_major_++;
|
||||
ESP_LOGD(TAG, "Incrementing scheduler major at %" PRIu64 "ms",
|
||||
now + (static_cast<uint64_t>(this->millis_major_) << 32));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
@@ -29,12 +29,16 @@ class Scheduler {
|
||||
|
||||
protected:
|
||||
struct SchedulerItem {
|
||||
// Ordered by size to minimize padding
|
||||
Component *component;
|
||||
std::string name;
|
||||
enum Type { TIMEOUT, INTERVAL } type;
|
||||
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_;
|
||||
std::string name;
|
||||
std::function<void()> callback;
|
||||
enum Type : uint8_t { TIMEOUT, INTERVAL } type;
|
||||
bool remove;
|
||||
|
||||
static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);
|
||||
|
@@ -5,7 +5,7 @@ import fnmatch
|
||||
import functools
|
||||
import inspect
|
||||
from io import BytesIO, TextIOBase, TextIOWrapper
|
||||
from ipaddress import _BaseAddress
|
||||
from ipaddress import _BaseAddress, _BaseNetwork
|
||||
import logging
|
||||
import math
|
||||
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(float, ESPHomeDumper.represent_float)
|
||||
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(TimePeriod, ESPHomeDumper.represent_stringify)
|
||||
ESPHomeDumper.add_multi_representer(Lambda, ESPHomeDumper.represent_lambda)
|
||||
|
@@ -13,7 +13,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
||||
esptool==4.9.0
|
||||
click==8.1.7
|
||||
esphome-dashboard==20250514.0
|
||||
aioesphomeapi==33.1.0
|
||||
aioesphomeapi==33.1.1
|
||||
zeroconf==0.147.0
|
||||
puremagic==1.29
|
||||
ruamel.yaml==0.18.14 # dashboard_import
|
||||
|
@@ -728,12 +728,15 @@ lvgl:
|
||||
value: 30
|
||||
max_value: 100
|
||||
min_value: 10
|
||||
start_value: 20
|
||||
mode: range
|
||||
on_click:
|
||||
then:
|
||||
- lvgl.bar.update:
|
||||
id: bar_id
|
||||
value: !lambda return (int)((float)rand() / RAND_MAX * 100);
|
||||
start_value: !lambda return (int)((float)rand() / RAND_MAX * 100);
|
||||
mode: symmetrical
|
||||
- logger.log:
|
||||
format: "bar value %f"
|
||||
args: [x]
|
||||
|
@@ -8,4 +8,6 @@ openthread:
|
||||
pan_id: 0x8f28
|
||||
ext_pan_id: 0xd63e8e3e495ebbc3
|
||||
pskc: 0xc23a76e98f1a6483639b1ac1271e2e27
|
||||
mesh_local_prefix: fd53:145f:ed22:ad81::/64
|
||||
force_dataset: true
|
||||
|
||||
|
10
tests/components/opt3001/common.yaml
Normal file
10
tests/components/opt3001/common.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
i2c:
|
||||
- id: i2c_opt3001
|
||||
scl: ${scl_pin}
|
||||
sda: ${sda_pin}
|
||||
|
||||
sensor:
|
||||
- platform: opt3001
|
||||
name: Living Room Brightness
|
||||
address: 0x44
|
||||
update_interval: 30s
|
5
tests/components/opt3001/test.esp32-ard.yaml
Normal file
5
tests/components/opt3001/test.esp32-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/opt3001/test.esp32-c3-ard.yaml
Normal file
5
tests/components/opt3001/test.esp32-c3-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/opt3001/test.esp32-c3-idf.yaml
Normal file
5
tests/components/opt3001/test.esp32-c3-idf.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/opt3001/test.esp32-idf.yaml
Normal file
5
tests/components/opt3001/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/opt3001/test.esp8266-ard.yaml
Normal file
5
tests/components/opt3001/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/opt3001/test.rp2040-ard.yaml
Normal file
5
tests/components/opt3001/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
Reference in New Issue
Block a user