Initial checkin.
This commit is contained in:
187
src/channel.c
Normal file
187
src/channel.c
Normal 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
20
src/led.c
Normal 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
14
src/main.c
Normal 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
85
src/mqtt.c
Normal 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
127
src/rpc.c
Normal 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);
|
||||
}
|
Reference in New Issue
Block a user