1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 08:41:59 +00:00

Merge remote-tracking branch 'upstream/dev' into integration

This commit is contained in:
J. Nick Koston
2025-12-05 15:58:18 -06:00
75 changed files with 1453 additions and 118 deletions

View File

@@ -1 +1 @@
29270eecb86ffa07b2b1d2a4ca56dd7f84762ddc89c6248dbf3f012eca8780b6
c01eec15857a784dd603c0afd194ab3b29a632422fe6f6b0a806ad4d81b5efc0

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
with:
category: "/language:${{matrix.language}}"

View File

@@ -41,7 +41,7 @@ jobs:
python script/run-in-env.py pre-commit run --all-files
- name: Commit changes
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
with:
commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@openhomefoundation.org>

View File

@@ -227,6 +227,7 @@ esphome/components/hte501/* @Stock-M
esphome/components/http_request/ota/* @oarcher
esphome/components/http_request/update/* @jesserockz
esphome/components/htu31d/* @betterengineering
esphome/components/hub75/* @stuartparmenter
esphome/components/hydreon_rgxx/* @functionpointer
esphome/components/hyt271/* @Philippe12
esphome/components/i2c/* @esphome/core

View File

@@ -2,8 +2,8 @@
<a href="https://esphome.io/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://esphome.io/_static/logo-text-on-dark.svg", alt="ESPHome Logo">
<img src="https://esphome.io/_static/logo-text-on-light.svg" alt="ESPHome Logo">
<source media="(prefers-color-scheme: dark)" srcset="https://media.esphome.io/logo/logo-text-on-dark.svg">
<img src="https://media.esphome.io/logo/logo-text-on-light.svg" alt="ESPHome Logo">
</picture>
</a>

View File

@@ -1,15 +1,17 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components.esp32 import VARIANT_ESP32P4, get_esp32_variant
from esphome.components.esp32.const import (
from esphome.components.esp32 import (
VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
get_esp32_variant,
)
import esphome.config_validation as cv
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
@@ -99,6 +101,13 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
5: adc_channel_t.ADC_CHANNEL_5,
6: adc_channel_t.ADC_CHANNEL_6,
},
# https://docs.espressif.com/projects/esp-idf/en/latest/esp32c61/api-reference/peripherals/gpio.html
VARIANT_ESP32C61: {
1: adc_channel_t.ADC_CHANNEL_0,
3: adc_channel_t.ADC_CHANNEL_1,
4: adc_channel_t.ADC_CHANNEL_2,
5: adc_channel_t.ADC_CHANNEL_3,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
VARIANT_ESP32H2: {
1: adc_channel_t.ADC_CHANNEL_0,
@@ -174,6 +183,8 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
VARIANT_ESP32C5: {}, # no ADC2
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
VARIANT_ESP32C6: {}, # no ADC2
# ESP32-C61 has no ADC2
VARIANT_ESP32C61: {}, # no ADC2
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
VARIANT_ESP32H2: {}, # no ADC2
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32p4/include/soc/adc_channel.h

View File

@@ -42,10 +42,11 @@ void ADCSensor::setup() {
adc_oneshot_unit_init_cfg_t init_config = {}; // Zero initialize
init_config.unit_id = this->adc_unit_;
init_config.ulp_mode = ADC_ULP_MODE_DISABLE;
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32H2
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
init_config.clk_src = ADC_DIGI_CLK_SRC_DEFAULT;
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 ||
// USE_ESP32_VARIANT_ESP32H2
// USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
esp_err_t err = adc_oneshot_new_unit(&init_config, &ADCSensor::shared_adc_handles[this->adc_unit_]);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error initializing %s: %d", LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)), err);
@@ -74,7 +75,7 @@ void ADCSensor::setup() {
adc_cali_handle_t handle = nullptr;
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
// RISC-V variants and S3 use curve fitting calibration
adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
@@ -111,7 +112,7 @@ void ADCSensor::setup() {
ESP_LOGW(TAG, "Line fitting calibration failed with error %d, will use uncalibrated readings", err);
this->setup_flags_.calibration_complete = false;
}
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32H2 || ESP32P4 || ESP32S3
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
}
this->setup_flags_.init_complete = true;
@@ -186,11 +187,11 @@ float ADCSensor::sample_fixed_attenuation_() {
ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err);
if (this->calibration_handle_ != nullptr) {
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
#else // Other ESP32 variants use line fitting calibration
adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32H2 || ESP32P4 || ESP32S3
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
this->calibration_handle_ = nullptr;
}
}
@@ -219,7 +220,7 @@ float ADCSensor::sample_autorange_() {
if (this->calibration_handle_ != nullptr) {
// Delete old calibration handle
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
#else
adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
@@ -231,7 +232,7 @@ float ADCSensor::sample_autorange_() {
adc_cali_handle_t handle = nullptr;
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
adc_cali_curve_fitting_config_t cali_config = {};
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
cali_config.chan = this->channel_;
@@ -266,7 +267,7 @@ float ADCSensor::sample_autorange_() {
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
if (handle != nullptr) {
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
adc_cali_delete_scheme_curve_fitting(handle);
#else
adc_cali_delete_scheme_line_fitting(handle);
@@ -288,7 +289,7 @@ float ADCSensor::sample_autorange_() {
}
// Clean up calibration handle
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
adc_cali_delete_scheme_curve_fitting(handle);
#else
adc_cali_delete_scheme_line_fitting(handle);

View File

@@ -506,7 +506,7 @@ class APIConnection final : public APIServerConnection {
class MessageCreator {
public:
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; }
explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; }
// Call operator - uses message_type to determine union type
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,

View File

@@ -69,7 +69,7 @@ CONFIG_SCHEMA = cv.All(
cv.only_on_esp8266,
cv.All(
cv.only_on_esp32,
esp32.only_on_variant(supported=[esp32.const.VARIANT_ESP32]),
esp32.only_on_variant(supported=[esp32.VARIANT_ESP32]),
),
),
)

View File

@@ -1,15 +1,18 @@
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import esp32, time
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import (
from esphome.components.esp32 import (
VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
get_esp32_variant,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
@@ -54,8 +57,11 @@ WAKEUP_PINS = {
],
VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5],
VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5],
VARIANT_ESP32C5: [0, 1, 2, 3, 4, 5, 6, 7],
VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7],
VARIANT_ESP32C61: [0, 1, 2, 3, 4, 5, 6],
VARIANT_ESP32H2: [7, 8, 9, 10, 11, 12, 13, 14],
VARIANT_ESP32P4: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
VARIANT_ESP32S2: [
0,
1,
@@ -122,8 +128,11 @@ def _validate_ex1_wakeup_mode(value):
if value == "ANY_LOW":
esp32.only_on_variant(
supported=[
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
],
@@ -218,7 +227,9 @@ CONFIG_SCHEMA = cv.All(
unsupported=[
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
],
msg_prefix="Wakeup from touch",

View File

@@ -81,7 +81,7 @@ class DeepSleepComponent : public Component {
#endif
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \
!defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
!defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32H2)
void set_touch_wakeup(bool touch_wakeup);
#endif

View File

@@ -18,6 +18,7 @@ namespace deep_sleep {
// | ESP32-C3 | | | | ✓ |
// | ESP32-C5 | | (✓) | | (✓) |
// | ESP32-C6 | | ✓ | | ✓ |
// | ESP32-C61 | | ✓ | | ✓ |
// | ESP32-H2 | | ✓ | | |
//
// Notes:
@@ -55,7 +56,7 @@ void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wa
#endif
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \
!defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
!defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32H2)
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
#endif
@@ -121,8 +122,9 @@ void DeepSleepComponent::deep_sleep_() {
}
#endif
// GPIO wakeup - C2, C3, C6 only
#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6)
// GPIO wakeup - C2, C3, C6, C61 only
#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
defined(USE_ESP32_VARIANT_ESP32C61)
if (this->wakeup_pin_ != nullptr) {
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLUP) {
@@ -155,7 +157,7 @@ void DeepSleepComponent::deep_sleep_() {
// Touch wakeup - ESP32, S2, S3 only
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \
!defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
!defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32H2)
if (this->touch_wakeup_.has_value() && *(this->touch_wakeup_)) {
esp_sleep_enable_touchpad_wakeup();
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);

View File

@@ -59,6 +59,7 @@ from .const import ( # noqa
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
@@ -126,6 +127,7 @@ CPU_FREQUENCIES = {
VARIANT_ESP32C3: get_cpu_frequencies(80, 160),
VARIANT_ESP32C5: get_cpu_frequencies(80, 160, 240),
VARIANT_ESP32C6: get_cpu_frequencies(80, 120, 160),
VARIANT_ESP32C61: get_cpu_frequencies(80, 120, 160),
VARIANT_ESP32H2: get_cpu_frequencies(16, 32, 48, 64, 96),
VARIANT_ESP32P4: get_cpu_frequencies(40, 360, 400),
VARIANT_ESP32S2: get_cpu_frequencies(80, 160, 240),

View File

@@ -4,6 +4,7 @@ from .const import (
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
@@ -17,6 +18,7 @@ STANDARD_BOARDS = {
VARIANT_ESP32C3: "esp32-c3-devkitm-1",
VARIANT_ESP32C5: "esp32-c5-devkitc-1",
VARIANT_ESP32C6: "esp32-c6-devkitm-1",
VARIANT_ESP32C61: "esp32-c61-devkitc1-n8r2",
VARIANT_ESP32H2: "esp32-h2-devkitm-1",
VARIANT_ESP32P4: "esp32-p4-evboard",
VARIANT_ESP32S2: "esp32-s2-kaluga-1",

View File

@@ -17,6 +17,7 @@ VARIANT_ESP32C2 = "ESP32C2"
VARIANT_ESP32C3 = "ESP32C3"
VARIANT_ESP32C5 = "ESP32C5"
VARIANT_ESP32C6 = "ESP32C6"
VARIANT_ESP32C61 = "ESP32C61"
VARIANT_ESP32H2 = "ESP32H2"
VARIANT_ESP32P4 = "ESP32P4"
VARIANT_ESP32S2 = "ESP32S2"
@@ -27,6 +28,7 @@ VARIANTS = [
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
@@ -39,6 +41,7 @@ VARIANT_FRIENDLY = {
VARIANT_ESP32C3: "ESP32-C3",
VARIANT_ESP32C5: "ESP32-C5",
VARIANT_ESP32C6: "ESP32-C6",
VARIANT_ESP32C61: "ESP32-C61",
VARIANT_ESP32H2: "ESP32-H2",
VARIANT_ESP32P4: "ESP32-P4",
VARIANT_ESP32S2: "ESP32-S2",

View File

@@ -29,6 +29,7 @@ from .const import (
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
@@ -40,6 +41,7 @@ from .gpio_esp32_c2 import esp32_c2_validate_gpio_pin, esp32_c2_validate_support
from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
from .gpio_esp32_c5 import esp32_c5_validate_gpio_pin, esp32_c5_validate_supports
from .gpio_esp32_c6 import esp32_c6_validate_gpio_pin, esp32_c6_validate_supports
from .gpio_esp32_c61 import esp32_c61_validate_gpio_pin, esp32_c61_validate_supports
from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
from .gpio_esp32_p4 import esp32_p4_validate_gpio_pin, esp32_p4_validate_supports
from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
@@ -110,6 +112,10 @@ _esp32_validations = {
pin_validation=esp32_c6_validate_gpio_pin,
usage_validation=esp32_c6_validate_supports,
),
VARIANT_ESP32C61: ESP32ValidationFunctions(
pin_validation=esp32_c61_validate_gpio_pin,
usage_validation=esp32_c61_validate_supports,
),
VARIANT_ESP32H2: ESP32ValidationFunctions(
pin_validation=esp32_h2_validate_gpio_pin,
usage_validation=esp32_h2_validate_supports,

View File

@@ -0,0 +1,46 @@
import logging
import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin
# GPIO14-17, GPIO19-21 are used for SPI flash/PSRAM
_ESP32C61_SPI_PSRAM_PINS = {
14: "SPICS0",
15: "SPICLK",
16: "SPID",
17: "SPIQ",
19: "SPIWP",
20: "SPIHD",
21: "VDD_SPI",
}
_ESP32C61_STRAPPING_PINS = {8, 9}
_LOGGER = logging.getLogger(__name__)
def esp32_c61_validate_gpio_pin(value):
if value < 0 or value > 29:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-29)")
if value in _ESP32C61_SPI_PSRAM_PINS:
raise cv.Invalid(
f"This pin cannot be used on ESP32-C61s and is already used by the SPI/PSRAM interface (function: {_ESP32C61_SPI_PSRAM_PINS[value]})"
)
return value
def esp32_c61_validate_supports(value):
num = value[CONF_NUMBER]
mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
if num < 0 or num > 29:
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-29)")
if is_input:
# All ESP32-C61 pins support input mode
pass
check_strapping_pin(value, _ESP32C61_STRAPPING_PINS, _LOGGER)
return value

View File

@@ -4,15 +4,17 @@ from esphome import pins
import esphome.codegen as cg
from esphome.components import canbus
from esphome.components.canbus import CONF_BIT_RATE, CanbusComponent, CanSpeed
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import (
from esphome.components.esp32 import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
get_esp32_variant,
)
import esphome.config_validation as cv
from esphome.const import (
@@ -58,14 +60,18 @@ CAN_SPEEDS_ESP32_S2 = {
CAN_SPEEDS_ESP32_S3 = {**CAN_SPEEDS_ESP32_S2}
CAN_SPEEDS_ESP32_C3 = {**CAN_SPEEDS_ESP32_S2}
CAN_SPEEDS_ESP32_C5 = {**CAN_SPEEDS_ESP32_S2}
CAN_SPEEDS_ESP32_C6 = {**CAN_SPEEDS_ESP32_S2}
CAN_SPEEDS_ESP32_C61 = {**CAN_SPEEDS_ESP32_S2}
CAN_SPEEDS_ESP32_H2 = {**CAN_SPEEDS_ESP32_S2}
CAN_SPEEDS_ESP32_P4 = {**CAN_SPEEDS_ESP32_S2}
CAN_SPEEDS = {
VARIANT_ESP32: CAN_SPEEDS_ESP32,
VARIANT_ESP32C3: CAN_SPEEDS_ESP32_C3,
VARIANT_ESP32C5: CAN_SPEEDS_ESP32_C5,
VARIANT_ESP32C6: CAN_SPEEDS_ESP32_C6,
VARIANT_ESP32C61: CAN_SPEEDS_ESP32_C61,
VARIANT_ESP32H2: CAN_SPEEDS_ESP32_H2,
VARIANT_ESP32P4: CAN_SPEEDS_ESP32_P4,
VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2,

View File

@@ -16,8 +16,9 @@ static const char *const TAG = "esp32_can";
static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) {
switch (bitrate) {
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || \
defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3)
case canbus::CAN_1KBPS:
*t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS();
return true;

View File

@@ -1,8 +1,7 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import output
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import VARIANT_ESP32, VARIANT_ESP32S2
from esphome.components.esp32 import VARIANT_ESP32, VARIANT_ESP32S2, get_esp32_variant
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN

View File

@@ -40,8 +40,8 @@ CONFIG_SCHEMA = cv.All(
),
esp32.only_on_variant(
supported=[
esp32.const.VARIANT_ESP32H2,
esp32.const.VARIANT_ESP32P4,
esp32.VARIANT_ESP32H2,
esp32.VARIANT_ESP32P4,
]
),
)

View File

@@ -9,7 +9,7 @@ def validate_clock_resolution():
cv.only_on_esp32(value)
value = cv.int_(value)
variant = esp32.get_esp32_variant()
if variant == esp32.const.VARIANT_ESP32H2 and value > 32000000:
if variant == esp32.VARIANT_ESP32H2 and value > 32000000:
raise cv.Invalid(
f"ESP32 variant {variant} has a max clock_resolution of 32000000."
)

View File

@@ -91,7 +91,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
cv.Optional(CONF_USE_DMA): cv.All(
esp32.only_on_variant(
supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3]
supported=[esp32.VARIANT_ESP32P4, esp32.VARIANT_ESP32S3]
),
cv.boolean,
),

View File

@@ -1,10 +1,11 @@
import esphome.codegen as cg
from esphome.components import esp32
from esphome.components.esp32 import get_esp32_variant, gpio
from esphome.components.esp32.const import (
from esphome.components.esp32 import (
VARIANT_ESP32,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
get_esp32_variant,
gpio,
)
import esphome.config_validation as cv
from esphome.const import (
@@ -255,9 +256,9 @@ CONFIG_SCHEMA = cv.All(
cv.has_none_or_all_keys(CONF_WATERPROOF_GUARD_RING, CONF_WATERPROOF_SHIELD_DRIVER),
esp32.only_on_variant(
supported=[
esp32.const.VARIANT_ESP32,
esp32.const.VARIANT_ESP32S2,
esp32.const.VARIANT_ESP32S3,
esp32.VARIANT_ESP32,
esp32.VARIANT_ESP32S2,
esp32.VARIANT_ESP32S3,
]
),
validate_variant_vars,

View File

@@ -3,16 +3,17 @@ import logging
from esphome import pins
import esphome.codegen as cg
from esphome.components.esp32 import (
add_idf_component,
add_idf_sdkconfig_option,
get_esp32_variant,
)
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
add_idf_component,
add_idf_sdkconfig_option,
get_esp32_variant,
)
from esphome.components.network import ip_address_literal
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
@@ -303,7 +304,14 @@ def _final_validate_spi(config):
return
if spi_configs := fv.full_config.get().get(CONF_SPI):
variant = get_esp32_variant()
if variant in (VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3):
if variant in (
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
):
spi_host = "SPI2_HOST"
else:
spi_host = "SPI3_HOST"

View File

@@ -87,8 +87,8 @@ void EthernetComponent::setup() {
.intr_flags = 0,
};
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || \
defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
auto host = SPI2_HOST;
#else
auto host = SPI3_HOST;

View File

@@ -0,0 +1,6 @@
from esphome.cpp_generator import MockObj
CODEOWNERS = ["@stuartparmenter"]
# Use fully-qualified namespace to avoid collision with external hub75 library's global ::hub75 namespace
hub75_ns = MockObj("::esphome::hub75", "::")

View File

@@ -0,0 +1,80 @@
"""Board presets for HUB75 displays.
Each board preset defines standard pin mappings for HUB75 controller boards.
"""
from dataclasses import dataclass, field
import importlib
import pkgutil
from typing import ClassVar
class BoardRegistry:
"""Global registry for board configurations."""
_boards: ClassVar[dict[str, "BoardConfig"]] = {}
@classmethod
def register(cls, board: "BoardConfig") -> None:
"""Register a board configuration."""
cls._boards[board.name] = board
@classmethod
def get_boards(cls) -> dict[str, "BoardConfig"]:
"""Return all registered boards."""
return cls._boards
@dataclass
class BoardConfig:
"""Board configuration storing HUB75 pin mappings."""
name: str
r1_pin: int
g1_pin: int
b1_pin: int
r2_pin: int
g2_pin: int
b2_pin: int
a_pin: int
b_pin: int
c_pin: int
d_pin: int
e_pin: int | None
lat_pin: int
oe_pin: int
clk_pin: int
ignore_strapping_pins: tuple[str, ...] = () # e.g., ("a_pin", "clk_pin")
# Derived field for pin lookup
pins: dict[str, int | None] = field(default_factory=dict, init=False, repr=False)
def __post_init__(self):
"""Initialize derived fields and register board."""
self.name = self.name.lower()
self.pins = {
"r1": self.r1_pin,
"g1": self.g1_pin,
"b1": self.b1_pin,
"r2": self.r2_pin,
"g2": self.g2_pin,
"b2": self.b2_pin,
"a": self.a_pin,
"b": self.b_pin,
"c": self.c_pin,
"d": self.d_pin,
"e": self.e_pin,
"lat": self.lat_pin,
"oe": self.oe_pin,
"clk": self.clk_pin,
}
BoardRegistry.register(self)
def get_pin(self, pin_name: str) -> int | None:
"""Get pin number for a given pin name."""
return self.pins.get(pin_name)
# Dynamically import all board definition modules
for module_info in pkgutil.iter_modules(__path__):
importlib.import_module(f".{module_info.name}", package=__package__)

View File

@@ -0,0 +1,23 @@
"""Adafruit Matrix Portal board definitions."""
from . import BoardConfig
# Adafruit Matrix Portal S3
BoardConfig(
"adafruit-matrix-portal-s3",
r1_pin=42,
g1_pin=41,
b1_pin=40,
r2_pin=38,
g2_pin=39,
b2_pin=37,
a_pin=45,
b_pin=36,
c_pin=48,
d_pin=35,
e_pin=21,
lat_pin=47,
oe_pin=14,
clk_pin=2,
ignore_strapping_pins=("a_pin",), # GPIO45 is a strapping pin
)

View File

@@ -0,0 +1,41 @@
"""Apollo Automation M1 board definitions."""
from . import BoardConfig
# Apollo Automation M1 Rev4
BoardConfig(
"apollo-automation-m1-rev4",
r1_pin=42,
g1_pin=41,
b1_pin=40,
r2_pin=38,
g2_pin=39,
b2_pin=37,
a_pin=45,
b_pin=36,
c_pin=48,
d_pin=35,
e_pin=21,
lat_pin=47,
oe_pin=14,
clk_pin=2,
)
# Apollo Automation M1 Rev6
BoardConfig(
"apollo-automation-m1-rev6",
r1_pin=1,
g1_pin=5,
b1_pin=6,
r2_pin=7,
g2_pin=13,
b2_pin=9,
a_pin=16,
b_pin=48,
c_pin=47,
d_pin=21,
e_pin=38,
lat_pin=8,
oe_pin=4,
clk_pin=18,
)

View File

@@ -0,0 +1,22 @@
"""Huidu board definitions."""
from . import BoardConfig
# Huidu HD-WF2
BoardConfig(
"huidu-hd-wf2",
r1_pin=2,
g1_pin=6,
b1_pin=10,
r2_pin=3,
g2_pin=7,
b2_pin=11,
a_pin=39,
b_pin=38,
c_pin=37,
d_pin=36,
e_pin=21,
lat_pin=33,
oe_pin=35,
clk_pin=34,
)

View File

@@ -0,0 +1,24 @@
"""ESP32 Trinity board definitions."""
from . import BoardConfig
# ESP32 Trinity
# https://esp32trinity.com/
# Pin assignments from: https://github.com/witnessmenow/ESP32-Trinity/blob/master/FAQ.md
BoardConfig(
"esp32-trinity",
r1_pin=25,
g1_pin=26,
b1_pin=27,
r2_pin=14,
g2_pin=12,
b2_pin=13,
a_pin=23,
b_pin=19,
c_pin=5,
d_pin=17,
e_pin=18,
lat_pin=4,
oe_pin=15,
clk_pin=16,
)

View File

@@ -0,0 +1,578 @@
from typing import Any
from esphome import pins
import esphome.codegen as cg
from esphome.components import display
from esphome.components.esp32 import add_idf_component
import esphome.config_validation as cv
from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
CONF_BIT_DEPTH,
CONF_BOARD,
CONF_BRIGHTNESS,
CONF_CLK_PIN,
CONF_GAMMA_CORRECT,
CONF_ID,
CONF_LAMBDA,
CONF_OE_PIN,
CONF_UPDATE_INTERVAL,
)
import esphome.final_validate as fv
from esphome.types import ConfigType
from . import boards, hub75_ns
DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@stuartparmenter"]
# Load all board presets
BOARDS = boards.BoardRegistry.get_boards()
# Constants
CONF_HUB75_ID = "hub75_id"
# Panel dimensions
CONF_PANEL_WIDTH = "panel_width"
CONF_PANEL_HEIGHT = "panel_height"
# Multi-panel layout
CONF_LAYOUT_ROWS = "layout_rows"
CONF_LAYOUT_COLS = "layout_cols"
CONF_LAYOUT = "layout"
# Panel hardware
CONF_SCAN_WIRING = "scan_wiring"
CONF_SHIFT_DRIVER = "shift_driver"
# RGB pins
CONF_R1_PIN = "r1_pin"
CONF_G1_PIN = "g1_pin"
CONF_B1_PIN = "b1_pin"
CONF_R2_PIN = "r2_pin"
CONF_G2_PIN = "g2_pin"
CONF_B2_PIN = "b2_pin"
# Address pins
CONF_A_PIN = "a_pin"
CONF_B_PIN = "b_pin"
CONF_C_PIN = "c_pin"
CONF_D_PIN = "d_pin"
CONF_E_PIN = "e_pin"
# Control pins
CONF_LAT_PIN = "lat_pin"
NEVER = 4294967295 # uint32_t max - value used when update_interval is "never"
# Pin mapping from config keys to board keys
PIN_MAPPING = {
CONF_R1_PIN: "r1",
CONF_G1_PIN: "g1",
CONF_B1_PIN: "b1",
CONF_R2_PIN: "r2",
CONF_G2_PIN: "g2",
CONF_B2_PIN: "b2",
CONF_A_PIN: "a",
CONF_B_PIN: "b",
CONF_C_PIN: "c",
CONF_D_PIN: "d",
CONF_E_PIN: "e",
CONF_LAT_PIN: "lat",
CONF_OE_PIN: "oe",
CONF_CLK_PIN: "clk",
}
# Required pins (E pin is optional)
REQUIRED_PINS = [key for key in PIN_MAPPING if key != CONF_E_PIN]
# Configuration
CONF_CLOCK_SPEED = "clock_speed"
CONF_LATCH_BLANKING = "latch_blanking"
CONF_CLOCK_PHASE = "clock_phase"
CONF_DOUBLE_BUFFER = "double_buffer"
CONF_MIN_REFRESH_RATE = "min_refresh_rate"
# Map to hub75 library enums (in global namespace)
ShiftDriver = cg.global_ns.enum("ShiftDriver", is_class=True)
SHIFT_DRIVERS = {
"GENERIC": ShiftDriver.GENERIC,
"FM6126A": ShiftDriver.FM6126A,
"ICN2038S": ShiftDriver.ICN2038S,
"FM6124": ShiftDriver.FM6124,
"MBI5124": ShiftDriver.MBI5124,
"DP3246": ShiftDriver.DP3246,
}
PanelLayout = cg.global_ns.enum("PanelLayout", is_class=True)
PANEL_LAYOUTS = {
"HORIZONTAL": PanelLayout.HORIZONTAL,
"TOP_LEFT_DOWN": PanelLayout.TOP_LEFT_DOWN,
"TOP_RIGHT_DOWN": PanelLayout.TOP_RIGHT_DOWN,
"BOTTOM_LEFT_UP": PanelLayout.BOTTOM_LEFT_UP,
"BOTTOM_RIGHT_UP": PanelLayout.BOTTOM_RIGHT_UP,
"TOP_LEFT_DOWN_ZIGZAG": PanelLayout.TOP_LEFT_DOWN_ZIGZAG,
"TOP_RIGHT_DOWN_ZIGZAG": PanelLayout.TOP_RIGHT_DOWN_ZIGZAG,
"BOTTOM_LEFT_UP_ZIGZAG": PanelLayout.BOTTOM_LEFT_UP_ZIGZAG,
"BOTTOM_RIGHT_UP_ZIGZAG": PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG,
}
ScanPattern = cg.global_ns.enum("ScanPattern", is_class=True)
SCAN_PATTERNS = {
"STANDARD_TWO_SCAN": ScanPattern.STANDARD_TWO_SCAN,
"FOUR_SCAN_16PX_HIGH": ScanPattern.FOUR_SCAN_16PX_HIGH,
"FOUR_SCAN_32PX_HIGH": ScanPattern.FOUR_SCAN_32PX_HIGH,
"FOUR_SCAN_64PX_HIGH": ScanPattern.FOUR_SCAN_64PX_HIGH,
}
Hub75ClockSpeed = cg.global_ns.enum("Hub75ClockSpeed", is_class=True)
CLOCK_SPEEDS = {
"8MHZ": Hub75ClockSpeed.HZ_8M,
"10MHZ": Hub75ClockSpeed.HZ_10M,
"16MHZ": Hub75ClockSpeed.HZ_16M,
"20MHZ": Hub75ClockSpeed.HZ_20M,
}
HUB75Display = hub75_ns.class_("HUB75Display", cg.PollingComponent, display.Display)
Hub75Config = cg.global_ns.struct("Hub75Config")
Hub75Pins = cg.global_ns.struct("Hub75Pins")
def _merge_board_pins(config: ConfigType) -> ConfigType:
"""Merge board preset pins with explicit pin overrides."""
board_name = config.get(CONF_BOARD)
if board_name is None:
# No board specified - validate that all required pins are present
errs = [
cv.Invalid(
f"Required pin '{pin_name}' is missing. "
f"Either specify a board preset or provide all pin mappings manually.",
path=[pin_name],
)
for pin_name in REQUIRED_PINS
if pin_name not in config
]
if errs:
raise cv.MultipleInvalid(errs)
# E_PIN is optional
return config
# Get board configuration
if board_name not in BOARDS:
raise cv.Invalid(
f"Unknown board '{board_name}'. Available boards: {', '.join(sorted(BOARDS.keys()))}"
)
board = BOARDS[board_name]
# Merge board pins with explicit overrides
# Explicit pins in config take precedence over board defaults
for conf_key, board_key in PIN_MAPPING.items():
if conf_key in config or (board_pin := board.get_pin(board_key)) is None:
continue
# Create pin config
pin_config = {"number": board_pin}
if conf_key in board.ignore_strapping_pins:
pin_config["ignore_strapping_warning"] = True
# Validate through pin schema to add required fields (id, etc.)
config[conf_key] = pins.gpio_output_pin_schema(pin_config)
return config
def _validate_config(config: ConfigType) -> ConfigType:
"""Validate driver and layout requirements."""
errs: list[cv.Invalid] = []
# MBI5124 requires inverted clock phase
driver = config.get(CONF_SHIFT_DRIVER, "GENERIC")
if driver == "MBI5124" and not config.get(CONF_CLOCK_PHASE, False):
errs.append(
cv.Invalid(
"MBI5124 shift driver requires 'clock_phase: true' to be set",
path=[CONF_CLOCK_PHASE],
)
)
# Prevent conflicting min_refresh_rate + update_interval configuration
# min_refresh_rate is auto-calculated from update_interval unless using LVGL mode
update_interval = config.get(CONF_UPDATE_INTERVAL)
if CONF_MIN_REFRESH_RATE in config and update_interval is not None:
# Handle both integer (NEVER) and time object cases
interval_ms = (
update_interval
if isinstance(update_interval, int)
else update_interval.total_milliseconds
)
if interval_ms != NEVER:
errs.append(
cv.Invalid(
"Cannot set both 'min_refresh_rate' and 'update_interval' (except 'never'). "
"Refresh rate is auto-calculated from update_interval. "
"Remove 'min_refresh_rate' or use 'update_interval: never' for LVGL mode.",
path=[CONF_MIN_REFRESH_RATE],
)
)
# Validate layout configuration (validate effective config including C++ defaults)
layout = config.get(CONF_LAYOUT, "HORIZONTAL")
layout_rows = config.get(CONF_LAYOUT_ROWS, 1)
layout_cols = config.get(CONF_LAYOUT_COLS, 1)
is_zigzag = "ZIGZAG" in layout
# Single panel (1x1) should use HORIZONTAL
if layout_rows == 1 and layout_cols == 1 and layout != "HORIZONTAL":
errs.append(
cv.Invalid(
f"Single panel (layout_rows=1, layout_cols=1) should use 'layout: HORIZONTAL' (got {layout})",
path=[CONF_LAYOUT],
)
)
# HORIZONTAL layout requires single row
if layout == "HORIZONTAL" and layout_rows != 1:
errs.append(
cv.Invalid(
f"HORIZONTAL layout requires 'layout_rows: 1' (got {layout_rows}). "
"For multi-row grids, use TOP_LEFT_DOWN or other grid layouts.",
path=[CONF_LAYOUT_ROWS],
)
)
# Grid layouts (non-HORIZONTAL) require more than one panel
if layout != "HORIZONTAL" and layout_rows == 1 and layout_cols == 1:
errs.append(
cv.Invalid(
f"Grid layout '{layout}' requires multiple panels (layout_rows > 1 or layout_cols > 1)",
path=[CONF_LAYOUT],
)
)
# Serpentine layouts (non-ZIGZAG) require multiple rows
# Serpentine physically rotates alternate rows upside down (Y-coordinate inversion)
# Single-row chains should use HORIZONTAL or ZIGZAG variants
if not is_zigzag and layout != "HORIZONTAL" and layout_rows == 1:
errs.append(
cv.Invalid(
f"Serpentine layout '{layout}' requires layout_rows > 1 "
f"(got layout_rows={layout_rows}). "
"Serpentine wiring physically rotates alternate rows upside down. "
"For single-row chains, use 'layout: HORIZONTAL' or add '_ZIGZAG' suffix.",
path=[CONF_LAYOUT_ROWS],
)
)
# ZIGZAG layouts require actual grid (both rows AND cols > 1)
if is_zigzag and (layout_rows == 1 or layout_cols == 1):
errs.append(
cv.Invalid(
f"ZIGZAG layout '{layout}' requires both layout_rows > 1 AND layout_cols > 1 "
f"(got rows={layout_rows}, cols={layout_cols}). "
"For single row/column chains, use non-zigzag layouts or HORIZONTAL.",
path=[CONF_LAYOUT],
)
)
if errs:
raise cv.MultipleInvalid(errs)
return config
def _final_validate(config: ConfigType) -> ConfigType:
"""Validate requirements when using HUB75 display."""
# Local imports to avoid circular dependencies
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import VARIANT_ESP32P4
from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
full_config = fv.full_config.get()
errs: list[cv.Invalid] = []
# ESP32-P4 requires PSRAM
variant = get_esp32_variant()
if variant == VARIANT_ESP32P4 and PSRAM_DOMAIN not in full_config:
errs.append(
cv.Invalid(
"HUB75 display on ESP32-P4 requires PSRAM. Add 'psram:' to your configuration.",
path=[CONF_ID],
)
)
# LVGL-specific validation
if LVGL_DOMAIN in full_config:
# Check update_interval (converted from "never" to NEVER constant)
update_interval = config.get(CONF_UPDATE_INTERVAL)
if update_interval is not None:
# Handle both integer (NEVER) and time object cases
interval_ms = (
update_interval
if isinstance(update_interval, int)
else update_interval.total_milliseconds
)
if interval_ms != NEVER:
errs.append(
cv.Invalid(
"HUB75 display with LVGL must have 'update_interval: never'. "
"LVGL manages its own refresh timing.",
path=[CONF_UPDATE_INTERVAL],
)
)
# Check auto_clear_enabled
auto_clear = config[CONF_AUTO_CLEAR_ENABLED]
if auto_clear is not False:
errs.append(
cv.Invalid(
f"HUB75 display with LVGL must have 'auto_clear_enabled: false' (got '{auto_clear}'). "
"LVGL manages screen clearing.",
path=[CONF_AUTO_CLEAR_ENABLED],
)
)
# Check double_buffer (C++ default: false)
double_buffer = config.get(CONF_DOUBLE_BUFFER, False)
if double_buffer is not False:
errs.append(
cv.Invalid(
f"HUB75 display with LVGL must have 'double_buffer: false' (got '{double_buffer}'). "
"LVGL uses its own buffering strategy.",
path=[CONF_DOUBLE_BUFFER],
)
)
if errs:
raise cv.MultipleInvalid(errs)
return config
FINAL_VALIDATE_SCHEMA = cv.Schema(_final_validate)
CONFIG_SCHEMA = cv.All(
display.FULL_DISPLAY_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(HUB75Display),
# Board preset (optional - provides default pin mappings)
cv.Optional(CONF_BOARD): cv.one_of(*BOARDS.keys(), lower=True),
# Panel dimensions
cv.Required(CONF_PANEL_WIDTH): cv.positive_int,
cv.Required(CONF_PANEL_HEIGHT): cv.positive_int,
# Multi-panel layout
cv.Optional(CONF_LAYOUT_ROWS): cv.positive_int,
cv.Optional(CONF_LAYOUT_COLS): cv.positive_int,
cv.Optional(CONF_LAYOUT): cv.enum(PANEL_LAYOUTS, upper=True, space="_"),
# Panel hardware configuration
cv.Optional(CONF_SCAN_WIRING): cv.enum(
SCAN_PATTERNS, upper=True, space="_"
),
cv.Optional(CONF_SHIFT_DRIVER): cv.enum(SHIFT_DRIVERS, upper=True),
# Display configuration
cv.Optional(CONF_DOUBLE_BUFFER): cv.boolean,
cv.Optional(CONF_BRIGHTNESS): cv.int_range(min=0, max=255),
cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=6, max=12),
cv.Optional(CONF_GAMMA_CORRECT): cv.enum(
{"LINEAR": 0, "CIE1931": 1, "GAMMA_2_2": 2}, upper=True
),
cv.Optional(CONF_MIN_REFRESH_RATE): cv.int_range(min=40, max=200),
# RGB data pins
cv.Optional(CONF_R1_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_G1_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_B1_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_R2_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_G2_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_B2_PIN): pins.gpio_output_pin_schema,
# Address pins
cv.Optional(CONF_A_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_B_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_C_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_D_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_E_PIN): pins.gpio_output_pin_schema,
# Control pins
cv.Optional(CONF_LAT_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_OE_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_CLK_PIN): pins.gpio_output_pin_schema,
# Timing configuration
cv.Optional(CONF_CLOCK_SPEED): cv.enum(CLOCK_SPEEDS, upper=True),
cv.Optional(CONF_LATCH_BLANKING): cv.positive_int,
cv.Optional(CONF_CLOCK_PHASE): cv.boolean,
}
),
_merge_board_pins,
_validate_config,
)
DEFAULT_REFRESH_RATE = 60 # Hz
def _calculate_min_refresh_rate(config: ConfigType) -> int:
"""Calculate minimum refresh rate for the display.
Priority:
1. Explicit min_refresh_rate setting (user override)
2. Derived from update_interval (ms to Hz conversion)
3. Default 60 Hz (for LVGL or unspecified interval)
"""
if CONF_MIN_REFRESH_RATE in config:
return config[CONF_MIN_REFRESH_RATE]
update_interval = config.get(CONF_UPDATE_INTERVAL)
if update_interval is None:
return DEFAULT_REFRESH_RATE
# update_interval can be TimePeriod object or NEVER constant (int)
interval_ms = (
update_interval
if isinstance(update_interval, int)
else update_interval.total_milliseconds
)
# "never" or zero means external refresh (e.g., LVGL)
if interval_ms in (NEVER, 0):
return DEFAULT_REFRESH_RATE
# Convert ms interval to Hz, clamped to valid range [40, 200]
return max(40, min(200, int(round(1000 / interval_ms))))
def _build_pins_struct(
pin_expressions: dict[str, Any], e_pin_num: int | cg.RawExpression
) -> cg.StructInitializer:
"""Build Hub75Pins struct from pin expressions."""
def pin_cast(pin):
return cg.RawExpression(f"static_cast<int8_t>({pin.get_pin()})")
return cg.StructInitializer(
Hub75Pins,
("r1", pin_cast(pin_expressions["r1"])),
("g1", pin_cast(pin_expressions["g1"])),
("b1", pin_cast(pin_expressions["b1"])),
("r2", pin_cast(pin_expressions["r2"])),
("g2", pin_cast(pin_expressions["g2"])),
("b2", pin_cast(pin_expressions["b2"])),
("a", pin_cast(pin_expressions["a"])),
("b", pin_cast(pin_expressions["b"])),
("c", pin_cast(pin_expressions["c"])),
("d", pin_cast(pin_expressions["d"])),
("e", e_pin_num),
("lat", pin_cast(pin_expressions["lat"])),
("oe", pin_cast(pin_expressions["oe"])),
("clk", pin_cast(pin_expressions["clk"])),
)
def _append_config_fields(
config: ConfigType,
field_mapping: list[tuple[str, str]],
config_fields: list[tuple[str, Any]],
) -> None:
"""Append config fields from mapping if present in config."""
for conf_key, struct_field in field_mapping:
if conf_key in config:
config_fields.append((struct_field, config[conf_key]))
def _build_config_struct(
config: ConfigType, pins_struct: cg.StructInitializer, min_refresh: int
) -> cg.StructInitializer:
"""Build Hub75Config struct from config.
Fields must be added in declaration order (see hub75_types.h) to satisfy
C++ designated initializer requirements. The order is:
1. fields_before_pins (panel_width through layout)
2. pins
3. output_clock_speed
4. min_refresh_rate
5. fields_after_min_refresh (latch_blanking through brightness)
"""
fields_before_pins = [
(CONF_PANEL_WIDTH, "panel_width"),
(CONF_PANEL_HEIGHT, "panel_height"),
# scan_pattern - auto-calculated, not set
(CONF_SCAN_WIRING, "scan_wiring"),
(CONF_SHIFT_DRIVER, "shift_driver"),
(CONF_LAYOUT_ROWS, "layout_rows"),
(CONF_LAYOUT_COLS, "layout_cols"),
(CONF_LAYOUT, "layout"),
]
fields_after_min_refresh = [
(CONF_LATCH_BLANKING, "latch_blanking"),
(CONF_DOUBLE_BUFFER, "double_buffer"),
(CONF_CLOCK_PHASE, "clk_phase_inverted"),
(CONF_BRIGHTNESS, "brightness"),
]
config_fields: list[tuple[str, Any]] = []
_append_config_fields(config, fields_before_pins, config_fields)
config_fields.append(("pins", pins_struct))
if CONF_CLOCK_SPEED in config:
config_fields.append(("output_clock_speed", config[CONF_CLOCK_SPEED]))
config_fields.append(("min_refresh_rate", min_refresh))
_append_config_fields(config, fields_after_min_refresh, config_fields)
return cg.StructInitializer(Hub75Config, *config_fields)
async def to_code(config: ConfigType) -> None:
add_idf_component(
name="esphome/esp-hub75",
ref="0.1.6",
)
# Set compile-time configuration via defines
if CONF_BIT_DEPTH in config:
cg.add_define("HUB75_BIT_DEPTH", config[CONF_BIT_DEPTH])
if CONF_GAMMA_CORRECT in config:
cg.add_define("HUB75_GAMMA_MODE", config[CONF_GAMMA_CORRECT])
# Await all pin expressions
pin_expressions = {
"r1": await cg.gpio_pin_expression(config[CONF_R1_PIN]),
"g1": await cg.gpio_pin_expression(config[CONF_G1_PIN]),
"b1": await cg.gpio_pin_expression(config[CONF_B1_PIN]),
"r2": await cg.gpio_pin_expression(config[CONF_R2_PIN]),
"g2": await cg.gpio_pin_expression(config[CONF_G2_PIN]),
"b2": await cg.gpio_pin_expression(config[CONF_B2_PIN]),
"a": await cg.gpio_pin_expression(config[CONF_A_PIN]),
"b": await cg.gpio_pin_expression(config[CONF_B_PIN]),
"c": await cg.gpio_pin_expression(config[CONF_C_PIN]),
"d": await cg.gpio_pin_expression(config[CONF_D_PIN]),
"lat": await cg.gpio_pin_expression(config[CONF_LAT_PIN]),
"oe": await cg.gpio_pin_expression(config[CONF_OE_PIN]),
"clk": await cg.gpio_pin_expression(config[CONF_CLK_PIN]),
}
# E pin is optional
if CONF_E_PIN in config:
e_pin = await cg.gpio_pin_expression(config[CONF_E_PIN])
e_pin_num = cg.RawExpression(f"static_cast<int8_t>({e_pin.get_pin()})")
else:
e_pin_num = -1
# Build structs
min_refresh = _calculate_min_refresh_rate(config)
pins_struct = _build_pins_struct(pin_expressions, e_pin_num)
hub75_config = _build_config_struct(config, pins_struct, min_refresh)
# Create display and register
var = cg.new_Pvariable(config[CONF_ID], hub75_config)
await display.register_display(var, config)
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))

View File

@@ -0,0 +1,192 @@
#include "hub75_component.h"
#include "esphome/core/application.h"
#ifdef USE_ESP32
namespace esphome::hub75 {
static const char *const TAG = "hub75";
// ========================================
// Constructor
// ========================================
HUB75Display::HUB75Display(const Hub75Config &config) : config_(config) {
// Initialize runtime state from config
this->brightness_ = config.brightness;
this->enabled_ = (config.brightness > 0);
}
// ========================================
// Core Component methods
// ========================================
void HUB75Display::setup() {
ESP_LOGCONFIG(TAG, "Setting up HUB75Display...");
// Create driver with pre-configured config
driver_ = new Hub75Driver(config_);
if (!driver_->begin()) {
ESP_LOGE(TAG, "Failed to initialize HUB75 driver!");
return;
}
this->enabled_ = true;
}
void HUB75Display::dump_config() {
LOG_DISPLAY("", "HUB75", this);
ESP_LOGCONFIG(TAG,
" Panel: %dx%d pixels\n"
" Layout: %dx%d panels\n"
" Virtual Display: %dx%d pixels",
config_.panel_width, config_.panel_height, config_.layout_cols, config_.layout_rows,
config_.panel_width * config_.layout_cols, config_.panel_height * config_.layout_rows);
ESP_LOGCONFIG(TAG,
" Scan Wiring: %d\n"
" Shift Driver: %d",
static_cast<int>(config_.scan_wiring), static_cast<int>(config_.shift_driver));
ESP_LOGCONFIG(TAG,
" Pins: R1:%i, G1:%i, B1:%i, R2:%i, G2:%i, B2:%i\n"
" Pins: A:%i, B:%i, C:%i, D:%i, E:%i\n"
" Pins: LAT:%i, OE:%i, CLK:%i",
config_.pins.r1, config_.pins.g1, config_.pins.b1, config_.pins.r2, config_.pins.g2, config_.pins.b2,
config_.pins.a, config_.pins.b, config_.pins.c, config_.pins.d, config_.pins.e, config_.pins.lat,
config_.pins.oe, config_.pins.clk);
ESP_LOGCONFIG(TAG,
" Clock Speed: %u MHz\n"
" Latch Blanking: %i\n"
" Clock Phase: %s\n"
" Min Refresh Rate: %i Hz\n"
" Bit Depth: %i\n"
" Double Buffer: %s",
static_cast<uint32_t>(config_.output_clock_speed) / 1000000, config_.latch_blanking,
TRUEFALSE(config_.clk_phase_inverted), config_.min_refresh_rate, HUB75_BIT_DEPTH,
YESNO(config_.double_buffer));
}
// ========================================
// Display/PollingComponent methods
// ========================================
void HUB75Display::update() {
if (!driver_) [[unlikely]]
return;
if (!this->enabled_) [[unlikely]]
return;
this->do_update_();
if (config_.double_buffer) {
driver_->flip_buffer();
}
}
void HUB75Display::fill(Color color) {
if (!driver_) [[unlikely]]
return;
if (!this->enabled_) [[unlikely]]
return;
// Special case: black (off) - use fast hardware clear
if (!color.is_on()) {
driver_->clear();
return;
}
// For non-black colors, fall back to base class (pixel-by-pixel)
Display::fill(color);
}
void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) {
if (!driver_) [[unlikely]]
return;
if (!this->enabled_) [[unlikely]]
return;
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) [[unlikely]]
return;
driver_->set_pixel(x, y, color.r, color.g, color.b);
App.feed_wdt();
}
void HOT HUB75Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
if (!driver_) [[unlikely]]
return;
if (!this->enabled_) [[unlikely]]
return;
// Map ESPHome enums to hub75 enums
Hub75PixelFormat format;
Hub75ColorOrder color_order = Hub75ColorOrder::RGB;
int bytes_per_pixel;
// Determine format based on bitness
if (bitness == ColorBitness::COLOR_BITNESS_565) {
format = Hub75PixelFormat::RGB565;
bytes_per_pixel = 2;
} else if (bitness == ColorBitness::COLOR_BITNESS_888) {
#ifdef USE_LVGL
#if LV_COLOR_DEPTH == 32
// 32-bit: 4 bytes per pixel with padding byte (LVGL mode)
format = Hub75PixelFormat::RGB888_32;
bytes_per_pixel = 4;
// Map ESPHome ColorOrder to Hub75ColorOrder
// ESPHome ColorOrder is typically BGR for little-endian 32-bit
color_order = (order == ColorOrder::COLOR_ORDER_RGB) ? Hub75ColorOrder::RGB : Hub75ColorOrder::BGR;
#elif LV_COLOR_DEPTH == 24
// 24-bit: 3 bytes per pixel, tightly packed
format = Hub75PixelFormat::RGB888;
bytes_per_pixel = 3;
// Note: 24-bit is always RGB order in LVGL
#else
ESP_LOGE(TAG, "Unsupported LV_COLOR_DEPTH: %d", LV_COLOR_DEPTH);
return;
#endif
#else
// Non-LVGL mode: standard 24-bit RGB888
format = Hub75PixelFormat::RGB888;
bytes_per_pixel = 3;
color_order = (order == ColorOrder::COLOR_ORDER_RGB) ? Hub75ColorOrder::RGB : Hub75ColorOrder::BGR;
#endif
} else {
ESP_LOGE(TAG, "Unsupported bitness: %d", static_cast<int>(bitness));
return;
}
// Check if buffer is tightly packed (no stride)
const int stride_px = x_offset + w + x_pad;
const bool is_packed = (x_offset == 0 && x_pad == 0 && y_offset == 0);
if (is_packed) {
// Tightly packed buffer - single bulk call for best performance
driver_->draw_pixels(x_start, y_start, w, h, ptr, format, color_order, big_endian);
} else {
// Buffer has stride (padding between rows) - draw row by row
for (int yy = 0; yy < h; ++yy) {
const size_t row_offset = ((y_offset + yy) * stride_px + x_offset) * bytes_per_pixel;
const uint8_t *row_ptr = ptr + row_offset;
driver_->draw_pixels(x_start, y_start + yy, w, 1, row_ptr, format, color_order, big_endian);
}
}
}
void HUB75Display::set_brightness(int brightness) {
this->brightness_ = brightness;
this->enabled_ = (brightness > 0);
if (this->driver_ != nullptr) {
this->driver_->set_brightness(brightness);
}
}
} // namespace esphome::hub75
#endif

View File

@@ -0,0 +1,55 @@
#pragma once
#ifdef USE_ESP32
#include <utility>
#include "esphome/components/display/display_buffer.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "hub75.h" // hub75 library
namespace esphome::hub75 {
using esphome::display::ColorBitness;
using esphome::display::ColorOrder;
class HUB75Display : public display::Display {
public:
// Constructor accepting config
explicit HUB75Display(const Hub75Config &config);
// Core Component methods
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
// Display/PollingComponent methods
void update() override;
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
void fill(Color color) override;
void draw_pixel_at(int x, int y, Color color) override;
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
// Brightness control (runtime mutable)
void set_brightness(int brightness);
protected:
// Display internal methods
int get_width_internal() override { return config_.panel_width * config_.layout_cols; }
int get_height_internal() override { return config_.panel_height * config_.layout_rows; }
// Member variables
Hub75Driver *driver_{nullptr};
Hub75Config config_; // Immutable configuration
// Runtime state (mutable)
int brightness_{128};
bool enabled_{false};
};
} // namespace esphome::hub75
#endif

View File

@@ -10,6 +10,7 @@ from esphome.components.esp32.const import (
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
@@ -75,6 +76,7 @@ I2S_PORTS = {
VARIANT_ESP32C3: 1,
VARIANT_ESP32C5: 1,
VARIANT_ESP32C6: 1,
VARIANT_ESP32C61: 1,
VARIANT_ESP32H2: 1,
VARIANT_ESP32P4: 3,
VARIANT_ESP32S2: 1,

View File

@@ -40,7 +40,7 @@ INTERNAL_DAC_OPTIONS = {
EXTERNAL_DAC_OPTIONS = [CONF_MONO, CONF_STEREO]
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
NO_INTERNAL_DAC_VARIANTS = [esp32.VARIANT_ESP32S2]
I2C_COMM_FMT_OPTIONS = ["lsb", "msb"]

View File

@@ -37,8 +37,8 @@ I2SAudioMicrophone = i2s_audio_ns.class_(
"I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
)
INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32]
PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3]
INTERNAL_ADC_VARIANTS = [esp32.VARIANT_ESP32]
PDM_VARIANTS = [esp32.VARIANT_ESP32, esp32.VARIANT_ESP32S3]
def _validate_esp32_variant(config):

View File

@@ -62,7 +62,7 @@ I2C_COMM_FMT_OPTIONS = {
"pcm_long": i2s_comm_format_t.I2S_COMM_FORMAT_PCM_LONG,
}
INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32]
INTERNAL_DAC_VARIANTS = [esp32.VARIANT_ESP32]
def _set_num_channels_from_config(config):

View File

@@ -1,7 +1,6 @@
import esphome.codegen as cg
from esphome.components import improv_base
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import VARIANT_ESP32S3
from esphome.components.esp32 import VARIANT_ESP32S3, get_esp32_variant
from esphome.components.logger import USB_CDC
import esphome.config_validation as cv
from esphome.const import CONF_BAUD_RATE, CONF_HARDWARE_UART, CONF_ID, CONF_LOGGER

View File

@@ -70,9 +70,10 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
case logger::UART_SELECTION_UART0:
case logger::UART_SELECTION_UART1:
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
!defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
case logger::UART_SELECTION_UART2:
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32C61 &&
// !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
if (this->uart_num_ >= 0) {
size_t available;
uart_get_buffered_data_len(this->uart_num_, &available);
@@ -137,7 +138,7 @@ void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size)
case logger::UART_SELECTION_UART0:
case logger::UART_SELECTION_UART1:
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
!defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
case logger::UART_SELECTION_UART2:
#endif
uart_write_bytes(this->uart_num_, this->tx_header_, header_tx_len);

View File

@@ -11,8 +11,8 @@
#ifdef USE_ESP32
#include <driver/uart.h>
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || \
defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32S3)
#include <driver/usb_serial_jtag.h>
#include <hal/usb_serial_jtag_ll.h>
#endif

View File

@@ -8,8 +8,8 @@ extern "C" {
uint8_t temprature_sens_read();
}
#elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#include "driver/temperature_sensor.h"
#endif // USE_ESP32_VARIANT
#endif // USE_ESP32
@@ -28,8 +28,8 @@ namespace internal_temperature {
static const char *const TAG = "internal_temperature";
#ifdef USE_ESP32
#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3)
defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
static temperature_sensor_handle_t tsensNew = NULL;
#endif // USE_ESP32_VARIANT
#endif // USE_ESP32
@@ -44,8 +44,8 @@ void InternalTemperatureSensor::update() {
temperature = (raw - 32) / 1.8f;
success = (raw != 128);
#elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature);
success = (result == ESP_OK);
if (!success) {
@@ -82,8 +82,8 @@ void InternalTemperatureSensor::update() {
void InternalTemperatureSensor::setup() {
#ifdef USE_ESP32
#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3)
defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
esp_err_t result = temperature_sensor_install(&tsens_config, &tsensNew);

View File

@@ -70,7 +70,7 @@ class AddressableLight : public LightOutput, public Component {
this->state_parent_ = state;
}
void update_state(LightState *state) override;
void schedule_show() { this->state_parent_->next_write_ = true; }
void schedule_show() { this->state_parent_->schedule_write_(); }
#ifdef USE_POWER_SUPPLY
void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_.set_parent(power_supply); }

View File

@@ -305,8 +305,7 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot
this->remote_values = target;
}
this->output_->update_state(this);
this->next_write_ = true;
this->enable_loop();
this->schedule_write_();
}
void LightState::disable_loop_if_idle_() {

View File

@@ -277,6 +277,12 @@ class LightState : public EntityBase, public Component {
/// Disable loop if neither transformer nor effect is active
void disable_loop_if_idle_();
/// Schedule a write to the light output and enable the loop to process it
void schedule_write_() {
this->next_write_ = true;
this->enable_loop();
}
/// Store the output to allow effects to have more access.
LightOutput *output_;
/// The currently active transformer for this light (transition/flash).

View File

@@ -3,17 +3,19 @@ import re
from esphome import automation
from esphome.automation import LambdaAction, StatelessLambdaAction
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
from esphome.components.esp32.const import (
from esphome.components.esp32 import (
VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
add_idf_sdkconfig_option,
get_esp32_variant,
)
from esphome.components.libretiny import get_libretiny_component, get_libretiny_family
from esphome.components.libretiny.const import (
@@ -104,6 +106,7 @@ UART_SELECTION_ESP32 = {
VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32C5: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32C61: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32P4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32S2: [UART0, UART1, USB_CDC],

View File

@@ -12,7 +12,7 @@ from esphome.components.const import (
CONF_DRAW_ROUNDING,
)
from esphome.components.display import CONF_SHOW_TEST_CARD
from esphome.components.esp32 import const, only_on_variant
from esphome.components.esp32 import VARIANT_ESP32P4, only_on_variant
from esphome.components.mipi import (
COLOR_ORDERS,
CONF_COLOR_DEPTH,
@@ -165,7 +165,7 @@ def model_schema(config):
)
return cv.All(
schema,
only_on_variant(supported=[const.VARIANT_ESP32P4]),
only_on_variant(supported=[VARIANT_ESP32P4]),
cv.only_with_esp_idf,
)

View File

@@ -11,7 +11,7 @@ from esphome.components.const import (
CONF_DRAW_ROUNDING,
)
from esphome.components.display import CONF_SHOW_TEST_CARD
from esphome.components.esp32 import const, only_on_variant
from esphome.components.esp32 import VARIANT_ESP32S3, only_on_variant
from esphome.components.mipi import (
COLOR_ORDERS,
CONF_DE_PIN,
@@ -224,7 +224,7 @@ def _config_schema(config):
schema = model_schema(config)
return cv.All(
schema,
only_on_variant(supported=[const.VARIANT_ESP32S3]),
only_on_variant(supported=[VARIANT_ESP32S3]),
cv.only_with_esp_idf,
)(config)

View File

@@ -148,6 +148,19 @@ ILI9341 = DriverChip(
),
),
)
# M5Stack Core2 uses ILI9341 chip - mirror_x disabled for correct orientation
ILI9341.extend(
"M5CORE2",
width=320,
height=240,
mirror_x=False,
cs_pin=5,
dc_pin=15,
invert_colors=True,
pixel_mode="18bit",
data_rate="40MHz",
)
DriverChip(
"ILI9481",
mirror_x=True,

View File

@@ -2,12 +2,12 @@ from dataclasses import dataclass
from typing import Any
import esphome.codegen as cg
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import (
from esphome.components.esp32 import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
get_esp32_variant,
)
import esphome.config_validation as cv
from esphome.const import (

View File

@@ -1,8 +1,7 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import light
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import VARIANT_ESP32C3, VARIANT_ESP32S3
from esphome.components.esp32 import VARIANT_ESP32C3, VARIANT_ESP32S3, get_esp32_variant
import esphome.config_validation as cv
from esphome.const import (
CONF_CHANNEL,

View File

@@ -7,14 +7,12 @@ from esphome.components.esp32 import (
CONF_CPU_FREQUENCY,
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES,
VARIANT_ESP32,
add_idf_sdkconfig_option,
get_esp32_variant,
)
from esphome.components.esp32.const import (
VARIANT_ESP32C5,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
add_idf_sdkconfig_option,
get_esp32_variant,
)
import esphome.config_validation as cv
from esphome.const import (

View File

@@ -65,7 +65,7 @@ RemoteReceiverComponent = remote_receiver_ns.class_(
def validate_config(config):
if CORE.is_esp32:
variant = esp32.get_esp32_variant()
if variant in (esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S2):
if variant in (esp32.VARIANT_ESP32, esp32.VARIANT_ESP32S2):
max_idle = 65535
else:
max_idle = 32767
@@ -148,7 +148,7 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
): cv.All(cv.only_on_esp32, cv.int_range(min=2)),
cv.Optional(CONF_USE_DMA): cv.All(
esp32.only_on_variant(
supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3]
supported=[esp32.VARIANT_ESP32P4, esp32.VARIANT_ESP32S3]
),
cv.boolean,
),

View File

@@ -90,12 +90,14 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
std::string error_string_{""};
#endif
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) || defined(USE_ESP32)
RemoteReceiverComponentStore store_;
#endif
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040)
HighFrequencyLoopRequester high_freq_;
#endif
RemoteReceiverComponentStore store_;
uint32_t buffer_size_{};
uint32_t filter_us_{10};
uint32_t idle_us_{10000};

View File

@@ -55,7 +55,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_EOT_LEVEL): cv.All(cv.only_on_esp32, cv.boolean),
cv.Optional(CONF_USE_DMA): cv.All(
esp32.only_on_variant(
supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3]
supported=[esp32.VARIANT_ESP32P4, esp32.VARIANT_ESP32S3]
),
cv.boolean,
),

View File

@@ -1,7 +1,7 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import display
from esphome.components.esp32 import const, only_on_variant
from esphome.components.esp32 import VARIANT_ESP32S3, only_on_variant
from esphome.components.mipi import (
CONF_DE_PIN,
CONF_HSYNC_BACK_PORCH,
@@ -121,7 +121,7 @@ CONFIG_SCHEMA = cv.All(
}
)
),
only_on_variant(supported=[const.VARIANT_ESP32S3]),
only_on_variant(supported=[VARIANT_ESP32S3]),
cv.only_with_esp_idf,
)

View File

@@ -3,16 +3,18 @@ from typing import Any
from esphome import pins
import esphome.codegen as cg
from esphome.components.esp32 import only_on_variant
from esphome.components.esp32.const import (
from esphome.components.esp32 import (
KEY_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
only_on_variant,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
@@ -128,7 +130,9 @@ def get_hw_interface_list():
if get_target_variant() in [
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
]:
return [["spi", "spi2"]]

View File

@@ -1,20 +1,25 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include "sps30.h"
namespace esphome {
namespace sps30 {
template<typename... Ts> class StartFanAction : public Action<Ts...> {
template<typename... Ts> class StartFanAction : public Action<Ts...>, public Parented<SPS30Component> {
public:
explicit StartFanAction(SPS30Component *sps30) : sps30_(sps30) {}
void play(const Ts &...x) override { this->parent_->start_fan_cleaning(); }
};
void play(const Ts &...x) override { this->sps30_->start_fan_cleaning(); }
template<typename... Ts> class StartMeasurementAction : public Action<Ts...>, public Parented<SPS30Component> {
public:
void play(const Ts &...x) override { this->parent_->start_measurement(); }
};
protected:
SPS30Component *sps30_;
template<typename... Ts> class StopMeasurementAction : public Action<Ts...>, public Parented<SPS30Component> {
public:
void play(const Ts &...x) override { this->parent_->stop_measurement(); }
};
} // namespace sps30

View File

@@ -38,8 +38,11 @@ SPS30Component = sps30_ns.class_(
# Actions
StartFanAction = sps30_ns.class_("StartFanAction", automation.Action)
StartMeasurementAction = sps30_ns.class_("StartMeasurementAction", automation.Action)
StopMeasurementAction = sps30_ns.class_("StopMeasurementAction", automation.Action)
CONF_AUTO_CLEANING_INTERVAL = "auto_cleaning_interval"
CONF_IDLE_INTERVAL = "idle_interval"
CONFIG_SCHEMA = (
cv.Schema(
@@ -109,6 +112,7 @@ CONFIG_SCHEMA = (
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.update_interval,
cv.Optional(CONF_IDLE_INTERVAL): cv.update_interval,
}
)
.extend(cv.polling_component_schema("60s"))
@@ -164,6 +168,9 @@ async def to_code(config):
if CONF_AUTO_CLEANING_INTERVAL in config:
cg.add(var.set_auto_cleaning_interval(config[CONF_AUTO_CLEANING_INTERVAL]))
if CONF_IDLE_INTERVAL in config:
cg.add(var.set_idle_interval(config[CONF_IDLE_INTERVAL]))
SPS30_ACTION_SCHEMA = maybe_simple_id(
{
@@ -175,6 +182,13 @@ SPS30_ACTION_SCHEMA = maybe_simple_id(
@automation.register_action(
"sps30.start_fan_autoclean", StartFanAction, SPS30_ACTION_SCHEMA
)
async def sps30_fan_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"sps30.start_measurement", StartMeasurementAction, SPS30_ACTION_SCHEMA
)
@automation.register_action(
"sps30.stop_measurement", StopMeasurementAction, SPS30_ACTION_SCHEMA
)
async def sps30_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var

View File

@@ -20,6 +20,7 @@ static const uint16_t SPS30_CMD_START_FAN_CLEANING = 0x5607;
static const uint16_t SPS30_CMD_SOFT_RESET = 0xD304;
static const size_t SERIAL_NUMBER_LENGTH = 8;
static const uint8_t MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR = 5;
static const uint32_t SPS30_WARM_UP_SEC = 30;
void SPS30Component::setup() {
this->write_command(SPS30_CMD_SOFT_RESET);
@@ -63,6 +64,8 @@ void SPS30Component::setup() {
this->status_clear_warning();
this->skipped_data_read_cycles_ = 0;
this->start_continuous_measurement_();
this->next_state_ms_ = millis() + SPS30_WARM_UP_SEC * 1000;
this->next_state_ = READ;
this->setup_complete_ = true;
});
});
@@ -101,6 +104,9 @@ void SPS30Component::dump_config() {
" Serial number: %s\n"
" Firmware version v%0d.%0d",
this->serial_number_, this->raw_firmware_version_ >> 8, this->raw_firmware_version_ & 0xFF);
if (this->idle_interval_.has_value()) {
ESP_LOGCONFIG(TAG, " Idle interval: %us", this->idle_interval_.value() / 1000);
}
LOG_SENSOR(" ", "PM1.0 Weight Concentration", this->pm_1_0_sensor_);
LOG_SENSOR(" ", "PM2.5 Weight Concentration", this->pm_2_5_sensor_);
LOG_SENSOR(" ", "PM4 Weight Concentration", this->pm_4_0_sensor_);
@@ -132,6 +138,26 @@ void SPS30Component::update() {
}
return;
}
// If its not time to take an action, do nothing.
const uint32_t update_start_ms = millis();
if (this->next_state_ != NONE && (int32_t) (this->next_state_ms_ - update_start_ms) > 0) {
ESP_LOGD(TAG, "Sensor waiting for %ums before transitioning to state %d.", (this->next_state_ms_ - update_start_ms),
this->next_state_);
return;
}
switch (this->next_state_) {
case WAKE:
this->start_measurement();
return;
case NONE:
return;
case READ:
// Read logic continues below
break;
}
/// Check if measurement is ready before reading the value
if (!this->write_command(SPS30_CMD_GET_DATA_READY_STATUS)) {
this->status_set_warning();
@@ -211,6 +237,16 @@ void SPS30Component::update() {
this->status_clear_warning();
this->skipped_data_read_cycles_ = 0;
// Stop measurements and wait if we have an idle interval. If not using idle mode, let the next state just execute
// on next update.
if (this->idle_interval_.has_value()) {
this->stop_measurement();
this->next_state_ms_ = millis() + this->idle_interval_.value();
this->next_state_ = WAKE;
} else {
this->next_state_ms_ = millis();
}
});
}
@@ -219,6 +255,26 @@ bool SPS30Component::start_continuous_measurement_() {
ESP_LOGE(TAG, "Error initiating measurements");
return false;
}
ESP_LOGD(TAG, "Started measurements");
// Notify the state machine to wait the warm up interval before reading
this->next_state_ms_ = millis() + SPS30_WARM_UP_SEC * 1000;
this->next_state_ = READ;
return true;
}
bool SPS30Component::start_measurement() { return start_continuous_measurement_(); }
bool SPS30Component::stop_measurement() {
if (!write_command(SPS30_CMD_STOP_MEASUREMENTS)) {
ESP_LOGE(TAG, "Error stopping measurements");
return false;
} else {
ESP_LOGD(TAG, "Stopped measurements");
// Exit the state machine if measurement is stopped.
this->next_state_ms_ = 0;
this->next_state_ = NONE;
}
return true;
}

View File

@@ -23,17 +23,23 @@ class SPS30Component : public PollingComponent, public sensirion_common::Sensiri
void set_pm_size_sensor(sensor::Sensor *pm_size) { pm_size_sensor_ = pm_size; }
void set_auto_cleaning_interval(uint32_t auto_cleaning_interval) { fan_interval_ = auto_cleaning_interval; }
void set_idle_interval(uint32_t idle_interval) { idle_interval_ = idle_interval; }
void setup() override;
void update() override;
void dump_config() override;
bool start_fan_cleaning();
bool stop_measurement();
bool start_measurement();
protected:
bool setup_complete_{false};
uint16_t raw_firmware_version_;
char serial_number_[17] = {0}; /// Terminating NULL character
uint8_t skipped_data_read_cycles_ = 0;
uint32_t next_state_ms_ = 0;
enum NextState : uint8_t { WAKE, READ, NONE } next_state_{NONE};
bool start_continuous_measurement_();
@@ -58,6 +64,7 @@ class SPS30Component : public PollingComponent, public sensirion_common::Sensiri
sensor::Sensor *pmc_10_0_sensor_{nullptr};
sensor::Sensor *pm_size_sensor_{nullptr};
optional<uint32_t> fan_interval_;
optional<uint32_t> idle_interval_;
};
} // namespace sps30

View File

@@ -1,7 +1,7 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import display, spi
from esphome.components.esp32 import const, only_on_variant
from esphome.components.esp32 import VARIANT_ESP32S3, only_on_variant
from esphome.components.mipi import (
CONF_DE_PIN,
CONF_HSYNC_BACK_PORCH,
@@ -161,7 +161,7 @@ CONFIG_SCHEMA = cv.All(
}
).extend(spi.spi_device_schema(cs_pin_required=False, default_data_rate=1e6))
),
only_on_variant(supported=[const.VARIANT_ESP32S3]),
only_on_variant(supported=[VARIANT_ESP32S3]),
cv.only_with_esp_idf,
)

View File

@@ -1,10 +1,11 @@
import esphome.codegen as cg
from esphome.components import esp32
from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
from esphome.components.esp32.const import (
from esphome.components.esp32 import (
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
add_idf_component,
add_idf_sdkconfig_option,
)
import esphome.config_validation as cv
from esphome.const import CONF_ID

View File

@@ -1744,8 +1744,7 @@ class SplitDefault(Optional):
def default(self):
keys = []
if CORE.is_esp32:
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import VARIANT_ESP32
from esphome.components.esp32 import VARIANT_ESP32, get_esp32_variant
variant = get_esp32_variant().replace(VARIANT_ESP32, "").lower()
framework = CORE.target_framework.replace("esp-", "")

View File

@@ -235,8 +235,8 @@
#if defined(USE_ESP32_VARIANT_ESP32S2)
#define USE_LOGGER_USB_CDC
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \
defined(USE_ESP32_VARIANT_ESP32S3)
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S3)
#define USE_LOGGER_USB_CDC
#define USE_LOGGER_USB_SERIAL_JTAG
#endif

View File

@@ -152,6 +152,7 @@ lib_deps =
esphome/ESP32-audioI2S@2.3.0 ; i2s_audio
droscy/esp_wireguard@0.4.2 ; wireguard
esphome/esp-audio-libs@2.0.1 ; audio
esphome/esp-hub75@0.1.6 ; hub75
build_flags =
${common:arduino.build_flags}
@@ -175,6 +176,7 @@ lib_deps =
droscy/esp_wireguard@0.4.2 ; wireguard
kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word
esphome/esp-audio-libs@2.0.1 ; audio
esphome/esp-hub75@0.1.6 ; hub75
build_flags =
${common:idf.build_flags}
-Wno-nonnull-compare

View File

@@ -17,8 +17,7 @@ def test_esp32_config(
) -> None:
set_core_config(PlatformFramework.ESP32_IDF)
from esphome.components.esp32 import CONFIG_SCHEMA
from esphome.components.esp32.const import VARIANT_ESP32, VARIANT_FRIENDLY
from esphome.components.esp32 import CONFIG_SCHEMA, VARIANT_ESP32, VARIANT_FRIENDLY
# Example ESP32 configuration
config = {

View File

@@ -4,7 +4,7 @@ from typing import Any
import pytest
from esphome.components.esp32.const import (
from esphome.components.esp32 import (
KEY_VARIANT,
VARIANT_ESP32,
VARIANT_ESP32C2,

View File

@@ -0,0 +1,39 @@
esp32:
board: esp32dev
framework:
type: esp-idf
display:
- platform: hub75
id: my_hub75
panel_width: 64
panel_height: 32
double_buffer: true
brightness: 128
r1_pin: GPIO25
g1_pin: GPIO26
b1_pin: GPIO27
r2_pin: GPIO14
g2_pin: GPIO12
b2_pin: GPIO13
a_pin: GPIO23
b_pin: GPIO19
c_pin: GPIO5
d_pin: GPIO17
e_pin: GPIO21
lat_pin: GPIO4
oe_pin: GPIO15
clk_pin: GPIO16
pages:
- id: page1
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- id: page2
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
on_page_change:
from: page1
to: page2
then:
lambda: |-
ESP_LOGD("display", "1 -> 2");

View File

@@ -0,0 +1,26 @@
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
display:
- platform: hub75
id: hub75_display_board
board: adafruit-matrix-portal-s3
panel_width: 64
panel_height: 32
double_buffer: true
brightness: 128
pages:
- id: page1
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- id: page2
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
on_page_change:
from: page1
to: page2
then:
lambda: |-
ESP_LOGD("display", "1 -> 2");

View File

@@ -0,0 +1,39 @@
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
display:
- platform: hub75
id: my_hub75
panel_width: 64
panel_height: 32
double_buffer: true
brightness: 128
r1_pin: GPIO42
g1_pin: GPIO41
b1_pin: GPIO40
r2_pin: GPIO38
g2_pin: GPIO39
b2_pin: GPIO37
a_pin: GPIO45
b_pin: GPIO36
c_pin: GPIO48
d_pin: GPIO35
e_pin: GPIO21
lat_pin: GPIO47
oe_pin: GPIO14
clk_pin: GPIO2
pages:
- id: page1
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- id: page2
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
on_page_change:
from: page1
to: page2
then:
lambda: |-
ESP_LOGD("display", "1 -> 2");

View File

@@ -30,3 +30,4 @@ sensor:
id: workshop_PMC_10_0
address: 0x69
update_interval: 10s
idle_interval: 5min

View File

@@ -6,7 +6,7 @@ import pytest
import voluptuous as vol
from esphome import config_validation
from esphome.components.esp32.const import (
from esphome.components.esp32 import (
VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
@@ -221,7 +221,7 @@ def hex_int__valid(value):
],
)
def test_split_default(framework, platform, variant, full, idf, arduino, simple):
from esphome.components.esp32.const import KEY_ESP32
from esphome.components.esp32 import KEY_ESP32
from esphome.const import (
KEY_CORE,
KEY_TARGET_FRAMEWORK,

View File

@@ -35,7 +35,7 @@ from esphome.__main__ import (
upload_program,
upload_using_esptool,
)
from esphome.components.esp32.const import KEY_ESP32, KEY_VARIANT, VARIANT_ESP32
from esphome.components.esp32 import KEY_ESP32, KEY_VARIANT, VARIANT_ESP32
from esphome.const import (
CONF_API,
CONF_BROKER,