From fbbb791b0dda790e6e6443766f0a04991bf2d73d Mon Sep 17 00:00:00 2001 From: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:37:43 +0200 Subject: [PATCH 1/4] [gt911] Use timeout instead of delay, shortened log msg (#10024) Co-authored-by: Keith Burzinski --- .../gt911/touchscreen/gt911_touchscreen.cpp | 30 +++++++++++++------ .../gt911/touchscreen/gt911_touchscreen.h | 28 +++++++++++++++-- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp index 0319b083ef..07218843dd 100644 --- a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp @@ -20,12 +20,11 @@ static const size_t MAX_BUTTONS = 4; // max number of buttons scanned #define ERROR_CHECK(err) \ if ((err) != i2c::ERROR_OK) { \ - this->status_set_warning("Communication failure"); \ + this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); \ return; \ } void GT911Touchscreen::setup() { - i2c::ErrorCode err; if (this->reset_pin_ != nullptr) { this->reset_pin_->setup(); this->reset_pin_->digital_write(false); @@ -35,9 +34,14 @@ void GT911Touchscreen::setup() { this->interrupt_pin_->digital_write(false); } delay(2); - this->reset_pin_->digital_write(true); - delay(50); // NOLINT + this->reset_pin_->digital_write(true); // wait 50ms after reset + this->set_timeout(50, [this] { this->setup_internal_(); }); + return; } + this->setup_internal_(); +} + +void GT911Touchscreen::setup_internal_() { if (this->interrupt_pin_ != nullptr) { // set pre-configured input mode this->interrupt_pin_->setup(); @@ -45,7 +49,7 @@ void GT911Touchscreen::setup() { // check the configuration of the int line. uint8_t data[4]; - err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES)); + i2c::ErrorCode err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES)); if (err != i2c::ERROR_OK && this->address_ == PRIMARY_ADDRESS) { this->address_ = SECONDARY_ADDRESS; err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES)); @@ -53,7 +57,7 @@ void GT911Touchscreen::setup() { if (err == i2c::ERROR_OK) { err = this->read(data, 1); if (err == i2c::ERROR_OK) { - ESP_LOGD(TAG, "Read from switches at address 0x%02X: 0x%02X", this->address_, data[0]); + ESP_LOGD(TAG, "Switches ADDR: 0x%02X DATA: 0x%02X", this->address_, data[0]); if (this->interrupt_pin_ != nullptr) { this->attach_interrupt_(this->interrupt_pin_, (data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE); @@ -75,16 +79,24 @@ void GT911Touchscreen::setup() { } } if (err != i2c::ERROR_OK) { - this->mark_failed("Failed to read calibration"); + this->mark_failed("Calibration error"); return; } } + if (err != i2c::ERROR_OK) { - this->mark_failed("Failed to communicate"); + this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + return; } + this->setup_done_ = true; } void GT911Touchscreen::update_touches() { + this->skip_update_ = true; // skip send touch events by default, set to false after successful error checks + if (!this->setup_done_) { + return; + } + i2c::ErrorCode err; uint8_t touch_state = 0; uint8_t data[MAX_TOUCHES + 1][8]; // 8 bytes each for each point, plus extra space for the key byte @@ -97,7 +109,6 @@ void GT911Touchscreen::update_touches() { uint8_t num_of_touches = touch_state & 0x07; if ((touch_state & 0x80) == 0 || num_of_touches > MAX_TOUCHES) { - this->skip_update_ = true; // skip send touch events, touchscreen is not ready yet. return; } @@ -107,6 +118,7 @@ void GT911Touchscreen::update_touches() { err = this->read(data[0], sizeof(data[0]) * num_of_touches + 1); ERROR_CHECK(err); + this->skip_update_ = false; // All error checks passed, send touch events for (uint8_t i = 0; i != num_of_touches; i++) { uint16_t id = data[i][0]; uint16_t x = encode_uint16(data[i][2], data[i][1]); diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.h b/esphome/components/gt911/touchscreen/gt911_touchscreen.h index 17636a2ada..85025b5522 100644 --- a/esphome/components/gt911/touchscreen/gt911_touchscreen.h +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.h @@ -15,8 +15,20 @@ class GT911ButtonListener { class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice { public: + /// @brief Initialize the GT911 touchscreen. + /// + /// If @ref reset_pin_ is set, the touchscreen will be hardware reset, + /// and the rest of the setup will be scheduled to run 50ms later using @ref set_timeout() + /// to allow the device to stabilize after reset. + /// + /// If @ref interrupt_pin_ is set, it will be temporarily configured during reset + /// to control I2C address selection. + /// + /// After the timeout, or immediately if no reset is performed, @ref setup_internal_() + /// is called to complete the initialization. void setup() override; void dump_config() override; + bool can_proceed() override { return this->setup_done_; } void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } @@ -25,8 +37,20 @@ class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice protected: void update_touches() override; - InternalGPIOPin *interrupt_pin_{}; - GPIOPin *reset_pin_{}; + /// @brief Perform the internal setup routine for the GT911 touchscreen. + /// + /// This function checks the I2C address, configures the interrupt pin (if available), + /// reads the touchscreen mode from the controller, and attempts to read calibration + /// data (maximum X and Y values) if not already set. + /// + /// On success, sets @ref setup_done_ to true. + /// On failure, calls @ref mark_failed() with an appropriate error message. + void setup_internal_(); + /// @brief True if the touchscreen setup has completed successfully. + bool setup_done_{false}; + + InternalGPIOPin *interrupt_pin_{nullptr}; + GPIOPin *reset_pin_{nullptr}; std::vector button_listeners_; uint8_t button_state_{0xFF}; // last button state. Initial FF guarantees first update. }; From d59476d0e102ba833eaa3a0d242d931a44fa245a Mon Sep 17 00:00:00 2001 From: Chris Beswick Date: Mon, 4 Aug 2025 15:43:44 +0100 Subject: [PATCH 2/4] [i2s_audio] Use high-pass filter for dc offset correction (#10005) --- .../microphone/i2s_audio_microphone.cpp | 64 +++++++++++++------ .../microphone/i2s_audio_microphone.h | 3 +- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp index 5ca33b3493..cdebc214e2 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -24,9 +24,6 @@ static const uint32_t READ_DURATION_MS = 16; static const size_t TASK_STACK_SIZE = 4096; static const ssize_t TASK_PRIORITY = 23; -// Use an exponential moving average to correct a DC offset with weight factor 1/1000 -static const int32_t DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR = 1000; - static const char *const TAG = "i2s_audio.microphone"; enum MicrophoneEventGroupBits : uint32_t { @@ -381,26 +378,57 @@ void I2SAudioMicrophone::mic_task(void *params) { } void I2SAudioMicrophone::fix_dc_offset_(std::vector &data) { + /** + * From https://www.musicdsp.org/en/latest/Filters/135-dc-filter.html: + * + * y(n) = x(n) - x(n-1) + R * y(n-1) + * R = 1 - (pi * 2 * frequency / samplerate) + * + * From https://en.wikipedia.org/wiki/Hearing_range: + * The human range is commonly given as 20Hz up. + * + * From https://en.wikipedia.org/wiki/High-resolution_audio: + * A reasonable upper bound for sample rate seems to be 96kHz. + * + * Calculate R value for 20Hz on a 96kHz sample rate: + * R = 1 - (pi * 2 * 20 / 96000) + * R = 0.9986910031 + * + * Transform floating point to bit-shifting approximation: + * output = input - prev_input + R * prev_output + * output = input - prev_input + (prev_output - (prev_output >> S)) + * + * Approximate bit-shift value S from R: + * R = 1 - (1 >> S) + * R = 1 - (1 / 2^S) + * R = 1 - 2^-S + * 0.9986910031 = 1 - 2^-S + * S = 9.57732 ~= 10 + * + * Actual R from S: + * R = 1 - 2^-10 = 0.9990234375 + * + * Confirm this has effect outside human hearing on 96000kHz sample: + * 0.9990234375 = 1 - (pi * 2 * f / 96000) + * f = 14.9208Hz + * + * Confirm this has effect outside human hearing on PDM 16kHz sample: + * 0.9990234375 = 1 - (pi * 2 * f / 16000) + * f = 2.4868Hz + * + */ + const uint8_t dc_filter_shift = 10; const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1); const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size()); - - if (total_samples == 0) { - return; - } - - int64_t offset_accumulator = 0; for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) { const uint32_t byte_index = sample_index * bytes_per_sample; - int32_t sample = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample); - offset_accumulator += sample; - sample -= this->dc_offset_; - audio::pack_q31_as_audio_sample(sample, &data[byte_index], bytes_per_sample); + int32_t input = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample); + int32_t output = input - this->dc_offset_prev_input_ + + (this->dc_offset_prev_output_ - (this->dc_offset_prev_output_ >> dc_filter_shift)); + this->dc_offset_prev_input_ = input; + this->dc_offset_prev_output_ = output; + audio::pack_q31_as_audio_sample(output, &data[byte_index], bytes_per_sample); } - - const int32_t new_offset = offset_accumulator / total_samples; - this->dc_offset_ = new_offset / DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR + - (DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR - 1) * this->dc_offset_ / - DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR; } size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) { diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h index 633bd0e7dd..de272ba23d 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h @@ -82,7 +82,8 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub bool correct_dc_offset_; bool locked_driver_{false}; - int32_t dc_offset_{0}; + int32_t dc_offset_prev_input_{0}; + int32_t dc_offset_prev_output_{0}; }; } // namespace i2s_audio From 701e6099aa42cc84be629cc0b6258992fe3aaef2 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:56:34 -0400 Subject: [PATCH 3/4] [espnow, web_server_idf] Fix IDF 5.5 compile issues (#10068) --- esphome/components/espnow/espnow_packet.h | 2 +- esphome/components/web_server_idf/web_server_idf.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/espnow/espnow_packet.h b/esphome/components/espnow/espnow_packet.h index d39f7d2c24..b6192a0d41 100644 --- a/esphome/components/espnow/espnow_packet.h +++ b/esphome/components/espnow/espnow_packet.h @@ -49,7 +49,7 @@ class ESPNowPacket { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) // Constructor for sent data ESPNowPacket(const esp_now_send_info_t *info, esp_now_send_status_t status) { - this->init_sent_data(info->src_addr, status); + this->init_sent_data_(info->src_addr, status); } #else // Constructor for sent data diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 40fb015b99..3226444b1b 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -116,7 +116,7 @@ esp_err_t AsyncWebServer::request_post_handler(httpd_req_t *r) { } // Handle regular form data - if (r->content_len > HTTPD_MAX_REQ_HDR_LEN) { + if (r->content_len > CONFIG_HTTPD_MAX_REQ_HDR_LEN) { ESP_LOGW(TAG, "Request size is to big: %zu", r->content_len); httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr); return ESP_FAIL; From 734b2691c895d2a15cbc05d365689a829d614a4b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Aug 2025 11:08:00 -1000 Subject: [PATCH 4/4] [api] Add helpful compile-time errors for Custom API Device methods --- esphome/components/api/custom_api_device.h | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index a39947e725..f67a0832be 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -56,6 +56,13 @@ class CustomAPIDevice { auto *service = new CustomAPIDeviceService(name, arg_names, (T *) this, callback); // NOLINT global_api_server->register_user_service(service); } +#else + template + void register_service(void (T::*callback)(Ts...), const std::string &name, + const std::array &arg_names) { + static_assert( + false, "register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration"); + } #endif /** Register a custom native API service that will show up in Home Assistant. @@ -81,6 +88,11 @@ class CustomAPIDevice { auto *service = new CustomAPIDeviceService(name, {}, (T *) this, callback); // NOLINT global_api_server->register_user_service(service); } +#else + template void register_service(void (T::*callback)(), const std::string &name) { + static_assert( + false, "register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration"); + } #endif #ifdef USE_API_HOMEASSISTANT_STATES @@ -135,6 +147,20 @@ class CustomAPIDevice { auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), f); } +#else + template + void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id, + const std::string &attribute = "") { + static_assert(false, "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " + "of your YAML configuration"); + } + + template + void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id, + const std::string &attribute = "") { + static_assert(false, "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " + "of your YAML configuration"); + } #endif #ifdef USE_API_HOMEASSISTANT_SERVICES @@ -222,6 +248,26 @@ class CustomAPIDevice { } global_api_server->send_homeassistant_service_call(resp); } +#else + void call_homeassistant_service(const std::string &service_name) { + static_assert(false, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' section " + "of your YAML configuration"); + } + + void call_homeassistant_service(const std::string &service_name, const std::map &data) { + static_assert(false, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' section " + "of your YAML configuration"); + } + + void fire_homeassistant_event(const std::string &event_name) { + static_assert(false, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' section of " + "your YAML configuration"); + } + + void fire_homeassistant_event(const std::string &service_name, const std::map &data) { + static_assert(false, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' section of " + "your YAML configuration"); + } #endif };