From b7ed0ec6868e6afed79b1217a220351fd472c44c Mon Sep 17 00:00:00 2001 From: Stefano Cotterli Date: Thu, 28 Dec 2017 19:37:56 +0100 Subject: [PATCH] a simple scheduler for relays --- code/espurna/ascheduler.ino | 101 +++++++++++++++++++++++++++++++++ code/espurna/config/hardware.h | 4 ++ code/espurna/espurna.ino | 6 ++ code/html/custom.css | 8 ++- code/html/custom.js | 67 +++++++++++++++++++++- code/html/index.html | 51 +++++++++++++++++ 6 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 code/espurna/ascheduler.ino diff --git a/code/espurna/ascheduler.ino b/code/espurna/ascheduler.ino new file mode 100644 index 00000000..0e3a7a7a --- /dev/null +++ b/code/espurna/ascheduler.ino @@ -0,0 +1,101 @@ +/* + +A SCHEDULER MODULE + +Copyright (C) 2017 by faina09 + +*/ + +#if SCHEDULER_SUPPORT +#include + +void _schWebSocketOnSend(JsonObject &root){ + root["maxScheduled"] = MAX_SCHEDULED; + JsonArray &sch = root.createNestedArray("schedule"); + for (byte i = 0; i < MAX_SCHEDULED; i++) { + if (!hasSetting("sch_switch", i)) + break; + JsonObject &scheduler = sch.createNestedObject(); + scheduler["sch_switch"] = getSetting("sch_switch", i, ""); + scheduler["sch_operation"] = getSetting("sch_operation", i, ""); + scheduler["sch_hour"] = getSetting("sch_hour", i, ""); + scheduler["sch_minute"] = getSetting("sch_minute", i, ""); + scheduler["sch_weekdays"] = getSetting("sch_weekdays", i, ""); + } +} + +void schSetup(){ + // Update websocket clients + #if WEB_SUPPORT + wsOnSendRegister(_schWebSocketOnSend); + #endif + + int i; + for (i = 0; i < MAX_SCHEDULED; i++) { + if (getSetting("sch_switch" + String(i)).length() == 0) + break; + String sch_weekdays = getSetting("sch_weekdays" + String(i)); + int sch_switch = getSetting("sch_switch" + String(i)).toInt(); + int sch_operation = getSetting("sch_operation" + String(i)).toInt(); + int sch_hour = getSetting("sch_hour" + String(i)).toInt(); + int sch_minute = getSetting("sch_minute" + String(i)).toInt(); + DEBUG_MSG_P(PSTR("[SCH] Turn switch #%d %d AT %02d:%02d ON %s\n"), sch_switch, sch_operation, sch_hour, sch_minute, (char *)sch_weekdays.c_str()); + } +} + +void schLoop(){ + // Check if we should compare scheduled and actual times + // TODO (?) start every minute and 0 seconds + static unsigned long last_update = 0; + static bool r_on = true; + if ((millis() - last_update > SCH_UPDATE_INTERVAL) || (last_update == 0)) { + last_update = millis(); + int i; + for (i = 0; i < MAX_SCHEDULED; i++) { + if (getSetting("sch_switch" + String(i)).length() == 0) + break; + String sch_weekdays = getSetting("sch_weekdays" + String(i)); + if (isThisWday(sch_weekdays)) { + int sch_switch = getSetting("sch_switch" + String(i)).toInt(); + int sch_operation = getSetting("sch_operation" + String(i)).toInt(); + int sch_hour = getSetting("sch_hour" + String(i)).toInt(); + int sch_minute = getSetting("sch_minute" + String(i)).toInt(); + DEBUG_MSG_P(PSTR("[SCH] Today it will turn switch #%d %d @ %02d:%02d\n"), sch_switch, sch_operation, sch_hour, sch_minute); + int minToTrigger = diffTime(sch_hour, sch_minute); + if (minToTrigger == 0) { + relayStatus(sch_switch, sch_operation); + DEBUG_MSG_P(PSTR("[SCH] TRIGGERED!!\n")); + } + if (minToTrigger < 0) { + //DEBUG_MSG_P(PSTR("[SCH] Time now: %s\n"), (char *)ntpDateTime().c_str()); // aaaa/mm/dd hh:mm:ss + DEBUG_MSG_P(PSTR("[SCH] Time now: %s, %d minutes to trigger %02d:%02d\n"), (char *)ntpDateTime().c_str(), minToTrigger, sch_hour, sch_minute); + } + } + } + } +} + +bool isThisWday(String weekdays){ + //Sunday = 1, Monday = 2, ... + int w = weekday(now()); + //DEBUG_MSG_P(PSTR("[SCH] ntp weekday: %d\n"), w); + char * pch; + char * p = (char *)weekdays.c_str(); + while ((pch = strtok_r(p, ",", &p)) != NULL) { + //DEBUG_MSG_P(PSTR("[SCH] w found: %d\n"), atoi(pch)); + if (atoi(pch) == w) return true; + } + return false; +} + +int diffTime(int schhour, int schminute){ + if (!ntpConnected()) + return 9999; //NTP time not set + String value = NTP.getTimeDateString(); + int hour = value.substring(0, 2).toInt(); + int minute = value.substring(3, 5).toInt(); + //DEBUG_MSG_P(PSTR("[SCH] ntp time: %02d:%02d\n"), hour, minute); + //DEBUG_MSG_P(PSTR("[SCH] cmp time: %02d:%02d\n"), schhour, schminute); + return (hour - schhour) * 60 + minute - schminute; +} +#endif diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 5bb5035b..db38262c 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -39,6 +39,10 @@ #define RELAY1_PIN 12 #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define SCHEDULER_SUPPORT 1 + #define SCH_UPDATE_INTERVAL 30000 + #define MAX_SCHEDULED 10 + // LEDs #define LED1_PIN 2 #define LED1_PIN_INVERSE 1 diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 24a35b8e..57ca0a88 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -383,6 +383,9 @@ void setup() { #if SENSOR_SUPPORT sensorSetup(); #endif + #if SCHEDULER_SUPPORT + schSetup(); + #endif // Prepare configuration for version 2.0 migrate(); @@ -438,6 +441,9 @@ void loop() { #if SENSOR_SUPPORT sensorLoop(); #endif + #if SCHEDULER_SUPPORT + schLoop(); + #endif // Power saving delay delay(_loopDelay); diff --git a/code/html/custom.css b/code/html/custom.css index 0542eca8..4e822796 100644 --- a/code/html/custom.css +++ b/code/html/custom.css @@ -62,17 +62,23 @@ margin-left: 5px; } .button-add-network, +.button-add-schedule, .button-rfb-learn { background: rgb(28, 184, 65); } -.button-del-network { +.button-del-network, +.button-del-schedule, { background: rgb(202, 60, 60); letter-spacing: 0px; } .button-more-network, +.button-more-schedule, .button-rfb-send { background: rgb(223, 117, 20); } +#schedules input[type=number]{ + max-width: 55px; +} .button-settings-backup, .button-settings-restore { background: rgb(0, 202, 0); diff --git a/code/html/custom.js b/code/html/custom.js index 68689e63..22ac0052 100644 --- a/code/html/custom.js +++ b/code/html/custom.js @@ -1,6 +1,7 @@ var websock; var password = false; var maxNetworks; +var maxSchedules; var messages = []; var webhost; @@ -43,7 +44,7 @@ function magnitudeType(type) { var types = [ "Temperature", "Humidity", "Pressure", "Current", "Voltage", "Active Power", "Apparent Power", - "Reactive Power", "Power Factor", "Energy", "Energy (delta)", + "Reactive Power", "Energy", "Energy (delta)", "Power Factor", "Analog", "Digital", "Events", "PM1.0", "PM2.5", "PM10", "CO2" ]; @@ -100,6 +101,7 @@ function validateForm(form) { // These fields will always be a list of values var is_group = [ "ssid", "pass", "gw", "mask", "ip", "dns", + "sch_switch","sch_operation","sch_hour","sch_minute","sch_weekdays", "relayBoot", "relayPulse", "relayTime", "mqttGroup", "mqttGroupInv", "dczRelayIdx", @@ -464,6 +466,39 @@ function moreNetwork() { $(".more", parent).toggle(); } +// ----------------------------------------------------------------------------- +// Relays scheduler +// ----------------------------------------------------------------------------- +function delSchedule() { + var parent = $(this).parents(".pure-g"); + $(parent).remove(); +} + +function moreSchedule() { + var parent = $(this).parents(".pure-g"); + $("div.more", parent).toggle(); +} + +function addSchedule() { + var numSchedules = $("#schedules > div").length; + if (numSchedules >= maxSchedules) { + alert("Max number of schedules reached"); + return; + } + var tabindex = 200 + numSchedules * 10; + var template = $("#scheduleTemplate").children(); + var line = $(template).clone(); + $(line).find("input").each(function() { + $(this).attr("tabindex", tabindex++); + }); + $(line).find(".button-del-schedule").on('click', delSchedule); + $(line).find(".button-more-schedule").on('click', moreSchedule); + line.appendTo("#schedules"); + return line; +} + + + // ----------------------------------------------------------------------------- // Relays // ----------------------------------------------------------------------------- @@ -817,6 +852,31 @@ function processData(data) { return; } + // ----------------------------------------------------------------------------- + // Relays scheduler + // ----------------------------------------------------------------------------- + if (key == "maxSchedules") { + maxSchedules = parseInt(data.maxSchedules); + return; + } + + if (key == "schedule") { + var schedule = data.schedule; + for (var i in schedule) { + // add a new row + var line = addSchedule(); + // fill in the blanks + var schedule = data.schedule[i]; + Object.keys(schedule).forEach(function(key) { + var element = $("input[name=" + key + "]", line); + if (element.length) element.val(schedule[key]); + var elementsel = $("select[name=" + key + "]", line); + if (elementsel.length) elementsel.prop("value", schedule[key]); + }); + } + return; + } + // --------------------------------------------------------------------- // Relays // --------------------------------------------------------------------- @@ -1036,7 +1096,10 @@ $(function() { $(".button-add-network").on('click', function() { $(".more", addNetwork()).toggle(); }); - + $(".button-add-schedule").on('click', function() { + $("div.more", addSchedule()).toggle(); + }); + $(document).on('change', 'input', hasChanged); $(document).on('change', 'select', hasChanged); diff --git a/code/html/index.html b/code/html/index.html index fffe6c6f..d9bd74e0 100644 --- a/code/html/index.html +++ b/code/html/index.html @@ -100,6 +100,10 @@ SWITCHES +
  • + SCHEDULE +
  • +
  • LIGHTS
  • @@ -525,6 +529,22 @@ +
    +
    +

    SCHEDULE

    +

    + Define actions based on the current time. +

    +
    +
    +
    +
    +
    + +
    +
    +
    +
    @@ -946,6 +966,37 @@
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    1 = Sunday, 2 = Monday, ...
    +
    +
    +
    +
    +