Jelajahi Sumber

Fixed hostname, hung session & soft reset

Benjamin Harris 1 bulan lalu
induk
melakukan
f1522380c8
3 mengubah file dengan 104 tambahan dan 17 penghapusan
  1. 45 8
      ModulosDSP_101.ino
  2. 17 6
      README.md
  3. 42 3
      ota_html.h

+ 45 - 8
ModulosDSP_101.ino

@@ -60,12 +60,14 @@
 #include "FS.h"
 #include <LittleFS.h>
 #include <Update.h>
+#include <ESPmDNS.h>
 
 //=============================================================
 // WiFi / UI
 //=============================================================
 const char* ssid     = "alfred";
 const char* password = "alfred16";
+const char* hostname = "modulos-dsp";
 const char* version  = "VER: 260304_13";
 
 #define I2C_SDA 13
@@ -119,6 +121,8 @@ static uint8_t dataBuffer[50 * 1024];
 #define CMD_WRITE 0x09
 #define CMD_READ  0x0A
 
+#define TCP_IDLE_TIMEOUT_MS 30000  // drop session if silent for 30 s
+
 constexpr int WRITE_HDR_LEN = 10;
 constexpr int READ_HDR_LEN  = 8;
 
@@ -318,8 +322,9 @@ static void printWifiInfo()
 {
   Serial.println();
   Serial.println("WiFi connected.");
-  Serial.print("WiFi IP: "); Serial.println(WiFi.localIP());
-  Serial.print("MAC: ");     Serial.println(WiFi.macAddress());
+  Serial.print("WiFi IP: ");       Serial.println(WiFi.localIP());
+  Serial.printf("Hostname: http://%s.local/\n", hostname);
+  Serial.print("MAC: ");           Serial.println(WiFi.macAddress());
   Serial.println("Modulos AudioDSP");
   Serial.println(version);
   if (s_lcdOk) {
@@ -443,6 +448,22 @@ static void handleUploadStream() {
   }
 }
 
+//=============================================================
+// HTTP DSP reset handler
+//=============================================================
+static void handleDspReset() {
+  Serial.println("DSP soft reset requested");
+  // Stop DSP execution, brief pause, restart. Program and parameter RAM
+  // are preserved — this restarts execution without reloading from EEPROM.
+  uint8_t stop[2]  = { 0x00, 0x00 };
+  uint8_t run[2]   = { 0x00, 0x01 };
+  DSPWriter::writeRegister(dspRegister::CoreRegister, sizeof(stop), stop);
+  delay(100);
+  DSPWriter::writeRegister(dspRegister::CoreRegister, sizeof(run),  run);
+  Serial.println("DSP soft reset complete");
+  httpServer.send(200, "text/plain", "DSP soft reset complete.");
+}
+
 //=============================================================
 // HTTP OTA handlers
 //=============================================================
@@ -532,6 +553,13 @@ void setup() {
     delay(5000); ESP.restart();
   }
 
+  if (MDNS.begin(hostname)) {
+    MDNS.addService("http", "tcp", 80);
+    Serial.printf("mDNS: http://%s.local/\n", hostname);
+  } else {
+    Serial.println("mDNS start failed");
+  }
+
   if (!LittleFS.begin(false)) {
     Serial.println("LittleFS mount failed, formatting...");
     if (!LittleFS.begin(true)) { Serial.println("LittleFS mount failed even after format"); return; }
@@ -548,6 +576,7 @@ void setup() {
   httpServer.on("/upload", HTTP_POST, handleUploadDone, handleUploadStream);
   httpServer.on("/ota", HTTP_GET, handleOtaPage);
   httpServer.on("/ota_do", HTTP_POST, handleOtaDone, handleOtaStream);
+  httpServer.on("/dsp_reset", HTTP_POST, handleDspReset);
   httpServer.onNotFound([]() {
     String uri = httpServer.uri();
     if (streamFromFS(uri)) return;
@@ -578,10 +607,11 @@ static void handleTcpBridgeClient(WiFiClient& client)
   Serial.println("TCP new connection");
   DSPWriter::resetSafeload();
 
-  int writeIndex        = 0;  // next free slot in dataBuffer
-  int readIndex         = 0;  // start of current unprocessed command
-  int receivedByteCount = 0;  // total bytes written into dataBuffer
-  int currentState      = STATE_START;
+  int      writeIndex        = 0;          // next free slot in dataBuffer
+  int      readIndex         = 0;          // start of current unprocessed command
+  int      receivedByteCount = 0;          // total bytes written into dataBuffer
+  int      currentState      = STATE_START;
+  uint32_t lastActivityMs    = millis();   // idle watchdog
 
   while (client.connected()) {
 
@@ -603,6 +633,7 @@ static void handleTcpBridgeClient(WiFiClient& client)
       if (b < 0) break;
       dataBuffer[writeIndex++] = (uint8_t)b;
       receivedByteCount++;
+      lastActivityMs = millis();
     }
 
     // ------------------------------------------------------------------
@@ -752,10 +783,14 @@ static void handleTcpBridgeClient(WiFiClient& client)
       }
     } // end process loop
 
-    // No idle timeout — stay connected until SigmaStudio disconnects.
-    // client.connected() will return false when the TCP connection drops.
+    // Idle path — no data waiting, no command in progress.
     if (currentState == STATE_START && receivedByteCount == readIndex) {
       if (!client.available()) {
+        if (millis() - lastActivityMs > TCP_IDLE_TIMEOUT_MS) {
+          Serial.println("TCP idle timeout — closing session");
+          ledErrorFlash();
+          break;
+        }
         httpServer.handleClient();
         delay(10);
       }
@@ -789,6 +824,8 @@ static void maintainWifi()
     WiFi.reconnect();
   } else if (s_wifiLost) {
     Serial.print("WiFi reconnected, IP: "); Serial.println(WiFi.localIP());
+    MDNS.begin(hostname);
+    MDNS.addService("http", "tcp", 80);
     tcpServer.begin();   // re-register listening socket with recovered stack
     printWifiInfo();     // update LCD with current IP
     s_wifiLost = false;

+ 17 - 6
README.md

@@ -51,14 +51,15 @@ Install via **Sketch → Include Library → Manage Libraries**:
 
 ## Configuration
 
-Before flashing, edit `ModulosDSP_101.ino` and update the WiFi credentials:
+Before flashing, edit `ModulosDSP_101.ino` and update the WiFi credentials and hostname:
 
 ```cpp
 const char* ssid     = "your_network_ssid";
 const char* password = "your_network_password";
+const char* hostname = "modulos-dsp";        // resolves as modulos-dsp.local
 ```
 
-There is no runtime configuration — credentials are compiled in.
+There is no runtime configuration — credentials are compiled in. The hostname is registered via mDNS (Bonjour/Zeroconf), so once on the network the device is reachable at `modulos-dsp.local` without knowing its IP address. Change the hostname constant if you run more than one unit on the same network.
 
 ---
 
@@ -80,12 +81,14 @@ The onboard NeoPixel (GPIO 21) shows current activity at a glance:
 
 ## Using with SigmaStudio
 
-1. Power on the board. The LCD displays the assigned IP address; Serial (115200 baud) also prints it.
+1. Power on the board. The LCD displays the assigned IP address; Serial (115200 baud) also prints it alongside the mDNS hostname.
 2. In SigmaStudio, open **Settings → Hardware Configuration**.
 3. Add a **USBi** interface and switch the connection type to **SigmaTCP**.
-4. Enter the ESP32's IP address and port **8086**.
+4. Enter `modulos-dsp.local` (or the IP address) and port **8086**.
 5. Click **Link → Compile Download** — SigmaStudio pushes firmware to the DSP wirelessly.
 
+Sessions that go idle for 30 seconds are closed automatically, freeing the bridge for a new connection.
+
 The bridge handles both **direct writes** and **safeload writes** (glitch-free atomic parameter updates on a live DSP). If the connection drops mid-safeload, any partial pending slots are flushed to parameter RAM on disconnect so the DSP's internal safeload counter is clean for the next session.
 
 ---
@@ -102,15 +105,23 @@ The 24C256 holds **32 KB**. The DSP reads this EEPROM at power-up via its SELFBO
 
 ---
 
-## Firmware Update (OTA)
+## Device Management Page (`/ota`)
+
+Navigate to `http://modulos-dsp.local/ota` in any browser.
 
-Navigate to `http://<ESP32-IP>/ota` in any browser:
+**Firmware Update (OTA):**
 
 1. In Arduino IDE, export the compiled binary: **Sketch → Export Compiled Binary** (produces a `.bin` file).
 2. Click **Choose File** and select the `.bin`.
 3. Click **Flash Firmware** — the Cyan LED indicates the write is in progress.
 4. On success the device reboots automatically. The page waits 5 seconds then redirects back to `/`.
 
+**DSP Soft Reset:**
+
+- Click **Soft Reset DSP** to stop and restart DSP program execution.
+- Parameter and program RAM are preserved — this does **not** reload from EEPROM.
+- Useful for recovering a stuck DSP without power-cycling the whole board.
+
 ---
 
 ## How It Works

+ 42 - 3
ota_html.h

@@ -12,18 +12,19 @@ static const char OTA_HTML[] PROGMEM = R"HTML(
   <link rel="stylesheet" href="font-awesome.min.css">
   <script src="jquery-3.7.1.slim.min.js" crossorigin="anonymous"></script>
 
-  <title>Modulos DSP - Firmware Update</title>
+  <title>Modulos DSP - Device Management</title>
   <style>
     body { padding-top: 40px; }
     .ota-card { max-width: 520px; margin: 0 auto; }
     #progressWrap { display: none; margin-top: 1.2rem; }
-    #resultBox   { display: none; margin-top: 1rem; }
+    #resultBox    { display: none; margin-top: 1rem; }
+    #resetResult  { display: none; margin-top: 0.6rem; }
   </style>
 </head>
 <body>
 <div class="container ota-card">
 
-  <h4 class="mb-1"><i class="fa fa-microchip"></i> Firmware Update (OTA)</h4>
+  <h4 class="mb-1"><i class="fa fa-microchip"></i> Device Management</h4>
   <p class="text-muted small mb-4">Device: <strong>{{IP}}</strong></p>
 
   <form id="otaForm" action="/ota_do" method="POST" enctype="multipart/form-data">
@@ -53,10 +54,48 @@ static const char OTA_HTML[] PROGMEM = R"HTML(
   <div id="resultBox" class="alert" role="alert"></div>
 
   <hr class="mt-4">
+  <h6 class="text-muted mb-2"><i class="fa fa-refresh"></i> DSP Control</h6>
+  <button id="resetBtn" class="btn btn-outline-secondary w-100"
+          onclick="dspReset()">
+    <i class="fa fa-power-off"></i>&nbsp; Soft Reset DSP
+  </button>
+  <div class="form-text mb-1">
+    Restarts DSP program execution. RAM is preserved — does not reload from EEPROM.
+  </div>
+  <div id="resetResult" class="alert alert-sm" role="alert"></div>
+
+  <hr class="mt-3">
   <a href="/" class="text-muted small"><i class="fa fa-arrow-left"></i> Back to EEPROM Uploader</a>
 </div>
 
 <script>
+function dspReset() {
+  var btn = document.getElementById('resetBtn');
+  var box = document.getElementById('resetResult');
+  btn.disabled = true;
+  box.style.display = 'none';
+  var xhr = new XMLHttpRequest();
+  xhr.open('POST', '/dsp_reset', true);
+  xhr.onload = function() {
+    box.style.display = 'block';
+    if (xhr.status === 200) {
+      box.className = 'alert alert-success alert-sm';
+      box.innerHTML = '<i class="fa fa-check"></i> ' + xhr.responseText;
+    } else {
+      box.className = 'alert alert-danger alert-sm';
+      box.innerHTML = '<i class="fa fa-times"></i> Reset failed.';
+    }
+    btn.disabled = false;
+  };
+  xhr.onerror = function() {
+    box.style.display = 'block';
+    box.className = 'alert alert-danger alert-sm';
+    box.innerHTML = '<i class="fa fa-times"></i> Request failed.';
+    btn.disabled = false;
+  };
+  xhr.send();
+}
+
 document.getElementById('otaForm').addEventListener('submit', function(e) {
   e.preventDefault();
   var file = document.getElementById('fwFile').files[0];