Sitelet https://github.com/akomakom/esp32-spa-controller/commit/189a1c933fd43effc9ff2280e41119803f9ba467
Skip to content

Commit 189a1c9

Browse files
akomakomclaude
andcommitted
Fix display RGB panel crash on IDF5; make heater schedule unit-aware
Display crash-loop: on IDF 5.x, esp_lcd_new_rgb_panel() requires the frame buffer size (h_res*v_res) to be a multiple of bounce_buffer_size_px. The vendored Arduino_GFX hardcoded "40 * w", which does not divide 480x272, so init aborted (ESP_ERR_INVALID_ARG). Set bounce_buffer_size_px = 0 to disable the bounce buffer (DMA straight from the PSRAM framebuffer); fine for this low-bandwidth panel. Web schedule dialog is now temperature-unit aware for sensor-based controls: the range and the on/off setpoint fields display in the active unit (e.g. 32-104F instead of the native 0-40C) and are converted back to Celsius on save, with validation in the display unit. Matches the card + display, which already convert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 610a427 commit 189a1c9

1 file changed

Lines changed: 29 additions & 14 deletions

File tree

‎controller/data/index.html‎

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,10 @@
252252

253253
// ---- helpers ----
254254
function isC() { return unit === 1; }
255-
function toDisplayTemp(c) { return isC() ? c : (c * 9 / 5 + 32); }
255+
function toDisplayTemp(c) { return isC() ? c : (c * 9 / 5 + 32); } // native C -> display unit
256+
function fromDisplayTemp(v) { return isC() ? v : (v - 32) * 5 / 9; } // display unit -> native C
256257
function unitLabel() { return isC() ? "°C" : "°F"; }
258+
function isTempControl(c) { return c && c.type === "sensor-based"; }
257259

258260
function humanize(sec) {
259261
sec = Math.round(sec);
@@ -441,17 +443,27 @@
441443

442444
// ---- schedule dialog ----
443445
var scheduleTarget = null;
446+
function rangeText(c) {
447+
if (isTempControl(c)) {
448+
return "(" + Math.round(toDisplayTemp(c.min)) + "–" + Math.round(toDisplayTemp(c.max)) + unitLabel() + ")";
449+
}
450+
return "(" + c.min + "–" + c.max + ")";
451+
}
444452
function openSchedule(name) {
445453
scheduleTarget = name;
446454
var c = statusByName[name] || {};
455+
var isTemp = isTempControl(c);
447456
document.getElementById("scheduleTitle").textContent = "Schedule: " + name;
448-
document.getElementById("onRange").textContent = "(" + c.min + "–" + c.max + ")";
449-
document.getElementById("offRange").textContent = "(" + c.min + "–" + c.max + ")";
457+
document.getElementById("onRange").textContent = rangeText(c);
458+
document.getElementById("offRange").textContent = rangeText(c);
450459
fetch("/configureControl?control=" + encodeURIComponent(name))
451460
.then(function (r) { return r.json(); })
452461
.then(function (cfg) {
453-
["percentageOfDayOnTime","numberOfTimesToRun","normalValueOn","normalValueOff","overrideDefaultDurationSeconds"]
462+
["percentageOfDayOnTime","numberOfTimesToRun","overrideDefaultDurationSeconds"]
454463
.forEach(function (k) { document.getElementById(k).value = cfg[k]; });
464+
// Temperatures are stored natively in Celsius; show them in the display unit.
465+
document.getElementById("normalValueOn").value = isTemp ? Math.round(toDisplayTemp(cfg.normalValueOn)) : cfg.normalValueOn;
466+
document.getElementById("normalValueOff").value = isTemp ? Math.round(toDisplayTemp(cfg.normalValueOff)) : cfg.normalValueOff;
455467
updateScheduleHints();
456468
document.getElementById("scheduleDialog").showModal();
457469
})
@@ -468,18 +480,21 @@
468480
document.getElementById("numberOfTimesToRun").addEventListener("input", updateScheduleHints);
469481
document.getElementById("scheduleSave").addEventListener("click", function () {
470482
var c = statusByName[scheduleTarget] || {};
483+
var isTemp = isTempControl(c);
471484
var data = { control: scheduleTarget };
472485
var ok = true;
473-
["percentageOfDayOnTime","numberOfTimesToRun","normalValueOn","normalValueOff","overrideDefaultDurationSeconds"]
474-
.forEach(function (k) {
475-
var el = document.getElementById(k);
476-
data[k] = el.value;
477-
if ((k === "normalValueOn" || k === "normalValueOff") &&
478-
(Number(el.value) < c.min || Number(el.value) > c.max)) {
479-
el.classList.add("invalid"); ok = false;
480-
} else { el.classList.remove("invalid"); }
481-
});
482-
if (!ok) { toast("Value out of range (" + c.min + "–" + c.max + ")"); return; }
486+
["percentageOfDayOnTime","numberOfTimesToRun","overrideDefaultDurationSeconds"]
487+
.forEach(function (k) { data[k] = document.getElementById(k).value; });
488+
// Temperature fields are entered in the display unit; validate there, then
489+
// convert back to native Celsius (the controller's storage unit).
490+
["normalValueOn","normalValueOff"].forEach(function (k) {
491+
var el = document.getElementById(k);
492+
var nativeVal = isTemp ? Math.round(fromDisplayTemp(Number(el.value))) : Number(el.value);
493+
if (nativeVal < c.min || nativeVal > c.max) { el.classList.add("invalid"); ok = false; }
494+
else { el.classList.remove("invalid"); }
495+
data[k] = nativeVal;
496+
});
497+
if (!ok) { toast("Value out of range " + rangeText(c)); return; }
483498
post("/configureControl", data)
484499
.then(function () { document.getElementById("scheduleDialog").close(); refresh(); })
485500
.catch(function (e) { toast("Save failed"); });

0 commit comments

Comments
 (0)