/*******************************************************************************
* Part 4 - Example 7: Hardware IPC Dashboard (CM55 Side)
*
* Full dashboard with TabView:
* - Tab 1: LED Controls + Button Status
* - Tab 2: IMU Chart + ADC Gauge (data via IPC from CM33)
* - Tab 3: IPC Statistics + System Health
*
* CRITICAL: All UI updates through flag-based pattern only!
******************************************************************************/
#include "lvgl.h"
#include "ipc/cm55_ipc_pipe.h"
#include "../../shared/ipc_shared.h"
#include "FreeRTOS.h"
#include "task.h"
/* ===== IPC Flag Structure ===== */
typedef struct {
volatile bool led_ack;
volatile bool btn_event;
volatile bool imu_data;
volatile bool adc_data;
volatile bool pong;
} ipc_flags_t;
static ipc_flags_t flags = {0};
/* ===== IPC Data Buffers (volatile) ===== */
static volatile ipc_led_data_t rx_led = {0};
static volatile ipc_button_data_t rx_button = {0};
static volatile ipc_imu_data_t rx_imu = {0};
static volatile ipc_adc_data_t rx_adc = {0};
static volatile uint32_t rx_pong_tick = 0;
/* ===== IPC Statistics ===== */
static volatile uint32_t total_rx_count = 0;
static volatile uint32_t total_tx_count = 0;
static volatile uint32_t ipc_error_count = 0;
static volatile uint32_t ping_send_tick = 0;
static volatile uint32_t ipc_rtt_ms = 0;
/* ===== LED State ===== */
#define NUM_LEDS 3
static bool led_states[NUM_LEDS] = {false, false, false};
static int led_indices[NUM_LEDS] = {0, 1, 2};
/* ===== UI Objects: Tab 1 (Controls) ===== */
static lv_obj_t * led_switches[NUM_LEDS];
static lv_obj_t * led_indicators[NUM_LEDS];
static lv_obj_t * led_status_labels[NUM_LEDS];
static lv_obj_t * brightness_slider;
static lv_obj_t * brightness_label;
static lv_obj_t * btn_status_label;
static lv_obj_t * btn_led_widget;
static lv_obj_t * btn_counter_label;
/* ===== UI Objects: Tab 2 (Sensors) ===== */
static lv_obj_t * imu_chart;
static lv_chart_series_t * imu_ser_x;
static lv_chart_series_t * imu_ser_y;
static lv_chart_series_t * imu_ser_z;
static lv_obj_t * imu_value_label;
static lv_obj_t * adc_arc;
static lv_obj_t * adc_value_label;
/* ===== UI Objects: Tab 3 (Status) ===== */
static lv_obj_t * stat_tx_label;
static lv_obj_t * stat_rx_label;
static lv_obj_t * stat_err_label;
static lv_obj_t * stat_rtt_label;
static lv_obj_t * stat_uptime_label;
static lv_obj_t * event_log_label;
/* Button counts */
static uint32_t press_count = 0;
/* Event log */
#define EVENT_LOG_MAX 6
static char event_log_buf[EVENT_LOG_MAX][40];
static int event_log_idx = 0;
static uint32_t start_tick = 0;
/*******************************************************************************
* IPC Receive Callback (ISR context)
******************************************************************************/
static void dashboard_ipc_rx_cb(const ipc_msg_t *msg, void *user_data)
{
(void)user_data;
total_rx_count++;
switch (msg->cmd) {
case IPC_CMD_ACK: {
ipc_led_data_t *d = (ipc_led_data_t *)msg->data;
rx_led = *d;
flags.led_ack = true;
break;
}
case IPC_CMD_BUTTON_EVENT: {
ipc_button_data_t *d = (ipc_button_data_t *)msg->data;
rx_button = *d;
flags.btn_event = true;
break;
}
case IPC_CMD_IMU_DATA: {
ipc_imu_data_t *d = (ipc_imu_data_t *)msg->data;
rx_imu = *d;
flags.imu_data = true;
break;
}
case IPC_CMD_ADC_DATA: {
ipc_adc_data_t *d = (ipc_adc_data_t *)msg->data;
rx_adc = *d;
flags.adc_data = true;
break;
}
case IPC_CMD_PONG:
rx_pong_tick = msg->value;
ipc_rtt_ms = xTaskGetTickCount() - ping_send_tick;
flags.pong = true;
break;
case IPC_CMD_NACK:
ipc_error_count++;
break;
default:
break;
}
}
/*******************************************************************************
* Event Log Helper
******************************************************************************/
static void log_event(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsnprintf(event_log_buf[event_log_idx],
sizeof(event_log_buf[0]), fmt, args);
va_end(args);
event_log_idx = (event_log_idx + 1) % EVENT_LOG_MAX;
}
static void refresh_event_log(void)
{
static char display_buf[320];
display_buf[0] = '\0';
for (int i = 0; i < EVENT_LOG_MAX; i++) {
int idx = (event_log_idx - EVENT_LOG_MAX + i + EVENT_LOG_MAX)
% EVENT_LOG_MAX;
if (event_log_buf[idx][0] != '\0') {
strcat(display_buf, event_log_buf[idx]);
strcat(display_buf, "\n");
}
}
lv_label_set_text(event_log_label, display_buf);
}
/*******************************************************************************
* LVGL Timer: Check ALL IPC flags and update UI
******************************************************************************/
static void dashboard_timer_cb(lv_timer_t *timer)
{
(void)timer;
/* --- LED ACK --- */
if (flags.led_ack) {
flags.led_ack = false;
uint8_t id = rx_led.led_id;
if (id < NUM_LEDS) {
led_states[id] = (rx_led.state != 0);
if (led_states[id]) {
lv_led_on(led_indicators[id]);
} else {
lv_led_off(led_indicators[id]);
}
lv_label_set_text_fmt(led_status_labels[id], "%s",
led_states[id] ? "ON" : "OFF");
lv_obj_set_style_text_color(led_status_labels[id],
led_states[id] ? lv_palette_main(LV_PALETTE_GREEN) :
lv_palette_main(LV_PALETTE_RED), 0);
log_event("LED%d -> %s", (int)id, led_states[id] ? "ON" : "OFF");
}
}
/* --- Button Event --- */
if (flags.btn_event) {
flags.btn_event = false;
bool pressed = (rx_button.pressed != 0);
bool long_p = (rx_button.long_press != 0);
if (pressed) {
lv_led_on(btn_led_widget);
if (long_p) {
lv_label_set_text(btn_status_label, "LONG PRESS");
lv_obj_set_style_text_color(btn_status_label,
lv_palette_main(LV_PALETTE_ORANGE), 0);
lv_led_set_color(btn_led_widget,
lv_palette_main(LV_PALETTE_ORANGE));
log_event("BTN: LONG_PRESS");
} else {
press_count++;
lv_label_set_text(btn_status_label, "PRESSED");
lv_obj_set_style_text_color(btn_status_label,
lv_palette_main(LV_PALETTE_GREEN), 0);
lv_led_set_color(btn_led_widget,
lv_palette_main(LV_PALETTE_GREEN));
log_event("BTN: PRESS #%u", (unsigned int)press_count);
}
} else {
lv_led_off(btn_led_widget);
lv_label_set_text(btn_status_label, "RELEASED");
lv_obj_set_style_text_color(btn_status_label,
lv_palette_main(LV_PALETTE_RED), 0);
}
lv_label_set_text_fmt(btn_counter_label,
"Count: %u", (unsigned int)press_count);
}
/* --- IMU Data --- */
if (flags.imu_data) {
flags.imu_data = false;
/* Scale: raw int16 -> chart range (e.g., -2000 to +2000) */
lv_chart_set_next_value(imu_chart, imu_ser_x, rx_imu.accel_x);
lv_chart_set_next_value(imu_chart, imu_ser_y, rx_imu.accel_y);
lv_chart_set_next_value(imu_chart, imu_ser_z, rx_imu.accel_z);
lv_chart_refresh(imu_chart);
lv_label_set_text_fmt(imu_value_label,
"X:%d Y:%d Z:%d",
(int)rx_imu.accel_x, (int)rx_imu.accel_y, (int)rx_imu.accel_z);
}
/* --- ADC Data --- */
if (flags.adc_data) {
flags.adc_data = false;
/* Scale ADC to 0-100 for arc display */
int32_t adc_pct = (int32_t)rx_adc.adc_ch0 * 100 / 4095;
lv_arc_set_value(adc_arc, adc_pct);
lv_label_set_text_fmt(adc_value_label,
"%d%%\n(%u)", (int)adc_pct, (unsigned int)rx_adc.adc_ch0);
}
/* --- PONG (Health) --- */
if (flags.pong) {
flags.pong = false;
lv_label_set_text_fmt(stat_rtt_label,
"RTT: %u ms", (unsigned int)ipc_rtt_ms);
log_event("PONG: RTT=%ums", (unsigned int)ipc_rtt_ms);
}
/* --- Update Status Tab stats (always) --- */
uint32_t uptime = (xTaskGetTickCount() - start_tick) / 1000;
lv_label_set_text_fmt(stat_tx_label,
"TX: %u", (unsigned int)total_tx_count);
lv_label_set_text_fmt(stat_rx_label,
"RX: %u", (unsigned int)total_rx_count);
lv_label_set_text_fmt(stat_err_label,
"Errors: %u", (unsigned int)ipc_error_count);
lv_label_set_text_fmt(stat_uptime_label,
"Uptime: %us", (unsigned int)uptime);
refresh_event_log();
}
/*******************************************************************************
* Sensor Request Timer (100ms) - ขอ data จาก CM33
******************************************************************************/
static void sensor_request_timer_cb(lv_timer_t *timer)
{
(void)timer;
/* Request IMU data */
cm55_ipc_request_sensor(IPC_CMD_IMU_DATA);
total_tx_count++;
/* 20ms delay ก่อนส่ง request ถัดไป */
vTaskDelay(pdMS_TO_TICKS(20));
/* Request ADC data */
cm55_ipc_request_sensor(IPC_CMD_ADC_DATA);
total_tx_count++;
}
/*******************************************************************************
* Health Check Timer (5000ms) - ส่ง PING
******************************************************************************/
static void health_timer_cb(lv_timer_t *timer)
{
(void)timer;
ping_send_tick = xTaskGetTickCount();
cm55_ipc_send_cmd(IPC_CMD_PING, ping_send_tick);
total_tx_count++;
}
/*******************************************************************************
* LED Switch Callback
******************************************************************************/
static void led_switch_cb(lv_event_t *e)
{
int *idx = (int *)lv_event_get_user_data(e);
lv_obj_t *sw = lv_event_get_target(e);
bool on = lv_obj_has_state(sw, LV_STATE_CHECKED);
cm55_ipc_set_led(*idx, on);
total_tx_count++;
CM55_LOGD("LED%d -> %s", *idx, on ? "ON" : "OFF");
}
/*******************************************************************************
* Brightness Slider Callback
******************************************************************************/
static void brightness_cb(lv_event_t *e)
{
lv_obj_t *slider = lv_event_get_target(e);
int32_t val = lv_slider_get_value(slider);
lv_label_set_text_fmt(brightness_label, "%d%%", (int)val);
/* ส่ง brightness ให้ LED ที่เปิดอยู่ (ทีละดวง + delay) */
for (int i = 0; i < NUM_LEDS; i++) {
if (led_states[i]) {
cm55_ipc_set_led_brightness(i, (uint8_t)val);
total_tx_count++;
vTaskDelay(pdMS_TO_TICKS(20));
}
}
}
/*******************************************************************************
* Build Tab 1: Controls
******************************************************************************/
static void build_controls_tab(lv_obj_t *tab)
{
lv_obj_set_style_bg_color(tab, lv_color_hex(0x0a0e27), 0);
lv_obj_clear_flag(tab, LV_OBJ_FLAG_SCROLLABLE);
/* --- LED Control Section --- */
lv_obj_t *led_panel = lv_obj_create(tab);
lv_obj_set_size(led_panel, 230, 160);
lv_obj_set_pos(led_panel, 5, 5);
lv_obj_set_style_bg_color(led_panel, lv_color_hex(0x111633), 0);
lv_obj_set_style_border_color(led_panel, lv_color_hex(0x333366), 0);
lv_obj_set_style_border_width(led_panel, 1, 0);
lv_obj_set_style_radius(led_panel, 6, 0);
lv_obj_clear_flag(led_panel, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t *led_title = lv_label_create(led_panel);
lv_label_set_text(led_title, "LED Control");
lv_obj_set_style_text_color(led_title, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_pos(led_title, 5, 2);
const char *led_names[] = {"Red", "Green", "Blue"};
lv_color_t led_colors[] = {
lv_palette_main(LV_PALETTE_RED),
lv_palette_main(LV_PALETTE_GREEN),
lv_palette_main(LV_PALETTE_BLUE)
};
for (int i = 0; i < NUM_LEDS; i++) {
int y = 22 + i * 35;
led_indicators[i] = lv_led_create(led_panel);
lv_obj_set_size(led_indicators[i], 20, 20);
lv_obj_set_pos(led_indicators[i], 8, y + 2);
lv_led_set_color(led_indicators[i], led_colors[i]);
lv_led_off(led_indicators[i]);
lv_obj_t *name = lv_label_create(led_panel);
lv_label_set_text(name, led_names[i]);
lv_obj_set_style_text_color(name, lv_color_hex(0xCCCCCC), 0);
lv_obj_set_style_text_font(name, &lv_font_montserrat_12, 0);
lv_obj_set_pos(name, 35, y + 4);
led_switches[i] = lv_switch_create(led_panel);
lv_obj_set_size(led_switches[i], 45, 22);
lv_obj_set_pos(led_switches[i], 95, y + 2);
lv_obj_set_style_bg_color(led_switches[i], led_colors[i],
LV_PART_INDICATOR | LV_STATE_CHECKED);
lv_obj_add_event_cb(led_switches[i], led_switch_cb,
LV_EVENT_VALUE_CHANGED, &led_indices[i]);
led_status_labels[i] = lv_label_create(led_panel);
lv_label_set_text(led_status_labels[i], "OFF");
lv_obj_set_style_text_color(led_status_labels[i],
lv_palette_main(LV_PALETTE_RED), 0);
lv_obj_set_style_text_font(led_status_labels[i],
&lv_font_montserrat_12, 0);
lv_obj_set_pos(led_status_labels[i], 150, y + 4);
}
/* Brightness */
lv_obj_t *br_lbl = lv_label_create(led_panel);
lv_label_set_text(br_lbl, "Bright:");
lv_obj_set_style_text_color(br_lbl, lv_color_hex(0x888888), 0);
lv_obj_set_style_text_font(br_lbl, &lv_font_montserrat_12, 0);
lv_obj_set_pos(br_lbl, 8, 130);
brightness_slider = lv_slider_create(led_panel);
lv_obj_set_size(brightness_slider, 100, 10);
lv_obj_set_pos(brightness_slider, 60, 134);
lv_slider_set_range(brightness_slider, 0, 100);
lv_slider_set_value(brightness_slider, 100, LV_ANIM_OFF);
lv_obj_set_style_bg_color(brightness_slider,
lv_palette_main(LV_PALETTE_AMBER), LV_PART_INDICATOR);
lv_obj_add_event_cb(brightness_slider, brightness_cb,
LV_EVENT_VALUE_CHANGED, NULL);
brightness_label = lv_label_create(led_panel);
lv_label_set_text(brightness_label, "100%");
lv_obj_set_style_text_color(brightness_label, lv_color_hex(0xCCCCCC), 0);
lv_obj_set_style_text_font(brightness_label, &lv_font_montserrat_12, 0);
lv_obj_set_pos(brightness_label, 170, 130);
/* --- Button Status Section --- */
lv_obj_t *btn_panel = lv_obj_create(tab);
lv_obj_set_size(btn_panel, 220, 160);
lv_obj_set_pos(btn_panel, 245, 5);
lv_obj_set_style_bg_color(btn_panel, lv_color_hex(0x111633), 0);
lv_obj_set_style_border_color(btn_panel, lv_color_hex(0x333366), 0);
lv_obj_set_style_border_width(btn_panel, 1, 0);
lv_obj_set_style_radius(btn_panel, 6, 0);
lv_obj_clear_flag(btn_panel, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t *btn_title = lv_label_create(btn_panel);
lv_label_set_text(btn_title, "Button Status");
lv_obj_set_style_text_color(btn_title, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_pos(btn_title, 5, 2);
btn_led_widget = lv_led_create(btn_panel);
lv_obj_set_size(btn_led_widget, 50, 50);
lv_obj_align(btn_led_widget, LV_ALIGN_CENTER, 0, -15);
lv_led_set_color(btn_led_widget, lv_palette_main(LV_PALETTE_GREEN));
lv_led_off(btn_led_widget);
btn_status_label = lv_label_create(btn_panel);
lv_label_set_text(btn_status_label, "RELEASED");
lv_obj_set_style_text_color(btn_status_label,
lv_palette_main(LV_PALETTE_RED), 0);
lv_obj_set_style_text_font(btn_status_label, &lv_font_montserrat_14, 0);
lv_obj_align(btn_status_label, LV_ALIGN_CENTER, 0, 30);
btn_counter_label = lv_label_create(btn_panel);
lv_label_set_text(btn_counter_label, "Count: 0");
lv_obj_set_style_text_color(btn_counter_label, lv_color_hex(0xCCCCCC), 0);
lv_obj_set_style_text_font(btn_counter_label, &lv_font_montserrat_12, 0);
lv_obj_align(btn_counter_label, LV_ALIGN_BOTTOM_MID, 0, -10);
}
/*******************************************************************************
* Build Tab 2: Sensors
******************************************************************************/
static void build_sensors_tab(lv_obj_t *tab)
{
lv_obj_set_style_bg_color(tab, lv_color_hex(0x0a0e27), 0);
lv_obj_clear_flag(tab, LV_OBJ_FLAG_SCROLLABLE);
/* --- IMU Chart --- */
lv_obj_t *imu_panel = lv_obj_create(tab);
lv_obj_set_size(imu_panel, 310, 160);
lv_obj_set_pos(imu_panel, 5, 5);
lv_obj_set_style_bg_color(imu_panel, lv_color_hex(0x111633), 0);
lv_obj_set_style_border_color(imu_panel, lv_color_hex(0x333366), 0);
lv_obj_set_style_border_width(imu_panel, 1, 0);
lv_obj_set_style_radius(imu_panel, 6, 0);
lv_obj_clear_flag(imu_panel, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t *imu_title = lv_label_create(imu_panel);
lv_label_set_text(imu_title, "IMU Accelerometer (via IPC)");
lv_obj_set_style_text_color(imu_title, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(imu_title, &lv_font_montserrat_12, 0);
lv_obj_set_pos(imu_title, 5, 2);
imu_chart = lv_chart_create(imu_panel);
lv_obj_set_size(imu_chart, 290, 100);
lv_obj_set_pos(imu_chart, 5, 18);
lv_chart_set_type(imu_chart, LV_CHART_TYPE_LINE);
lv_chart_set_point_count(imu_chart, 60);
lv_chart_set_range(imu_chart, LV_CHART_AXIS_PRIMARY_Y, -2000, 2000);
lv_obj_set_style_bg_color(imu_chart, lv_color_hex(0x0a0e27), 0);
lv_obj_set_style_line_width(imu_chart, 0, LV_PART_TICKS);
lv_chart_set_div_line_count(imu_chart, 3, 0);
imu_ser_x = lv_chart_add_series(imu_chart,
lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);
imu_ser_y = lv_chart_add_series(imu_chart,
lv_palette_main(LV_PALETTE_GREEN), LV_CHART_AXIS_PRIMARY_Y);
imu_ser_z = lv_chart_add_series(imu_chart,
lv_palette_main(LV_PALETTE_BLUE), LV_CHART_AXIS_PRIMARY_Y);
imu_value_label = lv_label_create(imu_panel);
lv_label_set_text(imu_value_label, "X:-- Y:-- Z:--");
lv_obj_set_style_text_color(imu_value_label, lv_color_hex(0xCCCCCC), 0);
lv_obj_set_style_text_font(imu_value_label, &lv_font_montserrat_12, 0);
lv_obj_set_pos(imu_value_label, 5, 125);
/* Legend */
lv_obj_t *legend = lv_label_create(imu_panel);
lv_label_set_text(legend, "R:X G:Y B:Z");
lv_obj_set_style_text_color(legend, lv_color_hex(0x888888), 0);
lv_obj_set_style_text_font(legend, &lv_font_montserrat_10, 0);
lv_obj_set_pos(legend, 220, 125);
/* --- ADC Gauge --- */
lv_obj_t *adc_panel = lv_obj_create(tab);
lv_obj_set_size(adc_panel, 140, 160);
lv_obj_set_pos(adc_panel, 325, 5);
lv_obj_set_style_bg_color(adc_panel, lv_color_hex(0x111633), 0);
lv_obj_set_style_border_color(adc_panel, lv_color_hex(0x333366), 0);
lv_obj_set_style_border_width(adc_panel, 1, 0);
lv_obj_set_style_radius(adc_panel, 6, 0);
lv_obj_clear_flag(adc_panel, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t *adc_title = lv_label_create(adc_panel);
lv_label_set_text(adc_title, "ADC (IPC)");
lv_obj_set_style_text_color(adc_title, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(adc_title, &lv_font_montserrat_12, 0);
lv_obj_set_pos(adc_title, 5, 2);
adc_arc = lv_arc_create(adc_panel);
lv_obj_set_size(adc_arc, 100, 100);
lv_obj_align(adc_arc, LV_ALIGN_CENTER, 0, 5);
lv_arc_set_range(adc_arc, 0, 100);
lv_arc_set_value(adc_arc, 0);
lv_arc_set_bg_angles(adc_arc, 135, 45);
lv_obj_remove_flag(adc_arc, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_style_arc_color(adc_arc,
lv_palette_main(LV_PALETTE_TEAL), LV_PART_INDICATOR);
lv_obj_set_style_arc_width(adc_arc, 10, LV_PART_INDICATOR);
lv_obj_set_style_arc_width(adc_arc, 10, LV_PART_MAIN);
adc_value_label = lv_label_create(adc_arc);
lv_label_set_text(adc_value_label, "--\n(0)");
lv_obj_set_style_text_color(adc_value_label, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(adc_value_label, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_align(adc_value_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_center(adc_value_label);
}
/*******************************************************************************
* Build Tab 3: Status
******************************************************************************/
static void build_status_tab(lv_obj_t *tab)
{
lv_obj_set_style_bg_color(tab, lv_color_hex(0x0a0e27), 0);
lv_obj_clear_flag(tab, LV_OBJ_FLAG_SCROLLABLE);
/* --- IPC Statistics Panel --- */
lv_obj_t *stats_panel = lv_obj_create(tab);
lv_obj_set_size(stats_panel, 200, 160);
lv_obj_set_pos(stats_panel, 5, 5);
lv_obj_set_style_bg_color(stats_panel, lv_color_hex(0x111633), 0);
lv_obj_set_style_border_color(stats_panel, lv_color_hex(0x333366), 0);
lv_obj_set_style_border_width(stats_panel, 1, 0);
lv_obj_set_style_radius(stats_panel, 6, 0);
lv_obj_clear_flag(stats_panel, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t *s_title = lv_label_create(stats_panel);
lv_label_set_text(s_title, "IPC Statistics");
lv_obj_set_style_text_color(s_title, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_pos(s_title, 5, 2);
lv_obj_t **stat_labels[] = {&stat_tx_label, &stat_rx_label,
&stat_err_label, &stat_rtt_label,
&stat_uptime_label};
const char *stat_defaults[] = {"TX: 0", "RX: 0", "Errors: 0",
"RTT: --", "Uptime: 0s"};
lv_color_t stat_colors[] = {
lv_palette_main(LV_PALETTE_CYAN),
lv_palette_main(LV_PALETTE_GREEN),
lv_palette_main(LV_PALETTE_RED),
lv_palette_main(LV_PALETTE_AMBER),
lv_palette_main(LV_PALETTE_GREY)
};
for (int i = 0; i < 5; i++) {
*stat_labels[i] = lv_label_create(stats_panel);
lv_label_set_text(*stat_labels[i], stat_defaults[i]);
lv_obj_set_style_text_color(*stat_labels[i], stat_colors[i], 0);
lv_obj_set_style_text_font(*stat_labels[i], &lv_font_montserrat_14, 0);
lv_obj_set_pos(*stat_labels[i], 10, 25 + i * 26);
}
/* --- Event Log Panel --- */
lv_obj_t *log_panel = lv_obj_create(tab);
lv_obj_set_size(log_panel, 255, 160);
lv_obj_set_pos(log_panel, 215, 5);
lv_obj_set_style_bg_color(log_panel, lv_color_hex(0x0d1117), 0);
lv_obj_set_style_border_color(log_panel, lv_color_hex(0x30363d), 0);
lv_obj_set_style_border_width(log_panel, 1, 0);
lv_obj_set_style_radius(log_panel, 6, 0);
lv_obj_clear_flag(log_panel, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t *log_title = lv_label_create(log_panel);
lv_label_set_text(log_title, "Event Log");
lv_obj_set_style_text_color(log_title, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_pos(log_title, 5, 2);
event_log_label = lv_label_create(log_panel);
lv_label_set_text(event_log_label, "(waiting...)");
lv_obj_set_style_text_color(event_log_label,
lv_palette_main(LV_PALETTE_LIME), 0);
lv_obj_set_style_text_font(event_log_label, &lv_font_montserrat_10, 0);
lv_obj_set_pos(event_log_label, 5, 20);
}
/*******************************************************************************
* Main Function: Build Full Dashboard
******************************************************************************/
void part4_ex7_hw_ipc_dashboard(void)
{
start_tick = xTaskGetTickCount();
/* Initialize event log */
for (int i = 0; i < EVENT_LOG_MAX; i++) {
event_log_buf[i][0] = '\0';
}
/* Register IPC callback */
cm55_ipc_register_callback(dashboard_ipc_rx_cb, NULL);
/* Background */
lv_obj_set_style_bg_color(lv_screen_active(),
lv_color_hex(0x0a0e27), LV_PART_MAIN);
lv_obj_clear_flag(lv_screen_active(), LV_OBJ_FLAG_SCROLLABLE);
/* Title bar */
lv_obj_t *title = lv_label_create(lv_screen_active());
lv_label_set_text(title, "IPC Dashboard");
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(title, &lv_font_montserrat_16, 0);
lv_obj_align(title, LV_ALIGN_TOP_LEFT, 10, 2);
/* ===== TabView ===== */
lv_obj_t *tv = lv_tabview_create(lv_screen_active());
lv_obj_set_size(tv, 480, 260);
lv_obj_align(tv, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_set_style_bg_color(tv, lv_color_hex(0x0a0e27), 0);
/* Style tab bar */
lv_obj_t *tab_bar = lv_tabview_get_tab_bar(tv);
lv_obj_set_style_bg_color(tab_bar, lv_color_hex(0x16213e), 0);
/* Create tabs */
lv_obj_t *tab1 = lv_tabview_add_tab(tv, "Controls");
lv_obj_t *tab2 = lv_tabview_add_tab(tv, "Sensors");
lv_obj_t *tab3 = lv_tabview_add_tab(tv, "Status");
/* Build tab contents */
build_controls_tab(tab1);
build_sensors_tab(tab2);
build_status_tab(tab3);
/* ===== Create Timers ===== */
/* UI update timer (50ms) */
lv_timer_create(dashboard_timer_cb, 50, NULL);
/* Sensor request timer (100ms) - requests IMU+ADC from CM33 */
lv_timer_create(sensor_request_timer_cb, 100, NULL);
/* Health check timer (5s) - sends PING */
lv_timer_create(health_timer_cb, 5000, NULL);
CM55_LOGI("Part4 Ex7: IPC Dashboard initialized");
log_event("Dashboard started");
}