Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LVGL] Add a way to control when screen is created #706

Closed
mvladic opened this issue Jan 19, 2025 · 13 comments
Closed

[LVGL] Add a way to control when screen is created #706

mvladic opened this issue Jan 19, 2025 · 13 comments

Comments

@mvladic
Copy link
Contributor

mvladic commented Jan 19, 2025

By default, all screens (i.e. pages) are created at startup (during ui_init() call) and kept in memory at all times. In case there is not enough memory for this mode of operation, it is possible to manually set when a screen is created and when it is deleted during execution. First, the following setting should be enabled:

Image

When this setting is enabled, it is possible to decide for each screen whether it will be created at the startup, and also whether it will be deleted on screen unload event:

Image

Change screen will always automatically create a screen if not already created.

Also, there are additional LVGL actions to manually create and delete screen, and also find out whether is screen created:

Image

This is also available from the code:

void create_screen_by_id(enum ScreensEnum screenId);
void delete_screen_by_id(enum ScreensEnum screenId);

To find out whether is screen created from the code, you can check objects.<screen_name> - it should be non zero.

Some caveats:

  • When screen is deleted then its flow state is also deleted, for example all local variables and all Watch actions. So, if you need to remember the state of some variable then you should use global instead of local variable. If you need to have some Watch action that runs all the time then you can create a page without any widget but with the Watch action and this page should be created at the start.
  • If, in your flow, widget in one screen is changed with the LVGL action from another screen then care must be taken to only do that if screen has been created.
@zalexzperez
Copy link

zalexzperez commented Jan 19, 2025

This is a promising idea! For clarity's sake let's call it the modular approach if you will.

But let me propose a different one here: the group approach. It would be similar to what we already have with widgets and the encoder/keypad groups for navigation.

With the group approach, the programmer wouldn't have to worry about managing which screen objects are already created and which ones require deleting. Upon adding new screens to the project in the future, the programmer would only have to assign the screen group to them. So it would escalate better. And to make it as flexible, a screen could belong to multiple groups.

So, when ui_init() call is made, the default group is loaded. Afterwards, the programmer would only use the Change screen group action widget/function.

What do you think?
Assuming what I said is right, I think it seems to be considerably harder to implement anyway, so it may not be worth it.

Edit:
How would all of this play with the Change Screen / Change to Previous Screen actions and the EEZ functions?
What if the screen we are requesting belongs to an inactive screen group?
I see we would need extra actions to retrieve information, defeating the initial idea.

@mvladic
Copy link
Contributor Author

mvladic commented Jan 19, 2025

... the group approach ...
What do you think?

I think we should eventually address this in phase 2, because it is more complex to implement.

How would all of this play with the Change Screen / Change to Previous Screen actions and the EEZ functions?

Change Screen / Change to Previous Screen will automatically create screen if it is not currently created.

We can also automatically delete screen if it is not active anymore. But, this should be an option where you can choose to "Keep in memory" or "Remove from memory" when inactive.

What if the screen we are requesting belongs to an inactive screen group?

In that case we can switch to that group - I suppose switch means: release screens of current active group and create screens of the new group and make it active. But, I can think of the situation in the really complex application where you want multiple groups to be active at the same time.


I have on question for you. In your example where you can have normal execution of the app and OTA update execution (10 additional screens), does this means that either app is run in Normal mode or OTA Update mode, i.e. you are going from one mode to another only after reset?

@zalexzperez
Copy link

zalexzperez commented Jan 19, 2025

I have on question for you. In your example where you can have normal execution of the app and OTA update execution (10 additional screens), does this means that either app is run in Normal mode or OTA Update mode, i.e. you are going from one mode to another only after reset?

Yes, in my case the app only works in one mode at a time and the working mode is changed after a reset.

By doing this, I managed to reduce system heap usage and have a working OTA update mode at the expense of the normal mode, as the added screens for the updater part demand an increase in LV_MEM_SIZE. However, increasing that memory pool compromises the update mode again.

Edit:
This lazy loading feature would not just help with the update/normal modes matter, though.
For instance, I could as well avoid loading all my menu screens which aren't used unless user enters the menu, further reducing the required LV_MEM_SIZE.

mvladic added a commit that referenced this issue Feb 2, 2025
@mvladic
Copy link
Contributor Author

mvladic commented Feb 2, 2025

This is now implemented. Check this issue description to see what are the changes.

@mvladic
Copy link
Contributor Author

mvladic commented Feb 2, 2025

@zalexzperez In your case when you have main application screens and OTA update screens, you can set all screens (except Main) to have "Create at start" disabled. Then, in the Main screen you can have the following:

Image

Note that "Change Screen" will automatically create screen if not already created.

@markonovic00
Copy link

Hello @mvladic I would like to know when can be this feature used in the EEZ studio. Thanks in advance.

@mvladic
Copy link
Contributor Author

mvladic commented Feb 2, 2025

@markonovic00 This feature will be in the next release (v0.22.0) which should be out in a week or two. You can build studio from source code if you want to try it now.

@markonovic00
Copy link

I built the EEZ Studio.

I am running into an issue where the ESP32 restarts when lv_obj_del() is called. I am using simple LVGL projects and I checked the checkbox in the settings.

Image

I had this same issue when I tried working on it alone.

I would like to say that I am using variables on the screens so that is causing the issue maybe as well.

@mvladic
Copy link
Contributor Author

mvladic commented Feb 2, 2025

Check if some object is used after lv_obj_del is called - note that if screen is deleted then all the objects that are children of the that screen will also be deleted.

If you can send me your eez-project file and all the relevant source code maybe I can help you more.

@markonovic00
Copy link

markonovic00 commented Feb 2, 2025

I disabled the tick function of the screens so there should be no usage of the objects. My steps are the following:

  1. Load middle screen - Simple blank screen used for transition
  2. Delete old screen
  3. Create new screen
  4. Load new screen

What I discovered is that after a couple of back and forth the issue is present. So my assumption is that there is a problem with memory fragmentation.

mvladic added a commit that referenced this issue Feb 3, 2025
@mvladic
Copy link
Contributor Author

mvladic commented Feb 3, 2025

@markonovic00 You can't delete old screen until screen transition from old screen to new screen is done. To know when it is done you need to handle LV_EVENT_SCREEN_UNLOADED event for the old screen.

According to the screenshot you send I can see that your are not using EEZ Flow. In that case, you can do something like this:

lvgl_change_screen_no_flow.zip

In this project only the Main screen is created at the start, and only the active screen is kept in memory. Check the changeToScreen in actions.c to see how this is implemented:

#include <stdint.h>

#include "actions.h"
#include "screens.h"
#include "ui.h"

////////////////////////////////////////////////////////////////////////////////

static lv_obj_t *getScreenObj(enum ScreensEnum screenId) {
    return ((lv_obj_t **)&objects)[screenId - 1];
}

static void on_screen_unloaded(lv_event_t *e) {
    if (lv_event_get_code(e) == LV_EVENT_SCREEN_UNLOADED) {
        enum ScreensEnum screenId =
            (enum ScreensEnum)(lv_uintptr_t)lv_event_get_user_data(e);
        delete_screen_by_id(screenId);
    }
}

static void deleteScreenOnUnload(enum ScreensEnum screenId) {
    lv_obj_add_event_cb(
        getScreenObj(screenId),
        on_screen_unloaded,
        LV_EVENT_SCREEN_UNLOADED,
        (void*)(lv_uintptr_t)(screenId)
    );    
}

static void changeToScreen(enum ScreensEnum screenId) {
    if (!getScreenObj(screenId)) {
        create_screen_by_id(screenId);
        if (!getScreenObj(screenId)) {
            return;
        }
        deleteScreenOnUnload(screenId);
    }

    loadScreen(screenId);
}

////////////////////////////////////////////////////////////////////////////////

void action_change_to_main_screen(lv_event_t *e) {
    changeToScreen(SCREEN_ID_MAIN);
}

void action_change_to_screen_1(lv_event_t *e) {
    changeToScreen(SCREEN_ID_SCREEN_1);
}

void action_change_to_screen_2(lv_event_t *e) {
    changeToScreen(SCREEN_ID_SCREEN_2);
}

mvladic added a commit that referenced this issue Feb 3, 2025
mvladic added a commit that referenced this issue Feb 3, 2025
@mvladic
Copy link
Contributor Author

mvladic commented Feb 3, 2025

I just made a change where I simplified a bit the way "Delete on unload" works internally.

@markonovic00
Copy link

@mvladic This is working great. I noticed one funny thing.

I have 3 screens, one without TabView and 2 with TabView. So here are some funny scenarios:

  1. Without TabView going to screen with TabView no problem
  2. TabView going to screen with TabView problem
  3. TabView to screen without TabView no problem

It is working now, I adjusted the navigation a little bit and it is working like a charm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants