From 1e72f0ee5aa347214d6e5476d26864758e912d83 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Thu, 2 Apr 2026 22:17:20 +0200 Subject: [PATCH] [nextion] Gate waveform code behind `USE_NEXTION_WAVEFORM`, use `StaticRingBuffer` (#15273) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- esphome/components/nextion/nextion.cpp | 53 ++++++++----- esphome/components/nextion/nextion.h | 16 +++- esphome/components/nextion/nextion_base.h | 2 + .../components/nextion/nextion_commands.cpp | 2 + .../nextion/nextion_component_base.h | 6 ++ esphome/components/nextion/sensor/__init__.py | 18 ++--- .../nextion/sensor/nextion_sensor.cpp | 78 ++++++++++--------- .../nextion/sensor/nextion_sensor.h | 23 ++++-- esphome/core/defines.h | 1 + 9 files changed, 125 insertions(+), 74 deletions(-) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index ab268fed7f2..4a15cbe64fa 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -150,10 +150,12 @@ void Nextion::reset_(bool reset_nextion) { delete entry; // NOLINT(cppcoreguidelines-owning-memory) } this->nextion_queue_.clear(); +#ifdef USE_NEXTION_WAVEFORM for (auto *entry : this->waveform_queue_) { delete entry; // NOLINT(cppcoreguidelines-owning-memory) } this->waveform_queue_.clear(); +#endif // USE_NEXTION_WAVEFORM } void Nextion::dump_config() { @@ -496,20 +498,21 @@ void Nextion::process_nextion_commands_() { ESP_LOGW(TAG, "Invalid baud rate"); break; case 0x12: // invalid Waveform ID or Channel # was used +#ifdef USE_NEXTION_WAVEFORM if (this->waveform_queue_.empty()) { ESP_LOGW(TAG, "Waveform ID/ch used but no sensor queued"); } else { auto &nb = this->waveform_queue_.front(); NextionComponentBase *component = nb->component; - ESP_LOGW(TAG, "Invalid waveform ID %d/ch %d", component->get_component_id(), component->get_wave_channel_id()); - ESP_LOGN(TAG, "Remove waveform ID %d/ch %d", component->get_component_id(), component->get_wave_channel_id()); - delete nb; // NOLINT(cppcoreguidelines-owning-memory) - this->waveform_queue_.pop_front(); + this->waveform_queue_.pop(); } +#else // USE_NEXTION_WAVEFORM + ESP_LOGW(TAG, "Waveform ID/ch error but waveform not enabled"); +#endif // USE_NEXTION_WAVEFORM break; case 0x1A: // variable name invalid ESP_LOGW(TAG, "Invalid variable name"); @@ -812,29 +815,30 @@ void Nextion::process_nextion_commands_() { } case 0xFD: { // data transparent transmit finished ESP_LOGVV(TAG, "Data transmit done"); +#ifdef USE_NEXTION_WAVEFORM this->check_pending_waveform_(); +#endif // USE_NEXTION_WAVEFORM break; } case 0xFE: { // data transparent transmit ready ESP_LOGVV(TAG, "Ready for transmit"); +#ifdef USE_NEXTION_WAVEFORM if (this->waveform_queue_.empty()) { ESP_LOGE(TAG, "No waveforms queued"); break; } - auto &nb = this->waveform_queue_.front(); auto *component = nb->component; - size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() - : 255; // ADDT command can only send 255 - + size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() : 255; this->write_array(component->get_wave_buffer().data(), static_cast(buffer_to_send)); - ESP_LOGN(TAG, "Send waveform: component id %d, waveform id %d, size %zu", component->get_component_id(), component->get_wave_channel_id(), buffer_to_send); - component->clear_wave_buffer(buffer_to_send); delete nb; // NOLINT(cppcoreguidelines-owning-memory) - this->waveform_queue_.pop_front(); + this->waveform_queue_.pop(); +#else // USE_NEXTION_WAVEFORM + ESP_LOGW(TAG, "Waveform transmit ready but waveform not enabled"); +#endif // USE_NEXTION_WAVEFORM break; } default: @@ -934,8 +938,13 @@ void Nextion::all_components_send_state_(bool force_update) { binarysensortype->send_state_to_nextion(); } for (auto *sensortype : this->sensortype_) { - if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_channel_id() == 0) +#ifdef USE_NEXTION_WAVEFORM + if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_channel_id() == UINT8_MAX) { +#else // USE_NEXTION_WAVEFORM + if (force_update || sensortype->get_needs_to_send_update()) { +#endif // USE_NEXTION_WAVEFORM sensortype->send_state_to_nextion(); + } } for (auto *switchtype : this->switchtype_) { if (force_update || switchtype->get_needs_to_send_update()) @@ -1239,13 +1248,11 @@ void Nextion::add_to_get_queue(NextionComponentBase *component) { } } +#ifdef USE_NEXTION_WAVEFORM /** - * @brief Add addt command to the queue + * @brief Add addt command to the waveform queue. * - * @param component_id The waveform component id - * @param wave_chan_id The waveform channel to send it to - * @param buffer_to_send The buffer size - * @param buffer_size The buffer data + * @param component Pointer to the Nextion component with waveform data to send. */ void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping()) @@ -1262,7 +1269,11 @@ void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { nextion_queue->component = component; nextion_queue->queue_time = App.get_loop_component_start_time(); - this->waveform_queue_.push_back(nextion_queue); + if (!this->waveform_queue_.push(nextion_queue)) { + ESP_LOGW(TAG, "Waveform queue full, drop"); + delete nextion_queue; // NOLINT(cppcoreguidelines-owning-memory) + return; + } if (this->waveform_queue_.size() == 1) this->check_pending_waveform_(); } @@ -1273,17 +1284,17 @@ void Nextion::check_pending_waveform_() { auto *nb = this->waveform_queue_.front(); auto *component = nb->component; - size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() - : 255; // ADDT command can only send 255 + size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() : 255; char command[24]; // "addt " + uint8 + "," + uint8 + "," + uint8 + null = max 17 chars buf_append_printf(command, sizeof(command), 0, "addt %u,%u,%zu", component->get_component_id(), component->get_wave_channel_id(), buffer_to_send); if (!this->send_command_(command)) { delete nb; // NOLINT(cppcoreguidelines-owning-memory) - this->waveform_queue_.pop_front(); + this->waveform_queue_.pop(); } } +#endif // USE_NEXTION_WAVEFORM void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; } diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index b3ecbf46b1c..d9103892891 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -9,6 +9,10 @@ #include "esphome/core/defines.h" #include "esphome/core/time.h" +#ifdef USE_NEXTION_WAVEFORM +#include "esphome/core/helpers.h" +#endif // USE_NEXTION_WAVEFORM + #include "nextion_base.h" #include "nextion_component.h" @@ -602,6 +606,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe */ void disable_component_touch(const char *component); +#ifdef USE_NEXTION_WAVEFORM /** * Add waveform data to a waveform component * @param component_id The integer component id. @@ -611,6 +616,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void add_waveform_data(uint8_t component_id, uint8_t channel_number, uint8_t value); void open_waveform_channel(uint8_t component_id, uint8_t channel_number, uint8_t value); +#endif // USE_NEXTION_WAVEFORM /** * Display a picture at coordinates. @@ -1205,7 +1211,9 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void add_to_get_queue(NextionComponentBase *component) override; +#ifdef USE_NEXTION_WAVEFORM void add_addt_command_to_queue(NextionComponentBase *component) override; +#endif // USE_NEXTION_WAVEFORM void update_components_by_prefix(const std::string &prefix); @@ -1391,7 +1399,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe #endif // USE_NEXTION_COMMAND_SPACING std::list nextion_queue_; - std::list waveform_queue_; +#ifdef USE_NEXTION_WAVEFORM + /// Fixed-size ring buffer for waveform queue. Nextion supports at most 4 waveform + /// channels (IDs 0-3), so 4 entries is both the correct maximum and a safe default. + StaticRingBuffer waveform_queue_; +#endif // USE_NEXTION_WAVEFORM uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); void all_components_send_state_(bool force_update = false); uint32_t comok_sent_ = 0; @@ -1460,7 +1472,9 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe const std::string &variable_name_to_send, const std::string &state_value, bool is_sleep_safe = false); +#ifdef USE_NEXTION_WAVEFORM void check_pending_waveform_(); +#endif // USE_NEXTION_WAVEFORM #ifdef USE_NEXTION_TFT_UPLOAD #ifdef USE_ESP8266 diff --git a/esphome/components/nextion/nextion_base.h b/esphome/components/nextion/nextion_base.h index 2c516fc80f2..4a2dc90d40e 100644 --- a/esphome/components/nextion/nextion_base.h +++ b/esphome/components/nextion/nextion_base.h @@ -33,7 +33,9 @@ class NextionBase { const std::string &variable_name_to_send, const std::string &state_value) = 0; +#ifdef USE_NEXTION_WAVEFORM virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0; +#endif // USE_NEXTION_WAVEFORM virtual void add_to_get_queue(NextionComponentBase *component) = 0; diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp index a7e65b5ddf9..a332d342ee5 100644 --- a/esphome/components/nextion/nextion_commands.cpp +++ b/esphome/components/nextion/nextion_commands.cpp @@ -217,6 +217,7 @@ void Nextion::set_component_value(const char *component, int32_t value) { this->add_no_result_to_queue_with_printf_(".val", "%s.val=%" PRId32, component, value); } +#ifdef USE_NEXTION_WAVEFORM void Nextion::add_waveform_data(uint8_t component_id, uint8_t channel_number, uint8_t value) { this->add_no_result_to_queue_with_printf_("add", "add %" PRIu8 ",%" PRIu8 ",%" PRIu8, component_id, channel_number, value); @@ -226,6 +227,7 @@ void Nextion::open_waveform_channel(uint8_t component_id, uint8_t channel_number this->add_no_result_to_queue_with_printf_("addt", "addt %" PRIu8 ",%" PRIu8 ",%" PRIu8, component_id, channel_number, value); } +#endif // USE_NEXTION_WAVEFORM void Nextion::set_component_coordinates(const char *component, uint16_t x, uint16_t y) { this->add_no_result_to_queue_with_printf_(".xcen", "%s.xcen=%" PRIu16, component, x); diff --git a/esphome/components/nextion/nextion_component_base.h b/esphome/components/nextion/nextion_component_base.h index c1d0ae8ed11..6676d019201 100644 --- a/esphome/components/nextion/nextion_component_base.h +++ b/esphome/components/nextion/nextion_component_base.h @@ -64,6 +64,7 @@ class NextionComponentBase { uint8_t get_component_id() const { return this->component_id_; } void set_component_id(uint8_t component_id) { this->component_id_ = component_id; } +#ifdef USE_NEXTION_WAVEFORM uint8_t get_wave_channel_id() const { return this->wave_chan_id_; } void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; } @@ -76,6 +77,7 @@ class NextionComponentBase { this->wave_buffer_.erase(this->wave_buffer_.begin(), this->wave_buffer_.begin() + buffer_sent); } } +#endif // USE_NEXTION_WAVEFORM const std::string &get_variable_name() const { return this->variable_name_; } const std::string &get_variable_name_to_send() const { return this->variable_name_to_send_; } @@ -85,19 +87,23 @@ class NextionComponentBase { virtual void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion){}; virtual void send_state_to_nextion(){}; bool get_needs_to_send_update() const { return this->needs_to_send_update_; } +#ifdef USE_NEXTION_WAVEFORM // Remove before 2026.10.0 ESPDEPRECATED("Use get_wave_channel_id() instead. Will be removed in 2026.10.0", "2026.4.0") uint8_t get_wave_chan_id() const { return this->get_wave_channel_id(); } void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; } +#endif // USE_NEXTION_WAVEFORM protected: std::string variable_name_; std::string variable_name_to_send_; uint8_t component_id_ = 0; +#ifdef USE_NEXTION_WAVEFORM uint8_t wave_chan_id_ = UINT8_MAX; std::vector wave_buffer_; int wave_max_length_ = 255; +#endif // USE_NEXTION_WAVEFORM bool needs_to_send_update_; }; diff --git a/esphome/components/nextion/sensor/__init__.py b/esphome/components/nextion/sensor/__init__.py index cab531f1db6..7351d8f1d54 100644 --- a/esphome/components/nextion/sensor/__init__.py +++ b/esphome/components/nextion/sensor/__init__.py @@ -85,16 +85,16 @@ async def to_code(config): cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) if CONF_WAVE_CHANNEL_ID in config: + cg.add_define("USE_NEXTION_WAVEFORM") cg.add(var.set_wave_channel_id(config[CONF_WAVE_CHANNEL_ID])) - - if CONF_WAVEFORM_SEND_LAST_VALUE in config: - cg.add(var.set_waveform_send_last_value(config[CONF_WAVEFORM_SEND_LAST_VALUE])) - - if CONF_WAVE_MAX_VALUE in config: - cg.add(var.set_wave_max_value(config[CONF_WAVE_MAX_VALUE])) - - if CONF_WAVE_MAX_LENGTH in config: - cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH])) + if CONF_WAVEFORM_SEND_LAST_VALUE in config: + cg.add( + var.set_waveform_send_last_value(config[CONF_WAVEFORM_SEND_LAST_VALUE]) + ) + if CONF_WAVE_MAX_VALUE in config: + cg.add(var.set_wave_max_value(config[CONF_WAVE_MAX_VALUE])) + if CONF_WAVE_MAX_LENGTH in config: + cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH])) @automation.register_action( diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index d4fad86286a..ca657522f96 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -10,37 +10,44 @@ void NextionSensor::process_sensor(const std::string &variable_name, int state) if (!this->nextion_->is_setup()) return; - if (this->wave_chan_id_ == UINT8_MAX && this->variable_name_ == variable_name) { +#ifdef USE_NEXTION_WAVEFORM + if (this->wave_chan_id_ == UINT8_MAX && this->variable_name_ == variable_name) +#else // USE_NEXTION_WAVEFORM + if (this->variable_name_ == variable_name) +#endif // USE_NEXTION_WAVEFORM + { this->publish_state(state); ESP_LOGD(TAG, "Sensor: %s=%d", variable_name.c_str(), state); } } +#ifdef USE_NEXTION_WAVEFORM void NextionSensor::add_to_wave_buffer(float state) { this->needs_to_send_update_ = true; - int wave_state = (int) ((state / (float) this->wave_maxvalue_) * 100); - - wave_buffer_.push_back(wave_state); - + this->wave_buffer_.push_back(wave_state); if (this->wave_buffer_.size() > (size_t) this->wave_max_length_) { this->wave_buffer_.erase(this->wave_buffer_.begin()); } } +#endif // USE_NEXTION_WAVEFORM void NextionSensor::update() { if (!this->nextion_->is_setup() || this->nextion_->is_updating()) return; +#ifdef USE_NEXTION_WAVEFORM if (this->wave_chan_id_ == UINT8_MAX) { this->nextion_->add_to_get_queue(this); } else { if (this->send_last_value_) { this->add_to_wave_buffer(this->last_value_); } - this->wave_update_(); } +#else // USE_NEXTION_WAVEFORM + this->nextion_->add_to_get_queue(this); +#endif // USE_NEXTION_WAVEFORM } void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { @@ -50,61 +57,60 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { if (std::isnan(state)) return; - if (this->wave_chan_id_ == UINT8_MAX) { - if (send_to_nextion) { - if (this->nextion_->is_sleeping() || !this->component_flags_.visible) { - this->needs_to_send_update_ = true; - } else { - this->needs_to_send_update_ = false; - - if (this->precision_ > 0) { - double to_multiply = pow(10, this->precision_); - int state_value = (int) (state * to_multiply); - - this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value); - } else { - this->nextion_->add_no_result_to_queue_with_set(this, (int) state); - } - } - } - } else { +#ifdef USE_NEXTION_WAVEFORM + if (this->wave_chan_id_ != UINT8_MAX) { + // Waveform sensor — buffer the value, don't send directly. if (this->send_last_value_) { this->last_value_ = state; // Update will handle setting the buffer } else { this->add_to_wave_buffer(state); } + this->update_component_settings(); + return; + } +#endif // USE_NEXTION_WAVEFORM + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->component_flags_.visible) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + if (this->precision_ > 0) { + double to_multiply = pow(10, this->precision_); + int state_value = (int) (state * to_multiply); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value); + } else { + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } } float published_state = state; - if (this->wave_chan_id_ == UINT8_MAX) { - if (publish) { - if (this->precision_ > 0) { - double to_multiply = pow(10, -this->precision_); - published_state = (float) (state * to_multiply); - } - - this->publish_state(published_state); + if (publish) { + if (this->precision_ > 0) { + double to_multiply = pow(10, -this->precision_); + published_state = (float) (state * to_multiply); } + this->publish_state(published_state); } this->update_component_settings(); ESP_LOGN(TAG, "Write: %s=%lf", this->variable_name_.c_str(), published_state); } +#ifdef USE_NEXTION_WAVEFORM void NextionSensor::wave_update_() { if (this->nextion_->is_sleeping() || this->wave_buffer_.empty()) { return; } - #ifdef NEXTION_PROTOCOL_LOG size_t buffer_to_send = this->wave_buffer_.size() < 255 ? this->wave_buffer_.size() : 255; // ADDT command can only send 255 - ESP_LOGN(TAG, "Wave update: %zu/%zu vals to comp %d ch %d", buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_); -#endif - +#endif // NEXTION_PROTOCOL_LOG this->nextion_->add_addt_command_to_queue(this); } +#endif // USE_NEXTION_WAVEFORM } // namespace esphome::nextion diff --git a/esphome/components/nextion/sensor/nextion_sensor.h b/esphome/components/nextion/sensor/nextion_sensor.h index f1a3ff72ec0..72e3982b3a1 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.h +++ b/esphome/components/nextion/sensor/nextion_sensor.h @@ -15,22 +15,30 @@ class NextionSensor : public NextionComponent, public sensor::Sensor, public Pol void update_component() override { this->update(); } void update() override; - void add_to_wave_buffer(float state); void set_precision(uint8_t precision) { this->precision_ = precision; } void set_component_id(uint8_t component_id) { this->component_id_ = component_id; } - void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; } - void set_wave_max_value(uint32_t wave_maxvalue) { this->wave_maxvalue_ = wave_maxvalue; } void process_sensor(const std::string &variable_name, int state) override; void set_state(float state) override { this->set_state(state, true, true); } void set_state(float state, bool publish) override { this->set_state(state, publish, true); } void set_state(float state, bool publish, bool send_to_nextion) override; + NextionQueueType get_queue_type() const override { +#ifdef USE_NEXTION_WAVEFORM + return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR; +#else // USE_NEXTION_WAVEFORM + return NextionQueueType::SENSOR; +#endif // USE_NEXTION_WAVEFORM + } + +#ifdef USE_NEXTION_WAVEFORM + void add_to_wave_buffer(float state); + void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; } + void set_wave_max_value(uint32_t wave_maxvalue) { this->wave_maxvalue_ = wave_maxvalue; } void set_waveform_send_last_value(bool send_last_value) { this->send_last_value_ = send_last_value; } void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; } - NextionQueueType get_queue_type() const override { - return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR; - } +#endif // USE_NEXTION_WAVEFORM + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { this->set_state(state_value, publish, send_to_nextion); @@ -38,10 +46,11 @@ class NextionSensor : public NextionComponent, public sensor::Sensor, public Pol protected: uint8_t precision_ = 0; +#ifdef USE_NEXTION_WAVEFORM uint32_t wave_maxvalue_ = 255; - float last_value_ = 0; bool send_last_value_ = true; void wave_update_(); +#endif // USE_NEXTION_WAVEFORM }; } // namespace esphome::nextion diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 23e65f55bc0..faa8c6d4b0e 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -123,6 +123,7 @@ #define USE_NEXTION_MAX_COMMANDS_PER_LOOP #define USE_NEXTION_MAX_QUEUE_SIZE #define USE_NEXTION_TFT_UPLOAD +#define USE_NEXTION_WAVEFORM #define USE_NUMBER #define USE_OUTPUT #define USE_POWER_SUPPLY