diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py index 0058d957dc..2057f21157 100644 --- a/esphome/components/nextion/base_component.py +++ b/esphome/components/nextion/base_component.py @@ -14,6 +14,7 @@ CONF_COMPONENT_NAME = "component_name" CONF_EXIT_REPARSE_ON_START = "exit_reparse_on_start" CONF_FONT_ID = "font_id" CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color" +CONF_MAX_QUEUE_SIZE = "max_queue_size" CONF_ON_BUFFER_OVERFLOW = "on_buffer_overflow" CONF_ON_PAGE = "on_page" CONF_ON_SETUP = "on_setup" diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 2e7c1c2825..b21a2286f9 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -16,6 +16,7 @@ from .base_component import ( CONF_AUTO_WAKE_ON_TOUCH, CONF_COMMAND_SPACING, CONF_EXIT_REPARSE_ON_START, + CONF_MAX_QUEUE_SIZE, CONF_ON_BUFFER_OVERFLOW, CONF_ON_SETUP, CONF_ON_SLEEP, @@ -93,6 +94,7 @@ CONFIG_SCHEMA = ( cv.positive_time_period_milliseconds, cv.Range(max=TimePeriod(milliseconds=255)), ), + cv.Optional(CONF_MAX_QUEUE_SIZE): cv.positive_int, } ) .extend(cv.polling_component_schema("5s")) @@ -125,6 +127,10 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await uart.register_uart_device(var, config) + if max_queue_size := config.get(CONF_MAX_QUEUE_SIZE): + cg.add_define("USE_NEXTION_MAX_QUEUE_SIZE") + cg.add(var.set_max_queue_size(max_queue_size)) + if command_spacing := config.get(CONF_COMMAND_SPACING): cg.add_define("USE_NEXTION_COMMAND_SPACING") cg.add(var.set_command_spacing(command_spacing.total_milliseconds)) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 66812170be..58ab621538 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -173,6 +173,10 @@ void Nextion::dump_config() { #ifdef USE_NEXTION_COMMAND_SPACING ESP_LOGCONFIG(TAG, " Command spacing: %" PRIu8 "ms", this->command_pacer_.get_spacing()); #endif // USE_NEXTION_COMMAND_SPACING + +#ifdef USE_NEXTION_MAX_QUEUE_SIZE + ESP_LOGCONFIG(TAG, " Max queue size: %zu", this->max_queue_size_); +#endif } float Nextion::get_setup_priority() const { return setup_priority::DATA; } @@ -998,11 +1002,24 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool } /** - * @brief + * @brief Add a command to the Nextion queue that expects no response. * - * @param variable_name Name for the queue + * This is typically used for write-only operations such as variable assignments or component updates + * where no return value or acknowledgment is expected from the display. + * + * If the `max_queue_size` limit is configured and reached, the command will be skipped. + * + * @param variable_name Name of the variable or component associated with the command. */ void Nextion::add_no_result_to_queue_(const std::string &variable_name) { +#ifdef USE_NEXTION_MAX_QUEUE_SIZE + if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) { + ESP_LOGW(TAG, "Nextion queue full (%zu entries), dropping NORESULT command: %s", this->nextion_queue_.size(), + variable_name.c_str()); + return; + } +#endif + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); nextion::NextionQueue *nextion_queue = allocator.allocate(1); if (nextion_queue == nullptr) { @@ -1138,10 +1155,27 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia state_value.c_str()); } +/** + * @brief Queue a GET command for a component that expects a response from the Nextion display. + * + * This method is used for querying values such as sensor states, text content, or switch status. + * The component will be added to the Nextion queue only if the display is already set up, + * the queue has not reached the configured maximum size (if set), and the command is sent successfully. + * + * @param component Pointer to the Nextion component that will handle the response. + */ void Nextion::add_to_get_queue(NextionComponentBase *component) { if ((!this->is_setup() && !this->ignore_is_setup_)) return; +#ifdef USE_NEXTION_MAX_QUEUE_SIZE + if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) { + ESP_LOGW(TAG, "Nextion queue full (%zu entries), dropping GET for \"%s\"", this->nextion_queue_.size(), + component->get_variable_name().c_str()); + return; + } +#endif + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); nextion::NextionQueue *nextion_queue = allocator.allocate(1); if (nextion_queue == nullptr) { diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 4bc5305923..f12b9519b3 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -75,6 +75,20 @@ class NextionCommandPacer { class Nextion : public NextionBase, public PollingComponent, public uart::UARTDevice { public: +#ifdef USE_NEXTION_MAX_QUEUE_SIZE + /** + * @brief Set the maximum allowed queue size + * @param size Max number of entries allowed in nextion_queue_ + */ + inline void set_max_queue_size(size_t size) { this->max_queue_size_ = size; } + + /** + * @brief Get the maximum allowed queue size + * @return Current limit (0 = unlimited) + */ + inline size_t get_max_queue_size() const { return this->max_queue_size_; } +#endif // USE_NEXTION_MAX_QUEUE_SIZE + #ifdef USE_NEXTION_COMMAND_SPACING /** * @brief Set the command spacing for the display @@ -1273,6 +1287,9 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe bool is_connected() { return this->is_connected_; } protected: +#ifdef USE_NEXTION_MAX_QUEUE_SIZE + size_t max_queue_size_{0}; +#endif // USE_NEXTION_MAX_QUEUE_SIZE #ifdef USE_NEXTION_COMMAND_SPACING NextionCommandPacer command_pacer_{0}; #endif // USE_NEXTION_COMMAND_SPACING diff --git a/tests/components/nextion/common.yaml b/tests/components/nextion/common.yaml index 44d6cdfbc9..d4e543fe25 100644 --- a/tests/components/nextion/common.yaml +++ b/tests/components/nextion/common.yaml @@ -281,6 +281,7 @@ display: id: main_lcd update_interval: 5s command_spacing: 5ms + max_queue_size: 50 on_sleep: then: lambda: 'ESP_LOGD("display","Display went to sleep");'