Serving /api/history (and /api/forecasts) built a ~12 KB String + JsonDocument every 15 s; over days this fragmented the ESP8266 heap until the TCP stack could no longer send a full response, causing the web page to fail with ERR_CONTENT_LENGTH_MISMATCH. Stream both array endpoints via chunked transfer (small stack buffer, no big allocations). Add free-heap to /api/current and Serial, plus a low-memory safety restart (history is persisted first). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
106 lines
3.5 KiB
C++
106 lines
3.5 KiB
C++
#include <ESP8266WebServer.h>
|
|
#include <ArduinoJson.h>
|
|
#include "state.h"
|
|
#include "app_settings.h"
|
|
#include "history.h"
|
|
#include "flog.h"
|
|
#include "forecast.h"
|
|
#include "web_page.h"
|
|
|
|
static ESP8266WebServer server(80);
|
|
|
|
static void handleRoot() {
|
|
server.send_P(200, "text/html", INDEX_HTML);
|
|
}
|
|
|
|
static void handleCurrent() {
|
|
JsonDocument doc;
|
|
char t[24];
|
|
snprintf(t, sizeof(t), "%04u-%02u-%02u %02u:%02u:%02u",
|
|
g_state.now.year, g_state.now.month, g_state.now.day,
|
|
g_state.now.hour, g_state.now.minute, g_state.now.second);
|
|
doc["time"] = t;
|
|
doc["abs"] = g_state.absHpa;
|
|
doc["msl"] = g_state.mslHpa;
|
|
doc["temp"] = g_state.tempC;
|
|
doc["trend"] = (int)g_state.trend;
|
|
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() {
|
|
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);
|
|
snprintf(buf, sizeof(buf), "%s{\"t\":%lu,\"msl\":%.1f,\"temp\":%.1f}",
|
|
i ? "," : "", (unsigned long)s.epoch, s.mslHpa, s.tempC);
|
|
server.sendContent(buf);
|
|
}
|
|
server.sendContent("]");
|
|
}
|
|
|
|
// Streamed (chunked). Forecast phrases contain no quotes/backslashes, so they
|
|
// are safe to embed directly in JSON.
|
|
static void handleForecasts() {
|
|
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
|
|
server.send(200, "application/json", "");
|
|
server.sendContent("[");
|
|
char buf[128];
|
|
int n = flogCount();
|
|
for (int i = n - 1, first = 1; i >= 0; i--, first = 0) { // newest first
|
|
FcastEntry e = flogGet(i);
|
|
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);
|
|
}
|
|
server.sendContent("]");
|
|
}
|
|
|
|
static void handleGetSettings() {
|
|
JsonDocument doc;
|
|
doc["altitude"] = settings.altitudeM;
|
|
doc["tz"] = settings.tzOffsetMin;
|
|
doc["lat"] = settings.lat;
|
|
doc["lon"] = settings.lon;
|
|
String out;
|
|
serializeJson(doc, out);
|
|
server.send(200, "application/json", out);
|
|
}
|
|
|
|
static void handlePostSettings() {
|
|
JsonDocument doc;
|
|
if (deserializeJson(doc, server.arg("plain"))) {
|
|
server.send(400, "application/json", "{\"ok\":false,\"err\":\"bad json\"}");
|
|
return;
|
|
}
|
|
settings.altitudeM = doc["altitude"] | settings.altitudeM;
|
|
settings.tzOffsetMin = doc["tz"] | settings.tzOffsetMin;
|
|
settings.lat = doc["lat"] | settings.lat;
|
|
settings.lon = doc["lon"] | settings.lon;
|
|
settingsSave();
|
|
server.send(200, "application/json", "{\"ok\":true}");
|
|
}
|
|
|
|
void webBegin() {
|
|
server.on("/", handleRoot);
|
|
server.on("/api/current", handleCurrent);
|
|
server.on("/api/history", handleHistory);
|
|
server.on("/api/forecasts", handleForecasts);
|
|
server.on("/api/settings", HTTP_GET, handleGetSettings);
|
|
server.on("/api/settings", HTTP_POST, handlePostSettings);
|
|
server.begin();
|
|
}
|
|
|
|
void webLoop() { server.handleClient(); }
|