Merge branch 'dev' into fix_rc5_recv

This commit is contained in:
Jon Krause 2025-01-16 08:13:30 -05:00 committed by GitHub
commit 85f6923782
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 1060 additions and 129 deletions

View File

@ -46,7 +46,7 @@ runs:
- name: Build and push to ghcr by digest - name: Build and push to ghcr by digest
id: build-ghcr id: build-ghcr
uses: docker/build-push-action@v6.11.0 uses: docker/build-push-action@v6.12.0
env: env:
DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false DOCKER_BUILD_RECORD_UPLOAD: false
@ -72,7 +72,7 @@ runs:
- name: Build and push to dockerhub by digest - name: Build and push to dockerhub by digest
id: build-dockerhub id: build-dockerhub
uses: docker/build-push-action@v6.11.0 uses: docker/build-push-action@v6.12.0
env: env:
DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false DOCKER_BUILD_RECORD_UPLOAD: false

View File

@ -131,6 +131,7 @@ esphome/components/ens160_base/* @latonita @vincentscode
esphome/components/ens160_i2c/* @latonita esphome/components/ens160_i2c/* @latonita
esphome/components/ens160_spi/* @latonita esphome/components/ens160_spi/* @latonita
esphome/components/ens210/* @itn3rd77 esphome/components/ens210/* @itn3rd77
esphome/components/es7210/* @kahrendt
esphome/components/es8311/* @kahrendt @kroimon esphome/components/es8311/* @kahrendt @kroimon
esphome/components/esp32/* @esphome/core esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @Rapsssito @jesserockz esphome/components/esp32_ble/* @Rapsssito @jesserockz
@ -338,7 +339,6 @@ esphome/components/radon_eye_rd200/* @jeffeb3
esphome/components/rc522/* @glmnet esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet esphome/components/rc522_spi/* @glmnet
esphome/components/resistance_sampler/* @jesserockz
esphome/components/restart/* @esphome/core esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz esphome/components/rgbct/* @jesserockz

View File

@ -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]))

View File

@ -0,0 +1,201 @@
#include "es7210.h"
#include "es7210_const.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <cinttypes>
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, &regv));
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, &regv));
regv = (regv & (~update_bits)) | (update_bits & data);
return this->write_byte(reg_addr, regv);
}
} // namespace es7210
} // namespace esphome

View File

@ -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

View File

@ -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

View File

@ -2,7 +2,7 @@ import esphome.codegen as cg
from esphome.components import i2c from esphome.components import i2c
from esphome.components.audio_dac import AudioDac from esphome.components.audio_dac import AudioDac
import esphome.config_validation as cv 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"] CODEOWNERS = ["@kroimon", "@kahrendt"]
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
@ -10,7 +10,6 @@ DEPENDENCIES = ["i2c"]
es8311_ns = cg.esphome_ns.namespace("es8311") es8311_ns = cg.esphome_ns.namespace("es8311")
ES8311 = es8311_ns.class_("ES8311", AudioDac, cg.Component, i2c.I2CDevice) ES8311 = es8311_ns.class_("ES8311", AudioDac, cg.Component, i2c.I2CDevice)
CONF_MIC_GAIN = "mic_gain"
CONF_USE_MCLK = "use_mclk" CONF_USE_MCLK = "use_mclk"
CONF_USE_MICROPHONE = "use_microphone" CONF_USE_MICROPHONE = "use_microphone"

View File

@ -8,11 +8,13 @@ namespace event {
static const char *const TAG = "event"; static const char *const TAG = "event";
void Event::trigger(const std::string &event_type) { 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()); ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
return; 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); this->event_callback_.call(event_type);
} }

View File

@ -23,6 +23,8 @@ namespace event {
class Event : public EntityBase, public EntityBase_DeviceClass { class Event : public EntityBase, public EntityBase_DeviceClass {
public: public:
const std::string *last_event_type;
void trigger(const std::string &event_type); void trigger(const std::string &event_type);
void set_event_types(const std::set<std::string> &event_types) { this->types_ = event_types; } void set_event_types(const std::set<std::string> &event_types) { this->types_ = event_types; }
std::set<std::string> get_event_types() const { return this->types_; } std::set<std::string> get_event_types() const { return this->types_; }

View File

@ -1,9 +1,12 @@
import logging
from esphome import core, pins from esphome import core, pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import display, spi from esphome.components import display, spi
from esphome.components.display import validate_rotation from esphome.components.display import validate_rotation
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
CONF_COLOR_ORDER, CONF_COLOR_ORDER,
CONF_COLOR_PALETTE, CONF_COLOR_PALETTE,
CONF_DC_PIN, CONF_DC_PIN,
@ -27,17 +30,12 @@ from esphome.const import (
CONF_WIDTH, CONF_WIDTH,
) )
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
from esphome.final_validate import full_config
DEPENDENCIES = ["spi"] DEPENDENCIES = ["spi"]
def AUTO_LOAD():
if CORE.is_esp32:
return ["psram"]
return []
CODEOWNERS = ["@nielsnl68", "@clydebarrow"] CODEOWNERS = ["@nielsnl68", "@clydebarrow"]
LOGGER = logging.getLogger(__name__)
ili9xxx_ns = cg.esphome_ns.namespace("ili9xxx") ili9xxx_ns = cg.esphome_ns.namespace("ili9xxx")
ILI9XXXDisplay = ili9xxx_ns.class_( ILI9XXXDisplay = ili9xxx_ns.class_(
@ -84,7 +82,7 @@ COLOR_ORDERS = {
"BGR": ColorOrder.COLOR_ORDER_BGR, "BGR": ColorOrder.COLOR_ORDER_BGR,
} }
COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE", "8BIT", upper=True)
CONF_LED_PIN = "led_pin" CONF_LED_PIN = "led_pin"
CONF_COLOR_PALETTE_IMAGES = "color_palette_images" CONF_COLOR_PALETTE_IMAGES = "color_palette_images"
@ -195,9 +193,27 @@ CONFIG_SCHEMA = cv.All(
_validate, _validate,
) )
FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema(
"ili9xxx", require_miso=False, require_mosi=True def final_validate(config):
) global_config = full_config.get()
# Ideally would calculate buffer size here, but that info is not available on the Python side
needs_buffer = (
CONF_LAMBDA in config or CONF_PAGES in config or config[CONF_AUTO_CLEAR_ENABLED]
)
if (
CORE.is_esp32
and config[CONF_COLOR_PALETTE] == "NONE"
and "psram" not in global_config
and needs_buffer
):
LOGGER.info("Consider enabling PSRAM if available for the display buffer")
return spi.final_validate_device_schema(
"ili9xxx", require_miso=False, require_mosi=True
)
FINAL_VALIDATE_SCHEMA = final_validate
async def to_code(config): async def to_code(config):
@ -283,6 +299,8 @@ async def to_code(config):
palette = converted.getpalette() palette = converted.getpalette()
assert len(palette) == 256 * 3 assert len(palette) == 256 * 3
rhs = palette rhs = palette
elif config[CONF_COLOR_PALETTE] == "8BIT":
cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_8))
else: else:
cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_16)) cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_16))

View File

@ -66,12 +66,9 @@ void ILI9XXXDisplay::setup() {
void ILI9XXXDisplay::alloc_buffer_() { void ILI9XXXDisplay::alloc_buffer_() {
if (this->buffer_color_mode_ == BITS_16) { if (this->buffer_color_mode_ == BITS_16) {
this->init_internal_(this->get_buffer_length_() * 2); this->init_internal_(this->get_buffer_length_() * 2);
if (this->buffer_ != nullptr) { } else {
return; this->init_internal_(this->get_buffer_length_());
}
this->buffer_color_mode_ = BITS_8;
} }
this->init_internal_(this->get_buffer_length_());
if (this->buffer_ == nullptr) { if (this->buffer_ == nullptr) {
this->mark_failed(); this->mark_failed();
} }

View File

@ -98,7 +98,8 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
protected: protected:
inline bool check_buffer_() { inline bool check_buffer_() {
if (this->buffer_ == nullptr) { if (this->buffer_ == nullptr) {
this->alloc_buffer_(); if (!this->is_failed())
this->alloc_buffer_();
return !this->is_failed(); return !this->is_failed();
} }
return true; return true;

View File

@ -82,11 +82,13 @@ class ImageEncoder:
self.dither = dither self.dither = dither
self.index = 0 self.index = 0
self.invert_alpha = invert_alpha self.invert_alpha = invert_alpha
self.path = ""
def convert(self, image): def convert(self, image, path):
""" """
Convert the image format Convert the image format
:param image: Input image :param image: Input image
:param path: Path to the image file
:return: converted image :return: converted image
""" """
return image return image
@ -103,6 +105,16 @@ class ImageEncoder:
""" """
def is_alpha_only(image: Image):
"""
Check if an image (assumed to be RGBA) is only alpha
"""
# Any alpha data?
if image.split()[-1].getextrema()[0] == 0xFF:
return False
return all(b.getextrema()[1] == 0 for b in image.split()[:-1])
class ImageBinary(ImageEncoder): class ImageBinary(ImageEncoder):
allow_config = {CONF_OPAQUE, CONF_INVERT_ALPHA, CONF_CHROMA_KEY} allow_config = {CONF_OPAQUE, CONF_INVERT_ALPHA, CONF_CHROMA_KEY}
@ -111,7 +123,9 @@ class ImageBinary(ImageEncoder):
super().__init__(self.width8, height, transparency, dither, invert_alpha) super().__init__(self.width8, height, transparency, dither, invert_alpha)
self.bitno = 0 self.bitno = 0
def convert(self, image): def convert(self, image, path):
if is_alpha_only(image):
image = image.split()[-1]
return image.convert("1", dither=self.dither) return image.convert("1", dither=self.dither)
def encode(self, pixel): def encode(self, pixel):
@ -136,7 +150,16 @@ class ImageBinary(ImageEncoder):
class ImageGrayscale(ImageEncoder): class ImageGrayscale(ImageEncoder):
allow_config = {CONF_ALPHA_CHANNEL, CONF_CHROMA_KEY, CONF_INVERT_ALPHA, CONF_OPAQUE} allow_config = {CONF_ALPHA_CHANNEL, CONF_CHROMA_KEY, CONF_INVERT_ALPHA, CONF_OPAQUE}
def convert(self, image): def convert(self, image, path):
if is_alpha_only(image):
if self.transparency != CONF_ALPHA_CHANNEL:
_LOGGER.warning(
"Grayscale image %s is alpha only, but transparency is set to %s",
path,
self.transparency,
)
self.transparency = CONF_ALPHA_CHANNEL
image = image.split()[-1]
return image.convert("LA") return image.convert("LA")
def encode(self, pixel): def encode(self, pixel):
@ -166,7 +189,7 @@ class ImageRGB565(ImageEncoder):
invert_alpha, invert_alpha,
) )
def convert(self, image): def convert(self, image, path):
return image.convert("RGBA") return image.convert("RGBA")
def encode(self, pixel): def encode(self, pixel):
@ -204,7 +227,7 @@ class ImageRGB(ImageEncoder):
invert_alpha, invert_alpha,
) )
def convert(self, image): def convert(self, image, path):
return image.convert("RGBA") return image.convert("RGBA")
def encode(self, pixel): def encode(self, pixel):
@ -308,7 +331,7 @@ def is_svg_file(file):
if not file: if not file:
return False return False
with open(file, "rb") as f: with open(file, "rb") as f:
return "<svg " in str(f.read(1024)) return "<svg" in str(f.read(1024))
def validate_cairosvg_installed(): def validate_cairosvg_installed():
@ -548,7 +571,7 @@ async def write_image(config, all_frames=False):
encoder = IMAGE_TYPE[type](width, total_rows, transparency, dither, invert_alpha) encoder = IMAGE_TYPE[type](width, total_rows, transparency, dither, invert_alpha)
for frame_index in range(frame_count): for frame_index in range(frame_count):
image.seek(frame_index) image.seek(frame_index)
pixels = encoder.convert(image.resize((width, height))).getdata() pixels = encoder.convert(image.resize((width, height)), path).getdata()
for row in range(height): for row in range(height):
for col in range(width): for col in range(width):
encoder.encode(pixels[row * width + col]) encoder.encode(pixels[row * width + col])
@ -557,7 +580,7 @@ async def write_image(config, all_frames=False):
rhs = [HexInt(x) for x in encoder.data] rhs = [HexInt(x) for x in encoder.data]
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
image_type = get_image_type_enum(type) image_type = get_image_type_enum(type)
trans_value = get_transparency_enum(transparency) trans_value = get_transparency_enum(encoder.transparency)
return prog_arr, width, height, image_type, trans_value, frame_count return prog_arr, width, height, image_type, trans_value, frame_count

View File

@ -22,10 +22,27 @@ void Image::draw(int x, int y, display::Display *display, Color color_on, Color
case IMAGE_TYPE_GRAYSCALE: case IMAGE_TYPE_GRAYSCALE:
for (int img_x = 0; img_x < width_; img_x++) { for (int img_x = 0; img_x < width_; img_x++) {
for (int img_y = 0; img_y < height_; img_y++) { for (int img_y = 0; img_y < height_; img_y++) {
auto color = this->get_grayscale_pixel_(img_x, img_y); const uint32_t pos = (img_x + img_y * this->width_);
if (color.w >= 0x80) { const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
display->draw_pixel_at(x + img_x, y + img_y, color); Color color = Color(gray, gray, gray, 0xFF);
switch (this->transparency_) {
case TRANSPARENCY_CHROMA_KEY:
if (gray == 1) {
continue; // skip drawing
}
break;
case TRANSPARENCY_ALPHA_CHANNEL: {
auto on = (float) gray / 255.0f;
auto off = 1.0f - on;
// blend color_on and color_off
color = Color(color_on.r * on + color_off.r * off, color_on.g * on + color_off.g * off,
color_on.b * on + color_off.b * off, 0xFF);
break;
}
default:
break;
} }
display->draw_pixel_at(x + img_x, y + img_y, color);
} }
} }
break; break;
@ -179,8 +196,16 @@ Color Image::get_rgb565_pixel_(int x, int y) const {
Color Image::get_grayscale_pixel_(int x, int y) const { Color Image::get_grayscale_pixel_(int x, int y) const {
const uint32_t pos = (x + y * this->width_); const uint32_t pos = (x + y * this->width_);
const uint8_t gray = progmem_read_byte(this->data_start_ + pos); const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
uint8_t alpha = (gray == 1 && this->transparency_ == TRANSPARENCY_CHROMA_KEY) ? 0 : 0xFF; switch (this->transparency_) {
return Color(gray, gray, gray, alpha); case TRANSPARENCY_CHROMA_KEY:
if (gray == 1)
return Color(0, 0, 0, 0);
return Color(gray, gray, gray, 0xFF);
case TRANSPARENCY_ALPHA_CHANNEL:
return Color(0, 0, 0, gray);
default:
return Color(gray, gray, gray, 0xFF);
}
} }
int Image::get_width() const { return this->width_; } int Image::get_width() const { return this->width_; }
int Image::get_height() const { return this->height_; } int Image::get_height() const { return this->height_; }

View File

@ -15,6 +15,7 @@ from .defines import (
CONF_FREEZE, CONF_FREEZE,
CONF_LVGL_ID, CONF_LVGL_ID,
CONF_SHOW_SNOW, CONF_SHOW_SNOW,
PARTS,
literal, literal,
) )
from .lv_validation import lv_bool, lv_color, lv_image, opacity from .lv_validation import lv_bool, lv_color, lv_image, opacity
@ -33,7 +34,7 @@ from .lvcode import (
lvgl_comp, lvgl_comp,
static_cast, 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 ( from .types import (
LV_STATE, LV_STATE,
LvglAction, LvglAction,
@ -41,6 +42,7 @@ from .types import (
ObjUpdateAction, ObjUpdateAction,
lv_disp_t, lv_disp_t,
lv_group_t, lv_group_t,
lv_obj_base_t,
lv_obj_t, lv_obj_t,
lv_pseudo_button_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) lv.group_focus_freeze(group, True)
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
return var 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)

View File

@ -501,9 +501,7 @@ size_t lv_millis(void) { return esphome::millis(); }
void *lv_custom_mem_alloc(size_t size) { void *lv_custom_mem_alloc(size_t size) {
auto *ptr = malloc(size); // NOLINT auto *ptr = malloc(size); // NOLINT
if (ptr == nullptr) { if (ptr == nullptr) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size);
esphome::ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size);
#endif
} }
return ptr; return ptr;
} }
@ -520,30 +518,22 @@ void *lv_custom_mem_alloc(size_t size) {
ptr = heap_caps_malloc(size, cap_bits); ptr = heap_caps_malloc(size, cap_bits);
} }
if (ptr == nullptr) { if (ptr == nullptr) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size);
esphome::ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size);
#endif
return nullptr; return nullptr;
} }
#ifdef ESPHOME_LOG_HAS_VERBOSE ESP_LOGV(esphome::lvgl::TAG, "allocate %zu - > %p", size, ptr);
esphome::ESP_LOGV(esphome::lvgl::TAG, "allocate %zu - > %p", size, ptr);
#endif
return ptr; return ptr;
} }
void lv_custom_mem_free(void *ptr) { void lv_custom_mem_free(void *ptr) {
#ifdef ESPHOME_LOG_HAS_VERBOSE ESP_LOGV(esphome::lvgl::TAG, "free %p", ptr);
esphome::ESP_LOGV(esphome::lvgl::TAG, "free %p", ptr);
#endif
if (ptr == nullptr) if (ptr == nullptr)
return; return;
heap_caps_free(ptr); heap_caps_free(ptr);
} }
void *lv_custom_mem_realloc(void *ptr, size_t size) { void *lv_custom_mem_realloc(void *ptr, size_t size) {
#ifdef ESPHOME_LOG_HAS_VERBOSE ESP_LOGV(esphome::lvgl::TAG, "realloc %p: %zu", ptr, size);
esphome::ESP_LOGV(esphome::lvgl::TAG, "realloc %p: %zu", ptr, size);
#endif
return heap_caps_realloc(ptr, size, cap_bits); return heap_caps_realloc(ptr, size, cap_bits);
} }
#endif #endif

View File

@ -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) 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 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 :param parts: The parts to include in the schema
:return: :return: The schema
""" """
parts = widget_type.parts
return cv.Schema({cv.Optional(part): STATE_SCHEMA for part in parts}).extend( return cv.Schema({cv.Optional(part): STATE_SCHEMA for part in parts}).extend(
STATE_SCHEMA 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 ( return (
part_schema(widget_type) part_schema(parts)
.extend( .extend(
{ {
cv.Required(CONF_ID): cv.ensure_list( cv.Required(CONF_ID): cv.ensure_list(
@ -245,7 +250,12 @@ def create_modify_schema(widget_type):
} }
) )
.extend(FLAG_SCHEMA) .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:
""" """
return ( return (
part_schema(widget_type) part_schema(widget_type.parts)
.extend(FLAG_SCHEMA) .extend(FLAG_SCHEMA)
.extend(LAYOUT_SCHEMA) .extend(LAYOUT_SCHEMA)
.extend(ALIGN_TO_SCHEMA) .extend(ALIGN_TO_SCHEMA)
@ -341,7 +351,6 @@ FLEX_OBJ_SCHEMA = {
cv.Optional(df.CONF_FLEX_GROW): cv.int_, cv.Optional(df.CONF_FLEX_GROW): cv.int_,
} }
DISP_BG_SCHEMA = cv.Schema( DISP_BG_SCHEMA = cv.Schema(
{ {
cv.Optional(df.CONF_DISP_BG_IMAGE): cv.Any( cv.Optional(df.CONF_DISP_BG_IMAGE): cv.Any(

View File

@ -37,7 +37,7 @@ DROPDOWN_BASE_SCHEMA = cv.Schema(
cv.Exclusive(CONF_SELECTED_INDEX, CONF_SELECTED_TEXT): lv_int, cv.Exclusive(CONF_SELECTED_INDEX, CONF_SELECTED_TEXT): lv_int,
cv.Exclusive(CONF_SELECTED_TEXT, CONF_SELECTED_TEXT): lv_text, cv.Exclusive(CONF_SELECTED_TEXT, CONF_SELECTED_TEXT): lv_text,
cv.Optional(CONF_DIR, default="BOTTOM"): DIRECTIONS.one_of, 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),
} }
) )

View File

@ -16,6 +16,11 @@ KEYBOARD_SCHEMA = {
cv.Optional(CONF_TEXTAREA): cv.use_id(lv_textarea_t), 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( lv_keyboard_t = LvType(
"LvKeyboardType", "LvKeyboardType",
parents=(KeyProvider, LvCompound), parents=(KeyProvider, LvCompound),
@ -32,6 +37,7 @@ class KeyboardType(WidgetType):
lv_keyboard_t, lv_keyboard_t,
(CONF_MAIN, CONF_ITEMS), (CONF_MAIN, CONF_ITEMS),
KEYBOARD_SCHEMA, KEYBOARD_SCHEMA,
modify_schema=KEYBOARD_MODIFY_SCHEMA,
) )
def get_uses(self): def get_uses(self):
@ -41,7 +47,8 @@ class KeyboardType(WidgetType):
lvgl_components_required.add("KEY_LISTENER") lvgl_components_required.add("KEY_LISTENER")
lvgl_components_required.add(CONF_KEYBOARD) lvgl_components_required.add(CONF_KEYBOARD)
add_lv_use("btnmatrix") 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): if ta := await get_widgets(config, CONF_TEXTAREA):
await w.set_property(CONF_TEXTAREA, ta[0].obj) await w.set_property(CONF_TEXTAREA, ta[0].obj)

View File

@ -51,7 +51,7 @@ MSGBOX_SCHEMA = container_schema(
cv.Required(CONF_TITLE): STYLED_TEXT_SCHEMA, cv.Required(CONF_TITLE): STYLED_TEXT_SCHEMA,
cv.Optional(CONF_BODY, default=""): STYLED_TEXT_SCHEMA, cv.Optional(CONF_BODY, default=""): STYLED_TEXT_SCHEMA,
cv.Optional(CONF_BUTTONS): cv.ensure_list(BUTTONMATRIX_BUTTON_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.Optional(CONF_CLOSE_BUTTON, default=True): lv_bool,
cv.GenerateID(CONF_BUTTON_TEXT_LIST_ID): cv.declare_id(char_ptr), cv.GenerateID(CONF_BUTTON_TEXT_LIST_ID): cv.declare_id(char_ptr),
} }

View File

@ -1,9 +1,5 @@
from esphome import automation
from ..automation import update_to_code
from ..defines import CONF_MAIN, CONF_OBJ, CONF_SCROLLBAR from ..defines import CONF_MAIN, CONF_OBJ, CONF_SCROLLBAR
from ..schemas import create_modify_schema from ..types import WidgetType, lv_obj_t
from ..types import ObjUpdateAction, WidgetType, lv_obj_t
class ObjType(WidgetType): class ObjType(WidgetType):
@ -21,10 +17,3 @@ class ObjType(WidgetType):
obj_spec = ObjType() obj_spec = ObjType()
@automation.register_action(
"lvgl.widget.update", ObjUpdateAction, create_modify_schema(obj_spec)
)
async def obj_update_to_code(config, action_id, template_arg, args):
return await update_to_code(config, action_id, template_arg, args)

View File

@ -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_POSITION, default="top"): DIRECTIONS.one_of,
cv.Optional(CONF_SIZE, default="10%"): size, cv.Optional(CONF_SIZE, default="10%"): size,
} }

View File

@ -2,7 +2,7 @@ from math import log
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import sensor, resistance_sampler from esphome.components import sensor
from esphome.const import ( from esphome.const import (
CONF_CALIBRATION, CONF_CALIBRATION,
CONF_REFERENCE_RESISTANCE, CONF_REFERENCE_RESISTANCE,
@ -15,8 +15,6 @@ from esphome.const import (
UNIT_CELSIUS, UNIT_CELSIUS,
) )
AUTO_LOAD = ["resistance_sampler"]
ntc_ns = cg.esphome_ns.namespace("ntc") ntc_ns = cg.esphome_ns.namespace("ntc")
NTC = ntc_ns.class_("NTC", cg.Component, sensor.Sensor) NTC = ntc_ns.class_("NTC", cg.Component, sensor.Sensor)
@ -126,7 +124,7 @@ CONFIG_SCHEMA = (
) )
.extend( .extend(
{ {
cv.Required(CONF_SENSOR): cv.use_id(resistance_sampler.ResistanceSampler), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
cv.Required(CONF_CALIBRATION): process_calibration, cv.Required(CONF_CALIBRATION): process_calibration,
} }
) )

View File

@ -59,6 +59,24 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) {
this->text_sensor_row_(stream, obj, area, node, friendly_name); this->text_sensor_row_(stream, obj, area, node, friendly_name);
#endif #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); req->send(stream);
} }
@ -511,6 +529,156 @@ void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_senso
} }
#endif #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 prometheus
} // namespace esphome } // namespace esphome
#endif #endif

View File

@ -128,6 +128,30 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
std::string &friendly_name); std::string &friendly_name);
#endif #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_; web_server_base::WebServerBase *base_;
bool include_internal_{false}; bool include_internal_{false};
std::map<EntityBase *, std::string> relabel_map_id_; std::map<EntityBase *, std::string> relabel_map_id_;

View File

@ -1,8 +1,7 @@
#pragma once #pragma once
#include "esphome/components/resistance_sampler/resistance_sampler.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome { namespace esphome {
namespace resistance { namespace resistance {
@ -12,7 +11,7 @@ enum ResistanceConfiguration {
DOWNSTREAM, DOWNSTREAM,
}; };
class ResistanceSensor : public Component, public sensor::Sensor, resistance_sampler::ResistanceSampler { class ResistanceSensor : public Component, public sensor::Sensor {
public: public:
void set_sensor(Sensor *sensor) { sensor_ = sensor; } void set_sensor(Sensor *sensor) { sensor_ = sensor; }
void set_configuration(ResistanceConfiguration configuration) { configuration_ = configuration; } void set_configuration(ResistanceConfiguration configuration) { configuration_ = configuration; }

View File

@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, resistance_sampler from esphome.components import sensor
from esphome.const import ( from esphome.const import (
CONF_REFERENCE_VOLTAGE, CONF_REFERENCE_VOLTAGE,
CONF_SENSOR, CONF_SENSOR,
@ -9,15 +9,8 @@ from esphome.const import (
ICON_FLASH, ICON_FLASH,
) )
AUTO_LOAD = ["resistance_sampler"]
resistance_ns = cg.esphome_ns.namespace("resistance") resistance_ns = cg.esphome_ns.namespace("resistance")
ResistanceSensor = resistance_ns.class_( ResistanceSensor = resistance_ns.class_("ResistanceSensor", cg.Component, sensor.Sensor)
"ResistanceSensor",
cg.Component,
sensor.Sensor,
resistance_sampler.ResistanceSampler,
)
CONF_CONFIGURATION = "configuration" CONF_CONFIGURATION = "configuration"
CONF_RESISTOR = "resistor" CONF_RESISTOR = "resistor"

View File

@ -1,6 +0,0 @@
import esphome.codegen as cg
resistance_sampler_ns = cg.esphome_ns.namespace("resistance_sampler")
ResistanceSampler = resistance_sampler_ns.class_("ResistanceSampler")
CODEOWNERS = ["@jesserockz"]

View File

@ -1,10 +0,0 @@
#pragma once
namespace esphome {
namespace resistance_sampler {
/// Abstract interface to mark components that provide resistance values.
class ResistanceSampler {};
} // namespace resistance_sampler
} // namespace esphome

View File

@ -7,6 +7,10 @@ namespace spi {
const char *const TAG = "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; } bool SPIDelegate::is_ready() { return true; }
GPIOPin *const NullPin::NULL_PIN = new NullPin(); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) 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); } 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); } void SPIDelegateBitBash::write(uint16_t data, size_t num_bits) { this->transfer_(data, num_bits); }

View File

@ -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 // 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. // a thin wrapper over SPIClass.
class SPIDelegate { class SPIDelegate {
@ -248,6 +250,21 @@ class SPIDelegate {
uint32_t data_rate_{1000000}; uint32_t data_rate_{1000000};
SPIMode mode_{MODE0}; SPIMode mode_{MODE0};
GPIOPin *cs_pin_{NullPin::NULL_PIN}; 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() { virtual void spi_teardown() {
this->parent_->unregister_device(this); this->parent_->unregister_device(this);
this->delegate_ = nullptr; this->delegate_ = SPIDelegate::NULL_DELEGATE;
} }
bool spi_is_ready() { return this->delegate_->is_ready(); } bool spi_is_ready() { return this->delegate_->is_ready(); }
@ -376,7 +393,7 @@ class SPIClient {
uint32_t data_rate_{1000000}; uint32_t data_rate_{1000000};
SPIComponent *parent_{nullptr}; SPIComponent *parent_{nullptr};
GPIOPin *cs_{nullptr}; GPIOPin *cs_{nullptr};
SPIDelegate *delegate_{nullptr}; SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE};
}; };
/** /**

View File

@ -1,14 +1,14 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, time from esphome.components import sensor, time
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_TIME_ID, CONF_TIME_ID,
DEVICE_CLASS_DURATION,
DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_TIMESTAMP,
ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_DIAGNOSTIC,
ICON_TIMER,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
UNIT_SECOND, UNIT_SECOND,
ICON_TIMER,
DEVICE_CLASS_DURATION,
) )
uptime_ns = cg.esphome_ns.namespace("uptime") uptime_ns = cg.esphome_ns.namespace("uptime")

View File

@ -0,0 +1,19 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC, ICON_TIMER
uptime_ns = cg.esphome_ns.namespace("uptime")
UptimeTextSensor = uptime_ns.class_(
"UptimeTextSensor", text_sensor.TextSensor, cg.PollingComponent
)
CONFIG_SCHEMA = text_sensor.text_sensor_schema(
UptimeTextSensor,
icon=ICON_TIMER,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
).extend(cv.polling_component_schema("60s"))
async def to_code(config):
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)

View File

@ -0,0 +1,46 @@
#include "uptime_text_sensor.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace uptime {
static const char *const TAG = "uptime.sensor";
void UptimeTextSensor::setup() { this->last_ms_ = millis(); }
void UptimeTextSensor::update() {
const auto now = millis();
// get whole seconds since last update. Note that even if the millis count has overflowed between updates,
// the difference will still be correct due to the way twos-complement arithmetic works.
const uint32_t delta = (now - this->last_ms_) / 1000;
if (delta == 0)
return;
// set last_ms_ to the last second boundary
this->last_ms_ = now - (now % 1000);
this->uptime_ += delta;
auto uptime = this->uptime_;
unsigned days = uptime / (24 * 3600);
unsigned seconds = uptime % (24 * 3600);
unsigned hours = seconds / 3600;
seconds %= 3600;
unsigned minutes = seconds / 60;
seconds %= 60;
if (days != 0) {
this->publish_state(str_sprintf("%dd%dh%dm%ds", days, hours, minutes, seconds));
} else if (hours != 0) {
this->publish_state(str_sprintf("%dh%dm%ds", hours, minutes, seconds));
} else if (minutes != 0) {
this->publish_state(str_sprintf("%dm%ds", minutes, seconds));
} else {
this->publish_state(str_sprintf("%ds", seconds));
}
}
float UptimeTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
void UptimeTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Uptime Text Sensor", this); }
} // namespace uptime
} // namespace esphome

View File

@ -0,0 +1,25 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace uptime {
class UptimeTextSensor : public text_sensor::TextSensor, public PollingComponent {
public:
void update() override;
void dump_config() override;
void setup() override;
float get_setup_priority() const override;
protected:
uint64_t uptime_{0};
uint64_t last_ms_{0};
};
} // namespace uptime
} // namespace esphome

View File

@ -455,8 +455,9 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
} else if (match.method == "toggle") { } else if (match.method == "toggle") {
this->schedule_([obj]() { obj->toggle().perform(); }); this->schedule_([obj]() { obj->toggle().perform(); });
request->send(200); request->send(200);
} else if (match.method == "turn_on") { } else if (match.method == "turn_on" || match.method == "turn_off") {
auto call = obj->turn_on(); auto call = match.method == "turn_on" ? obj->turn_on() : obj->turn_off();
if (request->hasParam("speed_level")) { if (request->hasParam("speed_level")) {
auto speed_level = request->getParam("speed_level")->value(); auto speed_level = request->getParam("speed_level")->value();
auto val = parse_number<int>(speed_level.c_str()); auto val = parse_number<int>(speed_level.c_str());
@ -486,9 +487,6 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
} }
this->schedule_([call]() mutable { call.perform(); }); this->schedule_([call]() mutable { call.perform(); });
request->send(200); request->send(200);
} else if (match.method == "turn_off") {
this->schedule_([obj]() { obj->turn_off().perform(); });
request->send(200);
} else { } else {
request->send(404); request->send(404);
} }

View File

@ -1,6 +1,6 @@
"""Constants used by esphome.""" """Constants used by esphome."""
__version__ = "2025.1.0-dev" __version__ = "2025.2.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = ( VALID_SUBSTITUTIONS_CHARACTERS = (
@ -490,6 +490,7 @@ CONF_MEMORY_BLOCKS = "memory_blocks"
CONF_MESSAGE = "message" CONF_MESSAGE = "message"
CONF_METHANE = "methane" CONF_METHANE = "methane"
CONF_METHOD = "method" CONF_METHOD = "method"
CONF_MIC_GAIN = "mic_gain"
CONF_MICROPHONE = "microphone" CONF_MICROPHONE = "microphone"
CONF_MIN_BRIGHTNESS = "min_brightness" CONF_MIN_BRIGHTNESS = "min_brightness"
CONF_MIN_COOLING_OFF_TIME = "min_cooling_off_time" CONF_MIN_COOLING_OFF_TIME = "min_cooling_off_time"

View File

@ -74,7 +74,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define esph_log_vv(tag, format, ...) \ #define esph_log_vv(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_VERY_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_VERY_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_VERY_VERBOSE #define ESPHOME_LOG_HAS_VERY_VERBOSE
#else #else
@ -83,7 +83,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
#define esph_log_v(tag, format, ...) \ #define esph_log_v(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_VERBOSE #define ESPHOME_LOG_HAS_VERBOSE
#else #else
@ -92,9 +92,9 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
#define esph_log_d(tag, format, ...) \ #define esph_log_d(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_DEBUG, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_DEBUG, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define esph_log_config(tag, format, ...) \ #define esph_log_config(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_DEBUG #define ESPHOME_LOG_HAS_DEBUG
#define ESPHOME_LOG_HAS_CONFIG #define ESPHOME_LOG_HAS_CONFIG
@ -105,7 +105,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_INFO #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_INFO
#define esph_log_i(tag, format, ...) \ #define esph_log_i(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_INFO, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_INFO, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_INFO #define ESPHOME_LOG_HAS_INFO
#else #else
@ -114,7 +114,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
#define esph_log_w(tag, format, ...) \ #define esph_log_w(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_WARN, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_WARN, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_WARN #define ESPHOME_LOG_HAS_WARN
#else #else
@ -123,7 +123,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR
#define esph_log_e(tag, format, ...) \ #define esph_log_e(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_ERROR, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_ERROR, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_ERROR #define ESPHOME_LOG_HAS_ERROR
#else #else

View File

@ -0,0 +1,6 @@
i2c:
- id: i2c_aic3204
scl: ${scl_pin}
sda: ${sda_pin}
es7210:

View File

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO16
sda_pin: GPIO17
<<: !include common.yaml

View File

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml

View File

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml

View File

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO16
sda_pin: GPIO17
<<: !include common.yaml

View File

@ -20,6 +20,7 @@ display:
it.rectangle(0, 0, it.get_width(), it.get_height()); it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: ili9xxx - platform: ili9xxx
invert_colors: false invert_colors: false
color_palette: 8bit
dimensions: dimensions:
width: 320 width: 320
height: 240 height: 240

View File

@ -795,6 +795,34 @@ lvgl:
color: 0xA0A0A0 color: 0xA0A0A0
r_mod: -20 r_mod: -20
opa: 0% opa: 0%
- id: page3
widgets:
- keyboard:
id: lv_keyboard
align: bottom_mid
on_value:
then:
- 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: font:
- file: "gfonts://Roboto" - file: "gfonts://Roboto"
@ -805,10 +833,13 @@ image:
- id: cat_image - id: cat_image
resize: 256x48 resize: 256x48
file: $component_dir/logo-text.svg file: $component_dir/logo-text.svg
type: RGB565
use_transparency: alpha_channel
- id: dog_image - id: dog_image
file: $component_dir/logo-text.svg file: $component_dir/logo-text.svg
resize: 256x48 resize: 256x48
type: TRANSPARENT_BINARY type: BINARY
use_transparency: chroma_key
color: color:
- id: light_blue - id: light_blue

View File

@ -78,6 +78,26 @@ lock:
} }
optimistic: true 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: prometheus:
include_internal: true include_internal: true
relabel: relabel:

View File

@ -1 +1,34 @@
<<: !include common.yaml <<: !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%

View File

@ -13,3 +13,7 @@ sensor:
- platform: uptime - platform: uptime
name: Uptime Sensor Timestamp name: Uptime Sensor Timestamp
type: timestamp type: timestamp
text_sensor:
- platform: uptime
name: Uptime Text