clydebarrow / esphome

ESPHome is a system to control your ESP8266/ESP32 by simple yet powerful configuration files and control them remotely through Home Automation systems.
https://esphome.io/
Other
23 stars 12 forks source link

Custom component/code integration for pages, widgets, etc. #109

Open segfly opened 4 months ago

segfly commented 4 months ago

I'd like to use a LVGL designer like EEZ Studio to generate the more complex pages, widgets, etc. and use the yaml syntax for the simpler stuff like simple pages/widgets, event handling, setup, etc.

Do you have any suggestions on how one might inject code within the generated pages and widgets blocks? Lambdas don't appear to be supported right now.

I've gotten pretty far by adding the generated code from EEZ Studio to the esphome includes: yaml. EEZ generates simple methods for each of the screens and child components (e.g. create_screen_main()) that registers a handle to a global objects struct.

Here is an example of an EEZ Studio generated create function:

void create_screen_main() {
    lv_obj_t *obj = lv_obj_create(0);
    objects.main = obj;
    lv_obj_set_pos(obj, 0, 0);
    lv_obj_set_size(obj, 240, 135);
    lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
    {
        lv_obj_t *parent_obj = obj;
        {
            // label_1
            lv_obj_t *obj = lv_label_create(parent_obj);
            objects.label_1 = obj;
            lv_obj_set_pos(obj, 0, 0);
            lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
            lv_label_set_long_mode(obj, LV_LABEL_LONG_CLIP);
            lv_label_set_text(obj, "Hello, world!");
            lv_obj_set_style_align(obj, LV_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
        }
    }
}

Using an empty page/widgets block:

lvgl:
  pages:
    - id: test_page
      widgets:

The LVGL component generates the following:

  test_page = new LvPageType();
  lvgl_component->add_init_lambda([=](lv_disp_t * lv_disp) {
      lvgl_component->add_page(test_page);
      test_page->index = 0;
      test_page->page = lv_obj_create(nullptr);
      test_page->skip = false;
      lvgl_component->set_page_wrap(true);

  });

If I could just replace or later reassign test_page->page = lv_obj_create(nullptr); I think I could use most of the generate code from EEZ Studio. The problem, is I can't find a good way to inject the code:

// Needs to be injected after lvgl_component->add_init_lambda
lv_obj_clean(test_page->page);
create_screen_main();
test_page->page = objects.main;

Placing this in a on_boot: lambda injects the code way too early in setup(). And trying to put it in on_loop: seems to be too late to have an effect.

It would be nice if I could do something like this for "external" pages/widgets along with yaml-only delcared pages/widgets:

lvgl:
  style_definitions:
    - id: custom_style
      lambda: |-
        ...
        return style;   
  pages:
    - id: test_page
      lambda: |-
        create_screen_main();
        return objects.main;      
    - id: another_page
      widgets:
      - label:
          align: CENTER
          id: lbl_id
          styles: custom_style          
          recolor: true
          text: "#FF0000 write# #00FF00 colored# #0000FF text#"
      - id: custom_widget
        lambda: |-
          ...
          return widget; 

And so on for the other LVGL types.

But I could be looking at this all the wrong way... Do you have a better idea on how to streamline the UI design process using this esphome component? While it's great to do simple stuff, the yaml->compile->upload->test cycle is very slow - lots trial/error and waiting?

segfly commented 4 months ago

@clydebarrow I've create a proof of concept for the lambda assignment of pages: https://github.com/segfly/esphome-lvgl

Basically, when the lambda: property is present, it takes the return value and assigns to {page.obj} struct instead of the generated lv_obj_create call. This allows a great deal of flexibility for page creation outside of yaml, while still allowing for other parts of the LVGL ESPHome component to interact with the "injected" page.

The only (minor) issues I ran into were with the EEZ Studio generated code, I had to change the default generation templates to specify the right "lvgl.h" path and add a fake method for getFlowState call, which I think is generated as a bug in their code for LVGL-only projects.

Once that was done, I can now design pages in the EEZ Studio, and then use with the LVGL ESPHome component.

Here is a snippet of what the yaml looks like:

sensor:
  - platform: uptime
    id: sys_uptime
    update_interval: 1s
    on_value:
      then:
        - lambda: lv_label_set_text(objects.label_uptime, std::to_string(static_cast<int>(id(sys_uptime).get_state())).c_str()); 

button:
  - platform:
    name: Show Uptime
    on_press:
      - lvgl.page.show: uptime_page 

lvgl:
  pages:
    - id: welcome_page
      widgets:
      - label:
          align: CENTER
          text: "Welcome!"
    - id: uptime_page
      lambda: |-
        create_screen_uptime_page();
        return objects.uptime_page; 

Thoughts?

nagyrobi commented 4 months ago

Let's tun things around: how about discussing with EEZ project (after this is merged) so they could directly output ESPHome yaml?

segfly commented 4 months ago

A similar thought crossed my mind, but that seems like a huge amount of work. It would be wonderful though, I don't disagree.

I was thinking as a short-term solution, this simple lambda approach provides a lot of flexibility - plus, it theoretically can be used with any other LVGL designer, like SquareLine Studio.

nagyrobi commented 4 months ago

yaml->compile->upload->test

You could use ESPHome host + vnc to speed things up considerably.

segfly commented 4 months ago

Ah interesting. I did not know this existed, probably because it's not documented: https://github.com/esphome/feature-requests/issues/2388#issuecomment-1913045915

It won't make UI design as fast or easy as using a UI designer, but will certainly streamline integration testing, before testing on device. Thanks.

clydebarrow commented 2 months ago

The sdl display driver is even simpler to use than VNC now, and it's merged into ESPHome release.