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

This commit is contained in:
J. Nick Koston
2026-03-18 09:45:16 -10:00
15 changed files with 75 additions and 116 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ runs:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: venv
# yamllint disable-line rule:line-length
+16 -16
View File
@@ -47,7 +47,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: venv
# yamllint disable-line rule:line-length
@@ -154,12 +154,12 @@ jobs:
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache
if: github.ref == 'refs/heads/dev'
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: venv
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
@@ -198,7 +198,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Restore components graph cache
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: .temp/components_graph.json
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
@@ -231,7 +231,7 @@ jobs:
echo "benchmarks=$(echo "$output" | jq -r '.benchmarks')" >> $GITHUB_OUTPUT
- name: Save components graph cache
if: github.ref == 'refs/heads/dev'
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: .temp/components_graph.json
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
@@ -253,7 +253,7 @@ jobs:
python-version: "3.13"
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
@@ -387,14 +387,14 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
@@ -466,14 +466,14 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
@@ -555,14 +555,14 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
@@ -817,7 +817,7 @@ jobs:
- name: Restore cached memory analysis
id: cache-memory-analysis
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: memory-analysis-target.json
key: ${{ steps.cache-key.outputs.cache-key }}
@@ -841,7 +841,7 @@ jobs:
- name: Cache platformio
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.platformio
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
@@ -882,7 +882,7 @@ jobs:
- name: Save memory analysis to cache
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: memory-analysis-target.json
key: ${{ steps.cache-key.outputs.cache-key }}
@@ -929,7 +929,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache platformio
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.platformio
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: 3.13
python-version: "3.14"
- name: Install Home Assistant
run: |
+4 -4
View File
@@ -251,11 +251,11 @@ void APDS9960::read_gesture_data_() {
uint8_t buf[128];
for (uint8_t pos = 0; pos < fifo_level * 4; pos += 32) {
// The ESP's i2c driver has a limited buffer size.
// This way of retrieving the data should be wrong according to the datasheet
// but it seems to work.
// Read in 32-byte chunks due to ESP8266 I2C buffer limit.
// Always read from 0xFC — the FIFO auto-increments through 0xFC-0xFF
// and advances its internal pointer after every 4th byte.
uint8_t read = std::min(32, fifo_level * 4 - pos);
APDS9960_WARNING_CHECK(this->read_bytes(0xFC + pos, buf + pos, read), "Reading FIFO buffer failed.");
APDS9960_WARNING_CHECK(this->read_bytes(0xFC, buf + pos, read), "Reading FIFO buffer failed.");
}
if (millis() - this->gesture_start_ > 500) {
-3
View File
@@ -53,9 +53,6 @@ void arch_init() {
}
void HOT arch_feed_wdt() { esp_task_wdt_reset(); }
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
const char *progmem_read_ptr(const char *const *addr) { return *addr; }
uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; }
uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
uint32_t arch_get_cpu_freq_hz() {
uint32_t freq = 0;
-3
View File
@@ -58,9 +58,6 @@ void HOT arch_feed_wdt() {
// pass
}
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
const char *progmem_read_ptr(const char *const *addr) { return *addr; }
uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; }
uint32_t arch_get_cpu_cycle_count() {
struct timespec spec;
clock_gettime(CLOCK_MONOTONIC, &spec);
-3
View File
@@ -54,9 +54,6 @@ void arch_restart() {
void HOT arch_feed_wdt() { lt_wdt_feed(); }
uint32_t arch_get_cpu_cycle_count() { return lt_cpu_get_cycle_count(); }
uint32_t arch_get_cpu_freq_hz() { return lt_cpu_get_freq(); }
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
const char *progmem_read_ptr(const char *const *addr) { return *addr; }
uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; }
} // namespace esphome
+1 -4
View File
@@ -81,10 +81,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config: dict[str, Any]) -> None:
if CORE.is_esp32:
# Re-enable ESP-IDF's legacy driver component (excluded by default to save compile time)
# Provides driver/timer.h header for hardware timer API
# TODO: Remove this once opentherm migrates to GPTimer API (driver/gptimer.h)
include_builtin_idf_component("driver")
include_builtin_idf_component("esp_driver_gptimer")
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
+28 -61
View File
@@ -8,10 +8,7 @@
#include "opentherm.h"
#include "esphome/core/helpers.h"
#include <cinttypes>
// TODO: Migrate from legacy timer API (driver/timer.h) to GPTimer API (driver/gptimer.h)
// The legacy timer API is deprecated in ESP-IDF 5.x. See opentherm.h for details.
#ifdef USE_ESP32
#include "driver/timer.h"
#include "esp_err.h"
#endif
#ifdef ESP8266
@@ -33,10 +30,6 @@ OpenTherm *OpenTherm::instance = nullptr;
OpenTherm::OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout)
: in_pin_(in_pin),
out_pin_(out_pin),
#ifdef USE_ESP32
timer_group_(TIMER_GROUP_0),
timer_idx_(TIMER_0),
#endif
mode_(OperationMode::IDLE),
error_type_(ProtocolErrorType::NO_ERROR),
capture_(0),
@@ -134,7 +127,12 @@ void IRAM_ATTR OpenTherm::read_() {
// period in OpenTherm.
}
#ifdef USE_ESP32
bool IRAM_ATTR OpenTherm::timer_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) {
auto *arg = static_cast<OpenTherm *>(user_ctx);
#else
bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) {
#endif
if (arg->mode_ == OperationMode::LISTEN) {
if (arg->timeout_counter_ == 0) {
arg->mode_ = OperationMode::ERROR_TIMEOUT;
@@ -243,67 +241,35 @@ void IRAM_ATTR OpenTherm::write_bit_(uint8_t high, uint8_t clock) {
#ifdef USE_ESP32
bool OpenTherm::init_esp32_timer_() {
// Search for a free timer. Maybe unstable, we'll see.
int cur_timer = 0;
timer_group_t timer_group = TIMER_GROUP_0;
timer_idx_t timer_idx = TIMER_0;
bool timer_found = false;
for (; cur_timer < SOC_TIMER_GROUP_TOTAL_TIMERS; cur_timer++) {
timer_config_t temp_config;
timer_group = cur_timer < 2 ? TIMER_GROUP_0 : TIMER_GROUP_1;
timer_idx = cur_timer < 2 ? (timer_idx_t) cur_timer : (timer_idx_t) (cur_timer - 2);
auto err = timer_get_config(timer_group, timer_idx, &temp_config);
if (err == ESP_ERR_INVALID_ARG) {
// Error means timer was not initialized (or other things, but we are careful with our args)
timer_found = true;
break;
}
ESP_LOGD(TAG, "Timer %d:%d seems to be occupied, will try another", timer_group, timer_idx);
}
if (!timer_found) {
ESP_LOGE(TAG, "No free timer was found! OpenTherm cannot function without a timer.");
return false;
}
ESP_LOGD(TAG, "Found free timer %d:%d", timer_group, timer_idx);
this->timer_group_ = timer_group;
this->timer_idx_ = timer_idx;
timer_config_t const config = {
.alarm_en = TIMER_ALARM_EN,
.counter_en = TIMER_PAUSE,
.intr_type = TIMER_INTR_LEVEL,
.counter_dir = TIMER_COUNT_UP,
.auto_reload = TIMER_AUTORELOAD_EN,
.clk_src = TIMER_SRC_CLK_DEFAULT,
.divider = 80,
// 80MHz / 80 = 1MHz resolution (1µs per tick)
gptimer_config_t config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1000000,
};
esp_err_t result;
result = timer_init(this->timer_group_, this->timer_idx_, &config);
esp_err_t result = gptimer_new_timer(&config, &this->timer_handle_);
if (result != ESP_OK) {
const auto *error = esp_err_to_name(result);
ESP_LOGE(TAG, "Failed to init timer. Error: %s", error);
ESP_LOGE(TAG, "Failed to create timer: %s", esp_err_to_name(result));
return false;
}
result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
gptimer_event_callbacks_t cbs = {
.on_alarm = OpenTherm::timer_isr,
};
result = gptimer_register_event_callbacks(this->timer_handle_, &cbs, this);
if (result != ESP_OK) {
const auto *error = esp_err_to_name(result);
ESP_LOGE(TAG, "Failed to set counter value. Error: %s", error);
ESP_LOGE(TAG, "Failed to register timer callback: %s", esp_err_to_name(result));
gptimer_del_timer(this->timer_handle_);
this->timer_handle_ = nullptr;
return false;
}
result = timer_isr_callback_add(this->timer_group_, this->timer_idx_, reinterpret_cast<bool (*)(void *)>(timer_isr),
this, 0);
result = gptimer_enable(this->timer_handle_);
if (result != ESP_OK) {
const auto *error = esp_err_to_name(result);
ESP_LOGE(TAG, "Failed to register timer interrupt. Error: %s", error);
ESP_LOGE(TAG, "Failed to enable timer: %s", esp_err_to_name(result));
gptimer_del_timer(this->timer_handle_);
this->timer_handle_ = nullptr;
return false;
}
@@ -315,12 +281,13 @@ void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) {
this->timer_error_ = ESP_OK;
this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
this->timer_error_ = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value);
this->alarm_config_.alarm_count = alarm_value;
this->timer_error_ = gptimer_set_alarm_action(this->timer_handle_, &this->alarm_config_);
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_);
this->timer_error_ = gptimer_start(this->timer_handle_);
if (this->timer_error_ != ESP_OK) {
this->timer_error_type_ = TimerErrorType::TIMER_START_ERROR;
}
@@ -356,12 +323,12 @@ void IRAM_ATTR OpenTherm::stop_timer_() {
this->timer_error_ = ESP_OK;
this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
this->timer_error_ = timer_pause(this->timer_group_, this->timer_idx_);
this->timer_error_ = gptimer_stop(this->timer_handle_);
if (this->timer_error_ != ESP_OK) {
this->timer_error_type_ = TimerErrorType::TIMER_PAUSE_ERROR;
return;
}
this->timer_error_ = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
this->timer_error_ = gptimer_set_raw_count(this->timer_handle_, 0);
if (this->timer_error_ != ESP_OK) {
this->timer_error_type_ = TimerErrorType::SET_COUNTER_VALUE_ERROR;
}
+11 -7
View File
@@ -12,12 +12,8 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
// TODO: Migrate from legacy timer API (driver/timer.h) to GPTimer API (driver/gptimer.h)
// The legacy timer API is deprecated in ESP-IDF 5.x. Migration would allow removing the
// "driver" IDF component dependency. See:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/migration-guides/release-5.x/5.0/peripherals.html#id4
#ifdef USE_ESP32
#include "driver/timer.h"
#include "driver/gptimer.h"
#endif
namespace esphome {
@@ -348,7 +344,11 @@ class OpenTherm {
const char *operation_mode_to_str(OperationMode mode);
const char *message_id_to_str(MessageId id);
#ifdef USE_ESP32
static bool timer_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx);
#else
static bool timer_isr(OpenTherm *arg);
#endif
#ifdef ESP8266
static void esp8266_timer_isr();
@@ -361,8 +361,12 @@ class OpenTherm {
ISRInternalGPIOPin isr_out_pin_;
#ifdef USE_ESP32
timer_group_t timer_group_;
timer_idx_t timer_idx_;
gptimer_handle_t timer_handle_{nullptr};
gptimer_alarm_config_t alarm_config_{
.alarm_count = 0,
.reload_count = 0,
.flags = {.auto_reload_on_alarm = true},
};
#endif
OperationMode mode_;
-7
View File
@@ -37,13 +37,6 @@ void arch_init() {
void HOT arch_feed_wdt() { watchdog_update(); }
uint8_t progmem_read_byte(const uint8_t *addr) {
return pgm_read_byte(addr); // NOLINT
}
const char *progmem_read_ptr(const char *const *addr) {
return reinterpret_cast<const char *>(pgm_read_ptr(addr)); // NOLINT
}
uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; }
uint32_t HOT arch_get_cpu_cycle_count() { return ulMainGetRunTimeCounterValue(); }
uint32_t arch_get_cpu_freq_hz() { return RP2040::f_cpu(); }
+3 -2
View File
@@ -50,8 +50,9 @@ void TC74Component::read_temperature_() {
}
}
uint8_t temperature_reg;
if (this->read_register(TC74_REGISTER_TEMPERATURE, &temperature_reg, 1) != i2c::ERROR_OK) {
int8_t temperature_reg;
if (this->read_register(TC74_REGISTER_TEMPERATURE, reinterpret_cast<uint8_t *>(&temperature_reg), 1) !=
i2c::ERROR_OK) {
this->status_set_warning();
return;
}
-3
View File
@@ -59,9 +59,6 @@ void arch_feed_wdt() {
void arch_restart() { sys_reboot(SYS_REBOOT_COLD); }
uint32_t arch_get_cpu_cycle_count() { return k_cycle_get_32(); }
uint32_t arch_get_cpu_freq_hz() { return sys_clock_hw_cycles_per_sec(); }
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
const char *progmem_read_ptr(const char *const *addr) { return *addr; }
uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; }
Mutex::Mutex() {
auto *mutex = new k_mutex();
+9
View File
@@ -41,8 +41,17 @@ void arch_init();
void arch_feed_wdt();
uint32_t arch_get_cpu_cycle_count();
uint32_t arch_get_cpu_freq_hz();
#ifdef USE_ESP8266
// ESP8266: pgm_read_* does real flash reads on Harvard architecture
uint8_t progmem_read_byte(const uint8_t *addr);
const char *progmem_read_ptr(const char *const *addr);
uint16_t progmem_read_uint16(const uint16_t *addr);
#else
// All other platforms: PROGMEM is a no-op, so these are direct dereferences
inline uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
inline const char *progmem_read_ptr(const char *const *addr) { return *addr; }
inline uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; }
#endif
} // namespace esphome
+1 -1
View File
@@ -12,7 +12,7 @@ platformio==6.1.19
esptool==5.2.0
click==8.3.1
esphome-dashboard==20260210.0
aioesphomeapi==44.5.2
aioesphomeapi==44.6.0
zeroconf==0.148.0
puremagic==1.30
ruamel.yaml==0.19.1 # dashboard_import