diff --git a/projects/tzpuPico/SD/default/webfs/config.htm b/projects/tzpuPico/SD/default/webfs/config.htm index 072e33c..a3ca2e5 100755 --- a/projects/tzpuPico/SD/default/webfs/config.htm +++ b/projects/tzpuPico/SD/default/webfs/config.htm @@ -72,6 +72,12 @@ Persona + +
  • + + Config GUI + +
  • diff --git a/projects/tzpuPico/SD/default/webfs/filemanager.htm b/projects/tzpuPico/SD/default/webfs/filemanager.htm index 3971f9b..a5cf89d 100755 --- a/projects/tzpuPico/SD/default/webfs/filemanager.htm +++ b/projects/tzpuPico/SD/default/webfs/filemanager.htm @@ -72,6 +72,12 @@ Persona + +
  • + + Config GUI + +
  • diff --git a/projects/tzpuPico/SD/default/webfs/index.htm b/projects/tzpuPico/SD/default/webfs/index.htm index 2e1ce76..a73349a 100755 --- a/projects/tzpuPico/SD/default/webfs/index.htm +++ b/projects/tzpuPico/SD/default/webfs/index.htm @@ -72,6 +72,12 @@ Persona + +
  • + + Config GUI + +
  • diff --git a/projects/tzpuPico/SD/default/webfs/ota-esp32.htm b/projects/tzpuPico/SD/default/webfs/ota-esp32.htm index d1e3707..135213d 100755 --- a/projects/tzpuPico/SD/default/webfs/ota-esp32.htm +++ b/projects/tzpuPico/SD/default/webfs/ota-esp32.htm @@ -73,6 +73,12 @@ Persona + +
  • + + Config GUI + +
  • diff --git a/projects/tzpuPico/SD/default/webfs/ota-rp2350.htm b/projects/tzpuPico/SD/default/webfs/ota-rp2350.htm index 35fa008..2f98782 100755 --- a/projects/tzpuPico/SD/default/webfs/ota-rp2350.htm +++ b/projects/tzpuPico/SD/default/webfs/ota-rp2350.htm @@ -73,6 +73,12 @@ Persona + +
  • + + Config GUI + +
  • diff --git a/projects/tzpuPico/SD/default/webfs/personality.htm b/projects/tzpuPico/SD/default/webfs/personality.htm index 10f7fa3..4358be7 100755 --- a/projects/tzpuPico/SD/default/webfs/personality.htm +++ b/projects/tzpuPico/SD/default/webfs/personality.htm @@ -67,10 +67,16 @@
  • - + Persona
  • + +
  • + + Config GUI + +
  • @@ -98,7 +104,6 @@
  • Change Floppy Disk 2 (%SK_FLOPPY2%)
  • Change QD Disk (%SK_QDDISK%)

  • -
  • Reload RP2350 Config
  • Reload RP2350 Config
  • diff --git a/projects/tzpuPico/SD/default/webfs/wifimanager.htm b/projects/tzpuPico/SD/default/webfs/wifimanager.htm index ef62130..f5599d8 100755 --- a/projects/tzpuPico/SD/default/webfs/wifimanager.htm +++ b/projects/tzpuPico/SD/default/webfs/wifimanager.htm @@ -71,6 +71,12 @@ Persona + +
  • + + Config GUI + +
  • @@ -98,7 +104,6 @@
  • Change Floppy Disk 2 (%SK_FLOPPY2%)
  • Change QD Disk (%SK_QDDISK%)

  • -
  • Reload RP2350 Config
  • Reload RP2350 Config
  • diff --git a/projects/tzpuPico/esp32/filepack_version.txt b/projects/tzpuPico/esp32/filepack_version.txt index fc249e9..7cd2026 100644 --- a/projects/tzpuPico/esp32/filepack_version.txt +++ b/projects/tzpuPico/esp32/filepack_version.txt @@ -1 +1 @@ -2.18 +2.43 diff --git a/projects/tzpuPico/esp32/main/WiFi.cpp b/projects/tzpuPico/esp32/main/WiFi.cpp index 6553181..dd75f3b 100644 --- a/projects/tzpuPico/esp32/main/WiFi.cpp +++ b/projects/tzpuPico/esp32/main/WiFi.cpp @@ -1569,7 +1569,57 @@ esp_err_t WiFi::defaultDataGETHandler(httpd_req_t *req) pThis->stringReplace(sdpath, "//", "/"); // Match URI and execute required data retrieval and return. - if (uriStr == "wifistatus") + if (uriStr == "config") + { + // Return the contents of config.json from the SD card root. + // The file is sent as-is (including any C-style comments and hex + // literals) — the browser-side JavaScript handles parsing. + std::string cfgPath = std::string(pThis->wifiCtrl.run.fsPath); + if (cfgPath.back() != '/') cfgPath += "/"; + cfgPath += "config.json"; + + FILE *cfgFd = fopen(cfgPath.c_str(), "rb"); + if (!cfgFd) + { + httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "config.json not found on SD card"); + return ESP_FAIL; + } + + // Send as chunked response to avoid allocating the entire file in memory. + httpd_resp_set_type(req, "application/json"); + httpd_resp_set_hdr(req, "Cache-Control", "no-cache"); + + std::unique_ptr cfgChunk(new char[MAX_CHUNK_SIZE]); + if (cfgChunk == nullptr) + { + fclose(cfgFd); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Memory allocation failed"); + return ESP_FAIL; + } + + size_t bytesRead; + esp_err_t cfgResult = ESP_OK; + do + { + bytesRead = fread(cfgChunk.get(), 1, MAX_CHUNK_SIZE, cfgFd); + if (bytesRead > 0) + { + if (httpd_resp_send_chunk(req, cfgChunk.get(), bytesRead) != ESP_OK) + { + cfgResult = ESP_FAIL; + break; + } + } + } while (bytesRead > 0); + + fclose(cfgFd); + + if (cfgResult == ESP_OK) + httpd_resp_send_chunk(req, NULL, 0); + + return cfgResult; + } + else if (uriStr == "wifistatus") { // JSON endpoint for AJAX polling of WiFi + system + RP2350 status. // All fields that can change at runtime are included so the dashboard @@ -3034,6 +3084,96 @@ esp_err_t WiFi::defaultDataPOSTHandler(httpd_req_t *req) resp = "File uploaded."; } } + else if (uriStr == "config") + { + // Receive a JSON body and write it to /config.json on the SD card. + // The body is raw JSON (not form-encoded), so we read it directly + // rather than using getPOSTData(). + int contentLen = req->content_len; + if (contentLen <= 0 || contentLen > (256 * 1024)) + { + resp = "Invalid or missing JSON body (size=" + std::to_string(contentLen) + ")"; + result = ESP_FAIL; + } + else + { + // Read the full JSON body. + std::string jsonBody; + jsonBody.reserve(contentLen); + char rcvBuf[512]; + int remaining = contentLen; + + while (remaining > 0) + { + int toRead = (remaining < (int)sizeof(rcvBuf)) ? remaining : (int)sizeof(rcvBuf); + int ret = httpd_req_recv(req, rcvBuf, toRead); + if (ret <= 0) + { + if (ret == HTTPD_SOCK_ERR_TIMEOUT) + continue; + result = ESP_FAIL; + resp = "Failed to receive POST body"; + break; + } + jsonBody.append(rcvBuf, ret); + remaining -= ret; + } + + if (result == ESP_OK) + { + // Basic validation: ensure it looks like JSON. + // Find the first non-whitespace character. + size_t firstNonWs = jsonBody.find_first_not_of(" \t\r\n"); + if (firstNonWs == std::string::npos || jsonBody[firstNonWs] != '{') + { + resp = "Invalid JSON: must start with '{'"; + result = ESP_FAIL; + } + } + + if (result == ESP_OK) + { + // Write to a temp file, then atomically rename with versioned backup. + std::string trgFile = sdpath + "config.json"; + std::string tmpFile = trgFile + ".tmp"; + + FILE *wf = fopen(tmpFile.c_str(), "wb"); + if (!wf) + { + resp = "Failed to create temp file: " + tmpFile; + result = ESP_FAIL; + } + else + { + size_t written = fwrite(jsonBody.c_str(), 1, jsonBody.size(), wf); + fclose(wf); + + if (written != jsonBody.size()) + { + std::remove(tmpFile.c_str()); + resp = "Failed to write config data (wrote " + std::to_string(written) + "/" + std::to_string(jsonBody.size()) + " bytes)"; + result = ESP_FAIL; + } + else + { + // Version the existing config.json and rename temp into place. + std::string errMsg; + result = pThis->versionedRename(tmpFile, trgFile, errMsg); + if (result != ESP_OK) + { + resp = "Failed to save config: " + errMsg; + } + else + { + // Notify the RP2350 to reload its configuration. + pThis->reloadRP2350Config(); + resp = "Configuration saved and RP2350 reload initiated."; + } + } + } + } + } + } else { result = pThis->getPOSTData(req, &pairs); @@ -3556,10 +3696,10 @@ esp_err_t WiFi::sendFileManagerDir(httpd_req_t *req) { htmlStr.append("
    ").append(sdpath).append("
    "); htmlStr.append("
    "); - htmlStr.append(" "); + htmlStr.append("
    "); htmlStr.append(" "); htmlStr.append(" "); - htmlStr.append(" "); + htmlStr.append(" "); htmlStr.append(" "); htmlStr.append(" "); htmlStr.append(" "); @@ -3812,9 +3952,12 @@ esp_err_t WiFi::defaultRebootHandler(httpd_req_t *req) // Build a response with a meta-refresh back to the originating IP. resp = "Rebooting...
    " + "
    " + "Rebooting...

    Redirecting to http://" + redirectIP + "/ in 5 seconds. " - "If this screen doesnt auto-refresh, please reload manually.

    "; + "If this screen doesnt auto-refresh, please reload manually.

    "; // Send the response and wait a while, then request reboot. result = httpd_resp_send(req, resp.c_str(), resp.size() + 1); diff --git a/projects/tzpuPico/esp32/main/tzpuPico.cpp b/projects/tzpuPico/esp32/main/tzpuPico.cpp index 672a789..5cb7283 100644 --- a/projects/tzpuPico/esp32/main/tzpuPico.cpp +++ b/projects/tzpuPico/esp32/main/tzpuPico.cpp @@ -384,10 +384,14 @@ void setupUSBTask(void *pvParameters) // 2. Force a clean USB disconnect/reconnect cycle. These delays run in this // background task so the main task is free to start the CommandProcessor. + // The disconnect duration must be long enough for the host OS to fully + // tear down its NCM driver; the post-connect wait allows the host to + // enumerate the device and start its DHCP client. Shorter timings cause + // intermittent failures where the host never brings the link up. tud_disconnect(); - vTaskDelay(pdMS_TO_TICKS(1500)); + vTaskDelay(pdMS_TO_TICKS(2000)); tud_connect(); - vTaskDelay(pdMS_TO_TICKS(500)); + vTaskDelay(pdMS_TO_TICKS(1500)); // 3. Initialise CDC-ACM for serial logging. tinyusb_config_cdcacm_t acm_cfg = {}; @@ -448,6 +452,14 @@ void setupUSBTask(void *pvParameters) esp_tusb_init_console(TINYUSB_CDC_ACM_0); } + // 7. Allow time for the host to complete DHCP and bring the link up before + // signalling ready. Without this the webserver may start before the host + // has an IP address, causing the first connection attempt to fail. + if (ncmOk) { + ESP_LOGI(MAINTAG, "USB NCM netif up — waiting for host DHCP..."); + vTaskDelay(pdMS_TO_TICKS(2000)); + } + ESP_LOGI(MAINTAG, "USB setup complete (CDC:%s NCM:%s)", cdcOk ? "OK" : "FAIL", ncmOk ? "OK" : "FAIL"); g_usbReady = true; vTaskDelete(NULL); diff --git a/projects/tzpuPico/esp32/make_filepack.sh b/projects/tzpuPico/esp32/make_filepack.sh index f2ec359..7d9db7b 100755 --- a/projects/tzpuPico/esp32/make_filepack.sh +++ b/projects/tzpuPico/esp32/make_filepack.sh @@ -48,6 +48,7 @@ if [[ ${ISNEWER} != "" ]]; then cp ota-rp2350.htm ${WEBFSDIR}/ota-rp2350.htm cp wifimanager.htm ${WEBFSDIR}/wifimanager.htm cp personality.htm ${WEBFSDIR}/personality.htm + cp configgui.htm ${WEBFSDIR}/configgui.htm (cd ${SRCDIR}/css; @@ -103,6 +104,7 @@ if [[ ${ISNEWER} != "" ]]; then cp ota-rp2350.js ${WEBFSDIR}/js/ota-rp2350.js cp wifimanager.js ${WEBFSDIR}/js/wifimanager.js cp personality.js ${WEBFSDIR}/js/personality.js + cp configgui.js ${WEBFSDIR}/js/configgui.js cp editor.js ${WEBFSDIR}/js/editor.js cp ui/icons.svg ${WEBFSDIR}/js/ui/icons.svg ) diff --git a/projects/tzpuPico/esp32/version.txt b/projects/tzpuPico/esp32/version.txt index e72716a..d74a9d3 100644 --- a/projects/tzpuPico/esp32/version.txt +++ b/projects/tzpuPico/esp32/version.txt @@ -1 +1 @@ -2.46 +2.54 diff --git a/projects/tzpuPico/esp32/webserver/config.htm b/projects/tzpuPico/esp32/webserver/config.htm index 0212399..d910b4d 100644 --- a/projects/tzpuPico/esp32/webserver/config.htm +++ b/projects/tzpuPico/esp32/webserver/config.htm @@ -23,6 +23,7 @@
    diff --git a/projects/tzpuPico/esp32/webserver/configgui.htm b/projects/tzpuPico/esp32/webserver/configgui.htm new file mode 100644 index 0000000..897fe94 --- /dev/null +++ b/projects/tzpuPico/esp32/webserver/configgui.htm @@ -0,0 +1,295 @@ + + + + + + + + + Dashboard - Pico %SK_PROCESSOR% Admin + + + + + + + + + + + + + + +
    + + + + +
    + +
    +
    +

    Pico %SK_PROCESSOR% Configuration

    + +
    + +

    This page provides a graphical interface to configure the Pico %SK_PROCESSOR%. Changes are saved to config.json on the SD card. The existing configuration is backed up before saving.

    +

    For advanced editing, use the Config Editor to modify the raw JSON directly.

    +
    +
    +
    + +
    +
    +
    +
    +

    Configuration

    +
    +
    + + +
    + +

    Loading configuration...

    +
    + + + + + + + +
    +
    +
    +
    + +
    + +
    + + + + + + + + + + diff --git a/projects/tzpuPico/esp32/webserver/css/sb-admin.css b/projects/tzpuPico/esp32/webserver/css/sb-admin.css index f371f07..51828b6 100644 --- a/projects/tzpuPico/esp32/webserver/css/sb-admin.css +++ b/projects/tzpuPico/esp32/webserver/css/sb-admin.css @@ -12,6 +12,23 @@ For more info and more free Bootstrap 3 HTML themes, visit http://startbootstrap body { margin-top: 50px; + background: + /* Subtle diagonal pinstripe pattern */ + repeating-linear-gradient( + 135deg, + transparent, + transparent 10px, + rgba(0,0,0,0.015) 10px, + rgba(0,0,0,0.015) 11px + ), + /* Soft radial vignette - lighter centre, slightly darker edges */ + radial-gradient( + ellipse at center, + #f5f6f8 0%, + #e8eaef 60%, + #dcdfe6 100% + ); + background-attachment: fixed; } #wrapper { @@ -23,6 +40,12 @@ body { padding: 5px 15px; } +/* Ensure all panel content can scroll horizontally on small screens */ +.panel-body { + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + /* Nav Messages */ .messages-dropdown .dropdown-menu .message-preview .avatar, @@ -86,21 +109,40 @@ table.tablesorter thead tr th:hover { /* Edit Below to Customize Widths > 768px */ @media (min-width:768px) { + /* Centered layout: sidebar(225) + content(1210) = 1435px total */ + + /* Navbar: dark background only within the centred area, page background outside */ + .navbar-inverse { + background-color: transparent; + border: none; + } + .nav-centered { + max-width: 1435px; + margin: 0 auto; + position: relative; + background-color: #222222; + } + /* Wrappers */ #wrapper { padding-left: 225px; + max-width: 1435px; + margin: 0 auto; } #page-wrapper { padding: 15px 25px; + max-width: 1210px; + background: #fff; + min-height: calc(100vh - 50px); } - /* Side Nav */ + /* Side Nav - fixed but tracks the centred layout */ .side-nav { - margin-left: -225px; - left: 225px; + margin-left: 0; + left: max(0px, calc(50% - 717.5px)); width: 225px; position: fixed; top: 50px; diff --git a/projects/tzpuPico/esp32/webserver/filemanager.htm b/projects/tzpuPico/esp32/webserver/filemanager.htm index f77dab5..14b94ed 100644 --- a/projects/tzpuPico/esp32/webserver/filemanager.htm +++ b/projects/tzpuPico/esp32/webserver/filemanager.htm @@ -23,6 +23,7 @@
    diff --git a/projects/tzpuPico/esp32/webserver/index.htm b/projects/tzpuPico/esp32/webserver/index.htm index 1255647..4cf599a 100644 --- a/projects/tzpuPico/esp32/webserver/index.htm +++ b/projects/tzpuPico/esp32/webserver/index.htm @@ -23,6 +23,7 @@
    @@ -199,6 +211,7 @@

    System Status

    +
    NameNameTypeSize (Bytes)Action
    @@ -227,6 +240,7 @@
    +
    @@ -239,6 +253,7 @@

    Active Personality

    +
    @@ -262,6 +277,7 @@
    +
    @@ -342,6 +358,8 @@