Merge pull request #89 from jpirnay/refactor-sysinfo-ui
refactor: Structure visual appearance of SystemInformation
This commit is contained in:
@@ -481,6 +481,16 @@ STR_MIN_FREE: "Min free"
|
||||
STR_MAX_BLOCK: "Max block"
|
||||
STR_FLASH_USED: "Flash used"
|
||||
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_GATHERING_DATA: "Gathering data..."
|
||||
STR_READING: "Reading..."
|
||||
|
||||
+21
-5
@@ -5,13 +5,18 @@
|
||||
#include <Logging.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_ota_ops.h>
|
||||
|
||||
#include "HalGPIO.h"
|
||||
#include "HalPowerManager.h"
|
||||
|
||||
// Snapshot of device system status, shared between the web server and the
|
||||
// System Information activity so both surfaces show consistent data.
|
||||
struct SystemStatus {
|
||||
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;
|
||||
uint32_t cpuFreqMHz;
|
||||
std::string ip;
|
||||
@@ -21,9 +26,8 @@ struct SystemStatus {
|
||||
uint32_t freeHeapBytes;
|
||||
uint32_t minFreeHeapBytes;
|
||||
uint32_t maxAllocHeapBytes;
|
||||
uint64_t flashBytes;
|
||||
uint64_t flashAppUsedBytes;
|
||||
uint64_t flashAppFreeBytes;
|
||||
uint64_t flashBytes; // Total flash chip size
|
||||
uint64_t flashAppPartitionSize; // Size of the running OTA app partition
|
||||
uint16_t batteryPercent;
|
||||
bool charging;
|
||||
uint32_t uptimeSeconds;
|
||||
@@ -34,6 +38,15 @@ struct SystemStatus {
|
||||
static SystemStatus collectFast() {
|
||||
SystemStatus s;
|
||||
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 += " rev ";
|
||||
s.chipVersion += std::to_string(ESP.getChipRevision());
|
||||
@@ -42,8 +55,11 @@ struct SystemStatus {
|
||||
s.minFreeHeapBytes = ESP.getMinFreeHeap();
|
||||
s.maxAllocHeapBytes = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
|
||||
s.flashBytes = static_cast<uint64_t>(ESP.getFlashChipSize());
|
||||
s.flashAppUsedBytes = static_cast<uint64_t>(ESP.getSketchSize());
|
||||
s.flashAppFreeBytes = static_cast<uint64_t>(ESP.getFreeSketchSpace());
|
||||
// ESP.getSketchSize() is unreliable on custom partition layouts (the
|
||||
// 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.charging = digitalRead(UART0_RXD) == HIGH;
|
||||
s.uptimeSeconds = millis() / 1000;
|
||||
|
||||
@@ -3,21 +3,53 @@
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "SystemStatus.h"
|
||||
#include "components/UITheme.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) {
|
||||
double div;
|
||||
const char* unit = pickUnit(bytes, div);
|
||||
char buf[16];
|
||||
if (bytes >= 1024ULL * 1024 * 1024) {
|
||||
snprintf(buf, sizeof(buf), "%.1f GB", bytes / (1024.0 * 1024.0 * 1024.0));
|
||||
} 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);
|
||||
if (div == 1.0) {
|
||||
snprintf(buf, sizeof(buf), "%llu %s", static_cast<unsigned long long>(bytes), unit);
|
||||
} 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;
|
||||
}
|
||||
@@ -62,29 +94,40 @@ void SystemInformationActivity::loop() {
|
||||
|
||||
void SystemInformationActivity::render(RenderLock&&) {
|
||||
const auto& metrics = UITheme::getInstance().getMetrics();
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const Rect contentRect = UITheme::getContentRect(renderer, /*hasBottomHints=*/true, /*hasSideHints=*/false);
|
||||
|
||||
renderer.clearScreen();
|
||||
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_SYSTEM_INFO),
|
||||
CROSSPOINT_VERSION);
|
||||
GUI.drawHeader(renderer,
|
||||
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
|
||||
const int leftX = metrics.verticalSpacing * 3;
|
||||
const int valueX = pageWidth / 2;
|
||||
// Two-column layout with interleaved section headers (drawn via the theme's
|
||||
// subheader so the full-width underline is consistent with the rest of the
|
||||
// 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 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) {
|
||||
const int y = startY + row * (lineH + metrics.verticalSpacing);
|
||||
auto drawSection = [&](const char* title) {
|
||||
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, valueX, y, value.c_str());
|
||||
y += rowStep;
|
||||
};
|
||||
|
||||
if (!status_.has_value()) {
|
||||
// Stats not yet collected — show a placeholder so the screen updates immediately
|
||||
drawRow(0, tr(STR_FW_VERSION), CROSSPOINT_VERSION);
|
||||
drawRow(2, "", tr(STR_GATHERING_DATA));
|
||||
drawRow(tr(STR_FW_VERSION), CROSSPOINT_VERSION);
|
||||
y += rowStep;
|
||||
drawRow("", tr(STR_GATHERING_DATA));
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
@@ -93,37 +136,46 @@ void SystemInformationActivity::render(RenderLock&&) {
|
||||
|
||||
const auto& status = *status_;
|
||||
|
||||
drawRow(0, tr(STR_FW_VERSION), status.version);
|
||||
drawRow(1, tr(STR_CHIP), status.chipVersion);
|
||||
drawRow(2, tr(STR_CPU), std::to_string(status.cpuFreqMHz) + " " + tr(STR_MHZ));
|
||||
drawRow(3, tr(STR_FREE_RAM), formatBytes(status.freeHeapBytes));
|
||||
drawRow(4, tr(STR_MIN_FREE), formatBytes(status.minFreeHeapBytes));
|
||||
drawRow(5, tr(STR_MAX_BLOCK), formatBytes(status.maxAllocHeapBytes));
|
||||
drawRow(
|
||||
6, tr(STR_FLASH_USED),
|
||||
formatBytes(status.flashAppUsedBytes) + " / " + formatBytes(status.flashAppUsedBytes + status.flashAppFreeBytes));
|
||||
drawSection(tr(STR_SEC_VERSION));
|
||||
drawRow(tr(STR_FW_VERSION), status.version);
|
||||
drawRow(tr(STR_DEVICE), std::string(status.deviceType) + " (" + std::to_string(status.displayWidth) + " x " +
|
||||
std::to_string(status.displayHeight) + " px)");
|
||||
|
||||
drawSection(tr(STR_SEC_CHIP));
|
||||
drawRow(tr(STR_CHIP), status.chipVersion);
|
||||
drawRow(tr(STR_CPU), std::to_string(status.cpuFreqMHz) + " " + tr(STR_MHZ));
|
||||
|
||||
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) + "%";
|
||||
if (status.charging) {
|
||||
batteryLabel += " (";
|
||||
batteryLabel += tr(STR_CHARGING);
|
||||
batteryLabel += ")";
|
||||
}
|
||||
drawRow(7, 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);
|
||||
drawRow(tr(STR_BATTERY), batteryLabel);
|
||||
|
||||
drawSection(tr(STR_SEC_STORAGE));
|
||||
if (!sdStatusReady_) {
|
||||
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) {
|
||||
drawRow(9, tr(STR_SD_CARD), formatBytes(status.sdUsedBytes) + " / " + formatBytes(status.sdTotalBytes));
|
||||
drawRow(tr(STR_SD_CARD), formatBytes(status.sdUsedBytes) + " / " + formatBytes(status.sdTotalBytes));
|
||||
} 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), "", "");
|
||||
|
||||
@@ -402,6 +402,9 @@ void CrossPointWebServer::handleStatus() const {
|
||||
|
||||
JsonDocument doc;
|
||||
doc["version"] = status.version;
|
||||
doc["deviceType"] = status.deviceType;
|
||||
doc["displayWidth"] = status.displayWidth;
|
||||
doc["displayHeight"] = status.displayHeight;
|
||||
doc["chipVersion"] = status.chipVersion;
|
||||
doc["cpuMHz"] = status.cpuFreqMHz;
|
||||
doc["ip"] = status.ip;
|
||||
@@ -412,8 +415,7 @@ void CrossPointWebServer::handleStatus() const {
|
||||
doc["minFreeHeap"] = status.minFreeHeapBytes;
|
||||
doc["maxAllocHeap"] = status.maxAllocHeapBytes;
|
||||
doc["flashTotal"] = status.flashBytes;
|
||||
doc["flashAppUsed"] = status.flashAppUsedBytes;
|
||||
doc["flashAppFree"] = status.flashAppFreeBytes;
|
||||
doc["appPartitionSize"] = status.flashAppPartitionSize;
|
||||
doc["batteryPercent"] = status.batteryPercent;
|
||||
doc["charging"] = status.charging;
|
||||
doc["uptime"] = status.uptimeSeconds;
|
||||
@@ -436,6 +438,9 @@ void CrossPointWebServer::handleStatusFast() const {
|
||||
|
||||
JsonDocument doc;
|
||||
doc["version"] = status.version;
|
||||
doc["deviceType"] = status.deviceType;
|
||||
doc["displayWidth"] = status.displayWidth;
|
||||
doc["displayHeight"] = status.displayHeight;
|
||||
doc["chipVersion"] = status.chipVersion;
|
||||
doc["cpuMHz"] = status.cpuFreqMHz;
|
||||
doc["ip"] = status.ip;
|
||||
@@ -446,8 +451,7 @@ void CrossPointWebServer::handleStatusFast() const {
|
||||
doc["minFreeHeap"] = status.minFreeHeapBytes;
|
||||
doc["maxAllocHeap"] = status.maxAllocHeapBytes;
|
||||
doc["flashTotal"] = status.flashBytes;
|
||||
doc["flashAppUsed"] = status.flashAppUsedBytes;
|
||||
doc["flashAppFree"] = status.flashAppFreeBytes;
|
||||
doc["appPartitionSize"] = status.flashAppPartitionSize;
|
||||
doc["batteryPercent"] = status.batteryPercent;
|
||||
doc["charging"] = status.charging;
|
||||
doc["uptime"] = status.uptimeSeconds;
|
||||
|
||||
+140
-69
@@ -58,6 +58,25 @@
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -130,6 +149,18 @@
|
||||
opacity: 0.6;
|
||||
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>
|
||||
</head>
|
||||
|
||||
@@ -144,68 +175,93 @@
|
||||
|
||||
<div class="card">
|
||||
<h2>Device Status</h2>
|
||||
<div class="info-row">
|
||||
<span class="label">Version</span>
|
||||
<span class="value" id="version">Loading...</span>
|
||||
|
||||
<div class="section">
|
||||
<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 class="info-row">
|
||||
<span class="label">Chip</span>
|
||||
<span class="value" id="chip-version">Loading...</span>
|
||||
|
||||
<div class="section">
|
||||
<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 class="info-row">
|
||||
<span class="label">CPU</span>
|
||||
<span class="value" id="cpu-mhz">Loading...</span>
|
||||
|
||||
<div class="section">
|
||||
<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 class="info-row">
|
||||
<span class="label">WiFi Status</span>
|
||||
<span class="status" id="wifi-status">Loading...</span>
|
||||
|
||||
<div class="section">
|
||||
<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 class="info-row">
|
||||
<span class="label">IP Address</span>
|
||||
<span class="value" id="ip-address">Loading...</span>
|
||||
|
||||
<div class="section">
|
||||
<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 class="info-row">
|
||||
<span class="label">MAC Address</span>
|
||||
<span class="value" id="mac-address">Loading...</span>
|
||||
|
||||
<div class="section">
|
||||
<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 class="info-row">
|
||||
<span class="label">Free Memory</span>
|
||||
<span class="value" id="free-heap">Loading...</span>
|
||||
|
||||
<div class="section">
|
||||
<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 class="info-row">
|
||||
<span class="label">Min Free</span>
|
||||
<span class="value" id="min-free-heap">Loading...</span>
|
||||
</div>
|
||||
<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 class="debug-footer">
|
||||
<span>Fast fetch: <span id="fast-status-duration">…</span></span>
|
||||
<span>Full fetch: <span id="full-status-duration">—</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -215,12 +271,25 @@
|
||||
</p>
|
||||
</div>
|
||||
<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) {
|
||||
if (bytes == null) return 'N/A';
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
let val = bytes, i = 0;
|
||||
while (val >= 1024 && i < units.length - 1) { val /= 1024; i++; }
|
||||
return parseFloat(val.toFixed(2)).toLocaleString() + ' ' + units[i];
|
||||
const { unit, div } = pickUnit(bytes);
|
||||
if (div === 1) return bytes.toLocaleString() + ' ' + unit;
|
||||
return parseFloat((bytes / div).toFixed(2)).toLocaleString() + ' ' + unit;
|
||||
}
|
||||
|
||||
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 = {
|
||||
@@ -246,6 +315,12 @@
|
||||
|
||||
function applyStatus(data, includeSd) {
|
||||
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('cpu-mhz').textContent = data.cpuMHz
|
||||
? data.cpuMHz.toLocaleString() + ' MHz'
|
||||
@@ -254,17 +329,13 @@
|
||||
wifiStatus.textContent = data.mode || 'N/A';
|
||||
document.getElementById('ip-address').textContent = data.ip || 'N/A';
|
||||
document.getElementById('mac-address').textContent = data.macAddress || 'N/A';
|
||||
document.getElementById('free-heap').textContent = data.freeHeap
|
||||
? formatBytes(data.freeHeap)
|
||||
document.getElementById('heap-combined').textContent =
|
||||
formatBytesTriple(data.freeHeap, data.minFreeHeap, data.maxAllocHeap);
|
||||
document.getElementById('app-partition-size').textContent = data.appPartitionSize != null
|
||||
? formatBytes(data.appPartitionSize)
|
||||
: 'N/A';
|
||||
document.getElementById('min-free-heap').textContent = data.minFreeHeap
|
||||
? formatBytes(data.minFreeHeap)
|
||||
: '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)))
|
||||
document.getElementById('flash-total').textContent = data.flashTotal != null
|
||||
? formatBytes(data.flashTotal)
|
||||
: 'N/A';
|
||||
document.getElementById('battery').textContent = (data.batteryPercent != null)
|
||||
? (data.batteryPercent + '%' + (data.charging ? ' (Charging)' : ''))
|
||||
|
||||
Reference in New Issue
Block a user