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.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
|
||||||
@ -17,6 +18,8 @@ struct channel_t {
|
|||||||
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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
@ -91,6 +95,8 @@ bool channel_init(const char *fn) {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user