From 5eae87277394101b7db058fab79fe0f85f81fdee Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Sun, 23 Aug 2026 12:40:52 -0400 Subject: [PATCH] Accept GET query params on HomeKit push endpoint (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Ecobee thermostat integration to dashboard - Server: PIN-based OAuth flow to authorize Ecobee access without exposing redirect URIs; tokens stored server-side and auto-refreshed - Server: /api/ecobee/config, /api/ecobee/auth/pin, /api/ecobee/auth/complete, /api/ecobee/data, /api/ecobee/runtime endpoints behind dashboard auth - Dashboard: Ecobee section card showing indoor temp, humidity, heat/cool setpoints, HVAC mode, equipment status badge, and per-sensor temperature readings from remote sensors - Dashboard: stacked bar chart of daily heating/cooling runtime hours for the past 7 days - Dashboard: Ecobee setup panel with API key entry and step-by-step PIN authorization flow Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01YMhuFaecxraQzUPN4h9nsB * Replace ecobee API integration with HomeKit push endpoint Ecobee removed public developer API access, so pivot to HomeKit: - Add POST /api/homekit/push accepting thermostat data from iOS Shortcuts - Add GET /api/homekit/data returning latest reading to dashboard - Add GET /api/homekit/config and POST /api/homekit/config/regenerate for token management - Auto-generate auth token on first run, persist to data/homekit-config.json - Auto-convert Celsius→Fahrenheit if temp < 50 (HomeKit always returns Celsius) - Update dashboard section card to show temp, setpoints, humidity, mode, HVAC state - Replace ecobee settings panel with push URL, token copy, and Shortcut setup instructions - Remove runtime chart (not available without API access) * Support GET query params on homekit push endpoint for iOS Shortcuts compatibility --------- Co-authored-by: Claude --- app/server.js | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/app/server.js b/app/server.js index 9bc2390..f54f02b 100644 --- a/app/server.js +++ b/app/server.js @@ -636,35 +636,36 @@ app.post('/api/homekit/config/regenerate', requireDashboardAuth, (_req, res) => res.json({ token: homekitConfig.token }); }); -// Receive thermostat data from iOS Shortcut — token header auth -app.post('/api/homekit/push', (req, res) => { - const token = req.headers['x-homekit-token'] || req.query.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 body = req.body || {}; - // HomeKit reports temperatures in Celsius; auto-detect and convert - let tempF = parseFloat(body.tempF ?? body.temperature); - if (!isNaN(tempF) && tempF < 50) tempF = +(tempF * 9 / 5 + 32).toFixed(1); + const p = { ...(req.body || {}), ...req.query }; - let heatSetF = body.heatSetF != null ? parseFloat(body.heatSetF) : null; - if (heatSetF != null && heatSetF < 50) heatSetF = +(heatSetF * 9 / 5 + 32).toFixed(1); - - let coolSetF = body.coolSetF != null ? parseFloat(body.coolSetF) : null; - if (coolSetF != null && coolSetF < 50) coolSetF = +(coolSetF * 9 / 5 + 32).toFixed(1); + 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: isNaN(tempF) ? null : tempF, - heatSetF: heatSetF != null && !isNaN(heatSetF) ? +parseFloat(heatSetF).toFixed(1) : null, - coolSetF: coolSetF != null && !isNaN(coolSetF) ? +parseFloat(coolSetF).toFixed(1) : null, - humidity: body.humidity != null ? +parseFloat(body.humidity).toFixed(0) : null, - mode: body.mode || null, - hvacState: body.hvacState || null, + 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) => {