From 4f7eaff3b5f0a0af6427b54526ab3c033f435df2 Mon Sep 17 00:00:00 2001 From: Mit4el Date: Fri, 17 Nov 2023 23:00:52 +0300 Subject: [PATCH] new Nextion --- src/modules/display/Nextion/ESPNexUpload.cpp | 657 +++++++++++++++++++ src/modules/display/Nextion/ESPNexUpload.h | 288 ++++++++ src/modules/display/Nextion/Nextion.cpp | 312 +++++++++ src/modules/display/Nextion/modinfo.json | 98 +++ 4 files changed, 1355 insertions(+) create mode 100644 src/modules/display/Nextion/ESPNexUpload.cpp create mode 100644 src/modules/display/Nextion/ESPNexUpload.h create mode 100644 src/modules/display/Nextion/Nextion.cpp create mode 100644 src/modules/display/Nextion/modinfo.json diff --git a/src/modules/display/Nextion/ESPNexUpload.cpp b/src/modules/display/Nextion/ESPNexUpload.cpp new file mode 100644 index 00000000..ad58883f --- /dev/null +++ b/src/modules/display/Nextion/ESPNexUpload.cpp @@ -0,0 +1,657 @@ +/** + * @file NexUpload.cpp + * + * The implementation of uploading tft file for nextion displays. + * + * Original version (a part of https://github.com/itead/ITEADLIB_Arduino_Nextion) + * @author Chen Zengpeng (email:) + * @date 2016/3/29 + * @copyright + * Copyright (C) 2014-2015 ITEAD Intelligent Systems Co., Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#define DEBUG_SERIAL_ENABLE +#include "ESPNexUpload.h" + +#ifdef DEBUG_SERIAL_ENABLE +#define dbSerialPrint(a) Serial.print(a) +#define dbSerialPrintHex(a) Serial.print(a, HEX) +#define dbSerialPrintln(a) Serial.println(a) +#define dbSerialBegin(a) Serial.begin(a) +#else +#define dbSerialPrint(a) \ + do \ + { \ + } while (0) +#define dbSerialPrintHex(a) \ + do \ + { \ + } while (0) +#define dbSerialPrintln(a) \ + do \ + { \ + } while (0) +#define dbSerialBegin(a) \ + do \ + { \ + } while (0) +#endif + +ESPNexUpload::ESPNexUpload(uint32_t upload_baudrate, int line, int rx, int tx) +{ + _upload_baudrate = upload_baudrate; + _rx = rx; + _tx = tx; + _line = line; + +#if defined ESP8266 + nexSerial = new SoftwareSerial(_rx, _tx); +#else + if (line >= 0) { + nexSerial = new HardwareSerial(line); + // ((HardwareSerial*)nexSerial)->begin(_upload_baudrate, SERIAL_8N1, _rx, _tx); + } else { + nexSerial = new SoftwareSerial(_rx, _tx); + // ((SoftwareSerial*)nexSerial)->begin(_upload_baudrate); + } +#endif + +} + +void ESPNexUpload::nexSerialBegin(uint32_t _speed, int _line, int _rx, int _tx) +{ +#if defined ESP8266 + nexSerial->begin(_speed); +#else + if (_line >= 0) { + ((HardwareSerial*)nexSerial)->begin(_speed, SERIAL_8N1, _rx, _tx); + } else { + ((SoftwareSerial*)nexSerial)->begin(_speed); + } +#endif +} + +bool ESPNexUpload::connect() +{ +#if defined ESP8266 + yield(); +#endif + + dbSerialBegin(115200); + _printInfoLine(F("serial tests & connect")); + + if (_getBaudrate() == 0) + { + statusMessage = F("get baudrate error"); + _printInfoLine(statusMessage); + return false; + } + + _setRunningMode(); + + if (!_echoTest("mystop_yesABC")) + { + statusMessage = F("echo test failed"); + _printInfoLine(statusMessage); + return false; + } + + if (!_handlingSleepAndDim()) + { + statusMessage = F("handling sleep and dim settings failed"); + _printInfoLine(statusMessage); + return false; + } + + if (!_setPrepareForFirmwareUpdate(_upload_baudrate)) + { + statusMessage = F("modifybaudrate error"); + _printInfoLine(statusMessage); + return false; + } + + return true; +} + +bool ESPNexUpload::prepareUpload(uint32_t file_size) +{ + _undownloadByte = file_size; + return this->connect(); +} + +uint16_t ESPNexUpload::_getBaudrate(void) +{ + + _baudrate = 0; + uint32_t baudrate_array[7] = {115200, 19200, 9600, 57600, 38400, 4800, 2400}; + for (uint8_t i = 0; i < 7; i++) + { + if (_searchBaudrate(baudrate_array[i])) + { + _baudrate = baudrate_array[i]; + _printInfoLine(F("baudrate determined")); + break; + } + delay(1500); // wait for 1500 ms + } + return _baudrate; +} + +bool ESPNexUpload::_searchBaudrate(uint32_t baudrate) +{ + +#if defined ESP8266 + yield(); +#endif + + String response = String(""); + _printInfoLine(); + dbSerialPrint(F("init nextion serial interface on baudrate: ")); + dbSerialPrintln(baudrate); + + nexSerialBegin(baudrate, _line, _rx, _tx); + _printInfoLine(F("ESP baudrate established, try to connect to display")); + const char _nextion_FF_FF[3] = {0xFF, 0xFF, 0x00}; + + this->sendCommand("DRAKJHSUYDGBNCJHGJKSHBDN"); + this->sendCommand("", true, true); // 0x00 0xFF 0xFF 0xFF + + this->recvRetString(response); + if (response[0] != 0x1A) + { + _printInfoLine(F("first indication that baudrate is wrong")); + } + else + { + _printInfoLine(F("first respone from display, first indication that baudrate is correct")); + } + + this->sendCommand("connect"); // first connect attempt + + this->recvRetString(response); + if (response.indexOf(F("comok")) == -1) + { + _printInfoLine(F("display doesn't accept the first connect request")); + } + else + { + _printInfoLine(F("display accept the first connect request")); + } + + response = String(""); + delay(110); // based on serial analyser from Nextion editor V0.58 to Nextion display NX4024T032_011R + this->sendCommand(_nextion_FF_FF, false); + + this->sendCommand("connect"); // second attempt + this->recvRetString(response); + if (response.indexOf(F("comok")) == -1 && response[0] != 0x1A) + { + _printInfoLine(F("display doesn't accept the second connect request")); + _printInfoLine(F("conclusion, wrong baudrate")); + return 0; + } + else + { + _printInfoLine(F("display accept the second connect request")); + _printInfoLine(F("conclusion, correct baudrate")); + } + + return 1; +} + +void ESPNexUpload::sendCommand(const char *cmd, bool tail, bool null_head) +{ + +#if defined ESP8266 + yield(); +#endif + + if (null_head) + { + ((HardwareSerial*)nexSerial)->write(0x00); + } + + while (nexSerial->available()) + { + nexSerial->read(); + } + + nexSerial->print(cmd); + if (tail) + { + nexSerial->write(0xFF); + nexSerial->write(0xFF); + nexSerial->write(0xFF); + } + _printSerialData(true, cmd); +} + +uint16_t ESPNexUpload::recvRetString(String &response, uint32_t timeout, bool recv_flag) +{ + +#if defined ESP8266 + yield(); +#endif + + uint16_t ret = 0; + uint8_t c = 0; + uint8_t nr_of_FF_bytes = 0; + long start; + bool exit_flag = false; + bool ff_flag = false; + if (timeout != 500) + _printInfoLine("timeout setting serial read: " + String(timeout)); + + start = millis(); + + while (millis() - start <= timeout) + { + + while (nexSerial->available()) + { + + c = nexSerial->read(); + if (c == 0) + { + continue; + } + + if (c == 0xFF) + nr_of_FF_bytes++; + else + { + nr_of_FF_bytes = 0; + ff_flag = false; + } + + if (nr_of_FF_bytes >= 3) + ff_flag = true; + + response += (char)c; + + if (recv_flag) + { + if (response.indexOf(0x05) != -1) + { + exit_flag = true; + } + } + } + if (exit_flag || ff_flag) + { + break; + } + } + _printSerialData(false, response); + + // if the exit flag and the ff flag are both not found, than there is a timeout + // if(!exit_flag && !ff_flag) + // _printInfoLine(F("recvRetString: timeout")); + + if (ff_flag) + response = response.substring(0, response.length() - 3); // Remove last 3 0xFF + + ret = response.length(); + return ret; +} + +bool ESPNexUpload::_setPrepareForFirmwareUpdate(uint32_t upload_baudrate) +{ + +#if defined ESP8266 + yield(); +#endif + + String response = String(""); + String cmd = String(""); + + cmd = F("00"); + this->sendCommand(cmd.c_str()); + delay(0.1); + + this->recvRetString(response, 800, true); // normal response time is 400ms + + String filesize_str = String(_undownloadByte, 10); + String baudrate_str = String(upload_baudrate); + cmd = "whmi-wri " + filesize_str + "," + baudrate_str + ",0"; + + this->sendCommand(cmd.c_str()); + + // Without flush, the whmi command will NOT transmitted by the ESP in the current baudrate + // because switching to another baudrate (nexSerialBegin command) has an higher prio. + // The ESP will first jump to the new 'upload_baudrate' and than process the serial 'transmit buffer' + // The flush command forced the ESP to wait until the 'transmit buffer' is empty + nexSerial->flush(); + + nexSerialBegin(upload_baudrate, _line, _rx, _tx); + _printInfoLine(F("changing upload baudrate...")); + _printInfoLine(String(upload_baudrate)); + + this->recvRetString(response, 800, true); // normal response time is 400ms + + // The Nextion display will, if it's ready to accept data, send a 0x05 byte. + if (response.indexOf(0x05) != -1) + { + _printInfoLine(F("preparation for firmware update done")); + return 1; + } + else + { + _printInfoLine(F("preparation for firmware update failed")); + return 0; + } +} + +void ESPNexUpload::setUpdateProgressCallback(THandlerFunction value) +{ + _updateProgressCallback = value; +} + +bool ESPNexUpload::upload(const uint8_t *file_buf, size_t buf_size) +{ + +#if defined ESP8266 + yield(); +#endif + + uint8_t c; + uint8_t timeout = 0; + String string = String(""); + + for (uint16_t i = 0; i < buf_size; i++) + { + + // Users must split the .tft file contents into 4096 byte sized packets with the final partial packet size equal to the last remaining bytes (<4096 bytes). + if (_sent_packets == 4096) + { + + // wait for the Nextion to return its 0x05 byte confirming reception and readiness to receive the next packets + this->recvRetString(string, 500, true); + if (string.indexOf(0x05) != -1) + { + + // reset sent packets counter + _sent_packets = 0; + + // reset receive String + string = ""; + } + else + { + if (timeout >= 8) + { + statusMessage = F("serial connection lost"); + _printInfoLine(statusMessage); + return false; + } + + timeout++; + } + + // delay current byte + i--; + } + else + { + + // read buffer + c = file_buf[i]; + + // write byte to nextion over serial + nexSerial->write(c); + + // update sent packets counter + _sent_packets++; + } + } + + return true; +} + +bool ESPNexUpload::upload(Stream &myFile) +{ +#if defined ESP8266 + yield(); +#endif + + // create buffer for read + uint8_t buff[2048] = {0}; + + // read all data from server + while (_undownloadByte > 0 || _undownloadByte == -1) + { + + // get available data size + size_t size = myFile.available(); + + if (size) + { + // read up to 2048 byte into the buffer + int c = myFile.readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); + + // Write the buffered bytes to the nextion. If this fails, return false. + if (!this->upload(buff, c)) + { + return false; + } + else + { + if (_updateProgressCallback) + { + _updateProgressCallback(); + } + } + + if (_undownloadByte > 0) + { + _undownloadByte -= c; + } + } + delay(1); + } + + return true; +} + +void ESPNexUpload::softReset(void) +{ + // soft reset nextion device + this->sendCommand("rest"); +} + +void ESPNexUpload::end() +{ + + // wait for the nextion to finish internal processes + delay(1600); + + // soft reset the nextion + this->softReset(); + + // end Serial connection + ((HardwareSerial*)nexSerial)->end(); + + // reset sent packets counter + _sent_packets = 0; + + statusMessage = F("upload ok"); + _printInfoLine(statusMessage + F("\r\n")); +} + +void ESPNexUpload::_setRunningMode(void) +{ + String cmd = String(""); + delay(100); + cmd = F("runmod=2"); + this->sendCommand(cmd.c_str()); + delay(60); +} + +bool ESPNexUpload::_echoTest(String input) +{ + String cmd = String(""); + String response = String(""); + + cmd = "print \"" + input + "\""; + this->sendCommand(cmd.c_str()); + + uint32_t duration_ms = calculateTransmissionTimeMs(cmd) * 2 + 10; // times 2 (send + receive) and 10 ms extra + this->recvRetString(response, duration_ms); + + return (response.indexOf(input) != -1); +} + +bool ESPNexUpload::_handlingSleepAndDim(void) +{ + String cmd = String(""); + String response = String(""); + bool set_sleep = false; + bool set_dim = false; + + cmd = F("get sleep"); + this->sendCommand(cmd.c_str()); + + this->recvRetString(response); + + if (response[0] != 0x71) + { + statusMessage = F("unknown response from 'get sleep' request"); + _printInfoLine(statusMessage); + return false; + } + + if (response[1] != 0x00) + { + _printInfoLine(F("sleep enabled")); + set_sleep = true; + } + else + { + _printInfoLine(F("sleep disabled")); + } + + response = String(""); + cmd = F("get dim"); + this->sendCommand(cmd.c_str()); + + this->recvRetString(response); + + if (response[0] != 0x71) + { + statusMessage = F("unknown response from 'get dim' request"); + _printInfoLine(statusMessage); + return false; + } + + if (response[1] == 0x00) + { + _printInfoLine(F("dim is 0%, backlight from display is turned off")); + set_dim = true; + } + else + { + _printInfoLine(); + dbSerialPrint(F("dim ")); + dbSerialPrint((uint8_t)response[1]); + dbSerialPrintln(F("%")); + } + + if (!_echoTest("ABC")) + { + statusMessage = F("echo test in 'handling sleep and dim' failed"); + _printInfoLine(statusMessage); + return false; + } + + if (set_sleep) + { + cmd = F("sleep=0"); + this->sendCommand(cmd.c_str()); + // Unfortunately the display doesn't send any respone on the wake up request (sleep=0) + // Let the ESP wait for one second, this is based on serial analyser from Nextion editor V0.58 to Nextion display NX4024T032_011R + // This gives the Nextion display some time to wake up + delay(1000); + } + + if (set_dim) + { + cmd = F("dim=100"); + this->sendCommand(cmd.c_str()); + delay(15); + } + + return true; +} + +void ESPNexUpload::_printSerialData(bool esp_request, String input) +{ + + char c; + if (esp_request) + dbSerialPrint(F("ESP request: ")); + else + dbSerialPrint(F("Nextion respone: ")); + + if (input.length() == 0) + { + dbSerialPrintln(F("none")); + return; + } + + for (int i = 0; i < input.length(); i++) + { + + c = input[i]; + if ((uint8_t)c >= 0x20 && (uint8_t)c <= 0x7E) + dbSerialPrint(c); + else + { + dbSerialPrint(F("0x")); + dbSerialPrintHex(c); + dbSerialPrint(F(" ")); + } + } + dbSerialPrintln(); +} + +uint32_t ESPNexUpload::calculateTransmissionTimeMs(String message) +{ + // In general, 1 second (s) = 1000 (10^-3) millisecond (ms) or + // 1 second (s) = 1000 000 (10^-6) microsecond (us). + // To calculate how much microsecond one BIT of data takes with a certain baudrate you have to divide + // the baudrate by one second. + // For example 9600 baud = 1000 000 us / 9600 ≈ 104 us + // The time to transmit one DATA byte (if we use default UART modulation) takes 10 bits. + // 8 DATA bits and one START and one STOP bit makes 10 bits. + // In this example (9600 baud) a byte will take 1041 us to send or receive. + // Multiply this value by the length of the message (number of bytes) and the total transmit/ receive time + // is calculated. + + uint32_t duration_one_byte_us = 10000000 / _baudrate; // 1000 000 * 10 bits / baudrate + uint16_t nr_of_bytes = message.length() + 3; // 3 times 0xFF byte + uint32_t duration_message_us = nr_of_bytes * duration_one_byte_us; + uint32_t return_value_ms = duration_message_us / 1000; + + _printInfoLine("calculated transmission time: " + String(return_value_ms) + " ms"); + return return_value_ms; +} + +void ESPNexUpload::_printInfoLine(String line) +{ + dbSerialPrint(F("Status info: ")); + if (line.length() != 0) + dbSerialPrintln(line); +} \ No newline at end of file diff --git a/src/modules/display/Nextion/ESPNexUpload.h b/src/modules/display/Nextion/ESPNexUpload.h new file mode 100644 index 00000000..3a22ac15 --- /dev/null +++ b/src/modules/display/Nextion/ESPNexUpload.h @@ -0,0 +1,288 @@ +/** + * @file NexUpload.h + * The definition of class NexUpload. + * + * + * 1 - BugFix when display baudrate is diffrent from initial ESP baudrate + * 2 - Improved debug information + * 3 - Make delay commands dependent on the baudrate + * @author Machiel Mastenbroek (machiel.mastenbroek@gmail.com) + * @date 2019/11/04 + * @version 0.5.5 + * + * Stability improvement, Nextion display doesn’t freeze after the seconds 4096 trance of firmware bytes. + * Now the firmware upload process is stabled without the need of a hard Display power off-on intervention. + * Undocumented features (not mentioned in nextion-hmi-upload-protocol-v1-1 specification) are added. + * This implementation is based in on a reverse engineering with a UART logic analyser between + * the Nextion editor v0.58 and a NX4024T032_011R Display. + * + * @author Machiel Mastenbroek (machiel.mastenbroek@gmail.com) + * @date 2019/10/24 + * @version 0.5.0 + * + * Modified to work with ESP32, HardwareSerial and removed SPIFFS dependency + * @author Onno Dirkzwager (onno.dirkzwager@gmail.com) + * @date 2018/12/26 + * @version 0.3.0 + * + * Modified to work with ESP8266 and SoftwareSerial + * @author Ville Vilpas (psoden@gmail.com) + * @date 2018/2/3 + * @version 0.2.0 + * + * Original version (a part of https://github.com/itead/ITEADLIB_Arduino_Nextion) + * @author Chen Zengpeng (email:) + * @date 2016/3/29 + * @copyright + * Copyright (C) 2014-2015 ITEAD Intelligent Systems Co., Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef __ESPNEXUPLOAD_H__ +#define __ESPNEXUPLOAD_H__ +#include +#include +#include + +#ifdef ESP8266 +#include +#else +#include +#include +#endif + +/** + * @addtogroup CoreAPI + * @{ + */ + +// callback template definition +typedef std::function THandlerFunction; + +/** + * + * Provides the API for nextion to upload the ftf file. + */ +class ESPNexUpload +{ +public: /* methods */ + // callback template definition + typedef std::function THandlerFunction; + + /** + * Constructor. + * + * @param uint32_t upload_baudrate - set upload baudrate. + */ + ESPNexUpload(uint32_t upload_baudrate, int line, int rx, int tx); + + /** + * destructor. + * + */ + ~ESPNexUpload() {} + + /** + * Connect to Nextion over serial + * + * @return true or false. + */ + bool connect(); + + /** + * prepare upload. Set file size & Connect to Nextion over serial + * + * @return true if success, false for failure. + */ + bool prepareUpload(uint32_t file_size); + + /** + * set Update Progress Callback. (What to do during update progress) + * + * @return none + */ + void setUpdateProgressCallback(THandlerFunction value); + + /** + * start update tft file to nextion. + * + * @param const uint8_t *file_buf + * @param size_t buf_size + * @return true if success, false for failure. + */ + bool upload(const uint8_t *file_buf, size_t buf_size); + + /** + * start update tft file to nextion. + * + * @param Stream &myFile + * @return true if success, false for failure. + */ + bool upload(Stream &myFile); + + /** + * Send reset command to Nextion over serial + * + * @return none. + */ + void softReset(void); + + /** + * Send reset, end serial, reset _sent_packets & update status message + * + * @return none. + */ + void end(void); + +public: /* data */ + String statusMessage = ""; + +private: /* methods */ + /* + * get communicate baudrate. + * + * @return communicate baudrate. + * + */ + uint16_t _getBaudrate(void); + + /* + * search communicate baudrate. + * + * @param baudrate - communicate baudrate. + * + * @return true if success, false for failure. + */ + bool _searchBaudrate(uint32_t baudrate); + + /* + * set download baudrate. + * + * @param baudrate - set download baudrate. + * + * @return true if success, false for failure. + */ + bool _setPrepareForFirmwareUpdate(uint32_t upload_baudrate); + + /* + * set Nextion running mode. + * + * Undocumented feature of the Nextion protocol. + * It's used by the 'upload to Nextion device' feature of the Nextion Editor V0.58 + * + * The nextion display doesn't send any response + * + */ + void _setRunningMode(void); + + /* + * Test UART nextion connection availability + * + * @param input - echo string, + * + * @return true when the 'echo string' that is send is equal to the received string + * + * This test is used by the 'upload to Nextion device' feature of the Nextion Editor V0.58 + * + */ + bool _echoTest(String input); + + /* + * This function get the sleep and dim value from the Nextion display. + * + * If sleep = 1 meaning: sleep is enabled + * action : sleep will be disabled + * If dim = 0, meaning: the display backlight is turned off + * action : dim will be set to 100 (percent) + * + */ + bool _handlingSleepAndDim(void); + + /* + * This function (debug) print the Nextion response to a human readable string + * + * @param esp_request - true: request message from esp to nextion + * false: response message from nextion to esp + * + * @param input - string to print + * + */ + void _printSerialData(bool esp_request, String input); + + /* + * This function print a prefix debug line + * + * @param line: optional debug/ info line + */ + void _printInfoLine(String line = ""); + + /* + * Send command to Nextion. + * + * @param cmd - the string of command. + * @param tail - end the string with tripple 0xFF byte + * @param null_head - start the string with a single 0x00 byte + * + * @return none. + */ + void sendCommand(const char *cmd, bool tail = true, bool null_head = false); + + /* + * Receive string data. + * + * @param buffer - save string data. + * @param timeout - set timeout time. + * @param recv_flag - if recv_flag is true,will braak when receive 0x05. + * + * @return the length of string buffer. + * + */ + uint16_t recvRetString(String &string, uint32_t timeout = 500, bool recv_flag = false); + + /* + * + * This function calculates the transmission time, the transmission time + * is based on the length of the message and the baudrate. + * + * @param message - only used to determine the length of the message + * + * @return time in us length of string buffer. + * + */ + uint32_t calculateTransmissionTimeMs(String message); + + void nexSerialBegin(uint32_t upload_baudrate, int line, int rx, int tx); + +private: /* data */ + uint32_t _baudrate; /* nextion serail baudrate */ + uint32_t _undownloadByte; /* undownload byte of tft file */ + uint32_t _upload_baudrate; /* upload baudrate */ + uint16_t _sent_packets = 0; /* upload baudrate */ + uint8_t _rx; + uint8_t _tx; + uint8_t _line; + THandlerFunction _updateProgressCallback; + +#ifdef ESP8266 + SoftwareSerial* nexSerial; +#else + Stream* nexSerial; +#endif +}; +/** + * @} + */ + +#endif /* #ifndef __ESPNEXUPLOAD_H__ */ \ No newline at end of file diff --git a/src/modules/display/Nextion/Nextion.cpp b/src/modules/display/Nextion/Nextion.cpp new file mode 100644 index 00000000..4fe3af4f --- /dev/null +++ b/src/modules/display/Nextion/Nextion.cpp @@ -0,0 +1,312 @@ + +#define DEBUG_SERIAL_ENABLE +#include "Global.h" +#include "classes/IoTUart.h" +#include "ESPNexUpload.h" +bool updated = false; + +class Nextion : public IoTUart +{ +private: + String _url; + String _host; + int _tx, _rx, _speed, _line; + bool _UpTelegram; + char _inc; + String _inStr = ""; // буфер приема строк в режимах 0, 1, 2 + + // Выводим русские буквы на экран Nextion (преобразуем в кодировку ISO-8859-5) + String convertRUS(String text) + { + const char *in = text.c_str(); + String out; + if (in == NULL) + return out; + + uint32_t codepoint = 0; + while (*in != 0) + { + uint8_t ch = (uint8_t)(*in); + if (ch <= 0x7f) + codepoint = ch; + else if (ch <= 0xbf) + codepoint = (codepoint << 6) | (ch & 0x3f); + else if (ch <= 0xdf) + codepoint = ch & 0x1f; + else if (ch <= 0xef) + codepoint = ch & 0x0f; + else + codepoint = ch & 0x07; + ++in; + if (((*in & 0xc0) != 0x80) && (codepoint <= 0x10ffff)) + { + if (codepoint <= 255) + { + out += (char)codepoint; + } + else + { + if (codepoint > 0x400) + out += (char)(codepoint - 0x360); + } + } + } + return out; + } + +public: + Nextion(String parameters) : IoTUart(parameters) + { + _url = jsonReadStr(parameters, "url"); + _url = "/" + _url; + _host = jsonReadStr(parameters, "host"); + jsonRead(parameters, "rx", _rx); + jsonRead(parameters, "tx", _tx); + jsonRead(parameters, "speed", _speed); + jsonRead(parameters, "line", _line); + jsonRead(parameters, "uploadTelegram", _UpTelegram); + } + + IoTValue execute(String command, std::vector ¶m) + { + + if (command == "Update") + { + updateServer(); + } + else if (command == "printFFF") + { + if (param.size() == 2) + //UART.printFFF("auto.val=1",0) + { + String strToUart = ""; + strToUart = param[0].valS; + + if (param[1].valD) + uartPrintFFF("\"" + strToUart + "\""); + else + uartPrintFFF(strToUart); + } + // отправка данных на Nextion - UART.printFFF("t1.txt=", ID_vidget + " °", 1); + // или UART.printRusFFF("buttons.bt0.txt="," Гостинная",1); + if (param.size() == 3) + { + String strToUart = ""; + strToUart = param[0].valS; + + if (param[2].valD) + uartPrintFFF(strToUart + "\"" + param[1].valS + "\""); + else + uartPrintFFF(strToUart + param[1].valS); + } + } + // отправка кирилических символов на Nextion (русские буквы) + else if (command == "printRusFFF") + { + if (param.size() == 2) + { + String strToUart = ""; + strToUart = param[0].valS; + + if (param[1].valD) + uartPrintFFF(convertRUS("\"" + strToUart + "\"")); + else + uartPrintFFF(convertRUS(strToUart)); + } + if (param.size() == 3) + { + String strToUart = ""; + strToUart = param[0].valS; + + if (param[2].valD) + uartPrintFFF(convertRUS(strToUart + "\"" + param[1].valS + "\"")); + else + uartPrintFFF(convertRUS(strToUart + param[1].valS)); + } + }// else { // не забываем, что переопределяем execute и нужно проверить что в базовом классе проверяется + // return IoTUart::execute(command, param); + // } + return {}; + } + + void onModuleOrder(String &key, String &value) { + if (key == "uploadServer") { + updateServer(); + } + } + + void uartPrintFFF(const String& msg) { + if (_myUART) { + SerialPrint("I", F("Nextion"), "uartPrintFFF -> "+msg+" +FFFFFF"); + _myUART->print(msg); + _myUART->write(0xff); + _myUART->write(0xff); + _myUART->write(0xff); + } + } +//---------------------NEXTION-UART---START------------------------ + void uartHandle() { + if (!_myUART) return; + if (_myUART->available()) { + _inc = _myUART->read(); + if (_inc == 0xFF) { + _inc = _myUART->read(); + _inc = _myUART->read(); + _inStr = ""; + return; + } + + if (_inc == '\r') return; + + if (_inc == '\n') { + if (_inStr.indexOf("=") == -1) { // если входящее сообщение не по формату, то работаем как в режиме 0 + setValue(_inStr); + return; + } + String id = selectToMarker(_inStr, "="); + String valStr = selectToMarkerLast(_inStr, "="); + valStr.replace("\"", ""); + id.replace(".val", "_val"); + id.replace(".txt", "_txt"); + generateOrder(id, valStr); + _inStr = ""; + } else _inStr += _inc; + } + } + + void onRegEvent(IoTItem* eventItem) { + if (!_myUART || !eventItem) return; + int indexOf_; + String printStr = ""; + + printStr += eventItem->getID(); + indexOf_ = printStr.indexOf("_"); + if (indexOf_ == -1) return; // пропускаем событие, если нет используемого признака типа данных - _txt или _vol + + if (printStr.indexOf("_txt") > 0) { + printStr.replace("_txt", ".txt=\""); + printStr += eventItem->getValue(); + printStr += "\""; + } else if (printStr.indexOf("_val") > 0) { + printStr += eventItem->getValue(); + printStr.replace(".", ""); + printStr.replace("_val", ".val="); + } else { + if (indexOf_ == printStr.length()-1) printStr.replace("_", ""); + else printStr.replace("_", "."); + printStr += "="; + printStr += eventItem->getValue(); + } + + uartPrintFFF(convertRUS(printStr)); + } + +//---------------------NEXTION-UART---END------------------------ + +//---------------------NEXTION-UPDATE---START------------------------ + void updateServer() + { + SerialPrint("I", F("NextionUpdate"), "Update .... "); + + if (!updated) + { + SerialPrint("I", F("NextionUpdate"), "connecting to " + (String)_host); + HTTPClient http; +#if defined ESP8266 + if (!http.begin(_host, 80, _url)) + SerialPrint("I", F("NextionUpdate"), "connection failed "); +#elif defined ESP32 + if (!http.begin(String("http://") + _host + _url)) + SerialPrint("I", F("NextionUpdate"), "connection failed "); +#endif + + SerialPrint("I", F("NextionUpdate"), "Requesting file: " + (String)_url); + int code = http.GET(); + // Update the nextion display + if (code == 200) + flashNextion(http); + else + SerialPrint("I", F("NextionUpdate"), "HTTP error: " + (String)http.errorToString(code).c_str()); + + http.end(); + SerialPrint("I", F("NextionUpdate"), "Closing connection "); + } + } + + void uploadNextionTlgrm(String &url) + { + if (!_UpTelegram) + return; + if (!updated) + { + SerialPrint("I", F("NextionUpdate"), "connecting to " + url); + + HTTPClient http; + +#ifdef ESP8266 + SerialPrint("I", F("NextionUpdate"), "Update impossible esp8266: Change boards to esp32 :)"); + return; +#else + if (!http.begin(url)) // пингуем файл + SerialPrint("I", F("NextionUpdate"), "connection failed "); +#endif + SerialPrint("I", F("NextionUpdate"), "Requesting file: OK" ); + int code = http.GET(); + // Update the nextion display + if (code == 200) + flashNextion(http); + else + SerialPrint("I", F("NextionUpdate"), "HTTP error: " + (String)http.errorToString(code).c_str()); + + http.end(); + SerialPrint("I", F("NextionUpdate"), "Closing connection "); + } + } + + void flashNextion(HTTPClient &http) + { + int contentLength = http.getSize(); + SerialPrint("I", F("NextionUpdate"), "File received. Update Nextion... "); + bool result; + ESPNexUpload nexUp(_speed, _line, _rx, _tx); + nexUp.setUpdateProgressCallback([]() + { SerialPrint("I", F("NextionUpdate"), "... "); }); + + result = nexUp.prepareUpload(contentLength); + if (!result) + { + SerialPrint("I", F("NextionUpdate"), "Error: " + (String)nexUp.statusMessage); + } + else + { + SerialPrint("I", F("NextionUpdate"), "Start upload. File size is: " + (String)contentLength); + result = nexUp.upload(*http.getStreamPtr()); + if (result) + { + updated = true; + SerialPrint("I", F("NextionUpdate"), "Succesfully updated Nextion! "); + } + else + { + SerialPrint("I", F("NextionUpdate"), "Error updating Nextion: " + (String)nexUp.statusMessage); + } + nexUp.end(); + } + } +//---------------------NEXTION-UPDATE---END------------------------ + + ~Nextion(){}; + +}; + +void *getAPI_Nextion(String subtype, String param) +{ + if (subtype == F("Nextion")) + { + return new Nextion(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/display/Nextion/modinfo.json b/src/modules/display/Nextion/modinfo.json new file mode 100644 index 00000000..d74991c1 --- /dev/null +++ b/src/modules/display/Nextion/modinfo.json @@ -0,0 +1,98 @@ +{ + "menuSection": "screens", + "configItem": [ + { + "global": 0, + "name": "Nextion", + "type": "Reading", + "subtype": "Nextion", + "id": "nex", + "widget": "", + "page": "", + "descr": "", + "host": "192.168.1.10:5500", + "url": "nextion.tft", + "btn-uploadServer": "", + "tx": 17, + "rx": 16, + "line": 2, + "speed": 9600, + "uploadTelegram": 1 + } + ], + "about": { + "authorName": "Bubnov Mikhail", + "authorContact": "https://t.me/Mit4bmw", + "authorGit": "https://github.com/Mit4el", + "specialThanks": "", + "moduleName": "Nextion", + "moduleVersion": "2.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Nextion", + "moduleDesc": "загрузка прошивки в дисплей Nextion. Команда для запуска обновления дисплея: Nextion.Update(); ", + "propInfo": { + "tx": "TX пин", + "rx": "RX пин", + "speed": "Скорость UART", + "line": "Актуально только для ESP32: номер линии hardUART. =2 rx=16 tx=17, для SoftwarwSerial в ESP32 line = -1", + "host": "Сервер обновления. Можно использовать LiveServer из VisualCode, указывать ip адрес", + "url": "файл прошивки экрана, указывать с расширением, например nextion.tft или iotm/test.tft", + "uploadTelegram": "1 - разрешает прошивать экран через модуль Telegram_v2", + "btn-uploadServer": "Кнопка загрузки прошивки с сервера LiveServer или другого по ip" + }, + "funcInfo": [ + { + "name": "Update", + "descr": "Функция сценария для загрузки прошивки с сервера LiveServer или другого по ip", + "params": [] + }, + { + "name": "printFFF", + "descr": "Отправить в UART текстовую строку и hex метку 3 байта 0xFF0xFF0xFF. Напимер nex.printFFF(\"auto.val=1\",0)", + "params": [ + "Строка текста", + "1 - обернуть строку в кавычки, 0 - отправить без кавычек" + ] + }, + { + "name": "printFFF", + "descr": "Отправить в UART текстовую строку и hex метку 3 байта 0xFF0xFF0xFF. Напимер nex.printFFF(\"t1.txt=\", ID_vidget + \" °\", 1);", + "params": [ + "Строка текста", + "ID Виджета или любое значение", + "1 - обернуть строку в кавычки, 0 - отправить без кавычек" + ] + }, + { + "name": "printRusFFF", + "descr": "Отправить в UART текстовую строку и hex метку 3 байта 0xFF0xFF0xFF. С предварительной конвертацией русских букв (преобразуем в кодировку ISO-8859-5)", + "params": [ + "Строка текста", + "1 - обернуть строку в кавычки, 0 - отправить без кавычек" + ] + }, + { + "name": "printRusFFF", + "descr": "Отправить в UART текстовую строку и hex метку 3 байта 0xFF0xFF0xFF. С предварительной конвертацией русских букв (преобразуем в кодировку ISO-8859-5)", + "params": [ + "Строка текста", + "ID Виджета или любое значение", + "1 - обернуть строку в кавычки, 0 - отправить без кавычек" + ] + } + ] + }, + "defActive": false, + "usedLibs": { + "esp32_4mb": [], + "esp32_4mb3f": [], + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [], + "esp8285_1mb": [], + "esp8285_1mb_ota": [] + } +} \ No newline at end of file