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
-
-
-
-
-
-
-
-
-
- Loading…
-
-
-
-
-
-
-
-`;
-
-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
+});