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