diff --git a/WeatherPredictor/WeatherPredictor.ino b/WeatherPredictor/WeatherPredictor.ino index 72715c6..b24e6f3 100644 --- a/WeatherPredictor/WeatherPredictor.ino +++ b/WeatherPredictor/WeatherPredictor.ino @@ -88,6 +88,15 @@ void loop() { tSample = now; sampleNow(); displayRender(g_state); + Serial.printf("heap=%u maxblock=%u\n", ESP.getFreeHeap(), ESP.getMaxFreeBlockSize()); + // Safety net: if the largest contiguous block gets too small, the TCP stack + // can no longer send responses. Persist and reboot to defragment. + if (ESP.getMaxFreeBlockSize() < 6000) { + Serial.println(F("Low memory -> saving history and restarting")); + historySave(); + delay(50); + ESP.restart(); + } } if (now - tHistory >= HISTORY_INTERVAL_MS) { diff --git a/WeatherPredictor/web_server.cpp b/WeatherPredictor/web_server.cpp index f5447d8..f583948 100644 --- a/WeatherPredictor/web_server.cpp +++ b/WeatherPredictor/web_server.cpp @@ -27,41 +27,44 @@ static void handleCurrent() { doc["haveTrend"] = g_state.haveTrend; doc["forecast"] = g_state.haveTrend ? g_state.forecast.text : "Collecting data..."; doc["category"] = g_state.haveTrend ? categoryShort(g_state.forecast.category) : "..."; + doc["heap"] = ESP.getFreeHeap(); // for monitoring memory health String out; serializeJson(doc, out); server.send(200, "application/json", out); } +// Streamed (chunked) so we never build a large String/JsonDocument on the heap. static void handleHistory() { - JsonDocument doc; - JsonArray arr = doc.to(); + server.setContentLength(CONTENT_LENGTH_UNKNOWN); + server.send(200, "application/json", ""); + server.sendContent("["); + char buf[80]; int n = historyCount(); for (int i = 0; i < n; i++) { Sample s = historyGet(i); - JsonObject o = arr.add(); - o["t"] = s.epoch; - o["msl"] = s.mslHpa; - o["temp"] = s.tempC; + snprintf(buf, sizeof(buf), "%s{\"t\":%lu,\"msl\":%.1f,\"temp\":%.1f}", + i ? "," : "", (unsigned long)s.epoch, s.mslHpa, s.tempC); + server.sendContent(buf); } - String out; - serializeJson(doc, out); - server.send(200, "application/json", out); + server.sendContent("]"); } +// Streamed (chunked). Forecast phrases contain no quotes/backslashes, so they +// are safe to embed directly in JSON. static void handleForecasts() { - JsonDocument doc; - JsonArray arr = doc.to(); + server.setContentLength(CONTENT_LENGTH_UNKNOWN); + server.send(200, "application/json", ""); + server.sendContent("["); + char buf[128]; int n = flogCount(); - for (int i = n - 1; i >= 0; i--) { // newest first + for (int i = n - 1, first = 1; i >= 0; i--, first = 0) { // newest first FcastEntry e = flogGet(i); - JsonObject o = arr.add(); - o["t"] = e.epoch; - o["cat"] = categoryShort((WxCategory)e.cat); - o["text"] = forecastTextForLetter(e.letter); + snprintf(buf, sizeof(buf), "%s{\"t\":%lu,\"cat\":\"%s\",\"text\":\"%s\"}", + first ? "" : ",", (unsigned long)e.epoch, + categoryShort((WxCategory)e.cat), forecastTextForLetter(e.letter)); + server.sendContent(buf); } - String out; - serializeJson(doc, out); - server.send(200, "application/json", out); + server.sendContent("]"); } static void handleGetSettings() {