Remove thermostat/indoor sensor integration
Ecobee API is closed to new developer registrations and iOS sub-daily automations are no longer available, making the HomeKit push approach unworkable. Removes all related code: - server.js: HomeKit push endpoints, token management, data files - dashboard.html: thermostat section card, indoor stat tiles, indoor charts, HomeKit settings panel, HVAC badge CSS, all related JS - index.html: Indoor Temp stat card - app.js: indoor sensor display logic Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
5eae872773
commit
e133781760
4 files changed
+1
-298
No files matched your search
@@ -3067,13 +3067,6 @@ function renderAmbientStation(device) {
|
||||
setEl('st-uv', data.uv != null ? data.uv : '--');
|
||||
setEl('st-solar', data.solarradiation != null ? Math.round(data.solarradiation) : '--');
|
||||
|
||||
const indoorCard = document.getElementById('st-indoor-card');
|
||||
if (data.tempinf != null) {
|
||||
setEl('st-tempin', fmtTemp(data.tempinf));
|
||||
setEl('st-humin', data.humidityin != null ? `${data.humidityin}% RH` : '');
|
||||
if (indoorCard) indoorCard.classList.remove('hidden');
|
||||
}
|
||||
|
||||
if (peekEl && data.tempf != null) {
|
||||
peekEl.textContent = `· ${fmtTemp(data.tempf)}°${tempUnit} ${data.humidity != null ? data.humidity + '% RH' : ''}`;
|
||||
}
|
||||
|
||||
+1
-213
@@ -179,19 +179,6 @@ a { color: var(--cool); text-decoration: none; }
|
||||
.today-lbl { font-size: 0.75rem; color: var(--ink-light); }
|
||||
|
||||
/* ── Ecobee section ── */
|
||||
.hvac-badge { display: inline-block; padding: 0.2rem 0.6rem; border-radius: 20px; font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.hvac-heating { background: rgba(194,90,42,0.18); color: var(--accent); border: 1px solid var(--accent); }
|
||||
.hvac-cooling { background: rgba(42,107,138,0.18); color: var(--cool); border: 1px solid var(--cool); }
|
||||
.hvac-fan { background: rgba(42,107,138,0.1); color: var(--cool); border: 1px solid var(--border); }
|
||||
.hvac-idle { background: var(--paper-darker); color: var(--ink-light); border: 1px solid var(--border); }
|
||||
.ecobee-sensors { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; }
|
||||
.sensor-chip { background: var(--paper); border: 1px solid var(--border); border-radius: 8px; padding: 0.35rem 0.7rem; font-size: 0.8rem; display: flex; align-items: center; gap: 0.4rem; }
|
||||
.sensor-chip-name { color: var(--ink-light); font-size: 0.72rem; }
|
||||
.sensor-chip-val { font-weight: 700; }
|
||||
.ecobee-row { display: flex; gap: 1.5rem; align-items: flex-start; flex-wrap: wrap; }
|
||||
.ecobee-main { flex: 0 0 auto; min-width: 120px; }
|
||||
.ecobee-setpoints { display: flex; gap: 1rem; margin-top: 0.4rem; font-size: 0.85rem; color: var(--ink-light); }
|
||||
.ecobee-setpoints strong { color: var(--ink); }
|
||||
|
||||
/* ── Tile color tints ── */
|
||||
.stat-hot { border-color: rgba(220,80,30,0.55) !important; background: rgba(220,80,30,0.07) !important; }
|
||||
@@ -294,7 +281,7 @@ canvas.sparkline { display: block; width: 100%; height: 22px; margin-top: 0.4rem
|
||||
<div class="today-item"><span class="today-icon">💨</span><span class="today-val" id="todayGust">—</span><span class="today-lbl"> mph peak gust</span></div>
|
||||
</div>
|
||||
|
||||
<!-- Secondary stats: wind, rain, indoor -->
|
||||
<!-- Secondary stats: wind, rain -->
|
||||
<div class="stats secondary" id="statsRow">
|
||||
<div class="stat" id="statWindTile">
|
||||
<div class="stat-label">Wind</div>
|
||||
@@ -307,10 +294,6 @@ canvas.sparkline { display: block; width: 100%; height: 22px; margin-top: 0.4rem
|
||||
<div class="stat"><div class="stat-label">Max Gust Today</div><div class="stat-val" id="sMaxGust">—</div><div class="stat-unit">mph</div></div>
|
||||
<div class="stat" id="statRainTile"><div class="stat-label">Rain Today</div><div class="stat-val" id="sRain">—</div><div class="stat-unit">in</div></div>
|
||||
<div class="stat" id="statRainRateTile"><div class="stat-label">Rain Rate</div><div class="stat-val" id="sRainRate">—</div><div class="stat-unit">in/hr</div></div>
|
||||
<div class="stat" id="indoorTempTile" style="display:none"><div class="stat-label">Temp In</div><div class="stat-val" id="sTempIn">—</div><div class="stat-unit">°F</div></div>
|
||||
<div class="stat" id="indoorFeelsTile" style="display:none"><div class="stat-label">Feels Like In</div><div class="stat-val" id="sFeelsIn">—</div><div class="stat-unit">°F</div></div>
|
||||
<div class="stat" id="indoorHumTile" style="display:none"><div class="stat-label">Humidity In</div><div class="stat-val" id="sHumIn">—</div><div class="stat-unit">%</div></div>
|
||||
<div class="stat" id="indoorDewTile" style="display:none"><div class="stat-label">Dew Point In</div><div class="stat-val" id="sDewIn">—</div><div class="stat-unit">°F</div></div>
|
||||
</div>
|
||||
|
||||
<!-- Totals & analytics -->
|
||||
@@ -334,29 +317,6 @@ canvas.sparkline { display: block; width: 100%; height: 22px; margin-top: 0.4rem
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Thermostat (HomeKit push) -->
|
||||
<div class="section-card" id="ecobeeSection" style="display:none">
|
||||
<div class="section-card-hdr" style="display:flex;align-items:center;gap:0.75rem">
|
||||
Thermostat
|
||||
<span id="ecobeeBadge" class="hvac-badge hvac-idle">—</span>
|
||||
<span style="font-size:0.72rem;color:var(--ink-light);margin-left:auto" id="ecobeeUpdated"></span>
|
||||
</div>
|
||||
<div class="ecobee-row">
|
||||
<div class="ecobee-main">
|
||||
<div class="hero-temp-row">
|
||||
<div class="hero-temp" id="ecobeeTempF" style="font-size:3.5rem">—</div>
|
||||
<div class="hero-temp-unit" style="font-size:1.4rem;margin-top:0.4rem">°F</div>
|
||||
</div>
|
||||
<div class="ecobee-setpoints">
|
||||
<span>Heat: <strong id="ecobeeHeatSet">—</strong>°</span>
|
||||
<span>Cool: <strong id="ecobeeCoolSet">—</strong>°</span>
|
||||
</div>
|
||||
<div style="margin-top:0.35rem;font-size:0.82rem;color:var(--ink-light)">Humidity: <strong id="ecobeeHum">—</strong>%</div>
|
||||
<div style="margin-top:0.25rem;font-size:0.82rem;color:var(--ink-light)">Mode: <strong id="ecobeeMode">—</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Range selector -->
|
||||
<div class="range-bar">
|
||||
<span>History:</span>
|
||||
@@ -403,43 +363,6 @@ canvas.sparkline { display: block; width: 100%; height: 22px; margin-top: 0.4rem
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- HomeKit Thermostat Push Settings -->
|
||||
<details class="settings-panel" id="ecobeeSettingsPanel" style="margin-top:1rem">
|
||||
<summary>Thermostat (HomeKit Push) Setup</summary>
|
||||
<div class="settings-body">
|
||||
<p id="ecobeeConfigStatus" class="keys-status"></p>
|
||||
|
||||
<div class="settings-section-hdr">iOS Shortcut Push URL</div>
|
||||
<p class="keys-hint" style="margin:0 0 0.5rem">Configure your iOS Shortcut to POST thermostat data to this URL:</p>
|
||||
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.75rem;max-width:560px">
|
||||
<input id="homekitPushUrl" type="text" readonly style="flex:1;font-family:monospace;font-size:0.8rem">
|
||||
<button id="homekitCopyUrlBtn">Copy</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-section-hdr">Auth Token</div>
|
||||
<p class="keys-hint" style="margin:0 0 0.5rem">Include this as the <code style="background:var(--paper-darker);padding:0.1rem 0.3rem;border-radius:4px">x-homekit-token</code> header in your Shortcut's URL request:</p>
|
||||
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.25rem;max-width:560px">
|
||||
<input id="homekitTokenDisplay" type="text" readonly style="flex:1;font-family:monospace;font-size:0.8rem">
|
||||
<button id="homekitCopyTokenBtn">Copy</button>
|
||||
<button id="homekitRegenBtn">Regenerate</button>
|
||||
</div>
|
||||
<span id="homekitRegenMsg" class="save-msg"></span>
|
||||
|
||||
<hr class="settings-divider">
|
||||
|
||||
<div class="settings-section-hdr">iOS Shortcut Setup</div>
|
||||
<ol class="keys-hint" style="margin:0;padding-left:1.4rem;line-height:2">
|
||||
<li>Open <strong>Shortcuts</strong> → tap <strong>+</strong> to create a new shortcut</li>
|
||||
<li>Add <strong>Get my HomeKit value for</strong> → select your thermostat → <strong>Current Temperature</strong> → save as variable <em>temp</em></li>
|
||||
<li>Repeat for <strong>Target Temperature</strong> (save as <em>targetTemp</em>) and <strong>Current Relative Humidity</strong> (save as <em>humidity</em>)</li>
|
||||
<li>Add <strong>Get contents of URL</strong> → paste the Push URL above → Method: <strong>POST</strong></li>
|
||||
<li>Add header: <code style="background:var(--paper-darker);padding:0.1rem 0.3rem;border-radius:4px">x-homekit-token</code> = (paste your token)</li>
|
||||
<li>Request Body: <strong>JSON</strong> → add keys: <code style="background:var(--paper-darker);padding:0.1rem 0.3rem;border-radius:4px">temperature</code>=<em>temp</em>, <code style="background:var(--paper-darker);padding:0.1rem 0.3rem;border-radius:4px">heatSetF</code>=<em>targetTemp</em>, <code style="background:var(--paper-darker);padding:0.1rem 0.3rem;border-radius:4px">humidity</code>=<em>humidity</em></li>
|
||||
<li>Go to <strong>Automation</strong> tab → <strong>+</strong> → <strong>Time of Day</strong> → set repeat every 5 min → run this shortcut (no notification)</li>
|
||||
</ol>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- Settings -->
|
||||
<details class="settings-panel" id="settingsPanel">
|
||||
<summary>Station API Keys</summary>
|
||||
@@ -810,15 +733,6 @@ function renderCharts(readings, range, yoy = null) {
|
||||
<div class="chart-wrap"><canvas id="cSolar"></canvas></div>
|
||||
</div>
|
||||
|
||||
${pts.some(r => r.tempinf != null) ? `
|
||||
<div class="chart-card">
|
||||
<div class="chart-title">Indoor Temperature & Dew Point (°F)</div>
|
||||
<div class="chart-wrap"><canvas id="cTempIn"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<div class="chart-title">Indoor Humidity (%)</div>
|
||||
<div class="chart-wrap"><canvas id="cHumIn"></canvas></div>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -866,23 +780,6 @@ function renderCharts(readings, range, yoy = null) {
|
||||
[lineDataset('Solar W/m²', field(pts, 'solarradiation'), c.accent)],
|
||||
lbls, 'W/m²', { scales: { y: { min: 0 } } });
|
||||
|
||||
if (pts.some(r => r.tempinf != null)) {
|
||||
const indoorDp = pts.map(r => {
|
||||
if (r.tempinf == null || r.humidityin == null) return null;
|
||||
const Tc = (r.tempinf - 32) * 5 / 9, RH = r.humidityin;
|
||||
const dp = (243.04 * (Math.log(RH / 100) + 17.625 * Tc / (243.04 + Tc))) /
|
||||
(17.625 - Math.log(RH / 100) - 17.625 * Tc / (243.04 + Tc));
|
||||
return +(dp * 9 / 5 + 32).toFixed(1);
|
||||
});
|
||||
makeChart('cTempIn', 'line',
|
||||
[lineDataset('Temp In °F', field(pts, 'tempinf'), c.accent),
|
||||
lineDataset('Dew Point In', indoorDp, c.cool, true)],
|
||||
lbls, '°F');
|
||||
makeChart('cHumIn', 'line',
|
||||
[lineDataset('Humidity In %', field(pts, 'humidityin'), c.cool)],
|
||||
lbls, '%', { scales: { y: { min: 0, max: 100 } } });
|
||||
}
|
||||
|
||||
// Today bar + sparklines use full readings (not downsampled pts)
|
||||
updateTodayBar(readings);
|
||||
updateSparklines(readings);
|
||||
@@ -929,24 +826,6 @@ async function updateLatest() {
|
||||
// Max daily gust
|
||||
if (d.maxdailygust != null) set('sMaxGust', d.maxdailygust);
|
||||
|
||||
// Indoor sensors
|
||||
if (d.tempinf != null) {
|
||||
set('sTempIn', d.tempinf);
|
||||
document.getElementById('indoorTempTile').style.display = '';
|
||||
}
|
||||
if (d.feelsLikein != null) {
|
||||
set('sFeelsIn', d.feelsLikein, 0);
|
||||
document.getElementById('indoorFeelsTile').style.display = '';
|
||||
}
|
||||
if (d.humidityin != null) {
|
||||
set('sHumIn', d.humidityin, 0);
|
||||
document.getElementById('indoorHumTile').style.display = '';
|
||||
}
|
||||
if (d.dewPointin != null) {
|
||||
set('sDewIn', d.dewPointin);
|
||||
document.getElementById('indoorDewTile').style.display = '';
|
||||
}
|
||||
|
||||
// Ambient rain accumulators
|
||||
if (d.eventrainin != null && d.eventrainin > 0) {
|
||||
set('sEventRain', d.eventrainin, 2);
|
||||
@@ -1618,94 +1497,6 @@ document.getElementById('saveLocBtn')?.addEventListener('click', async () => {
|
||||
} catch { msg.textContent = 'Error'; msg.className = 'save-msg err'; }
|
||||
});
|
||||
|
||||
// ── HomeKit thermostat push ───────────────────────────────────────────────────
|
||||
function hvacBadge(hvacState) {
|
||||
if (!hvacState) return { text: 'Idle', cls: 'hvac-idle' };
|
||||
const s = hvacState.toLowerCase();
|
||||
if (s.includes('cool')) return { text: 'Cooling', cls: 'hvac-cooling' };
|
||||
if (s.includes('heat')) return { text: 'Heating', cls: 'hvac-heating' };
|
||||
if (s.includes('fan')) return { text: 'Fan', cls: 'hvac-fan' };
|
||||
return { text: 'Idle', cls: 'hvac-idle' };
|
||||
}
|
||||
|
||||
async function loadHomekitData() {
|
||||
const section = document.getElementById('ecobeeSection');
|
||||
try {
|
||||
const r = await fetch('/api/homekit/data');
|
||||
if (!r.ok) return;
|
||||
const d = await r.json();
|
||||
if (d.tempF == null) return;
|
||||
|
||||
section.style.display = '';
|
||||
const set = (id, val) => { const el = document.getElementById(id); if (el && val != null) el.textContent = val; };
|
||||
set('ecobeeTempF', d.tempF != null ? d.tempF.toFixed(1) : '—');
|
||||
set('ecobeeHeatSet', d.heatSetF != null ? d.heatSetF.toFixed(0) : '—');
|
||||
set('ecobeeCoolSet', d.coolSetF != null ? d.coolSetF.toFixed(0) : '—');
|
||||
set('ecobeeHum', d.humidity ?? '—');
|
||||
set('ecobeeMode', d.mode ?? '—');
|
||||
|
||||
const badge = document.getElementById('ecobeeBadge');
|
||||
if (badge) {
|
||||
const { text, cls } = hvacBadge(d.hvacState);
|
||||
badge.textContent = text;
|
||||
badge.className = `hvac-badge ${cls}`;
|
||||
}
|
||||
|
||||
const upd = document.getElementById('ecobeeUpdated');
|
||||
if (upd && d.receivedAt) {
|
||||
upd.textContent = 'Updated ' + new Date(d.receivedAt).toLocaleTimeString();
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function loadHomekitConfigStatus() {
|
||||
try {
|
||||
const r = await fetch('/api/homekit/config');
|
||||
if (!r.ok) return;
|
||||
const d = await r.json();
|
||||
const el = document.getElementById('ecobeeConfigStatus');
|
||||
if (d.token) {
|
||||
el.textContent = '✓ Push endpoint active — configure your iOS Shortcut below';
|
||||
el.className = 'keys-status ok';
|
||||
} else {
|
||||
el.textContent = '⚠ Token not configured';
|
||||
el.className = 'keys-status missing';
|
||||
}
|
||||
const urlInput = document.getElementById('homekitPushUrl');
|
||||
if (urlInput) urlInput.value = window.location.origin + '/api/homekit/push';
|
||||
const tokenInput = document.getElementById('homekitTokenDisplay');
|
||||
if (tokenInput) tokenInput.value = d.token || '';
|
||||
} catch {}
|
||||
}
|
||||
|
||||
document.getElementById('homekitCopyUrlBtn')?.addEventListener('click', () => {
|
||||
const val = document.getElementById('homekitPushUrl')?.value;
|
||||
if (val) navigator.clipboard.writeText(val).catch(() => {});
|
||||
});
|
||||
|
||||
document.getElementById('homekitCopyTokenBtn')?.addEventListener('click', () => {
|
||||
const val = document.getElementById('homekitTokenDisplay')?.value;
|
||||
if (val) navigator.clipboard.writeText(val).catch(() => {});
|
||||
});
|
||||
|
||||
document.getElementById('homekitRegenBtn')?.addEventListener('click', async () => {
|
||||
const msg = document.getElementById('homekitRegenMsg');
|
||||
try {
|
||||
const r = await fetch('/api/homekit/config/regenerate', { method: 'POST' });
|
||||
const d = await r.json();
|
||||
if (r.ok && d.token) {
|
||||
const tokenInput = document.getElementById('homekitTokenDisplay');
|
||||
if (tokenInput) tokenInput.value = d.token;
|
||||
msg.textContent = 'Token regenerated — update your Shortcut'; msg.className = 'save-msg';
|
||||
} else { msg.textContent = 'Error'; msg.className = 'save-msg err'; }
|
||||
setTimeout(() => msg.textContent = '', 4000);
|
||||
} catch { msg.textContent = 'Error'; msg.className = 'save-msg err'; }
|
||||
});
|
||||
|
||||
document.getElementById('ecobeeSettingsPanel')?.addEventListener('toggle', function() {
|
||||
if (this.open) loadHomekitConfigStatus();
|
||||
});
|
||||
|
||||
// ── Init all new features ─────────────────────────────────────────────────────
|
||||
updateLatest();
|
||||
updatePressureTrend();
|
||||
@@ -1719,13 +1510,10 @@ loadThresholds();
|
||||
loadAlerts();
|
||||
loadStreak();
|
||||
loadLocation();
|
||||
loadHomekitData();
|
||||
|
||||
setInterval(updatePressureTrend, 120000);
|
||||
setInterval(updateExtStats, 300000);
|
||||
setInterval(updateTrends, 120000);
|
||||
setInterval(loadAlerts, 5 * 60 * 1000);
|
||||
setInterval(loadHomekitData, 3 * 60 * 1000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -289,11 +289,6 @@
|
||||
<div class="stat-label">Solar Rad.</div>
|
||||
<div class="stat-val"><span id="st-solar">--</span> <span class="stat-unit">W/m²</span></div>
|
||||
</div>
|
||||
<div class="stat-card" id="st-indoor-card" class="hidden">
|
||||
<div class="stat-label">Indoor Temp</div>
|
||||
<div class="stat-val"><span id="st-tempin">--</span>°</div>
|
||||
<div class="stat-sub" id="st-humin"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="station-error" class="station-error hidden"></div>
|
||||
|
||||
@@ -600,79 +600,6 @@ app.get('/api/ambient', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ── HomeKit thermostat push ───────────────────────────────────────────────────
|
||||
const HOMEKIT_CONFIG_FILE = path.join(DATA_DIR, 'homekit-config.json');
|
||||
const HOMEKIT_LATEST_FILE = path.join(DATA_DIR, 'homekit-latest.json');
|
||||
|
||||
let homekitConfig = { token: '' };
|
||||
let homekitLatest = null;
|
||||
|
||||
try {
|
||||
if (fs.existsSync(HOMEKIT_CONFIG_FILE)) {
|
||||
homekitConfig = JSON.parse(fs.readFileSync(HOMEKIT_CONFIG_FILE, 'utf8'));
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (!homekitConfig.token) {
|
||||
homekitConfig.token = crypto.randomBytes(32).toString('hex');
|
||||
try { fs.writeFileSync(HOMEKIT_CONFIG_FILE, JSON.stringify(homekitConfig)); } catch {}
|
||||
}
|
||||
|
||||
try {
|
||||
if (fs.existsSync(HOMEKIT_LATEST_FILE)) {
|
||||
homekitLatest = JSON.parse(fs.readFileSync(HOMEKIT_LATEST_FILE, 'utf8'));
|
||||
}
|
||||
} catch {}
|
||||
|
||||
// Get push token — dashboard auth
|
||||
app.get('/api/homekit/config', requireDashboardAuth, (_req, res) => {
|
||||
res.json({ token: homekitConfig.token });
|
||||
});
|
||||
|
||||
// Regenerate push token — dashboard auth
|
||||
app.post('/api/homekit/config/regenerate', requireDashboardAuth, (_req, res) => {
|
||||
homekitConfig.token = crypto.randomBytes(32).toString('hex');
|
||||
try { fs.writeFileSync(HOMEKIT_CONFIG_FILE, JSON.stringify(homekitConfig)); } catch {}
|
||||
res.json({ token: homekitConfig.token });
|
||||
});
|
||||
|
||||
// Receive thermostat data from iOS Shortcut — token in query string or header
|
||||
// Accepts both GET (query params) and POST (JSON body or query params)
|
||||
function handleHomekitPush(req, res) {
|
||||
const token = req.query.token || req.headers['x-homekit-token'] || (req.body || {}).token;
|
||||
if (!token || token !== homekitConfig.token) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
const p = { ...(req.body || {}), ...req.query };
|
||||
|
||||
function parseTemp(val) {
|
||||
const n = parseFloat(val);
|
||||
if (isNaN(n)) return null;
|
||||
return n < 50 ? +(n * 9 / 5 + 32).toFixed(1) : +n.toFixed(1);
|
||||
}
|
||||
|
||||
homekitLatest = {
|
||||
tempF: parseTemp(p.temperature ?? p.tempF),
|
||||
heatSetF: parseTemp(p.heatSetF),
|
||||
coolSetF: parseTemp(p.coolSetF),
|
||||
humidity: p.humidity != null ? +parseFloat(p.humidity).toFixed(0) : null,
|
||||
mode: p.mode || null,
|
||||
hvacState: p.hvacState || null,
|
||||
receivedAt: Date.now(),
|
||||
};
|
||||
try { fs.writeFileSync(HOMEKIT_LATEST_FILE, JSON.stringify(homekitLatest)); } catch {}
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
app.get('/api/homekit/push', handleHomekitPush);
|
||||
app.post('/api/homekit/push', handleHomekitPush);
|
||||
|
||||
// Latest thermostat reading — dashboard auth
|
||||
app.get('/api/homekit/data', requireDashboardAuth, (_req, res) => {
|
||||
if (!homekitLatest) return res.status(503).json({ error: 'No data yet' });
|
||||
res.json(homekitLatest);
|
||||
});
|
||||
|
||||
// ── Personal weather station proxy ────────────────────────────────────────────
|
||||
app.get('/api/pws', async (req, res) => {
|
||||
const { url } = req.query;
|
||||
|
||||
Reference in new issue
Block a user