1
0
mirror of https://github.com/esphome/esphome.git synced 2025-01-31 10:10:56 +00:00

Merge branch 'dev' into nvds-new-espnow

This commit is contained in:
NP v/d Spek 2024-12-21 18:56:47 +01:00 committed by GitHub
commit 5c7d0782fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
88 changed files with 2233 additions and 906 deletions

View File

@ -46,7 +46,7 @@ jobs:
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1 uses: docker/setup-buildx-action@v3.8.0
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3.2.0 uses: docker/setup-qemu-action@v3.2.0

View File

@ -13,6 +13,7 @@ on:
- ".github/workflows/ci.yml" - ".github/workflows/ci.yml"
- "!.yamllint" - "!.yamllint"
- "!.github/dependabot.yml" - "!.github/dependabot.yml"
- "!docker/**"
merge_group: merge_group:
permissions: permissions:

View File

@ -90,7 +90,7 @@ jobs:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1 uses: docker/setup-buildx-action@v3.8.0
- name: Set up QEMU - name: Set up QEMU
if: matrix.platform != 'linux/amd64' if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.2.0 uses: docker/setup-qemu-action@v3.2.0
@ -141,7 +141,7 @@ jobs:
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
- name: Upload digests - name: Upload digests
uses: actions/upload-artifact@v4.4.3 uses: actions/upload-artifact@v4.5.0
with: with:
name: digests-${{ steps.sanitize.outputs.name }} name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests path: /tmp/digests
@ -184,7 +184,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1 uses: docker/setup-buildx-action@v3.8.0
- name: Log in to docker hub - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'

View File

@ -1,11 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER import esphome.codegen as cg
from esphome.core import CORE
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
from esphome.const import PLATFORM_ESP8266
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
VARIANT_ESP32, VARIANT_ESP32,
VARIANT_ESP32C2, VARIANT_ESP32C2,
@ -15,6 +10,9 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
) )
import esphome.config_validation as cv
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
from esphome.core import CORE
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
@ -102,11 +100,11 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
6: adc1_channel_t.ADC1_CHANNEL_6, 6: adc1_channel_t.ADC1_CHANNEL_6,
}, },
VARIANT_ESP32H2: { VARIANT_ESP32H2: {
0: adc1_channel_t.ADC1_CHANNEL_0, 1: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1, 2: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2, 3: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3, 4: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4, 5: adc1_channel_t.ADC1_CHANNEL_4,
}, },
} }

View File

@ -0,0 +1,48 @@
#ifdef USE_LIBRETINY
#include "adc_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace adc {
static const char *const TAG = "adc.libretiny";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
this->pin_->setup();
#endif // !USE_ADC_SENSOR_VCC
}
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else // USE_ADC_SENSOR_VCC
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::sample() {
uint32_t raw = 0;
if (this->output_raw_) {
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogRead(this->pin_->get_pin()); // NOLINT
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
return raw;
}
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogReadVoltage(this->pin_->get_pin()); // NOLINT
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
return raw / 1000.0f;
}
} // namespace adc
} // namespace esphome
#endif // USE_LIBRETINY

View File

@ -30,109 +30,162 @@ static const char *const TAG = "debug";
std::string DebugComponent::get_reset_reason_() { std::string DebugComponent::get_reset_reason_() {
std::string reset_reason; std::string reset_reason;
switch (rtc_get_reset_reason(0)) { switch (esp_reset_reason()) {
case POWERON_RESET: case ESP_RST_POWERON:
reset_reason = "Power On Reset"; reset_reason = "Reset due to power-on event";
break; break;
case ESP_RST_EXT:
reset_reason = "Reset by external pin";
break;
case ESP_RST_SW:
reset_reason = "Software reset via esp_restart";
break;
case ESP_RST_PANIC:
reset_reason = "Software reset due to exception/panic";
break;
case ESP_RST_INT_WDT:
reset_reason = "Reset (software or hardware) due to interrupt watchdog";
break;
case ESP_RST_TASK_WDT:
reset_reason = "Reset due to task watchdog";
break;
case ESP_RST_WDT:
reset_reason = "Reset due to other watchdogs";
break;
case ESP_RST_DEEPSLEEP:
reset_reason = "Reset after exiting deep sleep mode";
break;
case ESP_RST_BROWNOUT:
reset_reason = "Brownout reset (software or hardware)";
break;
case ESP_RST_SDIO:
reset_reason = "Reset over SDIO";
break;
#ifdef USE_ESP32_VARIANT_ESP32
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4))
case ESP_RST_USB:
reset_reason = "Reset by USB peripheral";
break;
case ESP_RST_JTAG:
reset_reason = "Reset by JTAG";
break;
case ESP_RST_EFUSE:
reset_reason = "Reset due to efuse error";
break;
case ESP_RST_PWR_GLITCH:
reset_reason = "Reset due to power glitch detected";
break;
case ESP_RST_CPU_LOCKUP:
reset_reason = "Reset due to CPU lock up (double exception)";
break;
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4)
#endif // USE_ESP32_VARIANT_ESP32
default: // Includes ESP_RST_UNKNOWN
switch (rtc_get_reset_reason(0)) {
case POWERON_RESET:
reset_reason = "Power On Reset";
break;
#if defined(USE_ESP32_VARIANT_ESP32) #if defined(USE_ESP32_VARIANT_ESP32)
case SW_RESET: case SW_RESET:
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \ #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6) defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case RTC_SW_SYS_RESET: case RTC_SW_SYS_RESET:
#endif #endif
reset_reason = "Software Reset Digital Core"; reset_reason = "Software Reset Digital Core";
break; break;
#if defined(USE_ESP32_VARIANT_ESP32) #if defined(USE_ESP32_VARIANT_ESP32)
case OWDT_RESET: case OWDT_RESET:
reset_reason = "Watch Dog Reset Digital Core"; reset_reason = "Watch Dog Reset Digital Core";
break; break;
#endif #endif
case DEEPSLEEP_RESET: case DEEPSLEEP_RESET:
reset_reason = "Deep Sleep Reset Digital Core"; reset_reason = "Deep Sleep Reset Digital Core";
break; break;
#if defined(USE_ESP32_VARIANT_ESP32) #if defined(USE_ESP32_VARIANT_ESP32)
case SDIO_RESET: case SDIO_RESET:
reset_reason = "SLC Module Reset Digital Core"; reset_reason = "SLC Module Reset Digital Core";
break; break;
#endif #endif
case TG0WDT_SYS_RESET: case TG0WDT_SYS_RESET:
reset_reason = "Timer Group 0 Watch Dog Reset Digital Core"; reset_reason = "Timer Group 0 Watch Dog Reset Digital Core";
break; break;
case TG1WDT_SYS_RESET: case TG1WDT_SYS_RESET:
reset_reason = "Timer Group 1 Watch Dog Reset Digital Core"; reset_reason = "Timer Group 1 Watch Dog Reset Digital Core";
break; break;
case RTCWDT_SYS_RESET: case RTCWDT_SYS_RESET:
reset_reason = "RTC Watch Dog Reset Digital Core"; reset_reason = "RTC Watch Dog Reset Digital Core";
break; break;
#if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2) #if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
case INTRUSION_RESET: case INTRUSION_RESET:
reset_reason = "Intrusion Reset CPU"; reset_reason = "Intrusion Reset CPU";
break; break;
#endif #endif
#if defined(USE_ESP32_VARIANT_ESP32) #if defined(USE_ESP32_VARIANT_ESP32)
case TGWDT_CPU_RESET: case TGWDT_CPU_RESET:
reset_reason = "Timer Group Reset CPU"; reset_reason = "Timer Group Reset CPU";
break; break;
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \ #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6) defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case TG0WDT_CPU_RESET: case TG0WDT_CPU_RESET:
reset_reason = "Timer Group 0 Reset CPU"; reset_reason = "Timer Group 0 Reset CPU";
break; break;
#endif #endif
#if defined(USE_ESP32_VARIANT_ESP32) #if defined(USE_ESP32_VARIANT_ESP32)
case SW_CPU_RESET: case SW_CPU_RESET:
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \ #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6) defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case RTC_SW_CPU_RESET: case RTC_SW_CPU_RESET:
#endif #endif
reset_reason = "Software Reset CPU"; reset_reason = "Software Reset CPU";
break; break;
case RTCWDT_CPU_RESET: case RTCWDT_CPU_RESET:
reset_reason = "RTC Watch Dog Reset CPU"; reset_reason = "RTC Watch Dog Reset CPU";
break; break;
#if defined(USE_ESP32_VARIANT_ESP32) #if defined(USE_ESP32_VARIANT_ESP32)
case EXT_CPU_RESET: case EXT_CPU_RESET:
reset_reason = "External CPU Reset"; reset_reason = "External CPU Reset";
break; break;
#endif #endif
case RTCWDT_BROWN_OUT_RESET: case RTCWDT_BROWN_OUT_RESET:
reset_reason = "Voltage Unstable Reset"; reset_reason = "Voltage Unstable Reset";
break; break;
case RTCWDT_RTC_RESET: case RTCWDT_RTC_RESET:
reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module"; reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module";
break; break;
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \ #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
defined(USE_ESP32_VARIANT_ESP32C6) defined(USE_ESP32_VARIANT_ESP32C6)
case TG1WDT_CPU_RESET: case TG1WDT_CPU_RESET:
reset_reason = "Timer Group 1 Reset CPU"; reset_reason = "Timer Group 1 Reset CPU";
break; break;
case SUPER_WDT_RESET: case SUPER_WDT_RESET:
reset_reason = "Super Watchdog Reset Digital Core And RTC Module"; reset_reason = "Super Watchdog Reset Digital Core And RTC Module";
break; break;
case EFUSE_RESET: case EFUSE_RESET:
reset_reason = "eFuse Reset Digital Core"; reset_reason = "eFuse Reset Digital Core";
break; break;
#endif #endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
case GLITCH_RTC_RESET: case GLITCH_RTC_RESET:
reset_reason = "Glitch Reset Digital Core And RTC Module"; reset_reason = "Glitch Reset Digital Core And RTC Module";
break; break;
#endif #endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6) #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case USB_UART_CHIP_RESET: case USB_UART_CHIP_RESET:
reset_reason = "USB UART Reset Digital Core"; reset_reason = "USB UART Reset Digital Core";
break; break;
case USB_JTAG_CHIP_RESET: case USB_JTAG_CHIP_RESET:
reset_reason = "USB JTAG Reset Digital Core"; reset_reason = "USB JTAG Reset Digital Core";
break; break;
#endif #endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
case POWER_GLITCH_RESET: case POWER_GLITCH_RESET:
reset_reason = "Power Glitch Reset Digital Core And RTC Module"; reset_reason = "Power Glitch Reset Digital Core And RTC Module";
break; break;
#endif #endif
default: default:
reset_reason = "Unknown Reset Reason"; reset_reason = "Unknown Reset Reason";
}
break;
} }
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
return reset_reason; return reset_reason;
@ -294,4 +347,4 @@ void DebugComponent::update_platform_() {
} // namespace debug } // namespace debug
} // namespace esphome } // namespace esphome
#endif #endif // USE_ESP32

View File

@ -602,6 +602,9 @@ async def to_code(config):
cg.add_platformio_option( cg.add_platformio_option(
"platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"] "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"]
) )
add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
add_idf_sdkconfig_option( add_idf_sdkconfig_option(

View File

@ -27,6 +27,9 @@ namespace esp32_ble {
static const char *const TAG = "esp32_ble"; static const char *const TAG = "esp32_ble";
static RAMAllocator<BLEEvent> EVENT_ALLOCATOR( // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
void ESP32BLE::setup() { void ESP32BLE::setup() {
global_ble = this; global_ble = this;
ESP_LOGCONFIG(TAG, "Setting up BLE..."); ESP_LOGCONFIG(TAG, "Setting up BLE...");
@ -322,7 +325,8 @@ void ESP32BLE::loop() {
default: default:
break; break;
} }
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) ble_event->~BLEEvent();
EVENT_ALLOCATOR.deallocate(ble_event, 1);
ble_event = this->ble_events_.pop(); ble_event = this->ble_events_.pop();
} }
if (this->advertising_ != nullptr) { if (this->advertising_ != nullptr) {
@ -331,9 +335,14 @@ void ESP32BLE::loop() {
} }
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
BLEEvent *new_event = new BLEEvent(event, param); // NOLINT(cppcoreguidelines-owning-memory) BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
if (new_event == nullptr) {
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
return;
}
new (new_event) BLEEvent(event, param);
global_ble->ble_events_.push(new_event); global_ble->ble_events_.push(new_event);
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) } // NOLINT(clang-analyzer-unix.Malloc)
void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event); ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event);
@ -344,9 +353,14 @@ void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap
void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) { esp_ble_gatts_cb_param_t *param) {
BLEEvent *new_event = new BLEEvent(event, gatts_if, param); // NOLINT(cppcoreguidelines-owning-memory) BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
if (new_event == nullptr) {
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
return;
}
new (new_event) BLEEvent(event, gatts_if, param);
global_ble->ble_events_.push(new_event); global_ble->ble_events_.push(new_event);
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) } // NOLINT(clang-analyzer-unix.Malloc)
void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) { esp_ble_gatts_cb_param_t *param) {
@ -358,9 +372,14 @@ void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if
void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *param) {
BLEEvent *new_event = new BLEEvent(event, gattc_if, param); // NOLINT(cppcoreguidelines-owning-memory) BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
if (new_event == nullptr) {
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
return;
}
new (new_event) BLEEvent(event, gattc_if, param);
global_ble->ble_events_.push(new_event); global_ble->ble_events_.push(new_event);
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) } // NOLINT(clang-analyzer-unix.Malloc)
void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *param) {

View File

@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() {
esp_err_t err; esp_err_t err;
this->advertising_data_.set_scan_rsp = false; this->advertising_data_.set_scan_rsp = false;
this->advertising_data_.include_name = true; this->advertising_data_.include_name = !this->scan_response_;
this->advertising_data_.include_txpower = !this->scan_response_; this->advertising_data_.include_txpower = !this->scan_response_;
err = esp_ble_gap_config_adv_data(&this->advertising_data_); err = esp_ble_gap_config_adv_data(&this->advertising_data_);
if (err != ESP_OK) { if (err != ESP_OK) {

View File

@ -26,10 +26,10 @@ template<class T> class Queue {
void push(T *element) { void push(T *element) {
if (element == nullptr) if (element == nullptr)
return; return;
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { // It is not called from main loop. Thus it won't block main thread.
q_.push(element); xSemaphoreTake(m_, portMAX_DELAY);
xSemaphoreGive(m_); q_.push(element);
} xSemaphoreGive(m_);
} }
T *pop() { T *pop() {

View File

@ -1,7 +1,8 @@
import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32 from esphome.components import esp32
import esphome.config_validation as cv
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION
from esphome.core import CORE
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
@ -36,8 +37,32 @@ RMT_CHANNEL_ENUMS = {
} }
def validate_rmt_channel(*, tx: bool): def use_new_rmt_driver():
framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
if CORE.using_esp_idf and framework_version >= cv.Version(5, 0, 0):
return True
return False
def validate_clock_resolution():
def _validator(value):
cv.only_on_esp32(value)
value = cv.int_(value)
variant = esp32.get_esp32_variant()
if variant == esp32.const.VARIANT_ESP32H2 and value > 32000000:
raise cv.Invalid(
f"ESP32 variant {variant} has a max clock_resolution of 32000000."
)
if value > 80000000:
raise cv.Invalid(
f"ESP32 variant {variant} has a max clock_resolution of 80000000."
)
return value
return _validator
def validate_rmt_channel(*, tx: bool):
rmt_channels = RMT_TX_CHANNELS if tx else RMT_RX_CHANNELS rmt_channels = RMT_TX_CHANNELS if tx else RMT_RX_CHANNELS
def _validator(value): def _validator(value):

View File

@ -1,5 +1,5 @@
#include <cinttypes>
#include "led_strip.h" #include "led_strip.h"
#include <cinttypes>
#ifdef USE_ESP32 #ifdef USE_ESP32
@ -13,9 +13,13 @@ namespace esp32_rmt_led_strip {
static const char *const TAG = "esp32_rmt_led_strip"; static const char *const TAG = "esp32_rmt_led_strip";
#ifdef USE_ESP32_VARIANT_ESP32H2
static const uint32_t RMT_CLK_FREQ = 32000000;
static const uint8_t RMT_CLK_DIV = 1;
#else
static const uint32_t RMT_CLK_FREQ = 80000000; static const uint32_t RMT_CLK_FREQ = 80000000;
static const uint8_t RMT_CLK_DIV = 2; static const uint8_t RMT_CLK_DIV = 2;
#endif
void ESP32RMTLEDStripLightOutput::setup() { void ESP32RMTLEDStripLightOutput::setup() {
ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip..."); ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip...");
@ -37,9 +41,48 @@ void ESP32RMTLEDStripLightOutput::setup() {
return; return;
} }
#if ESP_IDF_VERSION_MAJOR >= 5
RAMAllocator<rmt_symbol_word_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_symbol_word_t>::ALLOC_INTERNAL);
// 8 bits per byte, 1 rmt_symbol_word_t per bit + 1 rmt_symbol_word_t for reset
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1);
rmt_tx_channel_config_t channel;
memset(&channel, 0, sizeof(channel));
channel.clk_src = RMT_CLK_SRC_DEFAULT;
channel.resolution_hz = RMT_CLK_FREQ / RMT_CLK_DIV;
channel.gpio_num = gpio_num_t(this->pin_);
channel.mem_block_symbols = this->rmt_symbols_;
channel.trans_queue_depth = 1;
channel.flags.io_loop_back = 0;
channel.flags.io_od_mode = 0;
channel.flags.invert_out = 0;
channel.flags.with_dma = 0;
channel.intr_priority = 0;
if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) {
ESP_LOGE(TAG, "Channel creation failed");
this->mark_failed();
return;
}
rmt_copy_encoder_config_t encoder;
memset(&encoder, 0, sizeof(encoder));
if (rmt_new_copy_encoder(&encoder, &this->encoder_) != ESP_OK) {
ESP_LOGE(TAG, "Encoder creation failed");
this->mark_failed();
return;
}
if (rmt_enable(this->channel_) != ESP_OK) {
ESP_LOGE(TAG, "Enabling channel failed");
this->mark_failed();
return;
}
#else
RAMAllocator<rmt_item32_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_item32_t>::ALLOC_INTERNAL); RAMAllocator<rmt_item32_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_item32_t>::ALLOC_INTERNAL);
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 +
1); // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1);
rmt_config_t config; rmt_config_t config;
memset(&config, 0, sizeof(config)); memset(&config, 0, sizeof(config));
@ -64,6 +107,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
#endif
} }
void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high,
@ -100,7 +144,12 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
ESP_LOGVV(TAG, "Writing RGB values to bus..."); ESP_LOGVV(TAG, "Writing RGB values to bus...");
if (rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)) != ESP_OK) { #if ESP_IDF_VERSION_MAJOR >= 5
esp_err_t error = rmt_tx_wait_all_done(this->channel_, 1000);
#else
esp_err_t error = rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000));
#endif
if (error != ESP_OK) {
ESP_LOGE(TAG, "RMT TX timeout"); ESP_LOGE(TAG, "RMT TX timeout");
this->status_set_warning(); this->status_set_warning();
return; return;
@ -112,7 +161,11 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
size_t size = 0; size_t size = 0;
size_t len = 0; size_t len = 0;
uint8_t *psrc = this->buf_; uint8_t *psrc = this->buf_;
#if ESP_IDF_VERSION_MAJOR >= 5
rmt_symbol_word_t *pdest = this->rmt_buf_;
#else
rmt_item32_t *pdest = this->rmt_buf_; rmt_item32_t *pdest = this->rmt_buf_;
#endif
while (size < buffer_size) { while (size < buffer_size) {
uint8_t b = *psrc; uint8_t b = *psrc;
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
@ -130,7 +183,16 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
len++; len++;
} }
if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) { #if ESP_IDF_VERSION_MAJOR >= 5
rmt_transmit_config_t config;
memset(&config, 0, sizeof(config));
config.loop_count = 0;
config.flags.eot_level = 0;
error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config);
#else
error = rmt_write_items(this->channel_, this->rmt_buf_, len, false);
#endif
if (error != ESP_OK) {
ESP_LOGE(TAG, "RMT TX error"); ESP_LOGE(TAG, "RMT TX error");
this->status_set_warning(); this->status_set_warning();
return; return;
@ -186,7 +248,11 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index
void ESP32RMTLEDStripLightOutput::dump_config() { void ESP32RMTLEDStripLightOutput::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:"); ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:");
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
#if ESP_IDF_VERSION_MAJOR >= 5
ESP_LOGCONFIG(TAG, " RMT Symbols: %" PRIu32, this->rmt_symbols_);
#else
ESP_LOGCONFIG(TAG, " Channel: %u", this->channel_); ESP_LOGCONFIG(TAG, " Channel: %u", this->channel_);
#endif
const char *rgb_order; const char *rgb_order;
switch (this->rgb_order_) { switch (this->rgb_order_) {
case ORDER_RGB: case ORDER_RGB:

View File

@ -9,8 +9,14 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include <driver/gpio.h> #include <driver/gpio.h>
#include <driver/rmt.h>
#include <esp_err.h> #include <esp_err.h>
#include <esp_idf_version.h>
#if ESP_IDF_VERSION_MAJOR >= 5
#include <driver/rmt_tx.h>
#else
#include <driver/rmt.h>
#endif
namespace esphome { namespace esphome {
namespace esp32_rmt_led_strip { namespace esp32_rmt_led_strip {
@ -54,7 +60,11 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
uint32_t reset_time_high, uint32_t reset_time_low); uint32_t reset_time_high, uint32_t reset_time_low);
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
#if ESP_IDF_VERSION_MAJOR >= 5
void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; }
#else
void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; } void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; }
#endif
void clear_effect_data() override { void clear_effect_data() override {
for (int i = 0; i < this->size(); i++) for (int i = 0; i < this->size(); i++)
@ -70,7 +80,17 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
uint8_t *buf_{nullptr}; uint8_t *buf_{nullptr};
uint8_t *effect_data_{nullptr}; uint8_t *effect_data_{nullptr};
#if ESP_IDF_VERSION_MAJOR >= 5
rmt_channel_handle_t channel_{nullptr};
rmt_encoder_handle_t encoder_{nullptr};
rmt_symbol_word_t *rmt_buf_{nullptr};
rmt_symbol_word_t bit0_, bit1_, reset_;
uint32_t rmt_symbols_;
#else
rmt_item32_t *rmt_buf_{nullptr}; rmt_item32_t *rmt_buf_{nullptr};
rmt_item32_t bit0_, bit1_, reset_;
rmt_channel_t channel_{RMT_CHANNEL_0};
#endif
uint8_t pin_; uint8_t pin_;
uint16_t num_leds_; uint16_t num_leds_;
@ -78,9 +98,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
bool is_wrgb_; bool is_wrgb_;
bool use_psram_; bool use_psram_;
rmt_item32_t bit0_, bit1_, reset_;
RGBOrder rgb_order_; RGBOrder rgb_order_;
rmt_channel_t channel_;
uint32_t last_refresh_{0}; uint32_t last_refresh_{0};
optional<uint32_t> max_refresh_rate_{}; optional<uint32_t> max_refresh_rate_{};

View File

@ -13,6 +13,7 @@ from esphome.const import (
CONF_PIN, CONF_PIN,
CONF_RGB_ORDER, CONF_RGB_ORDER,
CONF_RMT_CHANNEL, CONF_RMT_CHANNEL,
CONF_RMT_SYMBOLS,
) )
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
@ -23,8 +24,6 @@ ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_(
"ESP32RMTLEDStripLightOutput", light.AddressableLight "ESP32RMTLEDStripLightOutput", light.AddressableLight
) )
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder") RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder")
RGB_ORDERS = { RGB_ORDERS = {
@ -65,6 +64,13 @@ CONF_RESET_HIGH = "reset_high"
CONF_RESET_LOW = "reset_low" CONF_RESET_LOW = "reset_low"
def final_validation(config):
if not esp32_rmt.use_new_rmt_driver() and CONF_RMT_CHANNEL not in config:
raise cv.Invalid("rmt_channel is a required option.")
FINAL_VALIDATE_SCHEMA = final_validation
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
light.ADDRESSABLE_LIGHT_SCHEMA.extend( light.ADDRESSABLE_LIGHT_SCHEMA.extend(
{ {
@ -72,7 +78,18 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
cv.Required(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=True), cv.Optional(CONF_RMT_CHANNEL): cv.All(
cv.only_with_arduino, esp32_rmt.validate_rmt_channel(tx=True)
),
cv.SplitDefault(
CONF_RMT_SYMBOLS,
esp32_idf=64,
esp32_s2_idf=64,
esp32_s3_idf=48,
esp32_c3_idf=48,
esp32_c6_idf=48,
esp32_h2_idf=48,
): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)),
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
@ -148,8 +165,12 @@ async def to_code(config):
cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) cg.add(var.set_is_wrgb(config[CONF_IS_WRGB]))
cg.add(var.set_use_psram(config[CONF_USE_PSRAM])) cg.add(var.set_use_psram(config[CONF_USE_PSRAM]))
cg.add( if esp32_rmt.use_new_rmt_driver():
var.set_rmt_channel( cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}") else:
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
cg.add(
var.set_rmt_channel(
getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}")
)
) )
)

View File

@ -51,8 +51,11 @@ CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs"
# Cache loaded freetype fonts # Cache loaded freetype fonts
class FontCache(dict): class FontCache(dict):
def __missing__(self, key): def __missing__(self, key):
res = self[key] = freetype.Face(key) try:
return res res = self[key] = freetype.Face(key)
return res
except freetype.FT_Exception as e:
raise cv.Invalid(f"Could not load Font file {key}: {e}") from e
FONT_CACHE = FontCache() FONT_CACHE = FontCache()

View File

@ -247,7 +247,7 @@ void I2SAudioSpeaker::speaker_task(void *params) {
// Ensure ring buffer is at least as large as the total size of the DMA buffers // Ensure ring buffer is at least as large as the total size of the DMA buffers
const size_t ring_buffer_size = const size_t ring_buffer_size =
std::min((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms); std::max((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms);
if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) { if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) {
// Failed to allocate buffers // Failed to allocate buffers

View File

@ -1,38 +1,20 @@
#include "json_util.h" #include "json_util.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef USE_ESP8266
#include <Esp.h>
#endif
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#endif
#ifdef USE_RP2040
#include <Arduino.h>
#endif
namespace esphome { namespace esphome {
namespace json { namespace json {
static const char *const TAG = "json"; static const char *const TAG = "json";
static std::vector<char> global_json_build_buffer; // NOLINT static std::vector<char> global_json_build_buffer; // NOLINT
static const auto ALLOCATOR = RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::ALLOC_INTERNAL);
std::string build_json(const json_build_t &f) { std::string build_json(const json_build_t &f) {
// Here we are allocating up to 5kb of memory, // Here we are allocating up to 5kb of memory,
// with the heap size minus 2kb to be safe if less than 5kb // with the heap size minus 2kb to be safe if less than 5kb
// as we can not have a true dynamic sized document. // as we can not have a true dynamic sized document.
// The excess memory is freed below with `shrinkToFit()` // The excess memory is freed below with `shrinkToFit()`
#ifdef USE_ESP8266 auto free_heap = ALLOCATOR.get_max_free_block_size();
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
#elif defined(USE_RP2040)
const size_t free_heap = rp2040.getFreeHeap();
#elif defined(USE_LIBRETINY)
const size_t free_heap = lt_heap_get_free();
#endif
size_t request_size = std::min(free_heap, (size_t) 512); size_t request_size = std::min(free_heap, (size_t) 512);
while (true) { while (true) {
ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size); ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size);
@ -67,20 +49,12 @@ bool parse_json(const std::string &data, const json_parse_t &f) {
// with the heap size minus 2kb to be safe if less than that // with the heap size minus 2kb to be safe if less than that
// as we can not have a true dynamic sized document. // as we can not have a true dynamic sized document.
// The excess memory is freed below with `shrinkToFit()` // The excess memory is freed below with `shrinkToFit()`
#ifdef USE_ESP8266 auto free_heap = ALLOCATOR.get_max_free_block_size();
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
#elif defined(USE_RP2040)
const size_t free_heap = rp2040.getFreeHeap();
#elif defined(USE_LIBRETINY)
const size_t free_heap = lt_heap_get_free();
#endif
size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5)); size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
while (true) { while (true) {
DynamicJsonDocument json_document(request_size); DynamicJsonDocument json_document(request_size);
if (json_document.capacity() == 0) { if (json_document.capacity() == 0) {
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %zu bytes, free heap: %zu", request_size,
free_heap); free_heap);
return false; return false;
} }

View File

@ -23,7 +23,7 @@ from esphome.helpers import write_file_if_changed
from . import defines as df, helpers, lv_validation as lvalid from . import defines as df, helpers, lv_validation as lvalid
from .automation import disp_update, focused_widgets, update_to_code from .automation import disp_update, focused_widgets, update_to_code
from .defines import add_define from .defines import CONF_DRAW_ROUNDING, add_define
from .encoders import ( from .encoders import (
ENCODERS_CONFIG, ENCODERS_CONFIG,
encoders_to_code, encoders_to_code,
@ -205,6 +205,10 @@ def final_validation(configs):
raise cv.Invalid( raise cv.Invalid(
"Using auto_clear_enabled: true in display config not compatible with LVGL" "Using auto_clear_enabled: true in display config not compatible with LVGL"
) )
if draw_rounding := display.get(CONF_DRAW_ROUNDING):
config[CONF_DRAW_ROUNDING] = max(
draw_rounding, config[CONF_DRAW_ROUNDING]
)
buffer_frac = config[CONF_BUFFER_SIZE] buffer_frac = config[CONF_BUFFER_SIZE]
if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config: if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config:
LOGGER.warning("buffer_size: may need to be reduced without PSRAM") LOGGER.warning("buffer_size: may need to be reduced without PSRAM")

View File

@ -19,10 +19,12 @@ template<typename... Ts> class MideaActionBase : public Action<Ts...> {
template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> { template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> {
TEMPLATABLE_VALUE(float, temperature) TEMPLATABLE_VALUE(float, temperature)
TEMPLATABLE_VALUE(bool, use_fahrenheit)
TEMPLATABLE_VALUE(bool, beeper) TEMPLATABLE_VALUE(bool, beeper)
void play(Ts... x) override { void play(Ts... x) override {
this->parent_->do_follow_me(this->temperature_.value(x...), this->beeper_.value(x...)); this->parent_->do_follow_me(this->temperature_.value(x...), this->use_fahrenheit_.value(x...),
this->beeper_.value(x...));
} }
}; };

View File

@ -1,6 +1,7 @@
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "air_conditioner.h" #include "air_conditioner.h"
#include "ac_adapter.h" #include "ac_adapter.h"
#include <cmath> #include <cmath>
@ -121,7 +122,7 @@ void AirConditioner::dump_config() {
/* ACTIONS */ /* ACTIONS */
void AirConditioner::do_follow_me(float temperature, bool beeper) { void AirConditioner::do_follow_me(float temperature, bool use_fahrenheit, bool beeper) {
#ifdef USE_REMOTE_TRANSMITTER #ifdef USE_REMOTE_TRANSMITTER
// Check if temperature is finite (not NaN or infinite) // Check if temperature is finite (not NaN or infinite)
if (!std::isfinite(temperature)) { if (!std::isfinite(temperature)) {
@ -131,13 +132,14 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) {
// Round and convert temperature to long, then clamp and convert it to uint8_t // Round and convert temperature to long, then clamp and convert it to uint8_t
uint8_t temp_uint8 = uint8_t temp_uint8 =
static_cast<uint8_t>(std::max(0L, std::min(static_cast<long>(UINT8_MAX), std::lroundf(temperature)))); static_cast<uint8_t>(esphome::clamp<long>(std::lroundf(temperature), 0L, static_cast<long>(UINT8_MAX)));
ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %f °C, rounded to: %u °C", temperature, char temp_symbol = use_fahrenheit ? 'F' : 'C';
temp_uint8); ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %.5f °%c, rounded to: %u °%c", temperature,
temp_symbol, temp_uint8, temp_symbol);
// Create and transmit the data // Create and transmit the data
IrFollowMeData data(temp_uint8, beeper); IrFollowMeData data(temp_uint8, use_fahrenheit, beeper);
this->transmitter_.transmit(data); this->transmitter_.transmit(data);
#else #else
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");

View File

@ -32,7 +32,7 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>,
/* ### ACTIONS ### */ /* ### ACTIONS ### */
/* ############### */ /* ############### */
void do_follow_me(float temperature, bool beeper = false); void do_follow_me(float temperature, bool use_fahrenheit, bool beeper = false);
void do_display_toggle(); void do_display_toggle();
void do_swing_step(); void do_swing_step();
void do_beeper_on() { this->set_beeper_feedback(true); } void do_beeper_on() { this->set_beeper_feedback(true); }

View File

@ -18,6 +18,7 @@ from esphome.const import (
CONF_SUPPORTED_SWING_MODES, CONF_SUPPORTED_SWING_MODES,
CONF_TIMEOUT, CONF_TIMEOUT,
CONF_TEMPERATURE, CONF_TEMPERATURE,
CONF_USE_FAHRENHEIT,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
@ -172,11 +173,10 @@ MIDEA_ACTION_BASE_SCHEMA = cv.Schema(
) )
# FollowMe action # FollowMe action
MIDEA_FOLLOW_ME_MIN = 0
MIDEA_FOLLOW_ME_MAX = 37
MIDEA_FOLLOW_ME_SCHEMA = cv.Schema( MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
{ {
cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature), cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.templatable(cv.boolean),
cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean), cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean),
} }
) )
@ -186,6 +186,8 @@ MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
async def follow_me_to_code(var, config, args): async def follow_me_to_code(var, config, args):
template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_) template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_)
cg.add(var.set_beeper(template_)) cg.add(var.set_beeper(template_))
template_ = await cg.templatable(config[CONF_USE_FAHRENHEIT], args, cg.bool_)
cg.add(var.set_use_fahrenheit(template_))
template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_) template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_)
cg.add(var.set_temperature(template_)) cg.add(var.set_temperature(template_))

View File

@ -16,22 +16,53 @@ class IrFollowMeData : public IrData {
IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {} IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {}
// Copy from Base // Copy from Base
IrFollowMeData(const IrData &data) : IrData(data) {} IrFollowMeData(const IrData &data) : IrData(data) {}
// Direct from temperature and beeper values // Direct from temperature in celsius and beeper values
IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() { IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() {
this->set_temp(temp); this->set_temp(temp, false);
this->set_beeper(beeper);
}
// Direct from temperature, fahrenheit and beeper values
IrFollowMeData(uint8_t temp, bool fahrenheit, bool beeper) : IrFollowMeData() {
this->set_temp(temp, fahrenheit);
this->set_beeper(beeper); this->set_beeper(beeper);
} }
/* TEMPERATURE */ /* TEMPERATURE */
uint8_t temp() const { return this->get_value_(4) - 1; } uint8_t temp() const {
void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); } if (this->fahrenheit()) {
return this->get_value_(4) + 31;
}
return this->get_value_(4) - 1;
}
void set_temp(uint8_t val, bool fahrenheit = false) {
this->set_fahrenheit(fahrenheit);
if (this->fahrenheit()) {
// see https://github.com/esphome/feature-requests/issues/1627#issuecomment-1365639966
val = esphome::clamp<uint8_t>(val, MIN_TEMP_F, MAX_TEMP_F) - 31;
} else {
val = esphome::clamp<uint8_t>(val, MIN_TEMP_C, MAX_TEMP_C) + 1;
}
this->set_value_(4, val);
}
/* BEEPER */ /* BEEPER */
bool beeper() const { return this->get_value_(3, 128); } bool beeper() const { return this->get_value_(3, 128); }
void set_beeper(bool val) { this->set_mask_(3, val, 128); } void set_beeper(bool val) { this->set_mask_(3, val, 128); }
/* FAHRENHEIT */
bool fahrenheit() const { return this->get_value_(2, 32); }
void set_fahrenheit(bool val) { this->set_mask_(2, val, 32); }
protected: protected:
static const uint8_t MAX_TEMP = 37; static const uint8_t MIN_TEMP_C = 0;
static const uint8_t MAX_TEMP_C = 37;
// see
// https://github.com/crankyoldgit/IRremoteESP8266/blob/9bdf8abcb465268c5409db99dc83a26df64c7445/src/ir_Midea.h#L116
static const uint8_t MIN_TEMP_F = 32;
// see
// https://github.com/crankyoldgit/IRremoteESP8266/blob/9bdf8abcb465268c5409db99dc83a26df64c7445/src/ir_Midea.h#L117
static const uint8_t MAX_TEMP_F = 99;
}; };
class IrSpecialData : public IrData { class IrSpecialData : public IrData {

View File

@ -119,17 +119,17 @@ async def to_code(config):
cg.add_library("ESP8266HTTPClient", None) cg.add_library("ESP8266HTTPClient", None)
if CONF_TOUCH_SLEEP_TIMEOUT in config: if CONF_TOUCH_SLEEP_TIMEOUT in config:
cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT])) cg.add(var.set_touch_sleep_timeout(config[CONF_TOUCH_SLEEP_TIMEOUT]))
if CONF_WAKE_UP_PAGE in config: if CONF_WAKE_UP_PAGE in config:
cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE])) cg.add(var.set_wake_up_page(config[CONF_WAKE_UP_PAGE]))
if CONF_START_UP_PAGE in config: if CONF_START_UP_PAGE in config:
cg.add(var.set_start_up_page_internal(config[CONF_START_UP_PAGE])) cg.add(var.set_start_up_page(config[CONF_START_UP_PAGE]))
cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH])) cg.add(var.set_auto_wake_on_touch(config[CONF_AUTO_WAKE_ON_TOUCH]))
cg.add(var.set_exit_reparse_on_start_internal(config[CONF_EXIT_REPARSE_ON_START])) cg.add(var.set_exit_reparse_on_start(config[CONF_EXIT_REPARSE_ON_START]))
cg.add(var.set_skip_connection_handshake(config[CONF_SKIP_CONNECTION_HANDSHAKE])) cg.add(var.set_skip_connection_handshake(config[CONF_SKIP_CONNECTION_HANDSHAKE]))

View File

@ -40,7 +40,7 @@ bool Nextion::send_command_(const std::string &command) {
} }
bool Nextion::check_connect_() { bool Nextion::check_connect_() {
if (this->get_is_connected_()) if (this->is_connected_)
return true; return true;
// Check if the handshake should be skipped for the Nextion connection // Check if the handshake should be skipped for the Nextion connection
@ -280,14 +280,6 @@ void Nextion::loop() {
this->goto_page(this->start_up_page_); this->goto_page(this->start_up_page_);
} }
// This could probably be removed from the loop area, as those are redundant.
this->set_auto_wake_on_touch(this->auto_wake_on_touch_);
this->set_exit_reparse_on_start(this->exit_reparse_on_start_);
if (this->touch_sleep_timeout_ != 0) {
this->set_touch_sleep_timeout(this->touch_sleep_timeout_);
}
if (this->wake_up_page_ != -1) { if (this->wake_up_page_ != -1) {
this->set_wake_up_page(this->wake_up_page_); this->set_wake_up_page(this->wake_up_page_);
} }

View File

@ -856,76 +856,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
*/ */
void set_backlight_brightness(float brightness); void set_backlight_brightness(float brightness);
/**
* Set the touch sleep timeout of the display.
* @param timeout Timeout in seconds.
*
* Example:
* ```cpp
* it.set_touch_sleep_timeout(30);
* ```
*
* After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up
* `thup`.
*/
void set_touch_sleep_timeout(uint16_t timeout);
/**
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
* @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
* wakes up to current page.
*
* Example:
* ```cpp
* it.set_wake_up_page(2);
* ```
*
* The display will wake up to page 2.
*/
void set_wake_up_page(uint8_t page_id = 255);
/**
* Sets which page Nextion loads when connecting to ESPHome.
* @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
* wakes up to current page.
*
* Example:
* ```cpp
* it.set_start_up_page(2);
* ```
*
* The display will go to page 2 when it establishes a connection to ESPHome.
*/
void set_start_up_page(uint8_t page_id = 255);
/**
* Sets if Nextion should auto-wake from sleep when touch press occurs.
* @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode,
* the first touch will only trigger the auto wake mode and not trigger a Touch Event.
*
* Example:
* ```cpp
* it.set_auto_wake_on_touch(true);
* ```
*
* The display will wake up by touch.
*/
void set_auto_wake_on_touch(bool auto_wake);
/**
* Sets if Nextion should exit the active reparse mode before the "connect" command is sent
* @param exit_reparse True or false. When exit_reparse is true, the exit reparse command
* will be sent before requesting the connection from Nextion.
*
* Example:
* ```cpp
* it.set_exit_reparse_on_start(true);
* ```
*
* The display will be requested to leave active reparse mode before setup.
*/
void set_exit_reparse_on_start(bool exit_reparse);
/** /**
* Sets whether the Nextion display should skip the connection handshake process. * Sets whether the Nextion display should skip the connection handshake process.
* @param skip_handshake True or false. When skip_connection_handshake is true, * @param skip_handshake True or false. When skip_connection_handshake is true,
@ -1172,15 +1102,75 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
void update_components_by_prefix(const std::string &prefix); void update_components_by_prefix(const std::string &prefix);
void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) { /**
this->touch_sleep_timeout_ = touch_sleep_timeout; * Set the touch sleep timeout of the display.
} * @param timeout Timeout in seconds.
void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; } *
void set_start_up_page_internal(uint8_t start_up_page) { this->start_up_page_ = start_up_page; } * Example:
void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } * ```cpp
void set_exit_reparse_on_start_internal(bool exit_reparse_on_start) { * it.set_touch_sleep_timeout(30);
this->exit_reparse_on_start_ = exit_reparse_on_start; * ```
} *
* After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up
* `thup`.
*/
void set_touch_sleep_timeout(uint32_t touch_sleep_timeout);
/**
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
* @param wake_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
* wakes up to current page.
*
* Example:
* ```cpp
* it.set_wake_up_page(2);
* ```
*
* The display will wake up to page 2.
*/
void set_wake_up_page(uint8_t wake_up_page = 255);
/**
* Sets which page Nextion loads when connecting to ESPHome.
* @param start_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
* wakes up to current page.
*
* Example:
* ```cpp
* it.set_start_up_page(2);
* ```
*
* The display will go to page 2 when it establishes a connection to ESPHome.
*/
void set_start_up_page(uint8_t start_up_page = 255) { this->start_up_page_ = start_up_page; }
/**
* Sets if Nextion should auto-wake from sleep when touch press occurs.
* @param auto_wake_on_touch True or false. When auto_wake is true and Nextion is in sleep mode,
* the first touch will only trigger the auto wake mode and not trigger a Touch Event.
*
* Example:
* ```cpp
* it.set_auto_wake_on_touch(true);
* ```
*
* The display will wake up by touch.
*/
void set_auto_wake_on_touch(bool auto_wake_on_touch);
/**
* Sets if Nextion should exit the active reparse mode before the "connect" command is sent
* @param exit_reparse_on_start True or false. When exit_reparse_on_start is true, the exit reparse command
* will be sent before requesting the connection from Nextion.
*
* Example:
* ```cpp
* it.set_exit_reparse_on_start(true);
* ```
*
* The display will be requested to leave active reparse mode before setup.
*/
void set_exit_reparse_on_start(bool exit_reparse_on_start) { this->exit_reparse_on_start_ = exit_reparse_on_start; }
/** /**
* @brief Retrieves the number of commands pending in the Nextion command queue. * @brief Retrieves the number of commands pending in the Nextion command queue.
@ -1217,6 +1207,25 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
*/ */
bool is_updating() override; bool is_updating() override;
/**
* @brief Check if the Nextion display is successfully connected.
*
* This method returns whether a successful connection has been established with
* the Nextion display. A connection is considered established when:
*
* - The initial handshake with the display is completed successfully, or
* - The handshake is skipped via skip_connection_handshake_ flag
*
* The connection status is particularly useful when:
* - Troubleshooting communication issues
* - Ensuring the display is ready before sending commands
* - Implementing connection-dependent behaviors
*
* @return true if the Nextion display is connected and ready to receive commands
* @return false if the display is not yet connected or connection was lost
*/
bool is_connected() { return this->is_connected_; }
protected: protected:
std::deque<NextionQueue *> nextion_queue_; std::deque<NextionQueue *> nextion_queue_;
std::deque<NextionQueue *> waveform_queue_; std::deque<NextionQueue *> waveform_queue_;
@ -1315,8 +1324,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
#endif // USE_NEXTION_TFT_UPLOAD #endif // USE_NEXTION_TFT_UPLOAD
bool get_is_connected_() { return this->is_connected_; }
bool check_connect_(); bool check_connect_();
std::vector<NextionComponentBase *> touch_; std::vector<NextionComponentBase *> touch_;

View File

@ -10,19 +10,19 @@ static const char *const TAG = "nextion";
// Sleep safe commands // Sleep safe commands
void Nextion::soft_reset() { this->send_command_("rest"); } void Nextion::soft_reset() { this->send_command_("rest"); }
void Nextion::set_wake_up_page(uint8_t page_id) { void Nextion::set_wake_up_page(uint8_t wake_up_page) {
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true); this->wake_up_page_ = wake_up_page;
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", wake_up_page, true);
} }
void Nextion::set_start_up_page(uint8_t page_id) { this->start_up_page_ = page_id; } void Nextion::set_touch_sleep_timeout(uint32_t touch_sleep_timeout) {
if (touch_sleep_timeout < 3) {
void Nextion::set_touch_sleep_timeout(uint16_t timeout) {
if (timeout < 3) {
ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535"); ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535");
return; return;
} }
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true); this->touch_sleep_timeout_ = touch_sleep_timeout;
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", touch_sleep_timeout, true);
} }
void Nextion::sleep(bool sleep) { void Nextion::sleep(bool sleep) {
@ -54,7 +54,6 @@ bool Nextion::set_protocol_reparse_mode(bool active_mode) {
this->ignore_is_setup_ = false; this->ignore_is_setup_ = false;
return all_commands_sent; return all_commands_sent;
} }
void Nextion::set_exit_reparse_on_start(bool exit_reparse) { this->exit_reparse_on_start_ = exit_reparse; }
// Set Colors - Background // Set Colors - Background
void Nextion::set_component_background_color(const char *component, uint16_t color) { void Nextion::set_component_background_color(const char *component, uint16_t color) {
@ -191,8 +190,9 @@ void Nextion::set_backlight_brightness(float brightness) {
this->add_no_result_to_queue_with_printf_("backlight_brightness", "dim=%d", static_cast<int>(brightness * 100)); this->add_no_result_to_queue_with_printf_("backlight_brightness", "dim=%d", static_cast<int>(brightness * 100));
} }
void Nextion::set_auto_wake_on_touch(bool auto_wake) { void Nextion::set_auto_wake_on_touch(bool auto_wake_on_touch) {
this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0); this->auto_wake_on_touch_ = auto_wake_on_touch;
this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake_on_touch ? 1 : 0);
} }
// General Component // General Component

View File

@ -80,15 +80,7 @@ bool OnlineImage::resize_(int width_in, int height_in) {
this->width_ = width; this->width_ = width;
ESP_LOGD(TAG, "New size: (%d, %d)", width, height); ESP_LOGD(TAG, "New size: (%d, %d)", width, height);
} else { } else {
#if defined(USE_ESP8266) ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %zu Bytes", this->allocator_.get_max_free_block_size());
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
int max_block = ESP.getMaxFreeBlockSize();
#elif defined(USE_ESP32)
int max_block = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
#else
int max_block = -1;
#endif
ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %d Bytes", max_block);
this->end_connection_(); this->end_connection_();
return false; return false;
} }

View File

@ -1,10 +1,12 @@
from typing import Any from typing import Any
import logging
from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.components import sensor from esphome.components import sensor
from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266 from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266, CONF_TRIGGER_ID
from . import const, schema, validate, generate from . import const, schema, validate, generate
CODEOWNERS = ["@olegtarasov"] CODEOWNERS = ["@olegtarasov"]
@ -20,7 +22,21 @@ CONF_CH2_ACTIVE = "ch2_active"
CONF_SUMMER_MODE_ACTIVE = "summer_mode_active" CONF_SUMMER_MODE_ACTIVE = "summer_mode_active"
CONF_DHW_BLOCK = "dhw_block" CONF_DHW_BLOCK = "dhw_block"
CONF_SYNC_MODE = "sync_mode" CONF_SYNC_MODE = "sync_mode"
CONF_OPENTHERM_VERSION = "opentherm_version" CONF_OPENTHERM_VERSION = "opentherm_version" # Deprecated, will be removed
CONF_BEFORE_SEND = "before_send"
CONF_BEFORE_PROCESS_RESPONSE = "before_process_response"
# Triggers
BeforeSendTrigger = generate.opentherm_ns.class_(
"BeforeSendTrigger",
automation.Trigger.template(generate.OpenthermData.operator("ref")),
)
BeforeProcessResponseTrigger = generate.opentherm_ns.class_(
"BeforeProcessResponseTrigger",
automation.Trigger.template(generate.OpenthermData.operator("ref")),
)
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
@ -36,7 +52,19 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean, cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean,
cv.Optional(CONF_DHW_BLOCK, False): cv.boolean, cv.Optional(CONF_DHW_BLOCK, False): cv.boolean,
cv.Optional(CONF_SYNC_MODE, False): cv.boolean, cv.Optional(CONF_SYNC_MODE, False): cv.boolean,
cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float, cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float, # Deprecated
cv.Optional(CONF_BEFORE_SEND): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BeforeSendTrigger),
}
),
cv.Optional(CONF_BEFORE_PROCESS_RESPONSE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
BeforeProcessResponseTrigger
),
}
),
} }
) )
.extend( .extend(
@ -44,6 +72,11 @@ CONFIG_SCHEMA = cv.All(
schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor)) schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor))
) )
) )
.extend(
validate.create_entities_schema(
schema.SETTINGS, (lambda s: s.validation_schema)
)
)
.extend(cv.COMPONENT_SCHEMA), .extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
) )
@ -60,18 +93,33 @@ async def to_code(config: dict[str, Any]) -> None:
out_pin = await cg.gpio_pin_expression(config[CONF_OUT_PIN]) out_pin = await cg.gpio_pin_expression(config[CONF_OUT_PIN])
cg.add(var.set_out_pin(out_pin)) cg.add(var.set_out_pin(out_pin))
non_sensors = {CONF_ID, CONF_IN_PIN, CONF_OUT_PIN} non_sensors = {
CONF_ID,
CONF_IN_PIN,
CONF_OUT_PIN,
CONF_BEFORE_SEND,
CONF_BEFORE_PROCESS_RESPONSE,
}
input_sensors = [] input_sensors = []
settings = []
for key, value in config.items(): for key, value in config.items():
if key in non_sensors: if key in non_sensors:
continue continue
if key in schema.INPUTS: if key in schema.INPUTS:
input_sensor = await cg.get_variable(value) input_sensor = await cg.get_variable(value)
cg.add( cg.add(getattr(var, f"set_{key}_{const.INPUT_SENSOR}")(input_sensor))
getattr(var, f"set_{key}_{const.INPUT_SENSOR.lower()}")(input_sensor)
)
input_sensors.append(key) input_sensors.append(key)
elif key in schema.SETTINGS:
if value == schema.SETTINGS[key].default_value:
continue
cg.add(getattr(var, f"set_{key}_{const.SETTING}")(value))
settings.append(key)
else: else:
if key == CONF_OPENTHERM_VERSION:
_LOGGER.warning(
"opentherm_version is deprecated and will be removed in esphome 2025.2.0\n"
"Please change to 'opentherm_version_controller'."
)
cg.add(getattr(var, f"set_{key}")(value)) cg.add(getattr(var, f"set_{key}")(value))
if len(input_sensors) > 0: if len(input_sensors) > 0:
@ -81,3 +129,21 @@ async def to_code(config: dict[str, Any]) -> None:
) )
generate.define_readers(const.INPUT_SENSOR, input_sensors) generate.define_readers(const.INPUT_SENSOR, input_sensors)
generate.add_messages(var, input_sensors, schema.INPUTS) generate.add_messages(var, input_sensors, schema.INPUTS)
if len(settings) > 0:
generate.define_has_settings(settings, schema.SETTINGS)
generate.define_message_handler(const.SETTING, settings, schema.SETTINGS)
generate.define_setting_readers(const.SETTING, settings)
generate.add_messages(var, settings, schema.SETTINGS)
for conf in config.get(CONF_BEFORE_SEND, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(generate.OpenthermData.operator("ref"), "x")], conf
)
for conf in config.get(CONF_BEFORE_PROCESS_RESPONSE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(generate.OpenthermData.operator("ref"), "x")], conf
)

View File

@ -0,0 +1,25 @@
#pragma once
#include "esphome/core/automation.h"
#include "hub.h"
#include "opentherm.h"
namespace esphome {
namespace opentherm {
class BeforeSendTrigger : public Trigger<OpenthermData &> {
public:
BeforeSendTrigger(OpenthermHub *hub) {
hub->add_on_before_send_callback([this](OpenthermData &x) { this->trigger(x); });
}
};
class BeforeProcessResponseTrigger : public Trigger<OpenthermData &> {
public:
BeforeProcessResponseTrigger(OpenthermHub *hub) {
hub->add_on_before_process_response_callback([this](OpenthermData &x) { this->trigger(x); });
}
};
} // namespace opentherm
} // namespace esphome

View File

@ -9,3 +9,4 @@ SWITCH = "switch"
NUMBER = "number" NUMBER = "number"
OUTPUT = "output" OUTPUT = "output"
INPUT_SENSOR = "input_sensor" INPUT_SENSOR = "input_sensor"
SETTING = "setting"

View File

@ -1,13 +1,14 @@
from collections.abc import Awaitable from collections.abc import Awaitable
from typing import Any, Callable from typing import Any, Callable, Optional
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import CONF_ID from esphome.const import CONF_ID
from . import const from . import const
from .schema import TSchema from .schema import TSchema, SettingSchema
opentherm_ns = cg.esphome_ns.namespace("opentherm") opentherm_ns = cg.esphome_ns.namespace("opentherm")
OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component) OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component)
OpenthermData = opentherm_ns.class_("OpenthermData")
def define_has_component(component_type: str, keys: list[str]) -> None: def define_has_component(component_type: str, keys: list[str]) -> None:
@ -21,6 +22,24 @@ def define_has_component(component_type: str, keys: list[str]) -> None:
cg.add_define(f"OPENTHERM_HAS_{component_type.upper()}_{key}") cg.add_define(f"OPENTHERM_HAS_{component_type.upper()}_{key}")
# We need a separate set of macros for settings because there are different backing field types we need to take
# into account
def define_has_settings(keys: list[str], schemas: dict[str, SettingSchema]) -> None:
cg.add_define(
"OPENTHERM_SETTING_LIST(F, sep)",
cg.RawExpression(
" sep ".join(
map(
lambda key: f"F({schemas[key].backing_type}, {key}_setting, {schemas[key].default_value})",
keys,
)
)
),
)
for key in keys:
cg.add_define(f"OPENTHERM_HAS_SETTING_{key}")
def define_message_handler( def define_message_handler(
component_type: str, keys: list[str], schemas: dict[str, TSchema] component_type: str, keys: list[str], schemas: dict[str, TSchema]
) -> None: ) -> None:
@ -74,16 +93,30 @@ def define_readers(component_type: str, keys: list[str]) -> None:
) )
def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]): def define_setting_readers(component_type: str, keys: list[str]) -> None:
messages: set[tuple[str, bool]] = set()
for key in keys: for key in keys:
messages.add((schemas[key].message, schemas[key].keep_updated)) cg.add_define(
for msg, keep_updated in messages: f"OPENTHERM_READ_{key}",
cg.RawExpression(f"this->{key}_{component_type.lower()}"),
)
def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]):
messages: dict[str, tuple[bool, Optional[int]]] = {}
for key in keys:
messages[schemas[key].message] = (
schemas[key].keep_updated,
schemas[key].order if hasattr(schemas[key], "order") else None,
)
for msg, (keep_updated, order) in messages.items():
msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}") msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}")
if keep_updated: if keep_updated:
cg.add(hub.add_repeating_message(msg_expr)) cg.add(hub.add_repeating_message(msg_expr))
else: else:
cg.add(hub.add_initial_message(msg_expr)) if order is not None:
cg.add(hub.add_initial_message(msg_expr, order))
else:
cg.add(hub.add_initial_message(msg_expr))
def add_property_set(var: cg.MockObj, config_key: str, config: dict[str, Any]) -> None: def add_property_set(var: cg.MockObj, config_key: str, config: dict[str, Any]) -> None:

View File

@ -63,7 +63,7 @@ void write_f88(const float value, OpenthermData &data) { data.f88(value); }
OpenthermData OpenthermHub::build_request_(MessageId request_id) const { OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
OpenthermData data; OpenthermData data;
data.type = 0; data.type = 0;
data.id = 0; data.id = request_id;
data.valueHB = 0; data.valueHB = 0;
data.valueLB = 0; data.valueLB = 0;
@ -82,28 +82,13 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
// NOLINTEND // NOLINTEND
data.type = MessageType::READ_DATA; data.type = MessageType::READ_DATA;
data.id = MessageId::STATUS;
data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4) | data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4) |
(summer_mode_is_active << 5) | (dhw_blocked << 6); (summer_mode_is_active << 5) | (dhw_blocked << 6);
return data; return data;
} }
// Another special case is OpenTherm version number which is configured at hub level as a constant // Next, we start with write requests from switches and other inputs,
if (request_id == MessageId::OT_VERSION_CONTROLLER) {
data.type = MessageType::WRITE_DATA;
data.id = MessageId::OT_VERSION_CONTROLLER;
data.f88(this->opentherm_version_);
return data;
}
// Disable incomplete switch statement warnings, because the cases in each
// switch are generated based on the configured sensors and inputs.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch"
// Next, we start with the write requests from switches and other inputs,
// because we would want to write that data if it is available, rather than // because we would want to write that data if it is available, rather than
// request a read for that type (in the case that both read and write are // request a read for that type (in the case that both read and write are
// supported). // supported).
@ -116,14 +101,23 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, , OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, ,
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
OPENTHERM_SETTING_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_SETTING, ,
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
default:
break;
} }
// Finally, handle the simple read requests, which only change with the message id. // Finally, handle the simple read requests, which only change with the message id.
switch (request_id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) } switch (request_id) {
OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , )
default:
break;
}
switch (request_id) { switch (request_id) {
OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , )
default:
break;
} }
#pragma GCC diagnostic pop
// And if we get here, a message was requested which somehow wasn't handled. // And if we get here, a message was requested which somehow wasn't handled.
// This shouldn't happen due to the way the defines are configured, so we // This shouldn't happen due to the way the defines are configured, so we
@ -163,19 +157,37 @@ void OpenthermHub::setup() {
// communicate at least once every second. Sending the status request is // communicate at least once every second. Sending the status request is
// good practice anyway. // good practice anyway.
this->add_repeating_message(MessageId::STATUS); this->add_repeating_message(MessageId::STATUS);
this->write_initial_messages_(this->messages_);
// Also ensure that we start communication with the STATUS message this->message_iterator_ = this->messages_.begin();
this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::STATUS);
if (this->opentherm_version_ > 0.0f) {
this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::OT_VERSION_CONTROLLER);
}
this->current_message_iterator_ = this->initial_messages_.begin();
} }
void OpenthermHub::on_shutdown() { this->opentherm_->stop(); } void OpenthermHub::on_shutdown() { this->opentherm_->stop(); }
// Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?)
void OpenthermHub::write_initial_messages_(std::vector<MessageId> &target) { // NOLINT
std::vector<std::pair<MessageId, uint8_t>> sorted;
std::copy_if(this->configured_messages_.begin(), this->configured_messages_.end(), std::back_inserter(sorted),
[](const std::pair<MessageId, uint8_t> &pair) { return pair.second < REPEATING_MESSAGE_ORDER; });
std::sort(sorted.begin(), sorted.end(),
[](const std::pair<MessageId, uint8_t> &a, const std::pair<MessageId, uint8_t> &b) {
return a.second < b.second;
});
target.clear();
std::transform(sorted.begin(), sorted.end(), std::back_inserter(target),
[](const std::pair<MessageId, uint8_t> &pair) { return pair.first; });
}
// Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?)
void OpenthermHub::write_repeating_messages_(std::vector<MessageId> &target) { // NOLINT
target.clear();
for (auto const &pair : this->configured_messages_) {
if (pair.second == REPEATING_MESSAGE_ORDER) {
target.push_back(pair.first);
}
}
}
void OpenthermHub::loop() { void OpenthermHub::loop() {
if (this->sync_mode_) { if (this->sync_mode_) {
this->sync_loop_(); this->sync_loop_();
@ -184,29 +196,18 @@ void OpenthermHub::loop() {
auto cur_time = millis(); auto cur_time = millis();
auto const cur_mode = this->opentherm_->get_mode(); auto const cur_mode = this->opentherm_->get_mode();
if (this->handle_error_(cur_mode)) {
return;
}
switch (cur_mode) { switch (cur_mode) {
case OperationMode::WRITE: case OperationMode::WRITE:
case OperationMode::READ: case OperationMode::READ:
case OperationMode::LISTEN: case OperationMode::LISTEN:
if (!this->check_timings_(cur_time)) {
break;
}
this->last_mode_ = cur_mode;
break;
case OperationMode::ERROR_PROTOCOL:
if (this->last_mode_ == OperationMode::WRITE) {
this->handle_protocol_write_error_();
} else if (this->last_mode_ == OperationMode::READ) {
this->handle_protocol_read_error_();
}
this->stop_opentherm_();
break;
case OperationMode::ERROR_TIMEOUT:
this->handle_timeout_error_();
this->stop_opentherm_();
break; break;
case OperationMode::IDLE: case OperationMode::IDLE:
this->check_timings_(cur_time);
if (this->should_skip_loop_(cur_time)) { if (this->should_skip_loop_(cur_time)) {
break; break;
} }
@ -219,6 +220,28 @@ void OpenthermHub::loop() {
case OperationMode::RECEIVED: case OperationMode::RECEIVED:
this->read_response_(); this->read_response_();
break; break;
default:
break;
}
this->last_mode_ = cur_mode;
}
bool OpenthermHub::handle_error_(OperationMode mode) {
switch (mode) {
case OperationMode::ERROR_PROTOCOL:
// Protocol error can happen only while reading boiler response.
this->handle_protocol_error_();
return true;
case OperationMode::ERROR_TIMEOUT:
// Timeout error might happen while we wait for device to respond.
this->handle_timeout_error_();
return true;
case OperationMode::ERROR_TIMER:
// Timer error can happen only on ESP32.
this->handle_timer_error_();
return true;
default:
return false;
} }
} }
@ -237,16 +260,20 @@ void OpenthermHub::sync_loop_() {
} }
this->start_conversation_(); this->start_conversation_();
// There may be a timer error at this point
if (this->handle_error_(this->opentherm_->get_mode())) {
return;
}
// Spin while message is being sent to device
if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) { if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
ESP_LOGE(TAG, "Hub timeout triggered during send"); ESP_LOGE(TAG, "Hub timeout triggered during send");
this->stop_opentherm_(); this->stop_opentherm_();
return; return;
} }
if (this->opentherm_->is_error()) { // Check for errors and ensure we are in the right state (message sent successfully)
this->handle_protocol_write_error_(); if (this->handle_error_(this->opentherm_->get_mode())) {
this->stop_opentherm_();
return; return;
} else if (!this->opentherm_->is_sent()) { } else if (!this->opentherm_->is_sent()) {
ESP_LOGW(TAG, "Unexpected state after sending request: %s", ESP_LOGW(TAG, "Unexpected state after sending request: %s",
@ -257,19 +284,20 @@ void OpenthermHub::sync_loop_() {
// Listen for the response // Listen for the response
this->opentherm_->listen(); this->opentherm_->listen();
// There may be a timer error at this point
if (this->handle_error_(this->opentherm_->get_mode())) {
return;
}
// Spin while response is being received
if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) { if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
ESP_LOGE(TAG, "Hub timeout triggered during receive"); ESP_LOGE(TAG, "Hub timeout triggered during receive");
this->stop_opentherm_(); this->stop_opentherm_();
return; return;
} }
if (this->opentherm_->is_timeout()) { // Check for errors and ensure we are in the right state (message received successfully)
this->handle_timeout_error_(); if (this->handle_error_(this->opentherm_->get_mode())) {
this->stop_opentherm_();
return;
} else if (this->opentherm_->is_protocol_error()) {
this->handle_protocol_read_error_();
this->stop_opentherm_();
return; return;
} else if (!this->opentherm_->has_message()) { } else if (!this->opentherm_->has_message()) {
ESP_LOGW(TAG, "Unexpected state after receiving response: %s", ESP_LOGW(TAG, "Unexpected state after receiving response: %s",
@ -281,17 +309,13 @@ void OpenthermHub::sync_loop_() {
this->read_response_(); this->read_response_();
} }
bool OpenthermHub::check_timings_(uint32_t cur_time) { void OpenthermHub::check_timings_(uint32_t cur_time) {
if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) { if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) {
ESP_LOGW(TAG, ESP_LOGW(TAG,
"%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other " "%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other "
"components that might slow the loop down.", "components that might slow the loop down.",
(int) (cur_time - this->last_conversation_start_)); (int) (cur_time - this->last_conversation_start_));
this->stop_opentherm_();
return false;
} }
return true;
} }
bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const { bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
@ -304,14 +328,17 @@ bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
} }
void OpenthermHub::start_conversation_() { void OpenthermHub::start_conversation_() {
if (this->sending_initial_ && this->current_message_iterator_ == this->initial_messages_.end()) { if (this->message_iterator_ == this->messages_.end()) {
this->sending_initial_ = false; if (this->sending_initial_) {
this->current_message_iterator_ = this->repeating_messages_.begin(); this->sending_initial_ = false;
} else if (this->current_message_iterator_ == this->repeating_messages_.end()) { this->write_repeating_messages_(this->messages_);
this->current_message_iterator_ = this->repeating_messages_.begin(); }
this->message_iterator_ = this->messages_.begin();
} }
auto request = this->build_request_(*this->current_message_iterator_); auto request = this->build_request_(*this->message_iterator_);
this->before_send_callback_.call(request);
ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id, ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id,
this->opentherm_->message_id_to_str((MessageId) request.id)); this->opentherm_->message_id_to_str((MessageId) request.id));
@ -331,37 +358,48 @@ void OpenthermHub::read_response_() {
this->stop_opentherm_(); this->stop_opentherm_();
this->before_process_response_callback_.call(response);
this->process_response(response); this->process_response(response);
this->current_message_iterator_++; this->message_iterator_++;
} }
void OpenthermHub::stop_opentherm_() { void OpenthermHub::stop_opentherm_() {
this->opentherm_->stop(); this->opentherm_->stop();
this->last_conversation_end_ = millis(); this->last_conversation_end_ = millis();
} }
void OpenthermHub::handle_protocol_write_error_() {
ESP_LOGW(TAG, "Error while sending request: %s", void OpenthermHub::handle_protocol_error_() {
this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode()));
this->opentherm_->debug_data(this->last_request_);
}
void OpenthermHub::handle_protocol_read_error_() {
OpenThermError error; OpenThermError error;
this->opentherm_->get_protocol_error(error); this->opentherm_->get_protocol_error(error);
ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", ESP_LOGW(TAG, "Protocol error occured while receiving response: %s",
this->opentherm_->protocol_error_to_to_str(error.error_type)); this->opentherm_->protocol_error_to_str(error.error_type));
this->opentherm_->debug_error(error); this->opentherm_->debug_error(error);
}
void OpenthermHub::handle_timeout_error_() {
ESP_LOGW(TAG, "Receive response timed out at a protocol level");
this->stop_opentherm_(); this->stop_opentherm_();
} }
void OpenthermHub::handle_timeout_error_() {
ESP_LOGW(TAG, "Timeout while waiting for response from device");
this->stop_opentherm_();
}
void OpenthermHub::handle_timer_error_() {
this->opentherm_->report_and_reset_timer_error();
this->stop_opentherm_();
// Timer error is critical, there is no point in retrying.
this->mark_failed();
}
void OpenthermHub::dump_config() { void OpenthermHub::dump_config() {
std::vector<MessageId> initial_messages;
std::vector<MessageId> repeating_messages;
this->write_initial_messages_(initial_messages);
this->write_repeating_messages_(repeating_messages);
ESP_LOGCONFIG(TAG, "OpenTherm:"); ESP_LOGCONFIG(TAG, "OpenTherm:");
LOG_PIN(" In: ", this->in_pin_); LOG_PIN(" In: ", this->in_pin_);
LOG_PIN(" Out: ", this->out_pin_); LOG_PIN(" Out: ", this->out_pin_);
ESP_LOGCONFIG(TAG, " Sync mode: %d", this->sync_mode_); ESP_LOGCONFIG(TAG, " Sync mode: %s", YESNO(this->sync_mode_));
ESP_LOGCONFIG(TAG, " Sensors: %s", SHOW(OPENTHERM_SENSOR_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Sensors: %s", SHOW(OPENTHERM_SENSOR_LIST(ID, )));
ESP_LOGCONFIG(TAG, " Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, )));
ESP_LOGCONFIG(TAG, " Switches: %s", SHOW(OPENTHERM_SWITCH_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Switches: %s", SHOW(OPENTHERM_SWITCH_LIST(ID, )));
@ -369,12 +407,12 @@ void OpenthermHub::dump_config() {
ESP_LOGCONFIG(TAG, " Outputs: %s", SHOW(OPENTHERM_OUTPUT_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Outputs: %s", SHOW(OPENTHERM_OUTPUT_LIST(ID, )));
ESP_LOGCONFIG(TAG, " Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, )));
ESP_LOGCONFIG(TAG, " Initial requests:"); ESP_LOGCONFIG(TAG, " Initial requests:");
for (auto type : this->initial_messages_) { for (auto type : initial_messages) {
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type))); ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
} }
ESP_LOGCONFIG(TAG, " Repeating requests:"); ESP_LOGCONFIG(TAG, " Repeating requests:");
for (auto type : this->repeating_messages_) { for (auto type : repeating_messages) {
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type))); ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
} }
} }

View File

@ -38,6 +38,9 @@
namespace esphome { namespace esphome {
namespace opentherm { namespace opentherm {
static const uint8_t REPEATING_MESSAGE_ORDER = 255;
static const uint8_t INITIAL_UNORDERED_MESSAGE_ORDER = 254;
// OpenTherm component for ESPHome // OpenTherm component for ESPHome
class OpenthermHub : public Component { class OpenthermHub : public Component {
protected: protected:
@ -58,15 +61,12 @@ class OpenthermHub : public Component {
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, ) OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, )
// The set of initial messages to send on starting communication with the boiler OPENTHERM_SETTING_LIST(OPENTHERM_DECLARE_SETTING, )
std::vector<MessageId> initial_messages_;
// and the repeating messages which are sent repeatedly to update various sensors
// and boiler parameters (like the setpoint).
std::vector<MessageId> repeating_messages_;
// Indicates if we are still working on the initial requests or not
bool sending_initial_ = true; bool sending_initial_ = true;
// Index for the current request in one of the _requests sets. std::unordered_map<MessageId, uint8_t> configured_messages_;
std::vector<MessageId>::const_iterator current_message_iterator_; std::vector<MessageId> messages_;
std::vector<MessageId>::const_iterator message_iterator_;
uint32_t last_conversation_start_ = 0; uint32_t last_conversation_start_ = 0;
uint32_t last_conversation_end_ = 0; uint32_t last_conversation_end_ = 0;
@ -78,20 +78,25 @@ class OpenthermHub : public Component {
// Very likely to happen while using Dallas temperature sensors. // Very likely to happen while using Dallas temperature sensors.
bool sync_mode_ = false; bool sync_mode_ = false;
float opentherm_version_ = 0.0f; CallbackManager<void(OpenthermData &)> before_send_callback_;
CallbackManager<void(OpenthermData &)> before_process_response_callback_;
// Create OpenTherm messages based on the message id // Create OpenTherm messages based on the message id
OpenthermData build_request_(MessageId request_id) const; OpenthermData build_request_(MessageId request_id) const;
void handle_protocol_write_error_(); bool handle_error_(OperationMode mode);
void handle_protocol_read_error_(); void handle_protocol_error_();
void handle_timeout_error_(); void handle_timeout_error_();
void handle_timer_error_();
void stop_opentherm_(); void stop_opentherm_();
void start_conversation_(); void start_conversation_();
void read_response_(); void read_response_();
bool check_timings_(uint32_t cur_time); void check_timings_(uint32_t cur_time);
bool should_skip_loop_(uint32_t cur_time) const; bool should_skip_loop_(uint32_t cur_time) const;
void sync_loop_(); void sync_loop_();
void write_initial_messages_(std::vector<MessageId> &target);
void write_repeating_messages_(std::vector<MessageId> &target);
template<typename F> bool spin_wait_(uint32_t timeout, F func) { template<typename F> bool spin_wait_(uint32_t timeout, F func) {
auto start_time = millis(); auto start_time = millis();
while (func()) { while (func()) {
@ -127,13 +132,18 @@ class OpenthermHub : public Component {
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, ) OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, )
OPENTHERM_SETTING_LIST(OPENTHERM_SET_SETTING, )
// Add a request to the vector of initial requests // Add a request to the vector of initial requests
void add_initial_message(MessageId message_id) { this->initial_messages_.push_back(message_id); } void add_initial_message(MessageId message_id) {
this->configured_messages_[message_id] = INITIAL_UNORDERED_MESSAGE_ORDER;
}
void add_initial_message(MessageId message_id, uint8_t order) { this->configured_messages_[message_id] = order; }
// Add a request to the set of repeating requests. Note that a large number of repeating // Add a request to the set of repeating requests. Note that a large number of repeating
// requests will slow down communication with the boiler. Each request may take up to 1 second, // requests will slow down communication with the boiler. Each request may take up to 1 second,
// so with all sensors enabled, it may take about half a minute before a change in setpoint // so with all sensors enabled, it may take about half a minute before a change in setpoint
// will be processed. // will be processed.
void add_repeating_message(MessageId message_id) { this->repeating_messages_.push_back(message_id); } void add_repeating_message(MessageId message_id) { this->configured_messages_[message_id] = REPEATING_MESSAGE_ORDER; }
// There are seven status variables, which can either be set as a simple variable, // There are seven status variables, which can either be set as a simple variable,
// or using a switch. ch_enable and dhw_enable default to true, the others to false. // or using a switch. ch_enable and dhw_enable default to true, the others to false.
@ -149,7 +159,13 @@ class OpenthermHub : public Component {
void set_summer_mode_active(bool value) { this->summer_mode_active = value; } void set_summer_mode_active(bool value) { this->summer_mode_active = value; }
void set_dhw_block(bool value) { this->dhw_block = value; } void set_dhw_block(bool value) { this->dhw_block = value; }
void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; } void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; }
void set_opentherm_version(float value) { this->opentherm_version_ = value; }
void add_on_before_send_callback(std::function<void(OpenthermData &)> &&callback) {
this->before_send_callback_.add(std::move(callback));
}
void add_on_before_process_response_callback(std::function<void(OpenthermData &)> &&callback) {
this->before_process_response_callback_.add(std::move(callback));
}
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }

View File

@ -52,7 +52,9 @@ bool OpenTherm::initialize() {
OpenTherm::instance = this; OpenTherm::instance = this;
#endif #endif
this->in_pin_->pin_mode(gpio::FLAG_INPUT); this->in_pin_->pin_mode(gpio::FLAG_INPUT);
this->in_pin_->setup();
this->out_pin_->pin_mode(gpio::FLAG_OUTPUT); this->out_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->out_pin_->setup();
this->out_pin_->digital_write(true); this->out_pin_->digital_write(true);
#if defined(ESP32) || defined(USE_ESP_IDF) #if defined(ESP32) || defined(USE_ESP_IDF)
@ -182,7 +184,7 @@ bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) {
} }
arg->capture_ = 1; // reset counter arg->capture_ = 1; // reset counter
} else if (arg->capture_ > 0xFF) { } else if (arg->capture_ > 0xFF) {
// no change for too long, invalid mancheter encoding // no change for too long, invalid manchester encoding
arg->mode_ = OperationMode::ERROR_PROTOCOL; arg->mode_ = OperationMode::ERROR_PROTOCOL;
arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG; arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG;
arg->stop_timer_(); arg->stop_timer_();
@ -312,21 +314,31 @@ bool OpenTherm::init_esp32_timer_() {
} }
void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) { void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) {
esp_err_t result; // We will report timer errors outside of interrupt handler
this->timer_error_ = ESP_OK;
this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
result = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value); this->timer_error_ = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value);
if (result != ESP_OK) { if (this->timer_error_ != ESP_OK) {
const auto *error = esp_err_to_name(result); this->timer_error_type_ = TimerErrorType::SET_ALARM_VALUE_ERROR;
ESP_LOGE(TAG, "Failed to set alarm value. Error: %s", error); return;
}
this->timer_error_ = timer_start(this->timer_group_, this->timer_idx_);
if (this->timer_error_ != ESP_OK) {
this->timer_error_type_ = TimerErrorType::TIMER_START_ERROR;
}
}
void OpenTherm::report_and_reset_timer_error() {
if (this->timer_error_ == ESP_OK) {
return; return;
} }
result = timer_start(this->timer_group_, this->timer_idx_); ESP_LOGE(TAG, "Error occured while manipulating timer (%s): %s", this->timer_error_to_str(this->timer_error_type_),
if (result != ESP_OK) { esp_err_to_name(this->timer_error_));
const auto *error = esp_err_to_name(result);
ESP_LOGE(TAG, "Failed to start the timer. Error: %s", error); this->timer_error_ = ESP_OK;
return; this->timer_error_type_ = NO_TIMER_ERROR;
}
} }
// 5 kHz timer_ // 5 kHz timer_
@ -343,21 +355,18 @@ void IRAM_ATTR OpenTherm::start_write_timer_() {
void IRAM_ATTR OpenTherm::stop_timer_() { void IRAM_ATTR OpenTherm::stop_timer_() {
InterruptLock const lock; InterruptLock const lock;
// We will report timer errors outside of interrupt handler
this->timer_error_ = ESP_OK;
this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
esp_err_t result; this->timer_error_ = timer_pause(this->timer_group_, this->timer_idx_);
if (this->timer_error_ != ESP_OK) {
result = timer_pause(this->timer_group_, this->timer_idx_); this->timer_error_type_ = TimerErrorType::TIMER_PAUSE_ERROR;
if (result != ESP_OK) {
const auto *error = esp_err_to_name(result);
ESP_LOGE(TAG, "Failed to pause the timer. Error: %s", error);
return; return;
} }
this->timer_error_ = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0); if (this->timer_error_ != ESP_OK) {
if (result != ESP_OK) { this->timer_error_type_ = TimerErrorType::SET_COUNTER_VALUE_ERROR;
const auto *error = esp_err_to_name(result);
ESP_LOGE(TAG, "Failed to set timer counter to 0 after pausing. Error: %s", error);
return;
} }
} }
@ -386,6 +395,9 @@ void IRAM_ATTR OpenTherm::stop_timer_() {
timer1_detachInterrupt(); timer1_detachInterrupt();
} }
// There is nothing to report on ESP8266
void OpenTherm::report_and_reset_timer_error() {}
#endif // END ESP8266 #endif // END ESP8266
// https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd // https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd
@ -412,11 +424,12 @@ const char *OpenTherm::operation_mode_to_str(OperationMode mode) {
TO_STRING_MEMBER(SENT) TO_STRING_MEMBER(SENT)
TO_STRING_MEMBER(ERROR_PROTOCOL) TO_STRING_MEMBER(ERROR_PROTOCOL)
TO_STRING_MEMBER(ERROR_TIMEOUT) TO_STRING_MEMBER(ERROR_TIMEOUT)
TO_STRING_MEMBER(ERROR_TIMER)
default: default:
return "<INVALID>"; return "<INVALID>";
} }
} }
const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) { const char *OpenTherm::protocol_error_to_str(ProtocolErrorType error_type) {
switch (error_type) { switch (error_type) {
TO_STRING_MEMBER(NO_ERROR) TO_STRING_MEMBER(NO_ERROR)
TO_STRING_MEMBER(NO_TRANSITION) TO_STRING_MEMBER(NO_TRANSITION)
@ -427,6 +440,17 @@ const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) {
return "<INVALID>"; return "<INVALID>";
} }
} }
const char *OpenTherm::timer_error_to_str(TimerErrorType error_type) {
switch (error_type) {
TO_STRING_MEMBER(NO_TIMER_ERROR)
TO_STRING_MEMBER(SET_ALARM_VALUE_ERROR)
TO_STRING_MEMBER(TIMER_START_ERROR)
TO_STRING_MEMBER(TIMER_PAUSE_ERROR)
TO_STRING_MEMBER(SET_COUNTER_VALUE_ERROR)
default:
return "<INVALID>";
}
}
const char *OpenTherm::message_type_to_str(MessageType message_type) { const char *OpenTherm::message_type_to_str(MessageType message_type) {
switch (message_type) { switch (message_type) {
TO_STRING_MEMBER(READ_DATA) TO_STRING_MEMBER(READ_DATA)

View File

@ -36,11 +36,12 @@ enum OperationMode {
READ = 2, // reading 32-bit data frame READ = 2, // reading 32-bit data frame
RECEIVED = 3, // data frame received with valid start and stop bit RECEIVED = 3, // data frame received with valid start and stop bit
WRITE = 4, // writing data with timer_ WRITE = 4, // writing data to output
SENT = 5, // all data written to output SENT = 5, // all data written to output
ERROR_PROTOCOL = 8, // manchester protocol data transfer error ERROR_PROTOCOL = 8, // protocol error, can happed only during READ
ERROR_TIMEOUT = 9 // read timeout ERROR_TIMEOUT = 9, // timeout while waiting for response from device, only during LISTEN
ERROR_TIMER = 10 // error operating the ESP32 timer
}; };
enum ProtocolErrorType { enum ProtocolErrorType {
@ -51,6 +52,14 @@ enum ProtocolErrorType {
NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks
}; };
enum TimerErrorType {
NO_TIMER_ERROR = 0, // No error
SET_ALARM_VALUE_ERROR = 1, // No transition in the middle of the bit
TIMER_START_ERROR = 2, // Stop bit wasn't present when expected
TIMER_PAUSE_ERROR = 3, // Parity check didn't pass
SET_COUNTER_VALUE_ERROR = 4, // No level change for too much timer ticks
};
enum MessageType { enum MessageType {
READ_DATA = 0, READ_DATA = 0,
READ_ACK = 4, READ_ACK = 4,
@ -299,7 +308,9 @@ class OpenTherm {
* *
* @return true if last listen() or send() operation ends up with an error. * @return true if last listen() or send() operation ends up with an error.
*/ */
bool is_error() { return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL; } bool is_error() {
return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL || mode_ == ERROR_TIMER;
}
/** /**
* Indicates whether last listen() or send() operation ends up with a *timeout* error * Indicates whether last listen() or send() operation ends up with a *timeout* error
@ -313,14 +324,22 @@ class OpenTherm {
*/ */
bool is_protocol_error() { return mode_ == OperationMode::ERROR_PROTOCOL; } bool is_protocol_error() { return mode_ == OperationMode::ERROR_PROTOCOL; }
/**
* Indicates whether start_esp32_timer_() or stop_timer_() had an error. Only relevant when used on ESP32.
* @return true if there was an error.
*/
bool is_timer_error() { return mode_ == OperationMode::ERROR_TIMER; }
bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; } bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
OperationMode get_mode() { return mode_; } OperationMode get_mode() { return mode_; }
void debug_data(OpenthermData &data); void debug_data(OpenthermData &data);
void debug_error(OpenThermError &error) const; void debug_error(OpenThermError &error) const;
void report_and_reset_timer_error();
const char *protocol_error_to_to_str(ProtocolErrorType error_type); const char *protocol_error_to_str(ProtocolErrorType error_type);
const char *timer_error_to_str(TimerErrorType error_type);
const char *message_type_to_str(MessageType message_type); const char *message_type_to_str(MessageType message_type);
const char *operation_mode_to_str(OperationMode mode); const char *operation_mode_to_str(OperationMode mode);
const char *message_id_to_str(MessageId id); const char *message_id_to_str(MessageId id);
@ -349,10 +368,12 @@ class OpenTherm {
uint32_t data_; uint32_t data_;
uint8_t bit_pos_; uint8_t bit_pos_;
int32_t timeout_counter_; // <0 no timeout int32_t timeout_counter_; // <0 no timeout
int32_t device_timeout_; int32_t device_timeout_;
#if defined(ESP32) || defined(USE_ESP_IDF) #if defined(ESP32) || defined(USE_ESP_IDF)
esp_err_t timer_error_ = ESP_OK;
TimerErrorType timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
bool init_esp32_timer_(); bool init_esp32_timer_();
void start_esp32_timer_(uint64_t alarm_value); void start_esp32_timer_(uint64_t alarm_value);
#endif #endif

View File

@ -28,6 +28,9 @@ namespace opentherm {
#ifndef OPENTHERM_INPUT_SENSOR_LIST #ifndef OPENTHERM_INPUT_SENSOR_LIST
#define OPENTHERM_INPUT_SENSOR_LIST(F, sep) #define OPENTHERM_INPUT_SENSOR_LIST(F, sep)
#endif #endif
#ifndef OPENTHERM_SETTING_LIST
#define OPENTHERM_SETTING_LIST(F, sep)
#endif
// Use macros to create fields for every entity specified in the ESPHome configuration // Use macros to create fields for every entity specified in the ESPHome configuration
#define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity; #define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity;
@ -36,6 +39,7 @@ namespace opentherm {
#define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity; #define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity;
#define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity; #define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity;
#define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity; #define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity;
#define OPENTHERM_DECLARE_SETTING(type, entity, def) type entity = def;
// Setter macros // Setter macros
#define OPENTHERM_SET_SENSOR(entity) \ #define OPENTHERM_SET_SENSOR(entity) \
@ -56,6 +60,9 @@ namespace opentherm {
#define OPENTHERM_SET_INPUT_SENSOR(entity) \ #define OPENTHERM_SET_INPUT_SENSOR(entity) \
void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; } void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; }
#define OPENTHERM_SET_SETTING(type, entity, def) \
void set_##entity(type value) { this->entity = value; }
// ===== hub.cpp macros ===== // ===== hub.cpp macros =====
// *_MESSAGE_HANDLERS are generated in defines.h and look like this: // *_MESSAGE_HANDLERS are generated in defines.h and look like this:
@ -85,6 +92,9 @@ namespace opentherm {
#ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS #ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS
#define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) #define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)
#endif #endif
#ifndef OPENTHERM_SETTING_MESSAGE_HANDLERS
#define OPENTHERM_SETTING_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)
#endif
// Write data request builders // Write data request builders
#define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \ #define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \
@ -92,6 +102,7 @@ namespace opentherm {
data.type = MessageType::WRITE_DATA; \ data.type = MessageType::WRITE_DATA; \
data.id = request_id; data.id = request_id;
#define OPENTHERM_MESSAGE_WRITE_ENTITY(key, msg_data) message_data::write_##msg_data(this->key->state, data); #define OPENTHERM_MESSAGE_WRITE_ENTITY(key, msg_data) message_data::write_##msg_data(this->key->state, data);
#define OPENTHERM_MESSAGE_WRITE_SETTING(key, msg_data) message_data::write_##msg_data(this->key, data);
#define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \ #define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \
return data; \ return data; \
} }

View File

@ -2,8 +2,9 @@
# inputs of the OpenTherm component. # inputs of the OpenTherm component.
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional, TypeVar from typing import Optional, TypeVar, Any
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_EMPTY, UNIT_EMPTY,
@ -64,6 +65,7 @@ class SensorSchema(EntitySchema):
icon: Optional[str] = None icon: Optional[str] = None
device_class: Optional[str] = None device_class: Optional[str] = None
disabled_by_default: bool = False disabled_by_default: bool = False
order: Optional[int] = None
SENSORS: dict[str, SensorSchema] = { SENSORS: dict[str, SensorSchema] = {
@ -399,6 +401,7 @@ SENSORS: dict[str, SensorSchema] = {
message="OT_VERSION_DEVICE", message="OT_VERSION_DEVICE",
keep_updated=False, keep_updated=False,
message_data="f88", message_data="f88",
order=2,
), ),
"device_type": SensorSchema( "device_type": SensorSchema(
description="Device product type", description="Device product type",
@ -409,6 +412,7 @@ SENSORS: dict[str, SensorSchema] = {
message="VERSION_DEVICE", message="VERSION_DEVICE",
keep_updated=False, keep_updated=False,
message_data="u8_hb", message_data="u8_hb",
order=0,
), ),
"device_version": SensorSchema( "device_version": SensorSchema(
description="Device product version", description="Device product version",
@ -419,6 +423,7 @@ SENSORS: dict[str, SensorSchema] = {
message="VERSION_DEVICE", message="VERSION_DEVICE",
keep_updated=False, keep_updated=False,
message_data="u8_lb", message_data="u8_lb",
order=0,
), ),
"device_id": SensorSchema( "device_id": SensorSchema(
description="Device ID code", description="Device ID code",
@ -429,6 +434,7 @@ SENSORS: dict[str, SensorSchema] = {
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="u8_lb", message_data="u8_lb",
order=4,
), ),
"otc_hc_ratio_ub": SensorSchema( "otc_hc_ratio_ub": SensorSchema(
description="OTC heat curve ratio upper bound", description="OTC heat curve ratio upper bound",
@ -457,6 +463,7 @@ SENSORS: dict[str, SensorSchema] = {
class BinarySensorSchema(EntitySchema): class BinarySensorSchema(EntitySchema):
icon: Optional[str] = None icon: Optional[str] = None
device_class: Optional[str] = None device_class: Optional[str] = None
order: Optional[int] = None
BINARY_SENSORS: dict[str, BinarySensorSchema] = { BINARY_SENSORS: dict[str, BinarySensorSchema] = {
@ -525,48 +532,56 @@ BINARY_SENSORS: dict[str, BinarySensorSchema] = {
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_0", message_data="flag8_hb_0",
order=4,
), ),
"control_type_on_off": BinarySensorSchema( "control_type_on_off": BinarySensorSchema(
description="Configuration: Control type is on/off", description="Configuration: Control type is on/off",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_1", message_data="flag8_hb_1",
order=4,
), ),
"cooling_supported": BinarySensorSchema( "cooling_supported": BinarySensorSchema(
description="Configuration: Cooling supported", description="Configuration: Cooling supported",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_2", message_data="flag8_hb_2",
order=4,
), ),
"dhw_storage_tank": BinarySensorSchema( "dhw_storage_tank": BinarySensorSchema(
description="Configuration: DHW storage tank", description="Configuration: DHW storage tank",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_3", message_data="flag8_hb_3",
order=4,
), ),
"controller_pump_control_allowed": BinarySensorSchema( "controller_pump_control_allowed": BinarySensorSchema(
description="Configuration: Controller pump control allowed", description="Configuration: Controller pump control allowed",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_4", message_data="flag8_hb_4",
order=4,
), ),
"ch2_present": BinarySensorSchema( "ch2_present": BinarySensorSchema(
description="Configuration: CH2 present", description="Configuration: CH2 present",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_5", message_data="flag8_hb_5",
order=4,
), ),
"water_filling": BinarySensorSchema( "water_filling": BinarySensorSchema(
description="Configuration: Remote water filling", description="Configuration: Remote water filling",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_6", message_data="flag8_hb_6",
order=4,
), ),
"heat_mode": BinarySensorSchema( "heat_mode": BinarySensorSchema(
description="Configuration: Heating or cooling", description="Configuration: Heating or cooling",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_7", message_data="flag8_hb_7",
order=4,
), ),
"dhw_setpoint_transfer_enabled": BinarySensorSchema( "dhw_setpoint_transfer_enabled": BinarySensorSchema(
description="Remote boiler parameters: DHW setpoint transfer enabled", description="Remote boiler parameters: DHW setpoint transfer enabled",
@ -812,3 +827,65 @@ INPUTS: dict[str, InputSchema] = {
auto_max_value=AutoConfigure(message="OTC_CURVE_BOUNDS", message_data="u8_hb"), auto_max_value=AutoConfigure(message="OTC_CURVE_BOUNDS", message_data="u8_hb"),
), ),
} }
@dataclass
class SettingSchema(EntitySchema):
backing_type: str
validation_schema: cv.Schema
default_value: Any
order: Optional[int] = None
SETTINGS: dict[str, SettingSchema] = {
"controller_product_type": SettingSchema(
description="Controller product type",
message="VERSION_CONTROLLER",
keep_updated=False,
message_data="u8_hb",
backing_type="uint8_t",
validation_schema=cv.int_range(min=0, max=255),
default_value=0,
order=1,
),
"controller_product_version": SettingSchema(
description="Controller product version",
message="VERSION_CONTROLLER",
keep_updated=False,
message_data="u8_lb",
backing_type="uint8_t",
validation_schema=cv.int_range(min=0, max=255),
default_value=0,
order=1,
),
"opentherm_version_controller": SettingSchema(
description="Version of OpenTherm implemented by controller",
message="OT_VERSION_CONTROLLER",
keep_updated=False,
message_data="f88",
backing_type="float",
validation_schema=cv.positive_float,
default_value=0,
order=3,
),
"controller_configuration": SettingSchema(
description="Controller configuration",
message="CONTROLLER_CONFIG",
keep_updated=False,
message_data="u8_hb",
backing_type="uint8_t",
validation_schema=cv.int_range(min=0, max=255),
default_value=0,
order=5,
),
"controller_id": SettingSchema(
description="Controller ID code",
message="CONTROLLER_CONFIG",
keep_updated=False,
message_data="u8_lb",
backing_type="uint8_t",
validation_schema=cv.int_range(min=0, max=255),
default_value=0,
order=5,
),
}

View File

@ -9,12 +9,17 @@ from .schema import TSchema
def create_entities_schema( def create_entities_schema(
entities: dict[str, schema.EntitySchema], entities: dict[str, TSchema],
get_entity_validation_schema: Callable[[TSchema], cv.Schema], get_entity_validation_schema: Callable[[TSchema], cv.Schema],
) -> Schema: ) -> Schema:
entity_schema = {} entity_schema = {}
for key, entity in entities.items(): for key, entity in entities.items():
entity_schema[cv.Optional(key)] = get_entity_validation_schema(entity) schema_key = (
cv.Optional(key, entity.default_value)
if hasattr(entity, "default_value")
else cv.Optional(key)
)
entity_schema[schema_key] = get_entity_validation_schema(entity)
return cv.Schema(entity_schema) return cv.Schema(entity_schema)

View File

@ -13,9 +13,9 @@ PulseCounterStorageBase *get_storage(bool hw_pcnt) {
return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage) return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage)
: (PulseCounterStorageBase *) (new BasicPulseCounterStorage)); : (PulseCounterStorageBase *) (new BasicPulseCounterStorage));
} }
#else #else // HAS_PCNT
PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; } PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; }
#endif #endif // HAS_PCNT
void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) { void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) {
const uint32_t now = micros(); const uint32_t now = micros();
@ -28,14 +28,17 @@ void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg
switch (mode) { switch (mode) {
case PULSE_COUNTER_DISABLE: case PULSE_COUNTER_DISABLE:
break; break;
case PULSE_COUNTER_INCREMENT: case PULSE_COUNTER_INCREMENT: {
arg->counter++; auto x = arg->counter + 1;
break; arg->counter = x;
case PULSE_COUNTER_DECREMENT: } break;
arg->counter--; case PULSE_COUNTER_DECREMENT: {
break; auto x = arg->counter - 1;
arg->counter = x;
} break;
} }
} }
bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
this->pin = pin; this->pin = pin;
this->pin->setup(); this->pin->setup();
@ -43,6 +46,7 @@ bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
this->pin->attach_interrupt(BasicPulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); this->pin->attach_interrupt(BasicPulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE);
return true; return true;
} }
pulse_counter_t BasicPulseCounterStorage::read_raw_value() { pulse_counter_t BasicPulseCounterStorage::read_raw_value() {
pulse_counter_t counter = this->counter; pulse_counter_t counter = this->counter;
pulse_counter_t ret = counter - this->last_value; pulse_counter_t ret = counter - this->last_value;
@ -141,6 +145,7 @@ bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
} }
return true; return true;
} }
pulse_counter_t HwPulseCounterStorage::read_raw_value() { pulse_counter_t HwPulseCounterStorage::read_raw_value() {
pulse_counter_t counter; pulse_counter_t counter;
pcnt_get_counter_value(this->pcnt_unit, &counter); pcnt_get_counter_value(this->pcnt_unit, &counter);
@ -148,7 +153,7 @@ pulse_counter_t HwPulseCounterStorage::read_raw_value() {
this->last_value = counter; this->last_value = counter;
return ret; return ret;
} }
#endif #endif // HAS_PCNT
void PulseCounterSensor::setup() { void PulseCounterSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str()); ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str());

View File

@ -9,7 +9,7 @@
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) #if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
#include <driver/pcnt.h> #include <driver/pcnt.h>
#define HAS_PCNT #define HAS_PCNT
#endif #endif // defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
namespace esphome { namespace esphome {
namespace pulse_counter { namespace pulse_counter {
@ -22,9 +22,9 @@ enum PulseCounterCountMode {
#ifdef HAS_PCNT #ifdef HAS_PCNT
using pulse_counter_t = int16_t; using pulse_counter_t = int16_t;
#else #else // HAS_PCNT
using pulse_counter_t = int32_t; using pulse_counter_t = int32_t;
#endif #endif // HAS_PCNT
struct PulseCounterStorageBase { struct PulseCounterStorageBase {
virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0; virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0;
@ -57,7 +57,7 @@ struct HwPulseCounterStorage : public PulseCounterStorageBase {
pcnt_unit_t pcnt_unit; pcnt_unit_t pcnt_unit;
pcnt_channel_t pcnt_channel; pcnt_channel_t pcnt_channel;
}; };
#endif #endif // HAS_PCNT
PulseCounterStorageBase *get_storage(bool hw_pcnt = false); PulseCounterStorageBase *get_storage(bool hw_pcnt = false);

View File

@ -1 +1,4 @@
CODEOWNERS = ["@clydebarrow"] CODEOWNERS = ["@clydebarrow"]
CONF_DRAW_FROM_ORIGIN = "draw_from_origin"
CONF_DRAW_ROUNDING = "draw_rounding"

View File

@ -24,6 +24,7 @@ from esphome.const import (
) )
from esphome.core import TimePeriod from esphome.core import TimePeriod
from . import CONF_DRAW_FROM_ORIGIN, CONF_DRAW_ROUNDING
from .models import DriverChip from .models import DriverChip
DEPENDENCIES = ["spi"] DEPENDENCIES = ["spi"]
@ -41,7 +42,6 @@ COLOR_ORDERS = {
} }
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
CONF_DRAW_FROM_ORIGIN = "draw_from_origin"
DELAY_FLAG = 0xFF DELAY_FLAG = 0xFF
@ -78,56 +78,81 @@ def _validate(config):
return config return config
CONFIG_SCHEMA = cv.All( def power_of_two(value):
display.FULL_DISPLAY_SCHEMA.extend( value = cv.int_range(1, 128)(value)
cv.Schema( if value & (value - 1) != 0:
{ raise cv.Invalid("value must be a power of two")
cv.GenerateID(): cv.declare_id(QSPI_DBI), return value
cv.Required(CONF_MODEL): cv.one_of(
*DriverChip.chips.keys(), upper=True
), BASE_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend(
cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence), cv.Schema(
cv.Required(CONF_DIMENSIONS): cv.Any( {
cv.dimensions, cv.GenerateID(): cv.declare_id(QSPI_DBI),
cv.Schema( cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence),
{ cv.Required(CONF_DIMENSIONS): cv.Any(
cv.Required(CONF_WIDTH): validate_dimension, cv.dimensions,
cv.Required(CONF_HEIGHT): validate_dimension, cv.Schema(
cv.Optional(
CONF_OFFSET_HEIGHT, default=0
): validate_dimension,
cv.Optional(
CONF_OFFSET_WIDTH, default=0
): validate_dimension,
}
),
),
cv.Optional(CONF_TRANSFORM): cv.Schema(
{ {
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, cv.Required(CONF_WIDTH): validate_dimension,
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, cv.Required(CONF_HEIGHT): validate_dimension,
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, cv.Optional(CONF_OFFSET_HEIGHT, default=0): validate_dimension,
cv.Optional(CONF_OFFSET_WIDTH, default=0): validate_dimension,
} }
), ),
cv.Optional(CONF_COLOR_ORDER, default="RGB"): cv.enum( ),
COLOR_ORDERS, upper=True cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean,
), cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range(
cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, 0, 0xFF, min_included=True, max_included=True
cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range( ),
0, 0xFF, min_included=True, max_included=True }
), ).extend(
cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean, spi.spi_device_schema(
} cs_pin_required=False,
).extend( default_mode="MODE0",
spi.spi_device_schema( default_data_rate=10e6,
cs_pin_required=False, quad=True,
default_mode="MODE0",
default_data_rate=10e6,
quad=True,
)
) )
)
)
def model_property(name, defaults, fallback):
return cv.Optional(name, default=defaults.get(name, fallback))
def model_schema(defaults):
transform = cv.Schema(
{
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
}
)
if defaults.get(CONF_SWAP_XY, True):
transform = transform.extend(
{
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
}
)
return BASE_SCHEMA.extend(
{
model_property(CONF_INVERT_COLORS, defaults, False): cv.boolean,
model_property(CONF_COLOR_ORDER, defaults, "RGB"): cv.enum(
COLOR_ORDERS, upper=True
),
model_property(CONF_DRAW_ROUNDING, defaults, 2): power_of_two,
cv.Optional(CONF_TRANSFORM): transform,
}
)
CONFIG_SCHEMA = cv.All(
cv.typed_schema(
{k.upper(): model_schema(v.defaults) for k, v in DriverChip.chips.items()},
upper=True,
key=CONF_MODEL,
), ),
cv.only_with_esp_idf, cv.only_with_esp_idf,
) )
@ -152,6 +177,7 @@ async def to_code(config):
cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) cg.add(var.set_brightness(config[CONF_BRIGHTNESS]))
cg.add(var.set_model(config[CONF_MODEL])) cg.add(var.set_model(config[CONF_MODEL]))
cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN])) cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN]))
cg.add(var.set_draw_rounding(config[CONF_DRAW_ROUNDING]))
if enable_pin := config.get(CONF_ENABLE_PIN): if enable_pin := config.get(CONF_ENABLE_PIN):
enable = await cg.gpio_pin_expression(enable_pin) enable = await cg.gpio_pin_expression(enable_pin)
cg.add(var.set_enable_pin(enable)) cg.add(var.set_enable_pin(enable))
@ -163,7 +189,8 @@ async def to_code(config):
if transform := config.get(CONF_TRANSFORM): if transform := config.get(CONF_TRANSFORM):
cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) cg.add(var.set_mirror_x(transform[CONF_MIRROR_X]))
cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y]))
cg.add(var.set_swap_xy(transform[CONF_SWAP_XY])) # swap_xy is not implemented for some chips
cg.add(var.set_swap_xy(transform.get(CONF_SWAP_XY, False)))
if CONF_DIMENSIONS in config: if CONF_DIMENSIONS in config:
dimensions = config[CONF_DIMENSIONS] dimensions = config[CONF_DIMENSIONS]

View File

@ -1,5 +1,10 @@
# Commands # Commands
from esphome.const import CONF_INVERT_COLORS, CONF_SWAP_XY
from . import CONF_DRAW_ROUNDING
SW_RESET_CMD = 0x01 SW_RESET_CMD = 0x01
SLEEP_IN = 0x10
SLEEP_OUT = 0x11 SLEEP_OUT = 0x11
NORON = 0x13 NORON = 0x13
INVERT_OFF = 0x20 INVERT_OFF = 0x20
@ -24,11 +29,12 @@ PAGESEL = 0xFE
class DriverChip: class DriverChip:
chips = {} chips = {}
def __init__(self, name: str): def __init__(self, name: str, defaults=None):
name = name.upper() name = name.upper()
self.name = name self.name = name
self.chips[name] = self self.chips[name] = self
self.initsequence = [] self.initsequence = []
self.defaults = defaults or {}
def cmd(self, c, *args): def cmd(self, c, *args):
""" """
@ -59,9 +65,246 @@ chip.cmd(TEON, 0x00)
chip.cmd(PIXFMT, 0x55) chip.cmd(PIXFMT, 0x55)
chip.cmd(NORON) chip.cmd(NORON)
chip = DriverChip("AXS15231") chip = DriverChip("AXS15231", {CONF_DRAW_ROUNDING: 8, CONF_SWAP_XY: False})
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5)
chip.cmd(0xC1, 0x33) chip.cmd(0xC1, 0x33)
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
chip = DriverChip(
"JC4832W535",
{
CONF_DRAW_ROUNDING: 8,
CONF_SWAP_XY: False,
},
)
chip.cmd(DISPLAY_OFF)
chip.delay(20)
chip.cmd(SLEEP_IN)
chip.delay(80)
chip.cmd(SLEEP_OUT)
chip.cmd(INVERT_OFF)
# A magic sequence to enable the windowed drawing mode
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5)
chip.cmd(0xC1, 0x33)
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
chip = DriverChip("JC3636W518", {CONF_INVERT_COLORS: True})
chip.cmd(0xF0, 0x08)
chip.cmd(0xF2, 0x08)
chip.cmd(0x9B, 0x51)
chip.cmd(0x86, 0x53)
chip.cmd(0xF2, 0x80)
chip.cmd(0xF0, 0x00)
chip.cmd(0xF0, 0x01)
chip.cmd(0xF1, 0x01)
chip.cmd(0xB0, 0x54)
chip.cmd(0xB1, 0x3F)
chip.cmd(0xB2, 0x2A)
chip.cmd(0xB4, 0x46)
chip.cmd(0xB5, 0x34)
chip.cmd(0xB6, 0xD5)
chip.cmd(0xB7, 0x30)
chip.cmd(0xBA, 0x00)
chip.cmd(0xBB, 0x08)
chip.cmd(0xBC, 0x08)
chip.cmd(0xBD, 0x00)
chip.cmd(0xC0, 0x80)
chip.cmd(0xC1, 0x10)
chip.cmd(0xC2, 0x37)
chip.cmd(0xC3, 0x80)
chip.cmd(0xC4, 0x10)
chip.cmd(0xC5, 0x37)
chip.cmd(0xC6, 0xA9)
chip.cmd(0xC7, 0x41)
chip.cmd(0xC8, 0x51)
chip.cmd(0xC9, 0xA9)
chip.cmd(0xCA, 0x41)
chip.cmd(0xCB, 0x51)
chip.cmd(0xD0, 0x91)
chip.cmd(0xD1, 0x68)
chip.cmd(0xD2, 0x69)
chip.cmd(0xF5, 0x00, 0xA5)
chip.cmd(0xDD, 0x3F)
chip.cmd(0xDE, 0x3F)
chip.cmd(0xF1, 0x10)
chip.cmd(0xF0, 0x00)
chip.cmd(0xF0, 0x02)
chip.cmd(
0xE0,
0x70,
0x09,
0x12,
0x0C,
0x0B,
0x27,
0x38,
0x54,
0x4E,
0x19,
0x15,
0x15,
0x2C,
0x2F,
)
chip.cmd(
0xE1,
0x70,
0x08,
0x11,
0x0C,
0x0B,
0x27,
0x38,
0x43,
0x4C,
0x18,
0x14,
0x14,
0x2B,
0x2D,
)
chip.cmd(0xF0, 0x10)
chip.cmd(0xF3, 0x10)
chip.cmd(0xE0, 0x08)
chip.cmd(0xE1, 0x00)
chip.cmd(0xE2, 0x00)
chip.cmd(0xE3, 0x00)
chip.cmd(0xE4, 0xE0)
chip.cmd(0xE5, 0x06)
chip.cmd(0xE6, 0x21)
chip.cmd(0xE7, 0x00)
chip.cmd(0xE8, 0x05)
chip.cmd(0xE9, 0x82)
chip.cmd(0xEA, 0xDF)
chip.cmd(0xEB, 0x89)
chip.cmd(0xEC, 0x20)
chip.cmd(0xED, 0x14)
chip.cmd(0xEE, 0xFF)
chip.cmd(0xEF, 0x00)
chip.cmd(0xF8, 0xFF)
chip.cmd(0xF9, 0x00)
chip.cmd(0xFA, 0x00)
chip.cmd(0xFB, 0x30)
chip.cmd(0xFC, 0x00)
chip.cmd(0xFD, 0x00)
chip.cmd(0xFE, 0x00)
chip.cmd(0xFF, 0x00)
chip.cmd(0x60, 0x42)
chip.cmd(0x61, 0xE0)
chip.cmd(0x62, 0x40)
chip.cmd(0x63, 0x40)
chip.cmd(0x64, 0x02)
chip.cmd(0x65, 0x00)
chip.cmd(0x66, 0x40)
chip.cmd(0x67, 0x03)
chip.cmd(0x68, 0x00)
chip.cmd(0x69, 0x00)
chip.cmd(0x6A, 0x00)
chip.cmd(0x6B, 0x00)
chip.cmd(0x70, 0x42)
chip.cmd(0x71, 0xE0)
chip.cmd(0x72, 0x40)
chip.cmd(0x73, 0x40)
chip.cmd(0x74, 0x02)
chip.cmd(0x75, 0x00)
chip.cmd(0x76, 0x40)
chip.cmd(0x77, 0x03)
chip.cmd(0x78, 0x00)
chip.cmd(0x79, 0x00)
chip.cmd(0x7A, 0x00)
chip.cmd(0x7B, 0x00)
chip.cmd(0x80, 0x48)
chip.cmd(0x81, 0x00)
chip.cmd(0x82, 0x05)
chip.cmd(0x83, 0x02)
chip.cmd(0x84, 0xDD)
chip.cmd(0x85, 0x00)
chip.cmd(0x86, 0x00)
chip.cmd(0x87, 0x00)
chip.cmd(0x88, 0x48)
chip.cmd(0x89, 0x00)
chip.cmd(0x8A, 0x07)
chip.cmd(0x8B, 0x02)
chip.cmd(0x8C, 0xDF)
chip.cmd(0x8D, 0x00)
chip.cmd(0x8E, 0x00)
chip.cmd(0x8F, 0x00)
chip.cmd(0x90, 0x48)
chip.cmd(0x91, 0x00)
chip.cmd(0x92, 0x09)
chip.cmd(0x93, 0x02)
chip.cmd(0x94, 0xE1)
chip.cmd(0x95, 0x00)
chip.cmd(0x96, 0x00)
chip.cmd(0x97, 0x00)
chip.cmd(0x98, 0x48)
chip.cmd(0x99, 0x00)
chip.cmd(0x9A, 0x0B)
chip.cmd(0x9B, 0x02)
chip.cmd(0x9C, 0xE3)
chip.cmd(0x9D, 0x00)
chip.cmd(0x9E, 0x00)
chip.cmd(0x9F, 0x00)
chip.cmd(0xA0, 0x48)
chip.cmd(0xA1, 0x00)
chip.cmd(0xA2, 0x04)
chip.cmd(0xA3, 0x02)
chip.cmd(0xA4, 0xDC)
chip.cmd(0xA5, 0x00)
chip.cmd(0xA6, 0x00)
chip.cmd(0xA7, 0x00)
chip.cmd(0xA8, 0x48)
chip.cmd(0xA9, 0x00)
chip.cmd(0xAA, 0x06)
chip.cmd(0xAB, 0x02)
chip.cmd(0xAC, 0xDE)
chip.cmd(0xAD, 0x00)
chip.cmd(0xAE, 0x00)
chip.cmd(0xAF, 0x00)
chip.cmd(0xB0, 0x48)
chip.cmd(0xB1, 0x00)
chip.cmd(0xB2, 0x08)
chip.cmd(0xB3, 0x02)
chip.cmd(0xB4, 0xE0)
chip.cmd(0xB5, 0x00)
chip.cmd(0xB6, 0x00)
chip.cmd(0xB7, 0x00)
chip.cmd(0xB8, 0x48)
chip.cmd(0xB9, 0x00)
chip.cmd(0xBA, 0x0A)
chip.cmd(0xBB, 0x02)
chip.cmd(0xBC, 0xE2)
chip.cmd(0xBD, 0x00)
chip.cmd(0xBE, 0x00)
chip.cmd(0xBF, 0x00)
chip.cmd(0xC0, 0x12)
chip.cmd(0xC1, 0xAA)
chip.cmd(0xC2, 0x65)
chip.cmd(0xC3, 0x74)
chip.cmd(0xC4, 0x47)
chip.cmd(0xC5, 0x56)
chip.cmd(0xC6, 0x00)
chip.cmd(0xC7, 0x88)
chip.cmd(0xC8, 0x99)
chip.cmd(0xC9, 0x33)
chip.cmd(0xD0, 0x21)
chip.cmd(0xD1, 0xAA)
chip.cmd(0xD2, 0x65)
chip.cmd(0xD3, 0x74)
chip.cmd(0xD4, 0x47)
chip.cmd(0xD5, 0x56)
chip.cmd(0xD6, 0x00)
chip.cmd(0xD7, 0x88)
chip.cmd(0xD8, 0x99)
chip.cmd(0xD9, 0x33)
chip.cmd(0xF3, 0x01)
chip.cmd(0xF0, 0x00)
chip.cmd(0xF0, 0x01)
chip.cmd(0xF1, 0x01)
chip.cmd(0xA0, 0x0B)
chip.cmd(0xA3, 0x2A)
chip.cmd(0xA5, 0xC3)
chip.cmd(PIXFMT, 0x55)
DriverChip("Custom") DriverChip("Custom")

View File

@ -33,19 +33,12 @@ void QspiDbi::update() {
this->do_update_(); this->do_update_();
if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_) if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
return; return;
// Start addresses and widths/heights must be divisible by 2 (CASET/RASET restriction in datasheet) // Some chips require that the drawing window be aligned on certain boundaries
if (this->x_low_ % 2 == 1) { auto dr = this->draw_rounding_;
this->x_low_--; this->x_low_ = this->x_low_ / dr * dr;
} this->y_low_ = this->y_low_ / dr * dr;
if (this->x_high_ % 2 == 0) { this->x_high_ = (this->x_high_ + dr) / dr * dr - 1;
this->x_high_++; this->y_high_ = (this->y_high_ + dr) / dr * dr - 1;
}
if (this->y_low_ % 2 == 1) {
this->y_low_--;
}
if (this->y_high_ % 2 == 0) {
this->y_high_++;
}
if (this->draw_from_origin_) { if (this->draw_from_origin_) {
this->x_low_ = 0; this->x_low_ = 0;
this->y_low_ = 0; this->y_low_ = 0;
@ -175,10 +168,9 @@ void QspiDbi::write_to_display_(int x_start, int y_start, int w, int h, const ui
this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, ptr, w * h * 2, 4); this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, ptr, w * h * 2, 4);
} else { } else {
auto stride = x_offset + w + x_pad; auto stride = x_offset + w + x_pad;
uint16_t cmd = 0x2C00; this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, nullptr, 0, 4);
for (int y = 0; y != h; y++) { for (int y = 0; y != h; y++) {
this->write_cmd_addr_data(8, 0x32, 24, cmd, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4); this->write_cmd_addr_data(0, 0, 0, 0, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4);
cmd = 0x3C00;
} }
} }
this->disable(); this->disable();
@ -220,6 +212,7 @@ void QspiDbi::dump_config() {
ESP_LOGCONFIG("", "Model: %s", this->model_); ESP_LOGCONFIG("", "Model: %s", this->model_);
ESP_LOGCONFIG(TAG, " Height: %u", this->height_); ESP_LOGCONFIG(TAG, " Height: %u", this->height_);
ESP_LOGCONFIG(TAG, " Width: %u", this->width_); ESP_LOGCONFIG(TAG, " Width: %u", this->width_);
ESP_LOGCONFIG(TAG, " Draw rounding: %u", this->draw_rounding_);
LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));

View File

@ -4,12 +4,10 @@
#pragma once #pragma once
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
#include "esphome/core/component.h"
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
#include "esphome/components/display/display.h" #include "esphome/components/display/display.h"
#include "esphome/components/display/display_buffer.h" #include "esphome/components/display/display_buffer.h"
#include "esphome/components/display/display_color_utils.h" #include "esphome/components/display/display_color_utils.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h" #include "esp_lcd_panel_rgb.h"
@ -105,6 +103,7 @@ class QspiDbi : public display::DisplayBuffer,
int get_height_internal() override { return this->height_; } int get_height_internal() override { return this->height_; }
bool can_proceed() override { return this->setup_complete_; } bool can_proceed() override { return this->setup_complete_; }
void add_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequences_.push_back(sequence); } void add_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequences_.push_back(sequence); }
void set_draw_rounding(unsigned rounding) { this->draw_rounding_ = rounding; }
protected: protected:
void check_buffer_() { void check_buffer_() {
@ -161,6 +160,7 @@ class QspiDbi : public display::DisplayBuffer,
bool mirror_x_{}; bool mirror_x_{};
bool mirror_y_{}; bool mirror_y_{};
bool draw_from_origin_{false}; bool draw_from_origin_{false};
unsigned draw_rounding_{2};
uint8_t brightness_{0xD0}; uint8_t brightness_{0xD0};
const char *model_{"Unknown"}; const char *model_{"Unknown"};
std::vector<std::vector<uint8_t>> init_sequences_{}; std::vector<std::vector<uint8_t>> init_sequences_{};

View File

@ -8,7 +8,7 @@ namespace remote_base {
static const char *const TAG = "remote_base"; static const char *const TAG = "remote_base";
#ifdef USE_ESP32 #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5
RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_block_num) { RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_block_num) {
static rmt_channel_t next_rmt_channel = RMT_CHANNEL_0; static rmt_channel_t next_rmt_channel = RMT_CHANNEL_0;
this->channel_ = next_rmt_channel; this->channel_ = next_rmt_channel;

View File

@ -8,7 +8,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#ifdef USE_ESP32 #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5
#include <driver/rmt.h> #include <driver/rmt.h>
#endif #endif
@ -112,25 +112,43 @@ class RemoteComponentBase {
#ifdef USE_ESP32 #ifdef USE_ESP32
class RemoteRMTChannel { class RemoteRMTChannel {
public: public:
#if ESP_IDF_VERSION_MAJOR >= 5
void set_clock_resolution(uint32_t clock_resolution) { this->clock_resolution_ = clock_resolution; }
void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; }
#else
explicit RemoteRMTChannel(uint8_t mem_block_num = 1); explicit RemoteRMTChannel(uint8_t mem_block_num = 1);
explicit RemoteRMTChannel(rmt_channel_t channel, uint8_t mem_block_num = 1); explicit RemoteRMTChannel(rmt_channel_t channel, uint8_t mem_block_num = 1);
void config_rmt(rmt_config_t &rmt); void config_rmt(rmt_config_t &rmt);
void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; } void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; }
#endif
protected: protected:
uint32_t from_microseconds_(uint32_t us) { uint32_t from_microseconds_(uint32_t us) {
#if ESP_IDF_VERSION_MAJOR >= 5
const uint32_t ticks_per_ten_us = this->clock_resolution_ / 100000u;
#else
const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u;
#endif
return us * ticks_per_ten_us / 10; return us * ticks_per_ten_us / 10;
} }
uint32_t to_microseconds_(uint32_t ticks) { uint32_t to_microseconds_(uint32_t ticks) {
#if ESP_IDF_VERSION_MAJOR >= 5
const uint32_t ticks_per_ten_us = this->clock_resolution_ / 100000u;
#else
const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u;
#endif
return (ticks * 10) / ticks_per_ten_us; return (ticks * 10) / ticks_per_ten_us;
} }
RemoteComponentBase *remote_base_; RemoteComponentBase *remote_base_;
#if ESP_IDF_VERSION_MAJOR >= 5
uint32_t clock_resolution_{1000000};
uint32_t rmt_symbols_;
#else
rmt_channel_t channel_{RMT_CHANNEL_0}; rmt_channel_t channel_{RMT_CHANNEL_0};
uint8_t mem_block_num_; uint8_t mem_block_num_;
uint8_t clock_divider_{80}; uint8_t clock_divider_{80};
#endif
}; };
#endif #endif

View File

@ -1,10 +1,11 @@
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_rmt, remote_base from esphome.components import esp32, esp32_rmt, remote_base
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_BUFFER_SIZE, CONF_BUFFER_SIZE,
CONF_CLOCK_DIVIDER, CONF_CLOCK_DIVIDER,
CONF_CLOCK_RESOLUTION,
CONF_DUMP, CONF_DUMP,
CONF_FILTER, CONF_FILTER,
CONF_ID, CONF_ID,
@ -12,12 +13,17 @@ from esphome.const import (
CONF_MEMORY_BLOCKS, CONF_MEMORY_BLOCKS,
CONF_PIN, CONF_PIN,
CONF_RMT_CHANNEL, CONF_RMT_CHANNEL,
CONF_RMT_SYMBOLS,
CONF_TOLERANCE, CONF_TOLERANCE,
CONF_TYPE, CONF_TYPE,
CONF_USE_DMA,
CONF_VALUE, CONF_VALUE,
) )
from esphome.core import CORE, TimePeriod from esphome.core import CORE, TimePeriod
CONF_FILTER_SYMBOLS = "filter_symbols"
CONF_RECEIVE_SYMBOLS = "receive_symbols"
AUTO_LOAD = ["remote_base"] AUTO_LOAD = ["remote_base"]
remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver") remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver")
remote_base_ns = cg.esphome_ns.namespace("remote_base") remote_base_ns = cg.esphome_ns.namespace("remote_base")
@ -97,15 +103,43 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
cv.positive_time_period_microseconds, cv.positive_time_period_microseconds,
cv.Range(max=TimePeriod(microseconds=4294967295)), cv.Range(max=TimePeriod(microseconds=4294967295)),
), ),
cv.SplitDefault(CONF_CLOCK_DIVIDER, esp32=80): cv.All( cv.SplitDefault(CONF_CLOCK_DIVIDER, esp32_arduino=80): cv.All(
cv.only_on_esp32, cv.Range(min=1, max=255) cv.only_on_esp32,
cv.only_with_arduino,
cv.int_range(min=1, max=255),
),
cv.Optional(CONF_CLOCK_RESOLUTION): cv.All(
cv.only_on_esp32,
cv.only_with_esp_idf,
esp32_rmt.validate_clock_resolution(),
), ),
cv.Optional(CONF_IDLE, default="10ms"): cv.All( cv.Optional(CONF_IDLE, default="10ms"): cv.All(
cv.positive_time_period_microseconds, cv.positive_time_period_microseconds,
cv.Range(max=TimePeriod(microseconds=4294967295)), cv.Range(max=TimePeriod(microseconds=4294967295)),
), ),
cv.Optional(CONF_MEMORY_BLOCKS, default=3): cv.Range(min=1, max=8), cv.SplitDefault(CONF_MEMORY_BLOCKS, esp32_arduino=3): cv.All(
cv.Optional(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=False), cv.only_with_arduino, cv.int_range(min=1, max=8)
),
cv.Optional(CONF_RMT_CHANNEL): cv.All(
cv.only_with_arduino, esp32_rmt.validate_rmt_channel(tx=False)
),
cv.SplitDefault(
CONF_RMT_SYMBOLS,
esp32_idf=192,
esp32_s2_idf=192,
esp32_s3_idf=192,
esp32_c3_idf=96,
esp32_c6_idf=96,
esp32_h2_idf=96,
): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)),
cv.Optional(CONF_FILTER_SYMBOLS): cv.All(
cv.only_with_esp_idf, cv.int_range(min=0)
),
cv.SplitDefault(
CONF_RECEIVE_SYMBOLS,
esp32_idf=192,
): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)),
cv.Optional(CONF_USE_DMA): cv.All(cv.only_with_esp_idf, cv.boolean),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
) )
@ -114,13 +148,27 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
async def to_code(config): async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN]) pin = await cg.gpio_pin_expression(config[CONF_PIN])
if CORE.is_esp32: if CORE.is_esp32:
if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: if esp32_rmt.use_new_rmt_driver():
var = cg.new_Pvariable( var = cg.new_Pvariable(config[CONF_ID], pin)
config[CONF_ID], pin, rmt_channel, config[CONF_MEMORY_BLOCKS] cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
) cg.add(var.set_receive_symbols(config[CONF_RECEIVE_SYMBOLS]))
if CONF_USE_DMA in config:
cg.add(var.set_with_dma(config[CONF_USE_DMA]))
if CONF_CLOCK_RESOLUTION in config:
cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION]))
if CONF_FILTER_SYMBOLS in config:
cg.add(var.set_filter_symbols(config[CONF_FILTER_SYMBOLS]))
if CORE.using_esp_idf:
esp32.add_idf_sdkconfig_option("CONFIG_RMT_RECV_FUNC_IN_IRAM", True)
esp32.add_idf_sdkconfig_option("CONFIG_RMT_ISR_IRAM_SAFE", True)
else: else:
var = cg.new_Pvariable(config[CONF_ID], pin, config[CONF_MEMORY_BLOCKS]) if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None:
cg.add(var.set_clock_divider(config[CONF_CLOCK_DIVIDER])) var = cg.new_Pvariable(
config[CONF_ID], pin, rmt_channel, config[CONF_MEMORY_BLOCKS]
)
else:
var = cg.new_Pvariable(config[CONF_ID], pin, config[CONF_MEMORY_BLOCKS])
cg.add(var.set_clock_divider(config[CONF_CLOCK_DIVIDER]))
else: else:
var = cg.new_Pvariable(config[CONF_ID], pin) var = cg.new_Pvariable(config[CONF_ID], pin)

View File

@ -5,6 +5,10 @@
#include <cinttypes> #include <cinttypes>
#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5
#include <driver/rmt_rx.h>
#endif
namespace esphome { namespace esphome {
namespace remote_receiver { namespace remote_receiver {
@ -25,6 +29,21 @@ struct RemoteReceiverComponentStore {
uint32_t filter_us{10}; uint32_t filter_us{10};
ISRInternalGPIOPin pin; ISRInternalGPIOPin pin;
}; };
#elif defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5
struct RemoteReceiverComponentStore {
/// Stores RMT symbols and rx done event data
volatile uint8_t *buffer{nullptr};
/// The position last written to
volatile uint32_t buffer_write{0};
/// The position last read from
volatile uint32_t buffer_read{0};
bool overflow{false};
uint32_t buffer_size{1000};
uint32_t receive_size{0};
uint32_t filter_symbols{0};
esp_err_t error{ESP_OK};
rmt_receive_config_t config;
};
#endif #endif
class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
@ -33,9 +52,10 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
, ,
public remote_base::RemoteRMTChannel public remote_base::RemoteRMTChannel
#endif #endif
{ {
public: public:
#ifdef USE_ESP32 #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5
RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1)
: RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} : RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {}
@ -49,19 +69,32 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
void loop() override; void loop() override;
float get_setup_priority() const override { return setup_priority::DATA; } float get_setup_priority() const override { return setup_priority::DATA; }
#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5
void set_filter_symbols(uint32_t filter_symbols) { this->filter_symbols_ = filter_symbols; }
void set_receive_symbols(uint32_t receive_symbols) { this->receive_symbols_ = receive_symbols; }
void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; }
#endif
void set_buffer_size(uint32_t buffer_size) { this->buffer_size_ = buffer_size; } void set_buffer_size(uint32_t buffer_size) { this->buffer_size_ = buffer_size; }
void set_filter_us(uint32_t filter_us) { this->filter_us_ = filter_us; } void set_filter_us(uint32_t filter_us) { this->filter_us_ = filter_us; }
void set_idle_us(uint32_t idle_us) { this->idle_us_ = idle_us; } void set_idle_us(uint32_t idle_us) { this->idle_us_ = idle_us; }
protected: protected:
#ifdef USE_ESP32 #ifdef USE_ESP32
void decode_rmt_(rmt_item32_t *item, size_t len); #if ESP_IDF_VERSION_MAJOR >= 5
void decode_rmt_(rmt_symbol_word_t *item, size_t item_count);
rmt_channel_handle_t channel_{NULL};
uint32_t filter_symbols_{0};
uint32_t receive_symbols_{0};
bool with_dma_{false};
#else
void decode_rmt_(rmt_item32_t *item, size_t item_count);
RingbufHandle_t ringbuf_; RingbufHandle_t ringbuf_;
#endif
esp_err_t error_code_{ESP_OK}; esp_err_t error_code_{ESP_OK};
std::string error_string_{""}; std::string error_string_{""};
#endif #endif
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) #if defined(USE_ESP8266) || defined(USE_LIBRETINY) || (defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5)
RemoteReceiverComponentStore store_; RemoteReceiverComponentStore store_;
HighFrequencyLoopRequester high_freq_; HighFrequencyLoopRequester high_freq_;
#endif #endif

View File

@ -2,15 +2,104 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <driver/rmt.h>
namespace esphome { namespace esphome {
namespace remote_receiver { namespace remote_receiver {
static const char *const TAG = "remote_receiver.esp32"; static const char *const TAG = "remote_receiver.esp32";
#ifdef USE_ESP32_VARIANT_ESP32H2
static const uint32_t RMT_CLK_FREQ = 32000000;
#else
static const uint32_t RMT_CLK_FREQ = 80000000;
#endif
#if ESP_IDF_VERSION_MAJOR >= 5
static bool IRAM_ATTR HOT rmt_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *event, void *arg) {
RemoteReceiverComponentStore *store = (RemoteReceiverComponentStore *) arg;
rmt_rx_done_event_data_t *event_buffer = (rmt_rx_done_event_data_t *) (store->buffer + store->buffer_write);
uint32_t event_size = sizeof(rmt_rx_done_event_data_t);
uint32_t next_write = store->buffer_write + event_size + event->num_symbols * sizeof(rmt_symbol_word_t);
if (next_write + event_size + store->receive_size > store->buffer_size) {
next_write = 0;
}
if (store->buffer_read - next_write < event_size + store->receive_size) {
next_write = store->buffer_write;
store->overflow = true;
}
if (event->num_symbols <= store->filter_symbols) {
next_write = store->buffer_write;
}
store->error =
rmt_receive(channel, (uint8_t *) store->buffer + next_write + event_size, store->receive_size, &store->config);
event_buffer->num_symbols = event->num_symbols;
event_buffer->received_symbols = event->received_symbols;
store->buffer_write = next_write;
return false;
}
#endif
void RemoteReceiverComponent::setup() { void RemoteReceiverComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up Remote Receiver..."); ESP_LOGCONFIG(TAG, "Setting up Remote Receiver...");
#if ESP_IDF_VERSION_MAJOR >= 5
rmt_rx_channel_config_t channel;
memset(&channel, 0, sizeof(channel));
channel.clk_src = RMT_CLK_SRC_DEFAULT;
channel.resolution_hz = this->clock_resolution_;
channel.mem_block_symbols = rmt_symbols_;
channel.gpio_num = gpio_num_t(this->pin_->get_pin());
channel.intr_priority = 0;
channel.flags.invert_in = 0;
channel.flags.with_dma = this->with_dma_;
channel.flags.io_loop_back = 0;
esp_err_t error = rmt_new_rx_channel(&channel, &this->channel_);
if (error != ESP_OK) {
this->error_code_ = error;
if (error == ESP_ERR_NOT_FOUND) {
this->error_string_ = "out of RMT symbol memory";
} else {
this->error_string_ = "in rmt_new_rx_channel";
}
this->mark_failed();
return;
}
error = rmt_enable(this->channel_);
if (error != ESP_OK) {
this->error_code_ = error;
this->error_string_ = "in rmt_enable";
this->mark_failed();
return;
}
rmt_rx_event_callbacks_t callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.on_recv_done = rmt_callback;
error = rmt_rx_register_event_callbacks(this->channel_, &callbacks, &this->store_);
if (error != ESP_OK) {
this->error_code_ = error;
this->error_string_ = "in rmt_rx_register_event_callbacks";
this->mark_failed();
return;
}
uint32_t event_size = sizeof(rmt_rx_done_event_data_t);
uint32_t max_filter_ns = 255u * 1000 / (RMT_CLK_FREQ / 1000000);
uint32_t max_idle_ns = 65535u * 1000;
memset(&this->store_.config, 0, sizeof(this->store_.config));
this->store_.config.signal_range_min_ns = std::min(this->filter_us_ * 1000, max_filter_ns);
this->store_.config.signal_range_max_ns = std::min(this->idle_us_ * 1000, max_idle_ns);
this->store_.filter_symbols = this->filter_symbols_;
this->store_.receive_size = this->receive_symbols_ * sizeof(rmt_symbol_word_t);
this->store_.buffer_size = std::max((event_size + this->store_.receive_size) * 2, this->buffer_size_);
this->store_.buffer = new uint8_t[this->buffer_size_];
error = rmt_receive(this->channel_, (uint8_t *) this->store_.buffer + event_size, this->store_.receive_size,
&this->store_.config);
if (error != ESP_OK) {
this->error_code_ = error;
this->error_string_ = "in rmt_receive";
this->mark_failed();
return;
}
#else
this->pin_->setup(); this->pin_->setup();
rmt_config_t rmt{}; rmt_config_t rmt{};
this->config_rmt(rmt); this->config_rmt(rmt);
@ -59,7 +148,9 @@ void RemoteReceiverComponent::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
#endif
} }
void RemoteReceiverComponent::dump_config() { void RemoteReceiverComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Remote Receiver:"); ESP_LOGCONFIG(TAG, "Remote Receiver:");
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->pin_);
@ -67,9 +158,16 @@ void RemoteReceiverComponent::dump_config() {
ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to " ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to "
"invert the signal using 'inverted: True' in the pin schema!"); "invert the signal using 'inverted: True' in the pin schema!");
} }
#if ESP_IDF_VERSION_MAJOR >= 5
ESP_LOGCONFIG(TAG, " Clock resolution: %" PRIu32 " hz", this->clock_resolution_);
ESP_LOGCONFIG(TAG, " RMT symbols: %" PRIu32, this->rmt_symbols_);
ESP_LOGCONFIG(TAG, " Filter symbols: %" PRIu32, this->filter_symbols_);
ESP_LOGCONFIG(TAG, " Receive symbols: %" PRIu32, this->receive_symbols_);
#else
ESP_LOGCONFIG(TAG, " Channel: %d", this->channel_); ESP_LOGCONFIG(TAG, " Channel: %d", this->channel_);
ESP_LOGCONFIG(TAG, " RMT memory blocks: %d", this->mem_block_num_); ESP_LOGCONFIG(TAG, " RMT memory blocks: %d", this->mem_block_num_);
ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_); ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_);
#endif
ESP_LOGCONFIG(TAG, " Tolerance: %" PRIu32 "%s", this->tolerance_, ESP_LOGCONFIG(TAG, " Tolerance: %" PRIu32 "%s", this->tolerance_,
(this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%"); (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%");
ESP_LOGCONFIG(TAG, " Filter out pulses shorter than: %" PRIu32 " us", this->filter_us_); ESP_LOGCONFIG(TAG, " Filter out pulses shorter than: %" PRIu32 " us", this->filter_us_);
@ -81,10 +179,38 @@ void RemoteReceiverComponent::dump_config() {
} }
void RemoteReceiverComponent::loop() { void RemoteReceiverComponent::loop() {
#if ESP_IDF_VERSION_MAJOR >= 5
if (this->store_.error != ESP_OK) {
ESP_LOGE(TAG, "Receive error");
this->error_code_ = this->store_.error;
this->error_string_ = "in rmt_callback";
this->mark_failed();
}
if (this->store_.overflow) {
ESP_LOGW(TAG, "Buffer overflow");
this->store_.overflow = false;
}
uint32_t buffer_write = this->store_.buffer_write;
while (this->store_.buffer_read != buffer_write) {
rmt_rx_done_event_data_t *event = (rmt_rx_done_event_data_t *) (this->store_.buffer + this->store_.buffer_read);
uint32_t event_size = sizeof(rmt_rx_done_event_data_t);
uint32_t next_read = this->store_.buffer_read + event_size + event->num_symbols * sizeof(rmt_symbol_word_t);
if (next_read + event_size + this->store_.receive_size > this->store_.buffer_size) {
next_read = 0;
}
this->decode_rmt_(event->received_symbols, event->num_symbols);
this->store_.buffer_read = next_read;
if (!this->temp_.empty()) {
this->temp_.push_back(-this->idle_us_);
this->call_listeners_dumpers_();
}
}
#else
size_t len = 0; size_t len = 0;
auto *item = (rmt_item32_t *) xRingbufferReceive(this->ringbuf_, &len, 0); auto *item = (rmt_item32_t *) xRingbufferReceive(this->ringbuf_, &len, 0);
if (item != nullptr) { if (item != nullptr) {
this->decode_rmt_(item, len); this->decode_rmt_(item, len / sizeof(rmt_item32_t));
vRingbufferReturnItem(this->ringbuf_, item); vRingbufferReturnItem(this->ringbuf_, item);
if (this->temp_.empty()) if (this->temp_.empty())
@ -93,13 +219,18 @@ void RemoteReceiverComponent::loop() {
this->temp_.push_back(-this->idle_us_); this->temp_.push_back(-this->idle_us_);
this->call_listeners_dumpers_(); this->call_listeners_dumpers_();
} }
#endif
} }
void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) {
#if ESP_IDF_VERSION_MAJOR >= 5
void RemoteReceiverComponent::decode_rmt_(rmt_symbol_word_t *item, size_t item_count) {
#else
void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t item_count) {
#endif
bool prev_level = false; bool prev_level = false;
uint32_t prev_length = 0; uint32_t prev_length = 0;
this->temp_.clear(); this->temp_.clear();
int32_t multiplier = this->pin_->is_inverted() ? -1 : 1; int32_t multiplier = this->pin_->is_inverted() ? -1 : 1;
size_t item_count = len / sizeof(rmt_item32_t);
uint32_t filter_ticks = this->from_microseconds_(this->filter_us_); uint32_t filter_ticks = this->from_microseconds_(this->filter_us_);
ESP_LOGVV(TAG, "START:"); ESP_LOGVV(TAG, "START:");
@ -124,7 +255,8 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) {
this->temp_.reserve(item_count * 2); // each RMT item has 2 pulses this->temp_.reserve(item_count * 2); // each RMT item has 2 pulses
for (size_t i = 0; i < item_count; i++) { for (size_t i = 0; i < item_count; i++) {
if (item[i].duration0 == 0u) { if (item[i].duration0 == 0u) {
// Do nothing // EOF, sometimes garbage follows, break early
break;
} else if ((bool(item[i].level0) == prev_level) || (item[i].duration0 < filter_ticks)) { } else if ((bool(item[i].level0) == prev_level) || (item[i].duration0 < filter_ticks)) {
prev_length += item[i].duration0; prev_length += item[i].duration0;
} else { } else {
@ -140,7 +272,8 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) {
} }
if (item[i].duration1 == 0u) { if (item[i].duration1 == 0u) {
// Do nothing // EOF, sometimes garbage follows, break early
break;
} else if ((bool(item[i].level1) == prev_level) || (item[i].duration1 < filter_ticks)) { } else if ((bool(item[i].level1) == prev_level) || (item[i].duration1 < filter_ticks)) {
prev_length += item[i].duration1; prev_length += item[i].duration1;
} else { } else {

View File

@ -2,12 +2,23 @@ from esphome import automation, pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_rmt, remote_base from esphome.components import esp32_rmt, remote_base
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_ID, CONF_PIN, CONF_RMT_CHANNEL from esphome.const import (
CONF_CARRIER_DUTY_PERCENT,
CONF_CLOCK_DIVIDER,
CONF_CLOCK_RESOLUTION,
CONF_ID,
CONF_PIN,
CONF_RMT_CHANNEL,
CONF_RMT_SYMBOLS,
CONF_USE_DMA,
)
from esphome.core import CORE
AUTO_LOAD = ["remote_base"] AUTO_LOAD = ["remote_base"]
CONF_ON_TRANSMIT = "on_transmit" CONF_ON_TRANSMIT = "on_transmit"
CONF_ON_COMPLETE = "on_complete" CONF_ON_COMPLETE = "on_complete"
CONF_ONE_WIRE = "one_wire"
remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter") remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter")
RemoteTransmitterComponent = remote_transmitter_ns.class_( RemoteTransmitterComponent = remote_transmitter_ns.class_(
@ -22,7 +33,28 @@ CONFIG_SCHEMA = cv.Schema(
cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All( cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All(
cv.percentage_int, cv.Range(min=1, max=100) cv.percentage_int, cv.Range(min=1, max=100)
), ),
cv.Optional(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=True), cv.Optional(CONF_CLOCK_RESOLUTION): cv.All(
cv.only_on_esp32,
cv.only_with_esp_idf,
esp32_rmt.validate_clock_resolution(),
),
cv.Optional(CONF_CLOCK_DIVIDER): cv.All(
cv.only_on_esp32, cv.only_with_arduino, cv.int_range(min=1, max=255)
),
cv.Optional(CONF_ONE_WIRE): cv.All(cv.only_with_esp_idf, cv.boolean),
cv.Optional(CONF_USE_DMA): cv.All(cv.only_with_esp_idf, cv.boolean),
cv.SplitDefault(
CONF_RMT_SYMBOLS,
esp32_idf=64,
esp32_s2_idf=64,
esp32_s3_idf=48,
esp32_c3_idf=48,
esp32_c6_idf=48,
esp32_h2_idf=48,
): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)),
cv.Optional(CONF_RMT_CHANNEL): cv.All(
cv.only_with_arduino, esp32_rmt.validate_rmt_channel(tx=True)
),
cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True), cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True),
cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True), cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True),
} }
@ -31,8 +63,24 @@ CONFIG_SCHEMA = cv.Schema(
async def to_code(config): async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN]) pin = await cg.gpio_pin_expression(config[CONF_PIN])
if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: if CORE.is_esp32:
var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel) if esp32_rmt.use_new_rmt_driver():
var = cg.new_Pvariable(config[CONF_ID], pin)
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
if CONF_CLOCK_RESOLUTION in config:
cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION]))
if CONF_USE_DMA in config:
cg.add(var.set_with_dma(config[CONF_USE_DMA]))
if CONF_ONE_WIRE in config:
cg.add(var.set_one_wire(config[CONF_ONE_WIRE]))
else:
if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None:
var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel)
else:
var = cg.new_Pvariable(config[CONF_ID], pin)
if CONF_CLOCK_DIVIDER in config:
cg.add(var.set_clock_divider(config[CONF_CLOCK_DIVIDER]))
else: else:
var = cg.new_Pvariable(config[CONF_ID], pin) var = cg.new_Pvariable(config[CONF_ID], pin)
await cg.register_component(var, config) await cg.register_component(var, config)

View File

@ -5,6 +5,10 @@
#include <vector> #include <vector>
#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5
#include <driver/rmt_tx.h>
#endif
namespace esphome { namespace esphome {
namespace remote_transmitter { namespace remote_transmitter {
@ -16,7 +20,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
#endif #endif
{ {
public: public:
#ifdef USE_ESP32 #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5
RemoteTransmitterComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) RemoteTransmitterComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1)
: remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} : remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {}
@ -29,10 +33,16 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; } // transmitter setup must run after receiver setup to allow the same GPIO to be used by both
float get_setup_priority() const override { return setup_priority::DATA - 1; }
void set_carrier_duty_percent(uint8_t carrier_duty_percent) { this->carrier_duty_percent_ = carrier_duty_percent; } void set_carrier_duty_percent(uint8_t carrier_duty_percent) { this->carrier_duty_percent_ = carrier_duty_percent; }
#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5
void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; }
void set_one_wire(bool one_wire) { this->one_wire_ = one_wire; }
#endif
Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; };
Trigger<> *get_complete_trigger() const { return this->complete_trigger_; }; Trigger<> *get_complete_trigger() const { return this->complete_trigger_; };
@ -54,7 +64,15 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
uint32_t current_carrier_frequency_{38000}; uint32_t current_carrier_frequency_{38000};
bool initialized_{false}; bool initialized_{false};
#if ESP_IDF_VERSION_MAJOR >= 5
std::vector<rmt_symbol_word_t> rmt_temp_;
bool with_dma_{false};
bool one_wire_{false};
rmt_channel_handle_t channel_{NULL};
rmt_encoder_handle_t encoder_{NULL};
#else
std::vector<rmt_item32_t> rmt_temp_; std::vector<rmt_item32_t> rmt_temp_;
#endif
esp_err_t error_code_{ESP_OK}; esp_err_t error_code_{ESP_OK};
std::string error_string_{""}; std::string error_string_{""};
bool inverted_{false}; bool inverted_{false};

View File

@ -9,13 +9,22 @@ namespace remote_transmitter {
static const char *const TAG = "remote_transmitter"; static const char *const TAG = "remote_transmitter";
void RemoteTransmitterComponent::setup() { this->configure_rmt_(); } void RemoteTransmitterComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up Remote Transmitter...");
this->configure_rmt_();
}
void RemoteTransmitterComponent::dump_config() { void RemoteTransmitterComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Remote Transmitter..."); ESP_LOGCONFIG(TAG, "Remote Transmitter:");
#if ESP_IDF_VERSION_MAJOR >= 5
ESP_LOGCONFIG(TAG, " One wire: %s", this->one_wire_ ? "true" : "false");
ESP_LOGCONFIG(TAG, " Clock resolution: %" PRIu32 " hz", this->clock_resolution_);
ESP_LOGCONFIG(TAG, " RMT symbols: %" PRIu32, this->rmt_symbols_);
#else
ESP_LOGCONFIG(TAG, " Channel: %d", this->channel_); ESP_LOGCONFIG(TAG, " Channel: %d", this->channel_);
ESP_LOGCONFIG(TAG, " RMT memory blocks: %d", this->mem_block_num_); ESP_LOGCONFIG(TAG, " RMT memory blocks: %d", this->mem_block_num_);
ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_); ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_);
#endif
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->pin_);
if (this->current_carrier_frequency_ != 0 && this->carrier_duty_percent_ != 100) { if (this->current_carrier_frequency_ != 0 && this->carrier_duty_percent_ != 100) {
@ -29,6 +38,72 @@ void RemoteTransmitterComponent::dump_config() {
} }
void RemoteTransmitterComponent::configure_rmt_() { void RemoteTransmitterComponent::configure_rmt_() {
#if ESP_IDF_VERSION_MAJOR >= 5
esp_err_t error;
if (!this->initialized_) {
rmt_tx_channel_config_t channel;
memset(&channel, 0, sizeof(channel));
channel.clk_src = RMT_CLK_SRC_DEFAULT;
channel.resolution_hz = this->clock_resolution_;
channel.gpio_num = gpio_num_t(this->pin_->get_pin());
channel.mem_block_symbols = this->rmt_symbols_;
channel.trans_queue_depth = 1;
channel.flags.io_loop_back = this->one_wire_;
channel.flags.io_od_mode = this->one_wire_;
channel.flags.invert_out = 0;
channel.flags.with_dma = this->with_dma_;
channel.intr_priority = 0;
error = rmt_new_tx_channel(&channel, &this->channel_);
if (error != ESP_OK) {
this->error_code_ = error;
if (error == ESP_ERR_NOT_FOUND) {
this->error_string_ = "out of RMT symbol memory";
} else {
this->error_string_ = "in rmt_new_tx_channel";
}
this->mark_failed();
return;
}
rmt_copy_encoder_config_t encoder;
memset(&encoder, 0, sizeof(encoder));
error = rmt_new_copy_encoder(&encoder, &this->encoder_);
if (error != ESP_OK) {
this->error_code_ = error;
this->error_string_ = "in rmt_new_copy_encoder";
this->mark_failed();
return;
}
error = rmt_enable(this->channel_);
if (error != ESP_OK) {
this->error_code_ = error;
this->error_string_ = "in rmt_enable";
this->mark_failed();
return;
}
this->initialized_ = true;
}
if (this->current_carrier_frequency_ == 0 || this->carrier_duty_percent_ == 100) {
error = rmt_apply_carrier(this->channel_, nullptr);
} else {
rmt_carrier_config_t carrier;
memset(&carrier, 0, sizeof(carrier));
carrier.frequency_hz = this->current_carrier_frequency_;
carrier.duty_cycle = (float) this->carrier_duty_percent_ / 100.0f;
carrier.flags.polarity_active_low = this->inverted_;
carrier.flags.always_on = 1;
error = rmt_apply_carrier(this->channel_, &carrier);
}
if (error != ESP_OK) {
this->error_code_ = error;
this->error_string_ = "in rmt_apply_carrier";
this->mark_failed();
return;
}
#else
rmt_config_t c{}; rmt_config_t c{};
this->config_rmt(c); this->config_rmt(c);
@ -76,6 +151,7 @@ void RemoteTransmitterComponent::configure_rmt_() {
} }
this->initialized_ = true; this->initialized_ = true;
} }
#endif
} }
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
@ -90,7 +166,11 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
this->rmt_temp_.clear(); this->rmt_temp_.clear();
this->rmt_temp_.reserve((this->temp_.get_data().size() + 1) / 2); this->rmt_temp_.reserve((this->temp_.get_data().size() + 1) / 2);
uint32_t rmt_i = 0; uint32_t rmt_i = 0;
#if ESP_IDF_VERSION_MAJOR >= 5
rmt_symbol_word_t rmt_item;
#else
rmt_item32_t rmt_item; rmt_item32_t rmt_item;
#endif
for (int32_t val : this->temp_.get_data()) { for (int32_t val : this->temp_.get_data()) {
bool level = val >= 0; bool level = val >= 0;
@ -125,6 +205,31 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
return; return;
} }
this->transmit_trigger_->trigger(); this->transmit_trigger_->trigger();
#if ESP_IDF_VERSION_MAJOR >= 5
for (uint32_t i = 0; i < send_times; i++) {
rmt_transmit_config_t config;
memset(&config, 0, sizeof(config));
config.loop_count = 0;
config.flags.eot_level = this->inverted_;
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
this->rmt_temp_.size() * sizeof(rmt_symbol_word_t), &config);
if (error != ESP_OK) {
ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
this->status_set_warning();
} else {
this->status_clear_warning();
}
error = rmt_tx_wait_all_done(this->channel_, -1);
if (error != ESP_OK) {
ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
this->status_set_warning();
} else {
this->status_clear_warning();
}
if (i + 1 < send_times)
delayMicroseconds(send_wait);
}
#else
for (uint32_t i = 0; i < send_times; i++) { for (uint32_t i = 0; i < send_times; i++) {
esp_err_t error = rmt_write_items(this->channel_, this->rmt_temp_.data(), this->rmt_temp_.size(), true); esp_err_t error = rmt_write_items(this->channel_, this->rmt_temp_.data(), this->rmt_temp_.size(), true);
if (error != ESP_OK) { if (error != ESP_OK) {
@ -136,6 +241,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
if (i + 1 < send_times) if (i + 1 < send_times)
delayMicroseconds(send_wait); delayMicroseconds(send_wait);
} }
#endif
this->complete_trigger_->trigger(); this->complete_trigger_->trigger();
} }

View File

@ -93,13 +93,17 @@ void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore
int8_t rotation_dir = 0; int8_t rotation_dir = 0;
uint16_t new_state = STATE_LOOKUP_TABLE[input_state]; uint16_t new_state = STATE_LOOKUP_TABLE[input_state];
if ((new_state & arg->resolution & STATE_HAS_INCREMENTED) != 0) { if ((new_state & arg->resolution & STATE_HAS_INCREMENTED) != 0) {
if (arg->counter < arg->max_value) if (arg->counter < arg->max_value) {
arg->counter++; auto x = arg->counter + 1;
arg->counter = x;
}
rotation_dir = 1; rotation_dir = 1;
} }
if ((new_state & arg->resolution & STATE_HAS_DECREMENTED) != 0) { if ((new_state & arg->resolution & STATE_HAS_DECREMENTED) != 0) {
if (arg->counter > arg->min_value) if (arg->counter > arg->min_value) {
arg->counter--; auto x = arg->counter - 1;
arg->counter = x;
}
rotation_dir = -1; rotation_dir = -1;
} }

View File

@ -1,9 +1,9 @@
#ifdef USE_ESP32_FRAMEWORK_ARDUINO #ifdef USE_ESP32_FRAMEWORK_ARDUINO
#include "uart_component_esp32_arduino.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "uart_component_esp32_arduino.h"
#ifdef USE_LOGGER #ifdef USE_LOGGER
#include "esphome/components/logger/logger.h" #include "esphome/components/logger/logger.h"
@ -118,7 +118,7 @@ void ESP32ArduinoUARTComponent::setup() {
} }
#endif // USE_LOGGER #endif // USE_LOGGER
if (next_uart_num >= UART_NUM_MAX) { if (next_uart_num >= SOC_UART_NUM) {
ESP_LOGW(TAG, "Maximum number of UART components created already."); ESP_LOGW(TAG, "Maximum number of UART components created already.");
this->mark_failed(); this->mark_failed();
return; return;

View File

@ -1,11 +1,11 @@
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
#include "uart_component_esp_idf.h" #include "uart_component_esp_idf.h"
#include <cinttypes>
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes>
#ifdef USE_LOGGER #ifdef USE_LOGGER
#include "esphome/components/logger/logger.h" #include "esphome/components/logger/logger.h"
@ -84,7 +84,7 @@ void IDFUARTComponent::setup() {
} }
#endif // USE_LOGGER #endif // USE_LOGGER
if (next_uart_num >= UART_NUM_MAX) { if (next_uart_num >= SOC_UART_NUM) {
ESP_LOGW(TAG, "Maximum number of UART components created already."); ESP_LOGW(TAG, "Maximum number of UART components created already.");
this->mark_failed(); this->mark_failed();
return; return;

View File

@ -1660,6 +1660,12 @@ class SplitDefault(Optional):
esp32_c3=vol.UNDEFINED, esp32_c3=vol.UNDEFINED,
esp32_c3_arduino=vol.UNDEFINED, esp32_c3_arduino=vol.UNDEFINED,
esp32_c3_idf=vol.UNDEFINED, esp32_c3_idf=vol.UNDEFINED,
esp32_c6=vol.UNDEFINED,
esp32_c6_arduino=vol.UNDEFINED,
esp32_c6_idf=vol.UNDEFINED,
esp32_h2=vol.UNDEFINED,
esp32_h2_arduino=vol.UNDEFINED,
esp32_h2_idf=vol.UNDEFINED,
rp2040=vol.UNDEFINED, rp2040=vol.UNDEFINED,
bk72xx=vol.UNDEFINED, bk72xx=vol.UNDEFINED,
rtl87xx=vol.UNDEFINED, rtl87xx=vol.UNDEFINED,
@ -1691,6 +1697,18 @@ class SplitDefault(Optional):
self._esp32_c3_idf_default = vol.default_factory( self._esp32_c3_idf_default = vol.default_factory(
_get_priority_default(esp32_c3_idf, esp32_c3, esp32_idf, esp32) _get_priority_default(esp32_c3_idf, esp32_c3, esp32_idf, esp32)
) )
self._esp32_c6_arduino_default = vol.default_factory(
_get_priority_default(esp32_c6_arduino, esp32_c6, esp32_arduino, esp32)
)
self._esp32_c6_idf_default = vol.default_factory(
_get_priority_default(esp32_c6_idf, esp32_c6, esp32_idf, esp32)
)
self._esp32_h2_arduino_default = vol.default_factory(
_get_priority_default(esp32_h2_arduino, esp32_h2, esp32_arduino, esp32)
)
self._esp32_h2_idf_default = vol.default_factory(
_get_priority_default(esp32_h2_idf, esp32_h2, esp32_idf, esp32)
)
self._rp2040_default = vol.default_factory(rp2040) self._rp2040_default = vol.default_factory(rp2040)
self._bk72xx_default = vol.default_factory(bk72xx) self._bk72xx_default = vol.default_factory(bk72xx)
self._rtl87xx_default = vol.default_factory(rtl87xx) self._rtl87xx_default = vol.default_factory(rtl87xx)
@ -1704,6 +1722,8 @@ class SplitDefault(Optional):
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
VARIANT_ESP32C3, VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
) )
@ -1724,6 +1744,16 @@ class SplitDefault(Optional):
return self._esp32_c3_arduino_default return self._esp32_c3_arduino_default
if CORE.using_esp_idf: if CORE.using_esp_idf:
return self._esp32_c3_idf_default return self._esp32_c3_idf_default
elif variant == VARIANT_ESP32C6:
if CORE.using_arduino:
return self._esp32_c6_arduino_default
if CORE.using_esp_idf:
return self._esp32_c6_idf_default
elif variant == VARIANT_ESP32H2:
if CORE.using_arduino:
return self._esp32_h2_arduino_default
if CORE.using_esp_idf:
return self._esp32_h2_idf_default
else: else:
if CORE.using_arduino: if CORE.using_arduino:
return self._esp32_arduino_default return self._esp32_arduino_default

View File

@ -767,7 +767,8 @@ bool mac_address_is_valid(const uint8_t *mac) {
return !(is_all_zeros || is_all_ones); return !(is_all_zeros || is_all_ones);
} }
void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability void IRAM_ATTR HOT delay_microseconds_safe(uint32_t us) {
// avoids CPU locks that could trigger WDT or affect WiFi/BT stability
uint32_t start = micros(); uint32_t start = micros();
const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop. const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop.

View File

@ -11,6 +11,14 @@
#include "esphome/core/optional.h" #include "esphome/core/optional.h"
#ifdef USE_ESP8266
#include <Esp.h>
#endif
#ifdef USE_RP2040
#include <Arduino.h>
#endif
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_heap_caps.h> #include <esp_heap_caps.h>
#endif #endif
@ -684,20 +692,23 @@ template<class T> class RAMAllocator {
}; };
RAMAllocator() = default; RAMAllocator() = default;
RAMAllocator(uint8_t flags) : flags_{flags} {} RAMAllocator(uint8_t flags) {
// default is both external and internal
flags &= ALLOC_INTERNAL | ALLOC_EXTERNAL;
if (flags != 0)
this->flags_ = flags;
}
template<class U> constexpr RAMAllocator(const RAMAllocator<U> &other) : flags_{other.flags_} {} template<class U> constexpr RAMAllocator(const RAMAllocator<U> &other) : flags_{other.flags_} {}
T *allocate(size_t n) { T *allocate(size_t n) {
size_t size = n * sizeof(T); size_t size = n * sizeof(T);
T *ptr = nullptr; T *ptr = nullptr;
#ifdef USE_ESP32 #ifdef USE_ESP32
// External allocation by default or if explicitely requested if (this->flags_ & Flags::ALLOC_EXTERNAL) {
if ((this->flags_ & Flags::ALLOC_EXTERNAL) || ((this->flags_ & Flags::ALLOC_INTERNAL) == 0)) {
ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
} }
// Fallback to internal allocation if explicitely requested or no flag is specified if (ptr == nullptr && this->flags_ & Flags::ALLOC_INTERNAL) {
if (ptr == nullptr && ((this->flags_ & Flags::ALLOC_INTERNAL) || (this->flags_ & Flags::ALLOC_EXTERNAL) == 0)) { ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
ptr = static_cast<T *>(malloc(size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
} }
#else #else
// Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported // Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported
@ -710,8 +721,46 @@ template<class T> class RAMAllocator {
free(p); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) free(p); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
} }
/**
* Return the total heap space available via this allocator
*/
size_t get_free_heap_size() const {
#ifdef USE_ESP8266
return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
auto max_internal =
this->flags_ & ALLOC_INTERNAL ? heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL) : 0;
auto max_external =
this->flags_ & ALLOC_EXTERNAL ? heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM) : 0;
return max_internal + max_external;
#elif defined(USE_RP2040)
return ::rp2040.getFreeHeap();
#elif defined(USE_LIBRETINY)
return lt_heap_get_free();
#else
return 100000;
#endif
}
/**
* Return the maximum size block this allocator could allocate. This may be an approximation on some platforms
*/
size_t get_max_free_block_size() const {
#ifdef USE_ESP8266
return ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
auto max_internal =
this->flags_ & ALLOC_INTERNAL ? heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL) : 0;
auto max_external =
this->flags_ & ALLOC_EXTERNAL ? heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM) : 0;
return std::max(max_internal, max_external);
#else
return this->get_free_heap_size();
#endif
}
private: private:
uint8_t flags_{Flags::ALLOW_FAILURE}; uint8_t flags_{ALLOC_INTERNAL | ALLOC_EXTERNAL};
}; };
template<class T> using ExternalRAMAllocator = RAMAllocator<T>; template<class T> using ExternalRAMAllocator = RAMAllocator<T>;

View File

@ -13,8 +13,8 @@ static const char *const TAG = "ring_buffer";
RingBuffer::~RingBuffer() { RingBuffer::~RingBuffer() {
if (this->handle_ != nullptr) { if (this->handle_ != nullptr) {
vStreamBufferDelete(this->handle_); vRingbufferDelete(this->handle_);
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); RAMAllocator<uint8_t> allocator(RAMAllocator<uint8_t>::ALLOW_FAILURE);
allocator.deallocate(this->storage_, this->size_); allocator.deallocate(this->storage_, this->size_);
} }
} }
@ -22,26 +22,49 @@ RingBuffer::~RingBuffer() {
std::unique_ptr<RingBuffer> RingBuffer::create(size_t len) { std::unique_ptr<RingBuffer> RingBuffer::create(size_t len) {
std::unique_ptr<RingBuffer> rb = make_unique<RingBuffer>(); std::unique_ptr<RingBuffer> rb = make_unique<RingBuffer>();
rb->size_ = len + 1; rb->size_ = len;
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); RAMAllocator<uint8_t> allocator(RAMAllocator<uint8_t>::ALLOW_FAILURE);
rb->storage_ = allocator.allocate(rb->size_); rb->storage_ = allocator.allocate(rb->size_);
if (rb->storage_ == nullptr) { if (rb->storage_ == nullptr) {
return nullptr; return nullptr;
} }
rb->handle_ = xStreamBufferCreateStatic(rb->size_, 1, rb->storage_, &rb->structure_); rb->handle_ = xRingbufferCreateStatic(rb->size_, RINGBUF_TYPE_BYTEBUF, rb->storage_, &rb->structure_);
ESP_LOGD(TAG, "Created ring buffer with size %u", len); ESP_LOGD(TAG, "Created ring buffer with size %u", len);
return rb; return rb;
} }
size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) { size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) {
if (ticks_to_wait > 0) size_t bytes_read = 0;
xStreamBufferSetTriggerLevel(this->handle_, len);
size_t bytes_read = xStreamBufferReceive(this->handle_, data, len, ticks_to_wait); void *buffer_data = xRingbufferReceiveUpTo(this->handle_, &bytes_read, ticks_to_wait, len);
xStreamBufferSetTriggerLevel(this->handle_, 1); if (buffer_data == nullptr) {
return 0;
}
std::memcpy(data, buffer_data, bytes_read);
vRingbufferReturnItem(this->handle_, buffer_data);
if (bytes_read < len) {
// Data may have wrapped around, so read a second time to receive the remainder
size_t follow_up_bytes_read = 0;
size_t bytes_remaining = len - bytes_read;
buffer_data = xRingbufferReceiveUpTo(this->handle_, &follow_up_bytes_read, 0, bytes_remaining);
if (buffer_data == nullptr) {
return bytes_read;
}
std::memcpy((void *) ((uint8_t *) (data) + bytes_read), buffer_data, follow_up_bytes_read);
vRingbufferReturnItem(this->handle_, buffer_data);
bytes_read += follow_up_bytes_read;
}
return bytes_read; return bytes_read;
} }
@ -49,22 +72,55 @@ size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) {
size_t RingBuffer::write(const void *data, size_t len) { size_t RingBuffer::write(const void *data, size_t len) {
size_t free = this->free(); size_t free = this->free();
if (free < len) { if (free < len) {
size_t needed = len - free; // Free enough space in the ring buffer to fit the new data
uint8_t discard[needed]; this->discard_bytes_(len - free);
xStreamBufferReceive(this->handle_, discard, needed, 0);
} }
return xStreamBufferSend(this->handle_, data, len, 0); return this->write_without_replacement(data, len, 0);
} }
size_t RingBuffer::write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait) { size_t RingBuffer::write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait) {
return xStreamBufferSend(this->handle_, data, len, ticks_to_wait); if (!xRingbufferSend(this->handle_, data, len, ticks_to_wait)) {
// Couldn't fit all the data, so only write what will fit
size_t free = std::min(this->free(), len);
if (xRingbufferSend(this->handle_, data, free, 0)) {
return free;
}
return 0;
}
return len;
} }
size_t RingBuffer::available() const { return xStreamBufferBytesAvailable(this->handle_); } size_t RingBuffer::available() const {
UBaseType_t ux_items_waiting = 0;
vRingbufferGetInfo(this->handle_, nullptr, nullptr, nullptr, nullptr, &ux_items_waiting);
return ux_items_waiting;
}
size_t RingBuffer::free() const { return xStreamBufferSpacesAvailable(this->handle_); } size_t RingBuffer::free() const { return xRingbufferGetCurFreeSize(this->handle_); }
BaseType_t RingBuffer::reset() { return xStreamBufferReset(this->handle_); } BaseType_t RingBuffer::reset() {
// Discards all the available data
return this->discard_bytes_(this->available());
}
bool RingBuffer::discard_bytes_(size_t discard_bytes) {
size_t bytes_read = 0;
void *buffer_data = xRingbufferReceiveUpTo(this->handle_, &bytes_read, 0, discard_bytes);
if (buffer_data != nullptr)
vRingbufferReturnItem(this->handle_, buffer_data);
if (bytes_read < discard_bytes) {
size_t wrapped_bytes_read = 0;
buffer_data = xRingbufferReceiveUpTo(this->handle_, &wrapped_bytes_read, 0, discard_bytes - bytes_read);
if (buffer_data != nullptr) {
vRingbufferReturnItem(this->handle_, buffer_data);
bytes_read += wrapped_bytes_read;
}
}
return (bytes_read == discard_bytes);
}
} // namespace esphome } // namespace esphome

View File

@ -3,7 +3,7 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/stream_buffer.h> #include <freertos/ringbuf.h>
#include <cinttypes> #include <cinttypes>
#include <memory> #include <memory>
@ -82,9 +82,14 @@ class RingBuffer {
static std::unique_ptr<RingBuffer> create(size_t len); static std::unique_ptr<RingBuffer> create(size_t len);
protected: protected:
StreamBufferHandle_t handle_; /// @brief Discards data from the ring buffer.
StaticStreamBuffer_t structure_; /// @param discard_bytes amount of bytes to discard
uint8_t *storage_; /// @return True if all bytes were successfully discarded, false otherwise
bool discard_bytes_(size_t discard_bytes);
RingbufHandle_t handle_{nullptr};
StaticRingbuffer_t structure_;
uint8_t *storage_{nullptr};
size_t size_{0}; size_t size_{0};
}; };

View File

@ -108,6 +108,12 @@ def is_authenticated(handler: BaseHandler) -> bool:
return True return True
if settings.using_auth: if settings.using_auth:
if auth_header := handler.request.headers.get("Authorization"):
assert isinstance(auth_header, str)
if auth_header.startswith("Basic "):
auth_decoded = base64.b64decode(auth_header[6:]).decode()
username, password = auth_decoded.split(":", 1)
return settings.check_password(username, password)
return handler.get_secure_cookie(AUTH_COOKIE_NAME) == COOKIE_AUTHENTICATED_YES return handler.get_secure_cookie(AUTH_COOKIE_NAME) == COOKIE_AUTHENTICATED_YES
return True return True

View File

@ -12,7 +12,7 @@ pyserial==3.5
platformio==6.1.16 # When updating platformio, also update Dockerfile platformio==6.1.16 # When updating platformio, also update Dockerfile
esptool==4.7.0 esptool==4.7.0
click==8.1.7 click==8.1.7
esphome-dashboard==20241120.0 esphome-dashboard==20241217.1
aioesphomeapi==24.6.2 aioesphomeapi==24.6.2
zeroconf==0.132.2 zeroconf==0.132.2
puremagic==1.27 puremagic==1.27

View File

@ -0,0 +1,4 @@
sensor:
- platform: adc
pin: P23
name: Basic ADC Test

View File

@ -3,14 +3,12 @@ light:
id: led_strip id: led_strip
pin: 4 pin: 4
num_leds: 60 num_leds: 60
rmt_channel: 0
rgb_order: GRB rgb_order: GRB
chipset: ws2812 chipset: ws2812
- platform: esp32_rmt_led_strip - platform: esp32_rmt_led_strip
id: led_strip2 id: led_strip2
pin: 5 pin: 5
num_leds: 60 num_leds: 60
rmt_channel: 1
rgb_order: RGB rgb_order: RGB
bit0_high: 100µs bit0_high: 100µs
bit0_low: 100µs bit0_low: 100µs

View File

@ -3,14 +3,12 @@ light:
id: led_strip id: led_strip
pin: 13 pin: 13
num_leds: 60 num_leds: 60
rmt_channel: 6
rgb_order: GRB rgb_order: GRB
chipset: ws2812 chipset: ws2812
- platform: esp32_rmt_led_strip - platform: esp32_rmt_led_strip
id: led_strip2 id: led_strip2
pin: 14 pin: 14
num_leds: 60 num_leds: 60
rmt_channel: 2
rgb_order: RGB rgb_order: RGB
bit0_high: 100µs bit0_high: 100µs
bit0_low: 100µs bit0_low: 100µs

View File

@ -1,5 +1,7 @@
esphome: esphome:
on_boot: on_boot:
- lambda: 'ESP_LOGD("display","is_connected(): %s", YESNO(id(main_lcd).is_connected()));'
# Binary sensor publish action tests # Binary sensor publish action tests
- binary_sensor.nextion.publish: - binary_sensor.nextion.publish:
id: r0_sensor id: r0_sensor

View File

@ -16,6 +16,19 @@ opentherm:
summer_mode_active: true summer_mode_active: true
dhw_block: true dhw_block: true
sync_mode: true sync_mode: true
controller_product_type: 63
controller_product_version: 1
opentherm_version_controller: 2.2
controller_id: 1
controller_configuration: 1
before_send:
then:
- lambda: |-
ESP_LOGW("OT", ">> Sending message %d", x.id);
before_process_response:
then:
- lambda: |-
ESP_LOGW("OT", "<< Processing response %d", x.id);
output: output:
- platform: opentherm - platform: opentherm

View File

@ -0,0 +1,144 @@
on_abbwelcome:
then:
- logger.log:
format: "on_abbwelcome: %u"
args: ["x.data()[0]"]
on_aeha:
then:
- logger.log:
format: "on_aeha: %u %u"
args: ["x.address", "x.data.front()"]
on_byronsx:
then:
- logger.log:
format: "on_byronsx: %u %u"
args: ["x.address", "x.command"]
on_canalsat:
then:
- logger.log:
format: "on_canalsat: %u %u"
args: ["x.address", "x.command"]
# on_canalsatld:
# then:
# - logger.log:
# format: "on_canalsatld: %u %u"
# args: ["x.address", "x.command"]
on_coolix:
then:
- logger.log:
format: "on_coolix: %lu %lu"
args: ["long(x.first)", "long(x.second)"]
on_dish:
then:
- logger.log:
format: "on_dish: %u %u"
args: ["x.address", "x.command"]
on_dooya:
then:
- logger.log:
format: "on_dooya: %u %u %u"
args: ["x.channel", "x.button", "x.check"]
on_drayton:
then:
- logger.log:
format: "on_drayton: %u %u %u"
args: ["x.address", "x.channel", "x.command"]
on_jvc:
then:
- logger.log:
format: "on_jvc: %lu"
args: ["long(x.data)"]
on_keeloq:
then:
- logger.log:
format: "on_keeloq: %lu %lu %u"
args: ["long(x.encrypted)", "long(x.address)", "x.command"]
on_haier:
then:
- logger.log:
format: "on_haier: %u"
args: ["x.data.front()"]
on_lg:
then:
- logger.log:
format: "on_lg: %lu %u"
args: ["long(x.data)", "x.nbits"]
on_magiquest:
then:
- logger.log:
format: "on_magiquest: %u %lu"
args: ["x.magnitude", "long(x.wand_id)"]
on_midea:
then:
- logger.log:
format: "on_midea: %u %u"
args: ["x.size()", "x.data()[0]"]
on_nec:
then:
- logger.log:
format: "on_nec: %u %u"
args: ["x.address", "x.command"]
on_nexa:
then:
- logger.log:
format: "on_nexa: %lu %u %u %u %u"
args: ["long(x.device)", "x.group", "x.state", "x.channel", "x.level"]
on_panasonic:
then:
- logger.log:
format: "on_panasonic: %u %lu"
args: ["x.address", "long(x.command)"]
on_pioneer:
then:
- logger.log:
format: "on_pioneer: %u %u"
args: ["x.rc_code_1", "x.rc_code_2"]
on_pronto:
then:
- logger.log:
format: "on_pronto: %s"
args: ["x.data.c_str()"]
on_raw:
then:
- logger.log:
format: "on_raw: %lu"
args: ["long(x.front())"]
on_rc5:
then:
- logger.log:
format: "on_rc5: %u %u"
args: ["x.address", "x.command"]
on_rc6:
then:
- logger.log:
format: "on_rc6: %u %u"
args: ["x.address", "x.command"]
on_rc_switch:
then:
- logger.log:
format: "on_rc_switch: %llu %u"
args: ["x.code", "x.protocol"]
on_samsung:
then:
- logger.log:
format: "on_samsung: %llu %u"
args: ["x.data", "x.nbits"]
on_samsung36:
then:
- logger.log:
format: "on_samsung36: %u %lu"
args: ["x.address", "long(x.command)"]
on_sony:
then:
- logger.log:
format: "on_sony: %lu %u"
args: ["long(x.data)", "x.nbits"]
on_toshiba_ac:
then:
- logger.log:
format: "on_toshiba_ac: %llu %llu"
args: ["x.rc_code_1", "x.rc_code_2"]
on_mirage:
then:
- lambda: |-
ESP_LOGD("mirage", "Mirage data: %s", format_hex(x.data).c_str());

View File

@ -0,0 +1,14 @@
remote_receiver:
- id: rcvr
pin: ${pin}
rmt_channel: ${rmt_channel}
dump: all
tolerance: 25%
<<: !include common-actions.yaml
binary_sensor:
- platform: remote_receiver
name: Panasonic Remote Input
panasonic:
address: 0x4004
command: 0x100BCBD

View File

@ -0,0 +1,18 @@
remote_receiver:
- id: rcvr
pin: ${pin}
dump: all
tolerance: 25%
clock_resolution: ${clock_resolution}
filter_symbols: ${filter_symbols}
receive_symbols: ${receive_symbols}
rmt_symbols: ${rmt_symbols}
use_dma: ${use_dma}
<<: !include common-actions.yaml
binary_sensor:
- platform: remote_receiver
name: Panasonic Remote Input
panasonic:
address: 0x4004
command: 0x100BCBD

View File

@ -1,157 +0,0 @@
remote_receiver:
id: rcvr
pin: ${pin}
rmt_channel: ${rmt_channel}
dump: all
tolerance: 25%
on_abbwelcome:
then:
- logger.log:
format: "on_abbwelcome: %u"
args: ["x.data()[0]"]
on_aeha:
then:
- logger.log:
format: "on_aeha: %u %u"
args: ["x.address", "x.data.front()"]
on_byronsx:
then:
- logger.log:
format: "on_byronsx: %u %u"
args: ["x.address", "x.command"]
on_canalsat:
then:
- logger.log:
format: "on_canalsat: %u %u"
args: ["x.address", "x.command"]
# on_canalsatld:
# then:
# - logger.log:
# format: "on_canalsatld: %u %u"
# args: ["x.address", "x.command"]
on_coolix:
then:
- logger.log:
format: "on_coolix: %lu %lu"
args: ["long(x.first)", "long(x.second)"]
on_dish:
then:
- logger.log:
format: "on_dish: %u %u"
args: ["x.address", "x.command"]
on_dooya:
then:
- logger.log:
format: "on_dooya: %u %u %u"
args: ["x.channel", "x.button", "x.check"]
on_drayton:
then:
- logger.log:
format: "on_drayton: %u %u %u"
args: ["x.address", "x.channel", "x.command"]
on_jvc:
then:
- logger.log:
format: "on_jvc: %lu"
args: ["long(x.data)"]
on_keeloq:
then:
- logger.log:
format: "on_keeloq: %lu %lu %u"
args: ["long(x.encrypted)", "long(x.address)", "x.command"]
on_haier:
then:
- logger.log:
format: "on_haier: %u"
args: ["x.data.front()"]
on_lg:
then:
- logger.log:
format: "on_lg: %lu %u"
args: ["long(x.data)", "x.nbits"]
on_magiquest:
then:
- logger.log:
format: "on_magiquest: %u %lu"
args: ["x.magnitude", "long(x.wand_id)"]
on_midea:
then:
- logger.log:
format: "on_midea: %u %u"
args: ["x.size()", "x.data()[0]"]
on_nec:
then:
- logger.log:
format: "on_nec: %u %u"
args: ["x.address", "x.command"]
on_nexa:
then:
- logger.log:
format: "on_nexa: %lu %u %u %u %u"
args: ["long(x.device)", "x.group", "x.state", "x.channel", "x.level"]
on_panasonic:
then:
- logger.log:
format: "on_panasonic: %u %lu"
args: ["x.address", "long(x.command)"]
on_pioneer:
then:
- logger.log:
format: "on_pioneer: %u %u"
args: ["x.rc_code_1", "x.rc_code_2"]
on_pronto:
then:
- logger.log:
format: "on_pronto: %s"
args: ["x.data.c_str()"]
on_raw:
then:
- logger.log:
format: "on_raw: %lu"
args: ["long(x.front())"]
on_rc5:
then:
- logger.log:
format: "on_rc5: %u %u"
args: ["x.address", "x.command"]
on_rc6:
then:
- logger.log:
format: "on_rc6: %u %u"
args: ["x.address", "x.command"]
on_rc_switch:
then:
- logger.log:
format: "on_rc_switch: %llu %u"
args: ["x.code", "x.protocol"]
on_samsung:
then:
- logger.log:
format: "on_samsung: %llu %u"
args: ["x.data", "x.nbits"]
on_samsung36:
then:
- logger.log:
format: "on_samsung36: %u %lu"
args: ["x.address", "long(x.command)"]
on_sony:
then:
- logger.log:
format: "on_sony: %lu %u"
args: ["long(x.data)", "x.nbits"]
on_toshiba_ac:
then:
- logger.log:
format: "on_toshiba_ac: %llu %llu"
args: ["x.rc_code_1", "x.rc_code_2"]
on_mirage:
then:
- lambda: |-
ESP_LOGD("mirage", "Mirage data: %s", format_hex(x.data).c_str());
binary_sensor:
- platform: remote_receiver
name: Panasonic Remote Input
panasonic:
address: 0x4004
command: 0x100BCBD

View File

@ -3,4 +3,4 @@ substitutions:
rmt_channel: "2" rmt_channel: "2"
packages: packages:
common: !include esp32-common.yaml common: !include esp32-common-ard.yaml

View File

@ -3,4 +3,4 @@ substitutions:
rmt_channel: "2" rmt_channel: "2"
packages: packages:
common: !include esp32-common.yaml common: !include esp32-common-ard.yaml

View File

@ -1,6 +1,10 @@
substitutions: substitutions:
pin: GPIO2 pin: GPIO2
rmt_channel: "2" clock_resolution: "2000000"
filter_symbols: "2"
receive_symbols: "4"
rmt_symbols: "64"
use_dma: "true"
packages: packages:
common: !include esp32-common.yaml common: !include esp32-common-idf.yaml

View File

@ -1,6 +1,10 @@
substitutions: substitutions:
pin: GPIO2 pin: GPIO2
rmt_channel: "2" clock_resolution: "2000000"
filter_symbols: "2"
receive_symbols: "4"
rmt_symbols: "64"
use_dma: "true"
packages: packages:
common: !include esp32-common.yaml common: !include esp32-common-idf.yaml

View File

@ -1,6 +1,10 @@
substitutions: substitutions:
pin: GPIO38 pin: GPIO38
rmt_channel: "5" clock_resolution: "2000000"
filter_symbols: "2"
receive_symbols: "4"
rmt_symbols: "64"
use_dma: "true"
packages: packages:
common: !include esp32-common.yaml common: !include esp32-common-idf.yaml

View File

@ -2,150 +2,7 @@ remote_receiver:
id: rcvr id: rcvr
pin: GPIO5 pin: GPIO5
dump: all dump: all
on_abbwelcome: <<: !include common-actions.yaml
then:
- logger.log:
format: "on_abbwelcome: %u"
args: ["x.data()[0]"]
on_aeha:
then:
- logger.log:
format: "on_aeha: %u %u"
args: ["x.address", "x.data.front()"]
on_byronsx:
then:
- logger.log:
format: "on_byronsx: %u %u"
args: ["x.address", "x.command"]
on_canalsat:
then:
- logger.log:
format: "on_canalsat: %u %u"
args: ["x.address", "x.command"]
# on_canalsatld:
# then:
# - logger.log:
# format: "on_canalsatld: %u %u"
# args: ["x.address", "x.command"]
on_coolix:
then:
- logger.log:
format: "on_coolix: %u %u"
args: ["x.first", "x.second"]
on_dish:
then:
- logger.log:
format: "on_dish: %u %u"
args: ["x.address", "x.command"]
on_dooya:
then:
- logger.log:
format: "on_dooya: %u %u %u"
args: ["x.channel", "x.button", "x.check"]
on_drayton:
then:
- logger.log:
format: "on_drayton: %u %u %u"
args: ["x.address", "x.channel", "x.command"]
on_jvc:
then:
- logger.log:
format: "on_jvc: %u"
args: ["x.data"]
on_keeloq:
then:
- logger.log:
format: "on_keeloq: %u %u %u"
args: ["x.encrypted", "x.address", "x.command"]
on_haier:
then:
- logger.log:
format: "on_haier: %u"
args: ["x.data.front()"]
on_lg:
then:
- logger.log:
format: "on_lg: %u %u"
args: ["x.data", "x.nbits"]
on_magiquest:
then:
- logger.log:
format: "on_magiquest: %u %u"
args: ["x.magnitude", "x.wand_id"]
on_midea:
then:
- logger.log:
format: "on_midea: %u %u"
args: ["x.size()", "x.data()[0]"]
on_nec:
then:
- logger.log:
format: "on_nec: %u %u"
args: ["x.address", "x.command"]
on_nexa:
then:
- logger.log:
format: "on_nexa: %u %u %u %u %u"
args: ["x.device", "x.group", "x.state", "x.channel", "x.level"]
on_panasonic:
then:
- logger.log:
format: "on_panasonic: %u %u"
args: ["x.address", "x.command"]
on_pioneer:
then:
- logger.log:
format: "on_pioneer: %u %u"
args: ["x.rc_code_1", "x.rc_code_2"]
on_pronto:
then:
- logger.log:
format: "on_pronto: %s"
args: ["x.data.c_str()"]
on_raw:
then:
- logger.log:
format: "on_raw: %u"
args: ["x.front()"]
on_rc5:
then:
- logger.log:
format: "on_rc5: %u %u"
args: ["x.address", "x.command"]
on_rc6:
then:
- logger.log:
format: "on_rc6: %u %u"
args: ["x.address", "x.command"]
on_rc_switch:
then:
- logger.log:
format: "on_rc_switch: %llu %u"
args: ["x.code", "x.protocol"]
on_samsung:
then:
- logger.log:
format: "on_samsung: %llu %u"
args: ["x.data", "x.nbits"]
on_samsung36:
then:
- logger.log:
format: "on_samsung36: %u %u"
args: ["x.address", "x.command"]
on_sony:
then:
- logger.log:
format: "on_sony: %u %u"
args: ["x.data", "x.nbits"]
on_toshiba_ac:
then:
- logger.log:
format: "on_toshiba_ac: %llu %llu"
args: ["x.rc_code_1", "x.rc_code_2"]
on_mirage:
then:
- lambda: |-
ESP_LOGD("mirage", "Mirage data: %s", format_hex(x.data).c_str());
binary_sensor: binary_sensor:
- platform: remote_receiver - platform: remote_receiver

View File

@ -0,0 +1,8 @@
remote_transmitter:
- id: xmitr
pin: ${pin}
rmt_channel: ${rmt_channel}
carrier_duty_percent: 50%
packages:
buttons: !include common-buttons.yaml

View File

@ -0,0 +1,11 @@
remote_transmitter:
- id: xmitr
pin: ${pin}
carrier_duty_percent: 50%
clock_resolution: ${clock_resolution}
one_wire: ${one_wire}
rmt_symbols: ${rmt_symbols}
use_dma: ${use_dma}
packages:
buttons: !include common-buttons.yaml

View File

@ -1,8 +0,0 @@
remote_transmitter:
id: rcvr
pin: ${pin}
rmt_channel: ${rmt_channel}
carrier_duty_percent: 50%
packages:
buttons: !include common-buttons.yaml

View File

@ -3,4 +3,4 @@ substitutions:
rmt_channel: "2" rmt_channel: "2"
packages: packages:
common: !include esp32-common.yaml common: !include esp32-common-ard.yaml

View File

@ -3,4 +3,4 @@ substitutions:
rmt_channel: "1" rmt_channel: "1"
packages: packages:
common: !include esp32-common.yaml common: !include esp32-common-ard.yaml

View File

@ -1,6 +1,9 @@
substitutions: substitutions:
pin: GPIO2 pin: GPIO2
rmt_channel: "1" clock_resolution: "2000000"
one_wire: "true"
rmt_symbols: "64"
use_dma: "true"
packages: packages:
common: !include esp32-common.yaml common: !include esp32-common-idf.yaml

View File

@ -1,6 +1,9 @@
substitutions: substitutions:
pin: GPIO2 pin: GPIO2
rmt_channel: "2" clock_resolution: "2000000"
one_wire: "true"
rmt_symbols: "64"
use_dma: "true"
packages: packages:
common: !include esp32-common.yaml common: !include esp32-common-idf.yaml

View File

@ -1,6 +1,9 @@
substitutions: substitutions:
pin: GPIO38 pin: GPIO38
rmt_channel: "3" clock_resolution: "2000000"
one_wire: "true"
rmt_symbols: "64"
use_dma: "true"
packages: packages:
common: !include esp32-common.yaml common: !include esp32-common-idf.yaml

View File

@ -1,5 +1,5 @@
remote_transmitter: remote_transmitter:
id: trns id: xmitr
pin: GPIO5 pin: GPIO5
carrier_duty_percent: 50% carrier_duty_percent: 50%