@grovkillen, Thanks for the recommendation. When this project winds down I'll check out PlatformIO.
@everyone, Below is the revised Nextion Plugin, Version 3. Please try it out and share any issues or improvements.
Some comments:
The code still has the blocking delays in it. But they are dynamic and can range from ~10mS for 9600 Baud to as little as 1mS for 115.2K.
My attempt to eliminate the delays became an E-ticket adventure with an unhappy ending. It's the reason I held off until today to post the new beta release.
Here's the story:
The delay code was eliminated and replaced with a local command processor that parsed the Nextion sentences as a "background" task. Rather than wait ~10mS (and block all other timed events), the new code exits the plugin if the Nextion command sentence is incomplete. It picks up where it left off using the common 10Hz update schedule, so overall it is as fast as the old way but without the blocking.
But it can't work in all applications because while the ESP command processor is collecting bytes from a Nextion packet sentence, another rule or timer event can send a command to the Nextion. That's not a problem on its own, but the Nextion will respond by sending its status result code (typically 0x01). Unfortunately this status code is received by the Nextion Plugin's command processor, which corrupts the pending packet sentence.
The workaround is to ignore the status codes (bytes < 0x09). But then Nextion touch events can't be supported (because they are binary and often have values <0x09). The final solution was to revert back to the delay code. I'm not willing to give up yet. Anyone have any ideas?
- Thomas
Code: Select all
//#######################################################################################################
//#######################################################################################################
//################################### Plugin 075: Nextion <info@sensorio.cz> ###########################
//################################### Created on the work of majklovec ###########################
//################################### Revisions by BertB and ThomasB ###########################
//################################### Last Revision: July-13-2018 (TB) ###########################
//#######################################################################################################
#ifdef USES_P075
#include <ESPeasySoftwareSerial.h>
// *****************************************************************************************************
// Defines start here
// *****************************************************************************************************
// Plug-In defines
#define PLUGIN_075
#define PLUGIN_ID_075 75
#define PLUGIN_NAME_075 "Display - Nextion [TESTING_V3]"
#define PLUGIN_VALUENAME1_075 "idx"
#define PLUGIN_VALUENAME2_075 "value"
#define Nlines 12 // Qty of "optional" user entered Command-Text strings, 64 chars max per line.
// Nextion defines
#define RXBUFFSZ 80 // Local Serial RxD buffer.
#define TOUCH_BASE 500 // Base offset for 0X65 Touch Event.
// Serial defines
#define B9600 0
#define B38400 1
#define B57600 2
#define B115200 3
#define DEFAULT_BAUD B9600
// Global vars
ESPeasySoftwareSerial *SoftSerial = NULL;
int rxPin = -1;
int txPin = -1;
// *****************************************************************************************************
// PlugIn starts here
// *****************************************************************************************************
boolean Plugin_075(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
static boolean HwSerial = false;
static boolean AdvHwSerial = false;
uint32_t AdvHwBaud = 9600UL;
switch (function) {
case PLUGIN_DEVICE_ADD: {
Device[++deviceCount].Number = PLUGIN_ID_075;
Device[deviceCount].Type = DEVICE_TYPE_DUAL;
Device[deviceCount].VType = SENSOR_TYPE_DUAL;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = true;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = false;
Device[deviceCount].ValueCount = 2;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME: {
string = F(PLUGIN_NAME_075);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES: {
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0],PSTR(PLUGIN_VALUENAME1_075));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1],PSTR(PLUGIN_VALUENAME2_075));
break;
}
case PLUGIN_GET_DEVICEGPIONAMES: {
AdvHwSerial = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
rxPin = Settings.TaskDevicePin1[event->TaskIndex];
txPin = Settings.TaskDevicePin2[event->TaskIndex];
event->String1 = F("GPIO SS RX ← ");
event->String2 = F("GPIO SS TX → ");
if(AdvHwSerial == true) {
if ((rxPin == 3 && txPin == 1) || (rxPin == 13 && txPin == 15)) {
event->String1 = F("GPIO HW RX ← ");
event->String2 = F("GPIO HW TX → ");
}
}
break;
}
case PLUGIN_WEBFORM_LOAD: {
rxPin = Settings.TaskDevicePin1[event->TaskIndex];
txPin = Settings.TaskDevicePin2[event->TaskIndex];
if (!((rxPin == 3 && txPin == 1) || (rxPin == 13 && txPin == 15))) { // Hardware Serial Compatible?
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = false; // Not HW serial compatible, Reset Check Box.
}
if (rxPin == 3 && txPin == 1) { // USB Port?
if(Settings.TaskDevicePluginConfig[event->TaskIndex][0]==false && // Hardware serial disabled.
Settings.TaskDeviceEnabled[event->TaskIndex] == true) { // Plugin is enabled.
Settings.TaskDevicePluginConfig[event->TaskIndex][0]=true; // USB port access uses HW serial, Force set Check Box.
}
}
if (Settings.TaskDevicePluginConfig[event->TaskIndex][0] == false) { // Softserial mode.
Settings.TaskDevicePluginConfig[event->TaskIndex][1] = B9600; // Reset to 9600 baud.
}
addFormSeparator(2);
addFormSubHeader(F("Enhanced Serial Communication"));
addFormCheckBox(F("Use Hardware Serial"), F("AdvHwSerial"), Settings.TaskDevicePluginConfig[event->TaskIndex][0]);
byte choice = Settings.TaskDevicePluginConfig[event->TaskIndex][1];
String options[4];
options[0] = F("9600");
options[1] = F("38400");
options[2] = F("57600");
options[3] = F("115200");
addFormSelector(F("Baud Rate"), F("plugin_075_baud"), 4, options, NULL, choice);
addFormNote(F("Un-check box for Soft Serial communication (low performance mode, 9600 Baud)."));
addFormNote(F("Hardware Serial is available when the GPIO pins are RX=D7 and TX=D8."));
addFormNote(F("D8 (GPIO-15) requires a Buffer Circuit (PNP transistor) or ESP boot may fail."));
addFormNote(F("Do <b>NOT</b> enable the Serial Log file on Tools->Advanced->Serial Port."));
// ** DEVELOPER DEBUG MESSAGE AREA **
// addFormNote(ExtraTaskSettings.TaskDeviceName); // Debug value.
// int datax = (int)(Settings.TaskDeviceEnabled[event->TaskIndex]); // Debug value.
// String Data = "Debug. Plugin Enable State: ";
// Data += String(datax);
// addFormNote(Data);
addFormSubHeader(F("")); // Blank line, vertical space.
addFormHeader(F("Nextion Command-Text Strings (Optional)"));
char deviceTemplate[Nlines][64];
LoadCustomTaskSettings(event->TaskIndex, (byte*)&deviceTemplate, sizeof(deviceTemplate));
for (byte varNr = 0; varNr < Nlines; varNr++) {
addFormTextBox(String(F("Line ")) + (varNr + 1), String(F("Plugin_075_template")) + (varNr + 1), deviceTemplate[varNr], 64);
}
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE: {
String argName;
char deviceTemplate[Nlines][64];
for (byte varNr = 0; varNr < Nlines; varNr++)
{
String arg = F("Plugin_075_template");
arg += varNr + 1;
String tmpString = WebServer.arg(arg);
strncpy(deviceTemplate[varNr], tmpString.c_str(), sizeof(deviceTemplate[varNr])-1);
deviceTemplate[varNr][63]=0;
}
if(ExtraTaskSettings.TaskDeviceName[0]==0) { // User forgot to enter device name!
strcpy(ExtraTaskSettings.TaskDeviceName,"NEXTION"); // Give standard name.
}
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = isFormItemChecked(F("AdvHwSerial"));
Settings.TaskDevicePluginConfig[event->TaskIndex][1] = getFormItemInt(F("plugin_075_baud"));
SaveCustomTaskSettings(event->TaskIndex, (byte*)&deviceTemplate, sizeof(deviceTemplate));
success = true;
break;
}
case PLUGIN_INIT: {
AdvHwSerial = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
uint8_t BaudCode = Settings.TaskDevicePluginConfig[event->TaskIndex][1];
if(BaudCode > B115200) BaudCode = B9600;
const uint32_t BaudArray[4] = {9600UL, 38400UL, 57600UL, 115200UL};
AdvHwBaud = BaudArray[BaudCode];
if (Settings.TaskDevicePin1[event->TaskIndex] != -1) {
rxPin = Settings.TaskDevicePin1[event->TaskIndex];
}
if (Settings.TaskDevicePin2[event->TaskIndex] != -1) {
txPin = Settings.TaskDevicePin2[event->TaskIndex];
}
if (SoftSerial != NULL) {
delete SoftSerial;
SoftSerial = NULL;
}
String log = F("NEXTION075 : serial pin config RX:");
log += rxPin;
log += F(", TX:");
log += txPin;
if(Settings.TaskDeviceEnabled[event->TaskIndex]==true)
log += F(", Plugin Enabled"); // Plugin is enabled.
else log += F(", Plugin Disabled");
addLog(LOG_LEVEL_INFO, log);
if(Settings.TaskDeviceEnabled[event->TaskIndex] == true) { // Plugin is enabled.
// Hardware serial is RX on 13 and TX on 15 (swapped hw serial)
if (AdvHwSerial && rxPin == 13 && txPin == 15) {
log = F("NEXTION075 : Using swap hardware serial");
addLog(LOG_LEVEL_INFO, log);
HwSerial = true;
Settings.UseSerial = false; // Disable global Serial port.
Settings.SerialLogLevel = 0; // Disable logging on serial port.
Settings.BaudRate = AdvHwBaud; // Set BaudRate for Nextion.
Serial.flush();
Serial.begin(AdvHwBaud);
Serial.swap();
}
// Hardware serial is RX on 3 and TX on 1. USB serial for Nextion IDE (User MCU Input function).
else if(AdvHwSerial && rxPin == 3 && txPin == 1) {
log = F("NEXTION075 : Using USB hardware serial");
addLog(LOG_LEVEL_INFO, log);
HwSerial = true;
Settings.UseSerial = false; // Disable global Serial port.
Settings.SerialLogLevel = 0; // Disable logging on serial port.
Settings.BaudRate = AdvHwBaud; // Set BaudRate for Nextion.
Serial.flush();
Serial.begin(AdvHwBaud);
}
else {
log = F("NEXTION075 : Using software serial");
addLog(LOG_LEVEL_INFO, log);
HwSerial = false;
if (SoftSerial == NULL) {
SoftSerial = new ESPeasySoftwareSerial(rxPin, txPin);
}
SoftSerial->begin(9600);
SoftSerial->flush();
}
}
else {
}
success = true;
break;
}
case PLUGIN_READ: { // Get Plugin's optional command-text strings. Special RSSIBAR bargraph keyword is supported.
char deviceTemplate[Nlines][64];
int RssiIndex;
String newString;
String tmpString;
String UcTmpString;
LoadCustomTaskSettings(event->TaskIndex, (byte*)&deviceTemplate, sizeof(deviceTemplate));
for (byte x = 0; x < Nlines; x++) {
tmpString = deviceTemplate[x];
if (tmpString.length()) {
UcTmpString = deviceTemplate[x];
UcTmpString.toUpperCase();
RssiIndex = UcTmpString.indexOf(F("RSSIBAR")); // RSSI bargraph Keyword found, wifi value in dBm.
if(RssiIndex >= 0) {
int barVal;
newString = tmpString.substring(0, RssiIndex);
int nbars = WiFi.RSSI();
if (nbars < -100 || nbars >= 0)
barVal=0;
else if (nbars >= -100 && nbars < -95)
barVal=5;
else if (nbars >= -95 && nbars < -90)
barVal=10;
else if (nbars >= -90 && nbars < -85)
barVal=20;
else if (nbars >= -85 && nbars < -80)
barVal=30;
else if (nbars >= -80 && nbars < -75)
barVal=45;
else if (nbars >= -75 && nbars < -70)
barVal=60;
else if (nbars >= -70 && nbars < -65)
barVal=70;
else if (nbars >= -65 && nbars < -55)
barVal=80;
else if (nbars >= -55 && nbars < -50)
barVal=90;
else if (nbars >= -50)
barVal=100;
newString += String(barVal,DEC);
}
else {
newString = parseTemplate(tmpString, 0);
}
sendCommand(newString.c_str(), HwSerial);
}
}
success = true;
break;
}
case PLUGIN_WRITE: {
String tmpString = string;
int argIndex = tmpString.indexOf(',');
if (argIndex) tmpString = tmpString.substring(0, argIndex);
// String log = F("TaskDeviceName : ");
// log += ExtraTaskSettings.TaskDeviceName;
// addLog(LOG_LEVEL_INFO, log);
if (tmpString.equalsIgnoreCase(F("NEXTION"))) {
// if (tmpString.equalsIgnoreCase(ExtraTaskSettings.TaskDeviceName)) { // Use Plugin Name as command ID name.
argIndex = string.indexOf(',');
tmpString = string.substring(argIndex + 1);
sendCommand(tmpString.c_str(), HwSerial);
String log = F("NEXTION075 : WRITE, ");
log += F("Command is ");
log += (tmpString.c_str());
addLog(LOG_LEVEL_INFO, log);
success = true; // Set true only if plugin found a command to execute.
}
break;
}
case PLUGIN_EXIT: {
if (SoftSerial) {
delete SoftSerial;
SoftSerial=NULL;
}
if(HwSerial) {
HwSerial = false;
Settings.UseSerial = DEFAULT_USE_SERIAL;
Settings.BaudRate = DEFAULT_SERIAL_BAUD;
Serial.flush();
Serial.begin(DEFAULT_SERIAL_BAUD); // Restart Serial Logging with default baud.
}
break;
}
case PLUGIN_ONCE_A_SECOND: {
success = true;
break;
}
case PLUGIN_TEN_PER_SECOND: {
uint16_t i;
uint8_t c;
uint8_t charCount;
String log;
String Vidx;
String Nvalue;
String Svalue;
String Nswitch;
char __buffer[RXBUFFSZ+1];
if(HwSerial) charCount = Serial.available(); // Prime the Hardware Serial engine.
else charCount = SoftSerial->available(); // Prime the Soft Serial engine.
while (charCount) { // This is the serial engine. It processes the serial Rx stream.
if(HwSerial) c = Serial.read();
else c = SoftSerial->read();
if (c == 0x65) {
if (charCount < 6) delay((5/(AdvHwBaud/9600))+1); // Let's wait for a few more chars to arrive.
if (HwSerial) charCount = Serial.available();
else charCount = SoftSerial->available();
if (charCount >= 6) {
__buffer[0] = c;
for (i = 1; i < 7; i++) {
if(HwSerial) __buffer[i] = Serial.read();
else __buffer[i] = SoftSerial->read();
}
__buffer[i] = 0x00;
if (0xFF == __buffer[4] && 0xFF == __buffer[5] && 0xFF == __buffer[6]) {
UserVar[event->BaseVarIndex] = (__buffer[1] * 256) + __buffer[2] + TOUCH_BASE;
UserVar[event->BaseVarIndex + 1] = __buffer[3];
log = F("NEXTION075 : code: ");
log += __buffer[1];
log += ",";
log += __buffer[2];
log += ",";
log += __buffer[3];
addLog(LOG_LEVEL_INFO, log);
sendData(event);
}
}
}
else {
if (c == '|') {
__buffer[0] = c;
if (charCount < 8) delay((9/(AdvHwBaud/9600))+1); // Let's wait for more chars to arrive.
else delay((3/(AdvHwBaud/9600))+1); // Short wait for tardy chars.
if (HwSerial) charCount = Serial.available();
else charCount = SoftSerial->available();
if(HwSerial) {
i = 1;
while (Serial.available() > 0 && i<RXBUFFSZ) { // Copy global serial buffer to local buffer.
__buffer[i] = Serial.read();
if (__buffer[i]==0x0a || __buffer[i]==0x0d) break;
i++;
}
}
else {
i = 1;
while (SoftSerial->available() > 0 && i<RXBUFFSZ) { // Copy global serial buffer to local buffer.
__buffer[i] = SoftSerial->read();
if (__buffer[i]==0x0a || __buffer[i]==0x0d) break;
i++;
}
}
__buffer[i] = 0x00;
String tmpString = __buffer;
log = F("NEXTION075 : code: ");
log += tmpString;
addLog(LOG_LEVEL_INFO, log);
int argIndex = tmpString.indexOf(F(",i"));
int argEnd = tmpString.indexOf(',', argIndex + 1);
if (argIndex) Vidx = tmpString.substring(argIndex + 2,argEnd);
boolean GotPipeCmd = false;
switch (__buffer[1]){
case 'u':
GotPipeCmd = true;
argIndex = argEnd;
argEnd = tmpString.indexOf(',',argIndex + 1);
if (argIndex) Nvalue = tmpString.substring(argIndex + 2,argEnd);
argIndex = argEnd;
argEnd = tmpString.indexOf(0x0a);
if (argIndex) Svalue = tmpString.substring(argIndex + 2,argEnd);
break;
case 's':
GotPipeCmd = true;
argIndex = argEnd;
argEnd = tmpString.indexOf(0x0a);
if (argIndex) Nvalue = tmpString.substring(argIndex + 2,argEnd);
if (Nvalue == F("On")) Svalue='1';
if (Nvalue == F("Off")) Svalue='0';
break;
}
if (GotPipeCmd) {
UserVar[event->BaseVarIndex] = Vidx.toFloat();
UserVar[event->BaseVarIndex+1] = Svalue.toFloat();
sendData(event);
log = F("NEXTION075 : Pipe Command Sent: ");
log += __buffer;
log += UserVar[event->BaseVarIndex];
}
else {
log = F("NEXTION075 : Unknown Pipe Command, skipped");
}
addLog(LOG_LEVEL_INFO, log);
}
}
if(HwSerial) charCount = Serial.available();
else charCount = SoftSerial->available();
}
success = true;
break;
}
}
return success;
}
void sendCommand(const char *cmd, boolean SerialHW)
{
if(SerialHW) {
Serial.print(cmd);
Serial.write(0xff);
Serial.write(0xff);
Serial.write(0xff);
}
else {
SoftSerial->print(cmd);
SoftSerial->write(0xff);
SoftSerial->write(0xff);
SoftSerial->write(0xff);
}
}
#endif // USES_P075