/** * Timer light rule * * - btn_down: Light goes off * - long_push: Flash to confirm, enter "count mode" - counts seconds until next btn_down, then light goes on * - Normal btn_down (no long_push): Light goes off, then turns back on after the stored count elapsed * * Also syncs remote switch CC8DA243B0A0 inversely (light off = switch on, light on = switch off) */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); const STATE_FILE = path.join(__dirname, 'timer_state.json'); const WATER_BUTTON_MAC = '08A6F773510C'; const REMOTE_SWITCH_MAC = 'CC8DA243B0A0'; // Helper to set local light and sync remote switch inversely async function setLight(ctx, mac, on, brightness) { await ctx.sendRPC(mac, 'Light.Set', { id: 0, on, brightness }); // Remote switch is inverse: light off = switch on, light on = switch off await ctx.sendRPC(REMOTE_SWITCH_MAC, 'Switch.Set', { id: 0, on: !on }); } // Load persisted state function loadPersistedState() { try { if (fs.existsSync(STATE_FILE)) { return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); } } catch (err) { console.error('Error loading state:', err); } return {}; } // Save state to file function saveState(state) { try { // Only save storedDuration per MAC const toSave = {}; for (const [mac, data] of Object.entries(state)) { toSave[mac] = { storedDuration: data.storedDuration }; } fs.writeFileSync(STATE_FILE, JSON.stringify(toSave, null, 2)); } catch (err) { console.error('Error saving state:', err); } } // State for devices (in memory, with persistence for storedDuration) const persistedState = loadPersistedState(); const deviceState = new Map(); function getState(mac) { if (!deviceState.has(mac)) { const persisted = persistedState[mac] || {}; deviceState.set(mac, { countMode: false, countStart: 0, storedDuration: persisted.storedDuration || 5000, // Default 5 seconds timer: null }); } return deviceState.get(mac); } export default { async run(ctx) { // Auto-on for water button when it connects (only if remote switch is online) if (ctx.trigger.mac === WATER_BUTTON_MAC && ctx.trigger.field === 'online' && ctx.trigger.event === true) { const remoteSwitchConnected = await ctx.getState(REMOTE_SWITCH_MAC, 'system', 'online'); if (remoteSwitchConnected === true) { ctx.log('Water button connected - remote switch online, turning light on'); await setLight(ctx, ctx.trigger.mac, true, 20); // Double flash to indicate both devices are connected ctx.log('Double flashing to confirm connection'); for (let i = 0; i < 2; i++) { await ctx.sendRPC(WATER_BUTTON_MAC, 'Light.Set', { id: 0, on: true, brightness: 20 }); await sleep(200); await ctx.sendRPC(WATER_BUTTON_MAC, 'Light.Set', { id: 0, on: false, brightness: 0 }); await sleep(200); } } else { ctx.log('Water button connected - remote switch offline, keeping light off'); await ctx.sendRPC(WATER_BUTTON_MAC, 'Light.Set', { id: 0, on: false, brightness: 0 }); } return; } // Auto-off for remote switch when it connects if (ctx.trigger.mac === REMOTE_SWITCH_MAC && ctx.trigger.field === 'online' && ctx.trigger.event === true) { ctx.log('Remote switch connected - turning switch off, flashing light, then turning light on'); await ctx.sendRPC(REMOTE_SWITCH_MAC, 'Switch.Set', { id: 0, on: false }); // Double flash the light for (let i = 0; i < 2; i++) { await ctx.sendRPC(WATER_BUTTON_MAC, 'Light.Set', { id: 0, on: true, brightness: 20 }); await sleep(200); await ctx.sendRPC(WATER_BUTTON_MAC, 'Light.Set', { id: 0, on: false, brightness: 0 }); await sleep(200); } // Turn light on after flash await setLight(ctx, WATER_BUTTON_MAC, true, 20); return; } // Turn off light when remote switch goes offline if (ctx.trigger.mac === REMOTE_SWITCH_MAC && ctx.trigger.field === 'online' && ctx.trigger.event === false) { ctx.log('Remote switch went offline - turning light off'); await ctx.sendRPC(WATER_BUTTON_MAC, 'Light.Set', { id: 0, on: false, brightness: 0 }); // Clear any pending timer const state = getState(WATER_BUTTON_MAC); if (state.timer) { clearTimeout(state.timer); state.timer = null; } state.countMode = false; return; } if (ctx.trigger.field !== 'button') return; // Ignore button events if remote switch is offline const remoteSwitchOnline = await ctx.getState(REMOTE_SWITCH_MAC, 'system', 'online'); if (remoteSwitchOnline !== true) { ctx.log('Button ignored - remote switch is offline'); return; } const mac = ctx.trigger.mac; const state = getState(mac); ctx.log(`Event: ${ctx.trigger.event}, countMode: ${state.countMode}, storedDuration: ${state.storedDuration}ms`); // Handle btn_down if (ctx.trigger.event === 'btn_down') { // Clear any pending timer if (state.timer) { clearTimeout(state.timer); state.timer = null; } // Turn light off (remote switch turns on) await setLight(ctx, mac, false, 0); if (state.countMode) { // We're in count mode - calculate elapsed time and turn light on const elapsed = Date.now() - state.countStart; state.storedDuration = elapsed; state.countMode = false; ctx.log(`Count mode ended. Stored duration: ${elapsed}ms`); // Persist the new duration persistedState[mac] = { storedDuration: elapsed }; saveState(persistedState); // Turn light on immediately (remote switch turns off) await setLight(ctx, mac, true, 20); } else { // Normal mode - schedule light to turn on after stored duration ctx.log(`Light off. Will turn on in ${state.storedDuration}ms`); state.timer = setTimeout(async () => { ctx.log(`Timer elapsed. Turning light on.`); await setLight(ctx, mac, true, 20); state.timer = null; }, state.storedDuration); } } // Handle long_push - enter count mode if (ctx.trigger.event === 'long_push' && !state.countMode) { // Clear any pending timer if (state.timer) { clearTimeout(state.timer); state.timer = null; } ctx.log('Entering count mode...'); // Flash to confirm (don't sync remote during flash) for (let i = 0; i < 2; i++) { await ctx.sendRPC(mac, 'Light.Set', { id: 0, on: true, brightness: 20 }); await sleep(200); await ctx.sendRPC(mac, 'Light.Set', { id: 0, on: false, brightness: 0 }); await sleep(200); } // Ensure light stays off during count mode (remote switch on) await setLight(ctx, mac, false, 0); // Enter count mode state.countMode = true; state.countStart = Date.now(); ctx.log('Count mode active. Light stays off. Press button to set duration and turn on.'); } } };