diff --git a/rules/waterButtonLearnAndCall.js b/rules/waterButtonLearnAndCall.js index 2e53017..616ff28 100644 --- a/rules/waterButtonLearnAndCall.js +++ b/rules/waterButtonLearnAndCall.js @@ -19,6 +19,7 @@ import fs from 'fs'; import path from 'path'; import http from 'http'; import { fileURLToPath } from 'url'; +import { startStatusServer } from './waterTimerWebservice.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -566,707 +567,11 @@ export default { // ── HTTP Status Page ────────────────────────────────────────────────────────── -const STATUS_PORT = 8082; - -function getStatusJSON() { - const state = getState(WATER_BUTTON_MAC); - return { - valveOpen: state.timer !== null || (!state.countMode && state.timer === null), - timerActive: state.timer !== null, - countMode: state.countMode, - storedDuration: state.storedDuration, - timerStartedAt: state.timerStartedAt, - timerEndsAt: state.timerEndsAt, - now: Date.now() - }; -} - -const statusPageHTML = ` - - - - -Water Timer - - - -
-

💧 Water Timer

- -
-
- - - - - - - - - - -
-
-
IDLE
-
-
- -
- - Loading… -
- -
-
-
- - sec -
- -
- - -
-
- - - -`; - -function startStatusServer() { - // Prevent duplicate listeners across hot reloads - if (global.__waterStatusServer) { - try { global.__waterStatusServer.close(); } catch (e) { } - } - - const srv = http.createServer((req, res) => { - const url = new URL(req.url, `http://${req.headers.host}`); - - const jsonHeaders = { - 'Content-Type': 'application/json', - 'Cache-Control': 'no-cache', - 'Access-Control-Allow-Origin': '*' - }; - - if (url.pathname === '/api/status') { - res.writeHead(200, jsonHeaders); - res.end(JSON.stringify(getStatusJSON())); - return; - } - - // ── POST command endpoints ── - if (req.method === 'POST' && url.pathname.startsWith('/api/')) { - let body = ''; - req.on('data', c => body += c); - req.on('end', async () => { - const sendRPC = global.__waterBotRPC; - const state = getState(WATER_BUTTON_MAC); - - if (!sendRPC) { - res.writeHead(503, jsonHeaders); - res.end(JSON.stringify({ ok: false, error: 'Not ready — no device connection' })); - return; - } - - try { - if (url.pathname === '/api/run') { - if (state.timer) { clearTimeout(state.timer); state.timer = null; } - state.countMode = false; - await setLight(sendRPC, WATER_BUTTON_MAC, false, 0); - const duration = state.storedDuration; - state.timerStartedAt = Date.now(); - state.timerEndsAt = Date.now() + duration; - state.timer = setTimeout(async () => { - try { - const rpc = global.__waterBotRPC; - if (rpc) await setLight(rpc, WATER_BUTTON_MAC, true, 95); - state.timer = null; - state.timerStartedAt = null; - state.timerEndsAt = null; - } catch (err) { - console.error('[WaterStatus] Timer callback error:', err); - } - }, duration); - res.writeHead(200, jsonHeaders); - res.end(JSON.stringify({ ok: true, action: 'run', duration })); - - } else if (url.pathname === '/api/open') { - if (state.timer) { clearTimeout(state.timer); state.timer = null; } - state.countMode = false; - state.timerStartedAt = null; - state.timerEndsAt = null; - await setLight(sendRPC, WATER_BUTTON_MAC, false, 0); - res.writeHead(200, jsonHeaders); - res.end(JSON.stringify({ ok: true, action: 'open' })); - - } else if (url.pathname === '/api/close') { - if (state.timer) { clearTimeout(state.timer); state.timer = null; } - state.countMode = false; - state.timerStartedAt = null; - state.timerEndsAt = null; - await setLight(sendRPC, WATER_BUTTON_MAC, true, 95); - res.writeHead(200, jsonHeaders); - res.end(JSON.stringify({ ok: true, action: 'close' })); - - } else if (url.pathname === '/api/duration') { - const data = JSON.parse(body || '{}'); - const ms = Math.round((data.seconds || 0) * 1000); - if (ms <= 0 || ms > 300000) { - res.writeHead(400, jsonHeaders); - res.end(JSON.stringify({ ok: false, error: 'Duration must be 1-300s' })); - return; - } - state.storedDuration = ms; - persistedState[WATER_BUTTON_MAC] = { storedDuration: ms }; - saveState(persistedState); - res.writeHead(200, jsonHeaders); - res.end(JSON.stringify({ ok: true, action: 'duration', ms })); - - } else { - res.writeHead(404, jsonHeaders); - res.end(JSON.stringify({ ok: false, error: 'Unknown endpoint' })); - } - } catch (err) { - console.error('[WaterStatus] API error:', err); - res.writeHead(500, jsonHeaders); - res.end(JSON.stringify({ ok: false, error: err.message })); - } - }); - return; - } - - // Serve HTML status page - res.writeHead(200, { - 'Content-Type': 'text/html', - 'Cache-Control': 'no-cache' - }); - res.end(statusPageHTML); - }); - - srv.listen(STATUS_PORT, () => { - console.log(`[WaterStatus] Status page at http://localhost:${STATUS_PORT}`); - }); - - global.__waterStatusServer = srv; -} - -startStatusServer(); +startStatusServer({ + getState, + WATER_BUTTON_MAC, + getSendRPC: () => global.__waterBotRPC, + setLight, + persistedState, + saveState +});