ESP32 shows CPU/GPU/RAM/disk from host/send_sysinfo.ps1 over USB; firmware packages for 2.8 and 3.5 included. Co-authored-by: Cursor <cursoragent@cursor.com>
224 lines
6.9 KiB
C++
224 lines
6.9 KiB
C++
#include "ui.h"
|
|
|
|
#include "host_info.h"
|
|
#include "pins.h"
|
|
|
|
#include <Arduino.h>
|
|
#include <cstdio>
|
|
#include <lvgl.h>
|
|
|
|
static constexpr uint32_t COL_BG = 0x0B121A;
|
|
static constexpr uint32_t COL_INK = 0xE8EEF4;
|
|
static constexpr uint32_t COL_MUTED = 0x7A8B9C;
|
|
static constexpr uint32_t COL_LINE = 0x1A2734;
|
|
static constexpr uint32_t COL_OK = 0x3DDC97;
|
|
static constexpr uint32_t COL_WARN = 0xE8B84A;
|
|
static constexpr uint32_t COL_BAD = 0xE85D4C;
|
|
static constexpr uint32_t COL_ACCENT = 0x4FC3F7;
|
|
static constexpr uint32_t COL_BAR_BG = 0x162230;
|
|
|
|
struct MetricRow {
|
|
lv_obj_t *lbl_tag;
|
|
lv_obj_t *lbl_name;
|
|
lv_obj_t *lbl_pct;
|
|
lv_obj_t *bar;
|
|
};
|
|
|
|
static lv_obj_t *scr = nullptr;
|
|
static lv_obj_t *lbl_title = nullptr;
|
|
static lv_obj_t *lbl_host = nullptr;
|
|
static lv_obj_t *lbl_status = nullptr;
|
|
static MetricRow row_cpu = {};
|
|
static MetricRow row_gpu = {};
|
|
static MetricRow row_ram = {};
|
|
static MetricRow row_disk = {};
|
|
static uint32_t last_paint_ms = 0;
|
|
|
|
static int pct_of(float used, float total) {
|
|
if (total <= 0.01f) {
|
|
return 0;
|
|
}
|
|
int p = (int)((used * 100.0f) / total + 0.5f);
|
|
if (p < 0) {
|
|
p = 0;
|
|
}
|
|
if (p > 100) {
|
|
p = 100;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
static uint32_t color_for_pct(int pct) {
|
|
if (pct >= 85) {
|
|
return COL_BAD;
|
|
}
|
|
if (pct >= 65) {
|
|
return COL_WARN;
|
|
}
|
|
return COL_OK;
|
|
}
|
|
|
|
static void style_bar(lv_obj_t *bar) {
|
|
lv_obj_set_style_bg_color(bar, lv_color_hex(COL_BAR_BG), LV_PART_MAIN);
|
|
lv_obj_set_style_bg_opa(bar, LV_OPA_COVER, LV_PART_MAIN);
|
|
lv_obj_set_style_radius(bar, 4, LV_PART_MAIN);
|
|
lv_obj_set_style_pad_all(bar, 0, LV_PART_MAIN);
|
|
lv_obj_set_style_bg_color(bar, lv_color_hex(COL_OK), LV_PART_INDICATOR);
|
|
lv_obj_set_style_bg_opa(bar, LV_OPA_COVER, LV_PART_INDICATOR);
|
|
lv_obj_set_style_radius(bar, 4, LV_PART_INDICATOR);
|
|
lv_bar_set_range(bar, 0, 100);
|
|
lv_bar_set_value(bar, 0, LV_ANIM_OFF);
|
|
}
|
|
|
|
static MetricRow make_row(lv_obj_t *parent, const char *tag, int y) {
|
|
MetricRow r = {};
|
|
|
|
r.lbl_tag = lv_label_create(parent);
|
|
lv_label_set_text(r.lbl_tag, tag);
|
|
lv_obj_set_style_text_color(r.lbl_tag, lv_color_hex(COL_ACCENT), 0);
|
|
lv_obj_set_style_text_font(r.lbl_tag, &lv_font_montserrat_14, 0);
|
|
lv_obj_set_pos(r.lbl_tag, UX(8), UY(y));
|
|
|
|
r.lbl_name = lv_label_create(parent);
|
|
lv_label_set_text(r.lbl_name, "—");
|
|
lv_label_set_long_mode(r.lbl_name, LV_LABEL_LONG_DOT);
|
|
lv_obj_set_width(r.lbl_name, UW(190));
|
|
lv_obj_set_style_text_color(r.lbl_name, lv_color_hex(COL_INK), 0);
|
|
lv_obj_set_style_text_font(r.lbl_name, &lv_font_montserrat_14, 0);
|
|
lv_obj_set_pos(r.lbl_name, UX(52), UY(y));
|
|
|
|
r.lbl_pct = lv_label_create(parent);
|
|
lv_label_set_text(r.lbl_pct, "--%");
|
|
lv_obj_set_style_text_color(r.lbl_pct, lv_color_hex(COL_MUTED), 0);
|
|
lv_obj_set_style_text_font(r.lbl_pct, &lv_font_montserrat_14, 0);
|
|
lv_obj_align(r.lbl_pct, LV_ALIGN_TOP_RIGHT, UX(-8), UY(y));
|
|
|
|
r.bar = lv_bar_create(parent);
|
|
lv_obj_set_size(r.bar, UI_HOR_RES - UW(16), UH(10));
|
|
lv_obj_set_pos(r.bar, UX(8), UY(y + 18));
|
|
style_bar(r.bar);
|
|
|
|
return r;
|
|
}
|
|
|
|
static void set_row_pct(MetricRow &r, int pct, const char *name, const char *right) {
|
|
if (r.lbl_name && name) {
|
|
lv_label_set_text(r.lbl_name, name);
|
|
}
|
|
if (r.lbl_pct && right) {
|
|
lv_label_set_text(r.lbl_pct, right);
|
|
lv_obj_set_style_text_color(r.lbl_pct, lv_color_hex(color_for_pct(pct)), 0);
|
|
}
|
|
if (r.bar) {
|
|
lv_bar_set_value(r.bar, pct, LV_ANIM_OFF);
|
|
lv_obj_set_style_bg_color(r.bar, lv_color_hex(color_for_pct(pct)),
|
|
LV_PART_INDICATOR);
|
|
}
|
|
}
|
|
|
|
void ui_create() {
|
|
scr = lv_obj_create(nullptr);
|
|
lv_obj_set_style_bg_color(scr, lv_color_hex(COL_BG), 0);
|
|
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
|
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
|
|
lv_screen_load(scr);
|
|
|
|
lbl_title = lv_label_create(scr);
|
|
lv_label_set_text(lbl_title, "PC Monitor");
|
|
lv_obj_set_style_text_color(lbl_title, lv_color_hex(COL_INK), 0);
|
|
lv_obj_set_style_text_font(lbl_title, &lv_font_montserrat_18, 0);
|
|
lv_obj_set_pos(lbl_title, UX(8), UY(6));
|
|
|
|
lbl_status = lv_label_create(scr);
|
|
lv_label_set_text(lbl_status, "waiting");
|
|
lv_obj_set_style_text_color(lbl_status, lv_color_hex(COL_WARN), 0);
|
|
lv_obj_set_style_text_font(lbl_status, &lv_font_montserrat_14, 0);
|
|
lv_obj_align(lbl_status, LV_ALIGN_TOP_RIGHT, UX(-8), UY(8));
|
|
|
|
lbl_host = lv_label_create(scr);
|
|
lv_label_set_text(lbl_host, "Connect USB + run host script");
|
|
lv_label_set_long_mode(lbl_host, LV_LABEL_LONG_DOT);
|
|
lv_obj_set_width(lbl_host, UI_HOR_RES - UW(16));
|
|
lv_obj_set_style_text_color(lbl_host, lv_color_hex(COL_MUTED), 0);
|
|
lv_obj_set_style_text_font(lbl_host, &lv_font_montserrat_14, 0);
|
|
lv_obj_set_pos(lbl_host, UX(8), UY(28));
|
|
|
|
lv_obj_t *line = lv_obj_create(scr);
|
|
lv_obj_set_size(line, UI_HOR_RES - UW(16), 1);
|
|
lv_obj_set_pos(line, UX(8), UY(48));
|
|
lv_obj_set_style_bg_color(line, lv_color_hex(COL_LINE), 0);
|
|
lv_obj_set_style_bg_opa(line, LV_OPA_COVER, 0);
|
|
lv_obj_set_style_border_width(line, 0, 0);
|
|
lv_obj_set_style_pad_all(line, 0, 0);
|
|
lv_obj_clear_flag(line, LV_OBJ_FLAG_SCROLLABLE);
|
|
|
|
row_cpu = make_row(scr, "CPU", 56);
|
|
row_gpu = make_row(scr, "GPU", 96);
|
|
row_ram = make_row(scr, "RAM", 136);
|
|
row_disk = make_row(scr, "DISK", 176);
|
|
|
|
last_paint_ms = 0;
|
|
}
|
|
|
|
static void paint() {
|
|
const HostInfo &h = host_info_get();
|
|
const bool live = host_info_is_live(5000);
|
|
|
|
if (lbl_status) {
|
|
if (!h.valid) {
|
|
lv_label_set_text(lbl_status, "waiting");
|
|
lv_obj_set_style_text_color(lbl_status, lv_color_hex(COL_WARN), 0);
|
|
} else if (live) {
|
|
lv_label_set_text(lbl_status, "live");
|
|
lv_obj_set_style_text_color(lbl_status, lv_color_hex(COL_OK), 0);
|
|
} else {
|
|
lv_label_set_text(lbl_status, "stale");
|
|
lv_obj_set_style_text_color(lbl_status, lv_color_hex(COL_BAD), 0);
|
|
}
|
|
}
|
|
|
|
if (!h.valid) {
|
|
return;
|
|
}
|
|
|
|
if (lbl_host) {
|
|
lv_label_set_text(lbl_host, h.host[0] ? h.host : "(unknown host)");
|
|
}
|
|
|
|
char buf[48];
|
|
|
|
int cpu_pct = (int)(h.cpu_pct + 0.5f);
|
|
snprintf(buf, sizeof(buf), "%d%%", cpu_pct);
|
|
set_row_pct(row_cpu, cpu_pct, h.cpu[0] ? h.cpu : "CPU", buf);
|
|
|
|
if (h.gpu_pct < 0) {
|
|
set_row_pct(row_gpu, 0, h.gpu[0] ? h.gpu : "GPU", "n/a");
|
|
if (row_gpu.lbl_pct) {
|
|
lv_obj_set_style_text_color(row_gpu.lbl_pct, lv_color_hex(COL_MUTED), 0);
|
|
}
|
|
} else {
|
|
int gpu_pct = (int)(h.gpu_pct + 0.5f);
|
|
snprintf(buf, sizeof(buf), "%d%%", gpu_pct);
|
|
set_row_pct(row_gpu, gpu_pct, h.gpu[0] ? h.gpu : "GPU", buf);
|
|
}
|
|
|
|
int ram_pct = pct_of(h.ram_used_gb, h.ram_total_gb);
|
|
snprintf(buf, sizeof(buf), "%.1f/%.0fG", (double)h.ram_used_gb,
|
|
(double)h.ram_total_gb);
|
|
set_row_pct(row_ram, ram_pct, "Memory", buf);
|
|
|
|
int disk_pct = pct_of(h.disk_used_gb, h.disk_total_gb);
|
|
snprintf(buf, sizeof(buf), "%.0f/%.0fG", (double)h.disk_used_gb,
|
|
(double)h.disk_total_gb);
|
|
set_row_pct(row_disk, disk_pct, "System drive", buf);
|
|
}
|
|
|
|
void ui_tick() {
|
|
const uint32_t now = millis();
|
|
if (now - last_paint_ms < 200) {
|
|
return;
|
|
}
|
|
last_paint_ms = now;
|
|
paint();
|
|
}
|