Merge pull request #89 from jpirnay/refactor-sysinfo-ui

refactor: Structure visual appearance of SystemInformation
This commit is contained in:
jpirnay
2026-04-16 13:29:26 +02:00
committed by GitHub
5 changed files with 269 additions and 116 deletions
+10
View File
@@ -481,6 +481,16 @@ STR_MIN_FREE: "Min free"
STR_MAX_BLOCK: "Max block" STR_MAX_BLOCK: "Max block"
STR_FLASH_USED: "Flash used" STR_FLASH_USED: "Flash used"
STR_UPTIME: "Uptime" STR_UPTIME: "Uptime"
STR_SEC_VERSION: "Version"
STR_SEC_CHIP: "Chip"
STR_SEC_MEMORY: "Memory"
STR_SEC_RUNTIME: "Runtime"
STR_SEC_STORAGE: "Storage"
STR_MEM_COMBINED: "Free / Min / Max"
STR_DEVICE: "Device"
STR_SEC_FLASH: "Flash"
STR_APP_PARTITION: "App partition"
STR_FLASH_TOTAL: "Total flash"
STR_CHARGING: "Charging" STR_CHARGING: "Charging"
STR_GATHERING_DATA: "Gathering data..." STR_GATHERING_DATA: "Gathering data..."
STR_READING: "Reading..." STR_READING: "Reading..."
+21 -5
View File
@@ -5,13 +5,18 @@
#include <Logging.h> #include <Logging.h>
#include <WiFi.h> #include <WiFi.h>
#include <esp_heap_caps.h> #include <esp_heap_caps.h>
#include <esp_ota_ops.h>
#include "HalGPIO.h"
#include "HalPowerManager.h" #include "HalPowerManager.h"
// Snapshot of device system status, shared between the web server and the // Snapshot of device system status, shared between the web server and the
// System Information activity so both surfaces show consistent data. // System Information activity so both surfaces show consistent data.
struct SystemStatus { struct SystemStatus {
const char* version; const char* version;
const char* deviceType; // "X3" or "X4"
uint16_t displayWidth; // Native panel width in pixels (long edge)
uint16_t displayHeight; // Native panel height in pixels (short edge)
std::string chipVersion; std::string chipVersion;
uint32_t cpuFreqMHz; uint32_t cpuFreqMHz;
std::string ip; std::string ip;
@@ -21,9 +26,8 @@ struct SystemStatus {
uint32_t freeHeapBytes; uint32_t freeHeapBytes;
uint32_t minFreeHeapBytes; uint32_t minFreeHeapBytes;
uint32_t maxAllocHeapBytes; uint32_t maxAllocHeapBytes;
uint64_t flashBytes; uint64_t flashBytes; // Total flash chip size
uint64_t flashAppUsedBytes; uint64_t flashAppPartitionSize; // Size of the running OTA app partition
uint64_t flashAppFreeBytes;
uint16_t batteryPercent; uint16_t batteryPercent;
bool charging; bool charging;
uint32_t uptimeSeconds; uint32_t uptimeSeconds;
@@ -34,6 +38,15 @@ struct SystemStatus {
static SystemStatus collectFast() { static SystemStatus collectFast() {
SystemStatus s; SystemStatus s;
s.version = CROSSPOINT_VERSION; s.version = CROSSPOINT_VERSION;
if (gpio.deviceIsX3()) {
s.deviceType = "X3";
s.displayWidth = 792;
s.displayHeight = 528;
} else {
s.deviceType = "X4";
s.displayWidth = 800;
s.displayHeight = 480;
}
s.chipVersion = ESP.getChipModel(); s.chipVersion = ESP.getChipModel();
s.chipVersion += " rev "; s.chipVersion += " rev ";
s.chipVersion += std::to_string(ESP.getChipRevision()); s.chipVersion += std::to_string(ESP.getChipRevision());
@@ -42,8 +55,11 @@ struct SystemStatus {
s.minFreeHeapBytes = ESP.getMinFreeHeap(); s.minFreeHeapBytes = ESP.getMinFreeHeap();
s.maxAllocHeapBytes = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); s.maxAllocHeapBytes = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
s.flashBytes = static_cast<uint64_t>(ESP.getFlashChipSize()); s.flashBytes = static_cast<uint64_t>(ESP.getFlashChipSize());
s.flashAppUsedBytes = static_cast<uint64_t>(ESP.getSketchSize()); // ESP.getSketchSize() is unreliable on custom partition layouts (the
s.flashAppFreeBytes = static_cast<uint64_t>(ESP.getFreeSketchSpace()); // underlying esp_image_verify() call silently fails), so we report the
// running OTA partition capacity instead — a firm, measurable number.
const esp_partition_t* running = esp_ota_get_running_partition();
s.flashAppPartitionSize = running ? static_cast<uint64_t>(running->size) : 0;
s.batteryPercent = powerManager.getBatteryPercentage(); s.batteryPercent = powerManager.getBatteryPercentage();
s.charging = digitalRead(UART0_RXD) == HIGH; s.charging = digitalRead(UART0_RXD) == HIGH;
s.uptimeSeconds = millis() / 1000; s.uptimeSeconds = millis() / 1000;
@@ -3,21 +3,53 @@
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <I18n.h> #include <I18n.h>
#include <algorithm>
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "SystemStatus.h" #include "SystemStatus.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
static const char* pickUnit(uint64_t maxBytes, double& outDivisor) {
if (maxBytes >= 1024ULL * 1024 * 1024) {
outDivisor = 1024.0 * 1024.0 * 1024.0;
return "GB";
}
if (maxBytes >= 1024ULL * 1024) {
outDivisor = 1024.0 * 1024.0;
return "MB";
}
if (maxBytes >= 1024ULL) {
outDivisor = 1024.0;
return "KB";
}
outDivisor = 1.0;
return "B";
}
static std::string formatBytes(uint64_t bytes) { static std::string formatBytes(uint64_t bytes) {
double div;
const char* unit = pickUnit(bytes, div);
char buf[16]; char buf[16];
if (bytes >= 1024ULL * 1024 * 1024) { if (div == 1.0) {
snprintf(buf, sizeof(buf), "%.1f GB", bytes / (1024.0 * 1024.0 * 1024.0)); snprintf(buf, sizeof(buf), "%llu %s", static_cast<unsigned long long>(bytes), unit);
} else if (bytes >= 1024ULL * 1024) {
snprintf(buf, sizeof(buf), "%.1f MB", bytes / (1024.0 * 1024.0));
} else if (bytes >= 1024ULL) {
snprintf(buf, sizeof(buf), "%.1f KB", bytes / 1024.0);
} else { } else {
snprintf(buf, sizeof(buf), "%llu B", static_cast<unsigned long long>(bytes)); snprintf(buf, sizeof(buf), "%.1f %s", bytes / div, unit);
}
return buf;
}
// Format three byte values on a single line sharing one trailing unit. The
// unit is chosen from the largest of the three so all values fit sensibly.
static std::string formatBytesTriple(uint64_t a, uint64_t b, uint64_t c) {
double div;
const char* unit = pickUnit(std::max({a, b, c}), div);
char buf[48];
if (div == 1.0) {
snprintf(buf, sizeof(buf), "%llu / %llu / %llu %s", static_cast<unsigned long long>(a),
static_cast<unsigned long long>(b), static_cast<unsigned long long>(c), unit);
} else {
snprintf(buf, sizeof(buf), "%.1f / %.1f / %.1f %s", a / div, b / div, c / div, unit);
} }
return buf; return buf;
} }
@@ -62,29 +94,40 @@ void SystemInformationActivity::loop() {
void SystemInformationActivity::render(RenderLock&&) { void SystemInformationActivity::render(RenderLock&&) {
const auto& metrics = UITheme::getInstance().getMetrics(); const auto& metrics = UITheme::getInstance().getMetrics();
const auto pageWidth = renderer.getScreenWidth(); const Rect contentRect = UITheme::getContentRect(renderer, /*hasBottomHints=*/true, /*hasSideHints=*/false);
renderer.clearScreen(); renderer.clearScreen();
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_SYSTEM_INFO), GUI.drawHeader(renderer,
CROSSPOINT_VERSION); Rect{contentRect.x, contentRect.y + metrics.topPadding, contentRect.width, metrics.headerHeight},
tr(STR_SYSTEM_INFO), CROSSPOINT_VERSION);
// Layout: label on the left, value right of the midpoint // Two-column layout with interleaved section headers (drawn via the theme's
const int leftX = metrics.verticalSpacing * 3; // subheader so the full-width underline is consistent with the rest of the
const int valueX = pageWidth / 2; // UI). Data rows use a bold label on the left and the value at the column
// midpoint; row step is tightened so all sections fit on one screen.
const int leftX = contentRect.x + metrics.verticalSpacing * 3;
const int valueX = contentRect.x + contentRect.width / 2;
const int lineH = renderer.getLineHeight(UI_10_FONT_ID); const int lineH = renderer.getLineHeight(UI_10_FONT_ID);
const int startY = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing * 3; const int rowStep = lineH + 2;
const int subHeaderHeight = lineH + 6;
int y = contentRect.y + metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing;
auto drawRow = [&](int row, const char* label, const std::string& value) { auto drawSection = [&](const char* title) {
const int y = startY + row * (lineH + metrics.verticalSpacing); GUI.drawSubHeader(renderer, Rect{contentRect.x, y, contentRect.width, subHeaderHeight}, title);
y += subHeaderHeight + 2;
};
auto drawRow = [&](const char* label, const std::string& value) {
renderer.drawText(UI_10_FONT_ID, leftX, y, label, true, EpdFontFamily::BOLD); renderer.drawText(UI_10_FONT_ID, leftX, y, label, true, EpdFontFamily::BOLD);
renderer.drawText(UI_10_FONT_ID, valueX, y, value.c_str()); renderer.drawText(UI_10_FONT_ID, valueX, y, value.c_str());
y += rowStep;
}; };
if (!status_.has_value()) { if (!status_.has_value()) {
// Stats not yet collected — show a placeholder so the screen updates immediately // Stats not yet collected — show a placeholder so the screen updates immediately
drawRow(0, tr(STR_FW_VERSION), CROSSPOINT_VERSION); drawRow(tr(STR_FW_VERSION), CROSSPOINT_VERSION);
drawRow(2, "", tr(STR_GATHERING_DATA)); y += rowStep;
drawRow("", tr(STR_GATHERING_DATA));
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer(); renderer.displayBuffer();
@@ -93,37 +136,46 @@ void SystemInformationActivity::render(RenderLock&&) {
const auto& status = *status_; const auto& status = *status_;
drawRow(0, tr(STR_FW_VERSION), status.version); drawSection(tr(STR_SEC_VERSION));
drawRow(1, tr(STR_CHIP), status.chipVersion); drawRow(tr(STR_FW_VERSION), status.version);
drawRow(2, tr(STR_CPU), std::to_string(status.cpuFreqMHz) + " " + tr(STR_MHZ)); drawRow(tr(STR_DEVICE), std::string(status.deviceType) + " (" + std::to_string(status.displayWidth) + " x " +
drawRow(3, tr(STR_FREE_RAM), formatBytes(status.freeHeapBytes)); std::to_string(status.displayHeight) + " px)");
drawRow(4, tr(STR_MIN_FREE), formatBytes(status.minFreeHeapBytes));
drawRow(5, tr(STR_MAX_BLOCK), formatBytes(status.maxAllocHeapBytes)); drawSection(tr(STR_SEC_CHIP));
drawRow( drawRow(tr(STR_CHIP), status.chipVersion);
6, tr(STR_FLASH_USED), drawRow(tr(STR_CPU), std::to_string(status.cpuFreqMHz) + " " + tr(STR_MHZ));
formatBytes(status.flashAppUsedBytes) + " / " + formatBytes(status.flashAppUsedBytes + status.flashAppFreeBytes));
drawSection(tr(STR_SEC_MEMORY));
drawRow(tr(STR_MEM_COMBINED),
formatBytesTriple(status.freeHeapBytes, status.minFreeHeapBytes, status.maxAllocHeapBytes));
drawSection(tr(STR_SEC_FLASH));
drawRow(tr(STR_APP_PARTITION), formatBytes(status.flashAppPartitionSize));
drawRow(tr(STR_FLASH_TOTAL), formatBytes(status.flashBytes));
drawSection(tr(STR_SEC_RUNTIME));
const uint32_t h = status.uptimeSeconds / 3600;
const uint32_t m = (status.uptimeSeconds % 3600) / 60;
const uint32_t s = status.uptimeSeconds % 60;
char uptimeBuf[16];
snprintf(uptimeBuf, sizeof(uptimeBuf), "%uh %02um %02us", h, m, s);
drawRow(tr(STR_UPTIME), uptimeBuf);
std::string batteryLabel = std::to_string(status.batteryPercent) + "%"; std::string batteryLabel = std::to_string(status.batteryPercent) + "%";
if (status.charging) { if (status.charging) {
batteryLabel += " ("; batteryLabel += " (";
batteryLabel += tr(STR_CHARGING); batteryLabel += tr(STR_CHARGING);
batteryLabel += ")"; batteryLabel += ")";
} }
drawRow(7, tr(STR_BATTERY), batteryLabel); drawRow(tr(STR_BATTERY), batteryLabel);
const uint32_t h = status.uptimeSeconds / 3600;
const uint32_t m = (status.uptimeSeconds % 3600) / 60;
const uint32_t s = status.uptimeSeconds % 60;
char uptimeBuf[16];
snprintf(uptimeBuf, sizeof(uptimeBuf), "%uh %02um %02us", h, m, s);
drawRow(8, tr(STR_UPTIME), uptimeBuf);
drawSection(tr(STR_SEC_STORAGE));
if (!sdStatusReady_) { if (!sdStatusReady_) {
const char* sdMessage = sdLoadRequested_ ? tr(STR_READING) : tr(STR_SD_UPDATE_PROMPT); const char* sdMessage = sdLoadRequested_ ? tr(STR_READING) : tr(STR_SD_UPDATE_PROMPT);
drawRow(9, tr(STR_SD_CARD), sdMessage); drawRow(tr(STR_SD_CARD), sdMessage);
} else if (status.sdTotalBytes > 0) { } else if (status.sdTotalBytes > 0) {
drawRow(9, tr(STR_SD_CARD), formatBytes(status.sdUsedBytes) + " / " + formatBytes(status.sdTotalBytes)); drawRow(tr(STR_SD_CARD), formatBytes(status.sdUsedBytes) + " / " + formatBytes(status.sdTotalBytes));
} else { } else {
drawRow(9, tr(STR_SD_CARD), tr(STR_NOT_SET)); drawRow(tr(STR_SD_CARD), tr(STR_NOT_SET));
} }
const auto labels = mappedInput.mapLabels(tr(STR_BACK), sdStatusReady_ ? "" : tr(STR_UPDATE), "", ""); const auto labels = mappedInput.mapLabels(tr(STR_BACK), sdStatusReady_ ? "" : tr(STR_UPDATE), "", "");
+8 -4
View File
@@ -402,6 +402,9 @@ void CrossPointWebServer::handleStatus() const {
JsonDocument doc; JsonDocument doc;
doc["version"] = status.version; doc["version"] = status.version;
doc["deviceType"] = status.deviceType;
doc["displayWidth"] = status.displayWidth;
doc["displayHeight"] = status.displayHeight;
doc["chipVersion"] = status.chipVersion; doc["chipVersion"] = status.chipVersion;
doc["cpuMHz"] = status.cpuFreqMHz; doc["cpuMHz"] = status.cpuFreqMHz;
doc["ip"] = status.ip; doc["ip"] = status.ip;
@@ -412,8 +415,7 @@ void CrossPointWebServer::handleStatus() const {
doc["minFreeHeap"] = status.minFreeHeapBytes; doc["minFreeHeap"] = status.minFreeHeapBytes;
doc["maxAllocHeap"] = status.maxAllocHeapBytes; doc["maxAllocHeap"] = status.maxAllocHeapBytes;
doc["flashTotal"] = status.flashBytes; doc["flashTotal"] = status.flashBytes;
doc["flashAppUsed"] = status.flashAppUsedBytes; doc["appPartitionSize"] = status.flashAppPartitionSize;
doc["flashAppFree"] = status.flashAppFreeBytes;
doc["batteryPercent"] = status.batteryPercent; doc["batteryPercent"] = status.batteryPercent;
doc["charging"] = status.charging; doc["charging"] = status.charging;
doc["uptime"] = status.uptimeSeconds; doc["uptime"] = status.uptimeSeconds;
@@ -436,6 +438,9 @@ void CrossPointWebServer::handleStatusFast() const {
JsonDocument doc; JsonDocument doc;
doc["version"] = status.version; doc["version"] = status.version;
doc["deviceType"] = status.deviceType;
doc["displayWidth"] = status.displayWidth;
doc["displayHeight"] = status.displayHeight;
doc["chipVersion"] = status.chipVersion; doc["chipVersion"] = status.chipVersion;
doc["cpuMHz"] = status.cpuFreqMHz; doc["cpuMHz"] = status.cpuFreqMHz;
doc["ip"] = status.ip; doc["ip"] = status.ip;
@@ -446,8 +451,7 @@ void CrossPointWebServer::handleStatusFast() const {
doc["minFreeHeap"] = status.minFreeHeapBytes; doc["minFreeHeap"] = status.minFreeHeapBytes;
doc["maxAllocHeap"] = status.maxAllocHeapBytes; doc["maxAllocHeap"] = status.maxAllocHeapBytes;
doc["flashTotal"] = status.flashBytes; doc["flashTotal"] = status.flashBytes;
doc["flashAppUsed"] = status.flashAppUsedBytes; doc["appPartitionSize"] = status.flashAppPartitionSize;
doc["flashAppFree"] = status.flashAppFreeBytes;
doc["batteryPercent"] = status.batteryPercent; doc["batteryPercent"] = status.batteryPercent;
doc["charging"] = status.charging; doc["charging"] = status.charging;
doc["uptime"] = status.uptimeSeconds; doc["uptime"] = status.uptimeSeconds;
+140 -69
View File
@@ -58,6 +58,25 @@
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} }
.section {
margin-top: 18px;
}
.section:first-of-type {
margin-top: 0;
}
.section-title {
font-size: 0.85em;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--label-color);
margin: 0 0 4px 0;
padding-bottom: 4px;
border-bottom: 1px solid var(--accent-color);
}
.info-row { .info-row {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -130,6 +149,18 @@
opacity: 0.6; opacity: 0.6;
cursor: default; cursor: default;
} }
.debug-footer {
margin-top: 24px;
padding-top: 8px;
border-top: 1px dashed var(--border-color);
display: flex;
justify-content: space-between;
gap: 12px;
font-size: 0.75em;
color: var(--label-color);
opacity: 0.7;
}
</style> </style>
</head> </head>
@@ -144,68 +175,93 @@
<div class="card"> <div class="card">
<h2>Device Status</h2> <h2>Device Status</h2>
<div class="info-row">
<span class="label">Version</span> <div class="section">
<span class="value" id="version">Loading...</span> <div class="section-title">Version</div>
<div class="info-row">
<span class="label">Firmware</span>
<span class="value" id="version">Loading...</span>
</div>
<div class="info-row">
<span class="label">Device</span>
<span class="value" id="device">Loading...</span>
</div>
</div> </div>
<div class="info-row">
<span class="label">Chip</span> <div class="section">
<span class="value" id="chip-version">Loading...</span> <div class="section-title">Chip</div>
<div class="info-row">
<span class="label">Chip</span>
<span class="value" id="chip-version">Loading...</span>
</div>
<div class="info-row">
<span class="label">CPU</span>
<span class="value" id="cpu-mhz">Loading...</span>
</div>
</div> </div>
<div class="info-row">
<span class="label">CPU</span> <div class="section">
<span class="value" id="cpu-mhz">Loading...</span> <div class="section-title">Network</div>
<div class="info-row">
<span class="label">WiFi Status</span>
<span class="status" id="wifi-status">Loading...</span>
</div>
<div class="info-row">
<span class="label">IP Address</span>
<span class="value" id="ip-address">Loading...</span>
</div>
<div class="info-row">
<span class="label">MAC Address</span>
<span class="value" id="mac-address">Loading...</span>
</div>
</div> </div>
<div class="info-row">
<span class="label">WiFi Status</span> <div class="section">
<span class="status" id="wifi-status">Loading...</span> <div class="section-title">Memory</div>
<div class="info-row">
<span class="label">Free / Min / Max</span>
<span class="value" id="heap-combined">Loading...</span>
</div>
</div> </div>
<div class="info-row">
<span class="label">IP Address</span> <div class="section">
<span class="value" id="ip-address">Loading...</span> <div class="section-title">Flash</div>
<div class="info-row">
<span class="label">App partition</span>
<span class="value" id="app-partition-size">Loading...</span>
</div>
<div class="info-row">
<span class="label">Total flash</span>
<span class="value" id="flash-total">Loading...</span>
</div>
</div> </div>
<div class="info-row">
<span class="label">MAC Address</span> <div class="section">
<span class="value" id="mac-address">Loading...</span> <div class="section-title">Runtime</div>
<div class="info-row">
<span class="label">Uptime</span>
<span class="value" id="uptime">Loading...</span>
</div>
<div class="info-row">
<span class="label">Battery</span>
<span class="value" id="battery">Loading...</span>
</div>
</div> </div>
<div class="info-row">
<span class="label">Free Memory</span> <div class="section">
<span class="value" id="free-heap">Loading...</span> <div class="section-title">Storage</div>
<div class="info-row">
<span class="label">SD Card</span>
<span class="value" id="sd-space">Not loaded</span>
</div>
<div class="action-row">
<button id="load-sd-button">Load SD info</button>
</div>
</div> </div>
<div class="info-row">
<span class="label">Min Free</span> <div class="debug-footer">
<span class="value" id="min-free-heap">Loading...</span> <span>Fast fetch: <span id="fast-status-duration"></span></span>
</div> <span>Full fetch: <span id="full-status-duration"></span></span>
<div class="info-row">
<span class="label">Max Block</span>
<span class="value" id="max-alloc-heap">Loading...</span>
</div>
<div class="info-row">
<span class="label">Flash Used</span>
<span class="value" id="flash-used">Loading...</span>
</div>
<div class="info-row">
<span class="label">Battery</span>
<span class="value" id="battery">Loading...</span>
</div>
<div class="info-row">
<span class="label">Uptime</span>
<span class="value" id="uptime">Loading...</span>
</div>
<div class="info-row">
<span class="label">Fast status fetch</span>
<span class="value" id="fast-status-duration">Loading...</span>
</div>
<div class="info-row">
<span class="label">Full status fetch</span>
<span class="value" id="full-status-duration">Not loaded</span>
</div>
<div class="info-row">
<span class="label">SD Card</span>
<span class="value" id="sd-space">Not loaded</span>
</div>
<div class="action-row">
<button id="load-sd-button">Load SD info</button>
</div> </div>
</div> </div>
@@ -215,12 +271,25 @@
</p> </p>
</div> </div>
<script> <script>
function pickUnit(maxBytes) {
const units = ['B', 'KB', 'MB', 'GB'];
let div = 1, i = 0;
while (maxBytes >= 1024 && i < units.length - 1) { maxBytes /= 1024; div *= 1024; i++; }
return { unit: units[i], div };
}
function formatBytes(bytes) { function formatBytes(bytes) {
if (bytes == null) return 'N/A'; if (bytes == null) return 'N/A';
const units = ['B', 'KB', 'MB', 'GB']; const { unit, div } = pickUnit(bytes);
let val = bytes, i = 0; if (div === 1) return bytes.toLocaleString() + ' ' + unit;
while (val >= 1024 && i < units.length - 1) { val /= 1024; i++; } return parseFloat((bytes / div).toFixed(2)).toLocaleString() + ' ' + unit;
return parseFloat(val.toFixed(2)).toLocaleString() + ' ' + units[i]; }
function formatBytesTriple(a, b, c) {
if (a == null || b == null || c == null) return 'N/A';
const { unit, div } = pickUnit(Math.max(a, b, c));
const fmt = (v) => div === 1 ? v.toLocaleString() : parseFloat((v / div).toFixed(2)).toLocaleString();
return fmt(a) + ' / ' + fmt(b) + ' / ' + fmt(c) + ' ' + unit;
} }
const SD_LABELS = { const SD_LABELS = {
@@ -246,6 +315,12 @@
function applyStatus(data, includeSd) { function applyStatus(data, includeSd) {
document.getElementById('version').textContent = data.version || 'N/A'; document.getElementById('version').textContent = data.version || 'N/A';
const deviceLabel = data.deviceType
? (data.displayWidth && data.displayHeight
? data.deviceType + ' (' + data.displayWidth + ' × ' + data.displayHeight + ' px)'
: data.deviceType)
: 'N/A';
document.getElementById('device').textContent = deviceLabel;
document.getElementById('chip-version').textContent = data.chipVersion || 'N/A'; document.getElementById('chip-version').textContent = data.chipVersion || 'N/A';
document.getElementById('cpu-mhz').textContent = data.cpuMHz document.getElementById('cpu-mhz').textContent = data.cpuMHz
? data.cpuMHz.toLocaleString() + ' MHz' ? data.cpuMHz.toLocaleString() + ' MHz'
@@ -254,17 +329,13 @@
wifiStatus.textContent = data.mode || 'N/A'; wifiStatus.textContent = data.mode || 'N/A';
document.getElementById('ip-address').textContent = data.ip || 'N/A'; document.getElementById('ip-address').textContent = data.ip || 'N/A';
document.getElementById('mac-address').textContent = data.macAddress || 'N/A'; document.getElementById('mac-address').textContent = data.macAddress || 'N/A';
document.getElementById('free-heap').textContent = data.freeHeap document.getElementById('heap-combined').textContent =
? formatBytes(data.freeHeap) formatBytesTriple(data.freeHeap, data.minFreeHeap, data.maxAllocHeap);
document.getElementById('app-partition-size').textContent = data.appPartitionSize != null
? formatBytes(data.appPartitionSize)
: 'N/A'; : 'N/A';
document.getElementById('min-free-heap').textContent = data.minFreeHeap document.getElementById('flash-total').textContent = data.flashTotal != null
? formatBytes(data.minFreeHeap) ? formatBytes(data.flashTotal)
: 'N/A';
document.getElementById('max-alloc-heap').textContent = data.maxAllocHeap
? formatBytes(data.maxAllocHeap)
: 'N/A';
document.getElementById('flash-used').textContent = data.flashAppUsed
? (formatBytes(data.flashAppUsed) + ' / ' + formatBytes((data.flashAppUsed || 0) + (data.flashAppFree || 0)))
: 'N/A'; : 'N/A';
document.getElementById('battery').textContent = (data.batteryPercent != null) document.getElementById('battery').textContent = (data.batteryPercent != null)
? (data.batteryPercent + '%' + (data.charging ? ' (Charging)' : '')) ? (data.batteryPercent + '%' + (data.charging ? ' (Charging)' : ''))