diff --git a/docs/README.md b/docs/README.md index 93805f7d..1e5c90b3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3683,12 +3683,12 @@ Usage example: redirecting logs to syslog. ```c static void mylog(uint8_t ch) { - static char buf[128]; - static struct mg_iobuf log = { .buf = buf, .size = sizeof(buf), .len = 0}; - log.buf[log.len++] = ch; - if (ch == '\n' || log.len >= log.size) { - syslog(LOG_INFO, "%.*s", (int) log.len, log.buf); - log.len = 0; + static char buf[256]; + static size_t len; + buf[len++] = ch; + if (ch == '\n' || len >= sizeof(buf)) { + syslog(LOG_INFO, "%.*s", (int) len, buf); // Send logs + len = 0; } } ... diff --git a/examples/esp32/uart-bridge/CMakeLists.txt b/examples/esp32/uart-bridge/CMakeLists.txt new file mode 100644 index 00000000..88ab9137 --- /dev/null +++ b/examples/esp32/uart-bridge/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(mongoose-esp32-example) diff --git a/examples/esp32/uart-bridge/Makefile b/examples/esp32/uart-bridge/Makefile new file mode 100644 index 00000000..e9909d35 --- /dev/null +++ b/examples/esp32/uart-bridge/Makefile @@ -0,0 +1,29 @@ +CWD = $(realpath $(CURDIR)) +MNT = $(realpath $(CURDIR)/../../..) +PORT ?= /dev/ttyUSB0 +CMD ?= build + +all: example + +example: + true + +build: Makefile $(wildcard main/*) + docker run --rm $(DA) -v $(MNT):$(MNT) -w $(CWD) espressif/idf idf.py $(CMD) + +flash: build +flash: CMD = flash monitor +flash: DA = --device $(PORT) + +bridge.hex: build + esputil mkhex \ + 0x8000 build/partition_table/partition-table.bin \ + 0x1000 build/bootloader/bootloader.bin \ + 0x100000 build/mongoose-esp32-example.bin > $@ + +flash2: bridge.hex + esputil -b 921600 -fp 0x220 flash bridge.hex + esputil monitor + +clean: + rm -rf build diff --git a/examples/esp32/uart-bridge/README.md b/examples/esp32/uart-bridge/README.md new file mode 100644 index 00000000..aa30f0a7 --- /dev/null +++ b/examples/esp32/uart-bridge/README.md @@ -0,0 +1,61 @@ +# A UART to network bridge for ESP32 + +This example is a demonstration of how Mongoose Library could be integrated +into an embedded device and provide a UART-to-Network bridge capability: + +- A device opens listening TCP port and Websocket port and waits for connections +- When a client connects, data is exchanged with the device's UART +- Everything that client send, is sent to the UART +- Everything that is read from the UART, gets sent to the client +- Multiple clients are allowed +- Live UART console allows to talk to the UART from the web page +- Web UI is hardcoded into the binary and does not need a filesystem + +# Screenshots + +![](../../uart-bridge/screenshots/dashboard.png) + +# Build and flash + +Build requires Docker installed, and uses Espressif's ESP-IDF docker image: + +```sh +make build +make flash PORT=/dev/YOURSERIAL +``` + +# Flash pre-built firmware + +You can flash a pre-built firmware to the ESP32 device using the following +instructions: + +1. Connect your ESP32 device to the workstation. It should be accessible + via a serial port +2. Download and unzip ESP32 flashing tool from https://mongoose.ws/downloads/esputil.zip +3. Download a prebuilt firmware https://mongoose.ws/downloads/uart-bridge.hex into the unzipped directory +4. Start command prompt (or terminal on Mac/Linux). Run `cd + PATH/TO/esputil` to go into the unzipped `esputil/` directory. After that, run + the following command (change `COMPORT` to the board's serial port): + + | OS | Command | + | ------- | ------- | + | Windows |
.\windows\esputil -p COMPORT flash uart-bridge.hex
|
+ | Linux | ./linux/esputil -p COMPORT flash uart-bridge.hex
|
+ | MacOS | ./macos/esputil -p COMPORT flash uart-bridge.hex
|
+
+Next step is to monitor and follow the instructions.
+
+```sh
+esputil -p COMPORT monitor
+```
+
+Note: if monitor command shows constant restarts, the flash parameters
+settings can be wrong. Reflash your device with `-fp ...` flash parameters
+settings. For example, WROOM-32 based boards use `-fp 0x220`:
+
+```sh
+esputil -p COMPORT -fp 0x220 flash uart-bridge.hex
+```
+
+For more on possible options for flash parameters, see
+https://github.com/cpq/mdk#flash-parameters
diff --git a/examples/esp32/uart-bridge/main/CMakeLists.txt b/examples/esp32/uart-bridge/main/CMakeLists.txt
new file mode 100644
index 00000000..f8c1bbe1
--- /dev/null
+++ b/examples/esp32/uart-bridge/main/CMakeLists.txt
@@ -0,0 +1,10 @@
+idf_component_register(SRCS "main.c"
+ "wifi.c"
+ "uart.c"
+ "cli.c"
+ "net.c"
+ "packed_fs.c"
+ "mongoose.c")
+component_compile_options(-DMG_ENABLE_LINES=1)
+component_compile_options(-DMG_ENABLE_PACKED_FS=1)
+component_compile_options(-DUART_API_IMPLEMENTED=1)
diff --git a/examples/esp32/uart-bridge/main/cli.c b/examples/esp32/uart-bridge/main/cli.c
new file mode 100644
index 00000000..74cd220d
--- /dev/null
+++ b/examples/esp32/uart-bridge/main/cli.c
@@ -0,0 +1,89 @@
+#include "main.h"
+
+static void cli_wifi(const char *ssid, const char *pass) {
+ if (wifi_init(ssid, pass)) {
+ mg_file_printf(&mg_fs_posix, WIFI_FILE, "{%Q:%Q,%Q:%Q}\n", "ssid", ssid,
+ "pass", pass);
+ MG_INFO(("Reboot now"));
+ }
+}
+
+static void cli_ls(void) {
+ DIR *dirp = opendir(FS_ROOT);
+ struct dirent *dp;
+ if (dirp == NULL) {
+ MG_ERROR(("Cannot open FS: %d", errno));
+ } else {
+ while ((dp = readdir(dirp)) != NULL) {
+ /* Do not show current and parent dirs */
+ if (strcmp((const char *) dp->d_name, ".") == 0 ||
+ strcmp((const char *) dp->d_name, "..") == 0) {
+ continue;
+ } else {
+ printf("%s\n", dp->d_name);
+ }
+ }
+ closedir(dirp);
+ }
+}
+
+static void cli_cat(const char *fname) {
+ char path[MG_PATH_MAX];
+ snprintf(path, sizeof(path), "%s/%s", FS_ROOT, fname);
+ FILE *fp = fopen(path, "r");
+ if (fp != NULL) {
+ int ch;
+ while ((ch = fgetc(fp)) != EOF) putchar(ch);
+ fclose(fp);
+ }
+}
+
+static void cli_rm(const char *fname) {
+ char path[100];
+ snprintf(path, sizeof(path), "%s/%s", FS_ROOT, fname);
+ remove(path);
+}
+
+void cli(uint8_t input_byte) {
+ static struct mg_iobuf in;
+
+ if (input_byte == 0 || input_byte == 0xff) return;
+ if (in.len >= 128) in.len = 0;
+ mg_iobuf_add(&in, in.len, &input_byte, sizeof(input_byte), 32);
+
+ if (input_byte == '\n') {
+ const char *arrow = "---";
+ char buf0[10], buf1[50], buf2[250];
+
+ in.buf[in.len] = '\0';
+ buf0[0] = buf1[0] = buf2[0] = '\0';
+ sscanf((char *) in.buf, "%9s %49s %249[^\r\n]", buf0, buf1, buf2);
+
+ printf("%s CLI command: '%s'\n", arrow, buf0);
+ if (strcmp(buf0, "reboot") == 0) {
+ esp_restart();
+ } else if (strcmp(buf0, "ls") == 0) {
+ cli_ls();
+ } else if (strcmp(buf0, "cat") == 0) {
+ cli_cat(buf1);
+ } else if (strcmp(buf0, "rm") == 0) {
+ cli_rm(buf1);
+ } else if (strcmp(buf0, "reboot") == 0) {
+ esp_restart();
+ } else if (strcmp(buf0, "ll") == 0) {
+ mg_log_set(buf1);
+ } else if (strcmp(buf0, "wifi") == 0) {
+ cli_wifi(buf1, buf2);
+ } else {
+ printf("%s %s\n", arrow, "Unknown command. Usage:");
+ printf("%s %s\n", arrow, " set NAME VALUE");
+ printf("%s %s\n", arrow, " rm FILENAME");
+ printf("%s %s\n", arrow, " cat FILENAME");
+ printf("%s %s\n", arrow, " ls");
+ printf("%s %s\n", arrow, " reboot");
+ printf("%s %s\n", arrow, " wifi WIFI_NET WIFI_PASS");
+ }
+ printf("%s %s\n", arrow, "CLI output end");
+ in.len = 0;
+ }
+}
diff --git a/examples/esp32/uart-bridge/main/main.c b/examples/esp32/uart-bridge/main/main.c
new file mode 100644
index 00000000..a38e125d
--- /dev/null
+++ b/examples/esp32/uart-bridge/main/main.c
@@ -0,0 +1,49 @@
+// Copyright (c) 2020 Cesanta Software Limited
+// All rights reserved
+
+#include "main.h"
+
+const char *s_listening_url = "http://0.0.0.0:80";
+
+char *config_read(void) {
+ return mg_file_read(&mg_fs_posix, FS_ROOT "/config.json", NULL);
+}
+
+void config_write(struct mg_str config) {
+ mg_file_write(&mg_fs_posix, FS_ROOT "/config.json", config.ptr, config.len);
+}
+
+void app_main(void) {
+ // Mount filesystem
+ esp_vfs_spiffs_conf_t conf = {
+ .base_path = FS_ROOT, .max_files = 20, .format_if_mount_failed = true};
+ int res = esp_vfs_spiffs_register(&conf);
+ MG_INFO(("FS at %s initialised, status: %d", conf.base_path, res));
+
+ // Try to connect to wifi by using saved WiFi credentials
+ char *json = mg_file_read(&mg_fs_posix, WIFI_FILE, NULL);
+ if (json != NULL) {
+ char *ssid = mg_json_get_str(mg_str(json), "$.ssid");
+ char *pass = mg_json_get_str(mg_str(json), "$.pass");
+ while (!wifi_init(ssid, pass)) (void) 0;
+ free(ssid);
+ free(pass);
+ free(json);
+ } else {
+ // If WiFi is not configured, run CLI until configured
+ MG_INFO(("WiFi is not configured, running CLI. Press enter"));
+ for (;;) {
+ uint8_t ch = getchar();
+ cli(ch);
+ usleep(10000);
+ }
+ }
+
+ // Connected to WiFi, now start HTTP server
+ struct mg_mgr mgr;
+ mg_mgr_init(&mgr);
+ mg_log_set("3");
+ MG_INFO(("Mongoose v%s on %s", MG_VERSION, s_listening_url));
+ mg_http_listen(&mgr, s_listening_url, uart_bridge_fn, &mgr);
+ for (;;) mg_mgr_poll(&mgr, 10); // Infinite event loop
+}
diff --git a/examples/esp32/uart-bridge/main/main.h b/examples/esp32/uart-bridge/main/main.h
new file mode 100644
index 00000000..458e0503
--- /dev/null
+++ b/examples/esp32/uart-bridge/main/main.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "mongoose.h"
+
+#include "driver/gpio.h"
+#include "driver/uart.h"
+#include "esp_spiffs.h"
+#include "freertos/FreeRTOS.h"
+
+#define FS_ROOT "/spiffs"
+#define WIFI_FILE FS_ROOT "/wifi.json"
+#define UART_NO 1
+
+void uart_bridge_fn(struct mg_connection *, int, void *, void *);
+int uart_read(void *buf, size_t len);
+bool wifi_init(const char *ssid, const char *pass);
+void cli(uint8_t ch);
diff --git a/examples/esp32/uart-bridge/main/mongoose.c b/examples/esp32/uart-bridge/main/mongoose.c
new file mode 120000
index 00000000..7a2752cb
--- /dev/null
+++ b/examples/esp32/uart-bridge/main/mongoose.c
@@ -0,0 +1 @@
+../../../../mongoose.c
\ No newline at end of file
diff --git a/examples/esp32/uart-bridge/main/mongoose.h b/examples/esp32/uart-bridge/main/mongoose.h
new file mode 120000
index 00000000..daff1633
--- /dev/null
+++ b/examples/esp32/uart-bridge/main/mongoose.h
@@ -0,0 +1 @@
+../../../../mongoose.h
\ No newline at end of file
diff --git a/examples/esp32/uart-bridge/main/net.c b/examples/esp32/uart-bridge/main/net.c
new file mode 120000
index 00000000..11b4a308
--- /dev/null
+++ b/examples/esp32/uart-bridge/main/net.c
@@ -0,0 +1 @@
+../../../uart-bridge/net.c
\ No newline at end of file
diff --git a/examples/esp32/uart-bridge/main/packed_fs.c b/examples/esp32/uart-bridge/main/packed_fs.c
new file mode 120000
index 00000000..2ad10a44
--- /dev/null
+++ b/examples/esp32/uart-bridge/main/packed_fs.c
@@ -0,0 +1 @@
+../../../uart-bridge/packed_fs.c
\ No newline at end of file
diff --git a/examples/esp32/uart-bridge/main/uart.c b/examples/esp32/uart-bridge/main/uart.c
new file mode 100644
index 00000000..f0cc7e8a
--- /dev/null
+++ b/examples/esp32/uart-bridge/main/uart.c
@@ -0,0 +1,44 @@
+#include "main.h"
+
+int uart_close(int no) {
+ return uart_driver_delete(no);
+}
+
+int uart_open(int no, int rx, int tx, int cts, int rts, int baud) {
+ uart_config_t uart_config = {
+ .baud_rate = baud,
+ .data_bits = UART_DATA_8_BITS,
+ .parity = UART_PARITY_DISABLE,
+ .stop_bits = UART_STOP_BITS_1,
+ .flow_ctrl = cts > 0 && rts > 0 ? UART_HW_FLOWCTRL_CTS_RTS
+ : cts > 0 ? UART_HW_FLOWCTRL_CTS
+ : rts > 0 ? UART_HW_FLOWCTRL_RTS
+ : UART_HW_FLOWCTRL_DISABLE,
+ };
+ int e1 = uart_param_config(no, &uart_config);
+ int e2 = uart_set_pin(no, tx, rx, rts, cts);
+ int e3 =
+ uart_driver_install(no, UART_FIFO_LEN * 2, UART_FIFO_LEN * 2, 0, NULL, 0);
+ MG_INFO(("%d: %d/%d/%d, %d %d %d", no, rx, tx, baud, e1, e2, e3));
+ if (e1 != ESP_OK || e2 != ESP_OK || e3 != ESP_OK) return -1;
+ return no;
+}
+
+void uart_init(int tx, int rx, int baud) {
+ uart_open(UART_NO, rx, tx, -1, -1, baud);
+}
+
+int uart_read(void *buf, size_t len) {
+ size_t x = 0;
+ int no = UART_NO;
+ if (uart_get_buffered_data_len(no, &x) != ESP_OK || x == 0) return 0;
+ int n = uart_read_bytes(no, buf, len, 10 / portTICK_PERIOD_MS);
+ MG_DEBUG(("%d bytes: [%.*s]", n, n, (char *) buf));
+ return n;
+}
+
+int uart_write(const void *buf, int len) {
+ int no = UART_NO;
+ MG_DEBUG(("%d bytes: [%.*s]", len, len, (char *) buf));
+ return uart_write_bytes(no, (const char *) buf, len);
+}
diff --git a/examples/esp32/uart-bridge/main/wifi.c b/examples/esp32/uart-bridge/main/wifi.c
new file mode 100644
index 00000000..b4a7e2db
--- /dev/null
+++ b/examples/esp32/uart-bridge/main/wifi.c
@@ -0,0 +1,108 @@
+// Code taken from the ESP32 IDF WiFi station Example
+
+#include