mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 16:41:50 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			2022.11.0b
			...
			jesserockz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3792823c24 | 
@@ -3,15 +3,15 @@
 | 
			
		||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
			
		||||
repos:
 | 
			
		||||
  - repo: https://github.com/ambv/black
 | 
			
		||||
    rev: 22.10.0
 | 
			
		||||
    rev: 22.6.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: black
 | 
			
		||||
        args:
 | 
			
		||||
          - --safe
 | 
			
		||||
          - --quiet
 | 
			
		||||
        files: ^((esphome|script|tests)/.+)?[^/]+\.py$
 | 
			
		||||
  - repo: https://github.com/PyCQA/flake8
 | 
			
		||||
    rev: 5.0.4
 | 
			
		||||
  - repo: https://gitlab.com/pycqa/flake8
 | 
			
		||||
    rev: 4.0.1
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: flake8
 | 
			
		||||
        additional_dependencies:
 | 
			
		||||
@@ -27,7 +27,7 @@ repos:
 | 
			
		||||
          - --branch=release
 | 
			
		||||
          - --branch=beta
 | 
			
		||||
  - repo: https://github.com/asottile/pyupgrade
 | 
			
		||||
    rev: v3.2.0
 | 
			
		||||
    rev: v3.0.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: pyupgrade
 | 
			
		||||
        args: [--py39-plus]
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,6 @@ esphome/components/debug/* @OttoWinter
 | 
			
		||||
esphome/components/delonghi/* @grob6000
 | 
			
		||||
esphome/components/dfplayer/* @glmnet
 | 
			
		||||
esphome/components/dht/* @OttoWinter
 | 
			
		||||
esphome/components/display_menu_base/* @numo68
 | 
			
		||||
esphome/components/dps310/* @kbx81
 | 
			
		||||
esphome/components/ds1307/* @badbadc0ffee
 | 
			
		||||
esphome/components/dsmr/* @glmnet @zuidwijk
 | 
			
		||||
@@ -111,7 +110,6 @@ esphome/components/integration/* @OttoWinter
 | 
			
		||||
esphome/components/interval/* @esphome/core
 | 
			
		||||
esphome/components/json/* @OttoWinter
 | 
			
		||||
esphome/components/kalman_combinator/* @Cat-Ion
 | 
			
		||||
esphome/components/lcd_menu/* @numo68
 | 
			
		||||
esphome/components/ledc/* @OttoWinter
 | 
			
		||||
esphome/components/light/* @esphome/core
 | 
			
		||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ RUN \
 | 
			
		||||
    # Ubuntu python3-pip is missing wheel
 | 
			
		||||
    pip3 install --no-cache-dir \
 | 
			
		||||
        wheel==0.37.1 \
 | 
			
		||||
        platformio==6.1.5 \
 | 
			
		||||
        platformio==6.1.4 \
 | 
			
		||||
    # Change some platformio settings
 | 
			
		||||
    && platformio settings set enable_telemetry No \
 | 
			
		||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
			
		||||
 
 | 
			
		||||
@@ -1,430 +0,0 @@
 | 
			
		||||
import re
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation, core
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_ON_VALUE,
 | 
			
		||||
    CONF_COMMAND,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_FORMAT,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_ACTIVE,
 | 
			
		||||
)
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
from esphome.components.select import Select
 | 
			
		||||
from esphome.components.number import Number
 | 
			
		||||
from esphome.components.switch import Switch
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@numo68"]
 | 
			
		||||
 | 
			
		||||
display_menu_base_ns = cg.esphome_ns.namespace("display_menu_base")
 | 
			
		||||
 | 
			
		||||
CONF_DISPLAY_ID = "display_id"
 | 
			
		||||
 | 
			
		||||
CONF_ROTARY = "rotary"
 | 
			
		||||
CONF_JOYSTICK = "joystick"
 | 
			
		||||
CONF_LABEL = "label"
 | 
			
		||||
CONF_MENU = "menu"
 | 
			
		||||
CONF_BACK = "back"
 | 
			
		||||
CONF_TEXT = "text"
 | 
			
		||||
CONF_SELECT = "select"
 | 
			
		||||
CONF_SWITCH = "switch"
 | 
			
		||||
CONF_CUSTOM = "custom"
 | 
			
		||||
CONF_ITEMS = "items"
 | 
			
		||||
CONF_ON_TEXT = "on_text"
 | 
			
		||||
CONF_OFF_TEXT = "off_text"
 | 
			
		||||
CONF_VALUE_LAMBDA = "value_lambda"
 | 
			
		||||
CONF_IMMEDIATE_EDIT = "immediate_edit"
 | 
			
		||||
CONF_ROOT_ITEM_ID = "root_item_id"
 | 
			
		||||
CONF_ON_ENTER = "on_enter"
 | 
			
		||||
CONF_ON_LEAVE = "on_leave"
 | 
			
		||||
CONF_ON_NEXT = "on_next"
 | 
			
		||||
CONF_ON_PREV = "on_prev"
 | 
			
		||||
 | 
			
		||||
DisplayMenuComponent = display_menu_base_ns.class_("DisplayMenuComponent", cg.Component)
 | 
			
		||||
 | 
			
		||||
MenuItem = display_menu_base_ns.class_("MenuItem")
 | 
			
		||||
MenuItemConstPtr = MenuItem.operator("ptr").operator("const")
 | 
			
		||||
MenuItemMenu = display_menu_base_ns.class_("MenuItemMenu")
 | 
			
		||||
MenuItemSelect = display_menu_base_ns.class_("MenuItemSelect")
 | 
			
		||||
MenuItemNumber = display_menu_base_ns.class_("MenuItemNumber")
 | 
			
		||||
MenuItemSwitch = display_menu_base_ns.class_("MenuItemSwitch")
 | 
			
		||||
MenuItemCommand = display_menu_base_ns.class_("MenuItemCommand")
 | 
			
		||||
MenuItemCustom = display_menu_base_ns.class_("MenuItemCustom")
 | 
			
		||||
 | 
			
		||||
UpAction = display_menu_base_ns.class_("UpAction", automation.Action)
 | 
			
		||||
DownAction = display_menu_base_ns.class_("DownAction", automation.Action)
 | 
			
		||||
LeftAction = display_menu_base_ns.class_("LeftAction", automation.Action)
 | 
			
		||||
RightAction = display_menu_base_ns.class_("RightAction", automation.Action)
 | 
			
		||||
EnterAction = display_menu_base_ns.class_("EnterAction", automation.Action)
 | 
			
		||||
ShowAction = display_menu_base_ns.class_("ShowAction", automation.Action)
 | 
			
		||||
HideAction = display_menu_base_ns.class_("HideAction", automation.Action)
 | 
			
		||||
ShowMainAction = display_menu_base_ns.class_("ShowMainAction", automation.Action)
 | 
			
		||||
 | 
			
		||||
IsActiveCondition = display_menu_base_ns.class_(
 | 
			
		||||
    "IsActiveCondition", automation.Condition
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
MenuItemType = display_menu_base_ns.enum("MenuItemType")
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_TYPES = {
 | 
			
		||||
    CONF_LABEL: MenuItemType.MENU_ITEM_LABEL,
 | 
			
		||||
    CONF_MENU: MenuItemType.MENU_ITEM_MENU,
 | 
			
		||||
    CONF_BACK: MenuItemType.MENU_ITEM_BACK,
 | 
			
		||||
    CONF_SELECT: MenuItemType.MENU_ITEM_SELECT,
 | 
			
		||||
    CONF_NUMBER: MenuItemType.MENU_ITEM_NUMBER,
 | 
			
		||||
    CONF_SWITCH: MenuItemType.MENU_ITEM_SWITCH,
 | 
			
		||||
    CONF_COMMAND: MenuItemType.MENU_ITEM_COMMAND,
 | 
			
		||||
    CONF_CUSTOM: MenuItemType.MENU_ITEM_CUSTOM,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MENU_ITEMS_WITH_SPECIALIZED_CLASSES = (
 | 
			
		||||
    CONF_MENU,
 | 
			
		||||
    CONF_SELECT,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_SWITCH,
 | 
			
		||||
    CONF_COMMAND,
 | 
			
		||||
    CONF_CUSTOM,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MenuMode = display_menu_base_ns.enum("MenuMode")
 | 
			
		||||
 | 
			
		||||
MENU_MODES = {
 | 
			
		||||
    CONF_ROTARY: MenuMode.MENU_MODE_ROTARY,
 | 
			
		||||
    CONF_JOYSTICK: MenuMode.MENU_MODE_JOYSTICK,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DisplayMenuOnEnterTrigger = display_menu_base_ns.class_(
 | 
			
		||||
    "DisplayMenuOnEnterTrigger", automation.Trigger
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DisplayMenuOnLeaveTrigger = display_menu_base_ns.class_(
 | 
			
		||||
    "DisplayMenuOnLeaveTrigger", automation.Trigger
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DisplayMenuOnValueTrigger = display_menu_base_ns.class_(
 | 
			
		||||
    "DisplayMenuOnValueTrigger", automation.Trigger
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DisplayMenuOnNextTrigger = display_menu_base_ns.class_(
 | 
			
		||||
    "DisplayMenuOnNextTrigger", automation.Trigger
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DisplayMenuOnPrevTrigger = display_menu_base_ns.class_(
 | 
			
		||||
    "DisplayMenuOnPrevTrigger", automation.Trigger
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_format(format):
 | 
			
		||||
    if re.search(r"^%([+-])*(\d+)*(\.\d+)*[fg]$", format) is None:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"{CONF_FORMAT}: has to specify a printf-like format string specifying exactly one f or g type conversion, '{format}' provided"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return format
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Use a simple indirection to circumvent the recursion limitation
 | 
			
		||||
def menu_item_schema(value):
 | 
			
		||||
    return MENU_ITEM_SCHEMA(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_COMMON_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_TEXT): cv.templatable(cv.string),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_ENTER_LEAVE_SCHEMA = MENU_ITEM_COMMON_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_ON_ENTER): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnEnterTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_LEAVE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnLeaveTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_VALUE_SCHEMA = MENU_ITEM_COMMON_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_ON_VALUE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnValueTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA = MENU_ITEM_ENTER_LEAVE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_ON_VALUE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnValueTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_SCHEMA = cv.typed_schema(
 | 
			
		||||
    {
 | 
			
		||||
        CONF_LABEL: MENU_ITEM_COMMON_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItem),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_BACK: MENU_ITEM_COMMON_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItem),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_MENU: MENU_ITEM_ENTER_LEAVE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemMenu),
 | 
			
		||||
                cv.Required(CONF_ITEMS): cv.All(
 | 
			
		||||
                    cv.ensure_list(menu_item_schema), cv.Length(min=1)
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_SELECT: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemSelect),
 | 
			
		||||
                cv.Required(CONF_SELECT): cv.use_id(Select),
 | 
			
		||||
                cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_NUMBER: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemNumber),
 | 
			
		||||
                cv.Required(CONF_NUMBER): cv.use_id(Number),
 | 
			
		||||
                cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_FORMAT, default="%.1f"): cv.All(
 | 
			
		||||
                    cv.string_strict,
 | 
			
		||||
                    validate_format,
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_SWITCH: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemSwitch),
 | 
			
		||||
                cv.Required(CONF_SWITCH): cv.use_id(Switch),
 | 
			
		||||
                cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_ON_TEXT, default="On"): cv.string_strict,
 | 
			
		||||
                cv.Optional(CONF_OFF_TEXT, default="Off"): cv.string_strict,
 | 
			
		||||
                cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_COMMAND: MENU_ITEM_VALUE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemCommand),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_CUSTOM: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemCustom),
 | 
			
		||||
                cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
 | 
			
		||||
                cv.Optional(CONF_ON_NEXT): automation.validate_automation(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                            DisplayMenuOnNextTrigger
 | 
			
		||||
                        ),
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_ON_PREV): automation.validate_automation(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                            DisplayMenuOnPrevTrigger
 | 
			
		||||
                        ),
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    default_type="label",
 | 
			
		||||
    lower=True,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DISPLAY_MENU_BASE_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
 | 
			
		||||
        cv.GenerateID(CONF_ROOT_ITEM_ID): cv.declare_id(MenuItemMenu),
 | 
			
		||||
        cv.Optional(CONF_MODE, default=CONF_ROTARY): cv.enum(MENU_MODES),
 | 
			
		||||
        cv.Optional(CONF_ON_ENTER): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnEnterTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_LEAVE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnLeaveTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Required(CONF_ITEMS): cv.All(
 | 
			
		||||
            cv.ensure_list(MENU_ITEM_SCHEMA), cv.Length(min=1)
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
MENU_ACTION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_ID): cv.use_id(DisplayMenuComponent),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.up", UpAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_up_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.down", DownAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_down_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.left", LeftAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_left_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.right", RightAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_right_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.enter", EnterAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_enter_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.show", ShowAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_show_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.hide", HideAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_hide_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "display_menu.show_main", ShowMainAction, MENU_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def menu_show_main_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_condition(
 | 
			
		||||
    "display_menu.is_active",
 | 
			
		||||
    IsActiveCondition,
 | 
			
		||||
    automation.maybe_simple_id(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_ID): cv.use_id(DisplayMenuComponent),
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
async def display_menu_is_active_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(condition_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def menu_item_to_code(menu, config, parent):
 | 
			
		||||
    if config[CONF_TYPE] in MENU_ITEMS_WITH_SPECIALIZED_CLASSES:
 | 
			
		||||
        item = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    else:
 | 
			
		||||
        item = cg.new_Pvariable(config[CONF_ID], MENU_ITEM_TYPES[config[CONF_TYPE]])
 | 
			
		||||
    cg.add(parent.add_item(item))
 | 
			
		||||
    if CONF_TEXT in config:
 | 
			
		||||
        if isinstance(config[CONF_TEXT], core.Lambda):
 | 
			
		||||
            template_ = await cg.templatable(
 | 
			
		||||
                config[CONF_TEXT], [(MenuItemConstPtr, "it")], cg.std_string
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(item.set_text(template_))
 | 
			
		||||
        else:
 | 
			
		||||
            cg.add(item.set_text(config[CONF_TEXT]))
 | 
			
		||||
    if CONF_VALUE_LAMBDA in config:
 | 
			
		||||
        template_ = await cg.process_lambda(
 | 
			
		||||
            config[CONF_VALUE_LAMBDA],
 | 
			
		||||
            [(MenuItemConstPtr, "it")],
 | 
			
		||||
            return_type=cg.std_string,
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(item.set_value_lambda(template_))
 | 
			
		||||
    if CONF_ITEMS in config:
 | 
			
		||||
        for c in config[CONF_ITEMS]:
 | 
			
		||||
            await menu_item_to_code(menu, c, item)
 | 
			
		||||
    if CONF_IMMEDIATE_EDIT in config:
 | 
			
		||||
        cg.add(item.set_immediate_edit(config[CONF_IMMEDIATE_EDIT]))
 | 
			
		||||
    if config[CONF_TYPE] == CONF_SELECT:
 | 
			
		||||
        var = await cg.get_variable(config[CONF_SELECT])
 | 
			
		||||
        cg.add(item.set_select_variable(var))
 | 
			
		||||
    if config[CONF_TYPE] == CONF_NUMBER:
 | 
			
		||||
        var = await cg.get_variable(config[CONF_NUMBER])
 | 
			
		||||
        cg.add(item.set_number_variable(var))
 | 
			
		||||
        cg.add(item.set_format(config[CONF_FORMAT]))
 | 
			
		||||
    if config[CONF_TYPE] == CONF_SWITCH:
 | 
			
		||||
        var = await cg.get_variable(config[CONF_SWITCH])
 | 
			
		||||
        cg.add(item.set_switch_variable(var))
 | 
			
		||||
        cg.add(item.set_on_text(config[CONF_ON_TEXT]))
 | 
			
		||||
        cg.add(item.set_off_text(config[CONF_OFF_TEXT]))
 | 
			
		||||
    for conf in config.get(CONF_ON_ENTER, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_LEAVE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_VALUE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_NEXT, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_PREV, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def display_menu_to_code(menu, config):
 | 
			
		||||
    cg.add(menu.set_active(config[CONF_ACTIVE]))
 | 
			
		||||
    root_item = cg.new_Pvariable(config[CONF_ROOT_ITEM_ID])
 | 
			
		||||
    cg.add(menu.set_root_item(root_item))
 | 
			
		||||
    cg.add(menu.set_mode(config[CONF_MODE]))
 | 
			
		||||
    for c in config[CONF_ITEMS]:
 | 
			
		||||
        await menu_item_to_code(menu, c, root_item)
 | 
			
		||||
    for conf in config.get(CONF_ON_ENTER, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], root_item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_LEAVE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], root_item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
@@ -1,133 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "display_menu_base.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display_menu_base {
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class UpAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit UpAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->up(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DownAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DownAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->down(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class LeftAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit LeftAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->left(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class RightAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit RightAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->right(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EnterAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit EnterAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->enter(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class ShowAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit ShowAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->show(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class HideAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit HideAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->hide(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class ShowMainAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit ShowMainAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->show_main(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
template<typename... Ts> class IsActiveCondition : public Condition<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit IsActiveCondition(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
  bool check(Ts... x) override { return this->menu_->is_active(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayMenuOnEnterTrigger : public Trigger<const MenuItem *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayMenuOnEnterTrigger(MenuItem *parent) {
 | 
			
		||||
    parent->add_on_enter_callback([this, parent]() { this->trigger(parent); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayMenuOnLeaveTrigger : public Trigger<const MenuItem *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayMenuOnLeaveTrigger(MenuItem *parent) {
 | 
			
		||||
    parent->add_on_leave_callback([this, parent]() { this->trigger(parent); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayMenuOnValueTrigger : public Trigger<const MenuItem *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayMenuOnValueTrigger(MenuItem *parent) {
 | 
			
		||||
    parent->add_on_value_callback([this, parent]() { this->trigger(parent); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayMenuOnNextTrigger : public Trigger<const MenuItem *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayMenuOnNextTrigger(MenuItemCustom *parent) {
 | 
			
		||||
    parent->add_on_next_callback([this, parent]() { this->trigger(parent); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayMenuOnPrevTrigger : public Trigger<const MenuItem *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayMenuOnPrevTrigger(MenuItemCustom *parent) {
 | 
			
		||||
    parent->add_on_prev_callback([this, parent]() { this->trigger(parent); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace display_menu_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,315 +0,0 @@
 | 
			
		||||
#include "display_menu_base.h"
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display_menu_base {
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::up() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    bool changed = false;
 | 
			
		||||
 | 
			
		||||
    if (this->editing_) {
 | 
			
		||||
      switch (this->mode_) {
 | 
			
		||||
        case MENU_MODE_ROTARY:
 | 
			
		||||
          changed = this->get_selected_item_()->select_prev();
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      changed = this->cursor_up_();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changed)
 | 
			
		||||
      this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::down() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    bool changed = false;
 | 
			
		||||
 | 
			
		||||
    if (this->editing_) {
 | 
			
		||||
      switch (this->mode_) {
 | 
			
		||||
        case MENU_MODE_ROTARY:
 | 
			
		||||
          changed = this->get_selected_item_()->select_next();
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      changed = this->cursor_down_();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changed)
 | 
			
		||||
      this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::left() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    bool changed = false;
 | 
			
		||||
 | 
			
		||||
    switch (this->get_selected_item_()->get_type()) {
 | 
			
		||||
      case MENU_ITEM_SELECT:
 | 
			
		||||
      case MENU_ITEM_SWITCH:
 | 
			
		||||
      case MENU_ITEM_NUMBER:
 | 
			
		||||
      case MENU_ITEM_CUSTOM:
 | 
			
		||||
        switch (this->mode_) {
 | 
			
		||||
          case MENU_MODE_ROTARY:
 | 
			
		||||
            if (this->editing_) {
 | 
			
		||||
              this->finish_editing_();
 | 
			
		||||
              changed = true;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
          case MENU_MODE_JOYSTICK:
 | 
			
		||||
            if (this->editing_ || this->get_selected_item_()->get_immediate_edit())
 | 
			
		||||
              changed = this->get_selected_item_()->select_prev();
 | 
			
		||||
            break;
 | 
			
		||||
          default:
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case MENU_ITEM_BACK:
 | 
			
		||||
        changed = this->leave_menu_();
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changed)
 | 
			
		||||
      this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::right() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    bool changed = false;
 | 
			
		||||
 | 
			
		||||
    switch (this->get_selected_item_()->get_type()) {
 | 
			
		||||
      case MENU_ITEM_SELECT:
 | 
			
		||||
      case MENU_ITEM_SWITCH:
 | 
			
		||||
      case MENU_ITEM_NUMBER:
 | 
			
		||||
      case MENU_ITEM_CUSTOM:
 | 
			
		||||
        switch (this->mode_) {
 | 
			
		||||
          case MENU_MODE_JOYSTICK:
 | 
			
		||||
            if (this->editing_ || this->get_selected_item_()->get_immediate_edit())
 | 
			
		||||
              changed = this->get_selected_item_()->select_next();
 | 
			
		||||
          default:
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case MENU_ITEM_MENU:
 | 
			
		||||
        changed = this->enter_menu_();
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changed)
 | 
			
		||||
      this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::enter() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    bool changed = false;
 | 
			
		||||
    MenuItem *item = this->get_selected_item_();
 | 
			
		||||
 | 
			
		||||
    if (this->editing_) {
 | 
			
		||||
      this->finish_editing_();
 | 
			
		||||
      changed = true;
 | 
			
		||||
    } else {
 | 
			
		||||
      switch (item->get_type()) {
 | 
			
		||||
        case MENU_ITEM_MENU:
 | 
			
		||||
          changed = this->enter_menu_();
 | 
			
		||||
          break;
 | 
			
		||||
        case MENU_ITEM_BACK:
 | 
			
		||||
          changed = this->leave_menu_();
 | 
			
		||||
          break;
 | 
			
		||||
        case MENU_ITEM_SELECT:
 | 
			
		||||
        case MENU_ITEM_SWITCH:
 | 
			
		||||
        case MENU_ITEM_CUSTOM:
 | 
			
		||||
          if (item->get_immediate_edit()) {
 | 
			
		||||
            changed = item->select_next();
 | 
			
		||||
          } else {
 | 
			
		||||
            this->editing_ = true;
 | 
			
		||||
            item->on_enter();
 | 
			
		||||
            changed = true;
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case MENU_ITEM_NUMBER:
 | 
			
		||||
          // A number cannot be immediate in the rotary mode
 | 
			
		||||
          if (!item->get_immediate_edit() || this->mode_ == MENU_MODE_ROTARY) {
 | 
			
		||||
            this->editing_ = true;
 | 
			
		||||
            item->on_enter();
 | 
			
		||||
            changed = true;
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case MENU_ITEM_COMMAND:
 | 
			
		||||
          changed = item->select_next();
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changed)
 | 
			
		||||
      this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::draw() {
 | 
			
		||||
  if (this->check_healthy_and_active_())
 | 
			
		||||
    this->draw_menu();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::show_main() {
 | 
			
		||||
  bool disp_changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  this->process_initial_();
 | 
			
		||||
 | 
			
		||||
  if (this->active_ && this->editing_)
 | 
			
		||||
    this->finish_editing_();
 | 
			
		||||
 | 
			
		||||
  if (this->displayed_item_ != this->root_item_) {
 | 
			
		||||
    this->displayed_item_->on_leave();
 | 
			
		||||
    disp_changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->reset_();
 | 
			
		||||
  this->active_ = true;
 | 
			
		||||
 | 
			
		||||
  if (disp_changed) {
 | 
			
		||||
    this->displayed_item_->on_enter();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->draw_and_update();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::show() {
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  this->process_initial_();
 | 
			
		||||
 | 
			
		||||
  if (!this->active_) {
 | 
			
		||||
    this->active_ = true;
 | 
			
		||||
    this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::hide() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    if (this->editing_)
 | 
			
		||||
      this->finish_editing_();
 | 
			
		||||
    this->active_ = false;
 | 
			
		||||
    this->update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::reset_() {
 | 
			
		||||
  this->displayed_item_ = this->root_item_;
 | 
			
		||||
  this->cursor_index_ = this->top_index_ = 0;
 | 
			
		||||
  this->selection_stack_.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::process_initial_() {
 | 
			
		||||
  if (!this->root_on_enter_called_) {
 | 
			
		||||
    this->root_item_->on_enter();
 | 
			
		||||
    this->root_on_enter_called_ = true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DisplayMenuComponent::check_healthy_and_active_() {
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  this->process_initial_();
 | 
			
		||||
 | 
			
		||||
  return this->active_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DisplayMenuComponent::cursor_up_() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->cursor_index_ > 0) {
 | 
			
		||||
    changed = true;
 | 
			
		||||
 | 
			
		||||
    --this->cursor_index_;
 | 
			
		||||
 | 
			
		||||
    if (this->cursor_index_ < this->top_index_)
 | 
			
		||||
      this->top_index_ = this->cursor_index_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DisplayMenuComponent::cursor_down_() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->cursor_index_ + 1 < this->displayed_item_->items_size()) {
 | 
			
		||||
    changed = true;
 | 
			
		||||
 | 
			
		||||
    ++this->cursor_index_;
 | 
			
		||||
 | 
			
		||||
    if (this->cursor_index_ >= this->top_index_ + this->rows_)
 | 
			
		||||
      this->top_index_ = this->cursor_index_ - this->rows_ + 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DisplayMenuComponent::enter_menu_() {
 | 
			
		||||
  this->displayed_item_->on_leave();
 | 
			
		||||
  this->displayed_item_ = static_cast<MenuItemMenu *>(this->get_selected_item_());
 | 
			
		||||
  this->selection_stack_.push_front({this->top_index_, this->cursor_index_});
 | 
			
		||||
  this->cursor_index_ = this->top_index_ = 0;
 | 
			
		||||
  this->displayed_item_->on_enter();
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DisplayMenuComponent::leave_menu_() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->displayed_item_->get_parent() != nullptr) {
 | 
			
		||||
    this->displayed_item_->on_leave();
 | 
			
		||||
    this->displayed_item_ = this->displayed_item_->get_parent();
 | 
			
		||||
    this->top_index_ = this->selection_stack_.front().first;
 | 
			
		||||
    this->cursor_index_ = this->selection_stack_.front().second;
 | 
			
		||||
    this->selection_stack_.pop_front();
 | 
			
		||||
    this->displayed_item_->on_enter();
 | 
			
		||||
    changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::finish_editing_() {
 | 
			
		||||
  switch (this->get_selected_item_()->get_type()) {
 | 
			
		||||
    case MENU_ITEM_SELECT:
 | 
			
		||||
    case MENU_ITEM_NUMBER:
 | 
			
		||||
    case MENU_ITEM_SWITCH:
 | 
			
		||||
    case MENU_ITEM_CUSTOM:
 | 
			
		||||
      this->get_selected_item_()->on_leave();
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->editing_ = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::draw_menu() {
 | 
			
		||||
  for (size_t i = 0; i < this->rows_ && this->top_index_ + i < this->displayed_item_->items_size(); ++i) {
 | 
			
		||||
    this->draw_item(this->displayed_item_->get_item(this->top_index_ + i), i,
 | 
			
		||||
                    this->top_index_ + i == this->cursor_index_);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace display_menu_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,77 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
#include "menu_item.h"
 | 
			
		||||
 | 
			
		||||
#include <forward_list>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display_menu_base {
 | 
			
		||||
 | 
			
		||||
enum MenuMode {
 | 
			
		||||
  MENU_MODE_ROTARY,
 | 
			
		||||
  MENU_MODE_JOYSTICK,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MenuItem;
 | 
			
		||||
 | 
			
		||||
/** Class to display a hierarchical menu.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class DisplayMenuComponent : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_root_item(MenuItemMenu *item) { this->displayed_item_ = this->root_item_ = item; }
 | 
			
		||||
  void set_active(bool active) { this->active_ = active; }
 | 
			
		||||
  void set_mode(MenuMode mode) { this->mode_ = mode; }
 | 
			
		||||
  void set_rows(uint8_t rows) { this->rows_ = rows; }
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::PROCESSOR; }
 | 
			
		||||
 | 
			
		||||
  void up();
 | 
			
		||||
  void down();
 | 
			
		||||
  void left();
 | 
			
		||||
  void right();
 | 
			
		||||
  void enter();
 | 
			
		||||
 | 
			
		||||
  void show_main();
 | 
			
		||||
  void show();
 | 
			
		||||
  void hide();
 | 
			
		||||
 | 
			
		||||
  void draw();
 | 
			
		||||
 | 
			
		||||
  bool is_active() const { return this->active_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void reset_();
 | 
			
		||||
  void process_initial_();
 | 
			
		||||
  bool check_healthy_and_active_();
 | 
			
		||||
  MenuItem *get_selected_item_() { return this->displayed_item_->get_item(this->cursor_index_); }
 | 
			
		||||
  bool cursor_up_();
 | 
			
		||||
  bool cursor_down_();
 | 
			
		||||
  bool enter_menu_();
 | 
			
		||||
  bool leave_menu_();
 | 
			
		||||
  void finish_editing_();
 | 
			
		||||
  virtual void draw_menu();
 | 
			
		||||
  virtual void draw_item(const MenuItem *item, uint8_t row, bool selected) = 0;
 | 
			
		||||
  virtual void update() {}
 | 
			
		||||
  virtual void draw_and_update() {
 | 
			
		||||
    draw_menu();
 | 
			
		||||
    update();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t rows_;
 | 
			
		||||
  bool active_;
 | 
			
		||||
  MenuMode mode_;
 | 
			
		||||
  MenuItemMenu *root_item_{nullptr};
 | 
			
		||||
 | 
			
		||||
  MenuItemMenu *displayed_item_{nullptr};
 | 
			
		||||
  uint8_t top_index_{0};
 | 
			
		||||
  uint8_t cursor_index_{0};
 | 
			
		||||
  std::forward_list<std::pair<uint8_t, uint8_t>> selection_stack_{};
 | 
			
		||||
  bool editing_{false};
 | 
			
		||||
  bool root_on_enter_called_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace display_menu_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,179 +0,0 @@
 | 
			
		||||
#include "menu_item.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display_menu_base {
 | 
			
		||||
 | 
			
		||||
void MenuItem::on_enter() { this->on_enter_callbacks_.call(); }
 | 
			
		||||
 | 
			
		||||
void MenuItem::on_leave() { this->on_leave_callbacks_.call(); }
 | 
			
		||||
 | 
			
		||||
void MenuItem::on_value_() { this->on_value_callbacks_.call(); }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
std::string MenuItemSelect::get_value_text() const {
 | 
			
		||||
  std::string result;
 | 
			
		||||
 | 
			
		||||
  if (this->value_getter_.has_value()) {
 | 
			
		||||
    result = this->value_getter_.value()(this);
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->select_var_ != nullptr) {
 | 
			
		||||
      result = this->select_var_->state;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemSelect::select_next() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->select_var_ != nullptr) {
 | 
			
		||||
    this->select_var_->make_call().select_next(true).perform();
 | 
			
		||||
    changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemSelect::select_prev() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->select_var_ != nullptr) {
 | 
			
		||||
    this->select_var_->make_call().select_previous(true).perform();
 | 
			
		||||
    changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_SELECT
 | 
			
		||||
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
std::string MenuItemNumber::get_value_text() const {
 | 
			
		||||
  std::string result;
 | 
			
		||||
 | 
			
		||||
  if (this->value_getter_.has_value()) {
 | 
			
		||||
    result = this->value_getter_.value()(this);
 | 
			
		||||
  } else {
 | 
			
		||||
    char data[32];
 | 
			
		||||
    snprintf(data, sizeof(data), this->format_.c_str(), get_number_value_());
 | 
			
		||||
    result = data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemNumber::select_next() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->number_var_ != nullptr) {
 | 
			
		||||
    float last = this->number_var_->state;
 | 
			
		||||
    this->number_var_->make_call().number_increment(false).perform();
 | 
			
		||||
 | 
			
		||||
    if (this->number_var_->state != last) {
 | 
			
		||||
      this->on_value_();
 | 
			
		||||
      changed = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemNumber::select_prev() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->number_var_ != nullptr) {
 | 
			
		||||
    float last = this->number_var_->state;
 | 
			
		||||
    this->number_var_->make_call().number_decrement(false).perform();
 | 
			
		||||
 | 
			
		||||
    if (this->number_var_->state != last) {
 | 
			
		||||
      this->on_value_();
 | 
			
		||||
      changed = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float MenuItemNumber::get_number_value_() const {
 | 
			
		||||
  float result = 0.0;
 | 
			
		||||
 | 
			
		||||
  if (this->number_var_ != nullptr) {
 | 
			
		||||
    if (!this->number_var_->has_state() || this->number_var_->state < this->number_var_->traits.get_min_value()) {
 | 
			
		||||
      result = this->number_var_->traits.get_min_value();
 | 
			
		||||
    } else if (this->number_var_->state > this->number_var_->traits.get_max_value()) {
 | 
			
		||||
      result = this->number_var_->traits.get_max_value();
 | 
			
		||||
    } else {
 | 
			
		||||
      result = this->number_var_->state;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_NUMBER
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
std::string MenuItemSwitch::get_value_text() const {
 | 
			
		||||
  std::string result;
 | 
			
		||||
 | 
			
		||||
  if (this->value_getter_.has_value()) {
 | 
			
		||||
    result = this->value_getter_.value()(this);
 | 
			
		||||
  } else {
 | 
			
		||||
    result = this->get_switch_state_() ? this->switch_on_text_ : this->switch_off_text_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemSwitch::select_next() { return this->toggle_switch_(); }
 | 
			
		||||
 | 
			
		||||
bool MenuItemSwitch::select_prev() { return this->toggle_switch_(); }
 | 
			
		||||
 | 
			
		||||
bool MenuItemSwitch::get_switch_state_() const { return (this->switch_var_ != nullptr && this->switch_var_->state); }
 | 
			
		||||
 | 
			
		||||
bool MenuItemSwitch::toggle_switch_() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->switch_var_ != nullptr) {
 | 
			
		||||
    this->switch_var_->toggle();
 | 
			
		||||
    this->on_value_();
 | 
			
		||||
    changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_SWITCH
 | 
			
		||||
 | 
			
		||||
std::string MenuItemCustom::get_value_text() const {
 | 
			
		||||
  return (this->value_getter_.has_value()) ? this->value_getter_.value()(this) : "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemCommand::select_next() {
 | 
			
		||||
  this->on_value_();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemCommand::select_prev() {
 | 
			
		||||
  this->on_value_();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemCustom::select_next() {
 | 
			
		||||
  this->on_next_();
 | 
			
		||||
  this->on_value_();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemCustom::select_prev() {
 | 
			
		||||
  this->on_prev_();
 | 
			
		||||
  this->on_value_();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MenuItemCustom::on_next_() { this->on_next_callbacks_.call(); }
 | 
			
		||||
 | 
			
		||||
void MenuItemCustom::on_prev_() { this->on_prev_callbacks_.call(); }
 | 
			
		||||
 | 
			
		||||
}  // namespace display_menu_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,187 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
#include "esphome/components/number/number.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
#include "esphome/components/select/select.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
#include "esphome/components/switch/switch.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display_menu_base {
 | 
			
		||||
 | 
			
		||||
enum MenuItemType {
 | 
			
		||||
  MENU_ITEM_LABEL,
 | 
			
		||||
  MENU_ITEM_MENU,
 | 
			
		||||
  MENU_ITEM_BACK,
 | 
			
		||||
  MENU_ITEM_SELECT,
 | 
			
		||||
  MENU_ITEM_NUMBER,
 | 
			
		||||
  MENU_ITEM_SWITCH,
 | 
			
		||||
  MENU_ITEM_COMMAND,
 | 
			
		||||
  MENU_ITEM_CUSTOM,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MenuItem;
 | 
			
		||||
class MenuItemMenu;
 | 
			
		||||
using value_getter_t = std::function<std::string(const MenuItem *)>;
 | 
			
		||||
 | 
			
		||||
class MenuItem {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItem(MenuItemType t) : item_type_(t) {}
 | 
			
		||||
  void set_parent(MenuItemMenu *parent) { this->parent_ = parent; }
 | 
			
		||||
  MenuItemMenu *get_parent() { return this->parent_; }
 | 
			
		||||
  MenuItemType get_type() const { return this->item_type_; }
 | 
			
		||||
  template<typename V> void set_text(V val) { this->text_ = val; }
 | 
			
		||||
  void add_on_enter_callback(std::function<void()> &&cb) { this->on_enter_callbacks_.add(std::move(cb)); }
 | 
			
		||||
  void add_on_leave_callback(std::function<void()> &&cb) { this->on_leave_callbacks_.add(std::move(cb)); }
 | 
			
		||||
  void add_on_value_callback(std::function<void()> &&cb) { this->on_value_callbacks_.add(std::move(cb)); }
 | 
			
		||||
 | 
			
		||||
  std::string get_text() const { return const_cast<MenuItem *>(this)->text_.value(this); }
 | 
			
		||||
  virtual bool get_immediate_edit() const { return false; }
 | 
			
		||||
  virtual bool has_value() const { return false; }
 | 
			
		||||
  virtual std::string get_value_text() const { return ""; }
 | 
			
		||||
 | 
			
		||||
  virtual bool select_next() { return false; }
 | 
			
		||||
  virtual bool select_prev() { return false; }
 | 
			
		||||
 | 
			
		||||
  void on_enter();
 | 
			
		||||
  void on_leave();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void on_value_();
 | 
			
		||||
 | 
			
		||||
  MenuItemType item_type_;
 | 
			
		||||
  MenuItemMenu *parent_{nullptr};
 | 
			
		||||
  TemplatableValue<std::string, const MenuItem *> text_;
 | 
			
		||||
 | 
			
		||||
  CallbackManager<void()> on_enter_callbacks_{};
 | 
			
		||||
  CallbackManager<void()> on_leave_callbacks_{};
 | 
			
		||||
  CallbackManager<void()> on_value_callbacks_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MenuItemMenu : public MenuItem {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemMenu() : MenuItem(MENU_ITEM_MENU) {}
 | 
			
		||||
  void add_item(MenuItem *item) {
 | 
			
		||||
    item->set_parent(this);
 | 
			
		||||
    this->items_.push_back(item);
 | 
			
		||||
  }
 | 
			
		||||
  size_t items_size() const { return this->items_.size(); }
 | 
			
		||||
  MenuItem *get_item(size_t i) { return this->items_[i]; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::vector<MenuItem *> items_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MenuItemEditable : public MenuItem {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemEditable(MenuItemType t) : MenuItem(t) {}
 | 
			
		||||
  void set_immediate_edit(bool val) { this->immediate_edit_ = val; }
 | 
			
		||||
  bool get_immediate_edit() const override { return this->immediate_edit_; }
 | 
			
		||||
  void set_value_lambda(value_getter_t &&getter) { this->value_getter_ = getter; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool immediate_edit_{false};
 | 
			
		||||
  optional<value_getter_t> value_getter_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
class MenuItemSelect : public MenuItemEditable {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemSelect() : MenuItemEditable(MENU_ITEM_SELECT) {}
 | 
			
		||||
  void set_select_variable(select::Select *var) { this->select_var_ = var; }
 | 
			
		||||
 | 
			
		||||
  bool has_value() const override { return true; }
 | 
			
		||||
  std::string get_value_text() const override;
 | 
			
		||||
 | 
			
		||||
  bool select_next() override;
 | 
			
		||||
  bool select_prev() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  select::Select *select_var_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
class MenuItemNumber : public MenuItemEditable {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemNumber() : MenuItemEditable(MENU_ITEM_NUMBER) {}
 | 
			
		||||
  void set_number_variable(number::Number *var) { this->number_var_ = var; }
 | 
			
		||||
  void set_format(const std::string &fmt) { this->format_ = fmt; }
 | 
			
		||||
 | 
			
		||||
  bool has_value() const override { return true; }
 | 
			
		||||
  std::string get_value_text() const override;
 | 
			
		||||
 | 
			
		||||
  bool select_next() override;
 | 
			
		||||
  bool select_prev() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  float get_number_value_() const;
 | 
			
		||||
 | 
			
		||||
  number::Number *number_var_{nullptr};
 | 
			
		||||
  std::string format_;
 | 
			
		||||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
class MenuItemSwitch : public MenuItemEditable {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemSwitch() : MenuItemEditable(MENU_ITEM_SWITCH) {}
 | 
			
		||||
  void set_switch_variable(switch_::Switch *var) { this->switch_var_ = var; }
 | 
			
		||||
  void set_on_text(const std::string &t) { this->switch_on_text_ = t; }
 | 
			
		||||
  void set_off_text(const std::string &t) { this->switch_off_text_ = t; }
 | 
			
		||||
 | 
			
		||||
  bool has_value() const override { return true; }
 | 
			
		||||
  std::string get_value_text() const override;
 | 
			
		||||
 | 
			
		||||
  bool select_next() override;
 | 
			
		||||
  bool select_prev() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool get_switch_state_() const;
 | 
			
		||||
  bool toggle_switch_();
 | 
			
		||||
 | 
			
		||||
  switch_::Switch *switch_var_{nullptr};
 | 
			
		||||
  std::string switch_on_text_;
 | 
			
		||||
  std::string switch_off_text_;
 | 
			
		||||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
class MenuItemCommand : public MenuItem {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemCommand() : MenuItem(MENU_ITEM_COMMAND) {}
 | 
			
		||||
 | 
			
		||||
  bool select_next() override;
 | 
			
		||||
  bool select_prev() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MenuItemCustom : public MenuItemEditable {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemCustom() : MenuItemEditable(MENU_ITEM_CUSTOM) {}
 | 
			
		||||
  void add_on_next_callback(std::function<void()> &&cb) { this->on_next_callbacks_.add(std::move(cb)); }
 | 
			
		||||
  void add_on_prev_callback(std::function<void()> &&cb) { this->on_prev_callbacks_.add(std::move(cb)); }
 | 
			
		||||
 | 
			
		||||
  bool has_value() const override { return this->value_getter_.has_value(); }
 | 
			
		||||
  std::string get_value_text() const override;
 | 
			
		||||
 | 
			
		||||
  bool select_next() override;
 | 
			
		||||
  bool select_prev() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void on_next_();
 | 
			
		||||
  void on_prev_();
 | 
			
		||||
 | 
			
		||||
  CallbackManager<void()> on_next_callbacks_{};
 | 
			
		||||
  CallbackManager<void()> on_prev_callbacks_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace display_menu_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -105,12 +105,6 @@ _esp32_validations = {
 | 
			
		||||
 | 
			
		||||
def validate_gpio_pin(value):
 | 
			
		||||
    value = _translate_pin(value)
 | 
			
		||||
    board = CORE.data[KEY_ESP32][KEY_BOARD]
 | 
			
		||||
    board_pins = boards.ESP32_BOARD_PINS.get(board, {})
 | 
			
		||||
 | 
			
		||||
    if value in board_pins.values():
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
    variant = CORE.data[KEY_ESP32][KEY_VARIANT]
 | 
			
		||||
    if variant not in _esp32_validations:
 | 
			
		||||
        raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
 | 
			
		||||
 
 | 
			
		||||
@@ -83,8 +83,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
  if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d", this->connection_index_,
 | 
			
		||||
           this->address_str_.c_str(), event, esp_gattc_if);
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d conn_id=%d", this->connection_index_,
 | 
			
		||||
           this->address_str_.c_str(), event, esp_gattc_if, this->conn_id_);
 | 
			
		||||
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_REG_EVT: {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,53 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "ezo.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ezo {
 | 
			
		||||
 | 
			
		||||
class LedTrigger : public Trigger<bool> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit LedTrigger(EZOSensor *ezo) {
 | 
			
		||||
    ezo->add_led_state_callback([this](bool value) { this->trigger(value); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CustomTrigger : public Trigger<std::string> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit CustomTrigger(EZOSensor *ezo) {
 | 
			
		||||
    ezo->add_custom_callback([this](std::string value) { this->trigger(std::move(value)); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TTrigger : public Trigger<std::string> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit TTrigger(EZOSensor *ezo) {
 | 
			
		||||
    ezo->add_t_callback([this](std::string value) { this->trigger(std::move(value)); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CalibrationTrigger : public Trigger<std::string> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit CalibrationTrigger(EZOSensor *ezo) {
 | 
			
		||||
    ezo->add_calibration_callback([this](std::string value) { this->trigger(std::move(value)); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SlopeTrigger : public Trigger<std::string> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit SlopeTrigger(EZOSensor *ezo) {
 | 
			
		||||
    ezo->add_slope_callback([this](std::string value) { this->trigger(std::move(value)); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DeviceInformationTrigger : public Trigger<std::string> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DeviceInformationTrigger(EZOSensor *ezo) {
 | 
			
		||||
    ezo->add_device_infomation_callback([this](std::string value) { this->trigger(std::move(value)); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ezo
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -5,11 +5,11 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ezo {
 | 
			
		||||
 | 
			
		||||
static const char *const EZO_COMMAND_TYPE_STRINGS[] = {"EZO_READ",  "EZO_LED",         "EZO_DEVICE_INFORMATION",
 | 
			
		||||
                                                       "EZO_SLOPE", "EZO_CALIBRATION", "EZO_SLEEP",
 | 
			
		||||
                                                       "EZO_I2C",   "EZO_T",           "EZO_CUSTOM"};
 | 
			
		||||
static const char *const TAG = "ezo.sensor";
 | 
			
		||||
 | 
			
		||||
static const char *const EZO_CALIBRATION_TYPE_STRINGS[] = {"LOW", "MID", "HIGH"};
 | 
			
		||||
static const uint16_t EZO_STATE_WAIT = 1;
 | 
			
		||||
static const uint16_t EZO_STATE_SEND_TEMP = 2;
 | 
			
		||||
static const uint16_t EZO_STATE_WAIT_TEMP = 4;
 | 
			
		||||
 | 
			
		||||
void EZOSensor::dump_config() {
 | 
			
		||||
  LOG_SENSOR("", "EZO", this);
 | 
			
		||||
@@ -20,75 +20,37 @@ void EZOSensor::dump_config() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::update() {
 | 
			
		||||
  // Check if a read is in there already and if not insert on in the second position
 | 
			
		||||
 | 
			
		||||
  if (!this->commands_.empty() && this->commands_.front()->command_type != EzoCommandType::EZO_READ &&
 | 
			
		||||
      this->commands_.size() > 1) {
 | 
			
		||||
    bool found = false;
 | 
			
		||||
 | 
			
		||||
    for (auto &i : this->commands_) {
 | 
			
		||||
      if (i->command_type == EzoCommandType::EZO_READ) {
 | 
			
		||||
        found = true;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!found) {
 | 
			
		||||
      std::unique_ptr<EzoCommand> ezo_command(new EzoCommand);
 | 
			
		||||
      ezo_command->command = "R";
 | 
			
		||||
      ezo_command->command_type = EzoCommandType::EZO_READ;
 | 
			
		||||
      ezo_command->delay_ms = 900;
 | 
			
		||||
 | 
			
		||||
      auto it = this->commands_.begin();
 | 
			
		||||
      ++it;
 | 
			
		||||
      this->commands_.insert(it, std::move(ezo_command));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  if (this->state_ & EZO_STATE_WAIT) {
 | 
			
		||||
    ESP_LOGE(TAG, "update overrun, still waiting for previous response");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->get_state();
 | 
			
		||||
  uint8_t c = 'R';
 | 
			
		||||
  this->write(&c, 1);
 | 
			
		||||
  this->state_ |= EZO_STATE_WAIT;
 | 
			
		||||
  this->start_time_ = millis();
 | 
			
		||||
  this->wait_time_ = 900;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::loop() {
 | 
			
		||||
  if (this->commands_.empty()) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  EzoCommand *to_run = this->commands_.front().get();
 | 
			
		||||
 | 
			
		||||
  if (!to_run->command_sent) {
 | 
			
		||||
    const uint8_t *data = reinterpret_cast<const uint8_t *>(to_run->command.c_str());
 | 
			
		||||
    ESP_LOGVV(TAG, "Sending command \"%s\"", data);
 | 
			
		||||
 | 
			
		||||
    this->write(data, to_run->command.length());
 | 
			
		||||
 | 
			
		||||
    if (to_run->command_type == EzoCommandType::EZO_SLEEP ||
 | 
			
		||||
        to_run->command_type == EzoCommandType::EZO_I2C) {  // Commands with no return data
 | 
			
		||||
      this->commands_.pop_front();
 | 
			
		||||
      if (to_run->command_type == EzoCommandType::EZO_I2C)
 | 
			
		||||
        this->address_ = this->new_address_;
 | 
			
		||||
      return;
 | 
			
		||||
  uint8_t buf[21];
 | 
			
		||||
  if (!(this->state_ & EZO_STATE_WAIT)) {
 | 
			
		||||
    if (this->state_ & EZO_STATE_SEND_TEMP) {
 | 
			
		||||
      int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_);
 | 
			
		||||
      this->write(buf, len);
 | 
			
		||||
      this->state_ = EZO_STATE_WAIT | EZO_STATE_WAIT_TEMP;
 | 
			
		||||
      this->start_time_ = millis();
 | 
			
		||||
      this->wait_time_ = 300;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->start_time_ = millis();
 | 
			
		||||
    to_run->command_sent = true;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (millis() - this->start_time_ < to_run->delay_ms)
 | 
			
		||||
  if (millis() - this->start_time_ < this->wait_time_)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  uint8_t buf[32];
 | 
			
		||||
 | 
			
		||||
  buf[0] = 0;
 | 
			
		||||
 | 
			
		||||
  if (!this->read_bytes_raw(buf, 32)) {
 | 
			
		||||
  if (!this->read_bytes_raw(buf, 20)) {
 | 
			
		||||
    ESP_LOGE(TAG, "read error");
 | 
			
		||||
    this->commands_.pop_front();
 | 
			
		||||
    this->state_ = 0;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (buf[0]) {
 | 
			
		||||
    case 1:
 | 
			
		||||
      break;
 | 
			
		||||
@@ -104,142 +66,28 @@ void EZOSensor::loop() {
 | 
			
		||||
      ESP_LOGE(TAG, "device returned an unknown response: %d", buf[0]);
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->state_ & EZO_STATE_WAIT_TEMP) {
 | 
			
		||||
    this->state_ = 0;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->state_ &= ~EZO_STATE_WAIT;
 | 
			
		||||
  if (buf[0] != 1)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", buf, EZO_COMMAND_TYPE_STRINGS[to_run->command_type]);
 | 
			
		||||
 | 
			
		||||
  if ((buf[0] == 1) || (to_run->command_type == EzoCommandType::EZO_CALIBRATION)) {  // EZO_CALIBRATION returns 0-3
 | 
			
		||||
    // some sensors return multiple comma-separated values, terminate string after first one
 | 
			
		||||
    for (size_t i = 1; i < sizeof(buf) - 1; i++) {
 | 
			
		||||
      if (buf[i] == ',') {
 | 
			
		||||
        buf[i] = '\0';
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    std::string payload = reinterpret_cast<char *>(&buf[1]);
 | 
			
		||||
    if (!payload.empty()) {
 | 
			
		||||
      switch (to_run->command_type) {
 | 
			
		||||
        case EzoCommandType::EZO_READ: {
 | 
			
		||||
          auto val = parse_number<float>(payload);
 | 
			
		||||
          if (!val.has_value()) {
 | 
			
		||||
            ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
 | 
			
		||||
          } else {
 | 
			
		||||
            this->publish_state(*val);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_LED: {
 | 
			
		||||
          this->led_callback_.call(payload.back() == '1');
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_DEVICE_INFORMATION: {
 | 
			
		||||
          int start_location = 0;
 | 
			
		||||
          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
			
		||||
            this->device_infomation_callback_.call(payload.substr(start_location + 1));
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_SLOPE: {
 | 
			
		||||
          int start_location = 0;
 | 
			
		||||
          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
			
		||||
            this->slope_callback_.call(payload.substr(start_location + 1));
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_CALIBRATION: {
 | 
			
		||||
          int start_location = 0;
 | 
			
		||||
          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
			
		||||
            this->calibration_callback_.call(payload.substr(start_location + 1));
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_T: {
 | 
			
		||||
          this->t_callback_.call(payload);
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_CUSTOM: {
 | 
			
		||||
          this->custom_callback_.call(payload);
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        default: {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  // some sensors return multiple comma-separated values, terminate string after first one
 | 
			
		||||
  for (size_t i = 1; i < sizeof(buf) - 1; i++) {
 | 
			
		||||
    if (buf[i] == ',')
 | 
			
		||||
      buf[i] = '\0';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->commands_.pop_front();
 | 
			
		||||
  float val = parse_number<float>((char *) &buf[1]).value_or(0);
 | 
			
		||||
  this->publish_state(val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms) {
 | 
			
		||||
  std::unique_ptr<EzoCommand> ezo_command(new EzoCommand);
 | 
			
		||||
  ezo_command->command = command;
 | 
			
		||||
  ezo_command->command_type = command_type;
 | 
			
		||||
  ezo_command->delay_ms = delay_ms;
 | 
			
		||||
  this->commands_.push_back(std::move(ezo_command));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
 | 
			
		||||
  std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
 | 
			
		||||
  this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
 | 
			
		||||
void EZOSensor::set_tempcomp_value(float temp) {
 | 
			
		||||
  this->tempcomp_ = temp;
 | 
			
		||||
  this->state_ |= EZO_STATE_SEND_TEMP;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_address(uint8_t address) {
 | 
			
		||||
  if (address > 0 && address < 128) {
 | 
			
		||||
    std::string payload = str_sprintf("I2C,%u", address);
 | 
			
		||||
    this->new_address_ = address;
 | 
			
		||||
    this->add_command_(payload, EzoCommandType::EZO_I2C);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGE(TAG, "Invalid I2C address");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::get_device_information() { this->add_command_("i", EzoCommandType::EZO_DEVICE_INFORMATION); }
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_sleep() { this->add_command_("Sleep", EzoCommandType::EZO_SLEEP); }
 | 
			
		||||
 | 
			
		||||
void EZOSensor::get_state() { this->add_command_("R", EzoCommandType::EZO_READ, 900); }
 | 
			
		||||
 | 
			
		||||
void EZOSensor::get_slope() { this->add_command_("Slope,?", EzoCommandType::EZO_SLOPE); }
 | 
			
		||||
 | 
			
		||||
void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); }
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_t(float value) {
 | 
			
		||||
  std::string payload = str_sprintf("T,%0.2f", value);
 | 
			
		||||
  this->add_command_(payload, EzoCommandType::EZO_T);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_tempcomp_value(float temp) { this->set_t(temp); }
 | 
			
		||||
 | 
			
		||||
void EZOSensor::get_calibration() { this->add_command_("Cal,?", EzoCommandType::EZO_CALIBRATION); }
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_calibration_point_low(float value) {
 | 
			
		||||
  this->set_calibration_point_(EzoCalibrationType::EZO_CAL_LOW, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_calibration_point_mid(float value) {
 | 
			
		||||
  this->set_calibration_point_(EzoCalibrationType::EZO_CAL_MID, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_calibration_point_high(float value) {
 | 
			
		||||
  this->set_calibration_point_(EzoCalibrationType::EZO_CAL_HIGH, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_calibration_generic(float value) {
 | 
			
		||||
  std::string payload = str_sprintf("Cal,%0.2f", value);
 | 
			
		||||
  this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::clear_calibration() { this->add_command_("Cal,clear", EzoCommandType::EZO_CALIBRATION); }
 | 
			
		||||
 | 
			
		||||
void EZOSensor::get_led_state() { this->add_command_("L,?", EzoCommandType::EZO_LED); }
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_led_state(bool on) {
 | 
			
		||||
  std::string to_send = "L,";
 | 
			
		||||
  to_send += on ? "1" : "0";
 | 
			
		||||
  this->add_command_(to_send, EzoCommandType::EZO_LED);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::send_custom(const std::string &to_send) { this->add_command_(to_send, EzoCommandType::EZO_CUSTOM); }
 | 
			
		||||
 | 
			
		||||
}  // namespace ezo
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -3,35 +3,10 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include <deque>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ezo {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ezo.sensor";
 | 
			
		||||
 | 
			
		||||
enum EzoCommandType : uint8_t {
 | 
			
		||||
  EZO_READ = 0,
 | 
			
		||||
  EZO_LED = 1,
 | 
			
		||||
  EZO_DEVICE_INFORMATION = 2,
 | 
			
		||||
  EZO_SLOPE = 3,
 | 
			
		||||
  EZO_CALIBRATION,
 | 
			
		||||
  EZO_SLEEP = 4,
 | 
			
		||||
  EZO_I2C = 5,
 | 
			
		||||
  EZO_T = 6,
 | 
			
		||||
  EZO_CUSTOM = 7
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum EzoCalibrationType : uint8_t { EZO_CAL_LOW = 0, EZO_CAL_MID = 1, EZO_CAL_HIGH = 2 };
 | 
			
		||||
 | 
			
		||||
class EzoCommand {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string command;
 | 
			
		||||
  uint16_t delay_ms = 0;
 | 
			
		||||
  bool command_sent = false;
 | 
			
		||||
  EzoCommandType command_type;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// This class implements support for the EZO circuits in i2c mode
 | 
			
		||||
class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
@@ -40,71 +15,13 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2
 | 
			
		||||
  void update() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; };
 | 
			
		||||
 | 
			
		||||
  // I2C
 | 
			
		||||
  void set_address(uint8_t address);
 | 
			
		||||
 | 
			
		||||
  // Device Information
 | 
			
		||||
  void get_device_information();
 | 
			
		||||
  void add_device_infomation_callback(std::function<void(std::string)> &&callback) {
 | 
			
		||||
    this->device_infomation_callback_.add(std::move(callback));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Sleep
 | 
			
		||||
  void set_sleep();
 | 
			
		||||
 | 
			
		||||
  // R
 | 
			
		||||
  void get_state();
 | 
			
		||||
 | 
			
		||||
  // Slope
 | 
			
		||||
  void get_slope();
 | 
			
		||||
  void add_slope_callback(std::function<void(std::string)> &&callback) {
 | 
			
		||||
    this->slope_callback_.add(std::move(callback));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // T
 | 
			
		||||
  void get_t();
 | 
			
		||||
  void set_t(float value);
 | 
			
		||||
  void set_tempcomp_value(float temp);  // For backwards compatibility
 | 
			
		||||
  void add_t_callback(std::function<void(std::string)> &&callback) { this->t_callback_.add(std::move(callback)); }
 | 
			
		||||
 | 
			
		||||
  // Calibration
 | 
			
		||||
  void get_calibration();
 | 
			
		||||
  void set_calibration_point_low(float value);
 | 
			
		||||
  void set_calibration_point_mid(float value);
 | 
			
		||||
  void set_calibration_point_high(float value);
 | 
			
		||||
  void set_calibration_generic(float value);
 | 
			
		||||
  void clear_calibration();
 | 
			
		||||
  void add_calibration_callback(std::function<void(std::string)> &&callback) {
 | 
			
		||||
    this->calibration_callback_.add(std::move(callback));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // LED
 | 
			
		||||
  void get_led_state();
 | 
			
		||||
  void set_led_state(bool on);
 | 
			
		||||
  void add_led_state_callback(std::function<void(bool)> &&callback) { this->led_callback_.add(std::move(callback)); }
 | 
			
		||||
 | 
			
		||||
  // Custom
 | 
			
		||||
  void send_custom(const std::string &to_send);
 | 
			
		||||
  void add_custom_callback(std::function<void(std::string)> &&callback) {
 | 
			
		||||
    this->custom_callback_.add(std::move(callback));
 | 
			
		||||
  }
 | 
			
		||||
  void set_tempcomp_value(float temp);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::deque<std::unique_ptr<EzoCommand>> commands_;
 | 
			
		||||
  int new_address_;
 | 
			
		||||
 | 
			
		||||
  void add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms = 300);
 | 
			
		||||
 | 
			
		||||
  void set_calibration_point_(EzoCalibrationType type, float value);
 | 
			
		||||
 | 
			
		||||
  CallbackManager<void(std::string)> device_infomation_callback_{};
 | 
			
		||||
  CallbackManager<void(std::string)> calibration_callback_{};
 | 
			
		||||
  CallbackManager<void(std::string)> slope_callback_{};
 | 
			
		||||
  CallbackManager<void(std::string)> t_callback_{};
 | 
			
		||||
  CallbackManager<void(std::string)> custom_callback_{};
 | 
			
		||||
  CallbackManager<void(bool)> led_callback_{};
 | 
			
		||||
 | 
			
		||||
  uint32_t start_time_ = 0;
 | 
			
		||||
  uint32_t wait_time_ = 0;
 | 
			
		||||
  uint16_t state_ = 0;
 | 
			
		||||
  float tempcomp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ezo
 | 
			
		||||
 
 | 
			
		||||
@@ -1,81 +1,22 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@ssieb"]
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
CONF_ON_LED = "on_led"
 | 
			
		||||
CONF_ON_DEVICE_INFORMATION = "on_device_information"
 | 
			
		||||
CONF_ON_SLOPE = "on_slope"
 | 
			
		||||
CONF_ON_CALIBRATION = "on_calibration"
 | 
			
		||||
CONF_ON_T = "on_t"
 | 
			
		||||
CONF_ON_CUSTOM = "on_custom"
 | 
			
		||||
 | 
			
		||||
ezo_ns = cg.esphome_ns.namespace("ezo")
 | 
			
		||||
 | 
			
		||||
EZOSensor = ezo_ns.class_(
 | 
			
		||||
    "EZOSensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CustomTrigger = ezo_ns.class_(
 | 
			
		||||
    "CustomTrigger", automation.Trigger.template(cg.std_string)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
TTrigger = ezo_ns.class_("TTrigger", automation.Trigger.template(cg.std_string))
 | 
			
		||||
 | 
			
		||||
SlopeTrigger = ezo_ns.class_("SlopeTrigger", automation.Trigger.template(cg.std_string))
 | 
			
		||||
 | 
			
		||||
CalibrationTrigger = ezo_ns.class_(
 | 
			
		||||
    "CalibrationTrigger", automation.Trigger.template(cg.std_string)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DeviceInformationTrigger = ezo_ns.class_(
 | 
			
		||||
    "DeviceInformationTrigger", automation.Trigger.template(cg.std_string)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
LedTrigger = ezo_ns.class_("LedTrigger", automation.Trigger.template(cg.bool_))
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.SENSOR_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(EZOSensor),
 | 
			
		||||
            cv.Optional(CONF_ON_CUSTOM): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CustomTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_CALIBRATION): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CalibrationTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_SLOPE): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SlopeTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_T): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_DEVICE_INFORMATION): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                        DeviceInformationTrigger
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_LED): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LedTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
@@ -88,27 +29,3 @@ async def to_code(config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await sensor.register_sensor(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_CUSTOM, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_LED, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [(bool, "x")], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_DEVICE_INFORMATION, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_SLOPE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_CALIBRATION, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_T, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,74 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_DIMENSIONS,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core.entity_helpers import inherit_property_from
 | 
			
		||||
from esphome.components import lcd_base
 | 
			
		||||
from esphome.components.display_menu_base import (
 | 
			
		||||
    DISPLAY_MENU_BASE_SCHEMA,
 | 
			
		||||
    DisplayMenuComponent,
 | 
			
		||||
    display_menu_to_code,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@numo68"]
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["display_menu_base"]
 | 
			
		||||
 | 
			
		||||
lcd_menu_ns = cg.esphome_ns.namespace("lcd_menu")
 | 
			
		||||
 | 
			
		||||
CONF_DISPLAY_ID = "display_id"
 | 
			
		||||
 | 
			
		||||
CONF_MARK_SELECTED = "mark_selected"
 | 
			
		||||
CONF_MARK_EDITING = "mark_editing"
 | 
			
		||||
CONF_MARK_SUBMENU = "mark_submenu"
 | 
			
		||||
CONF_MARK_BACK = "mark_back"
 | 
			
		||||
 | 
			
		||||
MINIMUM_COLUMNS = 12
 | 
			
		||||
 | 
			
		||||
LCDCharacterMenuComponent = lcd_menu_ns.class_(
 | 
			
		||||
    "LCDCharacterMenuComponent", DisplayMenuComponent
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_lcd_dimensions(config):
 | 
			
		||||
    if config[CONF_DIMENSIONS][0] < MINIMUM_COLUMNS:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"LCD display must have at least {MINIMUM_COLUMNS} columns to be usable with the menu"
 | 
			
		||||
        )
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(LCDCharacterMenuComponent),
 | 
			
		||||
            cv.GenerateID(CONF_DISPLAY_ID): cv.use_id(lcd_base.LCDDisplay),
 | 
			
		||||
            cv.Optional(CONF_MARK_SELECTED, default=0x3E): cv.uint8_t,
 | 
			
		||||
            cv.Optional(CONF_MARK_EDITING, default=0x2A): cv.uint8_t,
 | 
			
		||||
            cv.Optional(CONF_MARK_SUBMENU, default=0x7E): cv.uint8_t,
 | 
			
		||||
            cv.Optional(CONF_MARK_BACK, default=0x5E): cv.uint8_t,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
    inherit_property_from(CONF_DIMENSIONS, CONF_DISPLAY_ID),
 | 
			
		||||
    validate_lcd_dimensions,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    disp = await cg.get_variable(config[CONF_DISPLAY_ID])
 | 
			
		||||
    cg.add(var.set_display(disp))
 | 
			
		||||
    cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1]))
 | 
			
		||||
    await display_menu_to_code(var, config)
 | 
			
		||||
    cg.add(var.set_mark_selected(config[CONF_MARK_SELECTED]))
 | 
			
		||||
    cg.add(var.set_mark_editing(config[CONF_MARK_EDITING]))
 | 
			
		||||
    cg.add(var.set_mark_submenu(config[CONF_MARK_SUBMENU]))
 | 
			
		||||
    cg.add(var.set_mark_back(config[CONF_MARK_BACK]))
 | 
			
		||||
@@ -1,74 +0,0 @@
 | 
			
		||||
#include "lcd_menu.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace lcd_menu {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "lcd_menu";
 | 
			
		||||
 | 
			
		||||
void LCDCharacterMenuComponent::setup() {
 | 
			
		||||
  if (this->display_->is_failed()) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  display_menu_base::DisplayMenuComponent::setup();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float LCDCharacterMenuComponent::get_setup_priority() const { return setup_priority::PROCESSOR - 1.0f; }
 | 
			
		||||
 | 
			
		||||
void LCDCharacterMenuComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "LCD Menu");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Columns: %u, Rows: %u", this->columns_, this->rows_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Mark characters: %02x, %02x, %02x, %02x", this->mark_selected_, this->mark_editing_,
 | 
			
		||||
                this->mark_submenu_, this->mark_back_);
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGE(TAG, "The connected display failed, the menu is disabled!");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LCDCharacterMenuComponent::draw_item(const display_menu_base::MenuItem *item, uint8_t row, bool selected) {
 | 
			
		||||
  char data[this->columns_ + 1];  // Bounded to 65 through the config
 | 
			
		||||
 | 
			
		||||
  memset(data, ' ', this->columns_);
 | 
			
		||||
 | 
			
		||||
  if (selected) {
 | 
			
		||||
    data[0] = (this->editing_ || (this->mode_ == display_menu_base::MENU_MODE_JOYSTICK && item->get_immediate_edit()))
 | 
			
		||||
                  ? this->mark_editing_
 | 
			
		||||
                  : this->mark_selected_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (item->get_type()) {
 | 
			
		||||
    case display_menu_base::MENU_ITEM_MENU:
 | 
			
		||||
      data[this->columns_ - 1] = this->mark_submenu_;
 | 
			
		||||
      break;
 | 
			
		||||
    case display_menu_base::MENU_ITEM_BACK:
 | 
			
		||||
      data[this->columns_ - 1] = this->mark_back_;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto text = item->get_text();
 | 
			
		||||
  size_t n = std::min(text.size(), (size_t) this->columns_ - 2);
 | 
			
		||||
  memcpy(data + 1, item->get_text().c_str(), n);
 | 
			
		||||
 | 
			
		||||
  if (item->has_value()) {
 | 
			
		||||
    std::string value = item->get_value_text();
 | 
			
		||||
 | 
			
		||||
    // Maximum: start mark, at least two chars of label, space, '[', value, ']',
 | 
			
		||||
    // end mark. Config guarantees columns >= 12
 | 
			
		||||
    size_t val_width = std::min((size_t) this->columns_ - 7, value.length());
 | 
			
		||||
    memcpy(data + this->columns_ - val_width - 4, " [", 2);
 | 
			
		||||
    memcpy(data + this->columns_ - val_width - 2, value.c_str(), val_width);
 | 
			
		||||
    data[this->columns_ - 2] = ']';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  data[this->columns_] = '\0';
 | 
			
		||||
 | 
			
		||||
  this->display_->print(0, row, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace lcd_menu
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/lcd_base/lcd_display.h"
 | 
			
		||||
#include "esphome/components/display_menu_base/display_menu_base.h"
 | 
			
		||||
 | 
			
		||||
#include <forward_list>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace lcd_menu {
 | 
			
		||||
 | 
			
		||||
/** Class to display a hierarchical menu.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class LCDCharacterMenuComponent : public display_menu_base::DisplayMenuComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_display(lcd_base::LCDDisplay *display) { this->display_ = display; }
 | 
			
		||||
  void set_dimensions(uint8_t columns, uint8_t rows) {
 | 
			
		||||
    this->columns_ = columns;
 | 
			
		||||
    set_rows(rows);
 | 
			
		||||
  }
 | 
			
		||||
  void set_mark_selected(uint8_t c) { this->mark_selected_ = c; }
 | 
			
		||||
  void set_mark_editing(uint8_t c) { this->mark_editing_ = c; }
 | 
			
		||||
  void set_mark_submenu(uint8_t c) { this->mark_submenu_ = c; }
 | 
			
		||||
  void set_mark_back(uint8_t c) { this->mark_back_ = c; }
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void draw_item(const display_menu_base::MenuItem *item, uint8_t row, bool selected) override;
 | 
			
		||||
  void update() override { this->display_->update(); }
 | 
			
		||||
 | 
			
		||||
  lcd_base::LCDDisplay *display_;
 | 
			
		||||
  uint8_t columns_;
 | 
			
		||||
  char mark_selected_;
 | 
			
		||||
  char mark_editing_;
 | 
			
		||||
  char mark_submenu_;
 | 
			
		||||
  char mark_back_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace lcd_menu
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -77,7 +77,7 @@ UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]
 | 
			
		||||
 | 
			
		||||
ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG]
 | 
			
		||||
 | 
			
		||||
UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1]
 | 
			
		||||
UART_SELECTION_RP2040 = [UART0, UART1]
 | 
			
		||||
 | 
			
		||||
HARDWARE_UART_TO_UART_SELECTION = {
 | 
			
		||||
    UART0: logger_ns.UART_SELECTION_UART0,
 | 
			
		||||
@@ -99,9 +99,10 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def uart_selection(value):
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        if value.upper() in ESP_IDF_UARTS and not CORE.using_esp_idf:
 | 
			
		||||
    if value.upper() in ESP_IDF_UARTS:
 | 
			
		||||
        if not CORE.using_esp_idf:
 | 
			
		||||
            raise cv.Invalid(f"Only esp-idf framework supports {value}.")
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        variant = get_esp32_variant()
 | 
			
		||||
        if variant in UART_SELECTION_ESP32:
 | 
			
		||||
            return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
 | 
			
		||||
@@ -136,12 +137,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
 | 
			
		||||
            cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
 | 
			
		||||
            cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
 | 
			
		||||
            cv.SplitDefault(
 | 
			
		||||
                CONF_HARDWARE_UART,
 | 
			
		||||
                esp8266=UART0,
 | 
			
		||||
                esp32=UART0,
 | 
			
		||||
                rp2040=USB_CDC,
 | 
			
		||||
            ): uart_selection,
 | 
			
		||||
            cv.Optional(CONF_HARDWARE_UART, default=UART0): uart_selection,
 | 
			
		||||
            cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level,
 | 
			
		||||
            cv.Optional(CONF_LOGS, default={}): cv.Schema(
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,15 @@
 | 
			
		||||
#include "logger.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#include <driver/uart.h>
 | 
			
		||||
#include "freertos/FreeRTOS.h"
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
#include <driver/uart.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF)
 | 
			
		||||
#include <esp_log.h>
 | 
			
		||||
#endif  // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#endif
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace logger {
 | 
			
		||||
@@ -161,13 +161,8 @@ void Logger::pre_setup() {
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
      case UART_SELECTION_UART0_SWAP:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
        this->hw_serial_ = &Serial1;
 | 
			
		||||
        Serial1.begin(this->baud_rate_);
 | 
			
		||||
#else
 | 
			
		||||
        this->hw_serial_ = &Serial;
 | 
			
		||||
        Serial.begin(this->baud_rate_);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
        if (this->uart_ == UART_SELECTION_UART0_SWAP) {
 | 
			
		||||
          Serial.swap();
 | 
			
		||||
@@ -176,13 +171,8 @@ void Logger::pre_setup() {
 | 
			
		||||
#endif
 | 
			
		||||
        break;
 | 
			
		||||
      case UART_SELECTION_UART1:
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
        this->hw_serial_ = &Serial2;
 | 
			
		||||
        Serial2.begin(this->baud_rate_);
 | 
			
		||||
#else
 | 
			
		||||
        this->hw_serial_ = &Serial1;
 | 
			
		||||
        Serial1.begin(this->baud_rate_);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
        Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
 | 
			
		||||
#endif
 | 
			
		||||
@@ -193,12 +183,6 @@ void Logger::pre_setup() {
 | 
			
		||||
        this->hw_serial_ = &Serial2;
 | 
			
		||||
        Serial2.begin(this->baud_rate_);
 | 
			
		||||
        break;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
      case UART_SELECTION_USB_CDC:
 | 
			
		||||
        this->hw_serial_ = &Serial;
 | 
			
		||||
        Serial.begin(this->baud_rate_);
 | 
			
		||||
        break;
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
@@ -287,7 +271,7 @@ const char *const UART_SELECTIONS[] = {
 | 
			
		||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"};
 | 
			
		||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1"};
 | 
			
		||||
#endif  // USE_ESP8266
 | 
			
		||||
void Logger::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Logger:");
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#include <driver/uart.h>
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 | 
			
		||||
@@ -34,22 +34,19 @@ enum UARTSelection {
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
  UART_SELECTION_UART2,
 | 
			
		||||
#endif  // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
  UART_SELECTION_USB_CDC,
 | 
			
		||||
#endif  // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
  UART_SELECTION_USB_SERIAL_JTAG,
 | 
			
		||||
#endif  // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
  UART_SELECTION_UART0_SWAP,
 | 
			
		||||
#endif  // USE_ESP8266
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  UART_SELECTION_USB_CDC,
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Logger : public Component {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,7 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
from esphome.const import CONF_ID, CONF_MODE, CONF_PARAMETERS
 | 
			
		||||
from esphome.core import CORE, EsphomeError
 | 
			
		||||
from esphome.const import CONF_ID, CONF_MODE
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
script_ns = cg.esphome_ns.namespace("script")
 | 
			
		||||
@@ -17,7 +16,6 @@ RestartScript = script_ns.class_("RestartScript", Script)
 | 
			
		||||
QueueingScript = script_ns.class_("QueueingScript", Script, cg.Component)
 | 
			
		||||
ParallelScript = script_ns.class_("ParallelScript", Script)
 | 
			
		||||
 | 
			
		||||
CONF_SCRIPT = "script"
 | 
			
		||||
CONF_SINGLE = "single"
 | 
			
		||||
CONF_RESTART = "restart"
 | 
			
		||||
CONF_QUEUED = "queued"
 | 
			
		||||
@@ -31,18 +29,6 @@ SCRIPT_MODES = {
 | 
			
		||||
    CONF_PARALLEL: ParallelScript,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PARAMETER_TYPE_TRANSLATIONS = {
 | 
			
		||||
    "string": "std::string",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_script(script_id):
 | 
			
		||||
    scripts = CORE.config.get(CONF_SCRIPT, {})
 | 
			
		||||
    for script in scripts:
 | 
			
		||||
        if script.get(CONF_ID, None) == script_id:
 | 
			
		||||
            return script
 | 
			
		||||
    raise cv.Invalid(f"Script id '{script_id}' not found")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_max_runs(value):
 | 
			
		||||
    if CONF_MAX_RUNS not in value:
 | 
			
		||||
@@ -61,44 +47,6 @@ def assign_declare_id(value):
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parameters_to_template(args):
 | 
			
		||||
 | 
			
		||||
    template_args = []
 | 
			
		||||
    func_args = []
 | 
			
		||||
    script_arg_names = []
 | 
			
		||||
    for name, type_ in args.items():
 | 
			
		||||
        array = False
 | 
			
		||||
        if type_.endswith("[]"):
 | 
			
		||||
            array = True
 | 
			
		||||
            type_ = type_[:-2]
 | 
			
		||||
        type_ = PARAMETER_TYPE_TRANSLATIONS.get(type_, type_)
 | 
			
		||||
        if array:
 | 
			
		||||
            type_ = f"std::vector<{type_}>"
 | 
			
		||||
        type_ = cg.esphome_ns.namespace(type_)
 | 
			
		||||
        template_args.append(type_)
 | 
			
		||||
        func_args.append((type_, name))
 | 
			
		||||
        script_arg_names.append(name)
 | 
			
		||||
    template = cg.TemplateArguments(*template_args)
 | 
			
		||||
    return template, func_args
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_parameter_name(value):
 | 
			
		||||
    value = cv.string(value)
 | 
			
		||||
    if value != CONF_ID:
 | 
			
		||||
        return value
 | 
			
		||||
    raise cv.Invalid(f"Script's parameter name cannot be {CONF_ID}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ALLOWED_PARAM_TYPE_CHARSET = set("abcdefghijklmnopqrstuvwxyz0123456789_:*&[]")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_parameter_type(value):
 | 
			
		||||
    value = cv.string_strict(value)
 | 
			
		||||
    if set(value.lower()) <= ALLOWED_PARAM_TYPE_CHARSET:
 | 
			
		||||
        return value
 | 
			
		||||
    raise cv.Invalid("Parameter type contains invalid characters")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = automation.validate_automation(
 | 
			
		||||
    {
 | 
			
		||||
        # Don't declare id as cv.declare_id yet, because the ID type
 | 
			
		||||
@@ -108,11 +56,6 @@ CONFIG_SCHEMA = automation.validate_automation(
 | 
			
		||||
            *SCRIPT_MODES, lower=True
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_MAX_RUNS): cv.positive_int,
 | 
			
		||||
        cv.Optional(CONF_PARAMETERS, default={}): cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
                validate_parameter_name: validate_parameter_type,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    extra_validators=cv.All(check_max_runs, assign_declare_id),
 | 
			
		||||
)
 | 
			
		||||
@@ -122,8 +65,7 @@ async def to_code(config):
 | 
			
		||||
    # Register all variables first, so that scripts can use other scripts
 | 
			
		||||
    triggers = []
 | 
			
		||||
    for conf in config:
 | 
			
		||||
        template, func_args = parameters_to_template(conf[CONF_PARAMETERS])
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_ID], template)
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_ID])
 | 
			
		||||
        # Add a human-readable name to the script
 | 
			
		||||
        cg.add(trigger.set_name(conf[CONF_ID].id))
 | 
			
		||||
 | 
			
		||||
@@ -133,10 +75,10 @@ async def to_code(config):
 | 
			
		||||
        if conf[CONF_MODE] == CONF_QUEUED:
 | 
			
		||||
            await cg.register_component(trigger, conf)
 | 
			
		||||
 | 
			
		||||
        triggers.append((trigger, func_args, conf))
 | 
			
		||||
        triggers.append((trigger, conf))
 | 
			
		||||
 | 
			
		||||
    for trigger, func_args, conf in triggers:
 | 
			
		||||
        await automation.build_automation(trigger, func_args, conf)
 | 
			
		||||
    for trigger, conf in triggers:
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
@@ -145,39 +87,12 @@ async def to_code(config):
 | 
			
		||||
    maybe_simple_id(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_ID): cv.use_id(Script),
 | 
			
		||||
            cv.Optional(validate_parameter_name): cv.templatable(cv.valid),
 | 
			
		||||
        },
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
async def script_execute_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    async def get_ordered_args(config, script_params):
 | 
			
		||||
        config_args = config.copy()
 | 
			
		||||
        config_args.pop(CONF_ID)
 | 
			
		||||
 | 
			
		||||
        # match script_args to the formal parameter order
 | 
			
		||||
        script_args = []
 | 
			
		||||
        for type, name in script_params:
 | 
			
		||||
            if name not in config_args:
 | 
			
		||||
                raise EsphomeError(
 | 
			
		||||
                    f"Missing parameter: '{name}' in script.execute {config[CONF_ID]}"
 | 
			
		||||
                )
 | 
			
		||||
            arg = await cg.templatable(config_args[name], args, type)
 | 
			
		||||
            script_args.append(arg)
 | 
			
		||||
        return script_args
 | 
			
		||||
 | 
			
		||||
    script = get_script(config[CONF_ID])
 | 
			
		||||
    params = script.get(CONF_PARAMETERS, [])
 | 
			
		||||
    template, script_params = parameters_to_template(params)
 | 
			
		||||
    script_args = await get_ordered_args(config, script_params)
 | 
			
		||||
 | 
			
		||||
    # We need to use the parent class 'Script' as the template argument
 | 
			
		||||
    # to match the partial specialization of the ScriptExecuteAction template
 | 
			
		||||
    template_arg = cg.TemplateArguments(Script.template(template), *template_arg)
 | 
			
		||||
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    cg.add(var.set_args(*script_args))
 | 
			
		||||
    return var
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
@@ -186,8 +101,7 @@ async def script_execute_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
 | 
			
		||||
)
 | 
			
		||||
async def script_stop_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
 | 
			
		||||
    template_arg = cg.TemplateArguments(full_id.type, *template_arg)
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -197,8 +111,7 @@ async def script_stop_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
 | 
			
		||||
)
 | 
			
		||||
async def script_wait_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
 | 
			
		||||
    template_arg = cg.TemplateArguments(full_id.type, *template_arg)
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    await cg.register_component(var, {})
 | 
			
		||||
    return var
 | 
			
		||||
@@ -210,6 +123,5 @@ async def script_wait_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    automation.maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
 | 
			
		||||
)
 | 
			
		||||
async def script_is_running_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
 | 
			
		||||
    template_arg = cg.TemplateArguments(full_id.type, *template_arg)
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(condition_id, template_arg, paren)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,61 @@ namespace script {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "script";
 | 
			
		||||
 | 
			
		||||
void ScriptLogger::esp_log_(int level, int line, const char *format, const char *param) {
 | 
			
		||||
  esp_log_printf_(level, TAG, line, format, param);
 | 
			
		||||
void SingleScript::execute() {
 | 
			
		||||
  if (this->is_action_running()) {
 | 
			
		||||
    ESP_LOGW(TAG, "Script '%s' is already running! (mode: single)", this->name_.c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->trigger();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RestartScript::execute() {
 | 
			
		||||
  if (this->is_action_running()) {
 | 
			
		||||
    ESP_LOGD(TAG, "Script '%s' restarting (mode: restart)", this->name_.c_str());
 | 
			
		||||
    this->stop_action();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->trigger();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QueueingScript::execute() {
 | 
			
		||||
  if (this->is_action_running()) {
 | 
			
		||||
    // num_runs_ is the number of *queued* instances, so total number of instances is
 | 
			
		||||
    // num_runs_ + 1
 | 
			
		||||
    if (this->max_runs_ != 0 && this->num_runs_ + 1 >= this->max_runs_) {
 | 
			
		||||
      ESP_LOGW(TAG, "Script '%s' maximum number of queued runs exceeded!", this->name_.c_str());
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ESP_LOGD(TAG, "Script '%s' queueing new instance (mode: queued)", this->name_.c_str());
 | 
			
		||||
    this->num_runs_++;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->trigger();
 | 
			
		||||
  // Check if the trigger was immediate and we can continue right away.
 | 
			
		||||
  this->loop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QueueingScript::stop() {
 | 
			
		||||
  this->num_runs_ = 0;
 | 
			
		||||
  Script::stop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QueueingScript::loop() {
 | 
			
		||||
  if (this->num_runs_ != 0 && !this->is_action_running()) {
 | 
			
		||||
    this->num_runs_--;
 | 
			
		||||
    this->trigger();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ParallelScript::execute() {
 | 
			
		||||
  if (this->max_runs_ != 0 && this->automation_parent_->num_running() >= this->max_runs_) {
 | 
			
		||||
    ESP_LOGW(TAG, "Script '%s' maximum number of parallel runs exceeded!", this->name_.c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->trigger();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace script
 | 
			
		||||
 
 | 
			
		||||
@@ -2,48 +2,27 @@
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace script {
 | 
			
		||||
 | 
			
		||||
class ScriptLogger {
 | 
			
		||||
 protected:
 | 
			
		||||
  void esp_logw_(int line, const char *format, const char *param) {
 | 
			
		||||
    esp_log_(ESPHOME_LOG_LEVEL_WARN, line, format, param);
 | 
			
		||||
  }
 | 
			
		||||
  void esp_logd_(int line, const char *format, const char *param) {
 | 
			
		||||
    esp_log_(ESPHOME_LOG_LEVEL_DEBUG, line, format, param);
 | 
			
		||||
  }
 | 
			
		||||
  void esp_log_(int level, int line, const char *format, const char *param);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// The abstract base class for all script types.
 | 
			
		||||
template<typename... Ts> class Script : public ScriptLogger, public Trigger<Ts...> {
 | 
			
		||||
class Script : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  /** Execute a new instance of this script.
 | 
			
		||||
   *
 | 
			
		||||
   * The behavior of this function when a script is already running is defined by the subtypes
 | 
			
		||||
   */
 | 
			
		||||
  virtual void execute(Ts...) = 0;
 | 
			
		||||
  virtual void execute() = 0;
 | 
			
		||||
  /// Check if any instance of this script is currently running.
 | 
			
		||||
  virtual bool is_running() { return this->is_action_running(); }
 | 
			
		||||
  /// Stop all instances of this script.
 | 
			
		||||
  virtual void stop() { this->stop_action(); }
 | 
			
		||||
 | 
			
		||||
  // execute this script using a tuple that contains the arguments
 | 
			
		||||
  void execute_tuple(const std::tuple<Ts...> &tuple) {
 | 
			
		||||
    this->execute_tuple_(tuple, typename gens<sizeof...(Ts)>::type());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Internal function to give scripts readable names.
 | 
			
		||||
  void set_name(const std::string &name) { name_ = name; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  template<int... S> void execute_tuple_(const std::tuple<Ts...> &tuple, seq<S...> /*unused*/) {
 | 
			
		||||
    this->execute(std::get<S>(tuple)...);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::string name_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -52,16 +31,9 @@ template<typename... Ts> class Script : public ScriptLogger, public Trigger<Ts..
 | 
			
		||||
 * If a new instance is executed while the previous one hasn't finished yet,
 | 
			
		||||
 * a warning is printed and the new instance is discarded.
 | 
			
		||||
 */
 | 
			
		||||
template<typename... Ts> class SingleScript : public Script<Ts...> {
 | 
			
		||||
class SingleScript : public Script {
 | 
			
		||||
 public:
 | 
			
		||||
  void execute(Ts... x) override {
 | 
			
		||||
    if (this->is_action_running()) {
 | 
			
		||||
      this->esp_logw_(__LINE__, "Script '%s' is already running! (mode: single)", this->name_.c_str());
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->trigger(x...);
 | 
			
		||||
  }
 | 
			
		||||
  void execute() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** A script type that restarts scripts from the beginning when a new instance is started.
 | 
			
		||||
@@ -69,55 +41,20 @@ template<typename... Ts> class SingleScript : public Script<Ts...> {
 | 
			
		||||
 * If a new instance is started but another one is already running, the existing
 | 
			
		||||
 * script is stopped and the new instance starts from the beginning.
 | 
			
		||||
 */
 | 
			
		||||
template<typename... Ts> class RestartScript : public Script<Ts...> {
 | 
			
		||||
class RestartScript : public Script {
 | 
			
		||||
 public:
 | 
			
		||||
  void execute(Ts... x) override {
 | 
			
		||||
    if (this->is_action_running()) {
 | 
			
		||||
      this->esp_logd_(__LINE__, "Script '%s' restarting (mode: restart)", this->name_.c_str());
 | 
			
		||||
      this->stop_action();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->trigger(x...);
 | 
			
		||||
  }
 | 
			
		||||
  void execute() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** A script type that queues new instances that are created.
 | 
			
		||||
 *
 | 
			
		||||
 * Only one instance of the script can be active at a time.
 | 
			
		||||
 */
 | 
			
		||||
template<typename... Ts> class QueueingScript : public Script<Ts...>, public Component {
 | 
			
		||||
class QueueingScript : public Script, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void execute(Ts... x) override {
 | 
			
		||||
    if (this->is_action_running()) {
 | 
			
		||||
      // num_runs_ is the number of *queued* instances, so total number of instances is
 | 
			
		||||
      // num_runs_ + 1
 | 
			
		||||
      if (this->max_runs_ != 0 && this->num_runs_ + 1 >= this->max_runs_) {
 | 
			
		||||
        this->esp_logw_(__LINE__, "Script '%s' maximum number of queued runs exceeded!", this->name_.c_str());
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this->esp_logd_(__LINE__, "Script '%s' queueing new instance (mode: queued)", this->name_.c_str());
 | 
			
		||||
      this->num_runs_++;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->trigger(x...);
 | 
			
		||||
    // Check if the trigger was immediate and we can continue right away.
 | 
			
		||||
    this->loop();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void stop() override {
 | 
			
		||||
    this->num_runs_ = 0;
 | 
			
		||||
    Script<Ts...>::stop();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void loop() override {
 | 
			
		||||
    if (this->num_runs_ != 0 && !this->is_action_running()) {
 | 
			
		||||
      this->num_runs_--;
 | 
			
		||||
      this->trigger();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void execute() override;
 | 
			
		||||
  void stop() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void set_max_runs(int max_runs) { max_runs_ = max_runs; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
@@ -130,84 +67,48 @@ template<typename... Ts> class QueueingScript : public Script<Ts...>, public Com
 | 
			
		||||
 * If a new instance is started while previous ones haven't finished yet,
 | 
			
		||||
 * the new one is executed in parallel to the other instances.
 | 
			
		||||
 */
 | 
			
		||||
template<typename... Ts> class ParallelScript : public Script<Ts...> {
 | 
			
		||||
class ParallelScript : public Script {
 | 
			
		||||
 public:
 | 
			
		||||
  void execute(Ts... x) override {
 | 
			
		||||
    if (this->max_runs_ != 0 && this->automation_parent_->num_running() >= this->max_runs_) {
 | 
			
		||||
      this->esp_logw_(__LINE__, "Script '%s' maximum number of parallel runs exceeded!", this->name_.c_str());
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this->trigger(x...);
 | 
			
		||||
  }
 | 
			
		||||
  void execute() override;
 | 
			
		||||
  void set_max_runs(int max_runs) { max_runs_ = max_runs; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  int max_runs_ = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<class S, typename... Ts> class ScriptExecuteAction;
 | 
			
		||||
 | 
			
		||||
template<class... As, typename... Ts> class ScriptExecuteAction<Script<As...>, Ts...> : public Action<Ts...> {
 | 
			
		||||
template<typename... Ts> class ScriptExecuteAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  ScriptExecuteAction(Script<As...> *script) : script_(script) {}
 | 
			
		||||
  ScriptExecuteAction(Script *script) : script_(script) {}
 | 
			
		||||
 | 
			
		||||
  using Args = std::tuple<TemplatableValue<As, Ts...>...>;
 | 
			
		||||
 | 
			
		||||
  template<typename... F> void set_args(F... x) { args_ = Args{x...}; }
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->script_->execute_tuple(this->eval_args_(x...)); }
 | 
			
		||||
  void play(Ts... x) override { this->script_->execute(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  // NOTE:
 | 
			
		||||
  //  `eval_args_impl` functions evaluates `I`th the functions in `args` member.
 | 
			
		||||
  //  and then recursively calls `eval_args_impl` for the `I+1`th arg.
 | 
			
		||||
  //  if `I` = `N` all args have been stored, and nothing is done.
 | 
			
		||||
 | 
			
		||||
  template<std::size_t N>
 | 
			
		||||
  void eval_args_impl_(std::tuple<As...> & /*unused*/, std::integral_constant<std::size_t, N> /*unused*/,
 | 
			
		||||
                       std::integral_constant<std::size_t, N> /*unused*/, Ts... /*unused*/) {}
 | 
			
		||||
 | 
			
		||||
  template<std::size_t I, std::size_t N>
 | 
			
		||||
  void eval_args_impl_(std::tuple<As...> &evaled_args, std::integral_constant<std::size_t, I> /*unused*/,
 | 
			
		||||
                       std::integral_constant<std::size_t, N> n, Ts... x) {
 | 
			
		||||
    std::get<I>(evaled_args) = std::get<I>(args_).value(x...);  // NOTE: evaluate `i`th arg, and store in tuple.
 | 
			
		||||
    eval_args_impl_(evaled_args, std::integral_constant<std::size_t, I + 1>{}, n,
 | 
			
		||||
                    x...);  // NOTE: recurse to next index.
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::tuple<As...> eval_args_(Ts... x) {
 | 
			
		||||
    std::tuple<As...> evaled_args;
 | 
			
		||||
    eval_args_impl_(evaled_args, std::integral_constant<std::size_t, 0>{}, std::tuple_size<Args>{}, x...);
 | 
			
		||||
    return evaled_args;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Script<As...> *script_;
 | 
			
		||||
  Args args_;
 | 
			
		||||
  Script *script_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<class C, typename... Ts> class ScriptStopAction : public Action<Ts...> {
 | 
			
		||||
template<typename... Ts> class ScriptStopAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  ScriptStopAction(C *script) : script_(script) {}
 | 
			
		||||
  ScriptStopAction(Script *script) : script_(script) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->script_->stop(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  C *script_;
 | 
			
		||||
  Script *script_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<class C, typename... Ts> class IsRunningCondition : public Condition<Ts...> {
 | 
			
		||||
template<typename... Ts> class IsRunningCondition : public Condition<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit IsRunningCondition(C *parent) : parent_(parent) {}
 | 
			
		||||
  explicit IsRunningCondition(Script *parent) : parent_(parent) {}
 | 
			
		||||
 | 
			
		||||
  bool check(Ts... x) override { return this->parent_->is_running(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  C *parent_;
 | 
			
		||||
  Script *parent_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>, public Component {
 | 
			
		||||
template<typename... Ts> class ScriptWaitAction : public Action<Ts...>, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  ScriptWaitAction(C *script) : script_(script) {}
 | 
			
		||||
  ScriptWaitAction(Script *script) : script_(script) {}
 | 
			
		||||
 | 
			
		||||
  void play_complex(Ts... x) override {
 | 
			
		||||
    this->num_running_++;
 | 
			
		||||
@@ -236,7 +137,7 @@ template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>,
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  C *script_;
 | 
			
		||||
  Script *script_;
 | 
			
		||||
  std::tuple<Ts...> var_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,6 @@ ESP32ArduinoUARTComponent = uart_ns.class_(
 | 
			
		||||
ESP8266UartComponent = uart_ns.class_(
 | 
			
		||||
    "ESP8266UartComponent", UARTComponent, cg.Component
 | 
			
		||||
)
 | 
			
		||||
RP2040UartComponent = uart_ns.class_("RP2040UartComponent", UARTComponent, cg.Component)
 | 
			
		||||
 | 
			
		||||
UARTDevice = uart_ns.class_("UARTDevice")
 | 
			
		||||
UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
 | 
			
		||||
@@ -90,8 +89,6 @@ def _uart_declare_type(value):
 | 
			
		||||
            return cv.declare_id(ESP32ArduinoUARTComponent)(value)
 | 
			
		||||
        if CORE.using_esp_idf:
 | 
			
		||||
            return cv.declare_id(IDFUARTComponent)(value)
 | 
			
		||||
    if CORE.is_rp2040:
 | 
			
		||||
        return cv.declare_id(RP2040UartComponent)(value)
 | 
			
		||||
    raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,6 @@ void ESP32ArduinoUARTComponent::setup() {
 | 
			
		||||
    this->hw_serial_ = &Serial;
 | 
			
		||||
  } else {
 | 
			
		||||
    static uint8_t next_uart_num = 1;
 | 
			
		||||
    this->number_ = next_uart_num;
 | 
			
		||||
    this->hw_serial_ = new HardwareSerial(next_uart_num++);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  }
 | 
			
		||||
  int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
 | 
			
		||||
@@ -105,7 +104,7 @@ void ESP32ArduinoUARTComponent::setup() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP32ArduinoUARTComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "UART Bus %d:", this->number_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "UART Bus:");
 | 
			
		||||
  LOG_PIN("  TX Pin: ", tx_pin_);
 | 
			
		||||
  LOG_PIN("  RX Pin: ", rx_pin_);
 | 
			
		||||
  if (this->rx_pin_ != nullptr) {
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,6 @@ class ESP32ArduinoUARTComponent : public UARTComponent, public Component {
 | 
			
		||||
  void check_logger_conflict() override;
 | 
			
		||||
 | 
			
		||||
  HardwareSerial *hw_serial_{nullptr};
 | 
			
		||||
  uint8_t number_{0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace uart
 | 
			
		||||
 
 | 
			
		||||
@@ -1,184 +0,0 @@
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include "uart_component_rp2040.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <hardware/uart.h>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_LOGGER
 | 
			
		||||
#include "esphome/components/logger/logger.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace uart {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "uart.arduino_rp2040";
 | 
			
		||||
 | 
			
		||||
uint16_t RP2040UartComponent::get_config() {
 | 
			
		||||
  uint16_t config = 0;
 | 
			
		||||
 | 
			
		||||
  if (this->parity_ == UART_CONFIG_PARITY_NONE) {
 | 
			
		||||
    config |= UART_PARITY_NONE;
 | 
			
		||||
  } else if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
 | 
			
		||||
    config |= UART_PARITY_EVEN;
 | 
			
		||||
  } else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
 | 
			
		||||
    config |= UART_PARITY_ODD;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (this->data_bits_) {
 | 
			
		||||
    case 5:
 | 
			
		||||
      config |= SERIAL_DATA_5;
 | 
			
		||||
      break;
 | 
			
		||||
    case 6:
 | 
			
		||||
      config |= SERIAL_DATA_6;
 | 
			
		||||
      break;
 | 
			
		||||
    case 7:
 | 
			
		||||
      config |= SERIAL_DATA_7;
 | 
			
		||||
      break;
 | 
			
		||||
    case 8:
 | 
			
		||||
      config |= SERIAL_DATA_8;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->stop_bits_ == 1) {
 | 
			
		||||
    config |= SERIAL_STOP_BIT_1;
 | 
			
		||||
  } else {
 | 
			
		||||
    config |= SERIAL_STOP_BIT_2;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return config;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RP2040UartComponent::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up UART bus...");
 | 
			
		||||
 | 
			
		||||
  uint16_t config = get_config();
 | 
			
		||||
 | 
			
		||||
  constexpr uint32_t valid_tx_uart_0 = __bitset({0, 12, 16, 28});
 | 
			
		||||
  constexpr uint32_t valid_tx_uart_1 = __bitset({4, 8, 20, 24});
 | 
			
		||||
 | 
			
		||||
  constexpr uint32_t valid_rx_uart_0 = __bitset({1, 13, 17, 29});
 | 
			
		||||
  constexpr uint32_t valid_rx_uart_1 = __bitset({5, 9, 21, 25});
 | 
			
		||||
 | 
			
		||||
  int8_t tx_hw = -1;
 | 
			
		||||
  int8_t rx_hw = -1;
 | 
			
		||||
 | 
			
		||||
  if (this->tx_pin_ != nullptr) {
 | 
			
		||||
    if (this->tx_pin_->is_inverted()) {
 | 
			
		||||
      ESP_LOGD(TAG, "An inverted TX pin %u can only be used with SerialPIO", this->tx_pin_->get_pin());
 | 
			
		||||
    } else {
 | 
			
		||||
      if (((1 << this->tx_pin_->get_pin()) & valid_tx_uart_0) != 0) {
 | 
			
		||||
        tx_hw = 0;
 | 
			
		||||
      } else if (((1 << this->tx_pin_->get_pin()) & valid_tx_uart_1) != 0) {
 | 
			
		||||
        tx_hw = 1;
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGD(TAG, "TX pin %u can only be used with SerialPIO", this->tx_pin_->get_pin());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->rx_pin_ != nullptr) {
 | 
			
		||||
    if (this->rx_pin_->is_inverted()) {
 | 
			
		||||
      ESP_LOGD(TAG, "An inverted RX pin %u can only be used with SerialPIO", this->rx_pin_->get_pin());
 | 
			
		||||
    } else {
 | 
			
		||||
      if (((1 << this->rx_pin_->get_pin()) & valid_rx_uart_0) != 0) {
 | 
			
		||||
        rx_hw = 0;
 | 
			
		||||
      } else if (((1 << this->rx_pin_->get_pin()) & valid_rx_uart_1) != 0) {
 | 
			
		||||
        rx_hw = 1;
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGD(TAG, "RX pin %u can only be used with SerialPIO", this->rx_pin_->get_pin());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_LOGGER
 | 
			
		||||
  if (tx_hw == rx_hw && logger::global_logger->get_uart() == tx_hw) {
 | 
			
		||||
    ESP_LOGD(TAG, "Using SerialPIO as UART%d is taken by the logger", tx_hw);
 | 
			
		||||
    tx_hw = -1;
 | 
			
		||||
    rx_hw = -1;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (tx_hw == -1 || rx_hw == -1 || tx_hw != rx_hw) {
 | 
			
		||||
    ESP_LOGV(TAG, "Using SerialPIO");
 | 
			
		||||
    pin_size_t tx = this->tx_pin_ == nullptr ? SerialPIO::NOPIN : this->tx_pin_->get_pin();
 | 
			
		||||
    pin_size_t rx = this->rx_pin_ == nullptr ? SerialPIO::NOPIN : this->rx_pin_->get_pin();
 | 
			
		||||
    auto *serial = new SerialPIO(tx, rx, this->rx_buffer_size_);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
    serial->begin(this->baud_rate_, config);
 | 
			
		||||
    if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted())
 | 
			
		||||
      gpio_set_outover(tx, GPIO_OVERRIDE_INVERT);
 | 
			
		||||
    if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted())
 | 
			
		||||
      gpio_set_inover(rx, GPIO_OVERRIDE_INVERT);
 | 
			
		||||
    this->serial_ = serial;
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGV(TAG, "Using Hardware Serial");
 | 
			
		||||
    SerialUART *serial;
 | 
			
		||||
    if (tx_hw == 0) {
 | 
			
		||||
      serial = &Serial1;
 | 
			
		||||
    } else {
 | 
			
		||||
      serial = &Serial2;
 | 
			
		||||
    }
 | 
			
		||||
    serial->setTX(this->tx_pin_->get_pin());
 | 
			
		||||
    serial->setRX(this->rx_pin_->get_pin());
 | 
			
		||||
    serial->setFIFOSize(this->rx_buffer_size_);
 | 
			
		||||
    serial->begin(this->baud_rate_, config);
 | 
			
		||||
    this->serial_ = serial;
 | 
			
		||||
    this->hw_serial_ = true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RP2040UartComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "UART Bus:");
 | 
			
		||||
  LOG_PIN("  TX Pin: ", tx_pin_);
 | 
			
		||||
  LOG_PIN("  RX Pin: ", rx_pin_);
 | 
			
		||||
  if (this->rx_pin_ != nullptr) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  RX Buffer Size: %u", this->rx_buffer_size_);
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Baud Rate: %u baud", this->baud_rate_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Data Bits: %u", this->data_bits_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_)));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Stop bits: %u", this->stop_bits_);
 | 
			
		||||
  if (this->hw_serial_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Using hardware serial");
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Using SerialPIO");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RP2040UartComponent::write_array(const uint8_t *data, size_t len) {
 | 
			
		||||
  this->serial_->write(data, len);
 | 
			
		||||
#ifdef USE_UART_DEBUGGER
 | 
			
		||||
  for (size_t i = 0; i < len; i++) {
 | 
			
		||||
    this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
bool RP2040UartComponent::peek_byte(uint8_t *data) {
 | 
			
		||||
  if (!this->check_read_timeout_())
 | 
			
		||||
    return false;
 | 
			
		||||
  *data = this->serial_->peek();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
bool RP2040UartComponent::read_array(uint8_t *data, size_t len) {
 | 
			
		||||
  if (!this->check_read_timeout_(len))
 | 
			
		||||
    return false;
 | 
			
		||||
  this->serial_->readBytes(data, len);
 | 
			
		||||
#ifdef USE_UART_DEBUGGER
 | 
			
		||||
  for (size_t i = 0; i < len; i++) {
 | 
			
		||||
    this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
int RP2040UartComponent::available() { return this->serial_->available(); }
 | 
			
		||||
void RP2040UartComponent::flush() {
 | 
			
		||||
  ESP_LOGVV(TAG, "    Flushing...");
 | 
			
		||||
  this->serial_->flush();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace uart
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
@@ -1,43 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
 | 
			
		||||
#include <SerialPIO.h>
 | 
			
		||||
#include <SerialUART.h>
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "uart_component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace uart {
 | 
			
		||||
 | 
			
		||||
class RP2040UartComponent : public UARTComponent, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::BUS; }
 | 
			
		||||
 | 
			
		||||
  void write_array(const uint8_t *data, size_t len) override;
 | 
			
		||||
 | 
			
		||||
  bool peek_byte(uint8_t *data) override;
 | 
			
		||||
  bool read_array(uint8_t *data, size_t len) override;
 | 
			
		||||
 | 
			
		||||
  int available() override;
 | 
			
		||||
  void flush() override;
 | 
			
		||||
 | 
			
		||||
  uint16_t get_config();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void check_logger_conflict() override {}
 | 
			
		||||
  bool hw_serial_{false};
 | 
			
		||||
 | 
			
		||||
  HardwareSerial *serial_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace uart
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
"""Constants used by esphome."""
 | 
			
		||||
 | 
			
		||||
__version__ = "2022.11.0b1"
 | 
			
		||||
__version__ = "2022.11.0-dev"
 | 
			
		||||
 | 
			
		||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
 | 
			
		||||
 | 
			
		||||
@@ -491,7 +491,6 @@ CONF_PACKAGES = "packages"
 | 
			
		||||
CONF_PAGE_ID = "page_id"
 | 
			
		||||
CONF_PAGES = "pages"
 | 
			
		||||
CONF_PANASONIC = "panasonic"
 | 
			
		||||
CONF_PARAMETERS = "parameters"
 | 
			
		||||
CONF_PASSWORD = "password"
 | 
			
		||||
CONF_PATH = "path"
 | 
			
		||||
CONF_PAYLOAD = "payload"
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,12 @@ tornado==6.2
 | 
			
		||||
tzlocal==4.2    # from time
 | 
			
		||||
tzdata>=2021.1  # from time
 | 
			
		||||
pyserial==3.5
 | 
			
		||||
platformio==6.1.5  # When updating platformio, also update Dockerfile
 | 
			
		||||
platformio==6.1.4  # When updating platformio, also update Dockerfile
 | 
			
		||||
esptool==3.3.1
 | 
			
		||||
click==8.1.3
 | 
			
		||||
esphome-dashboard==20221109.0
 | 
			
		||||
aioesphomeapi==11.4.3
 | 
			
		||||
zeroconf==0.39.4
 | 
			
		||||
esphome-dashboard==20221020.0
 | 
			
		||||
aioesphomeapi==11.4.2
 | 
			
		||||
zeroconf==0.39.1
 | 
			
		||||
 | 
			
		||||
# esp-idf requires this, but doesn't bundle it by default
 | 
			
		||||
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
pylint==2.15.5
 | 
			
		||||
flake8==5.0.4  # also change in .pre-commit-config.yaml when updating
 | 
			
		||||
black==22.10.0  # also change in .pre-commit-config.yaml when updating
 | 
			
		||||
flake8==5.0.4
 | 
			
		||||
black==22.8.0  # also change in .pre-commit-config.yaml when updating
 | 
			
		||||
pyupgrade==3.2.0  # also change in .pre-commit-config.yaml when updating
 | 
			
		||||
pre-commit
 | 
			
		||||
 | 
			
		||||
@@ -8,6 +8,6 @@ pre-commit
 | 
			
		||||
pytest==7.2.0
 | 
			
		||||
pytest-cov==4.0.0
 | 
			
		||||
pytest-mock==3.10.0
 | 
			
		||||
pytest-asyncio==0.20.1
 | 
			
		||||
pytest-asyncio==0.19.0
 | 
			
		||||
asyncmock==0.4.2
 | 
			
		||||
hypothesis==5.49.0
 | 
			
		||||
 
 | 
			
		||||
@@ -871,10 +871,8 @@ sensor:
 | 
			
		||||
          value: !lambda "return -1;"
 | 
			
		||||
    on_clockwise:
 | 
			
		||||
      - logger.log: Clockwise
 | 
			
		||||
      - display_menu.down:
 | 
			
		||||
    on_anticlockwise:
 | 
			
		||||
      - logger.log: Anticlockwise
 | 
			
		||||
      - display_menu.up:
 | 
			
		||||
  - platform: pulse_width
 | 
			
		||||
    name: Pulse Width
 | 
			
		||||
    pin: GPIO12
 | 
			
		||||
@@ -1291,16 +1289,6 @@ binary_sensor:
 | 
			
		||||
    pin: GPIO27
 | 
			
		||||
    threshold: 1000
 | 
			
		||||
    id: btn_left
 | 
			
		||||
    on_press:
 | 
			
		||||
      - if:
 | 
			
		||||
          condition:
 | 
			
		||||
            display_menu.is_active:
 | 
			
		||||
          then:
 | 
			
		||||
            - display_menu.enter:
 | 
			
		||||
          else:
 | 
			
		||||
            - display_menu.left:
 | 
			
		||||
            - display_menu.right:
 | 
			
		||||
            - display_menu.show:
 | 
			
		||||
  - platform: template
 | 
			
		||||
    name: Garage Door Open
 | 
			
		||||
    id: garage_door
 | 
			
		||||
@@ -2343,7 +2331,6 @@ color:
 | 
			
		||||
 | 
			
		||||
display:
 | 
			
		||||
  - platform: lcd_gpio
 | 
			
		||||
    id: my_lcd_gpio
 | 
			
		||||
    dimensions: 18x4
 | 
			
		||||
    data_pins:
 | 
			
		||||
      - GPIO19
 | 
			
		||||
@@ -3022,85 +3009,3 @@ button:
 | 
			
		||||
    name: Midea Power Inverse
 | 
			
		||||
    on_press:
 | 
			
		||||
      midea_ac.power_toggle:
 | 
			
		||||
 | 
			
		||||
lcd_menu:
 | 
			
		||||
  display_id: my_lcd_gpio
 | 
			
		||||
  mark_back: 0x5e
 | 
			
		||||
  mark_selected: 0x3e
 | 
			
		||||
  mark_editing: 0x2a
 | 
			
		||||
  mark_submenu: 0x7e
 | 
			
		||||
  active: false
 | 
			
		||||
  mode: rotary
 | 
			
		||||
  on_enter:
 | 
			
		||||
    then:
 | 
			
		||||
      lambda: 'ESP_LOGI("lcd_menu", "root enter");'
 | 
			
		||||
  on_leave:
 | 
			
		||||
    then:
 | 
			
		||||
      lambda: 'ESP_LOGI("lcd_menu", "root leave");'
 | 
			
		||||
  items:
 | 
			
		||||
    - type: back
 | 
			
		||||
      text: 'Back'
 | 
			
		||||
    - type: label
 | 
			
		||||
    - type: menu
 | 
			
		||||
      text: 'Submenu 1'
 | 
			
		||||
      items:
 | 
			
		||||
        - type: back
 | 
			
		||||
          text: 'Back'
 | 
			
		||||
        - type: menu
 | 
			
		||||
          text: 'Submenu 21'
 | 
			
		||||
          items:
 | 
			
		||||
            - type: back
 | 
			
		||||
              text: 'Back'
 | 
			
		||||
            - type: command
 | 
			
		||||
              text: 'Show Main'
 | 
			
		||||
              on_value:
 | 
			
		||||
                then:
 | 
			
		||||
                  - display_menu.show_main:
 | 
			
		||||
    - type: select
 | 
			
		||||
      text: 'Enum Item'
 | 
			
		||||
      immediate_edit: true
 | 
			
		||||
      select: test_select
 | 
			
		||||
      on_enter:
 | 
			
		||||
        then:
 | 
			
		||||
          lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());'
 | 
			
		||||
      on_leave:
 | 
			
		||||
        then:
 | 
			
		||||
          lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());'
 | 
			
		||||
      on_value:
 | 
			
		||||
        then:
 | 
			
		||||
          lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());'
 | 
			
		||||
    - type: number
 | 
			
		||||
      text: 'Number'
 | 
			
		||||
      number: test_number
 | 
			
		||||
      on_enter:
 | 
			
		||||
        then:
 | 
			
		||||
          lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());'
 | 
			
		||||
      on_leave:
 | 
			
		||||
        then:
 | 
			
		||||
          lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());'
 | 
			
		||||
      on_value:
 | 
			
		||||
        then:
 | 
			
		||||
          lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());'
 | 
			
		||||
    - type: command
 | 
			
		||||
      text: 'Hide'
 | 
			
		||||
      on_value:
 | 
			
		||||
        then:
 | 
			
		||||
          - display_menu.hide:
 | 
			
		||||
    - type: switch
 | 
			
		||||
      text: 'Switch'
 | 
			
		||||
      switch: my_switch
 | 
			
		||||
      on_text: 'Bright'
 | 
			
		||||
      off_text: 'Dark'
 | 
			
		||||
      immediate_edit: false
 | 
			
		||||
      on_value:
 | 
			
		||||
        then:
 | 
			
		||||
          lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());'
 | 
			
		||||
    - type: custom
 | 
			
		||||
      text: !lambda 'return "Custom";'
 | 
			
		||||
      value_lambda: 'return "Val";'
 | 
			
		||||
      on_next:
 | 
			
		||||
        then:
 | 
			
		||||
          lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());'
 | 
			
		||||
      on_prev:
 | 
			
		||||
        then:
 | 
			
		||||
          lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());'
 | 
			
		||||
 
 | 
			
		||||
@@ -532,16 +532,6 @@ text_sensor:
 | 
			
		||||
                ESP_LOGD("main", "The state is %s=%s", x.c_str(), id(version_sensor).state.c_str());
 | 
			
		||||
            # yamllint enable rule:line-length
 | 
			
		||||
      - script.execute: my_script
 | 
			
		||||
      - script.execute:
 | 
			
		||||
          id: my_script_with_params
 | 
			
		||||
          prefix: Running my_script_with_params
 | 
			
		||||
          param2: 100
 | 
			
		||||
          param3: true
 | 
			
		||||
      - script.execute:
 | 
			
		||||
          id: my_script_with_params
 | 
			
		||||
          prefix: Running my_script_with_params using lambda parameters
 | 
			
		||||
          param2: !lambda return 200;
 | 
			
		||||
          param3: !lambda return true;
 | 
			
		||||
      - homeassistant.service:
 | 
			
		||||
          service: notify.html5
 | 
			
		||||
          data:
 | 
			
		||||
@@ -607,13 +597,6 @@ script:
 | 
			
		||||
    mode: restart
 | 
			
		||||
    then:
 | 
			
		||||
      - lambda: 'ESP_LOGD("main", "Hello World!");'
 | 
			
		||||
  - id: my_script_with_params
 | 
			
		||||
    parameters:
 | 
			
		||||
      prefix: string
 | 
			
		||||
      param2: int
 | 
			
		||||
      param3: bool
 | 
			
		||||
    then:
 | 
			
		||||
      - lambda: 'ESP_LOGD("main", (prefix + " Hello World!" + to_string(param2) + " " + to_string(param3)).c_str());'
 | 
			
		||||
 | 
			
		||||
stepper:
 | 
			
		||||
  - platform: uln2003
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user