#include "nextion.h" #include "esphome/core/log.h" namespace esphome { namespace nextion { static const char *TAG = "nextion"; void Nextion::setup() { this->send_command_no_ack(""); this->send_command_printf("bkcmd=3"); this->goto_page("0"); } float Nextion::get_setup_priority() const { return setup_priority::PROCESSOR; } void Nextion::update() { if (this->writer_.has_value()) { (*this->writer_)(*this); } } void Nextion::send_command_no_ack(const char *command) { // Flush RX... this->loop(); this->write_str(command); const uint8_t data[3] = {0xFF, 0xFF, 0xFF}; this->write_array(data, sizeof(data)); } bool Nextion::ack_() { if (!this->wait_for_ack_) return true; uint32_t start = millis(); while (!this->read_until_ack_()) { if (millis() - start > 100) { ESP_LOGW(TAG, "Waiting for ACK timed out!"); return false; } } return true; } void Nextion::set_component_text(const char *component, const char *text) { this->send_command_printf("%s.txt=\"%s\"", component, text); } void Nextion::set_component_value(const char *component, int value) { this->send_command_printf("%s.val=%d", component, value); } void Nextion::display_picture(int picture_id, int x_start, int y_start) { this->send_command_printf("pic %d %d %d", picture_id, x_start, y_start); } void Nextion::set_component_background_color(const char *component, const char *color) { this->send_command_printf("%s.bco=\"%s\"", component, color); } void Nextion::set_component_pressed_background_color(const char *component, const char *color) { this->send_command_printf("%s.bco2=\"%s\"", component, color); } void Nextion::set_component_font_color(const char *component, const char *color) { this->send_command_printf("%s.pco=\"%s\"", component, color); } void Nextion::set_component_pressed_font_color(const char *component, const char *color) { this->send_command_printf("%s.pco2=\"%s\"", component, color); } void Nextion::set_component_coordinates(const char *component, int x, int y) { this->send_command_printf("%s.xcen=%d", component, x); this->send_command_printf("%s.ycen=%d", component, y); } void Nextion::set_component_font(const char *component, uint8_t font_id) { this->send_command_printf("%s.font=%d", component, font_id); } void Nextion::goto_page(const char *page) { this->send_command_printf("page %s", page); } bool Nextion::send_command_printf(const char *format, ...) { char buffer[256]; va_list arg; va_start(arg, format); int ret = vsnprintf(buffer, sizeof(buffer), format, arg); va_end(arg); if (ret <= 0) { ESP_LOGW(TAG, "Building command for format '%s' failed!", format); return false; } this->send_command_no_ack(buffer); if (!this->ack_()) { ESP_LOGW(TAG, "Sending command '%s' failed because no ACK was received", buffer); return false; } return true; } void Nextion::hide_component(const char *component) { this->send_command_printf("vis %s,0", component); } void Nextion::show_component(const char *component) { this->send_command_printf("vis %s,1", component); } void Nextion::enable_component_touch(const char *component) { this->send_command_printf("tsw %s,1", component); } void Nextion::disable_component_touch(const char *component) { this->send_command_printf("tsw %s,0", component); } void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) { this->send_command_printf("add %d,%u,%u", component_id, channel_number, value); } void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) { this->send_command_printf("fill %d,%d,%d,%d,%s", x1, y1, width, height, color); } void Nextion::line(int x1, int y1, int x2, int y2, const char *color) { this->send_command_printf("line %d,%d,%d,%d,%s", x1, y1, x2, y2, color); } void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) { this->send_command_printf("draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color); } void Nextion::circle(int center_x, int center_y, int radius, const char *color) { this->send_command_printf("cir %d,%d,%d,%s", center_x, center_y, radius, color); } void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) { this->send_command_printf("cirs %d,%d,%d,%s", center_x, center_y, radius, color); } bool Nextion::read_until_ack_() { while (this->available() >= 4) { // flush preceding filler bytes uint8_t temp; while (this->available() && this->peek_byte(&temp) && temp == 0xFF) this->read_byte(&temp); if (!this->available()) break; uint8_t event; // event type this->read_byte(&event); uint8_t data[255]; // total length of data (including end bytes) uint8_t data_length = 0; // message is terminated by three consecutive 0xFF // this variable keeps track of ohow many of those have // been received uint8_t end_length = 0; while (this->available() && end_length < 3 && data_length < sizeof(data)) { uint8_t byte; this->read_byte(&byte); if (byte == 0xFF) { end_length++; } else { end_length = 0; } data[data_length++] = byte; } if (end_length != 3) { ESP_LOGW(TAG, "Received unknown filler end bytes from Nextion!"); continue; } data_length -= 3; // remove filler bytes bool invalid_data_length = false; switch (event) { case 0x01: // successful execution of instruction (ACK) return true; case 0x00: // invalid instruction ESP_LOGW(TAG, "Nextion reported invalid instruction!"); break; case 0x02: // component ID invalid ESP_LOGW(TAG, "Nextion reported component ID invalid!"); break; case 0x03: // page ID invalid ESP_LOGW(TAG, "Nextion reported page ID invalid!"); break; case 0x04: // picture ID invalid ESP_LOGW(TAG, "Nextion reported picture ID invalid!"); break; case 0x05: // font ID invalid ESP_LOGW(TAG, "Nextion reported font ID invalid!"); break; case 0x11: // baud rate setting invalid ESP_LOGW(TAG, "Nextion reported baud rate invalid!"); break; case 0x12: // curve control ID number or channel number is invalid ESP_LOGW(TAG, "Nextion reported control/channel ID invalid!"); break; case 0x1A: // variable name invalid ESP_LOGW(TAG, "Nextion reported variable name invalid!"); break; case 0x1B: // variable operation invalid ESP_LOGW(TAG, "Nextion reported variable operation invalid!"); break; case 0x1C: // failed to assign ESP_LOGW(TAG, "Nextion reported failed to assign variable!"); break; case 0x1D: // operate EEPROM failed ESP_LOGW(TAG, "Nextion reported operating EEPROM failed!"); break; case 0x1E: // parameter quantity invalid ESP_LOGW(TAG, "Nextion reported parameter quantity invalid!"); break; case 0x1F: // IO operation failed ESP_LOGW(TAG, "Nextion reported component I/O operation invalid!"); break; case 0x20: // undefined escape characters ESP_LOGW(TAG, "Nextion reported undefined escape characters!"); break; case 0x23: // too long variable name ESP_LOGW(TAG, "Nextion reported too long variable name!"); break; case 0x65: { // touch event return data if (data_length != 3) { invalid_data_length = true; break; } uint8_t page_id = data[0]; uint8_t component_id = data[1]; uint8_t touch_event = data[2]; // 0 -> release, 1 -> press ESP_LOGD(TAG, "Got touch page=%u component=%u type=%s", page_id, component_id, touch_event ? "PRESS" : "RELEASE"); for (auto *touch : this->touch_) { touch->process(page_id, component_id, touch_event); } break; } case 0x67: case 0x68: { // touch coordinate data if (data_length != 5) { invalid_data_length = true; break; } uint16_t x = (uint16_t(data[0]) << 8) | data[1]; uint16_t y = (uint16_t(data[2]) << 8) | data[3]; uint8_t touch_event = data[4]; // 0 -> release, 1 -> press ESP_LOGD(TAG, "Got touch at x=%u y=%u type=%s", x, y, touch_event ? "PRESS" : "RELEASE"); break; } case 0x66: // sendme page id case 0x70: // string variable data return case 0x71: // numeric variable data return case 0x86: // device automatically enters into sleep mode case 0x87: // device automatically wakes up case 0x88: // system successful start up case 0x89: // start SD card upgrade case 0xFD: // data transparent transmit finished case 0xFE: // data transparent transmit ready break; default: ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", event); break; } if (invalid_data_length) { ESP_LOGW(TAG, "Invalid data length from nextion!"); } } return false; } void Nextion::loop() { while (this->available() >= 4) { this->read_until_ack_(); } } #ifdef USE_TIME void Nextion::set_nextion_rtc_time(time::ESPTime time) { this->send_command_printf("rtc0=%u", time.year); this->send_command_printf("rtc1=%u", time.month); this->send_command_printf("rtc2=%u", time.day_of_month); this->send_command_printf("rtc3=%u", time.hour); this->send_command_printf("rtc4=%u", time.minute); this->send_command_printf("rtc5=%u", time.second); } #endif void Nextion::set_backlight_brightness(uint8_t brightness) { this->send_command_printf("dim=%u", brightness); } void Nextion::set_touch_sleep_timeout(uint16_t timeout) { this->send_command_printf("thsp=%u", timeout); } void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; } void Nextion::set_component_text_printf(const char *component, const char *format, ...) { va_list arg; va_start(arg, format); char buffer[256]; int ret = vsnprintf(buffer, sizeof(buffer), format, arg); va_end(arg); if (ret > 0) this->set_component_text(component, buffer); } void Nextion::set_wait_for_ack(bool wait_for_ack) { this->wait_for_ack_ = wait_for_ack; } void NextionTouchComponent::process(uint8_t page_id, uint8_t component_id, bool on) { if (this->page_id_ == page_id && this->component_id_ == component_id) { this->publish_state(on); } } } // namespace nextion } // namespace esphome