esphome 2024.10.3__py3-none-any.whl → 2024.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esphome/__main__.py +22 -4
- esphome/automation.py +29 -2
- esphome/components/animation/__init__.py +5 -8
- esphome/components/animation/animation.cpp +1 -1
- esphome/components/audio/__init__.py +9 -0
- esphome/components/audio/audio.h +21 -0
- esphome/components/axs15231/__init__.py +6 -0
- esphome/components/axs15231/touchscreen/__init__.py +36 -0
- esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +64 -0
- esphome/components/axs15231/touchscreen/axs15231_touchscreen.h +27 -0
- esphome/components/bme68x_bsec2/__init__.py +1 -1
- esphome/components/bme68x_bsec2/bme68x_bsec2.cpp +50 -47
- esphome/components/bme68x_bsec2/bme68x_bsec2.h +0 -2
- esphome/components/bytebuffer/__init__.py +5 -0
- esphome/components/bytebuffer/bytebuffer.h +421 -0
- esphome/components/climate/__init__.py +14 -13
- esphome/components/datetime/__init__.py +3 -3
- esphome/components/debug/debug_esp32.cpp +16 -8
- esphome/components/dfplayer/dfplayer.cpp +132 -6
- esphome/components/dfplayer/dfplayer.h +19 -53
- esphome/components/display/display.cpp +142 -0
- esphome/components/display/display.h +7 -0
- esphome/components/es8311/__init__.py +0 -0
- esphome/components/es8311/audio_dac.py +70 -0
- esphome/components/es8311/es8311.cpp +227 -0
- esphome/components/es8311/es8311.h +135 -0
- esphome/components/es8311/es8311_const.h +195 -0
- esphome/components/esp32/boards.py +199 -1
- esphome/components/esp32/gpio.py +3 -1
- esphome/components/esp32_ble/const_esp32c6.h +7 -0
- esphome/components/esp32_ble_client/ble_client_base.h +1 -1
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +3 -0
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +2 -1
- esphome/components/esp32_rmt_led_strip/led_strip.cpp +2 -2
- esphome/components/esp32_rmt_led_strip/led_strip.h +2 -0
- esphome/components/esp32_rmt_led_strip/light.py +3 -1
- esphome/components/esp8266/gpio.py +7 -5
- esphome/components/ethernet/__init__.py +55 -1
- esphome/components/ethernet/ethernet_component.cpp +14 -1
- esphome/components/ethernet/ethernet_component.h +7 -1
- esphome/components/font/__init__.py +213 -108
- esphome/components/gp8403/output/__init__.py +1 -1
- esphome/components/host/gpio.py +6 -4
- esphome/components/http_request/__init__.py +12 -0
- esphome/components/http_request/http_request.h +65 -3
- esphome/components/http_request/http_request_arduino.cpp +4 -3
- esphome/components/http_request/http_request_idf.cpp +12 -14
- esphome/components/http_request/ota/ota_http_request.cpp +1 -1
- esphome/components/http_request/update/http_request_update.cpp +1 -1
- esphome/components/i2c_device/__init__.py +26 -0
- esphome/components/i2c_device/i2c_device.cpp +17 -0
- esphome/components/i2c_device/i2c_device.h +18 -0
- esphome/components/i2s_audio/__init__.py +1 -3
- esphome/components/i2s_audio/speaker/__init__.py +12 -4
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +426 -200
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +92 -33
- esphome/components/ili9xxx/display.py +5 -1
- esphome/components/image/__init__.py +5 -8
- esphome/components/image/image.cpp +14 -14
- esphome/components/image/image.h +20 -24
- esphome/components/internal_temperature/internal_temperature.cpp +51 -2
- esphome/components/internal_temperature/internal_temperature.h +1 -0
- esphome/components/ld2420/ld2420.cpp +1 -1
- esphome/components/libretiny/gpio.py +4 -2
- esphome/components/light/__init__.py +32 -1
- esphome/components/light/automation.py +39 -32
- esphome/components/light/effects.py +36 -36
- esphome/components/light/light_state.cpp +6 -16
- esphome/components/light/light_state.h +34 -0
- esphome/components/light/types.py +3 -1
- esphome/components/logger/logger_esp32.cpp +15 -0
- esphome/components/lvgl/__init__.py +202 -95
- esphome/components/lvgl/automation.py +42 -40
- esphome/components/lvgl/binary_sensor/__init__.py +8 -15
- esphome/components/lvgl/defines.py +14 -8
- esphome/components/lvgl/encoders.py +11 -8
- esphome/components/lvgl/keypads.py +77 -0
- esphome/components/lvgl/light/__init__.py +6 -8
- esphome/components/lvgl/lv_validation.py +2 -4
- esphome/components/lvgl/lvcode.py +3 -9
- esphome/components/lvgl/lvgl_esphome.cpp +210 -89
- esphome/components/lvgl/lvgl_esphome.h +113 -30
- esphome/components/lvgl/lvgl_proxy.h +17 -0
- esphome/components/lvgl/number/__init__.py +10 -15
- esphome/components/lvgl/schemas.py +4 -2
- esphome/components/lvgl/select/__init__.py +12 -37
- esphome/components/lvgl/select/lvgl_select.h +27 -33
- esphome/components/lvgl/sensor/__init__.py +8 -14
- esphome/components/lvgl/styles.py +3 -4
- esphome/components/lvgl/switch/__init__.py +8 -13
- esphome/components/lvgl/text/__init__.py +5 -6
- esphome/components/lvgl/text_sensor/__init__.py +15 -15
- esphome/components/lvgl/touchscreens.py +2 -3
- esphome/components/lvgl/trigger.py +7 -9
- esphome/components/lvgl/types.py +9 -3
- esphome/components/lvgl/widgets/__init__.py +32 -21
- esphome/components/lvgl/widgets/dropdown.py +22 -10
- esphome/components/lvgl/widgets/msgbox.py +6 -5
- esphome/components/lvgl/widgets/obj.py +4 -2
- esphome/components/lvgl/widgets/page.py +3 -2
- esphome/components/lvgl/widgets/qrcode.py +54 -0
- esphome/components/lvgl/widgets/roller.py +21 -14
- esphome/components/lvgl/widgets/tileview.py +2 -1
- esphome/components/max17043/__init__.py +1 -0
- esphome/components/max17043/automation.h +20 -0
- esphome/components/max17043/max17043.cpp +98 -0
- esphome/components/max17043/max17043.h +29 -0
- esphome/components/max17043/sensor.py +77 -0
- esphome/components/media_player/__init__.py +11 -0
- esphome/components/media_player/automation.h +10 -0
- esphome/components/media_player/media_player.cpp +4 -0
- esphome/components/midea/air_conditioner.cpp +17 -1
- esphome/components/mlx90393/sensor.py +1 -1
- esphome/components/modbus_controller/__init__.py +31 -1
- esphome/components/modbus_controller/automation.h +16 -0
- esphome/components/modbus_controller/const.py +2 -0
- esphome/components/modbus_controller/modbus_controller.cpp +14 -2
- esphome/components/modbus_controller/modbus_controller.h +9 -0
- esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +40 -21
- esphome/components/mopeka_pro_check/mopeka_pro_check.h +9 -2
- esphome/components/mopeka_pro_check/sensor.py +41 -0
- esphome/components/mqtt/__init__.py +36 -0
- esphome/components/mqtt/mqtt_client.cpp +27 -3
- esphome/components/mqtt/mqtt_client.h +27 -2
- esphome/components/mqtt/mqtt_climate.cpp +4 -2
- esphome/components/mqtt/mqtt_component.cpp +6 -0
- esphome/components/mqtt/mqtt_component.h +4 -0
- esphome/components/mqtt/mqtt_const.h +6 -0
- esphome/components/online_image/online_image.cpp +2 -8
- esphome/components/online_image/online_image.h +2 -6
- esphome/components/opentherm/__init__.py +35 -9
- esphome/components/opentherm/binary_sensor/__init__.py +33 -0
- esphome/components/opentherm/const.py +11 -0
- esphome/components/opentherm/generate.py +142 -0
- esphome/components/opentherm/hub.cpp +130 -24
- esphome/components/opentherm/hub.h +62 -9
- esphome/components/opentherm/input.h +18 -0
- esphome/components/opentherm/input.py +51 -0
- esphome/components/opentherm/number/__init__.py +74 -0
- esphome/components/opentherm/number/number.cpp +40 -0
- esphome/components/opentherm/number/number.h +31 -0
- esphome/components/opentherm/opentherm.cpp +30 -0
- esphome/components/opentherm/opentherm.h +34 -2
- esphome/components/opentherm/opentherm_macros.h +151 -0
- esphome/components/opentherm/output/__init__.py +47 -0
- esphome/components/opentherm/output/output.cpp +18 -0
- esphome/components/opentherm/output/output.h +33 -0
- esphome/components/opentherm/schema.py +814 -0
- esphome/components/opentherm/sensor/__init__.py +51 -0
- esphome/components/opentherm/switch/__init__.py +43 -0
- esphome/components/opentherm/switch/switch.cpp +28 -0
- esphome/components/opentherm/switch/switch.h +20 -0
- esphome/components/opentherm/validate.py +31 -0
- esphome/components/pcd8544/display.py +8 -4
- esphome/components/prometheus/prometheus_handler.cpp +176 -14
- esphome/components/prometheus/prometheus_handler.h +25 -7
- esphome/components/qspi_amoled/display.py +1 -141
- esphome/components/qspi_dbi/display.py +185 -0
- esphome/components/qspi_dbi/models.py +64 -0
- esphome/components/{qspi_amoled/qspi_amoled.cpp → qspi_dbi/qspi_dbi.cpp} +95 -46
- esphome/components/{qspi_amoled/qspi_amoled.h → qspi_dbi/qspi_dbi.h} +26 -15
- esphome/components/rp2040/__init__.py +6 -3
- esphome/components/rp2040/gpio.py +5 -3
- esphome/components/rtttl/rtttl.cpp +4 -1
- esphome/components/rtttl/rtttl.h +1 -0
- esphome/components/sdl/sdl_esphome.cpp +22 -5
- esphome/components/sdl/sdl_esphome.h +1 -0
- esphome/components/sdm_meter/sdm_meter.cpp +1 -1
- esphome/components/sensor/__init__.py +18 -8
- esphome/components/sensor/filter.cpp +19 -18
- esphome/components/sensor/filter.h +9 -10
- esphome/components/sgp4x/sgp4x.cpp +40 -74
- esphome/components/sgp4x/sgp4x.h +5 -3
- esphome/components/speaker/__init__.py +51 -5
- esphome/components/speaker/automation.h +25 -0
- esphome/components/speaker/speaker.h +72 -1
- esphome/components/spi/__init__.py +15 -14
- esphome/components/spi_device/__init__.py +4 -15
- esphome/components/ssd1306_spi/display.py +6 -2
- esphome/components/ssd1322_spi/display.py +6 -2
- esphome/components/ssd1325_spi/display.py +6 -2
- esphome/components/ssd1327_spi/display.py +6 -2
- esphome/components/ssd1331_spi/display.py +6 -2
- esphome/components/ssd1351_spi/display.py +6 -2
- esphome/components/st7567_spi/display.py +6 -2
- esphome/components/st7701s/display.py +5 -1
- esphome/components/st7735/display.py +10 -5
- esphome/components/st7789v/display.py +12 -7
- esphome/components/statsd/statsd.cpp +2 -0
- esphome/components/statsd/statsd.h +2 -0
- esphome/components/sun/sun.h +3 -0
- esphome/components/tc74/__init__.py +1 -0
- esphome/components/tc74/sensor.py +32 -0
- esphome/components/tc74/tc74.cpp +68 -0
- esphome/components/tc74/tc74.h +28 -0
- esphome/components/touchscreen/__init__.py +41 -50
- esphome/components/touchscreen/touchscreen.h +4 -8
- esphome/components/tuya/fan/tuya_fan.cpp +1 -1
- esphome/components/udp/udp_component.cpp +6 -3
- esphome/components/udp/udp_component.h +4 -2
- esphome/components/waveshare_epaper/display.py +6 -2
- esphome/components/web_server/web_server.cpp +22 -0
- esphome/components/web_server/web_server.h +3 -0
- esphome/components/weikai/weikai.h +2 -2
- esphome/components/wifi/wifi_component.cpp +2 -2
- esphome/components/wifi/wifi_component_esp32_arduino.cpp +4 -4
- esphome/components/wifi/wifi_component_esp8266.cpp +4 -4
- esphome/components/wifi/wifi_component_esp_idf.cpp +2 -2
- esphome/components/xpt2046/touchscreen/__init__.py +7 -32
- esphome/config_validation.py +3 -1
- esphome/const.py +9 -2
- esphome/core/defines.h +8 -2
- esphome/core/helpers.cpp +32 -17
- esphome/core/helpers.h +32 -16
- esphome/core/ring_buffer.cpp +2 -2
- esphome/core/ring_buffer.h +2 -2
- esphome/dashboard/core.py +25 -0
- esphome/dashboard/status/mdns.py +3 -4
- esphome/dashboard/web_server.py +54 -19
- esphome/espota2.py +36 -35
- esphome/helpers.py +68 -16
- esphome/mqtt.py +9 -2
- esphome/storage_json.py +4 -0
- esphome/writer.py +7 -18
- esphome/zeroconf.py +8 -6
- {esphome-2024.10.3.dist-info → esphome-2024.11.0.dist-info}/METADATA +7 -5
- {esphome-2024.10.3.dist-info → esphome-2024.11.0.dist-info}/RECORD +232 -186
- esphome/core/bytebuffer.cpp +0 -167
- esphome/core/bytebuffer.h +0 -144
- /esphome/components/{qspi_amoled → qspi_dbi}/__init__.py +0 -0
- {esphome-2024.10.3.dist-info → esphome-2024.11.0.dist-info}/LICENSE +0 -0
- {esphome-2024.10.3.dist-info → esphome-2024.11.0.dist-info}/WHEEL +0 -0
- {esphome-2024.10.3.dist-info → esphome-2024.11.0.dist-info}/entry_points.txt +0 -0
- {esphome-2024.10.3.dist-info → esphome-2024.11.0.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
|
|
1
|
+
from collections.abc import Iterable
|
1
2
|
import functools
|
2
3
|
import hashlib
|
3
4
|
import logging
|
@@ -5,6 +6,8 @@ import os
|
|
5
6
|
from pathlib import Path
|
6
7
|
import re
|
7
8
|
|
9
|
+
import freetype
|
10
|
+
import glyphsets
|
8
11
|
from packaging import version
|
9
12
|
import requests
|
10
13
|
|
@@ -43,6 +46,18 @@ GlyphData = font_ns.struct("GlyphData")
|
|
43
46
|
CONF_BPP = "bpp"
|
44
47
|
CONF_EXTRAS = "extras"
|
45
48
|
CONF_FONTS = "fonts"
|
49
|
+
CONF_GLYPHSETS = "glyphsets"
|
50
|
+
CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs"
|
51
|
+
|
52
|
+
|
53
|
+
# Cache loaded freetype fonts
|
54
|
+
class FontCache(dict):
|
55
|
+
def __missing__(self, key):
|
56
|
+
res = self[key] = freetype.Face(key)
|
57
|
+
return res
|
58
|
+
|
59
|
+
|
60
|
+
FONT_CACHE = FontCache()
|
46
61
|
|
47
62
|
|
48
63
|
def glyph_comparator(x, y):
|
@@ -59,36 +74,106 @@ def glyph_comparator(x, y):
|
|
59
74
|
return -1
|
60
75
|
if len(x_) > len(y_):
|
61
76
|
return 1
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
77
|
+
return 0
|
78
|
+
|
79
|
+
|
80
|
+
def flatten(lists) -> list:
|
81
|
+
"""
|
82
|
+
Given a list of lists, flatten it to a single list of all elements of all lists.
|
83
|
+
This wraps itertools.chain.from_iterable to make it more readable, and return a list
|
84
|
+
rather than a single use iterable.
|
85
|
+
"""
|
86
|
+
from itertools import chain
|
87
|
+
|
88
|
+
return list(chain.from_iterable(lists))
|
89
|
+
|
90
|
+
|
91
|
+
def check_missing_glyphs(file, codepoints: Iterable, warning: bool = False):
|
92
|
+
"""
|
93
|
+
Check that the given font file actually contains the requested glyphs
|
94
|
+
:param file: A Truetype font file
|
95
|
+
:param codepoints: A list of codepoints to check
|
96
|
+
:param warning: If true, log a warning instead of raising an exception
|
97
|
+
"""
|
98
|
+
|
99
|
+
font = FONT_CACHE[file]
|
100
|
+
missing = [chr(x) for x in codepoints if font.get_char_index(x) == 0]
|
101
|
+
if missing:
|
102
|
+
# Only list up to 10 missing glyphs
|
103
|
+
missing.sort(key=functools.cmp_to_key(glyph_comparator))
|
104
|
+
count = len(missing)
|
105
|
+
missing = missing[:10]
|
106
|
+
missing_str = "\n ".join(
|
107
|
+
f"{x} ({x.encode('unicode_escape')})" for x in missing
|
108
|
+
)
|
109
|
+
if count > 10:
|
110
|
+
missing_str += f"\n and {count - 10} more."
|
111
|
+
message = f"Font {Path(file).name} is missing {count} glyph{'s' if count != 1 else ''}:\n {missing_str}"
|
112
|
+
if warning:
|
113
|
+
_LOGGER.warning(message)
|
114
|
+
else:
|
115
|
+
raise cv.Invalid(message)
|
73
116
|
|
74
|
-
font_map = {}
|
75
117
|
|
118
|
+
def validate_glyphs(config):
|
119
|
+
"""
|
120
|
+
Check for duplicate codepoints, then check that all requested codepoints actually
|
121
|
+
have glyphs defined in the appropriate font file.
|
122
|
+
"""
|
76
123
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
124
|
+
# Collect all glyph codepoints and flatten to a list of chars
|
125
|
+
glyphspoints = flatten(
|
126
|
+
[x[CONF_GLYPHS] for x in config[CONF_EXTRAS]] + config[CONF_GLYPHS]
|
127
|
+
)
|
128
|
+
# Convert a list of strings to a list of chars (one char strings)
|
129
|
+
glyphspoints = flatten([list(x) for x in glyphspoints])
|
130
|
+
if len(set(glyphspoints)) != len(glyphspoints):
|
131
|
+
duplicates = {x for x in glyphspoints if glyphspoints.count(x) > 1}
|
132
|
+
dup_str = ", ".join(f"{x} ({x.encode('unicode_escape')})" for x in duplicates)
|
133
|
+
raise cv.Invalid(
|
134
|
+
f"Found duplicate glyph{'s' if len(duplicates) != 1 else ''}: {dup_str}"
|
86
135
|
)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
136
|
+
# convert to codepoints
|
137
|
+
glyphspoints = {ord(x) for x in glyphspoints}
|
138
|
+
fileconf = config[CONF_FILE]
|
139
|
+
setpoints = set(
|
140
|
+
flatten([glyphsets.unicodes_per_glyphset(x) for x in config[CONF_GLYPHSETS]])
|
141
|
+
)
|
142
|
+
# Make setpoints and glyphspoints disjoint
|
143
|
+
setpoints.difference_update(glyphspoints)
|
144
|
+
if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP:
|
145
|
+
# Pillow only allows 256 glyphs per bitmap font. Not sure if that is a Pillow limitation
|
146
|
+
# or a file format limitation
|
147
|
+
if any(x >= 256 for x in setpoints.copy().union(glyphspoints)):
|
148
|
+
raise cv.Invalid("Codepoints in bitmap fonts must be in the range 0-255")
|
149
|
+
else:
|
150
|
+
# for TT fonts, check that glyphs are actually present
|
151
|
+
# Check extras against their own font, exclude from parent font codepoints
|
152
|
+
for extra in config[CONF_EXTRAS]:
|
153
|
+
points = {ord(x) for x in flatten(extra[CONF_GLYPHS])}
|
154
|
+
glyphspoints.difference_update(points)
|
155
|
+
setpoints.difference_update(points)
|
156
|
+
check_missing_glyphs(extra[CONF_FILE][CONF_PATH], points)
|
157
|
+
|
158
|
+
# A named glyph that can't be provided is an error
|
159
|
+
check_missing_glyphs(fileconf[CONF_PATH], glyphspoints)
|
160
|
+
# A missing glyph from a set is a warning.
|
161
|
+
if not config[CONF_IGNORE_MISSING_GLYPHS]:
|
162
|
+
check_missing_glyphs(fileconf[CONF_PATH], setpoints, warning=True)
|
163
|
+
|
164
|
+
# Populate the default after the above checks so that use of the default doesn't trigger errors
|
165
|
+
if not config[CONF_GLYPHS] and not config[CONF_GLYPHSETS]:
|
166
|
+
if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP:
|
167
|
+
config[CONF_GLYPHS] = [DEFAULT_GLYPHS]
|
168
|
+
else:
|
169
|
+
# set a default glyphset, intersected with what the font actually offers
|
170
|
+
font = FONT_CACHE[fileconf[CONF_PATH]]
|
171
|
+
config[CONF_GLYPHS] = [
|
172
|
+
chr(x)
|
173
|
+
for x in glyphsets.unicodes_per_glyphset(DEFAULT_GLYPHSET)
|
174
|
+
if font.get_char_index(x) != 0
|
175
|
+
]
|
176
|
+
|
92
177
|
return config
|
93
178
|
|
94
179
|
|
@@ -98,13 +183,13 @@ def validate_pillow_installed(value):
|
|
98
183
|
except ImportError as err:
|
99
184
|
raise cv.Invalid(
|
100
185
|
"Please install the pillow python package to use this feature. "
|
101
|
-
'(pip install "pillow==10.
|
186
|
+
'(pip install "pillow==10.4.0")'
|
102
187
|
) from err
|
103
188
|
|
104
|
-
if version.parse(PIL.__version__) != version.parse("10.
|
189
|
+
if version.parse(PIL.__version__) != version.parse("10.4.0"):
|
105
190
|
raise cv.Invalid(
|
106
|
-
"Please update your pillow installation to 10.
|
107
|
-
'(pip install "pillow==10.
|
191
|
+
"Please update your pillow installation to 10.4.0. "
|
192
|
+
'(pip install "pillow==10.4.0")'
|
108
193
|
)
|
109
194
|
|
110
195
|
return value
|
@@ -120,7 +205,7 @@ def validate_truetype_file(value):
|
|
120
205
|
)
|
121
206
|
if not any(map(value.lower().endswith, FONT_EXTENSIONS)):
|
122
207
|
raise cv.Invalid(f"Only {FONT_EXTENSIONS} files are supported.")
|
123
|
-
return cv.file_(value)
|
208
|
+
return CORE.relative_config_path(cv.file_(value))
|
124
209
|
|
125
210
|
|
126
211
|
TYPE_LOCAL = "local"
|
@@ -139,6 +224,10 @@ LOCAL_BITMAP_SCHEMA = cv.Schema(
|
|
139
224
|
}
|
140
225
|
)
|
141
226
|
|
227
|
+
FULLPATH_SCHEMA = cv.maybe_simple_value(
|
228
|
+
{cv.Required(CONF_PATH): cv.string}, key=CONF_PATH
|
229
|
+
)
|
230
|
+
|
142
231
|
CONF_ITALIC = "italic"
|
143
232
|
FONT_WEIGHTS = {
|
144
233
|
"thin": 100,
|
@@ -167,13 +256,13 @@ def _compute_local_font_path(value: dict) -> Path:
|
|
167
256
|
return base_dir / key
|
168
257
|
|
169
258
|
|
170
|
-
def get_font_path(value,
|
171
|
-
if
|
259
|
+
def get_font_path(value, font_type) -> Path:
|
260
|
+
if font_type == TYPE_GFONTS:
|
172
261
|
name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1"
|
173
262
|
return external_files.compute_local_file_dir(DOMAIN) / f"{name}.ttf"
|
174
|
-
if
|
263
|
+
if font_type == TYPE_WEB:
|
175
264
|
return _compute_local_font_path(value) / "font.ttf"
|
176
|
-
|
265
|
+
assert False
|
177
266
|
|
178
267
|
|
179
268
|
def download_gfont(value):
|
@@ -203,7 +292,7 @@ def download_gfont(value):
|
|
203
292
|
_LOGGER.debug("download_gfont: ttf_url=%s", ttf_url)
|
204
293
|
|
205
294
|
external_files.download_content(ttf_url, path)
|
206
|
-
return
|
295
|
+
return FULLPATH_SCHEMA(path)
|
207
296
|
|
208
297
|
|
209
298
|
def download_web_font(value):
|
@@ -212,7 +301,7 @@ def download_web_font(value):
|
|
212
301
|
|
213
302
|
external_files.download_content(url, path)
|
214
303
|
_LOGGER.debug("download_web_font: path=%s", path)
|
215
|
-
return
|
304
|
+
return FULLPATH_SCHEMA(path)
|
216
305
|
|
217
306
|
|
218
307
|
EXTERNAL_FONT_SCHEMA = cv.Schema(
|
@@ -225,7 +314,6 @@ EXTERNAL_FONT_SCHEMA = cv.Schema(
|
|
225
314
|
}
|
226
315
|
)
|
227
316
|
|
228
|
-
|
229
317
|
GFONTS_SCHEMA = cv.All(
|
230
318
|
EXTERNAL_FONT_SCHEMA.extend(
|
231
319
|
{
|
@@ -259,10 +347,10 @@ def validate_file_shorthand(value):
|
|
259
347
|
}
|
260
348
|
if weight is not None:
|
261
349
|
data[CONF_WEIGHT] = weight[1:]
|
262
|
-
return
|
350
|
+
return font_file_schema(data)
|
263
351
|
|
264
352
|
if value.startswith("http://") or value.startswith("https://"):
|
265
|
-
return
|
353
|
+
return font_file_schema(
|
266
354
|
{
|
267
355
|
CONF_TYPE: TYPE_WEB,
|
268
356
|
CONF_URL: value,
|
@@ -270,14 +358,15 @@ def validate_file_shorthand(value):
|
|
270
358
|
)
|
271
359
|
|
272
360
|
if value.endswith(".pcf") or value.endswith(".bdf"):
|
273
|
-
|
274
|
-
|
275
|
-
CONF_TYPE: TYPE_LOCAL_BITMAP,
|
276
|
-
CONF_PATH: value,
|
277
|
-
}
|
361
|
+
value = convert_bitmap_to_pillow_font(
|
362
|
+
CORE.relative_config_path(cv.file_(value))
|
278
363
|
)
|
364
|
+
return {
|
365
|
+
CONF_TYPE: TYPE_LOCAL_BITMAP,
|
366
|
+
CONF_PATH: value,
|
367
|
+
}
|
279
368
|
|
280
|
-
return
|
369
|
+
return font_file_schema(
|
281
370
|
{
|
282
371
|
CONF_TYPE: TYPE_LOCAL,
|
283
372
|
CONF_PATH: value,
|
@@ -295,31 +384,35 @@ TYPED_FILE_SCHEMA = cv.typed_schema(
|
|
295
384
|
)
|
296
385
|
|
297
386
|
|
298
|
-
def
|
387
|
+
def font_file_schema(value):
|
299
388
|
if isinstance(value, str):
|
300
389
|
return validate_file_shorthand(value)
|
301
390
|
return TYPED_FILE_SCHEMA(value)
|
302
391
|
|
303
392
|
|
304
|
-
|
393
|
+
# Default if no glyphs or glyphsets are provided
|
394
|
+
DEFAULT_GLYPHSET = "GF_Latin_Kernel"
|
395
|
+
# default for bitmap fonts
|
396
|
+
DEFAULT_GLYPHS = ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz<C2><B0>'
|
305
397
|
|
306
|
-
DEFAULT_GLYPHS = (
|
307
|
-
' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
308
|
-
)
|
309
398
|
CONF_RAW_GLYPH_ID = "raw_glyph_id"
|
310
399
|
|
311
400
|
FONT_SCHEMA = cv.Schema(
|
312
401
|
{
|
313
402
|
cv.Required(CONF_ID): cv.declare_id(Font),
|
314
|
-
cv.Required(CONF_FILE):
|
315
|
-
cv.Optional(CONF_GLYPHS, default=
|
403
|
+
cv.Required(CONF_FILE): font_file_schema,
|
404
|
+
cv.Optional(CONF_GLYPHS, default=[]): cv.ensure_list(cv.string_strict),
|
405
|
+
cv.Optional(CONF_GLYPHSETS, default=[]): cv.ensure_list(
|
406
|
+
cv.one_of(*glyphsets.defined_glyphsets())
|
407
|
+
),
|
408
|
+
cv.Optional(CONF_IGNORE_MISSING_GLYPHS, default=False): cv.boolean,
|
316
409
|
cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1),
|
317
410
|
cv.Optional(CONF_BPP, default=1): cv.one_of(1, 2, 4, 8),
|
318
|
-
cv.Optional(CONF_EXTRAS): cv.ensure_list(
|
411
|
+
cv.Optional(CONF_EXTRAS, default=[]): cv.ensure_list(
|
319
412
|
cv.Schema(
|
320
413
|
{
|
321
|
-
cv.Required(CONF_FILE):
|
322
|
-
cv.Required(CONF_GLYPHS):
|
414
|
+
cv.Required(CONF_FILE): font_file_schema,
|
415
|
+
cv.Required(CONF_GLYPHS): cv.ensure_list(cv.string_strict),
|
323
416
|
}
|
324
417
|
)
|
325
418
|
),
|
@@ -328,7 +421,7 @@ FONT_SCHEMA = cv.Schema(
|
|
328
421
|
},
|
329
422
|
)
|
330
423
|
|
331
|
-
CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA,
|
424
|
+
CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, validate_glyphs)
|
332
425
|
|
333
426
|
|
334
427
|
# PIL doesn't provide a consistent interface for both TrueType and bitmap
|
@@ -344,7 +437,7 @@ class TrueTypeFontWrapper:
|
|
344
437
|
return offset_x, offset_y
|
345
438
|
|
346
439
|
def getmask(self, glyph, **kwargs):
|
347
|
-
return self.font.getmask(glyph, **kwargs)
|
440
|
+
return self.font.getmask(str(glyph), **kwargs)
|
348
441
|
|
349
442
|
def getmetrics(self, glyphs):
|
350
443
|
return self.font.getmetrics()
|
@@ -359,7 +452,7 @@ class BitmapFontWrapper:
|
|
359
452
|
return 0, 0
|
360
453
|
|
361
454
|
def getmask(self, glyph, **kwargs):
|
362
|
-
return self.font.getmask(glyph, **kwargs)
|
455
|
+
return self.font.getmask(str(glyph), **kwargs)
|
363
456
|
|
364
457
|
def getmetrics(self, glyphs):
|
365
458
|
max_height = 0
|
@@ -367,28 +460,20 @@ class BitmapFontWrapper:
|
|
367
460
|
mask = self.getmask(glyph, mode="1")
|
368
461
|
_, height = mask.size
|
369
462
|
max_height = max(max_height, height)
|
370
|
-
return
|
463
|
+
return max_height, 0
|
371
464
|
|
372
465
|
|
373
466
|
class EFont:
|
374
|
-
def __init__(self, file, size,
|
375
|
-
self.
|
467
|
+
def __init__(self, file, size, codepoints):
|
468
|
+
self.codepoints = codepoints
|
469
|
+
path = file[CONF_PATH]
|
470
|
+
self.name = Path(path).name
|
376
471
|
ftype = file[CONF_TYPE]
|
377
472
|
if ftype == TYPE_LOCAL_BITMAP:
|
378
|
-
font = load_bitmap_font(
|
379
|
-
elif ftype == TYPE_LOCAL:
|
380
|
-
path = CORE.relative_config_path(file[CONF_PATH])
|
381
|
-
font = load_ttf_font(path, size)
|
382
|
-
elif ftype in (TYPE_GFONTS, TYPE_WEB):
|
383
|
-
path = get_font_path(file, ftype)
|
384
|
-
font = load_ttf_font(path, size)
|
473
|
+
self.font = load_bitmap_font(path)
|
385
474
|
else:
|
386
|
-
|
387
|
-
self.
|
388
|
-
self.ascent, self.descent = font.getmetrics(glyphs)
|
389
|
-
|
390
|
-
def has_glyph(self, glyph):
|
391
|
-
return glyph in self.glyphs
|
475
|
+
self.font = load_ttf_font(path, size)
|
476
|
+
self.ascent, self.descent = self.font.getmetrics(codepoints)
|
392
477
|
|
393
478
|
|
394
479
|
def convert_bitmap_to_pillow_font(filepath):
|
@@ -400,6 +485,7 @@ def convert_bitmap_to_pillow_font(filepath):
|
|
400
485
|
|
401
486
|
copy_file_if_changed(filepath, local_bitmap_font_file)
|
402
487
|
|
488
|
+
local_pil_font_file = local_bitmap_font_file.with_suffix(".pil")
|
403
489
|
with open(local_bitmap_font_file, "rb") as fp:
|
404
490
|
try:
|
405
491
|
try:
|
@@ -409,28 +495,22 @@ def convert_bitmap_to_pillow_font(filepath):
|
|
409
495
|
p = BdfFontFile.BdfFontFile(fp)
|
410
496
|
|
411
497
|
# Convert to pillow-formatted fonts, which have a .pil and .pbm extension.
|
412
|
-
p.save(
|
498
|
+
p.save(local_pil_font_file)
|
413
499
|
except (SyntaxError, OSError) as err:
|
414
500
|
raise core.EsphomeError(
|
415
501
|
f"Failed to parse as bitmap font: '{filepath}': {err}"
|
416
502
|
)
|
417
503
|
|
418
|
-
|
419
|
-
return cv.file_(local_pil_font_file)
|
504
|
+
return str(local_pil_font_file)
|
420
505
|
|
421
506
|
|
422
507
|
def load_bitmap_font(filepath):
|
423
508
|
from PIL import ImageFont
|
424
509
|
|
425
|
-
# Convert bpf and pcf files to pillow fonts, first.
|
426
|
-
pil_font_path = convert_bitmap_to_pillow_font(filepath)
|
427
|
-
|
428
510
|
try:
|
429
|
-
font = ImageFont.load(str(
|
511
|
+
font = ImageFont.load(str(filepath))
|
430
512
|
except Exception as e:
|
431
|
-
raise core.EsphomeError(
|
432
|
-
f"Failed to load bitmap font file: {pil_font_path} : {e}"
|
433
|
-
)
|
513
|
+
raise core.EsphomeError(f"Failed to load bitmap font file: {filepath}: {e}")
|
434
514
|
|
435
515
|
return BitmapFontWrapper(font)
|
436
516
|
|
@@ -441,7 +521,7 @@ def load_ttf_font(path, size):
|
|
441
521
|
try:
|
442
522
|
font = ImageFont.truetype(str(path), size)
|
443
523
|
except Exception as e:
|
444
|
-
raise core.EsphomeError(f"Could not load
|
524
|
+
raise core.EsphomeError(f"Could not load TrueType file {path}: {e}")
|
445
525
|
|
446
526
|
return TrueTypeFontWrapper(font)
|
447
527
|
|
@@ -456,14 +536,35 @@ class GlyphInfo:
|
|
456
536
|
|
457
537
|
|
458
538
|
async def to_code(config):
|
459
|
-
|
460
|
-
|
461
|
-
glyphs
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
539
|
+
"""
|
540
|
+
Collect all glyph codepoints, construct a map from a codepoint to a font file.
|
541
|
+
Codepoints are either explicit (glyphs key in top level or extras) or part of a glyphset.
|
542
|
+
Codepoints listed in extras use the extra font and override codepoints from glyphsets.
|
543
|
+
Achieve this by processing the base codepoints first, then the extras
|
544
|
+
"""
|
545
|
+
|
546
|
+
# get the codepoints from glyphsets and flatten to a set of chrs.
|
547
|
+
point_set: set[str] = {
|
548
|
+
chr(x)
|
549
|
+
for x in flatten(
|
550
|
+
[glyphsets.unicodes_per_glyphset(x) for x in config[CONF_GLYPHSETS]]
|
551
|
+
)
|
552
|
+
}
|
553
|
+
# get the codepoints from the glyphs key, flatten to a list of chrs and combine with the points from glyphsets
|
554
|
+
point_set.update(flatten(config[CONF_GLYPHS]))
|
555
|
+
size = config[CONF_SIZE]
|
556
|
+
# Create the codepoint to font file map
|
557
|
+
base_font = EFont(config[CONF_FILE], size, point_set)
|
558
|
+
point_font_map: dict[str, EFont] = {c: base_font for c in point_set}
|
559
|
+
# process extras, updating the map and extending the codepoint list
|
560
|
+
for extra in config[CONF_EXTRAS]:
|
561
|
+
extra_points = flatten(extra[CONF_GLYPHS])
|
562
|
+
point_set.update(extra_points)
|
563
|
+
extra_font = EFont(extra[CONF_FILE], size, extra_points)
|
564
|
+
point_font_map.update({c: extra_font for c in extra_points})
|
565
|
+
|
566
|
+
codepoints = list(point_set)
|
567
|
+
codepoints.sort(key=functools.cmp_to_key(glyph_comparator))
|
467
568
|
glyph_args = {}
|
468
569
|
data = []
|
469
570
|
bpp = config[CONF_BPP]
|
@@ -473,10 +574,11 @@ async def to_code(config):
|
|
473
574
|
else:
|
474
575
|
mode = "L"
|
475
576
|
scale = 256 // (1 << bpp)
|
476
|
-
for
|
477
|
-
|
478
|
-
|
479
|
-
|
577
|
+
# create the data array for all glyphs
|
578
|
+
for codepoint in codepoints:
|
579
|
+
font = point_font_map[codepoint]
|
580
|
+
mask = font.font.getmask(codepoint, mode=mode)
|
581
|
+
offset_x, offset_y = font.font.getoffset(codepoint)
|
480
582
|
width, height = mask.size
|
481
583
|
glyph_data = [0] * ((height * width * bpp + 7) // 8)
|
482
584
|
pos = 0
|
@@ -487,31 +589,34 @@ async def to_code(config):
|
|
487
589
|
if pixel & (1 << (bpp - bit_num - 1)):
|
488
590
|
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
|
489
591
|
pos += 1
|
490
|
-
glyph_args[
|
592
|
+
glyph_args[codepoint] = GlyphInfo(len(data), offset_x, offset_y, width, height)
|
491
593
|
data += glyph_data
|
492
594
|
|
493
595
|
rhs = [HexInt(x) for x in data]
|
494
596
|
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
495
597
|
|
598
|
+
# Create the glyph table that points to data in the above array.
|
496
599
|
glyph_initializer = []
|
497
|
-
for
|
600
|
+
for codepoint in codepoints:
|
498
601
|
glyph_initializer.append(
|
499
602
|
cg.StructInitializer(
|
500
603
|
GlyphData,
|
501
604
|
(
|
502
605
|
"a_char",
|
503
|
-
cg.RawExpression(
|
606
|
+
cg.RawExpression(
|
607
|
+
f"(const uint8_t *){cpp_string_escape(codepoint)}"
|
608
|
+
),
|
504
609
|
),
|
505
610
|
(
|
506
611
|
"data",
|
507
612
|
cg.RawExpression(
|
508
|
-
f"{str(prog_arr)} + {str(glyph_args[
|
613
|
+
f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}"
|
509
614
|
),
|
510
615
|
),
|
511
|
-
("offset_x", glyph_args[
|
512
|
-
("offset_y", glyph_args[
|
513
|
-
("width", glyph_args[
|
514
|
-
("height", glyph_args[
|
616
|
+
("offset_x", glyph_args[codepoint].offset_x),
|
617
|
+
("offset_y", glyph_args[codepoint].offset_y),
|
618
|
+
("width", glyph_args[codepoint].width),
|
619
|
+
("height", glyph_args[codepoint].height),
|
515
620
|
)
|
516
621
|
)
|
517
622
|
|
@@ -521,7 +626,7 @@ async def to_code(config):
|
|
521
626
|
config[CONF_ID],
|
522
627
|
glyphs,
|
523
628
|
len(glyph_initializer),
|
524
|
-
|
525
|
-
|
629
|
+
base_font.ascent,
|
630
|
+
base_font.ascent + base_font.descent,
|
526
631
|
bpp,
|
527
632
|
)
|
@@ -16,7 +16,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
|
16
16
|
{
|
17
17
|
cv.GenerateID(): cv.declare_id(GP8403Output),
|
18
18
|
cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403),
|
19
|
-
cv.Required(CONF_CHANNEL): cv.
|
19
|
+
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=1),
|
20
20
|
}
|
21
21
|
).extend(cv.COMPONENT_SCHEMA)
|
22
22
|
|
esphome/components/host/gpio.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
import logging
|
2
2
|
|
3
|
+
from esphome import pins
|
4
|
+
import esphome.codegen as cg
|
5
|
+
import esphome.config_validation as cv
|
3
6
|
from esphome.const import (
|
4
7
|
CONF_ID,
|
5
8
|
CONF_INPUT,
|
@@ -11,9 +14,6 @@ from esphome.const import (
|
|
11
14
|
CONF_PULLDOWN,
|
12
15
|
CONF_PULLUP,
|
13
16
|
)
|
14
|
-
from esphome import pins
|
15
|
-
import esphome.config_validation as cv
|
16
|
-
import esphome.codegen as cg
|
17
17
|
|
18
18
|
from .const import host_ns
|
19
19
|
|
@@ -28,8 +28,10 @@ def _translate_pin(value):
|
|
28
28
|
"This variable only supports pin numbers, not full pin schemas "
|
29
29
|
"(with inverted and mode)."
|
30
30
|
)
|
31
|
-
if isinstance(value, int):
|
31
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
32
32
|
return value
|
33
|
+
if not isinstance(value, str):
|
34
|
+
raise cv.Invalid(f"Invalid pin number: {value}")
|
33
35
|
try:
|
34
36
|
return int(value)
|
35
37
|
except ValueError:
|
@@ -6,6 +6,7 @@ from esphome.const import (
|
|
6
6
|
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
7
7
|
CONF_ID,
|
8
8
|
CONF_METHOD,
|
9
|
+
CONF_ON_ERROR,
|
9
10
|
CONF_TIMEOUT,
|
10
11
|
CONF_TRIGGER_ID,
|
11
12
|
CONF_URL,
|
@@ -185,6 +186,13 @@ HTTP_REQUEST_ACTION_SCHEMA = cv.Schema(
|
|
185
186
|
cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(
|
186
187
|
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(HttpRequestResponseTrigger)}
|
187
188
|
),
|
189
|
+
cv.Optional(CONF_ON_ERROR): automation.validate_automation(
|
190
|
+
{
|
191
|
+
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
192
|
+
automation.Trigger.template()
|
193
|
+
)
|
194
|
+
}
|
195
|
+
),
|
188
196
|
cv.Optional(CONF_MAX_RESPONSE_BUFFER_SIZE, default="1kB"): cv.validate_bytes,
|
189
197
|
}
|
190
198
|
)
|
@@ -272,5 +280,9 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
|
272
280
|
],
|
273
281
|
conf,
|
274
282
|
)
|
283
|
+
for conf in config.get(CONF_ON_ERROR, []):
|
284
|
+
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
285
|
+
cg.add(var.register_error_trigger(trigger))
|
286
|
+
await automation.build_automation(trigger, [], conf)
|
275
287
|
|
276
288
|
return var
|