I'm using a Nextion display with multiple buttons to On/Off toggle lights. Holding the buttons takes me to another page with sliders to adjust brightness.
I'm usingy Perry Bebbington's serial method.
So I have a function which works for one button. What I would like to do is have one function and just pass the button press states to it.
b1Press goes true on button press, and false on button release.
this works:
bool b1Press, b1Held, b1State;
unsigned long currentMillis;
unsigned long pressTime;
unsigned long millisHeld;
void loop() {
if (b1Press == true && b1Held == false) {
pressTime = currentMillis;
millisHeld = 0;
b1Held = true;
}
if (b1Press == true && b1Held == true) {
millisHeld = currentMillis - pressTime;
}
if (b1Press == false && b1Held == true && millisHeld <= 1000) {
b1State = !b1State;
b1Held = false;
}
if (b1Press == true && b1Held == true && millisHeld >= 2000) {
gotoPage1();
b1Held = false;
b1Press = false; // nextion doesn't get to send a button release string
}
} // end loop
This is what I hoped would work as a function, but doesn't. MillisHeld never gets updated.
bool b1Press, b1State;
void loop() {
b1State = detectButton(b1Press, b1State);
} // end loop
bool detectButton(bool press, bool state ) {
bool held = false;
unsigned long millisHeld = 0, pressTime = 0;
if (press == true && held == false) {
pressTime = currentMillis;
millisHeld = 0;
held = true;
}
if (press == true && held == true) {
millisHeld = currentMillis - pressTime;
}
if (press == false && held == true && millisHeld <= 2000) {
held = false;
state = !state;
return state;
}
if (press == true && held == true && millisHeld > 2000) {
gotoPage1();
held = false;
press = false; // nextion doesn't get to send a button release string
}
Is what I'm trying to do possible?
Thanks for the feedback.
The buttons are not digital inputs, they are boolean values derived from the serial stream from the nextion.
There will be 32 buttons - hence wanting just one function.
Delta_G:
unsigned long millisHeld = 0, pressTime = 0;
Make them static and they won't lose their value every time the function exits.
Also, since you have b1Press and b1State as global variables, there's no need to pass them to the function. They already exist there.
And lastly, since you never get new values for b1Press or b1State, you're just passing false, false into your function every time. You need to use digitalRead to read the buttons at some point. You have the one at global scope, but it doesn't really work there and even if it does it will just keep using that same value over and over and over until you call digitalRead again.
Delta_G:
You still need to give them some values at some point in the code.
That's 100 lines of code that isn't relevant to the question. Just accept, there are 32 variables bxxPress, and 32 variables bxxState. State is to invert on a short press only, and long press sends a page change string.
Here's my code for dealing with the buttons - I know it's wrong regarding detecting button hold. I'm a hardware designer straying into unfamiliar territory.
bool b0Press, b1Press, b2Press, b3Press, b4Press, b5Press, b6Press, b7Press;
bool b0State, b1State, b2State, b3State, b4State, b5State, b6State, b7State;
int slider1;
bool page1;
unsigned long currentMillis;
void setup() {
Serial.begin(9600);
Serial1.begin(9600);
}
void loop() {
currentMillis = millis();
Nex1_read();
b0State = detectButton(b0Press, b0State);
b1State = detectButton(b1Press, b1State);
b2State = detectButton(b2Press, b2State);
b3State = detectButton(b3Press, b3State);
b4State = detectButton(b4Press, b4State);
b5State = detectButton(b5Press, b5State);
b6State = detectButton(b6Press, b6State);
b7State = detectButton(b7Press, b7State);
} // end loop
bool detectButton(bool press, bool state ) {
static bool held = false;
unsigned long millisHeld = 0, pressTime = 0;
if (press == true && held == false) {
pressTime = currentMillis;
millisHeld = 0;
held = true;
}
if (press == true && held == true) {
millisHeld = currentMillis - pressTime;
}
if (press == false && held == true && millisHeld <= 2000) {
held = false;
state = !state;
return state;
}
if (press == true && held == true && millisHeld > 2000) {
gotoPage1();
held = false;
millisHeld = 0;
pressTime = 0;
press = false; // nextion doesn't get to send a button release string
}
Serial.print("press ");
Serial.print(press);
Serial.print(" held ");
Serial.print(held);
Serial.print(" pressTime ");
Serial.print(pressTime);
Serial.print(" millisHeld ");
Serial.print(millisHeld);
Serial.print(" State ");
Serial.println(state);
}
void gotoPage1() {
if (page1 == false) {
Serial1.print(F("page 1"));
Serial1.write(0xff);
Serial1.write(0xff);
Serial1.write(0xff);
}
}
void Nex1_read() {
static uint8_t Nex1_read[10]; //temporary buffer
static uint8_t Nex1_read_i; //count bytes received
static uint8_t a5count; //start indicator
uint8_t readtemp; //last received byte
while (Serial1.available() > 0) { //Read
readtemp = Serial1.read();
if (readtemp == 0xa5) { //Count
++a5count;
if (a5count > 2) {
a5count = 0;
Nex1_read_i = 0;
}
}
else {
a5count = 0;
}
Nex1_read[Nex1_read_i] = readtemp;
if (Nex1_read_i == 5) {
switch (Nex1_read[1]) { // contains the page
case 0: // Case 0 page 0
page1 = false;
switch (Nex1_read[2]) { // Nex1_read[2] type
case 0: // case 0 page
break;
case 1: //button press
switch (Nex1_read[3]) { //Nex1_read[3] index
case 0:
b0Press = true;
break;
case 1:
b1Press = true;
break;
case 2:
b2Press = true;
break;
case 3:
b3Press = true;
break;
case 4:
b4Press = true;
break;
case 5:
b5Press = true;
break;
case 6:
b6Press = true;
break;
case 7:
b7Press = true;
break;
}
break;
case 2: // button release
switch (Nex1_read[3]) {
case 0:
b0Press = false;
break;
case 1:
b1Press = false;
break;
case 2:
b2Press = false;
break;
case 3:
b3Press = false;
break;
case 4:
b4Press = false;
break;
case 5:
b5Press = false;
break;
case 6:
b6Press = false;
break;
case 7:
b2Press = false;
break;
}
break;
}
break;
case 1: // Case 1 page 1
page1 = true;
switch (Nex1_read[2]) {
case 0: // post initialisation request - page number
break;
case 1: // slider on page 1
//Nex1_read[5] byte 1 of the slider value, Nex1_read[4] is byte 0 of the slider value
//uint16_t slider_val = ((Nex1_read[5] << 8 | Nex1_read[4]));
slider1 = ((Nex1_read[5] << 8 | Nex1_read[4]));
break;
}
break;
}
}
++Nex1_read_i;
if (Nex1_read_i > 9) {
Nex1_read_i = 9;
}
}
}
Hello mazellan,
Can I just check I have understood correctly what want to do, so I don't spend time answering the wrong question; I think you want to replace this:
switch (Nex1_read[1]) { // contains the page
case 0: // Case 0 page 0
page1 = false;
switch (Nex1_read[2]) { // Nex1_read[2] type
case 0: // case 0 page
break;
case 1: //button press
switch (Nex1_read[3]) { //Nex1_read[3] index
case 0:
b0Press = true;
break;
case 1:
b1Press = true;
break;
case 2:
b2Press = true;
break;
case 3:
b3Press = true;
break;
case 4:
b4Press = true;
break;
case 5:
b5Press = true;
break;
case 6:
b6Press = true;
break;
case 7:
b7Press = true;
break;
}
break;
case 2: // button release
switch (Nex1_read[3]) {
case 0:
b0Press = false;
break;
case 1:
b1Press = false;
break;
case 2:
b2Press = false;
break;
case 3:
b3Press = false;
break;
case 4:
b4Press = false;
break;
case 5:
b5Press = false;
break;
case 6:
b6Press = false;
break;
case 7:
b2Press = false;
break;
}
break;
}
With something that just passes a value to a function which updates the appropriate button state. In particular you want to get rid of the long string of switch cases that just change the button status. Am I correct? If not, then please re-state your question.
Thanks.
Hi Perry, thanks for replying. I find your serial method much easier to understand than using Nextion's own libraries.
I'm happy with the multiple switch/case lines. What I want is multiple dual state buttons (toggle) - press on, press off on page0. But a button hold takes you to page1 where I'll have a bunch of sliders.
I'm having 32 buttons on a page to control stuff, 8 of them control lights, with sliders to control brightness. I've got code that detects the hold condition, but rather than duplicate it 8 times I thought it might be possible to use a function. If not, I'll just do the former.
I find your serial method much easier to understand than using Nextion's own libraries.
Thank you!
I'm happy with the multiple switch/case lines.
DAMN! That's easy to fix and what I expected to be telling you. Hint, use an array for all your bxpresses and bxstates (you should be doing that anyway, it's good practice when you have multiple variables that are basically all the same like that).
I've got code that detects the hold condition, but rather than duplicate it 8 times I thought it might be possible to use a function. If not, I'll just do the former.
It will be possible. I am crap at understanding other people's code, so I have skimmed through yours too quickly and not properly understood it. You really do need to put your variables into an array:
bool bstate[8];
bool bpress[8];
or
bool buttons[8][2];
Then you can access each one with an index (0 to 7), which means you can use a function that finds the right one from the index, which means you can use the value from:
Nex1_read[3]
To go directly from a particular button press to the function that does stuff.
Is that enough to get you thinking?
I need help! I've got this far and it almost works. Sometimes button hold changes the page too early, and sometimes the state is not boolean.
bool bPress[8];
bool bState[8];
int slider1;
bool page1;
unsigned long currentMillis;
int buttonIndex;
void setup() {
Serial.begin(9600);
Serial1.begin(9600);
}
void loop() {
currentMillis = millis();
Nex1_read();
Serial.print("button ");
Serial.print(buttonIndex);
Serial.print(" State ");
Serial.println(bState[buttonIndex]);
} // end loop
bool detectButton(byte index, bool press ) {
static bool pressed = press;
static bool held = false;
static unsigned long millisHeld = 0, pressTime = 0;
if (pressed == true && held == false) {
pressTime = currentMillis;
millisHeld = 0;
held = true;
}
if (pressed == true && held == true) {
millisHeld = currentMillis - pressTime;
}
if (pressed == false && held == true && millisHeld <= 2000) {
held = false;
millisHeld = 0;
bState[index] = !bState[index];
return bState[index];
}
if (pressed == true && held == true && millisHeld > 2000) {
gotoPage1();
held = false;
millisHeld = 0;
pressTime = 0;
bPress[index] = false; // nextion doesn't get to send a button release string
}
}
void gotoPage1() {
if (page1 == false) {
Serial1.print(F("page 1"));
Serial1.write(0xff);
Serial1.write(0xff);
Serial1.write(0xff);
}
}
void Nex1_read() {
static uint8_t Nex1_read[10]; //temporary buffer
static uint8_t Nex1_read_i; //count bytes received
static uint8_t a5count; //start indicator
uint8_t readtemp; //last received byte
while (Serial1.available() > 0) { //Read
readtemp = Serial1.read();
if (readtemp == 0xa5) { //Count
++a5count;
if (a5count > 2) {
a5count = 0;
Nex1_read_i = 0;
}
}
else {
a5count = 0;
}
Nex1_read[Nex1_read_i] = readtemp;
if (Nex1_read_i == 5) {
switch (Nex1_read[1]) { // contains the page
case 0: // Case 0 page 0
page1 = false;
switch (Nex1_read[2]) { // Nex1_read[2] type
case 1: //button press
buttonIndex = (Nex1_read[3]);
bState[(Nex1_read[3])] = detectButton((Nex1_read[3]), true);
break;
case 2: // button release
bState[(Nex1_read[3])] = detectButton((Nex1_read[3]), false);
break;
}
break;
case 1: // Case 1 page 1
page1 = true;
switch (Nex1_read[2]) {
case 1: // slider on page 1
//Nex1_read[5] byte 1 of the slider value, Nex1_read[4] is byte 0 of the slider value
//uint16_t slider_val = ((Nex1_read[5] << 8 | Nex1_read[4]));
slider1 = ((Nex1_read[5] << 8 | Nex1_read[4]));
break;
}
break;
}
}
++Nex1_read_i;
if (Nex1_read_i > 9) {
Nex1_read_i = 9;
}
}
}
I have had a quick read through, I don't have much time as I have to go out.
Have you read my examples in part 4 of my tutorial? The ones in 2019_08_10_Nextion_additional_features.zip. I will warn you that, unlike the examples at the start of the tutorial, which I made as simple as I could to demonstrate the basics, I made these as comprehensive as possible with no regard to simplicity. However, in there you will find how to track which page you are on properly. Your
bool page1;
Is a step in the right direction but can be improved. For multiple pages you need a finite state machine, with a state per page, my examples show you how to do that.
I know there are other things in your code that need improving but I don't have time now, anyway, dealing with the above will keep you busy 
Right, I have a bit more time now.
Your code is way too complicated, it makes something simple into something hard work.
bool bPress[8];
bool bState[8];
That's good.
I gave up trying to follow how those get set true or false. All you need to do is this:
case 1: //button press (You already know that if you get to here then it's true, so set bState to true)
bState[(Nex1_read[3])] = true;
//Something that deals with the presses can go here if you like
break;
case 2: //button release (If you are here, it's false, so bState = false)
bState[(Nex1_read[3])] = false;
//Something that deals with the presses can go here if you like
break;
}
That's all you need inside my code to record the button state. (I removed buttonIndex, but if you find you need it then put it back)
You can then call your function to deal with the button press and its current status.
My additional features examples includes a state machine for the page the nextion is displaying. I suggest you understand that. I think also that the long string of if (this && that) you have could probably work as a state machine as well. There are other examples on this site, search for finite state machine and take some time reading about it. Using a state machine makes life so much easier when you understand the concept. Take you time, don't rush to coding a state machine before you understand it.
Have fun!
I understand what you are saying, but I think you perhaps misunderstand what I'm trying to do.
I want to determine if a button has been held for 2 seconds or more (to trigger a page change). If the button is held, I don't want the state to change
I can get just the state change no problems, it's the hold condition that's a problem.
I'll post my almost working code again, for clarification.
Function detectButton() determines if a given button:
is pressed for less than 2 seconds -> change state
longer than two seconds -> don't change state, change page number instead
bool bPress[8];
bool bState[8];
int slider1;
bool page1;
unsigned long currentMillis;
int buttonIndex;
void setup() {
Serial.begin(9600);
Serial1.begin(9600);
}
void loop() {
currentMillis = millis();
Nex1_read();
Serial.print("button ");
Serial.print(buttonIndex);
Serial.print(" State ");
Serial.println(bState[buttonIndex]);
} // end loop
bool detectButton(byte index, bool press ) {
static bool pressed = press;
static bool held = false;
static unsigned long millisHeld = 0, pressTime = 0;
if (pressed == true && held == false) {
pressTime = currentMillis;
millisHeld = 0;
held = true;
}
if (pressed == true && held == true) {
millisHeld = currentMillis - pressTime;
}
if (pressed == false && held == true && millisHeld <= 2000) {
held = false;
millisHeld = 0;
bState[index] = !bState[index];
return bState[index];
}
if (pressed == true && held == true && millisHeld > 2000) {
gotoPage1();
held = false;
millisHeld = 0;
pressTime = 0;
bPress[index] = false; // nextion doesn't get to send a button release string
}
}
void gotoPage1() {
if (page1 == false) {
Serial1.print(F("page 1"));
Serial1.write(0xff);
Serial1.write(0xff);
Serial1.write(0xff);
}
}
void Nex1_read() {
static uint8_t Nex1_read[10]; //temporary buffer
static uint8_t Nex1_read_i; //count bytes received
static uint8_t a5count; //start indicator
uint8_t readtemp; //last received byte
while (Serial1.available() > 0) { //Read
readtemp = Serial1.read();
if (readtemp == 0xa5) { //Count
++a5count;
if (a5count > 2) {
a5count = 0;
Nex1_read_i = 0;
}
}
else {
a5count = 0;
}
Nex1_read[Nex1_read_i] = readtemp;
if (Nex1_read_i == 5) {
switch (Nex1_read[1]) { // contains the page
case 0: // Case 0 page 0
page1 = false;
switch (Nex1_read[2]) { // Nex1_read[2] type
case 1: //button press
buttonIndex = (Nex1_read[3]);
bState[(Nex1_read[3])] = detectButton((Nex1_read[3]), true);
break;
case 2: // button release
bState[(Nex1_read[3])] = detectButton((Nex1_read[3]), false);
break;
}
break;
case 1: // Case 1 page 1
page1 = true;
switch (Nex1_read[2]) {
case 1: // slider on page 1
//Nex1_read[5] byte 1 of the slider value, Nex1_read[4] is byte 0 of the slider value
//uint16_t slider_val = ((Nex1_read[5] << 8 | Nex1_read[4]));
slider1 = ((Nex1_read[5] << 8 | Nex1_read[4]));
break;
}
break;
}
}
++Nex1_read_i;
if (Nex1_read_i > 9) {
Nex1_read_i = 9;
}
}
}
mazellan:
I understand what you are saying, but I think you perhaps misunderstand what I'm trying to do.
I want to determine if a button has been held for 2 seconds or more (to trigger a page change). If the button is held, I don't want the state to change
I can get just the state change no problems, it's the hold condition that's a problem.
I get you. I got you. You are looking for a change of state:
- Button not pressed
- Button pressed (start timer)
- Button not released (timer running)
- Button released (read timer and take action)
- Button not pressed
Those are state changes. You want to measure the time between them. While I have not worked out a solution for you (that's your job) I know I would think about it from a state machine point of view. The long list of if(); statements you have suggests a state machine to me, each if(); suggests a [possible state. I'm not saying it's the only way, there's always more than one way, I'm saying it's how I would approach it.
OK, so your problem has been bothering my all day!
How about this (untested and incomplete and offered as something to work on):
unsigned long currentMillis;
unsigned long previousMillis[8];
const long interval = 1000; //1000 gives 1 second delay, change this for the button hold period you want
switch (Nex1_read[1]) { // contains the page
case 0: // Case 0 page 0
page1 = false;
switch (Nex1_read[2]) { // Nex1_read[2] type
case 1: //button press
previousMillis[Nex1_read[3]] = millis(); //This records the time the button was pressed, as given by millis();
//Anything else you need when the button is pressed goes here
break;
case 2: // button release
currentMillis = millis();
if (currentMillis - interval > previousMillis[Nex1_read[3]]) {
//Button has been pressed for longer than interval so do long press code
}
else {
//Button has been pressed for less than interval so do short press code
}
//Anything else you need when button relesed goes here
break;
}
break;
//Rest of switch code
Thank you Perry, that has helped me immensely. Programming is not my forte, I'm a hardware person - I built my first digital clock in 1974 using nixie tubes and 74 series TTL logic that cost me two weeks wages!
Thank you Perry, that has helped me immensely.

I built my first digital clock in 1974 using nixie tubes and 74 series TTL logic
Do you still have it? Does it work? That sounds like something to be proud of.
PerryBebbington:

Do you still have it? Does it work? That sounds like something to be proud of.
In 1982 I left a job at a university engineering department, and left it in the workshop for the others. I regret that, and unfortunately I have no photos either.
It was based on a magazine project, here is an article with part of the circuitry:
In 1982 I left a job at a university engineering department, and left it in the workshop for the others. I regret that, and unfortunately I have no photos either.
Maybe contact the university and ask if it's still there. If you don't ask the answer is always no.