Add CYD host PC info panel with serial SI protocol and LVGL bars.
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>
This commit is contained in:
152
src/host_info.cpp
Normal file
152
src/host_info.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "host_info.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
static HostInfo g_info = {};
|
||||
static char g_line[384];
|
||||
static size_t g_len = 0;
|
||||
|
||||
static void copy_trunc(char *dst, size_t dst_sz, const char *src) {
|
||||
if (!dst || dst_sz == 0) {
|
||||
return;
|
||||
}
|
||||
if (!src) {
|
||||
dst[0] = '\0';
|
||||
return;
|
||||
}
|
||||
strncpy(dst, src, dst_sz - 1);
|
||||
dst[dst_sz - 1] = '\0';
|
||||
}
|
||||
|
||||
static const char *find_field(const char *line, const char *key) {
|
||||
// Look for |key=value| or start-of-string after "SI|"
|
||||
const size_t klen = strlen(key);
|
||||
const char *p = line;
|
||||
while (p && *p) {
|
||||
if (strncmp(p, key, klen) == 0 && p[klen] == '=') {
|
||||
return p + klen + 1;
|
||||
}
|
||||
p = strchr(p, '|');
|
||||
if (!p) {
|
||||
break;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void copy_field(char *dst, size_t dst_sz, const char *line, const char *key) {
|
||||
const char *v = find_field(line, key);
|
||||
if (!v) {
|
||||
dst[0] = '\0';
|
||||
return;
|
||||
}
|
||||
size_t n = 0;
|
||||
while (v[n] && v[n] != '|' && n + 1 < dst_sz) {
|
||||
dst[n] = v[n];
|
||||
++n;
|
||||
}
|
||||
dst[n] = '\0';
|
||||
}
|
||||
|
||||
static float field_float(const char *line, const char *key, float fallback) {
|
||||
const char *v = find_field(line, key);
|
||||
if (!v || !*v) {
|
||||
return fallback;
|
||||
}
|
||||
return (float)atof(v);
|
||||
}
|
||||
|
||||
static void apply_line(const char *line) {
|
||||
// SI|host=...|cpu=...|cpu%=..|gpu=...|gpu%=..|ram=used/total|disk=used/total
|
||||
if (strncmp(line, "SI|", 3) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
HostInfo next = g_info;
|
||||
next.valid = true;
|
||||
next.updated_ms = millis();
|
||||
|
||||
copy_field(next.host, sizeof(next.host), line, "host");
|
||||
copy_field(next.cpu, sizeof(next.cpu), line, "cpu");
|
||||
copy_field(next.gpu, sizeof(next.gpu), line, "gpu");
|
||||
next.cpu_pct = field_float(line, "cpu%", next.cpu_pct);
|
||||
next.gpu_pct = field_float(line, "gpu%", next.gpu_pct);
|
||||
|
||||
const char *ram = find_field(line, "ram");
|
||||
if (ram) {
|
||||
float u = 0, t = 0;
|
||||
if (sscanf(ram, "%f/%f", &u, &t) == 2) {
|
||||
next.ram_used_gb = u;
|
||||
next.ram_total_gb = t;
|
||||
}
|
||||
}
|
||||
const char *disk = find_field(line, "disk");
|
||||
if (disk) {
|
||||
float u = 0, t = 0;
|
||||
if (sscanf(disk, "%f/%f", &u, &t) == 2) {
|
||||
next.disk_used_gb = u;
|
||||
next.disk_total_gb = t;
|
||||
}
|
||||
}
|
||||
|
||||
if (next.cpu_pct < 0) {
|
||||
next.cpu_pct = 0;
|
||||
}
|
||||
if (next.cpu_pct > 100) {
|
||||
next.cpu_pct = 100;
|
||||
}
|
||||
if (next.gpu_pct > 100) {
|
||||
next.gpu_pct = 100;
|
||||
}
|
||||
|
||||
g_info = next;
|
||||
}
|
||||
|
||||
void host_info_begin() {
|
||||
memset(&g_info, 0, sizeof(g_info));
|
||||
g_info.gpu_pct = -1.0f;
|
||||
g_info.valid = false;
|
||||
g_len = 0;
|
||||
g_line[0] = '\0';
|
||||
}
|
||||
|
||||
bool host_info_poll() {
|
||||
bool got = false;
|
||||
while (Serial.available() > 0) {
|
||||
const int c = Serial.read();
|
||||
if (c < 0) {
|
||||
break;
|
||||
}
|
||||
if (c == '\r') {
|
||||
continue;
|
||||
}
|
||||
if (c == '\n') {
|
||||
g_line[g_len] = '\0';
|
||||
if (g_len > 0) {
|
||||
apply_line(g_line);
|
||||
got = g_info.valid;
|
||||
}
|
||||
g_len = 0;
|
||||
continue;
|
||||
}
|
||||
if (g_len + 1 < sizeof(g_line)) {
|
||||
g_line[g_len++] = (char)c;
|
||||
} else {
|
||||
// Overflow — drop line
|
||||
g_len = 0;
|
||||
}
|
||||
}
|
||||
return got;
|
||||
}
|
||||
|
||||
const HostInfo &host_info_get() { return g_info; }
|
||||
|
||||
bool host_info_is_live(uint32_t stale_ms) {
|
||||
if (!g_info.valid) {
|
||||
return false;
|
||||
}
|
||||
return (millis() - g_info.updated_ms) <= stale_ms;
|
||||
}
|
||||
52
src/main.cpp
Normal file
52
src/main.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include <Arduino.h>
|
||||
#include <lvgl.h>
|
||||
|
||||
#include "host_info.h"
|
||||
#include "pins.h"
|
||||
#include "touch.h"
|
||||
#include "ui.h"
|
||||
|
||||
static constexpr uint32_t DRAW_BUF_SIZE =
|
||||
(SCREEN_WIDTH * SCREEN_HEIGHT / DRAW_BUF_DIV * (LV_COLOR_DEPTH / 8));
|
||||
static uint32_t draw_buf[DRAW_BUF_SIZE / 4];
|
||||
|
||||
void setup() {
|
||||
Serial.setRxBufferSize(2048);
|
||||
Serial.begin(460800);
|
||||
delay(200);
|
||||
Serial.println();
|
||||
Serial.printf("LVGL %d.%d.%d | CYD Server Info\n", lv_version_major(),
|
||||
lv_version_minor(), lv_version_patch());
|
||||
Serial.println("BOOT_OK");
|
||||
Serial.println("PROTO SI|host=..|cpu=..|cpu%=..|gpu=..|gpu%=..|ram=u/t|disk=u/t");
|
||||
Serial.printf("heap=%u BL=%d panel=%dx%d ui=%dx%d\n",
|
||||
(unsigned)ESP.getFreeHeap(), TFT_BL_PIN, SCREEN_WIDTH,
|
||||
SCREEN_HEIGHT, UI_HOR_RES, UI_VER_RES);
|
||||
|
||||
pinMode(TFT_BL_PIN, OUTPUT);
|
||||
digitalWrite(TFT_BL_PIN, HIGH);
|
||||
|
||||
touch_init();
|
||||
host_info_begin();
|
||||
|
||||
lv_init();
|
||||
lv_display_t *disp =
|
||||
lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
|
||||
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);
|
||||
|
||||
lv_indev_t *indev = lv_indev_create();
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
|
||||
lv_indev_set_read_cb(indev, touch_read_cb);
|
||||
lv_indev_set_display(indev, disp);
|
||||
|
||||
ui_create();
|
||||
Serial.printf("ui_ok heap=%u\n", (unsigned)ESP.getFreeHeap());
|
||||
}
|
||||
|
||||
void loop() {
|
||||
host_info_poll();
|
||||
lv_timer_handler();
|
||||
lv_tick_inc(5);
|
||||
ui_tick();
|
||||
delay(5);
|
||||
}
|
||||
284
src/touch.cpp
Normal file
284
src/touch.cpp
Normal file
@@ -0,0 +1,284 @@
|
||||
#include "touch.h"
|
||||
#include "pins.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(CYD_BOARD_3248S035)
|
||||
#include <Wire.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 2.8": dedicated SoftSPI + BL/TR diagonal fix.
|
||||
* 3.5C (3248S035C): capacitive GT911 on I2C SDA33/SCL32/INT21/RST25.
|
||||
*/
|
||||
|
||||
int16_t g_dbg_phys_x = -1;
|
||||
int16_t g_dbg_phys_y = -1;
|
||||
int16_t g_dbg_logic_x = -1;
|
||||
int16_t g_dbg_logic_y = -1;
|
||||
bool g_dbg_touch_down = false;
|
||||
|
||||
static int16_t clampi(int32_t v, int32_t lo, int32_t hi) {
|
||||
if (v < lo) {
|
||||
return (int16_t)lo;
|
||||
}
|
||||
if (v > hi) {
|
||||
return (int16_t)hi;
|
||||
}
|
||||
return (int16_t)v;
|
||||
}
|
||||
|
||||
#if defined(CYD_BOARD_3248S035)
|
||||
|
||||
/* Minimal GT911 — no config rewrite (TAMC setResolution can hang Wire). */
|
||||
static uint8_t s_gt_addr = 0x5D;
|
||||
static constexpr uint16_t GT_POINT_INFO = 0x814E;
|
||||
static constexpr uint16_t GT_POINT_1 = 0x814F;
|
||||
|
||||
static bool gt_write8(uint16_t reg, uint8_t val) {
|
||||
Wire.beginTransmission(s_gt_addr);
|
||||
Wire.write((uint8_t)(reg >> 8));
|
||||
Wire.write((uint8_t)(reg & 0xFF));
|
||||
Wire.write(val);
|
||||
return Wire.endTransmission() == 0;
|
||||
}
|
||||
|
||||
static bool gt_read(uint16_t reg, uint8_t *buf, uint8_t len) {
|
||||
Wire.beginTransmission(s_gt_addr);
|
||||
Wire.write((uint8_t)(reg >> 8));
|
||||
Wire.write((uint8_t)(reg & 0xFF));
|
||||
if (Wire.endTransmission(false) != 0) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t n = Wire.requestFrom(s_gt_addr, len);
|
||||
if (n != len) {
|
||||
return false;
|
||||
}
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
buf[i] = Wire.read();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool gt_probe(uint8_t addr) {
|
||||
Wire.beginTransmission(addr);
|
||||
return Wire.endTransmission() == 0;
|
||||
}
|
||||
|
||||
/* TAMC-compatible address select: INT low => 0x5D, INT high => 0x14 */
|
||||
static void gt_hw_reset(bool addr_0x14) {
|
||||
pinMode(CYD_GT911_INT, OUTPUT);
|
||||
pinMode(CYD_GT911_RST, OUTPUT);
|
||||
digitalWrite(CYD_GT911_INT, LOW);
|
||||
digitalWrite(CYD_GT911_RST, LOW);
|
||||
delay(10);
|
||||
digitalWrite(CYD_GT911_INT, addr_0x14 ? HIGH : LOW);
|
||||
delay(1);
|
||||
digitalWrite(CYD_GT911_RST, HIGH);
|
||||
delay(5);
|
||||
digitalWrite(CYD_GT911_INT, LOW);
|
||||
delay(50);
|
||||
/* Backlight is GPIO27 — leave INT as input for polling / optional IRQ. */
|
||||
pinMode(CYD_GT911_INT, INPUT);
|
||||
delay(50);
|
||||
}
|
||||
|
||||
static bool gt_find_addr() {
|
||||
for (uint8_t a : {uint8_t(0x5D), uint8_t(0x14)}) {
|
||||
if (gt_probe(a)) {
|
||||
s_gt_addr = a;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void touch_init() {
|
||||
Serial.println("Touch: GT911 I2C capacitive (3.5C) minimal");
|
||||
Wire.begin(CYD_GT911_SDA, CYD_GT911_SCL);
|
||||
Wire.setClock(100000);
|
||||
#if defined(WIRE_HAS_TIMEOUT) || defined(ESP32)
|
||||
Wire.setTimeOut(50);
|
||||
#endif
|
||||
|
||||
if (!gt_find_addr()) {
|
||||
Serial.println("GT911 not found — hw reset 0x5D");
|
||||
gt_hw_reset(false);
|
||||
if (!gt_find_addr()) {
|
||||
Serial.println("GT911 not found — hw reset 0x14");
|
||||
gt_hw_reset(true);
|
||||
gt_find_addr();
|
||||
}
|
||||
}
|
||||
|
||||
Serial.printf("GT911 probe 0x5D=%s 0x14=%s using=0x%02X\n",
|
||||
gt_probe(0x5D) ? "OK" : "no", gt_probe(0x14) ? "OK" : "no",
|
||||
s_gt_addr);
|
||||
Serial.printf("GT911 SDA=%d SCL=%d INT=%d RST=%d\n", CYD_GT911_SDA,
|
||||
CYD_GT911_SCL, CYD_GT911_INT, CYD_GT911_RST);
|
||||
}
|
||||
|
||||
void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
|
||||
uint8_t info = 0;
|
||||
static uint32_t last_log = 0;
|
||||
const bool do_log = (millis() - last_log > 200);
|
||||
|
||||
if (!gt_read(GT_POINT_INFO, &info, 1)) {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
g_dbg_touch_down = false;
|
||||
if (do_log) {
|
||||
last_log = millis();
|
||||
Serial.println("TouchProbe GT911 I2C err");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t touches = info & 0x0F;
|
||||
const bool buffered = (info & 0x80) != 0;
|
||||
|
||||
if (!buffered || touches == 0) {
|
||||
if (buffered) {
|
||||
gt_write8(GT_POINT_INFO, 0);
|
||||
}
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
g_dbg_touch_down = false;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t pt[7];
|
||||
if (!gt_read(GT_POINT_1, pt, 7)) {
|
||||
gt_write8(GT_POINT_INFO, 0);
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
g_dbg_touch_down = false;
|
||||
return;
|
||||
}
|
||||
gt_write8(GT_POINT_INFO, 0);
|
||||
|
||||
const int16_t raw_x = (int16_t)(pt[1] | (pt[2] << 8));
|
||||
const int16_t raw_y = (int16_t)(pt[3] | (pt[4] << 8));
|
||||
|
||||
/* Native 320x480 → LVGL 270. Mirror long axis so L/R matches UI
|
||||
* (else preset 3 hits 10). Resulting UI ≈ (H-1-raw_y, raw_x). */
|
||||
const int16_t nx = clampi(raw_x, 0, SCREEN_WIDTH - 1);
|
||||
const int16_t ny = clampi(raw_y, 0, SCREEN_HEIGHT - 1);
|
||||
const int16_t feed_x = (int16_t)((SCREEN_WIDTH - 1) - nx);
|
||||
const int16_t feed_y = (int16_t)((SCREEN_HEIGHT - 1) - ny);
|
||||
const int16_t ui_x = (int16_t)((SCREEN_HEIGHT - 1) - ny);
|
||||
const int16_t ui_y = nx;
|
||||
|
||||
g_dbg_phys_x = raw_x;
|
||||
g_dbg_phys_y = raw_y;
|
||||
g_dbg_logic_x = ui_x;
|
||||
g_dbg_logic_y = ui_y;
|
||||
g_dbg_touch_down = true;
|
||||
|
||||
if (do_log) {
|
||||
last_log = millis();
|
||||
Serial.printf("Touch HIT GT911 raw(%d,%d) ~ui(%d,%d)\n", raw_x, raw_y,
|
||||
ui_x, ui_y);
|
||||
}
|
||||
|
||||
data->point.x = feed_x;
|
||||
data->point.y = feed_y;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void soft_write(uint8_t data) {
|
||||
for (int i = 7; i >= 0; --i) {
|
||||
digitalWrite(CYD_TOUCH_MOSI, (data >> i) & 1);
|
||||
digitalWrite(CYD_TOUCH_CLK, HIGH);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(CYD_TOUCH_CLK, LOW);
|
||||
delayMicroseconds(2);
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t soft_read12() {
|
||||
uint16_t v = 0;
|
||||
digitalWrite(CYD_TOUCH_CLK, HIGH);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(CYD_TOUCH_CLK, LOW);
|
||||
delayMicroseconds(2);
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
digitalWrite(CYD_TOUCH_CLK, HIGH);
|
||||
delayMicroseconds(2);
|
||||
v = (uint16_t)((v << 1) | (digitalRead(CYD_TOUCH_MISO) ? 1u : 0u));
|
||||
digitalWrite(CYD_TOUCH_CLK, LOW);
|
||||
delayMicroseconds(2);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static uint16_t xpt_read(uint8_t cmd) {
|
||||
digitalWrite(CYD_TOUCH_CS, LOW);
|
||||
soft_write(cmd);
|
||||
uint16_t v = soft_read12();
|
||||
digitalWrite(CYD_TOUCH_CS, HIGH);
|
||||
return v;
|
||||
}
|
||||
|
||||
void touch_init() {
|
||||
pinMode(CYD_TOUCH_CS, OUTPUT);
|
||||
pinMode(CYD_TOUCH_CLK, OUTPUT);
|
||||
pinMode(CYD_TOUCH_MOSI, OUTPUT);
|
||||
pinMode(CYD_TOUCH_MISO, INPUT);
|
||||
pinMode(CYD_TOUCH_IRQ, INPUT);
|
||||
digitalWrite(CYD_TOUCH_CS, HIGH);
|
||||
digitalWrite(CYD_TOUCH_CLK, LOW);
|
||||
Serial.println("Touch: SoftSPI + diagonal fix (BL/TR)");
|
||||
}
|
||||
|
||||
void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
|
||||
uint16_t adc_x = xpt_read(0x90);
|
||||
uint16_t adc_y = xpt_read(0xD0);
|
||||
adc_x = (uint16_t)((adc_x + xpt_read(0x90)) / 2);
|
||||
adc_y = (uint16_t)((adc_y + xpt_read(0xD0)) / 2);
|
||||
|
||||
const bool irq = (digitalRead(CYD_TOUCH_IRQ) == LOW);
|
||||
const bool in_range =
|
||||
(adc_x > 80 && adc_x < 4000 && adc_y > 80 && adc_y < 4000);
|
||||
|
||||
if (!in_range || (!irq && (adc_x < 200 || adc_y < 200))) {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
g_dbg_touch_down = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t p_x = (int16_t)adc_x;
|
||||
const int16_t p_y = (int16_t)(4095 - adc_y);
|
||||
|
||||
int16_t x = map(p_x, 200, 3700, 1, SCREEN_WIDTH);
|
||||
int16_t y = map(p_y, 240, 3800, 1, SCREEN_HEIGHT);
|
||||
x = clampi(x, 0, SCREEN_WIDTH - 1);
|
||||
y = clampi(y, 0, SCREEN_HEIGHT - 1);
|
||||
|
||||
const int32_t old_ux = y;
|
||||
const int32_t old_uy = (SCREEN_WIDTH - 1) - x;
|
||||
int16_t ui_x = clampi((old_uy * (UI_HOR_RES - 1)) / (UI_VER_RES - 1), 0,
|
||||
UI_HOR_RES - 1);
|
||||
int16_t ui_y = clampi((old_ux * (UI_VER_RES - 1)) / (UI_HOR_RES - 1), 0,
|
||||
UI_VER_RES - 1);
|
||||
|
||||
const int16_t feed_x = (int16_t)((SCREEN_WIDTH - 1) - ui_y);
|
||||
const int16_t feed_y = ui_x;
|
||||
|
||||
g_dbg_phys_x = x;
|
||||
g_dbg_phys_y = y;
|
||||
g_dbg_logic_x = ui_x;
|
||||
g_dbg_logic_y = ui_y;
|
||||
g_dbg_touch_down = true;
|
||||
|
||||
static uint32_t last_log = 0;
|
||||
if (millis() - last_log > 80) {
|
||||
last_log = millis();
|
||||
Serial.printf("Touch UI(%d,%d)\n", ui_x, ui_y);
|
||||
}
|
||||
|
||||
data->point.x = feed_x;
|
||||
data->point.y = feed_y;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
}
|
||||
|
||||
#endif
|
||||
223
src/ui.cpp
Normal file
223
src/ui.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#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();
|
||||
}
|
||||
Reference in New Issue
Block a user