Lv_scale animations with C++ class

Description

I am trying to implement round scale with needle-line (aka Speedometer) inside C++ class and function that run animation of moving needle to some value. When i am passing class object pointer to callback function, that invokes lv_scale_set_line_needle_value, my data corrupts and lv_scale_set_line_needle_value retruns nothing becourse scale->mode is random numbers (should be 8)

What MCU/Processor/Board and compiler are you using?

G++5 Linux Ubuntu 16.04 SDL simulator

What LVGL version are you using?

9.1 master

What do you want to achieve?

Class function called by class object run animation of moving needle line to set value.

What have you tried so far?

  1. Lambda for lv_anim_set_custom_exec_cb / lv_anim_set_exec_cb
  2. Passing objects with lv_anim user_data
    3.Passing objects with void * obj to callack function

Code to reproduce


/**
 * @file main
 *
 */

/*********************
 *      INCLUDES
 *********************/
#define _DEFAULT_SOURCE /* needed for usleep() */
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include "lvgl/lvgl.h"
//#include "lvgl/examples/lv_examples.h"
#include "lvgl/demos/lv_demos.h"
#include "main.h"

#include <sys/time.h>
#include <stdint.h>


#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#include <signal.h>


#include <functional>


/*********************
 *      DEFINES
 *********************/
pthread_mutex_t lock;
timer_t timer_id;

// Обработчик сигнала таймера
void timer_handler(int signum) {
  //pthread_mutex_lock(&lock);
    // Увеличить таймер LVGL
    lv_tick_inc(5);
   // pthread_mutex_unlock(&lock);

}

// Создать и запустить таймер LVGL
void lvgl_timer_init() {
    // Создать таймер
    struct sigevent sev;
    sev.sigev_notify = SIGEV_SIGNAL;
    sev.sigev_signo = SIGRTMIN;
    sev.sigev_value.sival_ptr = &timer_id;
    timer_create(CLOCK_REALTIME, &sev, &timer_id);

    // Установить интервал таймера
    struct itimerspec its;
    its.it_interval.tv_sec = 0;
    its.it_interval.tv_nsec = 5 * 1000 * 1000;  // 5 миллисекунд
    its.it_value.tv_sec = 0;
    its.it_value.tv_nsec = 5 * 1000 * 1000;  // 5 миллисекунд
    timer_settime(timer_id, 0, &its, NULL);

    // Установить обработчик сигнала таймера
    signal(SIGRTMIN, timer_handler);
}
/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static lv_display_t * hal_init(int32_t w, int32_t h);

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *      VARIABLES
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/



class Meter {
public:
    Meter(lv_obj_t * parent, const char * title, const char * text1, int pos_x, int pos_y, int min_value, int max_value, int start, int end);
    ~Meter();
    static void setValue(lv_anim_t * obj, int32_t val);
    static void anim_ready_cb(lv_anim_t * a);
    void runAnim(int32_t val);
    void getScale() {this->scale;};
    void getNeedle() {this->needle_line;};

    int32_t value = 0;
    int32_t ticks_count = 40 + 1;
    lv_obj_t * scale;
    lv_obj_t * needle_line;
    lv_anim_t * anim_scale_line2;
    void invalidateScale() {lv_obj_invalidate(this->needle_line);};
    void sett(int32_t val);
private:

};

Meter::Meter(lv_obj_t * parent, const char * title, const char * text1, int pos_x, int pos_y, int min_value, int max_value, int start, int end) {
    printf("Creating meter!\n");
    int32_t ticks_count = 40 + 1;
    lv_obj_t * scale = lv_scale_create(parent);
    lv_scale_set_mode(scale, LV_SCALE_MODE_ROUND_INNER);
    lv_scale_set_post_draw(scale, true);
    lv_obj_set_width(scale, LV_PCT(100));
    lv_obj_set_pos(scale, pos_x, pos_y);
    lv_scale_set_angle_range(scale, 220);
    lv_scale_set_rotation(scale, 160);
    lv_obj_set_size(scale, 150, 150);
    lv_scale_set_range(scale, min_value, max_value);
    lv_scale_set_total_tick_count(scale, ticks_count);
    lv_scale_set_major_tick_every(scale, (ticks_count / 4)); // big ticks count - 1
    lv_scale_set_label_show(scale, true);
    lv_obj_set_style_radius(scale, LV_RADIUS_CIRCLE, 0);
    lv_obj_set_style_length(scale, 10, LV_PART_INDICATOR | LV_STATE_DEFAULT);
    lv_obj_set_style_length(scale, 100, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_length(scale, 5, LV_PART_ITEMS | LV_STATE_DEFAULT);
    lv_obj_t * needle_line = lv_line_create(scale);
    lv_obj_set_style_line_width(needle_line, 6, LV_PART_MAIN);
    lv_obj_set_style_line_rounded(needle_line, true, LV_PART_MAIN);
    lv_obj_set_pos(needle_line, 0, 0);
    lv_scale_set_line_needle_value(scale, needle_line, 60, value);

    lv_obj_t * text = lv_label_create(scale);
    lv_obj_set_pos(text, 0, 102);
    lv_obj_set_size(text, LV_PCT(100), LV_PCT(30));
    lv_label_set_text(text, text1);
    lv_obj_set_style_bg_color(text, lv_color_hex(0xff000000), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_color(text, lv_color_hex(0xffffffff), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(text, LV_OPA_MAX, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_align(text, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
    printf("global %d\n", value);



}
void Meter::setValue(lv_anim_t * obj, int32_t val) {
      printf("Executing animation  %d\n", val);
      Meter *self = static_cast<Meter *>(lv_anim_get_user_data(obj));
      lv_scale_t *j = (lv_scale_t*)(self->scale);
      lv_obj_t *scale = static_cast<lv_obj_t *>(lv_anim_get_user_data(obj));
      lv_scale_set_line_needle_value((static_cast<Meter *>(lv_anim_get_user_data(obj)))->scale, (static_cast<Meter *>(lv_anim_get_user_data(obj)))->needle_line, 60, val);

      self->value = val;

}



void Meter::runAnim(int32_t val) {
  printf("Prepearing animation!\n");
     lv_anim_t anim_scale_line2;
    lv_anim_init(&anim_scale_line2);
    lv_anim_set_var(&anim_scale_line2, this->scale);
    lv_anim_set_completed_cb(&anim_scale_line2, anim_ready_cb);
    lv_anim_set_user_data(&anim_scale_line2, this);
    lv_anim_set_custom_exec_cb(&anim_scale_line2, setValue);
    lv_anim_set_duration(&anim_scale_line2, 2000);
    lv_anim_set_repeat_count(&anim_scale_line2, 0);

    lv_anim_set_values(&anim_scale_line2, 0, val);
    lv_anim_start(&anim_scale_line2);
    printf("Scale pointer is %");
}

void Meter::anim_ready_cb(lv_anim_t * a) {
  printf("Animation Completed!\n");
  Meter  *value = static_cast<Meter *>(lv_anim_get_user_data(a));
  printf("global %d\n", value->value);
}
Meter::~Meter() {};


/**********************
 *   GLOBAL FUNCTIONS
 **********************/


int main(int argc, char **argv)
{


  (void)argc; /*Unused*/
  (void)argv; /*Unused*/
  //lv_tick_set_cb(get_milliseconds);
  /*Initialize LVGL*/
  lv_init();

  /*Initialize the HAL (display, input devices, tick) for LVGL*/
  hal_init(1024, 768);

  //lv_demo_widgets();
  //create_screen_main();
  //lv_example_scale_6();
  lvgl_timer_init();

Meter Meter1(lv_screen_active(), "1", "1", 140, 201, 0, 120, 90, 120);

Meter1.runAnim(80);

  while(1) {
    /* Periodically call the lv_task handler.
     * It could be done in a timer interrupt or an OS task too.*/

    lv_refr_now(NULL);
    usleep(5 * 1000);
    lv_timer_handler();
  }

  return 0;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/**
 * Initialize the Hardware Abstraction Layer (HAL) for the LVGL graphics
 * library
 */



static lv_display_t * hal_init(int32_t w, int32_t h)
{

  lv_group_set_default(lv_group_create());

  lv_display_t * disp = lv_sdl_window_create(w, h);

  lv_indev_t * mouse = lv_sdl_mouse_create();
  lv_indev_set_group(mouse, lv_group_get_default());
  lv_indev_set_display(mouse, disp);
  lv_display_set_default(disp);


  lv_indev_t * mousewheel = lv_sdl_mousewheel_create();
  lv_indev_set_display(mousewheel, disp);
  lv_indev_set_group(mousewheel, lv_group_get_default());

  lv_indev_t * kb = lv_sdl_keyboard_create();
  lv_indev_set_display(kb, disp);
  lv_indev_set_group(kb, lv_group_get_default());

  return disp;
}


Screenshot and/or video

class Meter
{
  public:
    Meter(lv_obj_t * parent, const char * title, const char * text1, int pos_x, int pos_y, int min_value, int max_value, int start, int end);
    ~Meter();
    void set(int32_t val, lv_anim_enable_t anim_enable = LV_ANIM_ON);
    inline int32_t get()       { return this->value; }
  private:
    int32_t value = 0;
    lv_obj_t * scale;
};

Meter::Meter(lv_obj_t * parent, const char * title, const char * text1, int pos_x, int pos_y, int min_value, int max_value, int start, int end)
{
  printf("Creating meter!\n");
  int32_t ticks_count = 40 + 1;
  this->scale = lv_scale_create(parent);
  lv_scale_set_mode(scale, LV_SCALE_MODE_ROUND_INNER);
  lv_scale_set_post_draw(scale, true);
  lv_obj_set_width(scale, LV_PCT(100));
  lv_obj_set_pos(scale, pos_x, pos_y);
  lv_scale_set_angle_range(scale, 220);
  lv_scale_set_rotation(scale, 160);
  lv_obj_set_size(scale, 150, 150);
  lv_scale_set_range(scale, min_value, max_value);
  lv_scale_set_total_tick_count(scale, ticks_count);
  lv_scale_set_major_tick_every(scale, (ticks_count / 4)); // big ticks count - 1
  lv_scale_set_label_show(scale, true);
  lv_obj_set_style_radius(scale, LV_RADIUS_CIRCLE, 0);
  lv_obj_set_style_length(scale, 10, LV_PART_INDICATOR | LV_STATE_DEFAULT);
  lv_obj_set_style_length(scale, 100, LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_length(scale, 5, LV_PART_ITEMS | LV_STATE_DEFAULT);
  lv_obj_t * needle_line = lv_line_create(scale);
  lv_obj_set_style_line_width(needle_line, 6, LV_PART_MAIN);
  lv_obj_set_style_line_rounded(needle_line, true, LV_PART_MAIN);
  lv_obj_set_pos(needle_line, 0, 0);
  lv_scale_set_line_needle_value(scale, needle_line, 60, value);

  lv_obj_t * text = lv_label_create(scale);
  lv_obj_set_pos(text, 0, 102);
  lv_obj_set_size(text, LV_PCT(100), LV_PCT(30));
  lv_label_set_text(text, text1);
  lv_obj_set_style_bg_color(text, lv_color_hex(0xff000000), LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_text_color(text, lv_color_hex(0xffffffff), LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_bg_opa(text, LV_OPA_MAX, LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_text_align(text, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
  printf("global %d\n", value);

}

void Meter::set(int32_t val, lv_anim_enable_t anim_enable)
{
  if(anim_enable == LV_ANIM_OFF) {
    this->value = val;
    lv_obj_t * needle_line = lv_obj_get_child_by_type(this->scale, 0, &lv_line_class);
    lv_obj_t * text = lv_obj_get_child_by_type(this->scale, 0, &lv_label_class);
    lv_scale_set_line_needle_value(this->scale, needle_line, 60, this->value);
    lv_label_set_text_fmt(text, "%d", this->value);
  }
  else {
    lv_anim_t anim;
    lv_anim_init(&anim);
    lv_anim_set_var(&anim, this);
    lv_anim_set_custom_exec_cb(&anim, [](lv_anim_t * a, int32_t v) {
      Meter * meter = static_cast<Meter *>(a->var);
      meter->set(v, LV_ANIM_OFF);
    });
    lv_anim_set_completed_cb(&anim, [](lv_anim_t * a) {
      printf("Animation Completed!\n");
      Meter * meter = static_cast<Meter *>(a->var);
      printf("global %d\n", meter->get());
    });
    lv_anim_set_duration(&anim, 2000);
    lv_anim_set_repeat_count(&anim, 0);
    lv_anim_set_values(&anim, 0, val);
    lv_anim_start(&anim);
  }
}

Meter::~Meter() {};

Usage Example :

  Meter Meter1(lv_screen_active(), "1", "1", 140, 201, 0, 120, 90, 120);
  Meter1.set(80);                    // set value with ANIM
  // Meter1.set(80, LV_ANIM_OFF);    // set value without ANIM 
1 Like

Thounsand thanks, sir! It works!

1 Like

Hello @TridentTD and @ZooM
Thank you for the relevant question and answer🙂
I’m also looking into creating a class and animating or moving the meter scale elements.
As a LVGL simulator I use

Version 9.3

I took the

C++ class

from this topic as a basis.
The class is written according to the classic scheme using two files:

hdg_class.h
hdg_class.cpp

Archive in attachment.
hdg_class.zip (2.2 KB)

#include "lvgl.h"
#include "app_hal.h"
#include "hdg_class.h"

#ifdef ARDUINO
#include <Arduino.h>

void setup() {
  lv_init();
  hal_setup();
  // lv_demo_widgets();
}

void loop() {
  hal_loop();  /* Do not use while loop in this function */
}

#else

int main(void)
{
	lv_init();

	hal_setup();

  Hdg_class Meter1(lv_screen_active(),  // screen
                    "1",                // title
                    "Hello LVGL!",      // meter name
                    120,                // pos_x
                    120,                // pos_y
                    0,                  // min_value
                    300,                // max_value
                    250,                // size_x
                    250                 // size_y
                  );

  // Hdg_class Meter1(hdg_meter, "1", "Hello LVGL!", 150, 150, 0, 300, 90, 120);

  // Meter1.set(300);                    // set value with ANIM
  // Meter1.set(300, 5);                    // set value with ANIM and 5 repeat
  Meter1.set(75, LV_ANIM_OFF);    // set value without ANIM

	hal_loop();

  // lv_scale_set_rotation(hdg_scale, HDG);
}

#endif /*ARDUINO*/

I added a class to the ability to repeat the animation, using the function:
lv_anim_set_repeat_count(&anim, repeat);
But for some reason the animation is repeated only once, but if you add the value manually:
lv_anim_set_repeat_count(&anim, 3);
Then the repetition will be 3 times.
I also tried to make a looped animation back and forth, in the file

hdg_class.cpp

there is a commented code of the modified function at the end of the file.
But when you run this code, the animation is performed once, then the LVGL simulator application crashes and the console shows this error:

*** [execute] Error 3221225477

The animation looks good

In general, this is all just a joke, I was just looking for ways to translate the rendering functions into

C++ class

And I found it, thanks again to the author of the topic and for the answers.
I have a different question, are there ways to animate or control the entire scale rotation position?

I use the EEZ_studio program to draw graphics for LVGL.
In this program, on the STYLES tab, you can set the parameters for rotating the scale and pivot.
The code that the program exports is this

// HDG scale
hdg_scale = lv_scale_create(hdg_screen);
lv_obj_set_pos(hdg_scale, 40, 40);
lv_obj_set_size(hdg_scale, 400, 400);
lv_scale_set_mode(hdg_scale, LV_SCALE_MODE_ROUND_INNER);
lv_obj_set_style_bg_opa(hdg_scale, LV_OPA_60, 0);
lv_obj_set_style_bg_color(hdg_scale, lv_color_black(), 0);

lv_obj_set_style_transform_pivot_x(hdg_scale, 200, LV_PART_INDICATOR | LV_STATE_DEFAULT);
lv_obj_set_style_transform_pivot_y(hdg_scale, 200, LV_PART_INDICATOR | LV_STATE_DEFAULT);
lv_obj_set_style_align(hdg_scale, LV_ALIGN_DEFAULT, LV_PART_INDICATOR | LV_STATE_DEFAULT);
lv_obj_set_style_transform_rotation(hdg_scale, 90, LV_PART_INDICATOR | LV_STATE_DEFAULT);

But the paradox of the situation is that when you start to rotate the scale

lv_obj_set_style_transform_rotation

in the LVGL simulator, only the label numbers rotate, but not the scale - as it was done in the EEZ_studio program.

I need the meter scale to rotate around the axis by a given value, but I can’t understand - is it possible in principle to implement this using the LVGL library?

I tried to rotate the scale in an infinite loop directly
lv_scale_set_angle_range(hdg_scale, i++);
And I got this picture:

Thanks for the tips and help!