I am using a Nextion touchscreen connected to an Arduino Nano to control my project. I am using the SoftwareSerial.h library to connect the screen to the Arduino so that I can keep the primary serial ports free for uploading my sketches. I have had zero success using any of the existing Nextion libraries out there so I am creating my own functions to handle communication between the screen and Nano. I am trying to use an Event-Driven approach to create my code. I stumbled upon the Eventually.h library but it only handles pin inputs and timer events. If possible, how can I add to this functionality to the library to handle a Serial input? Is there any other event-driven libraries out there that will execute an event when data is received by the serial port?
are you using software serial at 115200 bauds? with lots of communication?
don't look further... use a real hardware port.
To me events are something that happens in almost "no time", like a status change of a pin. The reception of serial data involves events like falling/rising edges, but I would consider the reception itself as a process.
If what you want is to control your program depending on data/commands received you could just write a function that polls Serial and reacts when a valid command was received.
I am not sure if this is what you are looking for, but I have written a comprehensive example of a state machine controlled by Serial commands on Wokwi:
https://wokwi.com/projects/322586637175358035
If you start it and type "Help" or "?" in the Serial command line it will print the possible commands.
The sketch is quite long due to the different functions/states but the core is quite easy to understand and to adopt to other needs
Here is the sketch:
/*
Example of a State Machine 2022-01-31 ec2021
The states can change on different criteria:
Time:
The state WORKING changes after a given time
using the function "TimeToChange"
Counter:
The state COUNTING changes after an EndCount was reached
using the function "EndCounterReached"
Direct:
The states WORK, WAIT, COUNT and BATCH are used to prepare the corresponding
states WORKING, WAITING, COUNTING and RUNBATCH. Therefore they are passed only
once and link directly to their corresponding "main" state.
Externally:
All repetitive states can be quit by Serial command.
The state WAITING (entered via WAIT) can only be quit by Serial command.
If two states shall be performed one after another this can be initiated
by State.ActState = <FirstState> and State.NextState = <NextState> without changing
the state machine. However this way it is also possible to create new state that
calls two or more States one after another (it must keep track of the sequence
by its own of course).
The enum State_Type lists names for different states:
enum {
UNKNOWN, Kind of a "Null Pointer" telling that no legal state is used
WAIT, Prepares for WAITING
WAITING, Idles until quit by a Serial command
WORK, Prepares for WORKING
WORKING, In this example: Pretends to work for a certain time
COUNT, Prepares for COUNTING
COUNTING Counts until the counter reaches the limit "EndCount"
BATCH, Prepares RUNBATCH
RUNBATCH Example how to automatically control the state machine
} State_Type;
The structure State_Struct organizes the relevant data for the actual State and points
to the following (next) State:
typedef struct {
int ActState; Actual State to be performed (value from enum State_Type)
long ActDuration; Time how long the State shall be performed
int NextState; The State to change to when the ActState is done
int StateCount; Counter used for "Batch processing" of States
bool GetStarttime; Boolean to control the acquisition of Starttime
long Starttime; Time in millis() when the ActState started
long Worktime; Time in millis() used to control an internal function of "WORKING"
String WorkText; Text written during the state "WORKING"
long TimePerCount; Interval in millis() for counting
long Count; The variable that counts upwards until EndCount
long DefaultEndCount; Used as default for EndCount (can be changed via Serial)
bool DefaultEndCountHasChanged Forces EndCount to change when "COUNT" is called
long EndCount; End value for COUNTING (default or temporarily changed via Serial)
} State_Struct;
Input "?" or "Help" via Serial to get a list of the available commands.
You may play around with the State Machine, e.g. by command "Count 40" and then command "WAIT"
during the progress of COUNTING to see what happens. You can always quit states by "WAIT".
Up to now there is no "Stop" and "Resume" command available ... Of course this is possible:
It would require to store different data and restoring them after "Resume", including
- where appropriate - the consideration of the "remaining time-to-go", to finalize the
stopped state. Still something to do for the future ...
*/
enum {
UNKNOWN,
WAIT,
WAITING,
WORK,
WORKING,
COUNT,
COUNTING,
BATCH,
RUNBATCH
} State_Type;
typedef struct {
int ActState = WAIT;
long ActDuration = 0;
int NextState = UNKNOWN;
int StateCount = 0;
bool GetStarttime = true ;
long Starttime = 0;
long Worktime = 0;
String WorkText = "Work ";
long TimePerCount = 0;
long Count = 0;
long DefaultEndCount = 10;
bool DefaultEndCountHasChanged = false;
long EndCount = 10;
} State_Struct;
// Global Variables
State_Struct State;
String Serialcurrentline = "";
// State Functions
boolean TimeToChange() {
return (millis() - State.Starttime >= State.ActDuration);
}
boolean TimeToWork(long wTime) {
if (millis() - State.Worktime >= wTime) {
State.Worktime = millis();
return true;
} else return false;
}
boolean EndCounterReached(long Criteria) {
return (Criteria >= State.EndCount);
}
void PrintActState() {
String sState = "UNKNOWN";
switch (State.ActState) {
case WAIT : sState = "WAIT"; break;
case WAITING : sState = "WAITING"; break;
case WORK : sState = "WORK"; break;
case WORKING : sState = "WORKING"; break;
case COUNT : sState = "COUNT"; break;
case COUNTING : sState = "COUNTING"; break;
case BATCH : sState = "BATCH"; break;
case RUNBATCH : sState = "RUNBATCH"; break;
}
Serial.println();
Serial.println(sState);
}
void GetStarttime() {
if (State.GetStarttime) {
State.GetStarttime = false;
State.Starttime = millis();
State.Worktime = State.Starttime;
}
}
void ChangeState() {
if (!State.NextState == UNKNOWN) {
State.ActState = State.NextState;
State.NextState = UNKNOWN;
} else State.ActState = WAIT;
}
void InitStateMachine() {
State.ActState = "WAIT";
State.NextState = UNKNOWN;
}
void HandleStateBatch() {
State.NextState = RUNBATCH;
switch (State.StateCount) {
case 0 : State.ActState = COUNT; // Using the default entry to COUNT/COUNTING
State.EndCount = 3; // But change the EndCount which is not
break; // reset by COUNT
case 1 : State.ActDuration = 3200; // By setting the data like in the state "WORK"
State.ActState = WORKING; // we can jump to "WORKING" directly
State.GetStarttime = true; // and overwrite the defaults from "WORK"
State.WorkText = "Batch in Progress.. ";
State.ActState = WORKING;
PrintActState();
break;
case 2 : State.ActState = COUNT; // see above
State.EndCount = 7;
break;
default: State.ActState = WAIT; // Finally back to WAIT
break;
}
State.StateCount++;
}
void ResetCounter() {
State.EndCount = State.DefaultEndCount;
State.Count = 0;
}
void HandleStates() {
switch (State.ActState) {
case WAIT : State.ActDuration = 0;
State.ActState = WAITING;
State.GetStarttime = true;
PrintActState();
break;
case WAITING : // Wait forever or until Serial Cmd
break;
case WORK : State.ActDuration = 5200;
State.GetStarttime = true;
State.WorkText = "Work in Progress.. ";
State.ActState = WORKING;
PrintActState();
break;
case WORKING : GetStarttime();
if (TimeToWork(1000)) Serial.print(State.WorkText);
if (TimeToChange()) {
Serial.println();
ChangeState();
};
break;
case COUNT : State.Count = 0;
State.TimePerCount = 500;
State.ActState = COUNTING;
State.GetStarttime = true;
if (State.DefaultEndCountHasChanged) ResetCounter();
PrintActState();
break;
case COUNTING: GetStarttime();
if (TimeToWork(State.TimePerCount)) {
Serial.print(++State.Count);
if (State.Count < State.EndCount) Serial.print(F(","));
}
if (EndCounterReached(State.Count)) {
ResetCounter();
Serial.println();
ChangeState();
};
break;
case BATCH: State.StateCount = 0;
State.ActState = RUNBATCH;
break;
case RUNBATCH : HandleStateBatch();
break;
default : State.ActState = WAIT;
break;
}
}
// Help
void PrintHelp() {
Serial.println(F("Example State Machine"));
Serial.println(F("---------------------"));
Serial.println(F("Commands:"));
Serial.println(F("Help or ? : Print this help text"));
Serial.println(F("Wait : Do nothing but wait for next serial command"));
Serial.println(F("Work : Pretend to work ..."));
Serial.println(F("Count : Count to EndCount (default = 10, 2 counts per second)"));
Serial.println(F("Count <value> : Count to <value> = {1,2,3, ...}"));
Serial.println(F("Both : Do Work followed by Count "));
Serial.println(F("Batch : Perform a batch of states "));
Serial.println(F("DefEndCount : Prints value of default EndCount"));
Serial.println(F("SetEndCount <value> : Sets default EndCount to <value> = {1,2,3, ..."));
Serial.println();
}
// Serial Functions
void HandleSerial() {
while (Serial.available()) {
char c = Serial.read();
bool CR = (c == char(10));
if (c >= ' ') {
Serialcurrentline += c;
};
if (CR) {
HandleCommands(Serialcurrentline);
Serialcurrentline = "";
};
}
}
void HandleCounts(String Cmd) {
if (Cmd.length() > 5) {
long counts = Cmd.substring(6).toInt();
if (counts <= 0) counts = 1;
State.EndCount = counts;
} else State.EndCount = State.DefaultEndCount;
State.ActState = COUNT;
}
void SetDefEndCount(String Cmd) {
if (Cmd.length() > sizeof(F("SetEndCount"))) {
long counts = Cmd.substring(12).toInt();
if (counts <= 0) counts = 1;
State.DefaultEndCount = counts;
State.DefaultEndCountHasChanged = true;
Serial.print(F(" > Default EndCount set to "));
Serial.print(counts);
Serial.println(F(" < "));
};
}
void HandleCommands(String Cmd) {
bool unknown = true;
if (Cmd.startsWith(F("Help")) || Cmd.startsWith(F("?")) ) {
PrintHelp();
unknown = false;
};
if (Cmd.startsWith(F("Wait"))) {
State.ActState = WAIT;
unknown = false;
};
if (Cmd.startsWith(F("Work"))) {
State.ActState = WORK;
unknown = false;
};
if (Cmd.startsWith(F("Count"))) {
HandleCounts(Cmd);
unknown = false;
};
if (Cmd.startsWith(F("Both"))) {
State.ActState = WORK;
State.NextState = COUNT;
unknown = false;
};
if (Cmd.startsWith(F("Batch"))) {
State.ActState = BATCH;
State.NextState = UNKNOWN;
unknown = false;
};
if (Cmd.startsWith(F("DefEndCount"))) {
Serial.print(F("Default EndCount = "));
Serial.println(State.DefaultEndCount);
unknown = false;
};
if (Cmd.startsWith(F("SetEndCount"))) {
SetDefEndCount(Cmd);
unknown = false;
};
if (unknown) {
Serial.print (Cmd);
Serial.println(F(" is unknown! For Help input \"Help\" or \"?\" "));
};
}
// Setup
void setup() {
Serial.begin(115200);
Serial.println(F("Example State Machine"));
InitStateMachine(); // Not really necessary ;-)
}
// Loop
void loop() {
HandleSerial();
HandleStates();
}
Instead of using "if (Serial.available())
" in your loop() you can define a function named:
void serialEvent()
{
//statements
}
The Arduino core will call your serialEvent() function, right after your loop() returns, if a character is available.
Also works for other hardware serial ports:
void serialEvent1()
void serialEvent2()
void serialEvent3()
Hi @johnwasser , thanks for this information ... There is always something to learn
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.