mirror of
https://github.com/esphome/esphome.git
synced 2024-12-24 14:34:54 +01:00
Merge branch 'dev' into refactor_esp32camera
This commit is contained in:
commit
cd897a8845
25 changed files with 480 additions and 313 deletions
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -65,7 +65,7 @@ jobs:
|
|||
pip3 install build
|
||||
python3 -m build
|
||||
- name: Publish
|
||||
uses: pypa/gh-action-pypi-publish@v1.11.0
|
||||
uses: pypa/gh-action-pypi-publish@v1.12.2
|
||||
|
||||
deploy-docker:
|
||||
name: Build ESPHome ${{ matrix.platform }}
|
||||
|
|
|
@ -38,7 +38,7 @@ from esphome.const import (
|
|||
SECRETS_FILES,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, coroutine
|
||||
from esphome.helpers import indent, is_ip_address, get_bool_env
|
||||
from esphome.helpers import get_bool_env, indent, is_ip_address
|
||||
from esphome.log import Fore, color, setup_log
|
||||
from esphome.util import (
|
||||
get_serial_ports,
|
||||
|
@ -378,7 +378,7 @@ def show_logs(config, args, port):
|
|||
|
||||
port = mqtt.get_esphome_device_ip(
|
||||
config, args.username, args.password, args.client_id
|
||||
)
|
||||
)[0]
|
||||
|
||||
from esphome.components.api.client import run_logs
|
||||
|
||||
|
|
|
@ -8,8 +8,13 @@ extern "C" {
|
|||
uint8_t temprature_sens_read();
|
||||
}
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32C2)
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
#include "driver/temp_sensor.h"
|
||||
#else
|
||||
#include "driver/temperature_sensor.h"
|
||||
#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
#endif // USE_ESP32_VARIANT
|
||||
#endif // USE_ESP32
|
||||
#ifdef USE_RP2040
|
||||
|
@ -25,6 +30,13 @@ namespace esphome {
|
|||
namespace internal_temperature {
|
||||
|
||||
static const char *const TAG = "internal_temperature";
|
||||
#ifdef USE_ESP32
|
||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \
|
||||
(defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2))
|
||||
static temperature_sensor_handle_t tsensNew = NULL;
|
||||
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT
|
||||
#endif // USE_ESP32
|
||||
|
||||
void InternalTemperatureSensor::update() {
|
||||
float temperature = NAN;
|
||||
|
@ -36,7 +48,9 @@ void InternalTemperatureSensor::update() {
|
|||
temperature = (raw - 32) / 1.8f;
|
||||
success = (raw != 128);
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32C2)
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT();
|
||||
temp_sensor_set_config(tsens);
|
||||
temp_sensor_start();
|
||||
|
@ -47,6 +61,13 @@ void InternalTemperatureSensor::update() {
|
|||
esp_err_t result = temp_sensor_read_celsius(&temperature);
|
||||
temp_sensor_stop();
|
||||
success = (result == ESP_OK);
|
||||
#else
|
||||
esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature);
|
||||
success = (result == ESP_OK);
|
||||
if (!success) {
|
||||
ESP_LOGE(TAG, "Failed to get temperature: %d", result);
|
||||
}
|
||||
#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
#endif // USE_ESP32_VARIANT
|
||||
#endif // USE_ESP32
|
||||
#ifdef USE_RP2040
|
||||
|
@ -75,6 +96,32 @@ void InternalTemperatureSensor::update() {
|
|||
}
|
||||
}
|
||||
|
||||
void InternalTemperatureSensor::setup() {
|
||||
#ifdef USE_ESP32
|
||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \
|
||||
(defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2))
|
||||
ESP_LOGCONFIG(TAG, "Setting up temperature sensor...");
|
||||
|
||||
temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
|
||||
|
||||
esp_err_t result = temperature_sensor_install(&tsens_config, &tsensNew);
|
||||
if (result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to install temperature sensor: %d", result);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
result = temperature_sensor_enable(tsensNew);
|
||||
if (result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to enable temperature sensor: %d", result);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT
|
||||
#endif // USE_ESP32
|
||||
}
|
||||
|
||||
void InternalTemperatureSensor::dump_config() { LOG_SENSOR("", "Internal Temperature Sensor", this); }
|
||||
|
||||
} // namespace internal_temperature
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace internal_temperature {
|
|||
|
||||
class InternalTemperatureSensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void update() override;
|
||||
|
|
|
@ -27,7 +27,7 @@ from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code
|
|||
from .gradient import GRADIENT_SCHEMA, gradients_to_code
|
||||
from .hello_world import get_hello_world
|
||||
from .lv_validation import lv_bool, lv_images_used
|
||||
from .lvcode import LvContext, LvglComponent
|
||||
from .lvcode import LvContext, LvglComponent, lvgl_static
|
||||
from .schemas import (
|
||||
DISP_BG_SCHEMA,
|
||||
FLEX_OBJ_SCHEMA,
|
||||
|
@ -152,41 +152,70 @@ def generate_lv_conf_h():
|
|||
return LV_CONF_H_FORMAT.format("\n".join(definitions))
|
||||
|
||||
|
||||
def final_validation(config):
|
||||
if pages := config.get(CONF_PAGES):
|
||||
if all(p[df.CONF_SKIP] for p in pages):
|
||||
raise cv.Invalid("At least one page must not be skipped")
|
||||
def multi_conf_validate(configs: list[dict]):
|
||||
displays = [config[df.CONF_DISPLAYS] for config in configs]
|
||||
# flatten the display list
|
||||
display_list = [disp for disps in displays for disp in disps]
|
||||
if len(display_list) != len(set(display_list)):
|
||||
raise cv.Invalid("A display ID may be used in only one LVGL instance")
|
||||
base_config = configs[0]
|
||||
for config in configs[1:]:
|
||||
for item in (
|
||||
df.CONF_LOG_LEVEL,
|
||||
df.CONF_COLOR_DEPTH,
|
||||
df.CONF_BYTE_ORDER,
|
||||
df.CONF_TRANSPARENCY_KEY,
|
||||
):
|
||||
if base_config[item] != config[item]:
|
||||
raise cv.Invalid(
|
||||
f"Config item '{item}' must be the same for all LVGL instances"
|
||||
)
|
||||
|
||||
|
||||
def final_validation(configs):
|
||||
multi_conf_validate(configs)
|
||||
global_config = full_config.get()
|
||||
for display_id in config[df.CONF_DISPLAYS]:
|
||||
path = global_config.get_path_for_id(display_id)[:-1]
|
||||
display = global_config.get_config_for_path(path)
|
||||
if CONF_LAMBDA in display:
|
||||
raise cv.Invalid("Using lambda: in display config not compatible with LVGL")
|
||||
if display[CONF_AUTO_CLEAR_ENABLED]:
|
||||
raise cv.Invalid(
|
||||
"Using auto_clear_enabled: true in display config not compatible with LVGL"
|
||||
)
|
||||
buffer_frac = config[CONF_BUFFER_SIZE]
|
||||
if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config:
|
||||
LOGGER.warning("buffer_size: may need to be reduced without PSRAM")
|
||||
for image_id in lv_images_used:
|
||||
path = global_config.get_path_for_id(image_id)[:-1]
|
||||
image_conf = global_config.get_config_for_path(path)
|
||||
if image_conf[CONF_TYPE] in ("RGBA", "RGB24"):
|
||||
raise cv.Invalid(
|
||||
"Using RGBA or RGB24 in image config not compatible with LVGL", path
|
||||
)
|
||||
for w in focused_widgets:
|
||||
path = global_config.get_path_for_id(w)
|
||||
widget_conf = global_config.get_config_for_path(path[:-1])
|
||||
if df.CONF_ADJUSTABLE in widget_conf and not widget_conf[df.CONF_ADJUSTABLE]:
|
||||
raise cv.Invalid(
|
||||
"A non adjustable arc may not be focused",
|
||||
path,
|
||||
)
|
||||
for config in configs:
|
||||
if pages := config.get(CONF_PAGES):
|
||||
if all(p[df.CONF_SKIP] for p in pages):
|
||||
raise cv.Invalid("At least one page must not be skipped")
|
||||
for display_id in config[df.CONF_DISPLAYS]:
|
||||
path = global_config.get_path_for_id(display_id)[:-1]
|
||||
display = global_config.get_config_for_path(path)
|
||||
if CONF_LAMBDA in display:
|
||||
raise cv.Invalid(
|
||||
"Using lambda: in display config not compatible with LVGL"
|
||||
)
|
||||
if display[CONF_AUTO_CLEAR_ENABLED]:
|
||||
raise cv.Invalid(
|
||||
"Using auto_clear_enabled: true in display config not compatible with LVGL"
|
||||
)
|
||||
buffer_frac = config[CONF_BUFFER_SIZE]
|
||||
if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config:
|
||||
LOGGER.warning("buffer_size: may need to be reduced without PSRAM")
|
||||
for image_id in lv_images_used:
|
||||
path = global_config.get_path_for_id(image_id)[:-1]
|
||||
image_conf = global_config.get_config_for_path(path)
|
||||
if image_conf[CONF_TYPE] in ("RGBA", "RGB24"):
|
||||
raise cv.Invalid(
|
||||
"Using RGBA or RGB24 in image config not compatible with LVGL", path
|
||||
)
|
||||
for w in focused_widgets:
|
||||
path = global_config.get_path_for_id(w)
|
||||
widget_conf = global_config.get_config_for_path(path[:-1])
|
||||
if (
|
||||
df.CONF_ADJUSTABLE in widget_conf
|
||||
and not widget_conf[df.CONF_ADJUSTABLE]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
"A non adjustable arc may not be focused",
|
||||
path,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
async def to_code(configs):
|
||||
config_0 = configs[0]
|
||||
# Global configuration
|
||||
cg.add_library("lvgl/lvgl", "8.4.0")
|
||||
cg.add_define("USE_LVGL")
|
||||
# suppress default enabling of extra widgets
|
||||
|
@ -203,53 +232,33 @@ async def to_code(config):
|
|||
add_define("LV_MEM_CUSTOM_INCLUDE", '"esphome/components/lvgl/lvgl_hal.h"')
|
||||
|
||||
add_define(
|
||||
"LV_LOG_LEVEL", f"LV_LOG_LEVEL_{df.LV_LOG_LEVELS[config[df.CONF_LOG_LEVEL]]}"
|
||||
"LV_LOG_LEVEL",
|
||||
f"LV_LOG_LEVEL_{df.LV_LOG_LEVELS[config_0[df.CONF_LOG_LEVEL]]}",
|
||||
)
|
||||
cg.add_define(
|
||||
"LVGL_LOG_LEVEL",
|
||||
cg.RawExpression(f"ESPHOME_LOG_LEVEL_{config[df.CONF_LOG_LEVEL]}"),
|
||||
cg.RawExpression(f"ESPHOME_LOG_LEVEL_{config_0[df.CONF_LOG_LEVEL]}"),
|
||||
)
|
||||
add_define("LV_COLOR_DEPTH", config[df.CONF_COLOR_DEPTH])
|
||||
add_define("LV_COLOR_DEPTH", config_0[df.CONF_COLOR_DEPTH])
|
||||
for font in helpers.lv_fonts_used:
|
||||
add_define(f"LV_FONT_{font.upper()}")
|
||||
|
||||
if config[df.CONF_COLOR_DEPTH] == 16:
|
||||
if config_0[df.CONF_COLOR_DEPTH] == 16:
|
||||
add_define(
|
||||
"LV_COLOR_16_SWAP",
|
||||
"1" if config[df.CONF_BYTE_ORDER] == "big_endian" else "0",
|
||||
"1" if config_0[df.CONF_BYTE_ORDER] == "big_endian" else "0",
|
||||
)
|
||||
add_define(
|
||||
"LV_COLOR_CHROMA_KEY",
|
||||
await lvalid.lv_color.process(config[df.CONF_TRANSPARENCY_KEY]),
|
||||
await lvalid.lv_color.process(config_0[df.CONF_TRANSPARENCY_KEY]),
|
||||
)
|
||||
cg.add_build_flag("-Isrc")
|
||||
|
||||
cg.add_global(lvgl_ns.using)
|
||||
frac = config[CONF_BUFFER_SIZE]
|
||||
if frac >= 0.75:
|
||||
frac = 1
|
||||
elif frac >= 0.375:
|
||||
frac = 2
|
||||
elif frac > 0.19:
|
||||
frac = 4
|
||||
else:
|
||||
frac = 8
|
||||
displays = [await cg.get_variable(display) for display in config[df.CONF_DISPLAYS]]
|
||||
lv_component = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
displays,
|
||||
frac,
|
||||
config[df.CONF_FULL_REFRESH],
|
||||
config[df.CONF_DRAW_ROUNDING],
|
||||
config[df.CONF_RESUME_ON_INPUT],
|
||||
)
|
||||
await cg.register_component(lv_component, config)
|
||||
Widget.create(config[CONF_ID], lv_component, obj_spec, config)
|
||||
|
||||
for font in helpers.esphome_fonts_used:
|
||||
await cg.get_variable(font)
|
||||
cg.new_Pvariable(ID(f"{font}_engine", True, type=FontEngine), MockObj(font))
|
||||
default_font = config[df.CONF_DEFAULT_FONT]
|
||||
default_font = config_0[df.CONF_DEFAULT_FONT]
|
||||
if not lvalid.is_lv_font(default_font):
|
||||
add_define(
|
||||
"LV_FONT_CUSTOM_DECLARE", f"LV_FONT_DECLARE(*{df.DEFAULT_ESPHOME_FONT})"
|
||||
|
@ -265,39 +274,71 @@ async def to_code(config):
|
|||
add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT)
|
||||
else:
|
||||
add_define("LV_FONT_DEFAULT", await lvalid.lv_font.process(default_font))
|
||||
cg.add(lvgl_static.esphome_lvgl_init())
|
||||
|
||||
lv_scr_act = get_scr_act(lv_component)
|
||||
async with LvContext(lv_component):
|
||||
await touchscreens_to_code(lv_component, config)
|
||||
await encoders_to_code(lv_component, config)
|
||||
await theme_to_code(config)
|
||||
await styles_to_code(config)
|
||||
await gradients_to_code(config)
|
||||
await set_obj_properties(lv_scr_act, config)
|
||||
await add_widgets(lv_scr_act, config)
|
||||
await add_pages(lv_component, config)
|
||||
await add_top_layer(lv_component, config)
|
||||
await msgboxes_to_code(lv_component, config)
|
||||
await disp_update(lv_component.get_disp(), config)
|
||||
for config in configs:
|
||||
frac = config[CONF_BUFFER_SIZE]
|
||||
if frac >= 0.75:
|
||||
frac = 1
|
||||
elif frac >= 0.375:
|
||||
frac = 2
|
||||
elif frac > 0.19:
|
||||
frac = 4
|
||||
else:
|
||||
frac = 8
|
||||
displays = [
|
||||
await cg.get_variable(display) for display in config[df.CONF_DISPLAYS]
|
||||
]
|
||||
lv_component = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
displays,
|
||||
frac,
|
||||
config[df.CONF_FULL_REFRESH],
|
||||
config[df.CONF_DRAW_ROUNDING],
|
||||
config[df.CONF_RESUME_ON_INPUT],
|
||||
)
|
||||
await cg.register_component(lv_component, config)
|
||||
Widget.create(config[CONF_ID], lv_component, obj_spec, config)
|
||||
|
||||
lv_scr_act = get_scr_act(lv_component)
|
||||
async with LvContext():
|
||||
await touchscreens_to_code(lv_component, config)
|
||||
await encoders_to_code(lv_component, config)
|
||||
await theme_to_code(config)
|
||||
await styles_to_code(config)
|
||||
await gradients_to_code(config)
|
||||
await set_obj_properties(lv_scr_act, config)
|
||||
await add_widgets(lv_scr_act, config)
|
||||
await add_pages(lv_component, config)
|
||||
await add_top_layer(lv_component, config)
|
||||
await msgboxes_to_code(lv_component, config)
|
||||
await disp_update(lv_component.get_disp(), config)
|
||||
# Set this directly since we are limited in how many methods can be added to the Widget class.
|
||||
Widget.widgets_completed = True
|
||||
async with LvContext(lv_component):
|
||||
await generate_triggers(lv_component)
|
||||
await generate_page_triggers(lv_component, config)
|
||||
await initial_focus_to_code(config)
|
||||
for conf in config.get(CONF_ON_IDLE, ()):
|
||||
templ = await cg.templatable(conf[CONF_TIMEOUT], [], cg.uint32)
|
||||
idle_trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], lv_component, templ)
|
||||
await build_automation(idle_trigger, [], conf)
|
||||
for conf in config.get(df.CONF_ON_PAUSE, ()):
|
||||
pause_trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], lv_component, True)
|
||||
await build_automation(pause_trigger, [], conf)
|
||||
for conf in config.get(df.CONF_ON_RESUME, ()):
|
||||
resume_trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], lv_component, False
|
||||
)
|
||||
await build_automation(resume_trigger, [], conf)
|
||||
async with LvContext():
|
||||
await generate_triggers()
|
||||
for config in configs:
|
||||
lv_component = await cg.get_variable(config[CONF_ID])
|
||||
await generate_page_triggers(config)
|
||||
await initial_focus_to_code(config)
|
||||
for conf in config.get(CONF_ON_IDLE, ()):
|
||||
templ = await cg.templatable(conf[CONF_TIMEOUT], [], cg.uint32)
|
||||
idle_trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], lv_component, templ
|
||||
)
|
||||
await build_automation(idle_trigger, [], conf)
|
||||
for conf in config.get(df.CONF_ON_PAUSE, ()):
|
||||
pause_trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], lv_component, True
|
||||
)
|
||||
await build_automation(pause_trigger, [], conf)
|
||||
for conf in config.get(df.CONF_ON_RESUME, ()):
|
||||
resume_trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], lv_component, False
|
||||
)
|
||||
await build_automation(resume_trigger, [], conf)
|
||||
|
||||
# This must be done after all widgets are created
|
||||
for comp in helpers.lvgl_components_required:
|
||||
cg.add_define(f"USE_LVGL_{comp.upper()}")
|
||||
if "transform_angle" in styles_used:
|
||||
|
@ -312,7 +353,10 @@ async def to_code(config):
|
|||
|
||||
def display_schema(config):
|
||||
value = cv.ensure_list(cv.use_id(Display))(config)
|
||||
return value or [cv.use_id(Display)(config)]
|
||||
value = value or [cv.use_id(Display)(config)]
|
||||
if len(set(value)) != len(value):
|
||||
raise cv.Invalid("Display IDs must be unique")
|
||||
return value
|
||||
|
||||
|
||||
def add_hello_world(config):
|
||||
|
@ -324,7 +368,7 @@ def add_hello_world(config):
|
|||
|
||||
FINAL_VALIDATE_SCHEMA = final_validation
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
LVGL_SCHEMA = (
|
||||
cv.polling_component_schema("1s")
|
||||
.extend(obj_schema(obj_spec))
|
||||
.extend(
|
||||
|
@ -393,3 +437,16 @@ CONFIG_SCHEMA = (
|
|||
.extend(DISP_BG_SCHEMA)
|
||||
.add_extra(add_hello_world)
|
||||
)
|
||||
|
||||
|
||||
def lvgl_config_schema(config):
|
||||
"""
|
||||
Can't use cv.ensure_list here because it converts an empty config to an empty list,
|
||||
rather than a default config.
|
||||
"""
|
||||
if not config or isinstance(config, dict):
|
||||
return [LVGL_SCHEMA(config)]
|
||||
return cv.Schema([LVGL_SCHEMA])(config)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = lvgl_config_schema
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from collections.abc import Awaitable
|
||||
from typing import Callable
|
||||
from typing import Any, Callable
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
|
@ -23,7 +22,6 @@ from .lvcode import (
|
|||
UPDATE_EVENT,
|
||||
LambdaContext,
|
||||
LocalVariable,
|
||||
LvglComponent,
|
||||
ReturnStatement,
|
||||
add_line_marks,
|
||||
lv,
|
||||
|
@ -58,7 +56,7 @@ focused_widgets = set()
|
|||
|
||||
async def action_to_code(
|
||||
widgets: list[Widget],
|
||||
action: Callable[[Widget], Awaitable[None]],
|
||||
action: Callable[[Widget], Any],
|
||||
action_id,
|
||||
template_arg,
|
||||
args,
|
||||
|
@ -137,20 +135,18 @@ async def disp_update(disp, config: dict):
|
|||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(lv_obj_t),
|
||||
cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent),
|
||||
},
|
||||
key=CONF_ID,
|
||||
),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent),
|
||||
}
|
||||
),
|
||||
LVGL_SCHEMA,
|
||||
),
|
||||
)
|
||||
async def obj_invalidate_to_code(config, action_id, template_arg, args):
|
||||
lv_comp = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
widgets = await get_widgets(config) or [get_scr_act(lv_comp)]
|
||||
if CONF_LVGL_ID in config:
|
||||
lv_comp = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
widgets = [get_scr_act(lv_comp)]
|
||||
else:
|
||||
widgets = await get_widgets(config)
|
||||
|
||||
async def do_invalidate(widget: Widget):
|
||||
lv_obj.invalidate(widget.obj)
|
||||
|
@ -161,14 +157,12 @@ async def obj_invalidate_to_code(config, action_id, template_arg, args):
|
|||
@automation.register_action(
|
||||
"lvgl.update",
|
||||
LvglAction,
|
||||
DISP_BG_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(LvglComponent),
|
||||
}
|
||||
).add_extra(cv.has_at_least_one_key(CONF_DISP_BG_COLOR, CONF_DISP_BG_IMAGE)),
|
||||
DISP_BG_SCHEMA.extend(LVGL_SCHEMA).add_extra(
|
||||
cv.has_at_least_one_key(CONF_DISP_BG_COLOR, CONF_DISP_BG_IMAGE)
|
||||
),
|
||||
)
|
||||
async def lvgl_update_to_code(config, action_id, template_arg, args):
|
||||
widgets = await get_widgets(config)
|
||||
widgets = await get_widgets(config, CONF_LVGL_ID)
|
||||
w = widgets[0]
|
||||
disp = literal(f"{w.obj}->get_disp()")
|
||||
async with LambdaContext(LVGL_COMP_ARG, where=action_id) as context:
|
||||
|
@ -181,32 +175,33 @@ async def lvgl_update_to_code(config, action_id, template_arg, args):
|
|||
@automation.register_action(
|
||||
"lvgl.pause",
|
||||
LvglAction,
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(LvglComponent),
|
||||
cv.Optional(CONF_SHOW_SNOW, default=False): lv_bool,
|
||||
},
|
||||
LVGL_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_SHOW_SNOW, default=False): lv_bool,
|
||||
}
|
||||
),
|
||||
)
|
||||
async def pause_action_to_code(config, action_id, template_arg, args):
|
||||
lv_comp = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
async with LambdaContext(LVGL_COMP_ARG) as context:
|
||||
add_line_marks(where=action_id)
|
||||
lv_add(lvgl_comp.set_paused(True, config[CONF_SHOW_SNOW]))
|
||||
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
await cg.register_parented(var, lv_comp)
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.resume",
|
||||
LvglAction,
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(LvglComponent),
|
||||
},
|
||||
LVGL_SCHEMA,
|
||||
)
|
||||
async def resume_action_to_code(config, action_id, template_arg, args):
|
||||
lv_comp = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
async with LambdaContext(LVGL_COMP_ARG, where=action_id) as context:
|
||||
lv_add(lvgl_comp.set_paused(False, False))
|
||||
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
await cg.register_parented(var, lv_comp)
|
||||
return var
|
||||
|
||||
|
||||
|
@ -265,14 +260,15 @@ def focused_id(value):
|
|||
ObjUpdateAction,
|
||||
cv.Any(
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Optional(CONF_GROUP): cv.use_id(lv_group_t),
|
||||
cv.Required(CONF_ACTION): cv.one_of(
|
||||
"MARK", "RESTORE", "NEXT", "PREVIOUS", upper=True
|
||||
),
|
||||
cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent),
|
||||
cv.Optional(CONF_FREEZE, default=False): cv.boolean,
|
||||
},
|
||||
LVGL_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_GROUP): cv.use_id(lv_group_t),
|
||||
cv.Required(CONF_ACTION): cv.one_of(
|
||||
"MARK", "RESTORE", "NEXT", "PREVIOUS", upper=True
|
||||
),
|
||||
cv.Optional(CONF_FREEZE, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
key=CONF_ACTION,
|
||||
),
|
||||
cv.maybe_simple_value(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.components.binary_sensor import (
|
||||
BinarySensor,
|
||||
binary_sensor_schema,
|
||||
|
@ -6,36 +5,30 @@ from esphome.components.binary_sensor import (
|
|||
)
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from ..defines import CONF_LVGL_ID, CONF_WIDGET
|
||||
from ..lvcode import EVENT_ARG, LambdaContext, LvContext
|
||||
from ..schemas import LVGL_SCHEMA
|
||||
from ..defines import CONF_WIDGET
|
||||
from ..lvcode import EVENT_ARG, LambdaContext, LvContext, lvgl_static
|
||||
from ..types import LV_EVENT, lv_pseudo_button_t
|
||||
from ..widgets import Widget, get_widgets, wait_for_widgets
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
binary_sensor_schema(BinarySensor)
|
||||
.extend(LVGL_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(lv_pseudo_button_t),
|
||||
}
|
||||
)
|
||||
CONFIG_SCHEMA = binary_sensor_schema(BinarySensor).extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(lv_pseudo_button_t),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
sensor = await new_binary_sensor(config)
|
||||
paren = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
widget = await get_widgets(config, CONF_WIDGET)
|
||||
widget = widget[0]
|
||||
assert isinstance(widget, Widget)
|
||||
await wait_for_widgets()
|
||||
async with LambdaContext(EVENT_ARG) as pressed_ctx:
|
||||
pressed_ctx.add(sensor.publish_state(widget.is_pressed()))
|
||||
async with LvContext(paren) as ctx:
|
||||
async with LvContext() as ctx:
|
||||
ctx.add(sensor.publish_initial_state(widget.is_pressed()))
|
||||
ctx.add(
|
||||
paren.add_event_cb(
|
||||
lvgl_static.add_event_cb(
|
||||
widget.obj,
|
||||
await pressed_ctx.get_lambda(),
|
||||
LV_EVENT.PRESSING,
|
||||
|
|
|
@ -4,9 +4,8 @@ from esphome.components.light import LightOutput
|
|||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_GAMMA_CORRECT, CONF_OUTPUT_ID
|
||||
|
||||
from ..defines import CONF_LVGL_ID, CONF_WIDGET
|
||||
from ..defines import CONF_WIDGET
|
||||
from ..lvcode import LvContext
|
||||
from ..schemas import LVGL_SCHEMA
|
||||
from ..types import LvType, lvgl_ns
|
||||
from ..widgets import get_widgets, wait_for_widgets
|
||||
|
||||
|
@ -18,16 +17,15 @@ CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend(
|
|||
cv.Required(CONF_WIDGET): cv.use_id(lv_led_t),
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(LVLight),
|
||||
}
|
||||
).extend(LVGL_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
await light.register_light(var, config)
|
||||
|
||||
paren = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
widget = await get_widgets(config, CONF_WIDGET)
|
||||
widget = widget[0]
|
||||
await wait_for_widgets()
|
||||
async with LvContext(paren) as ctx:
|
||||
async with LvContext() as ctx:
|
||||
ctx.add(var.set_obj(widget.obj))
|
||||
|
|
|
@ -178,10 +178,9 @@ class LvContext(LambdaContext):
|
|||
|
||||
added_lambda_count = 0
|
||||
|
||||
def __init__(self, lv_component, args=None):
|
||||
def __init__(self, args=None):
|
||||
self.args = args or LVGL_COMP_ARG
|
||||
super().__init__(parameters=self.args)
|
||||
self.lv_component = lv_component
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
await super().__aexit__(exc_type, exc_val, exc_tb)
|
||||
|
@ -298,6 +297,7 @@ lv_expr = LvExpr("lv_")
|
|||
lv_obj = MockLv("lv_obj_")
|
||||
# Operations on the LVGL component
|
||||
lvgl_comp = MockObj(LVGL_COMP, "->")
|
||||
lvgl_static = MockObj("LvglComponent", "::")
|
||||
|
||||
|
||||
# equivalent to cg.add() for the current code context
|
||||
|
|
|
@ -98,19 +98,24 @@ void LvglComponent::set_paused(bool paused, bool show_snow) {
|
|||
this->pause_callbacks_.call(paused);
|
||||
}
|
||||
|
||||
void LvglComponent::esphome_lvgl_init() {
|
||||
lv_init();
|
||||
lv_update_event = static_cast<lv_event_code_t>(lv_event_register_id());
|
||||
lv_api_event = static_cast<lv_event_code_t>(lv_event_register_id());
|
||||
}
|
||||
void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event) {
|
||||
lv_obj_add_event_cb(obj, callback, event, this);
|
||||
lv_obj_add_event_cb(obj, callback, event, nullptr);
|
||||
}
|
||||
void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1,
|
||||
lv_event_code_t event2) {
|
||||
this->add_event_cb(obj, callback, event1);
|
||||
this->add_event_cb(obj, callback, event2);
|
||||
add_event_cb(obj, callback, event1);
|
||||
add_event_cb(obj, callback, event2);
|
||||
}
|
||||
void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1,
|
||||
lv_event_code_t event2, lv_event_code_t event3) {
|
||||
this->add_event_cb(obj, callback, event1);
|
||||
this->add_event_cb(obj, callback, event2);
|
||||
this->add_event_cb(obj, callback, event3);
|
||||
add_event_cb(obj, callback, event1);
|
||||
add_event_cb(obj, callback, event2);
|
||||
add_event_cb(obj, callback, event3);
|
||||
}
|
||||
void LvglComponent::add_page(LvPageType *page) {
|
||||
this->pages_.push_back(page);
|
||||
|
@ -218,8 +223,10 @@ PauseTrigger::PauseTrigger(LvglComponent *parent, TemplatableValue<bool> paused)
|
|||
}
|
||||
|
||||
#ifdef USE_LVGL_TOUCHSCREEN
|
||||
LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time) {
|
||||
LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time, LvglComponent *parent) {
|
||||
this->set_parent(parent);
|
||||
lv_indev_drv_init(&this->drv_);
|
||||
this->drv_.disp = parent->get_disp();
|
||||
this->drv_.long_press_repeat_time = long_press_repeat_time;
|
||||
this->drv_.long_press_time = long_press_time;
|
||||
this->drv_.type = LV_INDEV_TYPE_POINTER;
|
||||
|
@ -235,6 +242,7 @@ LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_r
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
void LVTouchListener::update(const touchscreen::TouchPoints_t &tpoints) {
|
||||
this->touch_pressed_ = !this->parent_->is_paused() && !tpoints.empty();
|
||||
if (this->touch_pressed_)
|
||||
|
@ -405,9 +413,6 @@ LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buf
|
|||
buffer_frac_(buffer_frac),
|
||||
full_refresh_(full_refresh),
|
||||
resume_on_input_(resume_on_input) {
|
||||
lv_init();
|
||||
lv_update_event = static_cast<lv_event_code_t>(lv_event_register_id());
|
||||
lv_api_event = static_cast<lv_event_code_t>(lv_event_register_id());
|
||||
auto *display = this->displays_[0];
|
||||
size_t buffer_pixels = display->get_width() * display->get_height() / this->buffer_frac_;
|
||||
auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
|
||||
|
|
|
@ -146,10 +146,14 @@ class LvglComponent : public PollingComponent {
|
|||
}
|
||||
}
|
||||
|
||||
void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event);
|
||||
void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2);
|
||||
void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2,
|
||||
lv_event_code_t event3);
|
||||
/**
|
||||
* Initialize the LVGL library and register custom events.
|
||||
*/
|
||||
static void esphome_lvgl_init();
|
||||
static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event);
|
||||
static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2);
|
||||
static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2,
|
||||
lv_event_code_t event3);
|
||||
void add_page(LvPageType *page);
|
||||
void show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time);
|
||||
void show_next_page(lv_scr_load_anim_t anim, uint32_t time);
|
||||
|
@ -231,7 +235,7 @@ template<typename... Ts> class LvglCondition : public Condition<Ts...>, public P
|
|||
#ifdef USE_LVGL_TOUCHSCREEN
|
||||
class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglComponent> {
|
||||
public:
|
||||
LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time);
|
||||
LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time, LvglComponent *parent);
|
||||
void update(const touchscreen::TouchPoints_t &tpoints) override;
|
||||
void release() override {
|
||||
touch_pressed_ = false;
|
||||
|
|
|
@ -3,7 +3,7 @@ from esphome.components import number
|
|||
import esphome.config_validation as cv
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from ..defines import CONF_ANIMATED, CONF_LVGL_ID, CONF_UPDATE_ON_RELEASE, CONF_WIDGET
|
||||
from ..defines import CONF_ANIMATED, CONF_UPDATE_ON_RELEASE, CONF_WIDGET
|
||||
from ..lv_validation import animated
|
||||
from ..lvcode import (
|
||||
API_EVENT,
|
||||
|
@ -13,28 +13,23 @@ from ..lvcode import (
|
|||
LvContext,
|
||||
lv,
|
||||
lv_add,
|
||||
lvgl_static,
|
||||
)
|
||||
from ..schemas import LVGL_SCHEMA
|
||||
from ..types import LV_EVENT, LvNumber, lvgl_ns
|
||||
from ..widgets import get_widgets, wait_for_widgets
|
||||
|
||||
LVGLNumber = lvgl_ns.class_("LVGLNumber", number.Number)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
number.number_schema(LVGLNumber)
|
||||
.extend(LVGL_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(LvNumber),
|
||||
cv.Optional(CONF_ANIMATED, default=True): animated,
|
||||
cv.Optional(CONF_UPDATE_ON_RELEASE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
CONFIG_SCHEMA = number.number_schema(LVGLNumber).extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(LvNumber),
|
||||
cv.Optional(CONF_ANIMATED, default=True): animated,
|
||||
cv.Optional(CONF_UPDATE_ON_RELEASE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
widget = await get_widgets(config, CONF_WIDGET)
|
||||
widget = widget[0]
|
||||
var = await number.new_number(
|
||||
|
@ -58,10 +53,10 @@ async def to_code(config):
|
|||
if not config[CONF_UPDATE_ON_RELEASE]
|
||||
else LV_EVENT.RELEASED
|
||||
)
|
||||
async with LvContext(paren):
|
||||
async with LvContext():
|
||||
lv_add(var.set_control_lambda(await control.get_lambda()))
|
||||
lv_add(
|
||||
paren.add_event_cb(
|
||||
lvgl_static.add_event_cb(
|
||||
widget.obj, await event.get_lambda(), UPDATE_EVENT, event_code
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,25 +1,19 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.components import select
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_OPTIONS
|
||||
|
||||
from ..defines import CONF_ANIMATED, CONF_LVGL_ID, CONF_WIDGET, literal
|
||||
from ..defines import CONF_ANIMATED, CONF_WIDGET, literal
|
||||
from ..lvcode import LvContext
|
||||
from ..schemas import LVGL_SCHEMA
|
||||
from ..types import LvSelect, lvgl_ns
|
||||
from ..widgets import get_widgets, wait_for_widgets
|
||||
|
||||
LVGLSelect = lvgl_ns.class_("LVGLSelect", select.Select)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
select.select_schema(LVGLSelect)
|
||||
.extend(LVGL_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(LvSelect),
|
||||
cv.Optional(CONF_ANIMATED, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
CONFIG_SCHEMA = select.select_schema(LVGLSelect).extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(LvSelect),
|
||||
cv.Optional(CONF_ANIMATED, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
@ -28,9 +22,8 @@ async def to_code(config):
|
|||
widget = widget[0]
|
||||
options = widget.config.get(CONF_OPTIONS, [])
|
||||
selector = await select.new_select(config, options=options)
|
||||
paren = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
await wait_for_widgets()
|
||||
async with LvContext(paren) as ctx:
|
||||
async with LvContext() as ctx:
|
||||
ctx.add(
|
||||
selector.set_widget(
|
||||
widget.var,
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.components.sensor import Sensor, new_sensor, sensor_schema
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from ..defines import CONF_LVGL_ID, CONF_WIDGET
|
||||
from ..defines import CONF_WIDGET
|
||||
from ..lvcode import (
|
||||
API_EVENT,
|
||||
EVENT_ARG,
|
||||
|
@ -11,34 +10,29 @@ from ..lvcode import (
|
|||
LambdaContext,
|
||||
LvContext,
|
||||
lv_add,
|
||||
lvgl_static,
|
||||
)
|
||||
from ..schemas import LVGL_SCHEMA
|
||||
from ..types import LV_EVENT, LvNumber
|
||||
from ..widgets import Widget, get_widgets, wait_for_widgets
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor_schema(Sensor)
|
||||
.extend(LVGL_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(LvNumber),
|
||||
}
|
||||
)
|
||||
CONFIG_SCHEMA = sensor_schema(Sensor).extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(LvNumber),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
sensor = await new_sensor(config)
|
||||
paren = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
widget = await get_widgets(config, CONF_WIDGET)
|
||||
widget = widget[0]
|
||||
assert isinstance(widget, Widget)
|
||||
await wait_for_widgets()
|
||||
async with LambdaContext(EVENT_ARG) as lamb:
|
||||
lv_add(sensor.publish_state(widget.get_value()))
|
||||
async with LvContext(paren, LVGL_COMP_ARG):
|
||||
async with LvContext(LVGL_COMP_ARG):
|
||||
lv_add(
|
||||
paren.add_event_cb(
|
||||
lvgl_static.add_event_cb(
|
||||
widget.obj,
|
||||
await lamb.get_lambda(),
|
||||
LV_EVENT.VALUE_CHANGED,
|
||||
|
|
|
@ -3,7 +3,7 @@ from esphome.components.switch import Switch, new_switch, switch_schema
|
|||
import esphome.config_validation as cv
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from ..defines import CONF_LVGL_ID, CONF_WIDGET, literal
|
||||
from ..defines import CONF_WIDGET, literal
|
||||
from ..lvcode import (
|
||||
API_EVENT,
|
||||
EVENT_ARG,
|
||||
|
@ -13,26 +13,21 @@ from ..lvcode import (
|
|||
LvContext,
|
||||
lv,
|
||||
lv_add,
|
||||
lvgl_static,
|
||||
)
|
||||
from ..schemas import LVGL_SCHEMA
|
||||
from ..types import LV_EVENT, LV_STATE, lv_pseudo_button_t, lvgl_ns
|
||||
from ..widgets import get_widgets, wait_for_widgets
|
||||
|
||||
LVGLSwitch = lvgl_ns.class_("LVGLSwitch", Switch)
|
||||
CONFIG_SCHEMA = (
|
||||
switch_schema(LVGLSwitch)
|
||||
.extend(LVGL_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(lv_pseudo_button_t),
|
||||
}
|
||||
)
|
||||
CONFIG_SCHEMA = switch_schema(LVGLSwitch).extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(lv_pseudo_button_t),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
switch = await new_switch(config)
|
||||
paren = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
widget = await get_widgets(config, CONF_WIDGET)
|
||||
widget = widget[0]
|
||||
await wait_for_widgets()
|
||||
|
@ -45,10 +40,10 @@ async def to_code(config):
|
|||
widget.clear_state(LV_STATE.CHECKED)
|
||||
lv.event_send(widget.obj, API_EVENT, cg.nullptr)
|
||||
control.add(switch.publish_state(literal("v")))
|
||||
async with LvContext(paren) as ctx:
|
||||
async with LvContext() as ctx:
|
||||
lv_add(switch.set_control_lambda(await control.get_lambda()))
|
||||
ctx.add(
|
||||
paren.add_event_cb(
|
||||
lvgl_static.add_event_cb(
|
||||
widget.obj,
|
||||
await checked_ctx.get_lambda(),
|
||||
LV_EVENT.VALUE_CHANGED,
|
||||
|
|
|
@ -3,7 +3,7 @@ from esphome.components import text
|
|||
from esphome.components.text import new_text
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from ..defines import CONF_LVGL_ID, CONF_WIDGET
|
||||
from ..defines import CONF_WIDGET
|
||||
from ..lvcode import (
|
||||
API_EVENT,
|
||||
EVENT_ARG,
|
||||
|
@ -12,14 +12,14 @@ from ..lvcode import (
|
|||
LvContext,
|
||||
lv,
|
||||
lv_add,
|
||||
lvgl_static,
|
||||
)
|
||||
from ..schemas import LVGL_SCHEMA
|
||||
from ..types import LV_EVENT, LvText, lvgl_ns
|
||||
from ..widgets import get_widgets, wait_for_widgets
|
||||
|
||||
LVGLText = lvgl_ns.class_("LVGLText", text.Text)
|
||||
|
||||
CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(LVGL_SCHEMA).extend(
|
||||
CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LVGLText),
|
||||
cv.Required(CONF_WIDGET): cv.use_id(LvText),
|
||||
|
@ -29,7 +29,6 @@ CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(LVGL_SCHEMA).extend(
|
|||
|
||||
async def to_code(config):
|
||||
textvar = await new_text(config)
|
||||
paren = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
widget = await get_widgets(config, CONF_WIDGET)
|
||||
widget = widget[0]
|
||||
await wait_for_widgets()
|
||||
|
@ -39,10 +38,10 @@ async def to_code(config):
|
|||
control.add(textvar.publish_state(widget.get_value()))
|
||||
async with LambdaContext(EVENT_ARG) as lamb:
|
||||
lv_add(textvar.publish_state(widget.get_value()))
|
||||
async with LvContext(paren):
|
||||
async with LvContext():
|
||||
lv_add(textvar.set_control_lambda(await control.get_lambda()))
|
||||
lv_add(
|
||||
paren.add_event_cb(
|
||||
lvgl_static.add_event_cb(
|
||||
widget.obj,
|
||||
await lamb.get_lambda(),
|
||||
LV_EVENT.VALUE_CHANGED,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.components.text_sensor import (
|
||||
TextSensor,
|
||||
new_text_sensor,
|
||||
|
@ -6,34 +5,35 @@ from esphome.components.text_sensor import (
|
|||
)
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from ..defines import CONF_LVGL_ID, CONF_WIDGET
|
||||
from ..lvcode import API_EVENT, EVENT_ARG, UPDATE_EVENT, LambdaContext, LvContext
|
||||
from ..schemas import LVGL_SCHEMA
|
||||
from ..defines import CONF_WIDGET
|
||||
from ..lvcode import (
|
||||
API_EVENT,
|
||||
EVENT_ARG,
|
||||
UPDATE_EVENT,
|
||||
LambdaContext,
|
||||
LvContext,
|
||||
lvgl_static,
|
||||
)
|
||||
from ..types import LV_EVENT, LvText
|
||||
from ..widgets import get_widgets, wait_for_widgets
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
text_sensor_schema(TextSensor)
|
||||
.extend(LVGL_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(LvText),
|
||||
}
|
||||
)
|
||||
CONFIG_SCHEMA = text_sensor_schema(TextSensor).extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(LvText),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
sensor = await new_text_sensor(config)
|
||||
paren = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
widget = await get_widgets(config, CONF_WIDGET)
|
||||
widget = widget[0]
|
||||
await wait_for_widgets()
|
||||
async with LambdaContext(EVENT_ARG) as pressed_ctx:
|
||||
pressed_ctx.add(sensor.publish_state(widget.get_value()))
|
||||
async with LvContext(paren) as ctx:
|
||||
async with LvContext() as ctx:
|
||||
ctx.add(
|
||||
paren.add_event_cb(
|
||||
lvgl_static.add_event_cb(
|
||||
widget.obj,
|
||||
await pressed_ctx.get_lambda(),
|
||||
LV_EVENT.VALUE_CHANGED,
|
||||
|
|
|
@ -33,13 +33,12 @@ def touchscreen_schema(config):
|
|||
return [TOUCHSCREENS_CONFIG(config)]
|
||||
|
||||
|
||||
async def touchscreens_to_code(var, config):
|
||||
async def touchscreens_to_code(lv_component, config):
|
||||
for tconf in config[CONF_TOUCHSCREENS]:
|
||||
lvgl_components_required.add(CONF_TOUCHSCREEN)
|
||||
touchscreen = await cg.get_variable(tconf[CONF_TOUCHSCREEN_ID])
|
||||
lpt = tconf[CONF_LONG_PRESS_TIME].total_milliseconds
|
||||
lprt = tconf[CONF_LONG_PRESS_REPEAT_TIME].total_milliseconds
|
||||
listener = cg.new_Pvariable(tconf[CONF_ID], lpt, lprt)
|
||||
await cg.register_parented(listener, var)
|
||||
listener = cg.new_Pvariable(tconf[CONF_ID], lpt, lprt, lv_component)
|
||||
lv.indev_drv_register(listener.get_drv())
|
||||
cg.add(touchscreen.register_listener(listener))
|
||||
|
|
|
@ -20,17 +20,16 @@ from .lvcode import (
|
|||
lv,
|
||||
lv_add,
|
||||
lv_event_t_ptr,
|
||||
lvgl_static,
|
||||
)
|
||||
from .types import LV_EVENT
|
||||
from .widgets import widget_map
|
||||
|
||||
|
||||
async def generate_triggers(lv_component):
|
||||
async def generate_triggers():
|
||||
"""
|
||||
Generate LVGL triggers for all defined widgets
|
||||
Must be done after all widgets completed
|
||||
:param lv_component: The parent component
|
||||
:return:
|
||||
"""
|
||||
|
||||
for w in widget_map.values():
|
||||
|
@ -43,11 +42,10 @@ async def generate_triggers(lv_component):
|
|||
conf = conf[0]
|
||||
w.add_flag("LV_OBJ_FLAG_CLICKABLE")
|
||||
event = literal("LV_EVENT_" + LV_EVENT_MAP[event[3:].upper()])
|
||||
await add_trigger(conf, lv_component, w, event)
|
||||
await add_trigger(conf, w, event)
|
||||
for conf in w.config.get(CONF_ON_VALUE, ()):
|
||||
await add_trigger(
|
||||
conf,
|
||||
lv_component,
|
||||
w,
|
||||
LV_EVENT.VALUE_CHANGED,
|
||||
API_EVENT,
|
||||
|
@ -63,7 +61,7 @@ async def generate_triggers(lv_component):
|
|||
lv.obj_align_to(w.obj, target, align, x, y)
|
||||
|
||||
|
||||
async def add_trigger(conf, lv_component, w, *events):
|
||||
async def add_trigger(conf, w, *events):
|
||||
tid = conf[CONF_TRIGGER_ID]
|
||||
trigger = cg.new_Pvariable(tid)
|
||||
args = w.get_args() + [(lv_event_t_ptr, "event")]
|
||||
|
@ -72,4 +70,4 @@ async def add_trigger(conf, lv_component, w, *events):
|
|||
async with LambdaContext(EVENT_ARG, where=tid) as context:
|
||||
with LvConditional(w.is_selected()):
|
||||
lv_add(trigger.trigger(*value, literal("event")))
|
||||
lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), *events))
|
||||
lv_add(lvgl_static.add_event_cb(w.obj, await context.get_lambda(), *events))
|
||||
|
|
|
@ -20,6 +20,7 @@ from ..lvcode import (
|
|||
add_line_marks,
|
||||
lv_add,
|
||||
lvgl_comp,
|
||||
lvgl_static,
|
||||
)
|
||||
from ..schemas import LVGL_SCHEMA
|
||||
from ..types import LvglAction, lv_page_t
|
||||
|
@ -139,7 +140,7 @@ async def add_pages(lv_component, config):
|
|||
await add_widgets(page, pconf)
|
||||
|
||||
|
||||
async def generate_page_triggers(lv_component, config):
|
||||
async def generate_page_triggers(config):
|
||||
for pconf in config.get(CONF_PAGES, ()):
|
||||
page = (await get_widgets(pconf))[0]
|
||||
for ev in (CONF_ON_LOAD, CONF_ON_UNLOAD):
|
||||
|
@ -149,7 +150,7 @@ async def generate_page_triggers(lv_component, config):
|
|||
async with LambdaContext(EVENT_ARG, where=id) as context:
|
||||
lv_add(trigger.trigger())
|
||||
lv_add(
|
||||
lv_component.add_event_cb(
|
||||
lvgl_static.add_event_cb(
|
||||
page.obj,
|
||||
await context.get_lambda(),
|
||||
literal(f"LV_EVENT_SCREEN_{ev[3:].upper()}_START"),
|
||||
|
|
|
@ -10,7 +10,7 @@ import sys
|
|||
import time
|
||||
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.helpers import is_ip_address, resolve_ip_address
|
||||
from esphome.helpers import resolve_ip_address
|
||||
|
||||
RESPONSE_OK = 0x00
|
||||
RESPONSE_REQUEST_AUTH = 0x01
|
||||
|
@ -311,44 +311,45 @@ def perform_ota(
|
|||
|
||||
|
||||
def run_ota_impl_(remote_host, remote_port, password, filename):
|
||||
if is_ip_address(remote_host):
|
||||
_LOGGER.info("Connecting to %s", remote_host)
|
||||
ip = remote_host
|
||||
else:
|
||||
_LOGGER.info("Resolving IP address of %s", remote_host)
|
||||
try:
|
||||
ip = resolve_ip_address(remote_host)
|
||||
except EsphomeError as err:
|
||||
_LOGGER.error(
|
||||
"Error resolving IP address of %s. Is it connected to WiFi?",
|
||||
remote_host,
|
||||
)
|
||||
_LOGGER.error(
|
||||
"(If this error persists, please set a static IP address: "
|
||||
"https://esphome.io/components/wifi.html#manual-ips)"
|
||||
)
|
||||
raise OTAError(err) from err
|
||||
_LOGGER.info(" -> %s", ip)
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(10.0)
|
||||
try:
|
||||
sock.connect((ip, remote_port))
|
||||
except OSError as err:
|
||||
sock.close()
|
||||
_LOGGER.error("Connecting to %s:%s failed: %s", remote_host, remote_port, err)
|
||||
return 1
|
||||
res = resolve_ip_address(remote_host, remote_port)
|
||||
except EsphomeError as err:
|
||||
_LOGGER.error(
|
||||
"Error resolving IP address of %s. Is it connected to WiFi?",
|
||||
remote_host,
|
||||
)
|
||||
_LOGGER.error(
|
||||
"(If this error persists, please set a static IP address: "
|
||||
"https://esphome.io/components/wifi.html#manual-ips)"
|
||||
)
|
||||
raise OTAError(err) from err
|
||||
|
||||
with open(filename, "rb") as file_handle:
|
||||
for r in res:
|
||||
af, socktype, _, _, sa = r
|
||||
_LOGGER.info("Connecting to %s port %s...", sa[0], sa[1])
|
||||
sock = socket.socket(af, socktype)
|
||||
sock.settimeout(10.0)
|
||||
try:
|
||||
perform_ota(sock, password, file_handle, filename)
|
||||
except OTAError as err:
|
||||
_LOGGER.error(str(err))
|
||||
return 1
|
||||
finally:
|
||||
sock.connect(sa)
|
||||
except OSError as err:
|
||||
sock.close()
|
||||
_LOGGER.error("Connecting to %s port %s failed: %s", sa[0], sa[1], err)
|
||||
continue
|
||||
|
||||
return 0
|
||||
_LOGGER.info("Connected to %s", sa[0])
|
||||
with open(filename, "rb") as file_handle:
|
||||
try:
|
||||
perform_ota(sock, password, file_handle, filename)
|
||||
except OTAError as err:
|
||||
_LOGGER.error(str(err))
|
||||
return 1
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
return 0
|
||||
|
||||
_LOGGER.error("Connection failed.")
|
||||
return 1
|
||||
|
||||
|
||||
def run_ota(remote_host, remote_port, password, filename):
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import codecs
|
||||
from contextlib import suppress
|
||||
import ipaddress
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
@ -91,12 +92,8 @@ def mkdir_p(path):
|
|||
|
||||
|
||||
def is_ip_address(host):
|
||||
parts = host.split(".")
|
||||
if len(parts) != 4:
|
||||
return False
|
||||
try:
|
||||
for p in parts:
|
||||
int(p)
|
||||
ipaddress.ip_address(host)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
@ -127,25 +124,80 @@ def _resolve_with_zeroconf(host):
|
|||
return info
|
||||
|
||||
|
||||
def resolve_ip_address(host):
|
||||
def addr_preference_(res):
|
||||
# Trivial alternative to RFC6724 sorting. Put sane IPv6 first, then
|
||||
# Legacy IP, then IPv6 link-local addresses without an actual link.
|
||||
sa = res[4]
|
||||
ip = ipaddress.ip_address(sa[0])
|
||||
if ip.version == 4:
|
||||
return 2
|
||||
if ip.is_link_local and sa[3] == 0:
|
||||
return 3
|
||||
return 1
|
||||
|
||||
|
||||
def resolve_ip_address(host, port):
|
||||
import socket
|
||||
|
||||
from esphome.core import EsphomeError
|
||||
|
||||
# There are five cases here. The host argument could be one of:
|
||||
# • a *list* of IP addresses discovered by MQTT,
|
||||
# • a single IP address specified by the user,
|
||||
# • a .local hostname to be resolved by mDNS,
|
||||
# • a normal hostname to be resolved in DNS, or
|
||||
# • A URL from which we should extract the hostname.
|
||||
#
|
||||
# In each of the first three cases, we end up with IP addresses in
|
||||
# string form which need to be converted to a 5-tuple to be used
|
||||
# for the socket connection attempt. The easiest way to construct
|
||||
# those is to pass the IP address string to getaddrinfo(). Which,
|
||||
# coincidentally, is how we do hostname lookups in the other cases
|
||||
# too. So first build a list which contains either IP addresses or
|
||||
# a single hostname, then call getaddrinfo() on each element of
|
||||
# that list.
|
||||
|
||||
errs = []
|
||||
if isinstance(host, list):
|
||||
addr_list = host
|
||||
elif is_ip_address(host):
|
||||
addr_list = [host]
|
||||
else:
|
||||
url = urlparse(host)
|
||||
if url.scheme != "":
|
||||
host = url.hostname
|
||||
|
||||
if host.endswith(".local"):
|
||||
addr_list = []
|
||||
if host.endswith(".local"):
|
||||
try:
|
||||
_LOGGER.info("Resolving IP address of %s in mDNS", host)
|
||||
addr_list = _resolve_with_zeroconf(host)
|
||||
except EsphomeError as err:
|
||||
errs.append(str(err))
|
||||
|
||||
# If not mDNS, or if mDNS failed, use normal DNS
|
||||
if not addr_list:
|
||||
addr_list = [host]
|
||||
|
||||
# Now we have a list containing either IP addresses or a hostname
|
||||
res = []
|
||||
for addr in addr_list:
|
||||
if not is_ip_address(addr):
|
||||
_LOGGER.info("Resolving IP address of %s", host)
|
||||
try:
|
||||
return _resolve_with_zeroconf(host)
|
||||
except EsphomeError as err:
|
||||
r = socket.getaddrinfo(addr, port, proto=socket.IPPROTO_TCP)
|
||||
except OSError as err:
|
||||
errs.append(str(err))
|
||||
raise EsphomeError(
|
||||
f"Error resolving IP address: {', '.join(errs)}"
|
||||
) from err
|
||||
|
||||
try:
|
||||
host_url = host if (urlparse(host).scheme != "") else "http://" + host
|
||||
return socket.gethostbyname(urlparse(host_url).hostname)
|
||||
except OSError as err:
|
||||
errs.append(str(err))
|
||||
raise EsphomeError(f"Error resolving IP address: {', '.join(errs)}") from err
|
||||
res = res + r
|
||||
|
||||
# Zeroconf tends to give us link-local IPv6 addresses without specifying
|
||||
# the link. Put those last in the list to be attempted.
|
||||
res.sort(key=addr_preference_)
|
||||
return res
|
||||
|
||||
|
||||
def get_bool_env(var, default=False):
|
||||
|
|
|
@ -175,8 +175,15 @@ def get_esphome_device_ip(
|
|||
_LOGGER.Warn("Wrong device answer")
|
||||
return
|
||||
|
||||
if "ip" in data:
|
||||
dev_ip = data["ip"]
|
||||
dev_ip = []
|
||||
key = "ip"
|
||||
n = 0
|
||||
while key in data:
|
||||
dev_ip.append(data[key])
|
||||
n = n + 1
|
||||
key = "ip" + str(n)
|
||||
|
||||
if dev_ip:
|
||||
client.disconnect()
|
||||
|
||||
def on_connect(client, userdata, flags, return_code):
|
||||
|
|
|
@ -182,8 +182,8 @@ class EsphomeZeroconf(Zeroconf):
|
|||
if (
|
||||
info.load_from_cache(self)
|
||||
or (timeout and info.request(self, timeout * 1000))
|
||||
) and (addresses := info.ip_addresses_by_version(IPVersion.V4Only)):
|
||||
return str(addresses[0])
|
||||
) and (addresses := info.parsed_scoped_addresses(IPVersion.All)):
|
||||
return addresses
|
||||
return None
|
||||
|
||||
|
||||
|
@ -194,6 +194,6 @@ class AsyncEsphomeZeroconf(AsyncZeroconf):
|
|||
if (
|
||||
info.load_from_cache(self.zeroconf)
|
||||
or (timeout and await info.async_request(self.zeroconf, timeout * 1000))
|
||||
) and (addresses := info.ip_addresses_by_version(IPVersion.V4Only)):
|
||||
return str(addresses[0])
|
||||
) and (addresses := info.parsed_scoped_addresses(IPVersion.All)):
|
||||
return addresses
|
||||
return None
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
display:
|
||||
- platform: sdl
|
||||
id: sdl0
|
||||
auto_clear_enabled: false
|
||||
dimensions:
|
||||
width: 480
|
||||
height: 320
|
||||
- platform: sdl
|
||||
id: sdl1
|
||||
auto_clear_enabled: false
|
||||
dimensions:
|
||||
width: 480
|
||||
|
@ -7,5 +14,30 @@ display:
|
|||
|
||||
touchscreen:
|
||||
- platform: sdl
|
||||
display: sdl0
|
||||
sdl_id: sdl0
|
||||
|
||||
lvgl:
|
||||
- id: lvgl_0
|
||||
displays: sdl0
|
||||
- id: lvgl_1
|
||||
displays: sdl1
|
||||
on_idle:
|
||||
timeout: 8s
|
||||
then:
|
||||
if:
|
||||
condition:
|
||||
lvgl.is_idle:
|
||||
lvgl_id: lvgl_1
|
||||
timeout: 5s
|
||||
then:
|
||||
logger.log: Lvgl2 is idle
|
||||
widgets:
|
||||
- button:
|
||||
align: center
|
||||
widgets:
|
||||
- label:
|
||||
text: Click ME
|
||||
on_click:
|
||||
logger.log: Clicked
|
||||
|
||||
|
|
Loading…
Reference in a new issue