mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'dev' into gsm
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -138,3 +138,5 @@ sdkconfig.* | |||||||
| .tests/ | .tests/ | ||||||
|  |  | ||||||
| /components | /components | ||||||
|  | /managed_components | ||||||
|  |  | ||||||
|   | |||||||
| @@ -60,7 +60,7 @@ bool AdE7953Spi::ade_read_16(uint16_t reg, uint16_t *value) { | |||||||
|   this->write_byte16(reg); |   this->write_byte16(reg); | ||||||
|   this->transfer_byte(0x80); |   this->transfer_byte(0x80); | ||||||
|   uint8_t recv[2]; |   uint8_t recv[2]; | ||||||
|   this->read_array(recv, 4); |   this->read_array(recv, 2); | ||||||
|   *value = encode_uint16(recv[0], recv[1]); |   *value = encode_uint16(recv[0], recv[1]); | ||||||
|   this->disable(); |   this->disable(); | ||||||
|   return false; |   return false; | ||||||
|   | |||||||
| @@ -1,25 +1,27 @@ | |||||||
| import base64 | import base64 | ||||||
|  |  | ||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome import automation | from esphome import automation | ||||||
| from esphome.automation import Condition | from esphome.automation import Condition | ||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|  |     CONF_ACTION, | ||||||
|  |     CONF_ACTIONS, | ||||||
|     CONF_DATA, |     CONF_DATA, | ||||||
|     CONF_DATA_TEMPLATE, |     CONF_DATA_TEMPLATE, | ||||||
|  |     CONF_EVENT, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_KEY, |     CONF_KEY, | ||||||
|  |     CONF_ON_CLIENT_CONNECTED, | ||||||
|  |     CONF_ON_CLIENT_DISCONNECTED, | ||||||
|     CONF_PASSWORD, |     CONF_PASSWORD, | ||||||
|     CONF_PORT, |     CONF_PORT, | ||||||
|     CONF_REBOOT_TIMEOUT, |     CONF_REBOOT_TIMEOUT, | ||||||
|     CONF_SERVICE, |     CONF_SERVICE, | ||||||
|     CONF_VARIABLES, |  | ||||||
|     CONF_SERVICES, |     CONF_SERVICES, | ||||||
|     CONF_TRIGGER_ID, |  | ||||||
|     CONF_EVENT, |  | ||||||
|     CONF_TAG, |     CONF_TAG, | ||||||
|     CONF_ON_CLIENT_CONNECTED, |     CONF_TRIGGER_ID, | ||||||
|     CONF_ON_CLIENT_DISCONNECTED, |     CONF_VARIABLES, | ||||||
| ) | ) | ||||||
| from esphome.core import coroutine_with_priority | from esphome.core import coroutine_with_priority | ||||||
|  |  | ||||||
| @@ -63,40 +65,51 @@ def validate_encryption_key(value): | |||||||
|     return value |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema( | ACTIONS_SCHEMA = automation.validate_automation( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(APIServer), |         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger), | ||||||
|         cv.Optional(CONF_PORT, default=6053): cv.port, |         cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.valid_name, | ||||||
|         cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, |         cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.valid_name, | ||||||
|         cv.Optional( |         cv.Optional(CONF_VARIABLES, default={}): cv.Schema( | ||||||
|             CONF_REBOOT_TIMEOUT, default="15min" |  | ||||||
|         ): cv.positive_time_period_milliseconds, |  | ||||||
|         cv.Optional(CONF_SERVICES): automation.validate_automation( |  | ||||||
|             { |             { | ||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger), |                 cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True), | ||||||
|                 cv.Required(CONF_SERVICE): cv.valid_name, |  | ||||||
|                 cv.Optional(CONF_VARIABLES, default={}): cv.Schema( |  | ||||||
|                     { |  | ||||||
|                         cv.validate_id_name: cv.one_of( |  | ||||||
|                             *SERVICE_ARG_NATIVE_TYPES, lower=True |  | ||||||
|                         ), |  | ||||||
|                     } |  | ||||||
|                 ), |  | ||||||
|             } |             } | ||||||
|         ), |         ), | ||||||
|         cv.Optional(CONF_ENCRYPTION): cv.Schema( |     }, | ||||||
|             { |     cv.All( | ||||||
|                 cv.Required(CONF_KEY): validate_encryption_key, |         cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION), | ||||||
|             } |         cv.rename_key(CONF_SERVICE, CONF_ACTION), | ||||||
|         ), |     ), | ||||||
|         cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( | ) | ||||||
|             single=True |  | ||||||
|         ), | CONFIG_SCHEMA = cv.All( | ||||||
|         cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation( |     cv.Schema( | ||||||
|             single=True |         { | ||||||
|         ), |             cv.GenerateID(): cv.declare_id(APIServer), | ||||||
|     } |             cv.Optional(CONF_PORT, default=6053): cv.port, | ||||||
| ).extend(cv.COMPONENT_SCHEMA) |             cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, | ||||||
|  |             cv.Optional( | ||||||
|  |                 CONF_REBOOT_TIMEOUT, default="15min" | ||||||
|  |             ): cv.positive_time_period_milliseconds, | ||||||
|  |             cv.Exclusive( | ||||||
|  |                 CONF_SERVICES, group_of_exclusion=CONF_ACTIONS | ||||||
|  |             ): ACTIONS_SCHEMA, | ||||||
|  |             cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA, | ||||||
|  |             cv.Optional(CONF_ENCRYPTION): cv.Schema( | ||||||
|  |                 { | ||||||
|  |                     cv.Required(CONF_KEY): validate_encryption_key, | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( | ||||||
|  |                 single=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation( | ||||||
|  |                 single=True | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ).extend(cv.COMPONENT_SCHEMA), | ||||||
|  |     cv.rename_key(CONF_SERVICES, CONF_ACTIONS), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine_with_priority(40.0) | @coroutine_with_priority(40.0) | ||||||
| @@ -108,7 +121,7 @@ async def to_code(config): | |||||||
|     cg.add(var.set_password(config[CONF_PASSWORD])) |     cg.add(var.set_password(config[CONF_PASSWORD])) | ||||||
|     cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) |     cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_SERVICES, []): |     for conf in config.get(CONF_ACTIONS, []): | ||||||
|         template_args = [] |         template_args = [] | ||||||
|         func_args = [] |         func_args = [] | ||||||
|         service_arg_names = [] |         service_arg_names = [] | ||||||
| @@ -119,7 +132,7 @@ async def to_code(config): | |||||||
|             service_arg_names.append(name) |             service_arg_names.append(name) | ||||||
|         templ = cg.TemplateArguments(*template_args) |         templ = cg.TemplateArguments(*template_args) | ||||||
|         trigger = cg.new_Pvariable( |         trigger = cg.new_Pvariable( | ||||||
|             conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names |             conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names | ||||||
|         ) |         ) | ||||||
|         cg.add(var.register_user_service(trigger)) |         cg.add(var.register_user_service(trigger)) | ||||||
|         await automation.build_automation(trigger, func_args, conf) |         await automation.build_automation(trigger, func_args, conf) | ||||||
| @@ -152,28 +165,43 @@ async def to_code(config): | |||||||
|  |  | ||||||
| KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)}) | KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)}) | ||||||
|  |  | ||||||
| HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema( |  | ||||||
|     { | HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All( | ||||||
|         cv.GenerateID(): cv.use_id(APIServer), |     cv.Schema( | ||||||
|         cv.Required(CONF_SERVICE): cv.templatable(cv.string), |         { | ||||||
|         cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, |             cv.GenerateID(): cv.use_id(APIServer), | ||||||
|         cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, |             cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.templatable( | ||||||
|         cv.Optional(CONF_VARIABLES, default={}): cv.Schema( |                 cv.string | ||||||
|             {cv.string: cv.returning_lambda} |             ), | ||||||
|         ), |             cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.templatable( | ||||||
|     } |                 cv.string | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, | ||||||
|  |             cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, | ||||||
|  |             cv.Optional(CONF_VARIABLES, default={}): cv.Schema( | ||||||
|  |                 {cv.string: cv.returning_lambda} | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  |     cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION), | ||||||
|  |     cv.rename_key(CONF_SERVICE, CONF_ACTION), | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "homeassistant.action", | ||||||
|  |     HomeAssistantServiceCallAction, | ||||||
|  |     HOMEASSISTANT_ACTION_ACTION_SCHEMA, | ||||||
|  | ) | ||||||
| @automation.register_action( | @automation.register_action( | ||||||
|     "homeassistant.service", |     "homeassistant.service", | ||||||
|     HomeAssistantServiceCallAction, |     HomeAssistantServiceCallAction, | ||||||
|     HOMEASSISTANT_SERVICE_ACTION_SCHEMA, |     HOMEASSISTANT_ACTION_ACTION_SCHEMA, | ||||||
| ) | ) | ||||||
| async def homeassistant_service_to_code(config, action_id, template_arg, args): | async def homeassistant_service_to_code(config, action_id, template_arg, args): | ||||||
|     serv = await cg.get_variable(config[CONF_ID]) |     serv = await cg.get_variable(config[CONF_ID]) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, serv, False) |     var = cg.new_Pvariable(action_id, template_arg, serv, False) | ||||||
|     templ = await cg.templatable(config[CONF_SERVICE], args, None) |     templ = await cg.templatable(config[CONF_ACTION], args, None) | ||||||
|     cg.add(var.set_service(templ)) |     cg.add(var.set_service(templ)) | ||||||
|     for key, value in config[CONF_DATA].items(): |     for key, value in config[CONF_DATA].items(): | ||||||
|         templ = await cg.templatable(value, args, None) |         templ = await cg.templatable(value, args, None) | ||||||
|   | |||||||
| @@ -138,8 +138,8 @@ void HttpRequestUpdate::update() { | |||||||
|   this->publish_state(); |   this->publish_state(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void HttpRequestUpdate::perform() { | void HttpRequestUpdate::perform(bool force) { | ||||||
|   if (this->state_ != update::UPDATE_STATE_AVAILABLE) { |   if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) { | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent { | |||||||
|   void setup() override; |   void setup() override; | ||||||
|   void update() override; |   void update() override; | ||||||
|  |  | ||||||
|   void perform() override; |   void perform(bool force) override; | ||||||
|  |  | ||||||
|   void set_source_url(const std::string &source_url) { this->source_url_ = source_url; } |   void set_source_url(const std::string &source_url) { this->source_url_ = source_url; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,8 +1,7 @@ | |||||||
| import re | import re | ||||||
|  |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import __version__ | from esphome.const import __version__ | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| @@ -39,4 +38,4 @@ def _process_next_url(url: str): | |||||||
| async def setup_improv_core(var, config): | async def setup_improv_core(var, config): | ||||||
|     if CONF_NEXT_URL in config: |     if CONF_NEXT_URL in config: | ||||||
|         cg.add(var.set_next_url(_process_next_url(config[CONF_NEXT_URL]))) |         cg.add(var.set_next_url(_process_next_url(config[CONF_NEXT_URL]))) | ||||||
|     cg.add_library("esphome/Improv", "1.2.3") |     cg.add_library("improv/Improv", "1.2.4") | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import logging | import logging | ||||||
|  |  | ||||||
|  | from esphome.automation import build_automation, register_action, validate_automation | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components.display import Display | from esphome.components.display import Display | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| @@ -8,7 +9,11 @@ from esphome.const import ( | |||||||
|     CONF_BUFFER_SIZE, |     CONF_BUFFER_SIZE, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_LAMBDA, |     CONF_LAMBDA, | ||||||
|  |     CONF_ON_IDLE, | ||||||
|     CONF_PAGES, |     CONF_PAGES, | ||||||
|  |     CONF_TIMEOUT, | ||||||
|  |     CONF_TRIGGER_ID, | ||||||
|  |     CONF_TYPE, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, ID, Lambda | from esphome.core import CORE, ID, Lambda | ||||||
| from esphome.cpp_generator import MockObj | from esphome.cpp_generator import MockObj | ||||||
| @@ -16,21 +21,26 @@ from esphome.final_validate import full_config | |||||||
| from esphome.helpers import write_file_if_changed | from esphome.helpers import write_file_if_changed | ||||||
|  |  | ||||||
| from . import defines as df, helpers, lv_validation as lvalid | from . import defines as df, helpers, lv_validation as lvalid | ||||||
|  | from .automation import update_to_code | ||||||
| from .btn import btn_spec | from .btn import btn_spec | ||||||
| from .label import label_spec | from .label import label_spec | ||||||
| from .lvcode import ConstantLiteral, LvContext | from .lv_validation import lv_images_used | ||||||
|  | from .lvcode import LvContext | ||||||
| from .obj import obj_spec | from .obj import obj_spec | ||||||
| from .schemas import any_widget_schema, obj_schema | from .rotary_encoders import ROTARY_ENCODER_CONFIG, rotary_encoders_to_code | ||||||
|  | from .schemas import any_widget_schema, create_modify_schema, obj_schema | ||||||
| from .touchscreens import touchscreen_schema, touchscreens_to_code | from .touchscreens import touchscreen_schema, touchscreens_to_code | ||||||
|  | from .trigger import generate_triggers | ||||||
| from .types import ( | from .types import ( | ||||||
|     WIDGET_TYPES, |     WIDGET_TYPES, | ||||||
|     FontEngine, |     FontEngine, | ||||||
|  |     IdleTrigger, | ||||||
|     LvglComponent, |     LvglComponent, | ||||||
|     lv_disp_t_ptr, |     ObjUpdateAction, | ||||||
|     lv_font_t, |     lv_font_t, | ||||||
|     lvgl_ns, |     lvgl_ns, | ||||||
| ) | ) | ||||||
| from .widget import LvScrActType, Widget, add_widgets, set_obj_properties | from .widget import Widget, add_widgets, lv_scr_act, set_obj_properties | ||||||
|  |  | ||||||
| DOMAIN = "lvgl" | DOMAIN = "lvgl" | ||||||
| DEPENDENCIES = ("display",) | DEPENDENCIES = ("display",) | ||||||
| @@ -41,17 +51,21 @@ LOGGER = logging.getLogger(__name__) | |||||||
| for w_type in (label_spec, obj_spec, btn_spec): | for w_type in (label_spec, obj_spec, btn_spec): | ||||||
|     WIDGET_TYPES[w_type.name] = w_type |     WIDGET_TYPES[w_type.name] = w_type | ||||||
|  |  | ||||||
| lv_scr_act_spec = LvScrActType() |  | ||||||
| lv_scr_act = Widget.create( |  | ||||||
|     None, ConstantLiteral("lv_scr_act()"), lv_scr_act_spec, {}, parent=None |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| WIDGET_SCHEMA = any_widget_schema() | WIDGET_SCHEMA = any_widget_schema() | ||||||
|  |  | ||||||
|  | for w_type in WIDGET_TYPES.values(): | ||||||
|  |     register_action( | ||||||
|  |         f"lvgl.{w_type.name}.update", | ||||||
|  |         ObjUpdateAction, | ||||||
|  |         create_modify_schema(w_type), | ||||||
|  |     )(update_to_code) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def add_init_lambda(lv_component, init): | async def add_init_lambda(lv_component, init): | ||||||
|     if init: |     if init: | ||||||
|         lamb = await cg.process_lambda(Lambda(init), [(lv_disp_t_ptr, "lv_disp")]) |         lamb = await cg.process_lambda( | ||||||
|  |             Lambda(init), [(LvglComponent.operator("ptr"), "lv_component")] | ||||||
|  |         ) | ||||||
|         cg.add(lv_component.add_init_lambda(lamb)) |         cg.add(lv_component.add_init_lambda(lamb)) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -99,6 +113,13 @@ def final_validation(config): | |||||||
|     buffer_frac = config[CONF_BUFFER_SIZE] |     buffer_frac = config[CONF_BUFFER_SIZE] | ||||||
|     if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config: |     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") |         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 | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
| @@ -174,9 +195,15 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     with LvContext(): |     with LvContext(): | ||||||
|         await touchscreens_to_code(lv_component, config) |         await touchscreens_to_code(lv_component, config) | ||||||
|  |         await rotary_encoders_to_code(lv_component, config) | ||||||
|         await set_obj_properties(lv_scr_act, config) |         await set_obj_properties(lv_scr_act, config) | ||||||
|         await add_widgets(lv_scr_act, config) |         await add_widgets(lv_scr_act, config) | ||||||
|     Widget.set_completed() |         Widget.set_completed() | ||||||
|  |         await generate_triggers(lv_component) | ||||||
|  |         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) | ||||||
|     await add_init_lambda(lv_component, LvContext.get_code()) |     await add_init_lambda(lv_component, LvContext.get_code()) | ||||||
|     for comp in helpers.lvgl_components_required: |     for comp in helpers.lvgl_components_required: | ||||||
|         CORE.add_define(f"USE_LVGL_{comp.upper()}") |         CORE.add_define(f"USE_LVGL_{comp.upper()}") | ||||||
| @@ -212,9 +239,18 @@ CONFIG_SCHEMA = ( | |||||||
|             cv.Optional(df.CONF_BYTE_ORDER, default="big_endian"): cv.one_of( |             cv.Optional(df.CONF_BYTE_ORDER, default="big_endian"): cv.one_of( | ||||||
|                 "big_endian", "little_endian" |                 "big_endian", "little_endian" | ||||||
|             ), |             ), | ||||||
|  |             cv.Optional(CONF_ON_IDLE): validate_automation( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(IdleTrigger), | ||||||
|  |                     cv.Required(CONF_TIMEOUT): cv.templatable( | ||||||
|  |                         cv.positive_time_period_milliseconds | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|             cv.Optional(df.CONF_WIDGETS): cv.ensure_list(WIDGET_SCHEMA), |             cv.Optional(df.CONF_WIDGETS): cv.ensure_list(WIDGET_SCHEMA), | ||||||
|             cv.Optional(df.CONF_TRANSPARENCY_KEY, default=0x000400): lvalid.lv_color, |             cv.Optional(df.CONF_TRANSPARENCY_KEY, default=0x000400): lvalid.lv_color, | ||||||
|             cv.GenerateID(df.CONF_TOUCHSCREENS): touchscreen_schema, |             cv.GenerateID(df.CONF_TOUCHSCREENS): touchscreen_schema, | ||||||
|  |             cv.GenerateID(df.CONF_ROTARY_ENCODERS): ROTARY_ENCODER_CONFIG, | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
| ).add_extra(cv.has_at_least_one_key(CONF_PAGES, df.CONF_WIDGETS)) | ).add_extra(cv.has_at_least_one_key(CONF_PAGES, df.CONF_WIDGETS)) | ||||||
|   | |||||||
							
								
								
									
										188
									
								
								esphome/components/lvgl/automation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								esphome/components/lvgl/automation.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | |||||||
|  | from esphome import automation | ||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_ID, CONF_TIMEOUT | ||||||
|  | from esphome.core import Lambda | ||||||
|  | from esphome.cpp_generator import RawStatement | ||||||
|  | from esphome.cpp_types import nullptr | ||||||
|  |  | ||||||
|  | from .defines import CONF_LVGL_ID, CONF_SHOW_SNOW, literal | ||||||
|  | from .lv_validation import lv_bool | ||||||
|  | from .lvcode import ( | ||||||
|  |     LambdaContext, | ||||||
|  |     ReturnStatement, | ||||||
|  |     add_line_marks, | ||||||
|  |     lv, | ||||||
|  |     lv_add, | ||||||
|  |     lv_obj, | ||||||
|  |     lvgl_comp, | ||||||
|  | ) | ||||||
|  | from .schemas import ACTION_SCHEMA, LVGL_SCHEMA | ||||||
|  | from .types import ( | ||||||
|  |     LvglAction, | ||||||
|  |     LvglComponent, | ||||||
|  |     LvglComponentPtr, | ||||||
|  |     LvglCondition, | ||||||
|  |     ObjUpdateAction, | ||||||
|  |     lv_obj_t, | ||||||
|  | ) | ||||||
|  | from .widget import Widget, get_widget, lv_scr_act, set_obj_properties | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def action_to_code(action: list, action_id, widget: Widget, template_arg, args): | ||||||
|  |     with LambdaContext() as context: | ||||||
|  |         lv.cond_if(widget.obj == nullptr) | ||||||
|  |         lv_add(RawStatement("  return;")) | ||||||
|  |         lv.cond_endif() | ||||||
|  |     code = context.get_code() | ||||||
|  |     code.extend(action) | ||||||
|  |     action = "\n".join(code) + "\n\n" | ||||||
|  |     lamb = await cg.process_lambda(Lambda(action), args) | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg, lamb) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def update_to_code(config, action_id, template_arg, args): | ||||||
|  |     if config is not None: | ||||||
|  |         widget = await get_widget(config) | ||||||
|  |         with LambdaContext() as context: | ||||||
|  |             add_line_marks(action_id) | ||||||
|  |             await set_obj_properties(widget, config) | ||||||
|  |             await widget.type.to_code(widget, config) | ||||||
|  |             if ( | ||||||
|  |                 widget.type.w_type.value_property is not None | ||||||
|  |                 and widget.type.w_type.value_property in config | ||||||
|  |             ): | ||||||
|  |                 lv.event_send(widget.obj, literal("LV_EVENT_VALUE_CHANGED"), nullptr) | ||||||
|  |         return await action_to_code( | ||||||
|  |             context.get_code(), action_id, widget, template_arg, args | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_condition( | ||||||
|  |     "lvgl.is_paused", | ||||||
|  |     LvglCondition, | ||||||
|  |     LVGL_SCHEMA, | ||||||
|  | ) | ||||||
|  | async def lvgl_is_paused(config, condition_id, template_arg, args): | ||||||
|  |     lvgl = config[CONF_LVGL_ID] | ||||||
|  |     with LambdaContext( | ||||||
|  |         [(LvglComponentPtr, "lvgl_comp")], return_type=cg.bool_ | ||||||
|  |     ) as context: | ||||||
|  |         lv_add(ReturnStatement(lvgl_comp.is_paused())) | ||||||
|  |     var = cg.new_Pvariable(condition_id, template_arg, await context.get_lambda()) | ||||||
|  |     await cg.register_parented(var, lvgl) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_condition( | ||||||
|  |     "lvgl.is_idle", | ||||||
|  |     LvglCondition, | ||||||
|  |     LVGL_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_TIMEOUT): cv.templatable( | ||||||
|  |                 cv.positive_time_period_milliseconds | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def lvgl_is_idle(config, condition_id, template_arg, args): | ||||||
|  |     lvgl = config[CONF_LVGL_ID] | ||||||
|  |     timeout = await cg.templatable(config[CONF_TIMEOUT], [], cg.uint32) | ||||||
|  |     with LambdaContext( | ||||||
|  |         [(LvglComponentPtr, "lvgl_comp")], return_type=cg.bool_ | ||||||
|  |     ) as context: | ||||||
|  |         lv_add(ReturnStatement(lvgl_comp.is_idle(timeout))) | ||||||
|  |     var = cg.new_Pvariable(condition_id, template_arg, await context.get_lambda()) | ||||||
|  |     await cg.register_parented(var, lvgl) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.widget.redraw", | ||||||
|  |     ObjUpdateAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.Optional(CONF_ID): cv.use_id(lv_obj_t), | ||||||
|  |             cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def obj_invalidate_to_code(config, action_id, template_arg, args): | ||||||
|  |     if CONF_ID in config: | ||||||
|  |         w = await get_widget(config) | ||||||
|  |     else: | ||||||
|  |         w = lv_scr_act | ||||||
|  |     with LambdaContext() as context: | ||||||
|  |         add_line_marks(action_id) | ||||||
|  |         lv_obj.invalidate(w.obj) | ||||||
|  |     return await action_to_code(context.get_code(), action_id, w, template_arg, args) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.pause", | ||||||
|  |     LvglAction, | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.use_id(LvglComponent), | ||||||
|  |         cv.Optional(CONF_SHOW_SNOW, default=False): lv_bool, | ||||||
|  |     }, | ||||||
|  | ) | ||||||
|  | async def pause_action_to_code(config, action_id, template_arg, args): | ||||||
|  |     with LambdaContext([(LvglComponentPtr, "lvgl_comp")]) as context: | ||||||
|  |         add_line_marks(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]) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.resume", | ||||||
|  |     LvglAction, | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.use_id(LvglComponent), | ||||||
|  |     }, | ||||||
|  | ) | ||||||
|  | async def resume_action_to_code(config, action_id, template_arg, args): | ||||||
|  |     with LambdaContext([(LvglComponentPtr, "lvgl_comp")]) as context: | ||||||
|  |         add_line_marks(action_id) | ||||||
|  |         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]) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action("lvgl.widget.disable", ObjUpdateAction, ACTION_SCHEMA) | ||||||
|  | async def obj_disable_to_code(config, action_id, template_arg, args): | ||||||
|  |     w = await get_widget(config) | ||||||
|  |     with LambdaContext() as context: | ||||||
|  |         add_line_marks(action_id) | ||||||
|  |         w.add_state("LV_STATE_DISABLED") | ||||||
|  |     return await action_to_code(context.get_code(), action_id, w, template_arg, args) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action("lvgl.widget.enable", ObjUpdateAction, ACTION_SCHEMA) | ||||||
|  | async def obj_enable_to_code(config, action_id, template_arg, args): | ||||||
|  |     w = await get_widget(config) | ||||||
|  |     with LambdaContext() as context: | ||||||
|  |         add_line_marks(action_id) | ||||||
|  |         w.clear_state("LV_STATE_DISABLED") | ||||||
|  |     return await action_to_code(context.get_code(), action_id, w, template_arg, args) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action("lvgl.widget.hide", ObjUpdateAction, ACTION_SCHEMA) | ||||||
|  | async def obj_hide_to_code(config, action_id, template_arg, args): | ||||||
|  |     w = await get_widget(config) | ||||||
|  |     with LambdaContext() as context: | ||||||
|  |         add_line_marks(action_id) | ||||||
|  |         w.add_flag("LV_OBJ_FLAG_HIDDEN") | ||||||
|  |     return await action_to_code(context.get_code(), action_id, w, template_arg, args) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action("lvgl.widget.show", ObjUpdateAction, ACTION_SCHEMA) | ||||||
|  | async def obj_show_to_code(config, action_id, template_arg, args): | ||||||
|  |     w = await get_widget(config) | ||||||
|  |     with LambdaContext() as context: | ||||||
|  |         add_line_marks(action_id) | ||||||
|  |         w.clear_flag("LV_OBJ_FLAG_HIDDEN") | ||||||
|  |     return await action_to_code(context.get_code(), action_id, w, template_arg, args) | ||||||
| @@ -9,9 +9,6 @@ class BtnType(WidgetType): | |||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         super().__init__(CONF_BUTTON, LvBoolean("lv_btn_t"), (CONF_MAIN,)) |         super().__init__(CONF_BUTTON, LvBoolean("lv_btn_t"), (CONF_MAIN,)) | ||||||
|  |  | ||||||
|     async def to_code(self, w, config): |  | ||||||
|         return [] |  | ||||||
|  |  | ||||||
|     def obj_creator(self, parent: MockObjClass, config: dict): |     def obj_creator(self, parent: MockObjClass, config: dict): | ||||||
|         """ |         """ | ||||||
|         LVGL 8 calls buttons `btn` |         LVGL 8 calls buttons `btn` | ||||||
| @@ -21,5 +18,8 @@ class BtnType(WidgetType): | |||||||
|     def get_uses(self): |     def get_uses(self): | ||||||
|         return ("btn",) |         return ("btn",) | ||||||
|  |  | ||||||
|  |     async def to_code(self, w, config): | ||||||
|  |         return [] | ||||||
|  |  | ||||||
|  |  | ||||||
| btn_spec = BtnType() | btn_spec = BtnType() | ||||||
|   | |||||||
| @@ -4,12 +4,32 @@ Constants already defined in esphome.const are not duplicated here and must be i | |||||||
|  |  | ||||||
| """ | """ | ||||||
|  |  | ||||||
|  | from typing import Union | ||||||
|  |  | ||||||
| from esphome import codegen as cg, config_validation as cv | from esphome import codegen as cg, config_validation as cv | ||||||
| from esphome.core import ID, Lambda | from esphome.core import ID, Lambda | ||||||
|  | from esphome.cpp_generator import Literal | ||||||
| from esphome.cpp_types import uint32 | from esphome.cpp_types import uint32 | ||||||
| from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor | from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor | ||||||
|  |  | ||||||
| from .lvcode import ConstantLiteral | from .helpers import requires_component | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ConstantLiteral(Literal): | ||||||
|  |     __slots__ = ("constant",) | ||||||
|  |  | ||||||
|  |     def __init__(self, constant: str): | ||||||
|  |         super().__init__() | ||||||
|  |         self.constant = constant | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return self.constant | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def literal(arg: Union[str, ConstantLiteral]): | ||||||
|  |     if isinstance(arg, str): | ||||||
|  |         return ConstantLiteral(arg) | ||||||
|  |     return arg | ||||||
|  |  | ||||||
|  |  | ||||||
| class LValidator: | class LValidator: | ||||||
| @@ -18,14 +38,19 @@ class LValidator: | |||||||
|     has `process()` to convert a value during code generation |     has `process()` to convert a value during code generation | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, validator, rtype, idtype=None, idexpr=None, retmapper=None): |     def __init__( | ||||||
|  |         self, validator, rtype, idtype=None, idexpr=None, retmapper=None, requires=None | ||||||
|  |     ): | ||||||
|         self.validator = validator |         self.validator = validator | ||||||
|         self.rtype = rtype |         self.rtype = rtype | ||||||
|         self.idtype = idtype |         self.idtype = idtype | ||||||
|         self.idexpr = idexpr |         self.idexpr = idexpr | ||||||
|         self.retmapper = retmapper |         self.retmapper = retmapper | ||||||
|  |         self.requires = requires | ||||||
|  |  | ||||||
|     def __call__(self, value): |     def __call__(self, value): | ||||||
|  |         if self.requires: | ||||||
|  |             value = requires_component(self.requires)(value) | ||||||
|         if isinstance(value, cv.Lambda): |         if isinstance(value, cv.Lambda): | ||||||
|             return cv.returning_lambda(value) |             return cv.returning_lambda(value) | ||||||
|         if self.idtype is not None and isinstance(value, ID): |         if self.idtype is not None and isinstance(value, ID): | ||||||
| @@ -422,6 +447,7 @@ CONF_RECOLOR = "recolor" | |||||||
| CONF_RIGHT_BUTTON = "right_button" | CONF_RIGHT_BUTTON = "right_button" | ||||||
| CONF_ROLLOVER = "rollover" | CONF_ROLLOVER = "rollover" | ||||||
| CONF_ROOT_BACK_BTN = "root_back_btn" | CONF_ROOT_BACK_BTN = "root_back_btn" | ||||||
|  | CONF_ROTARY_ENCODERS = "rotary_encoders" | ||||||
| CONF_ROWS = "rows" | CONF_ROWS = "rows" | ||||||
| CONF_SCALES = "scales" | CONF_SCALES = "scales" | ||||||
| CONF_SCALE_LINES = "scale_lines" | CONF_SCALE_LINES = "scale_lines" | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import esphome.codegen as cg | |||||||
| from esphome.components.binary_sensor import BinarySensor | from esphome.components.binary_sensor import BinarySensor | ||||||
| from esphome.components.color import ColorStruct | from esphome.components.color import ColorStruct | ||||||
| from esphome.components.font import Font | from esphome.components.font import Font | ||||||
|  | from esphome.components.image import Image_ | ||||||
| from esphome.components.sensor import Sensor | from esphome.components.sensor import Sensor | ||||||
| from esphome.components.text_sensor import TextSensor | from esphome.components.text_sensor import TextSensor | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| @@ -13,22 +14,15 @@ from esphome.helpers import cpp_string_escape | |||||||
| from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor | from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor | ||||||
|  |  | ||||||
| from . import types as ty | from . import types as ty | ||||||
| from .defines import LV_FONTS, LValidator, LvConstant | from .defines import LV_FONTS, ConstantLiteral, LValidator, LvConstant, literal | ||||||
| from .helpers import ( | from .helpers import ( | ||||||
|     esphome_fonts_used, |     esphome_fonts_used, | ||||||
|     lv_fonts_used, |     lv_fonts_used, | ||||||
|     lvgl_components_required, |     lvgl_components_required, | ||||||
|     requires_component, |     requires_component, | ||||||
| ) | ) | ||||||
| from .lvcode import ConstantLiteral, lv_expr | from .lvcode import lv_expr | ||||||
| from .types import lv_font_t | from .types import lv_font_t, lv_img_t | ||||||
|  |  | ||||||
|  |  | ||||||
| def literal_mapper(value, args=()): |  | ||||||
|     if isinstance(value, str): |  | ||||||
|         return ConstantLiteral(value) |  | ||||||
|     return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") | opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") | ||||||
|  |  | ||||||
| @@ -43,7 +37,7 @@ def opacity_validator(value): | |||||||
|     return value |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
| opacity = LValidator(opacity_validator, uint32, retmapper=literal_mapper) | opacity = LValidator(opacity_validator, uint32, retmapper=literal) | ||||||
|  |  | ||||||
|  |  | ||||||
| @schema_extractor("one_of") | @schema_extractor("one_of") | ||||||
| @@ -79,9 +73,7 @@ def pixels_or_percent_validator(value): | |||||||
|     return f"lv_pct({int(cv.percentage(value) * 100)})" |     return f"lv_pct({int(cv.percentage(value) * 100)})" | ||||||
|  |  | ||||||
|  |  | ||||||
| pixels_or_percent = LValidator( | pixels_or_percent = LValidator(pixels_or_percent_validator, uint32, retmapper=literal) | ||||||
|     pixels_or_percent_validator, uint32, retmapper=literal_mapper |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def zoom(value): | def zoom(value): | ||||||
| @@ -115,7 +107,7 @@ def size_validator(value): | |||||||
|     return f"lv_pct({int(cv.percentage(value) * 100)})" |     return f"lv_pct({int(cv.percentage(value) * 100)})" | ||||||
|  |  | ||||||
|  |  | ||||||
| size = LValidator(size_validator, uint32, retmapper=literal_mapper) | size = LValidator(size_validator, uint32, retmapper=literal) | ||||||
|  |  | ||||||
| radius_consts = LvConstant("LV_RADIUS_", "CIRCLE") | radius_consts = LvConstant("LV_RADIUS_", "CIRCLE") | ||||||
|  |  | ||||||
| @@ -130,21 +122,37 @@ def radius_validator(value): | |||||||
|     return value |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | radius = LValidator(radius_validator, uint32, retmapper=literal) | ||||||
|  |  | ||||||
|  |  | ||||||
| def id_name(value): | def id_name(value): | ||||||
|     if value == SCHEMA_EXTRACT: |     if value == SCHEMA_EXTRACT: | ||||||
|         return "id" |         return "id" | ||||||
|     return cv.validate_id_name(value) |     return cv.validate_id_name(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| radius = LValidator(radius_validator, uint32, retmapper=literal_mapper) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def stop_value(value): | def stop_value(value): | ||||||
|     return cv.int_range(0, 255)(value) |     return cv.int_range(0, 255)(value) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | lv_images_used = set() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def image_validator(value): | ||||||
|  |     value = requires_component("image")(value) | ||||||
|  |     value = cv.use_id(Image_)(value) | ||||||
|  |     lv_images_used.add(value) | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | lv_image = LValidator( | ||||||
|  |     image_validator, | ||||||
|  |     lv_img_t, | ||||||
|  |     retmapper=lambda x: lv_expr.img_from(MockObj(x)), | ||||||
|  |     requires="image", | ||||||
|  | ) | ||||||
| lv_bool = LValidator( | lv_bool = LValidator( | ||||||
|     cv.boolean, cg.bool_, BinarySensor, "get_state()", retmapper=literal_mapper |     cv.boolean, cg.bool_, BinarySensor, "get_state()", retmapper=literal | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,8 +8,8 @@ from esphome.cpp_generator import ( | |||||||
|     AssignmentExpression, |     AssignmentExpression, | ||||||
|     CallExpression, |     CallExpression, | ||||||
|     Expression, |     Expression, | ||||||
|  |     ExpressionStatement, | ||||||
|     LambdaExpression, |     LambdaExpression, | ||||||
|     Literal, |  | ||||||
|     MockObj, |     MockObj, | ||||||
|     RawExpression, |     RawExpression, | ||||||
|     RawStatement, |     RawStatement, | ||||||
| @@ -19,7 +19,9 @@ from esphome.cpp_generator import ( | |||||||
|     statement, |     statement, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | from .defines import ConstantLiteral | ||||||
| from .helpers import get_line_marks | from .helpers import get_line_marks | ||||||
|  | from .types import lv_group_t | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| @@ -105,29 +107,40 @@ class LambdaContext(CodeContext): | |||||||
|  |  | ||||||
|     def __init__( |     def __init__( | ||||||
|         self, |         self, | ||||||
|         parameters: list[tuple[SafeExpType, str]], |         parameters: list[tuple[SafeExpType, str]] = None, | ||||||
|         return_type: SafeExpType = None, |         return_type: SafeExpType = cg.void, | ||||||
|  |         capture: str = "", | ||||||
|     ): |     ): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|         self.code_list: list[Statement] = [] |         self.code_list: list[Statement] = [] | ||||||
|         self.parameters = parameters |         self.parameters = parameters | ||||||
|         self.return_type = return_type |         self.return_type = return_type | ||||||
|  |         self.capture = capture | ||||||
|  |  | ||||||
|     def add(self, expression: Union[Expression, Statement]): |     def add(self, expression: Union[Expression, Statement]): | ||||||
|         self.code_list.append(expression) |         self.code_list.append(expression) | ||||||
|         return expression |         return expression | ||||||
|  |  | ||||||
|     async def code(self) -> LambdaExpression: |     async def get_lambda(self) -> LambdaExpression: | ||||||
|  |         code_text = self.get_code() | ||||||
|  |         return await cg.process_lambda( | ||||||
|  |             Lambda("\n".join(code_text) + "\n\n"), | ||||||
|  |             self.parameters, | ||||||
|  |             capture=self.capture, | ||||||
|  |             return_type=self.return_type, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def get_code(self): | ||||||
|         code_text = [] |         code_text = [] | ||||||
|         for exp in self.code_list: |         for exp in self.code_list: | ||||||
|             text = str(statement(exp)) |             text = str(statement(exp)) | ||||||
|             text = text.rstrip() |             text = text.rstrip() | ||||||
|             code_text.append(text) |             code_text.append(text) | ||||||
|         return await cg.process_lambda( |         return code_text | ||||||
|             Lambda("\n".join(code_text) + "\n\n"), |  | ||||||
|             self.parameters, |     def __enter__(self): | ||||||
|             return_type=self.return_type, |         super().__enter__() | ||||||
|         ) |         return self | ||||||
|  |  | ||||||
|  |  | ||||||
| class LocalVariable(MockObj): | class LocalVariable(MockObj): | ||||||
| @@ -187,13 +200,18 @@ class MockLv: | |||||||
|         return result |         return result | ||||||
|  |  | ||||||
|     def cond_if(self, expression: Expression): |     def cond_if(self, expression: Expression): | ||||||
|         CodeContext.append(RawExpression(f"if({expression}) {{")) |         CodeContext.append(RawStatement(f"if {expression} {{")) | ||||||
|  |  | ||||||
|     def cond_else(self): |     def cond_else(self): | ||||||
|         CodeContext.append(RawExpression("} else {")) |         CodeContext.append(RawStatement("} else {")) | ||||||
|  |  | ||||||
|     def cond_endif(self): |     def cond_endif(self): | ||||||
|         CodeContext.append(RawExpression("}")) |         CodeContext.append(RawStatement("}")) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ReturnStatement(ExpressionStatement): | ||||||
|  |     def __str__(self): | ||||||
|  |         return f"return {self.expression};" | ||||||
|  |  | ||||||
|  |  | ||||||
| class LvExpr(MockLv): | class LvExpr(MockLv): | ||||||
| @@ -210,6 +228,7 @@ lv = MockLv("lv_") | |||||||
| lv_expr = LvExpr("lv_") | lv_expr = LvExpr("lv_") | ||||||
| # Mock for lv_obj_ calls | # Mock for lv_obj_ calls | ||||||
| lv_obj = MockLv("lv_obj_") | lv_obj = MockLv("lv_obj_") | ||||||
|  | lvgl_comp = MockObj("lvgl_comp", "->") | ||||||
|  |  | ||||||
|  |  | ||||||
| # equivalent to cg.add() for the lvgl init context | # equivalent to cg.add() for the lvgl init context | ||||||
| @@ -226,12 +245,19 @@ def lv_assign(target, expression): | |||||||
|     lv_add(RawExpression(f"{target} = {expression}")) |     lv_add(RawExpression(f"{target} = {expression}")) | ||||||
|  |  | ||||||
|  |  | ||||||
| class ConstantLiteral(Literal): | lv_groups = {}  # Widget group names | ||||||
|     __slots__ = ("constant",) |  | ||||||
|  |  | ||||||
|     def __init__(self, constant: str): |  | ||||||
|         super().__init__() |  | ||||||
|         self.constant = constant |  | ||||||
|  |  | ||||||
|     def __str__(self): | def add_group(name): | ||||||
|         return self.constant |     if name is None: | ||||||
|  |         return None | ||||||
|  |     fullname = f"lv_esp_group_{name}" | ||||||
|  |     if name not in lv_groups: | ||||||
|  |         gid = ID(fullname, True, type=lv_group_t.operator("ptr")) | ||||||
|  |         lv_add( | ||||||
|  |             AssignmentExpression( | ||||||
|  |                 type_=gid.type, modifier="", name=fullname, rhs=lv_expr.group_create() | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |         lv_groups[name] = ConstantLiteral(fullname) | ||||||
|  |     return lv_groups[name] | ||||||
|   | |||||||
| @@ -19,13 +19,35 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, const uint8_t *ptr) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void LvglComponent::flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { | void LvglComponent::flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { | ||||||
|   auto now = millis(); |   if (!this->paused_) { | ||||||
|   this->draw_buffer_(area, (const uint8_t *) color_p); |     auto now = millis(); | ||||||
|   ESP_LOGV(TAG, "flush_cb, area=%d/%d, %d/%d took %dms", area->x1, area->y1, lv_area_get_width(area), |     this->draw_buffer_(area, (const uint8_t *) color_p); | ||||||
|            lv_area_get_height(area), (int) (millis() - now)); |     ESP_LOGV(TAG, "flush_cb, area=%d/%d, %d/%d took %dms", area->x1, area->y1, lv_area_get_width(area), | ||||||
|  |              lv_area_get_height(area), (int) (millis() - now)); | ||||||
|  |   } | ||||||
|   lv_disp_flush_ready(disp_drv); |   lv_disp_flush_ready(disp_drv); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void LvglComponent::write_random_() { | ||||||
|  |   // length of 2 lines in 32 bit units | ||||||
|  |   // we write 2 lines for the benefit of displays that won't write one line at a time. | ||||||
|  |   size_t line_len = this->disp_drv_.hor_res * LV_COLOR_DEPTH / 8 / 4 * 2; | ||||||
|  |   for (size_t i = 0; i != line_len; i++) { | ||||||
|  |     ((uint32_t *) (this->draw_buf_.buf1))[i] = random_uint32(); | ||||||
|  |   } | ||||||
|  |   lv_area_t area; | ||||||
|  |   area.x1 = 0; | ||||||
|  |   area.x2 = this->disp_drv_.hor_res - 1; | ||||||
|  |   if (this->snow_line_ == this->disp_drv_.ver_res / 2) { | ||||||
|  |     area.y1 = static_cast<lv_coord_t>(random_uint32() % (this->disp_drv_.ver_res / 2) * 2); | ||||||
|  |   } else { | ||||||
|  |     area.y1 = this->snow_line_++ * 2; | ||||||
|  |   } | ||||||
|  |   // write 2 lines | ||||||
|  |   area.y2 = area.y1 + 1; | ||||||
|  |   this->draw_buffer_(&area, (const uint8_t *) this->draw_buf_.buf1); | ||||||
|  | } | ||||||
|  |  | ||||||
| void LvglComponent::setup() { | void LvglComponent::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "LVGL Setup starts"); |   ESP_LOGCONFIG(TAG, "LVGL Setup starts"); | ||||||
| #if LV_USE_LOG | #if LV_USE_LOG | ||||||
| @@ -74,10 +96,53 @@ void LvglComponent::setup() { | |||||||
|   ESP_LOGV(TAG, "sw_rotate = %d, rotated=%d", this->disp_drv_.sw_rotate, this->disp_drv_.rotated); |   ESP_LOGV(TAG, "sw_rotate = %d, rotated=%d", this->disp_drv_.sw_rotate, this->disp_drv_.rotated); | ||||||
|   this->disp_ = lv_disp_drv_register(&this->disp_drv_); |   this->disp_ = lv_disp_drv_register(&this->disp_drv_); | ||||||
|   for (const auto &v : this->init_lambdas_) |   for (const auto &v : this->init_lambdas_) | ||||||
|     v(this->disp_); |     v(this); | ||||||
|   lv_disp_trig_activity(this->disp_); |   lv_disp_trig_activity(this->disp_); | ||||||
|   ESP_LOGCONFIG(TAG, "LVGL Setup complete"); |   ESP_LOGCONFIG(TAG, "LVGL Setup complete"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #ifdef USE_LVGL_IMAGE | ||||||
|  | lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc) { | ||||||
|  |   if (img_dsc == nullptr) | ||||||
|  |     img_dsc = new lv_img_dsc_t();  // NOLINT | ||||||
|  |   img_dsc->header.always_zero = 0; | ||||||
|  |   img_dsc->header.reserved = 0; | ||||||
|  |   img_dsc->header.w = src->get_width(); | ||||||
|  |   img_dsc->header.h = src->get_height(); | ||||||
|  |   img_dsc->data = src->get_data_start(); | ||||||
|  |   img_dsc->data_size = image_type_to_width_stride(img_dsc->header.w * img_dsc->header.h, src->get_type()); | ||||||
|  |   switch (src->get_type()) { | ||||||
|  |     case image::IMAGE_TYPE_BINARY: | ||||||
|  |       img_dsc->header.cf = LV_IMG_CF_ALPHA_1BIT; | ||||||
|  |       break; | ||||||
|  |  | ||||||
|  |     case image::IMAGE_TYPE_GRAYSCALE: | ||||||
|  |       img_dsc->header.cf = LV_IMG_CF_ALPHA_8BIT; | ||||||
|  |       break; | ||||||
|  |  | ||||||
|  |     case image::IMAGE_TYPE_RGB24: | ||||||
|  |       img_dsc->header.cf = LV_IMG_CF_RGB888; | ||||||
|  |       break; | ||||||
|  |  | ||||||
|  |     case image::IMAGE_TYPE_RGB565: | ||||||
|  | #if LV_COLOR_DEPTH == 16 | ||||||
|  |       img_dsc->header.cf = src->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR; | ||||||
|  | #else | ||||||
|  |       img_dsc->header.cf = LV_IMG_CF_RGB565; | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |  | ||||||
|  |     case image::IMAGE_TYPE_RGBA: | ||||||
|  | #if LV_COLOR_DEPTH == 32 | ||||||
|  |       img_dsc->header.cf = LV_IMG_CF_TRUE_COLOR; | ||||||
|  | #else | ||||||
|  |       img_dsc->header.cf = LV_IMG_CF_RGBA8888; | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |   return img_dsc; | ||||||
|  | } | ||||||
|  | #endif | ||||||
| }  // namespace lvgl | }  // namespace lvgl | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,23 +1,32 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #ifdef USE_LVGL |  | ||||||
|  | #ifdef USE_LVGL_BINARY_SENSOR | ||||||
|  | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
|  | #endif  // USE_LVGL_BINARY_SENSOR | ||||||
|  | #ifdef USE_LVGL_ROTARY_ENCODER | ||||||
|  | #include "esphome/components/rotary_encoder/rotary_encoder.h" | ||||||
|  | #endif  // USE_LVGL_ROTARY_ENCODER | ||||||
|  |  | ||||||
| // required for clang-tidy | // required for clang-tidy | ||||||
| #ifndef LV_CONF_H | #ifndef LV_CONF_H | ||||||
| #define LV_CONF_SKIP 1  // NOLINT | #define LV_CONF_SKIP 1  // NOLINT | ||||||
| #endif | #endif                  // LV_CONF_H | ||||||
|  |  | ||||||
| #include "esphome/components/display/display.h" | #include "esphome/components/display/display.h" | ||||||
| #include "esphome/components/display/display_color_utils.h" | #include "esphome/components/display/display_color_utils.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/hal.h" |  | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include <lvgl.h> | #include <lvgl.h> | ||||||
|  | #include <utility> | ||||||
| #include <vector> | #include <vector> | ||||||
|  | #ifdef USE_LVGL_IMAGE | ||||||
|  | #include "esphome/components/image/image.h" | ||||||
|  | #endif  // USE_LVGL_IMAGE | ||||||
|  |  | ||||||
| #ifdef USE_LVGL_FONT | #ifdef USE_LVGL_FONT | ||||||
| #include "esphome/components/font/font.h" | #include "esphome/components/font/font.h" | ||||||
| #endif | #endif  // USE_LVGL_FONT | ||||||
| #ifdef USE_LVGL_TOUCHSCREEN | #ifdef USE_LVGL_TOUCHSCREEN | ||||||
| #include "esphome/components/touchscreen/touchscreen.h" | #include "esphome/components/touchscreen/touchscreen.h" | ||||||
| #endif  // USE_LVGL_TOUCHSCREEN | #endif  // USE_LVGL_TOUCHSCREEN | ||||||
| @@ -40,7 +49,7 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT | |||||||
| // Parent class for things that wrap an LVGL object | // Parent class for things that wrap an LVGL object | ||||||
| class LvCompound final { | class LvCompound final { | ||||||
|  public: |  public: | ||||||
|   virtual void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; } |   void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; } | ||||||
|   lv_obj_t *obj{}; |   lv_obj_t *obj{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -49,6 +58,15 @@ using set_value_lambda_t = std::function<void(float)>; | |||||||
| using event_callback_t = void(_lv_event_t *); | using event_callback_t = void(_lv_event_t *); | ||||||
| using text_lambda_t = std::function<const char *()>; | using text_lambda_t = std::function<const char *()>; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class ObjUpdateAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   explicit ObjUpdateAction(std::function<void(Ts...)> &&lamb) : lamb_(std::move(lamb)) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { this->lamb_(x...); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   std::function<void(Ts...)> lamb_; | ||||||
|  | }; | ||||||
| #ifdef USE_LVGL_FONT | #ifdef USE_LVGL_FONT | ||||||
| class FontEngine { | class FontEngine { | ||||||
|  public: |  public: | ||||||
| @@ -67,6 +85,9 @@ class FontEngine { | |||||||
|   lv_font_t lv_font_{}; |   lv_font_t lv_font_{}; | ||||||
| }; | }; | ||||||
| #endif  // USE_LVGL_FONT | #endif  // USE_LVGL_FONT | ||||||
|  | #ifdef USE_LVGL_IMAGE | ||||||
|  | lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc = nullptr); | ||||||
|  | #endif  // USE_LVGL_IMAGE | ||||||
|  |  | ||||||
| class LvglComponent : public PollingComponent { | class LvglComponent : public PollingComponent { | ||||||
|   constexpr static const char *const TAG = "lvgl"; |   constexpr static const char *const TAG = "lvgl"; | ||||||
| @@ -92,27 +113,54 @@ class LvglComponent : public PollingComponent { | |||||||
|       area->y2++; |       area->y2++; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void loop() override { lv_timer_handler_run_in_period(5); } |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
|  |  | ||||||
|   void update() override {} |   void update() override { | ||||||
|  |     // update indicators | ||||||
|  |     if (this->paused_) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this->idle_callbacks_.call(lv_disp_get_inactive_time(this->disp_)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void loop() override { | ||||||
|  |     if (this->paused_) { | ||||||
|  |       if (this->show_snow_) | ||||||
|  |         this->write_random_(); | ||||||
|  |     } | ||||||
|  |     lv_timer_handler_run_in_period(5); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void add_on_idle_callback(std::function<void(uint32_t)> &&callback) { | ||||||
|  |     this->idle_callbacks_.add(std::move(callback)); | ||||||
|  |   } | ||||||
|   void add_display(display::Display *display) { this->displays_.push_back(display); } |   void add_display(display::Display *display) { this->displays_.push_back(display); } | ||||||
|   void add_init_lambda(const std::function<void(lv_disp_t *)> &lamb) { this->init_lambdas_.push_back(lamb); } |   void add_init_lambda(const std::function<void(LvglComponent *)> &lamb) { this->init_lambdas_.push_back(lamb); } | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void set_full_refresh(bool full_refresh) { this->full_refresh_ = full_refresh; } |   void set_full_refresh(bool full_refresh) { this->full_refresh_ = full_refresh; } | ||||||
|  |   bool is_idle(uint32_t idle_ms) { return lv_disp_get_inactive_time(this->disp_) > idle_ms; } | ||||||
|   void set_buffer_frac(size_t frac) { this->buffer_frac_ = frac; } |   void set_buffer_frac(size_t frac) { this->buffer_frac_ = frac; } | ||||||
|   lv_disp_t *get_disp() { return this->disp_; } |   lv_disp_t *get_disp() { return this->disp_; } | ||||||
|   void set_paused(bool paused, bool show_snow) { |   void set_paused(bool paused, bool show_snow) { | ||||||
|     this->paused_ = paused; |     this->paused_ = paused; | ||||||
|  |     this->show_snow_ = show_snow; | ||||||
|  |     this->snow_line_ = 0; | ||||||
|     if (!paused && lv_scr_act() != nullptr) { |     if (!paused && lv_scr_act() != nullptr) { | ||||||
|       lv_disp_trig_activity(this->disp_);  // resets the inactivity time |       lv_disp_trig_activity(this->disp_);  // resets the inactivity time | ||||||
|       lv_obj_invalidate(lv_scr_act()); |       lv_obj_invalidate(lv_scr_act()); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void 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); | ||||||
|  |     if (event == LV_EVENT_VALUE_CHANGED) { | ||||||
|  |       lv_obj_add_event_cb(obj, callback, lv_custom_event, this); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   bool is_paused() const { return this->paused_; } |   bool is_paused() const { return this->paused_; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|  |   void write_random_(); | ||||||
|   void draw_buffer_(const lv_area_t *area, const uint8_t *ptr); |   void draw_buffer_(const lv_area_t *area, const uint8_t *ptr); | ||||||
|   void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p); |   void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p); | ||||||
|   std::vector<display::Display *> displays_{}; |   std::vector<display::Display *> displays_{}; | ||||||
| @@ -120,12 +168,52 @@ class LvglComponent : public PollingComponent { | |||||||
|   lv_disp_drv_t disp_drv_{}; |   lv_disp_drv_t disp_drv_{}; | ||||||
|   lv_disp_t *disp_{}; |   lv_disp_t *disp_{}; | ||||||
|   bool paused_{}; |   bool paused_{}; | ||||||
|  |   bool show_snow_{}; | ||||||
|  |   lv_coord_t snow_line_{}; | ||||||
|  |  | ||||||
|   std::vector<std::function<void(lv_disp_t *)>> init_lambdas_; |   std::vector<std::function<void(LvglComponent *lv_component)>> init_lambdas_; | ||||||
|  |   CallbackManager<void(uint32_t)> idle_callbacks_{}; | ||||||
|   size_t buffer_frac_{1}; |   size_t buffer_frac_{1}; | ||||||
|   bool full_refresh_{}; |   bool full_refresh_{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class IdleTrigger : public Trigger<> { | ||||||
|  |  public: | ||||||
|  |   explicit IdleTrigger(LvglComponent *parent, TemplatableValue<uint32_t> timeout) : timeout_(std::move(timeout)) { | ||||||
|  |     parent->add_on_idle_callback([this](uint32_t idle_time) { | ||||||
|  |       if (!this->is_idle_ && idle_time > this->timeout_.value()) { | ||||||
|  |         this->is_idle_ = true; | ||||||
|  |         this->trigger(); | ||||||
|  |       } else if (this->is_idle_ && idle_time < this->timeout_.value()) { | ||||||
|  |         this->is_idle_ = false; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   TemplatableValue<uint32_t> timeout_; | ||||||
|  |   bool is_idle_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class LvglAction : public Action<Ts...>, public Parented<LvglComponent> { | ||||||
|  |  public: | ||||||
|  |   explicit LvglAction(std::function<void(LvglComponent *)> &&lamb) : action_(std::move(lamb)) {} | ||||||
|  |   void play(Ts... x) override { this->action_(this->parent_); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   std::function<void(LvglComponent *)> action_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class LvglCondition : public Condition<Ts...>, public Parented<LvglComponent> { | ||||||
|  |  public: | ||||||
|  |   LvglCondition(std::function<bool(LvglComponent *)> &&condition_lambda) | ||||||
|  |       : condition_lambda_(std::move(condition_lambda)) {} | ||||||
|  |   bool check(Ts... x) override { return this->condition_lambda_(this->parent_); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   std::function<bool(LvglComponent *)> condition_lambda_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
| #ifdef USE_LVGL_TOUCHSCREEN | #ifdef USE_LVGL_TOUCHSCREEN | ||||||
| class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglComponent> { | class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglComponent> { | ||||||
|  public: |  public: | ||||||
| @@ -160,7 +248,62 @@ class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglC | |||||||
|   bool touch_pressed_{}; |   bool touch_pressed_{}; | ||||||
| }; | }; | ||||||
| #endif  // USE_LVGL_TOUCHSCREEN | #endif  // USE_LVGL_TOUCHSCREEN | ||||||
|  |  | ||||||
|  | #ifdef USE_LVGL_KEY_LISTENER | ||||||
|  | class LVEncoderListener : public Parented<LvglComponent> { | ||||||
|  |  public: | ||||||
|  |   LVEncoderListener(lv_indev_type_t type, uint16_t lpt, uint16_t lprt) { | ||||||
|  |     lv_indev_drv_init(&this->drv_); | ||||||
|  |     this->drv_.type = type; | ||||||
|  |     this->drv_.user_data = this; | ||||||
|  |     this->drv_.long_press_time = lpt; | ||||||
|  |     this->drv_.long_press_repeat_time = lprt; | ||||||
|  |     this->drv_.read_cb = [](lv_indev_drv_t *d, lv_indev_data_t *data) { | ||||||
|  |       auto *l = static_cast<LVEncoderListener *>(d->user_data); | ||||||
|  |       data->state = l->pressed_ ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; | ||||||
|  |       data->key = l->key_; | ||||||
|  |       data->enc_diff = (int16_t) (l->count_ - l->last_count_); | ||||||
|  |       l->last_count_ = l->count_; | ||||||
|  |       data->continue_reading = false; | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_left_button(binary_sensor::BinarySensor *left_button) { | ||||||
|  |     left_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_LEFT, state); }); | ||||||
|  |   } | ||||||
|  |   void set_right_button(binary_sensor::BinarySensor *right_button) { | ||||||
|  |     right_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_RIGHT, state); }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_enter_button(binary_sensor::BinarySensor *enter_button) { | ||||||
|  |     enter_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_ENTER, state); }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_sensor(rotary_encoder::RotaryEncoderSensor *sensor) { | ||||||
|  |     sensor->register_listener([this](int32_t count) { this->set_count(count); }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void event(int key, bool pressed) { | ||||||
|  |     if (!this->parent_->is_paused()) { | ||||||
|  |       this->pressed_ = pressed; | ||||||
|  |       this->key_ = key; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_count(int32_t count) { | ||||||
|  |     if (!this->parent_->is_paused()) | ||||||
|  |       this->count_ = count; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   lv_indev_drv_t *get_drv() { return &this->drv_; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   lv_indev_drv_t drv_{}; | ||||||
|  |   bool pressed_{}; | ||||||
|  |   int32_t count_{}; | ||||||
|  |   int32_t last_count_{}; | ||||||
|  |   int key_{}; | ||||||
|  | }; | ||||||
|  | #endif  // USE_LVGL_KEY_LISTENER | ||||||
| }  // namespace lvgl | }  // namespace lvgl | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
| #endif  // USE_LVGL |  | ||||||
|   | |||||||
| @@ -1,5 +1,9 @@ | |||||||
|  | from esphome import automation | ||||||
|  |  | ||||||
|  | from .automation import update_to_code | ||||||
| from .defines import CONF_MAIN, CONF_OBJ | from .defines import CONF_MAIN, CONF_OBJ | ||||||
| from .types import WidgetType, lv_obj_t | from .schemas import create_modify_schema | ||||||
|  | from .types import ObjUpdateAction, WidgetType, lv_obj_t | ||||||
|  |  | ||||||
|  |  | ||||||
| class ObjType(WidgetType): | class ObjType(WidgetType): | ||||||
| @@ -15,3 +19,10 @@ class ObjType(WidgetType): | |||||||
|  |  | ||||||
|  |  | ||||||
| obj_spec = ObjType() | obj_spec = ObjType() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.widget.update", ObjUpdateAction, create_modify_schema(obj_spec) | ||||||
|  | ) | ||||||
|  | async def obj_update_to_code(config, action_id, template_arg, args): | ||||||
|  |     return await update_to_code(config, action_id, template_arg, args) | ||||||
|   | |||||||
							
								
								
									
										62
									
								
								esphome/components/lvgl/rotary_encoders.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								esphome/components/lvgl/rotary_encoders.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components.binary_sensor import BinarySensor | ||||||
|  | from esphome.components.rotary_encoder.sensor import RotaryEncoderSensor | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_GROUP, CONF_ID, CONF_SENSOR | ||||||
|  |  | ||||||
|  | from .defines import ( | ||||||
|  |     CONF_ENTER_BUTTON, | ||||||
|  |     CONF_LEFT_BUTTON, | ||||||
|  |     CONF_LONG_PRESS_REPEAT_TIME, | ||||||
|  |     CONF_LONG_PRESS_TIME, | ||||||
|  |     CONF_RIGHT_BUTTON, | ||||||
|  |     CONF_ROTARY_ENCODERS, | ||||||
|  | ) | ||||||
|  | from .helpers import lvgl_components_required | ||||||
|  | from .lvcode import add_group, lv, lv_add, lv_expr | ||||||
|  | from .schemas import ENCODER_SCHEMA | ||||||
|  | from .types import lv_indev_type_t | ||||||
|  |  | ||||||
|  | ROTARY_ENCODER_CONFIG = cv.ensure_list( | ||||||
|  |     ENCODER_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_ENTER_BUTTON): cv.use_id(BinarySensor), | ||||||
|  |             cv.Required(CONF_SENSOR): cv.Any( | ||||||
|  |                 cv.use_id(RotaryEncoderSensor), | ||||||
|  |                 cv.Schema( | ||||||
|  |                     { | ||||||
|  |                         cv.Required(CONF_LEFT_BUTTON): cv.use_id(BinarySensor), | ||||||
|  |                         cv.Required(CONF_RIGHT_BUTTON): cv.use_id(BinarySensor), | ||||||
|  |                     } | ||||||
|  |                 ), | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def rotary_encoders_to_code(var, config): | ||||||
|  |     for enc_conf in config.get(CONF_ROTARY_ENCODERS, ()): | ||||||
|  |         lvgl_components_required.add("KEY_LISTENER") | ||||||
|  |         lvgl_components_required.add("ROTARY_ENCODER") | ||||||
|  |         lpt = enc_conf[CONF_LONG_PRESS_TIME].total_milliseconds | ||||||
|  |         lprt = enc_conf[CONF_LONG_PRESS_REPEAT_TIME].total_milliseconds | ||||||
|  |         listener = cg.new_Pvariable( | ||||||
|  |             enc_conf[CONF_ID], lv_indev_type_t.LV_INDEV_TYPE_ENCODER, lpt, lprt | ||||||
|  |         ) | ||||||
|  |         await cg.register_parented(listener, var) | ||||||
|  |         if sensor_config := enc_conf.get(CONF_SENSOR): | ||||||
|  |             if isinstance(sensor_config, dict): | ||||||
|  |                 b_sensor = await cg.get_variable(sensor_config[CONF_LEFT_BUTTON]) | ||||||
|  |                 cg.add(listener.set_left_button(b_sensor)) | ||||||
|  |                 b_sensor = await cg.get_variable(sensor_config[CONF_RIGHT_BUTTON]) | ||||||
|  |                 cg.add(listener.set_right_button(b_sensor)) | ||||||
|  |             else: | ||||||
|  |                 sensor_config = await cg.get_variable(sensor_config) | ||||||
|  |                 lv_add(listener.set_sensor(sensor_config)) | ||||||
|  |         b_sensor = await cg.get_variable(enc_conf[CONF_ENTER_BUTTON]) | ||||||
|  |         cg.add(listener.set_enter_button(b_sensor)) | ||||||
|  |         if group := add_group(enc_conf.get(CONF_GROUP)): | ||||||
|  |             lv.indev_set_group(lv_expr.indev_drv_register(listener.get_drv()), group) | ||||||
|  |         else: | ||||||
|  |             lv.indev_drv_register(listener.get_drv()) | ||||||
| @@ -1,10 +1,21 @@ | |||||||
| from esphome import config_validation as cv | from esphome import config_validation as cv | ||||||
| from esphome.const import CONF_ARGS, CONF_FORMAT, CONF_ID, CONF_STATE, CONF_TYPE | from esphome.automation import Trigger, validate_automation | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ARGS, | ||||||
|  |     CONF_FORMAT, | ||||||
|  |     CONF_GROUP, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_ON_VALUE, | ||||||
|  |     CONF_STATE, | ||||||
|  |     CONF_TRIGGER_ID, | ||||||
|  |     CONF_TYPE, | ||||||
|  | ) | ||||||
|  | from esphome.core import TimePeriod | ||||||
| from esphome.schema_extractors import SCHEMA_EXTRACT | from esphome.schema_extractors import SCHEMA_EXTRACT | ||||||
|  |  | ||||||
| from . import defines as df, lv_validation as lvalid, types as ty | from . import defines as df, lv_validation as lvalid, types as ty | ||||||
| from .helpers import add_lv_use, requires_component, validate_printf | from .helpers import add_lv_use, requires_component, validate_printf | ||||||
| from .lv_validation import lv_font | from .lv_validation import id_name, lv_font | ||||||
| from .types import WIDGET_TYPES, WidgetType | from .types import WIDGET_TYPES, WidgetType | ||||||
|  |  | ||||||
| # A schema for text properties | # A schema for text properties | ||||||
| @@ -27,6 +38,28 @@ TEXT_SCHEMA = cv.Schema( | |||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | ACTION_SCHEMA = cv.maybe_simple_value( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_ID): cv.use_id(ty.lv_pseudo_button_t), | ||||||
|  |     }, | ||||||
|  |     key=CONF_ID, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | PRESS_TIME = cv.All( | ||||||
|  |     lvalid.lv_milliseconds, cv.Range(max=TimePeriod(milliseconds=65535)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | ENCODER_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.All( | ||||||
|  |             cv.declare_id(ty.LVEncoderListener), requires_component("binary_sensor") | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_GROUP): lvalid.id_name, | ||||||
|  |         cv.Optional(df.CONF_LONG_PRESS_TIME, default="400ms"): PRESS_TIME, | ||||||
|  |         cv.Optional(df.CONF_LONG_PRESS_REPEAT_TIME, default="100ms"): PRESS_TIME, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
| # All LVGL styles and their validators | # All LVGL styles and their validators | ||||||
| STYLE_PROPS = { | STYLE_PROPS = { | ||||||
|     "align": df.CHILD_ALIGNMENTS.one_of, |     "align": df.CHILD_ALIGNMENTS.one_of, | ||||||
| @@ -43,6 +76,7 @@ STYLE_PROPS = { | |||||||
|     "bg_image_opa": lvalid.opacity, |     "bg_image_opa": lvalid.opacity, | ||||||
|     "bg_image_recolor": lvalid.lv_color, |     "bg_image_recolor": lvalid.lv_color, | ||||||
|     "bg_image_recolor_opa": lvalid.opacity, |     "bg_image_recolor_opa": lvalid.opacity, | ||||||
|  |     "bg_image_src": lvalid.lv_image, | ||||||
|     "bg_main_stop": lvalid.stop_value, |     "bg_main_stop": lvalid.stop_value, | ||||||
|     "bg_opa": lvalid.opacity, |     "bg_opa": lvalid.opacity, | ||||||
|     "border_color": lvalid.lv_color, |     "border_color": lvalid.lv_color, | ||||||
| @@ -151,6 +185,39 @@ def part_schema(widget_type: WidgetType): | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def automation_schema(typ: ty.LvType): | ||||||
|  |     if typ.has_on_value: | ||||||
|  |         events = df.LV_EVENT_TRIGGERS + (CONF_ON_VALUE,) | ||||||
|  |     else: | ||||||
|  |         events = df.LV_EVENT_TRIGGERS | ||||||
|  |     if isinstance(typ, ty.LvType): | ||||||
|  |         template = Trigger.template(typ.get_arg_type()) | ||||||
|  |     else: | ||||||
|  |         template = Trigger.template() | ||||||
|  |     return { | ||||||
|  |         cv.Optional(event): validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(template), | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |         for event in events | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def create_modify_schema(widget_type): | ||||||
|  |     return ( | ||||||
|  |         part_schema(widget_type) | ||||||
|  |         .extend( | ||||||
|  |             { | ||||||
|  |                 cv.Required(CONF_ID): cv.use_id(widget_type), | ||||||
|  |                 cv.Optional(CONF_STATE): SET_STATE_SCHEMA, | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |         .extend(FLAG_SCHEMA) | ||||||
|  |         .extend(widget_type.modify_schema) | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def obj_schema(widget_type: WidgetType): | def obj_schema(widget_type: WidgetType): | ||||||
|     """ |     """ | ||||||
|     Create a schema for a widget type itself i.e. no allowance for children |     Create a schema for a widget type itself i.e. no allowance for children | ||||||
| @@ -161,10 +228,12 @@ def obj_schema(widget_type: WidgetType): | |||||||
|         part_schema(widget_type) |         part_schema(widget_type) | ||||||
|         .extend(FLAG_SCHEMA) |         .extend(FLAG_SCHEMA) | ||||||
|         .extend(ALIGN_TO_SCHEMA) |         .extend(ALIGN_TO_SCHEMA) | ||||||
|  |         .extend(automation_schema(widget_type.w_type)) | ||||||
|         .extend( |         .extend( | ||||||
|             cv.Schema( |             cv.Schema( | ||||||
|                 { |                 { | ||||||
|                     cv.Optional(CONF_STATE): SET_STATE_SCHEMA, |                     cv.Optional(CONF_STATE): SET_STATE_SCHEMA, | ||||||
|  |                     cv.Optional(CONF_GROUP): id_name, | ||||||
|                 } |                 } | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
| @@ -188,6 +257,13 @@ STYLED_TEXT_SCHEMA = cv.maybe_simple_value( | |||||||
|     STYLE_SCHEMA.extend(TEXT_SCHEMA), key=df.CONF_TEXT |     STYLE_SCHEMA.extend(TEXT_SCHEMA), key=df.CONF_TEXT | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | # For use by platform components | ||||||
|  | LVGL_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(df.CONF_LVGL_ID): cv.use_id(ty.LvglComponent), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
| ALL_STYLES = { | ALL_STYLES = { | ||||||
|     **STYLE_PROPS, |     **STYLE_PROPS, | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import esphome.codegen as cg | |||||||
| from esphome.components.touchscreen import CONF_TOUCHSCREEN_ID, Touchscreen | from esphome.components.touchscreen import CONF_TOUCHSCREEN_ID, Touchscreen | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
| from esphome.core import CORE, TimePeriod | from esphome.core import CORE | ||||||
|  |  | ||||||
| from .defines import ( | from .defines import ( | ||||||
|     CONF_LONG_PRESS_REPEAT_TIME, |     CONF_LONG_PRESS_REPEAT_TIME, | ||||||
| @@ -10,11 +10,10 @@ from .defines import ( | |||||||
|     CONF_TOUCHSCREENS, |     CONF_TOUCHSCREENS, | ||||||
| ) | ) | ||||||
| from .helpers import lvgl_components_required | from .helpers import lvgl_components_required | ||||||
| from .lv_validation import lv_milliseconds |  | ||||||
| from .lvcode import lv | from .lvcode import lv | ||||||
|  | from .schemas import PRESS_TIME | ||||||
| from .types import LVTouchListener | from .types import LVTouchListener | ||||||
|  |  | ||||||
| PRESS_TIME = cv.All(lv_milliseconds, cv.Range(max=TimePeriod(milliseconds=65535))) |  | ||||||
| CONF_TOUCHSCREEN = "touchscreen" | CONF_TOUCHSCREEN = "touchscreen" | ||||||
| TOUCHSCREENS_CONFIG = cv.maybe_simple_value( | TOUCHSCREENS_CONFIG = cv.maybe_simple_value( | ||||||
|     { |     { | ||||||
|   | |||||||
							
								
								
									
										61
									
								
								esphome/components/lvgl/trigger.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								esphome/components/lvgl/trigger.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | from esphome import automation | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.const import CONF_ID, CONF_ON_VALUE, CONF_TRIGGER_ID | ||||||
|  |  | ||||||
|  | from .defines import ( | ||||||
|  |     CONF_ALIGN, | ||||||
|  |     CONF_ALIGN_TO, | ||||||
|  |     CONF_X, | ||||||
|  |     CONF_Y, | ||||||
|  |     LV_EVENT, | ||||||
|  |     LV_EVENT_TRIGGERS, | ||||||
|  |     literal, | ||||||
|  | ) | ||||||
|  | from .lvcode import LambdaContext, add_line_marks, lv, lv_add | ||||||
|  | from .widget import widget_map | ||||||
|  |  | ||||||
|  | lv_event_t_ptr = cg.global_ns.namespace("lv_event_t").operator("ptr") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def generate_triggers(lv_component): | ||||||
|  |     """ | ||||||
|  |     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(): | ||||||
|  |         if w.config: | ||||||
|  |             for event, conf in { | ||||||
|  |                 event: conf | ||||||
|  |                 for event, conf in w.config.items() | ||||||
|  |                 if event in LV_EVENT_TRIGGERS | ||||||
|  |             }.items(): | ||||||
|  |                 conf = conf[0] | ||||||
|  |                 w.add_flag("LV_OBJ_FLAG_CLICKABLE") | ||||||
|  |                 event = "LV_EVENT_" + LV_EVENT[event[3:].upper()] | ||||||
|  |                 await add_trigger(conf, event, lv_component, w) | ||||||
|  |             for conf in w.config.get(CONF_ON_VALUE, ()): | ||||||
|  |                 await add_trigger(conf, "LV_EVENT_VALUE_CHANGED", lv_component, w) | ||||||
|  |  | ||||||
|  |             # Generate align to directives while we're here | ||||||
|  |             if align_to := w.config.get(CONF_ALIGN_TO): | ||||||
|  |                 target = widget_map[align_to[CONF_ID]].obj | ||||||
|  |                 align = align_to[CONF_ALIGN] | ||||||
|  |                 x = align_to[CONF_X] | ||||||
|  |                 y = align_to[CONF_Y] | ||||||
|  |                 lv.obj_align_to(w.obj, target, align, x, y) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def add_trigger(conf, event, lv_component, w): | ||||||
|  |     tid = conf[CONF_TRIGGER_ID] | ||||||
|  |     add_line_marks(tid) | ||||||
|  |     trigger = cg.new_Pvariable(tid) | ||||||
|  |     args = w.get_args() | ||||||
|  |     value = w.get_value() | ||||||
|  |     await automation.build_automation(trigger, args, conf) | ||||||
|  |     with LambdaContext([(lv_event_t_ptr, "event_data")]) as context: | ||||||
|  |         add_line_marks(tid) | ||||||
|  |         lv_add(trigger.trigger(value)) | ||||||
|  |     lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), literal(event))) | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| from esphome import codegen as cg | from esphome import automation, codegen as cg | ||||||
| from esphome.core import ID | from esphome.core import ID | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
|  |  | ||||||
| @@ -23,8 +23,14 @@ lvgl_ns = cg.esphome_ns.namespace("lvgl") | |||||||
| char_ptr = cg.global_ns.namespace("char").operator("ptr") | char_ptr = cg.global_ns.namespace("char").operator("ptr") | ||||||
| void_ptr = cg.void.operator("ptr") | void_ptr = cg.void.operator("ptr") | ||||||
| LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent) | LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent) | ||||||
|  | LvglComponentPtr = LvglComponent.operator("ptr") | ||||||
| lv_event_code_t = cg.global_ns.namespace("lv_event_code_t") | lv_event_code_t = cg.global_ns.namespace("lv_event_code_t") | ||||||
|  | lv_indev_type_t = cg.global_ns.enum("lv_indev_type_t") | ||||||
| FontEngine = lvgl_ns.class_("FontEngine") | FontEngine = lvgl_ns.class_("FontEngine") | ||||||
|  | IdleTrigger = lvgl_ns.class_("IdleTrigger", automation.Trigger.template()) | ||||||
|  | ObjUpdateAction = lvgl_ns.class_("ObjUpdateAction", automation.Action) | ||||||
|  | LvglCondition = lvgl_ns.class_("LvglCondition", automation.Condition) | ||||||
|  | LvglAction = lvgl_ns.class_("LvglAction", automation.Action) | ||||||
| LvCompound = lvgl_ns.class_("LvCompound") | LvCompound = lvgl_ns.class_("LvCompound") | ||||||
| lv_font_t = cg.global_ns.class_("lv_font_t") | lv_font_t = cg.global_ns.class_("lv_font_t") | ||||||
| lv_style_t = cg.global_ns.struct("lv_style_t") | lv_style_t = cg.global_ns.struct("lv_style_t") | ||||||
| @@ -33,9 +39,11 @@ lv_obj_base_t = cg.global_ns.class_("lv_obj_t", lv_pseudo_button_t) | |||||||
| lv_obj_t_ptr = lv_obj_base_t.operator("ptr") | lv_obj_t_ptr = lv_obj_base_t.operator("ptr") | ||||||
| lv_disp_t_ptr = cg.global_ns.struct("lv_disp_t").operator("ptr") | lv_disp_t_ptr = cg.global_ns.struct("lv_disp_t").operator("ptr") | ||||||
| lv_color_t = cg.global_ns.struct("lv_color_t") | lv_color_t = cg.global_ns.struct("lv_color_t") | ||||||
|  | lv_group_t = cg.global_ns.struct("lv_group_t") | ||||||
| LVTouchListener = lvgl_ns.class_("LVTouchListener") | LVTouchListener = lvgl_ns.class_("LVTouchListener") | ||||||
| LVEncoderListener = lvgl_ns.class_("LVEncoderListener") | LVEncoderListener = lvgl_ns.class_("LVEncoderListener") | ||||||
| lv_obj_t = LvType("lv_obj_t") | lv_obj_t = LvType("lv_obj_t") | ||||||
|  | lv_img_t = LvType("lv_img_t") | ||||||
|  |  | ||||||
|  |  | ||||||
| # this will be populated later, in __init__.py to avoid circular imports. | # this will be populated later, in __init__.py to avoid circular imports. | ||||||
| @@ -58,7 +66,7 @@ class LvBoolean(LvType): | |||||||
|         super().__init__( |         super().__init__( | ||||||
|             *args, |             *args, | ||||||
|             largs=[(cg.bool_, "x")], |             largs=[(cg.bool_, "x")], | ||||||
|             lvalue=lambda w: w.is_checked(), |             lvalue=lambda w: w.has_state("LV_STATE_CHECKED"), | ||||||
|             has_on_value=True, |             has_on_value=True, | ||||||
|             **kwargs, |             **kwargs, | ||||||
|         ) |         ) | ||||||
| @@ -83,11 +91,14 @@ class WidgetType: | |||||||
|         self.name = name |         self.name = name | ||||||
|         self.w_type = w_type |         self.w_type = w_type | ||||||
|         self.parts = parts |         self.parts = parts | ||||||
|         self.schema = schema or {} |         if schema is None: | ||||||
|         if modify_schema is None: |             self.schema = {} | ||||||
|             self.modify_schema = schema |  | ||||||
|         else: |         else: | ||||||
|             self.modify_schema = modify_schema |             self.schema = schema | ||||||
|  |         if modify_schema is None: | ||||||
|  |             self.modify_schema = self.schema | ||||||
|  |         else: | ||||||
|  |             self.modify_schema = self.schema | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def animated(self): |     def animated(self): | ||||||
|   | |||||||
| @@ -4,9 +4,9 @@ from typing import Any | |||||||
| from esphome import codegen as cg, config_validation as cv | from esphome import codegen as cg, config_validation as cv | ||||||
| from esphome.config_validation import Invalid | from esphome.config_validation import Invalid | ||||||
| from esphome.const import CONF_GROUP, CONF_ID, CONF_STATE | from esphome.const import CONF_GROUP, CONF_ID, CONF_STATE | ||||||
| from esphome.core import ID, TimePeriod | from esphome.core import CORE, TimePeriod | ||||||
| from esphome.coroutine import FakeAwaitable | from esphome.coroutine import FakeAwaitable | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObj, MockObjClass, VariableDeclarationExpression | ||||||
|  |  | ||||||
| from .defines import ( | from .defines import ( | ||||||
|     CONF_DEFAULT, |     CONF_DEFAULT, | ||||||
| @@ -16,13 +16,15 @@ from .defines import ( | |||||||
|     OBJ_FLAGS, |     OBJ_FLAGS, | ||||||
|     PARTS, |     PARTS, | ||||||
|     STATES, |     STATES, | ||||||
|  |     ConstantLiteral, | ||||||
|     LValidator, |     LValidator, | ||||||
|     join_enums, |     join_enums, | ||||||
|  |     literal, | ||||||
| ) | ) | ||||||
| from .helpers import add_lv_use | from .helpers import add_lv_use | ||||||
| from .lvcode import ConstantLiteral, add_line_marks, lv, lv_add, lv_assign, lv_obj | from .lvcode import add_group, add_line_marks, lv, lv_add, lv_assign, lv_expr, lv_obj | ||||||
| from .schemas import ALL_STYLES, STYLE_REMAP | from .schemas import ALL_STYLES, STYLE_REMAP | ||||||
| from .types import WIDGET_TYPES, WidgetType, lv_obj_t | from .types import WIDGET_TYPES, LvType, WidgetType, lv_obj_t, lv_obj_t_ptr | ||||||
|  |  | ||||||
| EVENT_LAMB = "event_lamb__" | EVENT_LAMB = "event_lamb__" | ||||||
|  |  | ||||||
| @@ -76,17 +78,20 @@ class Widget: | |||||||
|             return f"{self.var}->obj" |             return f"{self.var}->obj" | ||||||
|         return self.var |         return self.var | ||||||
|  |  | ||||||
|     def add_state(self, *args): |     def add_state(self, state): | ||||||
|         return lv_obj.add_state(self.obj, *args) |         return lv_obj.add_state(self.obj, literal(state)) | ||||||
|  |  | ||||||
|     def clear_state(self, *args): |     def clear_state(self, state): | ||||||
|         return lv_obj.clear_state(self.obj, *args) |         return lv_obj.clear_state(self.obj, literal(state)) | ||||||
|  |  | ||||||
|     def add_flag(self, *args): |     def has_state(self, state): | ||||||
|         return lv_obj.add_flag(self.obj, *args) |         return lv_expr.obj_get_state(self.obj) & literal(state) != 0 | ||||||
|  |  | ||||||
|     def clear_flag(self, *args): |     def add_flag(self, flag): | ||||||
|         return lv_obj.clear_flag(self.obj, *args) |         return lv_obj.add_flag(self.obj, literal(flag)) | ||||||
|  |  | ||||||
|  |     def clear_flag(self, flag): | ||||||
|  |         return lv_obj.clear_flag(self.obj, literal(flag)) | ||||||
|  |  | ||||||
|     def set_property(self, prop, value, animated: bool = None, ltype=None): |     def set_property(self, prop, value, animated: bool = None, ltype=None): | ||||||
|         if isinstance(value, dict): |         if isinstance(value, dict): | ||||||
| @@ -125,6 +130,16 @@ class Widget: | |||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return f"({self.var}, {self.type})" |         return f"({self.var}, {self.type})" | ||||||
|  |  | ||||||
|  |     def get_args(self): | ||||||
|  |         if isinstance(self.type.w_type, LvType): | ||||||
|  |             return self.type.w_type.args | ||||||
|  |         return [(lv_obj_t_ptr, "obj")] | ||||||
|  |  | ||||||
|  |     def get_value(self): | ||||||
|  |         if isinstance(self.type.w_type, LvType): | ||||||
|  |             return self.type.w_type.value(self) | ||||||
|  |         return self.obj | ||||||
|  |  | ||||||
|  |  | ||||||
| # Map of widgets to their config, used for trigger generation | # Map of widgets to their config, used for trigger generation | ||||||
| widget_map: dict[Any, Widget] = {} | widget_map: dict[Any, Widget] = {} | ||||||
| @@ -146,7 +161,8 @@ def get_widget_generator(wid): | |||||||
|         yield |         yield | ||||||
|  |  | ||||||
|  |  | ||||||
| async def get_widget(wid: ID) -> Widget: | async def get_widget(config: dict, id: str = CONF_ID) -> Widget: | ||||||
|  |     wid = config[id] | ||||||
|     if obj := widget_map.get(wid): |     if obj := widget_map.get(wid): | ||||||
|         return obj |         return obj | ||||||
|     return await FakeAwaitable(get_widget_generator(wid)) |     return await FakeAwaitable(get_widget_generator(wid)) | ||||||
| @@ -204,9 +220,10 @@ async def set_obj_properties(w: Widget, config): | |||||||
|             }.items(): |             }.items(): | ||||||
|                 if isinstance(ALL_STYLES[prop], LValidator): |                 if isinstance(ALL_STYLES[prop], LValidator): | ||||||
|                     value = await ALL_STYLES[prop].process(value) |                     value = await ALL_STYLES[prop].process(value) | ||||||
|                     # Remapping for backwards compatibility of style names |  | ||||||
|                 prop_r = STYLE_REMAP.get(prop, prop) |                 prop_r = STYLE_REMAP.get(prop, prop) | ||||||
|                 w.set_style(prop_r, value, lv_state) |                 w.set_style(prop_r, value, lv_state) | ||||||
|  |     if group := add_group(config.get(CONF_GROUP)): | ||||||
|  |         lv.group_add_obj(group, w.obj) | ||||||
|     flag_clr = set() |     flag_clr = set() | ||||||
|     flag_set = set() |     flag_set = set() | ||||||
|     props = parts[CONF_MAIN][CONF_DEFAULT] |     props = parts[CONF_MAIN][CONF_DEFAULT] | ||||||
| @@ -241,7 +258,7 @@ async def set_obj_properties(w: Widget, config): | |||||||
|             w.clear_state(clears) |             w.clear_state(clears) | ||||||
|         for key, value in lambs.items(): |         for key, value in lambs.items(): | ||||||
|             lamb = await cg.process_lambda(value, [], return_type=cg.bool_) |             lamb = await cg.process_lambda(value, [], return_type=cg.bool_) | ||||||
|             state = ConstantLiteral(f"LV_STATE_{key.upper}") |             state = f"LV_STATE_{key.upper}" | ||||||
|             lv.cond_if(lamb) |             lv.cond_if(lamb) | ||||||
|             w.add_state(state) |             w.add_state(state) | ||||||
|             lv.cond_else() |             lv.cond_else() | ||||||
| @@ -281,10 +298,19 @@ async def widget_to_code(w_cnfig, w_type, parent): | |||||||
|         var = cg.new_Pvariable(wid) |         var = cg.new_Pvariable(wid) | ||||||
|         lv_add(var.set_obj(creator)) |         lv_add(var.set_obj(creator)) | ||||||
|     else: |     else: | ||||||
|         var = cg.Pvariable(wid, cg.nullptr, type_=lv_obj_t) |         var = MockObj(wid, "->") | ||||||
|  |         decl = VariableDeclarationExpression(lv_obj_t, "*", wid) | ||||||
|  |         CORE.add_global(decl) | ||||||
|  |         CORE.register_variable(wid, var) | ||||||
|         lv_assign(var, creator) |         lv_assign(var, creator) | ||||||
|  |  | ||||||
|     widget = Widget.create(wid, var, spec, w_cnfig, parent) |     widget = Widget.create(wid, var, spec, w_cnfig, parent) | ||||||
|     await set_obj_properties(widget, w_cnfig) |     await set_obj_properties(widget, w_cnfig) | ||||||
|     await add_widgets(widget, w_cnfig) |     await add_widgets(widget, w_cnfig) | ||||||
|     await spec.to_code(widget, w_cnfig) |     await spec.to_code(widget, w_cnfig) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | lv_scr_act_spec = LvScrActType() | ||||||
|  | lv_scr_act = Widget.create( | ||||||
|  |     None, ConstantLiteral("lv_scr_act()"), lv_scr_act_spec, {}, parent=None | ||||||
|  | ) | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ static const char *const TAG = "matrix_keypad"; | |||||||
|  |  | ||||||
| void MatrixKeypad::setup() { | void MatrixKeypad::setup() { | ||||||
|   for (auto *pin : this->rows_) { |   for (auto *pin : this->rows_) { | ||||||
|  |     pin->setup(); | ||||||
|     if (!has_diodes_) { |     if (!has_diodes_) { | ||||||
|       pin->pin_mode(gpio::FLAG_INPUT); |       pin->pin_mode(gpio::FLAG_INPUT); | ||||||
|     } else { |     } else { | ||||||
| @@ -15,6 +16,7 @@ void MatrixKeypad::setup() { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   for (auto *pin : this->columns_) { |   for (auto *pin : this->columns_) { | ||||||
|  |     pin->setup(); | ||||||
|     if (has_pulldowns_) { |     if (has_pulldowns_) { | ||||||
|       pin->pin_mode(gpio::FLAG_INPUT); |       pin->pin_mode(gpio::FLAG_INPUT); | ||||||
|     } else { |     } else { | ||||||
|   | |||||||
| @@ -148,7 +148,7 @@ WakeWordModel::WakeWordModel(const uint8_t *model_start, float probability_cutof | |||||||
| }; | }; | ||||||
|  |  | ||||||
| bool WakeWordModel::determine_detected() { | bool WakeWordModel::determine_detected() { | ||||||
|   int32_t sum = 0; |   uint32_t sum = 0; | ||||||
|   for (auto &prob : this->recent_streaming_probabilities_) { |   for (auto &prob : this->recent_streaming_probabilities_) { | ||||||
|     sum += prob; |     sum += prob; | ||||||
|   } |   } | ||||||
| @@ -175,12 +175,14 @@ VADModel::VADModel(const uint8_t *model_start, float probability_cutoff, size_t | |||||||
| }; | }; | ||||||
|  |  | ||||||
| bool VADModel::determine_detected() { | bool VADModel::determine_detected() { | ||||||
|   uint8_t max = 0; |   uint32_t sum = 0; | ||||||
|   for (auto &prob : this->recent_streaming_probabilities_) { |   for (auto &prob : this->recent_streaming_probabilities_) { | ||||||
|     max = std::max(prob, max); |     sum += prob; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return max > this->probability_cutoff_; |   float sliding_window_average = static_cast<float>(sum) / static_cast<float>(255 * this->sliding_window_size_); | ||||||
|  |  | ||||||
|  |   return sliding_window_average > this->probability_cutoff_; | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace micro_wake_word | }  // namespace micro_wake_word | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ from esphome.const import ( | |||||||
|     CONF_ICON, |     CONF_ICON, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_IGNORE_OUT_OF_RANGE, |     CONF_IGNORE_OUT_OF_RANGE, | ||||||
|  |     CONF_MULTIPLE, | ||||||
|     CONF_ON_RAW_VALUE, |     CONF_ON_RAW_VALUE, | ||||||
|     CONF_ON_VALUE, |     CONF_ON_VALUE, | ||||||
|     CONF_ON_VALUE_RANGE, |     CONF_ON_VALUE_RANGE, | ||||||
| @@ -249,6 +250,7 @@ CalibratePolynomialFilter = sensor_ns.class_("CalibratePolynomialFilter", Filter | |||||||
| SensorInRangeCondition = sensor_ns.class_("SensorInRangeCondition", Filter) | SensorInRangeCondition = sensor_ns.class_("SensorInRangeCondition", Filter) | ||||||
| ClampFilter = sensor_ns.class_("ClampFilter", Filter) | ClampFilter = sensor_ns.class_("ClampFilter", Filter) | ||||||
| RoundFilter = sensor_ns.class_("RoundFilter", Filter) | RoundFilter = sensor_ns.class_("RoundFilter", Filter) | ||||||
|  | RoundMultipleFilter = sensor_ns.class_("RoundMultipleFilter", Filter) | ||||||
|  |  | ||||||
| validate_unit_of_measurement = cv.string_strict | validate_unit_of_measurement = cv.string_strict | ||||||
| validate_accuracy_decimals = cv.int_ | validate_accuracy_decimals = cv.int_ | ||||||
| @@ -734,6 +736,23 @@ async def round_filter_to_code(config, filter_id): | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @FILTER_REGISTRY.register( | ||||||
|  |     "round_to_multiple_of", | ||||||
|  |     RoundMultipleFilter, | ||||||
|  |     cv.maybe_simple_value( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_MULTIPLE): cv.positive_not_null_float, | ||||||
|  |         }, | ||||||
|  |         key=CONF_MULTIPLE, | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def round_multiple_filter_to_code(config, filter_id): | ||||||
|  |     return cg.new_Pvariable( | ||||||
|  |         filter_id, | ||||||
|  |         config[CONF_MULTIPLE], | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def build_filters(config): | async def build_filters(config): | ||||||
|     return await cg.build_registry_list(FILTER_REGISTRY, config) |     return await cg.build_registry_list(FILTER_REGISTRY, config) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -472,5 +472,13 @@ optional<float> RoundFilter::new_value(float value) { | |||||||
|   return value; |   return value; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | RoundMultipleFilter::RoundMultipleFilter(float multiple) : multiple_(multiple) {} | ||||||
|  | optional<float> RoundMultipleFilter::new_value(float value) { | ||||||
|  |   if (std::isfinite(value)) { | ||||||
|  |     return value - remainderf(value, this->multiple_); | ||||||
|  |   } | ||||||
|  |   return value; | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace sensor | }  // namespace sensor | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -431,5 +431,14 @@ class RoundFilter : public Filter { | |||||||
|   uint8_t precision_; |   uint8_t precision_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class RoundMultipleFilter : public Filter { | ||||||
|  |  public: | ||||||
|  |   explicit RoundMultipleFilter(float multiple); | ||||||
|  |   optional<float> new_value(float value) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   float multiple_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace sensor | }  // namespace sensor | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -1,10 +1,11 @@ | |||||||
| from esphome import automation | from esphome import automation | ||||||
|  | import esphome.codegen as cg | ||||||
| from esphome.components import mqtt, web_server | from esphome.components import mqtt, web_server | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| import esphome.codegen as cg |  | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_DEVICE_CLASS, |     CONF_DEVICE_CLASS, | ||||||
|     CONF_ENTITY_CATEGORY, |     CONF_ENTITY_CATEGORY, | ||||||
|  |     CONF_FORCE_UPDATE, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_MQTT_ID, |     CONF_MQTT_ID, | ||||||
|     CONF_WEB_SERVER_ID, |     CONF_WEB_SERVER_ID, | ||||||
| @@ -23,8 +24,12 @@ UpdateEntity = update_ns.class_("UpdateEntity", cg.EntityBase) | |||||||
|  |  | ||||||
| UpdateInfo = update_ns.struct("UpdateInfo") | UpdateInfo = update_ns.struct("UpdateInfo") | ||||||
|  |  | ||||||
| PerformAction = update_ns.class_("PerformAction", automation.Action) | PerformAction = update_ns.class_( | ||||||
| IsAvailableCondition = update_ns.class_("IsAvailableCondition", automation.Condition) |     "PerformAction", automation.Action, cg.Parented.template(UpdateEntity) | ||||||
|  | ) | ||||||
|  | IsAvailableCondition = update_ns.class_( | ||||||
|  |     "IsAvailableCondition", automation.Condition, cg.Parented.template(UpdateEntity) | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEVICE_CLASSES = [ | DEVICE_CLASSES = [ | ||||||
|     DEVICE_CLASS_EMPTY, |     DEVICE_CLASS_EMPTY, | ||||||
| @@ -92,24 +97,37 @@ async def to_code(config): | |||||||
|     cg.add_global(update_ns.using) |     cg.add_global(update_ns.using) | ||||||
|  |  | ||||||
|  |  | ||||||
| UPDATE_AUTOMATION_SCHEMA = cv.Schema( | @automation.register_action( | ||||||
|     { |     "update.perform", | ||||||
|         cv.GenerateID(): cv.use_id(UpdateEntity), |     PerformAction, | ||||||
|     } |     automation.maybe_simple_id( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(UpdateEntity), | ||||||
|  |             cv.Optional(CONF_FORCE_UPDATE, default=False): cv.templatable(cv.boolean), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action("update.perform", PerformAction, UPDATE_AUTOMATION_SCHEMA) |  | ||||||
| async def update_perform_action_to_code(config, action_id, template_arg, args): | async def update_perform_action_to_code(config, action_id, template_arg, args): | ||||||
|     paren = await cg.get_variable(config[CONF_ID]) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     return cg.new_Pvariable(action_id, paren, paren) |     await cg.register_parented(var, config[CONF_ID]) | ||||||
|  |  | ||||||
|  |     force = await cg.templatable(config[CONF_FORCE_UPDATE], args, cg.bool_) | ||||||
|  |     cg.add(var.set_force(force)) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_condition( | @automation.register_condition( | ||||||
|     "update.is_available", IsAvailableCondition, UPDATE_AUTOMATION_SCHEMA |     "update.is_available", | ||||||
|  |     IsAvailableCondition, | ||||||
|  |     automation.maybe_simple_id( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(UpdateEntity), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
| ) | ) | ||||||
| async def update_is_available_condition_to_code( | async def update_is_available_condition_to_code( | ||||||
|     config, condition_id, template_arg, args |     config, condition_id, template_arg, args | ||||||
| ): | ): | ||||||
|     paren = await cg.get_variable(config[CONF_ID]) |     var = cg.new_Pvariable(condition_id, template_arg) | ||||||
|     return cg.new_Pvariable(condition_id, paren, paren) |     await cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     return var | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								esphome/components/update/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphome/components/update/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "update_entity.h" | ||||||
|  |  | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace update { | ||||||
|  |  | ||||||
|  | template<typename... Ts> class PerformAction : public Action<Ts...>, public Parented<UpdateEntity> { | ||||||
|  |   TEMPLATABLE_VALUE(bool, force) | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   void play(Ts... x) override { this->parent_->perform(this->force_.value(x...)); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class IsAvailableCondition : public Condition<Ts...>, public Parented<UpdateEntity> { | ||||||
|  |  public: | ||||||
|  |   bool check(Ts... x) override { return this->parent_->state == UPDATE_STATE_AVAILABLE; } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace update | ||||||
|  | }  // namespace esphome | ||||||
| @@ -32,7 +32,9 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass { | |||||||
|  |  | ||||||
|   void publish_state(); |   void publish_state(); | ||||||
|  |  | ||||||
|   virtual void perform() = 0; |   void perform() { this->perform(false); } | ||||||
|  |  | ||||||
|  |   virtual void perform(bool force) = 0; | ||||||
|  |  | ||||||
|   const UpdateInfo &update_info = update_info_; |   const UpdateInfo &update_info = update_info_; | ||||||
|   const UpdateState &state = state_; |   const UpdateState &state = state_; | ||||||
|   | |||||||
| @@ -464,6 +464,7 @@ zero_to_one_float = float_range(min=0, max=1) | |||||||
| negative_one_to_one_float = float_range(min=-1, max=1) | negative_one_to_one_float = float_range(min=-1, max=1) | ||||||
| positive_int = int_range(min=0) | positive_int = int_range(min=0) | ||||||
| positive_not_null_int = int_range(min=0, min_included=False) | positive_not_null_int = int_range(min=0, min_included=False) | ||||||
|  | positive_not_null_float = float_range(min=0, min_included=False) | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_id_name(value): | def validate_id_name(value): | ||||||
| @@ -2181,3 +2182,13 @@ SOURCE_SCHEMA = Any( | |||||||
|         } |         } | ||||||
|     ), |     ), | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def rename_key(old_key, new_key): | ||||||
|  |     def validator(config: dict) -> dict: | ||||||
|  |         config = config.copy() | ||||||
|  |         if old_key in config: | ||||||
|  |             config[new_key] = config.pop(old_key) | ||||||
|  |         return config | ||||||
|  |  | ||||||
|  |     return validator | ||||||
|   | |||||||
| @@ -37,8 +37,10 @@ CONF_ACCELERATION_Y = "acceleration_y" | |||||||
| CONF_ACCELERATION_Z = "acceleration_z" | CONF_ACCELERATION_Z = "acceleration_z" | ||||||
| CONF_ACCURACY = "accuracy" | CONF_ACCURACY = "accuracy" | ||||||
| CONF_ACCURACY_DECIMALS = "accuracy_decimals" | CONF_ACCURACY_DECIMALS = "accuracy_decimals" | ||||||
|  | CONF_ACTION = "action" | ||||||
| CONF_ACTION_ID = "action_id" | CONF_ACTION_ID = "action_id" | ||||||
| CONF_ACTION_STATE_TOPIC = "action_state_topic" | CONF_ACTION_STATE_TOPIC = "action_state_topic" | ||||||
|  | CONF_ACTIONS = "actions" | ||||||
| CONF_ACTIVE = "active" | CONF_ACTIVE = "active" | ||||||
| CONF_ACTIVE_POWER = "active_power" | CONF_ACTIVE_POWER = "active_power" | ||||||
| CONF_ACTUAL_GAIN = "actual_gain" | CONF_ACTUAL_GAIN = "actual_gain" | ||||||
| @@ -501,6 +503,7 @@ CONF_MOTION = "motion" | |||||||
| CONF_MOVEMENT_COUNTER = "movement_counter" | CONF_MOVEMENT_COUNTER = "movement_counter" | ||||||
| CONF_MQTT = "mqtt" | CONF_MQTT = "mqtt" | ||||||
| CONF_MQTT_ID = "mqtt_id" | CONF_MQTT_ID = "mqtt_id" | ||||||
|  | CONF_MULTIPLE = "multiple" | ||||||
| CONF_MULTIPLEXER = "multiplexer" | CONF_MULTIPLEXER = "multiplexer" | ||||||
| CONF_MULTIPLY = "multiply" | CONF_MULTIPLY = "multiply" | ||||||
| CONF_NAME = "name" | CONF_NAME = "name" | ||||||
|   | |||||||
| @@ -39,9 +39,12 @@ | |||||||
| #define USE_LOCK | #define USE_LOCK | ||||||
| #define USE_LOGGER | #define USE_LOGGER | ||||||
| #define USE_LVGL | #define USE_LVGL | ||||||
|  | #define USE_LVGL_BINARY_SENSOR | ||||||
| #define USE_LVGL_FONT | #define USE_LVGL_FONT | ||||||
| #define USE_LVGL_IMAGE | #define USE_LVGL_IMAGE | ||||||
|  | #define USE_LVGL_KEY_LISTENER | ||||||
| #define USE_LVGL_TOUCHSCREEN | #define USE_LVGL_TOUCHSCREEN | ||||||
|  | #define USE_LVGL_ROTARY_ENCODER | ||||||
| #define USE_MDNS | #define USE_MDNS | ||||||
| #define USE_MEDIA_PLAYER | #define USE_MEDIA_PLAYER | ||||||
| #define USE_MQTT | #define USE_MQTT | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ build_flags = | |||||||
| lib_deps = | lib_deps = | ||||||
|     esphome/noise-c@0.1.4                  ; api |     esphome/noise-c@0.1.4                  ; api | ||||||
|     makuna/NeoPixelBus@2.7.3               ; neopixelbus |     makuna/NeoPixelBus@2.7.3               ; neopixelbus | ||||||
|     esphome/Improv@1.2.3                   ; improv_serial / esp32_improv |     improv/Improv@1.2.4                    ; improv_serial / esp32_improv | ||||||
|     bblanchon/ArduinoJson@6.18.5           ; json |     bblanchon/ArduinoJson@6.18.5           ; json | ||||||
|     wjtje/qr-code-generator-library@1.7.0  ; qr_code |     wjtje/qr-code-generator-library@1.7.0  ; qr_code | ||||||
|     functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 |     functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 | ||||||
|   | |||||||
| @@ -5,8 +5,8 @@ esphome: | |||||||
|           event: esphome.button_pressed |           event: esphome.button_pressed | ||||||
|           data: |           data: | ||||||
|             message: Button was pressed |             message: Button was pressed | ||||||
|       - homeassistant.service: |       - homeassistant.action: | ||||||
|           service: notify.html5 |           action: notify.html5 | ||||||
|           data: |           data: | ||||||
|             message: Button was pressed |             message: Button was pressed | ||||||
|       - homeassistant.tag_scanned: pulse |       - homeassistant.tag_scanned: pulse | ||||||
| @@ -21,8 +21,8 @@ api: | |||||||
|   reboot_timeout: 0min |   reboot_timeout: 0min | ||||||
|   encryption: |   encryption: | ||||||
|     key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= |     key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= | ||||||
|   services: |   actions: | ||||||
|     - service: hello_world |     - action: hello_world | ||||||
|       variables: |       variables: | ||||||
|         name: string |         name: string | ||||||
|       then: |       then: | ||||||
| @@ -30,10 +30,10 @@ api: | |||||||
|             format: Hello World %s! |             format: Hello World %s! | ||||||
|             args: |             args: | ||||||
|               - name.c_str() |               - name.c_str() | ||||||
|     - service: empty_service |     - action: empty_action | ||||||
|       then: |       then: | ||||||
|         - logger.log: Service Called |         - logger.log: Action Called | ||||||
|     - service: all_types |     - action: all_types | ||||||
|       variables: |       variables: | ||||||
|         bool_: bool |         bool_: bool | ||||||
|         int_: int |         int_: int | ||||||
| @@ -41,7 +41,7 @@ api: | |||||||
|         string_: string |         string_: string | ||||||
|       then: |       then: | ||||||
|         - logger.log: Something happened |         - logger.log: Something happened | ||||||
|     - service: array_types |     - action: array_types | ||||||
|       variables: |       variables: | ||||||
|         bool_arr: bool[] |         bool_arr: bool[] | ||||||
|         int_arr: int[] |         int_arr: int[] | ||||||
|   | |||||||
| @@ -13,12 +13,12 @@ esphome: | |||||||
|             message: The humidity is {{ my_variable }}%. |             message: The humidity is {{ my_variable }}%. | ||||||
|           variables: |           variables: | ||||||
|             my_variable: "return id(ha_hello_world_temperature).state;" |             my_variable: "return id(ha_hello_world_temperature).state;" | ||||||
|       - homeassistant.service: |       - homeassistant.action: | ||||||
|           service: notify.html5 |           action: notify.html5 | ||||||
|           data: |           data: | ||||||
|             message: Button was pressed |             message: Button was pressed | ||||||
|       - homeassistant.service: |       - homeassistant.action: | ||||||
|           service: notify.html5 |           action: notify.html5 | ||||||
|           data: |           data: | ||||||
|             title: New Humidity |             title: New Humidity | ||||||
|           data_template: |           data_template: | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ lvgl: | |||||||
|       long_press_time: 500ms |       long_press_time: 500ms | ||||||
|   widgets: |   widgets: | ||||||
|     - label: |     - label: | ||||||
|  |         id: hello_label | ||||||
|         text: Hello world |         text: Hello world | ||||||
|         text_color: 0xFF8000 |         text_color: 0xFF8000 | ||||||
|         align: center |         align: center | ||||||
| @@ -95,9 +96,43 @@ lvgl: | |||||||
|         height: 10% |         height: 10% | ||||||
|         pressed: |         pressed: | ||||||
|           bg_color: light_blue |           bg_color: light_blue | ||||||
|  |         checkable: true | ||||||
|  |         checked: | ||||||
|  |           bg_color: 0x000000 | ||||||
|         widgets: |         widgets: | ||||||
|           - label: |           - label: | ||||||
|               text: Button |               text: Button | ||||||
|  |         on_click: | ||||||
|  |           lvgl.label.update: | ||||||
|  |             id: hello_label | ||||||
|  |             bg_color: 0x123456 | ||||||
|  |             text: clicked | ||||||
|  |         on_value: | ||||||
|  |           logger.log: | ||||||
|  |             format: "state now %d" | ||||||
|  |             args: [x] | ||||||
|  |         on_short_click: | ||||||
|  |           lvgl.widget.hide: hello_label | ||||||
|  |         on_long_press: | ||||||
|  |           lvgl.widget.show: hello_label | ||||||
|  |         on_cancel: | ||||||
|  |           lvgl.widget.enable: hello_label | ||||||
|  |         on_ready: | ||||||
|  |           lvgl.widget.disable: hello_label | ||||||
|  |         on_defocus: | ||||||
|  |           lvgl.widget.hide: hello_label | ||||||
|  |         on_focus: | ||||||
|  |           logger.log: Button clicked | ||||||
|  |         on_scroll: | ||||||
|  |           logger.log: Button clicked | ||||||
|  |         on_scroll_end: | ||||||
|  |           logger.log: Button clicked | ||||||
|  |         on_scroll_begin: | ||||||
|  |           logger.log: Button clicked | ||||||
|  |         on_release: | ||||||
|  |           logger.log: Button clicked | ||||||
|  |         on_long_press_repeat: | ||||||
|  |           logger.log: Button clicked | ||||||
|  |  | ||||||
| font: | font: | ||||||
|   - file: "gfonts://Roboto" |   - file: "gfonts://Roboto" | ||||||
|   | |||||||
| @@ -6,6 +6,23 @@ i2c: | |||||||
|   sda: GPIO18 |   sda: GPIO18 | ||||||
|   scl: GPIO19 |   scl: GPIO19 | ||||||
|  |  | ||||||
|  | sensor: | ||||||
|  |   - platform: rotary_encoder | ||||||
|  |     name: "Rotary Encoder" | ||||||
|  |     id: encoder | ||||||
|  |     pin_a: 2 | ||||||
|  |     pin_b: 1 | ||||||
|  |     internal: true | ||||||
|  |  | ||||||
|  | binary_sensor: | ||||||
|  |   - platform: gpio | ||||||
|  |     id: pushbutton | ||||||
|  |     name: Pushbutton | ||||||
|  |     pin: | ||||||
|  |       number: 0 | ||||||
|  |       inverted: true | ||||||
|  |       ignore_strapping_warning: true | ||||||
|  |  | ||||||
| display: | display: | ||||||
|   - platform: ili9xxx |   - platform: ili9xxx | ||||||
|     model: st7789v |     model: st7789v | ||||||
| @@ -50,5 +67,9 @@ lvgl: | |||||||
|   displays: |   displays: | ||||||
|     - tft_display |     - tft_display | ||||||
|     - second_display |     - second_display | ||||||
|  |   rotary_encoders: | ||||||
|  |     sensor: encoder | ||||||
|  |     enter_button: pushbutton | ||||||
|  |     group: general | ||||||
|  |  | ||||||
| <<: !include common.yaml | <<: !include common.yaml | ||||||
|   | |||||||
| @@ -1 +1,28 @@ | |||||||
|  | substitutions: | ||||||
|  |   verify_ssl: "true" | ||||||
|  |  | ||||||
|  | esphome: | ||||||
|  |   on_boot: | ||||||
|  |     then: | ||||||
|  |       - if: | ||||||
|  |           condition: | ||||||
|  |             update.is_available: | ||||||
|  |           then: | ||||||
|  |             - logger.log: "Update available" | ||||||
|  |       - update.perform: | ||||||
|  |           force_update: true | ||||||
|  |  | ||||||
|  | wifi: | ||||||
|  |   ssid: MySSID | ||||||
|  |   password: password1 | ||||||
|  |  | ||||||
|  | http_request: | ||||||
|  |   verify_ssl: ${verify_ssl} | ||||||
|  |  | ||||||
|  | ota: | ||||||
|  |   - platform: http_request | ||||||
|  |  | ||||||
| update: | update: | ||||||
|  |   - platform: http_request | ||||||
|  |     name: Firmware Update | ||||||
|  |     source: http://example.com/manifest.json | ||||||
|   | |||||||
| @@ -1 +1,4 @@ | |||||||
|  | substitutions: | ||||||
|  |   verify_ssl: "false" | ||||||
|  |  | ||||||
| <<: !include common.yaml | <<: !include common.yaml | ||||||
|   | |||||||
| @@ -1 +1,4 @@ | |||||||
|  | substitutions: | ||||||
|  |   verify_ssl: "false" | ||||||
|  |  | ||||||
| <<: !include common.yaml | <<: !include common.yaml | ||||||
|   | |||||||
| @@ -1 +1,4 @@ | |||||||
|  | substitutions: | ||||||
|  |   verify_ssl: "false" | ||||||
|  |  | ||||||
| <<: !include common.yaml | <<: !include common.yaml | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user