Stop a running function

Hey friends.

Its me again and i stuck again. I am using a Nextion and an ESP32

I try to learn how to stop a running function. To be more specific. I have a Start and a Stop button.

When i push the START button: A counter in a textfield starts to count down and a LED turns off.
When i push the STOP button: I want to stop the counter and turn the LED off. (In the futre the LED will be replaced with a motor, and i want a STOP button to stop everything)

My problem right now is, i cant figure it out how to manage it, that my code recognizes that the STOP button was pushed and cancles my counter routine.

Here the code:

class EineKlasse
{
  private:
  double value;
  int value2;
  public:
  void  set_value(double v); 
  double get_value();
  void  set_value2(int v); 
  int get_value2();
};

void EineKlasse::set_value(double v)
{
    value = v;  
}
void EineKlasse::set_value2(int v)
{
    value2 = v;  
}
double EineKlasse::get_value()
{
  return value;
}
int EineKlasse::get_value2()
{
  return value2;
}

class Kalibrieren
{
  private:
  int menge;
  int zeit;
  double durchschnitt;
  public:
  Kalibrieren(int z, int m)
  {
    zeit = z,
    menge = m;
  }
  int get_time()
  {
    return zeit;
  }
    int get_menge()
  {
    return menge;
  }
  double CalcDurchschnitt()
  {
    return (((double(menge)/double(zeit))*3600)/1000);
  }
};


/***************************************************
 * *************************************************
 ***************************************************/

//Librarys
#include <Nextion.h>
#include <SimpleTimer.h>
#include <iostream>
using namespace std;

//Klassenobjekte
EineKlasse r1;
SimpleTimer timer;
//Kalibrieren pcal4time(int a, int b);
Kalibrieren *kal2;

//Fading LED
int freq = 5000;
int ledChannel = 0;
int resolution = 8;

// LED Pins
int Dosierungs_LED = 25;
int Betriebs_LED = 27;
int Impuls_Button = 13;


//Display
HardwareSerial Serial2(2);

void startPopCallback(void *ptr);
void stopPopCallback(void *ptr);


int buttonState2 = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button


NexText t0      = NexText(2, 5, "t0");

NexText t5      = NexText(8, 15, "t5"); 
NexText t6      = NexText(8, 16, "t6"); 
NexText t7      = NexText(8, 17, "t7"); 

NexButton start   = NexButton(8, 19, "start");
NexButton stop    = NexButton(8, 18, "stop");     

//memset-buffer
char buffer[100] = {0};


NexTouch *nex_listen_list[] =
{

  &start,    
  &stop,     
  NULL
};


void startPopCallback(void *ptr)
{
  CalibrationWithTime();
}

void stopPopCallback(void *ptr)
{
  buttonState2 = 1;
}


void setup(void)
{
  //Serial COM
  Serial.begin(115200);
  Serial2.begin(9600);

  // LED - Pins
  pinMode(Dosierungs_LED, OUTPUT);
  pinMode(Betriebs_LED, OUTPUT);
  pinMode(Impuls_Button, INPUT_PULLUP);
  
  nexInit();
  start.attachPop(startPopCallback);
  stop.attachPop(stopPopCallback);
  
  //PWM für ESP32
  ledcSetup(ledChannel, freq, resolution);
  ledcAttachPin(25, ledChannel);
}

void loop(void)
{
  nexLoop(nex_listen_list);
  timer.run();
}

//----------------------------------------------------------------
//------------------------ FUNCTIONS------------------------------
//----------------------------------------------------------------


void setPumpoff()
{
  ledcWrite(ledChannel, map(0, 0, 100, 0, 255)); 
}

 ////////   wenn Zeit eingegeben wird   /////////////////
void CalibrationWithTime()
{
  int zeit;
  int menge;
  int period = 1000;
  unsigned long time_now = 0; 
  
  memset(buffer, 0, sizeof(buffer));           
  t6.getText(buffer, sizeof(buffer));
  zeit = atoi(buffer);
  
  memset(buffer, 0, sizeof(buffer));           
  t5.getText(buffer, sizeof(buffer));
  menge = atoi(buffer);

  int zeit_timer = zeit;
  Kalibrieren pcal4time (zeit,menge);
  
  ledcWrite(ledChannel, map(100, 0, 100, 0, 255));                      
  timer.setTimeout((pcal4time.get_time())*1000, setPumpoff);                     

  while (!ifcondtionismetbreak(buttonState2))
  {
    while(zeit_timer>=0)
    {    
      if(millis() > time_now + period)
      {
        time_now = millis();   
        memset(buffer, 0, sizeof(buffer));
        itoa(zeit_timer, buffer, 10);
        t6.setText(buffer);      
        zeit_timer--;
        //if (ifcondtionismetbreak(buttonState2))
        //break;
      }  
    }return;
  }   
}

 bool ifcondtionismetbreak(int x){
 if (x == 1)
 {
      return true;
 }
 else 
 {
  //cout <<"Continue";
  return false;
 }
 }

  bool ifcondtionismetbreakII(int x){
 if (x == 1)
 {
      return true;
 }
 else 
 {
  //cout <<"Continue";
  return false;
 }
 }

I am kinda frustrated. I know that this code is false. But i am unable to figure it out how to solve the puzzle. I know that somehow the info, that the STOP button was pushed must interrupt the while loop.

Anyone has an idea?

Thank you for all the support

best regards

ideally the "function" you want to stop is driven by the state of your program and is not really a piece of code currently executing otherwise you won't be able to stop it unless the function itself tests for a specific input or you use interrupts which sets a flag and the function tests for this flag to stop.

the easiest way is to call your function in the loop() if you have not received the stop() message and the function() only performs one step in due time

here is a basic example using the Serial Console set at 115200 bauds

void counter()
{
  static unsigned long chrono = 0;
  static const unsigned long period = 250; // increment every "period" ms, here 1/4 s
  static unsigned long count = 0;

  if (millis() - chrono >= period) {
    Serial.println(count++);
    chrono = millis();
  }
}

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  static bool isRunning = false;

  // this parts tests if we receive an order to run or stop
  int c = Serial.read(); // returns -1 if nothing to read

  switch (c) {
    case 'r':  isRunning = true; // r for RUN
      break;
    case 's':  isRunning = false; // s for STOP
      break;
    default:
      break;
  }

  if (isRunning) counter(); // only run the function necessary
}

you type

r

in the console (and validate) to start running the counter and you type

s

to stop running the counter.

the function counter() handles its own chrono to know when to do something, here every 250ms

WHILE you're executing your CalibrationWithTime function is running, you will NOT get Nextion callbacks. Those happen only DURING the nexLoop call in loop().

So, if you want STOP to interrupt CalibrationWithTime , then you need to either rewrite it so it executes in steps over several iterations of loop(), or call nexLoop from enough places within CalibrationWithTime to give you the response time you want. Or, you can let the button set a Nextion variable, and test that variable during CalibrationWithTime. But that is a lousy solution, because calling the Nextion library is pretty time-consuming itself.

Regards,
Ray L.

First of all, thank you for your help

J-M-L:
the easiest way is to call your function in the loop() if you have not received the stop() message and the function() only performs one step in due time

I dont know if i understand you correctly. I tried to adapt your idea following:

int ISCalibrationWithTime_running;

void startPopCallback(void *ptr)
{
	ISCalibrationWithTime_running = 0;
}

void stopPopCallback(void *ptr)
{
	ISCalibrationWithTime_running = 1;
}

void setup(void)
{
  //Serial COM
  Serial.begin(115200);
  Serial2.begin(9600);

  // LED - Pins
  pinMode(Dosierungs_LED, OUTPUT);
  pinMode(Betriebs_LED, OUTPUT);
  pinMode(Impuls_Button, INPUT_PULLUP);
 
  nexInit();
  start.attachPop(startPopCallback);
  stop.attachPop(stopPopCallback);
 
  //PWM für ESP32
  ledcSetup(ledChannel, freq, resolution);
  ledcAttachPin(25, ledChannel);
}

void loop(void)
{
	static bool isRunning = false;
	
	nexLoop(nex_listen_list);
	timer.run();

	switch (ISCalibrationWithTime_running) {
		case '0':  isRunning = true; 
		break;
		case '1':  isRunning = false;
		break;
		default:
		break;
	}

  if (isRunning) CalibrationWithTime();  
}

//----------------------------------------------------------------
//------------------------ FUNCTIONS------------------------------
//----------------------------------------------------------------

void CalibrationWithTime()
{
  int zeit;
  int menge;
  int period = 1000;
  unsigned long time_now = 0;
  
 
  memset(buffer, 0, sizeof(buffer));           
  t6.getText(buffer, sizeof(buffer));
  zeit = atoi(buffer);
 
  memset(buffer, 0, sizeof(buffer));           
  t5.getText(buffer, sizeof(buffer));
  menge = atoi(buffer);

  int zeit_timer = zeit;
  Kalibrieren pcal4time (zeit,menge);
 
  ledcWrite(ledChannel, map(100, 0, 100, 0, 255));                     
  timer.setTimeout((pcal4time.get_time())*1000, setPumpoff);                     

  while (!ifcondtionismetbreak(buttonState2))
  {
    while(zeit_timer>=0)
    {   
      if(millis() > time_now + period)
      {
        time_now = millis();   
        memset(buffer, 0, sizeof(buffer));
        itoa(zeit_timer, buffer, 10);
        t6.setText(buffer);     
        zeit_timer--;
      } 
    } return;
  }   
}

Since i am not at home. i couldnt test the code. So please dont kill me if the code wont compile. Just wanna know in the first place if i understood you right?

I always tried to put no code into the loop. to avoid performance-loss

Thank you

I never killed anyone, wont start today :slight_smile:

When you use truth variables, such as ISCalibrationWithTime_running best type is bool (or boolean) and use true or false instead of 1 and 0 in assignments. That will be much more readable and you save memory.

You don’t need my isRunning variable, just use yours to decide if you want to call CalibrationWithTime().

But You need to ensure though that this function returns quickly, ie performs one step of whatever you need to do and returns, then will continue calibration at next call. The big while() you have is a problem currently, this is what you need to break in smaller steps and simulate that while through coming back regularly to the function from the loop

In a nutshell, if you don’t come back to the loop, you’ll never know if someone touched your stop button because the callback is only executed from the call to nexLoop(nex_listen_list); - which will set your boolean variable

Uff i kinda know what you mean, but somehow it still twists my brain. But i ll give my best to crack the nut

bool ISCalibrationWithTime_running = false;

void startPopCallback(void *ptr)
{
	ISCalibrationWithTime_running = true;
}

void stopPopCallback(void *ptr)
{
	ISCalibrationWithTime_running = false;
}

void setup(void)
{
  //Serial COM
  Serial.begin(115200);
  Serial2.begin(9600);

  // LED - Pins
  pinMode(Dosierungs_LED, OUTPUT);
  pinMode(Betriebs_LED, OUTPUT);
  pinMode(Impuls_Button, INPUT_PULLUP);
 
  nexInit();
  start.attachPop(startPopCallback);
  stop.attachPop(stopPopCallback);
 
  //PWM für ESP32
  ledcSetup(ledChannel, freq, resolution);
  ledcAttachPin(25, ledChannel);
}

void loop(void)
{
	nexLoop(nex_listen_list);
	timer.run();

        if (ISCalibrationWithTime_running) CalibrationWithTime(); 
}

//----------------------------------------------------------------
//------------------------ FUNCTIONS------------------------------
//----------------------------------------------------------------

void CalibrationWithTime()
{
  int zeit;
  int menge;
  int period = 1000;
  unsigned long time_now = 0;
 
 
  memset(buffer, 0, sizeof(buffer));           
  t6.getText(buffer, sizeof(buffer));
  zeit = atoi(buffer);
 
  memset(buffer, 0, sizeof(buffer));           
  t5.getText(buffer, sizeof(buffer));
  menge = atoi(buffer);

  int zeit_timer = zeit;
  Kalibrieren pcal4time (zeit,menge);
 
  ledcWrite(ledChannel, map(100, 0, 100, 0, 255));                     
  timer.setTimeout((pcal4time.get_time())*1000, setPumpoff);                     

  {
    while(zeit_timer>=0)
    {   
      if(millis() > time_now + period)
      {
        time_now = millis();   
        memset(buffer, 0, sizeof(buffer));
        itoa(zeit_timer, buffer, 10);
        t6.setText(buffer);     
        zeit_timer--;
      }
    } return;
    ISCalibrationWithTime_running = false;  
}

I try to formulate what i think my code is doing: ISCalibrationWithTime_running is always false. When i push my button, ISCalibrationWithTime_running will be true and the function CalibrationWithTime will be
executed. After the whole function is finished, ISCalibrationWithTime_running will be set to false again, that we are not in an infinite loop.

The problem right now: Like you mentioned, my countdown (while loop) is a problem. From what i understand is, that as long as we are in the while loop, no information (if the stop button was hit) comes to me. ergo i can not stop the countdown.

Do i understand it right?

Your wrote:

J-M-L:
The big while() you have is a problem currently, this is what you need to break in smaller steps and simulate that while through coming back regularly to the function from the loop

I dont know how i can break my while-countdown loop into smaller pieces.

1000 thanks for the help

well currently your function (besides misaligned {}) is doing a return before setting ISCalibrationWithTime_running to false, so that last statement will never be executed :slight_smile:

Can you describe in english what your function is doing

  • read zeit and menge from the Nextion I suppose

what is this?

  int zeit_timer = zeit;
  Kalibrieren pcal4time(zeit, menge);

this is not the right way to check timeif (millis() > time_now + period)as time_now + period will overflow in ~50 days and do something possibly bad. You need to handle that with a subtraction if (millis() - time_now > period)then you are fine

The way to break the function is to have first an "initialization" that you call only once when start is pressed where you'll read zeit and menge and do whatever else needs to happen only once, and then have the function that will mimic the while() loop being called regularly.

Hello

Sorry for misaligned, fixed it!

J-M-L:
well currently your function (besides misaligned {}) is doing a return before setting ISCalibrationWithTime_running to false, so that last statement will never be executed :slight_smile:

Yes your are right, fixed that too.

J-M-L:
Can you describe in english what your function is doing

  • read zeit and menge from the Nextion I suppose
    what is this?

Yes. It reads the value "zeit" and "menge" from my Nextion. "Zeit" (which means time) is a value that i can enter. After that i store both values into a class "Kalibrieren" ( for future use).

What should my function do. When i click a "start-button" on my nextion the LED should turn on and the zeit (time) that i entered should count down. So my plan was to create a function that turns a LED on (will be a motor in the future) just for a specific time and lists it in a textfield on my nextion. After countdown reaches 0, turn LED off. But i also want a "stop" button to interrupt this countdown (if something went wrong in the future).

J-M-L:
this is not the right way to check time..

Yes Sir you are right again. fixed that too

I hope i am a step closer to end the drama :slight_smile:

bool ISCalibrationWithTime_running = false;

void startPopCallback(void *ptr)
	{
                CalibrationWithTime();
		ISCalibrationWithTime_running = true;
	}

void stopPopCallback(void *ptr)
	{
		ISCalibrationWithTime_running = false;
	}

void setup(void)
{
	//Serial COM
	Serial.begin(115200);
	Serial2.begin(9600);

	// LED - Pins
	pinMode(Dosierungs_LED, OUTPUT);
	pinMode(Betriebs_LED, OUTPUT);
	pinMode(Impuls_Button, INPUT_PULLUP);

	nexInit();
	start.attachPop(startPopCallback);
	stop.attachPop(stopPopCallback);

	//PWM für ESP32
	ledcSetup(ledChannel, freq, resolution);
	ledcAttachPin(25, ledChannel);
}

void loop(void)
{
	nexLoop(nex_listen_list);
	timer.run();
	if (ISCalibrationWithTime_running) WritesTheTime();
}

//----------------------------------------------------------------
//------------------------ FUNCTIONS------------------------------
//----------------------------------------------------------------
void CalibrationWithTime()
{
	int zeit;
	int menge;
	int period = 1000;
	unsigned long time_now = 0;

	memset(buffer, 0, sizeof(buffer));           
	t6.getText(buffer, sizeof(buffer));
	zeit = atoi(buffer);

	memset(buffer, 0, sizeof(buffer));           
	t5.getText(buffer, sizeof(buffer));
	menge = atoi(buffer);

	int zeit_timer = zeit;
	Kalibrieren pcal4time (zeit,menge);

	ledcWrite(ledChannel, map(100, 0, 100, 0, 255));                     
	timer.setTimeout((pcal4time.get_time())*1000, setPumpoff);   //this only need to be done once                     
}

void WritesTheTime()
{
	memset(buffer, 0, sizeof(buffer));           
	t6.getText(buffer, sizeof(buffer));
	zeit = atoi(buffer);
	int period = 1000;
	unsigned long time_now = 0;
	
	while(zeit>=0)
	{   
		if(millis() - time_now > period)
		{
			time_now = millis();   
			memset(buffer, 0, sizeof(buffer));
			itoa(zeit, buffer, 10);
			t6.setText(buffer);     
			zeit--;
                        time_now = millis();
		}
	} 
	ISCalibrationWithTime_running = false;
	return;	
}

But with this method. Isnt it still the case that my while loop is blocking me from getting the information that the stop button was hit?

what do you need the timer for, since you have a count-down - use that to stop the motor / LED when time arrives to 0.

I'd look at it somewhat like this (typed here so really don't know if that works since this is missing plenty of other code and variable declaration like buffer etc)

int zeit, menge;
unsigned long zeit_timer;
unsigned long startChrono;
const unsigned long refreshDisplayPeriod = 1000UL;

void readParameters()
{
  *buffer = '\0';
  t6.getText(buffer, sizeof(buffer));
  zeit = atoi(buffer);

  *buffer = '\0';
  t5.getText(buffer, sizeof(buffer));
  menge = atoi(buffer);

  Kalibrieren pcal4time (zeit, menge);
  zeit_timer = pcal4time.get_time(); // ASSUMING THIS IS THE SAME THING AS ZEIT OR IS MENGE INVOLVED IN THIS??
}

void startPump()
{
  startChrono = millis();
  ledcWrite(ledChannel, 255);
}

void stopPump()
{
  ISCalibrationWithTime_running = false;
  ledcWrite(ledChannel, 0);
}


void startPopCallback(void *ptr)
{
  readParameters();
  startPump();
  ISCalibrationWithTime_running = true;
}

void stopPopCallback(void *ptr)
{
  stopPump();
}

// display a countdown every second
void writeCountDown()
{
  if (ISCalibrationWithTime_running) {
    if (millis() - startChrono > refreshDisplayPeriod) {
      *buffer = '\0';
      itoa(zeit_timer, buffer, 10);
      t6.setText(buffer);
      startChrono += refreshDisplayPeriod;
      zeit_timer--;
      if (zeit_timer <= 0) stopPump();
    }
  }
}

//----------------------------------------------------------------

void setup(void)
{
  //Serial COM
  Serial.begin(115200);
  Serial2.begin(9600);

  // LED - Pins
  pinMode(Dosierungs_LED, OUTPUT);
  pinMode(Betriebs_LED, OUTPUT);
  pinMode(Impuls_Button, INPUT_PULLUP);

  nexInit();
  start.attachPop(startPopCallback);
  stop.attachPop(stopPopCallback);

  //PWM für ESP32
  ledcSetup(ledChannel, freq, resolution);
  ledcAttachPin(25, ledChannel);
}

void loop(void)
{
  nexLoop(nex_listen_list);
  writeCountDown();
}

basically pressing the start button read the parameters and starts the pump and set the flag ISCalibrationWithTime_running to true. The loop loops and calls writeCountDown() which will count down to zero and stops the pump. But if you press the stop button, since the writeCountDown() is non blocking, it will be trapped in the loop when you call nexLoop(nex_listen_list) and that will trigger the stopPopCallback() call back, which will call stopPump(). In that function we stop the pump (no need to use map() since you know the exact value you want to use) and change the flag ISCalibrationWithTime_running to false.

that's the general idea. need fine tuning I'm sure

Wow. Its looks logical to me.
Damn you are good!

As soon i am at home i will test my setup and let you know.

J-M-L:
But if you press the stop button, since the writeCountDown() is non blocking, it will be trapped in the loop when you call nexLoop(nex_listen_list) and that will trigger the stopPopCallback() call back, which will call stopPump().

do i understand it right, that the "ISCalibrationWithTime_running = false;" in

void stopPopCallback(void *ptr)
{
  stopPump();
  ISCalibrationWithTime_running = false;
}

is unnecessary, since it will be set to false already in stopPump()?

Thank you very much

Manuel_o:
do i understand it right, that the "ISCalibrationWithTime_running = false;" in

void stopPopCallback(void *ptr)

{
  stopPump();
  ISCalibrationWithTime_running = false;
}




is unnecessary, since it will be set to false already in stopPump()?

correct - you don't need it twice. You might want to think if you want it in stopPump() or just in the callback as one could argue that this function should only worry about the pump - but in which case at the end of the time in calibration, after calling stopPump you should set the variable to false as it will not go through the callBack. (same thought needed for start)

Hey

I just wanna say thank you. the code works and stops the function fine=)

solved=)

Best regards

great! well done