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.h"
#include "mgos_gpio.h" #include "mgos_gpio.h"
#include "timespec.h"
#define CHANNEL_MAX 16 #define CHANNEL_MAX 16
#define GPIO_INVALID 255 #define GPIO_INVALID 255
@ -10,13 +11,15 @@
#define GPIO_MAX 16 #define GPIO_MAX 16
struct channel_t { struct channel_t {
uint8_t button_gpio; uint8_t button_gpio;
uint8_t led_gpio; uint8_t led_gpio;
bool led_invert; bool led_invert;
uint8_t relay_gpio; uint8_t relay_gpio;
bool relay_invert; bool relay_invert;
bool relay_state; bool relay_state;
double button_last_change; double button_last_change;
bool channel_override;
struct mgos_timespec *timespec;
}; };
void statusled_blink(); void statusled_blink();
@ -30,5 +33,6 @@ void channel_set_duration(int idx, bool state, uint16_t seconds);
bool channel_get(int idx); bool channel_get(int idx);
int channel_get_total(); int channel_get_total();
void channel_handler(int gpio, void *arg); void channel_handler(int gpio, void *arg);
void channel_override_set(int idx);
#endif // __MAIN_H #endif // __MAIN_H

View File

@ -1,7 +1,11 @@
#include "main.h" #include "main.h"
#include "mqtt.h" #include "mqtt.h"
#include "timespec.h"
#include "frozen/frozen.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 struct channel_t s_channels[CHANNEL_MAX];
static int s_num_channels = 0; static int s_num_channels = 0;
@ -86,11 +90,13 @@ bool channel_init(const char *fn) {
goto exit; 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)); 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].button_gpio = button;
s_channels[idx].relay_gpio = relay; s_channels[idx].relay_gpio = relay;
s_channels[idx].relay_invert = relay_invert; s_channels[idx].relay_invert = relay_invert;
s_channels[idx].led_gpio = led; s_channels[idx].led_gpio = led;
s_channels[idx].led_invert = led_invert; s_channels[idx].led_invert = led_invert;
s_channels[idx].channel_override = false;
s_channels[idx].timespec = timespec_create();
} }
ret = true; ret = true;
@ -128,6 +134,9 @@ exit:
if (json) { if (json) {
free(json); free(json);
} }
// Install timespec timer.
mgos_set_timer(1000, true, channel_timespec_cb, NULL);
return ret; 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)); 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_set(cb_args->idx, cb_args->value);
channel_override_set(cb_args->idx);
free(arg); free(arg);
} }
@ -175,6 +185,7 @@ void channel_set_duration(int idx, bool state, uint16_t seconds) {
} }
channel_set(idx, state); channel_set(idx, state);
channel_override_set(idx);
// Set a timer to call back with the new intended state on the channel. // Set a timer to call back with the new intended state on the channel.
cb_args->value = !state; cb_args->value = !state;
@ -217,6 +228,89 @@ bool channel_get(int idx) {
return s_channels[idx].relay_state == 1; 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. // 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) { void channel_handler(int gpio, void *arg) {
uint8_t idx; uint8_t idx;
@ -248,6 +342,6 @@ void channel_handler(int gpio, void *arg) {
state = channel_get(idx); state = channel_get(idx);
channel_set(idx, !state); channel_set(idx, !state);
channel_override_set(idx);
(void)arg; (void)arg;
} }

View File

@ -118,6 +118,7 @@ static void rpc_channel_set_handler(struct mg_rpc_request_info *ri, void *cb_arg
} else { } else {
channel_set(idx, (bool)value); channel_set(idx, (bool)value);
} }
channel_override_set(idx);
mg_rpc_send_responsef(ri, "{idx: %d, relay_state: %d}", idx, channel_get(idx)); mg_rpc_send_responsef(ri, "{idx: %d, relay_state: %d}", idx, channel_get(idx));
ri = NULL; ri = NULL;