diff --git a/mongoose.h b/mongoose.h index d1f15158..2bb2e67f 100644 --- a/mongoose.h +++ b/mongoose.h @@ -3414,7 +3414,7 @@ struct mg_upload_state { static inline void mg_dash_file_add(struct mg_dash *dash, struct mg_str name, size_t size) { - struct mg_dash_file *f = mg_calloc(1, sizeof(*f)); + struct mg_dash_file *f = (struct mg_dash_file *) mg_calloc(1, sizeof(*f)); f->name = mg_strdup(name).buf; f->size = size; f->next = dash->files; @@ -3477,16 +3477,16 @@ static void file_ev_handler(struct mg_connection *c, int ev, void *ev_data) { // Serve file char path[128]; mg_snprintf(path, sizeof(path), "/tmp/%.*s", hm->uri.len, hm->uri.buf); - mg_http_serve_file(c, ev_data, path, NULL); + mg_http_serve_file(c, hm, path, NULL); } } else if (ev == MG_EV_READ && us->marker == 'U') { // Write uploaded data MG_DEBUG(("Uploading.. recv.len=%u", c->recv.len)); us->received += c->recv.len; - fwrite(c->recv.buf, 1, c->recv.len, us->ctx); + fwrite(c->recv.buf, 1, c->recv.len, (FILE *) us->ctx); c->recv.len = 0; if (us->received >= us->expected) { - fclose(us->ctx); + fclose((FILE *) us->ctx); MG_DEBUG(("Uploaded %lu", us->expected)); mg_http_reply(c, 200, NULL, "%lu uploaded\n", us->expected); mg_dash_send_change(c->mgr, dash, "files"); diff --git a/src/dash.h b/src/dash.h index 95f0dcb5..5223f806 100644 --- a/src/dash.h +++ b/src/dash.h @@ -269,7 +269,7 @@ struct mg_upload_state { static inline void mg_dash_file_add(struct mg_dash *dash, struct mg_str name, size_t size) { - struct mg_dash_file *f = mg_calloc(1, sizeof(*f)); + struct mg_dash_file *f = (struct mg_dash_file *) mg_calloc(1, sizeof(*f)); f->name = mg_strdup(name).buf; f->size = size; f->next = dash->files; @@ -332,16 +332,16 @@ static void file_ev_handler(struct mg_connection *c, int ev, void *ev_data) { // Serve file char path[128]; mg_snprintf(path, sizeof(path), "/tmp/%.*s", hm->uri.len, hm->uri.buf); - mg_http_serve_file(c, ev_data, path, NULL); + mg_http_serve_file(c, hm, path, NULL); } } else if (ev == MG_EV_READ && us->marker == 'U') { // Write uploaded data MG_DEBUG(("Uploading.. recv.len=%u", c->recv.len)); us->received += c->recv.len; - fwrite(c->recv.buf, 1, c->recv.len, us->ctx); + fwrite(c->recv.buf, 1, c->recv.len, (FILE *) us->ctx); c->recv.len = 0; if (us->received >= us->expected) { - fclose(us->ctx); + fclose((FILE *) us->ctx); MG_DEBUG(("Uploaded %lu", us->expected)); mg_http_reply(c, 200, NULL, "%lu uploaded\n", us->expected); mg_dash_send_change(c->mgr, dash, "files"); diff --git a/tutorials/device-dashboard/full/Makefile b/tutorials/device-dashboard/full/Makefile index b50492ed..0ec49cb9 100644 --- a/tutorials/device-dashboard/full/Makefile +++ b/tutorials/device-dashboard/full/Makefile @@ -3,7 +3,7 @@ PROG ?= example TOP ?= ../../.. SOURCES = main.c dashboard.c file_data.c $(TOP)/mongoose.c -CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -O0 +CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -O0 $(CFLAGS_EXTRA) PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_TLS=MG_TLS_BUILTIN -DMG_ENABLE_LINES=1 AFLAGS ?= -fsanitize=signed-integer-overflow,address,undefined,alignment diff --git a/tutorials/device-dashboard/full/dashboard.c b/tutorials/device-dashboard/full/dashboard.c index ccf3b9f1..9d02a6d6 100644 --- a/tutorials/device-dashboard/full/dashboard.c +++ b/tutorials/device-dashboard/full/dashboard.c @@ -3,6 +3,8 @@ #include "mongoose.h" +static struct mg_dash s_dashboard; + // Control panel // s_led1, s_led2, s_led3 are used to communicate LED status static bool s_led1, s_led2, s_led3; @@ -11,7 +13,7 @@ static struct mg_field fields_leds[] = { {"led1", MG_VAL_BOOL, &s_led1, sizeof(s_led1)}, {"led2", MG_VAL_BOOL, &s_led2, sizeof(s_led2)}, {"led3", MG_VAL_BOOL, &s_led3, sizeof(s_led3)}, - {NULL, 0, NULL, 0}, + {NULL, MG_VAL_INT, NULL, 0}, }; static void write_leds(void) { @@ -34,7 +36,7 @@ static struct mg_field fields_metrics[] = { {"ram", MG_VAL_INT, &s_ram, sizeof(s_ram)}, {"cpu", MG_VAL_INT, &s_cpu, sizeof(s_cpu)}, {"temperature", MG_VAL_DBL, &s_temperature, sizeof(s_temperature)}, - {NULL, 0, NULL, 0}, + {NULL, MG_VAL_INT, NULL, 0}, }; static void read_metrics(void) { @@ -52,7 +54,7 @@ static struct mg_field fields_settings[] = { {"volume", MG_VAL_DBL, &s_volume, sizeof(s_volume)}, {"name", MG_VAL_STR, &s_name, sizeof(s_name)}, {"log_level", MG_VAL_INT, &s_log_level, sizeof(s_log_level)}, - {NULL, 0, NULL, 0}, + {NULL, MG_VAL_INT, NULL, 0}, }; static void write_settings(void) { @@ -81,7 +83,7 @@ static char s_chart1_data[NUM_POINTS * POINT_SIZE + 2 + 1]; static struct mg_field fields_chart1[] = { {"data", MG_VAL_RAW, s_chart1_data, sizeof(s_chart1_data)}, - {NULL, 0, NULL, 0}, + {NULL, MG_VAL_INT, NULL, 0}, }; // Randomly generate graph points and serialise them into a string @@ -103,9 +105,8 @@ void read_chart1(void) { static char s_files[1024]; static struct mg_field fields_files[] = { {"data", MG_VAL_RAW, s_files, sizeof(s_files)}, - {NULL, 0, NULL, 0}, + {NULL, MG_VAL_INT, NULL, 0}, }; -static struct mg_dash s_dashboard; static void read_files(void) { size_t len = 0; struct mg_dash_file *f; @@ -127,9 +128,9 @@ static struct mg_field_set field_sets[] = { {"files", fields_files, read_files, NULL, 0, 0}, {0, 0, NULL, NULL, 0, 0}, }; -static struct mg_dash s_dashboard = {field_sets, NULL, NULL}; void mg_dash_init(struct mg_mgr *mgr) { + s_dashboard.sets = field_sets; mg_dash_file_add(&s_dashboard, mg_str("device-config.json"), 1234); mg_dash_file_add(&s_dashboard, mg_str("device-log-2026-04-25.txt"), 1327854); mg_http_listen(mgr, MG_HTTP_ADDR, mg_dash_ev_handler, &s_dashboard); diff --git a/tutorials/device-dashboard/full/dashboard.html b/tutorials/device-dashboard/full/dashboard.html index 29e42e33..3b051c6f 100644 --- a/tutorials/device-dashboard/full/dashboard.html +++ b/tutorials/device-dashboard/full/dashboard.html @@ -118,7 +118,7 @@ a, a:visited, a:link { text-decoration: none; } .header-container { max-width: var(--max-width); margin: 0 auto; display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-4); padding: var(--spacing-3) var(--spacing-6); } .header-left { display: flex; align-items: center; gap: var(--spacing-4); } .header-actions { display: inline-flex; align-items: center; justify-content: flex-end; gap: var(--spacing-3); flex-wrap: wrap; } -.logo { height: 2.8rem; margin-right: 1rem; width: auto; } +.logo { height: 2.8rem; margin: 0 1rem; width: auto; } .connection-badge { display: inline-flex; align-items: center; gap: 0.4rem; padding: 4px 14px; border-radius: 14px; font-weight: 600; } .connection-badge-online { color: rgb(4, 120, 87); border: 1px solid rgb(167, 243, 208); background: rgb(236, 253, 245); } .connection-badge-offline { color: rgb(120, 44, 57); border: 1px solid rgb(243, 177, 208); background: rgb(253, 246, 245); } diff --git a/tutorials/device-dashboard/minimal/Makefile b/tutorials/device-dashboard/minimal/Makefile new file mode 100644 index 00000000..0ec49cb9 --- /dev/null +++ b/tutorials/device-dashboard/minimal/Makefile @@ -0,0 +1,31 @@ +# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/ + +PROG ?= example +TOP ?= ../../.. +SOURCES = main.c dashboard.c file_data.c $(TOP)/mongoose.c +CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -O0 $(CFLAGS_EXTRA) +PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_TLS=MG_TLS_BUILTIN -DMG_ENABLE_LINES=1 +AFLAGS ?= -fsanitize=signed-integer-overflow,address,undefined,alignment + +all: $(PROG) + ./$(PROG) + +# Rebuild the program +$(PROG): $(SOURCES) $(TOP)/mongoose.h + $(CC) $(SOURCES) $(CFLAGS) $(PFLAGS) $(AFLAGS) -o $@ + +# Create an embedded filesystem containing compressed dashboard.html +file_data.c: dashboard.html + node $(TOP)/resources/inline.js dashboard.html -o out.html + node $(TOP)/resources/pack.js out.html:dashboard.html:gzip > file_data.c + @echo "On-flash size: `grep -oE '[0-9]+' file_data.c | wc -l`" + +purge: + curl -s https://purge.jsdelivr.net/gh/cesanta/mongoose@master/resources/dashboard.js + +# For development: rebuild and restart if any of the sources change +watch: + node $(TOP)/resources/watch.js -w main.c -w dashboard.c -w $(TOP)/mongoose.h -w dashboard.html -- make + +clean: + rm -rf $(PROG) *.exe *.obj *.dSYM mongoose out.* file_data.c diff --git a/tutorials/device-dashboard/minimal/dashboard.c b/tutorials/device-dashboard/minimal/dashboard.c new file mode 100644 index 00000000..278d93d6 --- /dev/null +++ b/tutorials/device-dashboard/minimal/dashboard.c @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2026 Cesanta Software Limited +// SPDX-License-Identifier: GPL-2.0-only or commercial + +#include "mongoose.h" + +static bool s_led1; +static char s_version[10]; + +static struct mg_field fields_status[] = { + {"led1", MG_VAL_BOOL, &s_led1, sizeof(s_led1)}, + {"version", MG_VAL_STR, &s_version, sizeof(s_version)}, + {NULL, MG_VAL_INT, NULL, 0}, +}; + +static void read_status(void) { + MG_INFO(("READING STATUS")); + //s_led1 = gpio_read(LED1_PIN); + mg_snprintf(s_version, sizeof(s_version), "1.2.3"); +} + +static void write_status(void) { + MG_INFO(("WRITING STATUS")); + //gpio_write(LED1_PIN, s_led1); +} + +// Each field set translates to REST API /api/NAME +static struct mg_field_set field_sets[] = { + {"status", fields_status, read_status, write_status, 0, 0}, + {0, 0, 0, 0, 0, 0}, +}; +static struct mg_dash s_dashboard = {field_sets, NULL, NULL}; + +void mg_dash_init(struct mg_mgr *mgr) { + mg_http_listen(mgr, MG_HTTP_ADDR, mg_dash_ev_handler, &s_dashboard); +} + +void mg_dash_poll(struct mg_mgr *mgr) { + (void) mgr; +} diff --git a/tutorials/device-dashboard/minimal/dashboard.html b/tutorials/device-dashboard/minimal/dashboard.html new file mode 100644 index 00000000..7f89b622 --- /dev/null +++ b/tutorials/device-dashboard/minimal/dashboard.html @@ -0,0 +1,60 @@ + + + + + + LED Control Dashboard + + + + + + + + +
+

LED control version ${status.version}

+ +
+
+ +
LED ${status.led1 ? 'ON' : 'OFF'}
+
+ +
+
+ This toggle button is a stylised checkbox. + data-bind="status.led1" binds it to the device variable. + data-autosave="1" makes it send POST /api/status + {"led1": VALUE} to the device every time the user toggles the + button. On the device side, sync_status() is called every time + the LED state is read or written. +
+ +
+ Open this page in two or more browser windows and see how the dashboards + stay synchronised when changes are made in any of them. +
+
+ +
+ +
+ + diff --git a/tutorials/device-dashboard/minimal/main.c b/tutorials/device-dashboard/minimal/main.c new file mode 100644 index 00000000..13db0c87 --- /dev/null +++ b/tutorials/device-dashboard/minimal/main.c @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2026 Cesanta Software Limited +// SPDX-License-Identifier: GPL-2.0-only or commercial + +#include "mongoose.h" + +int main(void) { + struct mg_mgr mgr; + + mg_mgr_init(&mgr); + mg_dash_init(&mgr); + + for (;;) { + mg_mgr_poll(&mgr, 10); + mg_dash_poll(&mgr); + } + + return 0; +}