Initial checkin.
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
build/
|
||||
deps/
|
12
README.md
Normal file
12
README.md
Normal file
@ -0,0 +1,12 @@
|
||||
## GPIO for 3-gang
|
||||
|
||||
* 0: Top green LED
|
||||
* 16: Top blue LED
|
||||
* 14: Middle blue LED
|
||||
* 2: Bottom blue LED
|
||||
* 12: Top button
|
||||
* 3: Middle button
|
||||
* 5: Bottom button
|
||||
* 13: Top relay (L1)
|
||||
* 4: Middle relay (L2)
|
||||
* 15: Bottom relay (L3)
|
9
fs/3gang.json
Normal file
9
fs/3gang.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "3 Gang",
|
||||
"status_led": 0,
|
||||
"channels": [
|
||||
{ "led": 2, "relay": 15, "button": 5 },
|
||||
{ "led": 14, "relay": 4, "button": 3 },
|
||||
{ "led": 16, "relay": 13, "button": 12 }
|
||||
]
|
||||
}
|
23
fs/mqtt.pem
Normal file
23
fs/mqtt.pem
Normal file
@ -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-----
|
32
include/main.h
Normal file
32
include/main.h
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef __MAIN_H
|
||||
#define __MAIN_H
|
||||
|
||||
#include "mgos.h"
|
||||
#include "mgos_gpio.h"
|
||||
|
||||
#define CHANNEL_CHANGE_COOLDOWN_SECONDS 0.250
|
||||
#define CHANNEL_MAX 16
|
||||
#define GREEN_LED_GPIO 0
|
||||
#define GPIO_INVALID 255
|
||||
#define GPIO_MIN 0
|
||||
#define GPIO_MAX 16
|
||||
|
||||
struct channel_t {
|
||||
uint8_t button_gpio;
|
||||
uint8_t led_gpio;
|
||||
uint8_t relay_gpio;
|
||||
bool relay_state;
|
||||
double button_last_change;
|
||||
};
|
||||
|
||||
void led_green_blink();
|
||||
|
||||
bool channel_init(const char *fn);
|
||||
uint8_t channel_gpio_by_idx(int idx);
|
||||
uint8_t channel_idx_by_gpio(int gpio);
|
||||
void channel_set(int idx, bool state);
|
||||
bool channel_get(int idx);
|
||||
void channel_report(int idx, char *msg, int msg_len);
|
||||
void channel_handler(int gpio, void *arg);
|
||||
|
||||
#endif // __MAIN_H
|
14
include/mqtt.h
Normal file
14
include/mqtt.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef __MQTT_H
|
||||
#define __MQTT_H
|
||||
|
||||
#include "mgos.h"
|
||||
#include "mgos_mqtt.h"
|
||||
|
||||
#define MQTT_TOPIC_PREFIX ""
|
||||
#define MQTT_TOPIC_BROADCAST_CMD "/mongoose/broadcast"
|
||||
#define MQTT_TOPIC_BROADCAST_STAT "/mongoose/broadcast/stat"
|
||||
|
||||
void mqtt_init();
|
||||
void mqtt_publish_stat(const char *stat, const char *msg);
|
||||
|
||||
#endif // __MQTT_H
|
10
include/rpc.h
Normal file
10
include/rpc.h
Normal file
@ -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
|
29
libs/ota-http-client/include/mgos_ota_http_client.h
Normal file
29
libs/ota-http-client/include/mgos_ota_http_client.h
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* An updater implementation that fetches FW from the given URL.
|
||||
*/
|
||||
|
||||
#ifndef CS_MOS_LIBS_OTA_HTTP_CLIENT_SRC_MGOS_OTA_HTTP_CLIENT_H_
|
||||
#define CS_MOS_LIBS_OTA_HTTP_CLIENT_SRC_MGOS_OTA_HTTP_CLIENT_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "mgos_updater_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#if MGOS_ENABLE_UPDATER
|
||||
bool mgos_ota_http_client_init(void);
|
||||
|
||||
void mgos_ota_http_start(struct update_context *ctx, const char *url);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_MOS_LIBS_OTA_HTTP_CLIENT_SRC_MGOS_OTA_HTTP_CLIENT_H_ */
|
28
libs/ota-http-client/mos.yml
Normal file
28
libs/ota-http-client/mos.yml
Normal file
@ -0,0 +1,28 @@
|
||||
author: mongoose-os
|
||||
description: Implements Mongoose OS OTA HTTP client
|
||||
type: lib
|
||||
version: 1.18
|
||||
|
||||
sources:
|
||||
- src
|
||||
includes:
|
||||
- include
|
||||
|
||||
config_schema:
|
||||
- ["update.url", "s", {title : "Fetch updates form here"}]
|
||||
- ["update.interval", "i", {title : "Check for updates this often"}]
|
||||
|
||||
# Default CA bundle for updating from mongoose-os.com (and any other site that uses LetsEncrypt) and S3.
|
||||
- ["update.ssl_ca_file", "s", "ca.pem", {title : "TLS CA file"}]
|
||||
- ["update.ssl_client_cert_file", "s", {title: "TLS client cert file"}]
|
||||
- ["update.ssl_server_name", "s", {title : "TLS Server Name"}]
|
||||
|
||||
tags:
|
||||
- c
|
||||
- ota
|
||||
- http
|
||||
|
||||
build_vars:
|
||||
MGOS_ENABLE_UPDATER: 1
|
||||
|
||||
manifest_version: 2017-09-29
|
187
libs/ota-http-client/src/mgos_ota_http_client.c
Normal file
187
libs/ota-http-client/src/mgos_ota_http_client.c
Normal file
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mgos_ota_http_client.h"
|
||||
|
||||
#include "common/cs_dbg.h"
|
||||
#include "mgos_hal.h"
|
||||
#include "mgos_mongoose.h"
|
||||
#include "mgos_config.h"
|
||||
#include "mgos_ro_vars.h"
|
||||
#include "mgos_timers.h"
|
||||
#include "mgos_utils.h"
|
||||
|
||||
#if MGOS_ENABLE_UPDATER
|
||||
|
||||
static void fw_download_handler(struct mg_connection *c, int ev, void *p,
|
||||
void *user_data) {
|
||||
struct mbuf *io = &c->recv_mbuf;
|
||||
struct update_context *ctx = (struct update_context *) user_data;
|
||||
int res = 0;
|
||||
struct mg_str *loc;
|
||||
(void) p;
|
||||
|
||||
switch (ev) {
|
||||
case MG_EV_CONNECT: {
|
||||
int result = *((int *) p);
|
||||
if (result != 0) LOG(LL_ERROR, ("connect error: %d", result));
|
||||
break;
|
||||
}
|
||||
case MG_EV_RECV: {
|
||||
if (ctx->file_size == 0) {
|
||||
LOG(LL_DEBUG, ("Looking for HTTP header"));
|
||||
struct http_message hm;
|
||||
int parsed = mg_parse_http(io->buf, io->len, &hm, 0);
|
||||
if (parsed <= 0) {
|
||||
return;
|
||||
}
|
||||
if (hm.resp_code != 200) {
|
||||
if (hm.resp_code == 304) {
|
||||
ctx->result = 1;
|
||||
ctx->need_reboot = false;
|
||||
ctx->status_msg = "Not Modified";
|
||||
updater_finish(ctx);
|
||||
} else if ((hm.resp_code == 301 || hm.resp_code == 302) &&
|
||||
(loc = mg_get_http_header(&hm, "Location")) != NULL) {
|
||||
/* NUL-terminate the URL. Every header must be followed by \r\n,
|
||||
* so there is deifnitely space there. */
|
||||
((char *) loc->p)[loc->len] = '\0';
|
||||
/* We were told to look elsewhere. Detach update context from this
|
||||
* connection so that it doesn't get finalized when it's closed. */
|
||||
mgos_ota_http_start(ctx, loc->p);
|
||||
c->user_data = NULL;
|
||||
} else {
|
||||
ctx->result = -hm.resp_code;
|
||||
ctx->need_reboot = false;
|
||||
ctx->status_msg = "Invalid HTTP response code";
|
||||
updater_finish(ctx);
|
||||
}
|
||||
c->flags |= MG_F_CLOSE_IMMEDIATELY;
|
||||
return;
|
||||
}
|
||||
if (hm.body.len != 0) {
|
||||
LOG(LL_DEBUG, ("HTTP header: file size: %d", (int) hm.body.len));
|
||||
if (hm.body.len == (size_t) ~0) {
|
||||
LOG(LL_ERROR, ("Invalid content-length, perhaps chunked-encoding"));
|
||||
ctx->status_msg =
|
||||
"Invalid content-length, perhaps chunked-encoding";
|
||||
c->flags |= MG_F_CLOSE_IMMEDIATELY;
|
||||
break;
|
||||
} else {
|
||||
ctx->file_size = hm.body.len;
|
||||
}
|
||||
|
||||
mbuf_remove(io, parsed);
|
||||
}
|
||||
}
|
||||
|
||||
if (io->len != 0) {
|
||||
res = updater_process(ctx, io->buf, io->len);
|
||||
mbuf_remove(io, io->len);
|
||||
|
||||
if (res == 0) {
|
||||
if (is_write_finished(ctx)) res = updater_finalize(ctx);
|
||||
if (res == 0) {
|
||||
/* Need more data, everything is OK */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (res < 0) {
|
||||
/* Error */
|
||||
LOG(LL_ERROR, ("Update error: %d %s", ctx->result, ctx->status_msg));
|
||||
}
|
||||
c->flags |= MG_F_CLOSE_IMMEDIATELY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MG_EV_CLOSE: {
|
||||
if (ctx == NULL) break;
|
||||
|
||||
if (is_write_finished(ctx)) updater_finalize(ctx);
|
||||
|
||||
if (!is_update_finished(ctx)) {
|
||||
/* Update failed or connection was terminated by server */
|
||||
if (ctx->status_msg == NULL) ctx->status_msg = "Update failed";
|
||||
ctx->result = -1;
|
||||
} else if (is_reboot_required(ctx)) {
|
||||
LOG(LL_INFO, ("Rebooting device"));
|
||||
mgos_system_restart_after(100);
|
||||
}
|
||||
updater_finish(ctx);
|
||||
updater_context_free(ctx);
|
||||
c->user_data = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mgos_ota_http_start(struct update_context *ctx, const char *url) {
|
||||
LOG(LL_INFO, ("Update URL: %s, ct: %d, isv? %d", url,
|
||||
ctx->fctx.commit_timeout, ctx->ignore_same_version));
|
||||
|
||||
struct mg_connect_opts opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
|
||||
#if MG_ENABLE_SSL
|
||||
if (strlen(url) > 8 && strncmp(url, "https://", 8) == 0) {
|
||||
opts.ssl_server_name = mgos_sys_config_get_update_ssl_server_name();
|
||||
opts.ssl_ca_cert = mgos_sys_config_get_update_ssl_ca_file();
|
||||
opts.ssl_cert = mgos_sys_config_get_update_ssl_client_cert_file();
|
||||
}
|
||||
#endif
|
||||
|
||||
char ehb[150];
|
||||
char *extra_headers = ehb;
|
||||
mg_asprintf(&extra_headers, sizeof(ehb),
|
||||
"X-MGOS-Device-ID: %s %s\r\n"
|
||||
"X-MGOS-FW-Version: %s %s %s\r\n",
|
||||
(mgos_sys_config_get_device_id() ? mgos_sys_config_get_device_id() : "-"),
|
||||
mgos_sys_ro_vars_get_mac_address(),
|
||||
mgos_sys_ro_vars_get_arch(),
|
||||
mgos_sys_ro_vars_get_fw_version(),
|
||||
mgos_sys_ro_vars_get_fw_id());
|
||||
|
||||
struct mg_connection *c = mg_connect_http_opt(
|
||||
mgos_get_mgr(), fw_download_handler, ctx, opts, url, extra_headers, NULL);
|
||||
|
||||
if (extra_headers != ehb) free(extra_headers);
|
||||
|
||||
if (c == NULL) {
|
||||
LOG(LL_ERROR, ("Failed to connect to %s", url));
|
||||
ctx->result = -10;
|
||||
ctx->need_reboot = false;
|
||||
ctx->status_msg = "Failed to connect";
|
||||
updater_finish(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->nc = c;
|
||||
}
|
||||
|
||||
static void mgos_ota_timer_cb(void *arg) {
|
||||
const struct mgos_config_update *mcu = mgos_sys_config_get_update();
|
||||
if (mcu->url == NULL) return;
|
||||
struct update_context *ctx = updater_context_create();
|
||||
if (ctx == NULL) return;
|
||||
ctx->ignore_same_version = true;
|
||||
ctx->fctx.commit_timeout = mcu->commit_timeout;
|
||||
mgos_ota_http_start(ctx, mcu->url);
|
||||
|
||||
(void) arg;
|
||||
}
|
||||
|
||||
bool mgos_ota_http_client_init(void) {
|
||||
const struct mgos_config_update *mcu = mgos_sys_config_get_update();
|
||||
if (mcu->url != NULL && mcu->interval > 0) {
|
||||
LOG(LL_INFO,
|
||||
("Updates from %s, every %d seconds", mcu->url, mcu->interval));
|
||||
mgos_set_timer(mcu->interval * 1000, true /* repeat */, mgos_ota_timer_cb,
|
||||
mcu->url);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* MGOS_ENABLE_UPDATER */
|
23
libs/ota-http-server/include/mgos_ota_http_server.h
Normal file
23
libs/ota-http-server/include/mgos_ota_http_server.h
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef CS_MOS_LIBS_OTA_HTTP_SERVER_SRC_MGOS_OTA_HTTP_SERVER_H_
|
||||
#define CS_MOS_LIBS_OTA_HTTP_SERVER_SRC_MGOS_OTA_HTTP_SERVER_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#if MGOS_ENABLE_UPDATER
|
||||
bool mgos_ota_http_server_init(void);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_MOS_LIBS_OTA_HTTP_SERVER_SRC_MGOS_OTA_HTTP_SERVER_H_ */
|
22
libs/ota-http-server/mos.yml
Normal file
22
libs/ota-http-server/mos.yml
Normal file
@ -0,0 +1,22 @@
|
||||
author: mongoose-os
|
||||
description: Implements Mongoose OS OTA HTTP server
|
||||
type: lib
|
||||
version: 1.18
|
||||
|
||||
sources:
|
||||
- src
|
||||
includes:
|
||||
- include
|
||||
config_schema:
|
||||
- ["update.enable_post", "b", true, {title : "Enable POST updates"}]
|
||||
|
||||
libs:
|
||||
- origin: https://github.com/mongoose-os-libs/http-server
|
||||
- origin: libs/ota-http-client
|
||||
|
||||
tags:
|
||||
- c
|
||||
- ota
|
||||
- http
|
||||
|
||||
manifest_version: 2017-09-29
|
238
libs/ota-http-server/src/mgos_ota_http_server.c
Normal file
238
libs/ota-http-server/src/mgos_ota_http_server.c
Normal file
@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mgos_ota_http_server.h"
|
||||
#include "mgos_ota_http_client.h"
|
||||
|
||||
#include "mgos_http_server.h"
|
||||
|
||||
#include "common/cs_dbg.h"
|
||||
#include "mgos_hal.h"
|
||||
#include "mgos_mongoose.h"
|
||||
#include "mgos_config.h"
|
||||
#include "mgos_timers.h"
|
||||
#include "mgos_utils.h"
|
||||
|
||||
static void handle_update_post(struct mg_connection *c, int ev, void *p) {
|
||||
struct mg_http_multipart_part *mp = (struct mg_http_multipart_part *) p;
|
||||
struct update_context *ctx = (struct update_context *) c->user_data;
|
||||
if (ctx == NULL && ev != MG_EV_HTTP_MULTIPART_REQUEST) return;
|
||||
switch (ev) {
|
||||
case MG_EV_HTTP_MULTIPART_REQUEST: {
|
||||
ctx = updater_context_create();
|
||||
if (ctx != NULL) {
|
||||
ctx->nc = c;
|
||||
c->user_data = ctx;
|
||||
} else {
|
||||
c->flags |= MG_F_CLOSE_IMMEDIATELY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MG_EV_HTTP_PART_BEGIN: {
|
||||
LOG(LL_DEBUG, ("MG_EV_HTTP_PART_BEGIN: %p %s %s", ctx, mp->var_name,
|
||||
mp->file_name));
|
||||
/* We use ctx->file_name as a temp buffer for non-file variable values. */
|
||||
if (mp->file_name[0] == '\0') {
|
||||
ctx->file_name[0] = '\0';
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MG_EV_HTTP_PART_DATA: {
|
||||
LOG(LL_DEBUG, ("MG_EV_HTTP_PART_DATA: %p %s %s %d", ctx, mp->var_name,
|
||||
mp->file_name, (int) mp->data.len));
|
||||
|
||||
if (mp->file_name[0] == '\0') {
|
||||
/* It's a non-file form variable. */
|
||||
size_t l = strlen(ctx->file_name);
|
||||
size_t avail = sizeof(ctx->file_name) - l - 1;
|
||||
strncat(ctx->file_name, mp->data.p, MIN(mp->data.len, avail));
|
||||
break;
|
||||
} else if (!is_update_finished(ctx)) {
|
||||
updater_process(ctx, mp->data.p, mp->data.len);
|
||||
LOG(LL_DEBUG, ("updater_process res: %d", ctx->result));
|
||||
} else {
|
||||
/* Don't close connection just yet, not all browsers like that. */
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MG_EV_HTTP_PART_END: {
|
||||
LOG(LL_DEBUG, ("MG_EV_HTTP_PART_END: %p %s %s %d", ctx, mp->var_name,
|
||||
mp->file_name, mp->status));
|
||||
/* Part finished with an error. REQUEST_END will follow. */
|
||||
if (mp->status < 0) break;
|
||||
if (mp->file_name[0] == '\0') {
|
||||
/* It's a non-file form variable. Value is in ctx->file_name. */
|
||||
LOG(LL_DEBUG, ("Got var: %s=%s", mp->var_name, ctx->file_name));
|
||||
/* Commit timeout can be set after flashing. */
|
||||
if (strcmp(mp->var_name, "commit_timeout") == 0) {
|
||||
ctx->fctx.commit_timeout = atoi(ctx->file_name);
|
||||
}
|
||||
} else {
|
||||
/* End of the fw part, but there may still be parts with vars to follow,
|
||||
* which can modify settings (that can be applied post-flashing). */
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MG_EV_HTTP_MULTIPART_REQUEST_END: {
|
||||
LOG(LL_DEBUG,
|
||||
("MG_EV_HTTP_MULTIPART_REQUEST_END: %p %d", ctx, mp->status));
|
||||
/* Whatever happens, this is the last thing we do. */
|
||||
c->flags |= MG_F_SEND_AND_CLOSE;
|
||||
|
||||
if (ctx == NULL) break;
|
||||
if (is_write_finished(ctx)) updater_finalize(ctx);
|
||||
if (!is_update_finished(ctx)) {
|
||||
ctx->result = -1;
|
||||
ctx->status_msg = "Update aborted";
|
||||
updater_finish(ctx);
|
||||
}
|
||||
if (mp->status < 0) {
|
||||
/* mp->status < 0 means connection is dead, do not send reply */
|
||||
} else {
|
||||
int code = (ctx->result > 0 ? 200 : 400);
|
||||
mg_send_response_line(c, code,
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n");
|
||||
mg_printf(c, "%s\r\n",
|
||||
ctx->status_msg ? ctx->status_msg : "Unknown error");
|
||||
if (is_reboot_required(ctx)) {
|
||||
LOG(LL_INFO, ("Rebooting device"));
|
||||
mgos_system_restart_after(101);
|
||||
}
|
||||
c->flags |= MG_F_SEND_AND_CLOSE;
|
||||
}
|
||||
updater_context_free(ctx);
|
||||
c->user_data = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct mg_connection *s_update_request_conn;
|
||||
|
||||
static void mgos_ota_result_cb(struct update_context *ctx) {
|
||||
if (ctx != updater_context_get_current()) return;
|
||||
if (s_update_request_conn != NULL) {
|
||||
int code = (ctx->result > 0 ? 200 : 500);
|
||||
mg_send_response_line(s_update_request_conn, code,
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n");
|
||||
mg_printf(s_update_request_conn, "(%d) %s\r\n", ctx->result,
|
||||
ctx->status_msg);
|
||||
s_update_request_conn->flags |= MG_F_SEND_AND_CLOSE;
|
||||
s_update_request_conn = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void update_handler(struct mg_connection *c, int ev, void *ev_data,
|
||||
void *user_data) {
|
||||
switch (ev) {
|
||||
case MG_EV_HTTP_MULTIPART_REQUEST:
|
||||
case MG_EV_HTTP_PART_BEGIN:
|
||||
case MG_EV_HTTP_PART_DATA:
|
||||
case MG_EV_HTTP_PART_END:
|
||||
case MG_EV_HTTP_MULTIPART_REQUEST_END: {
|
||||
if (mgos_sys_config_get_update_enable_post()) {
|
||||
handle_update_post(c, ev, ev_data);
|
||||
} else {
|
||||
mg_send_response_line(c, 400,
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n");
|
||||
mg_printf(c, "POST updates are disabled.");
|
||||
c->flags |= MG_F_SEND_AND_CLOSE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case MG_EV_HTTP_REQUEST: {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
if (updater_context_get_current() != NULL) {
|
||||
mg_send_response_line(c, 409,
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n");
|
||||
mg_printf(c, "Another update is in progress.\r\n");
|
||||
c->flags |= MG_F_SEND_AND_CLOSE;
|
||||
return;
|
||||
}
|
||||
const struct mgos_config_update *mcu = mgos_sys_config_get_update();
|
||||
char *url = mcu->url;
|
||||
int commit_timeout = mcu->commit_timeout;
|
||||
bool ignore_same_version = true;
|
||||
struct mg_str params =
|
||||
(mg_vcmp(&hm->method, "POST") == 0 ? hm->body : hm->query_string);
|
||||
size_t buf_len = params.len;
|
||||
char *buf = calloc(params.len, 1), *p = buf;
|
||||
int len = mg_get_http_var(¶ms, "url", p, buf_len);
|
||||
if (len > 0) {
|
||||
url = p;
|
||||
p += len + 1;
|
||||
buf_len -= len + 1;
|
||||
}
|
||||
len = mg_get_http_var(¶ms, "commit_timeout", p, buf_len);
|
||||
if (len > 0) {
|
||||
commit_timeout = atoi(p);
|
||||
}
|
||||
len = mg_get_http_var(¶ms, "ignore_same_version", p, buf_len);
|
||||
if (len > 0) {
|
||||
ignore_same_version = (atoi(p) > 0);
|
||||
}
|
||||
if (url != NULL) {
|
||||
s_update_request_conn = c;
|
||||
struct update_context *ctx = updater_context_create();
|
||||
if (ctx == NULL) {
|
||||
mg_send_response_line(c, 409,
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n");
|
||||
mg_printf(c, "Failed to create updater context.\r\n");
|
||||
c->flags |= MG_F_SEND_AND_CLOSE;
|
||||
return;
|
||||
}
|
||||
ctx->ignore_same_version = ignore_same_version;
|
||||
ctx->fctx.commit_timeout = commit_timeout;
|
||||
ctx->result_cb = mgos_ota_result_cb;
|
||||
mgos_ota_http_start(ctx, url);
|
||||
|
||||
} else {
|
||||
mg_send_response_line(c, 400,
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n");
|
||||
mg_printf(c, "Update URL not specified and none is configured.\r\n");
|
||||
c->flags |= MG_F_SEND_AND_CLOSE;
|
||||
}
|
||||
free(buf);
|
||||
break;
|
||||
}
|
||||
case MG_EV_CLOSE: {
|
||||
if (s_update_request_conn == c) {
|
||||
/* Client went away while waiting for response. */
|
||||
s_update_request_conn = NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
(void) user_data;
|
||||
}
|
||||
|
||||
static void update_action_handler(struct mg_connection *c, int ev, void *p,
|
||||
void *user_data) {
|
||||
if (ev != MG_EV_HTTP_REQUEST) return;
|
||||
struct http_message *hm = (struct http_message *) p;
|
||||
bool is_commit = (mg_vcmp(&hm->uri, "/update/commit") == 0);
|
||||
bool ok =
|
||||
(is_commit ? mgos_upd_commit() : mgos_upd_revert(false /* reboot */));
|
||||
mg_send_response_line(c, (ok ? 200 : 400),
|
||||
"Content-Type: text/html\r\n"
|
||||
"Connection: close");
|
||||
mg_printf(c, "\r\n%s\r\n", (ok ? "Ok" : "Error"));
|
||||
c->flags |= MG_F_SEND_AND_CLOSE;
|
||||
if (ok && !is_commit) mgos_system_restart_after(100);
|
||||
(void) user_data;
|
||||
}
|
||||
|
||||
bool mgos_ota_http_server_init(void) {
|
||||
mgos_register_http_endpoint("/update/commit", update_action_handler, NULL);
|
||||
mgos_register_http_endpoint("/update/revert", update_action_handler, NULL);
|
||||
mgos_register_http_endpoint("/update", update_handler, NULL);
|
||||
return true;
|
||||
}
|
26
libs/rpc-service-ota/include/mgos_rpc_service_ota.h
Normal file
26
libs/rpc-service-ota/include/mgos_rpc_service_ota.h
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef CS_FW_SRC_MGOS_UPDATER_MG_RPC_H_
|
||||
#define CS_FW_SRC_MGOS_UPDATER_MG_RPC_H_
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include "common/mg_str.h"
|
||||
#include "mgos_features.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
bool mgos_rpc_service_ota_init(void);
|
||||
void mgos_updater_rpc_finish(int error_code, int64_t id,
|
||||
const struct mg_str src);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_FW_SRC_MGOS_UPDATER_MG_RPC_H_ */
|
17
libs/rpc-service-ota/mos.yml
Normal file
17
libs/rpc-service-ota/mos.yml
Normal file
@ -0,0 +1,17 @@
|
||||
author: mongoose-os
|
||||
description: Support for Over-The-Air update via RPC
|
||||
type: lib
|
||||
version: 1.18
|
||||
manifest_version: 2017-09-29
|
||||
sources:
|
||||
- src
|
||||
includes:
|
||||
- include
|
||||
libs:
|
||||
- origin: https://github.com/mongoose-os-libs/rpc-common
|
||||
- origin: libs/ota-http-client
|
||||
tags:
|
||||
- c
|
||||
- ota
|
||||
- rpc
|
||||
- updater
|
227
libs/rpc-service-ota/src/mgos_rpc_service_ota.c
Normal file
227
libs/rpc-service-ota/src/mgos_rpc_service_ota.c
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mgos_rpc_service_ota.h"
|
||||
|
||||
#include "mg_rpc.h"
|
||||
#include "mgos_rpc.h"
|
||||
#include "mgos_ota_http_client.h"
|
||||
|
||||
#include "common/cs_dbg.h"
|
||||
#include "common/mg_str.h"
|
||||
#include "mgos_mongoose.h"
|
||||
#include "mgos_updater_common.h"
|
||||
#include "mgos_utils.h"
|
||||
|
||||
static struct mg_rpc_request_info *s_update_req;
|
||||
|
||||
static void mg_rpc_updater_result(struct update_context *ctx) {
|
||||
if (s_update_req == NULL) return;
|
||||
mg_rpc_send_errorf(s_update_req, (ctx->result > 0 ? 0 : -1), ctx->status_msg);
|
||||
s_update_req = NULL;
|
||||
}
|
||||
|
||||
static void handle_update_req(struct mg_rpc_request_info *ri, void *cb_arg,
|
||||
struct mg_rpc_frame_info *fi,
|
||||
struct mg_str args) {
|
||||
char *blob_url = NULL;
|
||||
struct json_token url_tok = JSON_INVALID_TOKEN;
|
||||
int commit_timeout = 0;
|
||||
struct update_context *ctx = NULL;
|
||||
|
||||
LOG(LL_DEBUG, ("Update request received: %.*s", (int) args.len, args.p));
|
||||
|
||||
const char *reply = "Malformed request";
|
||||
|
||||
if (args.len == 0) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
json_scanf(args.p, args.len, ri->args_fmt, &url_tok, &commit_timeout);
|
||||
|
||||
if (url_tok.len == 0 || url_tok.type != JSON_TYPE_STRING) goto clean;
|
||||
|
||||
LOG(LL_DEBUG, ("URL: %.*s commit_timeout: %d", url_tok.len, url_tok.ptr,
|
||||
commit_timeout));
|
||||
|
||||
/*
|
||||
* If user setup callback for updater, just call it.
|
||||
* User can start update with Sys.updater.start()
|
||||
*/
|
||||
|
||||
blob_url = calloc(1, url_tok.len + 1);
|
||||
if (blob_url == NULL) {
|
||||
LOG(LL_ERROR, ("Out of memory"));
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(blob_url, url_tok.ptr, url_tok.len);
|
||||
|
||||
ctx = updater_context_create();
|
||||
if (ctx == NULL) {
|
||||
reply = "Failed to init updater";
|
||||
goto clean;
|
||||
}
|
||||
ctx->fctx.commit_timeout = commit_timeout;
|
||||
ctx->result_cb = mg_rpc_updater_result;
|
||||
s_update_req = ri;
|
||||
|
||||
mgos_ota_http_start(ctx, blob_url);
|
||||
free(blob_url);
|
||||
return;
|
||||
|
||||
clean:
|
||||
if (blob_url != NULL) free(blob_url);
|
||||
LOG(LL_ERROR, ("Failed to start update: %s", reply));
|
||||
mg_rpc_send_errorf(ri, -1, reply);
|
||||
ri = NULL;
|
||||
(void) cb_arg;
|
||||
(void) fi;
|
||||
}
|
||||
|
||||
static void handle_commit_req(struct mg_rpc_request_info *ri, void *cb_arg,
|
||||
struct mg_rpc_frame_info *fi,
|
||||
struct mg_str args) {
|
||||
if (mgos_upd_commit()) {
|
||||
mg_rpc_send_responsef(ri, NULL);
|
||||
} else {
|
||||
mg_rpc_send_errorf(ri, -1, NULL);
|
||||
}
|
||||
ri = NULL;
|
||||
(void) cb_arg;
|
||||
(void) fi;
|
||||
(void) args;
|
||||
}
|
||||
|
||||
static void handle_revert_req(struct mg_rpc_request_info *ri, void *cb_arg,
|
||||
struct mg_rpc_frame_info *fi,
|
||||
struct mg_str args) {
|
||||
if (mgos_upd_revert(false /* reboot */)) {
|
||||
mg_rpc_send_responsef(ri, NULL);
|
||||
mgos_system_restart_after(100);
|
||||
} else {
|
||||
mg_rpc_send_errorf(ri, -1, NULL);
|
||||
}
|
||||
ri = NULL;
|
||||
(void) cb_arg;
|
||||
(void) fi;
|
||||
(void) args;
|
||||
}
|
||||
|
||||
static void handle_create_snapshot_req(struct mg_rpc_request_info *ri,
|
||||
void *cb_arg,
|
||||
struct mg_rpc_frame_info *fi,
|
||||
struct mg_str args) {
|
||||
const char *err_msg = NULL;
|
||||
int ret = -1;
|
||||
if (mgos_upd_is_committed()) {
|
||||
ret = mgos_upd_create_snapshot();
|
||||
if (ret >= 0) {
|
||||
bool set_as_revert = false;
|
||||
int commit_timeout = -1;
|
||||
json_scanf(args.p, args.len, ri->args_fmt, &set_as_revert,
|
||||
&commit_timeout);
|
||||
if (set_as_revert) {
|
||||
struct mgos_upd_boot_state bs;
|
||||
if (mgos_upd_boot_get_state(&bs)) {
|
||||
bs.is_committed = false;
|
||||
bs.revert_slot = ret;
|
||||
if (mgos_upd_boot_set_state(&bs)) {
|
||||
if (commit_timeout >= 0 &&
|
||||
!mgos_upd_set_commit_timeout(commit_timeout)) {
|
||||
ret = -4;
|
||||
err_msg = "Failed to set commit timeout";
|
||||
}
|
||||
} else {
|
||||
ret = -3;
|
||||
err_msg = "Failed to set boot state";
|
||||
}
|
||||
} else {
|
||||
ret = -2;
|
||||
err_msg = "Failed to get boot state";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err_msg = "Failed to create snapshot";
|
||||
}
|
||||
} else {
|
||||
ret = -1;
|
||||
err_msg = "Cannot create snapshots in uncommitted state";
|
||||
}
|
||||
if (ret >= 0) {
|
||||
mg_rpc_send_responsef(ri, "{slot: %d}", ret);
|
||||
} else {
|
||||
mg_rpc_send_errorf(ri, ret, err_msg);
|
||||
}
|
||||
(void) cb_arg;
|
||||
(void) fi;
|
||||
}
|
||||
|
||||
static void handle_get_boot_state_req(struct mg_rpc_request_info *ri,
|
||||
void *cb_arg,
|
||||
struct mg_rpc_frame_info *fi,
|
||||
struct mg_str args) {
|
||||
struct mgos_upd_boot_state bs;
|
||||
if (!mgos_upd_boot_get_state(&bs)) {
|
||||
mg_rpc_send_errorf(ri, -1, NULL);
|
||||
} else {
|
||||
mg_rpc_send_responsef(ri,
|
||||
"{active_slot: %d, is_committed: %B, revert_slot: "
|
||||
"%d, commit_timeout: %d}",
|
||||
bs.active_slot, bs.is_committed, bs.revert_slot,
|
||||
mgos_upd_get_commit_timeout());
|
||||
}
|
||||
(void) cb_arg;
|
||||
(void) fi;
|
||||
(void) args;
|
||||
}
|
||||
|
||||
static void handle_set_boot_state_req(struct mg_rpc_request_info *ri,
|
||||
void *cb_arg,
|
||||
struct mg_rpc_frame_info *fi,
|
||||
struct mg_str args) {
|
||||
int ret = 0;
|
||||
struct mgos_upd_boot_state bs;
|
||||
if (mgos_upd_boot_get_state(&bs)) {
|
||||
int commit_timeout = -1;
|
||||
if (json_scanf(args.p, args.len, ri->args_fmt, &bs.active_slot,
|
||||
&bs.is_committed, &bs.revert_slot, &commit_timeout) > 0) {
|
||||
ret = mgos_upd_boot_set_state(&bs) ? 0 : -3;
|
||||
if (ret == 0 && commit_timeout >= 0) {
|
||||
ret = mgos_upd_set_commit_timeout(commit_timeout) ? 0 : -4;
|
||||
}
|
||||
} else {
|
||||
ret = -2;
|
||||
}
|
||||
} else {
|
||||
ret = -1;
|
||||
}
|
||||
if (ret == 0) {
|
||||
mg_rpc_send_responsef(ri, NULL);
|
||||
} else {
|
||||
mg_rpc_send_errorf(ri, ret, NULL);
|
||||
}
|
||||
(void) cb_arg;
|
||||
(void) fi;
|
||||
}
|
||||
|
||||
bool mgos_rpc_service_ota_init(void) {
|
||||
struct mg_rpc *mg_rpc = mgos_rpc_get_global();
|
||||
if (mg_rpc == NULL) return true;
|
||||
mg_rpc_add_handler(mg_rpc, "OTA.Update", "{url: %T, commit_timeout: %d}",
|
||||
handle_update_req, NULL);
|
||||
mg_rpc_add_handler(mg_rpc, "OTA.Commit", "", handle_commit_req, NULL);
|
||||
mg_rpc_add_handler(mg_rpc, "OTA.Revert", "", handle_revert_req, NULL);
|
||||
mg_rpc_add_handler(mg_rpc, "OTA.CreateSnapshot",
|
||||
"{set_as_revert: %B, commit_timeout: %d}",
|
||||
handle_create_snapshot_req, NULL);
|
||||
mg_rpc_add_handler(mg_rpc, "OTA.GetBootState", "", handle_get_boot_state_req,
|
||||
NULL);
|
||||
mg_rpc_add_handler(mg_rpc, "OTA.SetBootState",
|
||||
"{active_slot: %d, is_committed: %B, revert_slot: %d, "
|
||||
"commit_timeout: %d}",
|
||||
handle_set_boot_state_req, NULL);
|
||||
return true;
|
||||
}
|
62
mos.yml
Normal file
62
mos.yml
Normal file
@ -0,0 +1,62 @@
|
||||
author: Pim van Pelt <pim@ipng.nl>
|
||||
description: A Mongoose-OS Light Switch
|
||||
version: 1.0
|
||||
platform: esp8266
|
||||
|
||||
libs_version: ${mos.version}
|
||||
modules_version: ${mos.version}
|
||||
mongoose_os_version: ${mos.version}
|
||||
|
||||
tags:
|
||||
- c
|
||||
|
||||
# List of files / directories with C sources. No slashes at the end of dir names.
|
||||
sources:
|
||||
- src
|
||||
|
||||
fs:
|
||||
- fs/mqtt.pem
|
||||
|
||||
includes:
|
||||
- include
|
||||
|
||||
# List of dirs. Files from these dirs will be copied to the device filesystem
|
||||
filesystem:
|
||||
- fs
|
||||
|
||||
config_schema:
|
||||
- ["wifi.ap.enable", false]
|
||||
- ["wifi.sta.enable", true]
|
||||
- ["wifi.sta.ssid", "dapches-iot"]
|
||||
- ["wifi.sta.pass", "marielle"]
|
||||
- ["debug.stderr_uart", -1]
|
||||
- ["debug.stdout_uart", -1]
|
||||
- ["debug.udp_log_addr", "192.168.2.176:1234"]
|
||||
- ["mqtt.enable", true]
|
||||
- ["http.enable", true]
|
||||
- ["mqtt.server", "mqtt.ipng.nl:8883"]
|
||||
- ["mqtt.ssl_ca_cert", "mqtt.pem"]
|
||||
- ["rpc.mqtt.enable", true]
|
||||
- ["app", "o", {title: "APP settings"}]
|
||||
- ["app.hostname", "s", {title: "Device hostname"}]
|
||||
- ["app.hostname", "lightswitch0"]
|
||||
- ["app.config", "s", {title: "Application specific config file"}]
|
||||
- ["app.config", "3gang.json"]
|
||||
|
||||
|
||||
build_vars:
|
||||
FLASH_SIZE: 1048576
|
||||
|
||||
# List of libraries used by this app, in order of initialisation
|
||||
libs:
|
||||
- origin: https://github.com/mongoose-os-libs/wifi
|
||||
- origin: https://github.com/mongoose-os-libs/http-server
|
||||
- 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-service-fs
|
||||
- origin: https://github.com/mongoose-os-libs/rpc-mqtt
|
||||
- origin: https://github.com/mongoose-os-libs/mqtt
|
||||
- origin: libs/rpc-service-ota
|
||||
|
||||
# Used by the mos tool to catch mos binaries incompatible with this file format
|
||||
manifest_version: 2017-05-18
|
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);
|
||||
}
|
2
unittest/.gitignore
vendored
Normal file
2
unittest/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.o
|
||||
test
|
26
unittest/Makefile
Normal file
26
unittest/Makefile
Normal file
@ -0,0 +1,26 @@
|
||||
TARGET = test
|
||||
LIBS =
|
||||
CC = gcc
|
||||
CFLAGS = -g -Wall -I../include -I./ -I ./mongoose/
|
||||
|
||||
.PHONY: default all clean
|
||||
|
||||
default: $(TARGET)
|
||||
all: default
|
||||
|
||||
OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c))
|
||||
SRCS = frozen/frozen.c ../src/channel.c ../src/mqtt.c ../src/led.c
|
||||
HEADERS = $(wildcard *.h)
|
||||
|
||||
%.o: %.c $(HEADERS)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
.PRECIOUS: $(TARGET) $(OBJECTS)
|
||||
|
||||
$(TARGET): $(OBJECTS)
|
||||
$(CC) $(CFLAGS) $(OBJECTS) $(LIBS) $(SRCS) -o $@
|
||||
|
||||
clean:
|
||||
-rm -f $(OBJECTS)
|
||||
-rm -f $(TARGET)
|
||||
|
1373
unittest/frozen/frozen.c
Normal file
1373
unittest/frozen/frozen.c
Normal file
File diff suppressed because it is too large
Load Diff
300
unittest/frozen/frozen.h
Normal file
300
unittest/frozen/frozen.h
Normal file
@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||
* Copyright (c) 2013 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* This library is dual-licensed: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation. For the terms of this
|
||||
* license, see <http: *www.gnu.org/licenses/>.
|
||||
*
|
||||
* You are free to use this library under the terms of the GNU General
|
||||
* Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* Alternatively, you can license this library under a commercial
|
||||
* license, as set out in <http://cesanta.com/products.html>.
|
||||
*/
|
||||
|
||||
#ifndef CS_FROZEN_FROZEN_H_
|
||||
#define CS_FROZEN_FROZEN_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(_WIN32) && _MSC_VER < 1700
|
||||
typedef int bool;
|
||||
enum { false = 0, true = 1 };
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
/* JSON token type */
|
||||
enum json_token_type {
|
||||
JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */
|
||||
JSON_TYPE_STRING,
|
||||
JSON_TYPE_NUMBER,
|
||||
JSON_TYPE_TRUE,
|
||||
JSON_TYPE_FALSE,
|
||||
JSON_TYPE_NULL,
|
||||
JSON_TYPE_OBJECT_START,
|
||||
JSON_TYPE_OBJECT_END,
|
||||
JSON_TYPE_ARRAY_START,
|
||||
JSON_TYPE_ARRAY_END,
|
||||
|
||||
JSON_TYPES_CNT
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure containing token type and value. Used in `json_walk()` and
|
||||
* `json_scanf()` with the format specifier `%T`.
|
||||
*/
|
||||
struct json_token {
|
||||
const char *ptr; /* Points to the beginning of the value */
|
||||
int len; /* Value length */
|
||||
enum json_token_type type; /* Type of the token, possible values are above */
|
||||
};
|
||||
|
||||
#define JSON_INVALID_TOKEN \
|
||||
{ 0, 0, JSON_TYPE_INVALID }
|
||||
|
||||
/* Error codes */
|
||||
#define JSON_STRING_INVALID -1
|
||||
#define JSON_STRING_INCOMPLETE -2
|
||||
|
||||
/*
|
||||
* Callback-based SAX-like API.
|
||||
*
|
||||
* Property name and length is given only if it's available: i.e. if current
|
||||
* event is an object's property. In other cases, `name` is `NULL`. For
|
||||
* example, name is never given:
|
||||
* - For the first value in the JSON string;
|
||||
* - For events JSON_TYPE_OBJECT_END and JSON_TYPE_ARRAY_END
|
||||
*
|
||||
* E.g. for the input `{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }`,
|
||||
* the sequence of callback invocations will be as follows:
|
||||
*
|
||||
* - type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL
|
||||
* - type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123"
|
||||
* - type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL
|
||||
* - type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1"
|
||||
* - type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2"
|
||||
* - type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL
|
||||
* - type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true"
|
||||
* - type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{ \"baz\":
|
||||
*true }"
|
||||
* - type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2, {
|
||||
*\"baz\": true } ]"
|
||||
* - type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{ \"foo\": 123,
|
||||
*\"bar\": [ 1, 2, { \"baz\": true } ] }"
|
||||
*/
|
||||
typedef void (*json_walk_callback_t)(void *callback_data, const char *name,
|
||||
size_t name_len, const char *path,
|
||||
const struct json_token *token);
|
||||
|
||||
/*
|
||||
* Parse `json_string`, invoking `callback` in a way similar to SAX parsers;
|
||||
* see `json_walk_callback_t`.
|
||||
* Return number of processed bytes, or a negative error code.
|
||||
*/
|
||||
int json_walk(const char *json_string, int json_string_length,
|
||||
json_walk_callback_t callback, void *callback_data);
|
||||
|
||||
/*
|
||||
* JSON generation API.
|
||||
* struct json_out abstracts output, allowing alternative printing plugins.
|
||||
*/
|
||||
struct json_out {
|
||||
int (*printer)(struct json_out *, const char *str, size_t len);
|
||||
union {
|
||||
struct {
|
||||
char *buf;
|
||||
size_t size;
|
||||
size_t len;
|
||||
} buf;
|
||||
void *data;
|
||||
FILE *fp;
|
||||
} u;
|
||||
};
|
||||
|
||||
extern int json_printer_buf(struct json_out *, const char *, size_t);
|
||||
extern int json_printer_file(struct json_out *, const char *, size_t);
|
||||
|
||||
#define JSON_OUT_BUF(buf, len) \
|
||||
{ \
|
||||
json_printer_buf, { \
|
||||
{ buf, len, 0 } \
|
||||
} \
|
||||
}
|
||||
#define JSON_OUT_FILE(fp) \
|
||||
{ \
|
||||
json_printer_file, { \
|
||||
{ (char *) fp, 0, 0 } \
|
||||
} \
|
||||
}
|
||||
|
||||
typedef int (*json_printf_callback_t)(struct json_out *, va_list *ap);
|
||||
|
||||
/*
|
||||
* Generate formatted output into a given sting buffer.
|
||||
* This is a superset of printf() function, with extra format specifiers:
|
||||
* - `%B` print json boolean, `true` or `false`. Accepts an `int`.
|
||||
* - `%Q` print quoted escaped string or `null`. Accepts a `const char *`.
|
||||
* - `%.*Q` same as `%Q`, but with length. Accepts `int`, `const char *`
|
||||
* - `%V` print quoted base64-encoded string. Accepts a `const char *`, `int`.
|
||||
* - `%H` print quoted hex-encoded string. Accepts a `int`, `const char *`.
|
||||
* - `%M` invokes a json_printf_callback_t function. That callback function
|
||||
* can consume more parameters.
|
||||
*
|
||||
* Return number of bytes printed. If the return value is bigger then the
|
||||
* supplied buffer, that is an indicator of overflow. In the overflow case,
|
||||
* overflown bytes are not printed.
|
||||
*/
|
||||
int json_printf(struct json_out *, const char *fmt, ...);
|
||||
int json_vprintf(struct json_out *, const char *fmt, va_list ap);
|
||||
|
||||
/*
|
||||
* Same as json_printf, but prints to a file.
|
||||
* File is created if does not exist. File is truncated if already exists.
|
||||
*/
|
||||
int json_fprintf(const char *file_name, const char *fmt, ...);
|
||||
int json_vfprintf(const char *file_name, const char *fmt, va_list ap);
|
||||
|
||||
/*
|
||||
* Helper %M callback that prints contiguous C arrays.
|
||||
* Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt
|
||||
* Return number of bytes printed.
|
||||
*/
|
||||
int json_printf_array(struct json_out *, va_list *ap);
|
||||
|
||||
/*
|
||||
* Scan JSON string `str`, performing scanf-like conversions according to `fmt`.
|
||||
* This is a `scanf()` - like function, with following differences:
|
||||
*
|
||||
* 1. Object keys in the format string may be not quoted, e.g. "{key: %d}"
|
||||
* 2. Order of keys in an object is irrelevant.
|
||||
* 3. Several extra format specifiers are supported:
|
||||
* - %B: consumes `int *` (or 'char *', if sizeof(bool) == sizeof(char)),
|
||||
* expects boolean `true` or `false`.
|
||||
* - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned
|
||||
* string is malloc-ed, caller must free() the string.
|
||||
* - %V: consumes `char **`, `int *`. Expects base64-encoded string.
|
||||
* Result string is base64-decoded, malloced and NUL-terminated.
|
||||
* The length of result string is stored in `int *` placeholder.
|
||||
* Caller must free() the result.
|
||||
* - %H: consumes `int *`, `char **`.
|
||||
* Expects a hex-encoded string, e.g. "fa014f".
|
||||
* Result string is hex-decoded, malloced and NUL-terminated.
|
||||
* The length of the result string is stored in `int *` placeholder.
|
||||
* Caller must free() the result.
|
||||
* - %M: consumes custom scanning function pointer and
|
||||
* `void *user_data` parameter - see json_scanner_t definition.
|
||||
* - %T: consumes `struct json_token *`, fills it out with matched token.
|
||||
*
|
||||
* Return number of elements successfully scanned & converted.
|
||||
* Negative number means scan error.
|
||||
*/
|
||||
int json_scanf(const char *str, int str_len, const char *fmt, ...);
|
||||
int json_vscanf(const char *str, int str_len, const char *fmt, va_list ap);
|
||||
|
||||
/* json_scanf's %M handler */
|
||||
typedef void (*json_scanner_t)(const char *str, int len, void *user_data);
|
||||
|
||||
/*
|
||||
* Helper function to scan array item with given path and index.
|
||||
* Fills `token` with the matched JSON token.
|
||||
* Return 0 if no array element found, otherwise non-0.
|
||||
*/
|
||||
int json_scanf_array_elem(const char *s, int len, const char *path, int index,
|
||||
struct json_token *token);
|
||||
|
||||
/*
|
||||
* Unescape JSON-encoded string src,slen into dst, dlen.
|
||||
* src and dst may overlap.
|
||||
* If destination buffer is too small (or zero-length), result string is not
|
||||
* written but the length is counted nevertheless (similar to snprintf).
|
||||
* Return the length of unescaped string in bytes.
|
||||
*/
|
||||
int json_unescape(const char *src, int slen, char *dst, int dlen);
|
||||
|
||||
/*
|
||||
* Escape a string `str`, `str_len` into the printer `out`.
|
||||
* Return the number of bytes printed.
|
||||
*/
|
||||
int json_escape(struct json_out *out, const char *str, size_t str_len);
|
||||
|
||||
/*
|
||||
* Read the whole file in memory.
|
||||
* Return malloc-ed file content, or NULL on error. The caller must free().
|
||||
*/
|
||||
char *json_fread(const char *file_name);
|
||||
|
||||
/*
|
||||
* Update given JSON string `s,len` by changing the value at given `json_path`.
|
||||
* The result is saved to `out`. If `json_fmt` == NULL, that deletes the key.
|
||||
* If path is not present, missing keys are added. Array path without an
|
||||
* index pushes a value to the end of an array.
|
||||
* Return 1 if the string was changed, 0 otherwise.
|
||||
*
|
||||
* Example: s is a JSON string { "a": 1, "b": [ 2 ] }
|
||||
* json_setf(s, len, out, ".a", "7"); // { "a": 7, "b": [ 2 ] }
|
||||
* json_setf(s, len, out, ".b", "7"); // { "a": 1, "b": 7 }
|
||||
* json_setf(s, len, out, ".b[]", "7"); // { "a": 1, "b": [ 2,7 ] }
|
||||
* json_setf(s, len, out, ".b", NULL); // { "a": 1 }
|
||||
*/
|
||||
int json_setf(const char *s, int len, struct json_out *out,
|
||||
const char *json_path, const char *json_fmt, ...);
|
||||
|
||||
int json_vsetf(const char *s, int len, struct json_out *out,
|
||||
const char *json_path, const char *json_fmt, va_list ap);
|
||||
|
||||
/*
|
||||
* Pretty-print JSON string `s,len` into `out`.
|
||||
* Return number of processed bytes in `s`.
|
||||
*/
|
||||
int json_prettify(const char *s, int len, struct json_out *out);
|
||||
|
||||
/*
|
||||
* Prettify JSON file `file_name`.
|
||||
* Return number of processed bytes, or negative number of error.
|
||||
* On error, file content is not modified.
|
||||
*/
|
||||
int json_prettify_file(const char *file_name);
|
||||
|
||||
/*
|
||||
* Iterate over an object at given JSON `path`.
|
||||
* On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL
|
||||
* for `key`, or `val`, in which case they won't be populated.
|
||||
* Return an opaque value suitable for the next iteration, or NULL when done.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```c
|
||||
* void *h = NULL;
|
||||
* struct json_token key, val;
|
||||
* while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL) {
|
||||
* printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
void *json_next_key(const char *s, int len, void *handle, const char *path,
|
||||
struct json_token *key, struct json_token *val);
|
||||
|
||||
/*
|
||||
* Iterate over an array at given JSON `path`.
|
||||
* Similar to `json_next_key`, but fills array index `idx` instead of `key`.
|
||||
*/
|
||||
void *json_next_elem(const char *s, int len, void *handle, const char *path,
|
||||
int *idx, struct json_token *val);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_FROZEN_FROZEN_H_ */
|
18
unittest/main.c
Normal file
18
unittest/main.c
Normal file
@ -0,0 +1,18 @@
|
||||
#include "test.h"
|
||||
|
||||
int test_failures=0;
|
||||
int assert_count=0;
|
||||
|
||||
uint32_t mqtt_pub_count;
|
||||
uint32_t mqtt_sub_count;
|
||||
|
||||
int main() {
|
||||
test_buttons();
|
||||
|
||||
if (test_failures) {
|
||||
LOG(LL_ERROR, ("%d test failures", test_failures));
|
||||
return -1;
|
||||
}
|
||||
LOG(LL_INFO, ("All tests passed, %d assertions OK", assert_count));
|
||||
return 0;
|
||||
}
|
16
unittest/mgos.h
Normal file
16
unittest/mgos.h
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef __MGOS_H
|
||||
#define __MGOS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mgos_mock.h"
|
||||
#include "mgos_gpio.h"
|
||||
#include "mgos_net.h"
|
||||
#include "mgos_mqtt.h"
|
||||
|
||||
#endif // __MGOS_H
|
5
unittest/mgos_config.c
Normal file
5
unittest/mgos_config.c
Normal file
@ -0,0 +1,5 @@
|
||||
#include "mgos_config.h"
|
||||
|
||||
const char *mgos_sys_config_get_device_id() {
|
||||
return "esp8266_ABCDEF";
|
||||
}
|
6
unittest/mgos_config.h
Normal file
6
unittest/mgos_config.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef __MGOS_CONFIG_H
|
||||
#define __MGOS_CONFIG_H
|
||||
|
||||
const char *mgos_sys_config_get_device_id();
|
||||
|
||||
#endif // __MGOS_CONFIG_H
|
31
unittest/mgos_gpio.c
Normal file
31
unittest/mgos_gpio.c
Normal file
@ -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);
|
||||
}
|
||||
|
39
unittest/mgos_gpio.h
Normal file
39
unittest/mgos_gpio.h
Normal file
@ -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
|
75
unittest/mgos_mock.c
Normal file
75
unittest/mgos_mock.c
Normal file
@ -0,0 +1,75 @@
|
||||
/* Some functions mocked from MGOS, so we can run unit tests standalone.
|
||||
*/
|
||||
|
||||
#include "mgos_mock.h"
|
||||
|
||||
int _mgos_timers = 0;
|
||||
|
||||
int log_print_prefix(enum cs_log_level l, const char *func, const char *file) {
|
||||
char ll_str[6];
|
||||
|
||||
switch(l) {
|
||||
case LL_ERROR:
|
||||
strncpy(ll_str, "ERROR", sizeof(ll_str));
|
||||
break;
|
||||
case LL_WARN:
|
||||
strncpy(ll_str, "WARN", sizeof(ll_str));
|
||||
break;
|
||||
case LL_INFO:
|
||||
strncpy(ll_str, "INFO", sizeof(ll_str));
|
||||
break;
|
||||
case LL_DEBUG:
|
||||
strncpy(ll_str, "DEBUG", sizeof(ll_str));
|
||||
break;
|
||||
case LL_VERBOSE_DEBUG:
|
||||
strncpy(ll_str, "VERB", sizeof(ll_str));
|
||||
break;
|
||||
default: // LL_NONE
|
||||
return 0;
|
||||
}
|
||||
printf ("%-5s %-15s %-40s| ", ll_str, file, func);
|
||||
return 1;
|
||||
}
|
||||
|
||||
mgos_timer_id mgos_set_timer(int msecs, int flags, timer_callback cb, void *cb_arg) {
|
||||
_mgos_timers++;
|
||||
LOG(LL_INFO, ("Installing timer -- %d timers currently installed", _mgos_timers));
|
||||
(void) msecs;
|
||||
(void) flags;
|
||||
(void) cb;
|
||||
(void) cb_arg;
|
||||
|
||||
return _mgos_timers;
|
||||
}
|
||||
|
||||
void mgos_clear_timer(mgos_timer_id id) {
|
||||
_mgos_timers--;
|
||||
LOG(LL_INFO, ("Clearing timer -- %d timers currently installed", _mgos_timers));
|
||||
(void) id;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
double mg_time() {
|
||||
return (float) time(NULL);
|
||||
}
|
||||
|
||||
double mgos_uptime() {
|
||||
return (double) time(NULL);
|
||||
}
|
||||
|
||||
char *mgos_sys_ro_vars_get_mac_address() {
|
||||
return "00:11:22:33:44:55";
|
||||
}
|
||||
|
||||
char *mgos_sys_ro_vars_get_arch() {
|
||||
return "esp32";
|
||||
}
|
||||
|
||||
bool mgos_net_get_ip_info(enum mgos_net_if_type if_type, int if_instance, struct mgos_net_ip_info *ip_info) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void mgos_net_ip_to_str(const struct sockaddr_in *sin, char *out) {
|
||||
return;
|
||||
}
|
48
unittest/mgos_mock.h
Normal file
48
unittest/mgos_mock.h
Normal file
@ -0,0 +1,48 @@
|
||||
#ifndef __MGOS_MOCK_H
|
||||
#define __MGOS_MOCK_H
|
||||
|
||||
/* Some functions mocked from MGOS, so we can run unit tests standalone.
|
||||
*/
|
||||
|
||||
#include "mgos.h"
|
||||
#include <time.h>
|
||||
#include "mongoose/mongoose.h"
|
||||
|
||||
#define MGOS_APP "unittest"
|
||||
|
||||
// mgos_log
|
||||
enum cs_log_level {
|
||||
LL_NONE = -1,
|
||||
LL_ERROR = 0,
|
||||
LL_WARN = 1,
|
||||
LL_INFO = 2,
|
||||
LL_DEBUG = 3,
|
||||
LL_VERBOSE_DEBUG = 4,
|
||||
|
||||
_LL_MIN = -2,
|
||||
_LL_MAX = 5,
|
||||
};
|
||||
|
||||
int log_print_prefix(enum cs_log_level l, const char *func, const char *file);
|
||||
|
||||
#define LOG(l, x) \
|
||||
do { \
|
||||
if (log_print_prefix(l, __func__, __FILE__)) printf x; \
|
||||
printf("\r\n"); \
|
||||
} while (0)
|
||||
|
||||
|
||||
// mgos_timer
|
||||
#define MGOS_TIMER_REPEAT 1
|
||||
typedef uintptr_t mgos_timer_id;
|
||||
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);
|
||||
|
||||
double mgos_uptime();
|
||||
|
||||
char *mgos_sys_ro_vars_get_mac_address();
|
||||
char *mgos_sys_ro_vars_get_arch();
|
||||
|
||||
#endif // __MGOS_MOCK_H
|
35
unittest/mgos_mqtt.c
Normal file
35
unittest/mgos_mqtt.c
Normal file
@ -0,0 +1,35 @@
|
||||
#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;
|
||||
|
||||
static mg_event_handler_t s_global_handler;
|
||||
static void *s_global_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);
|
||||
}
|
||||
|
||||
void mgos_mqtt_add_global_handler(mqtt_event_handler_t handler, void *ud) {
|
||||
s_global_handler = handler;
|
||||
s_global_handler_ud = ud;
|
||||
}
|
||||
|
23
unittest/mgos_mqtt.h
Normal file
23
unittest/mgos_mqtt.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef __MGOS_MQTT_H
|
||||
#define __MGOS_MQTT_H
|
||||
|
||||
#include "mgos.h"
|
||||
#include "mongoose/mongoose.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);
|
||||
|
||||
typedef void (*mqtt_event_handler_t)(struct mg_connection *nc, int ev,
|
||||
void *ev_data, void *user_data);
|
||||
|
||||
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);
|
||||
void mgos_mqtt_add_global_handler(mqtt_event_handler_t handler, void *ud);
|
||||
|
||||
|
||||
#endif // __MGOS_MQTT_H
|
32
unittest/mgos_net.h
Normal file
32
unittest/mgos_net.h
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef __MGOS_NET_H
|
||||
#define __MGOS_NET_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#define MGOS_NET_IF_WIFI_STA 0
|
||||
#define MGOS_NET_IF_WIFI_AP 1
|
||||
|
||||
enum mgos_net_if_type {
|
||||
MGOS_NET_IF_TYPE_WIFI,
|
||||
MGOS_NET_IF_TYPE_ETHERNET,
|
||||
MGOS_NET_IF_TYPE_PPP,
|
||||
/* This is a sentinel in case all networking interface types are disabled. */
|
||||
MGOS_NET_IF_MAX,
|
||||
};
|
||||
|
||||
struct mgos_net_ip_info {
|
||||
struct sockaddr_in ip;
|
||||
struct sockaddr_in netmask;
|
||||
struct sockaddr_in gw;
|
||||
};
|
||||
|
||||
bool mgos_net_get_ip_info(enum mgos_net_if_type if_type, int if_instance,
|
||||
struct mgos_net_ip_info *ip_info);
|
||||
|
||||
|
||||
void mgos_net_ip_to_str(const struct sockaddr_in *sin, char *out);
|
||||
|
||||
|
||||
#endif // __MGOS_NET_H
|
6124
unittest/mongoose/mongoose.h
Normal file
6124
unittest/mongoose/mongoose.h
Normal file
File diff suppressed because it is too large
Load Diff
26
unittest/test.h
Normal file
26
unittest/test.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef __TEST_H
|
||||
#define __TEST_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "frozen/frozen.h"
|
||||
#include "mgos_mock.h"
|
||||
#include "main.h"
|
||||
|
||||
extern int test_failures;
|
||||
extern int assert_count;
|
||||
|
||||
#define ASSERT(expr, errstr) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
LOG(LL_ERROR, ("ASSERT FAIL: "errstr)); \
|
||||
test_failures++; \
|
||||
} \
|
||||
assert_count++; \
|
||||
} while (0)
|
||||
|
||||
|
||||
|
||||
int test_buttons(void);
|
||||
|
||||
#endif // __TEST_H
|
9
unittest/test_buttons.c
Normal file
9
unittest/test_buttons.c
Normal file
@ -0,0 +1,9 @@
|
||||
#include "test.h"
|
||||
|
||||
int test_buttons() {
|
||||
channel_init("testdata/testconfig0.json");
|
||||
channel_init("testdata/testconfig1.json");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
9
unittest/testdata/testconfig0.json
vendored
Normal file
9
unittest/testdata/testconfig0.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "3 Gang",
|
||||
"status_led": 0,
|
||||
"channels": [
|
||||
{ "led": 2, "relay": 15, "button": 5 },
|
||||
{ "led": 14, "relay": 4, "button": 3 },
|
||||
{ "led": 16, "relay": 13, "button": 12 }
|
||||
]
|
||||
}
|
9
unittest/testdata/testconfig1.json
vendored
Normal file
9
unittest/testdata/testconfig1.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "3 Gang, No LEDs",
|
||||
"status_led": -1,
|
||||
"channels": [
|
||||
{ "led": -1, "relay": 15, "button": 5 },
|
||||
{ "led": -1, "relay": 4, "button": 3 },
|
||||
{ "led": -1, "relay": 13, "button": 12 }
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user