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:
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user