| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172 |
- // Modulos ADAU DSP WiFi + EEPROM (HTTP uploader + Sigma TCP bridge)
- // Build: 260304_13 (YYMMDD_rev)
- #include <WiFi.h>
- #include <Wire.h>
- #include <WebServer.h>
- #include <Preferences.h>
- #include "DSPWriter.h"
- #include <hd44780.h>
- #include <hd44780ioClass/hd44780_I2Cexp.h>
- #include <Adafruit_NeoPixel.h>
- #include "FS.h"
- #include <LittleFS.h>
- #include <Update.h>
- #include <ESPmDNS.h>
- #include "esp_rom_crc.h"
- //=============================================================
- // WiFi / UI
- //=============================================================
- const char* hostname = "modulos-dsp";
- const char* version = "VER: 260304_13";
- // Compile-time default credentials — used when NVS has no saved credentials.
- // Change these before flashing. If NVS credentials are saved (via the config
- // portal) they take priority over these on subsequent boots.
- static const char DEFAULT_SSID[] = "alfred";
- static const char DEFAULT_PASS[] = "alfred16";
- // Runtime WiFi credentials — populated from NVS or defaults at boot
- static char s_ssid[64] = "";
- static char s_pass[64] = "";
- #define I2C_SDA 13
- #define I2C_SCL 12
- WiFiServer tcpServer(8086);
- WebServer httpServer(80);
- #include "index_html.h"
- #include "ota_html.h"
- #include "ap_html.h"
- #include "params_html.h"
- hd44780_I2Cexp lcd;
- static bool s_lcdOk = false;
- //=============================================================
- // NeoPixel status LED (Waveshare ESP32-S3 Zero, GPIO 21)
- //=============================================================
- #define NEOPIXEL_PIN 21
- #define NEOPIXEL_COUNT 1
- #define NEOPIXEL_BRIGHT 40
- Adafruit_NeoPixel statusLed(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
- static void ledSet(uint8_t r, uint8_t g, uint8_t b)
- {
- statusLed.setPixelColor(0, statusLed.Color(r, g, b));
- statusLed.show();
- }
- static void ledOff() { ledSet(0, 0, 0); }
- static void ledCyan() { ledSet(0, NEOPIXEL_BRIGHT/2, NEOPIXEL_BRIGHT/2); }
- static void ledGreen() { ledSet(0, NEOPIXEL_BRIGHT, 0); }
- static void ledBlue() { ledSet(0, 0, NEOPIXEL_BRIGHT); }
- static void ledYellow() { ledSet(NEOPIXEL_BRIGHT, NEOPIXEL_BRIGHT, 0); }
- static void ledMagenta() { ledSet(NEOPIXEL_BRIGHT, 0, NEOPIXEL_BRIGHT); }
- static void ledErrorFlash()
- {
- for (int i = 0; i < 2; i++) {
- ledSet(NEOPIXEL_BRIGHT, 0, 0); delay(80);
- ledOff(); delay(80);
- }
- }
- //=============================================================
- // TCP protocol buffer
- //=============================================================
- static uint8_t dataBuffer[50 * 1024];
- #define STATE_START 0
- #define STATE_READ_CMD 1
- #define STATE_WRITE_CMD 2
- #define CMD_WRITE 0x09
- #define CMD_READ 0x0A
- #define TCP_IDLE_TIMEOUT_MS 30000 // drop session if silent for 30 s
- #define TCP_DEBUG 0 // set to 1 to log raw RX bytes to Serial
- constexpr int WRITE_HDR_LEN = 10;
- constexpr int READ_HDR_LEN = 8;
- struct adauWriteHeader {
- uint8_t command;
- uint8_t safeload;
- uint8_t placement;
- uint16_t totalLen;
- uint8_t chipAddr;
- uint16_t dataLen;
- uint16_t address;
- };
- struct adauReadHeader {
- uint8_t command;
- uint16_t totalLen;
- uint8_t chipAddr;
- uint16_t dataLen;
- uint16_t address;
- };
- static adauWriteHeader writeHeader;
- static adauReadHeader readHeader;
- static constexpr uint8_t DSP_7BIT = DSP_I2C_ADDRESS; // 0x34
- static constexpr uint8_t EEPROM_7BIT = EEPROM_I2C_ADDRESS; // 0x50
- //=============================================================
- // 24C256 EEPROM
- //=============================================================
- static constexpr uint32_t EEPROM_SIZE_BYTES = 32768;
- static constexpr uint16_t EEPROM_PAGE_SIZE = 64;
- static constexpr uint8_t I2C_MAX_DATA_PER_TX = 28;
- //=============================================================
- // Helpers
- //=============================================================
- static uint8_t chipAddrTo7bit(uint8_t chipAddr)
- {
- switch (chipAddr) {
- case 0x01: return 0x34; // chip index 1 = DSP
- case 0x02: return 0x50; // chip index 2 = EEPROM
- case 0x68: return 0x34;
- case 0xA0: return 0x50;
- case 0x34: return 0x34;
- case 0x50: return 0x50;
- default:
- if (chipAddr > 0x7F) return (uint8_t)(chipAddr >> 1);
- return chipAddr;
- }
- }
- static uint8_t registerSizeForAddress(uint16_t address, uint16_t dataLen)
- {
- if (address == dspRegister::CoreRegister) {
- if (dataLen == 2) return CORE_REGISTER_R0_REGSIZE;
- if (dataLen == 24) return HARDWARE_CONF_REGSIZE;
- return CORE_REGISTER_R0_REGSIZE;
- }
- if (address >= DSP_PROG_RAM_START && address <= DSP_PROG_RAM_END) return PROGRAM_REGSIZE;
- if (address < DSP_PROG_RAM_START) return PARAMETER_REGSIZE;
- return HARDWARE_CONF_REGSIZE;
- }
- static bool i2cAckPoll(uint8_t addr7, uint32_t timeoutMs = 80)
- {
- uint32_t start = millis();
- while ((millis() - start) < timeoutMs) {
- Wire.beginTransmission(addr7);
- if (Wire.endTransmission() == 0) return true;
- delay(1);
- }
- return false;
- }
- static bool eepromWritePageChunk(uint16_t memAddr, const uint8_t* data, uint16_t len)
- {
- Wire.beginTransmission(EEPROM_7BIT);
- Wire.write((uint8_t)(memAddr >> 8));
- Wire.write((uint8_t)(memAddr & 0xFF));
- for (uint16_t i = 0; i < len; i++) Wire.write(data[i]);
- if (Wire.endTransmission() != 0) return false;
- return i2cAckPoll(EEPROM_7BIT, 120);
- }
- static bool eepromWriteBlock(uint16_t memAddr, const uint8_t* data, uint16_t len)
- {
- while (len) {
- uint16_t pageOff = memAddr % EEPROM_PAGE_SIZE;
- uint16_t spaceInPage = EEPROM_PAGE_SIZE - pageOff;
- uint16_t chunk = len;
- if (chunk > spaceInPage) chunk = spaceInPage;
- if (chunk > I2C_MAX_DATA_PER_TX) chunk = I2C_MAX_DATA_PER_TX;
- if (!eepromWritePageChunk(memAddr, data, chunk)) return false;
- memAddr += chunk; data += chunk; len -= chunk;
- delay(0);
- }
- return true;
- }
- static bool i2cReadBlock(uint8_t addr7, uint16_t memAddr, uint8_t* out, uint16_t len)
- {
- uint16_t got = 0;
- while (got < len) {
- uint8_t ask = (len - got) > 32 ? 32 : (len - got);
- Wire.beginTransmission(addr7);
- Wire.write((uint8_t)((memAddr + got) >> 8));
- Wire.write((uint8_t)((memAddr + got) & 0xFF));
- if (Wire.endTransmission(false) != 0) return false;
- if (Wire.requestFrom((int)addr7, (int)ask) != ask) return false;
- for (uint8_t i = 0; i < ask; i++) out[got++] = Wire.read();
- }
- return true;
- }
- // SigmaTCP read response: 0x0B, totalLen_hi, totalLen_lo, status, dataLen_hi, dataLen_lo, [payload]
- static bool sendReadResponse(WiFiClient& client, const uint8_t* data, uint16_t dataLen, bool ok)
- {
- uint16_t totalLen = 6 + dataLen;
- uint8_t hdr[6];
- hdr[0] = 0x0B;
- hdr[1] = (uint8_t)(totalLen >> 8);
- hdr[2] = (uint8_t)(totalLen & 0xFF);
- hdr[3] = ok ? 0x00 : 0x01;
- hdr[4] = (uint8_t)(dataLen >> 8);
- hdr[5] = (uint8_t)(dataLen & 0xFF);
- if (client.write(hdr, sizeof(hdr)) != sizeof(hdr)) return false;
- if (dataLen > 0) {
- if (ok && data) {
- if (client.write(data, dataLen) != dataLen) return false;
- } else {
- // send zeros so SigmaStudio doesn't stall on a failed read
- static uint8_t zeros[256];
- uint16_t rem = dataLen;
- while (rem) {
- uint16_t chunk = rem > sizeof(zeros) ? sizeof(zeros) : rem;
- if (client.write(zeros, chunk) != chunk) return false;
- rem -= chunk;
- }
- }
- }
- return true;
- }
- // SigmaTCP write ack: 0x09, 0x00, 0x04, status
- static bool sendWriteAck(WiFiClient& client, bool ok)
- {
- uint8_t ack[4] = { 0x09, 0x00, 0x04, ok ? (uint8_t)0x00 : (uint8_t)0x01 };
- return client.write(ack, sizeof(ack)) == sizeof(ack);
- }
- static void printHex(const char* label, const uint8_t* buf, uint16_t len, uint16_t maxPrint = 24)
- {
- Serial.print(label);
- uint16_t n = len < maxPrint ? len : maxPrint;
- for (uint16_t i = 0; i < n; i++) {
- if (buf[i] < 0x10) Serial.print("0");
- Serial.print(buf[i], HEX);
- Serial.print(" ");
- }
- if (len > maxPrint) Serial.print("...");
- Serial.println();
- }
- //=============================================================
- // NVS credential helpers
- //=============================================================
- static Preferences s_prefs;
- static bool loadWifiCreds()
- {
- s_prefs.begin("wifi", true);
- String ssid = s_prefs.getString("ssid", "");
- String pass = s_prefs.getString("pass", "");
- s_prefs.end();
- if (ssid.length() == 0) return false;
- ssid.toCharArray(s_ssid, sizeof(s_ssid));
- pass.toCharArray(s_pass, sizeof(s_pass));
- return true;
- }
- static void saveWifiCreds(const String& ssid, const String& pass)
- {
- s_prefs.begin("wifi", false);
- s_prefs.putString("ssid", ssid);
- s_prefs.putString("pass", pass);
- s_prefs.end();
- }
- static void clearWifiCreds()
- {
- s_prefs.begin("wifi", false);
- s_prefs.clear();
- s_prefs.end();
- }
- //=============================================================
- // SoftAP config portal — runs when no credentials are stored
- // or when a previous connection attempt failed.
- // Serves a simple HTML form; never returns.
- //=============================================================
- static void startConfigAP()
- {
- Serial.println("Starting config AP: ModulosDSP-Setup");
- WiFi.mode(WIFI_AP);
- WiFi.softAP("ModulosDSP-Setup");
- delay(100);
- IPAddress apIP = WiFi.softAPIP();
- Serial.print("AP IP: "); Serial.println(apIP);
- if (s_lcdOk) {
- lcd.clear();
- lcd.setCursor(0, 0); lcd.print("WiFi Setup Mode");
- lcd.setCursor(0, 1); lcd.print("ModulosDSP-Setup");
- lcd.setCursor(0, 2); lcd.print(apIP);
- }
- httpServer.on("/", HTTP_GET, []() {
- httpServer.send_P(200, "text/html", AP_HTML);
- });
- httpServer.on("/save", HTTP_POST, []() {
- if (!httpServer.hasArg("ssid") || httpServer.arg("ssid").length() == 0) {
- httpServer.send(400, "text/plain", "SSID is required.");
- return;
- }
- String newSsid = httpServer.arg("ssid");
- String newPass = httpServer.arg("pass");
- saveWifiCreds(newSsid, newPass);
- Serial.printf("Credentials saved for SSID: %s\n", newSsid.c_str());
- httpServer.send(200, "text/html",
- "<html><body style='font-family:sans-serif;max-width:380px;margin:60px auto;padding:0 20px'>"
- "<h3>Saved!</h3><p>Connecting to <strong>" + newSsid +
- "</strong>… The device will reboot now.</p></body></html>");
- delay(1000);
- ESP.restart();
- });
- httpServer.begin();
- Serial.println("Config portal active — waiting for credentials");
- while (true) {
- httpServer.handleClient();
- delay(1);
- }
- }
- static void printWifiInfo()
- {
- Serial.println();
- Serial.println("WiFi connected.");
- 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) {
- lcd.setCursor(4, 3);
- lcd.print(WiFi.localIP());
- }
- }
- //=============================================================
- // HTTP EEPROM uploader state
- //=============================================================
- static volatile bool uploadActive = false;
- static volatile bool uploadVerify = false;
- static volatile bool uploadFailed = false;
- static volatile uint32_t uploadBytes = 0;
- static volatile uint32_t uploadCrc = 0;
- static char uploadErrorMsg[80] = "";
- static uint8_t s_uploadFirstBytes[16];
- static uint8_t s_uploadFirstCount = 0;
- //=============================================================
- // Capture-to-EEPROM state
- // captureBuffer holds the selfboot image built during each TCP
- // session. It is populated by captureWrite() for every sl=0
- // DSP write and is committed to 24C256 by handleSaveEeprom().
- //=============================================================
- static constexpr int CAPTURE_MAX_SIZE = 28 * 1024;
- static uint8_t captureBuffer[CAPTURE_MAX_SIZE];
- static int captureLen = 0;
- static bool captureReady = false;
- // Encode one sl=0 DSP write into the selfboot EEPROM format
- // (Analog Devices datasheet Table 19: [0x01][len_hi][len_lo][0x00][addr_hi][addr_lo][data...])
- // and append it to captureBuffer. Splits large writes into 32-word chunks.
- static void captureWrite(uint16_t address, const uint8_t* data, int dataLen)
- {
- int wordSize = 4; // parameter RAM
- if (address >= 0x0400 && address <= 0x07FF) wordSize = 5; // program RAM
- else if (address >= 0x0800) wordSize = 1; // hardware regs
- int maxChunk = 32 * wordSize;
- int offset = 0;
- while (offset < dataLen) {
- int bytes = min(dataLen - offset, maxChunk);
- uint16_t addr = address + (uint16_t)(offset / wordSize);
- if (captureLen + 6 + bytes + 2 > CAPTURE_MAX_SIZE) {
- Serial.println("CAP: buffer full");
- return;
- }
- uint16_t count = (uint16_t)(bytes + 3); // chipAddr(1) + regAddr(2) + data
- captureBuffer[captureLen++] = 0x01;
- captureBuffer[captureLen++] = (uint8_t)(count >> 8);
- captureBuffer[captureLen++] = (uint8_t)(count & 0xFF);
- captureBuffer[captureLen++] = 0x00;
- captureBuffer[captureLen++] = (uint8_t)(addr >> 8);
- captureBuffer[captureLen++] = (uint8_t)(addr & 0xFF);
- memcpy(&captureBuffer[captureLen], data + offset, bytes);
- captureLen += bytes;
- offset += bytes;
- }
- }
- //=============================================================
- // HTTP handlers
- //=============================================================
- static String contentTypeFor(const String& path) {
- if (path.endsWith(".html")) return "text/html";
- if (path.endsWith(".css")) return "text/css";
- if (path.endsWith(".js")) return "application/javascript";
- if (path.endsWith(".png")) return "image/png";
- if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg";
- if (path.endsWith(".webp")) return "image/webp";
- if (path.endsWith(".svg")) return "image/svg+xml";
- if (path.endsWith(".ico")) return "image/x-icon";
- if (path.endsWith(".woff")) return "font/woff";
- if (path.endsWith(".woff2"))return "font/woff2";
- return "application/octet-stream";
- }
- static bool streamFromFS(String path) {
- if (!LittleFS.exists(path)) {
- if (path.startsWith("/")) {
- String alt = path.substring(1);
- if (LittleFS.exists(alt)) path = alt; else return false;
- } else {
- String alt = "/" + path;
- if (LittleFS.exists(alt)) path = alt; else return false;
- }
- }
- File f = LittleFS.open(path, "r");
- if (!f) return false;
- httpServer.streamFile(f, contentTypeFor(path));
- f.close();
- return true;
- }
- static void handleRoot() {
- String html = FPSTR(INDEX_HTML);
- html.replace("{{IP}}", WiFi.localIP().toString());
- httpServer.send(200, "text/html; charset=utf-8", html);
- }
- static void handleUploadDone() {
- if (uploadFailed) {
- char buf[128];
- snprintf(buf, sizeof(buf), "{\"ok\":false,\"error\":\"%s\"}", uploadErrorMsg);
- httpServer.send(500, "application/json", buf);
- return;
- }
- char buf[80];
- snprintf(buf, sizeof(buf),
- "{\"ok\":true,\"bytes\":%u,\"crc\":\"0x%08X\",\"verified\":%s}",
- (uint32_t)uploadBytes, (uint32_t)uploadCrc,
- uploadVerify ? "true" : "false");
- httpServer.send(200, "application/json", buf);
- }
- static void handleUploadStream() {
- HTTPUpload& up = httpServer.upload();
- if (up.status == UPLOAD_FILE_START) {
- uploadActive = true; uploadFailed = false; uploadBytes = 0; uploadCrc = 0;
- uploadErrorMsg[0] = '\0'; s_uploadFirstCount = 0;
- uploadVerify = httpServer.hasArg("verify");
- Serial.println(); Serial.print("HTTP upload start: "); Serial.println(up.filename);
- Serial.print("Verify: "); Serial.println(uploadVerify ? "yes" : "no");
- Wire.beginTransmission(EEPROM_7BIT);
- uint8_t probeErr = Wire.endTransmission();
- Serial.print("EEPROM probe err: "); Serial.println(probeErr);
- if (probeErr != 0) {
- snprintf(uploadErrorMsg, sizeof(uploadErrorMsg), "EEPROM not found (I2C err %u)", probeErr);
- uploadFailed = true;
- }
- }
- else if (up.status == UPLOAD_FILE_WRITE) {
- if (uploadFailed) return;
- if ((uploadBytes + up.currentSize) > EEPROM_SIZE_BYTES) {
- snprintf(uploadErrorMsg, sizeof(uploadErrorMsg),
- "File too large: %u bytes (max 32768)", uploadBytes + up.currentSize);
- Serial.println(uploadErrorMsg); uploadFailed = true; return;
- }
- if (!eepromWriteBlock((uint16_t)uploadBytes, up.buf, (uint16_t)up.currentSize)) {
- snprintf(uploadErrorMsg, sizeof(uploadErrorMsg),
- "EEPROM I2C write failed at offset %u", (uint32_t)uploadBytes);
- Serial.println(uploadErrorMsg); uploadFailed = true; return;
- }
- if (s_uploadFirstCount == 0) {
- s_uploadFirstCount = up.currentSize < 16 ? up.currentSize : 16;
- memcpy(s_uploadFirstBytes, up.buf, s_uploadFirstCount);
- }
- uploadCrc = esp_rom_crc32_le(uploadCrc, up.buf, up.currentSize);
- uploadBytes += up.currentSize;
- ledMagenta();
- }
- else if (up.status == UPLOAD_FILE_END) {
- Serial.print("HTTP upload end, bytes="); Serial.println((uint32_t)uploadBytes);
- if (uploadVerify && !uploadFailed) {
- Serial.println("Verify start (CRC32)...");
- // Diagnostic: compare first bytes uploaded vs first bytes in EEPROM
- if (s_uploadFirstCount > 0) {
- uint8_t eepFirst[16];
- Serial.print("Uploaded[0]: ");
- for (uint8_t i = 0; i < s_uploadFirstCount; i++) Serial.printf("0x%02X ", s_uploadFirstBytes[i]);
- Serial.println();
- if (i2cReadBlock(EEPROM_7BIT, 0, eepFirst, s_uploadFirstCount)) {
- Serial.print("EEPROM [0]: ");
- for (uint8_t i = 0; i < s_uploadFirstCount; i++) Serial.printf("0x%02X ", eepFirst[i]);
- Serial.println();
- } else {
- Serial.println("EEPROM [0]: read failed");
- }
- }
- uint32_t crc = 0;
- static uint8_t tmp[256];
- uint32_t remaining = uploadBytes; uint16_t addr = 0;
- while (remaining) {
- uint16_t n = remaining > sizeof(tmp) ? sizeof(tmp) : (uint16_t)remaining;
- if (!i2cReadBlock(EEPROM_7BIT, addr, tmp, n)) {
- snprintf(uploadErrorMsg, sizeof(uploadErrorMsg),
- "EEPROM read failed at offset %u", (uint32_t)addr);
- Serial.println(uploadErrorMsg); uploadFailed = true; break;
- }
- crc = esp_rom_crc32_le(crc, tmp, n);
- addr += n; remaining -= n; delay(0);
- }
- Serial.printf("Verify CRC32: wrote=0x%08X read=0x%08X\n", (uint32_t)uploadCrc, crc);
- if (!uploadFailed && crc != uploadCrc) {
- snprintf(uploadErrorMsg, sizeof(uploadErrorMsg),
- "CRC mismatch: wrote 0x%08X, read 0x%08X", (uint32_t)uploadCrc, crc);
- Serial.println(uploadErrorMsg); uploadFailed = true;
- }
- }
- uploadActive = false; ledOff();
- Serial.println(uploadFailed ? "HTTP upload result: FAIL" : "HTTP upload result: OK");
- }
- else if (up.status == UPLOAD_FILE_ABORTED) {
- Serial.println("HTTP upload aborted"); uploadActive = false; uploadFailed = true; ledOff();
- }
- }
- //=============================================================
- // 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 DSP status handler GET /dsp_status
- //=============================================================
- static void handleDspStatus() {
- // Core Register (0x081C) — 2 bytes, bit 0 = Run
- uint8_t coreRaw[2] = {0, 0};
- bool coreOk = i2cReadBlock(DSP_7BIT, dspRegister::CoreRegister, coreRaw, 2);
- uint16_t coreVal = ((uint16_t)coreRaw[0] << 8) | coreRaw[1];
- // GPIO All Register (0x0808) — 1 byte
- uint8_t gpio = 0;
- bool gpioOk = i2cReadBlock(DSP_7BIT, dspRegister::GpioAllRegister, &gpio, 1);
- // ADC0–3 (0x0809–0x080C) — 1 byte each, read as a 4-byte burst
- uint8_t adc[4] = {0, 0, 0, 0};
- bool adcOk = i2cReadBlock(DSP_7BIT, dspRegister::Adc0, adc, 4);
- bool allOk = coreOk && gpioOk && adcOk;
- char buf[200];
- snprintf(buf, sizeof(buf),
- "{\"ok\":%s,\"running\":%s,"
- "\"coreReg\":\"0x%04X\","
- "\"gpio\":\"0x%02X\","
- "\"adc\":[\"0x%02X\",\"0x%02X\",\"0x%02X\",\"0x%02X\"]}",
- allOk ? "true" : "false",
- (coreOk && (coreVal & 0x01)) ? "true" : "false",
- coreVal, gpio,
- adc[0], adc[1], adc[2], adc[3]);
- httpServer.send(allOk ? 200 : 500, "application/json", buf);
- }
- //=============================================================
- // HTTP parameter tuner GET /params GET /param POST /param
- //=============================================================
- static void handleParamsPage() {
- String html = FPSTR(PARAMS_HTML);
- html.replace("{{IP}}", WiFi.localIP().toString());
- httpServer.send(200, "text/html; charset=utf-8", html);
- }
- static void handleParamGet() {
- if (!httpServer.hasArg("addr")) {
- httpServer.send(400, "text/plain", "Missing 'addr'.");
- return;
- }
- long addr = strtol(httpServer.arg("addr").c_str(), nullptr, 0);
- if (addr < 0 || addr > (long)DSP_PARAM_RAM_END) {
- httpServer.send(400, "text/plain", "addr must be 0x0000-0x03FF.");
- return;
- }
- uint8_t raw[4] = {0, 0, 0, 0};
- bool ok = i2cReadBlock(DSP_7BIT, (uint16_t)addr, raw, 4);
- // 5.23 fixed-point → float
- int32_t fixed = ((int32_t)raw[0] << 24) | ((int32_t)raw[1] << 16) |
- ((int32_t)raw[2] << 8) | (int32_t)raw[3];
- float fval = (float)fixed / 8388608.0f;
- char buf[128];
- snprintf(buf, sizeof(buf),
- "{\"ok\":%s,\"addr\":%ld,\"hex\":\"0x%02X%02X%02X%02X\",\"float\":%.7f}",
- ok ? "true" : "false", addr,
- raw[0], raw[1], raw[2], raw[3], fval);
- httpServer.send(ok ? 200 : 500, "application/json", buf);
- }
- static void handleParamSet() {
- if (!httpServer.hasArg("addr") || !httpServer.hasArg("value")) {
- httpServer.send(400, "text/plain", "Missing 'addr' or 'value'.");
- return;
- }
- long addr = strtol(httpServer.arg("addr").c_str(), nullptr, 0);
- if (addr < 0 || addr > (long)DSP_PARAM_RAM_END) {
- httpServer.send(400, "text/plain", "addr must be 0x0000-0x03FF.");
- return;
- }
- String mode = httpServer.arg("mode");
- DSPWriter dspWriter;
- if (mode == "hex") {
- String hexStr = httpServer.arg("value");
- if (hexStr.startsWith("0x") || hexStr.startsWith("0X")) hexStr = hexStr.substring(2);
- hexStr.replace(" ", "");
- hexStr.replace("_", "");
- if (hexStr.length() != 8) {
- httpServer.send(400, "text/plain", "Hex value must be exactly 8 hex characters.");
- return;
- }
- uint32_t v = strtoul(hexStr.c_str(), nullptr, 16);
- uint8_t sl[5] = { 0x00,
- (uint8_t)((v >> 24) & 0xFF),
- (uint8_t)((v >> 16) & 0xFF),
- (uint8_t)((v >> 8) & 0xFF),
- (uint8_t)( v & 0xFF) };
- dspWriter.safeload_writeRegister((uint16_t)addr, sl, true);
- Serial.printf("Param write (hex) addr=0x%04lX val=0x%08X\n", addr, v);
- } else {
- // Float mode — safeload_writeRegister handles 5.23 conversion internally
- float fval = httpServer.arg("value").toFloat();
- dspWriter.safeload_writeRegister((uint16_t)addr, fval, true);
- Serial.printf("Param write (float) addr=0x%04lX val=%.7f\n", addr, fval);
- }
- httpServer.send(200, "text/plain", "OK");
- }
- //=============================================================
- // HTTP WiFi reset handler POST /wifi_reset
- //=============================================================
- static void handleWifiReset() {
- Serial.println("WiFi credentials cleared — rebooting to AP setup mode");
- clearWifiCreds();
- httpServer.send(200, "text/plain",
- "Credentials cleared. Rebooting into setup mode.\n"
- "Connect to 'ModulosDSP-Setup' and open 192.168.4.1.");
- delay(500);
- ESP.restart();
- }
- //=============================================================
- // HTTP GPIO handlers GET /gpio POST /gpio
- //=============================================================
- static void handleGpioGet() {
- uint8_t gpio = 0;
- bool gpioOk = i2cReadBlock(DSP_7BIT, dspRegister::GpioAllRegister, &gpio, 1);
- uint8_t mpcfg[2] = {0, 0};
- bool cfgOk = i2cReadBlock(DSP_7BIT, dspRegister::MpCfg0, mpcfg, 2);
- bool ok = gpioOk && cfgOk;
- char buf[100];
- snprintf(buf, sizeof(buf),
- "{\"ok\":%s,\"gpio\":%u,\"mpcfg0\":%u,\"mpcfg1\":%u}",
- ok ? "true" : "false", gpio, mpcfg[0], mpcfg[1]);
- httpServer.send(ok ? 200 : 500, "application/json", buf);
- }
- static void handleGpioSet() {
- if (!httpServer.hasArg("value")) {
- httpServer.send(400, "text/plain", "Missing 'value' parameter.");
- return;
- }
- long val = httpServer.arg("value").toInt();
- if (val < 0 || val > 255) {
- httpServer.send(400, "text/plain", "value must be 0-255.");
- return;
- }
- uint8_t b = (uint8_t)val;
- DSPWriter::writeRegister(dspRegister::GpioAllRegister, 1, &b);
- Serial.printf("GPIO set: 0x%02X\n", b);
- httpServer.send(200, "text/plain", "OK");
- }
- //=============================================================
- // HTTP OTA handlers
- //=============================================================
- static void handleOtaPage() {
- String html = FPSTR(OTA_HTML);
- html.replace("{{IP}}", WiFi.localIP().toString());
- httpServer.send(200, "text/html; charset=utf-8", html);
- }
- static void handleOtaDone() {
- if (Update.hasError()) {
- String err = "OTA failed: " + String(Update.errorString());
- Serial.println(err);
- httpServer.send(500, "text/plain", err);
- } else {
- httpServer.send(200, "text/plain", "Firmware updated — rebooting now.");
- Serial.println("OTA success — rebooting");
- delay(500);
- ESP.restart();
- }
- }
- static void handleOtaStream() {
- HTTPUpload& up = httpServer.upload();
- if (up.status == UPLOAD_FILE_START) {
- Serial.printf("OTA start: %s\n", up.filename.c_str());
- ledCyan();
- if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
- Serial.print("OTA begin failed: ");
- Update.printError(Serial);
- }
- }
- else if (up.status == UPLOAD_FILE_WRITE) {
- if (Update.write(up.buf, up.currentSize) != up.currentSize) {
- Serial.print("OTA write failed: ");
- Update.printError(Serial);
- ledErrorFlash();
- }
- }
- else if (up.status == UPLOAD_FILE_END) {
- if (Update.end(true)) {
- Serial.printf("OTA end: %u bytes written\n", up.totalSize);
- } else {
- Serial.print("OTA end failed: ");
- Update.printError(Serial);
- ledErrorFlash();
- }
- ledOff();
- }
- else if (up.status == UPLOAD_FILE_ABORTED) {
- Update.abort();
- Serial.println("OTA aborted");
- ledOff();
- }
- }
- //=============================================================
- // Capture-to-EEPROM HTTP handlers
- //=============================================================
- static void handleCaptureStatus() {
- char buf[64];
- snprintf(buf, sizeof(buf), "{\"ready\":%s,\"bytes\":%d}",
- captureReady ? "true" : "false", captureLen);
- httpServer.send(200, "application/json", buf);
- }
- static void handleSaveEeprom() {
- if (!captureReady || captureLen == 0) {
- httpServer.send(400, "application/json",
- "{\"ok\":false,\"error\":\"No capture — connect SigmaStudio and download first.\"}");
- return;
- }
- Serial.printf("Save to EEPROM: %d bytes\n", captureLen);
- // Append selfboot end marker
- captureBuffer[captureLen] = 0x00;
- captureBuffer[captureLen + 1] = 0x00;
- int totalLen = captureLen + 2;
- ledYellow();
- bool ok = eepromWriteBlock(0, captureBuffer, (uint16_t)totalLen);
- ledOff();
- if (ok) {
- char buf[64];
- snprintf(buf, sizeof(buf), "{\"ok\":true,\"bytes\":%d}", totalLen);
- httpServer.send(200, "application/json", buf);
- Serial.printf("Save to EEPROM OK: %d bytes written\n", totalLen);
- } else {
- httpServer.send(500, "application/json",
- "{\"ok\":false,\"error\":\"EEPROM I2C write failed\"}");
- Serial.println("Save to EEPROM FAILED");
- }
- }
- //=============================================================
- // Setup
- //=============================================================
- void setup() {
- Wire.begin(I2C_SDA, I2C_SCL);
- Wire.setClock(100000); // PCF8574 backpacks are often 100 kHz only — use safe speed for LCD init
- statusLed.begin();
- statusLed.setBrightness(NEOPIXEL_BRIGHT);
- statusLed.show();
- s_lcdOk = (lcd.begin(20, 4) == 0);
- Wire.setClock(400000); // restore for DSP / EEPROM
- if (s_lcdOk) {
- lcd.display(); lcd.backlight();
- lcd.setCursor(2, 0); lcd.print("Modulos AudioDSP"); delay(1000);
- lcd.setCursor(5, 1); lcd.print("Booting..."); delay(1000);
- } else {
- Serial.println("LCD not found - continuing without display");
- }
- Serial.begin(115200); delay(1500);
- Serial.println(); Serial.println("Booting...");
- Serial.printf("Reset reason: %d\n", (int)esp_reset_reason());
- if (!loadWifiCreds()) {
- // NVS empty — fall back to compile-time defaults so an unattended
- // device can reach the network without needing the config portal.
- strlcpy(s_ssid, DEFAULT_SSID, sizeof(s_ssid));
- strlcpy(s_pass, DEFAULT_PASS, sizeof(s_pass));
- Serial.println("No NVS credentials — using defaults");
- }
- Serial.printf("Connecting to %s", s_ssid);
- WiFi.mode(WIFI_STA);
- WiFi.setAutoReconnect(true);
- WiFi.begin(s_ssid, s_pass);
- uint32_t t0 = millis();
- while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) {
- delay(500); Serial.print(".");
- }
- Serial.println();
- if (WiFi.status() != WL_CONNECTED) {
- Serial.println("WiFi connect failed — entering setup mode");
- startConfigAP(); // never returns
- }
- 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; }
- }
- Serial.println("LittleFS mounted OK");
- File root = LittleFS.open("/"); File f = root.openNextFile();
- while (f) { Serial.print("LittleFS: "); Serial.println(f.name()); f = root.openNextFile(); }
- if (s_lcdOk) { lcd.setCursor(3, 2); lcd.print("File System OK"); delay(1000); }
- tcpServer.begin();
- httpServer.on("/", HTTP_GET, handleRoot);
- 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.on("/dsp_status", HTTP_GET, handleDspStatus);
- httpServer.on("/gpio", HTTP_GET, handleGpioGet);
- httpServer.on("/gpio", HTTP_POST, handleGpioSet);
- httpServer.on("/wifi_reset", HTTP_POST, handleWifiReset);
- httpServer.on("/params", HTTP_GET, handleParamsPage);
- httpServer.on("/param", HTTP_GET, handleParamGet);
- httpServer.on("/param", HTTP_POST, handleParamSet);
- httpServer.on("/capture_status", HTTP_GET, handleCaptureStatus);
- httpServer.on("/save_eeprom", HTTP_POST, handleSaveEeprom);
- httpServer.onNotFound([]() {
- String uri = httpServer.uri();
- if (streamFromFS(uri)) return;
- Serial.print("HTTP 404: "); Serial.println(uri);
- httpServer.send(404, "text/plain", "Not found: " + uri);
- });
- httpServer.begin();
- if (s_lcdOk) {
- lcd.setCursor(4, 3); lcd.print("System Ready"); delay(1000);
- lcd.clear();
- lcd.setCursor(2, 0); lcd.print("Modulos AudioDSP");
- lcd.setCursor(3, 1); lcd.print(version); delay(500);
- }
- printWifiInfo();
- Serial.print("HTTP uploader: http://"); Serial.print(WiFi.localIP()); Serial.println("/");
- }
- //=============================================================
- // TCP bridge
- //=============================================================
- //=============================================================
- // TCP bridge
- //=============================================================
- static void handleTcpBridgeClient(WiFiClient& client)
- {
- Serial.println("TCP new connection");
- DSPWriter::resetSafeload();
- captureLen = 0; // fresh capture for this session
- captureReady = false;
- 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()) {
- httpServer.handleClient();
- delay(0);
- // ------------------------------------------------------------------
- // STEP 1: Always drain the TCP stack into dataBuffer.
- // Do this unconditionally every loop iteration — this is what keeps
- // the TCP receive window open. If we only drain when we feel like it,
- // the window goes to zero and SigmaStudio stops sending (ZeroWindow).
- // ------------------------------------------------------------------
- int avail = client.available();
- if (avail > 0) {
- int space = (int)sizeof(dataBuffer) - writeIndex;
- if (avail > space) {
- Serial.println("TCP RX overflow");
- ledErrorFlash(); client.stop(); return;
- }
- int got = client.read(&dataBuffer[writeIndex], avail);
- if (got > 0) {
- writeIndex += got;
- receivedByteCount += got;
- lastActivityMs = millis();
- }
- }
- // ------------------------------------------------------------------
- // STEP 2: Process whatever is in the buffer.
- // This is driven purely by buffer contents, not by client.available().
- // We loop here processing commands until we run out of buffered data.
- // ------------------------------------------------------------------
- bool processedSomething = true;
- while (processedSomething && client.connected()) {
- processedSomething = false;
- // --- STATE_START: identify opcode ---
- if (currentState == STATE_START) {
- if (receivedByteCount <= readIndex) {
- // Buffer empty — reset for next command
- writeIndex = readIndex = receivedByteCount = 0;
- ledOff();
- break; // nothing to process, go back to receive loop
- }
- #if TCP_DEBUG
- printHex("TCP RX: ", &dataBuffer[readIndex], (uint16_t)(receivedByteCount - readIndex));
- #endif
- uint8_t op = dataBuffer[readIndex];
- if (op == CMD_WRITE) { currentState = STATE_WRITE_CMD; processedSomething = true; }
- else if (op == CMD_READ) { currentState = STATE_READ_CMD; processedSomething = true; }
- else {
- Serial.printf("TCP invalid opcode: 0x%02X\n", op);
- ledErrorFlash();
- client.stop(); return;
- }
- }
- // --- STATE_WRITE_CMD ---
- if (currentState == STATE_WRITE_CMD) {
- // Need full header first
- if (receivedByteCount < (readIndex + WRITE_HDR_LEN)) break;
- writeHeader.safeload = dataBuffer[readIndex + 1];
- writeHeader.placement = dataBuffer[readIndex + 2];
- writeHeader.totalLen = (uint16_t)((dataBuffer[readIndex + 3] << 8) | dataBuffer[readIndex + 4]);
- writeHeader.chipAddr = dataBuffer[readIndex + 5];
- writeHeader.dataLen = (uint16_t)((dataBuffer[readIndex + 6] << 8) | dataBuffer[readIndex + 7]);
- writeHeader.address = (uint16_t)((dataBuffer[readIndex + 8] << 8) | dataBuffer[readIndex + 9]);
- if (writeHeader.totalLen != WRITE_HDR_LEN + writeHeader.dataLen) {
- Serial.printf("TCP WRITE bad totalLen: got %u expected %u\n",
- writeHeader.totalLen, WRITE_HDR_LEN + writeHeader.dataLen);
- ledErrorFlash(); client.stop(); return;
- }
- // Need full payload — if not here yet, break back to receive loop
- if (receivedByteCount < (readIndex + WRITE_HDR_LEN + (int)writeHeader.dataLen)) {
- Serial.printf("TCP WRITE buffering: have %d need %d bytes\n",
- receivedByteCount - readIndex,
- WRITE_HDR_LEN + (int)writeHeader.dataLen);
- break;
- }
- readIndex += WRITE_HDR_LEN;
- uint8_t target7 = chipAddrTo7bit(writeHeader.chipAddr);
- if (target7 == EEPROM_7BIT) {
- ledYellow();
- bool ok = eepromWriteBlock(writeHeader.address, &dataBuffer[readIndex], writeHeader.dataLen);
- readIndex += writeHeader.dataLen;
- sendWriteAck(client, ok);
- if (!ok) { Serial.println("TCP EEPROM write failed"); ledErrorFlash(); }
- else ledOff();
- currentState = STATE_START;
- processedSomething = true;
- continue;
- }
- if (target7 != DSP_7BIT) {
- Serial.printf("TCP unknown chipAddr: 0x%02X\n", writeHeader.chipAddr);
- ledErrorFlash(); client.stop(); return;
- }
- ledGreen();
- uint8_t registerSize = registerSizeForAddress(writeHeader.address, writeHeader.dataLen);
- uint16_t regAddress = writeHeader.address;
- Serial.printf("TCP WRITE addr=0x%04X len=%u regSz=%u safeload=%u\n",
- writeHeader.address, writeHeader.dataLen, registerSize, writeHeader.safeload);
- bool writeOk = true;
- if (writeHeader.safeload == 1) {
- if (writeHeader.dataLen % 4 != 0) {
- Serial.printf("TCP safeload dataLen %u not multiple of 4\n", writeHeader.dataLen);
- ledErrorFlash(); client.stop(); return;
- }
- int writeCount = writeHeader.dataLen / 4;
- int slri = readIndex;
- DSPWriter dspWriter;
- while (writeCount > 0) {
- uint8_t da[5] = { 0x00, dataBuffer[slri], dataBuffer[slri+1],
- dataBuffer[slri+2], dataBuffer[slri+3] };
- dspWriter.safeload_writeRegister(regAddress, da, writeCount == 1);
- regAddress++; slri += 4; writeCount--; delay(0);
- }
- } else {
- writeOk = DSPWriter::writeRegisterBlock(regAddress, writeHeader.dataLen,
- &dataBuffer[readIndex], registerSize);
- if (!writeOk) Serial.println("TCP DSP block write failed");
- // Capture sl=0 writes into selfboot image (captureWrite before DSPRUN check
- // so the final "start DSP" CoreRegister write is included in the image).
- if (writeOk && !captureReady) {
- captureWrite(writeHeader.address, &dataBuffer[readIndex], (int)writeHeader.dataLen);
- }
- // Detect DSPRUN bit going high = download sequence complete
- if (writeHeader.address == dspRegister::CoreRegister && writeHeader.dataLen >= 2) {
- bool running = (dataBuffer[readIndex + writeHeader.dataLen - 1] & 0x01) != 0;
- if (running && !captureReady) {
- captureReady = true;
- Serial.printf("CAP: capture ready — %d bytes\n", captureLen);
- }
- }
- }
- readIndex += writeHeader.dataLen;
- if (!writeOk) ledErrorFlash(); else ledOff();
- sendWriteAck(client, writeOk);
- currentState = STATE_START;
- processedSomething = true;
- continue;
- }
- // --- STATE_READ_CMD ---
- if (currentState == STATE_READ_CMD) {
- if (receivedByteCount < (readIndex + READ_HDR_LEN)) break;
- readHeader.totalLen = (uint16_t)((dataBuffer[readIndex + 1] << 8) | dataBuffer[readIndex + 2]);
- readHeader.chipAddr = dataBuffer[readIndex + 3];
- readHeader.dataLen = (uint16_t)((dataBuffer[readIndex + 4] << 8) | dataBuffer[readIndex + 5]);
- readHeader.address = (uint16_t)((dataBuffer[readIndex + 6] << 8) | dataBuffer[readIndex + 7]);
- readIndex += READ_HDR_LEN;
- uint8_t target7 = chipAddrTo7bit(readHeader.chipAddr);
- Serial.printf("TCP READ chip=0x%02X addr=0x%04X len=%u\n",
- readHeader.chipAddr, readHeader.address, readHeader.dataLen);
- if (readHeader.dataLen > 4096) { readHeader.dataLen = 4096; }
- static uint8_t readOut[4096];
- bool ok = false;
- ledBlue();
- if (target7 == EEPROM_7BIT) ok = i2cReadBlock(EEPROM_7BIT, readHeader.address, readOut, readHeader.dataLen);
- else if (target7 == DSP_7BIT) ok = i2cReadBlock(DSP_7BIT, readHeader.address, readOut, readHeader.dataLen);
- else {
- Serial.printf("TCP unknown chipAddr (READ): 0x%02X\n", readHeader.chipAddr);
- }
- if (!ok) Serial.println("TCP READ I2C failed - sending zeros");
- if (!sendReadResponse(client, ok ? readOut : nullptr, readHeader.dataLen, ok)) {
- Serial.println("TCP READ send failed"); ledErrorFlash(); client.stop(); return;
- }
- ledOff();
- currentState = STATE_START;
- processedSomething = true;
- continue;
- }
- } // end process loop
- // 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);
- }
- }
- } // end main while loop
- DSPWriter::resetSafeload(); // flush any mid-session safeload before disconnect
- client.stop();
- Serial.println("TCP disconnected");
- ledOff();
- }
- //=============================================================
- // WiFi watchdog
- //=============================================================
- static uint32_t s_wifiLastCheck = 0;
- static bool s_wifiLost = false;
- static void maintainWifi()
- {
- if (millis() - s_wifiLastCheck < 5000) return;
- s_wifiLastCheck = millis();
- if (WiFi.status() != WL_CONNECTED) {
- if (!s_wifiLost) {
- Serial.println("WiFi lost");
- if (s_lcdOk) { lcd.setCursor(0, 3); lcd.print("WiFi lost... "); }
- s_wifiLost = true;
- }
- 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;
- }
- }
- //=============================================================
- // Loop
- //=============================================================
- void loop() {
- maintainWifi();
- httpServer.handleClient();
- if (uploadActive) { delay(1); return; }
- WiFiClient client = tcpServer.available();
- if (client) handleTcpBridgeClient(client);
- delay(1);
- }
|