// Home security script for SmartI8: 8 DI zones, 8 DO alarm relays, 8 AI telemetry macro GET_BYPASS(idx, &val) { val = 0; if (idx == 1) { val = bypass1; } else { if (idx == 2) { val = bypass2; } else { if (idx == 3) { val = bypass3; } else { if (idx == 4) { val = bypass4; } else { if (idx == 5) { val = bypass5; } else { if (idx == 6) { val = bypass6; } else { if (idx == 7) { val = bypass7; } else { val = bypass8; } } } } } } } } macro SET_BYPASS(idx, val) { if (idx == 1) { bypass1 = val; save(bypass1); } // NVS writes are limited; avoid frequent SAVE calls. else { if (idx == 2) { bypass2 = val; save(bypass2); } // NVS writes are limited; avoid frequent SAVE calls. else { if (idx == 3) { bypass3 = val; save(bypass3); } // NVS writes are limited; avoid frequent SAVE calls. else { if (idx == 4) { bypass4 = val; save(bypass4); } // NVS writes are limited; avoid frequent SAVE calls. else { if (idx == 5) { bypass5 = val; save(bypass5); } // NVS writes are limited; avoid frequent SAVE calls. else { if (idx == 6) { bypass6 = val; save(bypass6); } // NVS writes are limited; avoid frequent SAVE calls. else { if (idx == 7) { bypass7 = val; save(bypass7); } // NVS writes are limited; avoid frequent SAVE calls. else { bypass8 = val; save(bypass8); } // NVS writes are limited; avoid frequent SAVE calls. } } } } } } } macro CLEAR_BYPASS_ALL() { bypass1 = 0; bypass2 = 0; bypass3 = 0; bypass4 = 0; bypass5 = 0; bypass6 = 0; bypass7 = 0; bypass8 = 0; save(bypass1); // NVS writes are limited; avoid frequent SAVE calls. save(bypass2); // NVS writes are limited; avoid frequent SAVE calls. save(bypass3); // NVS writes are limited; avoid frequent SAVE calls. save(bypass4); // NVS writes are limited; avoid frequent SAVE calls. save(bypass5); // NVS writes are limited; avoid frequent SAVE calls. save(bypass6); // NVS writes are limited; avoid frequent SAVE calls. save(bypass7); // NVS writes are limited; avoid frequent SAVE calls. save(bypass8); // NVS writes are limited; avoid frequent SAVE calls. } macro APPLY_ALARM_OUTPUTS(active) { if (active) { out(ALARM_OUT1) = 1; out(ALARM_OUT2) = 1; } else { out(ALARM_OUT1) = 0; out(ALARM_OUT2) = 0; } } load(armed, 1); // persisted arm state (1=armed, 0=disarmed) load(bypass1, 0); // per-zone bypass flags (persisted individually) load(bypass2, 0); load(bypass3, 0); load(bypass4, 0); load(bypass5, 0); load(bypass6, 0); load(bypass7, 0); load(bypass8, 0); ALARM_OUT1 = 1; ALARM_OUT2 = 2; ZONES = 8; POLL_MS = 200; STATUS_MS = 5000; HEARTBEAT_MS = 60000; alarm = 0; last_zone = 0; alarm_since = 0; reset_req = 0; every(POLL_MS) { any = 0; zone_hit = 0; for (i = 1; i <= ZONES; i = i + 1) { GET_BYPASS(i, &bypass); if (in(i) && armed && bypass == 0) { any = 1; if (zone_hit == 0) { zone_hit = i; } } } if (any) { if (alarm == 0) { alarm = 1; last_zone = zone_hit; alarm_since = nowutc(); publish("alarm", "zone", last_zone, "armed", armed, "ts", alarm_since); logn("ALARM zone ", last_zone); } } if (reset_req) { alarm = 0; reset_req = 0; publish("reset_ack", "armed", armed, "ts", nowutc()); } APPLY_ALARM_OUTPUTS(alarm); } every(STATUS_MS) { publish("status", "armed", armed, "alarm", alarm, "last_zone", last_zone, "utc", nowutc(), "hour", hour(), "min", min(), "dow", dow(), "bypass1", bypass1, "bypass2", bypass2, "bypass3", bypass3, "bypass4", bypass4, "bypass5", bypass5, "bypass6", bypass6, "bypass7", bypass7, "bypass8", bypass8); } every(HEARTBEAT_MS) { // publish analog snapshot for situational awareness publish("ai", ain(1), ain(2), ain(3), ain(4), ain(5), ain(6), ain(7), ain(8)); publish("heartbeat", "ts", nowutc()); } onmqtt("arm_script") { if (msg_is_on) { armed = 1; save(armed); } // NVS writes are limited; avoid frequent SAVE calls. if (msg_is_off) { armed = 0; save(armed); } // NVS writes are limited; avoid frequent SAVE calls. if (msg_is_number) { switch (msg_value) { case 0: { armed = 0; break; } default: { armed = 1; break; } } save(armed); // NVS writes are limited; avoid frequent SAVE calls. } if (alarm == 0) { out(ALARM_OUT1) = armed; out(ALARM_OUT2) = armed; } publish("armed_state", "armed", armed); } onmqtt("reset_script") { if (msg_is_on || msg_is_number) { reset_req = 1; } } onmqtt("bypass_script") { // msg_value >0 sets bypass for that zone, <0 clears, 0 clears all if (msg_is_number) { z = msg_value; if (z == 0) { CLEAR_BYPASS_ALL(); } else { idx = z; if (idx < 0) { idx = 0 - idx; } // absolute value if (idx < 1) { idx = 1; } if (idx > ZONES) { idx = ZONES; } val = 0; if (z > 0) { val = 1; } SET_BYPASS(idx, val); } publish("bypass_state", "b1", bypass1, "b2", bypass2, "b3", bypass3, "b4", bypass4, "b5", bypass5, "b6", bypass6, "b7", bypass7, "b8", bypass8); } } onmqtt("panic_script") { if (msg_is_on || msg_is_number) { alarm = 1; last_zone = 99; alarm_since = nowutc(); publish("alarm", "zone", last_zone, "armed", armed, "ts", alarm_since, "panic", 1); } }