How to determine which check box is clicked in the callback function?

LVGL version 7.11 in simulator.
Here I have a list of dynamically formed labels and associated check boxes for each to enable or disable. In a callback function I am using Y position on the screen of a check box to determine which one is clicked. It is kind a primitive solution. If anyone would suggest other way to accomplish this task I would appreciate it. This code is working as desired, I am just looking for alternative solution.

/* List of visible sensors */
lv_coord_t Y_POS = 60;
for (int i = 0; i < sizeof(sensors)/sizeof(sensors[0]); i++) {
	#if EVENTS_DEBUG
	printf("%s\n", sensors[i].name);
	#endif
	lv_obj_t * lbl_sen = lv_label_create(lv_scr_act(), NULL);
	lv_label_set_text(lbl_sen, sensors[i].name);
	lv_obj_add_style(lbl_sen, LV_BTN_PART_MAIN, &style_lbl_dt);
	lv_obj_align(lbl_sen, NULL, LV_ALIGN_IN_TOP_LEFT, 20, Y_POS);

	lv_obj_t * cbx_sen = lv_checkbox_create(lv_scr_act(), NULL);
	lv_checkbox_set_text(cbx_sen, "");
	lv_obj_align(cbx_sen, NULL, LV_ALIGN_IN_TOP_LEFT, 120, Y_POS);
	lv_obj_set_event_cb(cbx_sen, cbx_sensors_event_handler);

	Y_POS += 30;
}
/* Callback function */
void cbx_sensors_event_handler(lv_obj_t * obj, lv_event_t event) 
{
	if (event == LV_EVENT_VALUE_CHANGED) {
		lv_coord_t y = lv_obj_get_y(obj); // Y_POS: 60, 90, 120, etc...
		int sn = (y - 60) / 30;
		#if EVENTS_DEBUG
		printf("%d\n", y);
		printf("Sensor %d. state is %s\n", sn + 1, lv_checkbox_is_checked(obj) ? "On" : "Off");
		#endif
		if (lv_checkbox_is_checked(obj)) {
			#if EVENTS_DEBUG
			printf("Sensor %d. readings enabled.\n", sn + 1);
			#endif
		}
		else {
			#if EVENTS_DEBUG
			printf("Sensor %d. readings disabled.\n", sn + 1);
			#endif
		}
	}
}

sensors

2 solutions:

  1. or you made 3 handler (1 for each sensor )
  2. or you can use text of any sensor for know what sensor need ceck with lv_checkbox_get_text (const [lv_obj_t]
    Checkbox (lv_cb) — LVGL documentation

How do you want to use this information?

Thanks gabriele_ponte. Second solution looks more elegant. It even eliminates the labels.

What do you mean glory-man?

I mean that event handler already knows which object generate event - 1st arg of event function. lv_obj_t can have (if defined in config) a member

lv_obj_user_data_t user_data

which defined as simple pointer to void

typedef void *lv_obj_user_data_t;

So during creation you can init it with any data, for exmple sensor name; if sensors[i].name in your example global or static something like this

lv_obj_set_user_data(cbx_sen, sensors[i].name)

lv_obj_get_user_data will return sensors name.
if sensors is structure then you can init with pointer to sensor[i], and work with struct elements in event handler.
user_data may even be a pointer to a function, which you need call when event occures.

But with lv_checkbox_get_text (that is chars) you can perform a switch…case easy and add new sensors case here