diff --git a/fs/mqtt.pem b/fs/mqtt.pem new file mode 100644 index 0000000..bb2b1d6 --- /dev/null +++ b/fs/mqtt.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDvzCCAqegAwIBAgIJAOfvQdPViBT2MA0GCSqGSIb3DQEBCwUAMHYxCzAJBgNV +BAYTAkNIMQswCQYDVQQIDAJaSDEVMBMGA1UEBwwMQnJ1dHRpc2VsbGVuMRAwDgYD +VQQKDAdJUG5nLm5sMRUwEwYDVQQDDAxtcXR0LmlwbmcubmwxGjAYBgkqhkiG9w0B +CQEWC3BpbUBpcG5nLm5sMB4XDTE3MTIxNzEwMDMwOVoXDTIyMTIxNzEwMDMwOVow +djELMAkGA1UEBhMCQ0gxCzAJBgNVBAgMAlpIMRUwEwYDVQQHDAxCcnV0dGlzZWxs +ZW4xEDAOBgNVBAoMB0lQbmcubmwxFTATBgNVBAMMDG1xdHQuaXBuZy5ubDEaMBgG +CSqGSIb3DQEJARYLcGltQGlwbmcubmwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDH8aZRJPF/wHHLG7LDPp6zJsAv4MbgmhEMLsl4pv3Z7gp0ftsJEqVy +Wkb5ab5jNDbPgxn2djJG1j1947boCV1AnRQQY2klfwFSEi41o+ncKzu8vjYETvJA +8jz+iqh9Izw+31HNp+TxVabQ5AQnd+9sMq+enTTC/fQoY9TSSh4pMqMuT7kBE5zu +lecHGi78Mw4v2WXXUFm3otfVa4disFBw3EAZMQOeh+C9sTx+QGjDQJ2n+dvn5HUI +23xfRsaX9U8nGbf57/QyoG+r0gP83DK6Ed7Z1AtEY7VUcB7+qOloEh7lweb6h+Z3 +RL3N6o5eCLPaEfIhBTbli4kCmZzTarULAgMBAAGjUDBOMB0GA1UdDgQWBBRJgIbw +FOVojHrq/OhjjTpqdWXwHjAfBgNVHSMEGDAWgBRJgIbwFOVojHrq/OhjjTpqdWXw +HjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBFI5a7ZRr5li/yYOmt +VOHcLysxVSF1uDkAwoA/L+NL4/xWp9MYo9XlJ5Ug730PztC5rhpV5fAo57X8v4P8 +dWJgnoJ40ivExLrp87pzrO9p1IkDcfFXVfCyatd453iBiD1iRkwheqTld+I9mKNa +Spf8kJxODz5C6LSqr6AuBMNOJVs0rXSzHawiFxGFZKMJ0glMAuH5mnmXD6+KVhOG +MbCKQxstq4e6/0VexzPXIcqEEf1XmB9ga1AwFJm46N6u4XTM99YpvSh1/6DqRHHF +6sdWSTx++N+sQJJhBDTQ5E6Zb/AOkmT+bipU49heRk5uGgTyR0wPDWywP73ZHKas +kMT3 +-----END CERTIFICATE----- diff --git a/include/buttons.h b/include/buttons.h new file mode 100644 index 0000000..9cfc11f --- /dev/null +++ b/include/buttons.h @@ -0,0 +1,26 @@ +#ifndef __BUTTONS_H +#define __BUTTONS_H + +#include "mgos.h" +#include + +#define MAX_BUTTONS 8 + +struct button_t { + uint8_t gpio; + float last_change; +}; + +int buttons_init(const char *flag); +uint8_t buttons_find_by_gpio(uint8_t gpio); +bool buttons_touch_by_gpio(uint8_t gpio); +float buttons_get_last_change_by_gpio(uint8_t gpio); + +bool buttons_touch_by_idx(uint8_t idx); +float buttons_get_last_change_by_idx(uint8_t idx); +uint8_t buttons_get_gpio_by_idx(uint8_t idx); + +void buttons_cb(int gpio, void *args); + +#endif // __BUTTONS_H + diff --git a/include/helper.h b/include/helper.h new file mode 100644 index 0000000..a097fbd --- /dev/null +++ b/include/helper.h @@ -0,0 +1,19 @@ +#ifndef __HELPER_H +#define __HELPER_H + +#include "mgos.h" +#include +#include + +#define MIN_GPIO 0 +#define MAX_GPIO 16 +#define MQTT_TOPIC_PREFIX "" +#define MQTT_TOPIC_BROADCAST_CMD "/mongoose/broadcast" +#define MQTT_TOPIC_BROADCAST_STAT "/mongoose/broadcast/stat" + +#define ERR_GPIO_INVALID 255 + +bool is_button_flag_valid(const char *flag); +bool is_relay_flag_valid(const char *flag); + +#endif // __HELPER_H diff --git a/include/lightswitch.h b/include/lightswitch.h new file mode 100644 index 0000000..9adf057 --- /dev/null +++ b/include/lightswitch.h @@ -0,0 +1,10 @@ +#ifndef __LIGHTSWITCH_H +#define __LIGHTSWITCH_h + +#include +#include "mgos.h" +#include "helper.h" +#include "buttons.h" +#include "relays.h" + +#endif // __LIGHTSWITCH_h diff --git a/include/mqtt.h b/include/mqtt.h new file mode 100644 index 0000000..d1e04cd --- /dev/null +++ b/include/mqtt.h @@ -0,0 +1,10 @@ +#ifndef __MQTT_H +#define __MQTT_H + +#include "mgos.h" +#include "mgos_mqtt.h" + +void mqtt_init(); +void mqtt_publish_stat(const char *stat, const char *msg); + +#endif // __MQTT_H diff --git a/include/relays.h b/include/relays.h new file mode 100644 index 0000000..8da1695 --- /dev/null +++ b/include/relays.h @@ -0,0 +1,26 @@ +#ifndef __RELAYS_H +#define __RELAYS_H + +#include "mgos.h" +#include + +#define MAX_RELAYS 8 + +struct relay_t { + uint8_t gpio; + float last_change; + bool state; +}; + +int relays_init(const char *flag); +uint8_t relays_find_by_gpio(uint8_t gpio); +bool relays_set_by_gpio(uint8_t gpio, bool state); +bool relays_get_by_gpio(uint8_t gpio); +float relays_get_last_change_by_gpio(uint8_t gpio); + +bool relays_set_by_idx(uint8_t idx, bool state); +bool relays_get_by_idx(uint8_t idx); +float relays_get_last_change_by_idx(uint8_t idx); +uint8_t relays_get_gpio_by_idx(uint8_t idx); +#endif // __RELAYS_H + diff --git a/include/rpc.h b/include/rpc.h new file mode 100644 index 0000000..5817383 --- /dev/null +++ b/include/rpc.h @@ -0,0 +1,10 @@ +#ifndef __RPC_H +#define __RPC_H + +#include "mgos.h" +#include "mgos_rpc.h" +#include "common/mg_str.h" + +void rpc_init(); + +#endif // __RPC_H diff --git a/mos.yml b/mos.yml index 9b2477b..c7d5bc3 100644 --- a/mos.yml +++ b/mos.yml @@ -46,6 +46,10 @@ config_schema: - ["app.inactivity_timeout", 10] - ["app.battery_calibration", i, {title: "Battery ADC value at 4000mV"}] - ["app.battery_calibration", 2360] + - ["app.buttons", "s", {title: "comma-separated list of button pins"}] + - ["app.buttons", "0,2"] + - ["app.relays", "s", {title: "comma-separated list of relay pins"}] + - ["app.relays", "16,12,13,14"] - ["spi.enable", true] - ["spi.cs1_gpio", -1] - ["spi.cs2_gpio", -1] @@ -53,7 +57,11 @@ config_schema: - ["stmpe610.cs_index", 1] - ["stmpe610.orientation", 5] # Set to 4 for 2.4" Featherwing, set to 5 for 3.5" Featherwing - ["mqtt.enable", true] - - ["mqtt.server", "chbtl01.paphosting.net:1883"] + - ["http.enable", true] + - ["mqtt.server", "mqtt.ipng.nl:8883"] + - ["mqtt.ssl_ca_cert", "mqtt.pem"] + - ["rpc.mqtt.enable", true] + conds: - when: mos.platform == "esp32" @@ -83,14 +91,15 @@ libs: - origin: https://github.com/mongoose-os-libs/rpc-service-config - origin: https://github.com/mongoose-os-libs/rpc-service-fs - origin: https://github.com/mongoose-os-libs/prometheus-metrics + - origin: https://github.com/mongoose-os-libs/rpc-common + - origin: https://github.com/mongoose-os-libs/rpc-service-config + - origin: https://github.com/mongoose-os-libs/rpc-mqtt - origin: https://github.com/mongoose-os-libs/mqtt - origin: https://github.com/mongoose-os-libs/pwm - origin: https://github.com/mongoose-os-libs/adc - origin: https://github.com/mongoose-os-libs/spi - origin: https://github.com/mongoose-os-libs/ili9341-spi - origin: https://github.com/mongoose-os-libs/stmpe610-spi - - origin: libs/ota-http-client - - origin: libs/ota-http-server - origin: libs/rpc-service-ota diff --git a/src/buttons.c b/src/buttons.c new file mode 100644 index 0000000..9c86fa2 --- /dev/null +++ b/src/buttons.c @@ -0,0 +1,119 @@ +#include "mqtt.h" +#include "mgos_gpio.h" +#include "buttons.h" +#include "relays.h" +#include "helper.h" + +struct button_t s_buttons[MAX_BUTTONS]; + +void buttons_cb(int gpio, void *arg) { + int idx = buttons_find_by_gpio(gpio); + + if (ERR_GPIO_INVALID == idx) { + LOG(LL_ERROR, ("No button on gpio=%d", gpio)); + return; + } + LOG(LL_INFO, ("Button on gpio=%d idx=%d", gpio, idx)); + + buttons_touch_by_idx(idx); + return; + (void) arg; +} + +int buttons_init(const char *flag) { + int next_button=0; + int i; + + memset(s_buttons, -1, sizeof(s_buttons)); + + LOG(LL_DEBUG, ("Flag='%s'", flag)); + if (!is_button_flag_valid(flag)) { + return -1; + } + + i=0; + while(i<(int) strlen(flag)) { + int gpio = -1; + + if (!isdigit((int) flag[i])) { + i++; + continue; + } + + gpio = atoi((char *)flag+i); +// LOG(LL_DEBUG, ("Token: %d at pos %u", gpio, i)); + if (gpioMAX_GPIO) { + LOG(LL_ERROR, ("GPIO values must be within [%d,%d], gpio=%d is invalid", MIN_GPIO, MAX_GPIO, gpio)); + return -2; + } + s_buttons[next_button].gpio=gpio; + s_buttons[next_button].last_change=-1; + mgos_gpio_set_mode(gpio, MGOS_GPIO_MODE_INPUT); + mgos_gpio_set_button_handler(gpio, MGOS_GPIO_PULL_UP, MGOS_GPIO_INT_EDGE_NEG, 100, buttons_cb, NULL); + + next_button++; + if (next_button>MAX_BUTTONS) { + LOG(LL_ERROR, ("Too many buttons defined, max is %d", MAX_BUTTONS)); + return -3; + } + + while (i<(int)strlen(flag) && isdigit((int) flag[i])) i++; + } + + return 0; +} + +uint8_t buttons_get_gpio_by_idx(uint8_t idx) { + if (idx>=MAX_BUTTONS) + return ERR_GPIO_INVALID; + return s_buttons[idx].gpio; +} + + + +uint8_t buttons_find_by_gpio(uint8_t gpio) { + int i; + for (i=0; i=MAX_BUTTONS || s_buttons[idx].gpio == ERR_GPIO_INVALID) { + LOG(LL_ERROR, ("No button at idx=%d", idx)); + return false; + } + s_buttons[idx].last_change=mg_time(); + + snprintf(msg, sizeof(msg)-1, "{\"idx\": %d, \"gpio\": %d}", idx, s_buttons[idx].gpio); + mqtt_publish_stat("button", msg); + + // Lookup corresponding relay at the same index. If it exists, trigger it. + relay_gpio = relays_get_gpio_by_idx(idx); + if (ERR_GPIO_INVALID == relay_gpio) + return true; + + relays_set_by_gpio(relay_gpio, !relays_get_by_gpio(relay_gpio)); + return true; +} + +bool buttons_touch_by_gpio(uint8_t gpio) { + return buttons_touch_by_idx(buttons_find_by_gpio(gpio)); +} + +float buttons_get_last_change_by_gpio(uint8_t gpio) { + return buttons_get_last_change_by_idx(buttons_find_by_gpio(gpio)); +} + +float buttons_get_last_change_by_idx(uint8_t idx) { + if (idx>=MAX_BUTTONS || s_buttons[idx].gpio == ERR_GPIO_INVALID) { + LOG(LL_ERROR, ("No button at idx=%d", idx)); + return -1; + } + return s_buttons[idx].last_change; +} diff --git a/src/helper.c b/src/helper.c new file mode 100644 index 0000000..bfe96df --- /dev/null +++ b/src/helper.c @@ -0,0 +1,22 @@ +#include "helper.h" + +static bool is_csv_of_digits(const char *flag) { + unsigned char *p = (unsigned char*)flag; + + while (*p) { + if (*p!=' ' && !isdigit(*p) && *p!=',') { + LOG(LL_ERROR, ("Flag value '%s' invalid CSV of digits at pos %d: use only digits, ' ' and ','", flag, (int) ((char*)p-(char*)flag))); + return false; + } + p++; + } + return true; +} + +bool is_button_flag_valid(const char *flag) { + return is_csv_of_digits(flag); +} + +bool is_relay_flag_valid(const char *flag) { + return is_csv_of_digits(flag); +} diff --git a/src/main.c b/src/main.c index 87a92e7..d201e3e 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,11 @@ #include "mongoose-touch.h" #include "fonts/FreeSerifBold9pt7b.h" #include "fonts/FreeMonoBold9pt7b.h" +#include "mgos_config.h" +#include "buttons.h" +#include "relays.h" +#include "mqtt.h" +#include "rpc.h" struct screen_t *s_screen = NULL; @@ -62,18 +67,13 @@ void tft_demo(void) LOG(LL_INFO, ("Screen '%s' has %d widgets", s_screen->name, screen_get_num_widgets(s_screen))); } -static void mqtt_recv_cb(struct mg_connection *nc, const char *topic, int topic_len, const char *msg, int msg_len, void *user_data) { - widget_network_recv(); - LOG(LL_INFO, ("topic='%.*s' msg='%.*s'", topic_len, topic, msg_len, msg)); - (void) nc; - (void) user_data; -} - - enum mgos_app_init_result mgos_app_init(void) { + buttons_init(mgos_sys_config_get_app_buttons()); + relays_init(mgos_sys_config_get_app_relays()); + mqtt_init(); + rpc_init(); backlight_init(); - mgos_mqtt_sub("/s/#", mqtt_recv_cb, NULL); tft_demo(); diff --git a/src/mqtt.c b/src/mqtt.c new file mode 100644 index 0000000..cc786dd --- /dev/null +++ b/src/mqtt.c @@ -0,0 +1,84 @@ +#include "mgos.h" +#include "mgos_mqtt.h" +#include "mgos_config.h" +#include "mqtt.h" +#include "buttons.h" +#include "relays.h" +#include "helper.h" + +static void mqtt_publish_broadcast_stat(const char *stat, const char *msg) { + char topic[80]; + 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 >= 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]; + 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); + + // Subscribe to Sonoff updates + mgos_mqtt_sub("/s/#", mqtt_cb, NULL); + + return; +} diff --git a/src/relays.c b/src/relays.c new file mode 100644 index 0000000..fe13a8e --- /dev/null +++ b/src/relays.c @@ -0,0 +1,112 @@ +#include "mgos_gpio.h" +#include "mqtt.h" +#include "relays.h" +#include "helper.h" + +struct relay_t s_relays[MAX_RELAYS]; + +int relays_init(const char *flag) { + int next_relay=0; + int i; + + memset(s_relays, -1, sizeof(s_relays)); + + LOG(LL_DEBUG, ("Flag='%s'", flag)); + if (!is_relay_flag_valid(flag)) { + return -1; + } + + i=0; + while(i<(int) strlen(flag)) { + int gpio = -1; + + if (!isdigit((int) flag[i])) { + i++; + continue; + } + + gpio = atoi((char *)flag+i); +// LOG(LL_DEBUG, ("Token: %d at pos %u", gpio, i)); + if (gpioMAX_GPIO) { + LOG(LL_ERROR, ("GPIO values must be within [%d,%d], gpio=%d is invalid", MIN_GPIO, MAX_GPIO, gpio)); + return -2; + } + s_relays[next_relay].gpio=gpio; + s_relays[next_relay].last_change=-1; + s_relays[next_relay].state=0; + mgos_gpio_set_mode(gpio, MGOS_GPIO_MODE_OUTPUT); + mgos_gpio_write(gpio, !s_relays[next_relay].state); + next_relay++; + if (next_relay>MAX_RELAYS) { + LOG(LL_ERROR, ("Too many relays defined, max is %d", MAX_RELAYS)); + return -3; + } + + while (i<(int)strlen(flag) && isdigit((int) flag[i])) i++; + } + + return 0; +} + +uint8_t relays_get_gpio_by_idx(uint8_t idx) { + if (idx>=MAX_RELAYS) + return ERR_GPIO_INVALID; + return s_relays[idx].gpio; +} + +uint8_t relays_find_by_gpio(uint8_t gpio) { + int i; + for (i=0; i=MAX_RELAYS || s_relays[idx].gpio == ERR_GPIO_INVALID) { + LOG(LL_ERROR, ("No relay at idx=%d", idx)); + return false; + } + s_relays[idx].last_change=mg_time(); + if (state != s_relays[idx].state) { + + LOG(LL_INFO, ("Setting relay idx=%d gpio=%d to %s", idx, s_relays[idx].gpio, state==true?"ON":"OFF")); + s_relays[idx].state=state; + mgos_gpio_write(s_relays[idx].gpio, !s_relays[idx].state); + } + + snprintf(msg, sizeof(msg)-1, "{\"idx\": %d, \"gpio\": %d, \"state\": %d}", idx, s_relays[idx].gpio, state); + mqtt_publish_stat("relay", msg); + return true; +} + +bool relays_get_by_gpio(uint8_t gpio) { + return relays_get_by_idx(relays_find_by_gpio(gpio)); +} + +bool relays_get_by_idx(uint8_t idx) { + if (idx>=MAX_RELAYS || s_relays[idx].gpio == ERR_GPIO_INVALID) { + LOG(LL_ERROR, ("No relay at idx=%d", idx)); + return false; + } + return s_relays[idx].state; +} + +float relays_get_last_change_by_gpio(uint8_t gpio) { + return relays_get_last_change_by_idx(relays_find_by_gpio(gpio)); +} + +float relays_get_last_change_by_idx(uint8_t idx) { + if (idx>=MAX_RELAYS || s_relays[idx].gpio == ERR_GPIO_INVALID) { + LOG(LL_ERROR, ("No relay at idx=%d", idx)); + return -1; + } + return s_relays[idx].last_change; +} diff --git a/src/rpc.c b/src/rpc.c new file mode 100644 index 0000000..6054786 --- /dev/null +++ b/src/rpc.c @@ -0,0 +1,133 @@ +#include "rpc.h" +#include "relays.h" +#include "buttons.h" +#include "helper.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 +} + + +static void rpc_button_touch_handler(struct mg_rpc_request_info *ri, void *cb_arg, struct mg_rpc_frame_info *fi, struct mg_str args) { + int idx; + uint8_t gpio; + + rpc_log(ri, args); + + if (json_scanf(args.p, args.len, ri->args_fmt, &idx) != 1) { + mg_rpc_send_errorf(ri, 400, "idx is required"); + ri = NULL; + return; + } + + if (ERR_GPIO_INVALID==(gpio=buttons_get_gpio_by_idx(idx))) { + mg_rpc_send_errorf(ri, 400, "No button at idx=%d", idx); + ri = NULL; + return; + } + + buttons_touch_by_idx(idx); + mg_rpc_send_responsef(ri, "{idx: %d, gpio: %d}", idx, gpio); + ri = NULL; + + (void) ri; + (void) cb_arg; + (void) fi; + (void) args; +} + +static void rpc_relay_get_handler(struct mg_rpc_request_info *ri, void *cb_arg, struct mg_rpc_frame_info *fi, struct mg_str args) { + int idx; + uint8_t gpio; + bool state; + + rpc_log(ri, args); + + if (json_scanf(args.p, args.len, ri->args_fmt, &idx) != 1) { + mg_rpc_send_errorf(ri, 400, "idx is required"); + ri = NULL; + return; + } + + if (ERR_GPIO_INVALID==(gpio=relays_get_gpio_by_idx(idx))) { + mg_rpc_send_errorf(ri, 400, "No relay at idx=%d", idx); + ri = NULL; + return; + } + + state=relays_get_by_idx(idx); + mg_rpc_send_responsef(ri, "{idx: %d, gpio: %d, state: %d}", idx, gpio, state); + ri = NULL; + + (void) cb_arg; + (void) fi; +} + +static void rpc_relay_set_handler(struct mg_rpc_request_info *ri, void *cb_arg, struct mg_rpc_frame_info *fi, struct mg_str args) { + int idx; + uint8_t gpio; + int state; + + rpc_log(ri, args); + + if (json_scanf(args.p, args.len, ri->args_fmt, &idx, &state) != 2) { + mg_rpc_send_errorf(ri, 400, "idx and state are required"); + ri = NULL; + return; + } + if (state!=0 && state!=1) { + mg_rpc_send_errorf(ri, 400, "state must be 0 or 1"); + ri = NULL; + return; + } + + if (ERR_GPIO_INVALID==(gpio=relays_get_gpio_by_idx(idx))) { + mg_rpc_send_errorf(ri, 400, "No relay at idx=%d", idx); + ri = NULL; + return; + } + + relays_set_by_idx(idx, state); + mg_rpc_send_responsef(ri, "{idx: %d, gpio: %d, state: %d}", idx, gpio, state); + + (void) cb_arg; + (void) fi; +} + +static void rpc_relay_toggle_handler(struct mg_rpc_request_info *ri, void *cb_arg, struct mg_rpc_frame_info *fi, struct mg_str args) { + int idx; + uint8_t gpio; + bool state; + + rpc_log(ri, args); + + if (json_scanf(args.p, args.len, ri->args_fmt, &idx) != 1) { + mg_rpc_send_errorf(ri, 400, "idx is required"); + ri = NULL; + return; + } + + if (ERR_GPIO_INVALID==(gpio=relays_get_gpio_by_idx(idx))) { + mg_rpc_send_errorf(ri, 400, "No relay at idx=%d", idx); + ri = NULL; + return; + } + + state=!relays_get_by_idx(idx); + relays_set_by_idx(idx, state); + mg_rpc_send_responsef(ri, "{idx: %d, gpio: %d, state: %d}", idx, gpio, state); + + (void) cb_arg; + (void) fi; +} + +void rpc_init() { + struct mg_rpc *c = mgos_rpc_get_global(); + mg_rpc_add_handler(c, "Button.Touch", "{idx: %d}", rpc_button_touch_handler, NULL); + mg_rpc_add_handler(c, "Relay.Get", "{idx: %d}", rpc_relay_get_handler, NULL); + mg_rpc_add_handler(c, "Relay.Set", "{idx: %d, state:%d}", rpc_relay_set_handler, NULL); + mg_rpc_add_handler(c, "Relay.Toggle", "{idx: %d}", rpc_relay_toggle_handler, NULL); +} diff --git a/unittest/Makefile b/unittest/Makefile index dba770d..f0c7be1 100644 --- a/unittest/Makefile +++ b/unittest/Makefile @@ -9,7 +9,7 @@ default: $(TARGET) all: default OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c)) -SRCS = frozen/frozen.c ../src/widget.c ../src/screen.c +SRCS = frozen/frozen.c ../src/widget.c ../src/screen.c ../src/helper.c ../src/buttons.c ../src/relays.c ../src/mqtt.c HEADERS = $(wildcard *.h) %.o: %.c $(HEADERS) diff --git a/unittest/mgos.h b/unittest/mgos.h index e88810b..4715af1 100644 --- a/unittest/mgos.h +++ b/unittest/mgos.h @@ -2,6 +2,7 @@ #define __MGOS_H #include +#include #include #include #include diff --git a/unittest/mgos_config.c b/unittest/mgos_config.c new file mode 100644 index 0000000..fc3585c --- /dev/null +++ b/unittest/mgos_config.c @@ -0,0 +1,5 @@ +#include "mgos_config.h" + +const char *mgos_sys_config_get_device_id() { + return "esp8266_ABCDEF"; +} diff --git a/unittest/mgos_config.h b/unittest/mgos_config.h new file mode 100644 index 0000000..e073d08 --- /dev/null +++ b/unittest/mgos_config.h @@ -0,0 +1,6 @@ +#ifndef __MGOS_CONFIG_H +#define __MGOS_CONFIG_H + +const char *mgos_sys_config_get_device_id(); + +#endif // __MGOS_CONFIG_H diff --git a/unittest/mgos_gpio.c b/unittest/mgos_gpio.c new file mode 100644 index 0000000..0bdab03 --- /dev/null +++ b/unittest/mgos_gpio.c @@ -0,0 +1,31 @@ +#include "mgos.h" +#include "mgos_gpio.h" + +static mgos_gpio_int_handler_f s_handler_cb; +static void *s_handler_cb_arg; + +bool mgos_gpio_set_mode(int pin, enum mgos_gpio_mode mode) { + LOG(LL_INFO, ("Setting pin=%d to mode=%d", pin, mode)); + return true; +} + +void mgos_gpio_write(int pin, bool level) { + LOG(LL_INFO, ("Setting pin=%d to %s", pin, level?"HIGH":"LOW")); +} + +bool mgos_gpio_set_button_handler(int pin, enum mgos_gpio_pull_type pull_type, enum mgos_gpio_int_mode int_mode, int debounce_ms, mgos_gpio_int_handler_f cb, void *arg) { + s_handler_cb = cb; + s_handler_cb_arg = arg; + + return true; + (void) debounce_ms; + (void) int_mode; + (void) pull_type; + (void) pin; +} + +void mgos_gpio_inject(int pin) { + if (s_handler_cb) + s_handler_cb(pin, s_handler_cb_arg); +} + diff --git a/unittest/mgos_gpio.h b/unittest/mgos_gpio.h new file mode 100644 index 0000000..c61998f --- /dev/null +++ b/unittest/mgos_gpio.h @@ -0,0 +1,39 @@ +#ifndef __MGOS_GPIO_H +#define __MGOS_GPIO_H + +#include "mgos.h" + +enum mgos_gpio_mode { + MGOS_GPIO_MODE_INPUT = 0, /* input mode */ + MGOS_GPIO_MODE_OUTPUT = 1 /* output mode */ +}; + +enum mgos_gpio_pull_type { + MGOS_GPIO_PULL_NONE = 0, + MGOS_GPIO_PULL_UP = 1, /* pin is pilled to the high voltage */ + MGOS_GPIO_PULL_DOWN = 2 /* pin is pulled to the low voltage */ +}; + +enum mgos_gpio_int_mode { + MGOS_GPIO_INT_NONE = 0, + MGOS_GPIO_INT_EDGE_POS = 1, /* positive edge */ + MGOS_GPIO_INT_EDGE_NEG = 2, /* negative edge */ + MGOS_GPIO_INT_EDGE_ANY = 3, /* any edge - positive or negative */ + MGOS_GPIO_INT_LEVEL_HI = 4, /* high voltage level */ + MGOS_GPIO_INT_LEVEL_LO = 5 /* low voltage level */ +}; + +typedef void (*mgos_gpio_int_handler_f)(int pin, void *arg); + +bool mgos_gpio_set_mode(int pin, enum mgos_gpio_mode mode); +void mgos_gpio_write(int pin, bool level); + +bool mgos_gpio_set_button_handler(int pin, enum mgos_gpio_pull_type pull_type, + enum mgos_gpio_int_mode int_mode, + int debounce_ms, mgos_gpio_int_handler_f cb, + void *arg); + +void mgos_gpio_inject(int pin); + + +#endif // __MGOS_GPIO_H diff --git a/unittest/mgos_mock.c b/unittest/mgos_mock.c index 961274c..1e42fb8 100644 --- a/unittest/mgos_mock.c +++ b/unittest/mgos_mock.c @@ -49,3 +49,7 @@ void mgos_clear_timer(mgos_timer_id id) { return; } + +float mg_time() { + return (float) time(NULL); +} diff --git a/unittest/mgos_mock.h b/unittest/mgos_mock.h index 3b6e4f9..b28d80b 100644 --- a/unittest/mgos_mock.h +++ b/unittest/mgos_mock.h @@ -6,6 +6,8 @@ #include "mgos.h" +#define MGOS_APP "unittest" + // mgos_log enum cs_log_level { LL_NONE = -1, @@ -36,4 +38,6 @@ typedef void (*timer_callback)(void *param); mgos_timer_id mgos_set_timer(int msecs, int flags, timer_callback cb, void *cb_arg); void mgos_clear_timer(mgos_timer_id id); +float mg_time(); + #endif // __MGOS_MOCK_H diff --git a/unittest/mgos_mqtt.c b/unittest/mgos_mqtt.c new file mode 100644 index 0000000..53064d9 --- /dev/null +++ b/unittest/mgos_mqtt.c @@ -0,0 +1,26 @@ +#include "mgos.h" +#include "mgos_mqtt.h" + +uint32_t mqtt_pub_count=0; +uint32_t mqtt_sub_count=0; + +static sub_handler_t s_handler; +static void *s_handler_ud; + +void mgos_mqtt_pub(char *t, char *m, int m_len, int flags, bool persist) { + LOG(LL_INFO, ("Sending topic='%s' msg='%s' persist=%s", t, m, persist?"ON":"OFF")); + mqtt_pub_count++; +} + +void mgos_mqtt_sub(char *t, sub_handler_t cb, void *ud) { + LOG(LL_INFO, ("Subscribing to topic='%s'", t)); + s_handler = cb; + s_handler_ud = ud; +} + +void mgos_mqtt_inject(char *topic, char *msg) { + LOG(LL_INFO, ("Injecting topic='%s' msg='%s'", topic, msg)); + mqtt_sub_count++; + if (s_handler) + s_handler(NULL, topic, strlen(topic), msg, strlen(msg), s_handler_ud); +} diff --git a/unittest/mgos_mqtt.h b/unittest/mgos_mqtt.h new file mode 100644 index 0000000..5e9caea --- /dev/null +++ b/unittest/mgos_mqtt.h @@ -0,0 +1,17 @@ +#ifndef __MGOS_MQTT_H +#define __MGOS_MQTT_H + +#include "mgos.h" + +struct mg_connection; + +typedef void (*sub_handler_t)(struct mg_connection *nc, const char *topic, + int topic_len, const char *msg, int msg_len, + void *ud); + +void mgos_mqtt_pub(char *t, char *m, int m_len, int flags, bool persist); +void mgos_mqtt_sub(char *t, sub_handler_t cb, void *ud); + +void mgos_mqtt_inject(char *topic, char *msg); + +#endif // __MGOS_MQTT_H