Implement bed cooler...

This commit is contained in:
2026-06-28 16:25:55 -05:00
parent e188930755
commit c6213e1752
16 changed files with 422 additions and 1 deletions

View File

@@ -5,6 +5,10 @@ ApiDef customApiDefs[] = {
{"/api/ping", "GET", "", true, handlePing, nullptr},
{"/api/add", "POST", "", true, handleAdd, nullptr},
{"/api/led", "POST", "", true, handleLed, nullptr},
{"/api/pump", "GET", "WebUIConnect", false, handlePump, nullptr},
{"/api/pump", "POST", "WebUIConnect", false, handlePump, nullptr},
{"/api/program", "GET", "WebUIConnect", false, handleProgram, nullptr},
{"/api/program", "POST", "WebUIConnect", false, handleProgram, nullptr},
{"/api/temperature", "GET", "WebUIConnect", false, handleTemperature, nullptr},
{"/api/temperature/events", "GET", "WebUIConnect", false, handleTemperatureEvents, nullptr},
};

View File

@@ -10,6 +10,10 @@ void applyLed() {
analogWrite(LED_BUILTIN, duty);
}
void applyPump() {
digitalWrite(PUMP_PIN, pumpEnabled ? PUMP_ACTIVE_LEVEL : !PUMP_ACTIVE_LEVEL);
}
void factoryReset() {
prefs.clear();
LittleFS.remove(LOG_FILE_PATH);

View File

@@ -27,6 +27,9 @@ size_t maxLogBytes = 50 * 1024;
bool setupMode = false;
uint8_t ledBrightness = 0;
bool ledInverted = DEFAULT_LED_INVERTED;
bool pumpEnabled = false;
ProgramMode programMode = PROGRAM_OFF;
float programTargetC = PROGRAM_DEFAULT_TARGET_C;
String updateUrl = DEFAULT_UPDATE_URL;
String networkHostname;
bool networkDhcp = true;
@@ -106,6 +109,9 @@ void loadSettings() {
maxLogBytes = prefs.getUInt("logMax", 50 * 1024);
ledInverted = prefs.getBool("ledInv", DEFAULT_LED_INVERTED);
ledBrightness = constrain(prefs.getUChar("ledBright", 0), 0, 100);
programMode = (ProgramMode)constrain(prefs.getUChar("progMode", PROGRAM_OFF), PROGRAM_OFF, PROGRAM_WARM);
programTargetC = prefs.getFloat("progTargetC", PROGRAM_DEFAULT_TARGET_C);
if (isnan(programTargetC) || programTargetC < -40.0f || programTargetC > 85.0f) programTargetC = PROGRAM_DEFAULT_TARGET_C;
updateUrl = prefString("updateUrl", DEFAULT_UPDATE_URL);
networkHostname = prefString("netHost", "");
networkDhcp = prefs.getBool("netDhcp", true);

View File

@@ -22,7 +22,7 @@ static void beginTemperatureSensor() {
temperatureStarted = true;
}
static void updateTemperatureSensor() {
void updateTemperatureSensor() {
beginTemperatureSensor();
uint32_t now = millis();
if (temperaturePending && now - temperatureRequestMs >= 750) {
@@ -37,6 +37,11 @@ static void updateTemperatureSensor() {
}
}
float currentTemperatureC() {
updateTemperatureSensor();
return lastTemperatureC;
}
static String temperatureJsonFields() {
updateTemperatureSensor();
String json = "\"pin\":" + String(DS18B20_PIN);
@@ -49,6 +54,85 @@ static String temperatureJsonFields() {
return json;
}
static String pumpJsonFields() {
return "\"enabled\":" + String(pumpEnabled ? "true" : "false") + ",\"pin\":" + String(PUMP_PIN);
}
String programModeName() {
if (programMode == PROGRAM_COOL) return "cool";
if (programMode == PROGRAM_WARM) return "warm";
return "off";
}
static ProgramMode parseProgramMode(const String &value, ProgramMode fallback) {
String mode = value;
mode.toLowerCase();
if (mode == "cool") return PROGRAM_COOL;
if (mode == "warm") return PROGRAM_WARM;
if (mode == "off") return PROGRAM_OFF;
return fallback;
}
static float jsonFloatValue(const String &json, const char *key, float fallback) {
String needle = "\"" + String(key) + "\"";
int p = json.indexOf(needle);
if (p < 0) return fallback;
p = json.indexOf(':', p + needle.length());
if (p < 0) return fallback;
p++;
while (p < (int)json.length() && isspace(json[p])) p++;
if (p >= (int)json.length()) return fallback;
if (json[p] == '"') return jsonStringValue(json, key, String(fallback, 2)).toFloat();
return json.substring(p).toFloat();
}
static void setPumpEnabled(bool enabled, const String &reason) {
if (pumpEnabled == enabled) return;
pumpEnabled = enabled;
applyPump();
appendLog(LOG_INFO, "Pump switched " + String(pumpEnabled ? "on" : "off") + reason);
}
static String programJsonFields() {
float temperatureC = currentTemperatureC();
String json = "\"mode\":\"" + programModeName() + "\"";
json += ",\"targetTemperatureC\":" + String(programTargetC, 2);
json += ",\"targetTemperatureF\":" + String((programTargetC * 9.0f / 5.0f) + 32.0f, 2);
json += ",\"toleranceC\":" + String(PROGRAM_TOLERANCE_C, 2);
json += ",\"pumpEnabled\":" + String(pumpEnabled ? "true" : "false");
json += ",\"sensorConnected\":";
json += isnan(temperatureC) ? "false" : "true";
json += ",\"temperatureC\":";
json += isnan(temperatureC) ? "null" : String(temperatureC, 2);
return json;
}
void updateProgram() {
static uint32_t lastProgramMs = 0;
uint32_t now = millis();
updateTemperatureSensor();
if (now - lastProgramMs < 500) return;
lastProgramMs = now;
if (programMode == PROGRAM_OFF) {
setPumpEnabled(false, " by program");
return;
}
float temperatureC = currentTemperatureC();
if (isnan(temperatureC)) {
setPumpEnabled(false, " by program sensor fault");
return;
}
float roundedTemperatureC = roundf(temperatureC * 100.0f) / 100.0f;
if (programMode == PROGRAM_COOL) {
setPumpEnabled(roundedTemperatureC >= programTargetC + PROGRAM_TOLERANCE_C, " by cool program");
} else if (programMode == PROGRAM_WARM) {
setPumpEnabled(roundedTemperatureC <= programTargetC - PROGRAM_TOLERANCE_C, " by warm program");
}
}
void handlePing() {
if (!authorize()) return;
sendJson(200, jsonOk("\"uptimeMs\":" + String(millis()) + ",\"version\":\"" + APP_VERSION + "\",\"name\":\"" + jsonEscape(deviceName()) + "\",\"ip\":\"" + WiFi.localIP().toString() + "\""));
@@ -71,6 +155,29 @@ void handleLed() {
sendJson(200, jsonOk("\"brightness\":" + String(ledBrightness)));
}
void handlePump() {
if (!authorize()) return;
if (server.method() == HTTP_POST) {
setPumpEnabled(jsonBoolValue(requestBody(), "enabled", server.arg("enabled") == "true"), " manually");
}
sendJson(200, jsonOk(pumpJsonFields()));
}
void handleProgram() {
if (!authorize()) return;
if (server.method() == HTTP_POST) {
String body = requestBody();
programMode = parseProgramMode(jsonStringValue(body, "mode", programModeName()), programMode);
programTargetC = constrain(jsonFloatValue(body, "targetTemperatureC", programTargetC), -40.0f, 85.0f);
programTargetC = roundf(programTargetC * 100.0f) / 100.0f;
prefs.putUChar("progMode", programMode);
prefs.putFloat("progTargetC", programTargetC);
updateProgram();
appendLog(LOG_INFO, "Program set to " + programModeName() + " target " + String(programTargetC, 2) + " C");
}
sendJson(200, jsonOk(programJsonFields()));
}
void handleTemperature() {
if (!authorize()) return;
sendJson(200, jsonOk(temperatureJsonFields()));
@@ -81,6 +188,8 @@ void handleTemperatureEvents() {
String payload = "retry: 1000\n";
payload += "event: temperature\n";
payload += "data: {" + temperatureJsonFields() + "}\n\n";
payload += "event: pump\n";
payload += "data: {" + pumpJsonFields() + "}\n\n";
server.sendHeader("Cache-Control", "no-store");
server.sendHeader("Connection", "close");
server.send(200, "text/event-stream", payload);

View File

@@ -10,6 +10,8 @@ void setup() {
loadSettings();
pinMode(LED_BUILTIN, OUTPUT);
applyLed();
pinMode(PUMP_PIN, OUTPUT);
applyPump();
checkFactoryResetPin();
bool configured = prefs.getBool("configured", false);
@@ -34,5 +36,6 @@ void setup() {
void loop() {
if (setupMode) dnsServer.processNextRequest();
updateProgram();
server.handleClient();
}