Add pot/thermometer visualization to browser GUI
- Add volumen field to all sud JSON pot entries (30 L) - Fix parseSudDoc to read from nested doc.pot (was using stale flat keys) - SVG widget shows transparent rectangular pot with stirrer, thermometer with live temperature, and water fill level (water_mass/volumen) when a sud is loaded; no water shown when no sud is loaded Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DGKe8JzTM2Gr1fdx9wYXTi
This commit is contained in:
+60
-3
@@ -43,6 +43,7 @@ let sudName = '';
|
||||
let sudPotMass = 0;
|
||||
let sudGrainMass = 0;
|
||||
let sudWaterMass = 0;
|
||||
let sudVolumen = 0;
|
||||
let sudEmpty = true;
|
||||
let sudLastLoadedDoc = null;
|
||||
let sudState = null;
|
||||
@@ -127,14 +128,16 @@ function buildStep(number, defaults, rawStep) {
|
||||
}
|
||||
|
||||
function parseSudDoc(doc) {
|
||||
const pot = doc.pot || {};
|
||||
const stepDefaults = (doc.default && doc.default.step) || {};
|
||||
const schedule = (doc.steps || []).map((raw, i) => buildStep(i + 1, stepDefaults, raw));
|
||||
return {
|
||||
name: doc.Name || '',
|
||||
schedule: schedule,
|
||||
potMass: doc.pot_mass || 0,
|
||||
grainMass: doc.grain_mass || 0,
|
||||
waterMass: doc.water_mass || 0,
|
||||
potMass: pot.mass || 0,
|
||||
grainMass: pot.grain_mass || 0,
|
||||
waterMass: pot.water_mass || 0,
|
||||
volumen: pot.volumen || 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -262,6 +265,56 @@ function updateStepPlates() {
|
||||
});
|
||||
}
|
||||
|
||||
// --- Pot visualization ---
|
||||
|
||||
function wavePathD(x1, x2, y, amplitude, wavelength) {
|
||||
let d = `M ${x1} ${y.toFixed(1)}`;
|
||||
for (let x = x1 + 3; x <= x2; x += 3) {
|
||||
const yw = y + amplitude * Math.sin(2 * Math.PI * (x - x1) / wavelength);
|
||||
d += ` L ${x} ${yw.toFixed(1)}`;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
function currentStepWaterMass() {
|
||||
if (sudStepIndex !== null && sudStepIndex >= 0 && sudStepIndex < sudSchedule.length) {
|
||||
return sudSchedule[sudStepIndex].water_mass || 0;
|
||||
}
|
||||
return sudWaterMass;
|
||||
}
|
||||
|
||||
function updatePotVisualization() {
|
||||
// Thermometer mercury column (0°C = y=174, 100°C = y=28, tube length = 146)
|
||||
const tubeBottom = 174;
|
||||
const tubeLen = 146;
|
||||
const temp = plotTempIst !== null ? Math.max(0, Math.min(100, plotTempIst)) : 0;
|
||||
const mercuryH = (temp / 100) * tubeLen;
|
||||
document.getElementById('thermo-mercury').setAttribute('y', (tubeBottom - mercuryH).toFixed(1));
|
||||
document.getElementById('thermo-mercury').setAttribute('height', mercuryH.toFixed(1));
|
||||
document.getElementById('thermo-temp-label').textContent =
|
||||
plotTempIst !== null ? plotTempIst.toFixed(1) + ' °C' : '--';
|
||||
|
||||
// Water fill
|
||||
const waterFillEl = document.getElementById('water-fill');
|
||||
const waterWaveEl = document.getElementById('water-wave');
|
||||
const potInnerTop = 30;
|
||||
const potInnerBottom = 172;
|
||||
const potInnerH = potInnerBottom - potInnerTop; // 142
|
||||
|
||||
if (sudEmpty || sudVolumen <= 0) {
|
||||
waterFillEl.setAttribute('height', '0');
|
||||
waterWaveEl.setAttribute('d', '');
|
||||
return;
|
||||
}
|
||||
|
||||
const fillFraction = Math.min(1, Math.max(0, currentStepWaterMass() / sudVolumen));
|
||||
const waterH = fillFraction * potInnerH;
|
||||
const waterTop = potInnerBottom - waterH;
|
||||
waterFillEl.setAttribute('y', waterTop.toFixed(1));
|
||||
waterFillEl.setAttribute('height', waterH.toFixed(1));
|
||||
waterWaveEl.setAttribute('d', wavePathD(63, 177, waterTop, 3, 28));
|
||||
}
|
||||
|
||||
// --- Status line - mirrors update_status_step_label(). ---
|
||||
|
||||
function updateStatusLine() {
|
||||
@@ -345,6 +398,7 @@ function updateSudForecast(doc) {
|
||||
sudPotMass = parsed.potMass;
|
||||
sudGrainMass = parsed.grainMass;
|
||||
sudWaterMass = parsed.waterMass;
|
||||
sudVolumen = parsed.volumen;
|
||||
sudEmpty = sudSchedule.length === 0;
|
||||
if (isNewSchedule) {
|
||||
sudStepDescr = null;
|
||||
@@ -362,6 +416,7 @@ function updateSudForecast(doc) {
|
||||
rebuildStepPlates();
|
||||
updateStatusLine();
|
||||
updateSudActions();
|
||||
updatePotVisualization();
|
||||
document.title = sudName ? `BrewPi - ${sudName}` : 'BrewPi';
|
||||
}
|
||||
|
||||
@@ -450,6 +505,7 @@ function onTempCtrlChanged(msg) {
|
||||
plotTempIst = ist.Temp;
|
||||
document.getElementById('lcd-temp-ist').textContent = ist.Temp.toFixed(1);
|
||||
updateStepPlates();
|
||||
updatePotVisualization();
|
||||
}
|
||||
if ('Rate' in ist) {
|
||||
document.getElementById('lcd-rate-ist').textContent = ist.Rate.toFixed(1);
|
||||
@@ -504,6 +560,7 @@ function onSudChanged(msg) {
|
||||
sudStepType = step.Type;
|
||||
updateStatusLine();
|
||||
updateStepPlates();
|
||||
updatePotVisualization();
|
||||
} else if (key === 'HoldRemaining') {
|
||||
sudHoldRemaining = msg.HoldRemaining;
|
||||
updateStatusLine();
|
||||
|
||||
Reference in New Issue
Block a user