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:
@@ -1 +1 @@
|
||||
29270eecb86ffa07b2b1d2a4ca56dd7f84762ddc89c6248dbf3f012eca8780b6
|
||||
c01eec15857a784dd603c0afd194ab3b29a632422fe6f6b0a806ad4d81b5efc0
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -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}}"
|
||||
|
||||
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
46
esphome/components/esp32/gpio_esp32_c61.py
Normal file
46
esphome/components/esp32/gpio_esp32_c61.py
Normal 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
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
6
esphome/components/hub75/__init__.py
Normal file
6
esphome/components/hub75/__init__.py
Normal 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", "::")
|
||||
80
esphome/components/hub75/boards/__init__.py
Normal file
80
esphome/components/hub75/boards/__init__.py
Normal 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__)
|
||||
23
esphome/components/hub75/boards/adafruit.py
Normal file
23
esphome/components/hub75/boards/adafruit.py
Normal 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
|
||||
)
|
||||
41
esphome/components/hub75/boards/apollo.py
Normal file
41
esphome/components/hub75/boards/apollo.py
Normal 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,
|
||||
)
|
||||
22
esphome/components/hub75/boards/huidu.py
Normal file
22
esphome/components/hub75/boards/huidu.py
Normal 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,
|
||||
)
|
||||
24
esphome/components/hub75/boards/trinity.py
Normal file
24
esphome/components/hub75/boards/trinity.py
Normal 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,
|
||||
)
|
||||
578
esphome/components/hub75/display.py
Normal file
578
esphome/components/hub75/display.py
Normal 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_))
|
||||
192
esphome/components/hub75/hub75.cpp
Normal file
192
esphome/components/hub75/hub75.cpp
Normal 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
|
||||
55
esphome/components/hub75/hub75_component.h
Normal file
55
esphome/components/hub75/hub75_component.h
Normal 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
|
||||
@@ -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,
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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_() {
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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"]]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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-", "")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
39
tests/components/hub75/test.esp32-idf.yaml
Normal file
39
tests/components/hub75/test.esp32-idf.yaml
Normal 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");
|
||||
26
tests/components/hub75/test.esp32-s3-idf-board.yaml
Normal file
26
tests/components/hub75/test.esp32-s3-idf-board.yaml
Normal 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");
|
||||
39
tests/components/hub75/test.esp32-s3-idf.yaml
Normal file
39
tests/components/hub75/test.esp32-s3-idf.yaml
Normal 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");
|
||||
@@ -30,3 +30,4 @@ sensor:
|
||||
id: workshop_PMC_10_0
|
||||
address: 0x69
|
||||
update_interval: 10s
|
||||
idle_interval: 5min
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user