mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Compare commits
	
		
			58 Commits
		
	
	
		
			jesserockz
			...
			2022.11.2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					9e516efe10 | ||
| 
						 | 
					366e29439e | ||
| 
						 | 
					1c9c700d7f | ||
| 
						 | 
					b2e6b9d31f | ||
| 
						 | 
					7623f63846 | ||
| 
						 | 
					2bfaf9dce3 | ||
| 
						 | 
					5c2c1560bb | ||
| 
						 | 
					f7096ab78e | ||
| 
						 | 
					98f8feb625 | ||
| 
						 | 
					9944ca414e | ||
| 
						 | 
					70f1c71a9f | ||
| 
						 | 
					816df5ad47 | ||
| 
						 | 
					7e88eea532 | ||
| 
						 | 
					d1cdfd3b72 | ||
| 
						 | 
					d6a03d48f5 | ||
| 
						 | 
					d453b42b1a | ||
| 
						 | 
					7c19b961e2 | ||
| 
						 | 
					d924702825 | ||
| 
						 | 
					e8784ba383 | ||
| 
						 | 
					e3a454d1a6 | ||
| 
						 | 
					58fda40389 | ||
| 
						 | 
					6a73699a38 | ||
| 
						 | 
					3bd6456fbe | ||
| 
						 | 
					608be4e050 | ||
| 
						 | 
					10f590324b | ||
| 
						 | 
					39f0f748bf | ||
| 
						 | 
					9efe59a984 | ||
| 
						 | 
					fcb02af782 | ||
| 
						 | 
					3aeef1afd4 | ||
| 
						 | 
					a45ee8f4ac | ||
| 
						 | 
					31b62d7dca | ||
| 
						 | 
					22f81475db | ||
| 
						 | 
					cc7cf73d59 | ||
| 
						 | 
					9682e60a25 | ||
| 
						 | 
					c6afae0da5 | ||
| 
						 | 
					4fa0e860ad | ||
| 
						 | 
					8c122aa372 | ||
| 
						 | 
					5a0bf9fee9 | ||
| 
						 | 
					dc794918ed | ||
| 
						 | 
					02b15dbc4a | ||
| 
						 | 
					ed316b1ce3 | ||
| 
						 | 
					d7858f16c1 | ||
| 
						 | 
					291deb12ad | ||
| 
						 | 
					3e110681c9 | ||
| 
						 | 
					65fbfa2097 | ||
| 
						 | 
					16ebf9da4c | ||
| 
						 | 
					2c76381fcd | ||
| 
						 | 
					90683223dd | ||
| 
						 | 
					de79171815 | ||
| 
						 | 
					fd8b9fb028 | ||
| 
						 | 
					bdf1813b3a | ||
| 
						 | 
					45b6c93f5f | ||
| 
						 | 
					6124531479 | ||
| 
						 | 
					b8549d323c | ||
| 
						 | 
					01adece673 | ||
| 
						 | 
					0220934e4c | ||
| 
						 | 
					ca09693efa | ||
| 
						 | 
					e96d7483b3 | 
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -31,7 +31,7 @@ jobs:
 | 
			
		||||
            today="$(date --utc '+%Y%m%d')"
 | 
			
		||||
            TAG="${TAG}${today}"
 | 
			
		||||
          fi
 | 
			
		||||
          echo "::set-output name=tag::${TAG}"
 | 
			
		||||
          echo "tag=${TAG}" >> $GITHUB_OUTPUT
 | 
			
		||||
        # yamllint enable rule:line-length
 | 
			
		||||
 | 
			
		||||
  deploy-pypi:
 | 
			
		||||
 
 | 
			
		||||
@@ -3,15 +3,15 @@
 | 
			
		||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
			
		||||
repos:
 | 
			
		||||
  - repo: https://github.com/ambv/black
 | 
			
		||||
    rev: 22.6.0
 | 
			
		||||
    rev: 22.10.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: black
 | 
			
		||||
        args:
 | 
			
		||||
          - --safe
 | 
			
		||||
          - --quiet
 | 
			
		||||
        files: ^((esphome|script|tests)/.+)?[^/]+\.py$
 | 
			
		||||
  - repo: https://gitlab.com/pycqa/flake8
 | 
			
		||||
    rev: 4.0.1
 | 
			
		||||
  - repo: https://github.com/PyCQA/flake8
 | 
			
		||||
    rev: 5.0.4
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: flake8
 | 
			
		||||
        additional_dependencies:
 | 
			
		||||
@@ -27,7 +27,7 @@ repos:
 | 
			
		||||
          - --branch=release
 | 
			
		||||
          - --branch=beta
 | 
			
		||||
  - repo: https://github.com/asottile/pyupgrade
 | 
			
		||||
    rev: v3.0.0
 | 
			
		||||
    rev: v3.2.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: pyupgrade
 | 
			
		||||
        args: [--py39-plus]
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,7 @@ 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
 | 
			
		||||
@@ -110,6 +111,7 @@ 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.4 \
 | 
			
		||||
        platformio==6.1.5 \
 | 
			
		||||
    # Change some platformio settings
 | 
			
		||||
    && platformio settings set enable_telemetry No \
 | 
			
		||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
			
		||||
@@ -94,7 +94,7 @@ RUN \
 | 
			
		||||
    apt-get update \
 | 
			
		||||
    # Use pinned versions so that we get updates with build caching
 | 
			
		||||
    && apt-get install -y --no-install-recommends \
 | 
			
		||||
        nginx-light=1.18.0-6.1+deb11u2 \
 | 
			
		||||
        nginx-light=1.18.0-6.1+deb11u3 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,10 @@ ADC_MODE(ADC_VCC)
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include <hardware/adc.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adc {
 | 
			
		||||
 | 
			
		||||
@@ -32,9 +36,13 @@ static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;    // 4095 (12 b
 | 
			
		||||
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;  // 2048 (12 bit) or 4096 (13 bit)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void ADCSensor::setup() {
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
extern "C"
 | 
			
		||||
#endif
 | 
			
		||||
    void
 | 
			
		||||
    ADCSensor::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
			
		||||
#ifndef USE_ADC_SENSOR_VCC
 | 
			
		||||
#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
 | 
			
		||||
  pin_->setup();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -63,6 +71,16 @@ void ADCSensor::setup() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  static bool initialized = false;
 | 
			
		||||
  if (!initialized) {
 | 
			
		||||
    adc_init();
 | 
			
		||||
    initialized = true;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADCSensor::dump_config() {
 | 
			
		||||
@@ -98,6 +116,12 @@ void ADCSensor::dump_config() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  if (this->is_temperature_)
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Pin: Temperature");
 | 
			
		||||
  else
 | 
			
		||||
    LOG_PIN("  Pin: ", pin_);
 | 
			
		||||
#endif
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -175,6 +199,29 @@ float ADCSensor::sample() {
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
float ADCSensor::sample() {
 | 
			
		||||
  if (this->is_temperature_) {
 | 
			
		||||
    adc_set_temp_sensor_enabled(true);
 | 
			
		||||
    delay(1);
 | 
			
		||||
    adc_select_input(4);
 | 
			
		||||
  } else {
 | 
			
		||||
    uint8_t pin = this->pin_->get_pin();
 | 
			
		||||
    adc_gpio_init(pin);
 | 
			
		||||
    adc_select_input(pin - 26);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int raw = adc_read();
 | 
			
		||||
  if (this->is_temperature_) {
 | 
			
		||||
    adc_set_temp_sensor_enabled(false);
 | 
			
		||||
  }
 | 
			
		||||
  if (output_raw_) {
 | 
			
		||||
    return raw;
 | 
			
		||||
  }
 | 
			
		||||
  return raw * 3.3f / 4096.0f;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -38,10 +38,18 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
  std::string unique_id() override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  void set_is_temperature() { is_temperature_ = true; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  InternalGPIOPin *pin_;
 | 
			
		||||
  bool output_raw_{false};
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  bool is_temperature_{false};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  adc_atten_t attenuation_{ADC_ATTEN_DB_0};
 | 
			
		||||
  adc1_channel_t channel_{};
 | 
			
		||||
 
 | 
			
		||||
@@ -94,6 +94,9 @@ def validate_adc_pin(value):
 | 
			
		||||
    if str(value).upper() == "VCC":
 | 
			
		||||
        return cv.only_on_esp8266("VCC")
 | 
			
		||||
 | 
			
		||||
    if str(value).upper() == "TEMPERATURE":
 | 
			
		||||
        return cv.only_on_rp2040("TEMPERATURE")
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        value = pins.internal_gpio_input_pin_number(value)
 | 
			
		||||
        variant = get_esp32_variant()
 | 
			
		||||
@@ -117,6 +120,12 @@ def validate_adc_pin(value):
 | 
			
		||||
            {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
 | 
			
		||||
        )(value)
 | 
			
		||||
 | 
			
		||||
    if CORE.is_rp2040:
 | 
			
		||||
        value = pins.internal_gpio_input_pin_number(value)
 | 
			
		||||
        if value not in (26, 27, 28, 29):
 | 
			
		||||
            raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
 | 
			
		||||
        return pins.internal_gpio_input_pin_schema(value)
 | 
			
		||||
 | 
			
		||||
    raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -160,6 +169,8 @@ async def to_code(config):
 | 
			
		||||
 | 
			
		||||
    if config[CONF_PIN] == "VCC":
 | 
			
		||||
        cg.add_define("USE_ADC_SENSOR_VCC")
 | 
			
		||||
    elif config[CONF_PIN] == "TEMPERATURE":
 | 
			
		||||
        cg.add(var.set_is_temperature())
 | 
			
		||||
    else:
 | 
			
		||||
        pin = await cg.gpio_pin_expression(config[CONF_PIN])
 | 
			
		||||
        cg.add(var.set_pin(pin))
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ CODEOWNERS = ["@OttoWinter"]
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema({}),
 | 
			
		||||
    cv.only_with_arduino,
 | 
			
		||||
    cv.only_on(["esp32", "esp8266"]),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
from esphome.components import web_server_base
 | 
			
		||||
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
 | 
			
		||||
from esphome.const import CONF_ID, CONF_NETWORKS, CONF_PASSWORD, CONF_SSID, CONF_WIFI
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
from esphome.core import coroutine_with_priority, CORE
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["web_server_base"]
 | 
			
		||||
@@ -13,7 +12,6 @@ CODEOWNERS = ["@OttoWinter"]
 | 
			
		||||
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
 | 
			
		||||
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
 | 
			
		||||
 | 
			
		||||
CONF_KEEP_USER_CREDENTIALS = "keep_user_credentials"
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
@@ -21,29 +19,13 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
 | 
			
		||||
                web_server_base.WebServerBase
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_KEEP_USER_CREDENTIALS, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.only_with_arduino,
 | 
			
		||||
    cv.only_on(["esp32", "esp8266"]),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_wifi(config):
 | 
			
		||||
    wifi_conf = fv.full_config.get()[CONF_WIFI]
 | 
			
		||||
    if config.get(CONF_KEEP_USER_CREDENTIALS, False) and (
 | 
			
		||||
        CONF_SSID in wifi_conf
 | 
			
		||||
        or CONF_PASSWORD in wifi_conf
 | 
			
		||||
        or CONF_NETWORKS in wifi_conf
 | 
			
		||||
    ):
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"WiFi credentials cannot be used together with {CONF_KEEP_USER_CREDENTIALS}"
 | 
			
		||||
        )
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = validate_wifi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(64.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
 | 
			
		||||
@@ -57,6 +39,3 @@ async def to_code(config):
 | 
			
		||||
        cg.add_library("WiFi", None)
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        cg.add_library("DNSServer", None)
 | 
			
		||||
 | 
			
		||||
    if config.get(CONF_KEEP_USER_CREDENTIALS, False):
 | 
			
		||||
        cg.add_define("USE_CAPTIVE_PORTAL_KEEP_USER_CREDENTIALS")
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ using namespace esphome::cover;
 | 
			
		||||
CoverTraits CurrentBasedCover::get_traits() {
 | 
			
		||||
  auto traits = CoverTraits();
 | 
			
		||||
  traits.set_supports_position(true);
 | 
			
		||||
  traits.set_supports_toggle(true);
 | 
			
		||||
  traits.set_is_assumed_state(false);
 | 
			
		||||
  return traits;
 | 
			
		||||
}
 | 
			
		||||
@@ -20,6 +21,20 @@ void CurrentBasedCover::control(const CoverCall &call) {
 | 
			
		||||
  if (call.get_stop()) {
 | 
			
		||||
    this->direction_idle_();
 | 
			
		||||
  }
 | 
			
		||||
  if (call.get_toggle().has_value()) {
 | 
			
		||||
    if (this->current_operation != COVER_OPERATION_IDLE) {
 | 
			
		||||
      this->start_direction_(COVER_OPERATION_IDLE);
 | 
			
		||||
      this->publish_state();
 | 
			
		||||
    } else {
 | 
			
		||||
      if (this->position == COVER_CLOSED || this->last_operation_ == COVER_OPERATION_CLOSING) {
 | 
			
		||||
        this->target_position_ = COVER_OPEN;
 | 
			
		||||
        this->start_direction_(COVER_OPERATION_OPENING);
 | 
			
		||||
      } else {
 | 
			
		||||
        this->target_position_ = COVER_CLOSED;
 | 
			
		||||
        this->start_direction_(COVER_OPERATION_CLOSING);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (call.get_position().has_value()) {
 | 
			
		||||
    auto pos = *call.get_position();
 | 
			
		||||
    if (pos == this->position) {
 | 
			
		||||
@@ -202,9 +217,11 @@ void CurrentBasedCover::start_direction_(CoverOperation dir) {
 | 
			
		||||
      trig = this->stop_trigger_;
 | 
			
		||||
      break;
 | 
			
		||||
    case COVER_OPERATION_OPENING:
 | 
			
		||||
      this->last_operation_ = dir;
 | 
			
		||||
      trig = this->open_trigger_;
 | 
			
		||||
      break;
 | 
			
		||||
    case COVER_OPERATION_CLOSING:
 | 
			
		||||
      this->last_operation_ = dir;
 | 
			
		||||
      trig = this->close_trigger_;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
 
 | 
			
		||||
@@ -89,6 +89,8 @@ class CurrentBasedCover : public cover::Cover, public Component {
 | 
			
		||||
  uint32_t start_dir_time_{0};
 | 
			
		||||
  uint32_t last_publish_time_{0};
 | 
			
		||||
  float target_position_{0};
 | 
			
		||||
 | 
			
		||||
  cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace current_based
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										430
									
								
								esphome/components/display_menu_base/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										430
									
								
								esphome/components/display_menu_base/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,430 @@
 | 
			
		||||
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)
 | 
			
		||||
							
								
								
									
										133
									
								
								esphome/components/display_menu_base/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								esphome/components/display_menu_base/automation.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
#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
 | 
			
		||||
							
								
								
									
										315
									
								
								esphome/components/display_menu_base/display_menu_base.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								esphome/components/display_menu_base/display_menu_base.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,315 @@
 | 
			
		||||
#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
 | 
			
		||||
							
								
								
									
										77
									
								
								esphome/components/display_menu_base/display_menu_base.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								esphome/components/display_menu_base/display_menu_base.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
#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
 | 
			
		||||
							
								
								
									
										179
									
								
								esphome/components/display_menu_base/menu_item.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								esphome/components/display_menu_base/menu_item.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,179 @@
 | 
			
		||||
#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
 | 
			
		||||
							
								
								
									
										187
									
								
								esphome/components/display_menu_base/menu_item.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								esphome/components/display_menu_base/menu_item.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,187 @@
 | 
			
		||||
#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,6 +105,12 @@ _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 conn_id=%d", this->connection_index_,
 | 
			
		||||
           this->address_str_.c_str(), event, esp_gattc_if, this->conn_id_);
 | 
			
		||||
  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);
 | 
			
		||||
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_REG_EVT: {
 | 
			
		||||
@@ -299,7 +299,7 @@ BLEDescriptor *BLEClientBase::get_config_descriptor(uint16_t handle) {
 | 
			
		||||
  auto *chr = this->get_characteristic(handle);
 | 
			
		||||
  if (chr != nullptr) {
 | 
			
		||||
    for (auto &desc : chr->descriptors) {
 | 
			
		||||
      if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
 | 
			
		||||
      if (desc->uuid.get_uuid().uuid.uuid16 == 0x2902)
 | 
			
		||||
        return desc;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -449,10 +449,13 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
 | 
			
		||||
ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) {
 | 
			
		||||
  ESPBTUUID ret;
 | 
			
		||||
  ret.uuid_.len = uuid.len;
 | 
			
		||||
  ret.uuid_.uuid.uuid16 = uuid.uuid.uuid16;
 | 
			
		||||
  ret.uuid_.uuid.uuid32 = uuid.uuid.uuid32;
 | 
			
		||||
  for (size_t i = 0; i < ESP_UUID_LEN_128; i++)
 | 
			
		||||
    ret.uuid_.uuid.uuid128[i] = uuid.uuid.uuid128[i];
 | 
			
		||||
  if (uuid.len == ESP_UUID_LEN_16) {
 | 
			
		||||
    ret.uuid_.uuid.uuid16 = uuid.uuid.uuid16;
 | 
			
		||||
  } else if (uuid.len == ESP_UUID_LEN_32) {
 | 
			
		||||
    ret.uuid_.uuid.uuid32 = uuid.uuid.uuid32;
 | 
			
		||||
  } else if (uuid.len == ESP_UUID_LEN_128) {
 | 
			
		||||
    memcpy(ret.uuid_.uuid.uuid128, uuid.uuid.uuid128, ESP_UUID_LEN_128);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
ESPBTUUID ESPBTUUID::as_128bit() const {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								esphome/components/ezo/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								esphome/components/ezo/automation.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
#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 TAG = "ezo.sensor";
 | 
			
		||||
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 uint16_t EZO_STATE_WAIT = 1;
 | 
			
		||||
static const uint16_t EZO_STATE_SEND_TEMP = 2;
 | 
			
		||||
static const uint16_t EZO_STATE_WAIT_TEMP = 4;
 | 
			
		||||
static const char *const EZO_CALIBRATION_TYPE_STRINGS[] = {"LOW", "MID", "HIGH"};
 | 
			
		||||
 | 
			
		||||
void EZOSensor::dump_config() {
 | 
			
		||||
  LOG_SENSOR("", "EZO", this);
 | 
			
		||||
@@ -20,37 +20,75 @@ void EZOSensor::dump_config() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::update() {
 | 
			
		||||
  if (this->state_ & EZO_STATE_WAIT) {
 | 
			
		||||
    ESP_LOGE(TAG, "update overrun, still waiting for previous response");
 | 
			
		||||
  // 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));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t c = 'R';
 | 
			
		||||
  this->write(&c, 1);
 | 
			
		||||
  this->state_ |= EZO_STATE_WAIT;
 | 
			
		||||
  this->start_time_ = millis();
 | 
			
		||||
  this->wait_time_ = 900;
 | 
			
		||||
 | 
			
		||||
  this->get_state();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::loop() {
 | 
			
		||||
  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;
 | 
			
		||||
  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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->start_time_ = millis();
 | 
			
		||||
    to_run->command_sent = true;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (millis() - this->start_time_ < this->wait_time_)
 | 
			
		||||
 | 
			
		||||
  if (millis() - this->start_time_ < to_run->delay_ms)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  uint8_t buf[32];
 | 
			
		||||
 | 
			
		||||
  buf[0] = 0;
 | 
			
		||||
  if (!this->read_bytes_raw(buf, 20)) {
 | 
			
		||||
 | 
			
		||||
  if (!this->read_bytes_raw(buf, 32)) {
 | 
			
		||||
    ESP_LOGE(TAG, "read error");
 | 
			
		||||
    this->state_ = 0;
 | 
			
		||||
    this->commands_.pop_front();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (buf[0]) {
 | 
			
		||||
    case 1:
 | 
			
		||||
      break;
 | 
			
		||||
@@ -66,28 +104,142 @@ 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;
 | 
			
		||||
 | 
			
		||||
  // 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';
 | 
			
		||||
  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;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  float val = parse_number<float>((char *) &buf[1]).value_or(0);
 | 
			
		||||
  this->publish_state(val);
 | 
			
		||||
  this->commands_.pop_front();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_tempcomp_value(float temp) {
 | 
			
		||||
  this->tempcomp_ = temp;
 | 
			
		||||
  this->state_ |= EZO_STATE_SEND_TEMP;
 | 
			
		||||
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_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,10 +3,35 @@
 | 
			
		||||
#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:
 | 
			
		||||
@@ -15,13 +40,71 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2
 | 
			
		||||
  void update() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; };
 | 
			
		||||
 | 
			
		||||
  void set_tempcomp_value(float temp);
 | 
			
		||||
  // 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));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 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,22 +1,81 @@
 | 
			
		||||
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
 | 
			
		||||
from esphome.const import CONF_ID, CONF_TRIGGER_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"))
 | 
			
		||||
@@ -29,3 +88,27 @@ 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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								esphome/components/lcd_menu/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								esphome/components/lcd_menu/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
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]))
 | 
			
		||||
							
								
								
									
										74
									
								
								esphome/components/lcd_menu/lcd_menu.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								esphome/components/lcd_menu/lcd_menu.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
#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
 | 
			
		||||
							
								
								
									
										45
									
								
								esphome/components/lcd_menu/lcd_menu.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/lcd_menu/lcd_menu.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
#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 = [UART0, UART1]
 | 
			
		||||
UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1]
 | 
			
		||||
 | 
			
		||||
HARDWARE_UART_TO_UART_SELECTION = {
 | 
			
		||||
    UART0: logger_ns.UART_SELECTION_UART0,
 | 
			
		||||
@@ -99,10 +99,9 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def uart_selection(value):
 | 
			
		||||
    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:
 | 
			
		||||
        if value.upper() in ESP_IDF_UARTS and not CORE.using_esp_idf:
 | 
			
		||||
            raise cv.Invalid(f"Only esp-idf framework supports {value}.")
 | 
			
		||||
        variant = get_esp32_variant()
 | 
			
		||||
        if variant in UART_SELECTION_ESP32:
 | 
			
		||||
            return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
 | 
			
		||||
@@ -137,7 +136,12 @@ 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.Optional(CONF_HARDWARE_UART, default=UART0): uart_selection,
 | 
			
		||||
            cv.SplitDefault(
 | 
			
		||||
                CONF_HARDWARE_UART,
 | 
			
		||||
                esp8266=UART0,
 | 
			
		||||
                esp32=UART0,
 | 
			
		||||
                rp2040=USB_CDC,
 | 
			
		||||
            ): 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 "freertos/FreeRTOS.h"
 | 
			
		||||
#include <driver/uart.h>
 | 
			
		||||
#endif
 | 
			
		||||
#include "freertos/FreeRTOS.h"
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF)
 | 
			
		||||
#include <esp_log.h>
 | 
			
		||||
#endif
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#endif  // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace logger {
 | 
			
		||||
@@ -161,8 +161,13 @@ 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();
 | 
			
		||||
@@ -171,8 +176,13 @@ 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
 | 
			
		||||
@@ -183,6 +193,12 @@ 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
 | 
			
		||||
@@ -271,7 +287,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"};
 | 
			
		||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"};
 | 
			
		||||
#endif  // USE_ESP8266
 | 
			
		||||
void Logger::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Logger:");
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#include <driver/uart.h>
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 | 
			
		||||
@@ -34,19 +34,22 @@ 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
 | 
			
		||||
#endif  // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
  UART_SELECTION_USB_CDC,
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
  UART_SELECTION_USB_SERIAL_JTAG,
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
  UART_SELECTION_UART0_SWAP,
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP8266
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  UART_SELECTION_USB_CDC,
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Logger : public Component {
 | 
			
		||||
 
 | 
			
		||||
@@ -250,6 +250,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    validate_config,
 | 
			
		||||
    cv.only_on(["esp32", "esp8266"]),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -103,7 +103,7 @@ CONFIG_SCHEMA = (
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def auto_data_rate(config):
 | 
			
		||||
    interval_sec = config[CONF_UPDATE_INTERVAL].seconds
 | 
			
		||||
    interval_sec = config[CONF_UPDATE_INTERVAL].total_milliseconds / 1000
 | 
			
		||||
    interval_hz = 1.0 / interval_sec
 | 
			
		||||
    for datarate in sorted(QMC5883LDatarates.keys()):
 | 
			
		||||
        if float(datarate) >= interval_hz:
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,9 @@
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/macros.h"
 | 
			
		||||
 | 
			
		||||
#include <PinNames.h>
 | 
			
		||||
#include <hardware/clocks.h>
 | 
			
		||||
#include <hardware/gpio.h>
 | 
			
		||||
#include <hardware/pwm.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace rp2040_pwm {
 | 
			
		||||
@@ -15,10 +17,17 @@ static const char *const TAG = "rp2040_pwm";
 | 
			
		||||
 | 
			
		||||
void RP2040PWM::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up RP2040 PWM Output...");
 | 
			
		||||
  this->pin_->setup();
 | 
			
		||||
  this->pwm_ = new mbed::PwmOut((PinName) this->pin_->get_pin());
 | 
			
		||||
  this->turn_off();
 | 
			
		||||
 | 
			
		||||
  this->setup_pwm_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RP2040PWM::setup_pwm_() {
 | 
			
		||||
  pwm_config config = pwm_get_default_config();
 | 
			
		||||
  pwm_config_set_clkdiv(&config, clock_get_hz(clk_sys) / (255.0f * this->frequency_));
 | 
			
		||||
  pwm_config_set_wrap(&config, 254);
 | 
			
		||||
  pwm_init(pwm_gpio_to_slice_num(this->pin_->get_pin()), &config, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RP2040PWM::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "RP2040 PWM:");
 | 
			
		||||
  LOG_PIN("  Pin: ", this->pin_);
 | 
			
		||||
@@ -33,10 +42,13 @@ void HOT RP2040PWM::write_state(float state) {
 | 
			
		||||
    state = 1.0f - state;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto total_time_us = static_cast<uint32_t>(roundf(1e6f / this->frequency_));
 | 
			
		||||
  if (this->frequency_changed_) {
 | 
			
		||||
    this->setup_pwm_();
 | 
			
		||||
    this->frequency_changed_ = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->pwm_->period_us(total_time_us);
 | 
			
		||||
  this->pwm_->write(state);
 | 
			
		||||
  gpio_set_function(this->pin_->get_pin(), GPIO_FUNC_PWM);
 | 
			
		||||
  pwm_set_gpio_level(this->pin_->get_pin(), state * 255.0f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace rp2040_pwm
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,6 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
#include "drivers/PwmOut.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace rp2040_pwm {
 | 
			
		||||
 | 
			
		||||
@@ -19,6 +17,7 @@ class RP2040PWM : public output::FloatOutput, public Component {
 | 
			
		||||
  /// Dynamically update frequency
 | 
			
		||||
  void update_frequency(float frequency) override {
 | 
			
		||||
    this->set_frequency(frequency);
 | 
			
		||||
    this->frequency_changed_ = true;
 | 
			
		||||
    this->write_state(this->last_output_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -31,11 +30,13 @@ class RP2040PWM : public output::FloatOutput, public Component {
 | 
			
		||||
 protected:
 | 
			
		||||
  void write_state(float state) override;
 | 
			
		||||
 | 
			
		||||
  void setup_pwm_();
 | 
			
		||||
 | 
			
		||||
  InternalGPIOPin *pin_;
 | 
			
		||||
  mbed::PwmOut *pwm_;
 | 
			
		||||
  float frequency_{1000.0};
 | 
			
		||||
  /// Cache last output level for dynamic frequency updating
 | 
			
		||||
  float last_output_{0.0};
 | 
			
		||||
  bool frequency_changed_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class SetFrequencyAction : public Action<Ts...> {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,8 @@ 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
 | 
			
		||||
from esphome.const import CONF_ID, CONF_MODE, CONF_PARAMETERS
 | 
			
		||||
from esphome.core import CORE, EsphomeError
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
script_ns = cg.esphome_ns.namespace("script")
 | 
			
		||||
@@ -16,6 +17,7 @@ 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"
 | 
			
		||||
@@ -29,6 +31,18 @@ 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:
 | 
			
		||||
@@ -47,6 +61,44 @@ 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
 | 
			
		||||
@@ -56,6 +108,11 @@ 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),
 | 
			
		||||
)
 | 
			
		||||
@@ -65,7 +122,8 @@ async def to_code(config):
 | 
			
		||||
    # Register all variables first, so that scripts can use other scripts
 | 
			
		||||
    triggers = []
 | 
			
		||||
    for conf in config:
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_ID])
 | 
			
		||||
        template, func_args = parameters_to_template(conf[CONF_PARAMETERS])
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_ID], template)
 | 
			
		||||
        # Add a human-readable name to the script
 | 
			
		||||
        cg.add(trigger.set_name(conf[CONF_ID].id))
 | 
			
		||||
 | 
			
		||||
@@ -75,10 +133,10 @@ async def to_code(config):
 | 
			
		||||
        if conf[CONF_MODE] == CONF_QUEUED:
 | 
			
		||||
            await cg.register_component(trigger, conf)
 | 
			
		||||
 | 
			
		||||
        triggers.append((trigger, conf))
 | 
			
		||||
        triggers.append((trigger, func_args, conf))
 | 
			
		||||
 | 
			
		||||
    for trigger, conf in triggers:
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
    for trigger, func_args, conf in triggers:
 | 
			
		||||
        await automation.build_automation(trigger, func_args, conf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
@@ -87,12 +145,39 @@ 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])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    cg.add(var.set_args(*script_args))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
@@ -101,7 +186,8 @@ 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):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
 | 
			
		||||
    template_arg = cg.TemplateArguments(full_id.type, *template_arg)
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -111,7 +197,8 @@ 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):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
 | 
			
		||||
    template_arg = cg.TemplateArguments(full_id.type, *template_arg)
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    await cg.register_component(var, {})
 | 
			
		||||
    return var
 | 
			
		||||
@@ -123,5 +210,6 @@ 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):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
 | 
			
		||||
    template_arg = cg.TemplateArguments(full_id.type, *template_arg)
 | 
			
		||||
    return cg.new_Pvariable(condition_id, template_arg, paren)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,61 +6,8 @@ namespace script {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "script";
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
void ScriptLogger::esp_log_(int level, int line, const char *format, const char *param) {
 | 
			
		||||
  esp_log_printf_(level, TAG, line, format, param);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace script
 | 
			
		||||
 
 | 
			
		||||
@@ -2,27 +2,48 @@
 | 
			
		||||
 | 
			
		||||
#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.
 | 
			
		||||
class Script : public Trigger<> {
 | 
			
		||||
template<typename... Ts> class Script : public ScriptLogger, public Trigger<Ts...> {
 | 
			
		||||
 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() = 0;
 | 
			
		||||
  virtual void execute(Ts...) = 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_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -31,9 +52,16 @@ class Script : public Trigger<> {
 | 
			
		||||
 * If a new instance is executed while the previous one hasn't finished yet,
 | 
			
		||||
 * a warning is printed and the new instance is discarded.
 | 
			
		||||
 */
 | 
			
		||||
class SingleScript : public Script {
 | 
			
		||||
template<typename... Ts> class SingleScript : public Script<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  void execute() override;
 | 
			
		||||
  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...);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** A script type that restarts scripts from the beginning when a new instance is started.
 | 
			
		||||
@@ -41,20 +69,55 @@ class SingleScript : public Script {
 | 
			
		||||
 * 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.
 | 
			
		||||
 */
 | 
			
		||||
class RestartScript : public Script {
 | 
			
		||||
template<typename... Ts> class RestartScript : public Script<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  void execute() override;
 | 
			
		||||
  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...);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** A script type that queues new instances that are created.
 | 
			
		||||
 *
 | 
			
		||||
 * Only one instance of the script can be active at a time.
 | 
			
		||||
 */
 | 
			
		||||
class QueueingScript : public Script, public Component {
 | 
			
		||||
template<typename... Ts> class QueueingScript : public Script<Ts...>, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void execute() override;
 | 
			
		||||
  void stop() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  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 set_max_runs(int max_runs) { max_runs_ = max_runs; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
@@ -67,48 +130,84 @@ class QueueingScript : public Script, public Component {
 | 
			
		||||
 * If a new instance is started while previous ones haven't finished yet,
 | 
			
		||||
 * the new one is executed in parallel to the other instances.
 | 
			
		||||
 */
 | 
			
		||||
class ParallelScript : public Script {
 | 
			
		||||
template<typename... Ts> class ParallelScript : public Script<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  void execute() override;
 | 
			
		||||
  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 set_max_runs(int max_runs) { max_runs_ = max_runs; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  int max_runs_ = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class ScriptExecuteAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  ScriptExecuteAction(Script *script) : script_(script) {}
 | 
			
		||||
template<class S, typename... Ts> class ScriptExecuteAction;
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->script_->execute(); }
 | 
			
		||||
template<class... As, typename... Ts> class ScriptExecuteAction<Script<As...>, Ts...> : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  ScriptExecuteAction(Script<As...> *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...)); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Script *script_;
 | 
			
		||||
  // 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_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class ScriptStopAction : public Action<Ts...> {
 | 
			
		||||
template<class C, typename... Ts> class ScriptStopAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  ScriptStopAction(Script *script) : script_(script) {}
 | 
			
		||||
  ScriptStopAction(C *script) : script_(script) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->script_->stop(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Script *script_;
 | 
			
		||||
  C *script_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class IsRunningCondition : public Condition<Ts...> {
 | 
			
		||||
template<class C, typename... Ts> class IsRunningCondition : public Condition<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit IsRunningCondition(Script *parent) : parent_(parent) {}
 | 
			
		||||
  explicit IsRunningCondition(C *parent) : parent_(parent) {}
 | 
			
		||||
 | 
			
		||||
  bool check(Ts... x) override { return this->parent_->is_running(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Script *parent_;
 | 
			
		||||
  C *parent_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class ScriptWaitAction : public Action<Ts...>, public Component {
 | 
			
		||||
template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  ScriptWaitAction(Script *script) : script_(script) {}
 | 
			
		||||
  ScriptWaitAction(C *script) : script_(script) {}
 | 
			
		||||
 | 
			
		||||
  void play_complex(Ts... x) override {
 | 
			
		||||
    this->num_running_++;
 | 
			
		||||
@@ -137,7 +236,7 @@ template<typename... Ts> class ScriptWaitAction : public Action<Ts...>, public C
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Script *script_;
 | 
			
		||||
  C *script_;
 | 
			
		||||
  std::tuple<Ts...> var_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,9 @@
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#include "sntp.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include "lwip/apps/sntp.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Yes, the server names are leaked, but that's fine.
 | 
			
		||||
#ifdef CLANG_TIDY
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ optional<std::string> ToUpperFilter::new_value(std::string value) {
 | 
			
		||||
// ToLowerFilter
 | 
			
		||||
optional<std::string> ToLowerFilter::new_value(std::string value) {
 | 
			
		||||
  for (char &c : value)
 | 
			
		||||
    c = ::toupper(c);
 | 
			
		||||
    c = ::tolower(c);
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,9 @@
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#include "sys/time.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include <sys/time.h>
 | 
			
		||||
#endif
 | 
			
		||||
#include <cerrno>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ 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)
 | 
			
		||||
@@ -89,6 +90,8 @@ 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,6 +90,7 @@ 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;
 | 
			
		||||
@@ -104,7 +105,7 @@ void ESP32ArduinoUARTComponent::setup() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP32ArduinoUARTComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "UART Bus:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "UART Bus %d:", this->number_);
 | 
			
		||||
  LOG_PIN("  TX Pin: ", tx_pin_);
 | 
			
		||||
  LOG_PIN("  RX Pin: ", rx_pin_);
 | 
			
		||||
  if (this->rx_pin_ != nullptr) {
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ class ESP32ArduinoUARTComponent : public UARTComponent, public Component {
 | 
			
		||||
  void check_logger_conflict() override;
 | 
			
		||||
 | 
			
		||||
  HardwareSerial *hw_serial_{nullptr};
 | 
			
		||||
  uint8_t number_{0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace uart
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										184
									
								
								esphome/components/uart/uart_component_rp2040.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								esphome/components/uart/uart_component_rp2040.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,184 @@
 | 
			
		||||
#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
 | 
			
		||||
							
								
								
									
										43
									
								
								esphome/components/uart/uart_component_rp2040.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/uart/uart_component_rp2040.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
#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
 | 
			
		||||
@@ -75,6 +75,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.only_with_arduino,
 | 
			
		||||
    cv.only_on(["esp32", "esp8266"]),
 | 
			
		||||
    default_url,
 | 
			
		||||
    validate_local,
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -39,11 +39,8 @@ void WiFiComponent::setup() {
 | 
			
		||||
  this->last_connected_ = millis();
 | 
			
		||||
  this->wifi_pre_setup_();
 | 
			
		||||
 | 
			
		||||
#ifndef USE_CAPTIVE_PORTAL_KEEP_USER_CREDENTIALS
 | 
			
		||||
  uint32_t hash = fnv1_hash(App.get_compilation_time());
 | 
			
		||||
#else
 | 
			
		||||
  uint32_t hash = 88491487UL;
 | 
			
		||||
#endif
 | 
			
		||||
  uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time()) : 88491487UL;
 | 
			
		||||
 | 
			
		||||
  this->pref_ = global_preferences->make_preference<wifi::SavedWifiSettings>(hash, true);
 | 
			
		||||
 | 
			
		||||
  SavedWifiSettings save{};
 | 
			
		||||
 
 | 
			
		||||
@@ -548,6 +548,7 @@ def only_with_framework(frameworks):
 | 
			
		||||
 | 
			
		||||
only_on_esp32 = only_on("esp32")
 | 
			
		||||
only_on_esp8266 = only_on("esp8266")
 | 
			
		||||
only_on_rp2040 = only_on("rp2040")
 | 
			
		||||
only_with_arduino = only_with_framework("arduino")
 | 
			
		||||
only_with_esp_idf = only_with_framework("esp-idf")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
"""Constants used by esphome."""
 | 
			
		||||
 | 
			
		||||
__version__ = "2022.11.0-dev"
 | 
			
		||||
__version__ = "2022.11.2"
 | 
			
		||||
 | 
			
		||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
 | 
			
		||||
 | 
			
		||||
@@ -491,6 +491,7 @@ 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"
 | 
			
		||||
 
 | 
			
		||||
@@ -313,7 +313,11 @@ class EsphomeUploadHandler(EsphomeCommandWebSocket):
 | 
			
		||||
class EsphomeCompileHandler(EsphomeCommandWebSocket):
 | 
			
		||||
    def build_command(self, json_message):
 | 
			
		||||
        config_file = settings.rel_path(json_message["configuration"])
 | 
			
		||||
        return ["esphome", "--dashboard", "compile", config_file]
 | 
			
		||||
        command = ["esphome", "--dashboard", "compile"]
 | 
			
		||||
        if json_message.get("only_generate", False):
 | 
			
		||||
            command.append("--only-generate")
 | 
			
		||||
        command.append(config_file)
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EsphomeValidateHandler(EsphomeCommandWebSocket):
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,12 @@ tornado==6.2
 | 
			
		||||
tzlocal==4.2    # from time
 | 
			
		||||
tzdata>=2021.1  # from time
 | 
			
		||||
pyserial==3.5
 | 
			
		||||
platformio==6.1.4  # When updating platformio, also update Dockerfile
 | 
			
		||||
platformio==6.1.5  # When updating platformio, also update Dockerfile
 | 
			
		||||
esptool==3.3.1
 | 
			
		||||
click==8.1.3
 | 
			
		||||
esphome-dashboard==20221020.0
 | 
			
		||||
aioesphomeapi==11.4.2
 | 
			
		||||
zeroconf==0.39.1
 | 
			
		||||
esphome-dashboard==20221109.0
 | 
			
		||||
aioesphomeapi==11.4.3
 | 
			
		||||
zeroconf==0.39.4
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
black==22.8.0  # also change in .pre-commit-config.yaml when updating
 | 
			
		||||
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
 | 
			
		||||
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.19.0
 | 
			
		||||
pytest-asyncio==0.20.1
 | 
			
		||||
asyncmock==0.4.2
 | 
			
		||||
hypothesis==5.49.0
 | 
			
		||||
 
 | 
			
		||||
@@ -871,8 +871,10 @@ 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
 | 
			
		||||
@@ -1289,6 +1291,16 @@ 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
 | 
			
		||||
@@ -2331,6 +2343,7 @@ color:
 | 
			
		||||
 | 
			
		||||
display:
 | 
			
		||||
  - platform: lcd_gpio
 | 
			
		||||
    id: my_lcd_gpio
 | 
			
		||||
    dimensions: 18x4
 | 
			
		||||
    data_pins:
 | 
			
		||||
      - GPIO19
 | 
			
		||||
@@ -3009,3 +3022,85 @@ 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,6 +532,16 @@ 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:
 | 
			
		||||
@@ -597,6 +607,13 @@ 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
 | 
			
		||||
 
 | 
			
		||||
@@ -884,6 +884,7 @@ binary_sensor:
 | 
			
		||||
      then:
 | 
			
		||||
        - cover.toggle: time_based_cover
 | 
			
		||||
        - cover.toggle: endstop_cover
 | 
			
		||||
        - cover.toggle: current_based_cover
 | 
			
		||||
  - platform: hydreon_rgxx
 | 
			
		||||
    hydreon_rgxx_id: hydreon_rg9
 | 
			
		||||
    too_cold:
 | 
			
		||||
@@ -1246,6 +1247,7 @@ cover:
 | 
			
		||||
    close_duration: 4.5min
 | 
			
		||||
  - platform: current_based
 | 
			
		||||
    name: Current Based Cover
 | 
			
		||||
    id: current_based_cover
 | 
			
		||||
    open_sensor: ade7953_current_a
 | 
			
		||||
    open_moving_current_threshold: 0.5
 | 
			
		||||
    open_obstacle_current_threshold: 0.8
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user