Files
Aveline 51db49ba5e feat: add lv_obj_fade_in, lv_obj_fade_out (#6)
* feat: add lv_obj_fade_in, lv_obj_fade_out

* Update ctrl.c

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-12 11:01:13 +02:00

553 lines
16 KiB
C

#include <stdio.h>
#include <string.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <stdint.h>
#include <fcntl.h>
#include "frozen.h"
#include "video.h"
#include "screen.h"
#include "edid.h"
#include "lvgl/lvgl.h"
typedef struct
{
bool ready;
const char *error;
u_int16_t width;
u_int16_t height;
double frame_per_second;
} state_t;
state_t state;
struct sockaddr_un addr;
int ctrl_client_fd = 0;
int write_json(int fd, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
char *json_str = json_vasprintf(fmt, ap);
va_end(ap);
if (json_str == NULL)
{
return -1;
}
int written = write(fd, json_str, strlen(json_str));
free(json_str);
return written;
}
int write_json_error(int fd, int seq, const char *error)
{
return write_json(fd, "{seq: %d, error: %Q, errno: %d}", seq, error, errno);
}
static int write_input_video_state(int fd)
{
return write_json(fd, "{event: %Q, data: {ready: %B, error: %Q, width: %u, height:%u, frame_per_second: %f}}",
"video_input_state", state.ready, state.error, state.width, state.height, state.frame_per_second);
}
void report_video_format(bool ready, const char *error, u_int16_t width, u_int16_t height, double frame_per_second)
{
state.ready = ready;
state.error = error;
state.width = width;
state.height = height;
state.frame_per_second = frame_per_second;
if (ctrl_client_fd > 0)
{
if (write_input_video_state(ctrl_client_fd) < 0)
{
perror("error writing video state");
}
}
}
/**
* @brief Convert a hexadecimal string to an array of uint8_t bytes
*
* @param hex_str The input hexadecimal string
* @param bytes The output byte array (must be pre-allocated)
* @param max_len The maximum number of bytes that can be stored in the output array
* @return int The number of bytes converted, or -1 on error
*/
int hex_to_bytes(const char *hex_str, uint8_t *bytes, size_t max_len)
{
size_t hex_len = strnlen(hex_str, 4096);
if (hex_len % 2 != 0 || hex_len / 2 > max_len)
{
return -1; // Invalid input length or insufficient output buffer
}
for (size_t i = 0; i < hex_len; i += 2)
{
char byte_str[3] = {hex_str[i], hex_str[i + 1], '\0'};
char *end_ptr;
long value = strtol(byte_str, &end_ptr, 16);
if (*end_ptr != '\0' || value < 0 || value > 255)
{
return -1; // Invalid hexadecimal value
}
bytes[i / 2] = (uint8_t)value;
}
return hex_len / 2;
}
/**
* @brief Convert an array of uint8_t bytes to a hexadecimal string, user must free the returned string
*
* @param bytes The input byte array
* @param len The number of bytes in the input array
* @return char* The output hexadecimal string (dynamically allocated, must be freed by the caller), or NULL on error
*/
const char *bytes_to_hex(const uint8_t *bytes, size_t len)
{
if (bytes == NULL || len == 0)
{
return NULL;
}
char *hex_str = malloc(2 * len + 1); // Each byte becomes 2 hex chars, plus null terminator
if (hex_str == NULL)
{
return NULL; // Memory allocation failed
}
for (size_t i = 0; i < len; i++)
{
snprintf(hex_str + (2 * i), 3, "%02x", bytes[i]);
}
hex_str[2 * len] = '\0'; // Ensure null termination
return hex_str;
}
lv_obj_flag_t str_to_lv_obj_flag(const char *flag)
{
if (strcmp(flag, "LV_OBJ_FLAG_HIDDEN") == 0)
{
return LV_OBJ_FLAG_HIDDEN;
}
else if (strcmp(flag, "LV_OBJ_FLAG_CLICKABLE") == 0)
{
return LV_OBJ_FLAG_CLICKABLE;
}
else if (strcmp(flag, "LV_OBJ_FLAG_SCROLLABLE") == 0)
{
return LV_OBJ_FLAG_SCROLLABLE;
}
else if (strcmp(flag, "LV_OBJ_FLAG_CLICK_FOCUSABLE") == 0)
{
return LV_OBJ_FLAG_CLICK_FOCUSABLE;
}
else if (strcmp(flag, "LV_OBJ_FLAG_SCROLL_ON_FOCUS") == 0)
{
return LV_OBJ_FLAG_SCROLL_ON_FOCUS;
}
else if (strcmp(flag, "LV_OBJ_FLAG_SCROLL_CHAIN") == 0)
{
return LV_OBJ_FLAG_SCROLL_CHAIN;
}
else if (strcmp(flag, "LV_OBJ_FLAG_PRESS_LOCK") == 0)
{
return LV_OBJ_FLAG_PRESS_LOCK;
}
else if (strcmp(flag, "LV_OBJ_FLAG_OVERFLOW_VISIBLE") == 0)
{
return LV_OBJ_FLAG_OVERFLOW_VISIBLE;
}
else
{
return 0; // Unknown flag
}
}
void handle_lvgl_call(const int seq, const char *method, const char *json, size_t json_len)
{
lv_obj_t *obj = NULL;
char *obj_name = NULL;
if (strcmp(method, "lv_disp_set_rotation") == 0)
{
char *rotation = NULL;
if (json_scanf(json, json_len, "{params: {rotation: %Q}}", &rotation) > 0)
{
lv_disp_rot_t rot;
if (strcmp(rotation, "90") == 0)
{
rot = LV_DISP_ROT_90;
}
else if (strcmp(rotation, "270") == 0)
{
rot = LV_DISP_ROT_270;
}
else
{
rot = -1;
}
free(rotation);
if (rot != -1)
{
lv_disp_set_rotation(NULL, rot);
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else
{
write_json_error(ctrl_client_fd, seq, "illegal rotation value");
}
}
else
{
write_json_error(ctrl_client_fd, seq, "missing rotation parameter");
}
}
else if (json_scanf(json, json_len, "{params: {obj: %Q}}", &obj_name) > 0)
{
obj = ui_get_obj(obj_name);
free(obj_name);
if (obj == NULL)
{
write_json_error(ctrl_client_fd, seq, "object not found");
return;
}
if (strcmp(method, "lv_scr_load") == 0)
{
if (lv_scr_act() != obj)
{
lv_scr_load(obj);
}
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else if (strcmp(method, "lv_label_set_text") == 0)
{
char *text = NULL;
if (json_scanf(json, json_len, "{params: {text: %Q}}", &text) > 0)
{
lv_label_set_text(obj, text);
free(text);
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else
{
write_json_error(ctrl_client_fd, seq, "missing text parameter");
}
}
else if (strcmp(method, "lv_img_set_src") == 0)
{
char *src = NULL;
if (json_scanf(json, json_len, "{params: {src: %Q}}", &src) > 0)
{
lv_img_dsc_t *img = ui_get_image(src);
free(src);
if (img != NULL)
{
lv_img_set_src(obj, img);
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else
{
write_json_error(ctrl_client_fd, seq, "image source not found");
}
}
else
{
write_json_error(ctrl_client_fd, seq, "missing src parameter");
}
}
else if (strcmp(method, "lv_obj_set_state") == 0)
{
char *state = NULL;
if (json_scanf(json, json_len, "{params: {state: %Q}}", &state) > 0)
{
lv_obj_add_state(obj, LV_STATE_USER_1);
lv_state_t state_val = LV_STATE_DEFAULT;
if (strcmp(state, "LV_STATE_USER_1") == 0)
{
state_val = LV_STATE_USER_1;
}
else if (strcmp(state, "LV_STATE_USER_2") == 0)
{
state_val = LV_STATE_USER_2;
}
free(state);
lv_obj_clear_state(obj, LV_STATE_USER_1 | LV_STATE_USER_2);
lv_obj_add_state(obj, state_val);
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else
{
write_json_error(ctrl_client_fd, seq, "missing state parameter");
}
}
else if (strcmp(method, "lv_obj_add_flag") == 0)
{
char *flag = NULL;
if (json_scanf(json, json_len, "{params: {flag: %Q}}", &flag) > 0)
{
lv_obj_flag_t flag_val = str_to_lv_obj_flag(flag);
if (flag_val == 0)
{
free(flag);
write_json_error(ctrl_client_fd, seq, "unknown flag");
return;
}
lv_obj_add_flag(obj, flag_val);
free(flag);
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else
{
write_json_error(ctrl_client_fd, seq, "missing flag parameter");
}
}
else if (strcmp(method, "lv_obj_clear_flag") == 0)
{
char *flag = NULL;
if (json_scanf(json, json_len, "{params: {flag: %Q}}", &flag) > 0)
{
lv_obj_flag_t flag_val = str_to_lv_obj_flag(flag);
if (flag_val == 0)
{
free(flag);
write_json_error(ctrl_client_fd, seq, "unknown flag");
return;
}
lv_obj_clear_flag(obj, flag_val);
free(flag);
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else
{
write_json_error(ctrl_client_fd, seq, "missing flag parameter");
}
}
else if (strcmp(method, "lv_obj_fade_in") == 0)
{
uint32_t duration = 0;
if (json_scanf(json, json_len, "{params: {duration: %u}}", &duration) > 0)
{
lv_obj_fade_in(obj, duration, 0);
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else
{
write_json_error(ctrl_client_fd, seq, "missing duration parameter");
}
}
else if (strcmp(method, "lv_obj_fade_out") == 0)
{
uint32_t duration = 0;
if (json_scanf(json, json_len, "{params: {duration: %u}}", &duration) > 0)
{
lv_obj_fade_out(obj, duration, 0);
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else
{
write_json_error(ctrl_client_fd, seq, "missing duration parameter");
}
}
else
{
write_json_error(ctrl_client_fd, seq, "unknown method");
}
}
else
{
printf("handle_lvgl_call: No object name provided for method %s\n", method);
}
}
void *handle_client(void *arg)
{
const int BUF_SIZE = 1024;
char buf_in[BUF_SIZE];
// TODO: fix memory leak caused by continue
while (true)
{
ssize_t n = read(ctrl_client_fd, buf_in, BUF_SIZE);
if (n > 0)
{
buf_in[n] = '\0';
char *method;
int32_t seq = -1;
if (json_scanf(buf_in, n, "{action: %Q, seq: %d}", &method, &seq) > 0)
{
if (strcmp("start_video", method) == 0)
{
video_start_streaming();
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else if (strcmp("stop_video", method) == 0)
{
video_stop_streaming();
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else if (strcmp("set_video_quality_factor", method) == 0)
{
float quality_factor;
if (json_scanf(buf_in, n, "{params: {quality_factor: %f}}", &quality_factor) > 0)
{
if (quality_factor < 0 || quality_factor > 1)
{
write_json_error(ctrl_client_fd, seq, "invalid quality factor");
}
else
{
video_set_quality_factor(quality_factor);
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
}
else
{
write_json_error(ctrl_client_fd, seq, "invalid quality factor");
}
}
// if method starts with lv_, call the corresponding lvgl function
else if (strncmp("lv_", method, 3) == 0)
{
handle_lvgl_call(seq, method, buf_in, n);
}
else if (strcmp("ui_set_text", method) == 0)
{
char *name;
char *text;
if (json_scanf(buf_in, n, "{name: %Q, text: %Q}", &name, &text) > 0)
{
ui_set_text(name, text);
free(name);
free(text);
}
if (seq > 0)
{
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
}
else if (strcmp("set_edid", method) == 0)
{
uint8_t edid[256];
char *edid_hex;
if (json_scanf(buf_in, n, "{params: {edid: %Q} }", &edid_hex) > 0)
{
int edid_len = hex_to_bytes(edid_hex, edid, 256);
free(edid_hex);
if (edid_len < 0)
{
write_json_error(ctrl_client_fd, seq, "invalid edid");
continue;
}
if (set_edid(edid, edid_len) < 0)
{
write_json_error(ctrl_client_fd, seq, "failed to set EDID");
}
write_json(ctrl_client_fd, "{seq: %d}", seq);
}
else
{
write_json_error(ctrl_client_fd, seq, "invalid edid");
}
}
else if (strcmp("get_edid", method) == 0)
{
uint8_t edid[256];
int edid_len = get_edid(edid, 256);
if (edid_len < 0)
{
write_json_error(ctrl_client_fd, seq, "failed to get EDID");
continue;
}
char *edid_hex = bytes_to_hex(edid, edid_len);
if (edid_hex == NULL)
{
write_json_error(ctrl_client_fd, seq, "failed to convert EDID to hex");
free(edid_hex);
continue;
}
write_json(ctrl_client_fd, "{seq: %d, result: {edid: %Q}}", seq, edid_hex);
free(edid_hex);
}
free(method);
}
}
else if (n == 0)
{
// EOF
break;
}
else
{
perror("error read from ctrl client");
break;
}
}
close(ctrl_client_fd);
return NULL;
}
int connect_ctrl_client(const char *path)
{
int client_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (client_fd == -1)
{
perror("can not create socket");
return -1;
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if (connect(client_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
perror("can not connect to ctrl server");
close(client_fd);
return -1;
}
ctrl_client_fd = client_fd;
return 0;
}
pthread_t ctrl_thread;
void start_ctrl_loop()
{
if (pthread_create(&ctrl_thread, NULL, (void *(*)(void *))handle_client, NULL) != 0)
{
perror("Failed to create ctrl_thread");
}
}
void stop_ctrl_loop()
{
if (ctrl_client_fd != 0)
{
if (shutdown(ctrl_client_fd, SHUT_RDWR) == -1)
{
perror("Error shutting down ctrl client socket");
}
if (close(ctrl_client_fd) == -1)
{
perror("Error closing client socket");
}
ctrl_client_fd = 0;
}
}