1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-02 16:11:53 +00:00

Compare commits

..

19 Commits

Author SHA1 Message Date
Jesse Hills
628e47f670 Merge pull request #8099 from esphome/bump-2024.12.3
2024.12.3
2025-01-17 13:32:12 +13:00
Jesse Hills
7666581c54 Bump version to 2024.12.3 2025-01-17 12:24:22 +13:00
Kevin Ahrendt
03c36920ff [http_request] Bugfix: run update function in a task (#8018) 2025-01-17 12:24:22 +13:00
Piotr Szulc
abdd6b232f Fixed libretiny preference wrongly detecting change in the data to store (#7990) 2025-01-17 12:24:22 +13:00
Keith Burzinski
4b51ba3fa4 Merge pull request #7989 from esphome/bump-2024.12.2
2024.12.2
2024-12-20 18:56:03 -06:00
Keith Burzinski
499953e3f4 Bump version to 2024.12.2 2024-12-20 14:34:11 -06:00
Keith Burzinski
69f1a81e1d [esp32_ble] Fix for Improv (#7984) 2024-12-20 14:34:11 -06:00
Keith Burzinski
37fcccbb1c [esp32] Fix flash size warning when using IDF (#7983) 2024-12-20 14:34:10 -06:00
Jesse Hills
fe0700166a Merge pull request #7982 from esphome/bump-2024.12.1
2024.12.1
2024-12-19 19:47:30 +13:00
Jesse Hills
d28cf011d1 Bump version to 2024.12.1 2024-12-19 17:07:43 +13:00
Kevin Ahrendt
434879ea04 [core] Bugfix: Implement ring buffer with xRingbuffer (#7973) 2024-12-19 17:07:43 +13:00
Jesse Hills
0f0b829bc6 Merge pull request #7976 from esphome/bump-2024.12.0
2024.12.0
2024-12-18 17:06:44 +13:00
Jesse Hills
d330e73c1e Bump version to 2024.12.0 2024-12-18 11:35:43 +13:00
Jesse Hills
561d92d402 Merge pull request #7975 from esphome/bump-2024.12.0b3
2024.12.0b3
2024-12-18 10:02:03 +13:00
Jesse Hills
1a69236473 Bump version to 2024.12.0b3 2024-12-18 07:43:38 +13:00
Jesse Hills
c86ea99145 [esp32_ble] Use RAMAllocator to avoid panic abort from `new` (#7936) 2024-12-18 07:43:38 +13:00
Jesse Hills
7661609049 Bump esphome-dashboard to 20241217.1 (#7971) 2024-12-18 07:43:38 +13:00
Jesse Hills
c38826824f [dashboard] Accept basic auth header (#7965) 2024-12-18 07:43:38 +13:00
Clyde Stubbs
e890486043 [font] cleanly handle font file format exception (Bugfix) (#7970) 2024-12-18 07:43:38 +13:00
37 changed files with 605 additions and 869 deletions

View File

@@ -1,6 +1,11 @@
from esphome import pins
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
from esphome.components.esp32 import get_esp32_variant
from esphome.const import PLATFORM_ESP8266
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C2,
@@ -10,9 +15,6 @@ 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"]
@@ -100,11 +102,11 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
6: adc1_channel_t.ADC1_CHANNEL_6,
},
VARIANT_ESP32H2: {
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,
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,
},
}

View File

@@ -30,162 +30,109 @@ static const char *const TAG = "debug";
std::string DebugComponent::get_reset_reason_() {
std::string reset_reason;
switch (esp_reset_reason()) {
case ESP_RST_POWERON:
reset_reason = "Reset due to power-on event";
switch (rtc_get_reset_reason(0)) {
case POWERON_RESET:
reset_reason = "Power On Reset";
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;
#endif
default:
reset_reason = "Unknown Reset Reason";
}
case POWER_GLITCH_RESET:
reset_reason = "Power Glitch Reset Digital Core And RTC Module";
break;
#endif
default:
reset_reason = "Unknown Reset Reason";
}
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
return reset_reason;
@@ -347,4 +294,4 @@ void DebugComponent::update_platform_() {
} // namespace debug
} // namespace esphome
#endif // USE_ESP32
#endif

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,8 @@
#include "esp_crt_bundle.h"
#endif
#include "esp_task_wdt.h"
namespace esphome {
namespace http_request {
@@ -117,11 +119,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
return nullptr;
}
App.feed_wdt();
container->feed_wdt();
container->content_length = esp_http_client_fetch_headers(client);
App.feed_wdt();
container->feed_wdt();
container->status_code = esp_http_client_get_status_code(client);
App.feed_wdt();
container->feed_wdt();
if (is_success(container->status_code)) {
container->duration_ms = millis() - start;
return container;
@@ -151,11 +153,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
return nullptr;
}
App.feed_wdt();
container->feed_wdt();
container->content_length = esp_http_client_fetch_headers(client);
App.feed_wdt();
container->feed_wdt();
container->status_code = esp_http_client_get_status_code(client);
App.feed_wdt();
container->feed_wdt();
if (is_success(container->status_code)) {
container->duration_ms = millis() - start;
return container;
@@ -185,8 +187,9 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
return 0;
}
App.feed_wdt();
this->feed_wdt();
int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
this->feed_wdt();
this->bytes_read_ += read_len;
this->duration_ms += (millis() - start);
@@ -201,6 +204,13 @@ void HttpContainerIDF::end() {
esp_http_client_cleanup(this->client_);
}
void HttpContainerIDF::feed_wdt() {
// Tests to see if the executing task has a watchdog timer attached
if (esp_task_wdt_status(nullptr) == ESP_OK) {
App.feed_wdt();
}
}
} // namespace http_request
} // namespace esphome

View File

@@ -18,6 +18,9 @@ class HttpContainerIDF : public HttpContainer {
int read(uint8_t *buf, size_t max_len) override;
void end() override;
/// @brief Feeds the watchdog timer if the executing task has one attached
void feed_wdt();
protected:
esp_http_client_handle_t client_;
};

View File

@@ -1,11 +1,15 @@
import esphome.codegen as cg
from esphome.components import update
import esphome.config_validation as cv
from esphome.const import CONF_SOURCE
import esphome.codegen as cg
from .. import CONF_HTTP_REQUEST_ID, HttpRequestComponent, http_request_ns
from esphome.components import update
from esphome.const import (
CONF_SOURCE,
)
from .. import http_request_ns, CONF_HTTP_REQUEST_ID, HttpRequestComponent
from ..ota import OtaHttpRequestComponent
AUTO_LOAD = ["json"]
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["ota.http_request"]
@@ -21,7 +25,7 @@ CONFIG_SCHEMA = update.UPDATE_SCHEMA.extend(
cv.GenerateID(): cv.declare_id(HttpRequestUpdate),
cv.GenerateID(CONF_OTA_ID): cv.use_id(OtaHttpRequestComponent),
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
cv.Required(CONF_SOURCE): cv.ensure_list(cv.url),
cv.Required(CONF_SOURCE): cv.url,
}
).extend(cv.polling_component_schema("6h"))
@@ -33,7 +37,7 @@ async def to_code(config):
request_parent = await cg.get_variable(config[CONF_HTTP_REQUEST_ID])
cg.add(var.set_request_parent(request_parent))
cg.add(var.set_source_urls(config[CONF_SOURCE]))
cg.add(var.set_source_url(config[CONF_SOURCE]))
cg.add_define("USE_OTA_STATE_CALLBACK")

View File

@@ -9,6 +9,13 @@
namespace esphome {
namespace http_request {
// The update function runs in a task only on ESP32s.
#ifdef USE_ESP32
#define UPDATE_RETURN vTaskDelete(nullptr) // Delete the current update task
#else
#define UPDATE_RETURN return
#endif
static const char *const TAG = "http_request.update";
static const size_t MAX_READ_SIZE = 256;
@@ -21,135 +28,139 @@ void HttpRequestUpdate::setup() {
this->update_info_.progress = progress;
this->publish_state();
} else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
if (this->current_source_ + 1 < this->source_urls_.size()) {
this->current_source_++;
this->defer("update", [this]() {
this->update();
this->perform(true);
});
} else {
this->current_source_ = 0;
this->state_ = update::UPDATE_STATE_AVAILABLE;
this->status_set_error("Failed to install firmware");
this->publish_state();
}
this->state_ = update::UPDATE_STATE_AVAILABLE;
this->status_set_error("Failed to install firmware");
this->publish_state();
}
});
}
void HttpRequestUpdate::update() {
std::string current_source = this->source_urls_[this->current_source_];
auto container = this->request_parent_->get(current_source);
#ifdef USE_ESP32
xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_);
#else
this->update_task(this);
#endif
}
void HttpRequestUpdate::update_task(void *params) {
HttpRequestUpdate *this_update = (HttpRequestUpdate *) params;
auto container = this_update->request_parent_->get(this_update->source_url_);
if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
std::string msg = str_sprintf("Failed to fetch manifest from %s", current_source.c_str());
this->status_set_error(msg.c_str());
if (this->current_source_ + 1 < this->source_urls_.size()) {
this->current_source_++;
this->defer("update", [this]() { this->update(); });
}
return;
std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str());
this_update->status_set_error(msg.c_str());
UPDATE_RETURN;
}
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
uint8_t *data = allocator.allocate(container->content_length);
if (data == nullptr) {
std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length);
this->status_set_error(msg.c_str());
this_update->status_set_error(msg.c_str());
container->end();
return;
UPDATE_RETURN;
}
size_t read_index = 0;
while (container->get_bytes_read() < container->content_length) {
int read_bytes = container->read(data + read_index, MAX_READ_SIZE);
App.feed_wdt();
yield();
read_index += read_bytes;
}
std::string response((char *) data, read_index);
allocator.deallocate(data, container->content_length);
bool valid = false;
{ // Ensures the response string falls out of scope and deallocates before the task ends
std::string response((char *) data, read_index);
allocator.deallocate(data, container->content_length);
container->end();
container->end();
container.reset(); // Release ownership of the container's shared_ptr
bool valid = json::parse_json(response, [this](JsonObject root) -> bool {
if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
ESP_LOGE(TAG, "Manifest does not contain required fields");
return false;
}
this->update_info_.title = root["name"].as<std::string>();
this->update_info_.latest_version = root["version"].as<std::string>();
for (auto build : root["builds"].as<JsonArray>()) {
if (!build.containsKey("chipFamily")) {
valid = json::parse_json(response, [this_update](JsonObject root) -> bool {
if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
ESP_LOGE(TAG, "Manifest does not contain required fields");
return false;
}
if (build["chipFamily"] == ESPHOME_VARIANT) {
if (!build.containsKey("ota")) {
this_update->update_info_.title = root["name"].as<std::string>();
this_update->update_info_.latest_version = root["version"].as<std::string>();
for (auto build : root["builds"].as<JsonArray>()) {
if (!build.containsKey("chipFamily")) {
ESP_LOGE(TAG, "Manifest does not contain required fields");
return false;
}
auto ota = build["ota"];
if (!ota.containsKey("path") || !ota.containsKey("md5")) {
ESP_LOGE(TAG, "Manifest does not contain required fields");
return false;
if (build["chipFamily"] == ESPHOME_VARIANT) {
if (!build.containsKey("ota")) {
ESP_LOGE(TAG, "Manifest does not contain required fields");
return false;
}
auto ota = build["ota"];
if (!ota.containsKey("path") || !ota.containsKey("md5")) {
ESP_LOGE(TAG, "Manifest does not contain required fields");
return false;
}
this_update->update_info_.firmware_url = ota["path"].as<std::string>();
this_update->update_info_.md5 = ota["md5"].as<std::string>();
if (ota.containsKey("summary"))
this_update->update_info_.summary = ota["summary"].as<std::string>();
if (ota.containsKey("release_url"))
this_update->update_info_.release_url = ota["release_url"].as<std::string>();
return true;
}
this->update_info_.firmware_url = ota["path"].as<std::string>();
this->update_info_.md5 = ota["md5"].as<std::string>();
if (ota.containsKey("summary"))
this->update_info_.summary = ota["summary"].as<std::string>();
if (ota.containsKey("release_url"))
this->update_info_.release_url = ota["release_url"].as<std::string>();
return true;
}
}
return false;
});
return false;
});
}
if (!valid) {
std::string msg = str_sprintf("Failed to parse JSON from %s", current_source.c_str());
this->status_set_error(msg.c_str());
return;
std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str());
this_update->status_set_error(msg.c_str());
UPDATE_RETURN;
}
// Merge source_url_ and this->update_info_.firmware_url
if (this->update_info_.firmware_url.find("http") == std::string::npos) {
std::string path = this->update_info_.firmware_url;
// Merge source_url_ and this_update->update_info_.firmware_url
if (this_update->update_info_.firmware_url.find("http") == std::string::npos) {
std::string path = this_update->update_info_.firmware_url;
if (path[0] == '/') {
std::string domain = current_source.substr(0, current_source.find('/', 8));
this->update_info_.firmware_url = domain + path;
std::string domain = this_update->source_url_.substr(0, this_update->source_url_.find('/', 8));
this_update->update_info_.firmware_url = domain + path;
} else {
std::string domain = current_source.substr(0, current_source.rfind('/') + 1);
this->update_info_.firmware_url = domain + path;
std::string domain = this_update->source_url_.substr(0, this_update->source_url_.rfind('/') + 1);
this_update->update_info_.firmware_url = domain + path;
}
}
std::string current_version;
{ // Ensures the current version string falls out of scope and deallocates before the task ends
std::string current_version;
#ifdef ESPHOME_PROJECT_VERSION
current_version = ESPHOME_PROJECT_VERSION;
current_version = ESPHOME_PROJECT_VERSION;
#else
current_version = ESPHOME_VERSION;
current_version = ESPHOME_VERSION;
#endif
this->update_info_.current_version = current_version;
if (this->update_info_.latest_version.empty() || this->update_info_.latest_version == update_info_.current_version) {
this->state_ = update::UPDATE_STATE_NO_UPDATE;
} else {
this->state_ = update::UPDATE_STATE_AVAILABLE;
this_update->update_info_.current_version = current_version;
}
this->update_info_.has_progress = false;
this->update_info_.progress = 0.0f;
if (this_update->update_info_.latest_version.empty() ||
this_update->update_info_.latest_version == this_update->update_info_.current_version) {
this_update->state_ = update::UPDATE_STATE_NO_UPDATE;
} else {
this_update->state_ = update::UPDATE_STATE_AVAILABLE;
}
this->status_clear_error();
this->publish_state();
this_update->update_info_.has_progress = false;
this_update->update_info_.progress = 0.0f;
this_update->status_clear_error();
this_update->publish_state();
UPDATE_RETURN;
}
void HttpRequestUpdate::perform(bool force) {

View File

@@ -7,6 +7,10 @@
#include "esphome/components/http_request/ota/ota_http_request.h"
#include "esphome/components/update/update_entity.h"
#ifdef USE_ESP32
#include <freertos/FreeRTOS.h>
#endif
namespace esphome {
namespace http_request {
@@ -18,8 +22,7 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
void perform(bool force) override;
void check() override { this->update(); }
void set_source_url(const std::string &source_urls) { this->source_urls_ = {source_urls}; }
void set_source_urls(const std::vector<std::string> &source_urls) { this->source_urls_ = source_urls; }
void set_source_url(const std::string &source_url) { this->source_url_ = source_url; }
void set_request_parent(HttpRequestComponent *request_parent) { this->request_parent_ = request_parent; }
void set_ota_parent(OtaHttpRequestComponent *ota_parent) { this->ota_parent_ = ota_parent; }
@@ -29,8 +32,12 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
protected:
HttpRequestComponent *request_parent_;
OtaHttpRequestComponent *ota_parent_;
std::vector<std::string> source_urls_;
size_t current_source_{0};
std::string source_url_;
static void update_task(void *params);
#ifdef USE_ESP32
TaskHandle_t update_task_handle_{nullptr};
#endif
};
} // namespace http_request

View File

@@ -147,7 +147,7 @@ class LibreTinyPreferences : public ESPPreferences {
ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str());
return true;
}
stored_data.data.reserve(kv.value_len);
stored_data.data.resize(kv.value_len);
fdb_blob_make(&blob, stored_data.data.data(), kv.value_len);
size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob);
if (actual_len != kv.value_len) {

View File

@@ -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(config[CONF_TOUCH_SLEEP_TIMEOUT]))
cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT]))
if CONF_WAKE_UP_PAGE in config:
cg.add(var.set_wake_up_page(config[CONF_WAKE_UP_PAGE]))
cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE]))
if CONF_START_UP_PAGE in config:
cg.add(var.set_start_up_page(config[CONF_START_UP_PAGE]))
cg.add(var.set_start_up_page_internal(config[CONF_START_UP_PAGE]))
cg.add(var.set_auto_wake_on_touch(config[CONF_AUTO_WAKE_ON_TOUCH]))
cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH]))
cg.add(var.set_exit_reparse_on_start(config[CONF_EXIT_REPARSE_ON_START]))
cg.add(var.set_exit_reparse_on_start_internal(config[CONF_EXIT_REPARSE_ON_START]))
cg.add(var.set_skip_connection_handshake(config[CONF_SKIP_CONNECTION_HANDSHAKE]))

View File

@@ -40,7 +40,7 @@ bool Nextion::send_command_(const std::string &command) {
}
bool Nextion::check_connect_() {
if (this->is_connected_)
if (this->get_is_connected_())
return true;
// Check if the handshake should be skipped for the Nextion connection
@@ -280,6 +280,14 @@ 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_);
}

View File

@@ -856,6 +856,76 @@ 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,
@@ -1102,75 +1172,15 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
void update_components_by_prefix(const std::string &prefix);
/**
* 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; }
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;
}
/**
* @brief Retrieves the number of commands pending in the Nextion command queue.
@@ -1207,25 +1217,6 @@ 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_;
@@ -1324,6 +1315,8 @@ 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_;

View File

@@ -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 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_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_touch_sleep_timeout(uint32_t touch_sleep_timeout) {
if (touch_sleep_timeout < 3) {
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) {
ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535");
return;
}
this->touch_sleep_timeout_ = touch_sleep_timeout;
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", touch_sleep_timeout, true);
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true);
}
void Nextion::sleep(bool sleep) {
@@ -54,6 +54,7 @@ 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) {
@@ -190,9 +191,8 @@ 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_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);
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);
}
// General Component

View File

@@ -1,12 +1,10 @@
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, CONF_TRIGGER_ID
from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266
from . import const, schema, validate, generate
CODEOWNERS = ["@olegtarasov"]
@@ -22,21 +20,7 @@ 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" # 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__)
CONF_OPENTHERM_VERSION = "opentherm_version"
CONFIG_SCHEMA = cv.All(
cv.Schema(
@@ -52,19 +36,7 @@ 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, # 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
),
}
),
cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float,
}
)
.extend(
@@ -72,11 +44,6 @@ 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]),
)
@@ -93,33 +60,18 @@ 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,
CONF_BEFORE_SEND,
CONF_BEFORE_PROCESS_RESPONSE,
}
non_sensors = {CONF_ID, CONF_IN_PIN, CONF_OUT_PIN}
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}")(input_sensor))
cg.add(
getattr(var, f"set_{key}_{const.INPUT_SENSOR.lower()}")(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:
@@ -129,21 +81,3 @@ 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
)

View File

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

View File

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

View File

@@ -1,14 +1,13 @@
from collections.abc import Awaitable
from typing import Any, Callable, Optional
from typing import Any, Callable
import esphome.codegen as cg
from esphome.const import CONF_ID
from . import const
from .schema import TSchema, SettingSchema
from .schema import TSchema
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:
@@ -22,24 +21,6 @@ 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:
@@ -93,30 +74,16 @@ def define_readers(component_type: str, keys: list[str]) -> None:
)
def define_setting_readers(component_type: str, keys: list[str]) -> None:
for key in keys:
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]]] = {}
messages: set[tuple[str, bool]] = set()
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():
messages.add((schemas[key].message, schemas[key].keep_updated))
for msg, keep_updated in messages:
msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}")
if keep_updated:
cg.add(hub.add_repeating_message(msg_expr))
else:
if order is not None:
cg.add(hub.add_initial_message(msg_expr, order))
else:
cg.add(hub.add_initial_message(msg_expr))
cg.add(hub.add_initial_message(msg_expr))
def add_property_set(var: cg.MockObj, config_key: str, config: dict[str, Any]) -> None:

View File

@@ -63,7 +63,7 @@ void write_f88(const float value, OpenthermData &data) { data.f88(value); }
OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
OpenthermData data;
data.type = 0;
data.id = request_id;
data.id = 0;
data.valueHB = 0;
data.valueLB = 0;
@@ -82,13 +82,28 @@ 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;
}
// Next, we start with write requests from switches and other inputs,
// 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,
// 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).
@@ -101,23 +116,14 @@ 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, , , )
default:
break;
}
switch (request_id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) }
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
@@ -157,37 +163,19 @@ void OpenthermHub::setup() {
// communicate at least once every second. Sending the status request is
// good practice anyway.
this->add_repeating_message(MessageId::STATUS);
this->write_initial_messages_(this->messages_);
this->message_iterator_ = this->messages_.begin();
// 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();
}
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_();
@@ -196,18 +184,29 @@ 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;
}
@@ -220,28 +219,6 @@ 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;
}
}
@@ -260,20 +237,16 @@ 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;
}
// Check for errors and ensure we are in the right state (message sent successfully)
if (this->handle_error_(this->opentherm_->get_mode())) {
if (this->opentherm_->is_error()) {
this->handle_protocol_write_error_();
this->stop_opentherm_();
return;
} else if (!this->opentherm_->is_sent()) {
ESP_LOGW(TAG, "Unexpected state after sending request: %s",
@@ -284,20 +257,19 @@ 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;
}
// Check for errors and ensure we are in the right state (message received successfully)
if (this->handle_error_(this->opentherm_->get_mode())) {
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_();
return;
} else if (!this->opentherm_->has_message()) {
ESP_LOGW(TAG, "Unexpected state after receiving response: %s",
@@ -309,13 +281,17 @@ void OpenthermHub::sync_loop_() {
this->read_response_();
}
void OpenthermHub::check_timings_(uint32_t cur_time) {
bool 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 {
@@ -328,17 +304,14 @@ bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
}
void OpenthermHub::start_conversation_() {
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();
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();
}
auto request = this->build_request_(*this->message_iterator_);
this->before_send_callback_.call(request);
auto request = this->build_request_(*this->current_message_iterator_);
ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id,
this->opentherm_->message_id_to_str((MessageId) request.id));
@@ -358,48 +331,37 @@ void OpenthermHub::read_response_() {
this->stop_opentherm_();
this->before_process_response_callback_.call(response);
this->process_response(response);
this->message_iterator_++;
this->current_message_iterator_++;
}
void OpenthermHub::stop_opentherm_() {
this->opentherm_->stop();
this->last_conversation_end_ = millis();
}
void OpenthermHub::handle_protocol_error_() {
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_() {
OpenThermError error;
this->opentherm_->get_protocol_error(error);
ESP_LOGW(TAG, "Protocol error occured while receiving response: %s",
this->opentherm_->protocol_error_to_str(error.error_type));
this->opentherm_->protocol_error_to_to_str(error.error_type));
this->opentherm_->debug_error(error);
this->stop_opentherm_();
}
void OpenthermHub::handle_timeout_error_() {
ESP_LOGW(TAG, "Timeout while waiting for response from device");
ESP_LOGW(TAG, "Receive response timed out at a protocol level");
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: %s", YESNO(this->sync_mode_));
ESP_LOGCONFIG(TAG, " Sync mode: %d", 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, )));
@@ -407,12 +369,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 : initial_messages) {
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
for (auto type : this->initial_messages_) {
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type)));
}
ESP_LOGCONFIG(TAG, " Repeating requests:");
for (auto type : repeating_messages) {
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
for (auto type : this->repeating_messages_) {
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type)));
}
}

View File

@@ -38,9 +38,6 @@
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:
@@ -61,12 +58,15 @@ class OpenthermHub : public Component {
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, )
OPENTHERM_SETTING_LIST(OPENTHERM_DECLARE_SETTING, )
// 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
bool sending_initial_ = true;
std::unordered_map<MessageId, uint8_t> configured_messages_;
std::vector<MessageId> messages_;
std::vector<MessageId>::const_iterator message_iterator_;
// Index for the current request in one of the _requests sets.
std::vector<MessageId>::const_iterator current_message_iterator_;
uint32_t last_conversation_start_ = 0;
uint32_t last_conversation_end_ = 0;
@@ -78,25 +78,20 @@ class OpenthermHub : public Component {
// Very likely to happen while using Dallas temperature sensors.
bool sync_mode_ = false;
CallbackManager<void(OpenthermData &)> before_send_callback_;
CallbackManager<void(OpenthermData &)> before_process_response_callback_;
float opentherm_version_ = 0.0f;
// Create OpenTherm messages based on the message id
OpenthermData build_request_(MessageId request_id) const;
bool handle_error_(OperationMode mode);
void handle_protocol_error_();
void handle_protocol_write_error_();
void handle_protocol_read_error_();
void handle_timeout_error_();
void handle_timer_error_();
void stop_opentherm_();
void start_conversation_();
void read_response_();
void check_timings_(uint32_t cur_time);
bool 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()) {
@@ -132,18 +127,13 @@ 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->configured_messages_[message_id] = INITIAL_UNORDERED_MESSAGE_ORDER;
}
void add_initial_message(MessageId message_id, uint8_t order) { this->configured_messages_[message_id] = order; }
void add_initial_message(MessageId message_id) { this->initial_messages_.push_back(message_id); }
// 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->configured_messages_[message_id] = REPEATING_MESSAGE_ORDER; }
void add_repeating_message(MessageId message_id) { this->repeating_messages_.push_back(message_id); }
// 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.
@@ -159,13 +149,7 @@ 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 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));
}
void set_opentherm_version(float value) { this->opentherm_version_ = value; }
float get_setup_priority() const override { return setup_priority::HARDWARE; }

View File

@@ -52,9 +52,7 @@ 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)
@@ -184,7 +182,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 manchester encoding
// no change for too long, invalid mancheter encoding
arg->mode_ = OperationMode::ERROR_PROTOCOL;
arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG;
arg->stop_timer_();
@@ -314,31 +312,21 @@ bool OpenTherm::init_esp32_timer_() {
}
void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) {
// We will report timer errors outside of interrupt handler
this->timer_error_ = ESP_OK;
this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
esp_err_t result;
this->timer_error_ = timer_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) {
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);
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;
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;
}
}
// 5 kHz timer_
@@ -355,18 +343,21 @@ 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;
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;
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);
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;
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;
}
}
@@ -395,9 +386,6 @@ 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
@@ -424,12 +412,11 @@ 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_str(ProtocolErrorType error_type) {
const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) {
switch (error_type) {
TO_STRING_MEMBER(NO_ERROR)
TO_STRING_MEMBER(NO_TRANSITION)
@@ -440,17 +427,6 @@ const char *OpenTherm::protocol_error_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)

View File

@@ -36,12 +36,11 @@ enum OperationMode {
READ = 2, // reading 32-bit data frame
RECEIVED = 3, // data frame received with valid start and stop bit
WRITE = 4, // writing data to output
WRITE = 4, // writing data with timer_
SENT = 5, // all data written to output
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
ERROR_PROTOCOL = 8, // manchester protocol data transfer error
ERROR_TIMEOUT = 9 // read timeout
};
enum ProtocolErrorType {
@@ -52,14 +51,6 @@ 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,
@@ -308,9 +299,7 @@ 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 || mode_ == ERROR_TIMER;
}
bool is_error() { return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL; }
/**
* Indicates whether last listen() or send() operation ends up with a *timeout* error
@@ -324,22 +313,14 @@ 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_str(ProtocolErrorType error_type);
const char *timer_error_to_str(TimerErrorType error_type);
const char *protocol_error_to_to_str(ProtocolErrorType 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);
@@ -368,12 +349,10 @@ 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

View File

@@ -28,9 +28,6 @@ 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;
@@ -39,7 +36,6 @@ 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) \
@@ -60,9 +56,6 @@ 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:
@@ -92,9 +85,6 @@ 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) \
@@ -102,7 +92,6 @@ 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; \
}

View File

@@ -2,9 +2,8 @@
# inputs of the OpenTherm component.
from dataclasses import dataclass
from typing import Optional, TypeVar, Any
from typing import Optional, TypeVar
import esphome.config_validation as cv
from esphome.const import (
UNIT_CELSIUS,
UNIT_EMPTY,
@@ -65,7 +64,6 @@ 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] = {
@@ -401,7 +399,6 @@ SENSORS: dict[str, SensorSchema] = {
message="OT_VERSION_DEVICE",
keep_updated=False,
message_data="f88",
order=2,
),
"device_type": SensorSchema(
description="Device product type",
@@ -412,7 +409,6 @@ SENSORS: dict[str, SensorSchema] = {
message="VERSION_DEVICE",
keep_updated=False,
message_data="u8_hb",
order=0,
),
"device_version": SensorSchema(
description="Device product version",
@@ -423,7 +419,6 @@ SENSORS: dict[str, SensorSchema] = {
message="VERSION_DEVICE",
keep_updated=False,
message_data="u8_lb",
order=0,
),
"device_id": SensorSchema(
description="Device ID code",
@@ -434,7 +429,6 @@ 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",
@@ -463,7 +457,6 @@ 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] = {
@@ -532,56 +525,48 @@ 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",
@@ -827,65 +812,3 @@ 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,
),
}

View File

@@ -9,17 +9,12 @@ from .schema import TSchema
def create_entities_schema(
entities: dict[str, TSchema],
entities: dict[str, schema.EntitySchema],
get_entity_validation_schema: Callable[[TSchema], cv.Schema],
) -> Schema:
entity_schema = {}
for key, entity in entities.items():
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)
entity_schema[cv.Optional(key)] = get_entity_validation_schema(entity)
return cv.Schema(entity_schema)

View File

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

View File

@@ -9,7 +9,7 @@
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
#include <driver/pcnt.h>
#define HAS_PCNT
#endif // defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
#endif
namespace esphome {
namespace pulse_counter {
@@ -22,9 +22,9 @@ enum PulseCounterCountMode {
#ifdef HAS_PCNT
using pulse_counter_t = int16_t;
#else // HAS_PCNT
#else
using pulse_counter_t = int32_t;
#endif // HAS_PCNT
#endif
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 // HAS_PCNT
#endif
PulseCounterStorageBase *get_storage(bool hw_pcnt = false);

View File

@@ -93,17 +93,13 @@ 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) {
auto x = arg->counter + 1;
arg->counter = x;
}
if (arg->counter < arg->max_value)
arg->counter++;
rotation_dir = 1;
}
if ((new_state & arg->resolution & STATE_HAS_DECREMENTED) != 0) {
if (arg->counter > arg->min_value) {
auto x = arg->counter - 1;
arg->counter = x;
}
if (arg->counter > arg->min_value)
arg->counter--;
rotation_dir = -1;
}

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2025.1.0-dev"
__version__ = "2024.12.3"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,5 @@
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

View File

@@ -16,19 +16,6 @@ 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