mirror of
https://github.com/esphome/esphome.git
synced 2025-11-09 11:31:50 +00:00
Compare commits
27 Commits
2025.3.0b2
...
2025.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
573088aadb | ||
|
|
031b1c8bd0 | ||
|
|
f95b2ba898 | ||
|
|
ea4b573f9a | ||
|
|
8fcbd57f2f | ||
|
|
f131186e6b | ||
|
|
20c7778524 | ||
|
|
2d8e86324b | ||
|
|
8ea4d8402f | ||
|
|
2c53408cfc | ||
|
|
33dce6e522 | ||
|
|
e213932b7c | ||
|
|
42fb0e2809 | ||
|
|
c4de9e87e4 | ||
|
|
918924d697 | ||
|
|
e2c16b4baa | ||
|
|
10a9162f48 | ||
|
|
fbc884772c | ||
|
|
54e3153f27 | ||
|
|
c2e0a01106 | ||
|
|
d2c2439b97 | ||
|
|
a8d33dd26a | ||
|
|
da41a9204e | ||
|
|
5c6368b6b8 | ||
|
|
9bd7060f6b | ||
|
|
fb1d178abc | ||
|
|
90c96a0a0f |
@@ -33,9 +33,9 @@ RUN \
|
|||||||
python3-venv=3.11.2-1+b1 \
|
python3-venv=3.11.2-1+b1 \
|
||||||
python3-wheel=0.38.4-2 \
|
python3-wheel=0.38.4-2 \
|
||||||
iputils-ping=3:20221126-1+deb12u1 \
|
iputils-ping=3:20221126-1+deb12u1 \
|
||||||
git=1:2.39.5-0+deb12u1 \
|
git=1:2.39.5-0+deb12u2 \
|
||||||
curl=7.88.1-10+deb12u8 \
|
curl=7.88.1-10+deb12u12 \
|
||||||
openssh-client=1:9.2p1-2+deb12u4 \
|
openssh-client=1:9.2p1-2+deb12u5 \
|
||||||
python3-cffi=1.15.1-5 \
|
python3-cffi=1.15.1-5 \
|
||||||
libcairo2=1.16.0-7 \
|
libcairo2=1.16.0-7 \
|
||||||
libmagic1=1:5.44-3 \
|
libmagic1=1:5.44-3 \
|
||||||
@@ -76,7 +76,7 @@ BUILD_DEPS="
|
|||||||
python3-dev=3.11.2-1+b1
|
python3-dev=3.11.2-1+b1
|
||||||
zlib1g-dev=1:1.2.13.dfsg-1
|
zlib1g-dev=1:1.2.13.dfsg-1
|
||||||
libjpeg-dev=1:2.1.5-2
|
libjpeg-dev=1:2.1.5-2
|
||||||
libfreetype-dev=2.12.1+dfsg-5+deb12u3
|
libfreetype-dev=2.12.1+dfsg-5+deb12u4
|
||||||
libssl-dev=3.0.15-1~deb12u1
|
libssl-dev=3.0.15-1~deb12u1
|
||||||
libffi-dev=3.4.4-1
|
libffi-dev=3.4.4-1
|
||||||
cargo=0.66.0+ds1-1
|
cargo=0.66.0+ds1-1
|
||||||
@@ -84,7 +84,7 @@ BUILD_DEPS="
|
|||||||
"
|
"
|
||||||
LIB_DEPS="
|
LIB_DEPS="
|
||||||
libtiff6=4.5.0-6+deb12u1
|
libtiff6=4.5.0-6+deb12u1
|
||||||
libopenjp2-7=2.5.0-2
|
libopenjp2-7=2.5.0-2+deb12u1
|
||||||
"
|
"
|
||||||
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
|
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
|
||||||
then
|
then
|
||||||
@@ -160,7 +160,7 @@ RUN \
|
|||||||
apt-get update \
|
apt-get update \
|
||||||
# Use pinned versions so that we get updates with build caching
|
# Use pinned versions so that we get updates with build caching
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
nginx-light=1.22.1-9 \
|
nginx-light=1.22.1-9+deb12u1 \
|
||||||
&& rm -rf \
|
&& rm -rf \
|
||||||
/tmp/* \
|
/tmp/* \
|
||||||
/var/{cache,log}/* \
|
/var/{cache,log}/* \
|
||||||
|
|||||||
@@ -118,4 +118,4 @@ def final_validate_audio_schema(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_library("esphome/esp-audio-libs", "1.1.2")
|
cg.add_library("esphome/esp-audio-libs", "1.1.3")
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ void CST226Touchscreen::continue_setup_() {
|
|||||||
if (this->read16_(0xD1F8, buffer, 4)) {
|
if (this->read16_(0xD1F8, buffer, 4)) {
|
||||||
this->x_raw_max_ = buffer[0] + (buffer[1] << 8);
|
this->x_raw_max_ = buffer[0] + (buffer[1] << 8);
|
||||||
this->y_raw_max_ = buffer[2] + (buffer[3] << 8);
|
this->y_raw_max_ = buffer[2] + (buffer[3] << 8);
|
||||||
|
if (this->swap_x_y_)
|
||||||
|
std::swap(this->x_raw_max_, this->y_raw_max_);
|
||||||
} else {
|
} else {
|
||||||
this->x_raw_max_ = this->display_->get_native_width();
|
this->x_raw_max_ = this->display_->get_native_width();
|
||||||
this->y_raw_max_ = this->display_->get_native_height();
|
this->y_raw_max_ = this->display_->get_native_height();
|
||||||
|
|||||||
@@ -34,26 +34,29 @@ void EKTF2232Touchscreen::setup() {
|
|||||||
|
|
||||||
// Get touch resolution
|
// Get touch resolution
|
||||||
uint8_t received[4];
|
uint8_t received[4];
|
||||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
if (this->x_raw_max_ == 0 || this->y_raw_max_ == 0) {
|
||||||
this->write(GET_X_RES, 4);
|
auto err = this->write(GET_X_RES, 4);
|
||||||
if (this->read(received, 4)) {
|
if (err == i2c::ERROR_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to read X resolution!");
|
err = this->read(received, 4);
|
||||||
this->interrupt_pin_->detach_interrupt();
|
if (err == i2c::ERROR_OK) {
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->x_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
this->x_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||||
|
err = this->write(GET_Y_RES, 4);
|
||||||
|
if (err == i2c::ERROR_OK) {
|
||||||
|
err = this->read(received, 4);
|
||||||
|
if (err == i2c::ERROR_OK) {
|
||||||
|
this->y_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
}
|
||||||
this->write(GET_Y_RES, 4);
|
}
|
||||||
if (this->read(received, 4)) {
|
if (err != i2c::ERROR_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to read Y resolution!");
|
ESP_LOGE(TAG, "Failed to read calibration values!");
|
||||||
this->interrupt_pin_->detach_interrupt();
|
this->interrupt_pin_->detach_interrupt();
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->y_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
if (this->swap_x_y_)
|
||||||
|
std::swap(this->x_raw_max_, this->y_raw_max_);
|
||||||
}
|
}
|
||||||
this->set_power_state(true);
|
this->set_power_state(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,15 @@ from pathlib import Path
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import esphome_glyphsets as glyphsets
|
import esphome_glyphsets as glyphsets
|
||||||
from freetype import Face, ft_pixel_mode_grays, ft_pixel_mode_mono
|
|
||||||
|
# pylint: disable=no-name-in-module
|
||||||
|
from freetype import (
|
||||||
|
FT_LOAD_NO_BITMAP,
|
||||||
|
FT_LOAD_RENDER,
|
||||||
|
FT_LOAD_TARGET_MONO,
|
||||||
|
Face,
|
||||||
|
ft_pixel_mode_mono,
|
||||||
|
)
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from esphome import external_files
|
from esphome import external_files
|
||||||
@@ -146,6 +154,13 @@ def check_missing_glyphs(file, codepoints, warning: bool = False):
|
|||||||
raise cv.Invalid(message)
|
raise cv.Invalid(message)
|
||||||
|
|
||||||
|
|
||||||
|
def pt_to_px(pt):
|
||||||
|
"""
|
||||||
|
Convert a point size to pixels, rounding up to the nearest pixel
|
||||||
|
"""
|
||||||
|
return (pt + 63) // 64
|
||||||
|
|
||||||
|
|
||||||
def validate_font_config(config):
|
def validate_font_config(config):
|
||||||
"""
|
"""
|
||||||
Check for duplicate codepoints, then check that all requested codepoints actually
|
Check for duplicate codepoints, then check that all requested codepoints actually
|
||||||
@@ -172,11 +187,7 @@ def validate_font_config(config):
|
|||||||
)
|
)
|
||||||
# Make setpoints and glyphspoints disjoint
|
# Make setpoints and glyphspoints disjoint
|
||||||
setpoints.difference_update(glyphspoints)
|
setpoints.difference_update(glyphspoints)
|
||||||
if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP:
|
# check that glyphs are actually present
|
||||||
if any(x >= 256 for x in setpoints.copy().union(glyphspoints)):
|
|
||||||
raise cv.Invalid("Codepoints in bitmap fonts must be in the range 0-255")
|
|
||||||
else:
|
|
||||||
# for TT fonts, check that glyphs are actually present
|
|
||||||
# Check extras against their own font, exclude from parent font codepoints
|
# Check extras against their own font, exclude from parent font codepoints
|
||||||
for extra in config[CONF_EXTRAS]:
|
for extra in config[CONF_EXTRAS]:
|
||||||
points = {ord(x) for x in flatten(extra[CONF_GLYPHS])}
|
points = {ord(x) for x in flatten(extra[CONF_GLYPHS])}
|
||||||
@@ -192,22 +203,27 @@ def validate_font_config(config):
|
|||||||
check_missing_glyphs(fileconf, setpoints, warning=True)
|
check_missing_glyphs(fileconf, setpoints, warning=True)
|
||||||
|
|
||||||
# Populate the default after the above checks so that use of the default doesn't trigger errors
|
# Populate the default after the above checks so that use of the default doesn't trigger errors
|
||||||
if not config[CONF_GLYPHS] and not config[CONF_GLYPHSETS]:
|
|
||||||
if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP:
|
|
||||||
config[CONF_GLYPHS] = [DEFAULT_GLYPHS]
|
|
||||||
else:
|
|
||||||
# set a default glyphset, intersected with what the font actually offers
|
|
||||||
font = FONT_CACHE[fileconf]
|
font = FONT_CACHE[fileconf]
|
||||||
|
if not config[CONF_GLYPHS] and not config[CONF_GLYPHSETS]:
|
||||||
|
# set a default glyphset, intersected with what the font actually offers
|
||||||
config[CONF_GLYPHS] = [
|
config[CONF_GLYPHS] = [
|
||||||
chr(x)
|
chr(x)
|
||||||
for x in glyphsets.unicodes_per_glyphset(DEFAULT_GLYPHSET)
|
for x in glyphsets.unicodes_per_glyphset(DEFAULT_GLYPHSET)
|
||||||
if font.get_char_index(x) != 0
|
if font.get_char_index(x) != 0
|
||||||
]
|
]
|
||||||
|
|
||||||
if config[CONF_FILE][CONF_TYPE] == TYPE_LOCAL_BITMAP:
|
if not font.is_scalable:
|
||||||
if CONF_SIZE in config:
|
sizes = [pt_to_px(x.size) for x in font.available_sizes]
|
||||||
|
if not sizes:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
"Size is not a valid option for bitmap fonts, which are inherently fixed size"
|
f"Font {FontCache.get_name(fileconf)} has no available sizes"
|
||||||
|
)
|
||||||
|
if CONF_SIZE not in config:
|
||||||
|
config[CONF_SIZE] = sizes[0]
|
||||||
|
elif config[CONF_SIZE] not in sizes:
|
||||||
|
sizes = ", ".join(str(x) for x in sizes)
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Font {FontCache.get_name(fileconf)} only has size{'s' if len(sizes) != 1 else ''} {sizes} available"
|
||||||
)
|
)
|
||||||
elif CONF_SIZE not in config:
|
elif CONF_SIZE not in config:
|
||||||
config[CONF_SIZE] = 20
|
config[CONF_SIZE] = 20
|
||||||
@@ -215,14 +231,7 @@ def validate_font_config(config):
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
FONT_EXTENSIONS = (".ttf", ".woff", ".otf")
|
FONT_EXTENSIONS = (".ttf", ".woff", ".otf", "bdf", ".pcf")
|
||||||
BITMAP_EXTENSIONS = (".bdf", ".pcf")
|
|
||||||
|
|
||||||
|
|
||||||
def validate_bitmap_file(value):
|
|
||||||
if not any(map(value.lower().endswith, BITMAP_EXTENSIONS)):
|
|
||||||
raise cv.Invalid(f"Only {', '.join(BITMAP_EXTENSIONS)} files are supported.")
|
|
||||||
return CORE.relative_config_path(cv.file_(value))
|
|
||||||
|
|
||||||
|
|
||||||
def validate_truetype_file(value):
|
def validate_truetype_file(value):
|
||||||
@@ -246,7 +255,6 @@ def add_local_file(value):
|
|||||||
|
|
||||||
|
|
||||||
TYPE_LOCAL = "local"
|
TYPE_LOCAL = "local"
|
||||||
TYPE_LOCAL_BITMAP = "local_bitmap"
|
|
||||||
TYPE_GFONTS = "gfonts"
|
TYPE_GFONTS = "gfonts"
|
||||||
TYPE_WEB = "web"
|
TYPE_WEB = "web"
|
||||||
LOCAL_SCHEMA = cv.All(
|
LOCAL_SCHEMA = cv.All(
|
||||||
@@ -258,15 +266,6 @@ LOCAL_SCHEMA = cv.All(
|
|||||||
add_local_file,
|
add_local_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
LOCAL_BITMAP_SCHEMA = cv.All(
|
|
||||||
cv.Schema(
|
|
||||||
{
|
|
||||||
cv.Required(CONF_PATH): validate_bitmap_file,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
add_local_file,
|
|
||||||
)
|
|
||||||
|
|
||||||
FULLPATH_SCHEMA = cv.maybe_simple_value(
|
FULLPATH_SCHEMA = cv.maybe_simple_value(
|
||||||
{cv.Required(CONF_PATH): cv.string}, key=CONF_PATH
|
{cv.Required(CONF_PATH): cv.string}, key=CONF_PATH
|
||||||
)
|
)
|
||||||
@@ -403,15 +402,6 @@ def validate_file_shorthand(value):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
extension = Path(value).suffix
|
|
||||||
if extension in BITMAP_EXTENSIONS:
|
|
||||||
return font_file_schema(
|
|
||||||
{
|
|
||||||
CONF_TYPE: TYPE_LOCAL_BITMAP,
|
|
||||||
CONF_PATH: value,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return font_file_schema(
|
return font_file_schema(
|
||||||
{
|
{
|
||||||
CONF_TYPE: TYPE_LOCAL,
|
CONF_TYPE: TYPE_LOCAL,
|
||||||
@@ -424,7 +414,6 @@ TYPED_FILE_SCHEMA = cv.typed_schema(
|
|||||||
{
|
{
|
||||||
TYPE_LOCAL: LOCAL_SCHEMA,
|
TYPE_LOCAL: LOCAL_SCHEMA,
|
||||||
TYPE_GFONTS: GFONTS_SCHEMA,
|
TYPE_GFONTS: GFONTS_SCHEMA,
|
||||||
TYPE_LOCAL_BITMAP: LOCAL_BITMAP_SCHEMA,
|
|
||||||
TYPE_WEB: WEB_FONT_SCHEMA,
|
TYPE_WEB: WEB_FONT_SCHEMA,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -520,15 +509,23 @@ async def to_code(config):
|
|||||||
glyph_args = {}
|
glyph_args = {}
|
||||||
data = []
|
data = []
|
||||||
bpp = config[CONF_BPP]
|
bpp = config[CONF_BPP]
|
||||||
mode = ft_pixel_mode_grays
|
|
||||||
scale = 256 // (1 << bpp)
|
scale = 256 // (1 << bpp)
|
||||||
|
size = config[CONF_SIZE]
|
||||||
# create the data array for all glyphs
|
# create the data array for all glyphs
|
||||||
for codepoint in codepoints:
|
for codepoint in codepoints:
|
||||||
font = point_font_map[codepoint]
|
font = point_font_map[codepoint]
|
||||||
if not font.has_fixed_sizes:
|
if not font.is_scalable:
|
||||||
font.set_pixel_sizes(config[CONF_SIZE], 0)
|
sizes = [pt_to_px(x.size) for x in font.available_sizes]
|
||||||
font.load_char(codepoint)
|
if size in sizes:
|
||||||
font.glyph.render(mode)
|
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
|
width = font.glyph.bitmap.width
|
||||||
height = font.glyph.bitmap.rows
|
height = font.glyph.bitmap.rows
|
||||||
buffer = font.glyph.bitmap.buffer
|
buffer = font.glyph.bitmap.buffer
|
||||||
@@ -550,17 +547,17 @@ async def to_code(config):
|
|||||||
if pixel & (1 << (bpp - bit_num - 1)):
|
if pixel & (1 << (bpp - bit_num - 1)):
|
||||||
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
|
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
|
||||||
pos += 1
|
pos += 1
|
||||||
ascender = font.size.ascender // 64
|
ascender = pt_to_px(font.size.ascender)
|
||||||
if ascender == 0:
|
if ascender == 0:
|
||||||
if font.has_fixed_sizes:
|
if not font.is_scalable:
|
||||||
ascender = font.available_sizes[0].height
|
ascender = size
|
||||||
else:
|
else:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Unable to determine ascender of font %s", config[CONF_FILE]
|
"Unable to determine ascender of font %s", config[CONF_FILE]
|
||||||
)
|
)
|
||||||
glyph_args[codepoint] = GlyphInfo(
|
glyph_args[codepoint] = GlyphInfo(
|
||||||
len(data),
|
len(data),
|
||||||
font.glyph.metrics.horiAdvance // 64,
|
pt_to_px(font.glyph.metrics.horiAdvance),
|
||||||
font.glyph.bitmap_left,
|
font.glyph.bitmap_left,
|
||||||
ascender - font.glyph.bitmap_top,
|
ascender - font.glyph.bitmap_top,
|
||||||
width,
|
width,
|
||||||
@@ -599,11 +596,11 @@ async def to_code(config):
|
|||||||
|
|
||||||
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 = base_font.size.height // 64
|
font_height = pt_to_px(base_font.size.height)
|
||||||
ascender = base_font.size.ascender // 64
|
ascender = pt_to_px(base_font.size.ascender)
|
||||||
if font_height == 0:
|
if font_height == 0:
|
||||||
if base_font.has_fixed_sizes:
|
if not base_font.is_scalable:
|
||||||
font_height = base_font.available_sizes[0].height
|
font_height = size
|
||||||
ascender = font_height
|
ascender = font_height
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("Unable to determine height of font %s", config[CONF_FILE])
|
_LOGGER.error("Unable to determine height of font %s", config[CONF_FILE])
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ void FT63X6Touchscreen::setup() {
|
|||||||
|
|
||||||
// Get touch resolution
|
// Get touch resolution
|
||||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||||
this->x_raw_max_ = 320;
|
this->x_raw_max_ = this->display_->get_native_width();
|
||||||
}
|
}
|
||||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||||
this->y_raw_max_ = 480;
|
this->y_raw_max_ = this->display_->get_native_height();
|
||||||
}
|
}
|
||||||
uint8_t chip_id = this->read_byte_(FT63X6_ADDR_CHIP_ID);
|
uint8_t chip_id = this->read_byte_(FT63X6_ADDR_CHIP_ID);
|
||||||
if (chip_id != 0) {
|
if (chip_id != 0) {
|
||||||
@@ -71,6 +71,8 @@ void FT63X6Touchscreen::dump_config() {
|
|||||||
LOG_I2C_DEVICE(this);
|
LOG_I2C_DEVICE(this);
|
||||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
|
ESP_LOGCONFIG(TAG, " X Calibration: [%d, %d]", this->x_raw_min_, this->x_raw_max_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Y Calibration: [%d, %d]", this->y_raw_min_, this->y_raw_max_);
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,10 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo
|
|||||||
yrange = ymax - ymin;
|
yrange = ymax - ymin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store graph limts
|
||||||
|
this->graph_limit_max_ = ymax;
|
||||||
|
this->graph_limit_min_ = ymin;
|
||||||
|
|
||||||
/// Draw grid
|
/// Draw grid
|
||||||
if (!std::isnan(this->gridspacing_y_)) {
|
if (!std::isnan(this->gridspacing_y_)) {
|
||||||
for (int y = yn; y <= ym; y++) {
|
for (int y = yn; y <= ym; y++) {
|
||||||
|
|||||||
@@ -161,11 +161,15 @@ class Graph : public Component {
|
|||||||
uint32_t get_duration() { return duration_; }
|
uint32_t get_duration() { return duration_; }
|
||||||
uint32_t get_width() { return width_; }
|
uint32_t get_width() { return width_; }
|
||||||
uint32_t get_height() { return height_; }
|
uint32_t get_height() { return height_; }
|
||||||
|
float get_graph_limit_min() { return graph_limit_min_; }
|
||||||
|
float get_graph_limit_max() { return graph_limit_max_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint32_t duration_; /// in seconds
|
uint32_t duration_; /// in seconds
|
||||||
uint32_t width_; /// in pixels
|
uint32_t width_; /// in pixels
|
||||||
uint32_t height_; /// in pixels
|
uint32_t height_; /// in pixels
|
||||||
|
float graph_limit_min_{NAN};
|
||||||
|
float graph_limit_max_{NAN};
|
||||||
float min_value_{NAN};
|
float min_value_{NAN};
|
||||||
float max_value_{NAN};
|
float max_value_{NAN};
|
||||||
float min_range_{1.0};
|
float min_range_{1.0};
|
||||||
|
|||||||
@@ -60,20 +60,25 @@ void GT911Touchscreen::setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this->x_raw_max_ == 0 || this->y_raw_max_ == 0) {
|
||||||
|
// no calibration? Attempt to read the max values from the touchscreen.
|
||||||
if (err == i2c::ERROR_OK) {
|
if (err == i2c::ERROR_OK) {
|
||||||
err = this->write(GET_MAX_VALUES, 2);
|
err = this->write(GET_MAX_VALUES, 2);
|
||||||
if (err == i2c::ERROR_OK) {
|
if (err == i2c::ERROR_OK) {
|
||||||
err = this->read(data, sizeof(data));
|
err = this->read(data, sizeof(data));
|
||||||
if (err == i2c::ERROR_OK) {
|
if (err == i2c::ERROR_OK) {
|
||||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
|
||||||
this->x_raw_max_ = encode_uint16(data[1], data[0]);
|
this->x_raw_max_ = encode_uint16(data[1], data[0]);
|
||||||
}
|
|
||||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
|
||||||
this->y_raw_max_ = encode_uint16(data[3], data[2]);
|
this->y_raw_max_ = encode_uint16(data[3], data[2]);
|
||||||
|
if (this->swap_x_y_)
|
||||||
|
std::swap(this->x_raw_max_, this->y_raw_max_);
|
||||||
}
|
}
|
||||||
esph_log_d(TAG, "calibration max_x/max_y %d/%d", this->x_raw_max_, this->y_raw_max_);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (err != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to read calibration values from touchscreen!");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (err != i2c::ERROR_OK) {
|
if (err != i2c::ERROR_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to communicate!");
|
ESP_LOGE(TAG, "Failed to communicate!");
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace esphome {
|
|||||||
namespace ld2450 {
|
namespace ld2450 {
|
||||||
|
|
||||||
static const char *const TAG = "ld2450";
|
static const char *const TAG = "ld2450";
|
||||||
|
static const char *const NO_MAC("08:05:04:03:02:01");
|
||||||
static const char *const UNKNOWN_MAC("unknown");
|
static const char *const UNKNOWN_MAC("unknown");
|
||||||
|
|
||||||
// LD2450 UART Serial Commands
|
// LD2450 UART Serial Commands
|
||||||
@@ -614,12 +615,12 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
|||||||
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
|
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
if (this->mac_text_sensor_ != nullptr) {
|
if (this->mac_text_sensor_ != nullptr) {
|
||||||
this->mac_text_sensor_->publish_state(this->mac_);
|
this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
if (this->bluetooth_switch_ != nullptr) {
|
if (this->bluetooth_switch_ != nullptr) {
|
||||||
this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC);
|
this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -423,8 +423,6 @@ LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buf
|
|||||||
this->disp_drv_.full_refresh = this->full_refresh_;
|
this->disp_drv_.full_refresh = this->full_refresh_;
|
||||||
this->disp_drv_.flush_cb = static_flush_cb;
|
this->disp_drv_.flush_cb = static_flush_cb;
|
||||||
this->disp_drv_.rounder_cb = rounder_cb;
|
this->disp_drv_.rounder_cb = rounder_cb;
|
||||||
this->disp_drv_.hor_res = 0;
|
|
||||||
this->disp_drv_.ver_res = 0;
|
|
||||||
this->disp_ = lv_disp_drv_register(&this->disp_drv_);
|
this->disp_ = lv_disp_drv_register(&this->disp_drv_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,14 +439,14 @@ void LvglComponent::setup() {
|
|||||||
this->status_set_error("Memory allocation failure");
|
this->status_set_error("Memory allocation failure");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buf_bytes);
|
lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buffer_pixels);
|
||||||
this->disp_drv_.hor_res = width;
|
this->disp_drv_.hor_res = width;
|
||||||
this->disp_drv_.ver_res = height;
|
this->disp_drv_.ver_res = height;
|
||||||
// this->setup_driver_(display->get_width(), display->get_height());
|
// this->setup_driver_(display->get_width(), display->get_height());
|
||||||
lv_disp_drv_update(this->disp_, &this->disp_drv_);
|
lv_disp_drv_update(this->disp_, &this->disp_drv_);
|
||||||
this->rotation = display->get_rotation();
|
this->rotation = display->get_rotation();
|
||||||
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
|
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
|
||||||
this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(this->draw_buf_.size)); // NOLINT
|
this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes)); // NOLINT
|
||||||
if (this->rotate_buf_ == nullptr) {
|
if (this->rotate_buf_ == nullptr) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
this->status_set_error("Memory allocation failure");
|
this->status_set_error("Memory allocation failure");
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ const char *media_player_command_to_string(MediaPlayerCommand command) {
|
|||||||
|
|
||||||
void MediaPlayerCall::validate_() {
|
void MediaPlayerCall::validate_() {
|
||||||
if (this->media_url_.has_value()) {
|
if (this->media_url_.has_value()) {
|
||||||
if (this->command_.has_value()) {
|
if (this->command_.has_value() && this->command_.value() != MEDIA_PLAYER_COMMAND_ENQUEUE) {
|
||||||
|
// Don't remove an enqueue command
|
||||||
ESP_LOGW(TAG, "MediaPlayerCall: Setting both command and media_url is not needed.");
|
ESP_LOGW(TAG, "MediaPlayerCall: Setting both command and media_url is not needed.");
|
||||||
this->command_.reset();
|
this->command_.reset();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,77 +138,48 @@ void SpeakerMediaPlayer::watch_media_commands_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MediaCallCommand media_command;
|
MediaCallCommand media_command;
|
||||||
esp_err_t err = ESP_OK;
|
|
||||||
|
|
||||||
if (xQueueReceive(this->media_control_command_queue_, &media_command, 0) == pdTRUE) {
|
if (xQueueReceive(this->media_control_command_queue_, &media_command, 0) == pdTRUE) {
|
||||||
bool new_url = media_command.new_url.has_value() && media_command.new_url.value();
|
|
||||||
bool new_file = media_command.new_file.has_value() && media_command.new_file.value();
|
|
||||||
|
|
||||||
if (new_url || new_file) {
|
|
||||||
bool enqueue = media_command.enqueue.has_value() && media_command.enqueue.value();
|
bool enqueue = media_command.enqueue.has_value() && media_command.enqueue.value();
|
||||||
|
|
||||||
if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
|
if (media_command.url.has_value() || media_command.file.has_value()) {
|
||||||
// Announcement playlist/pipeline
|
PlaylistItem playlist_item;
|
||||||
|
if (media_command.url.has_value()) {
|
||||||
|
playlist_item.url = *media_command.url.value();
|
||||||
|
delete media_command.url.value();
|
||||||
|
}
|
||||||
|
if (media_command.file.has_value()) {
|
||||||
|
playlist_item.file = media_command.file.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
|
||||||
if (!enqueue) {
|
if (!enqueue) {
|
||||||
// Clear the queue and ensure the loaded next item doesn't start playing
|
// Ensure the loaded next item doesn't start playing, clear the queue, start the file, and unpause
|
||||||
this->cancel_timeout("next_ann");
|
this->cancel_timeout("next_ann");
|
||||||
this->announcement_playlist_.clear();
|
this->announcement_playlist_.clear();
|
||||||
}
|
if (media_command.file.has_value()) {
|
||||||
|
|
||||||
PlaylistItem playlist_item;
|
|
||||||
if (new_url) {
|
|
||||||
playlist_item.url = this->announcement_url_;
|
|
||||||
if (!enqueue) {
|
|
||||||
// Not adding to the queue, so directly start playback and internally unpause the pipeline
|
|
||||||
this->announcement_pipeline_->start_url(playlist_item.url.value());
|
|
||||||
this->announcement_pipeline_->set_pause_state(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
playlist_item.file = this->announcement_file_;
|
|
||||||
if (!enqueue) {
|
|
||||||
// Not adding to the queue, so directly start playback and internally unpause the pipeline
|
|
||||||
this->announcement_pipeline_->start_file(playlist_item.file.value());
|
this->announcement_pipeline_->start_file(playlist_item.file.value());
|
||||||
this->announcement_pipeline_->set_pause_state(false);
|
} else if (media_command.url.has_value()) {
|
||||||
|
this->announcement_pipeline_->start_url(playlist_item.url.value());
|
||||||
}
|
}
|
||||||
|
this->announcement_pipeline_->set_pause_state(false);
|
||||||
}
|
}
|
||||||
this->announcement_playlist_.push_back(playlist_item);
|
this->announcement_playlist_.push_back(playlist_item);
|
||||||
} else {
|
} else {
|
||||||
// Media playlist/pipeline
|
|
||||||
|
|
||||||
if (!enqueue) {
|
if (!enqueue) {
|
||||||
// Clear the queue and ensure the loaded next item doesn't start playing
|
// Ensure the loaded next item doesn't start playing, clear the queue, start the file, and unpause
|
||||||
this->cancel_timeout("next_media");
|
this->cancel_timeout("next_media");
|
||||||
this->media_playlist_.clear();
|
this->media_playlist_.clear();
|
||||||
}
|
if (media_command.file.has_value()) {
|
||||||
|
|
||||||
this->is_paused_ = false;
|
|
||||||
PlaylistItem playlist_item;
|
|
||||||
if (new_url) {
|
|
||||||
playlist_item.url = this->media_url_;
|
|
||||||
if (!enqueue) {
|
|
||||||
// Not adding to the queue, so directly start playback and internally unpause the pipeline
|
|
||||||
this->media_pipeline_->start_url(playlist_item.url.value());
|
|
||||||
this->media_pipeline_->set_pause_state(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
playlist_item.file = this->media_file_;
|
|
||||||
if (!enqueue) {
|
|
||||||
// Not adding to the queue, so directly start playback and internally unpause the pipeline
|
|
||||||
this->media_pipeline_->start_file(playlist_item.file.value());
|
this->media_pipeline_->start_file(playlist_item.file.value());
|
||||||
this->media_pipeline_->set_pause_state(false);
|
} else if (media_command.url.has_value()) {
|
||||||
|
this->media_pipeline_->start_url(playlist_item.url.value());
|
||||||
}
|
}
|
||||||
|
this->media_pipeline_->set_pause_state(false);
|
||||||
}
|
}
|
||||||
this->media_playlist_.push_back(playlist_item);
|
this->media_playlist_.push_back(playlist_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "Error starting the audio pipeline: %s", esp_err_to_name(err));
|
|
||||||
this->status_set_error();
|
|
||||||
} else {
|
|
||||||
this->status_clear_error();
|
|
||||||
}
|
|
||||||
|
|
||||||
return; // Don't process the new file play command further
|
return; // Don't process the new file play command further
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,12 +400,10 @@ void SpeakerMediaPlayer::play_file(audio::AudioFile *media_file, bool announceme
|
|||||||
|
|
||||||
MediaCallCommand media_command;
|
MediaCallCommand media_command;
|
||||||
|
|
||||||
media_command.new_file = true;
|
media_command.file = media_file;
|
||||||
if (this->single_pipeline_() || announcement) {
|
if (this->single_pipeline_() || announcement) {
|
||||||
this->announcement_file_ = media_file;
|
|
||||||
media_command.announce = true;
|
media_command.announce = true;
|
||||||
} else {
|
} else {
|
||||||
this->media_file_ = media_file;
|
|
||||||
media_command.announce = false;
|
media_command.announce = false;
|
||||||
}
|
}
|
||||||
media_command.enqueue = enqueue;
|
media_command.enqueue = enqueue;
|
||||||
@@ -456,14 +425,8 @@ void SpeakerMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (call.get_media_url().has_value()) {
|
if (call.get_media_url().has_value()) {
|
||||||
std::string new_uri = call.get_media_url().value();
|
media_command.url = new std::string(
|
||||||
|
call.get_media_url().value()); // Must be manually deleted after receiving media_command from a queue
|
||||||
media_command.new_url = true;
|
|
||||||
if (this->single_pipeline_() || (call.get_announcement().has_value() && call.get_announcement().value())) {
|
|
||||||
this->announcement_url_ = new_uri;
|
|
||||||
} else {
|
|
||||||
this->media_url_ = new_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (call.get_command().has_value()) {
|
if (call.get_command().has_value()) {
|
||||||
if (call.get_command().value() == media_player::MEDIA_PLAYER_COMMAND_ENQUEUE) {
|
if (call.get_command().value() == media_player::MEDIA_PLAYER_COMMAND_ENQUEUE) {
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ struct MediaCallCommand {
|
|||||||
optional<media_player::MediaPlayerCommand> command;
|
optional<media_player::MediaPlayerCommand> command;
|
||||||
optional<float> volume;
|
optional<float> volume;
|
||||||
optional<bool> announce;
|
optional<bool> announce;
|
||||||
optional<bool> new_url;
|
optional<std::string *> url; // Must be manually deleted after receiving this struct from a queue
|
||||||
optional<bool> new_file;
|
optional<audio::AudioFile *> file;
|
||||||
optional<bool> enqueue;
|
optional<bool> enqueue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,15 +109,11 @@ class SpeakerMediaPlayer : public Component, public media_player::MediaPlayer {
|
|||||||
|
|
||||||
optional<media_player::MediaPlayerSupportedFormat> media_format_;
|
optional<media_player::MediaPlayerSupportedFormat> media_format_;
|
||||||
AudioPipelineState media_pipeline_state_{AudioPipelineState::STOPPED};
|
AudioPipelineState media_pipeline_state_{AudioPipelineState::STOPPED};
|
||||||
std::string media_url_{}; // only modified by control function
|
|
||||||
audio::AudioFile *media_file_{}; // only modified by play_file function
|
|
||||||
bool media_repeat_one_{false};
|
bool media_repeat_one_{false};
|
||||||
uint32_t media_playlist_delay_ms_{0};
|
uint32_t media_playlist_delay_ms_{0};
|
||||||
|
|
||||||
optional<media_player::MediaPlayerSupportedFormat> announcement_format_;
|
optional<media_player::MediaPlayerSupportedFormat> announcement_format_;
|
||||||
AudioPipelineState announcement_pipeline_state_{AudioPipelineState::STOPPED};
|
AudioPipelineState announcement_pipeline_state_{AudioPipelineState::STOPPED};
|
||||||
std::string announcement_url_{}; // only modified by control function
|
|
||||||
audio::AudioFile *announcement_file_{}; // only modified by play_file function
|
|
||||||
bool announcement_repeat_one_{false};
|
bool announcement_repeat_one_{false};
|
||||||
uint32_t announcement_playlist_delay_ms_{0};
|
uint32_t announcement_playlist_delay_ms_{0};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "2025.3.0b2"
|
__version__ = "2025.3.2"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
|||||||
@@ -189,10 +189,11 @@ def _is_target_platform(name):
|
|||||||
from esphome.loader import get_component
|
from esphome.loader import get_component
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if get_component(name, True).is_target_platform:
|
return get_component(name, True).is_target_platform
|
||||||
return True
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ lib_deps =
|
|||||||
DNSServer ; captive_portal (Arduino built-in)
|
DNSServer ; captive_portal (Arduino built-in)
|
||||||
esphome/ESP32-audioI2S@2.0.7 ; i2s_audio
|
esphome/ESP32-audioI2S@2.0.7 ; i2s_audio
|
||||||
droscy/esp_wireguard@0.4.2 ; wireguard
|
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||||
esphome/esp-audio-libs@1.1.2 ; audio
|
esphome/esp-audio-libs@1.1.3 ; audio
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:arduino.build_flags}
|
${common:arduino.build_flags}
|
||||||
@@ -149,7 +149,7 @@ lib_deps =
|
|||||||
${common:idf.lib_deps}
|
${common:idf.lib_deps}
|
||||||
droscy/esp_wireguard@0.4.2 ; wireguard
|
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||||
kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word
|
kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word
|
||||||
esphome/esp-audio-libs@1.1.2 ; audio
|
esphome/esp-audio-libs@1.1.3 ; audio
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:idf.build_flags}
|
${common:idf.build_flags}
|
||||||
-Wno-nonnull-compare
|
-Wno-nonnull-compare
|
||||||
|
|||||||
3246
tests/components/font/Tamzen5x9b.bdf
Normal file
3246
tests/components/font/Tamzen5x9b.bdf
Normal file
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,9 @@ font:
|
|||||||
id: default_font
|
id: default_font
|
||||||
- file: $component_dir/x11.pcf
|
- file: $component_dir/x11.pcf
|
||||||
id: pcf_font
|
id: pcf_font
|
||||||
|
- file: $component_dir/Tamzen5x9b.bdf
|
||||||
|
id: bdf_font
|
||||||
|
size: 7
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
scl: ${i2c_scl}
|
scl: ${i2c_scl}
|
||||||
|
|||||||
Reference in New Issue
Block a user