diff --git a/app/public/app.js b/app/public/app.js index 67bedf5..0558ea5 100644 --- a/app/public/app.js +++ b/app/public/app.js @@ -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' : ''}`; } diff --git a/app/public/dashboard.html b/app/public/dashboard.html index db41467..ca436b0 100644 --- a/app/public/dashboard.html +++ b/app/public/dashboard.html @@ -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
💨— mph peak gust
- +
Wind
@@ -307,10 +294,6 @@ canvas.sparkline { display: block; width: 100%; height: 22px; margin-top: 0.4rem
Max Gust Today
—
mph
Rain Today
—
in
Rain Rate
—
in/hr
- - - -
@@ -334,29 +317,6 @@ canvas.sparkline { display: block; width: 100%; height: 22px; margin-top: 0.4rem
- - -
History: @@ -403,43 +363,6 @@ canvas.sparkline { display: block; width: 100%; height: 22px; margin-top: 0.4rem
- -
- Thermostat (HomeKit Push) Setup -
-

- -
iOS Shortcut Push URL
-

Configure your iOS Shortcut to POST thermostat data to this URL:

-
- - -
- -
Auth Token
-

Include this as the x-homekit-token header in your Shortcut's URL request:

-
- - - -
- - -
- -
iOS Shortcut Setup
-
    -
  1. Open Shortcuts → tap + to create a new shortcut
  2. -
  3. Add Get my HomeKit value for → select your thermostat → Current Temperature → save as variable temp
  4. -
  5. Repeat for Target Temperature (save as targetTemp) and Current Relative Humidity (save as humidity)
  6. -
  7. Add Get contents of URL → paste the Push URL above → Method: POST
  8. -
  9. Add header: x-homekit-token = (paste your token)
  10. -
  11. Request Body: JSON → add keys: temperature=temp, heatSetF=targetTemp, humidity=humidity
  12. -
  13. Go to Automation tab → + → Time of Day → set repeat every 5 min → run this shortcut (no notification)
  14. -
-
-
-
Station API Keys @@ -810,15 +733,6 @@ function renderCharts(readings, range, yoy = null) {
- ${pts.some(r => r.tempinf != null) ? ` -
-
Indoor Temperature & Dew Point (°F)
-
-
-
-
Indoor Humidity (%)
-
-
` : ''} `; @@ -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); diff --git a/app/public/index.html b/app/public/index.html index 51edf15..7a7f9bf 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -289,11 +289,6 @@
Solar Rad.
-- W/m²
-
-
Indoor Temp
-
--°
-
-
diff --git a/app/server.js b/app/server.js index f54f02b..f2998b5 100644 --- a/app/server.js +++ b/app/server.js @@ -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;