Have a function run until button click on message box

I have a function that I need to run repeatedly until a message box with a ‘stop’ button is pressed.

I’m not sure how to do this. Is there a way to tie a message box button to an interrupt or a different way altogether to accomplish this?

I suggest creating a task and using the button’s callback event to either set a flag to prevent that task from processing or to terminate completely.

You did not provide a ton of specific detail as to what you want to do, so rather than guessing I’ll leave my reply intentionally vague and allow you to fill in the blanks as you need :slight_smile:

Thank you! This does appear to be what I need to do.

I have not used the task functionality. Would the user data sent with lv_task_create(…) be the flag I would set with the button callback event?

I never use the user data functionality, but yes that is probably the right way to do it. I prefer to just use global variables even though it’s not usually considered ‘good’ programming.

I’m having some trouble with this, I have provided some trimmed down code. Any help would be appreciated.

The task correctly runs the function one time but then the GUI freezes.

General flow of the code:

  1. User clicks the mbox trigger button, then a message box with a button appears.
  2. When the user clicks the button on this message box the flag switches and the task should start triggering the function.
  3. The button on the message box changes to ‘End’. GUI freezes after the button changes, the task does not execute again.
  4. The user presses the button on the message box and the task should end.

I really appreciate the help. I’m hoping I just don’t understand something basic and it’s a quick fix. Here are some snippets of relevant code.

static void mbox_trigger(lv_obj_t * obj, lv_event_t event){
    if (event == LV_EVENT_PRESSED && lv_btn_get_state(obj) == LV_BTN_STATE_PR) {
	    lv_task_t * task = lv_task_create(run_environmental_test, 0, LV_TASK_PRIO_HIGHEST, NULL);
        create_msg_box();//Function which creates the message box and buttons
    }
}
static void mbox_run_event_cb(lv_obj_t *obj, lv_event_t evt) {
//Run button was clicked
	if (evt == LV_EVENT_VALUE_CHANGED) {
		flag = 1;
        //Stuff
		lv_obj_set_event_cb(msg_box, mbox_end_event_cb); //Button changes to "End"
	}
}
static void mbox_end_event_cb(lv_obj_t *obj, lv_event_t evt) {
//End Button was clicked	
    if (evt == LV_EVENT_VALUE_CHANGED) {
		flag = 0;
        //Stuff
	}
}
void run_test(lv_task_t * t) {
	if (flag) {
		//Stuff
	}
}

int main(void){
    run_GUI();
    while (1) {
		lv_task_handler();
		usleep(5 * 1000);
    }
}

First off, is run_GUI() an infinite loop? If so, this is incorrect as it never gives the task handler a chance to run. I only ask because in my mind a function with the prefix run means that it is going to run infinitely until the thread is ended.

Second, you do not explicitly need to create separate callback functions for start/stop events. Presumably you are only changing the text of that mbox button, and if so you can simply toggle the flag inside your single callback and then change the label of the button. For example,

static void mbox_event_cb(lv_obj_t *obj, lv_event_t evt) {
//Button was clicked
	if (evt == LV_EVENT_VALUE_CHANGED) {
		flag ^= 1;
                char temp[12];
                sprintf(temp, "%s", flag ? "Running" : "Halted");
                lv_mbox_set_text(mbox1, temp);
                if (flag)
                    ; // flag == true stuff
                else
                    ; // flag == false stuff
	}
}

I don’t think you can do this. I don’t see any way to modify the label of the button inside the message box (more specifically - inside the button matrix inside the mbox). Short of creating your own custom mbox-like object you might want to create three buttons - On, Off, Close (or merge the Off/Close into the same functionality if that suits your needs).

I think you could call lv_mbox_add_btns inside the event handler. The naming is inaccurate; that function replaces the existing button map rather than adding to it.

1 Like

First off, is run_GUI() an infinite loop?

No, the run_GUI() function that initalizes the lvgl library and HAL and then creates the GUI. Probably would be better called init_GUI(). I can confirm that the program does correctly enter the while loop.

I redid the popup to have the three buttons, as that seems cleaner than attempting to change the buttons. However I still cannot get the get the task to repeatedly run the function I need it to. After the On button is pressed, the function runs once then the GUI freezes and I cannot make any more inputs.

Am I creating the task in the correct place (create_msg_box function)?

static void btn_event_handler(lv_obj_t * obj, lv_event_t event) {
// This function triggers the message box to pop up
	if (event == LV_EVENT_PRESSED && lv_btn_get_state(obj) == LV_BTN_STATE_PR) {
                create_msg_box(); // setups the message box
			}
}

void create_msg_box() {
	// creates and formats message box
    //I cut out some formatting stuff I don't believe is relevant

	static const char * run_btns[] = { "On", "Off", "Close", "" };
	lv_mbox_add_btns(msg_box, run_btns);
	lv_mbox_set_text(msg_box,
			"Click 'On' to to start Test.\nClick 'Off' to stop.\n\n");
	lv_obj_set_event_cb(msg_box, mbox_run_event_cb);
	lv_task_t * task = lv_task_create(run_environmental_test, 500,
			LV_TASK_PRIO_HIGHEST, &flag);
}

static void mbox_run_event_cb(lv_obj_t *obj, lv_event_t evt) {
	if (evt == LV_EVENT_DELETE && obj == msg_box) {
		lv_obj_del_async(lv_obj_get_parent(msg_box));
		msg_box = NULL; /* happens before object is actually deleted! */
	} else if (evt == LV_EVENT_VALUE_CHANGED) {
		if (lv_mbox_get_active_btn_text(obj) == "On") {
			flag = 1; //Flag should trigger the function within the task to run.
			std::cout << "On" << std::endl;
		} else if (lv_mbox_get_active_btn_text(obj) == "Off") {
			flag = 0;
			std::cout << "Off" << std::endl;
		} else if (lv_mbox_get_active_btn_text(obj) == "Close") {
			flag = 0;
			std::cout << "Close" << std::endl;
			lv_mbox_start_auto_close(msg_box, 0);
		}
	}
}

void run_test(lv_task_t * t) {
	if (flag) {
		//Other stuff
		std::cout << "Complete" << std::endl;
	}
}

There are a few things that look off in your code. I modified to get your example to work in my test code. Specifically your btn_event_handler is using the wrong flag. LV_EVENT_PRESSED will return true every time the callback is called until the button is released - you only want it to run one time.

I also explicitly created a new msg_box object as I don’t see you actually creating the object in your sample above (presumably this was created elsewhere).

Here’s the sample I’ve got working in my simulator.

main.c

#if WIN32
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int nCmdShow)
#else
int main(int argc, char** argv)
#endif // WIN32
{
    /*Initialize LittlevGL*/
    lv_init();

    /*Initialize the HAL for LittlevGL*/
    hal_init();

    /*Load a demo*/
    //demo_create();

    /*Try the benchmark to see how fast is your GUI*/
    //benchmark_create();

    //sysmon_create();
    //terminal_create();
    //tpcal_create();

    /*Check the themes too*/
    lv_disp_set_default(lv_windows_disp);
    //demo_create();
    menu_create();
    //lv_test_theme_2();
    //lv_tutorial_animations();
#if WIN32
    while(!lv_win_exit_flag) {
#else
    while(1) {
#endif // WIN32
        /* Periodically call the lv_task handler.
         * It could be done in a timer interrupt or an OS task too.*/
        lv_task_handler();
        usleep(1000);       /*Just to let the system breath*/
    }
    return 0;
}

menu.c

static int flag = 0;
static lv_obj_t *msg_box;
static lv_obj_t *cont;

static void btn_event_handler(lv_obj_t * obj, lv_event_t event);
void create_msg_box();
static void mbox_run_event_cb(lv_obj_t *obj, lv_event_t evt);
void run_test(lv_task_t * t);

static void btn_event_handler(lv_obj_t * obj, lv_event_t event) {
// This function triggers the message box to pop up
	if (event == LV_EVENT_CLICKED) {
        create_msg_box(); // setups the message box
    }
}

void create_msg_box() {
	// creates and formats message box
    //I cut out some formatting stuff I don't believe is relevant

	static const char * run_btns[] = { "On", "Off", "Close", "" };
	cont = lv_cont_create(lv_scr_act(), NULL);
	lv_obj_set_pos(cont, 10, 10); // x.y = 10.10
    // 460x198 pixels - 10px padding
    lv_obj_set_size(cont, 460, 212);
    lv_cont_set_fit(cont, LV_FIT_NONE); // Container is static
    // We want our layout to be a grid, and align our children as such
    lv_cont_set_layout(cont, LV_LAYOUT_PRETTY);

	msg_box = lv_mbox_create(cont, NULL);
	lv_mbox_add_btns(msg_box, run_btns);
	lv_mbox_set_text(msg_box,
			"Click 'On' to to start Test.\nClick 'Off' to stop.\n\n");
	lv_obj_set_event_cb(msg_box, mbox_run_event_cb);
	lv_task_t * task = lv_task_create(run_test, 500,
			LV_TASK_PRIO_HIGHEST, &flag);
}

static void mbox_run_event_cb(lv_obj_t *obj, lv_event_t evt) {
	if (evt == LV_EVENT_DELETE && obj == msg_box) {
		lv_obj_del_async(lv_obj_get_parent(msg_box));
		msg_box = NULL; /* happens before object is actually deleted! */
	} else if (evt == LV_EVENT_VALUE_CHANGED) {
		if (strcmp(lv_mbox_get_active_btn_text(obj), "On") == 0) {
			flag = 1; //Flag should trigger the function within the task to run.
			lv_mbox_set_text(msg_box, "Flag is On!");
		} else if (strcmp(lv_mbox_get_active_btn_text(obj), "Off") == 0) {
			flag = 0;
			printf("Off\n");
			lv_mbox_set_text(msg_box, "Flag is Off!");
		} else if (strcmp(lv_mbox_get_active_btn_text(obj), "Close") == 0){
			flag = 0;
			lv_mbox_set_text(msg_box, "Closing!!");
			lv_mbox_start_auto_close(msg_box, 0);
		}
	}
}

void run_test(lv_task_t * t) {
    static int counter = 0;
	if (flag) {
		//Other stuff
		counter++;
        char temp[12];
        sprintf(temp, "Task %d", counter);
		lv_mbox_set_text(msg_box, temp);
	}
}

/**
 * Create a menu application
 */
void menu_create(void)
{
    lv_obj_t *btn = lv_btn_create(lv_scr_act(), NULL);
    lv_obj_set_event_cb(btn, btn_event_handler);
}

Thank you for the help. I was able to figure out the issue. The function I was calling was adding text to a text box and apparently there is a limit to how much text can be there. Clearing the text in the textbox ( i.e. lv_ta_set_text(text_box, “”) )at the beginning of the task function allowed it to work.

I have made some of the code improvements you suggested. I really appreciate the help!

Well, that’s interesting. Out of curiosity just how much text were you stuffing in there? Were you meaning to append the new data to the old, or overwrite it? I’ve gotten nowhere near this amount in a text area, but the documentation talks about 20k+ characters in a scrolling text area!

It appears I was close to 9.5k characters. So if the limit is 20k, I’m not sure why it was freezing. It appears to be working correctly now. I just let it run for over 1000 times in a row and it had no issues.

The text area has worked with over 32 kilobytes of text in the past (I’ve needed it), so either something is broken with LittlevGL now or something is wrong with @kw531’s setup.

I can try it again on the weekend (if I remember :slightly_smiling_face:).

It’s probably something in my setup but I don’t even know where to start looking for what could be the issue. Here is what I do to create the textbox.

    static lv_style_t text_box_style;
	lv_style_copy(&text_box_style, &lv_style_scr);
	text_box_style.text.font = &lv_font_roboto_22;

	mm_textbox = lv_ta_create(parent, NULL);
	lv_obj_set_style(mm_textbox, &text_box_style);
	lv_obj_set_size(mm_textbox, 760, 195);
	lv_obj_align(mm_textbox, btn_SIU_BIT, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6);
	lv_ta_set_cursor_type(mm_textbox, LV_CURSOR_NONE);
	lv_ta_set_cursor_click_pos(mm_textbox, 0);
	lv_ta_set_text(mm_textbox, "");
	add_text_status("Waiting for Commands...");

Text is added to the box via this function.

void add_text_status(const char * text) {
	lv_ta_add_text(mm_textbox, text);
}

My program outputs a large amount of data, so a lot of functions are adding data to the textbox.

Could you run strlen(lv_ta_get_text(mm_textbox)) every so often? It will get slow when you have a lot of data but that way we could see the approximate length that it reaches.

After doing this, it appears that that it’s capping out at 9k characters.

What is the size of your heap? Maybe there’s not enough RAM to store more characters. Usually LittlevGL freezes on an assertion if a memory allocation returns NULL.