diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index b366ff04..a868933c 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -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..." diff --git a/src/SystemStatus.h b/src/SystemStatus.h index 238ee460..4134d074 100644 --- a/src/SystemStatus.h +++ b/src/SystemStatus.h @@ -5,13 +5,18 @@ #include #include #include +#include +#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(ESP.getFlashChipSize()); - s.flashAppUsedBytes = static_cast(ESP.getSketchSize()); - s.flashAppFreeBytes = static_cast(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(running->size) : 0; s.batteryPercent = powerManager.getBatteryPercentage(); s.charging = digitalRead(UART0_RXD) == HIGH; s.uptimeSeconds = millis() / 1000; diff --git a/src/activities/settings/SystemInformationActivity.cpp b/src/activities/settings/SystemInformationActivity.cpp index 1483247a..6d855495 100644 --- a/src/activities/settings/SystemInformationActivity.cpp +++ b/src/activities/settings/SystemInformationActivity.cpp @@ -3,21 +3,53 @@ #include #include +#include + #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(bytes), unit); } else { - snprintf(buf, sizeof(buf), "%llu B", static_cast(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(a), + static_cast(b), static_cast(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), "", ""); diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 366e1ba0..71f83812 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -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; diff --git a/src/network/html/HomePage.html b/src/network/html/HomePage.html index 2d1177bd..9b8b6eac 100644 --- a/src/network/html/HomePage.html +++ b/src/network/html/HomePage.html @@ -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; + } @@ -144,68 +175,93 @@

Device Status

-
- Version - Loading... + +
+
Version
+
+ Firmware + Loading... +
+
+ Device + Loading... +
-
- Chip - Loading... + +
+
Chip
+
+ Chip + Loading... +
+
+ CPU + Loading... +
-
- CPU - Loading... + +
+
Network
+
+ WiFi Status + Loading... +
+
+ IP Address + Loading... +
+
+ MAC Address + Loading... +
-
- WiFi Status - Loading... + +
+
Memory
+
+ Free / Min / Max + Loading... +
-
- IP Address - Loading... + +
+
Flash
+
+ App partition + Loading... +
+
+ Total flash + Loading... +
-
- MAC Address - Loading... + +
+
Runtime
+
+ Uptime + Loading... +
+
+ Battery + Loading... +
-
- Free Memory - Loading... + +
+
Storage
+
+ SD Card + Not loaded +
+
+ +
-
- Min Free - Loading... -
-
- Max Block - Loading... -
-
- Flash Used - Loading... -
-
- Battery - Loading... -
-
- Uptime - Loading... -
-
- Fast status fetch - Loading... -
-
- Full status fetch - Not loaded -
-
- SD Card - Not loaded -
-
- + +
@@ -215,12 +271,25 @@