diff --git a/CODEOWNERS b/CODEOWNERS index 088e350f5d..ba7106e6a3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -131,6 +131,7 @@ esphome/components/ens160_base/* @latonita @vincentscode esphome/components/ens160_i2c/* @latonita esphome/components/ens160_spi/* @latonita esphome/components/ens210/* @itn3rd77 +esphome/components/es7210/* @kahrendt esphome/components/es8311/* @kahrendt @kroimon esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @Rapsssito @jesserockz diff --git a/esphome/components/es7210/__init__.py b/esphome/components/es7210/__init__.py new file mode 100644 index 0000000000..8e63d7f04f --- /dev/null +++ b/esphome/components/es7210/__init__.py @@ -0,0 +1,67 @@ +import esphome.codegen as cg +from esphome.components import i2c +import esphome.config_validation as cv +from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID, CONF_MIC_GAIN, CONF_SAMPLE_RATE + +CODEOWNERS = ["@kahrendt"] +DEPENDENCIES = ["i2c"] + +es7210_ns = cg.esphome_ns.namespace("es7210") +ES7210 = es7210_ns.class_("ES7210", cg.Component, i2c.I2CDevice) + + +es7210_bits_per_sample = es7210_ns.enum("ES7210BitsPerSample") +ES7210_BITS_PER_SAMPLE_ENUM = { + 16: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_16, + 24: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_24, + 32: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_32, +} + + +es7210_mic_gain = es7210_ns.enum("ES7210MicGain") +ES7210_MIC_GAIN_ENUM = { + "0DB": es7210_mic_gain.ES7210_MIC_GAIN_0DB, + "3DB": es7210_mic_gain.ES7210_MIC_GAIN_3DB, + "6DB": es7210_mic_gain.ES7210_MIC_GAIN_6DB, + "9DB": es7210_mic_gain.ES7210_MIC_GAIN_9DB, + "12DB": es7210_mic_gain.ES7210_MIC_GAIN_12DB, + "15DB": es7210_mic_gain.ES7210_MIC_GAIN_15DB, + "18DB": es7210_mic_gain.ES7210_MIC_GAIN_18DB, + "21DB": es7210_mic_gain.ES7210_MIC_GAIN_21DB, + "24DB": es7210_mic_gain.ES7210_MIC_GAIN_24DB, + "27DB": es7210_mic_gain.ES7210_MIC_GAIN_27DB, + "30DB": es7210_mic_gain.ES7210_MIC_GAIN_30DB, + "33DB": es7210_mic_gain.ES7210_MIC_GAIN_33DB, + "34.5DB": es7210_mic_gain.ES7210_MIC_GAIN_34_5DB, + "36DB": es7210_mic_gain.ES7210_MIC_GAIN_36DB, + "37.5DB": es7210_mic_gain.ES7210_MIC_GAIN_37_5DB, +} + +_validate_bits = cv.float_with_unit("bits", "bit") + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ES7210), + cv.Optional(CONF_BITS_PER_SAMPLE, default="16bit"): cv.All( + _validate_bits, cv.enum(ES7210_BITS_PER_SAMPLE_ENUM) + ), + cv.Optional(CONF_MIC_GAIN, default="24DB"): cv.enum( + ES7210_MIC_GAIN_ENUM, upper=True + ), + cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x40)) +) + + +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_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) + cg.add(var.set_mic_gain(config[CONF_MIC_GAIN])) + cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) diff --git a/esphome/components/es7210/es7210.cpp b/esphome/components/es7210/es7210.cpp new file mode 100644 index 0000000000..d2f2c3c1ff --- /dev/null +++ b/esphome/components/es7210/es7210.cpp @@ -0,0 +1,201 @@ +#include "es7210.h" +#include "es7210_const.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace es7210 { + +static const char *const TAG = "es7210"; + +static const size_t MCLK_DIV_FRE = 256; + +// Mark the component as failed; use only in setup +#define ES7210_ERROR_FAILED(func) \ + if (!(func)) { \ + this->mark_failed(); \ + return; \ + } + +// Return false; use outside of setup +#define ES7210_ERROR_CHECK(func) \ + if (!(func)) { \ + return false; \ + } + +void ES7210::dump_config() { + ESP_LOGCONFIG(TAG, "ES7210 ADC:"); + ESP_LOGCONFIG(TAG, " Bits Per Sample: %" PRIu8, this->bits_per_sample_); + ESP_LOGCONFIG(TAG, " Sample Rate: %" PRIu32, this->sample_rate_); + + if (this->is_failed()) { + ESP_LOGCONFIG(TAG, " Failed to initialize!"); + return; + } +} + +void ES7210::setup() { + ESP_LOGCONFIG(TAG, "Setting up ES7210..."); + + // Software reset + ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0xff)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0x32)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_CLOCK_OFF_REG01, 0x3f)); + + // Set initialization time when device powers up + ES7210_ERROR_FAILED(this->write_byte(ES7210_TIME_CONTROL0_REG09, 0x30)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_TIME_CONTROL1_REG0A, 0x30)); + + // Configure HFP for all ADC channels + ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC12_HPF2_REG23, 0x2a)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC12_HPF1_REG22, 0x0a)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC34_HPF2_REG20, 0x0a)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC34_HPF1_REG21, 0x2a)); + + // Secondary I2S mode settings + ES7210_ERROR_FAILED(this->es7210_update_reg_bit_(ES7210_MODE_CONFIG_REG08, 0x01, 0x00)); + + // Configure analog power + ES7210_ERROR_FAILED(this->write_byte(ES7210_ANALOG_REG40, 0xC3)); + + // Set mic bias + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC12_BIAS_REG41, 0x70)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC34_BIAS_REG42, 0x70)); + + // Configure I2S settings, sample rate, and microphone gains + ES7210_ERROR_FAILED(this->configure_i2s_format_()); + ES7210_ERROR_FAILED(this->configure_sample_rate_()); + ES7210_ERROR_FAILED(this->configure_mic_gain_()); + + // Power on mics 1 through 4 + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC1_POWER_REG47, 0x08)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC2_POWER_REG48, 0x08)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC3_POWER_REG49, 0x08)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC4_POWER_REG4A, 0x08)); + + // Power down DLL + ES7210_ERROR_FAILED(this->write_byte(ES7210_POWER_DOWN_REG06, 0x04)); + + // Power on MIC1-4 bias & ADC1-4 & PGA1-4 Power + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x0F)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x0F)); + + // Enable device + ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0x71)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0x41)); +} + +bool ES7210::configure_sample_rate_() { + int mclk_fre = this->sample_rate_ * MCLK_DIV_FRE; + int coeff = -1; + + for (int i = 0; i < (sizeof(ES7210_COEFFICIENTS) / sizeof(ES7210_COEFFICIENTS[0])); ++i) { + if (ES7210_COEFFICIENTS[i].lrclk == this->sample_rate_ && ES7210_COEFFICIENTS[i].mclk == mclk_fre) + coeff = i; + } + + if (coeff >= 0) { + // Set adc_div & doubler & dll + uint8_t regv; + ES7210_ERROR_CHECK(this->read_byte(ES7210_MAINCLK_REG02, ®v)); + regv = regv & 0x00; + regv |= ES7210_COEFFICIENTS[coeff].adc_div; + regv |= ES7210_COEFFICIENTS[coeff].doubler << 6; + regv |= ES7210_COEFFICIENTS[coeff].dll << 7; + + ES7210_ERROR_CHECK(this->write_byte(ES7210_MAINCLK_REG02, regv)); + + // Set osr + regv = ES7210_COEFFICIENTS[coeff].osr; + ES7210_ERROR_CHECK(this->write_byte(ES7210_OSR_REG07, regv)); + // Set lrck + regv = ES7210_COEFFICIENTS[coeff].lrck_h; + ES7210_ERROR_CHECK(this->write_byte(ES7210_LRCK_DIVH_REG04, regv)); + regv = ES7210_COEFFICIENTS[coeff].lrck_l; + ES7210_ERROR_CHECK(this->write_byte(ES7210_LRCK_DIVL_REG05, regv)); + } else { + // Invalid sample frequency + ESP_LOGE(TAG, "Invalid sample rate"); + return false; + } + + return true; +} +bool ES7210::configure_mic_gain_() { + for (int i = 0; i < 4; ++i) { + this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43 + i, 0x10, 0x00); + } + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0xff)); + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0xff)); + + // Configure mic 1 + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x10, 0x10)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x0f, this->mic_gain_)); + + // Configure mic 2 + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x10, 0x10)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x0f, this->mic_gain_)); + + // Configure mic 3 + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x10, 0x10)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x0f, this->mic_gain_)); + + // Configure mic 4 + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x10, 0x10)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x0f, this->mic_gain_)); + + return true; +} + +bool ES7210::configure_i2s_format_() { + // Configure bits per sample + uint8_t reg_val = 0; + switch (this->bits_per_sample_) { + case ES7210_BITS_PER_SAMPLE_16: + reg_val = 0x60; + break; + case ES7210_BITS_PER_SAMPLE_18: + reg_val = 0x40; + break; + case ES7210_BITS_PER_SAMPLE_20: + reg_val = 0x20; + break; + case ES7210_BITS_PER_SAMPLE_24: + reg_val = 0x00; + break; + case ES7210_BITS_PER_SAMPLE_32: + reg_val = 0x80; + break; + default: + return false; + } + ES7210_ERROR_CHECK(this->write_byte(ES7210_SDP_INTERFACE1_REG11, reg_val)); + + if (this->enable_tdm_) { + ES7210_ERROR_CHECK(this->write_byte(ES7210_SDP_INTERFACE2_REG12, 0x02)); + } else { + // Microphones 1 and 2 output on SDOUT1, microphones 3 and 4 output on SDOUT2 + ES7210_ERROR_CHECK(this->write_byte(ES7210_SDP_INTERFACE2_REG12, 0x00)); + } + + return true; +} + +bool ES7210::es7210_update_reg_bit_(uint8_t reg_addr, uint8_t update_bits, uint8_t data) { + uint8_t regv; + ES7210_ERROR_CHECK(this->read_byte(reg_addr, ®v)); + regv = (regv & (~update_bits)) | (update_bits & data); + return this->write_byte(reg_addr, regv); +} + +} // namespace es7210 +} // namespace esphome diff --git a/esphome/components/es7210/es7210.h b/esphome/components/es7210/es7210.h new file mode 100644 index 0000000000..a40dde5aa5 --- /dev/null +++ b/esphome/components/es7210/es7210.h @@ -0,0 +1,69 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace es7210 { + +enum ES7210BitsPerSample : uint8_t { + ES7210_BITS_PER_SAMPLE_16 = 16, + ES7210_BITS_PER_SAMPLE_18 = 18, + ES7210_BITS_PER_SAMPLE_20 = 20, + ES7210_BITS_PER_SAMPLE_24 = 24, + ES7210_BITS_PER_SAMPLE_32 = 32, +}; + +enum ES7210MicGain : uint8_t { + ES7210_MIC_GAIN_0DB = 0, + ES7210_MIC_GAIN_3DB, + ES7210_MIC_GAIN_6DB, + ES7210_MIC_GAIN_9DB, + ES7210_MIC_GAIN_12DB, + ES7210_MIC_GAIN_15DB, + ES7210_MIC_GAIN_18DB, + ES7210_MIC_GAIN_21DB, + ES7210_MIC_GAIN_24DB, + ES7210_MIC_GAIN_27DB, + ES7210_MIC_GAIN_30DB, + ES7210_MIC_GAIN_33DB, + ES7210_MIC_GAIN_34_5DB, + ES7210_MIC_GAIN_36DB, + ES7210_MIC_GAIN_37_5DB, +}; + +class ES7210 : public Component, public i2c::I2CDevice { + /* Class for configuring an ES7210 ADC for microphone input. + * Based on code from: + * - https://github.com/espressif/esp-bsp/ (accessed 20241219) + * - https://github.com/espressif/esp-adf/ (accessed 20241219) + */ + public: + void setup() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void dump_config() override; + + void set_bits_per_sample(ES7210BitsPerSample bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } + void set_mic_gain(ES7210MicGain mic_gain) { this->mic_gain_ = mic_gain; } + void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; } + + protected: + /// @brief Updates an I2C registry address by modifying the current state + /// @param reg_addr I2C register address + /// @param update_bits Mask of allowed bits to be modified + /// @param data Bit values to be written + /// @return True if successful, false otherwise + bool es7210_update_reg_bit_(uint8_t reg_addr, uint8_t update_bits, uint8_t data); + + bool configure_i2s_format_(); + bool configure_mic_gain_(); + bool configure_sample_rate_(); + + bool enable_tdm_{false}; // TDM is unsupported in ESPHome as of version 2024.12 + ES7210MicGain mic_gain_; + ES7210BitsPerSample bits_per_sample_; + uint32_t sample_rate_; +}; + +} // namespace es7210 +} // namespace esphome diff --git a/esphome/components/es7210/es7210_const.h b/esphome/components/es7210/es7210_const.h new file mode 100644 index 0000000000..87fd6d86d2 --- /dev/null +++ b/esphome/components/es7210/es7210_const.h @@ -0,0 +1,126 @@ +#pragma once + +#include "es7210.h" + +namespace esphome { +namespace es7210 { + +// ES7210 register addresses +static const uint8_t ES7210_RESET_REG00 = 0x00; /* Reset control */ +static const uint8_t ES7210_CLOCK_OFF_REG01 = 0x01; /* Used to turn off the ADC clock */ +static const uint8_t ES7210_MAINCLK_REG02 = 0x02; /* Set ADC clock frequency division */ + +static const uint8_t ES7210_MASTER_CLK_REG03 = 0x03; /* MCLK source $ SCLK division */ +static const uint8_t ES7210_LRCK_DIVH_REG04 = 0x04; /* lrck_divh */ +static const uint8_t ES7210_LRCK_DIVL_REG05 = 0x05; /* lrck_divl */ +static const uint8_t ES7210_POWER_DOWN_REG06 = 0x06; /* power down */ +static const uint8_t ES7210_OSR_REG07 = 0x07; +static const uint8_t ES7210_MODE_CONFIG_REG08 = 0x08; /* Set primary/secondary & channels */ +static const uint8_t ES7210_TIME_CONTROL0_REG09 = 0x09; /* Set Chip intial state period*/ +static const uint8_t ES7210_TIME_CONTROL1_REG0A = 0x0A; /* Set Power up state period */ +static const uint8_t ES7210_SDP_INTERFACE1_REG11 = 0x11; /* Set sample & fmt */ +static const uint8_t ES7210_SDP_INTERFACE2_REG12 = 0x12; /* Pins state */ +static const uint8_t ES7210_ADC_AUTOMUTE_REG13 = 0x13; /* Set mute */ +static const uint8_t ES7210_ADC34_MUTERANGE_REG14 = 0x14; /* Set mute range */ +static const uint8_t ES7210_ADC12_MUTERANGE_REG15 = 0x15; /* Set mute range */ +static const uint8_t ES7210_ADC34_HPF2_REG20 = 0x20; /* HPF */ +static const uint8_t ES7210_ADC34_HPF1_REG21 = 0x21; /* HPF */ +static const uint8_t ES7210_ADC12_HPF1_REG22 = 0x22; /* HPF */ +static const uint8_t ES7210_ADC12_HPF2_REG23 = 0x23; /* HPF */ +static const uint8_t ES7210_ANALOG_REG40 = 0x40; /* ANALOG Power */ +static const uint8_t ES7210_MIC12_BIAS_REG41 = 0x41; +static const uint8_t ES7210_MIC34_BIAS_REG42 = 0x42; +static const uint8_t ES7210_MIC1_GAIN_REG43 = 0x43; +static const uint8_t ES7210_MIC2_GAIN_REG44 = 0x44; +static const uint8_t ES7210_MIC3_GAIN_REG45 = 0x45; +static const uint8_t ES7210_MIC4_GAIN_REG46 = 0x46; +static const uint8_t ES7210_MIC1_POWER_REG47 = 0x47; +static const uint8_t ES7210_MIC2_POWER_REG48 = 0x48; +static const uint8_t ES7210_MIC3_POWER_REG49 = 0x49; +static const uint8_t ES7210_MIC4_POWER_REG4A = 0x4A; +static const uint8_t ES7210_MIC12_POWER_REG4B = 0x4B; /* MICBias & ADC & PGA Power */ +static const uint8_t ES7210_MIC34_POWER_REG4C = 0x4C; + +/* + * Clock coefficient structer + */ +struct ES7210Coefficient { + uint32_t mclk; // mclk frequency + uint32_t lrclk; + uint8_t ss_ds; + uint8_t adc_div; + uint8_t dll; // dll_bypass + uint8_t doubler; // doubler_enable + uint8_t osr; // adc osr + uint8_t mclk_src; // sselect mclk source + uint8_t lrck_h; // High 4 bits of lrck + uint8_t lrck_l; // Low 8 bits of lrck +}; + +/* Codec hifi mclk clock divider coefficients + * MEMBER REG + * mclk: 0x03 + * lrck: standard + * ss_ds: -- + * adc_div: 0x02 + * dll: 0x06 + * doubler: 0x02 + * osr: 0x07 + * mclk_src: 0x03 + * lrckh: 0x04 + * lrckl: 0x05 + */ +static const ES7210Coefficient ES7210_COEFFICIENTS[] = { + // mclk lrck ss_ds adc_div dll doubler osr mclk_src lrckh lrckl + /* 8k */ + {12288000, 8000, 0x00, 0x03, 0x01, 0x00, 0x20, 0x00, 0x06, 0x00}, + {16384000, 8000, 0x00, 0x04, 0x01, 0x00, 0x20, 0x00, 0x08, 0x00}, + {19200000, 8000, 0x00, 0x1e, 0x00, 0x01, 0x28, 0x00, 0x09, 0x60}, + {4096000, 8000, 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00}, + + /* 11.025k */ + {11289600, 11025, 0x00, 0x02, 0x01, 0x00, 0x20, 0x00, 0x01, 0x00}, + + /* 12k */ + {12288000, 12000, 0x00, 0x02, 0x01, 0x00, 0x20, 0x00, 0x04, 0x00}, + {19200000, 12000, 0x00, 0x14, 0x00, 0x01, 0x28, 0x00, 0x06, 0x40}, + + /* 16k */ + {4096000, 16000, 0x00, 0x01, 0x01, 0x01, 0x20, 0x00, 0x01, 0x00}, + {19200000, 16000, 0x00, 0x0a, 0x00, 0x00, 0x1e, 0x00, 0x04, 0x80}, + {16384000, 16000, 0x00, 0x02, 0x01, 0x00, 0x20, 0x00, 0x04, 0x00}, + {12288000, 16000, 0x00, 0x03, 0x01, 0x01, 0x20, 0x00, 0x03, 0x00}, + + /* 22.05k */ + {11289600, 22050, 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00}, + + /* 24k */ + {12288000, 24000, 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00}, + {19200000, 24000, 0x00, 0x0a, 0x00, 0x01, 0x28, 0x00, 0x03, 0x20}, + + /* 32k */ + {12288000, 32000, 0x00, 0x03, 0x00, 0x00, 0x20, 0x00, 0x01, 0x80}, + {16384000, 32000, 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00}, + {19200000, 32000, 0x00, 0x05, 0x00, 0x00, 0x1e, 0x00, 0x02, 0x58}, + + /* 44.1k */ + {11289600, 44100, 0x00, 0x01, 0x01, 0x01, 0x20, 0x00, 0x01, 0x00}, + + /* 48k */ + {12288000, 48000, 0x00, 0x01, 0x01, 0x01, 0x20, 0x00, 0x01, 0x00}, + {19200000, 48000, 0x00, 0x05, 0x00, 0x01, 0x28, 0x00, 0x01, 0x90}, + + /* 64k */ + {16384000, 64000, 0x01, 0x01, 0x01, 0x00, 0x20, 0x00, 0x01, 0x00}, + {19200000, 64000, 0x00, 0x05, 0x00, 0x01, 0x1e, 0x00, 0x01, 0x2c}, + + /* 88.2k */ + {11289600, 88200, 0x01, 0x01, 0x01, 0x01, 0x20, 0x00, 0x00, 0x80}, + + /* 96k */ + {12288000, 96000, 0x01, 0x01, 0x01, 0x01, 0x20, 0x00, 0x00, 0x80}, + {19200000, 96000, 0x01, 0x05, 0x00, 0x01, 0x28, 0x00, 0x00, 0xc8}, +}; + +} // namespace es7210 +} // namespace esphome diff --git a/esphome/components/es8311/audio_dac.py b/esphome/components/es8311/audio_dac.py index 1b450c3c11..7d80cfd5fb 100644 --- a/esphome/components/es8311/audio_dac.py +++ b/esphome/components/es8311/audio_dac.py @@ -2,7 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c from esphome.components.audio_dac import AudioDac import esphome.config_validation as cv -from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID, CONF_SAMPLE_RATE +from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID, CONF_MIC_GAIN, CONF_SAMPLE_RATE CODEOWNERS = ["@kroimon", "@kahrendt"] DEPENDENCIES = ["i2c"] @@ -10,7 +10,6 @@ DEPENDENCIES = ["i2c"] es8311_ns = cg.esphome_ns.namespace("es8311") ES8311 = es8311_ns.class_("ES8311", AudioDac, cg.Component, i2c.I2CDevice) -CONF_MIC_GAIN = "mic_gain" CONF_USE_MCLK = "use_mclk" CONF_USE_MICROPHONE = "use_microphone" diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index 061afcb026..d27b3b378e 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -8,11 +8,13 @@ namespace event { static const char *const TAG = "event"; void Event::trigger(const std::string &event_type) { - if (types_.find(event_type) == types_.end()) { + auto found = types_.find(event_type); + if (found == types_.end()) { ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str()); return; } - ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), event_type.c_str()); + last_event_type = &(*found); + ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str()); this->event_callback_.call(event_type); } diff --git a/esphome/components/event/event.h b/esphome/components/event/event.h index 067a867360..03c3c8d95a 100644 --- a/esphome/components/event/event.h +++ b/esphome/components/event/event.h @@ -23,6 +23,8 @@ namespace event { class Event : public EntityBase, public EntityBase_DeviceClass { public: + const std::string *last_event_type; + void trigger(const std::string &event_type); void set_event_types(const std::set &event_types) { this->types_ = event_types; } std::set get_event_types() const { return this->types_; } diff --git a/esphome/components/lvgl/automation.py b/esphome/components/lvgl/automation.py index 7db6e1f045..168fc03cb7 100644 --- a/esphome/components/lvgl/automation.py +++ b/esphome/components/lvgl/automation.py @@ -15,6 +15,7 @@ from .defines import ( CONF_FREEZE, CONF_LVGL_ID, CONF_SHOW_SNOW, + PARTS, literal, ) from .lv_validation import lv_bool, lv_color, lv_image, opacity @@ -33,7 +34,7 @@ from .lvcode import ( lvgl_comp, static_cast, ) -from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA +from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA, base_update_schema from .types import ( LV_STATE, LvglAction, @@ -41,6 +42,7 @@ from .types import ( ObjUpdateAction, lv_disp_t, lv_group_t, + lv_obj_base_t, lv_obj_t, lv_pseudo_button_t, ) @@ -336,3 +338,14 @@ async def widget_focus(config, action_id, template_arg, args): lv.group_focus_freeze(group, True) var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) return var + + +@automation.register_action( + "lvgl.widget.update", ObjUpdateAction, base_update_schema(lv_obj_base_t, PARTS) +) +async def obj_update_to_code(config, action_id, template_arg, args): + async def do_update(widget: Widget): + await set_obj_properties(widget, config) + + widgets = await get_widgets(config[CONF_ID]) + return await action_to_code(widgets, do_update, action_id, template_arg, args) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 271dbea19f..f0318dd17a 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -199,13 +199,12 @@ FLAG_SCHEMA = cv.Schema({cv.Optional(flag): lvalid.lv_bool for flag in df.OBJ_FL FLAG_LIST = cv.ensure_list(df.LvConstant("LV_OBJ_FLAG_", *df.OBJ_FLAGS).one_of) -def part_schema(widget_type: WidgetType): +def part_schema(parts): """ Generate a schema for the various parts (e.g. main:, indicator:) of a widget type - :param widget_type: The type of widget to generate for - :return: + :param parts: The parts to include in the schema + :return: The schema """ - parts = widget_type.parts return cv.Schema({cv.Optional(part): STATE_SCHEMA for part in parts}).extend( STATE_SCHEMA ) @@ -228,9 +227,15 @@ def automation_schema(typ: LvType): } -def create_modify_schema(widget_type): +def base_update_schema(widget_type, parts): + """ + Create a schema for updating a widgets style properties, states and flags + :param widget_type: The type of the ID + :param parts: The allowable parts to specify + :return: + """ return ( - part_schema(widget_type) + part_schema(parts) .extend( { cv.Required(CONF_ID): cv.ensure_list( @@ -245,7 +250,12 @@ def create_modify_schema(widget_type): } ) .extend(FLAG_SCHEMA) - .extend(widget_type.modify_schema) + ) + + +def create_modify_schema(widget_type): + return base_update_schema(widget_type.w_type, widget_type.parts).extend( + widget_type.modify_schema ) @@ -256,7 +266,7 @@ def obj_schema(widget_type: WidgetType): :return: """ return ( - part_schema(widget_type) + part_schema(widget_type.parts) .extend(FLAG_SCHEMA) .extend(LAYOUT_SCHEMA) .extend(ALIGN_TO_SCHEMA) @@ -341,7 +351,6 @@ FLEX_OBJ_SCHEMA = { cv.Optional(df.CONF_FLEX_GROW): cv.int_, } - DISP_BG_SCHEMA = cv.Schema( { cv.Optional(df.CONF_DISP_BG_IMAGE): cv.Any( diff --git a/esphome/components/lvgl/widgets/dropdown.py b/esphome/components/lvgl/widgets/dropdown.py index a6bfc6bb88..b32b5a2b2e 100644 --- a/esphome/components/lvgl/widgets/dropdown.py +++ b/esphome/components/lvgl/widgets/dropdown.py @@ -37,7 +37,7 @@ DROPDOWN_BASE_SCHEMA = cv.Schema( cv.Exclusive(CONF_SELECTED_INDEX, CONF_SELECTED_TEXT): lv_int, cv.Exclusive(CONF_SELECTED_TEXT, CONF_SELECTED_TEXT): lv_text, cv.Optional(CONF_DIR, default="BOTTOM"): DIRECTIONS.one_of, - cv.Optional(CONF_DROPDOWN_LIST): part_schema(dropdown_list_spec), + cv.Optional(CONF_DROPDOWN_LIST): part_schema(dropdown_list_spec.parts), } ) diff --git a/esphome/components/lvgl/widgets/keyboard.py b/esphome/components/lvgl/widgets/keyboard.py index ba7edb302e..d4a71078d0 100644 --- a/esphome/components/lvgl/widgets/keyboard.py +++ b/esphome/components/lvgl/widgets/keyboard.py @@ -16,6 +16,11 @@ KEYBOARD_SCHEMA = { cv.Optional(CONF_TEXTAREA): cv.use_id(lv_textarea_t), } +KEYBOARD_MODIFY_SCHEMA = { + cv.Optional(CONF_MODE): KEYBOARD_MODES.one_of, + cv.Optional(CONF_TEXTAREA): cv.use_id(lv_textarea_t), +} + lv_keyboard_t = LvType( "LvKeyboardType", parents=(KeyProvider, LvCompound), @@ -32,6 +37,7 @@ class KeyboardType(WidgetType): lv_keyboard_t, (CONF_MAIN, CONF_ITEMS), KEYBOARD_SCHEMA, + modify_schema=KEYBOARD_MODIFY_SCHEMA, ) def get_uses(self): @@ -41,7 +47,8 @@ class KeyboardType(WidgetType): lvgl_components_required.add("KEY_LISTENER") lvgl_components_required.add(CONF_KEYBOARD) add_lv_use("btnmatrix") - await w.set_property(CONF_MODE, await KEYBOARD_MODES.process(config[CONF_MODE])) + if mode := config.get(CONF_MODE): + await w.set_property(CONF_MODE, await KEYBOARD_MODES.process(mode)) if ta := await get_widgets(config, CONF_TEXTAREA): await w.set_property(CONF_TEXTAREA, ta[0].obj) diff --git a/esphome/components/lvgl/widgets/msgbox.py b/esphome/components/lvgl/widgets/msgbox.py index c3393940b6..82b2442378 100644 --- a/esphome/components/lvgl/widgets/msgbox.py +++ b/esphome/components/lvgl/widgets/msgbox.py @@ -51,7 +51,7 @@ MSGBOX_SCHEMA = container_schema( cv.Required(CONF_TITLE): STYLED_TEXT_SCHEMA, cv.Optional(CONF_BODY, default=""): STYLED_TEXT_SCHEMA, cv.Optional(CONF_BUTTONS): cv.ensure_list(BUTTONMATRIX_BUTTON_SCHEMA), - cv.Optional(CONF_BUTTON_STYLE): part_schema(buttonmatrix_spec), + cv.Optional(CONF_BUTTON_STYLE): part_schema(buttonmatrix_spec.parts), cv.Optional(CONF_CLOSE_BUTTON, default=True): lv_bool, cv.GenerateID(CONF_BUTTON_TEXT_LIST_ID): cv.declare_id(char_ptr), } diff --git a/esphome/components/lvgl/widgets/obj.py b/esphome/components/lvgl/widgets/obj.py index afb4c97f33..ab22a5ce86 100644 --- a/esphome/components/lvgl/widgets/obj.py +++ b/esphome/components/lvgl/widgets/obj.py @@ -1,9 +1,5 @@ -from esphome import automation - -from ..automation import update_to_code from ..defines import CONF_MAIN, CONF_OBJ, CONF_SCROLLBAR -from ..schemas import create_modify_schema -from ..types import ObjUpdateAction, WidgetType, lv_obj_t +from ..types import WidgetType, lv_obj_t class ObjType(WidgetType): @@ -21,10 +17,3 @@ class ObjType(WidgetType): obj_spec = ObjType() - - -@automation.register_action( - "lvgl.widget.update", ObjUpdateAction, create_modify_schema(obj_spec) -) -async def obj_update_to_code(config, action_id, template_arg, args): - return await update_to_code(config, action_id, template_arg, args) diff --git a/esphome/components/lvgl/widgets/tabview.py b/esphome/components/lvgl/widgets/tabview.py index 226fc3f286..1d18ddd259 100644 --- a/esphome/components/lvgl/widgets/tabview.py +++ b/esphome/components/lvgl/widgets/tabview.py @@ -38,7 +38,7 @@ TABVIEW_SCHEMA = cv.Schema( }, ) ), - cv.Optional(CONF_TAB_STYLE): part_schema(buttonmatrix_spec), + cv.Optional(CONF_TAB_STYLE): part_schema(buttonmatrix_spec.parts), cv.Optional(CONF_POSITION, default="top"): DIRECTIONS.one_of, cv.Optional(CONF_SIZE, default="10%"): size, } diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 5d1861202a..2d39d8ef3f 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -59,6 +59,24 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { this->text_sensor_row_(stream, obj, area, node, friendly_name); #endif +#ifdef USE_NUMBER + this->number_type_(stream); + for (auto *obj : App.get_numbers()) + this->number_row_(stream, obj, area, node, friendly_name); +#endif + +#ifdef USE_SELECT + this->select_type_(stream); + for (auto *obj : App.get_selects()) + this->select_row_(stream, obj, area, node, friendly_name); +#endif + +#ifdef USE_MEDIA_PLAYER + this->media_player_type_(stream); + for (auto *obj : App.get_media_players()) + this->media_player_row_(stream, obj, area, node, friendly_name); +#endif + req->send(stream); } @@ -511,6 +529,156 @@ void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_senso } #endif +// Type-specific implementation +#ifdef USE_NUMBER +void PrometheusHandler::number_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_number_value gauge\n")); + stream->print(F("#TYPE esphome_number_failed gauge\n")); +} +void PrometheusHandler::number_row_(AsyncResponseStream *stream, number::Number *obj, std::string &area, + std::string &node, std::string &friendly_name) { + if (obj->is_internal() && !this->include_internal_) + return; + if (!std::isnan(obj->state)) { + // We have a valid value, output this value + stream->print(F("esphome_number_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_number_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} ")); + stream->print(obj->state); + stream->print(F("\n")); + } else { + // Invalid state + stream->print(F("esphome_number_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 1\n")); + } +} +#endif + +#ifdef USE_SELECT +void PrometheusHandler::select_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_select_value gauge\n")); + stream->print(F("#TYPE esphome_select_failed gauge\n")); +} +void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select *obj, std::string &area, + std::string &node, std::string &friendly_name) { + if (obj->is_internal() && !this->include_internal_) + return; + if (obj->has_state()) { + // We have a valid value, output this value + stream->print(F("esphome_select_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_select_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\",value=\"")); + stream->print(obj->state.c_str()); + stream->print(F("\"} ")); + stream->print(F("1.0")); + stream->print(F("\n")); + } else { + // Invalid state + stream->print(F("esphome_select_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 1\n")); + } +} +#endif + +#ifdef USE_MEDIA_PLAYER +void PrometheusHandler::media_player_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_media_player_state_value gauge\n")); + stream->print(F("#TYPE esphome_media_player_volume gauge\n")); + stream->print(F("#TYPE esphome_media_player_is_muted gauge\n")); + stream->print(F("#TYPE esphome_media_player_failed gauge\n")); +} +void PrometheusHandler::media_player_row_(AsyncResponseStream *stream, media_player::MediaPlayer *obj, + std::string &area, std::string &node, std::string &friendly_name) { + if (obj->is_internal() && !this->include_internal_) + return; + stream->print(F("esphome_media_player_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_media_player_state_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\",value=\"")); + stream->print(media_player::media_player_state_to_string(obj->state)); + stream->print(F("\"} ")); + stream->print(F("1.0")); + stream->print(F("\n")); + stream->print(F("esphome_media_player_volume{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} ")); + stream->print(obj->volume); + stream->print(F("\n")); + stream->print(F("esphome_media_player_is_muted{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} ")); + if (obj->is_muted()) { + stream->print(F("1.0")); + } else { + stream->print(F("0.0")); + } + stream->print(F("\n")); +} +#endif + } // namespace prometheus } // namespace esphome #endif diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 5d08aca63a..41a06537ed 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -128,6 +128,30 @@ class PrometheusHandler : public AsyncWebHandler, public Component { std::string &friendly_name); #endif +#ifdef USE_NUMBER + /// Return the type for prometheus + void number_type_(AsyncResponseStream *stream); + /// Return the sensor state as prometheus data point + void number_row_(AsyncResponseStream *stream, number::Number *obj, std::string &area, std::string &node, + std::string &friendly_name); +#endif + +#ifdef USE_SELECT + /// Return the type for prometheus + void select_type_(AsyncResponseStream *stream); + /// Return the select state as prometheus data point + void select_row_(AsyncResponseStream *stream, select::Select *obj, std::string &area, std::string &node, + std::string &friendly_name); +#endif + +#ifdef USE_MEDIA_PLAYER + /// Return the type for prometheus + void media_player_type_(AsyncResponseStream *stream); + /// Return the select state as prometheus data point + void media_player_row_(AsyncResponseStream *stream, media_player::MediaPlayer *obj, std::string &area, + std::string &node, std::string &friendly_name); +#endif + web_server_base::WebServerBase *base_; bool include_internal_{false}; std::map relabel_map_id_; diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index f9435b0424..b13826c443 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -7,6 +7,10 @@ namespace spi { const char *const TAG = "spi"; +SPIDelegate *const SPIDelegate::NULL_DELEGATE = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + new SPIDelegateDummy(); +// https://bugs.llvm.org/show_bug.cgi?id=48040 + bool SPIDelegate::is_ready() { return true; } GPIOPin *const NullPin::NULL_PIN = new NullPin(); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -75,6 +79,8 @@ void SPIComponent::dump_config() { } } +void SPIDelegateDummy::begin_transaction() { ESP_LOGE(TAG, "SPIDevice not initialised - did you call spi_setup()?"); } + uint8_t SPIDelegateBitBash::transfer(uint8_t data) { return this->transfer_(data, 8); } void SPIDelegateBitBash::write(uint16_t data, size_t num_bits) { this->transfer_(data, num_bits); } diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 4cd8d3383c..f581dc3f56 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -163,6 +163,8 @@ class Utility { } }; +class SPIDelegateDummy; + // represents a device attached to an SPI bus, with a defined clock rate, mode and bit order. On Arduino this is // a thin wrapper over SPIClass. class SPIDelegate { @@ -248,6 +250,21 @@ class SPIDelegate { uint32_t data_rate_{1000000}; SPIMode mode_{MODE0}; GPIOPin *cs_pin_{NullPin::NULL_PIN}; + static SPIDelegate *const NULL_DELEGATE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +}; + +/** + * A dummy SPIDelegate that complains if it's used. + */ + +class SPIDelegateDummy : public SPIDelegate { + public: + SPIDelegateDummy() = default; + + uint8_t transfer(uint8_t data) override { return 0; } + void end_transaction() override{}; + + void begin_transaction() override; }; /** @@ -365,7 +382,7 @@ class SPIClient { virtual void spi_teardown() { this->parent_->unregister_device(this); - this->delegate_ = nullptr; + this->delegate_ = SPIDelegate::NULL_DELEGATE; } bool spi_is_ready() { return this->delegate_->is_ready(); } @@ -376,7 +393,7 @@ class SPIClient { uint32_t data_rate_{1000000}; SPIComponent *parent_{nullptr}; GPIOPin *cs_{nullptr}; - SPIDelegate *delegate_{nullptr}; + SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE}; }; /** diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index ed0cb3db2c..8c09d607a7 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -455,8 +455,9 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc } else if (match.method == "toggle") { this->schedule_([obj]() { obj->toggle().perform(); }); request->send(200); - } else if (match.method == "turn_on") { - auto call = obj->turn_on(); + } else if (match.method == "turn_on" || match.method == "turn_off") { + auto call = match.method == "turn_on" ? obj->turn_on() : obj->turn_off(); + if (request->hasParam("speed_level")) { auto speed_level = request->getParam("speed_level")->value(); auto val = parse_number(speed_level.c_str()); @@ -486,9 +487,6 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc } this->schedule_([call]() mutable { call.perform(); }); request->send(200); - } else if (match.method == "turn_off") { - this->schedule_([obj]() { obj->turn_off().perform(); }); - request->send(200); } else { request->send(404); } diff --git a/esphome/const.py b/esphome/const.py index 0f41dc1aec..284f8d5f78 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2025.1.0-dev" +__version__ = "2025.2.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( @@ -490,6 +490,7 @@ CONF_MEMORY_BLOCKS = "memory_blocks" CONF_MESSAGE = "message" CONF_METHANE = "methane" CONF_METHOD = "method" +CONF_MIC_GAIN = "mic_gain" CONF_MICROPHONE = "microphone" CONF_MIN_BRIGHTNESS = "min_brightness" CONF_MIN_COOLING_OFF_TIME = "min_cooling_off_time" diff --git a/tests/components/es7210/common.yaml b/tests/components/es7210/common.yaml new file mode 100644 index 0000000000..5c30f7e883 --- /dev/null +++ b/tests/components/es7210/common.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_aic3204 + scl: ${scl_pin} + sda: ${sda_pin} + +es7210: diff --git a/tests/components/es7210/test.esp32-ard.yaml b/tests/components/es7210/test.esp32-ard.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/es7210/test.esp32-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/es7210/test.esp32-c3-ard.yaml b/tests/components/es7210/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es7210/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/es7210/test.esp32-c3-idf.yaml b/tests/components/es7210/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es7210/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/es7210/test.esp32-idf.yaml b/tests/components/es7210/test.esp32-idf.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/es7210/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 7c59cfa171..b3227bb96e 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -805,9 +805,24 @@ lvgl: - logger.log: format: "keyboard value %s" args: [text.c_str()] + - lvgl.keyboard.update: + id: lv_keyboard + hidden: true + on_ready: + - lvgl.widget.update: + id: lv_keyboard + - lvgl.keyboard.update: + id: lv_keyboard + hidden: true + - keyboard: id: lv_keyboard1 mode: special + on_ready: + lvgl.keyboard.update: + id: lv_keyboard1 + hidden: true + mode: text_lower font: - file: "gfonts://Roboto" diff --git a/tests/components/prometheus/common.yaml b/tests/components/prometheus/common.yaml index 68ef2a2f58..1b87c1d6c1 100644 --- a/tests/components/prometheus/common.yaml +++ b/tests/components/prometheus/common.yaml @@ -78,6 +78,26 @@ lock: } optimistic: true +select: + - platform: template + id: template_select1 + name: "Template select" + optimistic: true + options: + - one + - two + - three + initial_option: two + +number: + - platform: template + id: template_number1 + name: "Template number" + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + prometheus: include_internal: true relabel: diff --git a/tests/components/prometheus/test.esp32-ard.yaml b/tests/components/prometheus/test.esp32-ard.yaml index dade44d145..3045a6db13 100644 --- a/tests/components/prometheus/test.esp32-ard.yaml +++ b/tests/components/prometheus/test.esp32-ard.yaml @@ -1 +1,34 @@ <<: !include common.yaml + +i2s_audio: + i2s_lrclk_pin: 1 + i2s_bclk_pin: 2 + i2s_mclk_pin: 3 + +media_player: + - platform: i2s_audio + name: "Media Player" + dac_type: external + i2s_dout_pin: 18 + mute_pin: 19 + on_state: + - media_player.play: + - media_player.play_media: http://localhost/media.mp3 + - media_player.play_media: !lambda 'return "http://localhost/media.mp3";' + on_idle: + - media_player.pause: + on_play: + - media_player.stop: + on_pause: + - media_player.toggle: + - wait_until: + media_player.is_idle: + - wait_until: + media_player.is_playing: + - wait_until: + media_player.is_announcing: + - wait_until: + media_player.is_paused: + - media_player.volume_up: + - media_player.volume_down: + - media_player.volume_set: 50%