diff --git a/include/main.h b/include/main.h index e43fabd..4af4387 100644 --- a/include/main.h +++ b/include/main.h @@ -3,6 +3,7 @@ #include "mgos.h" #include "mgos_gpio.h" +#include "timespec.h" #define CHANNEL_MAX 16 #define GPIO_INVALID 255 @@ -10,13 +11,15 @@ #define GPIO_MAX 16 struct channel_t { - uint8_t button_gpio; - uint8_t led_gpio; - bool led_invert; - uint8_t relay_gpio; - bool relay_invert; - bool relay_state; - double button_last_change; + uint8_t button_gpio; + uint8_t led_gpio; + bool led_invert; + uint8_t relay_gpio; + bool relay_invert; + bool relay_state; + double button_last_change; + bool channel_override; + struct mgos_timespec *timespec; }; void statusled_blink(); @@ -30,5 +33,6 @@ void channel_set_duration(int idx, bool state, uint16_t seconds); bool channel_get(int idx); int channel_get_total(); void channel_handler(int gpio, void *arg); +void channel_override_set(int idx); #endif // __MAIN_H diff --git a/src/channel.c b/src/channel.c index 2a4a488..18e4083 100644 --- a/src/channel.c +++ b/src/channel.c @@ -1,7 +1,11 @@ #include "main.h" #include "mqtt.h" +#include "timespec.h" #include "frozen/frozen.h" +static void channel_timespec_cb(void *arg); +static bool channel_timespec(int idx); + static struct channel_t s_channels[CHANNEL_MAX]; static int s_num_channels = 0; @@ -86,11 +90,13 @@ bool channel_init(const char *fn) { goto exit; } LOG(LL_INFO, ("Channel[%d]: led=%d%s relay=%d%s button=%d ", idx, led, led_invert ? "(inverted)" : "", relay, relay_invert ? "(inverted)" : "", button)); - s_channels[idx].button_gpio = button; - s_channels[idx].relay_gpio = relay; - s_channels[idx].relay_invert = relay_invert; - s_channels[idx].led_gpio = led; - s_channels[idx].led_invert = led_invert; + s_channels[idx].button_gpio = button; + s_channels[idx].relay_gpio = relay; + s_channels[idx].relay_invert = relay_invert; + s_channels[idx].led_gpio = led; + s_channels[idx].led_invert = led_invert; + s_channels[idx].channel_override = false; + s_channels[idx].timespec = timespec_create(); } ret = true; @@ -128,6 +134,9 @@ exit: if (json) { free(json); } + + // Install timespec timer. + mgos_set_timer(1000, true, channel_timespec_cb, NULL); return ret; } @@ -163,6 +172,7 @@ static void channel_set_cb(void *arg) { } LOG(LL_INFO, ("Channel callback for channel %d value %d", cb_args->idx, cb_args->value)); channel_set(cb_args->idx, cb_args->value); + channel_override_set(cb_args->idx); free(arg); } @@ -175,6 +185,7 @@ void channel_set_duration(int idx, bool state, uint16_t seconds) { } channel_set(idx, state); + channel_override_set(idx); // Set a timer to call back with the new intended state on the channel. cb_args->value = !state; @@ -217,6 +228,89 @@ bool channel_get(int idx) { return s_channels[idx].relay_state == 1; } +// This method is called every second. It looks at the timespec for the channel +// and compares it with the channel_override flag, to see if the channel should +// be switched. Logic: +// - if channel_override is false: follow timespec and set channel accordingly. +// - if channel_override ie true: +// - if timespec wants the channel set differently, leave it as-is +// - if timespec agrees with the current setting, clear channel_override +// This behavior lets users set the channel (RPC or Button), and timespec will +// pick it up and govern the channel after the current (RPC/Button) state +// converges with the timespec. +// It returns the current state of the relay. +static bool channel_timespec(int idx) { + bool match; + + if (idx < 0 || idx >= channel_get_total()) { + return false; + } + match = timespec_match_now(s_channels[idx].timespec); + if (!s_channels[idx].channel_override) { + // Follow timespec + if (match == s_channels[idx].relay_state) { + return s_channels[idx].relay_state == 1; + } + LOG(LL_INFO, ("Timespec drives channel %d to relay_state %d", idx, match)); + channel_set(idx, match); + } else { + // Follow override until it matches timespec + if (match == s_channels[idx].relay_state) { + LOG(LL_INFO, ("User state agrees with timespec, clearing override")); + s_channels[idx].channel_override = false; + return s_channels[idx].relay_state == 1; + } + } + + return s_channels[idx].relay_state == 1; +} + +static void channel_timespec_cb(void *arg) { + uint8_t i; + time_t now; + struct tm *tm; + + now = time(NULL); + if (now < 1541340332) { + LOG(LL_WARN, ("NTP not yet synced, skipping.")); + return; + } + + tm = localtime(&now); + if (!tm) { + LOG(LL_ERROR, ("Could not convert time (%u) into localtime", (uint32_t)now)); + return; + } + + // LOG(LL_INFO, ("Time %u is %02d:%02d:%02d", (uint32_t) now, tm->tm_hour, tm->tm_min, tm->tm_sec)); + for (i = 0; i < channel_get_total(); i++) { + channel_timespec(i); + } + (void)arg; + return; +} + +// Set an override flag if the user intended for a different state than the +// timespec. Do nothing otherwise. +void channel_override_set(int idx) { + bool match; + + if (idx < 0 || idx >= channel_get_total()) { + return; + } + match = timespec_match_now(s_channels[idx].timespec); + if (match != s_channels[idx].relay_state) { + LOG(LL_INFO, ("User state disagrees with timespec, setting override")); + s_channels[idx].channel_override = true; + return; + } + if (s_channels[idx].channel_override && (match == s_channels[idx].relay_state)) { + LOG(LL_INFO, ("User state agrees with timespec, clearing override")); + s_channels[idx].channel_override = false; + return; + } +} + // Channel handler arg is non-NULL if it was a pushbutton, and NULL if it was an RPC. void channel_handler(int gpio, void *arg) { uint8_t idx; @@ -248,6 +342,6 @@ void channel_handler(int gpio, void *arg) { state = channel_get(idx); channel_set(idx, !state); - + channel_override_set(idx); (void)arg; } diff --git a/src/rpc.c b/src/rpc.c index 8567941..cca6d59 100644 --- a/src/rpc.c +++ b/src/rpc.c @@ -118,6 +118,7 @@ static void rpc_channel_set_handler(struct mg_rpc_request_info *ri, void *cb_arg } else { channel_set(idx, (bool)value); } + channel_override_set(idx); mg_rpc_send_responsef(ri, "{idx: %d, relay_state: %d}", idx, channel_get(idx)); ri = NULL;