mirror of
https://github.com/alexandrebobkov/ESP-Nodes.git
synced 2025-08-08 07:16:08 +00:00
mongoose
This commit is contained in:
3
ESP-Mongoose_UI/CMakeLists.txt
Normal file
3
ESP-Mongoose_UI/CMakeLists.txt
Normal 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
69
ESP-Mongoose_UI/README.md
Normal 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/)
|
4
ESP-Mongoose_UI/main/CMakeLists.txt
Normal file
4
ESP-Mongoose_UI/main/CMakeLists.txt
Normal 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")
|
15
ESP-Mongoose_UI/main/main.c
Normal file
15
ESP-Mongoose_UI/main/main.c
Normal 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();
|
||||
}
|
70
ESP-Mongoose_UI/main/wifi.c
Normal file
70
ESP-Mongoose_UI/main/wifi.c
Normal 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"));
|
||||
}
|
||||
}
|
22919
ESP-Mongoose_UI/mongoose/mongoose.c
Normal file
22919
ESP-Mongoose_UI/mongoose/mongoose.c
Normal file
File diff suppressed because it is too large
Load Diff
3413
ESP-Mongoose_UI/mongoose/mongoose.h
Normal file
3413
ESP-Mongoose_UI/mongoose/mongoose.h
Normal file
File diff suppressed because it is too large
Load Diff
6
ESP-Mongoose_UI/mongoose/mongoose_config.h
Normal file
6
ESP-Mongoose_UI/mongoose/mongoose_config.h
Normal 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
|
42
ESP-Mongoose_UI/mongoose/mongoose_fs.c
Normal file
42
ESP-Mongoose_UI/mongoose/mongoose_fs.c
Normal file
File diff suppressed because one or more lines are too long
210
ESP-Mongoose_UI/mongoose/mongoose_glue.c
Normal file
210
ESP-Mongoose_UI/mongoose/mongoose_glue.c
Normal 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);
|
||||
}
|
169
ESP-Mongoose_UI/mongoose/mongoose_glue.h
Normal file
169
ESP-Mongoose_UI/mongoose/mongoose_glue.h
Normal 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
|
928
ESP-Mongoose_UI/mongoose/mongoose_impl.c
Normal file
928
ESP-Mongoose_UI/mongoose/mongoose_impl.c
Normal 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();
|
||||
}
|
1
ESP-Mongoose_UI/mongoose/mongoose_wizard.json
Normal file
1
ESP-Mongoose_UI/mongoose/mongoose_wizard.json
Normal file
File diff suppressed because one or more lines are too long
6
ESP-Mongoose_UI/partitions.csv
Normal file
6
ESP-Mongoose_UI/partitions.csv
Normal 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,
|
|
5
ESP-Mongoose_UI/sdkconfig.defaults
Normal file
5
ESP-Mongoose_UI/sdkconfig.defaults
Normal 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
|
Reference in New Issue
Block a user