Initial checkin.

This commit is contained in:
Pim van Pelt
2018-01-05 16:58:15 +01:00
commit 4679636fdc
42 changed files with 9600 additions and 0 deletions

187
src/channel.c Normal file
View File

@ -0,0 +1,187 @@
#include "main.h"
#include "mqtt.h"
#include "frozen/frozen.h"
static struct channel_t s_channels[CHANNEL_MAX];
static int s_num_channels=0;
static bool valid_gpio(const int gpio) {
if (gpio == -1)
return true;
if (gpio < GPIO_MIN || gpio > GPIO_MAX)
return false;
return true;
}
bool channel_init(const char *fn) {
char *json;
void *h = NULL;
struct json_token val;
bool ret = false;
char *name = NULL;
int status_led = -1;
int idx;
memset(s_channels, -1, sizeof(s_channels));
s_num_channels=0;
json = json_fread(fn);
if (!json) {
LOG(LL_ERROR, ("%s: Could not json_fread()", fn));
return false;
}
if (json_scanf(json, strlen(json), "{name:%Q, status_led:%d}", &name, &status_led) != 2) {
LOG(LL_ERROR, ("Incomplete JSON: require 'name' and 'status_led' fields"));
goto exit;
}
LOG(LL_INFO, ("Configuration: name='%s', status_led=%d", name, status_led));
if (!valid_gpio(status_led)) {
LOG(LL_ERROR, ("Status LED GPIO (%d) out of bounds [%d,%d]", status_led, GPIO_MIN, GPIO_MAX));
goto exit;
}
// Traverse Array
while ((h = json_next_elem(json, strlen(json), h, ".channels", &idx, &val)) != NULL) {
int led=-1, relay=-1, button=-1;
LOG(LL_DEBUG, ("[%d]: [%.*s]", idx, val.len, val.ptr));
if (val.len==0 || !val.ptr)
continue;
if (json_scanf(val.ptr, val.len, "{led:%d, relay:%d, button:%d}", &led, &relay, &button) != 3) {
LOG(LL_ERROR, ("Incomplete Channel JSON: require 'led' and 'relay' and 'button' fields"));
goto exit;
}
LOG(LL_INFO, ("Channel[%d]: led=%d relay=%d button=%d ", idx, led, relay, button));
if (!valid_gpio(led)) {
LOG(LL_ERROR, ("LED GPIO (%d) out of bounds [%d,%d]", led, GPIO_MIN, GPIO_MAX));
goto exit;
}
if (!valid_gpio(relay)) {
LOG(LL_ERROR, ("Relay GPIO (%d) out of bounds [%d,%d]", relay, GPIO_MIN, GPIO_MAX));
goto exit;
}
if (!valid_gpio(button)) {
LOG(LL_ERROR, ("Button GPIO (%d) out of bounds [%d,%d]", button, GPIO_MIN, GPIO_MAX));
goto exit;
}
if (idx==CHANNEL_MAX) {
LOG(LL_ERROR, ("Too many channels (max is %d)", CHANNEL_MAX));
goto exit;
}
s_channels[idx].button_gpio=button;
s_channels[idx].relay_gpio=relay;
s_channels[idx].led_gpio=led;
}
ret=true;
exit:
if (ret) {
if (status_led!=-1) {
mgos_gpio_set_mode(status_led, MGOS_GPIO_MODE_OUTPUT);
mgos_gpio_write(status_led, 0);
}
s_num_channels=idx+1;
LOG(LL_INFO, ("Configuring %d channels", s_num_channels));
for (; idx>=0; idx--) {
s_channels[idx].relay_state = 0;
if (s_channels[idx].relay_gpio!=GPIO_INVALID) {
mgos_gpio_set_mode(s_channels[idx].relay_gpio, MGOS_GPIO_MODE_OUTPUT);
mgos_gpio_write(s_channels[idx].relay_gpio, s_channels[idx].relay_state);
}
if (s_channels[idx].led_gpio!=GPIO_INVALID) {
mgos_gpio_set_mode(s_channels[idx].led_gpio, MGOS_GPIO_MODE_OUTPUT);
mgos_gpio_write(s_channels[idx].led_gpio, s_channels[idx].relay_state);
}
if (s_channels[idx].button_gpio!=GPIO_INVALID) {
mgos_gpio_set_mode(s_channels[idx].button_gpio, MGOS_GPIO_MODE_INPUT);
mgos_gpio_set_button_handler(s_channels[idx].button_gpio, MGOS_GPIO_PULL_NONE, MGOS_GPIO_INT_EDGE_ANY, 10, channel_handler, NULL);
}
}
}
if (name) free(name);
if (json) free(json);
return ret;
}
uint8_t channel_gpio_by_idx(int idx) {
if (idx<0 || idx>=s_num_channels)
return GPIO_INVALID;
return s_channels[idx].button_gpio;
}
uint8_t channel_idx_by_gpio(int gpio) {
uint8_t i;
for(i=0; i<3; i++) {
if (gpio == s_channels[i].button_gpio)
return i;
}
return GPIO_INVALID;
}
void channel_report(int idx, char *msg, int msg_len) {
if (!msg || msg_len==0)
return;
if (idx<0 || idx>=s_num_channels)
return;
snprintf(msg, msg_len-1, "{\"idx\": %d, \"button_gpio\": %d, \"led_gpio\": %d, \"relay_gpio\": %d, \"relay_state\": %d}",
idx, s_channels[idx].button_gpio, s_channels[idx].led_gpio, s_channels[idx].relay_gpio, s_channels[idx].relay_state);
}
void channel_set(int idx, bool state) {
double now = mg_time();
if (idx<0 || idx>=s_num_channels)
return;
s_channels[idx].button_last_change = now;
s_channels[idx].relay_state = state;
if (s_channels[idx].relay_gpio!=GPIO_INVALID)
mgos_gpio_write(s_channels[idx].relay_gpio, state);
if (s_channels[idx].led_gpio!=GPIO_INVALID)
mgos_gpio_write(s_channels[idx].led_gpio, state);
}
bool channel_get(int idx) {
if (idx<0 || idx>=s_num_channels)
return false;
return s_channels[idx].relay_state == 1;
}
void channel_handler(int gpio, void *arg) {
uint8_t idx;
char msg[100];
double now = mg_time();
bool state;
idx = channel_idx_by_gpio(gpio);
if (idx == GPIO_INVALID) {
LOG(LL_ERROR, ("GPIO %d is not a valid button", gpio));
return;
}
if (now<s_channels[idx].button_last_change+CHANNEL_CHANGE_COOLDOWN_SECONDS) {
LOG(LL_INFO, ("GPIO %d is cooling down -- skipping", gpio));
return;
}
LOG(LL_INFO, ("GPIO %d triggered button %d", gpio, idx));
state = channel_get(idx);
channel_set(idx, !state);
channel_report(idx, msg, sizeof(msg));
mqtt_publish_stat("channel", msg);
(void) arg;
}

20
src/led.c Normal file
View File

@ -0,0 +1,20 @@
#include "main.h"
static mgos_timer_id led_green_timer_id = 0;
static void led_green_off_cb(void *arg) {
mgos_gpio_write(GREEN_LED_GPIO, 0);
led_green_timer_id=0;
(void) arg;
}
void led_green_blink() {
mgos_gpio_write(GREEN_LED_GPIO, 1);
if (led_green_timer_id) {
mgos_clear_timer(led_green_timer_id);
led_green_timer_id=0;
}
led_green_timer_id = mgos_set_timer(100, false, led_green_off_cb, NULL);
}

14
src/main.c Normal file
View File

@ -0,0 +1,14 @@
#include "main.h"
#include "mqtt.h"
#include "mgos_config.h"
#include "rpc.h"
enum mgos_app_init_result mgos_app_init(void) {
mgos_gpio_set_mode(GREEN_LED_GPIO, MGOS_GPIO_MODE_OUTPUT);
mgos_gpio_write(GREEN_LED_GPIO, 0);
channel_init(mgos_sys_config_get_app_config());
mqtt_init();
rpc_init();
return MGOS_APP_INIT_SUCCESS;
}

85
src/mqtt.c Normal file
View File

@ -0,0 +1,85 @@
#include "mgos.h"
#include "mgos_mqtt.h"
#include "mgos_config.h"
#include "mqtt.h"
#include "main.h"
static void mqtt_publish_broadcast_stat(const char *stat, const char *msg) {
char topic[80];
led_green_blink();
snprintf(topic, sizeof(topic)-1, "%s/%s", MQTT_TOPIC_BROADCAST_STAT, stat);
mgos_mqtt_pub((char*)topic, (char*)msg, strlen(msg), 0, false);
LOG(LL_INFO, ("Sent topic='%s' msg='%s'", topic, msg));
}
static void mqtt_broadcast_cmd_id() {
char resp[300];
struct mgos_net_ip_info ip_info;
char sta_ip[16], ap_ip[16];
memset(sta_ip, 0, sizeof(sta_ip));
memset(ap_ip, 0, sizeof(ap_ip));
if (mgos_net_get_ip_info(MGOS_NET_IF_TYPE_WIFI, MGOS_NET_IF_WIFI_STA, &ip_info))
mgos_net_ip_to_str(&ip_info.ip, sta_ip);
if (mgos_net_get_ip_info(MGOS_NET_IF_TYPE_WIFI, MGOS_NET_IF_WIFI_AP, &ip_info))
mgos_net_ip_to_str(&ip_info.ip, ap_ip);
snprintf(resp, sizeof(resp)-1, "{\"deviceid\": \"%s\", \"macaddress\": \"%s\", \"sta_ip\": \"%s\", \"ap_ip\": \"%s\", \"app\": \"%s\", \"arch\": \"%s\", \"uptime\": %lu}",
mgos_sys_config_get_device_id(),
mgos_sys_ro_vars_get_mac_address(),
sta_ip,
ap_ip,
MGOS_APP,
mgos_sys_ro_vars_get_arch(), (unsigned long) mgos_uptime());
mqtt_publish_broadcast_stat("id", resp);
}
static void mqtt_cb(struct mg_connection *nc, const char *topic, int topic_len, const char *msg, int msg_len, void *ud) {
LOG(LL_INFO, ("Received topic='%.*s' msg='%.*s'", topic_len, topic, msg_len, msg));
if (topic_len >= (int) strlen(MQTT_TOPIC_BROADCAST_CMD) && 0 == strncmp(MQTT_TOPIC_BROADCAST_CMD, topic, strlen(MQTT_TOPIC_BROADCAST_CMD)))
mqtt_broadcast_cmd_id();
(void) nc;
(void) ud;
}
static void mqtt_ev(struct mg_connection *nc, int ev, void *ev_data, void *user_data) {
switch (ev) {
case MG_EV_MQTT_CONNACK:
mqtt_broadcast_cmd_id();
break;
}
(void) nc;
(void) ev_data;
(void) user_data;
}
void mqtt_publish_stat(const char *stat, const char *msg) {
char topic[80];
led_green_blink();
snprintf(topic, sizeof(topic)-1, "%s%s/stat/%s", MQTT_TOPIC_PREFIX, mgos_sys_config_get_device_id(), stat);
mgos_mqtt_pub((char*)topic, (char*)msg, strlen(msg), 0, false);
LOG(LL_INFO, ("Sent topic='%s' msg='%s'", topic, msg));
}
void mqtt_init() {
char topic[100];
mgos_mqtt_add_global_handler(mqtt_ev, NULL);
// Subscribe to broadcast for identification purposes
mgos_mqtt_sub(MQTT_TOPIC_BROADCAST_CMD, mqtt_cb, NULL);
// Subscribe to broadcast/appname
snprintf(topic, sizeof(topic)-1, "%s/%s", MQTT_TOPIC_BROADCAST_CMD, MGOS_APP);
mgos_mqtt_sub(topic, mqtt_cb, NULL);
return;
}

127
src/rpc.c Normal file
View File

@ -0,0 +1,127 @@
#include "rpc.h"
#include "main.h"
#include "mqtt.h"
static void rpc_log(struct mg_rpc_request_info *ri, struct mg_str args) {
LOG(LL_INFO, ("tag=%.*s src=%.*s method=%.*s args='%.*s'",
ri->tag.len, ri->tag.p, ri->src.len, ri->src.p,
ri->method.len, ri->method.p, args.len, args.p));
// TODO(pim): log to MQTT
}
// Grab the idx from args and resolve to a GPIO, return true if found, false if not.
// If we return false, return_idx and return_gpio are not usable.
static bool rpc_args_to_idx_and_gpio(struct mg_rpc_request_info *ri, struct mg_str args, int *return_idx, uint8_t *return_gpio) {
int idx;
int gpio;
if (json_scanf(args.p, args.len, ri->args_fmt, &idx) != 1) {
mg_rpc_send_errorf(ri, 400, "idx is required");
ri = NULL;
return false;
}
if (idx<0 || idx>2) {
mg_rpc_send_errorf(ri, 400, "idx must be 0, 1, 2");
ri = NULL;
return false;
}
gpio = channel_gpio_by_idx(idx);
if (gpio == GPIO_INVALID) {
mg_rpc_send_errorf(ri, 400, "No GPIO for idx");
ri = NULL;
return false;
}
*return_gpio = gpio;
*return_idx = idx;
return true;
}
static void rpc_channel_toggle_handler(struct mg_rpc_request_info *ri, void *cb_arg, struct mg_rpc_frame_info *fi, struct mg_str args) {
uint8_t gpio;
char msg[100];
int idx;
rpc_log(ri, args);
if (!rpc_args_to_idx_and_gpio(ri, args, &idx, &gpio))
return;
channel_handler(gpio, NULL);
channel_report(idx, msg, sizeof(msg));
mg_rpc_send_responsef(ri, msg);
ri = NULL;
(void) ri;
(void) cb_arg;
(void) fi;
(void) args;
}
static void rpc_channel_get_handler(struct mg_rpc_request_info *ri, void *cb_arg, struct mg_rpc_frame_info *fi, struct mg_str args) {
uint8_t gpio;
char msg[100];
int idx;
rpc_log(ri, args);
if (!rpc_args_to_idx_and_gpio(ri, args, &idx, &gpio))
return;
channel_report(idx, msg, sizeof(msg));
mqtt_publish_stat("channel", msg);
mg_rpc_send_responsef(ri, msg);
ri = NULL;
(void) ri;
(void) cb_arg;
(void) fi;
(void) args;
}
static void rpc_channel_set_handler(struct mg_rpc_request_info *ri, void *cb_arg, struct mg_rpc_frame_info *fi, struct mg_str args) {
uint8_t gpio;
char msg[100];
int idx;
int value;
rpc_log(ri, args);
if (json_scanf(args.p, args.len, ri->args_fmt, &idx, &value) != 2) {
mg_rpc_send_errorf(ri, 400, "idx and value are required");
ri = NULL;
return;
}
if (idx<0 || idx>2) {
mg_rpc_send_errorf(ri, 400, "idx must be 0, 1, 2");
ri = NULL;
return;
}
gpio = channel_gpio_by_idx(idx);
if (gpio == GPIO_INVALID) {
mg_rpc_send_errorf(ri, 400, "No GPIO for idx");
ri = NULL;
return;
}
channel_set(idx, (bool) value);
channel_report(idx, msg, sizeof(msg));
mg_rpc_send_responsef(ri, msg);
ri = NULL;
(void) ri;
(void) cb_arg;
(void) fi;
(void) args;
}
void rpc_init() {
struct mg_rpc *c = mgos_rpc_get_global();
mg_rpc_add_handler(c, "Channel.Toggle", "{idx: %d}", rpc_channel_toggle_handler, NULL);
mg_rpc_add_handler(c, "Channel.Get", "{idx: %d}", rpc_channel_get_handler, NULL);
mg_rpc_add_handler(c, "Channel.Set", "{idx: %d, value: %d}", rpc_channel_set_handler, NULL);
}