mirror of
https://github.com/esphome/esphome.git
synced 2025-01-18 20:10:55 +00:00
Merge branch 'dev' into nvds-new-espnow
This commit is contained in:
commit
5c7d0782fd
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- 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
|
||||
uses: docker/setup-qemu-action@v3.2.0
|
||||
|
||||
|
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -13,6 +13,7 @@ on:
|
||||
- ".github/workflows/ci.yml"
|
||||
- "!.yamllint"
|
||||
- "!.github/dependabot.yml"
|
||||
- "!docker/**"
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -90,7 +90,7 @@ jobs:
|
||||
python-version: "3.9"
|
||||
|
||||
- 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
|
||||
if: matrix.platform != 'linux/amd64'
|
||||
uses: docker/setup-qemu-action@v3.2.0
|
||||
@ -141,7 +141,7 @@ jobs:
|
||||
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload digests
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
uses: actions/upload-artifact@v4.5.0
|
||||
with:
|
||||
name: digests-${{ steps.sanitize.outputs.name }}
|
||||
path: /tmp/digests
|
||||
@ -184,7 +184,7 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- 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
|
||||
if: matrix.registry == 'dockerhub'
|
||||
|
@ -1,11 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER
|
||||
|
||||
from esphome.core import CORE
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.const import PLATFORM_ESP8266
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
@ -15,6 +10,9 @@ from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32S2,
|
||||
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"]
|
||||
|
||||
@ -102,11 +100,11 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
|
||||
6: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
},
|
||||
VARIANT_ESP32H2: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
5: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
}
|
||||
|
||||
|
48
esphome/components/adc/adc_sensor_libretiny.cpp
Normal file
48
esphome/components/adc/adc_sensor_libretiny.cpp
Normal 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
|
@ -30,109 +30,162 @@ static const char *const TAG = "debug";
|
||||
|
||||
std::string DebugComponent::get_reset_reason_() {
|
||||
std::string reset_reason;
|
||||
switch (rtc_get_reset_reason(0)) {
|
||||
case POWERON_RESET:
|
||||
reset_reason = "Power On Reset";
|
||||
switch (esp_reset_reason()) {
|
||||
case ESP_RST_POWERON:
|
||||
reset_reason = "Reset due to power-on event";
|
||||
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)
|
||||
case SW_RESET:
|
||||
case SW_RESET:
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case RTC_SW_SYS_RESET:
|
||||
case RTC_SW_SYS_RESET:
|
||||
#endif
|
||||
reset_reason = "Software Reset Digital Core";
|
||||
break;
|
||||
reset_reason = "Software Reset Digital Core";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case OWDT_RESET:
|
||||
reset_reason = "Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case OWDT_RESET:
|
||||
reset_reason = "Watch Dog Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
case DEEPSLEEP_RESET:
|
||||
reset_reason = "Deep Sleep Reset Digital Core";
|
||||
break;
|
||||
case DEEPSLEEP_RESET:
|
||||
reset_reason = "Deep Sleep Reset Digital Core";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case SDIO_RESET:
|
||||
reset_reason = "SLC Module Reset Digital Core";
|
||||
break;
|
||||
case SDIO_RESET:
|
||||
reset_reason = "SLC Module Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
case TG0WDT_SYS_RESET:
|
||||
reset_reason = "Timer Group 0 Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case TG1WDT_SYS_RESET:
|
||||
reset_reason = "Timer Group 1 Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case RTCWDT_SYS_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case TG0WDT_SYS_RESET:
|
||||
reset_reason = "Timer Group 0 Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case TG1WDT_SYS_RESET:
|
||||
reset_reason = "Timer Group 1 Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case RTCWDT_SYS_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset Digital Core";
|
||||
break;
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
case INTRUSION_RESET:
|
||||
reset_reason = "Intrusion Reset CPU";
|
||||
break;
|
||||
case INTRUSION_RESET:
|
||||
reset_reason = "Intrusion Reset CPU";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case TGWDT_CPU_RESET:
|
||||
reset_reason = "Timer Group Reset CPU";
|
||||
break;
|
||||
case TGWDT_CPU_RESET:
|
||||
reset_reason = "Timer Group Reset CPU";
|
||||
break;
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case TG0WDT_CPU_RESET:
|
||||
reset_reason = "Timer Group 0 Reset CPU";
|
||||
break;
|
||||
case TG0WDT_CPU_RESET:
|
||||
reset_reason = "Timer Group 0 Reset CPU";
|
||||
break;
|
||||
#endif
|
||||
#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) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case RTC_SW_CPU_RESET:
|
||||
case RTC_SW_CPU_RESET:
|
||||
#endif
|
||||
reset_reason = "Software Reset CPU";
|
||||
break;
|
||||
case RTCWDT_CPU_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset CPU";
|
||||
break;
|
||||
reset_reason = "Software Reset CPU";
|
||||
break;
|
||||
case RTCWDT_CPU_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset CPU";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case EXT_CPU_RESET:
|
||||
reset_reason = "External CPU Reset";
|
||||
break;
|
||||
case EXT_CPU_RESET:
|
||||
reset_reason = "External CPU Reset";
|
||||
break;
|
||||
#endif
|
||||
case RTCWDT_BROWN_OUT_RESET:
|
||||
reset_reason = "Voltage Unstable Reset";
|
||||
break;
|
||||
case RTCWDT_RTC_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module";
|
||||
break;
|
||||
case RTCWDT_BROWN_OUT_RESET:
|
||||
reset_reason = "Voltage Unstable Reset";
|
||||
break;
|
||||
case RTCWDT_RTC_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case TG1WDT_CPU_RESET:
|
||||
reset_reason = "Timer Group 1 Reset CPU";
|
||||
break;
|
||||
case SUPER_WDT_RESET:
|
||||
reset_reason = "Super Watchdog Reset Digital Core And RTC Module";
|
||||
break;
|
||||
case EFUSE_RESET:
|
||||
reset_reason = "eFuse Reset Digital Core";
|
||||
break;
|
||||
case TG1WDT_CPU_RESET:
|
||||
reset_reason = "Timer Group 1 Reset CPU";
|
||||
break;
|
||||
case SUPER_WDT_RESET:
|
||||
reset_reason = "Super Watchdog Reset Digital Core And RTC Module";
|
||||
break;
|
||||
case EFUSE_RESET:
|
||||
reset_reason = "eFuse Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case GLITCH_RTC_RESET:
|
||||
reset_reason = "Glitch Reset Digital Core And RTC Module";
|
||||
break;
|
||||
case GLITCH_RTC_RESET:
|
||||
reset_reason = "Glitch Reset Digital Core And RTC Module";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case USB_UART_CHIP_RESET:
|
||||
reset_reason = "USB UART Reset Digital Core";
|
||||
break;
|
||||
case USB_JTAG_CHIP_RESET:
|
||||
reset_reason = "USB JTAG Reset Digital Core";
|
||||
break;
|
||||
case USB_UART_CHIP_RESET:
|
||||
reset_reason = "USB UART Reset Digital Core";
|
||||
break;
|
||||
case USB_JTAG_CHIP_RESET:
|
||||
reset_reason = "USB JTAG Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case POWER_GLITCH_RESET:
|
||||
reset_reason = "Power Glitch Reset Digital Core And RTC Module";
|
||||
break;
|
||||
case POWER_GLITCH_RESET:
|
||||
reset_reason = "Power Glitch Reset Digital Core And RTC Module";
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
reset_reason = "Unknown Reset Reason";
|
||||
default:
|
||||
reset_reason = "Unknown Reset Reason";
|
||||
}
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
|
||||
return reset_reason;
|
||||
@ -294,4 +347,4 @@ void DebugComponent::update_platform_() {
|
||||
|
||||
} // namespace debug
|
||||
} // namespace esphome
|
||||
#endif
|
||||
#endif // USE_ESP32
|
||||
|
@ -602,6 +602,9 @@ async def to_code(config):
|
||||
cg.add_platformio_option(
|
||||
"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_CUSTOM", True)
|
||||
add_idf_sdkconfig_option(
|
||||
|
@ -27,6 +27,9 @@ namespace 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() {
|
||||
global_ble = this;
|
||||
ESP_LOGCONFIG(TAG, "Setting up BLE...");
|
||||
@ -322,7 +325,8 @@ void ESP32BLE::loop() {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_event->~BLEEvent();
|
||||
EVENT_ALLOCATOR.deallocate(ble_event, 1);
|
||||
ble_event = this->ble_events_.pop();
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
} // 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) {
|
||||
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,
|
||||
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);
|
||||
} // 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,
|
||||
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,
|
||||
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);
|
||||
} // 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,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
|
@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() {
|
||||
esp_err_t err;
|
||||
|
||||
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_;
|
||||
err = esp_ble_gap_config_adv_data(&this->advertising_data_);
|
||||
if (err != ESP_OK) {
|
||||
|
@ -26,10 +26,10 @@ template<class T> class Queue {
|
||||
void push(T *element) {
|
||||
if (element == nullptr)
|
||||
return;
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
q_.push(element);
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
// It is not called from main loop. Thus it won't block main thread.
|
||||
xSemaphoreTake(m_, portMAX_DELAY);
|
||||
q_.push(element);
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
|
||||
T *pop() {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
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"]
|
||||
|
||||
@ -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
|
||||
|
||||
def _validator(value):
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include <cinttypes>
|
||||
#include "led_strip.h"
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@ -13,9 +13,13 @@ namespace 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 uint8_t RMT_CLK_DIV = 2;
|
||||
#endif
|
||||
|
||||
void ESP32RMTLEDStripLightOutput::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip...");
|
||||
@ -37,9 +41,48 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
||||
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);
|
||||
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;
|
||||
memset(&config, 0, sizeof(config));
|
||||
@ -64,6 +107,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
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...");
|
||||
|
||||
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");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@ -112,7 +161,11 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
||||
size_t size = 0;
|
||||
size_t len = 0;
|
||||
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_;
|
||||
#endif
|
||||
while (size < buffer_size) {
|
||||
uint8_t b = *psrc;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
@ -130,7 +183,16 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
||||
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");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@ -186,7 +248,11 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index
|
||||
void ESP32RMTLEDStripLightOutput::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:");
|
||||
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_);
|
||||
#endif
|
||||
const char *rgb_order;
|
||||
switch (this->rgb_order_) {
|
||||
case ORDER_RGB:
|
||||
|
@ -9,8 +9,14 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/rmt.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 esp32_rmt_led_strip {
|
||||
@ -54,7 +60,11 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
uint32_t reset_time_high, uint32_t reset_time_low);
|
||||
|
||||
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; }
|
||||
#endif
|
||||
|
||||
void clear_effect_data() override {
|
||||
for (int i = 0; i < this->size(); i++)
|
||||
@ -70,7 +80,17 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
|
||||
uint8_t *buf_{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 bit0_, bit1_, reset_;
|
||||
rmt_channel_t channel_{RMT_CHANNEL_0};
|
||||
#endif
|
||||
|
||||
uint8_t pin_;
|
||||
uint16_t num_leds_;
|
||||
@ -78,9 +98,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
bool is_wrgb_;
|
||||
bool use_psram_;
|
||||
|
||||
rmt_item32_t bit0_, bit1_, reset_;
|
||||
RGBOrder rgb_order_;
|
||||
rmt_channel_t channel_;
|
||||
|
||||
uint32_t last_refresh_{0};
|
||||
optional<uint32_t> max_refresh_rate_{};
|
||||
|
@ -13,6 +13,7 @@ from esphome.const import (
|
||||
CONF_PIN,
|
||||
CONF_RGB_ORDER,
|
||||
CONF_RMT_CHANNEL,
|
||||
CONF_RMT_SYMBOLS,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
@ -23,8 +24,6 @@ ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_(
|
||||
"ESP32RMTLEDStripLightOutput", light.AddressableLight
|
||||
)
|
||||
|
||||
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
|
||||
|
||||
RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder")
|
||||
|
||||
RGB_ORDERS = {
|
||||
@ -65,6 +64,13 @@ CONF_RESET_HIGH = "reset_high"
|
||||
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(
|
||||
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_NUM_LEDS): cv.positive_not_null_int,
|
||||
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_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
|
||||
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_use_psram(config[CONF_USE_PSRAM]))
|
||||
|
||||
cg.add(
|
||||
var.set_rmt_channel(
|
||||
getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}")
|
||||
if esp32_rmt.use_new_rmt_driver():
|
||||
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
|
||||
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]}")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -51,8 +51,11 @@ CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs"
|
||||
# Cache loaded freetype fonts
|
||||
class FontCache(dict):
|
||||
def __missing__(self, key):
|
||||
res = self[key] = freetype.Face(key)
|
||||
return res
|
||||
try:
|
||||
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()
|
||||
|
@ -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
|
||||
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))) {
|
||||
// Failed to allocate buffers
|
||||
|
@ -1,38 +1,20 @@
|
||||
#include "json_util.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 json {
|
||||
|
||||
static const char *const TAG = "json";
|
||||
|
||||
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) {
|
||||
// Here we are allocating up to 5kb of memory,
|
||||
// with the heap size minus 2kb to be safe if less than 5kb
|
||||
// as we can not have a true dynamic sized document.
|
||||
// The excess memory is freed below with `shrinkToFit()`
|
||||
#ifdef USE_ESP8266
|
||||
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
|
||||
|
||||
auto free_heap = ALLOCATOR.get_max_free_block_size();
|
||||
size_t request_size = std::min(free_heap, (size_t) 512);
|
||||
while (true) {
|
||||
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
|
||||
// as we can not have a true dynamic sized document.
|
||||
// The excess memory is freed below with `shrinkToFit()`
|
||||
#ifdef USE_ESP8266
|
||||
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
|
||||
auto free_heap = ALLOCATOR.get_max_free_block_size();
|
||||
size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
|
||||
while (true) {
|
||||
DynamicJsonDocument json_document(request_size);
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ from esphome.helpers import write_file_if_changed
|
||||
|
||||
from . import defines as df, helpers, lv_validation as lvalid
|
||||
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 (
|
||||
ENCODERS_CONFIG,
|
||||
encoders_to_code,
|
||||
@ -205,6 +205,10 @@ def final_validation(configs):
|
||||
raise cv.Invalid(
|
||||
"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]
|
||||
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")
|
||||
|
@ -19,10 +19,12 @@ template<typename... Ts> class MideaActionBase : public Action<Ts...> {
|
||||
|
||||
template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> {
|
||||
TEMPLATABLE_VALUE(float, temperature)
|
||||
TEMPLATABLE_VALUE(bool, use_fahrenheit)
|
||||
TEMPLATABLE_VALUE(bool, beeper)
|
||||
|
||||
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...));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "air_conditioner.h"
|
||||
#include "ac_adapter.h"
|
||||
#include <cmath>
|
||||
@ -121,7 +122,7 @@ void AirConditioner::dump_config() {
|
||||
|
||||
/* 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
|
||||
// Check if temperature is finite (not NaN or infinite)
|
||||
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
|
||||
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,
|
||||
temp_uint8);
|
||||
char temp_symbol = use_fahrenheit ? 'F' : 'C';
|
||||
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
|
||||
IrFollowMeData data(temp_uint8, beeper);
|
||||
IrFollowMeData data(temp_uint8, use_fahrenheit, beeper);
|
||||
this->transmitter_.transmit(data);
|
||||
#else
|
||||
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
|
||||
|
@ -32,7 +32,7 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>,
|
||||
/* ### 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_swing_step();
|
||||
void do_beeper_on() { this->set_beeper_feedback(true); }
|
||||
|
@ -18,6 +18,7 @@ from esphome.const import (
|
||||
CONF_SUPPORTED_SWING_MODES,
|
||||
CONF_TIMEOUT,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_USE_FAHRENHEIT,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
@ -172,11 +173,10 @@ MIDEA_ACTION_BASE_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
# FollowMe action
|
||||
MIDEA_FOLLOW_ME_MIN = 0
|
||||
MIDEA_FOLLOW_ME_MAX = 37
|
||||
MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
|
||||
{
|
||||
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),
|
||||
}
|
||||
)
|
||||
@ -186,6 +186,8 @@ MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
|
||||
async def follow_me_to_code(var, config, args):
|
||||
template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_)
|
||||
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_)
|
||||
cg.add(var.set_temperature(template_))
|
||||
|
||||
|
@ -16,22 +16,53 @@ class IrFollowMeData : public IrData {
|
||||
IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {}
|
||||
// Copy from Base
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
|
||||
/* TEMPERATURE */
|
||||
uint8_t temp() const { return this->get_value_(4) - 1; }
|
||||
void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); }
|
||||
uint8_t temp() const {
|
||||
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 */
|
||||
bool beeper() const { return this->get_value_(3, 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:
|
||||
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 {
|
||||
|
@ -119,17 +119,17 @@ async def to_code(config):
|
||||
cg.add_library("ESP8266HTTPClient", None)
|
||||
|
||||
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:
|
||||
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:
|
||||
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]))
|
||||
|
||||
|
@ -40,7 +40,7 @@ bool Nextion::send_command_(const std::string &command) {
|
||||
}
|
||||
|
||||
bool Nextion::check_connect_() {
|
||||
if (this->get_is_connected_())
|
||||
if (this->is_connected_)
|
||||
return true;
|
||||
|
||||
// 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 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) {
|
||||
this->set_wake_up_page(this->wake_up_page_);
|
||||
}
|
||||
|
@ -856,76 +856,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
*/
|
||||
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.
|
||||
* @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 set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) {
|
||||
this->touch_sleep_timeout_ = touch_sleep_timeout;
|
||||
}
|
||||
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; }
|
||||
void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; }
|
||||
void set_exit_reparse_on_start_internal(bool exit_reparse_on_start) {
|
||||
this->exit_reparse_on_start_ = exit_reparse_on_start;
|
||||
}
|
||||
/**
|
||||
* 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(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.
|
||||
@ -1217,6 +1207,25 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
*/
|
||||
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:
|
||||
std::deque<NextionQueue *> nextion_queue_;
|
||||
std::deque<NextionQueue *> waveform_queue_;
|
||||
@ -1315,8 +1324,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
|
||||
#endif // USE_NEXTION_TFT_UPLOAD
|
||||
|
||||
bool get_is_connected_() { return this->is_connected_; }
|
||||
|
||||
bool check_connect_();
|
||||
|
||||
std::vector<NextionComponentBase *> touch_;
|
||||
|
@ -10,19 +10,19 @@ static const char *const TAG = "nextion";
|
||||
// Sleep safe commands
|
||||
void Nextion::soft_reset() { this->send_command_("rest"); }
|
||||
|
||||
void Nextion::set_wake_up_page(uint8_t page_id) {
|
||||
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true);
|
||||
void Nextion::set_wake_up_page(uint8_t wake_up_page) {
|
||||
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(uint16_t timeout) {
|
||||
if (timeout < 3) {
|
||||
void Nextion::set_touch_sleep_timeout(uint32_t touch_sleep_timeout) {
|
||||
if (touch_sleep_timeout < 3) {
|
||||
ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535");
|
||||
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) {
|
||||
@ -54,7 +54,6 @@ bool Nextion::set_protocol_reparse_mode(bool active_mode) {
|
||||
this->ignore_is_setup_ = false;
|
||||
return all_commands_sent;
|
||||
}
|
||||
void Nextion::set_exit_reparse_on_start(bool exit_reparse) { this->exit_reparse_on_start_ = exit_reparse; }
|
||||
|
||||
// Set Colors - Background
|
||||
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));
|
||||
}
|
||||
|
||||
void Nextion::set_auto_wake_on_touch(bool auto_wake) {
|
||||
this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0);
|
||||
void Nextion::set_auto_wake_on_touch(bool auto_wake_on_touch) {
|
||||
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
|
||||
|
@ -80,15 +80,7 @@ bool OnlineImage::resize_(int width_in, int height_in) {
|
||||
this->width_ = width;
|
||||
ESP_LOGD(TAG, "New size: (%d, %d)", width, height);
|
||||
} else {
|
||||
#if defined(USE_ESP8266)
|
||||
// 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);
|
||||
ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %zu Bytes", this->allocator_.get_max_free_block_size());
|
||||
this->end_connection_();
|
||||
return false;
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
from typing import Any
|
||||
|
||||
import logging
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
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
|
||||
|
||||
CODEOWNERS = ["@olegtarasov"]
|
||||
@ -20,7 +22,21 @@ CONF_CH2_ACTIVE = "ch2_active"
|
||||
CONF_SUMMER_MODE_ACTIVE = "summer_mode_active"
|
||||
CONF_DHW_BLOCK = "dhw_block"
|
||||
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(
|
||||
cv.Schema(
|
||||
@ -36,7 +52,19 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean,
|
||||
cv.Optional(CONF_DHW_BLOCK, 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(
|
||||
@ -44,6 +72,11 @@ CONFIG_SCHEMA = cv.All(
|
||||
schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor))
|
||||
)
|
||||
)
|
||||
.extend(
|
||||
validate.create_entities_schema(
|
||||
schema.SETTINGS, (lambda s: s.validation_schema)
|
||||
)
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
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])
|
||||
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 = []
|
||||
settings = []
|
||||
for key, value in config.items():
|
||||
if key in non_sensors:
|
||||
continue
|
||||
if key in schema.INPUTS:
|
||||
input_sensor = await cg.get_variable(value)
|
||||
cg.add(
|
||||
getattr(var, f"set_{key}_{const.INPUT_SENSOR.lower()}")(input_sensor)
|
||||
)
|
||||
cg.add(getattr(var, f"set_{key}_{const.INPUT_SENSOR}")(input_sensor))
|
||||
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:
|
||||
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))
|
||||
|
||||
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.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
|
||||
)
|
||||
|
25
esphome/components/opentherm/automation.h
Normal file
25
esphome/components/opentherm/automation.h
Normal 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
|
@ -9,3 +9,4 @@ SWITCH = "switch"
|
||||
NUMBER = "number"
|
||||
OUTPUT = "output"
|
||||
INPUT_SENSOR = "input_sensor"
|
||||
SETTING = "setting"
|
||||
|
@ -1,13 +1,14 @@
|
||||
from collections.abc import Awaitable
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ID
|
||||
from . import const
|
||||
from .schema import TSchema
|
||||
from .schema import TSchema, SettingSchema
|
||||
|
||||
opentherm_ns = cg.esphome_ns.namespace("opentherm")
|
||||
OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component)
|
||||
OpenthermData = opentherm_ns.class_("OpenthermData")
|
||||
|
||||
|
||||
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}")
|
||||
|
||||
|
||||
# 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(
|
||||
component_type: str, keys: list[str], schemas: dict[str, TSchema]
|
||||
) -> 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]):
|
||||
messages: set[tuple[str, bool]] = set()
|
||||
def define_setting_readers(component_type: str, keys: list[str]) -> None:
|
||||
for key in keys:
|
||||
messages.add((schemas[key].message, schemas[key].keep_updated))
|
||||
for msg, keep_updated in messages:
|
||||
cg.add_define(
|
||||
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}")
|
||||
if keep_updated:
|
||||
cg.add(hub.add_repeating_message(msg_expr))
|
||||
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:
|
||||
|
@ -63,7 +63,7 @@ void write_f88(const float value, OpenthermData &data) { data.f88(value); }
|
||||
OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
|
||||
OpenthermData data;
|
||||
data.type = 0;
|
||||
data.id = 0;
|
||||
data.id = request_id;
|
||||
data.valueHB = 0;
|
||||
data.valueLB = 0;
|
||||
|
||||
@ -82,28 +82,13 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
|
||||
// NOLINTEND
|
||||
|
||||
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) |
|
||||
(summer_mode_is_active << 5) | (dhw_blocked << 6);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Another special case is OpenTherm version number which is configured at hub level as a constant
|
||||
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,
|
||||
// Next, we start with write requests from switches and other inputs,
|
||||
// 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
|
||||
// supported).
|
||||
@ -116,14 +101,23 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
|
||||
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
|
||||
OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, ,
|
||||
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.
|
||||
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) {
|
||||
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.
|
||||
// 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
|
||||
// good practice anyway.
|
||||
this->add_repeating_message(MessageId::STATUS);
|
||||
|
||||
// Also ensure that we start communication with the STATUS message
|
||||
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();
|
||||
this->write_initial_messages_(this->messages_);
|
||||
this->message_iterator_ = this->messages_.begin();
|
||||
}
|
||||
|
||||
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() {
|
||||
if (this->sync_mode_) {
|
||||
this->sync_loop_();
|
||||
@ -184,29 +196,18 @@ void OpenthermHub::loop() {
|
||||
|
||||
auto cur_time = millis();
|
||||
auto const cur_mode = this->opentherm_->get_mode();
|
||||
|
||||
if (this->handle_error_(cur_mode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cur_mode) {
|
||||
case OperationMode::WRITE:
|
||||
case OperationMode::READ:
|
||||
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;
|
||||
case OperationMode::IDLE:
|
||||
this->check_timings_(cur_time);
|
||||
if (this->should_skip_loop_(cur_time)) {
|
||||
break;
|
||||
}
|
||||
@ -219,6 +220,28 @@ void OpenthermHub::loop() {
|
||||
case OperationMode::RECEIVED:
|
||||
this->read_response_();
|
||||
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_();
|
||||
// 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(); })) {
|
||||
ESP_LOGE(TAG, "Hub timeout triggered during send");
|
||||
this->stop_opentherm_();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->opentherm_->is_error()) {
|
||||
this->handle_protocol_write_error_();
|
||||
this->stop_opentherm_();
|
||||
// Check for errors and ensure we are in the right state (message sent successfully)
|
||||
if (this->handle_error_(this->opentherm_->get_mode())) {
|
||||
return;
|
||||
} else if (!this->opentherm_->is_sent()) {
|
||||
ESP_LOGW(TAG, "Unexpected state after sending request: %s",
|
||||
@ -257,19 +284,20 @@ void OpenthermHub::sync_loop_() {
|
||||
|
||||
// Listen for the response
|
||||
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(); })) {
|
||||
ESP_LOGE(TAG, "Hub timeout triggered during receive");
|
||||
this->stop_opentherm_();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->opentherm_->is_timeout()) {
|
||||
this->handle_timeout_error_();
|
||||
this->stop_opentherm_();
|
||||
return;
|
||||
} else if (this->opentherm_->is_protocol_error()) {
|
||||
this->handle_protocol_read_error_();
|
||||
this->stop_opentherm_();
|
||||
// Check for errors and ensure we are in the right state (message received successfully)
|
||||
if (this->handle_error_(this->opentherm_->get_mode())) {
|
||||
return;
|
||||
} else if (!this->opentherm_->has_message()) {
|
||||
ESP_LOGW(TAG, "Unexpected state after receiving response: %s",
|
||||
@ -281,17 +309,13 @@ void OpenthermHub::sync_loop_() {
|
||||
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) {
|
||||
ESP_LOGW(TAG,
|
||||
"%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.",
|
||||
(int) (cur_time - this->last_conversation_start_));
|
||||
this->stop_opentherm_();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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_() {
|
||||
if (this->sending_initial_ && this->current_message_iterator_ == this->initial_messages_.end()) {
|
||||
this->sending_initial_ = false;
|
||||
this->current_message_iterator_ = this->repeating_messages_.begin();
|
||||
} else if (this->current_message_iterator_ == this->repeating_messages_.end()) {
|
||||
this->current_message_iterator_ = this->repeating_messages_.begin();
|
||||
if (this->message_iterator_ == this->messages_.end()) {
|
||||
if (this->sending_initial_) {
|
||||
this->sending_initial_ = false;
|
||||
this->write_repeating_messages_(this->messages_);
|
||||
}
|
||||
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,
|
||||
this->opentherm_->message_id_to_str((MessageId) request.id));
|
||||
@ -331,37 +358,48 @@ void OpenthermHub::read_response_() {
|
||||
|
||||
this->stop_opentherm_();
|
||||
|
||||
this->before_process_response_callback_.call(response);
|
||||
this->process_response(response);
|
||||
|
||||
this->current_message_iterator_++;
|
||||
this->message_iterator_++;
|
||||
}
|
||||
|
||||
void OpenthermHub::stop_opentherm_() {
|
||||
this->opentherm_->stop();
|
||||
this->last_conversation_end_ = millis();
|
||||
}
|
||||
void OpenthermHub::handle_protocol_write_error_() {
|
||||
ESP_LOGW(TAG, "Error while sending request: %s",
|
||||
this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode()));
|
||||
this->opentherm_->debug_data(this->last_request_);
|
||||
}
|
||||
void OpenthermHub::handle_protocol_read_error_() {
|
||||
|
||||
void OpenthermHub::handle_protocol_error_() {
|
||||
OpenThermError error;
|
||||
this->opentherm_->get_protocol_error(error);
|
||||
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);
|
||||
}
|
||||
void OpenthermHub::handle_timeout_error_() {
|
||||
ESP_LOGW(TAG, "Receive response timed out at a protocol level");
|
||||
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() {
|
||||
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:");
|
||||
LOG_PIN(" In: ", this->in_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, " Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_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, " Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, )));
|
||||
ESP_LOGCONFIG(TAG, " Initial requests:");
|
||||
for (auto type : this->initial_messages_) {
|
||||
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type)));
|
||||
for (auto type : initial_messages) {
|
||||
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Repeating requests:");
|
||||
for (auto type : this->repeating_messages_) {
|
||||
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type)));
|
||||
for (auto type : repeating_messages) {
|
||||
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,9 @@
|
||||
namespace esphome {
|
||||
namespace opentherm {
|
||||
|
||||
static const uint8_t REPEATING_MESSAGE_ORDER = 255;
|
||||
static const uint8_t INITIAL_UNORDERED_MESSAGE_ORDER = 254;
|
||||
|
||||
// OpenTherm component for ESPHome
|
||||
class OpenthermHub : public Component {
|
||||
protected:
|
||||
@ -58,15 +61,12 @@ class OpenthermHub : public Component {
|
||||
|
||||
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, )
|
||||
|
||||
// The set of initial messages to send on starting communication with the boiler
|
||||
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
|
||||
OPENTHERM_SETTING_LIST(OPENTHERM_DECLARE_SETTING, )
|
||||
|
||||
bool sending_initial_ = true;
|
||||
// Index for the current request in one of the _requests sets.
|
||||
std::vector<MessageId>::const_iterator current_message_iterator_;
|
||||
std::unordered_map<MessageId, uint8_t> configured_messages_;
|
||||
std::vector<MessageId> messages_;
|
||||
std::vector<MessageId>::const_iterator message_iterator_;
|
||||
|
||||
uint32_t last_conversation_start_ = 0;
|
||||
uint32_t last_conversation_end_ = 0;
|
||||
@ -78,20 +78,25 @@ class OpenthermHub : public Component {
|
||||
// Very likely to happen while using Dallas temperature sensors.
|
||||
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
|
||||
OpenthermData build_request_(MessageId request_id) const;
|
||||
void handle_protocol_write_error_();
|
||||
void handle_protocol_read_error_();
|
||||
bool handle_error_(OperationMode mode);
|
||||
void handle_protocol_error_();
|
||||
void handle_timeout_error_();
|
||||
void handle_timer_error_();
|
||||
void stop_opentherm_();
|
||||
void start_conversation_();
|
||||
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;
|
||||
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) {
|
||||
auto start_time = millis();
|
||||
while (func()) {
|
||||
@ -127,13 +132,18 @@ class OpenthermHub : public Component {
|
||||
|
||||
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, )
|
||||
|
||||
OPENTHERM_SETTING_LIST(OPENTHERM_SET_SETTING, )
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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,
|
||||
// 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_dhw_block(bool value) { this->dhw_block = value; }
|
||||
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; }
|
||||
|
||||
|
@ -52,7 +52,9 @@ bool OpenTherm::initialize() {
|
||||
OpenTherm::instance = this;
|
||||
#endif
|
||||
this->in_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->in_pin_->setup();
|
||||
this->out_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->out_pin_->setup();
|
||||
this->out_pin_->digital_write(true);
|
||||
|
||||
#if defined(ESP32) || defined(USE_ESP_IDF)
|
||||
@ -182,7 +184,7 @@ bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) {
|
||||
}
|
||||
arg->capture_ = 1; // reset counter
|
||||
} 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->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG;
|
||||
arg->stop_timer_();
|
||||
@ -312,21 +314,31 @@ bool OpenTherm::init_esp32_timer_() {
|
||||
}
|
||||
|
||||
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);
|
||||
if (result != ESP_OK) {
|
||||
const auto *error = esp_err_to_name(result);
|
||||
ESP_LOGE(TAG, "Failed to set alarm value. Error: %s", error);
|
||||
this->timer_error_ = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value);
|
||||
if (this->timer_error_ != ESP_OK) {
|
||||
this->timer_error_type_ = TimerErrorType::SET_ALARM_VALUE_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;
|
||||
}
|
||||
|
||||
result = timer_start(this->timer_group_, this->timer_idx_);
|
||||
if (result != ESP_OK) {
|
||||
const auto *error = esp_err_to_name(result);
|
||||
ESP_LOGE(TAG, "Failed to start the timer. Error: %s", error);
|
||||
return;
|
||||
}
|
||||
ESP_LOGE(TAG, "Error occured while manipulating timer (%s): %s", this->timer_error_to_str(this->timer_error_type_),
|
||||
esp_err_to_name(this->timer_error_));
|
||||
|
||||
this->timer_error_ = ESP_OK;
|
||||
this->timer_error_type_ = NO_TIMER_ERROR;
|
||||
}
|
||||
|
||||
// 5 kHz timer_
|
||||
@ -343,21 +355,18 @@ void IRAM_ATTR OpenTherm::start_write_timer_() {
|
||||
|
||||
void IRAM_ATTR OpenTherm::stop_timer_() {
|
||||
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;
|
||||
|
||||
result = timer_pause(this->timer_group_, this->timer_idx_);
|
||||
if (result != ESP_OK) {
|
||||
const auto *error = esp_err_to_name(result);
|
||||
ESP_LOGE(TAG, "Failed to pause the timer. Error: %s", error);
|
||||
this->timer_error_ = timer_pause(this->timer_group_, this->timer_idx_);
|
||||
if (this->timer_error_ != ESP_OK) {
|
||||
this->timer_error_type_ = TimerErrorType::TIMER_PAUSE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
|
||||
if (result != ESP_OK) {
|
||||
const auto *error = esp_err_to_name(result);
|
||||
ESP_LOGE(TAG, "Failed to set timer counter to 0 after pausing. Error: %s", error);
|
||||
return;
|
||||
this->timer_error_ = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
|
||||
if (this->timer_error_ != ESP_OK) {
|
||||
this->timer_error_type_ = TimerErrorType::SET_COUNTER_VALUE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,6 +395,9 @@ void IRAM_ATTR OpenTherm::stop_timer_() {
|
||||
timer1_detachInterrupt();
|
||||
}
|
||||
|
||||
// There is nothing to report on ESP8266
|
||||
void OpenTherm::report_and_reset_timer_error() {}
|
||||
|
||||
#endif // END ESP8266
|
||||
|
||||
// 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(ERROR_PROTOCOL)
|
||||
TO_STRING_MEMBER(ERROR_TIMEOUT)
|
||||
TO_STRING_MEMBER(ERROR_TIMER)
|
||||
default:
|
||||
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) {
|
||||
TO_STRING_MEMBER(NO_ERROR)
|
||||
TO_STRING_MEMBER(NO_TRANSITION)
|
||||
@ -427,6 +440,17 @@ const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) {
|
||||
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) {
|
||||
switch (message_type) {
|
||||
TO_STRING_MEMBER(READ_DATA)
|
||||
|
@ -36,11 +36,12 @@ enum OperationMode {
|
||||
READ = 2, // reading 32-bit data frame
|
||||
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
|
||||
|
||||
ERROR_PROTOCOL = 8, // manchester protocol data transfer error
|
||||
ERROR_TIMEOUT = 9 // read timeout
|
||||
ERROR_PROTOCOL = 8, // protocol error, can happed only during READ
|
||||
ERROR_TIMEOUT = 9, // timeout while waiting for response from device, only during LISTEN
|
||||
ERROR_TIMER = 10 // error operating the ESP32 timer
|
||||
};
|
||||
|
||||
enum ProtocolErrorType {
|
||||
@ -51,6 +52,14 @@ enum ProtocolErrorType {
|
||||
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 {
|
||||
READ_DATA = 0,
|
||||
READ_ACK = 4,
|
||||
@ -299,7 +308,9 @@ class OpenTherm {
|
||||
*
|
||||
* @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
|
||||
@ -313,14 +324,22 @@ class OpenTherm {
|
||||
*/
|
||||
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; }
|
||||
|
||||
OperationMode get_mode() { return mode_; }
|
||||
|
||||
void debug_data(OpenthermData &data);
|
||||
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 *operation_mode_to_str(OperationMode mode);
|
||||
const char *message_id_to_str(MessageId id);
|
||||
@ -349,10 +368,12 @@ class OpenTherm {
|
||||
uint32_t data_;
|
||||
uint8_t bit_pos_;
|
||||
int32_t timeout_counter_; // <0 no timeout
|
||||
|
||||
int32_t device_timeout_;
|
||||
|
||||
#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_();
|
||||
void start_esp32_timer_(uint64_t alarm_value);
|
||||
#endif
|
||||
|
@ -28,6 +28,9 @@ namespace opentherm {
|
||||
#ifndef OPENTHERM_INPUT_SENSOR_LIST
|
||||
#define OPENTHERM_INPUT_SENSOR_LIST(F, sep)
|
||||
#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
|
||||
#define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity;
|
||||
@ -36,6 +39,7 @@ namespace opentherm {
|
||||
#define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity;
|
||||
#define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity;
|
||||
#define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity;
|
||||
#define OPENTHERM_DECLARE_SETTING(type, entity, def) type entity = def;
|
||||
|
||||
// Setter macros
|
||||
#define OPENTHERM_SET_SENSOR(entity) \
|
||||
@ -56,6 +60,9 @@ namespace opentherm {
|
||||
#define OPENTHERM_SET_INPUT_SENSOR(entity) \
|
||||
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 =====
|
||||
|
||||
// *_MESSAGE_HANDLERS are generated in defines.h and look like this:
|
||||
@ -85,6 +92,9 @@ namespace opentherm {
|
||||
#ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS
|
||||
#define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)
|
||||
#endif
|
||||
#ifndef OPENTHERM_SETTING_MESSAGE_HANDLERS
|
||||
#define OPENTHERM_SETTING_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)
|
||||
#endif
|
||||
|
||||
// Write data request builders
|
||||
#define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \
|
||||
@ -92,6 +102,7 @@ namespace opentherm {
|
||||
data.type = MessageType::WRITE_DATA; \
|
||||
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_SETTING(key, msg_data) message_data::write_##msg_data(this->key, data);
|
||||
#define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \
|
||||
return data; \
|
||||
}
|
||||
|
@ -2,8 +2,9 @@
|
||||
# inputs of the OpenTherm component.
|
||||
|
||||
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 (
|
||||
UNIT_CELSIUS,
|
||||
UNIT_EMPTY,
|
||||
@ -64,6 +65,7 @@ class SensorSchema(EntitySchema):
|
||||
icon: Optional[str] = None
|
||||
device_class: Optional[str] = None
|
||||
disabled_by_default: bool = False
|
||||
order: Optional[int] = None
|
||||
|
||||
|
||||
SENSORS: dict[str, SensorSchema] = {
|
||||
@ -399,6 +401,7 @@ SENSORS: dict[str, SensorSchema] = {
|
||||
message="OT_VERSION_DEVICE",
|
||||
keep_updated=False,
|
||||
message_data="f88",
|
||||
order=2,
|
||||
),
|
||||
"device_type": SensorSchema(
|
||||
description="Device product type",
|
||||
@ -409,6 +412,7 @@ SENSORS: dict[str, SensorSchema] = {
|
||||
message="VERSION_DEVICE",
|
||||
keep_updated=False,
|
||||
message_data="u8_hb",
|
||||
order=0,
|
||||
),
|
||||
"device_version": SensorSchema(
|
||||
description="Device product version",
|
||||
@ -419,6 +423,7 @@ SENSORS: dict[str, SensorSchema] = {
|
||||
message="VERSION_DEVICE",
|
||||
keep_updated=False,
|
||||
message_data="u8_lb",
|
||||
order=0,
|
||||
),
|
||||
"device_id": SensorSchema(
|
||||
description="Device ID code",
|
||||
@ -429,6 +434,7 @@ SENSORS: dict[str, SensorSchema] = {
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="u8_lb",
|
||||
order=4,
|
||||
),
|
||||
"otc_hc_ratio_ub": SensorSchema(
|
||||
description="OTC heat curve ratio upper bound",
|
||||
@ -457,6 +463,7 @@ SENSORS: dict[str, SensorSchema] = {
|
||||
class BinarySensorSchema(EntitySchema):
|
||||
icon: Optional[str] = None
|
||||
device_class: Optional[str] = None
|
||||
order: Optional[int] = None
|
||||
|
||||
|
||||
BINARY_SENSORS: dict[str, BinarySensorSchema] = {
|
||||
@ -525,48 +532,56 @@ BINARY_SENSORS: dict[str, BinarySensorSchema] = {
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_0",
|
||||
order=4,
|
||||
),
|
||||
"control_type_on_off": BinarySensorSchema(
|
||||
description="Configuration: Control type is on/off",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_1",
|
||||
order=4,
|
||||
),
|
||||
"cooling_supported": BinarySensorSchema(
|
||||
description="Configuration: Cooling supported",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_2",
|
||||
order=4,
|
||||
),
|
||||
"dhw_storage_tank": BinarySensorSchema(
|
||||
description="Configuration: DHW storage tank",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_3",
|
||||
order=4,
|
||||
),
|
||||
"controller_pump_control_allowed": BinarySensorSchema(
|
||||
description="Configuration: Controller pump control allowed",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_4",
|
||||
order=4,
|
||||
),
|
||||
"ch2_present": BinarySensorSchema(
|
||||
description="Configuration: CH2 present",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_5",
|
||||
order=4,
|
||||
),
|
||||
"water_filling": BinarySensorSchema(
|
||||
description="Configuration: Remote water filling",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_6",
|
||||
order=4,
|
||||
),
|
||||
"heat_mode": BinarySensorSchema(
|
||||
description="Configuration: Heating or cooling",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_7",
|
||||
order=4,
|
||||
),
|
||||
"dhw_setpoint_transfer_enabled": BinarySensorSchema(
|
||||
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"),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@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,
|
||||
),
|
||||
}
|
||||
|
@ -9,12 +9,17 @@ from .schema import TSchema
|
||||
|
||||
|
||||
def create_entities_schema(
|
||||
entities: dict[str, schema.EntitySchema],
|
||||
entities: dict[str, TSchema],
|
||||
get_entity_validation_schema: Callable[[TSchema], cv.Schema],
|
||||
) -> Schema:
|
||||
entity_schema = {}
|
||||
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)
|
||||
|
||||
|
||||
|
@ -13,9 +13,9 @@ PulseCounterStorageBase *get_storage(bool hw_pcnt) {
|
||||
return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage)
|
||||
: (PulseCounterStorageBase *) (new BasicPulseCounterStorage));
|
||||
}
|
||||
#else
|
||||
#else // HAS_PCNT
|
||||
PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; }
|
||||
#endif
|
||||
#endif // HAS_PCNT
|
||||
|
||||
void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) {
|
||||
const uint32_t now = micros();
|
||||
@ -28,14 +28,17 @@ void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg
|
||||
switch (mode) {
|
||||
case PULSE_COUNTER_DISABLE:
|
||||
break;
|
||||
case PULSE_COUNTER_INCREMENT:
|
||||
arg->counter++;
|
||||
break;
|
||||
case PULSE_COUNTER_DECREMENT:
|
||||
arg->counter--;
|
||||
break;
|
||||
case PULSE_COUNTER_INCREMENT: {
|
||||
auto x = arg->counter + 1;
|
||||
arg->counter = x;
|
||||
} break;
|
||||
case PULSE_COUNTER_DECREMENT: {
|
||||
auto x = arg->counter - 1;
|
||||
arg->counter = x;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
|
||||
this->pin = pin;
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
pulse_counter_t BasicPulseCounterStorage::read_raw_value() {
|
||||
pulse_counter_t counter = this->counter;
|
||||
pulse_counter_t ret = counter - this->last_value;
|
||||
@ -141,6 +145,7 @@ bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pulse_counter_t HwPulseCounterStorage::read_raw_value() {
|
||||
pulse_counter_t counter;
|
||||
pcnt_get_counter_value(this->pcnt_unit, &counter);
|
||||
@ -148,7 +153,7 @@ pulse_counter_t HwPulseCounterStorage::read_raw_value() {
|
||||
this->last_value = counter;
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
#endif // HAS_PCNT
|
||||
|
||||
void PulseCounterSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str());
|
||||
|
@ -9,7 +9,7 @@
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
#include <driver/pcnt.h>
|
||||
#define HAS_PCNT
|
||||
#endif
|
||||
#endif // defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
|
||||
namespace esphome {
|
||||
namespace pulse_counter {
|
||||
@ -22,9 +22,9 @@ enum PulseCounterCountMode {
|
||||
|
||||
#ifdef HAS_PCNT
|
||||
using pulse_counter_t = int16_t;
|
||||
#else
|
||||
#else // HAS_PCNT
|
||||
using pulse_counter_t = int32_t;
|
||||
#endif
|
||||
#endif // HAS_PCNT
|
||||
|
||||
struct PulseCounterStorageBase {
|
||||
virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0;
|
||||
@ -57,7 +57,7 @@ struct HwPulseCounterStorage : public PulseCounterStorageBase {
|
||||
pcnt_unit_t pcnt_unit;
|
||||
pcnt_channel_t pcnt_channel;
|
||||
};
|
||||
#endif
|
||||
#endif // HAS_PCNT
|
||||
|
||||
PulseCounterStorageBase *get_storage(bool hw_pcnt = false);
|
||||
|
||||
|
@ -1 +1,4 @@
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
CONF_DRAW_FROM_ORIGIN = "draw_from_origin"
|
||||
CONF_DRAW_ROUNDING = "draw_rounding"
|
||||
|
@ -24,6 +24,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import TimePeriod
|
||||
|
||||
from . import CONF_DRAW_FROM_ORIGIN, CONF_DRAW_ROUNDING
|
||||
from .models import DriverChip
|
||||
|
||||
DEPENDENCIES = ["spi"]
|
||||
@ -41,7 +42,6 @@ COLOR_ORDERS = {
|
||||
}
|
||||
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
|
||||
|
||||
CONF_DRAW_FROM_ORIGIN = "draw_from_origin"
|
||||
DELAY_FLAG = 0xFF
|
||||
|
||||
|
||||
@ -78,56 +78,81 @@ def _validate(config):
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
display.FULL_DISPLAY_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(QSPI_DBI),
|
||||
cv.Required(CONF_MODEL): cv.one_of(
|
||||
*DriverChip.chips.keys(), upper=True
|
||||
),
|
||||
cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence),
|
||||
cv.Required(CONF_DIMENSIONS): cv.Any(
|
||||
cv.dimensions,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_WIDTH): validate_dimension,
|
||||
cv.Required(CONF_HEIGHT): validate_dimension,
|
||||
cv.Optional(
|
||||
CONF_OFFSET_HEIGHT, default=0
|
||||
): validate_dimension,
|
||||
cv.Optional(
|
||||
CONF_OFFSET_WIDTH, default=0
|
||||
): validate_dimension,
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_TRANSFORM): cv.Schema(
|
||||
def power_of_two(value):
|
||||
value = cv.int_range(1, 128)(value)
|
||||
if value & (value - 1) != 0:
|
||||
raise cv.Invalid("value must be a power of two")
|
||||
return value
|
||||
|
||||
|
||||
BASE_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(QSPI_DBI),
|
||||
cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence),
|
||||
cv.Required(CONF_DIMENSIONS): cv.Any(
|
||||
cv.dimensions,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
|
||||
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
|
||||
cv.Required(CONF_WIDTH): validate_dimension,
|
||||
cv.Required(CONF_HEIGHT): validate_dimension,
|
||||
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_INVERT_COLORS, default=False): cv.boolean,
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range(
|
||||
0, 0xFF, min_included=True, max_included=True
|
||||
),
|
||||
cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean,
|
||||
}
|
||||
).extend(
|
||||
spi.spi_device_schema(
|
||||
cs_pin_required=False,
|
||||
default_mode="MODE0",
|
||||
default_data_rate=10e6,
|
||||
quad=True,
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean,
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range(
|
||||
0, 0xFF, min_included=True, max_included=True
|
||||
),
|
||||
}
|
||||
).extend(
|
||||
spi.spi_device_schema(
|
||||
cs_pin_required=False,
|
||||
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,
|
||||
)
|
||||
@ -152,6 +177,7 @@ async def to_code(config):
|
||||
cg.add(var.set_brightness(config[CONF_BRIGHTNESS]))
|
||||
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_rounding(config[CONF_DRAW_ROUNDING]))
|
||||
if enable_pin := config.get(CONF_ENABLE_PIN):
|
||||
enable = await cg.gpio_pin_expression(enable_pin)
|
||||
cg.add(var.set_enable_pin(enable))
|
||||
@ -163,7 +189,8 @@ async def to_code(config):
|
||||
if transform := config.get(CONF_TRANSFORM):
|
||||
cg.add(var.set_mirror_x(transform[CONF_MIRROR_X]))
|
||||
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:
|
||||
dimensions = config[CONF_DIMENSIONS]
|
||||
|
@ -1,5 +1,10 @@
|
||||
# Commands
|
||||
from esphome.const import CONF_INVERT_COLORS, CONF_SWAP_XY
|
||||
|
||||
from . import CONF_DRAW_ROUNDING
|
||||
|
||||
SW_RESET_CMD = 0x01
|
||||
SLEEP_IN = 0x10
|
||||
SLEEP_OUT = 0x11
|
||||
NORON = 0x13
|
||||
INVERT_OFF = 0x20
|
||||
@ -24,11 +29,12 @@ PAGESEL = 0xFE
|
||||
class DriverChip:
|
||||
chips = {}
|
||||
|
||||
def __init__(self, name: str):
|
||||
def __init__(self, name: str, defaults=None):
|
||||
name = name.upper()
|
||||
self.name = name
|
||||
self.chips[name] = self
|
||||
self.initsequence = []
|
||||
self.defaults = defaults or {}
|
||||
|
||||
def cmd(self, c, *args):
|
||||
"""
|
||||
@ -59,9 +65,246 @@ chip.cmd(TEON, 0x00)
|
||||
chip.cmd(PIXFMT, 0x55)
|
||||
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(0xC1, 0x33)
|
||||
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")
|
||||
|
@ -33,19 +33,12 @@ void QspiDbi::update() {
|
||||
this->do_update_();
|
||||
if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
|
||||
return;
|
||||
// Start addresses and widths/heights must be divisible by 2 (CASET/RASET restriction in datasheet)
|
||||
if (this->x_low_ % 2 == 1) {
|
||||
this->x_low_--;
|
||||
}
|
||||
if (this->x_high_ % 2 == 0) {
|
||||
this->x_high_++;
|
||||
}
|
||||
if (this->y_low_ % 2 == 1) {
|
||||
this->y_low_--;
|
||||
}
|
||||
if (this->y_high_ % 2 == 0) {
|
||||
this->y_high_++;
|
||||
}
|
||||
// Some chips require that the drawing window be aligned on certain boundaries
|
||||
auto dr = this->draw_rounding_;
|
||||
this->x_low_ = this->x_low_ / dr * dr;
|
||||
this->y_low_ = this->y_low_ / dr * dr;
|
||||
this->x_high_ = (this->x_high_ + dr) / dr * dr - 1;
|
||||
this->y_high_ = (this->y_high_ + dr) / dr * dr - 1;
|
||||
if (this->draw_from_origin_) {
|
||||
this->x_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);
|
||||
} else {
|
||||
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++) {
|
||||
this->write_cmd_addr_data(8, 0x32, 24, cmd, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4);
|
||||
cmd = 0x3C00;
|
||||
this->write_cmd_addr_data(0, 0, 0, 0, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4);
|
||||
}
|
||||
}
|
||||
this->disable();
|
||||
@ -220,6 +212,7 @@ void QspiDbi::dump_config() {
|
||||
ESP_LOGCONFIG("", "Model: %s", this->model_);
|
||||
ESP_LOGCONFIG(TAG, " Height: %u", this->height_);
|
||||
ESP_LOGCONFIG(TAG, " Width: %u", this->width_);
|
||||
ESP_LOGCONFIG(TAG, " Draw rounding: %u", this->draw_rounding_);
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));
|
||||
|
@ -4,12 +4,10 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esphome/components/display/display_buffer.h"
|
||||
#include "esphome/components/display/display_color_utils.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
|
||||
#include "esp_lcd_panel_rgb.h"
|
||||
|
||||
@ -105,6 +103,7 @@ class QspiDbi : public display::DisplayBuffer,
|
||||
int get_height_internal() override { return this->height_; }
|
||||
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 set_draw_rounding(unsigned rounding) { this->draw_rounding_ = rounding; }
|
||||
|
||||
protected:
|
||||
void check_buffer_() {
|
||||
@ -161,6 +160,7 @@ class QspiDbi : public display::DisplayBuffer,
|
||||
bool mirror_x_{};
|
||||
bool mirror_y_{};
|
||||
bool draw_from_origin_{false};
|
||||
unsigned draw_rounding_{2};
|
||||
uint8_t brightness_{0xD0};
|
||||
const char *model_{"Unknown"};
|
||||
std::vector<std::vector<uint8_t>> init_sequences_{};
|
||||
|
@ -8,7 +8,7 @@ namespace 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) {
|
||||
static rmt_channel_t next_rmt_channel = RMT_CHANNEL_0;
|
||||
this->channel_ = next_rmt_channel;
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5
|
||||
#include <driver/rmt.h>
|
||||
#endif
|
||||
|
||||
@ -112,25 +112,43 @@ class RemoteComponentBase {
|
||||
#ifdef USE_ESP32
|
||||
class RemoteRMTChannel {
|
||||
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(rmt_channel_t channel, uint8_t mem_block_num = 1);
|
||||
|
||||
void config_rmt(rmt_config_t &rmt);
|
||||
void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
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;
|
||||
#endif
|
||||
return us * ticks_per_ten_us / 10;
|
||||
}
|
||||
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;
|
||||
#endif
|
||||
return (ticks * 10) / ticks_per_ten_us;
|
||||
}
|
||||
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};
|
||||
uint8_t mem_block_num_;
|
||||
uint8_t clock_divider_{80};
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
from esphome import pins
|
||||
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
|
||||
from esphome.const import (
|
||||
CONF_BUFFER_SIZE,
|
||||
CONF_CLOCK_DIVIDER,
|
||||
CONF_CLOCK_RESOLUTION,
|
||||
CONF_DUMP,
|
||||
CONF_FILTER,
|
||||
CONF_ID,
|
||||
@ -12,12 +13,17 @@ from esphome.const import (
|
||||
CONF_MEMORY_BLOCKS,
|
||||
CONF_PIN,
|
||||
CONF_RMT_CHANNEL,
|
||||
CONF_RMT_SYMBOLS,
|
||||
CONF_TOLERANCE,
|
||||
CONF_TYPE,
|
||||
CONF_USE_DMA,
|
||||
CONF_VALUE,
|
||||
)
|
||||
from esphome.core import CORE, TimePeriod
|
||||
|
||||
CONF_FILTER_SYMBOLS = "filter_symbols"
|
||||
CONF_RECEIVE_SYMBOLS = "receive_symbols"
|
||||
|
||||
AUTO_LOAD = ["remote_base"]
|
||||
remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver")
|
||||
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.Range(max=TimePeriod(microseconds=4294967295)),
|
||||
),
|
||||
cv.SplitDefault(CONF_CLOCK_DIVIDER, esp32=80): cv.All(
|
||||
cv.only_on_esp32, cv.Range(min=1, max=255)
|
||||
cv.SplitDefault(CONF_CLOCK_DIVIDER, esp32_arduino=80): cv.All(
|
||||
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.positive_time_period_microseconds,
|
||||
cv.Range(max=TimePeriod(microseconds=4294967295)),
|
||||
),
|
||||
cv.Optional(CONF_MEMORY_BLOCKS, default=3): cv.Range(min=1, max=8),
|
||||
cv.Optional(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=False),
|
||||
cv.SplitDefault(CONF_MEMORY_BLOCKS, esp32_arduino=3): cv.All(
|
||||
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)
|
||||
)
|
||||
@ -114,13 +148,27 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
|
||||
async def to_code(config):
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
if CORE.is_esp32:
|
||||
if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None:
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID], pin, rmt_channel, config[CONF_MEMORY_BLOCKS]
|
||||
)
|
||||
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]))
|
||||
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:
|
||||
var = cg.new_Pvariable(config[CONF_ID], pin, config[CONF_MEMORY_BLOCKS])
|
||||
cg.add(var.set_clock_divider(config[CONF_CLOCK_DIVIDER]))
|
||||
if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None:
|
||||
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:
|
||||
var = cg.new_Pvariable(config[CONF_ID], pin)
|
||||
|
||||
|
@ -5,6 +5,10 @@
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
||||
#include <driver/rmt_rx.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_receiver {
|
||||
|
||||
@ -25,6 +29,21 @@ struct RemoteReceiverComponentStore {
|
||||
uint32_t filter_us{10};
|
||||
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
|
||||
|
||||
class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
|
||||
@ -33,9 +52,10 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
|
||||
,
|
||||
public remote_base::RemoteRMTChannel
|
||||
#endif
|
||||
|
||||
{
|
||||
public:
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5
|
||||
RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1)
|
||||
: RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {}
|
||||
|
||||
@ -49,19 +69,32 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
|
||||
void loop() override;
|
||||
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_filter_us(uint32_t filter_us) { this->filter_us_ = filter_us; }
|
||||
void set_idle_us(uint32_t idle_us) { this->idle_us_ = idle_us; }
|
||||
|
||||
protected:
|
||||
#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_;
|
||||
#endif
|
||||
esp_err_t error_code_{ESP_OK};
|
||||
std::string error_string_{""};
|
||||
#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_;
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
#endif
|
||||
|
@ -2,15 +2,104 @@
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <driver/rmt.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_receiver {
|
||||
|
||||
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() {
|
||||
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();
|
||||
rmt_config_t rmt{};
|
||||
this->config_rmt(rmt);
|
||||
@ -59,7 +148,9 @@ void RemoteReceiverComponent::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RemoteReceiverComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Remote Receiver:");
|
||||
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 "
|
||||
"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, " RMT memory blocks: %d", this->mem_block_num_);
|
||||
ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_);
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, " Tolerance: %" PRIu32 "%s", this->tolerance_,
|
||||
(this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " 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() {
|
||||
#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;
|
||||
auto *item = (rmt_item32_t *) xRingbufferReceive(this->ringbuf_, &len, 0);
|
||||
if (item != nullptr) {
|
||||
this->decode_rmt_(item, len);
|
||||
this->decode_rmt_(item, len / sizeof(rmt_item32_t));
|
||||
vRingbufferReturnItem(this->ringbuf_, item);
|
||||
|
||||
if (this->temp_.empty())
|
||||
@ -93,13 +219,18 @@ void RemoteReceiverComponent::loop() {
|
||||
this->temp_.push_back(-this->idle_us_);
|
||||
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;
|
||||
uint32_t prev_length = 0;
|
||||
this->temp_.clear();
|
||||
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_);
|
||||
|
||||
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
|
||||
for (size_t i = 0; i < item_count; i++) {
|
||||
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)) {
|
||||
prev_length += item[i].duration0;
|
||||
} else {
|
||||
@ -140,7 +272,8 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) {
|
||||
}
|
||||
|
||||
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)) {
|
||||
prev_length += item[i].duration1;
|
||||
} else {
|
||||
|
@ -2,12 +2,23 @@ from esphome import automation, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32_rmt, remote_base
|
||||
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"]
|
||||
|
||||
CONF_ON_TRANSMIT = "on_transmit"
|
||||
CONF_ON_COMPLETE = "on_complete"
|
||||
CONF_ONE_WIRE = "one_wire"
|
||||
|
||||
remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter")
|
||||
RemoteTransmitterComponent = remote_transmitter_ns.class_(
|
||||
@ -22,7 +33,28 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All(
|
||||
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_COMPLETE): automation.validate_automation(single=True),
|
||||
}
|
||||
@ -31,8 +63,24 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
async def to_code(config):
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None:
|
||||
var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel)
|
||||
if CORE.is_esp32:
|
||||
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:
|
||||
var = cg.new_Pvariable(config[CONF_ID], pin)
|
||||
await cg.register_component(var, config)
|
||||
|
@ -5,6 +5,10 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
||||
#include <driver/rmt_tx.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_transmitter {
|
||||
|
||||
@ -16,7 +20,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5
|
||||
RemoteTransmitterComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1)
|
||||
: remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {}
|
||||
|
||||
@ -29,10 +33,16 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
||||
|
||||
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; }
|
||||
|
||||
#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_complete_trigger() const { return this->complete_trigger_; };
|
||||
|
||||
@ -54,7 +64,15 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
||||
|
||||
uint32_t current_carrier_frequency_{38000};
|
||||
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_;
|
||||
#endif
|
||||
esp_err_t error_code_{ESP_OK};
|
||||
std::string error_string_{""};
|
||||
bool inverted_{false};
|
||||
|
@ -9,13 +9,22 @@ namespace 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() {
|
||||
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, " RMT memory blocks: %d", this->mem_block_num_);
|
||||
ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_);
|
||||
#endif
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
|
||||
if (this->current_carrier_frequency_ != 0 && this->carrier_duty_percent_ != 100) {
|
||||
@ -29,6 +38,72 @@ void RemoteTransmitterComponent::dump_config() {
|
||||
}
|
||||
|
||||
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{};
|
||||
|
||||
this->config_rmt(c);
|
||||
@ -76,6 +151,7 @@ void RemoteTransmitterComponent::configure_rmt_() {
|
||||
}
|
||||
this->initialized_ = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
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_.reserve((this->temp_.get_data().size() + 1) / 2);
|
||||
uint32_t rmt_i = 0;
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
rmt_symbol_word_t rmt_item;
|
||||
#else
|
||||
rmt_item32_t rmt_item;
|
||||
#endif
|
||||
|
||||
for (int32_t val : this->temp_.get_data()) {
|
||||
bool level = val >= 0;
|
||||
@ -125,6 +205,31 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
|
||||
return;
|
||||
}
|
||||
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++) {
|
||||
esp_err_t error = rmt_write_items(this->channel_, this->rmt_temp_.data(), this->rmt_temp_.size(), true);
|
||||
if (error != ESP_OK) {
|
||||
@ -136,6 +241,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
|
||||
if (i + 1 < send_times)
|
||||
delayMicroseconds(send_wait);
|
||||
}
|
||||
#endif
|
||||
this->complete_trigger_->trigger();
|
||||
}
|
||||
|
||||
|
@ -93,13 +93,17 @@ void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore
|
||||
int8_t rotation_dir = 0;
|
||||
uint16_t new_state = STATE_LOOKUP_TABLE[input_state];
|
||||
if ((new_state & arg->resolution & STATE_HAS_INCREMENTED) != 0) {
|
||||
if (arg->counter < arg->max_value)
|
||||
arg->counter++;
|
||||
if (arg->counter < arg->max_value) {
|
||||
auto x = arg->counter + 1;
|
||||
arg->counter = x;
|
||||
}
|
||||
rotation_dir = 1;
|
||||
}
|
||||
if ((new_state & arg->resolution & STATE_HAS_DECREMENTED) != 0) {
|
||||
if (arg->counter > arg->min_value)
|
||||
arg->counter--;
|
||||
if (arg->counter > arg->min_value) {
|
||||
auto x = arg->counter - 1;
|
||||
arg->counter = x;
|
||||
}
|
||||
rotation_dir = -1;
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#include "uart_component_esp32_arduino.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "uart_component_esp32_arduino.h"
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
@ -118,7 +118,7 @@ void ESP32ArduinoUARTComponent::setup() {
|
||||
}
|
||||
#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.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
|
@ -1,11 +1,11 @@
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include "uart_component_esp_idf.h"
|
||||
#include <cinttypes>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
@ -84,7 +84,7 @@ void IDFUARTComponent::setup() {
|
||||
}
|
||||
#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.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
|
@ -1660,6 +1660,12 @@ class SplitDefault(Optional):
|
||||
esp32_c3=vol.UNDEFINED,
|
||||
esp32_c3_arduino=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,
|
||||
bk72xx=vol.UNDEFINED,
|
||||
rtl87xx=vol.UNDEFINED,
|
||||
@ -1691,6 +1697,18 @@ class SplitDefault(Optional):
|
||||
self._esp32_c3_idf_default = vol.default_factory(
|
||||
_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._bk72xx_default = vol.default_factory(bk72xx)
|
||||
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.const import (
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
@ -1724,6 +1744,16 @@ class SplitDefault(Optional):
|
||||
return self._esp32_c3_arduino_default
|
||||
if CORE.using_esp_idf:
|
||||
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:
|
||||
if CORE.using_arduino:
|
||||
return self._esp32_arduino_default
|
||||
|
@ -767,7 +767,8 @@ bool mac_address_is_valid(const uint8_t *mac) {
|
||||
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();
|
||||
|
||||
const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop.
|
||||
|
@ -11,6 +11,14 @@
|
||||
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <Esp.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_heap_caps.h>
|
||||
#endif
|
||||
@ -684,20 +692,23 @@ template<class T> class RAMAllocator {
|
||||
};
|
||||
|
||||
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_} {}
|
||||
|
||||
T *allocate(size_t n) {
|
||||
size_t size = n * sizeof(T);
|
||||
T *ptr = nullptr;
|
||||
#ifdef USE_ESP32
|
||||
// External allocation by default or if explicitely requested
|
||||
if ((this->flags_ & Flags::ALLOC_EXTERNAL) || ((this->flags_ & Flags::ALLOC_INTERNAL) == 0)) {
|
||||
if (this->flags_ & Flags::ALLOC_EXTERNAL) {
|
||||
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) || (this->flags_ & Flags::ALLOC_EXTERNAL) == 0)) {
|
||||
ptr = static_cast<T *>(malloc(size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
|
||||
if (ptr == nullptr && this->flags_ & Flags::ALLOC_INTERNAL) {
|
||||
ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
|
||||
}
|
||||
#else
|
||||
// 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
uint8_t flags_{Flags::ALLOW_FAILURE};
|
||||
uint8_t flags_{ALLOC_INTERNAL | ALLOC_EXTERNAL};
|
||||
};
|
||||
|
||||
template<class T> using ExternalRAMAllocator = RAMAllocator<T>;
|
||||
|
@ -13,8 +13,8 @@ static const char *const TAG = "ring_buffer";
|
||||
|
||||
RingBuffer::~RingBuffer() {
|
||||
if (this->handle_ != nullptr) {
|
||||
vStreamBufferDelete(this->handle_);
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
vRingbufferDelete(this->handle_);
|
||||
RAMAllocator<uint8_t> allocator(RAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
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> 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_);
|
||||
if (rb->storage_ == 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);
|
||||
|
||||
return rb;
|
||||
}
|
||||
|
||||
size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) {
|
||||
if (ticks_to_wait > 0)
|
||||
xStreamBufferSetTriggerLevel(this->handle_, len);
|
||||
size_t bytes_read = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
@ -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 free = this->free();
|
||||
if (free < len) {
|
||||
size_t needed = len - free;
|
||||
uint8_t discard[needed];
|
||||
xStreamBufferReceive(this->handle_, discard, needed, 0);
|
||||
// Free enough space in the ring buffer to fit the new data
|
||||
this->discard_bytes_(len - free);
|
||||
}
|
||||
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) {
|
||||
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
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/stream_buffer.h>
|
||||
#include <freertos/ringbuf.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
@ -82,9 +82,14 @@ class RingBuffer {
|
||||
static std::unique_ptr<RingBuffer> create(size_t len);
|
||||
|
||||
protected:
|
||||
StreamBufferHandle_t handle_;
|
||||
StaticStreamBuffer_t structure_;
|
||||
uint8_t *storage_;
|
||||
/// @brief Discards data from the ring buffer.
|
||||
/// @param discard_bytes amount of bytes to discard
|
||||
/// @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};
|
||||
};
|
||||
|
||||
|
@ -108,6 +108,12 @@ def is_authenticated(handler: BaseHandler) -> bool:
|
||||
return True
|
||||
|
||||
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 True
|
||||
|
@ -12,7 +12,7 @@ pyserial==3.5
|
||||
platformio==6.1.16 # When updating platformio, also update Dockerfile
|
||||
esptool==4.7.0
|
||||
click==8.1.7
|
||||
esphome-dashboard==20241120.0
|
||||
esphome-dashboard==20241217.1
|
||||
aioesphomeapi==24.6.2
|
||||
zeroconf==0.132.2
|
||||
puremagic==1.27
|
||||
|
4
tests/components/adc/test.bk72xx-ard.yaml
Normal file
4
tests/components/adc/test.bk72xx-ard.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
sensor:
|
||||
- platform: adc
|
||||
pin: P23
|
||||
name: Basic ADC Test
|
@ -3,14 +3,12 @@ light:
|
||||
id: led_strip
|
||||
pin: 4
|
||||
num_leds: 60
|
||||
rmt_channel: 0
|
||||
rgb_order: GRB
|
||||
chipset: ws2812
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led_strip2
|
||||
pin: 5
|
||||
num_leds: 60
|
||||
rmt_channel: 1
|
||||
rgb_order: RGB
|
||||
bit0_high: 100µs
|
||||
bit0_low: 100µs
|
||||
|
@ -3,14 +3,12 @@ light:
|
||||
id: led_strip
|
||||
pin: 13
|
||||
num_leds: 60
|
||||
rmt_channel: 6
|
||||
rgb_order: GRB
|
||||
chipset: ws2812
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led_strip2
|
||||
pin: 14
|
||||
num_leds: 60
|
||||
rmt_channel: 2
|
||||
rgb_order: RGB
|
||||
bit0_high: 100µs
|
||||
bit0_low: 100µs
|
||||
|
@ -1,5 +1,7 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
- lambda: 'ESP_LOGD("display","is_connected(): %s", YESNO(id(main_lcd).is_connected()));'
|
||||
|
||||
# Binary sensor publish action tests
|
||||
- binary_sensor.nextion.publish:
|
||||
id: r0_sensor
|
||||
|
@ -16,6 +16,19 @@ opentherm:
|
||||
summer_mode_active: true
|
||||
dhw_block: 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:
|
||||
- platform: opentherm
|
||||
|
144
tests/components/remote_receiver/common-actions.yaml
Normal file
144
tests/components/remote_receiver/common-actions.yaml
Normal 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());
|
14
tests/components/remote_receiver/esp32-common-ard.yaml
Normal file
14
tests/components/remote_receiver/esp32-common-ard.yaml
Normal 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
|
18
tests/components/remote_receiver/esp32-common-idf.yaml
Normal file
18
tests/components/remote_receiver/esp32-common-idf.yaml
Normal 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
|
@ -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
|
@ -3,4 +3,4 @@ substitutions:
|
||||
rmt_channel: "2"
|
||||
|
||||
packages:
|
||||
common: !include esp32-common.yaml
|
||||
common: !include esp32-common-ard.yaml
|
||||
|
@ -3,4 +3,4 @@ substitutions:
|
||||
rmt_channel: "2"
|
||||
|
||||
packages:
|
||||
common: !include esp32-common.yaml
|
||||
common: !include esp32-common-ard.yaml
|
||||
|
@ -1,6 +1,10 @@
|
||||
substitutions:
|
||||
pin: GPIO2
|
||||
rmt_channel: "2"
|
||||
clock_resolution: "2000000"
|
||||
filter_symbols: "2"
|
||||
receive_symbols: "4"
|
||||
rmt_symbols: "64"
|
||||
use_dma: "true"
|
||||
|
||||
packages:
|
||||
common: !include esp32-common.yaml
|
||||
common: !include esp32-common-idf.yaml
|
||||
|
@ -1,6 +1,10 @@
|
||||
substitutions:
|
||||
pin: GPIO2
|
||||
rmt_channel: "2"
|
||||
clock_resolution: "2000000"
|
||||
filter_symbols: "2"
|
||||
receive_symbols: "4"
|
||||
rmt_symbols: "64"
|
||||
use_dma: "true"
|
||||
|
||||
packages:
|
||||
common: !include esp32-common.yaml
|
||||
common: !include esp32-common-idf.yaml
|
||||
|
@ -1,6 +1,10 @@
|
||||
substitutions:
|
||||
pin: GPIO38
|
||||
rmt_channel: "5"
|
||||
clock_resolution: "2000000"
|
||||
filter_symbols: "2"
|
||||
receive_symbols: "4"
|
||||
rmt_symbols: "64"
|
||||
use_dma: "true"
|
||||
|
||||
packages:
|
||||
common: !include esp32-common.yaml
|
||||
common: !include esp32-common-idf.yaml
|
||||
|
@ -2,150 +2,7 @@ remote_receiver:
|
||||
id: rcvr
|
||||
pin: GPIO5
|
||||
dump: all
|
||||
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: %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());
|
||||
<<: !include common-actions.yaml
|
||||
|
||||
binary_sensor:
|
||||
- platform: remote_receiver
|
||||
|
@ -0,0 +1,8 @@
|
||||
remote_transmitter:
|
||||
- id: xmitr
|
||||
pin: ${pin}
|
||||
rmt_channel: ${rmt_channel}
|
||||
carrier_duty_percent: 50%
|
||||
|
||||
packages:
|
||||
buttons: !include common-buttons.yaml
|
11
tests/components/remote_transmitter/esp32-common-idf.yaml
Normal file
11
tests/components/remote_transmitter/esp32-common-idf.yaml
Normal 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
|
@ -1,8 +0,0 @@
|
||||
remote_transmitter:
|
||||
id: rcvr
|
||||
pin: ${pin}
|
||||
rmt_channel: ${rmt_channel}
|
||||
carrier_duty_percent: 50%
|
||||
|
||||
packages:
|
||||
buttons: !include common-buttons.yaml
|
@ -3,4 +3,4 @@ substitutions:
|
||||
rmt_channel: "2"
|
||||
|
||||
packages:
|
||||
common: !include esp32-common.yaml
|
||||
common: !include esp32-common-ard.yaml
|
||||
|
@ -3,4 +3,4 @@ substitutions:
|
||||
rmt_channel: "1"
|
||||
|
||||
packages:
|
||||
common: !include esp32-common.yaml
|
||||
common: !include esp32-common-ard.yaml
|
||||
|
@ -1,6 +1,9 @@
|
||||
substitutions:
|
||||
pin: GPIO2
|
||||
rmt_channel: "1"
|
||||
clock_resolution: "2000000"
|
||||
one_wire: "true"
|
||||
rmt_symbols: "64"
|
||||
use_dma: "true"
|
||||
|
||||
packages:
|
||||
common: !include esp32-common.yaml
|
||||
common: !include esp32-common-idf.yaml
|
||||
|
@ -1,6 +1,9 @@
|
||||
substitutions:
|
||||
pin: GPIO2
|
||||
rmt_channel: "2"
|
||||
clock_resolution: "2000000"
|
||||
one_wire: "true"
|
||||
rmt_symbols: "64"
|
||||
use_dma: "true"
|
||||
|
||||
packages:
|
||||
common: !include esp32-common.yaml
|
||||
common: !include esp32-common-idf.yaml
|
||||
|
@ -1,6 +1,9 @@
|
||||
substitutions:
|
||||
pin: GPIO38
|
||||
rmt_channel: "3"
|
||||
clock_resolution: "2000000"
|
||||
one_wire: "true"
|
||||
rmt_symbols: "64"
|
||||
use_dma: "true"
|
||||
|
||||
packages:
|
||||
common: !include esp32-common.yaml
|
||||
common: !include esp32-common-idf.yaml
|
||||
|
@ -1,5 +1,5 @@
|
||||
remote_transmitter:
|
||||
id: trns
|
||||
id: xmitr
|
||||
pin: GPIO5
|
||||
carrier_duty_percent: 50%
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user