mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			2023.5.3
			...
			jesserockz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					fcf0761ba3 | ||
| 
						 | 
					4b9732629e | ||
| 
						 | 
					5cef11acf0 | ||
| 
						 | 
					b1ad48c824 | ||
| 
						 | 
					35b1b5fae6 | ||
| 
						 | 
					ce20b64712 | ||
| 
						 | 
					1720b2a37e | ||
| 
						 | 
					c974fbbea8 | ||
| 
						 | 
					00c5233bf4 | ||
| 
						 | 
					7efb138933 | ||
| 
						 | 
					f73435f820 | ||
| 
						 | 
					bfc55beedc | ||
| 
						 | 
					9fa057eae8 | 
@@ -84,7 +84,6 @@ esphome/components/esp32_ble_server/* @jesserockz
 | 
			
		||||
esphome/components/esp32_camera_web_server/* @ayufan
 | 
			
		||||
esphome/components/esp32_can/* @Sympatron
 | 
			
		||||
esphome/components/esp32_improv/* @jesserockz
 | 
			
		||||
esphome/components/esp32_rmt_led_strip/* @jesserockz
 | 
			
		||||
esphome/components/esp8266/* @esphome/core
 | 
			
		||||
esphome/components/ethernet_info/* @gtjadsonsantos
 | 
			
		||||
esphome/components/exposure_notifications/* @OttoWinter
 | 
			
		||||
@@ -96,7 +95,6 @@ esphome/components/feedback/* @ianchi
 | 
			
		||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
 | 
			
		||||
esphome/components/fs3000/* @kahrendt
 | 
			
		||||
esphome/components/globals/* @esphome/core
 | 
			
		||||
esphome/components/gp8403/* @jesserockz
 | 
			
		||||
esphome/components/gpio/* @esphome/core
 | 
			
		||||
esphome/components/gps/* @coogle
 | 
			
		||||
esphome/components/graph/* @synco
 | 
			
		||||
@@ -109,7 +107,6 @@ esphome/components/heatpumpir/* @rob-deutsch
 | 
			
		||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
			
		||||
esphome/components/homeassistant/* @OttoWinter
 | 
			
		||||
esphome/components/honeywellabp/* @RubyBailey
 | 
			
		||||
esphome/components/host/* @esphome/core
 | 
			
		||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
			
		||||
esphome/components/hte501/* @Stock-M
 | 
			
		||||
esphome/components/hydreon_rgxx/* @functionpointer
 | 
			
		||||
@@ -118,7 +115,6 @@ esphome/components/i2c/* @esphome/core
 | 
			
		||||
esphome/components/i2s_audio/* @jesserockz
 | 
			
		||||
esphome/components/i2s_audio/media_player/* @jesserockz
 | 
			
		||||
esphome/components/i2s_audio/microphone/* @jesserockz
 | 
			
		||||
esphome/components/i2s_audio/speaker/* @jesserockz
 | 
			
		||||
esphome/components/ili9xxx/* @nielsnl68
 | 
			
		||||
esphome/components/improv_base/* @esphome/core
 | 
			
		||||
esphome/components/improv_serial/* @esphome/core
 | 
			
		||||
@@ -241,7 +237,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet
 | 
			
		||||
esphome/components/sigma_delta_output/* @Cat-Ion
 | 
			
		||||
esphome/components/sim800l/* @glmnet
 | 
			
		||||
esphome/components/sm10bit_base/* @Cossid
 | 
			
		||||
esphome/components/sm2135/* @BoukeHaarsma23 @dd32 @matika77
 | 
			
		||||
esphome/components/sm2135/* @BoukeHaarsma23
 | 
			
		||||
esphome/components/sm2235/* @Cossid
 | 
			
		||||
esphome/components/sm2335/* @Cossid
 | 
			
		||||
esphome/components/sml/* @alengwenus
 | 
			
		||||
@@ -249,7 +245,6 @@ esphome/components/smt100/* @piechade
 | 
			
		||||
esphome/components/sn74hc165/* @jesserockz
 | 
			
		||||
esphome/components/socket/* @esphome/core
 | 
			
		||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
			
		||||
esphome/components/speaker/* @jesserockz
 | 
			
		||||
esphome/components/spi/* @esphome/core
 | 
			
		||||
esphome/components/sprinkler/* @kbx81
 | 
			
		||||
esphome/components/sps30/* @martgras
 | 
			
		||||
 
 | 
			
		||||
@@ -1,118 +1 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_INPUT
 | 
			
		||||
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.components.esp32 import get_esp32_variant
 | 
			
		||||
from esphome.components.esp32.const import (
 | 
			
		||||
    VARIANT_ESP32,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
    VARIANT_ESP32H2,
 | 
			
		||||
    VARIANT_ESP32S2,
 | 
			
		||||
    VARIANT_ESP32S3,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
 | 
			
		||||
ATTENUATION_MODES = {
 | 
			
		||||
    "0db": cg.global_ns.ADC_ATTEN_DB_0,
 | 
			
		||||
    "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
 | 
			
		||||
    "6db": cg.global_ns.ADC_ATTEN_DB_6,
 | 
			
		||||
    "11db": cg.global_ns.ADC_ATTEN_DB_11,
 | 
			
		||||
    "auto": "auto",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
			
		||||
 | 
			
		||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
 | 
			
		||||
# pin to adc1 channel mapping
 | 
			
		||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
    VARIANT_ESP32: {
 | 
			
		||||
        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        38: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        39: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        32: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        33: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S2: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S3: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32H2: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
        if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
 | 
			
		||||
            raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
 | 
			
		||||
 | 
			
		||||
        if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
 | 
			
		||||
            raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
 | 
			
		||||
        return pins.internal_gpio_input_pin_schema(value)
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        from esphome.components.esp8266.gpio import CONF_ANALOG
 | 
			
		||||
 | 
			
		||||
        value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
 | 
			
		||||
            value
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if value != 17:  # A0
 | 
			
		||||
            raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
 | 
			
		||||
        return pins.gpio_pin_schema(
 | 
			
		||||
            {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
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,133 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import sensor, voltage_sampler
 | 
			
		||||
from esphome.components.esp32 import get_esp32_variant
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ATTENUATION,
 | 
			
		||||
    CONF_RAW,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INPUT,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_PIN,
 | 
			
		||||
    CONF_RAW,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
 | 
			
		||||
from . import (
 | 
			
		||||
    ATTENUATION_MODES,
 | 
			
		||||
    ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
 | 
			
		||||
    validate_adc_pin,
 | 
			
		||||
from esphome.components.esp32 import get_esp32_variant
 | 
			
		||||
from esphome.components.esp32.const import (
 | 
			
		||||
    VARIANT_ESP32,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
    VARIANT_ESP32H2,
 | 
			
		||||
    VARIANT_ESP32S2,
 | 
			
		||||
    VARIANT_ESP32S3,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["voltage_sampler"]
 | 
			
		||||
 | 
			
		||||
ATTENUATION_MODES = {
 | 
			
		||||
    "0db": cg.global_ns.ADC_ATTEN_DB_0,
 | 
			
		||||
    "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
 | 
			
		||||
    "6db": cg.global_ns.ADC_ATTEN_DB_6,
 | 
			
		||||
    "11db": cg.global_ns.ADC_ATTEN_DB_11,
 | 
			
		||||
    "auto": "auto",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
			
		||||
 | 
			
		||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
 | 
			
		||||
# pin to adc1 channel mapping
 | 
			
		||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
    VARIANT_ESP32: {
 | 
			
		||||
        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        38: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        39: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        32: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        33: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S2: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S3: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32H2: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
        if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
 | 
			
		||||
            raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
 | 
			
		||||
 | 
			
		||||
        if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
 | 
			
		||||
            raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
 | 
			
		||||
        return pins.internal_gpio_input_pin_schema(value)
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        from esphome.components.esp8266.gpio import CONF_ANALOG
 | 
			
		||||
 | 
			
		||||
        value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
 | 
			
		||||
            value
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if value != 17:  # A0
 | 
			
		||||
            raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
 | 
			
		||||
        return pins.gpio_pin_schema(
 | 
			
		||||
            {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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_config(config):
 | 
			
		||||
    if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ from esphome.components import i2c
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
AUTO_LOAD = ["sensor", "binary_sensor"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_APDS9960_ID = "apds9960_id"
 | 
			
		||||
 
 | 
			
		||||
@@ -116,12 +116,8 @@ void APDS9960::setup() {
 | 
			
		||||
  APDS9960_WRITE_BYTE(0x80, val);
 | 
			
		||||
}
 | 
			
		||||
bool APDS9960::is_color_enabled_() const {
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  return this->red_sensor_ != nullptr || this->green_sensor_ != nullptr || this->blue_sensor_ != nullptr ||
 | 
			
		||||
         this->clear_sensor_ != nullptr;
 | 
			
		||||
#else
 | 
			
		||||
  return false;
 | 
			
		||||
#endif
 | 
			
		||||
  return this->red_channel_ != nullptr || this->green_channel_ != nullptr || this->blue_channel_ != nullptr ||
 | 
			
		||||
         this->clear_channel_ != nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APDS9960::dump_config() {
 | 
			
		||||
@@ -129,15 +125,6 @@ void APDS9960::dump_config() {
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  LOG_SENSOR("  ", "Red channel", this->red_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Green channel", this->green_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Blue channel", this->blue_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Clear channel", this->clear_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Proximity", this->proximity_sensor_);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    switch (this->error_code_) {
 | 
			
		||||
      case COMMUNICATION_FAILED:
 | 
			
		||||
@@ -194,22 +181,17 @@ void APDS9960::read_color_data_(uint8_t status) {
 | 
			
		||||
  float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc);
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  if (this->clear_sensor_ != nullptr)
 | 
			
		||||
    this->clear_sensor_->publish_state(clear_perc);
 | 
			
		||||
  if (this->red_sensor_ != nullptr)
 | 
			
		||||
    this->red_sensor_->publish_state(red_perc);
 | 
			
		||||
  if (this->green_sensor_ != nullptr)
 | 
			
		||||
    this->green_sensor_->publish_state(green_perc);
 | 
			
		||||
  if (this->blue_sensor_ != nullptr)
 | 
			
		||||
    this->blue_sensor_->publish_state(blue_perc);
 | 
			
		||||
#endif
 | 
			
		||||
  if (this->clear_channel_ != nullptr)
 | 
			
		||||
    this->clear_channel_->publish_state(clear_perc);
 | 
			
		||||
  if (this->red_channel_ != nullptr)
 | 
			
		||||
    this->red_channel_->publish_state(red_perc);
 | 
			
		||||
  if (this->green_channel_ != nullptr)
 | 
			
		||||
    this->green_channel_->publish_state(green_perc);
 | 
			
		||||
  if (this->blue_channel_ != nullptr)
 | 
			
		||||
    this->blue_channel_->publish_state(blue_perc);
 | 
			
		||||
}
 | 
			
		||||
void APDS9960::read_proximity_data_(uint8_t status) {
 | 
			
		||||
#ifndef USE_SENSOR
 | 
			
		||||
  return;
 | 
			
		||||
#else
 | 
			
		||||
  if (this->proximity_sensor_ == nullptr)
 | 
			
		||||
  if (this->proximity_ == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if ((status & 0b10) == 0x00) {
 | 
			
		||||
@@ -222,8 +204,7 @@ void APDS9960::read_proximity_data_(uint8_t status) {
 | 
			
		||||
 | 
			
		||||
  float prox_perc = (prox / float(UINT8_MAX)) * 100.0f;
 | 
			
		||||
  ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc);
 | 
			
		||||
  this->proximity_sensor_->publish_state(prox_perc);
 | 
			
		||||
#endif
 | 
			
		||||
  this->proximity_->publish_state(prox_perc);
 | 
			
		||||
}
 | 
			
		||||
void APDS9960::read_gesture_data_() {
 | 
			
		||||
  if (!this->is_gesture_enabled_())
 | 
			
		||||
@@ -275,29 +256,28 @@ void APDS9960::read_gesture_data_() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APDS9960::report_gesture_(int gesture) {
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  binary_sensor::BinarySensor *bin;
 | 
			
		||||
  switch (gesture) {
 | 
			
		||||
    case 1:
 | 
			
		||||
      bin = this->up_direction_binary_sensor_;
 | 
			
		||||
      bin = this->up_direction_;
 | 
			
		||||
      this->gesture_up_started_ = false;
 | 
			
		||||
      this->gesture_down_started_ = false;
 | 
			
		||||
      ESP_LOGD(TAG, "Got gesture UP");
 | 
			
		||||
      break;
 | 
			
		||||
    case 2:
 | 
			
		||||
      bin = this->down_direction_binary_sensor_;
 | 
			
		||||
      bin = this->down_direction_;
 | 
			
		||||
      this->gesture_up_started_ = false;
 | 
			
		||||
      this->gesture_down_started_ = false;
 | 
			
		||||
      ESP_LOGD(TAG, "Got gesture DOWN");
 | 
			
		||||
      break;
 | 
			
		||||
    case 3:
 | 
			
		||||
      bin = this->left_direction_binary_sensor_;
 | 
			
		||||
      bin = this->left_direction_;
 | 
			
		||||
      this->gesture_left_started_ = false;
 | 
			
		||||
      this->gesture_right_started_ = false;
 | 
			
		||||
      ESP_LOGD(TAG, "Got gesture LEFT");
 | 
			
		||||
      break;
 | 
			
		||||
    case 4:
 | 
			
		||||
      bin = this->right_direction_binary_sensor_;
 | 
			
		||||
      bin = this->right_direction_;
 | 
			
		||||
      this->gesture_left_started_ = false;
 | 
			
		||||
      this->gesture_right_started_ = false;
 | 
			
		||||
      ESP_LOGD(TAG, "Got gesture RIGHT");
 | 
			
		||||
@@ -310,7 +290,6 @@ void APDS9960::report_gesture_(int gesture) {
 | 
			
		||||
    bin->publish_state(true);
 | 
			
		||||
    bin->publish_state(false);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
void APDS9960::process_dataset_(int up, int down, int left, int right) {
 | 
			
		||||
  /* Algorithm: (see Figure 11 in datasheet)
 | 
			
		||||
@@ -386,22 +365,10 @@ void APDS9960::process_dataset_(int up, int down, int left, int right) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
float APDS9960::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
bool APDS9960::is_proximity_enabled_() const {
 | 
			
		||||
  return
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
      this->proximity_sensor_ != nullptr
 | 
			
		||||
#else
 | 
			
		||||
      false
 | 
			
		||||
#endif
 | 
			
		||||
      || this->is_gesture_enabled_();
 | 
			
		||||
}
 | 
			
		||||
bool APDS9960::is_proximity_enabled_() const { return this->proximity_ != nullptr || this->is_gesture_enabled_(); }
 | 
			
		||||
bool APDS9960::is_gesture_enabled_() const {
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  return this->up_direction_binary_sensor_ != nullptr || this->left_direction_binary_sensor_ != nullptr ||
 | 
			
		||||
         this->down_direction_binary_sensor_ != nullptr || this->right_direction_binary_sensor_ != nullptr;
 | 
			
		||||
#else
 | 
			
		||||
  return false;
 | 
			
		||||
#endif
 | 
			
		||||
  return this->up_direction_ != nullptr || this->left_direction_ != nullptr || this->down_direction_ != nullptr ||
 | 
			
		||||
         this->right_direction_ != nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace apds9960
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +1,14 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace apds9960 {
 | 
			
		||||
 | 
			
		||||
class APDS9960 : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  SUB_SENSOR(red)
 | 
			
		||||
  SUB_SENSOR(green)
 | 
			
		||||
  SUB_SENSOR(blue)
 | 
			
		||||
  SUB_SENSOR(clear)
 | 
			
		||||
  SUB_SENSOR(proximity)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  SUB_BINARY_SENSOR(up_direction)
 | 
			
		||||
  SUB_BINARY_SENSOR(right_direction)
 | 
			
		||||
  SUB_BINARY_SENSOR(down_direction)
 | 
			
		||||
  SUB_BINARY_SENSOR(left_direction)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
@@ -43,6 +23,16 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
  void set_gesture_gain(uint8_t gain) { this->gesture_gain_ = gain; }
 | 
			
		||||
  void set_gesture_wait_time(uint8_t wait_time) { this->gesture_wait_time_ = wait_time; }
 | 
			
		||||
 | 
			
		||||
  void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; }
 | 
			
		||||
  void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; }
 | 
			
		||||
  void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; }
 | 
			
		||||
  void set_clear_channel(sensor::Sensor *clear_channel) { clear_channel_ = clear_channel; }
 | 
			
		||||
  void set_up_direction(binary_sensor::BinarySensor *up_direction) { up_direction_ = up_direction; }
 | 
			
		||||
  void set_right_direction(binary_sensor::BinarySensor *right_direction) { right_direction_ = right_direction; }
 | 
			
		||||
  void set_down_direction(binary_sensor::BinarySensor *down_direction) { down_direction_ = down_direction; }
 | 
			
		||||
  void set_left_direction(binary_sensor::BinarySensor *left_direction) { left_direction_ = left_direction; }
 | 
			
		||||
  void set_proximity(sensor::Sensor *proximity) { proximity_ = proximity; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool is_color_enabled_() const;
 | 
			
		||||
  bool is_proximity_enabled_() const;
 | 
			
		||||
@@ -60,6 +50,15 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
  uint8_t gesture_gain_;
 | 
			
		||||
  uint8_t gesture_wait_time_;
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *red_channel_{nullptr};
 | 
			
		||||
  sensor::Sensor *green_channel_{nullptr};
 | 
			
		||||
  sensor::Sensor *blue_channel_{nullptr};
 | 
			
		||||
  sensor::Sensor *clear_channel_{nullptr};
 | 
			
		||||
  binary_sensor::BinarySensor *up_direction_{nullptr};
 | 
			
		||||
  binary_sensor::BinarySensor *right_direction_{nullptr};
 | 
			
		||||
  binary_sensor::BinarySensor *down_direction_{nullptr};
 | 
			
		||||
  binary_sensor::BinarySensor *left_direction_{nullptr};
 | 
			
		||||
  sensor::Sensor *proximity_{nullptr};
 | 
			
		||||
  enum ErrorCode {
 | 
			
		||||
    NONE = 0,
 | 
			
		||||
    COMMUNICATION_FAILED,
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,19 @@ from . import APDS9960, CONF_APDS9960_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["apds9960"]
 | 
			
		||||
 | 
			
		||||
DIRECTIONS = ["up", "down", "left", "right"]
 | 
			
		||||
DIRECTIONS = {
 | 
			
		||||
    "UP": "set_up_direction",
 | 
			
		||||
    "DOWN": "set_down_direction",
 | 
			
		||||
    "LEFT": "set_left_direction",
 | 
			
		||||
    "RIGHT": "set_right_direction",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
 | 
			
		||||
    device_class=DEVICE_CLASS_MOVING
 | 
			
		||||
).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
			
		||||
        cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, lower=True),
 | 
			
		||||
        cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -21,5 +26,5 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_APDS9960_ID])
 | 
			
		||||
    var = await binary_sensor.new_binary_sensor(config)
 | 
			
		||||
    func = getattr(hub, f"set_{config[CONF_DIRECTION]}_direction_binary_sensor")
 | 
			
		||||
    func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
 | 
			
		||||
    cg.add(func(var))
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,13 @@ from . import APDS9960, CONF_APDS9960_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["apds9960"]
 | 
			
		||||
 | 
			
		||||
TYPES = ["clear", "red", "green", "blue", "proximity"]
 | 
			
		||||
TYPES = {
 | 
			
		||||
    "CLEAR": "set_clear_channel",
 | 
			
		||||
    "RED": "set_red_channel",
 | 
			
		||||
    "GREEN": "set_green_channel",
 | 
			
		||||
    "BLUE": "set_blue_channel",
 | 
			
		||||
    "PROXIMITY": "set_proximity",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
    unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
@@ -20,7 +26,7 @@ CONFIG_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
    state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_TYPE): cv.one_of(*TYPES, lower=True),
 | 
			
		||||
        cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
 | 
			
		||||
        cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
@@ -29,5 +35,5 @@ CONFIG_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_APDS9960_ID])
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
    func = getattr(hub, f"set_{config[CONF_TYPE]}_sensor")
 | 
			
		||||
    func = getattr(hub, TYPES[config[CONF_TYPE]])
 | 
			
		||||
    cg.add(func(var))
 | 
			
		||||
 
 | 
			
		||||
@@ -223,7 +223,6 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
 | 
			
		||||
  msg.assumed_state = traits.get_is_assumed_state();
 | 
			
		||||
  msg.supports_position = traits.get_supports_position();
 | 
			
		||||
  msg.supports_tilt = traits.get_supports_tilt();
 | 
			
		||||
  msg.supports_stop = traits.get_supports_stop();
 | 
			
		||||
  msg.device_class = cover->get_device_class();
 | 
			
		||||
  msg.disabled_by_default = cover->is_disabled_by_default();
 | 
			
		||||
  msg.icon = cover->get_icon();
 | 
			
		||||
@@ -979,8 +978,6 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
			
		||||
  resp.manufacturer = "Espressif";
 | 
			
		||||
#elif defined(USE_RP2040)
 | 
			
		||||
  resp.manufacturer = "Raspberry Pi";
 | 
			
		||||
#elif defined(USE_HOST)
 | 
			
		||||
  resp.manufacturer = "Host";
 | 
			
		||||
#endif
 | 
			
		||||
  resp.model = ESPHOME_BOARD;
 | 
			
		||||
#ifdef USE_DEEP_SLEEP
 | 
			
		||||
@@ -999,7 +996,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
			
		||||
                                     : bluetooth_proxy::PASSIVE_ONLY_VERSION;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version();
 | 
			
		||||
  resp.voice_assistant_version = 1;
 | 
			
		||||
#endif
 | 
			
		||||
  return resp;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,9 @@ import requests
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
from esphome import git
 | 
			
		||||
from esphome.components.packages import validate_source_shorthand
 | 
			
		||||
from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT
 | 
			
		||||
from esphome.const import CONF_REF, CONF_WIFI
 | 
			
		||||
from esphome.wizard import wizard_file
 | 
			
		||||
from esphome.yaml_util import dump
 | 
			
		||||
 | 
			
		||||
@@ -53,17 +52,6 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    validate_full_url,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _final_validate(config):
 | 
			
		||||
    full_config = fv.full_config.get()[CONF_ESPHOME]
 | 
			
		||||
    if CONF_PROJECT not in full_config:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Dashboard import requires the `esphome` -> `project` information to be provided."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = _final_validate
 | 
			
		||||
 | 
			
		||||
WIFI_CONFIG = """
 | 
			
		||||
 | 
			
		||||
wifi:
 | 
			
		||||
 
 | 
			
		||||
@@ -282,14 +282,10 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align
 | 
			
		||||
    int scan_x1, scan_y1, scan_width, scan_height;
 | 
			
		||||
    glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
      const int glyph_x_max = scan_x1 + scan_width;
 | 
			
		||||
      const int glyph_y_max = scan_y1 + scan_height;
 | 
			
		||||
      for (int glyph_x = scan_x1; glyph_x < glyph_x_max; glyph_x++) {
 | 
			
		||||
        for (int glyph_y = scan_y1; glyph_y < glyph_y_max; glyph_y++) {
 | 
			
		||||
          if (glyph.get_pixel(glyph_x, glyph_y)) {
 | 
			
		||||
            this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
 | 
			
		||||
          }
 | 
			
		||||
    for (int glyph_x = scan_x1; glyph_x < scan_x1 + scan_width; glyph_x++) {
 | 
			
		||||
      for (int glyph_y = scan_y1; glyph_y < scan_y1 + scan_height; glyph_y++) {
 | 
			
		||||
        if (glyph.get_pixel(glyph_x, glyph_y)) {
 | 
			
		||||
          this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								esphome/components/es8388/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								esphome/components/es8388/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
es8388_ns = cg.esphome_ns.namespace("es8388")
 | 
			
		||||
ES8388Component = es8388_ns.class_("ES8388Component", cg.Component, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema({cv.GenerateID(): cv.declare_id(ES8388Component)})
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x10))
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
							
								
								
									
										75
									
								
								esphome/components/es8388/es8388_component.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								esphome/components/es8388/es8388_component.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
#include "es8388_component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
#include <soc/io_mux_reg.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace es8388 {
 | 
			
		||||
 | 
			
		||||
void ES8388Component::setup() {
 | 
			
		||||
  PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
 | 
			
		||||
  WRITE_PERI_REG(PIN_CTRL, READ_PERI_REG(PIN_CTRL) & 0xFFFFFFF0);
 | 
			
		||||
 | 
			
		||||
  // mute
 | 
			
		||||
  this->write_byte(0x19, 0x04);
 | 
			
		||||
  // powerup
 | 
			
		||||
  this->write_byte(0x01, 0x50);
 | 
			
		||||
  this->write_byte(0x02, 0x00);
 | 
			
		||||
  // worker mode
 | 
			
		||||
  this->write_byte(0x08, 0x00);
 | 
			
		||||
  // DAC powerdown
 | 
			
		||||
  this->write_byte(0x04, 0xC0);
 | 
			
		||||
  // vmidsel/500k ADC/DAC idem
 | 
			
		||||
  this->write_byte(0x00, 0x12);
 | 
			
		||||
 | 
			
		||||
  // i2s 16 bits
 | 
			
		||||
  this->write_byte(0x17, 0x18);
 | 
			
		||||
  // sample freq 256
 | 
			
		||||
  this->write_byte(0x18, 0x02);
 | 
			
		||||
  // LIN2/RIN2 for mixer
 | 
			
		||||
  this->write_byte(0x26, 0x00);
 | 
			
		||||
  // left DAC to left mixer
 | 
			
		||||
  this->write_byte(0x27, 0x90);
 | 
			
		||||
  // right DAC to right mixer
 | 
			
		||||
  this->write_byte(0x2A, 0x90);
 | 
			
		||||
  // DACLRC ADCLRC idem
 | 
			
		||||
  this->write_byte(0x2B, 0x80);
 | 
			
		||||
  this->write_byte(0x2D, 0x00);
 | 
			
		||||
  // DAC volume max
 | 
			
		||||
  this->write_byte(0x1B, 0x00);
 | 
			
		||||
  this->write_byte(0x1A, 0x00);
 | 
			
		||||
 | 
			
		||||
  // ADC poweroff
 | 
			
		||||
  this->write_byte(0x03, 0xFF);
 | 
			
		||||
  // ADC amp 24dB
 | 
			
		||||
  this->write_byte(0x09, 0x88);
 | 
			
		||||
  // LINPUT1/RINPUT1
 | 
			
		||||
  this->write_byte(0x0A, 0x00);
 | 
			
		||||
  // ADC mono left
 | 
			
		||||
  this->write_byte(0x0B, 0x02);
 | 
			
		||||
  // i2S 16b
 | 
			
		||||
  this->write_byte(0x0C, 0x0C);
 | 
			
		||||
  // MCLK 256
 | 
			
		||||
  this->write_byte(0x0D, 0x02);
 | 
			
		||||
  // ADC Volume
 | 
			
		||||
  this->write_byte(0x10, 0x00);
 | 
			
		||||
  this->write_byte(0x11, 0x00);
 | 
			
		||||
  // ALC OFF
 | 
			
		||||
  this->write_byte(0x03, 0x09);
 | 
			
		||||
  this->write_byte(0x2B, 0x80);
 | 
			
		||||
 | 
			
		||||
  this->write_byte(0x02, 0xF0);
 | 
			
		||||
  delay(1);
 | 
			
		||||
  this->write_byte(0x02, 0x00);
 | 
			
		||||
  // DAC power-up LOUT1/ROUT1 enabled
 | 
			
		||||
  this->write_byte(0x04, 0x30);
 | 
			
		||||
  this->write_byte(0x03, 0x00);
 | 
			
		||||
  // DAC volume max
 | 
			
		||||
  this->write_byte(0x2E, 0x1C);
 | 
			
		||||
  this->write_byte(0x2F, 0x1C);
 | 
			
		||||
  // unmute
 | 
			
		||||
  this->write_byte(0x19, 0x00);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace es8388
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										17
									
								
								esphome/components/es8388/es8388_component.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								esphome/components/es8388/es8388_component.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace es8388 {
 | 
			
		||||
 | 
			
		||||
class ES8388Component : public Component, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::LATE - 1; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace es8388
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,207 +0,0 @@
 | 
			
		||||
#include "led_strip.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <esp_attr.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_rmt_led_strip {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32_rmt_led_strip";
 | 
			
		||||
 | 
			
		||||
static const uint8_t RMT_CLK_DIV = 2;
 | 
			
		||||
 | 
			
		||||
void ESP32RMTLEDStripLightOutput::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip...");
 | 
			
		||||
 | 
			
		||||
  size_t buffer_size = this->get_buffer_size_();
 | 
			
		||||
 | 
			
		||||
  ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
			
		||||
  this->buf_ = allocator.allocate(buffer_size);
 | 
			
		||||
  if (this->buf_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Cannot allocate LED buffer!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->effect_data_ = allocator.allocate(this->num_leds_);
 | 
			
		||||
  if (this->effect_data_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Cannot allocate effect data!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ExternalRAMAllocator<rmt_item32_t> rmt_allocator(ExternalRAMAllocator<rmt_item32_t>::ALLOW_FAILURE);
 | 
			
		||||
  this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8);  // 8 bits per byte, 1 rmt_item32_t per bit
 | 
			
		||||
 | 
			
		||||
  rmt_config_t config;
 | 
			
		||||
  memset(&config, 0, sizeof(config));
 | 
			
		||||
  config.channel = this->channel_;
 | 
			
		||||
  config.rmt_mode = RMT_MODE_TX;
 | 
			
		||||
  config.gpio_num = gpio_num_t(this->pin_);
 | 
			
		||||
  config.mem_block_num = 1;
 | 
			
		||||
  config.clk_div = RMT_CLK_DIV;
 | 
			
		||||
  config.tx_config.loop_en = false;
 | 
			
		||||
  config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
 | 
			
		||||
  config.tx_config.carrier_en = false;
 | 
			
		||||
  config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
 | 
			
		||||
  config.tx_config.idle_output_en = true;
 | 
			
		||||
 | 
			
		||||
  if (rmt_config(&config) != ESP_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "Cannot initialize RMT!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (rmt_driver_install(config.channel, 0, 0) != ESP_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "Cannot install RMT driver!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high,
 | 
			
		||||
                                                 uint32_t bit1_low) {
 | 
			
		||||
  float ratio = (float) APB_CLK_FREQ / RMT_CLK_DIV / 1e09f;
 | 
			
		||||
 | 
			
		||||
  // 0-bit
 | 
			
		||||
  this->bit0_.duration0 = (uint32_t) (ratio * bit0_high);
 | 
			
		||||
  this->bit0_.level0 = 1;
 | 
			
		||||
  this->bit0_.duration1 = (uint32_t) (ratio * bit0_low);
 | 
			
		||||
  this->bit0_.level1 = 0;
 | 
			
		||||
  // 1-bit
 | 
			
		||||
  this->bit1_.duration0 = (uint32_t) (ratio * bit1_high);
 | 
			
		||||
  this->bit1_.level0 = 1;
 | 
			
		||||
  this->bit1_.duration1 = (uint32_t) (ratio * bit1_low);
 | 
			
		||||
  this->bit1_.level1 = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
 | 
			
		||||
  // protect from refreshing too often
 | 
			
		||||
  uint32_t now = micros();
 | 
			
		||||
  if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
 | 
			
		||||
    // try again next loop iteration, so that this change won't get lost
 | 
			
		||||
    this->schedule_show();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->last_refresh_ = now;
 | 
			
		||||
  this->mark_shown_();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGVV(TAG, "Writing RGB values to bus...");
 | 
			
		||||
 | 
			
		||||
  if (rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)) != ESP_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "RMT TX timeout");
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  delayMicroseconds(50);
 | 
			
		||||
 | 
			
		||||
  size_t buffer_size = this->get_buffer_size_();
 | 
			
		||||
 | 
			
		||||
  size_t size = 0;
 | 
			
		||||
  size_t len = 0;
 | 
			
		||||
  uint8_t *psrc = this->buf_;
 | 
			
		||||
  rmt_item32_t *pdest = this->rmt_buf_;
 | 
			
		||||
  while (size < buffer_size) {
 | 
			
		||||
    uint8_t b = *psrc;
 | 
			
		||||
    for (int i = 0; i < 8; i++) {
 | 
			
		||||
      pdest->val = b & (1 << (7 - i)) ? this->bit1_.val : this->bit0_.val;
 | 
			
		||||
      pdest++;
 | 
			
		||||
      len++;
 | 
			
		||||
    }
 | 
			
		||||
    size++;
 | 
			
		||||
    psrc++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "RMT TX error");
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index) const {
 | 
			
		||||
  int32_t r = 0, g = 0, b = 0;
 | 
			
		||||
  switch (this->rgb_order_) {
 | 
			
		||||
    case ORDER_RGB:
 | 
			
		||||
      r = 0;
 | 
			
		||||
      g = 1;
 | 
			
		||||
      b = 2;
 | 
			
		||||
      break;
 | 
			
		||||
    case ORDER_RBG:
 | 
			
		||||
      r = 0;
 | 
			
		||||
      g = 2;
 | 
			
		||||
      b = 1;
 | 
			
		||||
      break;
 | 
			
		||||
    case ORDER_GRB:
 | 
			
		||||
      r = 1;
 | 
			
		||||
      g = 0;
 | 
			
		||||
      b = 2;
 | 
			
		||||
      break;
 | 
			
		||||
    case ORDER_GBR:
 | 
			
		||||
      r = 2;
 | 
			
		||||
      g = 0;
 | 
			
		||||
      b = 1;
 | 
			
		||||
      break;
 | 
			
		||||
    case ORDER_BGR:
 | 
			
		||||
      r = 2;
 | 
			
		||||
      g = 1;
 | 
			
		||||
      b = 0;
 | 
			
		||||
      break;
 | 
			
		||||
    case ORDER_BRG:
 | 
			
		||||
      r = 1;
 | 
			
		||||
      g = 2;
 | 
			
		||||
      b = 0;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t multiplier = this->is_rgbw_ ? 4 : 3;
 | 
			
		||||
  return {this->buf_ + (index * multiplier) + r,
 | 
			
		||||
          this->buf_ + (index * multiplier) + g,
 | 
			
		||||
          this->buf_ + (index * multiplier) + b,
 | 
			
		||||
          this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr,
 | 
			
		||||
          &this->effect_data_[index],
 | 
			
		||||
          &this->correction_};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP32RMTLEDStripLightOutput::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Channel: %u", this->channel_);
 | 
			
		||||
  const char *rgb_order;
 | 
			
		||||
  switch (this->rgb_order_) {
 | 
			
		||||
    case ORDER_RGB:
 | 
			
		||||
      rgb_order = "RGB";
 | 
			
		||||
      break;
 | 
			
		||||
    case ORDER_RBG:
 | 
			
		||||
      rgb_order = "RBG";
 | 
			
		||||
      break;
 | 
			
		||||
    case ORDER_GRB:
 | 
			
		||||
      rgb_order = "GRB";
 | 
			
		||||
      break;
 | 
			
		||||
    case ORDER_GBR:
 | 
			
		||||
      rgb_order = "GBR";
 | 
			
		||||
      break;
 | 
			
		||||
    case ORDER_BGR:
 | 
			
		||||
      rgb_order = "BGR";
 | 
			
		||||
      break;
 | 
			
		||||
    case ORDER_BRG:
 | 
			
		||||
      rgb_order = "BRG";
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      rgb_order = "UNKNOWN";
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  RGB Order: %s", rgb_order);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Max refresh rate: %u", *this->max_refresh_rate_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Number of LEDs: %u", this->num_leds_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ESP32RMTLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_rmt_led_strip
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -1,87 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/light/addressable_light.h"
 | 
			
		||||
#include "esphome/components/light/light_output.h"
 | 
			
		||||
#include "esphome/core/color.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#include <driver/gpio.h>
 | 
			
		||||
#include <driver/rmt.h>
 | 
			
		||||
#include <esp_err.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_rmt_led_strip {
 | 
			
		||||
 | 
			
		||||
enum RGBOrder : uint8_t {
 | 
			
		||||
  ORDER_RGB,
 | 
			
		||||
  ORDER_RBG,
 | 
			
		||||
  ORDER_GRB,
 | 
			
		||||
  ORDER_GBR,
 | 
			
		||||
  ORDER_BGR,
 | 
			
		||||
  ORDER_BRG,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void write_state(light::LightState *state) override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  int32_t size() const override { return this->num_leds_; }
 | 
			
		||||
  light::LightTraits get_traits() override {
 | 
			
		||||
    auto traits = light::LightTraits();
 | 
			
		||||
    if (this->is_rgbw_) {
 | 
			
		||||
      traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::RGB_WHITE});
 | 
			
		||||
    } else {
 | 
			
		||||
      traits.set_supported_color_modes({light::ColorMode::RGB});
 | 
			
		||||
    }
 | 
			
		||||
    return traits;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_pin(uint8_t pin) { this->pin_ = pin; }
 | 
			
		||||
  void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
 | 
			
		||||
  void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
 | 
			
		||||
 | 
			
		||||
  /// Set a maximum refresh rate in µs as some lights do not like being updated too often.
 | 
			
		||||
  void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
 | 
			
		||||
 | 
			
		||||
  void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low);
 | 
			
		||||
 | 
			
		||||
  void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
 | 
			
		||||
  void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; }
 | 
			
		||||
 | 
			
		||||
  void clear_effect_data() override {
 | 
			
		||||
    for (int i = 0; i < this->size(); i++)
 | 
			
		||||
      this->effect_data_[i] = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  light::ESPColorView get_view_internal(int32_t index) const override;
 | 
			
		||||
 | 
			
		||||
  size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); }
 | 
			
		||||
 | 
			
		||||
  uint8_t *buf_{nullptr};
 | 
			
		||||
  uint8_t *effect_data_{nullptr};
 | 
			
		||||
  rmt_item32_t *rmt_buf_{nullptr};
 | 
			
		||||
 | 
			
		||||
  uint8_t pin_;
 | 
			
		||||
  uint16_t num_leds_;
 | 
			
		||||
  bool is_rgbw_;
 | 
			
		||||
 | 
			
		||||
  rmt_item32_t bit0_, bit1_;
 | 
			
		||||
  RGBOrder rgb_order_;
 | 
			
		||||
  rmt_channel_t channel_;
 | 
			
		||||
 | 
			
		||||
  uint32_t last_refresh_{0};
 | 
			
		||||
  optional<uint32_t> max_refresh_rate_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_rmt_led_strip
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -1,151 +0,0 @@
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import esp32, light
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CHIPSET,
 | 
			
		||||
    CONF_MAX_REFRESH_RATE,
 | 
			
		||||
    CONF_NUM_LEDS,
 | 
			
		||||
    CONF_OUTPUT_ID,
 | 
			
		||||
    CONF_PIN,
 | 
			
		||||
    CONF_RGB_ORDER,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
 | 
			
		||||
esp32_rmt_led_strip_ns = cg.esphome_ns.namespace("esp32_rmt_led_strip")
 | 
			
		||||
ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_(
 | 
			
		||||
    "ESP32RMTLEDStripLightOutput", light.AddressableLight
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
 | 
			
		||||
 | 
			
		||||
RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder")
 | 
			
		||||
 | 
			
		||||
RGB_ORDERS = {
 | 
			
		||||
    "RGB": RGBOrder.ORDER_RGB,
 | 
			
		||||
    "RBG": RGBOrder.ORDER_RBG,
 | 
			
		||||
    "GRB": RGBOrder.ORDER_GRB,
 | 
			
		||||
    "GBR": RGBOrder.ORDER_GBR,
 | 
			
		||||
    "BGR": RGBOrder.ORDER_BGR,
 | 
			
		||||
    "BRG": RGBOrder.ORDER_BRG,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass
 | 
			
		||||
class LEDStripTimings:
 | 
			
		||||
    bit0_high: int
 | 
			
		||||
    bit0_low: int
 | 
			
		||||
    bit1_high: int
 | 
			
		||||
    bit1_low: int
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CHIPSETS = {
 | 
			
		||||
    "WS2812": LEDStripTimings(400, 1000, 1000, 400),
 | 
			
		||||
    "SK6812": LEDStripTimings(300, 900, 600, 600),
 | 
			
		||||
    "APA106": LEDStripTimings(350, 1360, 1360, 350),
 | 
			
		||||
    "SM16703": LEDStripTimings(300, 900, 1360, 350),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF_IS_RGBW = "is_rgbw"
 | 
			
		||||
CONF_BIT0_HIGH = "bit0_high"
 | 
			
		||||
CONF_BIT0_LOW = "bit0_low"
 | 
			
		||||
CONF_BIT1_HIGH = "bit1_high"
 | 
			
		||||
CONF_BIT1_LOW = "bit1_low"
 | 
			
		||||
CONF_RMT_CHANNEL = "rmt_channel"
 | 
			
		||||
 | 
			
		||||
RMT_CHANNELS = {
 | 
			
		||||
    esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7],
 | 
			
		||||
    esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3],
 | 
			
		||||
    esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3],
 | 
			
		||||
    esp32.const.VARIANT_ESP32C3: [0, 1],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _validate_rmt_channel(value):
 | 
			
		||||
    variant = esp32.get_esp32_variant()
 | 
			
		||||
    if variant not in RMT_CHANNELS:
 | 
			
		||||
        raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.")
 | 
			
		||||
    if value not in RMT_CHANNELS[variant]:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"RMT channel {value} is not supported for ESP32 variant {variant}."
 | 
			
		||||
        )
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    light.ADDRESSABLE_LIGHT_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput),
 | 
			
		||||
            cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
 | 
			
		||||
            cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
 | 
			
		||||
            cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
 | 
			
		||||
            cv.Required(CONF_RMT_CHANNEL): _validate_rmt_channel,
 | 
			
		||||
            cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
 | 
			
		||||
            cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
 | 
			
		||||
            cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
 | 
			
		||||
            cv.Inclusive(
 | 
			
		||||
                CONF_BIT0_HIGH,
 | 
			
		||||
                "custom",
 | 
			
		||||
            ): cv.positive_time_period_microseconds,
 | 
			
		||||
            cv.Inclusive(
 | 
			
		||||
                CONF_BIT0_LOW,
 | 
			
		||||
                "custom",
 | 
			
		||||
            ): cv.positive_time_period_microseconds,
 | 
			
		||||
            cv.Inclusive(
 | 
			
		||||
                CONF_BIT1_HIGH,
 | 
			
		||||
                "custom",
 | 
			
		||||
            ): cv.positive_time_period_microseconds,
 | 
			
		||||
            cv.Inclusive(
 | 
			
		||||
                CONF_BIT1_LOW,
 | 
			
		||||
                "custom",
 | 
			
		||||
            ): cv.positive_time_period_microseconds,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
 | 
			
		||||
    await light.register_light(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_num_leds(config[CONF_NUM_LEDS]))
 | 
			
		||||
    cg.add(var.set_pin(config[CONF_PIN]))
 | 
			
		||||
 | 
			
		||||
    if CONF_MAX_REFRESH_RATE in config:
 | 
			
		||||
        cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
 | 
			
		||||
 | 
			
		||||
    if CONF_CHIPSET in config:
 | 
			
		||||
        chipset = CHIPSETS[config[CONF_CHIPSET]]
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_led_params(
 | 
			
		||||
                chipset.bit0_high,
 | 
			
		||||
                chipset.bit0_low,
 | 
			
		||||
                chipset.bit1_high,
 | 
			
		||||
                chipset.bit1_low,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_led_params(
 | 
			
		||||
                config[CONF_BIT0_HIGH],
 | 
			
		||||
                config[CONF_BIT0_LOW],
 | 
			
		||||
                config[CONF_BIT1_HIGH],
 | 
			
		||||
                config[CONF_BIT1_LOW],
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
 | 
			
		||||
    cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
 | 
			
		||||
 | 
			
		||||
    cg.add(
 | 
			
		||||
        var.set_rmt_channel(
 | 
			
		||||
            getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}")
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
@@ -106,18 +106,20 @@ void EZOSensor::loop() {
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", &buf[1], EZO_COMMAND_TYPE_STRINGS[to_run->command_type]);
 | 
			
		||||
  ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", buf, EZO_COMMAND_TYPE_STRINGS[to_run->command_type]);
 | 
			
		||||
 | 
			
		||||
  if (buf[0] == 1) {
 | 
			
		||||
  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: {
 | 
			
		||||
          // some sensors return multiple comma-separated values, terminate string after first one
 | 
			
		||||
          int start_location = 0;
 | 
			
		||||
          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
			
		||||
            payload.erase(start_location);
 | 
			
		||||
          }
 | 
			
		||||
          auto val = parse_number<float>(payload);
 | 
			
		||||
          if (!val.has_value()) {
 | 
			
		||||
            ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
 | 
			
		||||
@@ -152,10 +154,7 @@ void EZOSensor::loop() {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_T: {
 | 
			
		||||
          int start_location = 0;
 | 
			
		||||
          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
			
		||||
            this->t_callback_.call(payload.substr(start_location + 1));
 | 
			
		||||
          }
 | 
			
		||||
          this->t_callback_.call(payload);
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_CUSTOM: {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,40 +0,0 @@
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.const import CONF_ID, CONF_VOLTAGE
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
gp8403_ns = cg.esphome_ns.namespace("gp8403")
 | 
			
		||||
GP8403 = gp8403_ns.class_("GP8403", cg.Component, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
GP8403Voltage = gp8403_ns.enum("GP8403Voltage")
 | 
			
		||||
 | 
			
		||||
CONF_GP8403_ID = "gp8403_id"
 | 
			
		||||
 | 
			
		||||
VOLTAGES = {
 | 
			
		||||
    "5V": GP8403Voltage.GP8403_VOLTAGE_5V,
 | 
			
		||||
    "10V": GP8403Voltage.GP8403_VOLTAGE_10V,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(GP8403),
 | 
			
		||||
            cv.Required(CONF_VOLTAGE): cv.enum(VOLTAGES, upper=True),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x58))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_voltage(config[CONF_VOLTAGE]))
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
#include "gp8403.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace gp8403 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "gp8403";
 | 
			
		||||
 | 
			
		||||
static const uint8_t RANGE_REGISTER = 0x01;
 | 
			
		||||
 | 
			
		||||
void GP8403::setup() { this->write_register(RANGE_REGISTER, (uint8_t *) (&this->voltage_), 1); }
 | 
			
		||||
 | 
			
		||||
void GP8403::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "GP8403:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Voltage: %dV", this->voltage_ == GP8403_VOLTAGE_5V ? 5 : 10);
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace gp8403
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace gp8403 {
 | 
			
		||||
 | 
			
		||||
enum GP8403Voltage {
 | 
			
		||||
  GP8403_VOLTAGE_5V = 0x00,
 | 
			
		||||
  GP8403_VOLTAGE_10V = 0x11,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class GP8403 : public Component, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
  void set_voltage(gp8403::GP8403Voltage voltage) { this->voltage_ = voltage; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  GP8403Voltage voltage_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace gp8403
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from esphome.components import i2c, output
 | 
			
		||||
from esphome.const import CONF_ID, CONF_CHANNEL
 | 
			
		||||
 | 
			
		||||
from .. import gp8403_ns, GP8403, CONF_GP8403_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["gp8403"]
 | 
			
		||||
 | 
			
		||||
GP8403Output = gp8403_ns.class_(
 | 
			
		||||
    "GP8403Output", cg.Component, i2c.I2CDevice, output.FloatOutput
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(GP8403Output),
 | 
			
		||||
        cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403),
 | 
			
		||||
        cv.Required(CONF_CHANNEL): cv.one_of(0, 1),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await output.register_output(var, config)
 | 
			
		||||
 | 
			
		||||
    await cg.register_parented(var, config[CONF_GP8403_ID])
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_channel(config[CONF_CHANNEL]))
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
#include "gp8403_output.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace gp8403 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "gp8403.output";
 | 
			
		||||
 | 
			
		||||
static const uint8_t OUTPUT_REGISTER = 0x02;
 | 
			
		||||
 | 
			
		||||
void GP8403Output::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "GP8403 Output:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Channel: %u", this->channel_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GP8403Output::write_state(float state) {
 | 
			
		||||
  uint16_t value = ((uint16_t) (state * 4095)) << 4;
 | 
			
		||||
  i2c::ErrorCode err = this->parent_->write_register(OUTPUT_REGISTER + (2 * this->channel_), (uint8_t *) &value, 2);
 | 
			
		||||
  if (err != i2c::ERROR_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "Error writing to GP8403, code %d", err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace gp8403
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/output/float_output.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/gp8403/gp8403.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace gp8403 {
 | 
			
		||||
 | 
			
		||||
class GP8403Output : public Component, public output::FloatOutput, public Parented<GP8403> {
 | 
			
		||||
 public:
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA - 1; }
 | 
			
		||||
 | 
			
		||||
  void set_channel(uint8_t channel) { this->channel_ = channel; }
 | 
			
		||||
 | 
			
		||||
  void write_state(float state) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t channel_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace gp8403
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,38 +0,0 @@
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    KEY_CORE,
 | 
			
		||||
    KEY_FRAMEWORK_VERSION,
 | 
			
		||||
    KEY_TARGET_FRAMEWORK,
 | 
			
		||||
    KEY_TARGET_PLATFORM,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from .const import KEY_HOST
 | 
			
		||||
 | 
			
		||||
# force import gpio to register pin schema
 | 
			
		||||
from .gpio import host_pin_to_code  # noqa
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
AUTO_LOAD = ["network"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_core_data(config):
 | 
			
		||||
    CORE.data[KEY_HOST] = {}
 | 
			
		||||
    CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "host"
 | 
			
		||||
    CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "host"
 | 
			
		||||
    CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(1, 0, 0)
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema({}),
 | 
			
		||||
    set_core_data,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add_build_flag("-DUSE_HOST")
 | 
			
		||||
    cg.add_define("ESPHOME_BOARD", "host")
 | 
			
		||||
    cg.add_platformio_option("platform", "platformio/native")
 | 
			
		||||
@@ -1,5 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
KEY_HOST = "host"
 | 
			
		||||
 | 
			
		||||
host_ns = cg.esphome_ns.namespace("host")
 | 
			
		||||
@@ -1,77 +0,0 @@
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "preferences.h"
 | 
			
		||||
 | 
			
		||||
#include <sched.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#include <cmath>
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR HOT yield() { ::sched_yield(); }
 | 
			
		||||
uint32_t IRAM_ATTR HOT millis() {
 | 
			
		||||
  struct timespec spec;
 | 
			
		||||
  clock_gettime(CLOCK_MONOTONIC, &spec);
 | 
			
		||||
  time_t seconds = spec.tv_sec;
 | 
			
		||||
  uint32_t ms = round(spec.tv_nsec / 1e6);
 | 
			
		||||
  return ((uint32_t) seconds) * 1000U + ms;
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR HOT delay(uint32_t ms) {
 | 
			
		||||
  struct timespec ts;
 | 
			
		||||
  ts.tv_sec = ms / 1000;
 | 
			
		||||
  ts.tv_nsec = (ms % 1000) * 1000000;
 | 
			
		||||
  int res;
 | 
			
		||||
  do {
 | 
			
		||||
    res = nanosleep(&ts, &ts);
 | 
			
		||||
  } while (res != 0 && errno == EINTR);
 | 
			
		||||
}
 | 
			
		||||
uint32_t IRAM_ATTR HOT micros() {
 | 
			
		||||
  struct timespec spec;
 | 
			
		||||
  clock_gettime(CLOCK_MONOTONIC, &spec);
 | 
			
		||||
  time_t seconds = spec.tv_sec;
 | 
			
		||||
  uint32_t us = round(spec.tv_nsec / 1e3);
 | 
			
		||||
  return ((uint32_t) seconds) * 1000000U + us;
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) {
 | 
			
		||||
  struct timespec ts;
 | 
			
		||||
  ts.tv_sec = us / 1000000U;
 | 
			
		||||
  ts.tv_nsec = (us % 1000000U) * 1000U;
 | 
			
		||||
  int res;
 | 
			
		||||
  do {
 | 
			
		||||
    res = nanosleep(&ts, &ts);
 | 
			
		||||
  } while (res != 0 && errno == EINTR);
 | 
			
		||||
}
 | 
			
		||||
void arch_restart() { exit(0); }
 | 
			
		||||
void arch_init() {
 | 
			
		||||
  // pass
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR HOT arch_feed_wdt() {
 | 
			
		||||
  // pass
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
 | 
			
		||||
uint32_t arch_get_cpu_cycle_count() {
 | 
			
		||||
  struct timespec spec;
 | 
			
		||||
  clock_gettime(CLOCK_MONOTONIC, &spec);
 | 
			
		||||
  time_t seconds = spec.tv_sec;
 | 
			
		||||
  uint32_t us = spec.tv_nsec;
 | 
			
		||||
  return ((uint32_t) seconds) * 1000000000U + us;
 | 
			
		||||
}
 | 
			
		||||
uint32_t arch_get_cpu_freq_hz() { return 1000000000U; }
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
void setup();
 | 
			
		||||
void loop();
 | 
			
		||||
int main() {
 | 
			
		||||
  esphome::host::setup_preferences();
 | 
			
		||||
  setup();
 | 
			
		||||
  while (true) {
 | 
			
		||||
    loop();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif  // USE_HOST
 | 
			
		||||
@@ -1,59 +0,0 @@
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
 | 
			
		||||
#include "gpio.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace host {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "host";
 | 
			
		||||
 | 
			
		||||
struct ISRPinArg {
 | 
			
		||||
  uint8_t pin;
 | 
			
		||||
  bool inverted;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ISRInternalGPIOPin HostGPIOPin::to_isr() const {
 | 
			
		||||
  auto *arg = new ISRPinArg{};  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  arg->pin = pin_;
 | 
			
		||||
  arg->inverted = inverted_;
 | 
			
		||||
  return ISRInternalGPIOPin((void *) arg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HostGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
 | 
			
		||||
  ESP_LOGD(TAG, "Attaching interrupt %p to pin %d and mode %d", func, pin_, (uint32_t) type);
 | 
			
		||||
}
 | 
			
		||||
void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); }
 | 
			
		||||
 | 
			
		||||
std::string HostGPIOPin::dump_summary() const {
 | 
			
		||||
  char buffer[32];
 | 
			
		||||
  snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
 | 
			
		||||
  return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HostGPIOPin::digital_read() { return inverted_; }
 | 
			
		||||
void HostGPIOPin::digital_write(bool value) {
 | 
			
		||||
  // pass
 | 
			
		||||
  ESP_LOGD(TAG, "Setting pin %d to %s", pin_, value != inverted_ ? "HIGH" : "LOW");
 | 
			
		||||
}
 | 
			
		||||
void HostGPIOPin::detach_interrupt() const {}
 | 
			
		||||
 | 
			
		||||
}  // namespace host
 | 
			
		||||
 | 
			
		||||
using namespace host;
 | 
			
		||||
 | 
			
		||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  return arg->inverted;
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
 | 
			
		||||
  // pass
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  ESP_LOGD(TAG, "Clearing interrupt for pin %d", arg->pin);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_HOST
 | 
			
		||||
@@ -1,37 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace host {
 | 
			
		||||
 | 
			
		||||
class HostGPIOPin : public InternalGPIOPin {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_pin(uint8_t pin) { pin_ = pin; }
 | 
			
		||||
  void set_inverted(bool inverted) { inverted_ = inverted; }
 | 
			
		||||
  void set_flags(gpio::Flags flags) { flags_ = flags; }
 | 
			
		||||
 | 
			
		||||
  void setup() override { pin_mode(flags_); }
 | 
			
		||||
  void pin_mode(gpio::Flags flags) override;
 | 
			
		||||
  bool digital_read() override;
 | 
			
		||||
  void digital_write(bool value) override;
 | 
			
		||||
  std::string dump_summary() const override;
 | 
			
		||||
  void detach_interrupt() const override;
 | 
			
		||||
  ISRInternalGPIOPin to_isr() const override;
 | 
			
		||||
  uint8_t get_pin() const override { return pin_; }
 | 
			
		||||
  bool is_inverted() const override { return inverted_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
 | 
			
		||||
 | 
			
		||||
  uint8_t pin_;
 | 
			
		||||
  bool inverted_;
 | 
			
		||||
  gpio::Flags flags_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace host
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_HOST
 | 
			
		||||
@@ -1,73 +0,0 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INPUT,
 | 
			
		||||
    CONF_INVERTED,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_OPEN_DRAIN,
 | 
			
		||||
    CONF_OUTPUT,
 | 
			
		||||
    CONF_PULLDOWN,
 | 
			
		||||
    CONF_PULLUP,
 | 
			
		||||
)
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from .const import host_ns
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
HostGPIOPin = host_ns.class_("HostGPIOPin", cg.InternalGPIOPin)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _translate_pin(value):
 | 
			
		||||
    if isinstance(value, dict) or value is None:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "This variable only supports pin numbers, not full pin schemas "
 | 
			
		||||
            "(with inverted and mode)."
 | 
			
		||||
        )
 | 
			
		||||
    if isinstance(value, int):
 | 
			
		||||
        return value
 | 
			
		||||
    try:
 | 
			
		||||
        return int(value)
 | 
			
		||||
    except ValueError:
 | 
			
		||||
        pass
 | 
			
		||||
    if value.startswith("GPIO"):
 | 
			
		||||
        return cv.int_(value[len("GPIO") :].strip())
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_gpio_pin(value):
 | 
			
		||||
    return _translate_pin(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
HOST_PIN_SCHEMA = cv.All(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(HostGPIOPin),
 | 
			
		||||
        cv.Required(CONF_NUMBER): validate_gpio_pin,
 | 
			
		||||
        cv.Optional(CONF_MODE, default={}): cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_INPUT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_PULLUP, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_PULLDOWN, default=False): cv.boolean,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_INVERTED, default=False): cv.boolean,
 | 
			
		||||
    },
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pins.PIN_SCHEMA_REGISTRY.register("host", HOST_PIN_SCHEMA)
 | 
			
		||||
async def host_pin_to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    num = config[CONF_NUMBER]
 | 
			
		||||
    cg.add(var.set_pin(num))
 | 
			
		||||
    cg.add(var.set_inverted(config[CONF_INVERTED]))
 | 
			
		||||
    cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
 | 
			
		||||
    return var
 | 
			
		||||
@@ -1,36 +0,0 @@
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
 | 
			
		||||
#include "preferences.h"
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include "esphome/core/preferences.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace host {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "host.preferences";
 | 
			
		||||
 | 
			
		||||
class HostPreferences : public ESPPreferences {
 | 
			
		||||
 public:
 | 
			
		||||
  ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { return {}; }
 | 
			
		||||
 | 
			
		||||
  ESPPreferenceObject make_preference(size_t length, uint32_t type) override { return {}; }
 | 
			
		||||
 | 
			
		||||
  bool sync() override { return true; }
 | 
			
		||||
  bool reset() override { return true; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void setup_preferences() {
 | 
			
		||||
  auto *pref = new HostPreferences();  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  global_preferences = pref;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace host
 | 
			
		||||
 | 
			
		||||
ESPPreferences *global_preferences;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_HOST
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace host {
 | 
			
		||||
 | 
			
		||||
void setup_preferences();
 | 
			
		||||
 | 
			
		||||
}  // namespace host
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_HOST
 | 
			
		||||
@@ -42,8 +42,8 @@ I2S_PORTS = {
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(I2SAudioComponent),
 | 
			
		||||
        cv.Required(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number,
 | 
			
		||||
        cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number,
 | 
			
		||||
        cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -66,6 +66,5 @@ async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN]))
 | 
			
		||||
    cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN]))
 | 
			
		||||
    if CONF_I2S_BCLK_PIN in config:
 | 
			
		||||
        cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN]))
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,15 @@ class I2SAudioComponent : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
 | 
			
		||||
  void register_audio_in(I2SAudioIn *in) {
 | 
			
		||||
    this->audio_in_ = in;
 | 
			
		||||
    in->set_parent(this);
 | 
			
		||||
  }
 | 
			
		||||
  void register_audio_out(I2SAudioOut *out) {
 | 
			
		||||
    this->audio_out_ = out;
 | 
			
		||||
    out->set_parent(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  i2s_pin_config_t get_pin_config() const {
 | 
			
		||||
    return {
 | 
			
		||||
        .mck_io_num = I2S_PIN_NO_CHANGE,
 | 
			
		||||
@@ -29,8 +38,8 @@ class I2SAudioComponent : public Component {
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_bclk_pin(int pin) { this->bclk_pin_ = pin; }
 | 
			
		||||
  void set_lrclk_pin(int pin) { this->lrclk_pin_ = pin; }
 | 
			
		||||
  void set_bclk_pin(uint8_t pin) { this->bclk_pin_ = pin; }
 | 
			
		||||
  void set_lrclk_pin(uint8_t pin) { this->lrclk_pin_ = pin; }
 | 
			
		||||
 | 
			
		||||
  void lock() { this->lock_.lock(); }
 | 
			
		||||
  bool try_lock() { return this->lock_.try_lock(); }
 | 
			
		||||
@@ -44,8 +53,8 @@ class I2SAudioComponent : public Component {
 | 
			
		||||
  I2SAudioIn *audio_in_{nullptr};
 | 
			
		||||
  I2SAudioOut *audio_out_{nullptr};
 | 
			
		||||
 | 
			
		||||
  int bclk_pin_{I2S_PIN_NO_CHANGE};
 | 
			
		||||
  int lrclk_pin_;
 | 
			
		||||
  uint8_t bclk_pin_;
 | 
			
		||||
  uint8_t lrclk_pin_;
 | 
			
		||||
  i2s_port_t port_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -84,7 +84,8 @@ async def to_code(config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await media_player.register_media_player(var, config)
 | 
			
		||||
 | 
			
		||||
    await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_I2S_AUDIO_ID])
 | 
			
		||||
    cg.add(parent.register_audio_out(var))
 | 
			
		||||
 | 
			
		||||
    if config[CONF_DAC_TYPE] == "internal":
 | 
			
		||||
        cg.add(var.set_internal_dac_mode(config[CONF_MODE]))
 | 
			
		||||
@@ -97,5 +98,5 @@ async def to_code(config):
 | 
			
		||||
 | 
			
		||||
    cg.add_library("WiFiClientSecure", None)
 | 
			
		||||
    cg.add_library("HTTPClient", None)
 | 
			
		||||
    cg.add_library("esphome/ESP32-audioI2S", "2.0.7")
 | 
			
		||||
    cg.add_library("esphome/ESP32-audioI2S", "2.0.6")
 | 
			
		||||
    cg.add_build_flag("-DAUDIO_NO_SD_FS")
 | 
			
		||||
 
 | 
			
		||||
@@ -22,14 +22,14 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
 | 
			
		||||
      this->start();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (this->i2s_state_ != I2S_STATE_RUNNING) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (call.get_volume().has_value()) {
 | 
			
		||||
    this->volume = call.get_volume().value();
 | 
			
		||||
    this->set_volume_(volume);
 | 
			
		||||
    this->unmute_();
 | 
			
		||||
  }
 | 
			
		||||
  if (this->i2s_state_ != I2S_STATE_RUNNING) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (call.get_command().has_value()) {
 | 
			
		||||
    switch (call.get_command().value()) {
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_PLAY:
 | 
			
		||||
@@ -97,8 +97,7 @@ void I2SAudioMediaPlayer::unmute_() {
 | 
			
		||||
  this->muted_ = false;
 | 
			
		||||
}
 | 
			
		||||
void I2SAudioMediaPlayer::set_volume_(float volume, bool publish) {
 | 
			
		||||
  if (this->audio_ != nullptr)
 | 
			
		||||
    this->audio_->setVolume(remap<uint8_t, float>(volume, 0.0f, 1.0f, 0, 21));
 | 
			
		||||
  this->audio_->setVolume(remap<uint8_t, float>(volume, 0.0f, 1.0f, 0, 21));
 | 
			
		||||
  if (publish)
 | 
			
		||||
    this->volume = volume;
 | 
			
		||||
}
 | 
			
		||||
@@ -133,7 +132,7 @@ void I2SAudioMediaPlayer::play_() {
 | 
			
		||||
 | 
			
		||||
void I2SAudioMediaPlayer::start() { this->i2s_state_ = I2S_STATE_STARTING; }
 | 
			
		||||
void I2SAudioMediaPlayer::start_() {
 | 
			
		||||
  if (!this->parent_->try_lock()) {
 | 
			
		||||
  if (this->parent_->try_lock()) {
 | 
			
		||||
    return;  // Waiting for another i2s to return lock
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -142,7 +141,7 @@ void I2SAudioMediaPlayer::start_() {
 | 
			
		||||
    this->audio_ = make_unique<Audio>(true, this->internal_dac_mode_, this->parent_->get_port());
 | 
			
		||||
  } else {
 | 
			
		||||
#endif
 | 
			
		||||
    this->audio_ = make_unique<Audio>(false, 3, this->parent_->get_port());
 | 
			
		||||
    this->audio_ = make_unique<Audio>(false, I2S_DAC_CHANNEL_BOTH_EN, this->parent_->get_port());
 | 
			
		||||
 | 
			
		||||
    i2s_pin_config_t pin_config = this->parent_->get_pin_config();
 | 
			
		||||
    pin_config.data_out_num = this->dout_pin_;
 | 
			
		||||
@@ -156,26 +155,15 @@ void I2SAudioMediaPlayer::start_() {
 | 
			
		||||
#if SOC_I2S_SUPPORTS_DAC
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  this->i2s_state_ = I2S_STATE_RUNNING;
 | 
			
		||||
  this->high_freq_.start();
 | 
			
		||||
  this->audio_->setVolume(remap<uint8_t, float>(this->volume, 0.0f, 1.0f, 0, 21));
 | 
			
		||||
  if (this->current_url_.has_value()) {
 | 
			
		||||
    this->audio_->connecttohost(this->current_url_.value().c_str());
 | 
			
		||||
    this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
 | 
			
		||||
    this->publish_state();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void I2SAudioMediaPlayer::stop() {
 | 
			
		||||
  if (this->i2s_state_ == I2S_STATE_STOPPED) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->i2s_state_ == I2S_STATE_STARTING) {
 | 
			
		||||
    this->i2s_state_ = I2S_STATE_STOPPED;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->i2s_state_ = I2S_STATE_STOPPING;
 | 
			
		||||
}
 | 
			
		||||
void I2SAudioMediaPlayer::stop() { this->i2s_state_ = I2S_STATE_STOPPING; }
 | 
			
		||||
void I2SAudioMediaPlayer::stop_() {
 | 
			
		||||
  if (this->audio_->isRunning()) {
 | 
			
		||||
    this->audio_->stopSong();
 | 
			
		||||
@@ -219,12 +207,6 @@ void I2SAudioMediaPlayer::dump_config() {
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
#endif
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  External DAC channels: %d", this->external_dac_channels_);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  I2S DOUT Pin: %d", this->dout_pin_);
 | 
			
		||||
    LOG_PIN("  Mute Pin: ", this->mute_pin_);
 | 
			
		||||
#if SOC_I2S_SUPPORTS_DAC
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,8 @@ import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_NUMBER
 | 
			
		||||
from esphome.components import microphone, esp32
 | 
			
		||||
from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
from esphome.components import microphone
 | 
			
		||||
 | 
			
		||||
from .. import (
 | 
			
		||||
    i2s_audio_ns,
 | 
			
		||||
@@ -17,82 +16,26 @@ from .. import (
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
DEPENDENCIES = ["i2s_audio"]
 | 
			
		||||
 | 
			
		||||
CONF_ADC_PIN = "adc_pin"
 | 
			
		||||
CONF_ADC_TYPE = "adc_type"
 | 
			
		||||
CONF_PDM = "pdm"
 | 
			
		||||
 | 
			
		||||
I2SAudioMicrophone = i2s_audio_ns.class_(
 | 
			
		||||
    "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t")
 | 
			
		||||
CHANNELS = {
 | 
			
		||||
    "left": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT,
 | 
			
		||||
    "right": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32]
 | 
			
		||||
PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_esp32_variant(config):
 | 
			
		||||
    variant = esp32.get_esp32_variant()
 | 
			
		||||
    if config[CONF_ADC_TYPE] == "external":
 | 
			
		||||
        if config[CONF_PDM]:
 | 
			
		||||
            if variant not in PDM_VARIANTS:
 | 
			
		||||
                raise cv.Invalid(f"{variant} does not support PDM")
 | 
			
		||||
        return config
 | 
			
		||||
    if config[CONF_ADC_TYPE] == "internal":
 | 
			
		||||
        if variant not in INTERNAL_ADC_VARIANTS:
 | 
			
		||||
            raise cv.Invalid(f"{variant} does not have an internal ADC")
 | 
			
		||||
        return config
 | 
			
		||||
    raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
 | 
			
		||||
CONFIG_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(I2SAudioMicrophone),
 | 
			
		||||
        cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
 | 
			
		||||
        cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS),
 | 
			
		||||
        cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.typed_schema(
 | 
			
		||||
        {
 | 
			
		||||
            "internal": BASE_SCHEMA.extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Required(CONF_ADC_PIN): validate_adc_pin,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            "external": BASE_SCHEMA.extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
 | 
			
		||||
                    cv.Required(CONF_PDM): cv.boolean,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        },
 | 
			
		||||
        key=CONF_ADC_TYPE,
 | 
			
		||||
    ),
 | 
			
		||||
    validate_esp32_variant,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_I2S_AUDIO_ID])
 | 
			
		||||
    cg.add(parent.register_audio_in(var))
 | 
			
		||||
 | 
			
		||||
    if config[CONF_ADC_TYPE] == "internal":
 | 
			
		||||
        variant = esp32.get_esp32_variant()
 | 
			
		||||
        pin_num = config[CONF_ADC_PIN][CONF_NUMBER]
 | 
			
		||||
        channel = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
 | 
			
		||||
        cg.add(var.set_adc_channel(channel))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
 | 
			
		||||
        cg.add(var.set_pdm(config[CONF_PDM]))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_channel(CHANNELS[config[CONF_CHANNEL]]))
 | 
			
		||||
    cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
 | 
			
		||||
 | 
			
		||||
    await microphone.register_microphone(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -17,39 +17,18 @@ static const char *const TAG = "i2s_audio.microphone";
 | 
			
		||||
void I2SAudioMicrophone::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone...");
 | 
			
		||||
  this->buffer_.resize(BUFFER_SIZE);
 | 
			
		||||
 | 
			
		||||
#if SOC_I2S_SUPPORTS_ADC
 | 
			
		||||
  if (this->adc_) {
 | 
			
		||||
    if (this->parent_->get_port() != I2S_NUM_0) {
 | 
			
		||||
      ESP_LOGE(TAG, "Internal ADC only works on I2S0!");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  } else
 | 
			
		||||
#endif
 | 
			
		||||
      if (this->pdm_) {
 | 
			
		||||
    if (this->parent_->get_port() != I2S_NUM_0) {
 | 
			
		||||
      ESP_LOGE(TAG, "PDM only works on I2S0!");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioMicrophone::start() {
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    return;
 | 
			
		||||
  this->state_ = microphone::STATE_STARTING;
 | 
			
		||||
}
 | 
			
		||||
void I2SAudioMicrophone::start() { this->state_ = microphone::STATE_STARTING; }
 | 
			
		||||
void I2SAudioMicrophone::start_() {
 | 
			
		||||
  if (!this->parent_->try_lock()) {
 | 
			
		||||
    return;  // Waiting for another i2s to return lock
 | 
			
		||||
  }
 | 
			
		||||
  i2s_driver_config_t config = {
 | 
			
		||||
      .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX),
 | 
			
		||||
      .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM),
 | 
			
		||||
      .sample_rate = 16000,
 | 
			
		||||
      .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
 | 
			
		||||
      .channel_format = this->channel_,
 | 
			
		||||
      .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
 | 
			
		||||
      .communication_format = I2S_COMM_FORMAT_STAND_I2S,
 | 
			
		||||
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
 | 
			
		||||
      .dma_buf_count = 4,
 | 
			
		||||
@@ -61,38 +40,19 @@ void I2SAudioMicrophone::start_() {
 | 
			
		||||
      .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
#if SOC_I2S_SUPPORTS_ADC
 | 
			
		||||
  if (this->adc_) {
 | 
			
		||||
    config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
 | 
			
		||||
    i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
 | 
			
		||||
  i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
 | 
			
		||||
 | 
			
		||||
    i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
 | 
			
		||||
    i2s_adc_enable(this->parent_->get_port());
 | 
			
		||||
  } else {
 | 
			
		||||
#endif
 | 
			
		||||
    if (this->pdm_)
 | 
			
		||||
      config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM);
 | 
			
		||||
  i2s_pin_config_t pin_config = this->parent_->get_pin_config();
 | 
			
		||||
  pin_config.data_in_num = this->din_pin_;
 | 
			
		||||
 | 
			
		||||
    i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
 | 
			
		||||
 | 
			
		||||
    i2s_pin_config_t pin_config = this->parent_->get_pin_config();
 | 
			
		||||
    pin_config.data_in_num = this->din_pin_;
 | 
			
		||||
 | 
			
		||||
    i2s_set_pin(this->parent_->get_port(), &pin_config);
 | 
			
		||||
#if SOC_I2S_SUPPORTS_ADC
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
  i2s_set_pin(this->parent_->get_port(), &pin_config);
 | 
			
		||||
  this->state_ = microphone::STATE_RUNNING;
 | 
			
		||||
  this->high_freq_.start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioMicrophone::stop() {
 | 
			
		||||
  if (this->state_ == microphone::STATE_STOPPED || this->is_failed())
 | 
			
		||||
  if (this->state_ == microphone::STATE_STOPPED)
 | 
			
		||||
    return;
 | 
			
		||||
  if (this->state_ == microphone::STATE_STARTING) {
 | 
			
		||||
    this->state_ = microphone::STATE_STOPPED;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->state_ = microphone::STATE_STOPPING;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,31 +18,15 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
 | 
			
		||||
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
  void set_din_pin(int8_t pin) { this->din_pin_ = pin; }
 | 
			
		||||
  void set_pdm(bool pdm) { this->pdm_ = pdm; }
 | 
			
		||||
 | 
			
		||||
#if SOC_I2S_SUPPORTS_ADC
 | 
			
		||||
  void set_adc_channel(adc1_channel_t channel) {
 | 
			
		||||
    this->adc_channel_ = channel;
 | 
			
		||||
    this->adc_ = true;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
 | 
			
		||||
  void set_din_pin(uint8_t pin) { this->din_pin_ = pin; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void start_();
 | 
			
		||||
  void stop_();
 | 
			
		||||
  void read_();
 | 
			
		||||
 | 
			
		||||
  int8_t din_pin_{I2S_PIN_NO_CHANGE};
 | 
			
		||||
#if SOC_I2S_SUPPORTS_ADC
 | 
			
		||||
  adc1_channel_t adc_channel_{ADC1_CHANNEL_MAX};
 | 
			
		||||
  bool adc_{false};
 | 
			
		||||
#endif
 | 
			
		||||
  bool pdm_{false};
 | 
			
		||||
  uint8_t din_pin_{0};
 | 
			
		||||
  std::vector<uint8_t> buffer_;
 | 
			
		||||
  i2s_channel_fmt_t channel_;
 | 
			
		||||
 | 
			
		||||
  HighFrequencyLoopRequester high_freq_;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,87 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_ID, CONF_MODE
 | 
			
		||||
from esphome.components import esp32, speaker
 | 
			
		||||
 | 
			
		||||
from .. import (
 | 
			
		||||
    CONF_I2S_AUDIO_ID,
 | 
			
		||||
    CONF_I2S_DOUT_PIN,
 | 
			
		||||
    I2SAudioComponent,
 | 
			
		||||
    I2SAudioOut,
 | 
			
		||||
    i2s_audio_ns,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
DEPENDENCIES = ["i2s_audio"]
 | 
			
		||||
 | 
			
		||||
I2SAudioSpeaker = i2s_audio_ns.class_(
 | 
			
		||||
    "I2SAudioSpeaker", cg.Component, speaker.Speaker, I2SAudioOut
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t")
 | 
			
		||||
 | 
			
		||||
CONF_MUTE_PIN = "mute_pin"
 | 
			
		||||
CONF_DAC_TYPE = "dac_type"
 | 
			
		||||
 | 
			
		||||
INTERNAL_DAC_OPTIONS = {
 | 
			
		||||
    "left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
 | 
			
		||||
    "right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
 | 
			
		||||
    "stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EXTERNAL_DAC_OPTIONS = ["mono", "stereo"]
 | 
			
		||||
 | 
			
		||||
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_esp32_variant(config):
 | 
			
		||||
    if config[CONF_DAC_TYPE] != "internal":
 | 
			
		||||
        return config
 | 
			
		||||
    variant = esp32.get_esp32_variant()
 | 
			
		||||
    if variant in NO_INTERNAL_DAC_VARIANTS:
 | 
			
		||||
        raise cv.Invalid(f"{variant} does not have an internal DAC")
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.typed_schema(
 | 
			
		||||
        {
 | 
			
		||||
            "internal": speaker.SPEAKER_SCHEMA.extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(): cv.declare_id(I2SAudioSpeaker),
 | 
			
		||||
                    cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
 | 
			
		||||
                    cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True),
 | 
			
		||||
                }
 | 
			
		||||
            ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
            "external": speaker.SPEAKER_SCHEMA.extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(): cv.declare_id(I2SAudioSpeaker),
 | 
			
		||||
                    cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
 | 
			
		||||
                    cv.Required(
 | 
			
		||||
                        CONF_I2S_DOUT_PIN
 | 
			
		||||
                    ): pins.internal_gpio_output_pin_number,
 | 
			
		||||
                    cv.Optional(CONF_MODE, default="mono"): cv.one_of(
 | 
			
		||||
                        *EXTERNAL_DAC_OPTIONS, lower=True
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
        },
 | 
			
		||||
        key=CONF_DAC_TYPE,
 | 
			
		||||
    ),
 | 
			
		||||
    validate_esp32_variant,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await speaker.register_speaker(var, config)
 | 
			
		||||
 | 
			
		||||
    await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
 | 
			
		||||
 | 
			
		||||
    if config[CONF_DAC_TYPE] == "internal":
 | 
			
		||||
        cg.add(var.set_internal_dac_mode(config[CONF_MODE]))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
 | 
			
		||||
        cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1))
 | 
			
		||||
@@ -1,212 +0,0 @@
 | 
			
		||||
#include "i2s_audio_speaker.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <driver/i2s.h>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace i2s_audio {
 | 
			
		||||
 | 
			
		||||
static const size_t BUFFER_COUNT = 10;
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "i2s_audio.speaker";
 | 
			
		||||
 | 
			
		||||
void I2SAudioSpeaker::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker...");
 | 
			
		||||
 | 
			
		||||
  this->buffer_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(DataEvent));
 | 
			
		||||
  this->event_queue_ = xQueueCreate(20, sizeof(TaskEvent));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioSpeaker::start() { this->state_ = speaker::STATE_STARTING; }
 | 
			
		||||
void I2SAudioSpeaker::start_() {
 | 
			
		||||
  if (!this->parent_->try_lock()) {
 | 
			
		||||
    return;  // Waiting for another i2s component to return lock
 | 
			
		||||
  }
 | 
			
		||||
  this->state_ = speaker::STATE_RUNNING;
 | 
			
		||||
 | 
			
		||||
  xTaskCreate(I2SAudioSpeaker::player_task, "speaker_task", 8192, (void *) this, 0, &this->player_task_handle_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioSpeaker::player_task(void *params) {
 | 
			
		||||
  I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params;
 | 
			
		||||
 | 
			
		||||
  TaskEvent event;
 | 
			
		||||
  event.type = TaskEventType::STARTING;
 | 
			
		||||
  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
			
		||||
 | 
			
		||||
  i2s_driver_config_t config = {
 | 
			
		||||
      .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX),
 | 
			
		||||
      .sample_rate = 16000,
 | 
			
		||||
      .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
 | 
			
		||||
      .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
 | 
			
		||||
      .communication_format = I2S_COMM_FORMAT_STAND_I2S,
 | 
			
		||||
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
 | 
			
		||||
      .dma_buf_count = 8,
 | 
			
		||||
      .dma_buf_len = 1024,
 | 
			
		||||
      .use_apll = false,
 | 
			
		||||
      .tx_desc_auto_clear = true,
 | 
			
		||||
      .fixed_mclk = I2S_PIN_NO_CHANGE,
 | 
			
		||||
      .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
 | 
			
		||||
      .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
 | 
			
		||||
  };
 | 
			
		||||
#if SOC_I2S_SUPPORTS_DAC
 | 
			
		||||
  if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
 | 
			
		||||
    config.mode = (i2s_mode_t) (config.mode | I2S_MODE_DAC_BUILT_IN);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  i2s_driver_install(this_speaker->parent_->get_port(), &config, 0, nullptr);
 | 
			
		||||
 | 
			
		||||
#if SOC_I2S_SUPPORTS_DAC
 | 
			
		||||
  if (this_speaker->internal_dac_mode_ == I2S_DAC_CHANNEL_DISABLE) {
 | 
			
		||||
#endif
 | 
			
		||||
    i2s_pin_config_t pin_config = this_speaker->parent_->get_pin_config();
 | 
			
		||||
    pin_config.data_out_num = this_speaker->dout_pin_;
 | 
			
		||||
 | 
			
		||||
    i2s_set_pin(this_speaker->parent_->get_port(), &pin_config);
 | 
			
		||||
#if SOC_I2S_SUPPORTS_DAC
 | 
			
		||||
  } else {
 | 
			
		||||
    i2s_set_dac_mode(this_speaker->internal_dac_mode_);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  DataEvent data_event;
 | 
			
		||||
 | 
			
		||||
  event.type = TaskEventType::STARTED;
 | 
			
		||||
  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
			
		||||
 | 
			
		||||
  int16_t buffer[BUFFER_SIZE / 2];
 | 
			
		||||
 | 
			
		||||
  while (true) {
 | 
			
		||||
    if (xQueueReceive(this_speaker->buffer_queue_, &data_event, 100 / portTICK_PERIOD_MS) != pdTRUE) {
 | 
			
		||||
      break;  // End of audio from main thread
 | 
			
		||||
    }
 | 
			
		||||
    if (data_event.stop) {
 | 
			
		||||
      // Stop signal from main thread
 | 
			
		||||
      while (xQueueReceive(this_speaker->buffer_queue_, &data_event, 0) == pdTRUE) {
 | 
			
		||||
        // Flush queue
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    size_t bytes_written;
 | 
			
		||||
 | 
			
		||||
    memmove(buffer, data_event.data, data_event.len);
 | 
			
		||||
    size_t remaining = data_event.len / 2;
 | 
			
		||||
    size_t current = 0;
 | 
			
		||||
 | 
			
		||||
    while (remaining > 0) {
 | 
			
		||||
      uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF);
 | 
			
		||||
 | 
			
		||||
      esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written,
 | 
			
		||||
                                (100 / portTICK_PERIOD_MS));
 | 
			
		||||
      if (err != ESP_OK) {
 | 
			
		||||
        event = {.type = TaskEventType::WARNING, .err = err};
 | 
			
		||||
        xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      remaining--;
 | 
			
		||||
      current++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    event.type = TaskEventType::PLAYING;
 | 
			
		||||
    xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  i2s_zero_dma_buffer(this_speaker->parent_->get_port());
 | 
			
		||||
 | 
			
		||||
  event.type = TaskEventType::STOPPING;
 | 
			
		||||
  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
			
		||||
 | 
			
		||||
  i2s_stop(this_speaker->parent_->get_port());
 | 
			
		||||
  i2s_driver_uninstall(this_speaker->parent_->get_port());
 | 
			
		||||
 | 
			
		||||
  event.type = TaskEventType::STOPPED;
 | 
			
		||||
  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
			
		||||
 | 
			
		||||
  while (true) {
 | 
			
		||||
    delay(10);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioSpeaker::stop() {
 | 
			
		||||
  if (this->state_ == speaker::STATE_STOPPED)
 | 
			
		||||
    return;
 | 
			
		||||
  if (this->state_ == speaker::STATE_STARTING) {
 | 
			
		||||
    this->state_ = speaker::STATE_STOPPED;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->state_ = speaker::STATE_STOPPING;
 | 
			
		||||
  DataEvent data;
 | 
			
		||||
  data.stop = true;
 | 
			
		||||
  xQueueSendToFront(this->buffer_queue_, &data, portMAX_DELAY);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioSpeaker::watch_() {
 | 
			
		||||
  TaskEvent event;
 | 
			
		||||
  if (xQueueReceive(this->event_queue_, &event, 0) == pdTRUE) {
 | 
			
		||||
    switch (event.type) {
 | 
			
		||||
      case TaskEventType::STARTING:
 | 
			
		||||
      case TaskEventType::STARTED:
 | 
			
		||||
      case TaskEventType::STOPPING:
 | 
			
		||||
        break;
 | 
			
		||||
      case TaskEventType::PLAYING:
 | 
			
		||||
        this->status_clear_warning();
 | 
			
		||||
        break;
 | 
			
		||||
      case TaskEventType::STOPPED:
 | 
			
		||||
        this->parent_->unlock();
 | 
			
		||||
        this->state_ = speaker::STATE_STOPPED;
 | 
			
		||||
        vTaskDelete(this->player_task_handle_);
 | 
			
		||||
        this->player_task_handle_ = nullptr;
 | 
			
		||||
        break;
 | 
			
		||||
      case TaskEventType::WARNING:
 | 
			
		||||
        ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(event.err));
 | 
			
		||||
        this->status_set_warning();
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioSpeaker::loop() {
 | 
			
		||||
  switch (this->state_) {
 | 
			
		||||
    case speaker::STATE_STARTING:
 | 
			
		||||
      this->start_();
 | 
			
		||||
      break;
 | 
			
		||||
    case speaker::STATE_RUNNING:
 | 
			
		||||
      this->watch_();
 | 
			
		||||
      break;
 | 
			
		||||
    case speaker::STATE_STOPPING:
 | 
			
		||||
    case speaker::STATE_STOPPED:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool I2SAudioSpeaker::play(const uint8_t *data, size_t length) {
 | 
			
		||||
  if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) {
 | 
			
		||||
    this->start();
 | 
			
		||||
  }
 | 
			
		||||
  size_t remaining = length;
 | 
			
		||||
  size_t index = 0;
 | 
			
		||||
  while (remaining > 0) {
 | 
			
		||||
    DataEvent event;
 | 
			
		||||
    event.stop = false;
 | 
			
		||||
    size_t to_send_length = std::min(remaining, BUFFER_SIZE);
 | 
			
		||||
    event.len = to_send_length;
 | 
			
		||||
    memcpy(event.data, data + index, to_send_length);
 | 
			
		||||
    if (xQueueSend(this->buffer_queue_, &event, 100 / portTICK_PERIOD_MS) == pdTRUE) {
 | 
			
		||||
      remaining -= to_send_length;
 | 
			
		||||
      index += to_send_length;
 | 
			
		||||
    }
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace i2s_audio
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -1,81 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "../i2s_audio.h"
 | 
			
		||||
 | 
			
		||||
#include <driver/i2s.h>
 | 
			
		||||
#include <freertos/FreeRTOS.h>
 | 
			
		||||
#include <freertos/queue.h>
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/speaker/speaker.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/gpio.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace i2s_audio {
 | 
			
		||||
 | 
			
		||||
static const size_t BUFFER_SIZE = 1024;
 | 
			
		||||
 | 
			
		||||
enum class TaskEventType : uint8_t {
 | 
			
		||||
  STARTING = 0,
 | 
			
		||||
  STARTED,
 | 
			
		||||
  PLAYING,
 | 
			
		||||
  STOPPING,
 | 
			
		||||
  STOPPED,
 | 
			
		||||
  WARNING = 255,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct TaskEvent {
 | 
			
		||||
  TaskEventType type;
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct DataEvent {
 | 
			
		||||
  bool stop;
 | 
			
		||||
  size_t len;
 | 
			
		||||
  uint8_t data[BUFFER_SIZE];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAudioOut {
 | 
			
		||||
 public:
 | 
			
		||||
  float get_setup_priority() const override { return esphome::setup_priority::LATE; }
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
  void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
 | 
			
		||||
#if SOC_I2S_SUPPORTS_DAC
 | 
			
		||||
  void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
 | 
			
		||||
#endif
 | 
			
		||||
  void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; }
 | 
			
		||||
 | 
			
		||||
  void start();
 | 
			
		||||
  void stop() override;
 | 
			
		||||
 | 
			
		||||
  bool play(const uint8_t *data, size_t length) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void start_();
 | 
			
		||||
  // void stop_();
 | 
			
		||||
  void watch_();
 | 
			
		||||
 | 
			
		||||
  static void player_task(void *params);
 | 
			
		||||
 | 
			
		||||
  TaskHandle_t player_task_handle_{nullptr};
 | 
			
		||||
  QueueHandle_t buffer_queue_;
 | 
			
		||||
  QueueHandle_t event_queue_;
 | 
			
		||||
 | 
			
		||||
  uint8_t dout_pin_{0};
 | 
			
		||||
 | 
			
		||||
#if SOC_I2S_SUPPORTS_DAC
 | 
			
		||||
  i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
 | 
			
		||||
#endif
 | 
			
		||||
  uint8_t external_dac_channels_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace i2s_audio
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -84,18 +84,9 @@ void ILI9XXXDisplay::fill(Color color) {
 | 
			
		||||
      break;
 | 
			
		||||
    case BITS_16:
 | 
			
		||||
      new_color = display::ColorUtil::color_to_565(color);
 | 
			
		||||
      {
 | 
			
		||||
        const uint32_t buffer_length_16_bits = this->get_buffer_length_() * 2;
 | 
			
		||||
        if (((uint8_t) (new_color >> 8)) == ((uint8_t) new_color)) {
 | 
			
		||||
          // Upper and lower is equal can use quicker memset operation. Takes ~20ms.
 | 
			
		||||
          memset(this->buffer_, (uint8_t) new_color, buffer_length_16_bits);
 | 
			
		||||
        } else {
 | 
			
		||||
          // Slower set of both buffers. Takes ~30ms.
 | 
			
		||||
          for (uint32_t i = 0; i < buffer_length_16_bits; i = i + 2) {
 | 
			
		||||
            this->buffer_[i] = (uint8_t) (new_color >> 8);
 | 
			
		||||
            this->buffer_[i + 1] = (uint8_t) new_color;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      for (uint32_t i = 0; i < this->get_buffer_length_() * 2; i = i + 2) {
 | 
			
		||||
        this->buffer_[i] = (uint8_t) (new_color >> 8);
 | 
			
		||||
        this->buffer_[i + 1] = (uint8_t) new_color;
 | 
			
		||||
      }
 | 
			
		||||
      return;
 | 
			
		||||
      break;
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.COMPONENT_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(KeyCollector),
 | 
			
		||||
            cv.Optional(CONF_SOURCE_ID): cv.use_id(key_provider.KeyProvider),
 | 
			
		||||
            cv.GenerateID(CONF_SOURCE_ID): cv.use_id(key_provider.KeyProvider),
 | 
			
		||||
            cv.Optional(CONF_MIN_LENGTH): cv.int_,
 | 
			
		||||
            cv.Optional(CONF_MAX_LENGTH): cv.int_,
 | 
			
		||||
            cv.Optional(CONF_START_KEYS): cv.string,
 | 
			
		||||
@@ -55,9 +55,8 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    if CONF_SOURCE_ID in config:
 | 
			
		||||
        source = await cg.get_variable(config[CONF_SOURCE_ID])
 | 
			
		||||
        cg.add(var.set_provider(source))
 | 
			
		||||
    source = await cg.get_variable(config[CONF_SOURCE_ID])
 | 
			
		||||
    cg.add(var.set_provider(source))
 | 
			
		||||
    if CONF_MIN_LENGTH in config:
 | 
			
		||||
        cg.add(var.set_min_length(config[CONF_MIN_LENGTH]))
 | 
			
		||||
    if CONF_MAX_LENGTH in config:
 | 
			
		||||
 
 | 
			
		||||
@@ -52,8 +52,6 @@ void KeyCollector::clear(bool progress_update) {
 | 
			
		||||
    this->progress_trigger_->trigger(this->result_, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void KeyCollector::send_key(uint8_t key) { this->key_pressed_(key); }
 | 
			
		||||
 | 
			
		||||
void KeyCollector::key_pressed_(uint8_t key) {
 | 
			
		||||
  this->last_key_time_ = millis();
 | 
			
		||||
  if (!this->start_keys_.empty() && !this->start_key_) {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@ class KeyCollector : public Component {
 | 
			
		||||
  void set_timeout(int timeout) { this->timeout_ = timeout; };
 | 
			
		||||
 | 
			
		||||
  void clear(bool progress_update = true);
 | 
			
		||||
  void send_key(uint8_t key);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void key_pressed_(uint8_t key);
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ class PulseLightEffect : public LightEffect {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    auto call = this->state_->turn_on();
 | 
			
		||||
    float out = this->on_ ? this->max_brightness : this->min_brightness;
 | 
			
		||||
    float out = this->on_ ? 1.0 : 0.0;
 | 
			
		||||
    call.set_brightness_if_supported(out);
 | 
			
		||||
    this->on_ = !this->on_;
 | 
			
		||||
    call.set_transition_length_if_supported(this->transition_length_);
 | 
			
		||||
@@ -41,18 +41,11 @@ class PulseLightEffect : public LightEffect {
 | 
			
		||||
 | 
			
		||||
  void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
 | 
			
		||||
 | 
			
		||||
  void set_min_max_brightness(float min, float max) {
 | 
			
		||||
    this->min_brightness = min;
 | 
			
		||||
    this->max_brightness = max;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool on_ = false;
 | 
			
		||||
  uint32_t last_color_change_{0};
 | 
			
		||||
  uint32_t transition_length_{};
 | 
			
		||||
  uint32_t update_interval_{};
 | 
			
		||||
  float min_brightness{0.0};
 | 
			
		||||
  float max_brightness{1.0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Random effect. Sets random colors every 10 seconds and slowly transitions between them.
 | 
			
		||||
 
 | 
			
		||||
@@ -28,8 +28,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_NUM_LEDS,
 | 
			
		||||
    CONF_RANDOM,
 | 
			
		||||
    CONF_SEQUENCE,
 | 
			
		||||
    CONF_MAX_BRIGHTNESS,
 | 
			
		||||
    CONF_MIN_BRIGHTNESS,
 | 
			
		||||
)
 | 
			
		||||
from esphome.util import Registry
 | 
			
		||||
from .types import (
 | 
			
		||||
@@ -176,19 +174,12 @@ async def automation_effect_to_code(config, effect_id):
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_UPDATE_INTERVAL, default="1s"
 | 
			
		||||
        ): cv.positive_time_period_milliseconds,
 | 
			
		||||
        cv.Optional(CONF_MIN_BRIGHTNESS, default="0%"): cv.percentage,
 | 
			
		||||
        cv.Optional(CONF_MAX_BRIGHTNESS, default="100%"): cv.percentage,
 | 
			
		||||
    },
 | 
			
		||||
)
 | 
			
		||||
async def pulse_effect_to_code(config, effect_id):
 | 
			
		||||
    effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
 | 
			
		||||
    cg.add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH]))
 | 
			
		||||
    cg.add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
 | 
			
		||||
    cg.add(
 | 
			
		||||
        effect.set_min_max_brightness(
 | 
			
		||||
            config[CONF_MIN_BRIGHTNESS], config[CONF_MAX_BRIGHTNESS]
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    return effect
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_TAG,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_TX_BUFFER_SIZE,
 | 
			
		||||
    PLATFORM_ESP32,
 | 
			
		||||
    PLATFORM_ESP8266,
 | 
			
		||||
    PLATFORM_RP2040,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
 | 
			
		||||
@@ -144,10 +141,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                esp8266=UART0,
 | 
			
		||||
                esp32=UART0,
 | 
			
		||||
                rp2040=USB_CDC,
 | 
			
		||||
            ): cv.All(
 | 
			
		||||
                cv.only_on([PLATFORM_ESP8266, PLATFORM_ESP32, PLATFORM_RP2040]),
 | 
			
		||||
                uart_selection,
 | 
			
		||||
            ),
 | 
			
		||||
            ): uart_selection,
 | 
			
		||||
            cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level,
 | 
			
		||||
            cv.Optional(CONF_LOGS, default={}): cv.Schema(
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
@@ -145,9 +145,6 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
 | 
			
		||||
  if (xPortGetFreeHeapSize() < 2048)
 | 
			
		||||
    return;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
  puts(msg);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  this->log_callback_.call(level, tag, msg);
 | 
			
		||||
}
 | 
			
		||||
@@ -265,11 +262,7 @@ void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
 | 
			
		||||
void Logger::set_log_level(const std::string &tag, int log_level) {
 | 
			
		||||
  this->log_levels_.push_back(LogLevelOverride{tag, log_level});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
 | 
			
		||||
UARTSelection Logger::get_uart() const { return this->uart_; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) {
 | 
			
		||||
  this->log_callback_.add(std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
@@ -301,10 +294,7 @@ void Logger::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Logger:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Log Baud Rate: %" PRIu32, this->baud_rate_);
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Hardware UART: %s", UART_SELECTIONS[this->uart_]);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  for (auto &it : this->log_levels_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ namespace esphome {
 | 
			
		||||
 | 
			
		||||
namespace logger {
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
 | 
			
		||||
/** Enum for logging UART selection
 | 
			
		||||
 *
 | 
			
		||||
 * Advanced configuration (pin selection, etc) is not supported.
 | 
			
		||||
@@ -53,7 +52,6 @@ enum UARTSelection {
 | 
			
		||||
  UART_SELECTION_USB_CDC,
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
};
 | 
			
		||||
#endif  // USE_ESP32 || USE_ESP8266
 | 
			
		||||
 | 
			
		||||
class Logger : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
@@ -68,11 +66,10 @@ class Logger : public Component {
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
  uart_port_t get_uart_num() const { return uart_num_; }
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
 | 
			
		||||
 | 
			
		||||
  void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
 | 
			
		||||
  /// Get the UART used by the logger.
 | 
			
		||||
  UARTSelection get_uart() const;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /// Set the log level of the specified tag.
 | 
			
		||||
  void set_log_level(const std::string &tag, int log_level);
 | 
			
		||||
@@ -142,9 +139,7 @@ class Logger : public Component {
 | 
			
		||||
  char *tx_buffer_{nullptr};
 | 
			
		||||
  int tx_buffer_at_{0};
 | 
			
		||||
  int tx_buffer_size_{0};
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
 | 
			
		||||
  UARTSelection uart_{UART_SELECTION_UART0};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  Stream *hw_serial_{nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/network/ip_address.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "mdns_component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mdns {
 | 
			
		||||
 | 
			
		||||
void MDNSComponent::setup() { this->compile_records_(); }
 | 
			
		||||
 | 
			
		||||
void MDNSComponent::on_shutdown() {}
 | 
			
		||||
 | 
			
		||||
}  // namespace mdns
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -23,9 +23,6 @@ bool is_connected() {
 | 
			
		||||
    return wifi::global_wifi_component->is_connected();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
  return true;  // Assume its connected
 | 
			
		||||
#endif
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,6 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_SULPHUR_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_VOLUME,
 | 
			
		||||
    DEVICE_CLASS_VOLUME_STORAGE,
 | 
			
		||||
@@ -110,7 +109,6 @@ DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_SULPHUR_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_VOLUME,
 | 
			
		||||
    DEVICE_CLASS_VOLUME_STORAGE,
 | 
			
		||||
 
 | 
			
		||||
@@ -6,5 +6,15 @@ namespace number {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "number";
 | 
			
		||||
 | 
			
		||||
void NumberTraits::set_unit_of_measurement(const std::string &unit_of_measurement) {
 | 
			
		||||
  this->unit_of_measurement_ = unit_of_measurement;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string NumberTraits::get_unit_of_measurement() {
 | 
			
		||||
  if (this->unit_of_measurement_.has_value())
 | 
			
		||||
    return *this->unit_of_measurement_;
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace number
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ enum NumberMode : uint8_t {
 | 
			
		||||
  NUMBER_MODE_SLIDER = 2,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class NumberTraits : public EntityBase_DeviceClass, public EntityBase_UnitOfMeasurement {
 | 
			
		||||
class NumberTraits : public EntityBase_DeviceClass {
 | 
			
		||||
 public:
 | 
			
		||||
  // Set/get the number value boundaries.
 | 
			
		||||
  void set_min_value(float min_value) { min_value_ = min_value; }
 | 
			
		||||
@@ -24,6 +24,11 @@ class NumberTraits : public EntityBase_DeviceClass, public EntityBase_UnitOfMeas
 | 
			
		||||
  void set_step(float step) { step_ = step; }
 | 
			
		||||
  float get_step() const { return step_; }
 | 
			
		||||
 | 
			
		||||
  /// Manually set the unit of measurement.
 | 
			
		||||
  void set_unit_of_measurement(const std::string &unit_of_measurement);
 | 
			
		||||
  /// Get the unit of measurement, using the manual override if set.
 | 
			
		||||
  std::string get_unit_of_measurement();
 | 
			
		||||
 | 
			
		||||
  // Set/get the frontend mode.
 | 
			
		||||
  void set_mode(NumberMode mode) { this->mode_ = mode; }
 | 
			
		||||
  NumberMode get_mode() const { return this->mode_; }
 | 
			
		||||
@@ -32,6 +37,7 @@ class NumberTraits : public EntityBase_DeviceClass, public EntityBase_UnitOfMeas
 | 
			
		||||
  float min_value_ = NAN;
 | 
			
		||||
  float max_value_ = NAN;
 | 
			
		||||
  float step_ = NAN;
 | 
			
		||||
  optional<std::string> unit_of_measurement_;  ///< Unit of measurement override
 | 
			
		||||
  NumberMode mode_{NUMBER_MODE_AUTO};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ void PsramComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Available: %s", YESNO(available));
 | 
			
		||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0)
 | 
			
		||||
  if (available) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Size: %d KB", heap_caps_get_total_size(MALLOC_CAP_SPIRAM) / 1024);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Size: %d MB", heap_caps_get_total_size(MALLOC_CAP_SPIRAM) / 1024 / 1024);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -252,7 +252,7 @@ void SEN5XComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Firmware version: %d", this->firmware_version_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Serial number %02d.%02d.%02d", serial_number_[0], serial_number_[1], serial_number_[2]);
 | 
			
		||||
  if (this->auto_cleaning_interval_.has_value()) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Auto cleaning interval %d seconds", auto_cleaning_interval_.value());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Auto auto cleaning interval %d seconds", auto_cleaning_interval_.value());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->acceleration_mode_.has_value()) {
 | 
			
		||||
    switch (this->acceleration_mode_.value()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -119,7 +119,7 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                device_class=DEVICE_CLASS_PM10,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.update_interval,
 | 
			
		||||
            cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.time_period_in_seconds_,
 | 
			
		||||
            cv.Optional(CONF_VOC): sensor.sensor_schema(
 | 
			
		||||
                icon=ICON_RADIATOR,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,6 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_TIMESTAMP,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_VOLUME,
 | 
			
		||||
    DEVICE_CLASS_VOLUME_STORAGE,
 | 
			
		||||
@@ -130,7 +129,6 @@ DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_TIMESTAMP,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_VOLUME,
 | 
			
		||||
    DEVICE_CLASS_VOLUME_STORAGE,
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,15 @@ std::string state_class_to_string(StateClass state_class) {
 | 
			
		||||
 | 
			
		||||
Sensor::Sensor() : state(NAN), raw_state(NAN) {}
 | 
			
		||||
 | 
			
		||||
std::string Sensor::get_unit_of_measurement() {
 | 
			
		||||
  if (this->unit_of_measurement_.has_value())
 | 
			
		||||
    return *this->unit_of_measurement_;
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) {
 | 
			
		||||
  this->unit_of_measurement_ = unit_of_measurement;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int8_t Sensor::get_accuracy_decimals() {
 | 
			
		||||
  if (this->accuracy_decimals_.has_value())
 | 
			
		||||
    return *this->accuracy_decimals_;
 | 
			
		||||
 
 | 
			
		||||
@@ -54,10 +54,15 @@ std::string state_class_to_string(StateClass state_class);
 | 
			
		||||
 *
 | 
			
		||||
 * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy.
 | 
			
		||||
 */
 | 
			
		||||
class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBase_UnitOfMeasurement {
 | 
			
		||||
class Sensor : public EntityBase, public EntityBase_DeviceClass {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit Sensor();
 | 
			
		||||
 | 
			
		||||
  /// Get the unit of measurement, using the manual override if set.
 | 
			
		||||
  std::string get_unit_of_measurement();
 | 
			
		||||
  /// Manually set the unit of measurement.
 | 
			
		||||
  void set_unit_of_measurement(const std::string &unit_of_measurement);
 | 
			
		||||
 | 
			
		||||
  /// Get the accuracy in decimals, using the manual override if set.
 | 
			
		||||
  int8_t get_accuracy_decimals();
 | 
			
		||||
  /// Manually set the accuracy in decimals.
 | 
			
		||||
@@ -153,6 +158,7 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
 | 
			
		||||
 | 
			
		||||
  Filter *filter_list_{nullptr};  ///< Store all active filters.
 | 
			
		||||
 | 
			
		||||
  optional<std::string> unit_of_measurement_;           ///< Unit of measurement override
 | 
			
		||||
  optional<int8_t> accuracy_decimals_;                  ///< Accuracy in decimals override
 | 
			
		||||
  optional<StateClass> state_class_{STATE_CLASS_NONE};  ///< State class override
 | 
			
		||||
  bool force_update_{false};                            ///< Force update mode
 | 
			
		||||
 
 | 
			
		||||
@@ -23,8 +23,6 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    CONF_MIN_BRIGHTNESS,
 | 
			
		||||
    CONF_MAX_BRIGHTNESS,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import HexInt, CORE
 | 
			
		||||
 | 
			
		||||
@@ -43,7 +41,8 @@ CONF_UPDATE = "update"
 | 
			
		||||
CONF_LEADING_EDGE = "leading_edge"
 | 
			
		||||
CONF_WARMUP_BRIGHTNESS = "warmup_brightness"
 | 
			
		||||
# CONF_WARMUP_TIME = "warmup_time"
 | 
			
		||||
 | 
			
		||||
CONF_MIN_BRIGHTNESS = "min_brightness"
 | 
			
		||||
CONF_MAX_BRIGHTNESS = "max_brightness"
 | 
			
		||||
 | 
			
		||||
CONF_NRST_PIN = "nrst_pin"
 | 
			
		||||
CONF_BOOT0_PIN = "boot0_pin"
 | 
			
		||||
 
 | 
			
		||||
@@ -8,49 +8,17 @@ from esphome.const import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["output"]
 | 
			
		||||
CODEOWNERS = ["@BoukeHaarsma23", "@matika77", "@dd32"]
 | 
			
		||||
CODEOWNERS = ["@BoukeHaarsma23"]
 | 
			
		||||
 | 
			
		||||
sm2135_ns = cg.esphome_ns.namespace("sm2135")
 | 
			
		||||
SM2135 = sm2135_ns.class_("SM2135", cg.Component)
 | 
			
		||||
 | 
			
		||||
CONF_RGB_CURRENT = "rgb_current"
 | 
			
		||||
CONF_CW_CURRENT = "cw_current"
 | 
			
		||||
 | 
			
		||||
SM2135Current = sm2135_ns.enum("SM2135Current")
 | 
			
		||||
 | 
			
		||||
DRIVE_STRENGTHS_CW = {
 | 
			
		||||
    "10mA": SM2135Current.SM2135_CURRENT_10MA,
 | 
			
		||||
    "15mA": SM2135Current.SM2135_CURRENT_15MA,
 | 
			
		||||
    "20mA": SM2135Current.SM2135_CURRENT_20MA,
 | 
			
		||||
    "25mA": SM2135Current.SM2135_CURRENT_25MA,
 | 
			
		||||
    "30mA": SM2135Current.SM2135_CURRENT_30MA,
 | 
			
		||||
    "35mA": SM2135Current.SM2135_CURRENT_35MA,
 | 
			
		||||
    "40mA": SM2135Current.SM2135_CURRENT_40MA,
 | 
			
		||||
    "45mA": SM2135Current.SM2135_CURRENT_45MA,
 | 
			
		||||
    "50mA": SM2135Current.SM2135_CURRENT_50MA,
 | 
			
		||||
    "55mA": SM2135Current.SM2135_CURRENT_55MA,
 | 
			
		||||
    "60mA": SM2135Current.SM2135_CURRENT_60MA,
 | 
			
		||||
}
 | 
			
		||||
DRIVE_STRENGTHS_RGB = {
 | 
			
		||||
    "10mA": SM2135Current.SM2135_CURRENT_10MA,
 | 
			
		||||
    "15mA": SM2135Current.SM2135_CURRENT_15MA,
 | 
			
		||||
    "20mA": SM2135Current.SM2135_CURRENT_20MA,
 | 
			
		||||
    "25mA": SM2135Current.SM2135_CURRENT_25MA,
 | 
			
		||||
    "30mA": SM2135Current.SM2135_CURRENT_30MA,
 | 
			
		||||
    "35mA": SM2135Current.SM2135_CURRENT_35MA,
 | 
			
		||||
    "40mA": SM2135Current.SM2135_CURRENT_40MA,
 | 
			
		||||
    "45mA": SM2135Current.SM2135_CURRENT_45MA,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(SM2135),
 | 
			
		||||
        cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
        cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
        cv.Optional(CONF_RGB_CURRENT, "20mA"): cv.enum(DRIVE_STRENGTHS_RGB),
 | 
			
		||||
        cv.Optional(CONF_CW_CURRENT, "10mA"): cv.enum(DRIVE_STRENGTHS_CW),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
@@ -63,6 +31,3 @@ async def to_code(config):
 | 
			
		||||
    cg.add(var.set_data_pin(data))
 | 
			
		||||
    clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
 | 
			
		||||
    cg.add(var.set_clock_pin(clock))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_rgb_current(config[CONF_RGB_CURRENT]))
 | 
			
		||||
    cg.add(var.set_cw_current(config[CONF_CW_CURRENT]))
 | 
			
		||||
 
 | 
			
		||||
@@ -19,125 +19,63 @@ static const uint8_t SM2135_ADDR_W = 0xC6;   // Warm
 | 
			
		||||
static const uint8_t SM2135_RGB = 0x00;  // RGB channel
 | 
			
		||||
static const uint8_t SM2135_CW = 0x80;   // CW channel (Chip default)
 | 
			
		||||
 | 
			
		||||
static const uint8_t SM2135_10MA = 0x00;
 | 
			
		||||
static const uint8_t SM2135_15MA = 0x01;
 | 
			
		||||
static const uint8_t SM2135_20MA = 0x02;  // RGB max current (Chip default)
 | 
			
		||||
static const uint8_t SM2135_25MA = 0x03;
 | 
			
		||||
static const uint8_t SM2135_30MA = 0x04;  // CW max current (Chip default)
 | 
			
		||||
static const uint8_t SM2135_35MA = 0x05;
 | 
			
		||||
static const uint8_t SM2135_40MA = 0x06;
 | 
			
		||||
static const uint8_t SM2135_45MA = 0x07;  // Max value for RGB
 | 
			
		||||
static const uint8_t SM2135_50MA = 0x08;
 | 
			
		||||
static const uint8_t SM2135_55MA = 0x09;
 | 
			
		||||
static const uint8_t SM2135_60MA = 0x0A;
 | 
			
		||||
 | 
			
		||||
static const uint8_t SM2135_CURRENT = (SM2135_20MA << 4) | SM2135_10MA;
 | 
			
		||||
 | 
			
		||||
void SM2135::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up SM2135OutputComponent...");
 | 
			
		||||
  this->data_pin_->setup();
 | 
			
		||||
  this->data_pin_->digital_write(false);
 | 
			
		||||
  this->data_pin_->pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  this->data_pin_->digital_write(true);
 | 
			
		||||
  this->clock_pin_->setup();
 | 
			
		||||
  this->clock_pin_->digital_write(false);
 | 
			
		||||
  this->data_pin_->pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
 | 
			
		||||
  this->data_pin_->pin_mode(gpio::FLAG_PULLUP);
 | 
			
		||||
  this->clock_pin_->pin_mode(gpio::FLAG_PULLUP);
 | 
			
		||||
 | 
			
		||||
  this->clock_pin_->digital_write(true);
 | 
			
		||||
  this->pwm_amounts_.resize(5, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SM2135::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "SM2135:");
 | 
			
		||||
  LOG_PIN("  Data Pin: ", this->data_pin_);
 | 
			
		||||
  LOG_PIN("  Clock Pin: ", this->clock_pin_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  CW Current: %dmA", 10 + (this->cw_current_ * 5));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  RGB Current: %dmA", 10 + (this->rgb_current_ * 5));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SM2135::write_byte_(uint8_t data) {
 | 
			
		||||
  for (uint8_t mask = 0x80; mask; mask >>= 1) {
 | 
			
		||||
    if (mask & data) {
 | 
			
		||||
      this->sm2135_set_high_(this->data_pin_);
 | 
			
		||||
    } else {
 | 
			
		||||
      this->sm2135_set_low_(this->data_pin_);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->sm2135_set_high_(this->clock_pin_);
 | 
			
		||||
    delayMicroseconds(4);
 | 
			
		||||
    this->sm2135_set_low_(clock_pin_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->sm2135_set_high_(this->data_pin_);
 | 
			
		||||
  this->sm2135_set_high_(this->clock_pin_);
 | 
			
		||||
  delayMicroseconds(2);
 | 
			
		||||
  this->sm2135_set_low_(this->clock_pin_);
 | 
			
		||||
  delayMicroseconds(2);
 | 
			
		||||
  this->sm2135_set_low_(this->data_pin_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SM2135::sm2135_start_() {
 | 
			
		||||
  this->sm2135_set_low_(this->data_pin_);
 | 
			
		||||
  delayMicroseconds(4);
 | 
			
		||||
  this->sm2135_set_low_(this->clock_pin_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SM2135::sm2135_stop_() {
 | 
			
		||||
  this->sm2135_set_low_(this->data_pin_);
 | 
			
		||||
  delayMicroseconds(4);
 | 
			
		||||
  this->sm2135_set_high_(this->clock_pin_);
 | 
			
		||||
  delayMicroseconds(4);
 | 
			
		||||
  this->sm2135_set_high_(this->data_pin_);
 | 
			
		||||
  delayMicroseconds(4);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SM2135::write_buffer_(uint8_t *buffer, uint8_t size) {
 | 
			
		||||
  this->sm2135_start_();
 | 
			
		||||
 | 
			
		||||
  this->data_pin_->digital_write(false);
 | 
			
		||||
  for (uint32_t i = 0; i < size; i++) {
 | 
			
		||||
    this->write_byte_(buffer[i]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->sm2135_stop_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SM2135::loop() {
 | 
			
		||||
  if (!this->update_)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  this->sm2135_start_();
 | 
			
		||||
  this->write_byte_(SM2135_ADDR_MC);
 | 
			
		||||
  this->write_byte_(current_mask_);
 | 
			
		||||
 | 
			
		||||
  uint8_t data[6];
 | 
			
		||||
  if (this->update_channel_ == 3 || this->update_channel_ == 4) {
 | 
			
		||||
    // No color so must be Cold/Warm
 | 
			
		||||
 | 
			
		||||
    this->write_byte_(SM2135_CW);
 | 
			
		||||
    this->sm2135_stop_();
 | 
			
		||||
    data[0] = SM2135_ADDR_MC;
 | 
			
		||||
    data[1] = SM2135_CURRENT;
 | 
			
		||||
    data[2] = SM2135_CW;
 | 
			
		||||
    this->write_buffer_(data, 3);
 | 
			
		||||
    delay(1);
 | 
			
		||||
    this->sm2135_start_();
 | 
			
		||||
    this->write_byte_(SM2135_ADDR_C);
 | 
			
		||||
    this->write_byte_(this->pwm_amounts_[4]);  // Warm
 | 
			
		||||
    this->write_byte_(this->pwm_amounts_[3]);  // Cold
 | 
			
		||||
    data[0] = SM2135_ADDR_C;
 | 
			
		||||
    data[1] = this->pwm_amounts_[4];  // Warm
 | 
			
		||||
    data[2] = this->pwm_amounts_[3];  // Cold
 | 
			
		||||
    this->write_buffer_(data, 3);
 | 
			
		||||
  } else {
 | 
			
		||||
    // Color
 | 
			
		||||
 | 
			
		||||
    this->write_byte_(SM2135_RGB);
 | 
			
		||||
    this->write_byte_(this->pwm_amounts_[1]);  // Green
 | 
			
		||||
    this->write_byte_(this->pwm_amounts_[0]);  // Red
 | 
			
		||||
    this->write_byte_(this->pwm_amounts_[2]);  // Blue
 | 
			
		||||
    data[0] = SM2135_ADDR_MC;
 | 
			
		||||
    data[1] = SM2135_CURRENT;
 | 
			
		||||
    data[2] = SM2135_RGB;
 | 
			
		||||
    data[3] = this->pwm_amounts_[1];  // Green
 | 
			
		||||
    data[4] = this->pwm_amounts_[0];  // Red
 | 
			
		||||
    data[5] = this->pwm_amounts_[2];  // Blue
 | 
			
		||||
    this->write_buffer_(data, 6);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->sm2135_stop_();
 | 
			
		||||
 | 
			
		||||
  this->update_ = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SM2135::set_channel_value_(uint8_t channel, uint8_t value) {
 | 
			
		||||
  if (this->pwm_amounts_[channel] != value) {
 | 
			
		||||
    this->update_ = true;
 | 
			
		||||
    this->update_channel_ = channel;
 | 
			
		||||
  }
 | 
			
		||||
  this->pwm_amounts_[channel] = value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SM2135::sm2135_set_low_(GPIOPin *pin) {
 | 
			
		||||
  pin->digital_write(false);
 | 
			
		||||
  pin->pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SM2135::sm2135_set_high_(GPIOPin *pin) {
 | 
			
		||||
  pin->digital_write(true);
 | 
			
		||||
  pin->pin_mode(gpio::FLAG_PULLUP);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace sm2135
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -1,43 +1,19 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "esphome/components/output/float_output.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/components/output/float_output.h"
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace sm2135 {
 | 
			
		||||
 | 
			
		||||
enum SM2135Current : uint8_t {
 | 
			
		||||
  SM2135_CURRENT_10MA = 0x00,
 | 
			
		||||
  SM2135_CURRENT_15MA = 0x01,
 | 
			
		||||
  SM2135_CURRENT_20MA = 0x02,
 | 
			
		||||
  SM2135_CURRENT_25MA = 0x03,
 | 
			
		||||
  SM2135_CURRENT_30MA = 0x04,
 | 
			
		||||
  SM2135_CURRENT_35MA = 0x05,
 | 
			
		||||
  SM2135_CURRENT_40MA = 0x06,
 | 
			
		||||
  SM2135_CURRENT_45MA = 0x07,  // Max value for RGB
 | 
			
		||||
  SM2135_CURRENT_50MA = 0x08,
 | 
			
		||||
  SM2135_CURRENT_55MA = 0x09,
 | 
			
		||||
  SM2135_CURRENT_60MA = 0x0A,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SM2135 : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  class Channel;
 | 
			
		||||
 | 
			
		||||
  void set_data_pin(GPIOPin *data_pin) { this->data_pin_ = data_pin; }
 | 
			
		||||
  void set_clock_pin(GPIOPin *clock_pin) { this->clock_pin_ = clock_pin; }
 | 
			
		||||
 | 
			
		||||
  void set_rgb_current(SM2135Current rgb_current) {
 | 
			
		||||
    this->rgb_current_ = rgb_current;
 | 
			
		||||
    this->current_mask_ = (this->rgb_current_ << 4) | this->cw_current_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_cw_current(SM2135Current cw_current) {
 | 
			
		||||
    this->cw_current_ = cw_current;
 | 
			
		||||
    this->current_mask_ = (this->rgb_current_ << 4) | this->cw_current_;
 | 
			
		||||
  }
 | 
			
		||||
  void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; }
 | 
			
		||||
  void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; }
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
 | 
			
		||||
@@ -64,20 +40,40 @@ class SM2135 : public Component {
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void set_channel_value_(uint8_t channel, uint8_t value);
 | 
			
		||||
  void sm2135_set_low_(GPIOPin *pin);
 | 
			
		||||
  void sm2135_set_high_(GPIOPin *pin);
 | 
			
		||||
  void set_channel_value_(uint8_t channel, uint8_t value) {
 | 
			
		||||
    if (this->pwm_amounts_[channel] != value) {
 | 
			
		||||
      this->update_ = true;
 | 
			
		||||
      this->update_channel_ = channel;
 | 
			
		||||
    }
 | 
			
		||||
    this->pwm_amounts_[channel] = value;
 | 
			
		||||
  }
 | 
			
		||||
  void write_bit_(bool value) {
 | 
			
		||||
    this->clock_pin_->digital_write(false);
 | 
			
		||||
    this->data_pin_->digital_write(value);
 | 
			
		||||
    this->clock_pin_->digital_write(true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void sm2135_start_();
 | 
			
		||||
  void sm2135_stop_();
 | 
			
		||||
  void write_byte_(uint8_t data);
 | 
			
		||||
  void write_buffer_(uint8_t *buffer, uint8_t size);
 | 
			
		||||
  void write_byte_(uint8_t data) {
 | 
			
		||||
    for (uint8_t mask = 0x80; mask; mask >>= 1) {
 | 
			
		||||
      this->write_bit_(data & mask);
 | 
			
		||||
    }
 | 
			
		||||
    this->clock_pin_->digital_write(false);
 | 
			
		||||
    this->data_pin_->digital_write(true);
 | 
			
		||||
    this->clock_pin_->digital_write(true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void write_buffer_(uint8_t *buffer, uint8_t size) {
 | 
			
		||||
    this->data_pin_->digital_write(false);
 | 
			
		||||
    for (uint32_t i = 0; i < size; i++) {
 | 
			
		||||
      this->write_byte_(buffer[i]);
 | 
			
		||||
    }
 | 
			
		||||
    this->clock_pin_->digital_write(false);
 | 
			
		||||
    this->clock_pin_->digital_write(true);
 | 
			
		||||
    this->data_pin_->digital_write(true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  GPIOPin *data_pin_;
 | 
			
		||||
  GPIOPin *clock_pin_;
 | 
			
		||||
  uint8_t current_mask_;
 | 
			
		||||
  SM2135Current rgb_current_;
 | 
			
		||||
  SM2135Current cw_current_;
 | 
			
		||||
  uint8_t update_channel_;
 | 
			
		||||
  std::vector<uint8_t> pwm_amounts_;
 | 
			
		||||
  bool update_{true};
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
            esp8266=IMPLEMENTATION_LWIP_TCP,
 | 
			
		||||
            esp32=IMPLEMENTATION_BSD_SOCKETS,
 | 
			
		||||
            rp2040=IMPLEMENTATION_LWIP_TCP,
 | 
			
		||||
            host=IMPLEMENTATION_BSD_SOCKETS,
 | 
			
		||||
        ): cv.one_of(
 | 
			
		||||
            IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_"
 | 
			
		||||
        ),
 | 
			
		||||
 
 | 
			
		||||
@@ -130,13 +130,6 @@ struct iovec {
 | 
			
		||||
#include <sys/uio.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <netinet/in.h>
 | 
			
		||||
#include <netinet/ip.h>
 | 
			
		||||
#include <netinet/tcp.h>
 | 
			
		||||
#endif  // USE_HOST
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
// arduino-esp32 declares a global var called INADDR_NONE which is replaced
 | 
			
		||||
// by the define
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,6 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace socket {
 | 
			
		||||
 | 
			
		||||
Socket::~Socket() {}
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<Socket> socket_ip(int type, int protocol) {
 | 
			
		||||
#if LWIP_IPV6
 | 
			
		||||
  return socket(AF_INET6, type, protocol);
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ namespace socket {
 | 
			
		||||
class Socket {
 | 
			
		||||
 public:
 | 
			
		||||
  Socket() = default;
 | 
			
		||||
  virtual ~Socket();
 | 
			
		||||
  virtual ~Socket() = default;
 | 
			
		||||
  Socket(const Socket &) = delete;
 | 
			
		||||
  Socket &operator=(const Socket &) = delete;
 | 
			
		||||
 | 
			
		||||
@@ -34,7 +34,7 @@ class Socket {
 | 
			
		||||
  virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0;
 | 
			
		||||
  virtual ssize_t write(const void *buf, size_t len) = 0;
 | 
			
		||||
  virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0;
 | 
			
		||||
  virtual ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) = 0;
 | 
			
		||||
  virtual ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
 | 
			
		||||
 | 
			
		||||
  virtual int setblocking(bool blocking) = 0;
 | 
			
		||||
  virtual int loop() { return 0; };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,87 +0,0 @@
 | 
			
		||||
from esphome import automation
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
from esphome.const import CONF_ID, CONF_DATA
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.coroutine import coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
speaker_ns = cg.esphome_ns.namespace("speaker")
 | 
			
		||||
 | 
			
		||||
Speaker = speaker_ns.class_("Speaker")
 | 
			
		||||
 | 
			
		||||
PlayAction = speaker_ns.class_(
 | 
			
		||||
    "PlayAction", automation.Action, cg.Parented.template(Speaker)
 | 
			
		||||
)
 | 
			
		||||
StopAction = speaker_ns.class_(
 | 
			
		||||
    "StopAction", automation.Action, cg.Parented.template(Speaker)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
IsPlayingCondition = speaker_ns.class_("IsPlayingCondition", automation.Condition)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_speaker_core_(var, config):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_speaker(var, config):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
        var = cg.Pvariable(config[CONF_ID], var)
 | 
			
		||||
    await setup_speaker_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
SPEAKER_SCHEMA = cv.Schema({})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
SPEAKER_AUTOMATION_SCHEMA = maybe_simple_id({cv.GenerateID(): cv.use_id(Speaker)})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def speaker_action(config, action_id, template_arg, args):
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
			
		||||
    await cg.register_parented(var, config[CONF_ID])
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "speaker.play",
 | 
			
		||||
    PlayAction,
 | 
			
		||||
    cv.maybe_simple_value(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.use_id(Speaker),
 | 
			
		||||
            cv.Required(CONF_DATA): cv.templatable(cv.ensure_list(cv.hex_uint8_t)),
 | 
			
		||||
        },
 | 
			
		||||
        key=CONF_DATA,
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
async def speaker_play_action(config, action_id, template_arg, args):
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
			
		||||
    await cg.register_parented(var, config[CONF_ID])
 | 
			
		||||
    data = config[CONF_DATA]
 | 
			
		||||
 | 
			
		||||
    if cg.is_template(data):
 | 
			
		||||
        templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
 | 
			
		||||
        cg.add(var.set_data_template(templ))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_data_static(data))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
automation.register_action("speaker.stop", StopAction, SPEAKER_AUTOMATION_SCHEMA)(
 | 
			
		||||
    speaker_action
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
automation.register_condition(
 | 
			
		||||
    "speaker.is_playing", IsPlayingCondition, SPEAKER_AUTOMATION_SCHEMA
 | 
			
		||||
)(speaker_action)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(100.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add_global(speaker_ns.using)
 | 
			
		||||
    cg.add_define("USE_SPEAKER")
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "speaker.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace speaker {
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class PlayAction : public Action<Ts...>, public Parented<Speaker> {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
 | 
			
		||||
    this->data_func_ = func;
 | 
			
		||||
    this->static_ = false;
 | 
			
		||||
  }
 | 
			
		||||
  void set_data_static(const std::vector<uint8_t> &data) {
 | 
			
		||||
    this->data_static_ = data;
 | 
			
		||||
    this->static_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    if (this->static_) {
 | 
			
		||||
      this->parent_->play(this->data_static_);
 | 
			
		||||
    } else {
 | 
			
		||||
      auto val = this->data_func_(x...);
 | 
			
		||||
      this->parent_->play(val);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool static_{false};
 | 
			
		||||
  std::function<std::vector<uint8_t>(Ts...)> data_func_{};
 | 
			
		||||
  std::vector<uint8_t> data_static_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class StopAction : public Action<Ts...>, public Parented<Speaker> {
 | 
			
		||||
 public:
 | 
			
		||||
  void play(Ts... x) override { this->parent_->stop(); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class IsPlayingCondition : public Condition<Ts...>, public Parented<Speaker> {
 | 
			
		||||
 public:
 | 
			
		||||
  bool check(Ts... x) override { return this->parent_->is_running(); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace speaker
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace speaker {
 | 
			
		||||
 | 
			
		||||
enum State : uint8_t {
 | 
			
		||||
  STATE_STOPPED = 0,
 | 
			
		||||
  STATE_STARTING,
 | 
			
		||||
  STATE_RUNNING,
 | 
			
		||||
  STATE_STOPPING,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Speaker {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual bool play(const uint8_t *data, size_t length) = 0;
 | 
			
		||||
  virtual bool play(const std::vector<uint8_t> &data) { return this->play(data.data(), data.size()); }
 | 
			
		||||
 | 
			
		||||
  virtual void stop() = 0;
 | 
			
		||||
 | 
			
		||||
  bool is_running() const { return this->state_ == STATE_RUNNING; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  State state_{STATE_STOPPED};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace speaker
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -275,7 +275,7 @@ SPRINKLER_ACTION_SET_RUN_DURATION_SCHEMA = cv.Schema(
 | 
			
		||||
SPRINKLER_ACTION_QUEUE_VALVE_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(Sprinkler),
 | 
			
		||||
        cv.Optional(CONF_RUN_DURATION, default="0s"): cv.templatable(
 | 
			
		||||
        cv.Optional(CONF_RUN_DURATION, default=0): cv.templatable(
 | 
			
		||||
            cv.positive_time_period_seconds
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Required(CONF_VALVE_NUMBER): cv.templatable(cv.positive_int),
 | 
			
		||||
@@ -599,6 +599,15 @@ async def to_code(config):
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(var.set_controller_auto_adv_switch(sw_aa_var))
 | 
			
		||||
 | 
			
		||||
            if CONF_QUEUE_ENABLE_SWITCH in sprinkler_controller:
 | 
			
		||||
                sw_qen_var = await switch.new_switch(
 | 
			
		||||
                    sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH]
 | 
			
		||||
                )
 | 
			
		||||
                await cg.register_component(
 | 
			
		||||
                    sw_qen_var, sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH]
 | 
			
		||||
                )
 | 
			
		||||
                cg.add(var.set_controller_queue_enable_switch(sw_qen_var))
 | 
			
		||||
 | 
			
		||||
            if CONF_REVERSE_SWITCH in sprinkler_controller:
 | 
			
		||||
                sw_rev_var = await switch.new_switch(
 | 
			
		||||
                    sprinkler_controller[CONF_REVERSE_SWITCH]
 | 
			
		||||
@@ -608,83 +617,78 @@ async def to_code(config):
 | 
			
		||||
                )
 | 
			
		||||
                cg.add(var.set_controller_reverse_switch(sw_rev_var))
 | 
			
		||||
 | 
			
		||||
        if CONF_STANDBY_SWITCH in sprinkler_controller:
 | 
			
		||||
            sw_stb_var = await switch.new_switch(
 | 
			
		||||
                sprinkler_controller[CONF_STANDBY_SWITCH]
 | 
			
		||||
            )
 | 
			
		||||
            await cg.register_component(
 | 
			
		||||
                sw_stb_var, sprinkler_controller[CONF_STANDBY_SWITCH]
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(var.set_controller_standby_switch(sw_stb_var))
 | 
			
		||||
 | 
			
		||||
        if CONF_QUEUE_ENABLE_SWITCH in sprinkler_controller:
 | 
			
		||||
            sw_qen_var = await switch.new_switch(
 | 
			
		||||
                sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH]
 | 
			
		||||
            )
 | 
			
		||||
            await cg.register_component(
 | 
			
		||||
                sw_qen_var, sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH]
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(var.set_controller_queue_enable_switch(sw_qen_var))
 | 
			
		||||
 | 
			
		||||
        if CONF_MULTIPLIER_NUMBER in sprinkler_controller:
 | 
			
		||||
            num_mult_var = await number.new_number(
 | 
			
		||||
                sprinkler_controller[CONF_MULTIPLIER_NUMBER],
 | 
			
		||||
                min_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_MIN_VALUE],
 | 
			
		||||
                max_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_MAX_VALUE],
 | 
			
		||||
                step=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_STEP],
 | 
			
		||||
            )
 | 
			
		||||
            await cg.register_component(
 | 
			
		||||
                num_mult_var, sprinkler_controller[CONF_MULTIPLIER_NUMBER]
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(
 | 
			
		||||
                num_mult_var.set_initial_value(
 | 
			
		||||
                    sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_INITIAL_VALUE]
 | 
			
		||||
            if CONF_STANDBY_SWITCH in sprinkler_controller:
 | 
			
		||||
                sw_stb_var = await switch.new_switch(
 | 
			
		||||
                    sprinkler_controller[CONF_STANDBY_SWITCH]
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(
 | 
			
		||||
                num_mult_var.set_restore_value(
 | 
			
		||||
                    sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_RESTORE_VALUE]
 | 
			
		||||
                await cg.register_component(
 | 
			
		||||
                    sw_stb_var, sprinkler_controller[CONF_STANDBY_SWITCH]
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
                cg.add(var.set_controller_standby_switch(sw_stb_var))
 | 
			
		||||
 | 
			
		||||
            if CONF_SET_ACTION in sprinkler_controller[CONF_MULTIPLIER_NUMBER]:
 | 
			
		||||
                await automation.build_automation(
 | 
			
		||||
                    num_mult_var.get_set_trigger(),
 | 
			
		||||
                    [(float, "x")],
 | 
			
		||||
                    sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_SET_ACTION],
 | 
			
		||||
            if CONF_MULTIPLIER_NUMBER in sprinkler_controller:
 | 
			
		||||
                num_mult_var = await number.new_number(
 | 
			
		||||
                    sprinkler_controller[CONF_MULTIPLIER_NUMBER],
 | 
			
		||||
                    min_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][
 | 
			
		||||
                        CONF_MIN_VALUE
 | 
			
		||||
                    ],
 | 
			
		||||
                    max_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][
 | 
			
		||||
                        CONF_MAX_VALUE
 | 
			
		||||
                    ],
 | 
			
		||||
                    step=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_STEP],
 | 
			
		||||
                )
 | 
			
		||||
                await cg.register_component(
 | 
			
		||||
                    num_mult_var, sprinkler_controller[CONF_MULTIPLIER_NUMBER]
 | 
			
		||||
                )
 | 
			
		||||
                cg.add(
 | 
			
		||||
                    num_mult_var.set_initial_value(
 | 
			
		||||
                        sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_INITIAL_VALUE]
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
                cg.add(
 | 
			
		||||
                    num_mult_var.set_restore_value(
 | 
			
		||||
                        sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_RESTORE_VALUE]
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            cg.add(var.set_controller_multiplier_number(num_mult_var))
 | 
			
		||||
                if CONF_SET_ACTION in sprinkler_controller[CONF_MULTIPLIER_NUMBER]:
 | 
			
		||||
                    await automation.build_automation(
 | 
			
		||||
                        num_mult_var.get_set_trigger(),
 | 
			
		||||
                        [(float, "x")],
 | 
			
		||||
                        sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_SET_ACTION],
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
        if CONF_REPEAT_NUMBER in sprinkler_controller:
 | 
			
		||||
            num_repeat_var = await number.new_number(
 | 
			
		||||
                sprinkler_controller[CONF_REPEAT_NUMBER],
 | 
			
		||||
                min_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MIN_VALUE],
 | 
			
		||||
                max_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MAX_VALUE],
 | 
			
		||||
                step=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_STEP],
 | 
			
		||||
            )
 | 
			
		||||
            await cg.register_component(
 | 
			
		||||
                num_repeat_var, sprinkler_controller[CONF_REPEAT_NUMBER]
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(
 | 
			
		||||
                num_repeat_var.set_initial_value(
 | 
			
		||||
                    sprinkler_controller[CONF_REPEAT_NUMBER][CONF_INITIAL_VALUE]
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(
 | 
			
		||||
                num_repeat_var.set_restore_value(
 | 
			
		||||
                    sprinkler_controller[CONF_REPEAT_NUMBER][CONF_RESTORE_VALUE]
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
                cg.add(var.set_controller_multiplier_number(num_mult_var))
 | 
			
		||||
 | 
			
		||||
            if CONF_SET_ACTION in sprinkler_controller[CONF_REPEAT_NUMBER]:
 | 
			
		||||
                await automation.build_automation(
 | 
			
		||||
                    num_repeat_var.get_set_trigger(),
 | 
			
		||||
                    [(float, "x")],
 | 
			
		||||
                    sprinkler_controller[CONF_REPEAT_NUMBER][CONF_SET_ACTION],
 | 
			
		||||
            if CONF_REPEAT_NUMBER in sprinkler_controller:
 | 
			
		||||
                num_repeat_var = await number.new_number(
 | 
			
		||||
                    sprinkler_controller[CONF_REPEAT_NUMBER],
 | 
			
		||||
                    min_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MIN_VALUE],
 | 
			
		||||
                    max_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MAX_VALUE],
 | 
			
		||||
                    step=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_STEP],
 | 
			
		||||
                )
 | 
			
		||||
                await cg.register_component(
 | 
			
		||||
                    num_repeat_var, sprinkler_controller[CONF_REPEAT_NUMBER]
 | 
			
		||||
                )
 | 
			
		||||
                cg.add(
 | 
			
		||||
                    num_repeat_var.set_initial_value(
 | 
			
		||||
                        sprinkler_controller[CONF_REPEAT_NUMBER][CONF_INITIAL_VALUE]
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
                cg.add(
 | 
			
		||||
                    num_repeat_var.set_restore_value(
 | 
			
		||||
                        sprinkler_controller[CONF_REPEAT_NUMBER][CONF_RESTORE_VALUE]
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            cg.add(var.set_controller_repeat_number(num_repeat_var))
 | 
			
		||||
                if CONF_SET_ACTION in sprinkler_controller[CONF_REPEAT_NUMBER]:
 | 
			
		||||
                    await automation.build_automation(
 | 
			
		||||
                        num_repeat_var.get_set_trigger(),
 | 
			
		||||
                        [(float, "x")],
 | 
			
		||||
                        sprinkler_controller[CONF_REPEAT_NUMBER][CONF_SET_ACTION],
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                cg.add(var.set_controller_repeat_number(num_repeat_var))
 | 
			
		||||
 | 
			
		||||
        for valve in sprinkler_controller[CONF_VALVES]:
 | 
			
		||||
            sw_valve_var = await switch.new_switch(valve[CONF_VALVE_SWITCH])
 | 
			
		||||
 
 | 
			
		||||
@@ -147,22 +147,22 @@ SprinklerValveOperator::SprinklerValveOperator(SprinklerValve *valve, Sprinkler
 | 
			
		||||
    : controller_(controller), valve_(valve) {}
 | 
			
		||||
 | 
			
		||||
void SprinklerValveOperator::loop() {
 | 
			
		||||
  if (millis() >= this->start_millis_) {  // dummy check
 | 
			
		||||
  if (millis() >= this->pinned_millis_) {  // dummy check
 | 
			
		||||
    switch (this->state_) {
 | 
			
		||||
      case STARTING:
 | 
			
		||||
        if (millis() > (this->start_millis_ + this->start_delay_)) {
 | 
			
		||||
        if (millis() > (this->pinned_millis_ + this->start_delay_)) {
 | 
			
		||||
          this->run_();  // start_delay_ has been exceeded, so ensure both valves are on and update the state
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case ACTIVE:
 | 
			
		||||
        if (millis() > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
 | 
			
		||||
        if (millis() > (this->pinned_millis_ + this->start_delay_ + this->run_duration_)) {
 | 
			
		||||
          this->stop();  // start_delay_ + run_duration_ has been exceeded, start shutting down
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case STOPPING:
 | 
			
		||||
        if (millis() > (this->stop_millis_ + this->stop_delay_)) {
 | 
			
		||||
        if (millis() > (this->pinned_millis_ + this->stop_delay_)) {
 | 
			
		||||
          this->kill_();  // stop_delay_has been exceeded, ensure all valves are off
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
@@ -183,12 +183,11 @@ void SprinklerValveOperator::set_controller(Sprinkler *controller) {
 | 
			
		||||
 | 
			
		||||
void SprinklerValveOperator::set_valve(SprinklerValve *valve) {
 | 
			
		||||
  if (valve != nullptr) {
 | 
			
		||||
    this->state_ = IDLE;      // reset state
 | 
			
		||||
    this->run_duration_ = 0;  // reset to ensure the valve isn't started without updating it
 | 
			
		||||
    this->start_millis_ = 0;  // reset because (new) valve has not been started yet
 | 
			
		||||
    this->stop_millis_ = 0;   // reset because (new) valve has not been started yet
 | 
			
		||||
    this->kill_();            // ensure everything is off before we let go!
 | 
			
		||||
    this->valve_ = valve;     // finally, set the pointer to the new valve
 | 
			
		||||
    this->state_ = IDLE;       // reset state
 | 
			
		||||
    this->run_duration_ = 0;   // reset to ensure the valve isn't started without updating it
 | 
			
		||||
    this->pinned_millis_ = 0;  // reset because (new) valve has not been started yet
 | 
			
		||||
    this->kill_();             // ensure everything is off before we let go!
 | 
			
		||||
    this->valve_ = valve;      // finally, set the pointer to the new valve
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -222,8 +221,7 @@ void SprinklerValveOperator::start() {
 | 
			
		||||
  } else {
 | 
			
		||||
    this->run_();  // there is no start_delay_, so just start the pump and valve
 | 
			
		||||
  }
 | 
			
		||||
  this->stop_millis_ = 0;
 | 
			
		||||
  this->start_millis_ = millis();  // save the time the start request was made
 | 
			
		||||
  this->pinned_millis_ = millis();  // save the time the start request was made
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SprinklerValveOperator::stop() {
 | 
			
		||||
@@ -240,33 +238,19 @@ void SprinklerValveOperator::stop() {
 | 
			
		||||
    if (this->pump_switch()->state()) {  // if the pump is still on at this point, it may be in use...
 | 
			
		||||
      this->valve_off_();                // ...so just switch the valve off now to ensure consistent run time
 | 
			
		||||
    }
 | 
			
		||||
    this->pinned_millis_ = millis();  // save the time the stop request was made
 | 
			
		||||
  } else {
 | 
			
		||||
    this->kill_();  // there is no stop_delay_, so just stop the pump and valve
 | 
			
		||||
  }
 | 
			
		||||
  this->stop_millis_ = millis();  // save the time the stop request was made
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t SprinklerValveOperator::run_duration() { return this->run_duration_ / 1000; }
 | 
			
		||||
uint32_t SprinklerValveOperator::run_duration() { return this->run_duration_; }
 | 
			
		||||
 | 
			
		||||
uint32_t SprinklerValveOperator::time_remaining() {
 | 
			
		||||
  if (this->start_millis_ == 0) {
 | 
			
		||||
    return this->run_duration();  // hasn't been started yet
 | 
			
		||||
  if ((this->state_ == STARTING) || (this->state_ == ACTIVE)) {
 | 
			
		||||
    return (this->pinned_millis_ + this->start_delay_ + this->run_duration_ - millis()) / 1000;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->stop_millis_) {
 | 
			
		||||
    if (this->stop_millis_ - this->start_millis_ >= this->start_delay_ + this->run_duration_) {
 | 
			
		||||
      return 0;  // valve was active for more than its configured duration, so we are done
 | 
			
		||||
    } else {
 | 
			
		||||
      // we're stopped; return time remaining
 | 
			
		||||
      return (this->run_duration_ - (this->stop_millis_ - this->start_millis_)) / 1000;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto completed_millis = this->start_millis_ + this->start_delay_ + this->run_duration_;
 | 
			
		||||
  if (completed_millis > millis()) {
 | 
			
		||||
    return (completed_millis - millis()) / 1000;  // running now
 | 
			
		||||
  }
 | 
			
		||||
  return 0;  // run completed
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SprinklerState SprinklerValveOperator::state() { return this->state_; }
 | 
			
		||||
@@ -402,9 +386,6 @@ void Sprinkler::loop() {
 | 
			
		||||
  for (auto &vo : this->valve_op_) {
 | 
			
		||||
    vo.loop();
 | 
			
		||||
  }
 | 
			
		||||
  if (this->prev_req_.has_request() && this->prev_req_.valve_operator()->state() == IDLE) {
 | 
			
		||||
    this->prev_req_.reset();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Sprinkler::add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControllerSwitch *enable_sw) {
 | 
			
		||||
@@ -751,7 +732,7 @@ bool Sprinkler::auto_advance() {
 | 
			
		||||
  if (this->auto_adv_sw_ != nullptr) {
 | 
			
		||||
    return this->auto_adv_sw_->state;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float Sprinkler::multiplier() {
 | 
			
		||||
@@ -991,14 +972,7 @@ optional<SprinklerValveRunRequestOrigin> Sprinkler::active_valve_request_is_from
 | 
			
		||||
  return nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
optional<size_t> Sprinkler::active_valve() {
 | 
			
		||||
  if (!this->valve_overlap_ && this->prev_req_.has_request() &&
 | 
			
		||||
      (this->prev_req_.valve_operator()->state() == STARTING || this->prev_req_.valve_operator()->state() == ACTIVE)) {
 | 
			
		||||
    return this->prev_req_.valve_as_opt();
 | 
			
		||||
  }
 | 
			
		||||
  return this->active_req_.valve_as_opt();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
optional<size_t> Sprinkler::active_valve() { return this->active_req_.valve_as_opt(); }
 | 
			
		||||
optional<size_t> Sprinkler::paused_valve() { return this->paused_valve_; }
 | 
			
		||||
 | 
			
		||||
optional<size_t> Sprinkler::queued_valve() {
 | 
			
		||||
@@ -1123,35 +1097,22 @@ uint32_t Sprinkler::total_cycle_time_enabled_valves() {
 | 
			
		||||
 | 
			
		||||
uint32_t Sprinkler::total_cycle_time_enabled_incomplete_valves() {
 | 
			
		||||
  uint32_t total_time_remaining = 0;
 | 
			
		||||
  uint32_t enabled_valve_count = 0;
 | 
			
		||||
  uint32_t incomplete_valve_count = 0;
 | 
			
		||||
  uint32_t valve_count = 0;
 | 
			
		||||
 | 
			
		||||
  for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
 | 
			
		||||
    if (this->valve_is_enabled_(valve)) {
 | 
			
		||||
      enabled_valve_count++;
 | 
			
		||||
      if (!this->valve_cycle_complete_(valve)) {
 | 
			
		||||
        if (!this->active_valve().has_value() || (valve != this->active_valve().value())) {
 | 
			
		||||
          total_time_remaining += this->valve_run_duration_adjusted(valve);
 | 
			
		||||
          incomplete_valve_count++;
 | 
			
		||||
        } else {
 | 
			
		||||
          // to get here, there must be an active valve and this valve must be equal to 'valve'
 | 
			
		||||
          if (this->active_req_.valve_operator() == nullptr) {  // no SVO has been assigned yet so it hasn't started
 | 
			
		||||
            total_time_remaining += this->valve_run_duration_adjusted(valve);
 | 
			
		||||
            incomplete_valve_count++;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
    if (this->valve_is_enabled_(valve) && !this->valve_cycle_complete_(valve)) {
 | 
			
		||||
      if (!this->active_valve().has_value() || (valve != this->active_valve().value())) {
 | 
			
		||||
        total_time_remaining += this->valve_run_duration_adjusted(valve);
 | 
			
		||||
        valve_count++;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (incomplete_valve_count >= enabled_valve_count) {
 | 
			
		||||
    incomplete_valve_count--;
 | 
			
		||||
  }
 | 
			
		||||
  if (incomplete_valve_count) {
 | 
			
		||||
  if (valve_count) {
 | 
			
		||||
    if (this->valve_overlap_) {
 | 
			
		||||
      total_time_remaining -= this->switching_delay_.value_or(0) * incomplete_valve_count;
 | 
			
		||||
      total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
 | 
			
		||||
    } else {
 | 
			
		||||
      total_time_remaining += this->switching_delay_.value_or(0) * incomplete_valve_count;
 | 
			
		||||
      total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -1188,32 +1149,31 @@ optional<uint32_t> Sprinkler::time_remaining_active_valve() {
 | 
			
		||||
      return this->active_req_.valve_operator()->time_remaining();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (this->prev_req_.has_request()) {  // try to return the value based on prev_req_...
 | 
			
		||||
    if (this->prev_req_.valve_operator() != nullptr) {
 | 
			
		||||
      return this->prev_req_.valve_operator()->time_remaining();
 | 
			
		||||
  for (auto &vo : this->valve_op_) {  // ...else return the value from the first non-IDLE SprinklerValveOperator
 | 
			
		||||
    if (vo.state() != IDLE) {
 | 
			
		||||
      return vo.time_remaining();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
optional<uint32_t> Sprinkler::time_remaining_current_operation() {
 | 
			
		||||
  if (!this->time_remaining_active_valve().has_value() && this->state_ == IDLE) {
 | 
			
		||||
    return nullopt;
 | 
			
		||||
  }
 | 
			
		||||
  auto total_time_remaining = this->time_remaining_active_valve();
 | 
			
		||||
 | 
			
		||||
  auto total_time_remaining = this->time_remaining_active_valve().value_or(0);
 | 
			
		||||
  if (this->auto_advance()) {
 | 
			
		||||
    total_time_remaining += this->total_cycle_time_enabled_incomplete_valves();
 | 
			
		||||
    if (this->repeat().value_or(0) > 0) {
 | 
			
		||||
      total_time_remaining +=
 | 
			
		||||
  if (total_time_remaining.has_value()) {
 | 
			
		||||
    if (this->auto_advance()) {
 | 
			
		||||
      total_time_remaining = total_time_remaining.value() + this->total_cycle_time_enabled_incomplete_valves();
 | 
			
		||||
      total_time_remaining =
 | 
			
		||||
          total_time_remaining.value() +
 | 
			
		||||
          (this->total_cycle_time_enabled_valves() * (this->repeat().value_or(0) - this->repeat_count().value_or(0)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->queue_enabled()) {
 | 
			
		||||
    total_time_remaining += this->total_queue_time();
 | 
			
		||||
    if (this->queue_enabled()) {
 | 
			
		||||
      total_time_remaining = total_time_remaining.value() + this->total_queue_time();
 | 
			
		||||
    }
 | 
			
		||||
    return total_time_remaining;
 | 
			
		||||
  }
 | 
			
		||||
  return total_time_remaining;
 | 
			
		||||
  return nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Sprinkler::any_controller_is_active() {
 | 
			
		||||
@@ -1345,12 +1305,6 @@ optional<size_t> Sprinkler::next_valve_number_in_cycle_(const optional<size_t> f
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Sprinkler::load_next_valve_run_request_(const optional<size_t> first_valve) {
 | 
			
		||||
  if (this->active_req_.has_request()) {
 | 
			
		||||
    this->prev_req_ = this->active_req_;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->prev_req_.reset();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->next_req_.has_request()) {
 | 
			
		||||
    if (!this->next_req_.run_duration()) {  // ensure the run duration is set correctly for consumption later on
 | 
			
		||||
      this->next_req_.set_run_duration(this->valve_run_duration_adjusted(this->next_req_.valve()));
 | 
			
		||||
 
 | 
			
		||||
@@ -170,8 +170,7 @@ class SprinklerValveOperator {
 | 
			
		||||
  uint32_t start_delay_{0};
 | 
			
		||||
  uint32_t stop_delay_{0};
 | 
			
		||||
  uint32_t run_duration_{0};
 | 
			
		||||
  uint64_t start_millis_{0};
 | 
			
		||||
  uint64_t stop_millis_{0};
 | 
			
		||||
  uint64_t pinned_millis_{0};
 | 
			
		||||
  Sprinkler *controller_{nullptr};
 | 
			
		||||
  SprinklerValve *valve_{nullptr};
 | 
			
		||||
  SprinklerState state_{IDLE};
 | 
			
		||||
@@ -539,18 +538,15 @@ class Sprinkler : public Component {
 | 
			
		||||
  /// The valve run request that is currently active
 | 
			
		||||
  SprinklerValveRunRequest active_req_;
 | 
			
		||||
 | 
			
		||||
  /// The next run request for the controller to consume after active_req_ is complete
 | 
			
		||||
  SprinklerValveRunRequest next_req_;
 | 
			
		||||
 | 
			
		||||
  /// The previous run request the controller processed
 | 
			
		||||
  SprinklerValveRunRequest prev_req_;
 | 
			
		||||
 | 
			
		||||
  /// The number of the manually selected valve currently selected
 | 
			
		||||
  optional<size_t> manual_valve_;
 | 
			
		||||
 | 
			
		||||
  /// The number of the valve to resume from (if paused)
 | 
			
		||||
  optional<size_t> paused_valve_;
 | 
			
		||||
 | 
			
		||||
  /// The next run request for the controller to consume after active_req_ is complete
 | 
			
		||||
  SprinklerValveRunRequest next_req_;
 | 
			
		||||
 | 
			
		||||
  /// Set the number of times to repeat a full cycle
 | 
			
		||||
  optional<uint32_t> target_repeats_;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,13 @@ namespace template_ {
 | 
			
		||||
static const char *const TAG = "template.sensor";
 | 
			
		||||
 | 
			
		||||
void TemplateSensor::update() {
 | 
			
		||||
  if (!this->f_.has_value())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  auto val = (*this->f_)();
 | 
			
		||||
  if (val.has_value()) {
 | 
			
		||||
    this->publish_state(*val);
 | 
			
		||||
  if (this->f_.has_value()) {
 | 
			
		||||
    auto val = (*this->f_)();
 | 
			
		||||
    if (val.has_value()) {
 | 
			
		||||
      this->publish_state(*val);
 | 
			
		||||
    }
 | 
			
		||||
  } else if (!std::isnan(this->get_raw_state())) {
 | 
			
		||||
    this->publish_state(this->get_raw_state());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
			
		||||
 
 | 
			
		||||
@@ -7,12 +7,13 @@ namespace template_ {
 | 
			
		||||
static const char *const TAG = "template.text_sensor";
 | 
			
		||||
 | 
			
		||||
void TemplateTextSensor::update() {
 | 
			
		||||
  if (!this->f_.has_value())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  auto val = (*this->f_)();
 | 
			
		||||
  if (val.has_value()) {
 | 
			
		||||
    this->publish_state(*val);
 | 
			
		||||
  if (this->f_.has_value()) {
 | 
			
		||||
    auto val = (*this->f_)();
 | 
			
		||||
    if (val.has_value()) {
 | 
			
		||||
      this->publish_state(*val);
 | 
			
		||||
    }
 | 
			
		||||
  } else if (this->has_state()) {
 | 
			
		||||
    this->publish_state(this->state);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_ID, CONF_SDO_PIN, CONF_SCL_PIN
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
AUTO_LOAD = ["binary_sensor"]
 | 
			
		||||
 | 
			
		||||
CONF_TTP229_ID = "ttp229_id"
 | 
			
		||||
 
 | 
			
		||||
@@ -57,43 +57,37 @@ void TuyaLight::setup() {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      float red, green, blue;
 | 
			
		||||
      switch (*this->color_type_) {
 | 
			
		||||
        case TuyaColorType::RGBHSV:
 | 
			
		||||
        case TuyaColorType::RGB: {
 | 
			
		||||
          auto rgb = parse_hex<uint32_t>(datapoint.value_string.substr(0, 6));
 | 
			
		||||
          if (!rgb.has_value())
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
          red = (*rgb >> 16) / 255.0f;
 | 
			
		||||
          green = ((*rgb >> 8) & 0xff) / 255.0f;
 | 
			
		||||
          blue = (*rgb & 0xff) / 255.0f;
 | 
			
		||||
          auto red = parse_hex<uint8_t>(datapoint.value_string.substr(0, 2));
 | 
			
		||||
          auto green = parse_hex<uint8_t>(datapoint.value_string.substr(2, 2));
 | 
			
		||||
          auto blue = parse_hex<uint8_t>(datapoint.value_string.substr(4, 2));
 | 
			
		||||
          if (red.has_value() && green.has_value() && blue.has_value()) {
 | 
			
		||||
            auto rgb_call = this->state_->make_call();
 | 
			
		||||
            rgb_call.set_rgb(float(*red) / 255, float(*green) / 255, float(*blue) / 255);
 | 
			
		||||
            rgb_call.perform();
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case TuyaColorType::HSV: {
 | 
			
		||||
          auto hue = parse_hex<uint16_t>(datapoint.value_string.substr(0, 4));
 | 
			
		||||
          auto saturation = parse_hex<uint16_t>(datapoint.value_string.substr(4, 4));
 | 
			
		||||
          auto value = parse_hex<uint16_t>(datapoint.value_string.substr(8, 4));
 | 
			
		||||
          if (!hue.has_value() || !saturation.has_value() || !value.has_value())
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
          hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue);
 | 
			
		||||
          if (hue.has_value() && saturation.has_value() && value.has_value()) {
 | 
			
		||||
            float red, green, blue;
 | 
			
		||||
            hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue);
 | 
			
		||||
            auto rgb_call = this->state_->make_call();
 | 
			
		||||
            rgb_call.set_rgb(red, green, blue);
 | 
			
		||||
            rgb_call.perform();
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      float current_red, current_green, current_blue;
 | 
			
		||||
      this->state_->current_values_as_rgb(¤t_red, ¤t_green, ¤t_blue);
 | 
			
		||||
      if (red == current_red && green == current_green && blue == current_blue)
 | 
			
		||||
        return;
 | 
			
		||||
      auto rgb_call = this->state_->make_call();
 | 
			
		||||
      rgb_call.set_rgb(red, green, blue);
 | 
			
		||||
      rgb_call.perform();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (min_value_datapoint_id_.has_value()) {
 | 
			
		||||
    this->parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_);
 | 
			
		||||
    parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -162,7 +156,7 @@ void TuyaLight::write_state(light::LightState *state) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!state->current_values.is_on() && this->switch_id_.has_value()) {
 | 
			
		||||
    this->parent_->set_boolean_datapoint_value(*this->switch_id_, false);
 | 
			
		||||
    parent_->set_boolean_datapoint_value(*this->switch_id_, false);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -172,14 +166,14 @@ void TuyaLight::write_state(light::LightState *state) {
 | 
			
		||||
      if (this->color_temperature_invert_) {
 | 
			
		||||
        color_temp_int = this->color_temperature_max_value_ - color_temp_int;
 | 
			
		||||
      }
 | 
			
		||||
      this->parent_->set_integer_datapoint_value(*this->color_temperature_id_, color_temp_int);
 | 
			
		||||
      parent_->set_integer_datapoint_value(*this->color_temperature_id_, color_temp_int);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this->dimmer_id_.has_value()) {
 | 
			
		||||
      auto brightness_int = static_cast<uint32_t>(brightness * this->max_value_);
 | 
			
		||||
      brightness_int = std::max(brightness_int, this->min_value_);
 | 
			
		||||
 | 
			
		||||
      this->parent_->set_integer_datapoint_value(*this->dimmer_id_, brightness_int);
 | 
			
		||||
      parent_->set_integer_datapoint_value(*this->dimmer_id_, brightness_int);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -216,7 +210,7 @@ void TuyaLight::write_state(light::LightState *state) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->switch_id_.has_value()) {
 | 
			
		||||
    this->parent_->set_boolean_datapoint_value(*this->switch_id_, true);
 | 
			
		||||
    parent_->set_boolean_datapoint_value(*this->switch_id_, true);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -86,26 +86,10 @@ void ESP32ArduinoUARTComponent::setup() {
 | 
			
		||||
  is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 1;
 | 
			
		||||
  is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 3;
 | 
			
		||||
#endif
 | 
			
		||||
  static uint8_t next_uart_num = 0;
 | 
			
		||||
  if (is_default_tx && is_default_rx && next_uart_num == 0) {
 | 
			
		||||
  if (is_default_tx && is_default_rx) {
 | 
			
		||||
    this->hw_serial_ = &Serial;
 | 
			
		||||
    next_uart_num++;
 | 
			
		||||
  } else {
 | 
			
		||||
#ifdef USE_LOGGER
 | 
			
		||||
    // The logger doesn't use this UART component, instead it targets the UARTs
 | 
			
		||||
    // directly (i.e. Serial/Serial0, Serial1, and Serial2). If the logger is
 | 
			
		||||
    // enabled, skip the UART that it is configured to use.
 | 
			
		||||
    if (logger::global_logger->get_baud_rate() > 0 && logger::global_logger->get_uart() == next_uart_num) {
 | 
			
		||||
      next_uart_num++;
 | 
			
		||||
    }
 | 
			
		||||
#endif  // USE_LOGGER
 | 
			
		||||
 | 
			
		||||
    if (next_uart_num >= UART_NUM_MAX) {
 | 
			
		||||
      ESP_LOGW(TAG, "Maximum number of UART components created already.");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static uint8_t next_uart_num = 1;
 | 
			
		||||
    this->number_ = next_uart_num;
 | 
			
		||||
    this->hw_serial_ = new HardwareSerial(next_uart_num++);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include <driver/uart.h>
 | 
			
		||||
#include <HardwareSerial.h>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from esphome.const import CONF_ID, CONF_MICROPHONE, CONF_SPEAKER
 | 
			
		||||
from esphome.const import CONF_ID, CONF_MICROPHONE
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import register_action
 | 
			
		||||
from esphome.components import microphone, speaker
 | 
			
		||||
from esphome.components import microphone
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["socket"]
 | 
			
		||||
DEPENDENCIES = ["api", "microphone"]
 | 
			
		||||
@@ -34,7 +34,6 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(VoiceAssistant),
 | 
			
		||||
        cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone),
 | 
			
		||||
        cv.Optional(CONF_SPEAKER): cv.use_id(speaker.Speaker),
 | 
			
		||||
        cv.Optional(CONF_ON_START): automation.validate_automation(single=True),
 | 
			
		||||
        cv.Optional(CONF_ON_STT_END): automation.validate_automation(single=True),
 | 
			
		||||
        cv.Optional(CONF_ON_TTS_START): automation.validate_automation(single=True),
 | 
			
		||||
@@ -52,10 +51,6 @@ async def to_code(config):
 | 
			
		||||
    mic = await cg.get_variable(config[CONF_MICROPHONE])
 | 
			
		||||
    cg.add(var.set_microphone(mic))
 | 
			
		||||
 | 
			
		||||
    if CONF_SPEAKER in config:
 | 
			
		||||
        spkr = await cg.get_variable(config[CONF_SPEAKER])
 | 
			
		||||
        cg.add(var.set_speaker(spkr))
 | 
			
		||||
 | 
			
		||||
    if CONF_ON_START in config:
 | 
			
		||||
        await automation.build_automation(
 | 
			
		||||
            var.get_start_trigger(), [], config[CONF_ON_START]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,7 @@
 | 
			
		||||
#include "voice_assistant.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace voice_assistant {
 | 
			
		||||
 | 
			
		||||
@@ -37,27 +33,6 @@ void VoiceAssistant::setup() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SPEAKER
 | 
			
		||||
  if (this->speaker_ != nullptr) {
 | 
			
		||||
    struct sockaddr_storage server;
 | 
			
		||||
 | 
			
		||||
    socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), 6055);
 | 
			
		||||
    if (sl == 0) {
 | 
			
		||||
      ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    server.ss_family = AF_INET;
 | 
			
		||||
 | 
			
		||||
    err = socket_->bind((struct sockaddr *) &server, sizeof(server));
 | 
			
		||||
    if (err != 0) {
 | 
			
		||||
      ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  this->mic_->add_data_callback([this](const std::vector<uint8_t> &data) {
 | 
			
		||||
    if (!this->running_) {
 | 
			
		||||
      return;
 | 
			
		||||
@@ -66,21 +41,6 @@ void VoiceAssistant::setup() {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void VoiceAssistant::loop() {
 | 
			
		||||
#ifdef USE_SPEAKER
 | 
			
		||||
  if (this->speaker_ == nullptr) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t buf[1024];
 | 
			
		||||
  auto len = this->socket_->read(buf, sizeof(buf));
 | 
			
		||||
  if (len == -1) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->speaker_->play(buf, len);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) {
 | 
			
		||||
  ESP_LOGD(TAG, "Starting...");
 | 
			
		||||
 | 
			
		||||
@@ -194,5 +154,3 @@ VoiceAssistant *global_voice_assistant = nullptr;  // NOLINT(cppcoreguidelines-a
 | 
			
		||||
 | 
			
		||||
}  // namespace voice_assistant
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_VOICE_ASSISTANT
 | 
			
		||||
 
 | 
			
		||||
@@ -1,49 +1,24 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/api/api_pb2.h"
 | 
			
		||||
#include "esphome/components/api/api_server.h"
 | 
			
		||||
#include "esphome/components/microphone/microphone.h"
 | 
			
		||||
#ifdef USE_SPEAKER
 | 
			
		||||
#include "esphome/components/speaker/speaker.h"
 | 
			
		||||
#endif
 | 
			
		||||
#include "esphome/components/socket/socket.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace voice_assistant {
 | 
			
		||||
 | 
			
		||||
// Version 1: Initial version
 | 
			
		||||
// Version 2: Adds raw speaker support
 | 
			
		||||
static const uint32_t INITIAL_VERSION = 1;
 | 
			
		||||
static const uint32_t SPEAKER_SUPPORT = 2;
 | 
			
		||||
 | 
			
		||||
class VoiceAssistant : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void start(struct sockaddr_storage *addr, uint16_t port);
 | 
			
		||||
 | 
			
		||||
  void set_microphone(microphone::Microphone *mic) { this->mic_ = mic; }
 | 
			
		||||
#ifdef USE_SPEAKER
 | 
			
		||||
  void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  uint32_t get_version() const {
 | 
			
		||||
#ifdef USE_SPEAKER
 | 
			
		||||
    if (this->speaker_ != nullptr)
 | 
			
		||||
      return SPEAKER_SUPPORT;
 | 
			
		||||
#endif
 | 
			
		||||
    return INITIAL_VERSION;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void request_start();
 | 
			
		||||
  void signal_stop();
 | 
			
		||||
@@ -69,9 +44,6 @@ class VoiceAssistant : public Component {
 | 
			
		||||
  Trigger<std::string, std::string> *error_trigger_ = new Trigger<std::string, std::string>();
 | 
			
		||||
 | 
			
		||||
  microphone::Microphone *mic_{nullptr};
 | 
			
		||||
#ifdef USE_SPEAKER
 | 
			
		||||
  speaker::Speaker *speaker_{nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  bool running_{false};
 | 
			
		||||
};
 | 
			
		||||
@@ -90,5 +62,3 @@ extern VoiceAssistant *global_voice_assistant;  // NOLINT(cppcoreguidelines-avoi
 | 
			
		||||
 | 
			
		||||
}  // namespace voice_assistant
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_VOICE_ASSISTANT
 | 
			
		||||
 
 | 
			
		||||
@@ -102,16 +102,6 @@ void Wiegand::loop() {
 | 
			
		||||
      uint8_t key = KEYS[value];
 | 
			
		||||
      this->send_key_(key);
 | 
			
		||||
    }
 | 
			
		||||
  } else if (count == 8) {
 | 
			
		||||
    if ((value ^ 0xf0) >> 4 == (value & 0xf)) {
 | 
			
		||||
      value &= 0xf;
 | 
			
		||||
      for (auto *trigger : this->key_triggers_)
 | 
			
		||||
        trigger->trigger(value);
 | 
			
		||||
      if (value < 12) {
 | 
			
		||||
        uint8_t key = KEYS[value];
 | 
			
		||||
        this->send_key_(key);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGD(TAG, "received unknown %d-bit value: %llx", count, value);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -706,7 +706,6 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  struct dhcps_lease lease {};
 | 
			
		||||
  lease.enable = true;
 | 
			
		||||
  network::IPAddress start_address = info.ip.addr;
 | 
			
		||||
  start_address[3] += 99;
 | 
			
		||||
  lease.start_ip.addr = static_cast<uint32_t>(start_address);
 | 
			
		||||
 
 | 
			
		||||
@@ -767,10 +767,11 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
 | 
			
		||||
    info.gw.addr = static_cast<uint32_t>(network::IPAddress(192, 168, 4, 1));
 | 
			
		||||
    info.netmask.addr = static_cast<uint32_t>(network::IPAddress(255, 255, 255, 0));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  err = esp_netif_dhcpc_stop(s_sta_netif);
 | 
			
		||||
  if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) {
 | 
			
		||||
    ESP_LOGV(TAG, "esp_netif_dhcpc_stop failed: %s", esp_err_to_name(err));
 | 
			
		||||
  esp_netif_dhcp_status_t dhcp_status;
 | 
			
		||||
  esp_netif_dhcps_get_status(s_sta_netif, &dhcp_status);
 | 
			
		||||
  err = esp_netif_dhcps_stop(s_sta_netif);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGV(TAG, "esp_netif_dhcps_stop failed! %d", err);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1455,7 +1455,6 @@ class SplitDefault(Optional):
 | 
			
		||||
        esp32_arduino=vol.UNDEFINED,
 | 
			
		||||
        esp32_idf=vol.UNDEFINED,
 | 
			
		||||
        rp2040=vol.UNDEFINED,
 | 
			
		||||
        host=vol.UNDEFINED,
 | 
			
		||||
    ):
 | 
			
		||||
        super().__init__(key)
 | 
			
		||||
        self._esp8266_default = vol.default_factory(esp8266)
 | 
			
		||||
@@ -1466,7 +1465,6 @@ class SplitDefault(Optional):
 | 
			
		||||
            esp32_idf if esp32 is vol.UNDEFINED else esp32
 | 
			
		||||
        )
 | 
			
		||||
        self._rp2040_default = vol.default_factory(rp2040)
 | 
			
		||||
        self._host_default = vol.default_factory(host)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def default(self):
 | 
			
		||||
@@ -1478,8 +1476,6 @@ class SplitDefault(Optional):
 | 
			
		||||
            return self._esp32_idf_default
 | 
			
		||||
        if CORE.is_rp2040:
 | 
			
		||||
            return self._rp2040_default
 | 
			
		||||
        if CORE.is_host:
 | 
			
		||||
            return self._host_default
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @default.setter
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,14 @@
 | 
			
		||||
"""Constants used by esphome."""
 | 
			
		||||
 | 
			
		||||
__version__ = "2023.5.3"
 | 
			
		||||
__version__ = "2023.5.0-dev"
 | 
			
		||||
 | 
			
		||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
 | 
			
		||||
 | 
			
		||||
PLATFORM_ESP32 = "esp32"
 | 
			
		||||
PLATFORM_ESP8266 = "esp8266"
 | 
			
		||||
PLATFORM_RP2040 = "rp2040"
 | 
			
		||||
PLATFORM_HOST = "host"
 | 
			
		||||
 | 
			
		||||
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_HOST]
 | 
			
		||||
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]
 | 
			
		||||
 | 
			
		||||
SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
 | 
			
		||||
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
 | 
			
		||||
@@ -377,7 +376,6 @@ CONF_MAKE_ID = "make_id"
 | 
			
		||||
CONF_MANUAL_IP = "manual_ip"
 | 
			
		||||
CONF_MANUFACTURER_ID = "manufacturer_id"
 | 
			
		||||
CONF_MASK_DISTURBER = "mask_disturber"
 | 
			
		||||
CONF_MAX_BRIGHTNESS = "max_brightness"
 | 
			
		||||
CONF_MAX_COOLING_RUN_TIME = "max_cooling_run_time"
 | 
			
		||||
CONF_MAX_CURRENT = "max_current"
 | 
			
		||||
CONF_MAX_DURATION = "max_duration"
 | 
			
		||||
@@ -397,7 +395,6 @@ CONF_MEDIUM = "medium"
 | 
			
		||||
CONF_MEMORY_BLOCKS = "memory_blocks"
 | 
			
		||||
CONF_METHOD = "method"
 | 
			
		||||
CONF_MICROPHONE = "microphone"
 | 
			
		||||
CONF_MIN_BRIGHTNESS = "min_brightness"
 | 
			
		||||
CONF_MIN_COOLING_OFF_TIME = "min_cooling_off_time"
 | 
			
		||||
CONF_MIN_COOLING_RUN_TIME = "min_cooling_run_time"
 | 
			
		||||
CONF_MIN_FAN_MODE_SWITCHING_TIME = "min_fan_mode_switching_time"
 | 
			
		||||
@@ -662,7 +659,6 @@ CONF_SLEEP_WHEN_DONE = "sleep_when_done"
 | 
			
		||||
CONF_SONY = "sony"
 | 
			
		||||
CONF_SOURCE = "source"
 | 
			
		||||
CONF_SOURCE_ID = "source_id"
 | 
			
		||||
CONF_SPEAKER = "speaker"
 | 
			
		||||
CONF_SPEED = "speed"
 | 
			
		||||
CONF_SPEED_COMMAND_TOPIC = "speed_command_topic"
 | 
			
		||||
CONF_SPEED_COUNT = "speed_count"
 | 
			
		||||
@@ -1004,7 +1000,6 @@ DEVICE_CLASS_TIMESTAMP = "timestamp"
 | 
			
		||||
DEVICE_CLASS_UPDATE = "update"
 | 
			
		||||
DEVICE_CLASS_VIBRATION = "vibration"
 | 
			
		||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
 | 
			
		||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS = "volatile_organic_compounds_parts"
 | 
			
		||||
DEVICE_CLASS_VOLTAGE = "voltage"
 | 
			
		||||
DEVICE_CLASS_VOLUME = "volume"
 | 
			
		||||
DEVICE_CLASS_VOLUME_STORAGE = "volume_storage"
 | 
			
		||||
 
 | 
			
		||||
@@ -602,10 +602,6 @@ class EsphomeCore:
 | 
			
		||||
    def is_rp2040(self):
 | 
			
		||||
        return self.target_platform == "rp2040"
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def is_host(self):
 | 
			
		||||
        return self.target_platform == "host"
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def target_framework(self):
 | 
			
		||||
        return self.data[KEY_CORE][KEY_TARGET_FRAMEWORK]
 | 
			
		||||
 
 | 
			
		||||
@@ -73,8 +73,6 @@
 | 
			
		||||
#define USE_WIFI_11KV_SUPPORT
 | 
			
		||||
#define USE_BLUETOOTH_PROXY
 | 
			
		||||
#define USE_VOICE_ASSISTANT
 | 
			
		||||
#define USE_MICROPHONE
 | 
			
		||||
#define USE_SPEAKER
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#define USE_ARDUINO_VERSION_CODE VERSION_CODE(2, 0, 5)
 | 
			
		||||
@@ -102,10 +100,6 @@
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
#define USE_SOCKET_IMPL_BSD_SOCKETS
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Disabled feature flags
 | 
			
		||||
//#define USE_BSEC  // Requires a library with proprietary license.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -84,13 +84,4 @@ std::string EntityBase_DeviceClass::get_device_class() {
 | 
			
		||||
 | 
			
		||||
void EntityBase_DeviceClass::set_device_class(const char *device_class) { this->device_class_ = device_class; }
 | 
			
		||||
 | 
			
		||||
std::string EntityBase_UnitOfMeasurement::get_unit_of_measurement() {
 | 
			
		||||
  if (this->unit_of_measurement_ == nullptr)
 | 
			
		||||
    return "";
 | 
			
		||||
  return this->unit_of_measurement_;
 | 
			
		||||
}
 | 
			
		||||
void EntityBase_UnitOfMeasurement::set_unit_of_measurement(const char *unit_of_measurement) {
 | 
			
		||||
  this->unit_of_measurement_ = unit_of_measurement;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -74,15 +74,4 @@ class EntityBase_DeviceClass {
 | 
			
		||||
  const char *device_class_{nullptr};  ///< Device class override
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class EntityBase_UnitOfMeasurement {
 | 
			
		||||
 public:
 | 
			
		||||
  /// Get the unit of measurement, using the manual override if set.
 | 
			
		||||
  std::string get_unit_of_measurement();
 | 
			
		||||
  /// Manually set the unit of measurement.
 | 
			
		||||
  void set_unit_of_measurement(const char *unit_of_measurement);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  const char *unit_of_measurement_{nullptr};  ///< Unit of measurement override
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,13 @@
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cctype>
 | 
			
		||||
#include <cmath>
 | 
			
		||||
#include <cstdarg>
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <cstdarg>
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP8266)
 | 
			
		||||
#include <osapi.h>
 | 
			
		||||
@@ -19,20 +18,17 @@
 | 
			
		||||
#elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
 | 
			
		||||
#include <Esp.h>
 | 
			
		||||
#elif defined(USE_ESP_IDF)
 | 
			
		||||
#include <freertos/FreeRTOS.h>
 | 
			
		||||
#include <freertos/portmacro.h>
 | 
			
		||||
#include "esp_mac.h"
 | 
			
		||||
#include "esp_random.h"
 | 
			
		||||
#include "esp_system.h"
 | 
			
		||||
#include <freertos/FreeRTOS.h>
 | 
			
		||||
#include <freertos/portmacro.h>
 | 
			
		||||
#elif defined(USE_RP2040)
 | 
			
		||||
#if defined(USE_WIFI)
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#endif
 | 
			
		||||
#include <hardware/structs/rosc.h>
 | 
			
		||||
#include <hardware/sync.h>
 | 
			
		||||
#elif defined(USE_HOST)
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <random>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
 | 
			
		||||
@@ -42,8 +38,6 @@
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "helpers";
 | 
			
		||||
 | 
			
		||||
// STL backports
 | 
			
		||||
 | 
			
		||||
#if _GLIBCXX_RELEASE < 7
 | 
			
		||||
@@ -112,11 +106,6 @@ uint32_t random_uint32() {
 | 
			
		||||
    result |= rosc_hw->randombit;
 | 
			
		||||
  }
 | 
			
		||||
  return result;
 | 
			
		||||
#elif defined(USE_HOST)
 | 
			
		||||
  std::random_device dev;
 | 
			
		||||
  std::mt19937 rng(dev());
 | 
			
		||||
  std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max());
 | 
			
		||||
  return dist(rng);
 | 
			
		||||
#else
 | 
			
		||||
#error "No random source available for this configuration."
 | 
			
		||||
#endif
 | 
			
		||||
@@ -138,19 +127,6 @@ bool random_bytes(uint8_t *data, size_t len) {
 | 
			
		||||
    *data++ = result;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
#elif defined(USE_HOST)
 | 
			
		||||
  FILE *fp = fopen("/dev/urandom", "r");
 | 
			
		||||
  if (fp == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Could not open /dev/urandom, errno=%d", errno);
 | 
			
		||||
    exit(1);
 | 
			
		||||
  }
 | 
			
		||||
  size_t read = fread(data, 1, len, fp);
 | 
			
		||||
  if (read != len) {
 | 
			
		||||
    ESP_LOGW(TAG, "Not enough data from /dev/urandom");
 | 
			
		||||
    exit(1);
 | 
			
		||||
  }
 | 
			
		||||
  fclose(fp);
 | 
			
		||||
  return true;
 | 
			
		||||
#else
 | 
			
		||||
#error "No random source available for this configuration."
 | 
			
		||||
#endif
 | 
			
		||||
@@ -169,7 +145,7 @@ std::string str_truncate(const std::string &str, size_t length) {
 | 
			
		||||
  return str.length() > length ? str.substr(0, length) : str;
 | 
			
		||||
}
 | 
			
		||||
std::string str_until(const char *str, char ch) {
 | 
			
		||||
  const char *pos = strchr(str, ch);
 | 
			
		||||
  char *pos = strchr(str, ch);
 | 
			
		||||
  return pos == nullptr ? std::string(str) : std::string(str, pos - str);
 | 
			
		||||
}
 | 
			
		||||
std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); }
 | 
			
		||||
@@ -419,7 +395,7 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// System APIs
 | 
			
		||||
#if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_HOST)
 | 
			
		||||
#if defined(USE_ESP8266) || defined(USE_RP2040)
 | 
			
		||||
// ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS.
 | 
			
		||||
Mutex::Mutex() {}
 | 
			
		||||
void Mutex::lock() {}
 | 
			
		||||
@@ -493,7 +469,6 @@ void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); }
 | 
			
		||||
 | 
			
		||||
void delay_microseconds_safe(uint32_t us) {  // avoids CPU locks that could trigger WDT or affect WiFi/BT stability
 | 
			
		||||
  uint32_t start = micros();
 | 
			
		||||
 | 
			
		||||
  const uint32_t lag = 5000;  // microseconds, specifies the maximum time for a CPU busy-loop.
 | 
			
		||||
                              // it must be larger than the worst-case duration of a delay(1) call (hardware tasks)
 | 
			
		||||
                              // 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_JSON
 | 
			
		||||
 
 | 
			
		||||
@@ -108,6 +108,7 @@ platform_packages =
 | 
			
		||||
    platformio/framework-arduinoespressif32@~3.20005.0
 | 
			
		||||
 | 
			
		||||
framework = arduino
 | 
			
		||||
board = nodemcu-32s
 | 
			
		||||
lib_deps =
 | 
			
		||||
    ; order matters with lib-deps; some of the libs in common:arduino.lib_deps
 | 
			
		||||
    ; don't declare built-in libraries as dependencies, so they have to be declared first
 | 
			
		||||
@@ -120,7 +121,7 @@ lib_deps =
 | 
			
		||||
    HTTPClient                           ; http_request,nextion (Arduino built-in)
 | 
			
		||||
    ESPmDNS                              ; mdns (Arduino built-in)
 | 
			
		||||
    DNSServer                            ; captive_portal (Arduino built-in)
 | 
			
		||||
    esphome/ESP32-audioI2S@2.0.7         ; i2s_audio
 | 
			
		||||
    esphome/ESP32-audioI2S@2.0.6         ; i2s_audio
 | 
			
		||||
    crankyoldgit/IRremoteESP8266@2.7.12  ; heatpumpir
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:arduino.build_flags}
 | 
			
		||||
@@ -167,9 +168,6 @@ build_flags =
 | 
			
		||||
    -DUSE_RP2040_FRAMEWORK_ARDUINO
 | 
			
		||||
 | 
			
		||||
; All the actual environments are defined below.
 | 
			
		||||
 | 
			
		||||
;;;;;;;; ESP8266 ;;;;;;;;
 | 
			
		||||
 | 
			
		||||
[env:esp8266-arduino]
 | 
			
		||||
extends = common:esp8266-arduino
 | 
			
		||||
board = nodemcuv2
 | 
			
		||||
@@ -184,8 +182,6 @@ build_flags =
 | 
			
		||||
    ${common:esp8266-arduino.build_flags}
 | 
			
		||||
    ${flags:clangtidy.build_flags}
 | 
			
		||||
 | 
			
		||||
;;;;;;;; ESP32 ;;;;;;;;
 | 
			
		||||
 | 
			
		||||
[env:esp32-arduino]
 | 
			
		||||
extends = common:esp32-arduino
 | 
			
		||||
board = esp32dev
 | 
			
		||||
@@ -193,7 +189,6 @@ board_build.partitions = huge_app.csv
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-arduino.build_flags}
 | 
			
		||||
    ${flags:runtime.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32
 | 
			
		||||
 | 
			
		||||
[env:esp32-arduino-tidy]
 | 
			
		||||
extends = common:esp32-arduino
 | 
			
		||||
@@ -201,7 +196,6 @@ board = esp32dev
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-arduino.build_flags}
 | 
			
		||||
    ${flags:clangtidy.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32
 | 
			
		||||
 | 
			
		||||
[env:esp32-idf]
 | 
			
		||||
extends = common:esp32-idf
 | 
			
		||||
@@ -210,7 +204,6 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-idf.build_flags}
 | 
			
		||||
    ${flags:runtime.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32
 | 
			
		||||
 | 
			
		||||
[env:esp32-idf-tidy]
 | 
			
		||||
extends = common:esp32-idf
 | 
			
		||||
@@ -219,25 +212,6 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf-tidy
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-idf.build_flags}
 | 
			
		||||
    ${flags:clangtidy.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32
 | 
			
		||||
 | 
			
		||||
;;;;;;;; ESP32-C3 ;;;;;;;;
 | 
			
		||||
 | 
			
		||||
[env:esp32c3-arduino]
 | 
			
		||||
extends = common:esp32-arduino
 | 
			
		||||
board = esp32-c3-devkitm-1
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-arduino.build_flags}
 | 
			
		||||
    ${flags:runtime.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32C3
 | 
			
		||||
 | 
			
		||||
[env:esp32c3-arduino-tidy]
 | 
			
		||||
extends = common:esp32-arduino
 | 
			
		||||
board = esp32-c3-devkitm-1
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-arduino.build_flags}
 | 
			
		||||
    ${flags:clangtidy.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32C3
 | 
			
		||||
 | 
			
		||||
[env:esp32c3-idf]
 | 
			
		||||
extends = common:esp32-idf
 | 
			
		||||
@@ -246,7 +220,6 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32c3-idf
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-idf.build_flags}
 | 
			
		||||
    ${flags:runtime.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32C3
 | 
			
		||||
 | 
			
		||||
[env:esp32c3-idf-tidy]
 | 
			
		||||
extends = common:esp32-idf
 | 
			
		||||
@@ -255,25 +228,6 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32c3-idf-tidy
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-idf.build_flags}
 | 
			
		||||
    ${flags:clangtidy.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32C3
 | 
			
		||||
 | 
			
		||||
;;;;;;;; ESP32-S2 ;;;;;;;;
 | 
			
		||||
 | 
			
		||||
[env:esp32s2-arduino]
 | 
			
		||||
extends = common:esp32-arduino
 | 
			
		||||
board = esp32-s2-kaluga-1
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-arduino.build_flags}
 | 
			
		||||
    ${flags:runtime.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32S2
 | 
			
		||||
 | 
			
		||||
[env:esp32s2-arduino-tidy]
 | 
			
		||||
extends = common:esp32-arduino
 | 
			
		||||
board = esp32-s2-kaluga-1
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-arduino.build_flags}
 | 
			
		||||
    ${flags:clangtidy.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32S2
 | 
			
		||||
 | 
			
		||||
[env:esp32s2-idf]
 | 
			
		||||
extends = common:esp32-idf
 | 
			
		||||
@@ -282,7 +236,6 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-idf.build_flags}
 | 
			
		||||
    ${flags:runtime.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32S2
 | 
			
		||||
 | 
			
		||||
[env:esp32s2-idf-tidy]
 | 
			
		||||
extends = common:esp32-idf
 | 
			
		||||
@@ -291,45 +244,6 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf-tidy
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-idf.build_flags}
 | 
			
		||||
    ${flags:clangtidy.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32S2
 | 
			
		||||
 | 
			
		||||
;;;;;;;; ESP32-S3 ;;;;;;;;
 | 
			
		||||
 | 
			
		||||
[env:esp32s3-arduino]
 | 
			
		||||
extends = common:esp32-arduino
 | 
			
		||||
board = esp32-s3-devkitc-1
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-arduino.build_flags}
 | 
			
		||||
    ${flags:runtime.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
 | 
			
		||||
[env:esp32s3-arduino-tidy]
 | 
			
		||||
extends = common:esp32-arduino
 | 
			
		||||
board = esp32-s3-devkitc-1
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-arduino.build_flags}
 | 
			
		||||
    ${flags:clangtidy.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
 | 
			
		||||
[env:esp32s3-idf]
 | 
			
		||||
extends = common:esp32-idf
 | 
			
		||||
board = esp32-s3-devkitc-1
 | 
			
		||||
board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s3-idf
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-idf.build_flags}
 | 
			
		||||
    ${flags:runtime.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
 | 
			
		||||
[env:esp32s3-idf-tidy]
 | 
			
		||||
extends = common:esp32-idf
 | 
			
		||||
board = esp32-s3-devkitc-1
 | 
			
		||||
board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s3-idf-tidy
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:esp32-idf.build_flags}
 | 
			
		||||
    ${flags:clangtidy.build_flags}
 | 
			
		||||
    -DUSE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
 | 
			
		||||
;;;;;;;; RP2040 ;;;;;;;;
 | 
			
		||||
 | 
			
		||||
[env:rp2040-pico-arduino]
 | 
			
		||||
extends = common:rp2040-arduino
 | 
			
		||||
@@ -337,12 +251,3 @@ board = rpipico
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:rp2040-arduino.build_flags}
 | 
			
		||||
    ${flags:runtime.build_flags}
 | 
			
		||||
 | 
			
		||||
[env:host]
 | 
			
		||||
extends = common
 | 
			
		||||
platform = platformio/native
 | 
			
		||||
lib_deps =
 | 
			
		||||
    esphome/noise-c@0.1.1  ; used by api
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common.build_flags}
 | 
			
		||||
    -DUSE_HOST
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,14 @@ PyYAML==6.0
 | 
			
		||||
paho-mqtt==1.6.1
 | 
			
		||||
colorama==0.4.6
 | 
			
		||||
tornado==6.3.1
 | 
			
		||||
tzlocal==5.0.1    # from time
 | 
			
		||||
tzlocal==4.2    # from time
 | 
			
		||||
tzdata>=2021.1  # from time
 | 
			
		||||
pyserial==3.5
 | 
			
		||||
platformio==6.1.6  # When updating platformio, also update Dockerfile
 | 
			
		||||
esptool==4.5.1
 | 
			
		||||
click==8.1.3
 | 
			
		||||
esphome-dashboard==20230516.0
 | 
			
		||||
aioesphomeapi==13.7.5
 | 
			
		||||
esphome-dashboard==20230214.0
 | 
			
		||||
aioesphomeapi==13.7.2
 | 
			
		||||
zeroconf==0.60.0
 | 
			
		||||
 | 
			
		||||
# esp-idf requires this, but doesn't bundle it by default
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ solve_registry = []
 | 
			
		||||
def get_component_names():
 | 
			
		||||
    from esphome.loader import CORE_COMPONENTS_PATH
 | 
			
		||||
 | 
			
		||||
    component_names = ["esphome", "sensor", "esp32", "esp8266"]
 | 
			
		||||
    component_names = ["esphome", "sensor"]
 | 
			
		||||
 | 
			
		||||
    for d in os.listdir(CORE_COMPONENTS_PATH):
 | 
			
		||||
        if not d.startswith("__") and os.path.isdir(
 | 
			
		||||
@@ -109,13 +109,6 @@ def write_file(name, obj):
 | 
			
		||||
    print(f"Wrote {full_path}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def delete_extra_files(keep_names):
 | 
			
		||||
    for d in os.listdir(args.output_path):
 | 
			
		||||
        if d.endswith(".json") and not d[:-5] in keep_names:
 | 
			
		||||
            os.remove(os.path.join(args.output_path, d))
 | 
			
		||||
            print(f"Deleted {d}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def register_module_schemas(key, module, manifest=None):
 | 
			
		||||
    for name, schema in module_schemas(module):
 | 
			
		||||
        register_known_schema(key, name, schema)
 | 
			
		||||
@@ -157,7 +150,7 @@ def module_schemas(module):
 | 
			
		||||
    schemas = {}
 | 
			
		||||
    for m_attr_name in dir(module):
 | 
			
		||||
        m_attr_obj = getattr(module, m_attr_name)
 | 
			
		||||
        if is_convertible_schema(m_attr_obj):
 | 
			
		||||
        if isConvertibleSchema(m_attr_obj):
 | 
			
		||||
            schemas[module_str.find(m_attr_name)] = [m_attr_name, m_attr_obj]
 | 
			
		||||
 | 
			
		||||
    for pos in sorted(schemas.keys()):
 | 
			
		||||
@@ -247,34 +240,25 @@ def do_pins():
 | 
			
		||||
        pins_providers.append(pin_registry)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def setBoards(obj, boards):
 | 
			
		||||
    obj[S_TYPE] = "enum"
 | 
			
		||||
    obj["values"] = {}
 | 
			
		||||
    for k, v in boards.items():
 | 
			
		||||
        obj["values"][k] = {"docs": v["name"]}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def do_esp32():
 | 
			
		||||
    import esphome.components.esp32.boards as esp32_boards
 | 
			
		||||
 | 
			
		||||
    setBoards(
 | 
			
		||||
    setEnum(
 | 
			
		||||
        output["esp32"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"]["board"],
 | 
			
		||||
        esp32_boards.BOARDS,
 | 
			
		||||
        list(esp32_boards.BOARDS.keys()),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def do_esp8266():
 | 
			
		||||
    import esphome.components.esp8266.boards as esp8266_boards
 | 
			
		||||
 | 
			
		||||
    setBoards(
 | 
			
		||||
    setEnum(
 | 
			
		||||
        output["esp8266"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"]["board"],
 | 
			
		||||
        esp8266_boards.BOARDS,
 | 
			
		||||
        list(esp8266_boards.ESP8266_BOARD_PINS.keys()),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fix_remote_receiver():
 | 
			
		||||
    if "remote_receiver.binary_sensor" not in output:
 | 
			
		||||
        return
 | 
			
		||||
    remote_receiver_schema = output["remote_receiver.binary_sensor"]["schemas"]
 | 
			
		||||
    remote_receiver_schema["CONFIG_SCHEMA"] = {
 | 
			
		||||
        "type": "schema",
 | 
			
		||||
@@ -291,8 +275,6 @@ def fix_remote_receiver():
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fix_script():
 | 
			
		||||
    if "script" not in output:
 | 
			
		||||
        return
 | 
			
		||||
    output["script"][S_SCHEMAS][S_CONFIG_SCHEMA][S_TYPE] = S_SCHEMA
 | 
			
		||||
    config_schema = output["script"][S_SCHEMAS][S_CONFIG_SCHEMA]
 | 
			
		||||
    config_schema[S_SCHEMA][S_CONFIG_VARS]["id"]["id_type"] = {
 | 
			
		||||
@@ -301,17 +283,7 @@ def fix_script():
 | 
			
		||||
    config_schema["is_list"] = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fix_font():
 | 
			
		||||
    if "font" not in output:
 | 
			
		||||
        return
 | 
			
		||||
    output["font"][S_SCHEMAS]["FILE_SCHEMA"] = output["font"][S_SCHEMAS].pop(
 | 
			
		||||
        "TYPED_FILE_SCHEMA"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fix_menu():
 | 
			
		||||
    if "display_menu_base" not in output:
 | 
			
		||||
        return
 | 
			
		||||
    # # Menu has a recursive schema which is not kept properly
 | 
			
		||||
    schemas = output["display_menu_base"][S_SCHEMAS]
 | 
			
		||||
    # 1. Move items to a new schema
 | 
			
		||||
@@ -358,8 +330,6 @@ def get_logger_tags():
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def add_logger_tags():
 | 
			
		||||
    if "logger" not in output or "schemas" not in output["logger"]:
 | 
			
		||||
        return
 | 
			
		||||
    tags = get_logger_tags()
 | 
			
		||||
    logs = output["logger"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"][
 | 
			
		||||
        "logs"
 | 
			
		||||
@@ -459,12 +429,6 @@ def merge(source, destination):
 | 
			
		||||
    return destination
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_platform_schema(schema_name):
 | 
			
		||||
    # added mostly because of schema_name == "microphone.MICROPHONE_SCHEMA"
 | 
			
		||||
    # which is shrunk because there is only one component of the schema (i2s_audio)
 | 
			
		||||
    return schema_name == "microphone.MICROPHONE_SCHEMA"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def shrink():
 | 
			
		||||
    """Shrink the extending schemas which has just an end type, e.g. at this point
 | 
			
		||||
    ota / port is type schema with extended pointing to core.port, this should instead be
 | 
			
		||||
@@ -490,7 +454,7 @@ def shrink():
 | 
			
		||||
                        add_referenced_recursive(referenced_schemas, vvv, [k, kv, kvv])
 | 
			
		||||
 | 
			
		||||
        for x, paths in referenced_schemas.items():
 | 
			
		||||
            if len(paths) == 1 and not is_platform_schema(x):
 | 
			
		||||
            if len(paths) == 1:
 | 
			
		||||
                key_s = get_str_path_schema(x)
 | 
			
		||||
                arr_s = get_arr_path_schema(paths[0])
 | 
			
		||||
                # key_s |= arr_s
 | 
			
		||||
@@ -544,7 +508,6 @@ def shrink():
 | 
			
		||||
            if (
 | 
			
		||||
                not s.endswith("." + S_CONFIG_SCHEMA)
 | 
			
		||||
                and s not in referenced_schemas.keys()
 | 
			
		||||
                and not is_platform_schema(s)
 | 
			
		||||
            ):
 | 
			
		||||
                print(f"Removing {s}")
 | 
			
		||||
                output[domain][S_SCHEMAS].pop(schema_name)
 | 
			
		||||
@@ -626,7 +589,6 @@ def build_schema():
 | 
			
		||||
    do_esp32()
 | 
			
		||||
    fix_remote_receiver()
 | 
			
		||||
    fix_script()
 | 
			
		||||
    fix_font()
 | 
			
		||||
    add_logger_tags()
 | 
			
		||||
    shrink()
 | 
			
		||||
    fix_menu()
 | 
			
		||||
@@ -649,13 +611,17 @@ def build_schema():
 | 
			
		||||
 | 
			
		||||
    for c, s in data.items():
 | 
			
		||||
        write_file(c, s)
 | 
			
		||||
    delete_extra_files(data.keys())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_convertible_schema(schema):
 | 
			
		||||
def setEnum(obj, items):
 | 
			
		||||
    obj[S_TYPE] = "enum"
 | 
			
		||||
    obj["values"] = items
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def isConvertibleSchema(schema):
 | 
			
		||||
    if schema is None:
 | 
			
		||||
        return False
 | 
			
		||||
    if isinstance(schema, (cv.Schema, cv.All, cv.Any)):
 | 
			
		||||
    if isinstance(schema, (cv.Schema, cv.All)):
 | 
			
		||||
        return True
 | 
			
		||||
    if repr(schema) in ejs.hidden_schemas:
 | 
			
		||||
        return True
 | 
			
		||||
@@ -674,23 +640,20 @@ def is_convertible_schema(schema):
 | 
			
		||||
 | 
			
		||||
def convert_config(schema, path):
 | 
			
		||||
    converted = {}
 | 
			
		||||
    convert(schema, converted, path)
 | 
			
		||||
    convert_1(schema, converted, path)
 | 
			
		||||
    return converted
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def convert(schema, config_var, path):
 | 
			
		||||
def convert_1(schema, config_var, path):
 | 
			
		||||
    """config_var can be a config_var or a schema: both are dicts
 | 
			
		||||
    config_var has a S_TYPE property, if this is S_SCHEMA, then it has a S_SCHEMA property
 | 
			
		||||
    schema does not have a type property, schema can have optionally both S_CONFIG_VARS and S_EXTENDS
 | 
			
		||||
    """
 | 
			
		||||
    repr_schema = repr(schema)
 | 
			
		||||
 | 
			
		||||
    if path.startswith("ads1115.sensor") and path.endswith("gain"):
 | 
			
		||||
        print(path)
 | 
			
		||||
 | 
			
		||||
    if repr_schema in known_schemas:
 | 
			
		||||
        schema_info = known_schemas[(repr_schema)]
 | 
			
		||||
        for schema_instance, name in schema_info:
 | 
			
		||||
        for (schema_instance, name) in schema_info:
 | 
			
		||||
            if schema_instance is schema:
 | 
			
		||||
                assert S_CONFIG_VARS not in config_var
 | 
			
		||||
                assert S_EXTENDS not in config_var
 | 
			
		||||
@@ -702,7 +665,7 @@ def convert(schema, config_var, path):
 | 
			
		||||
                    config_var[S_SCHEMA] = {}
 | 
			
		||||
                if S_EXTENDS not in config_var[S_SCHEMA]:
 | 
			
		||||
                    config_var[S_SCHEMA][S_EXTENDS] = [name]
 | 
			
		||||
                elif name not in config_var[S_SCHEMA][S_EXTENDS]:
 | 
			
		||||
                else:
 | 
			
		||||
                    config_var[S_SCHEMA][S_EXTENDS].append(name)
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
@@ -716,25 +679,25 @@ def convert(schema, config_var, path):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        assert len(extended) == 2
 | 
			
		||||
        convert(extended[0], config_var, path + "/extL")
 | 
			
		||||
        convert(extended[1], config_var, path + "/extR")
 | 
			
		||||
        convert_1(extended[0], config_var, path + "/extL")
 | 
			
		||||
        convert_1(extended[1], config_var, path + "/extR")
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    if isinstance(schema, cv.All):
 | 
			
		||||
        i = 0
 | 
			
		||||
        for inner in schema.validators:
 | 
			
		||||
            i = i + 1
 | 
			
		||||
            convert(inner, config_var, path + f"/val {i}")
 | 
			
		||||
            convert_1(inner, config_var, path + f"/val {i}")
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    if hasattr(schema, "validators"):
 | 
			
		||||
        i = 0
 | 
			
		||||
        for inner in schema.validators:
 | 
			
		||||
            i = i + 1
 | 
			
		||||
            convert(inner, config_var, path + f"/val {i}")
 | 
			
		||||
            convert_1(inner, config_var, path + f"/val {i}")
 | 
			
		||||
 | 
			
		||||
    if isinstance(schema, cv.Schema):
 | 
			
		||||
        convert(schema.schema, config_var, path + "/all")
 | 
			
		||||
        convert_1(schema.schema, config_var, path + "/all")
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    if isinstance(schema, dict):
 | 
			
		||||
@@ -744,7 +707,7 @@ def convert(schema, config_var, path):
 | 
			
		||||
    if repr_schema in ejs.list_schemas:
 | 
			
		||||
        config_var["is_list"] = True
 | 
			
		||||
        items_schema = ejs.list_schemas[repr_schema][0]
 | 
			
		||||
        convert(items_schema, config_var, path + "/list")
 | 
			
		||||
        convert_1(items_schema, config_var, path + "/list")
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    if DUMP_RAW:
 | 
			
		||||
@@ -778,10 +741,10 @@ def convert(schema, config_var, path):
 | 
			
		||||
        # enums, e.g. esp32/variant
 | 
			
		||||
        if schema_type == "one_of":
 | 
			
		||||
            config_var[S_TYPE] = "enum"
 | 
			
		||||
            config_var["values"] = dict.fromkeys(list(data))
 | 
			
		||||
            config_var["values"] = list(data)
 | 
			
		||||
        elif schema_type == "enum":
 | 
			
		||||
            config_var[S_TYPE] = "enum"
 | 
			
		||||
            config_var["values"] = dict.fromkeys(list(data.keys()))
 | 
			
		||||
            config_var["values"] = list(data.keys())
 | 
			
		||||
        elif schema_type == "maybe":
 | 
			
		||||
            config_var[S_TYPE] = S_SCHEMA
 | 
			
		||||
            config_var["maybe"] = data[1]
 | 
			
		||||
@@ -822,13 +785,13 @@ def convert(schema, config_var, path):
 | 
			
		||||
            config_var["filter"] = data[0]
 | 
			
		||||
        elif schema_type == "templatable":
 | 
			
		||||
            config_var["templatable"] = True
 | 
			
		||||
            convert(data, config_var, path + "/templat")
 | 
			
		||||
            convert_1(data, config_var, path + "/templat")
 | 
			
		||||
        elif schema_type == "triggers":
 | 
			
		||||
            # remote base
 | 
			
		||||
            convert(data, config_var, path + "/trigger")
 | 
			
		||||
            convert_1(data, config_var, path + "/trigger")
 | 
			
		||||
        elif schema_type == "sensor":
 | 
			
		||||
            schema = data
 | 
			
		||||
            convert(data, config_var, path + "/trigger")
 | 
			
		||||
            convert_1(data, config_var, path + "/trigger")
 | 
			
		||||
        elif schema_type == "declare_id":
 | 
			
		||||
            # pylint: disable=protected-access
 | 
			
		||||
            parents = data._parents
 | 
			
		||||
@@ -893,9 +856,6 @@ def convert(schema, config_var, path):
 | 
			
		||||
 | 
			
		||||
    if DUMP_PATH:
 | 
			
		||||
        config_var["path"] = path
 | 
			
		||||
    if S_TYPE not in config_var:
 | 
			
		||||
        pass
 | 
			
		||||
        # print(path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_overridden_config(key, converted):
 | 
			
		||||
@@ -961,7 +921,7 @@ def convert_keys(converted, schema, path):
 | 
			
		||||
                result["default"] = str(default_value)
 | 
			
		||||
 | 
			
		||||
        # Do value
 | 
			
		||||
        convert(v, result, path + f"/{str(k)}")
 | 
			
		||||
        convert_1(v, result, path + f"/{str(k)}")
 | 
			
		||||
        if "schema" not in converted:
 | 
			
		||||
            converted[S_TYPE] = "schema"
 | 
			
		||||
            converted["schema"] = {S_CONFIG_VARS: {}}
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user