This commit is contained in:
2025-03-30 02:37:21 -04:00
parent ff19da67f0
commit de1d2d024d
15 changed files with 27860 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(firmware)

69
ESP-Mongoose_UI/README.md Normal file
View File

@@ -0,0 +1,69 @@
# Mongoose Wizard Project
This project is generated by Mongoose Wizard, https://mongoose.ws/wizard/
## Build and flash
### If your target IDE is GCC+Make
**Option 1. Build and flash online**
a) Click on the "build" button, wait for the compiler to finish.
b) Click on the "flash" button; or, click on the "bin" button to download to your workstation
**Option 2. Download generated project to your workstation**
a) Click on the "sync" or ".zip" button to download the project to your workstation
b) Setup your build environment: https://mongoose.ws/documentation/tutorials/tools/
c) Open a terminal/command prompt, go to the destination directory, and type `make`
### Other target IDEs
**Step 1. Download generated project to your workstation**
Click on the "sync" or ".zip" button to download the project to your workstation
**Step 2. Build and run the project**
If your target IDE is CubeIDE, then start Cube IDE (use Cube 1.16.0 or later), then:
a) Choose File / Import
b) Choose "Existing Projects into Workspace", click Next,
c) Click on "Directory", choose dir with generated files, click Finish
If your target IDE is MCUXpresso, follow instructions for Cube IDE above
If your target IDE is Arduino, open `wizard.ino` in the Arduino IDE
If your target IDE is other - follow your IDE's guide
## Copying functionality to an existing firmware
In order to move this functionality to some existing firmware code, copy only
the following ("purple") files:
- mongoose.{c,h} - This is Mongoose Library, a full TCP/IP + TLS stack
- mongoose_config.h - (only if present). Mongoose settings file
- mongoose_fs.c - Embedded filesystem. Contains Web UI files and TLS certs
- mongoose_impl.c - Generated network functionality
- mongoose_glue.{c,h} - A "glue", or "binding" functions for your code
Note, the only file you should edit to integrate with your code, is
`mongoose_glue.c`. That file "glues" your firmware with the networking
functionality generated by the Mongoose Wizard. It contains functions that
return device data. Generated functions use "fake" mock data; you need to
edit those functions and return "real" device data.
Once you copy these files, add the following snippet somewhere in your code:
```c
#include "mongoose_glue.h"
...
run_mongoose(); // This function blocks forever. Call it at the end of main(),
// or in a separate RTOS task. Give that task 8k stack space.
```
For Ethernet hardware details, check [this tutorial](https://mongoose.ws/documentation/tutorials/hardware/)
## OTA firmware update
This feature uses binary images
- [Tutorial](https://mongoose.ws/documentation/tutorials/firmware-update/)
## Documentation and Tutorials
- [Tutorials](https://mongoose.ws/documentation/#tutorials)
- [Mongoose User Guide](https://mongoose.ws/documentation/)

View File

@@ -0,0 +1,4 @@
FILE(GLOB cfiles *.c)
FILE(GLOB mongoose_files ../mongoose/*.c)
idf_component_register(SRCS ${cfiles} ${mongoose_files}
INCLUDE_DIRS "." "../mongoose")

View File

@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2024 Cesanta Software Limited
// SPDX-License-Identifier: GPL-2.0-only or commercial
// Generated by Mongoose Wizard, https://mongoose.ws/wizard/
#include "mongoose_glue.h"
#define WIFI_SSID "My_WiFi_Network"
#define WIFI_PASS "My_WiFi_Password"
extern void wifi_init(const char *ssid, const char *pass);
void app_main() {
wifi_init(WIFI_SSID, WIFI_PASS); // This blocks until connected
run_mongoose();
}

View File

@@ -0,0 +1,70 @@
#include "esp_wifi.h"
#include "mongoose.h"
#include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
static int retry_count = 0;
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_STA_DISCONNECTED) {
esp_wifi_connect();
retry_count++;
MG_INFO(("Connecting to the AP fail, attempt #%d", retry_count));
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
MG_INFO(("Got IP ADDRESS: " IPSTR, IP2STR(&event->ip_info.ip)));
retry_count = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init(const char *ssid, const char *pass) {
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
wifi_config_t wc = {};
strncpy((char *) wc.sta.ssid, ssid, sizeof(wc.sta.ssid));
strncpy((char *) wc.sta.password, pass, sizeof(wc.sta.password));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wc));
ESP_ERROR_CHECK(esp_wifi_start());
MG_INFO(("Trying to connect to SSID:%s pass:%s", ssid, pass));
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
MG_INFO(("connected to ap SSID:%s pass:%s", ssid, pass));
} else if (bits & WIFI_FAIL_BIT) {
MG_ERROR(("Failed to connect to SSID:%s, pass:%s", ssid, pass));
} else {
MG_ERROR(("UNEXPECTED EVENT"));
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
#define MG_ARCH MG_ARCH_ESP32
#define MG_TLS MG_TLS_BUILTIN
#define MG_OTA MG_OTA_ESP32
#define MG_ENABLE_PACKED_FS 1
#define MG_ENABLE_POLL 1

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,210 @@
// SPDX-FileCopyrightText: 2024 Cesanta Software Limited
// SPDX-License-Identifier: GPL-2.0-only or commercial
// Generated by Mongoose Wizard, https://mongoose.ws/wizard/
// Default mock implementation of the API callbacks
#include "mongoose_glue.h"
void glue_lock_init(void) { // callback to initialize the MQTT semaphore
}
void glue_lock(void) { // Lock mutex. Implement only if you use MQTT publish
}
void glue_unlock(void) { // Unlock mutex. Implement only if you use MQTT publish
}
// Update this with the real CA for the WIZARD_MQTT_URL
#define TLS_CA \
""
void glue_mqtt_tls_init(struct mg_connection *c) {
bool is_tls = mg_url_is_ssl(WIZARD_MQTT_URL);
MG_DEBUG(("%lu TLS enabled: %s", c->id, is_tls ? "yes" : "no"));
if (is_tls) {
struct mg_tls_opts opts;
memset(&opts, 0, sizeof(opts));
opts.ca = mg_str(TLS_CA);
opts.name = mg_url_host(WIZARD_MQTT_URL);
mg_tls_init(c, &opts);
}
}
// Called when we connected to the MQTT server
void glue_mqtt_on_connect(struct mg_connection *c, int code) {
struct mg_mqtt_opts opts;
memset(&opts, 0, sizeof(opts));
opts.qos = 1;
opts.topic = mg_str("device1/rx");
mg_mqtt_sub(c, &opts);
MG_DEBUG(("%lu code %d. Subscribing to [%.*s]", c->id, code, opts.topic.len,
opts.topic.buf));
}
// This function gets called for every received MQTT message
void glue_mqtt_on_message(struct mg_connection *c, struct mg_str topic,
struct mg_str data) {
char tmp[100];
struct mg_mqtt_opts opts;
mg_snprintf(tmp, sizeof(tmp), "Got [%.*s] -> [%.*s] !", topic.len, topic.buf,
data.len, data.buf);
MG_DEBUG(("%lu %s", c->id, tmp));
// Send response to the TX topic
if (g_mqtt_conn != NULL) {
memset(&opts, 0, sizeof(opts));
opts.topic = mg_str("device1/tx");
opts.message = mg_str(tmp);
mg_mqtt_pub(g_mqtt_conn, &opts);
}
}
void glue_mqtt_on_cmd(struct mg_connection *c, struct mg_mqtt_message *mm) {
MG_DEBUG(("%lu cmd %d qos %d", c->id, mm->cmd, mm->qos));
}
struct mg_connection *glue_mqtt_connect(mg_event_handler_t fn) {
const char *url = WIZARD_MQTT_URL;
struct mg_mqtt_opts opts;
memset(&opts, 0, sizeof(opts));
opts.clean = true;
return mg_mqtt_connect(&g_mgr, url, &opts, fn, NULL);
}
void glue_sntp_on_time(uint64_t utc_time_in_milliseconds) {
MG_INFO(("UTC time in milliseconds from SNTP: %llu, current time: %llu",
utc_time_in_milliseconds, mg_now()));
}
// Authenticate user/password. Return access level for the authenticated user:
// 0 - authentication error
// 1,2,3... - authentication success. Higher levels are more privileged than lower
int glue_authenticate(const char *user, const char *pass) {
int level = 0; // Authentication failure
if (strcmp(user, "admin") == 0 && strcmp(pass, "admin") == 0) {
level = 7; // Administrator
} else if (strcmp(user, "user") == 0 && strcmp(pass, "user") == 0) {
level = 3; // Ordinary dude
}
return level;
}
static uint64_t s_action_timeout_reboot; // Time when reboot ends
bool glue_check_reboot(void) {
return s_action_timeout_reboot > mg_now(); // Return true if reboot is in progress
}
void glue_start_reboot(struct mg_str params) {
MG_DEBUG(("Passed parameters: [%.*s]", params.len, params.buf));
s_action_timeout_reboot = mg_now() + 1000; // Start reboot, finish after 1 second
}
static uint64_t s_action_timeout_reformat; // Time when reformat ends
bool glue_check_reformat(void) {
return s_action_timeout_reformat > mg_now(); // Return true if reformat is in progress
}
void glue_start_reformat(struct mg_str params) {
MG_DEBUG(("Passed parameters: [%.*s]", params.len, params.buf));
s_action_timeout_reformat = mg_now() + 1000; // Start reformat, finish after 1 second
}
void *glue_ota_begin_firmware_update(char *file_name, size_t total_size) {
bool ok = mg_ota_begin(total_size);
MG_DEBUG(("%s size %lu, ok: %d", file_name, total_size, ok));
return ok ? (void *) 1 : NULL;
}
bool glue_ota_end_firmware_update(void *context) {
mg_timer_add(&g_mgr, 500, 0, (void (*)(void *)) (void *) mg_ota_end, context);
return true;
}
bool glue_ota_write_firmware_update(void *context, void *buf, size_t len) {
MG_DEBUG(("ctx: %p %p/%lu", context, buf, len));
return mg_ota_write(buf, len);
}
void *glue_file_open_file_upload(char *file_name, size_t total_size) {
char path[128], *p = NULL;
FILE *fp = NULL;
if ((p = strrchr(file_name, '/')) == NULL) p = file_name;
mg_snprintf(path, sizeof(path), "/tmp/%s", p);
#if MG_ENABLE_POSIX_FS
fp = fopen(path, "w+b");
#endif
MG_DEBUG(("opening [%s] size %lu, fp %p", path, total_size, fp));
return fp;
}
bool glue_file_close_file_upload(void *fp) {
MG_DEBUG(("closing %p", fp));
#if MG_ENABLE_POSIX_FS
return fclose((FILE *) fp) == 0;
#else
return false;
#endif
}
bool glue_file_write_file_upload(void *fp, void *buf, size_t len) {
MG_DEBUG(("writing fp %p %p %lu bytes", fp, buf, len));
#if MG_ENABLE_POSIX_FS
return fwrite(buf, 1, len, (FILE *) fp) == len;
#else
return false;
#endif
}
void glue_reply_graph_data(struct mg_connection *c, struct mg_http_message *hm) {
const char *headers = "Cache-Control: no-cache\r\n" "Content-Type: application/json\r\n";
const char *value = "[[1724576787,20.3],[1724576847,27.2],[1724576907,29.7],[1724576967,27.9],[1724577027,25.1],[1724577087,23.8],[1724577147,22.5],[1724577207,22.2],[1724577267,23.3],[1724577327,23.9]]";
(void) hm;
mg_http_reply(c, 200, headers, "%s\n", value);
}
static struct state s_state = {42, 27, 67, 10, "1.0.0", true, false, 83};
void glue_get_state(struct state *data) {
*data = s_state; // Sync with your device
}
static struct leds s_leds = {false, true, false};
void glue_get_leds(struct leds *data) {
*data = s_leds; // Sync with your device
}
void glue_set_leds(struct leds *data) {
s_leds = *data; // Sync with your device
}
static struct network_settings s_network_settings = {"192.168.0.42", "192.168.0.1", "255.255.255.0", true};
void glue_get_network_settings(struct network_settings *data) {
*data = s_network_settings; // Sync with your device
}
void glue_set_network_settings(struct network_settings *data) {
s_network_settings = *data; // Sync with your device
}
static struct settings s_settings = {"edit & save me", "info", 123.12345, 17, true};
void glue_get_settings(struct settings *data) {
*data = s_settings; // Sync with your device
}
void glue_set_settings(struct settings *data) {
s_settings = *data; // Sync with your device
}
static struct security s_security = {"admin", "user"};
void glue_get_security(struct security *data) {
*data = s_security; // Sync with your device
}
void glue_set_security(struct security *data) {
s_security = *data; // Sync with your device
}
void glue_reply_loglevels(struct mg_connection *c, struct mg_http_message *hm) {
const char *headers = "Cache-Control: no-cache\r\n" "Content-Type: application/json\r\n";
const char *value = "[\"disabled\",\"error\",\"info\",\"debug\",\"verbose\"]";
(void) hm;
mg_http_reply(c, 200, headers, "%s\n", value);
}
void glue_reply_events(struct mg_connection *c, struct mg_http_message *hm) {
const char *headers = "Cache-Control: no-cache\r\n" "Content-Type: application/json\r\n";
const char *value = "[{\"priority\":1,\"timestamp\":1738653279,\"active\":false,\"message\":\"event 1\"},{\"priority\":2,\"timestamp\":1738653390,\"active\":true,\"message\":\"event 2\"}]";
(void) hm;
mg_http_reply(c, 200, headers, "%s\n", value);
}

View File

@@ -0,0 +1,169 @@
// SPDX-FileCopyrightText: 2024 Cesanta Software Limited
// SPDX-License-Identifier: GPL-2.0-only or commercial
// Generated by Mongoose Wizard, https://mongoose.ws/wizard/
#ifndef MONGOOSE_GLUE_H
#define MONGOOSE_GLUE_H
#ifdef __cplusplus
extern "C" {
#endif
#include "mongoose.h"
#define WIZARD_ENABLE_HTTP 1
#define WIZARD_ENABLE_HTTPS 1
#define WIZARD_ENABLE_HTTP_UI 1
#define WIZARD_ENABLE_HTTP_UI_LOGIN 1
#define WIZARD_ENABLE_WEBSOCKET 1
#define WIZARD_ENABLE_MQTT 1
#define WIZARD_MQTT_URL "mqtt://techquadbit.ddns.net"
#define WIZARD_ENABLE_SNTP 1 // Enable time sync.
#define WIZARD_SNTP_TYPE 0 // 0: default Google, 1: DHCP, 2: custom
#define WIZARD_SNTP_URL "udp://time.google.com:123" // Custom SNTP server URL
#define WIZARD_SNTP_INTERVAL_SECONDS 3600 // Frequency of SNTP syncs
#define WIZARD_DNS_TYPE 0 // 0: default Google, 1: DHCP, 2: custom
#define WIZARD_DNS_URL "udp://8.8.8.8:53" // Custom DNS server URL
#define WIZARD_CAPTIVE_PORTAL 0
#define WIZARD_ENABLE_MODBUS 0
#define WIZARD_MODBUS_PORT 502
#ifndef WIZARD_REBOOT_TIMEOUT_MS
#define WIZARD_REBOOT_TIMEOUT_MS 500
#endif
void mongoose_init(void); // Initialise Mongoose
void mongoose_poll(void); // Poll Mongoose
extern struct mg_mgr g_mgr; // Mongoose event manager
void mongoose_set_http_handlers(const char *name, ...);
void mongoose_add_ws_handler(unsigned ms, void (*)(struct mg_connection *));
struct mongoose_mqtt_handlers {
struct mg_connection *(*connect_fn)(mg_event_handler_t);
void (*tls_init_fn)(struct mg_connection *);
void (*on_connect_fn)(struct mg_connection *, int);
void (*on_message_fn)(struct mg_connection *, struct mg_str, struct mg_str);
void (*on_cmd_fn)(struct mg_connection *, struct mg_mqtt_message *);
};
void mongoose_set_mqtt_handlers(struct mongoose_mqtt_handlers *);
struct mongoose_modbus_handlers {
bool (*read_reg_fn)(uint16_t address, uint16_t *value);
bool (*write_reg_fn)(uint16_t address, uint16_t value);
};
void mongoose_set_modbus_handlers(struct mongoose_modbus_handlers *);
#define run_mongoose() \
do { \
mongoose_init(); \
for (;;) { \
mongoose_poll(); \
} \
} while (0)
#if WIZARD_ENABLE_MQTT
void glue_lock_init(void); // Initialise global Mongoose mutex
void glue_lock(void); // Lock global Mongoose mutex
void glue_unlock(void); // Unlock global Mongoose mutex
#else
#define glue_lock_init()
#define glue_lock()
#define glue_unlock()
#endif
// Increment device change state counter - trigger UI refresh
void glue_update_state(void);
// Firmware Glue
extern struct mg_connection *g_mqtt_conn; // MQTT client connection
void glue_mqtt_tls_init(struct mg_connection *);
struct mg_connection *glue_mqtt_connect(mg_event_handler_t ev_hanler_fn);
void glue_mqtt_on_connect(struct mg_connection *c, int code);
void glue_mqtt_on_message(struct mg_connection *c, struct mg_str topic,
struct mg_str data);
void glue_mqtt_on_cmd(struct mg_connection *c, struct mg_mqtt_message *mm);
void glue_sntp_on_time(uint64_t utc_time_in_milliseconds);
int glue_authenticate(const char *user, const char *pass);
void glue_start_reboot(struct mg_str); // Start an action
bool glue_check_reboot(void); // Check if action is still in progress
void glue_start_reformat(struct mg_str); // Start an action
bool glue_check_reformat(void); // Check if action is still in progress
void *glue_ota_begin_firmware_update(char *file_name, size_t total_size);
bool glue_ota_end_firmware_update(void *context);
bool glue_ota_write_firmware_update(void *context, void *buf, size_t len);
void *glue_file_open_file_upload(char *file_name, size_t total_size);
bool glue_file_close_file_upload(void *context);
bool glue_file_write_file_upload(void *context, void *buf, size_t len);
void glue_reply_graph_data(struct mg_connection *, struct mg_http_message *);
struct state {
int speed;
int temperature;
int humidity;
int uptime;
char version[20];
bool online;
bool lights;
int level;
};
void glue_get_state(struct state *);
struct leds {
bool led1;
bool led2;
bool led3;
};
void glue_get_leds(struct leds *);
void glue_set_leds(struct leds *);
struct network_settings {
char ip_address[20];
char gw_address[20];
char netmask[20];
bool dhcp;
};
void glue_get_network_settings(struct network_settings *);
void glue_set_network_settings(struct network_settings *);
struct settings {
char string_val[40];
char log_level[10];
double double_val;
int int_val;
bool bool_val;
};
void glue_get_settings(struct settings *);
void glue_set_settings(struct settings *);
struct security {
char admin_password[40];
char user_password[40];
};
void glue_get_security(struct security *);
void glue_set_security(struct security *);
void glue_reply_loglevels(struct mg_connection *, struct mg_http_message *);
void glue_reply_events(struct mg_connection *, struct mg_http_message *);
#ifdef __cplusplus
}
#endif
#endif // MONGOOSE_GLUE_H

View File

@@ -0,0 +1,928 @@
// SPDX-FileCopyrightText: 2024 Cesanta Software Limited
// SPDX-License-Identifier: GPL-2.0-only or commercial
// Generated by Mongoose Wizard, https://mongoose.ws/wizard/
#include "mongoose.h"
#include "mongoose_glue.h"
#if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32
#define HTTP_URL "http://0.0.0.0:8080"
#define HTTPS_URL "https://0.0.0.0:8443"
#else
#define HTTP_URL "http://0.0.0.0:80"
#define HTTPS_URL "https://0.0.0.0:443"
#endif
#ifndef offsetof
#define offsetof(st, m) ((size_t) ((char *) &((st *) 0)->m - (char *) 0))
#endif
#define NO_CACHE_HEADERS "Cache-Control: no-cache\r\n"
#define JSON_HEADERS "Content-Type: application/json\r\n" NO_CACHE_HEADERS
// How to create a self signed Elliptic Curve certificate, see
// https://github.com/cesanta/mongoose/blob/master/test/certs/generate.sh
#define TLS_CERT \
"-----BEGIN CERTIFICATE-----\n" \
"MIIBMTCB2aADAgECAgkAluqkgeuV/zUwCgYIKoZIzj0EAwIwEzERMA8GA1UEAwwI\n" \
"TW9uZ29vc2UwHhcNMjQwNTA3MTQzNzM2WhcNMzQwNTA1MTQzNzM2WjARMQ8wDQYD\n" \
"VQQDDAZzZXJ2ZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASo3oEiG+BuTt5y\n" \
"ZRyfwNr0C+SP+4M0RG2pYkb2v+ivbpfi72NHkmXiF/kbHXtgmSrn/PeTqiA8M+mg\n" \
"BhYjDX+zoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDRwAw\n" \
"RAIgTXW9MITQSwzqbNTxUUdt9DcB+8pPUTbWZpiXcA26GMYCIBiYw+DSFMLHmkHF\n" \
"+5U3NXW3gVCLN9ntD5DAx8LTG8sB\n" \
"-----END CERTIFICATE-----\n"
#define TLS_KEY \
"-----BEGIN EC PRIVATE KEY-----\n" \
"MHcCAQEEIAVdo8UAScxG7jiuNY2UZESNX/KPH8qJ0u0gOMMsAzYWoAoGCCqGSM49\n" \
"AwEHoUQDQgAEqN6BIhvgbk7ecmUcn8Da9Avkj/uDNERtqWJG9r/or26X4u9jR5Jl\n" \
"4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n" \
"-----END EC PRIVATE KEY-----\n"
struct mg_mgr g_mgr; // Mongoose event manager
#if WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS
// Every time device state changes, this counter increments.
// Used by the heartbeat endpoint, to signal the UI when to refresh
static unsigned long s_device_change_version = 0;
struct attribute {
const char *name;
const char *type;
const char *format;
size_t offset;
size_t size;
bool readonly;
};
struct apihandler {
const char *name;
const char *type;
bool readonly;
int read_level;
int write_level;
unsigned long version;
};
struct apihandler_custom {
struct apihandler common;
void (*reply)(struct mg_connection *, struct mg_http_message *);
};
struct apihandler_upload {
struct apihandler common;
void *(*opener)(char *, size_t); // Open function (OTA and upload)
bool (*closer)(void *); // Closer function (OTA and upload)
bool (*writer)(void *, void *, size_t); // Writer function (OTA and upload)
};
struct apihandler_ota {
struct apihandler common;
void *(*opener)(char *, size_t); // Open function (OTA and upload)
bool (*closer)(void *); // Closer function (OTA and upload)
bool (*writer)(void *, void *, size_t); // Writer function (OTA and upload)
};
struct apihandler_action {
struct apihandler common;
bool (*checker)(void); // Checker function for actions
void (*starter)(struct mg_str); // Starter function for actions
};
struct apihandler_data {
struct apihandler common;
const struct attribute *attributes; // Points to the strucure descriptor
size_t data_size; // Size of C structure
void (*getter)(void *); // Getter/check/begin function
void (*setter)(void *); // Setter/start/end function
};
struct apihandler_array {
struct apihandler common;
const struct attribute *attributes; // Points to the strucure descriptor
size_t data_size; // Size of C structure
void (*getter)(uint64_t, void *); // Getter/check/begin function
void (*setter)(uint64_t, void *); // Setter/start/end function
uint64_t (*sizer)(void); // Array size, for data handlers only
};
struct attribute s_state_attributes[] = {
{"speed", "int", NULL, offsetof(struct state, speed), 0, false},
{"temperature", "int", NULL, offsetof(struct state, temperature), 0, false},
{"humidity", "int", NULL, offsetof(struct state, humidity), 0, false},
{"uptime", "int", NULL, offsetof(struct state, uptime), 0, false},
{"version", "string", NULL, offsetof(struct state, version), 20, false},
{"online", "bool", NULL, offsetof(struct state, online), 0, false},
{"lights", "bool", NULL, offsetof(struct state, lights), 0, false},
{"level", "int", NULL, offsetof(struct state, level), 0, false},
{NULL, NULL, NULL, 0, 0, false}
};
struct attribute s_leds_attributes[] = {
{"led1", "bool", NULL, offsetof(struct leds, led1), 0, false},
{"led2", "bool", NULL, offsetof(struct leds, led2), 0, false},
{"led3", "bool", NULL, offsetof(struct leds, led3), 0, false},
{NULL, NULL, NULL, 0, 0, false}
};
struct attribute s_network_settings_attributes[] = {
{"ip_address", "string", NULL, offsetof(struct network_settings, ip_address), 20, false},
{"gw_address", "string", NULL, offsetof(struct network_settings, gw_address), 20, false},
{"netmask", "string", NULL, offsetof(struct network_settings, netmask), 20, false},
{"dhcp", "bool", NULL, offsetof(struct network_settings, dhcp), 0, false},
{NULL, NULL, NULL, 0, 0, false}
};
struct attribute s_settings_attributes[] = {
{"string_val", "string", NULL, offsetof(struct settings, string_val), 40, false},
{"log_level", "string", NULL, offsetof(struct settings, log_level), 10, false},
{"double_val", "double", "%.5f", offsetof(struct settings, double_val), 0, false},
{"int_val", "int", NULL, offsetof(struct settings, int_val), 0, false},
{"bool_val", "bool", NULL, offsetof(struct settings, bool_val), 0, false},
{NULL, NULL, NULL, 0, 0, false}
};
struct attribute s_security_attributes[] = {
{"admin_password", "string", NULL, offsetof(struct security, admin_password), 40, false},
{"user_password", "string", NULL, offsetof(struct security, user_password), 40, false},
{NULL, NULL, NULL, 0, 0, false}
};
struct apihandler_action s_apihandler_reboot = {{"reboot", "action", false, 3, 7, 0UL}, glue_check_reboot, glue_start_reboot};
struct apihandler_action s_apihandler_reformat = {{"reformat", "action", false, 3, 7, 0UL}, glue_check_reformat, glue_start_reformat};
struct apihandler_ota s_apihandler_firmware_update = {{"firmware_update", "ota", false, 3, 7, 0UL}, glue_ota_begin_firmware_update, glue_ota_end_firmware_update, glue_ota_write_firmware_update};
struct apihandler_upload s_apihandler_file_upload = {{"file_upload", "upload", false, 3, 7, 0UL}, glue_file_open_file_upload, glue_file_close_file_upload, glue_file_write_file_upload};
struct apihandler_custom s_apihandler_graph_data = {{"graph_data", "custom", false, 3, 3, 0UL}, glue_reply_graph_data};
struct apihandler_data s_apihandler_state = {{"state", "data", true, 0, 0, 0UL}, s_state_attributes, sizeof(struct state), (void (*)(void *)) glue_get_state, NULL};
struct apihandler_data s_apihandler_leds = {{"leds", "data", false, 3, 3, 0UL}, s_leds_attributes, sizeof(struct leds), (void (*)(void *)) glue_get_leds, (void (*)(void *)) glue_set_leds};
struct apihandler_data s_apihandler_network_settings = {{"network_settings", "data", false, 3, 7, 0UL}, s_network_settings_attributes, sizeof(struct network_settings), (void (*)(void *)) glue_get_network_settings, (void (*)(void *)) glue_set_network_settings};
struct apihandler_data s_apihandler_settings = {{"settings", "data", false, 3, 7, 0UL}, s_settings_attributes, sizeof(struct settings), (void (*)(void *)) glue_get_settings, (void (*)(void *)) glue_set_settings};
struct apihandler_data s_apihandler_security = {{"security", "data", false, 7, 7, 0UL}, s_security_attributes, sizeof(struct security), (void (*)(void *)) glue_get_security, (void (*)(void *)) glue_set_security};
struct apihandler_custom s_apihandler_loglevels = {{"loglevels", "custom", false, 0, 0, 0UL}, glue_reply_loglevels};
struct apihandler_custom s_apihandler_events = {{"events", "custom", false, 0, 0, 0UL}, glue_reply_events};
static struct apihandler *s_apihandlers[] = {
(struct apihandler *) &s_apihandler_reboot,
(struct apihandler *) &s_apihandler_reformat,
(struct apihandler *) &s_apihandler_firmware_update,
(struct apihandler *) &s_apihandler_file_upload,
(struct apihandler *) &s_apihandler_graph_data,
(struct apihandler *) &s_apihandler_state,
(struct apihandler *) &s_apihandler_leds,
(struct apihandler *) &s_apihandler_network_settings,
(struct apihandler *) &s_apihandler_settings,
(struct apihandler *) &s_apihandler_security,
(struct apihandler *) &s_apihandler_loglevels,
(struct apihandler *) &s_apihandler_events
};
static struct apihandler *get_api_handler(struct mg_str name) {
size_t num_handlers = sizeof(s_apihandlers) / sizeof(s_apihandlers[0]);
size_t i;
if (name.len == 0) return NULL;
if (num_handlers == 0) return NULL;
for (i = 0; i < num_handlers; i++) {
struct apihandler *h = s_apihandlers[i];
size_t n = strlen(h->name);
if (n > name.len) continue;
if (strncmp(name.buf, h->name, n) != 0) continue;
if (name.len > n && name.buf[n] != '/') continue;
return h;
}
return NULL;
}
static struct apihandler *find_handler(struct mg_http_message *hm) {
if (hm->uri.len < 6 || strncmp(hm->uri.buf, "/api/", 5) != 0) return NULL;
return get_api_handler(mg_str_n(hm->uri.buf + 5, hm->uri.len - 5));
}
void mg_json_get_str2(struct mg_str json, const char *path, char *buf,
size_t len) {
struct mg_str s = mg_json_get_tok(json, path);
if (s.len > 1 && s.buf[0] == '"') {
mg_json_unescape(mg_str_n(s.buf + 1, s.len - 2), buf, len);
}
}
void mongoose_set_http_handlers(const char *name, ...) {
struct apihandler *h = get_api_handler(mg_str(name));
va_list ap;
va_start(ap, name);
if (h == NULL) {
MG_ERROR(("No API with name [%s]", name));
} else if (strcmp(h->type, "data") == 0) {
((struct apihandler_data *) h)->getter = va_arg(ap, void (*)(void *));
((struct apihandler_data *) h)->setter = va_arg(ap, void (*)(void *));
} else if (strcmp(h->type, "action") == 0) {
((struct apihandler_action *) h)->checker = va_arg(ap, bool (*)(void));
((struct apihandler_action *) h)->starter =
va_arg(ap, void (*)(struct mg_str));
} else if (strcmp(h->type, "ota") == 0 || strcmp(h->type, "upload") == 0) {
((struct apihandler_ota *) h)->opener =
va_arg(ap, void *(*) (char *, size_t));
((struct apihandler_ota *) h)->closer = va_arg(ap, bool (*)(void *));
((struct apihandler_ota *) h)->writer =
va_arg(ap, bool (*)(void *, void *, size_t));
} else if (strcmp(h->type, "custom") == 0) {
((struct apihandler_custom *) h)->reply =
va_arg(ap, void (*)(struct mg_connection *, struct mg_http_message *));
} else {
MG_ERROR(("Setting [%s] failed: not implemented", name));
}
va_end(ap);
}
#if WIZARD_ENABLE_HTTP_UI_LOGIN
struct user {
struct user *next;
char name[32]; // User name
char token[21]; // Login token
int level; // Access level
};
static struct user *s_users; // List of authenticated users
// Parse HTTP requests, return authenticated user or NULL
static struct user *authenticate(struct mg_http_message *hm) {
char user[100], pass[100];
struct user *u, *result = NULL;
mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass));
if (user[0] != '\0' && pass[0] != '\0') {
// Both user and password is set, auth by user/password via glue API
int level = glue_authenticate(user, pass);
MG_DEBUG(("user %s, level: %d", user, level));
if (level > 0) { // Proceed only if the firmware authenticated us
// uint64_t uid = hash(3, mg_str(user), mg_str(":"), mg_str(pass));
for (u = s_users; u != NULL && result == NULL; u = u->next) {
if (strcmp(user, u->name) == 0) result = u;
}
// Not yet authenticated, add to the list
if (result == NULL) {
result = (struct user *) calloc(1, sizeof(*result));
mg_snprintf(result->name, sizeof(result->name), "%s", user);
mg_random_str(result->token, sizeof(result->token) - 1);
result->level = level, result->next = s_users, s_users = result;
}
}
} else if (user[0] == '\0' && pass[0] != '\0') {
for (u = s_users; u != NULL && result == NULL; u = u->next) {
if (strcmp(u->token, pass) == 0) result = u;
}
}
MG_VERBOSE(("[%s/%s] -> %s", user, pass, result ? "OK" : "FAIL"));
return result;
}
static void handle_login(struct mg_connection *c, struct user *u) {
char cookie[256];
mg_snprintf(cookie, sizeof(cookie),
"Set-Cookie: access_token=%s; Path=/; "
"%sHttpOnly; SameSite=Lax; Max-Age=%d\r\n",
u->token, c->is_tls ? "Secure; " : "", 3600 * 24);
mg_http_reply(c, 200, cookie, "{%m:%m,%m:%d}", //
MG_ESC("user"), MG_ESC(u->name), //
MG_ESC("level"), u->level);
}
static void handle_logout(struct mg_connection *c) {
char cookie[256];
mg_snprintf(cookie, sizeof(cookie),
"Set-Cookie: access_token=; Path=/; "
"Expires=Thu, 01 Jan 1970 00:00:00 UTC; "
"%sHttpOnly; Max-Age=0; \r\n",
c->is_tls ? "Secure; " : "");
mg_http_reply(c, 401, cookie, "Unauthorized\n");
}
#endif // WIZARD_ENABLE_HTTP_UI_LOGIN
struct upload_state {
char marker; // Tells that we're a file upload connection
size_t expected; // POST data length, bytes
size_t received; // Already received bytes
void *fp; // Opened file
bool (*fn_close)(void *); // Close function
bool (*fn_write)(void *, void *, size_t); // Write function
};
struct action_state {
char marker; // Tells that we're an action connection
bool (*fn)(void); // Action status function
};
static void close_uploaded_file(struct upload_state *us) {
us->marker = 0;
if (us->fn_close != NULL && us->fp != NULL) {
us->fn_close(us->fp);
us->fp = NULL;
}
memset(us, 0, sizeof(*us));
}
static void upload_handler(struct mg_connection *c, int ev, void *ev_data) {
struct upload_state *us = (struct upload_state *) c->data;
if (sizeof(*us) > sizeof(c->data)) {
mg_error(
c, "FAILURE: sizeof(c->data) == %lu, need %lu. Set -DMG_DATA_SIZE=XXX",
sizeof(c->data), sizeof(*us));
return;
}
// Catch uploaded file data for both MG_EV_READ and MG_EV_HTTP_HDRS
if (us->marker == 'U' && ev == MG_EV_READ && us->expected > 0 &&
c->recv.len > 0) {
size_t alignment = 512; // Maximum flash write granularity (iMXRT, Pico)
size_t aligned = (us->received + c->recv.len < us->expected)
? aligned = MG_ROUND_DOWN(c->recv.len, alignment)
: c->recv.len; // Last write can be unaligned
bool ok = aligned > 0 ? us->fn_write(us->fp, c->recv.buf, aligned) : true;
us->received += aligned;
MG_DEBUG(("%lu chunk: %lu/%lu, %lu/%lu, ok: %d", c->id, aligned,
c->recv.len, us->received, us->expected, ok));
mg_iobuf_del(&c->recv, 0, aligned); // Delete received data
if (ok == false) {
mg_http_reply(c, 400, "", "Upload error\n");
close_uploaded_file(us);
c->is_draining = 1; // Close connection when response it sent
} else if (us->received >= us->expected) {
// Uploaded everything. Send response back
MG_INFO(("%lu done, %lu bytes", c->id, us->received));
mg_http_reply(c, 200, NULL, "%lu ok\n", us->received);
close_uploaded_file(us);
c->is_draining = 1; // Close connection when response it sent
}
}
// Close uploading file descriptor
if (us->marker == 'U' && ev == MG_EV_CLOSE) close_uploaded_file(us);
(void) ev_data;
}
static void prep_upload(struct mg_connection *c, struct mg_http_message *hm,
void *(*fn_open)(char *, size_t),
bool (*fn_close)(void *),
bool (*fn_write)(void *, void *, size_t)) {
struct upload_state *us = (struct upload_state *) c->data;
struct mg_str parts[3];
char path[MG_PATH_MAX];
memset(us, 0, sizeof(*us)); // Cleanup upload state
memset(parts, 0, sizeof(parts)); // Init match parts
mg_match(hm->uri, mg_str("/api/*/#"), parts); // Fetch file name
mg_url_decode(parts[1].buf, parts[1].len, path, sizeof(path), 0);
us->fp = fn_open(path, hm->body.len);
MG_DEBUG(("file: [%s] size: %lu fp: %p", path, hm->body.len, us->fp));
us->marker = 'U'; // Mark us as an upload connection
if (us->fp == NULL) {
mg_http_reply(c, 400, JSON_HEADERS, "File open error\n");
c->is_draining = 1;
} else {
us->expected = hm->body.len; // Store number of bytes we expect
us->fn_close = fn_close; // Store closing function
us->fn_write = fn_write; // Store writing function
mg_iobuf_del(&c->recv, 0, hm->head.len); // Delete HTTP headers
c->fn = upload_handler; // Change event handler function
c->pfn = NULL; // Detach HTTP handler
mg_call(c, MG_EV_READ, &c->recv.len); // Write initial data
}
}
static void handle_uploads(struct mg_connection *c, int ev, void *ev_data) {
struct upload_state *us = (struct upload_state *) c->data;
// Catch /upload requests early, without buffering whole body
// When we receive MG_EV_HTTP_HDRS event, that means we've received all
// HTTP headers but not necessarily full HTTP body
if (ev == MG_EV_HTTP_HDRS && us->marker == 0) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
struct apihandler *h = find_handler(hm);
if (h != NULL &&
(strcmp(h->type, "upload") == 0 || strcmp(h->type, "ota") == 0)) {
struct apihandler_upload *hu = (struct apihandler_upload *) h;
prep_upload(c, hm, hu->opener, hu->closer, hu->writer);
}
}
}
static void handle_action(struct mg_connection *c, struct mg_http_message *hm,
bool (*check_fn)(void),
void (*start_fn)(struct mg_str)) {
if (hm->body.len > 0) {
start_fn(hm->body);
if (check_fn()) {
struct action_state *as = (struct action_state *) c->data;
as->marker = 'A';
as->fn = check_fn;
} else {
mg_http_reply(c, 200, JSON_HEADERS, "false");
}
} else {
mg_http_reply(c, 200, JSON_HEADERS, "%s", check_fn() ? "true" : "false");
}
}
size_t print_struct(void (*out)(char, void *), void *ptr, va_list *ap) {
const struct attribute *a = va_arg(*ap, struct attribute *);
char *data = va_arg(*ap, char *);
size_t i, len = 0;
for (i = 0; a[i].name != NULL; i++) {
char *attrptr = data + a[i].offset;
len += mg_xprintf(out, ptr, "%s%m:", i == 0 ? "" : ",", MG_ESC(a[i].name));
if (strcmp(a[i].type, "int") == 0) {
len += mg_xprintf(out, ptr, "%d", *(int *) attrptr);
} else if (strcmp(a[i].type, "double") == 0) {
const char *fmt = a[i].format;
if (fmt == NULL) fmt = "%g";
len += mg_xprintf(out, ptr, fmt, *(double *) attrptr);
} else if (strcmp(a[i].type, "bool") == 0) {
len += mg_xprintf(out, ptr, "%s", *(bool *) attrptr ? "true" : "false");
} else if (strcmp(a[i].type, "string") == 0) {
len += mg_xprintf(out, ptr, "%m", MG_ESC(attrptr));
} else {
len += mg_xprintf(out, ptr, "null");
}
}
return len;
}
static void handle_object(struct mg_connection *c, struct mg_http_message *hm,
struct apihandler_data *h) {
void *data = calloc(1, h->data_size);
h->getter(data);
if (hm->body.len > 0 && h->data_size > 0) {
char *tmp = calloc(1, h->data_size);
size_t i;
memcpy(tmp, data, h->data_size);
for (i = 0; h->attributes[i].name != NULL; i++) {
const struct attribute *a = &h->attributes[i];
char jpath[100];
mg_snprintf(jpath, sizeof(jpath), "$.%s", a->name);
if (strcmp(a->type, "int") == 0) {
double d;
if (mg_json_get_num(hm->body, jpath, &d)) {
int v = (int) d;
memcpy(tmp + a->offset, &v, sizeof(v));
}
} else if (strcmp(a->type, "bool") == 0) {
mg_json_get_bool(hm->body, jpath, (bool *) (tmp + a->offset));
} else if (strcmp(a->type, "double") == 0) {
mg_json_get_num(hm->body, jpath, (double *) (tmp + a->offset));
} else if (strcmp(a->type, "string") == 0) {
mg_json_get_str2(hm->body, jpath, tmp + a->offset, a->size);
}
}
// If structure changes, increment version
if (memcmp(data, tmp, h->data_size) != 0) s_device_change_version++;
if (h->setter != NULL) h->setter(tmp); // Can be NULL if readonly
free(tmp);
h->getter(data); // Re-sync again after setting
}
mg_http_reply(c, 200, JSON_HEADERS, "{%M}\n", print_struct, h->attributes,
data);
free(data);
}
static size_t print_array(void (*out)(char, void *), void *ptr, va_list *ap) {
struct apihandler_array *ha = va_arg(*ap, struct apihandler_array *);
uint64_t size = *va_arg(*ap, uint64_t *);
uint64_t start = *va_arg(*ap, uint64_t *);
size_t i, max = 20, len = 0;
void *data = calloc(1, ha->data_size);
for (i = 0; i < max && start + i < size; i++) {
ha->getter(start + i, data);
if (i > 0) len += mg_xprintf(out, ptr, ",");
len += mg_xprintf(out, ptr, "{%M}", print_struct, ha->attributes, data);
}
free(data);
return len;
}
static void handle_array(struct mg_connection *c, struct mg_http_message *hm,
struct apihandler_array *h) {
char buf[40] = "";
uint64_t size = h->sizer();
uint64_t start = 0;
mg_http_get_var(&hm->query, "start", buf, sizeof(buf));
if (!mg_str_to_num(mg_str(buf), 10, &start, sizeof(start))) start = 0;
mg_http_reply(c, 200, JSON_HEADERS, "{%m:%llu, %m:%llu, %m:[%M]}\n",
MG_ESC("size"), size, MG_ESC("start"), start, MG_ESC("data"),
print_array, h, &size, &start);
}
size_t print_timeseries(void (*out)(char, void *), void *ptr, va_list *ap) {
uint32_t *timestamps = va_arg(*ap, uint32_t *);
double *values = va_arg(*ap, double *);
size_t count = va_arg(*ap, size_t);
size_t i, len = 0;
for (i = 0; i < count; i++) {
const char *comma = i == 0 ? "" : ",";
len += mg_xprintf(out, ptr, "%s[%lu,%g]", comma, timestamps[i], values[i]);
}
return len;
}
static void handle_api_call(struct mg_connection *c, struct mg_http_message *hm,
struct apihandler *h) {
if (strcmp(h->type, "object") == 0 || strcmp(h->type, "data") == 0) {
handle_object(c, hm, (struct apihandler_data *) h);
} else if (strcmp(h->type, "array") == 0) {
handle_array(c, hm, (struct apihandler_array *) h);
} else if (strcmp(h->type, "action") == 0) {
struct apihandler_action *ha = (struct apihandler_action *) h;
handle_action(c, hm, ha->checker, ha->starter);
// } else if (strcmp(h->type, "graph") == 0) {
// handle_graph(c, hm, (struct apihandler_graph *) h);
} else if (strcmp(h->type, "custom") == 0) {
((struct apihandler_custom *) h)->reply(c, hm);
} else {
mg_http_reply(c, 500, JSON_HEADERS, "API type %s unknown\n", h->type);
}
}
void glue_update_state(void) {
s_device_change_version++;
}
// Mongoose event handler function, gets called by the mg_mgr_poll()
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_HDRS && c->data[0] == 0) {
#if WIZARD_ENABLE_HTTP_UI_LOGIN
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/#"), NULL) ||
mg_match(hm->uri, mg_str("/websocket"), NULL)) {
struct apihandler *h = find_handler(hm);
struct user *u = authenticate(hm);
if ((u == NULL ||
(h != NULL && (u->level < h->read_level ||
(hm->body.len > 0 && u->level < h->write_level))))) {
mg_http_reply(c, 403, JSON_HEADERS, "Not Authorised\n");
c->data[0] = 'Z'; // Mark this connection as handled
}
}
#endif
}
// We're checking c->is_websocket cause WS connection use c->data
if (c->is_websocket == 0) handle_uploads(c, ev, ev_data);
if (ev == MG_EV_POLL && c->is_websocket == 0 && c->data[0] == 'A') {
// Check if action in progress is complete
struct action_state *as = (struct action_state *) c->data;
if (as->fn() == false) {
mg_http_reply(c, 200, JSON_HEADERS, "true");
memset(as, 0, sizeof(*as));
}
} else if (ev == MG_EV_HTTP_MSG && c->is_websocket == 0 && c->data[0] == 0) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
#if WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS
struct apihandler *h = find_handler(hm);
#if WIZARD_ENABLE_HTTP_UI_LOGIN
struct user *u = authenticate(hm);
if (mg_match(hm->uri, mg_str("/api/login"), NULL)) {
handle_login(c, u);
} else if (mg_match(hm->uri, mg_str("/api/logout"), NULL)) {
handle_logout(c);
} else
#endif
if (mg_match(hm->uri, mg_str("/api/ok"), NULL)) {
mg_http_reply(c, 200, JSON_HEADERS, "true\n");
} else if (mg_match(hm->uri, mg_str("/websocket"), NULL)) {
mg_ws_upgrade(c, hm, NULL);
} else if (mg_match(hm->uri, mg_str("/api/heartbeat"), NULL)) {
mg_http_reply(c, 200, JSON_HEADERS, "{%m:%lu}\n", MG_ESC("version"),
s_device_change_version);
} else if (h != NULL) {
handle_api_call(c, hm, h);
} else if (c->data[0] == 0)
#endif // WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS
{
#if WIZARD_ENABLE_HTTP_UI
struct mg_http_serve_opts opts;
memset(&opts, 0, sizeof(opts));
opts.root_dir = "/web_root/";
opts.fs = &mg_fs_packed;
opts.extra_headers = NO_CACHE_HEADERS;
mg_http_serve_dir(c, hm, &opts);
#else
mg_http_reply(c, 200, "", ":)\n");
#endif // WIZARD_ENABLE_HTTP_UI
}
// Show this request
MG_DEBUG(("%lu %.*s %.*s %lu -> %.*s %lu", c->id, hm->method.len,
hm->method.buf, hm->uri.len, hm->uri.buf, hm->body.len,
c->send.len > 15 ? 3 : 0, &c->send.buf[9], c->send.len));
} else if (ev == MG_EV_WS_MSG) {
// Got websocket frame. Received data is wm->data
struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
mg_ws_send(c, wm->data.buf, wm->data.len, WEBSOCKET_OP_TEXT);
} else if (ev == MG_EV_WS_MSG || ev == MG_EV_WS_CTL) {
// Ignore received data
} else if (ev == MG_EV_ACCEPT) {
if (c->fn_data != NULL) { // TLS listener
struct mg_tls_opts opts;
memset(&opts, 0, sizeof(opts));
opts.cert = mg_str(TLS_CERT);
opts.key = mg_str(TLS_KEY);
mg_tls_init(c, &opts);
}
}
}
#if WIZARD_ENABLE_WEBSOCKET
struct ws_handler {
unsigned timeout_ms;
void (*fn)(struct mg_connection *);
};
static struct ws_handler
s_ws_handlers[sizeof(((struct mg_connection *) 0)->data) /
sizeof(struct ws_handler)];
static size_t s_ws_handlers_count;
void mongoose_add_ws_handler(unsigned ms, void (*fn)(struct mg_connection *)) {
size_t max = sizeof(s_ws_handlers) / sizeof(s_ws_handlers[0]);
if (s_ws_handlers_count >= max) {
MG_ERROR(("WS handlers limit exceeded, max %lu", max));
} else {
s_ws_handlers[s_ws_handlers_count].timeout_ms = ms;
s_ws_handlers[s_ws_handlers_count].fn = fn;
s_ws_handlers_count++;
}
};
static void send_websocket_data(void) {
struct mg_connection *c;
uint64_t now = mg_millis();
for (c = g_mgr.conns; c != NULL; c = c->next) {
uint64_t *timers = (uint64_t *) &c->data[0];
size_t i;
if (c->is_websocket == 0) continue; // Not a websocket connection? Skip
if (c->send.len > 2048) continue; // Too much data already? Skip
for (i = 0; i < s_ws_handlers_count; i++) {
if (c->pfn_data == NULL ||
mg_timer_expired(&timers[i], s_ws_handlers[i].timeout_ms, now)) {
s_ws_handlers[i].fn(c);
c->pfn_data = (void *) 1;
}
}
}
}
#else
void mongoose_add_ws_handler(unsigned ms, void (*fn)(struct mg_connection *)) {
(void) ms, (void) fn;
MG_ERROR(("Websocket support is not enabled!"));
}
#endif // WIZARD_ENABLE_WEBSOCKET
#endif // WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS
#if WIZARD_ENABLE_SNTP
static uint64_t s_sntp_timer = 0;
bool s_sntp_response_received = false;
static void sntp_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_SNTP_TIME) {
uint64_t t = *(uint64_t *) ev_data;
glue_sntp_on_time(t);
s_sntp_response_received = true;
s_sntp_timer += (WIZARD_SNTP_INTERVAL_SECONDS) * 1000;
}
(void) c;
}
static void sntp_timer(void *param) {
// uint64_t t1 = mg_now(), t2 = mg_millis();
uint64_t timeout = (WIZARD_SNTP_INTERVAL_SECONDS) * 1000;
if (s_sntp_response_received == false) timeout = 1000;
// This function is called every second. Once we received a response,
// trigger SNTP sync less frequently, as set by the define
if (mg_timer_expired(&s_sntp_timer, timeout, mg_millis())) {
mg_sntp_connect(param, WIZARD_SNTP_URL, sntp_ev_handler, NULL);
}
}
#endif // WIZARD_ENABLE_SNTP
#if WIZARD_ENABLE_MQTT
static struct mongoose_mqtt_handlers s_mqtt_handlers = {
glue_mqtt_connect, glue_mqtt_tls_init, glue_mqtt_on_connect,
glue_mqtt_on_message, glue_mqtt_on_cmd};
struct mg_connection *g_mqtt_conn; // MQTT client connection
static void mqtt_event_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_CONNECT) {
s_mqtt_handlers.tls_init_fn(c);
} else if (ev == MG_EV_MQTT_OPEN) {
s_mqtt_handlers.on_connect_fn(c, *(int *) ev_data);
} else if (ev == MG_EV_MQTT_CMD) {
s_mqtt_handlers.on_cmd_fn(c, ev_data);
} else if (ev == MG_EV_MQTT_MSG) {
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
s_mqtt_handlers.on_message_fn(c, mm->topic, mm->data);
} else if (ev == MG_EV_CLOSE) {
g_mqtt_conn = NULL;
}
}
static void mqtt_timer(void *arg) {
if (g_mqtt_conn == NULL) {
g_mqtt_conn = s_mqtt_handlers.connect_fn(mqtt_event_handler);
}
(void) arg;
}
void mongoose_set_mqtt_handlers(struct mongoose_mqtt_handlers *h) {
if (h->connect_fn) s_mqtt_handlers.connect_fn = h->connect_fn;
if (h->tls_init_fn) s_mqtt_handlers.tls_init_fn = h->tls_init_fn;
if (h->on_message_fn) s_mqtt_handlers.on_message_fn = h->on_message_fn;
if (h->on_connect_fn) s_mqtt_handlers.on_connect_fn = h->on_connect_fn;
if (h->on_cmd_fn) s_mqtt_handlers.on_cmd_fn = h->on_cmd_fn;
}
#endif // WIZARD_ENABLE_MQTT
#if WIZARD_ENABLE_MODBUS
static struct mongoose_modbus_handlers s_modbus_handlers = {
glue_modbus_read_reg, glue_modbus_write_reg};
static void handle_modbus_pdu(struct mg_connection *c, uint8_t *buf,
size_t len) {
MG_DEBUG(("Received PDU %p len %lu, hexdump:", buf, len));
mg_hexdump(buf, len);
// size_t hdr_size = 8, max_data_size = sizeof(response) - hdr_size;
if (len < 12) {
MG_ERROR(("PDU too small"));
} else {
uint8_t func = buf[7]; // Function
bool success = false;
size_t response_len = 0;
uint8_t response[260];
memcpy(response, buf, 8);
// uint16_t tid = mg_ntohs(*(uint16_t *) &buf[0]); // Transaction ID
// uint16_t pid = mg_ntohs(*(uint16_t *) &buf[0]); // Protocol ID
// uint16_t len = mg_ntohs(*(uint16_t *) &buf[4]); // PDU length
// uint8_t uid = buf[6]; // Unit identifier
if (func == 6) { // write single holding register
uint16_t start = mg_ntohs(*(uint16_t *) &buf[8]);
uint16_t value = mg_ntohs(*(uint16_t *) &buf[10]);
success = s_modbus_handlers.write_reg_fn(start, value);
*(uint16_t *) &response[8] = mg_htons(start);
*(uint16_t *) &response[10] = mg_htons(value);
response_len = 12;
MG_DEBUG(("Glue returned %s", success ? "success" : "failure"));
} else if (func == 16) { // Write multiple
uint16_t start = mg_ntohs(*(uint16_t *) &buf[8]);
uint16_t num = mg_ntohs(*(uint16_t *) &buf[10]);
uint16_t i, *data = (uint16_t *) &buf[13];
if ((size_t) (num * 2 + 10) < sizeof(response)) {
for (i = 0; i < num; i++) {
success = s_modbus_handlers.write_reg_fn((uint16_t) (start + i),
mg_htons(data[i]));
if (success == false) break;
}
*(uint16_t *) &response[8] = mg_htons(start);
*(uint16_t *) &response[10] = mg_htons(num);
response_len = 12;
MG_DEBUG(("Glue returned %s", success ? "success" : "failure"));
}
} else if (func == 3 || func == 4) { // Read multiple
uint16_t start = mg_ntohs(*(uint16_t *) &buf[8]);
uint16_t num = mg_ntohs(*(uint16_t *) &buf[10]);
if ((size_t) (num * 2 + 9) < sizeof(response)) {
uint16_t i, val, *data = (uint16_t *) &response[9];
for (i = 0; i < num; i++) {
success = s_modbus_handlers.read_reg_fn((uint16_t) (start + i), &val);
if (success == false) break;
data[i] = mg_htons(val);
}
response[8] = (uint8_t) (num * 2);
response_len = 9 + response[8];
MG_DEBUG(("Glue returned %s", success ? "success" : "failure"));
}
}
if (success == false) {
response_len = 9;
response[7] |= 0x80;
response[8] = 4; // Server Device Failure
}
*(uint16_t *) &response[4] = mg_htons((uint16_t) (response_len - 6));
MG_DEBUG(("Sending PDU response %lu:", response_len));
mg_hexdump(response, response_len);
mg_send(c, response, response_len);
}
}
static void modbus_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
// if (ev == MG_EV_OPEN) c->is_hexdumping = 1;
if (ev == MG_EV_READ) {
uint16_t len;
if (c->recv.len < 7) return; // Less than minimum length, buffer more
len = mg_ntohs(*(uint16_t *) &c->recv.buf[4]); // PDU length
MG_INFO(("Got %lu, expecting %lu", c->recv.len, len + 6));
if (c->recv.len < len + 6U) return; // Partial frame, buffer more
handle_modbus_pdu(c, c->recv.buf, len + 6); // Parse PDU and call user
mg_iobuf_del(&c->recv, 0, len + 6U); // Delete received PDU
}
(void) ev_data;
}
void mongoose_set_modbus_handlers(struct mongoose_modbus_handlers *h) {
if (h->read_reg_fn) s_modbus_handlers.read_reg_fn = h->read_reg_fn;
if (h->write_reg_fn) s_modbus_handlers.write_reg_fn = h->write_reg_fn;
}
#endif // WIZARD_ENABLE_MODBUS
#if WIZARD_CAPTIVE_PORTAL
#if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32
#define CAPTIVE_PORTAL_URL "udp://0.0.0.0:5533"
#else
#define CAPTIVE_PORTAL_URL "udp://0.0.0.0:53"
#endif
static const uint8_t answer[] = {
0xc0, 0x0c, // Point to the name in the DNS question
0, 1, // 2 bytes - record type, A
0, 1, // 2 bytes - address class, INET
0, 0, 0, 120, // 4 bytes - TTL
0, 4 // 2 bytes - address length
};
static void dns_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_READ) {
struct mg_dns_rr rr; // Parse first question, offset 12 is header size
size_t n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr);
MG_DEBUG(("DNS request parsed, result=%d", (int) n));
if (n > 0) {
char buf[512];
uint32_t ip;
struct mg_dns_header *h = (struct mg_dns_header *) buf;
memset(buf, 0, sizeof(buf)); // Clear the whole datagram
h->txnid = ((struct mg_dns_header *) c->recv.buf)->txnid; // Copy tnxid
h->num_questions = mg_htons(1); // We use only the 1st question
h->num_answers = mg_htons(1); // And only one answer
h->flags = mg_htons(0x8400); // Authoritative response
memcpy(buf + sizeof(*h), c->recv.buf + sizeof(*h), n); // Copy question
memcpy(buf + sizeof(*h) + n, answer, sizeof(answer)); // And answer
#if MG_ENABLE_TCPIP
ip = MG_TCPIP_IFACE(c->mgr)->ip;
#else
ip = MG_TCPIP_IP;
#endif
memcpy(buf + sizeof(*h) + n + sizeof(answer), &ip, 4);
mg_send(c, buf, 12 + n + sizeof(answer) + 4); // And send it!
}
mg_iobuf_del(&c->recv, 0, c->recv.len);
}
(void) ev_data;
}
#endif // WIZARD_CAPTIVE_PORTAL
void mongoose_init(void) {
mg_mgr_init(&g_mgr); // Initialise event manager
mg_log_set(MG_LL_DEBUG); // Set log level to debug
#if WIZARD_ENABLE_HTTP
MG_INFO(("Starting HTTP listener"));
mg_http_listen(&g_mgr, HTTP_URL, http_ev_handler, NULL);
#endif
#if WIZARD_ENABLE_HTTPS
MG_INFO(("Starting HTTPS listener"));
mg_http_listen(&g_mgr, HTTPS_URL, http_ev_handler, "");
#endif
#if WIZARD_ENABLE_SNTP
MG_INFO(("Starting SNTP timer"));
mg_timer_add(&g_mgr, 1000, MG_TIMER_REPEAT, sntp_timer, &g_mgr);
#endif
#if WIZARD_DNS_TYPE == 2
g_mgr.dns4.url = WIZARD_DNS_URL;
#endif
#if WIZARD_ENABLE_MQTT
MG_INFO(("Starting MQTT reconnection timer"));
mg_timer_add(&g_mgr, 1000, MG_TIMER_REPEAT, mqtt_timer, &g_mgr);
#endif
#if WIZARD_ENABLE_MODBUS
{
char url[100];
mg_snprintf(url, sizeof(url), "tcp://0.0.0.0:%d", WIZARD_MODBUS_PORT);
MG_INFO(("Starting Modbus-TCP server on port %d", WIZARD_MODBUS_PORT));
mg_listen(&g_mgr, url, modbus_ev_handler, NULL);
}
#endif
#if WIZARD_CAPTIVE_PORTAL
MG_INFO(("Starting captive portal"));
mg_listen(&g_mgr, CAPTIVE_PORTAL_URL, dns_fn, NULL);
#endif
MG_INFO(("Mongoose init complete"));
}
void mongoose_poll(void) {
glue_lock();
mg_mgr_poll(&g_mgr, 10);
#if WIZARD_ENABLE_WEBSOCKET
send_websocket_data();
#endif
glue_unlock();
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
app0, app, ota_0, 0x10000, 0x1b0000,
app1, app, ota_1, , 0x1b0000,
spiffs, data, spiffs, , 0x80000,
1 nvs data nvs 0x9000 0x4000
2 otadata data ota 0xd000 0x2000
3 phy_init data phy 0xf000 0x1000
4 app0 app ota_0 0x10000 0x1b0000
5 app1 app ota_1 0x1b0000
6 spiffs data spiffs 0x80000

View File

@@ -0,0 +1,5 @@
CONFIG_BT_ENABLED=n
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y