Add timespec() and channel_override logic -- so we can install a time specification with which the channels are driven, but yet allowing a human override (either by RPC or by Button)

This commit is contained in:
Pim van Pelt
2018-11-04 15:44:17 +01:00
parent d5778c6fba
commit 0ad33f5fbc
3 changed files with 112 additions and 13 deletions

View File

@ -3,6 +3,7 @@
#include "mgos.h"
#include "mgos_gpio.h"
#include "timespec.h"
#define CHANNEL_MAX 16
#define GPIO_INVALID 255
@ -17,6 +18,8 @@ struct channel_t {
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

View File

@ -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;
@ -91,6 +95,8 @@ bool channel_init(const char *fn) {
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;
}

View File

@ -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;