diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py index 9708379861..0058d957dc 100644 --- a/esphome/components/nextion/base_component.py +++ b/esphome/components/nextion/base_component.py @@ -7,28 +7,29 @@ from esphome.const import CONF_BACKGROUND_COLOR, CONF_FOREGROUND_COLOR, CONF_VIS from . import CONF_NEXTION_ID, Nextion -CONF_VARIABLE_NAME = "variable_name" +CONF_AUTO_WAKE_ON_TOUCH = "auto_wake_on_touch" +CONF_BACKGROUND_PRESSED_COLOR = "background_pressed_color" +CONF_COMMAND_SPACING = "command_spacing" CONF_COMPONENT_NAME = "component_name" -CONF_WAVE_CHANNEL_ID = "wave_channel_id" -CONF_WAVE_MAX_VALUE = "wave_max_value" -CONF_PRECISION = "precision" -CONF_WAVEFORM_SEND_LAST_VALUE = "waveform_send_last_value" -CONF_TFT_URL = "tft_url" +CONF_EXIT_REPARSE_ON_START = "exit_reparse_on_start" +CONF_FONT_ID = "font_id" +CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color" +CONF_ON_BUFFER_OVERFLOW = "on_buffer_overflow" +CONF_ON_PAGE = "on_page" +CONF_ON_SETUP = "on_setup" CONF_ON_SLEEP = "on_sleep" CONF_ON_WAKE = "on_wake" -CONF_ON_SETUP = "on_setup" -CONF_ON_PAGE = "on_page" -CONF_ON_BUFFER_OVERFLOW = "on_buffer_overflow" -CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout" -CONF_WAKE_UP_PAGE = "wake_up_page" -CONF_START_UP_PAGE = "start_up_page" -CONF_AUTO_WAKE_ON_TOUCH = "auto_wake_on_touch" -CONF_WAVE_MAX_LENGTH = "wave_max_length" -CONF_BACKGROUND_PRESSED_COLOR = "background_pressed_color" -CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color" -CONF_FONT_ID = "font_id" -CONF_EXIT_REPARSE_ON_START = "exit_reparse_on_start" +CONF_PRECISION = "precision" CONF_SKIP_CONNECTION_HANDSHAKE = "skip_connection_handshake" +CONF_START_UP_PAGE = "start_up_page" +CONF_TFT_URL = "tft_url" +CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout" +CONF_VARIABLE_NAME = "variable_name" +CONF_WAKE_UP_PAGE = "wake_up_page" +CONF_WAVE_CHANNEL_ID = "wave_channel_id" +CONF_WAVE_MAX_LENGTH = "wave_max_length" +CONF_WAVE_MAX_VALUE = "wave_max_value" +CONF_WAVEFORM_SEND_LAST_VALUE = "waveform_send_last_value" def NextionName(value): diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 60f26e5234..2e7c1c2825 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -9,16 +9,17 @@ from esphome.const import ( CONF_ON_TOUCH, CONF_TRIGGER_ID, ) -from esphome.core import CORE +from esphome.core import CORE, TimePeriod from . import Nextion, nextion_ns, nextion_ref from .base_component import ( CONF_AUTO_WAKE_ON_TOUCH, + CONF_COMMAND_SPACING, CONF_EXIT_REPARSE_ON_START, CONF_ON_BUFFER_OVERFLOW, - CONF_ON_PAGE, CONF_ON_SETUP, CONF_ON_SLEEP, + CONF_ON_PAGE, CONF_ON_WAKE, CONF_SKIP_CONNECTION_HANDSHAKE, CONF_START_UP_PAGE, @@ -88,6 +89,10 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_AUTO_WAKE_ON_TOUCH, default=True): cv.boolean, cv.Optional(CONF_EXIT_REPARSE_ON_START, default=False): cv.boolean, cv.Optional(CONF_SKIP_CONNECTION_HANDSHAKE, default=False): cv.boolean, + cv.Optional(CONF_COMMAND_SPACING): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=TimePeriod(milliseconds=255)), + ), } ) .extend(cv.polling_component_schema("5s")) @@ -120,6 +125,10 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await uart.register_uart_device(var, config) + 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)) + if CONF_BRIGHTNESS in config: cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 67f08f68f8..38e37300af 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -31,11 +31,22 @@ bool Nextion::send_command_(const std::string &command) { return false; } +#ifdef USE_NEXTION_COMMAND_SPACING + if (!this->ignore_is_setup_ && !this->command_pacer_.can_send()) { + return false; + } +#endif // USE_NEXTION_COMMAND_SPACING + ESP_LOGN(TAG, "send_command %s", command.c_str()); this->write_str(command.c_str()); const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF}; this->write_array(to_send, sizeof(to_send)); + +#ifdef USE_NEXTION_COMMAND_SPACING + this->command_pacer_.mark_sent(); +#endif // USE_NEXTION_COMMAND_SPACING + return true; } @@ -158,6 +169,10 @@ void Nextion::dump_config() { if (this->start_up_page_ != -1) { ESP_LOGCONFIG(TAG, " Start Up Page: %" PRId16, this->start_up_page_); } + +#ifdef USE_NEXTION_COMMAND_SPACING + ESP_LOGCONFIG(TAG, " Command spacing: %" PRIu8 "ms", this->command_pacer_.get_spacing()); +#endif // USE_NEXTION_COMMAND_SPACING } float Nextion::get_setup_priority() const { return setup_priority::DATA; } @@ -312,6 +327,11 @@ bool Nextion::remove_from_q_(bool report_empty) { } NextionQueue *nb = this->nextion_queue_.front(); + if (!nb || !nb->component) { + ESP_LOGE(TAG, "Invalid queue entry!"); + this->nextion_queue_.pop_front(); + return false; + } NextionComponentBase *component = nb->component; ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str()); @@ -341,6 +361,12 @@ void Nextion::process_nextion_commands_() { return; } +#ifdef USE_NEXTION_COMMAND_SPACING + if (!this->command_pacer_.can_send()) { + return; // Will try again in next loop iteration + } +#endif + size_t to_process_length = 0; std::string to_process; @@ -380,7 +406,9 @@ void Nextion::process_nextion_commands_() { this->setup_callback_.call(); } } - +#ifdef USE_NEXTION_COMMAND_SPACING + this->command_pacer_.mark_sent(); // Here is where we should mark the command as sent +#endif break; case 0x02: // invalid Component ID or name was used ESP_LOGW(TAG, "Nextion reported component ID or name invalid!"); @@ -524,6 +552,11 @@ void Nextion::process_nextion_commands_() { } NextionQueue *nb = this->nextion_queue_.front(); + if (!nb || !nb->component) { + ESP_LOGE(TAG, "Invalid queue entry!"); + this->nextion_queue_.pop_front(); + return; + } NextionComponentBase *component = nb->component; if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) { @@ -564,6 +597,11 @@ void Nextion::process_nextion_commands_() { } NextionQueue *nb = this->nextion_queue_.front(); + if (!nb || !nb->component) { + ESP_LOGE(TAG, "Invalid queue entry!"); + this->nextion_queue_.pop_front(); + return; + } NextionComponentBase *component = nb->component; if (component->get_queue_type() != NextionQueueType::SENSOR && diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index b2404e1f0d..4bc5305923 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -35,8 +35,54 @@ using nextion_writer_t = std::function; static const std::string COMMAND_DELIMITER{static_cast(255), static_cast(255), static_cast(255)}; +#ifdef USE_NEXTION_COMMAND_SPACING +class NextionCommandPacer { + public: + /** + * @brief Creates command pacer with initial spacing + * @param initial_spacing Initial time between commands in milliseconds + */ + explicit NextionCommandPacer(uint8_t initial_spacing = 0) : spacing_ms_(initial_spacing) {} + + /** + * @brief Set the minimum time between commands + * @param spacing_ms Spacing in milliseconds + */ + void set_spacing(uint8_t spacing_ms) { spacing_ms_ = spacing_ms; } + + /** + * @brief Get current command spacing + * @return Current spacing in milliseconds + */ + uint8_t get_spacing() const { return spacing_ms_; } + + /** + * @brief Check if enough time has passed to send next command + * @return true if enough time has passed since last command + */ + bool can_send() const { return (millis() - last_command_time_) >= spacing_ms_; } + + /** + * @brief Mark a command as sent, updating the timing + */ + void mark_sent() { last_command_time_ = millis(); } + + private: + uint8_t spacing_ms_; + uint32_t last_command_time_{0}; +}; +#endif // USE_NEXTION_COMMAND_SPACING + class Nextion : public NextionBase, public PollingComponent, public uart::UARTDevice { public: +#ifdef USE_NEXTION_COMMAND_SPACING + /** + * @brief Set the command spacing for the display + * @param spacing_ms Time in milliseconds between commands + */ + void set_command_spacing(uint32_t spacing_ms) { this->command_pacer_.set_spacing(spacing_ms); } +#endif // USE_NEXTION_COMMAND_SPACING + /** * Set the text of a component to a static string. * @param component The component name. @@ -1227,6 +1273,9 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe bool is_connected() { return this->is_connected_; } protected: +#ifdef USE_NEXTION_COMMAND_SPACING + NextionCommandPacer command_pacer_{0}; +#endif // USE_NEXTION_COMMAND_SPACING std::deque nextion_queue_; std::deque waveform_queue_; uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); @@ -1360,5 +1409,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe uint32_t started_ms_ = 0; bool sent_setup_commands_ = false; }; + } // namespace nextion } // namespace esphome diff --git a/tests/components/nextion/common.yaml b/tests/components/nextion/common.yaml index 589afcfefb..44d6cdfbc9 100644 --- a/tests/components/nextion/common.yaml +++ b/tests/components/nextion/common.yaml @@ -280,6 +280,7 @@ display: - platform: nextion id: main_lcd update_interval: 5s + command_spacing: 5ms on_sleep: then: lambda: 'ESP_LOGD("display","Display went to sleep");'