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

@@ -7,7 +7,9 @@
<script defer src="/js/app.js"></script>
<script type="module" src="/components/status-card.js"></script>
<script type="module" src="/components/temperature-card.js"></script>
<script type="module" src="/components/program-card.js"></script>
<script type="module" src="/components/led-card.js"></script>
<script type="module" src="/components/pump-card.js"></script>
<script type="module" src="/components/add-card.js"></script>
</head>
<body>
@@ -31,6 +33,8 @@
<div id="dashboard" class="panel active"><div class="grid">
<status-card></status-card>
<temperature-card></temperature-card>
<program-card></program-card>
<pump-card></pump-card>
<led-card></led-card>
<add-card></add-card>
</div></div>

View File

@@ -0,0 +1,104 @@
class ProgramCard extends HTMLElement {
constructor() {
super();
this.source = null;
}
connectedCallback() {
this.innerHTML = `
<style>
:host{display:block}section{height:100%;box-sizing:border-box}.radioRow{display:flex;gap:10px;flex-wrap:wrap;margin:8px 0}.radioRow label{display:inline-flex;gap:5px;align-items:center;margin:0}.radioRow input{width:auto}.row{display:flex;gap:8px;align-items:end}.row label{flex:1}.status{display:flex;gap:6px;flex-wrap:wrap;margin:8px 0}.badge{display:inline-flex;align-items:center;background:#eef3f6;border-radius:6px;padding:4px 7px;color:#263238;font-size:12px}
</style>
<section>
<h3>Program</h3>
<div class="row"><label>Target Temperature<input id="target" type="number" min="-40" max="85" step="0.01" value="22.00"></label><button id="save">Set</button></div>
<label>Mode</label>
<div class="radioRow">
<label><input type="radio" name="programMode" value="off"> Off</label>
<label><input type="radio" name="programMode" value="cool"> Cool</label>
<label><input type="radio" name="programMode" value="warm"> Warm</label>
</div>
<div class="status"><span id="temp" class="badge">--</span><span id="pump" class="badge">Pump --</span></div>
<pre id="out"></pre>
</section>`;
this.querySelector('#save').addEventListener('click', () => this.saveProgram());
this.querySelectorAll('input[name="programMode"]').forEach(input => input.addEventListener('change', () => this.saveProgram()));
window.addEventListener('app:login', event => this.startEvents(event.detail.token));
if (window.App && window.App.token()) this.startEvents(window.App.token());
}
disconnectedCallback() {
this.stopEvents();
}
startEvents(token) {
this.stopEvents();
this.loadProgram();
if (!token || !window.EventSource) return;
this.source = new EventSource('/api/temperature/events?token=' + encodeURIComponent(token));
this.source.addEventListener('temperature', event => {
try {
this.renderLiveTemperature(JSON.parse(event.data));
} catch (error) {}
});
this.source.addEventListener('pump', event => {
try {
this.renderPump(JSON.parse(event.data));
} catch (error) {}
});
}
stopEvents() {
if (this.source) this.source.close();
this.source = null;
}
selectedMode() {
const input = this.querySelector('input[name="programMode"]:checked');
return input ? input.value : 'off';
}
renderSettings(json) {
const mode = json.mode || 'off';
const modeInput = this.querySelector('input[name="programMode"][value="' + mode + '"]');
if (modeInput) modeInput.checked = true;
if (json.targetTemperatureC !== undefined) this.querySelector('#target').value = Number(json.targetTemperatureC).toFixed(2);
}
renderLiveTemperature(json) {
this.querySelector('#temp').textContent = json.sensorConnected && json.temperatureC !== null ? Number(json.temperatureC).toFixed(2) + ' C' : 'Sensor --';
}
renderPump(json) {
const enabled = json.enabled !== undefined ? json.enabled : json.pumpEnabled;
if (enabled === undefined) return;
this.querySelector('#pump').textContent = enabled ? 'Pump on' : 'Pump off';
}
render(json) {
this.renderSettings(json);
this.renderLiveTemperature(json);
this.renderPump(json);
}
async loadProgram() {
const text = await window.App.req('GET', '/api/program');
this.querySelector('#out').textContent = text;
try {
const json = JSON.parse(text);
if (json.success) this.render(json);
} catch (error) {}
}
async saveProgram() {
const target = Number(this.querySelector('#target').value);
const text = await window.App.req('POST', '/api/program', {mode: this.selectedMode(), targetTemperatureC: Number(target.toFixed(2))});
this.querySelector('#out').textContent = text;
try {
const json = JSON.parse(text);
if (json.success) this.render(json);
} catch (error) {}
}
}
customElements.define('program-card', ProgramCard);

View File

@@ -0,0 +1,51 @@
class PumpCard extends HTMLElement {
constructor() {
super();
this.enabled = false;
}
connectedCallback() {
this.innerHTML = `
<style>
:host{display:block}section{height:100%;box-sizing:border-box}.pumpState{font-size:32px;font-weight:700;line-height:1.15;margin:10px 0}.stateRow{display:flex;gap:8px;align-items:center}.dot{width:10px;height:10px;border-radius:50%;background:#607d8b}.dot.on{background:#188038}.actions{display:flex;gap:8px;flex-wrap:wrap}
</style>
<section>
<h3>Pump</h3>
<div class="muted">DC pump switch</div>
<div class="stateRow"><span id="dot" class="dot"></span><div id="state" class="pumpState">--</div></div>
<div class="actions"><button id="toggle">Toggle</button><button id="refresh" class="secondary">Refresh</button></div>
<pre id="out"></pre>
</section>`;
this.querySelector('#toggle').addEventListener('click', () => this.setPump(!this.enabled));
this.querySelector('#refresh').addEventListener('click', () => this.loadPump());
window.addEventListener('app:login', () => this.loadPump());
if (window.App && window.App.token()) this.loadPump();
}
render(json) {
this.enabled = !!json.enabled;
this.querySelector('#state').textContent = this.enabled ? 'On' : 'Off';
this.querySelector('#dot').classList.toggle('on', this.enabled);
this.querySelector('#toggle').textContent = this.enabled ? 'Turn off' : 'Turn on';
}
async loadPump() {
const text = await window.App.req('GET', '/api/pump');
this.querySelector('#out').textContent = text;
try {
const json = JSON.parse(text);
if (json.success) this.render(json);
} catch (error) {}
}
async setPump(enabled) {
const text = await window.App.req('POST', '/api/pump', {enabled});
this.querySelector('#out').textContent = text;
try {
const json = JSON.parse(text);
if (json.success) this.render(json);
} catch (error) {}
}
}
customElements.define('pump-card', PumpCard);