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:
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
.pio/
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
*.pyc
|
||||
__pycache__/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.elf
|
||||
# Build dir bins stay out; firmware/ prebuilt packages stay in
|
||||
!firmware/**/*.bin
|
||||
62
README.md
Normal file
62
README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# CYD 本机 / 服务器信息面板
|
||||
|
||||
在 **2.8" / 3.5" CYD** 上显示当前电脑的 **CPU、GPU、内存、磁盘** 等主要信息。显示/触控骨架对齐 [tftest](../tftest) / [wifitest](../wifitest);板型差异按 [CYD_PROJECT_GUIDE.md](../CYD_PROJECT_GUIDE.md)。
|
||||
|
||||
| 板型 | env | 屏 / 触控 | 背光 |
|
||||
|------|-----|-----------|------|
|
||||
| **2.8" · 2432S028R** | `cyd`(默认) | ILI9341 + XPT2046 SoftSPI | GPIO**21** |
|
||||
| **3.5" · 3248S035C** | `cyd_35r` | ST7796 + GT911 I2C | GPIO**27** |
|
||||
|
||||
本工程**无外设接线**(只吃 USB 供电 + 串口)。
|
||||
|
||||
## 原理
|
||||
|
||||
ESP32 无法直接读 PC 硬件。本机跑 [`host/send_sysinfo.ps1`](host/send_sysinfo.ps1),经 **USB 串口** 按行推送:
|
||||
|
||||
```
|
||||
SI|host=DESKTOP|cpu=i7-13700K|cpu%=12.3|gpu=RTX 4070|gpu%=5|ram=18.2/31.8|disk=512/931
|
||||
```
|
||||
|
||||
屏上 LVGL 面板解析后画进度条;超过约 5 秒无新包显示 **stale**。
|
||||
|
||||
GPU 占用:优先 `nvidia-smi`,否则尝试 Windows GPU Engine 计数器;都失败则显示 `n/a`(名称仍可读)。
|
||||
|
||||
## 刷写
|
||||
|
||||
| 板型 | 目录 |
|
||||
|------|------|
|
||||
| 2.8" | [`firmware/cyd/`](firmware/cyd/) |
|
||||
| 3.5" | [`firmware/cyd_35r/`](firmware/cyd_35r/) |
|
||||
|
||||
```powershell
|
||||
cd firmware\cyd # 或 firmware\cyd_35r
|
||||
.\flash.ps1
|
||||
# 或
|
||||
.\flash.ps1 -Port COM3
|
||||
```
|
||||
|
||||
源码烧录:`pio run -e cyd -t upload`(3.5:`-e cyd_35r`)
|
||||
重新打包:`.\firmware\export_from_build.ps1`(3.5 加 `-Env cyd_35r`)
|
||||
串口监视:`pio device monitor -b 460800`
|
||||
|
||||
## 推送本机信息
|
||||
|
||||
板子刷好并插上 USB 后,在 **PC** 上:
|
||||
|
||||
```powershell
|
||||
cd host
|
||||
.\send_sysinfo.ps1
|
||||
# 或
|
||||
.\send_sysinfo.ps1 -Port COM5 -IntervalMs 1000
|
||||
```
|
||||
|
||||
注意:串口同时只能被一个程序占用;跑推送脚本时不要开 `pio device monitor`。
|
||||
|
||||
## 文件
|
||||
|
||||
```
|
||||
include/pins.h, touch.h, host_info.h, ui.h, lv_conf.h
|
||||
src/main.cpp, touch.cpp, host_info.cpp, ui.cpp
|
||||
host/send_sysinfo.ps1
|
||||
firmware/
|
||||
```
|
||||
13
copy_lv_conf.py
Normal file
13
copy_lv_conf.py
Normal file
@@ -0,0 +1,13 @@
|
||||
Import("env") # noqa: F821
|
||||
from pathlib import Path
|
||||
|
||||
src = Path(env["PROJECT_DIR"]) / "include" / "lv_conf.h" # noqa: F821
|
||||
dst_dir = Path(env["PROJECT_LIBDEPS_DIR"]) / env["PIOENV"] # noqa: F821
|
||||
dst_dir.mkdir(parents=True, exist_ok=True)
|
||||
dst = dst_dir / "lv_conf.h"
|
||||
if src.exists():
|
||||
dst.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
lvgl = dst_dir / "lvgl" / "lv_conf.h"
|
||||
if (dst_dir / "lvgl").exists():
|
||||
lvgl.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
print(f"copy_lv_conf: synced -> {dst}")
|
||||
53
firmware/README.md
Normal file
53
firmware/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 预编译固件(可随时刷写)
|
||||
|
||||
| 目录 | 板子 |
|
||||
|------|------|
|
||||
| [`cyd/`](cyd/) | **2.8"** CYD ESP32-2432S028R(ILI9341) |
|
||||
| [`cyd_35r/`](cyd_35r/) | **3.5"** CYD ESP32-3248S035C(ST7796 + GT911) |
|
||||
|
||||
功能:**本机/服务器信息面板**(USB 串口收 `SI|...`,屏上显示 CPU/GPU/RAM/DISK)
|
||||
版本信息见各目录下 `VERSION.txt`。
|
||||
|
||||
也可在 [`../../cydweb`](../../cydweb)(CYD Flasher)里通过浏览器串口刷写本目录固件。
|
||||
|
||||
## 直接刷写(无需重新编译)
|
||||
|
||||
1. USB 连接板子
|
||||
2. 执行:
|
||||
|
||||
```powershell
|
||||
cd firmware\cyd # 3.5": firmware\cyd_35r
|
||||
.\flash.ps1
|
||||
# 或指定端口
|
||||
.\flash.ps1 -Port COM3
|
||||
```
|
||||
|
||||
也可运行 `flash.bat`。
|
||||
|
||||
依赖其一即可:
|
||||
|
||||
- 已安装 PlatformIO(`pio`,自带 esptool)
|
||||
- 或 `pip install esptool`
|
||||
|
||||
## 手动 esptool
|
||||
|
||||
```bash
|
||||
esptool.py --chip esp32 --port COM3 --baud 921600 write_flash -z ^
|
||||
--flash_mode dio --flash_freq 40m --flash_size 4MB ^
|
||||
0x1000 bootloader.bin ^
|
||||
0x8000 partitions.bin ^
|
||||
0xe000 boot_app0.bin ^
|
||||
0x10000 firmware.bin
|
||||
```
|
||||
|
||||
## 从源码重新打包进本目录
|
||||
|
||||
在仓库根目录:
|
||||
|
||||
```powershell
|
||||
.\firmware\export_from_build.ps1 # 2.8 -> firmware/cyd
|
||||
.\firmware\export_from_build.ps1 -Env cyd_35r # 3.5 -> firmware/cyd_35r
|
||||
.\firmware\export_from_build.ps1 -SkipBuild
|
||||
```
|
||||
|
||||
刷完后在 PC 上运行 `..\..\host\send_sysinfo.ps1` 推送本机信息。
|
||||
15
firmware/cyd/VERSION.txt
Normal file
15
firmware/cyd/VERSION.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
name: CYD serverinfotest (cyd)
|
||||
git: unknown
|
||||
date: 2026-10-27
|
||||
board: ESP32-2432S028R (ILI9341)
|
||||
flash_size: 4MB
|
||||
offsets:
|
||||
0x1000 bootloader.bin
|
||||
0x8000 partitions.bin
|
||||
0xe000 boot_app0.bin
|
||||
0x10000 firmware.bin
|
||||
sha256:
|
||||
bootloader.bin 3D234A7471F67B013686DABD4DEE7C1FA915C9928463616A94BC9297ACF1ABF8
|
||||
boot_app0.bin F94C5D786A7A8FAB06AC5D10E33BF37711A6697636DC037559EA19CC410A17F0
|
||||
firmware.bin 85838702F4062A3679CAE17D0365B1E14DDBB4C3BA235A0A6500FDDB84A56B6A
|
||||
partitions.bin 148B959CBFF1C38AA8E1D5C0BA9D612C54997B945E56A63F41223EEF650653A1
|
||||
BIN
firmware/cyd/boot_app0.bin
Normal file
BIN
firmware/cyd/boot_app0.bin
Normal file
Binary file not shown.
BIN
firmware/cyd/bootloader.bin
Normal file
BIN
firmware/cyd/bootloader.bin
Normal file
Binary file not shown.
BIN
firmware/cyd/firmware.bin
Normal file
BIN
firmware/cyd/firmware.bin
Normal file
Binary file not shown.
5
firmware/cyd/flash.bat
Normal file
5
firmware/cyd/flash.bat
Normal file
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
setlocal
|
||||
cd /d "%~dp0"
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0flash.ps1" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
98
firmware/cyd/flash.ps1
Normal file
98
firmware/cyd/flash.ps1
Normal file
@@ -0,0 +1,98 @@
|
||||
# Flash archived CYD Server Info panel firmware (no rebuild needed)
|
||||
# Usage:
|
||||
# .\flash.ps1
|
||||
# .\flash.ps1 -Port COM5
|
||||
# .\flash.ps1 -Port COM5 -Baud 460800
|
||||
|
||||
param(
|
||||
[string]$Port = "",
|
||||
[int]$Baud = 921600
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Set-Location $here
|
||||
|
||||
function Find-Esptool {
|
||||
$pio = Get-Command pio -ErrorAction SilentlyContinue
|
||||
if (-not $pio) {
|
||||
$pioPath = Join-Path $env:USERPROFILE "AppData\Local\Programs\Python\Python312\Scripts\pio.exe"
|
||||
if (Test-Path $pioPath) {
|
||||
$env:Path = "$(Split-Path $pioPath);$env:Path"
|
||||
$pio = Get-Command pio -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
if ($pio) {
|
||||
$pkg = Join-Path $env:USERPROFILE ".platformio\packages\tool-esptoolpy\esptool.py"
|
||||
if (Test-Path $pkg) { return @{ Kind = "py"; Path = $pkg } }
|
||||
}
|
||||
$py = Get-Command python -ErrorAction SilentlyContinue
|
||||
if ($py) {
|
||||
try {
|
||||
& python -c "import esptool" 2>$null
|
||||
if ($LASTEXITCODE -eq 0) { return @{ Kind = "mod"; Path = "esptool" } }
|
||||
} catch {}
|
||||
}
|
||||
$esp = Get-Command esptool.py -ErrorAction SilentlyContinue
|
||||
if ($esp) { return @{ Kind = "py"; Path = $esp.Source } }
|
||||
return $null
|
||||
}
|
||||
|
||||
if (-not $Port) {
|
||||
$ports = @()
|
||||
try {
|
||||
Add-Type -AssemblyName System.IO.Ports -ErrorAction SilentlyContinue | Out-Null
|
||||
$ports = [System.IO.Ports.SerialPort]::GetPortNames() | Sort-Object
|
||||
} catch {}
|
||||
if (-not $ports -or $ports.Count -eq 0) {
|
||||
$ports = Get-CimInstance Win32_SerialPort -ErrorAction SilentlyContinue |
|
||||
Select-Object -ExpandProperty DeviceID | Sort-Object
|
||||
}
|
||||
if (-not $ports -or $ports.Count -eq 0) {
|
||||
Write-Error "No serial ports found. Plug in the CYD USB cable, then retry with -Port COMx"
|
||||
}
|
||||
if ($ports.Count -eq 1) {
|
||||
$Port = $ports[0]
|
||||
} else {
|
||||
Write-Host "Available ports: $($ports -join ', ')"
|
||||
$Port = $ports[-1]
|
||||
Write-Host "Using last port: $Port (override with -Port)"
|
||||
}
|
||||
}
|
||||
|
||||
$tool = Find-Esptool
|
||||
if (-not $tool) {
|
||||
Write-Error "esptool not found. Install PlatformIO (`pio`) or: pip install esptool"
|
||||
}
|
||||
|
||||
$needed = @("bootloader.bin", "partitions.bin", "boot_app0.bin", "firmware.bin")
|
||||
foreach ($f in $needed) {
|
||||
if (-not (Test-Path (Join-Path $here $f))) {
|
||||
Write-Error "Missing $f — run ..\export_from_build.ps1 from repo root first"
|
||||
}
|
||||
}
|
||||
|
||||
$argsCommon = @(
|
||||
"--chip", "esp32",
|
||||
"--port", $Port,
|
||||
"--baud", "$Baud",
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
"--flash_mode", "dio",
|
||||
"--flash_freq", "40m",
|
||||
"--flash_size", "4MB",
|
||||
"0x1000", "bootloader.bin",
|
||||
"0x8000", "partitions.bin",
|
||||
"0xe000", "boot_app0.bin",
|
||||
"0x10000", "firmware.bin"
|
||||
)
|
||||
|
||||
Write-Host "Flashing $here -> $Port @ $Baud"
|
||||
if ($tool.Kind -eq "mod") {
|
||||
& python -m esptool @argsCommon
|
||||
} else {
|
||||
& python $tool.Path @argsCommon
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
Write-Host "Done. Open serial monitor at 460800 if needed."
|
||||
13
firmware/cyd/meta.json
Normal file
13
firmware/cyd/meta.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"boardId": "esp32-2432s028",
|
||||
"name": "CYD Server Info",
|
||||
"summary": "LVGL panel for host CPU / GPU / RAM / disk via USB serial",
|
||||
"description": "Receives SI|... lines from host/send_sysinfo.ps1 and shows live bars for CPU, GPU, memory and system drive. No external GPIO peripherals.",
|
||||
"tags": [
|
||||
"monitor",
|
||||
"host-info",
|
||||
"cpu",
|
||||
"gpu",
|
||||
"serial"
|
||||
]
|
||||
}
|
||||
BIN
firmware/cyd/partitions.bin
Normal file
BIN
firmware/cyd/partitions.bin
Normal file
Binary file not shown.
15
firmware/cyd_35r/VERSION.txt
Normal file
15
firmware/cyd_35r/VERSION.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
name: CYD serverinfotest (cyd_35r)
|
||||
git: unknown
|
||||
date: 2026-10-27
|
||||
board: ESP32-3248S035 (ST7796)
|
||||
flash_size: 4MB
|
||||
offsets:
|
||||
0x1000 bootloader.bin
|
||||
0x8000 partitions.bin
|
||||
0xe000 boot_app0.bin
|
||||
0x10000 firmware.bin
|
||||
sha256:
|
||||
bootloader.bin 3D234A7471F67B013686DABD4DEE7C1FA915C9928463616A94BC9297ACF1ABF8
|
||||
boot_app0.bin F94C5D786A7A8FAB06AC5D10E33BF37711A6697636DC037559EA19CC410A17F0
|
||||
firmware.bin 93C0D8E2A7C31848960C2DBA2894DE9D18BF21D3D94B47DDD5406CF18A1FD1B3
|
||||
partitions.bin 148B959CBFF1C38AA8E1D5C0BA9D612C54997B945E56A63F41223EEF650653A1
|
||||
BIN
firmware/cyd_35r/boot_app0.bin
Normal file
BIN
firmware/cyd_35r/boot_app0.bin
Normal file
Binary file not shown.
BIN
firmware/cyd_35r/bootloader.bin
Normal file
BIN
firmware/cyd_35r/bootloader.bin
Normal file
Binary file not shown.
BIN
firmware/cyd_35r/firmware.bin
Normal file
BIN
firmware/cyd_35r/firmware.bin
Normal file
Binary file not shown.
5
firmware/cyd_35r/flash.bat
Normal file
5
firmware/cyd_35r/flash.bat
Normal file
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
setlocal
|
||||
cd /d "%~dp0"
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0flash.ps1" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
98
firmware/cyd_35r/flash.ps1
Normal file
98
firmware/cyd_35r/flash.ps1
Normal file
@@ -0,0 +1,98 @@
|
||||
# Flash archived CYD Server Info panel firmware (no rebuild needed)
|
||||
# Usage:
|
||||
# .\flash.ps1
|
||||
# .\flash.ps1 -Port COM5
|
||||
# .\flash.ps1 -Port COM5 -Baud 460800
|
||||
|
||||
param(
|
||||
[string]$Port = "",
|
||||
[int]$Baud = 921600
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Set-Location $here
|
||||
|
||||
function Find-Esptool {
|
||||
$pio = Get-Command pio -ErrorAction SilentlyContinue
|
||||
if (-not $pio) {
|
||||
$pioPath = Join-Path $env:USERPROFILE "AppData\Local\Programs\Python\Python312\Scripts\pio.exe"
|
||||
if (Test-Path $pioPath) {
|
||||
$env:Path = "$(Split-Path $pioPath);$env:Path"
|
||||
$pio = Get-Command pio -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
if ($pio) {
|
||||
$pkg = Join-Path $env:USERPROFILE ".platformio\packages\tool-esptoolpy\esptool.py"
|
||||
if (Test-Path $pkg) { return @{ Kind = "py"; Path = $pkg } }
|
||||
}
|
||||
$py = Get-Command python -ErrorAction SilentlyContinue
|
||||
if ($py) {
|
||||
try {
|
||||
& python -c "import esptool" 2>$null
|
||||
if ($LASTEXITCODE -eq 0) { return @{ Kind = "mod"; Path = "esptool" } }
|
||||
} catch {}
|
||||
}
|
||||
$esp = Get-Command esptool.py -ErrorAction SilentlyContinue
|
||||
if ($esp) { return @{ Kind = "py"; Path = $esp.Source } }
|
||||
return $null
|
||||
}
|
||||
|
||||
if (-not $Port) {
|
||||
$ports = @()
|
||||
try {
|
||||
Add-Type -AssemblyName System.IO.Ports -ErrorAction SilentlyContinue | Out-Null
|
||||
$ports = [System.IO.Ports.SerialPort]::GetPortNames() | Sort-Object
|
||||
} catch {}
|
||||
if (-not $ports -or $ports.Count -eq 0) {
|
||||
$ports = Get-CimInstance Win32_SerialPort -ErrorAction SilentlyContinue |
|
||||
Select-Object -ExpandProperty DeviceID | Sort-Object
|
||||
}
|
||||
if (-not $ports -or $ports.Count -eq 0) {
|
||||
Write-Error "No serial ports found. Plug in the CYD USB cable, then retry with -Port COMx"
|
||||
}
|
||||
if ($ports.Count -eq 1) {
|
||||
$Port = $ports[0]
|
||||
} else {
|
||||
Write-Host "Available ports: $($ports -join ', ')"
|
||||
$Port = $ports[-1]
|
||||
Write-Host "Using last port: $Port (override with -Port)"
|
||||
}
|
||||
}
|
||||
|
||||
$tool = Find-Esptool
|
||||
if (-not $tool) {
|
||||
Write-Error "esptool not found. Install PlatformIO (`pio`) or: pip install esptool"
|
||||
}
|
||||
|
||||
$needed = @("bootloader.bin", "partitions.bin", "boot_app0.bin", "firmware.bin")
|
||||
foreach ($f in $needed) {
|
||||
if (-not (Test-Path (Join-Path $here $f))) {
|
||||
Write-Error "Missing $f — run ..\export_from_build.ps1 from repo root first"
|
||||
}
|
||||
}
|
||||
|
||||
$argsCommon = @(
|
||||
"--chip", "esp32",
|
||||
"--port", $Port,
|
||||
"--baud", "$Baud",
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
"--flash_mode", "dio",
|
||||
"--flash_freq", "40m",
|
||||
"--flash_size", "4MB",
|
||||
"0x1000", "bootloader.bin",
|
||||
"0x8000", "partitions.bin",
|
||||
"0xe000", "boot_app0.bin",
|
||||
"0x10000", "firmware.bin"
|
||||
)
|
||||
|
||||
Write-Host "Flashing $here -> $Port @ $Baud"
|
||||
if ($tool.Kind -eq "mod") {
|
||||
& python -m esptool @argsCommon
|
||||
} else {
|
||||
& python $tool.Path @argsCommon
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
Write-Host "Done. Open serial monitor at 460800 if needed."
|
||||
13
firmware/cyd_35r/meta.json
Normal file
13
firmware/cyd_35r/meta.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"boardId": "esp32-3248s035",
|
||||
"name": "CYD Server Info",
|
||||
"summary": "LVGL panel for host CPU / GPU / RAM / disk via USB serial",
|
||||
"description": "Receives SI|... lines from host/send_sysinfo.ps1 and shows live bars for CPU, GPU, memory and system drive. No external GPIO peripherals. 3.5C: ST7796 + GT911, BL=27.",
|
||||
"tags": [
|
||||
"monitor",
|
||||
"host-info",
|
||||
"cpu",
|
||||
"gpu",
|
||||
"serial"
|
||||
]
|
||||
}
|
||||
BIN
firmware/cyd_35r/partitions.bin
Normal file
BIN
firmware/cyd_35r/partitions.bin
Normal file
Binary file not shown.
71
firmware/export_from_build.ps1
Normal file
71
firmware/export_from_build.ps1
Normal file
@@ -0,0 +1,71 @@
|
||||
# Export firmware bins from PlatformIO build.
|
||||
# Default: 2.8" (cyd -> firmware/cyd)
|
||||
# 3.5": .\firmware\export_from_build.ps1 -Env cyd_35r
|
||||
# (only after: pio run -e cyd_35r)
|
||||
|
||||
param(
|
||||
[switch]$SkipBuild,
|
||||
[ValidateSet("cyd", "cyd_35r")]
|
||||
[string]$Env = "cyd"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
Set-Location $root
|
||||
|
||||
$env:Path = "C:\Users\dsx2016\AppData\Local\Programs\Python\Python312\Scripts;" + $env:Path
|
||||
|
||||
if (-not $SkipBuild) {
|
||||
pio run -e $Env
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
$src = Join-Path $root (".pio\build\" + $Env)
|
||||
if ($Env -eq "cyd_35r") {
|
||||
$dstName = "cyd_35r"
|
||||
$boardLine = "board: ESP32-3248S035 (ST7796)"
|
||||
$boardIdHint = "esp32-3248s035"
|
||||
} else {
|
||||
$dstName = "cyd"
|
||||
$boardLine = "board: ESP32-2432S028R (ILI9341)"
|
||||
$boardIdHint = "esp32-2432s028"
|
||||
}
|
||||
$dst = Join-Path $root ("firmware\" + $dstName)
|
||||
New-Item -ItemType Directory -Force -Path $dst | Out-Null
|
||||
|
||||
foreach ($f in @("bootloader.bin", "partitions.bin", "firmware.bin")) {
|
||||
$p = Join-Path $src $f
|
||||
if (-not (Test-Path $p)) { Write-Error "Missing build artifact: $p — build with: pio run -e $Env" }
|
||||
Copy-Item $p $dst -Force
|
||||
}
|
||||
|
||||
$bootApp = Join-Path $env:USERPROFILE ".platformio\packages\framework-arduinoespressif32\tools\partitions\boot_app0.bin"
|
||||
if (-not (Test-Path $bootApp)) {
|
||||
Write-Error "boot_app0.bin not found under PlatformIO packages"
|
||||
}
|
||||
Copy-Item $bootApp (Join-Path $dst "boot_app0.bin") -Force
|
||||
|
||||
$git = ""
|
||||
try { $git = (git -C $root rev-parse --short HEAD).Trim() } catch { $git = "unknown" }
|
||||
$date = Get-Date -Format "yyyy-MM-dd"
|
||||
$hashes = Get-ChildItem (Join-Path $dst "*.bin") | Get-FileHash -Algorithm SHA256
|
||||
$lines = @(
|
||||
"name: CYD serverinfotest ($dstName)",
|
||||
"git: $git",
|
||||
"date: $date",
|
||||
$boardLine,
|
||||
"flash_size: 4MB",
|
||||
"offsets:",
|
||||
" 0x1000 bootloader.bin",
|
||||
" 0x8000 partitions.bin",
|
||||
" 0xe000 boot_app0.bin",
|
||||
" 0x10000 firmware.bin",
|
||||
"sha256:"
|
||||
)
|
||||
foreach ($h in $hashes) {
|
||||
$lines += (" {0,-15} {1}" -f (Split-Path $h.Path -Leaf), $h.Hash)
|
||||
}
|
||||
$lines | Set-Content -Encoding utf8 (Join-Path $dst "VERSION.txt")
|
||||
|
||||
|
||||
Write-Host "Exported -> $dst (env=$Env git=$git)"
|
||||
162
host/send_sysinfo.ps1
Normal file
162
host/send_sysinfo.ps1
Normal file
@@ -0,0 +1,162 @@
|
||||
# Push this PC's CPU / GPU / RAM / disk stats to CYD over USB serial.
|
||||
# Usage:
|
||||
# .\send_sysinfo.ps1
|
||||
# .\send_sysinfo.ps1 -Port COM5
|
||||
# .\send_sysinfo.ps1 -Port COM5 -IntervalMs 1000
|
||||
|
||||
param(
|
||||
[string]$Port = "",
|
||||
[int]$IntervalMs = 1000,
|
||||
[int]$Baud = 460800
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Find-CydPort {
|
||||
$ports = @()
|
||||
try {
|
||||
Add-Type -AssemblyName System.IO.Ports -ErrorAction SilentlyContinue | Out-Null
|
||||
$ports = [System.IO.Ports.SerialPort]::GetPortNames() | Sort-Object
|
||||
} catch {}
|
||||
if (-not $ports -or $ports.Count -eq 0) {
|
||||
$ports = Get-CimInstance Win32_SerialPort -ErrorAction SilentlyContinue |
|
||||
Select-Object -ExpandProperty DeviceID | Sort-Object
|
||||
}
|
||||
if (-not $ports -or $ports.Count -eq 0) {
|
||||
throw "No serial ports found. Plug in the CYD USB cable."
|
||||
}
|
||||
if ($ports.Count -eq 1) { return $ports[0] }
|
||||
Write-Host "Available ports: $($ports -join ', ')"
|
||||
$pick = $ports[-1]
|
||||
Write-Host "Using last port: $pick (override with -Port)"
|
||||
return $pick
|
||||
}
|
||||
|
||||
function Sanitize([string]$s, [int]$max = 40) {
|
||||
if (-not $s) { return "" }
|
||||
$t = ($s -replace '[|=\r\n]', ' ').Trim()
|
||||
if ($t.Length -gt $max) { $t = $t.Substring(0, $max).Trim() }
|
||||
return $t
|
||||
}
|
||||
|
||||
function Get-CpuName {
|
||||
$p = Get-CimInstance Win32_Processor -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if (-not $p) { return "CPU" }
|
||||
$n = [string]$p.Name
|
||||
# Shorten common Intel/AMD noise
|
||||
$n = $n -replace '\(R\)|\(TM\)|CPU @.*|with Radeon.*', ''
|
||||
$n = ($n -replace '\s+', ' ').Trim()
|
||||
return Sanitize $n 44
|
||||
}
|
||||
|
||||
function Get-CpuPct {
|
||||
try {
|
||||
$c = Get-CimInstance Win32_Processor -ErrorAction Stop
|
||||
$vals = @($c | ForEach-Object { [double]$_.LoadPercentage })
|
||||
if ($vals.Count -eq 0) { return 0.0 }
|
||||
return [math]::Round(($vals | Measure-Object -Average).Average, 1)
|
||||
} catch {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
function Get-GpuName {
|
||||
$gpus = Get-CimInstance Win32_VideoController -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -and $_.Name -notmatch 'Microsoft Basic|Remote Desktop' }
|
||||
if (-not $gpus) { return "GPU" }
|
||||
# Prefer NVIDIA / AMD discrete if present
|
||||
$pick = $gpus | Where-Object { $_.Name -match 'NVIDIA|GeForce|RTX|GTX|Radeon|AMD' } |
|
||||
Select-Object -First 1
|
||||
if (-not $pick) { $pick = $gpus | Select-Object -First 1 }
|
||||
$n = [string]$pick.Name
|
||||
$n = $n -replace 'NVIDIA GeForce ', 'RTX/GTX '
|
||||
$n = $n -replace 'AMD ', ''
|
||||
return Sanitize $n 44
|
||||
}
|
||||
|
||||
function Get-GpuPct {
|
||||
# 1) nvidia-smi
|
||||
$smi = Get-Command nvidia-smi -ErrorAction SilentlyContinue
|
||||
if ($smi) {
|
||||
try {
|
||||
$out = & nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and $out) {
|
||||
$first = ([string]($out | Select-Object -First 1)).Trim()
|
||||
if ($first -match '^\d+') { return [double]$first }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
# 2) Windows GPU Engine counter (may need a few samples)
|
||||
try {
|
||||
$counters = Get-Counter '\GPU Engine(*)\Utilization Percentage' -ErrorAction Stop
|
||||
$samples = $counters.CounterSamples |
|
||||
Where-Object { $_.CookedValue -ge 0 -and $_.InstanceName -match 'engtype_3D|engtype_Graphics' }
|
||||
if ($samples) {
|
||||
$sum = ($samples | Measure-Object -Property CookedValue -Sum).Sum
|
||||
if ($sum -gt 100) { $sum = 100 }
|
||||
return [math]::Round($sum, 1)
|
||||
}
|
||||
} catch {}
|
||||
return -1.0
|
||||
}
|
||||
|
||||
function Get-RamGb {
|
||||
$os = Get-CimInstance Win32_OperatingSystem -ErrorAction Stop
|
||||
$total = [math]::Round($os.TotalVisibleMemorySize / 1MB, 1)
|
||||
$free = [math]::Round($os.FreePhysicalMemory / 1MB, 1)
|
||||
$used = [math]::Round($total - $free, 1)
|
||||
return @{ Used = $used; Total = $total }
|
||||
}
|
||||
|
||||
function Get-DiskGb {
|
||||
$d = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" -ErrorAction SilentlyContinue
|
||||
if (-not $d) {
|
||||
$d = Get-CimInstance Win32_LogicalDisk -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.DriveType -eq 3 -and $_.Size -gt 0 } |
|
||||
Select-Object -First 1
|
||||
}
|
||||
if (-not $d -or -not $d.Size) {
|
||||
return @{ Used = 0.0; Total = 0.0 }
|
||||
}
|
||||
$total = [math]::Round($d.Size / 1GB, 1)
|
||||
$free = [math]::Round($d.FreeSpace / 1GB, 1)
|
||||
$used = [math]::Round($total - $free, 1)
|
||||
return @{ Used = $used; Total = $total }
|
||||
}
|
||||
|
||||
function Build-Line {
|
||||
$hostName = Sanitize $env:COMPUTERNAME 36
|
||||
$cpu = Get-CpuName
|
||||
$cpuPct = Get-CpuPct
|
||||
$gpu = Get-GpuName
|
||||
$gpuPct = Get-GpuPct
|
||||
$ram = Get-RamGb
|
||||
$disk = Get-DiskGb
|
||||
$gpuField = if ($gpuPct -lt 0) { "gpu%=-1" } else { "gpu%=$gpuPct" }
|
||||
return "SI|host=$hostName|cpu=$cpu|cpu%=$cpuPct|gpu=$gpu|$gpuField|ram=$($ram.Used)/$($ram.Total)|disk=$($disk.Used)/$($disk.Total)"
|
||||
}
|
||||
|
||||
if (-not $Port) { $Port = Find-CydPort }
|
||||
|
||||
Add-Type -AssemblyName System.IO.Ports
|
||||
$sp = [System.IO.Ports.SerialPort]::new($Port, $Baud)
|
||||
$sp.ReadTimeout = 200
|
||||
$sp.WriteTimeout = 2000
|
||||
$sp.NewLine = "`n"
|
||||
$sp.DtrEnable = $true
|
||||
$sp.RtsEnable = $true
|
||||
|
||||
try {
|
||||
$sp.Open()
|
||||
Write-Host "Sending host info -> $Port @ $Baud every ${IntervalMs}ms (Ctrl+C to stop)"
|
||||
Start-Sleep -Milliseconds 400
|
||||
while ($true) {
|
||||
$line = Build-Line
|
||||
$sp.WriteLine($line)
|
||||
Write-Host $line
|
||||
Start-Sleep -Milliseconds $IntervalMs
|
||||
}
|
||||
} finally {
|
||||
if ($sp.IsOpen) { $sp.Close() }
|
||||
$sp.Dispose()
|
||||
}
|
||||
29
include/host_info.h
Normal file
29
include/host_info.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** Max chars for hostname / CPU / GPU name shown on UI. */
|
||||
static constexpr int HOST_NAME_MAX = 40;
|
||||
static constexpr int HOST_CPU_MAX = 48;
|
||||
static constexpr int HOST_GPU_MAX = 48;
|
||||
|
||||
struct HostInfo {
|
||||
char host[HOST_NAME_MAX];
|
||||
char cpu[HOST_CPU_MAX];
|
||||
char gpu[HOST_GPU_MAX];
|
||||
float cpu_pct; // 0..100
|
||||
float gpu_pct; // 0..100, -1 if unknown
|
||||
float ram_used_gb;
|
||||
float ram_total_gb;
|
||||
float disk_used_gb;
|
||||
float disk_total_gb;
|
||||
bool valid;
|
||||
uint32_t updated_ms;
|
||||
};
|
||||
|
||||
void host_info_begin();
|
||||
/** Poll USB Serial for SI|... lines; return true if a packet was applied. */
|
||||
bool host_info_poll();
|
||||
const HostInfo &host_info_get();
|
||||
/** True if last packet is fresher than stale_ms. */
|
||||
bool host_info_is_live(uint32_t stale_ms = 5000);
|
||||
1127
include/lv_conf.h
Normal file
1127
include/lv_conf.h
Normal file
File diff suppressed because it is too large
Load Diff
42
include/pins.h
Normal file
42
include/pins.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
// Board pins — 2.8" default; 3.5" when CYD_BOARD_3248S035 is set (env:cyd_35r)
|
||||
|
||||
#if defined(CYD_BOARD_3248S035)
|
||||
#define TFT_BL_PIN 27
|
||||
#define SCREEN_WIDTH 320
|
||||
#define SCREEN_HEIGHT 480
|
||||
#define UI_HOR_RES 480
|
||||
#define UI_VER_RES 320
|
||||
/* 3.5C capacitive GT911 (ESP32-3248S035C). GPIO33 is I2C SDA, not TOUCH_CS.
|
||||
* Backlight is GPIO27; INT is GPIO21 — they are NOT the same pin. */
|
||||
#define CYD_GT911_SDA 33
|
||||
#define CYD_GT911_SCL 32
|
||||
#define CYD_GT911_INT 21
|
||||
#define CYD_GT911_RST 25
|
||||
#else
|
||||
#define TFT_BL_PIN 21
|
||||
#define SCREEN_WIDTH 240
|
||||
#define SCREEN_HEIGHT 320
|
||||
#define UI_HOR_RES 320
|
||||
#define UI_VER_RES 240
|
||||
/* 2.8" CYD: dedicated SoftSPI touch bus. */
|
||||
#define CYD_TOUCH_IRQ 36
|
||||
#define CYD_TOUCH_MOSI 32
|
||||
#define CYD_TOUCH_MISO 39
|
||||
#define CYD_TOUCH_CLK 25
|
||||
#define CYD_TOUCH_CS 33
|
||||
#endif
|
||||
|
||||
/* Map 320x240 design coordinates onto the active UI resolution (identity on 2.8"). */
|
||||
#define UX(x) ((int)(((long)(x) * (UI_HOR_RES)) / 320))
|
||||
#define UY(y) ((int)(((long)(y) * (UI_VER_RES)) / 240))
|
||||
#define UW(w) UX(w)
|
||||
#define UH(h) UY(h)
|
||||
|
||||
/* Partial frame buffer: 3.5 needs smaller ratio to fit DRAM. */
|
||||
#if defined(CYD_BOARD_3248S035)
|
||||
#define DRAW_BUF_DIV 20
|
||||
#else
|
||||
#define DRAW_BUF_DIV 10
|
||||
#endif
|
||||
7
include/touch.h
Normal file
7
include/touch.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void touch_init();
|
||||
void touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data);
|
||||
4
include/ui.h
Normal file
4
include/ui.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void ui_create();
|
||||
void ui_tick();
|
||||
109
platformio.ini
Normal file
109
platformio.ini
Normal file
@@ -0,0 +1,109 @@
|
||||
; CYD — host PC / server info panel (USB serial feed + LVGL)
|
||||
[env:cyd]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
monitor_speed = 460800
|
||||
upload_speed = 460800
|
||||
board_build.partitions = default.csv
|
||||
|
||||
lib_deps =
|
||||
bodmer/TFT_eSPI@^2.5.43
|
||||
lvgl/lvgl@9.2.2
|
||||
|
||||
build_flags =
|
||||
-DCORE_DEBUG_LEVEL=0
|
||||
-DUSER_SETUP_LOADED=1
|
||||
-DILI9341_2_DRIVER=1
|
||||
-DTFT_WIDTH=240
|
||||
-DTFT_HEIGHT=320
|
||||
-DTFT_MISO=12
|
||||
-DTFT_MOSI=13
|
||||
-DTFT_SCLK=14
|
||||
-DTFT_CS=15
|
||||
-DTFT_DC=2
|
||||
-DTFT_RST=-1
|
||||
-DTFT_BL=21
|
||||
-DTFT_BACKLIGHT_ON=HIGH
|
||||
-DTOUCH_CS=-1
|
||||
-DLOAD_GLCD=1
|
||||
-DLOAD_FONT2=1
|
||||
-DLOAD_FONT4=1
|
||||
-DLOAD_FONT6=1
|
||||
-DLOAD_FONT7=1
|
||||
-DLOAD_FONT8=1
|
||||
-DLOAD_GFXFF=1
|
||||
-DSMOOTH_FONT=1
|
||||
-DSPI_FREQUENCY=40000000
|
||||
-DSPI_READ_FREQUENCY=20000000
|
||||
-DLV_CONF_SKIP
|
||||
-DLV_COLOR_DEPTH=16
|
||||
-DLV_MEM_SIZE=49152
|
||||
-DLV_FONT_MONTSERRAT_14=1
|
||||
-DLV_FONT_MONTSERRAT_16=1
|
||||
-DLV_FONT_MONTSERRAT_18=1
|
||||
-DLV_FONT_MONTSERRAT_20=1
|
||||
-DLV_USE_TFT_ESPI=1
|
||||
-DLV_USE_FLEX=1
|
||||
-DLV_USE_LABEL=1
|
||||
-DLV_USE_BAR=1
|
||||
-I${PROJECT_DIR}/include
|
||||
|
||||
extra_scripts = pre:copy_lv_conf.py
|
||||
|
||||
; Optional 3.5" CYD ESP32-3248S035C (ST7796 + GT911 capacitive). Default [env:cyd].
|
||||
; Build: pio run -e cyd_35r
|
||||
; Keep TOUCH_CS=-1 — GPIO33 is GT911 SDA, not resistive touch CS.
|
||||
; TFT_BL=GPIO27 (not 21 — 21 is GT911 INT). See ../CYD_PROJECT_GUIDE.md
|
||||
[env:cyd_35r]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
monitor_speed = 460800
|
||||
upload_speed = 460800
|
||||
board_build.partitions = default.csv
|
||||
lib_deps =
|
||||
bodmer/TFT_eSPI@^2.5.43
|
||||
lvgl/lvgl@9.2.2
|
||||
extra_scripts = pre:copy_lv_conf.py
|
||||
build_flags =
|
||||
-DCORE_DEBUG_LEVEL=0
|
||||
-DUSER_SETUP_LOADED=1
|
||||
-DST7796_DRIVER=1
|
||||
-DTFT_WIDTH=320
|
||||
-DTFT_HEIGHT=480
|
||||
-DTFT_MISO=12
|
||||
-DTFT_MOSI=13
|
||||
-DTFT_SCLK=14
|
||||
-DTFT_CS=15
|
||||
-DTFT_DC=2
|
||||
-DTFT_RST=-1
|
||||
-DTFT_BL=27
|
||||
-DTFT_BACKLIGHT_ON=HIGH
|
||||
-DTOUCH_CS=-1
|
||||
-DLOAD_GLCD=1
|
||||
-DLOAD_FONT2=1
|
||||
-DLOAD_FONT4=1
|
||||
-DLOAD_FONT6=1
|
||||
-DLOAD_FONT7=1
|
||||
-DLOAD_FONT8=1
|
||||
-DLOAD_GFXFF=1
|
||||
-DSMOOTH_FONT=1
|
||||
-DSPI_FREQUENCY=40000000
|
||||
-DSPI_READ_FREQUENCY=20000000
|
||||
-DUSE_HSPI_PORT
|
||||
-DSUPPORT_TRANSACTIONS
|
||||
-DTFT_RGB_ORDER=TFT_BGR
|
||||
-DCYD_BOARD_3248S035=1
|
||||
-DLV_CONF_SKIP
|
||||
-DLV_COLOR_DEPTH=16
|
||||
-DLV_MEM_SIZE=32768
|
||||
-DLV_FONT_MONTSERRAT_14=1
|
||||
-DLV_FONT_MONTSERRAT_16=1
|
||||
-DLV_FONT_MONTSERRAT_18=1
|
||||
-DLV_FONT_MONTSERRAT_20=1
|
||||
-DLV_USE_TFT_ESPI=1
|
||||
-DLV_USE_FLEX=1
|
||||
-DLV_USE_LABEL=1
|
||||
-DLV_USE_BAR=1
|
||||
-I${PROJECT_DIR}/include
|
||||
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