124 lines
3.3 KiB
C
124 lines
3.3 KiB
C
/*
|
|
* Host implementation of the firmware's lvgl_port.h contract.
|
|
*
|
|
* The firmware version (main/ui/lvgl_port.c) is built on FreeRTOS tasks and
|
|
* esp_timer. This file provides the same six entry points on pthreads so that
|
|
* main/ui/ui_manager.c -- the file whose layout we actually iterate on --
|
|
* compiles and runs unchanged on the desktop.
|
|
*
|
|
* The one deliberate behavioural difference is lvgl_port_start(): on the
|
|
* device it spawns the task that calls lv_timer_handler(), whereas here the
|
|
* simulator's own main loop drives LVGL. See the comment on that function.
|
|
*/
|
|
|
|
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
#include <time.h>
|
|
|
|
#include "esp_log.h"
|
|
#include "lvgl.h"
|
|
|
|
#include "lvgl_port.h"
|
|
|
|
static const char *TAG = "lvgl_port_sim";
|
|
|
|
static pthread_mutex_t lvgl_mutex;
|
|
static bool mutex_ready = false;
|
|
static volatile bool paused = false;
|
|
|
|
/* LVGL's millisecond time base. The firmware drives this from a 2ms esp_timer
|
|
* calling lv_tick_inc(); on the host a monotonic clock read is both simpler
|
|
* and more accurate. */
|
|
static uint32_t tick_get_cb(void)
|
|
{
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (uint32_t)(ts.tv_sec * 1000U + ts.tv_nsec / 1000000U);
|
|
}
|
|
|
|
esp_err_t lvgl_port_init(void)
|
|
{
|
|
/*
|
|
* Recursive, matching the device: lv_timer_handler() runs callbacks that
|
|
* may re-enter code which also takes the lock. Keeping the same semantics
|
|
* here means a deadlock introduced in shared UI code reproduces in the
|
|
* simulator instead of only showing up on hardware.
|
|
*/
|
|
pthread_mutexattr_t attr;
|
|
pthread_mutexattr_init(&attr);
|
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
|
|
|
if (pthread_mutex_init(&lvgl_mutex, &attr) != 0) {
|
|
pthread_mutexattr_destroy(&attr);
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
pthread_mutexattr_destroy(&attr);
|
|
mutex_ready = true;
|
|
|
|
lv_init();
|
|
lv_tick_set_cb(tick_get_cb);
|
|
|
|
ESP_LOGI(TAG, "LVGL %d.%d.%d initialized (simulator)",
|
|
lv_version_major(), lv_version_minor(), lv_version_patch());
|
|
return ESP_OK;
|
|
}
|
|
|
|
/*
|
|
* No-op by design.
|
|
*
|
|
* On the device this starts the tick timer and the LVGL service task. Here the
|
|
* tick is a clock callback installed in lvgl_port_init(), and lv_timer_handler()
|
|
* is called from the simulator's main loop -- running it from a second thread
|
|
* as well would mean two threads rendering into one framebuffer.
|
|
*
|
|
* It still exists so that shared start-up code can call it unconditionally.
|
|
*/
|
|
esp_err_t lvgl_port_start(void)
|
|
{
|
|
return ESP_OK;
|
|
}
|
|
|
|
void lvgl_port_pause(void)
|
|
{
|
|
paused = true;
|
|
}
|
|
|
|
void lvgl_port_resume(void)
|
|
{
|
|
paused = false;
|
|
}
|
|
|
|
bool lvgl_port_is_paused(void)
|
|
{
|
|
return paused;
|
|
}
|
|
|
|
bool lvgl_port_lock(int timeout_ms)
|
|
{
|
|
if (!mutex_ready) {
|
|
return false;
|
|
}
|
|
|
|
if (timeout_ms < 0) {
|
|
return pthread_mutex_lock(&lvgl_mutex) == 0;
|
|
}
|
|
|
|
struct timespec deadline;
|
|
clock_gettime(CLOCK_REALTIME, &deadline);
|
|
deadline.tv_sec += timeout_ms / 1000;
|
|
deadline.tv_nsec += (long)(timeout_ms % 1000) * 1000000L;
|
|
if (deadline.tv_nsec >= 1000000000L) {
|
|
deadline.tv_sec++;
|
|
deadline.tv_nsec -= 1000000000L;
|
|
}
|
|
|
|
return pthread_mutex_timedlock(&lvgl_mutex, &deadline) == 0;
|
|
}
|
|
|
|
void lvgl_port_unlock(void)
|
|
{
|
|
if (mutex_ready) {
|
|
pthread_mutex_unlock(&lvgl_mutex);
|
|
}
|
|
}
|