diff --git a/resources/AGENTS.md b/resources/AGENTS.md new file mode 100644 index 00000000..5dec3726 --- /dev/null +++ b/resources/AGENTS.md @@ -0,0 +1,86 @@ +# Device dashboard + +This is the AI specification file for the Mongoose based Web device dashboard functionality + +Mongoose-base device dashboard requires following files: +- mongoose.h +- mongoose.c +- dashboard.c +- file_data.c + +The `file_data.c` is a generated file from `dashboard.html` using Nodejs: +```sh +node html2c.js dashboard.html -o file_data.c +``` +The `html2c.js` file can be taken from https://github.com/cesanta/mongoose/blob/master/resources/html2c.js + + +## General rules + +Your goal is to produce a dashboard.c and dashboard.html files +If your project does not yet have those, fetch from here: + +https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/dashboard.c +https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/dashboard.html + +If your project already has dashboard.c and dashboard.html, do NOT fetch anything. + +The C API header file is at https://github.com/cesanta/mongoose/blob/master/src/dash.h +The minimal reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/ +The full reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/full/ + +## dashboard.html + +For editing dashboard.html, use only Mongoose `data-bind` and `${...}` template syntax +No vanilla JS event listeners, no fetch calls, no custom reactive logic. +Do not make any changes to dashboard.html unless asked by a user. + +## dashboard.c + +Do not make any changes to dashboard.c unless asked by a user. + +## CubeMX project + +If you have installed I-CUBE-Mongoose pack, then all required files are installed +in the `Middlewares/Third_Party/Cesanta_Mongoose/` directory. Do not fetch +dashboard.html, or any other file, from the Mongoose repository. + +Add `#include "mongoose.h"` to the "USER CODE BEGIN Includes" block + +Add automatic `file_data.c` generation for the build step - either Makefile, +or CMakeLists.txt or whatever is appropriate for the selected IDE. +```sh +node html2c.js dashboard.html -o file_data.c +``` + + +To the "USER CODE BEGIN 0" block, add debug print redirect. If syscalls.c already +has a weak _write override, ignore it and still add this. +```c +int _write(int fd, unsigned char *buf, int len) { + HAL_UART_Transmit(&huart3, buf, len, HAL_MAX_DELAY); + return len; +} +``` + +Make sure main() initialises and processes dashboard: + +```c +/* USER CODE BEGIN WHILE */ +struct mg_mgr mgr; +mg_mgr_init(&mgr); +mg_dash_init(&mgr); + +while (1) +{ + mg_mgr_poll(&mgr, 1); + mg_dash_poll(&mgr); +/* USER CODE END WHILE */ +``` + +In order to verify the validity of your edits, compile `dashboard.c` using +embedded or host compiler in `Middlewares/Third_Party/Cesanta_Mongoose/`: + +```sh +$(CC) -c dashboard.c +``` diff --git a/resources/I-CUBE-Mongoose/Cesanta.I-CUBE-Mongoose.pdsc b/resources/I-CUBE-Mongoose/Cesanta.I-CUBE-Mongoose.pdsc index a0a3c131..b6a49aaf 100644 --- a/resources/I-CUBE-Mongoose/Cesanta.I-CUBE-Mongoose.pdsc +++ b/resources/I-CUBE-Mongoose/Cesanta.I-CUBE-Mongoose.pdsc @@ -8,6 +8,7 @@ License.txt https://mongoose.ws/contact/ + See https://github.com/cesanta/mongoose/releases/tag/7.21 See https://github.com/cesanta/mongoose/releases/tag/7.21 See https://github.com/cesanta/mongoose/releases/tag/7.21 See https://github.com/cesanta/mongoose/releases/tag/7.21 @@ -23,24 +24,26 @@ - + Networking Core + - + Web Device Dashboard - + + - + MQTT Client diff --git a/resources/I-CUBE-Mongoose/Makefile b/resources/I-CUBE-Mongoose/Makefile index 4cd7b3ba..e11d5b2f 100644 --- a/resources/I-CUBE-Mongoose/Makefile +++ b/resources/I-CUBE-Mongoose/Makefile @@ -1,12 +1,14 @@ -VERSION = 7.21.5 +VERSION = 7.21.6 REPO_ROOT = ../.. +MX ?= /Applications/STMicroelectronics/STM32CubeMX.app/Contents/MacOs/STM32CubeMX FILES = Cesanta.I-CUBE-Mongoose.pdsc Release_Notes.html mongoose_config.h README.md GENERATED = License.txt \ mongoose.h mongoose.c \ - dashboard.c file_data.c dashboard.html \ + dashboard.c file_data.c dashboard.html html2c.js \ mongoose_mqtt.c -all: Cesanta.I-CUBE-Mongoose.$(VERSION).pack +PACK = Cesanta.I-CUBE-Mongoose.$(VERSION).pack +all: $(PACK) Cesanta.I-CUBE-Mongoose.$(VERSION).pack: $(FILES) $(GENERATED) zip -q -r $@ $^ @@ -33,6 +35,9 @@ file_data.c: make -C $(REPO_ROOT)/tutorials/device-dashboard/full $@ cp $(REPO_ROOT)/tutorials/device-dashboard/full/$@ $@ +html2c.js: + cp $(REPO_ROOT)/resources/$@ $@ + mongoose_mqtt.c: cp $(REPO_ROOT)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c $@ @@ -42,5 +47,23 @@ mongoose.h: Mongoose mongoose.c: Mongoose cp $(REPO_ROOT)/mongoose.c $@ +pk: $(PACK) + rm -f gen.mx + echo swmgr remove Cesanta.I-CUBE-Mongoose.$(VERSION) >> gen.mx + echo swmgr install `pwd`/Cesanta.I-CUBE-Mongoose.$(VERSION).pack ask >> gen.mx + echo exit >> gen.mx + $(MX) -q `pwd`/gen.mx + + +mx: $(PACK) + mkdir -p $@ + cp ../downloads/nucleo_h563zi.ioc $@ + V=`grep 'Mcu.ThirdParty0' $@/nucleo_h563zi.ioc | sed 's,^.*se.,,'`; sed -i '' "s,$$V,$(VERSION),g" $@/nucleo_h563zi.ioc + rm -f gen.mx + echo config load `pwd`/$@/nucleo_h563zi.ioc >> gen.mx + echo project generate >> gen.mx + echo exit >> gen.mx + $(MX) -q `pwd`/gen.mx + clean: - rm -rf *.pack STM32CubeMX/*.$(VERSION)_*.xml $(GENERATED) + rm -rf *.pack STM32CubeMX/*.$(VERSION)_*.xml $(GENERATED) gen.mx mx diff --git a/resources/I-CUBE-Mongoose/README.md b/resources/I-CUBE-Mongoose/README.md index 28c4c27d..bca3bca7 100644 --- a/resources/I-CUBE-Mongoose/README.md +++ b/resources/I-CUBE-Mongoose/README.md @@ -102,6 +102,11 @@ Hi from Mongoose! Tick 38187 ## Add full device dashboard Enable "Mongoose Web Device Dashboard" component in CubeMX. Regenerate code. +This component installs the following files: +- `dashboard.c` - a C backend side +- `dashboard.html` - an HTML frontend side: the Web UI itself +- `file_data.c` - this file is generated from `dashboard.html` +- `html2c.js` - a node.js script that generates `file_data.c` using this command: `node html2c.js dashboard.html -o file_data.c`. This script first inlines all external resources into an amalgamated HTML file - so it does not depend on anything external, and then zips it and packs into a Mongoose embedded filesystem. This way it takes the minimum space on flash. Remove the hello world server if you added it. Modify your while loop to look like this: @@ -122,6 +127,19 @@ while (1) Rebuild, reflash your board and enjoy the fully functional device dashboard that you can tailor to your firmware. +To customise your dashboard, edit `dashboard.c` and `dashboard.html`. +After editing `dashboard.html`, regenerate `file_data.c` using this command: + +```sh +node html2c.js dashboard.html -o file_data.c +``` + +For agentic development, use https://github.com/cesanta/mongoose/tree/master/resources/AGENTS.md +Use a prompt like this: + +> Read and apply https://github.com/cesanta/mongoose/tree/master/resources/AGENTS.md + + ## Add MQTT Client Enable "Mongoose MQTT Client" component in CubeMX. Regenerate code. diff --git a/resources/downloads/Makefile b/resources/downloads/Makefile index 18b8ca78..64aecb47 100644 --- a/resources/downloads/Makefile +++ b/resources/downloads/Makefile @@ -3,7 +3,7 @@ ST_TARGETS = nucleo_h723zg nucleo_f756zg nucleo_h563zi # ST_TARGETS = $(subst .ioc,,$(wildcard *.ioc)) PROJECTS = full minimal TARGETS = desktop $(ST_TARGETS) -MX ?= /Applications/STM32CubeMX.app/Contents/MacOs/STM32CubeMX +MX ?= /Applications/STMicroelectronics/STM32CubeMX.app/Contents/MacOs/STM32CubeMX DIRS = $(foreach P,$(PROJECTS),$(foreach T,$(TARGETS),$(T)_$(P))) ZIPS = $(foreach D,$(DIRS),$(D).zip) diff --git a/resources/html2c.js b/resources/html2c.js new file mode 100644 index 00000000..ec2253db --- /dev/null +++ b/resources/html2c.js @@ -0,0 +1,179 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved +// +// Generate a Mongoose packed filesystem C file from an HTML file after +// inlining referenced CSS, JS, images, and icons. +// +// Usage: +// node html2c.js [-o OUTPUT.c] HTML_FILE + +const fs = require('fs'); +const path = require('path'); +const http = require('http'); +const https = require('https'); + +const args = process.argv.slice(2); +let outputFile = null; +let inputFile = null; + +for (let i = 0; i < args.length; i++) { + if (args[i] === '-o' && i + 1 < args.length) { + outputFile = args[++i]; + } else if (!args[i].startsWith('-') && inputFile === null) { + inputFile = args[i]; + } +} + +if (!inputFile) { + console.error('Usage: node html2c.js [-o OUTPUT.c] HTML_FILE'); + process.exit(1); +} + +const baseDir = path.dirname(path.resolve(inputFile)); +const html = fs.readFileSync(inputFile, 'utf8'); + +function fetchUrl(url) { + return new Promise((resolve, reject) => { + const client = url.startsWith('https') ? https : http; + client + .get(url, res => { + const chunks = []; + res.on('data', chunk => chunks.push(chunk)); + res.on('end', () => resolve(Buffer.concat(chunks))); + res.on('error', reject); + }) + .on('error', reject); + }); +} + +async function readAsset(name, encoding) { + if (name.startsWith('http://') || name.startsWith('https://')) { + const data = await fetchUrl(name); + return encoding ? data.toString(encoding) : data; + } + return fs.readFileSync(path.join(baseDir, name), encoding || null); +} + +function attr(match, name) { + const re = new RegExp(`${name}=["']([^"']+)["']`, 'i'); + const m = match.match(re); + return m ? m[1] : null; +} + +function replaceAttr(match, name, value) { + const re = new RegExp(`${name}=(["'])[^"']+\\1`, 'i'); + return match.replace(re, `${name}="${value}"`); +} + +async function inlineCss(match) { + const href = attr(match, 'href'); + if (!href) return match; + const css = await readAsset(href, 'utf8'); + return ``; +} + +async function inlineJs(match) { + const src = attr(match, 'src'); + if (!src) return match; + const js = await readAsset(src, 'utf8'); + return ``; +} + +async function inlineImg(match) { + const src = attr(match, 'src'); + if (!src) return match; + const ext = path.extname(src).toLowerCase(); + let mime = 'image/png'; + if (ext === '.svg') mime = 'image/svg+xml'; + else if (ext === '.jpg' || ext === '.jpeg') mime = 'image/jpeg'; + else if (ext === '.gif') mime = 'image/gif'; + else if (ext === '.webp') mime = 'image/webp'; + else if (ext === '.ico') mime = 'image/x-icon'; + + const data = await readAsset(src); + if (ext === '.svg') { + const svg = Buffer.isBuffer(data) ? data.toString('utf8') : data; + return replaceAttr( + match, + 'src', + `data:${mime};utf8,${encodeURIComponent(svg)}`, + ); + } + return replaceAttr( + match, + 'src', + `data:${mime};base64,${Buffer.from(data).toString('base64')}`, + ); +} + +async function inlineSvg(match) { + const href = attr(match, 'href'); + if (!href || !href.toLowerCase().endsWith('.svg')) return match; + const data = await readAsset(href); + return replaceAttr( + match, + 'href', + `data:image/svg+xml;base64,${Buffer.from(data).toString('base64')}`, + ); +} + +async function processHtml(content) { + let result = content; + const cssRegex = /]+rel=["']stylesheet["'][^>]*>/g; + const jsRegex = /]+src=["'][^"']+["'][^>]*><\/script>/g; + const imgRegex = /]+src=["'][^"']+["'][^>]*>/g; + const svgRegex = /]+rel=["']icon["'][^>]*>/g; + + for (const match of content.match(cssRegex) || []) { + if (!match.includes('data:')) result = result.replace(match, await inlineCss(match)); + } + for (const match of content.match(jsRegex) || []) { + if (!match.includes('data:')) result = result.replace(match, await inlineJs(match)); + } + for (const match of content.match(imgRegex) || []) { + if (!match.includes('data:')) result = result.replace(match, await inlineImg(match)); + } + for (const match of content.match(svgRegex) || []) { + if (!match.includes('data:')) result = result.replace(match, await inlineSvg(match)); + } + + return result; +} + +function cArray(data) { + return Array.from(Buffer.from(data, 'utf8')).concat(0).join(','); +} + +function generateC(data) { + const destination = path.basename(inputFile); + const stat = fs.statSync(inputFile); + const mtime = parseInt(stat.mtimeMs / 1000); + const bytes = cArray(data); + + return `// DO NOT EDIT. This file is generated using this command: +// ${process.argv.join(' ')} + +#include "mongoose.h" + +static const unsigned char v0[] = {${bytes}}; + +const struct mg_mem_file mg_packed_files[] = { + {"/${destination}", v0, sizeof(v0) - 1, ${mtime}}, + {NULL, NULL, 0, 0} +}; +`; +} + +processHtml(html) + .then(result => { + const c = generateC(result); + if (outputFile) { + fs.writeFileSync(outputFile, c); + } else { + process.stdout.write(c); + } + }) + .catch(err => { + console.error('Error:', err.message); + process.exit(1); + }); diff --git a/tutorials/device-dashboard/full/Makefile b/tutorials/device-dashboard/full/Makefile index ddfa86cc..49b1bc05 100644 --- a/tutorials/device-dashboard/full/Makefile +++ b/tutorials/device-dashboard/full/Makefile @@ -2,10 +2,10 @@ PROG ?= example TOP ?= ../../.. -SOURCES = main.c dashboard.c file_data.c $(TOP)/mongoose.c +SOURCES = dashboard.c file_data.c $(TOP)/mongoose.c CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -O0 $(CFLAGS_EXTRA) -PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_ENABLE_LINES=1 +PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_ENABLE_LINES=1 -DMAIN AFLAGS ?= -fsanitize=signed-integer-overflow,address,undefined,alignment all: $(PROG) @@ -17,13 +17,12 @@ $(PROG): $(SOURCES) $(TOP)/mongoose.h # 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 + node $(TOP)/resources/html2c.js dashboard.html -o file_data.c @echo "On-flash size: `grep -oE '[0-9]+' file_data.c | wc -l`" # 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 + node $(TOP)/resources/watch.js -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/full/ai.spec.md b/tutorials/device-dashboard/full/ai.spec.md deleted file mode 100644 index e0a2133e..00000000 --- a/tutorials/device-dashboard/full/ai.spec.md +++ /dev/null @@ -1,32 +0,0 @@ -# Device dashboard - -This is the AI specification file for the Web device dashboard. - -## General rules - -Your goal is to produce a dashboard.c and dashboard.html files - -Fetch the initial implementation of these two files from: - -https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/full/dashboard.c -https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/full/dashboard.html - -The C API for dashboard.c is described at https://mongoose.ws/docs/api/dash/ - -The C API header file is at https://github.com/cesanta/mongoose/blob/master/src/dash.h - -The minimal reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/ - -The full reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/full/ - -For editing dashboard.html, use only Mongoose `data-bind` and `${...}` template syntax - -No vanilla JS event listeners, no fetch calls, no custom reactive logic. - -## dashboard.html - -Change theme to dark - -## dashboard.c - -Don't touch it diff --git a/tutorials/device-dashboard/full/dashboard.c b/tutorials/device-dashboard/full/dashboard.c index bd81b887..20ed46d3 100644 --- a/tutorials/device-dashboard/full/dashboard.c +++ b/tutorials/device-dashboard/full/dashboard.c @@ -216,3 +216,21 @@ void mg_dash_poll(struct mg_mgr *mgr) { mg_dash_send_change(mgr, &set_actions); } } + +// On desktop, build with -DMAIN flag to include main(). +// On embedded system, run this code in your main() function +#ifdef MAIN +int main(void) { + struct mg_mgr mgr; + + mg_mgr_init(&mgr); + mg_dash_init(&mgr); + + for (;;) { + mg_mgr_poll(&mgr, 1); + mg_dash_poll(&mgr); + } + + return 0; +} +#endif diff --git a/tutorials/device-dashboard/full/dashboard.html b/tutorials/device-dashboard/full/dashboard.html index 4bdb6cf2..b5a8a5b0 100644 --- a/tutorials/device-dashboard/full/dashboard.html +++ b/tutorials/device-dashboard/full/dashboard.html @@ -114,14 +114,6 @@ Dashboard.login().catch(err => showLogin(true)); // GRAPHS - // {stroke: '#14B8A6FF', fill: '#14B8A61A'}, // teal - // {stroke: '#F59E0BFF', fill: '#F59E0B1A'}, // amber - // {stroke: '#22C55EFF', fill: '#22C55E1A'}, // green - // {stroke: '#EF4444FF', fill: '#EF44441A'}, // red - // {stroke: '#8B5CF6FF', fill: '#8B5CF61A'}, // purple - // {stroke: '#058DC7FF', fill: '#058DC71A'}, // azure - // {stroke: '#ED7E17FF', fill: '#ED7E171A'}, // tangerine - const graphs = { graph1: { el: document.getElementById("graph1"), @@ -207,7 +199,6 @@ a, a:visited, a:link { text-decoration: none; } .nav-icon { height: 1rem; width: 1rem; flex-shrink: 0; } .tab-stage { max-width: var(--max-width); margin: 0 auto; padding: 0.5rem var(--spacing-6) var(--spacing-4); } .tab-panel { min-height: calc(100vh - 6rem); } -.metrics-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; } .metric-tile { padding: 0.75rem 1.5rem 0.75rem; border-radius: 1rem; background: #ffffff; border: 1px solid rgba(203, 213, 225, 0.75); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 1), 0 2px 6px rgba(148, 163, 184, 0.1), 0 8px 24px rgba(148, 163, 184, 0.08); display: flex; flex-direction: column; gap: 0.35rem; transition: box-shadow 0.15s ease, transform 0.15s ease; } .metric-tile:hover { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 1), 0 4px 12px rgba(148, 163, 184, 0.18), 0 12px 32px rgba(148, 163, 184, 0.12); transform: translateY(-1px); } .metric-label { color: #94a3b8; font-size: 0.9rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; } @@ -271,9 +262,8 @@ a, a:visited, a:link { text-decoration: none; } .toggle.edited { border-color: #0fe; } .u-under { top: 0 !important; } .hidden { display: none; } -@media (max-width: 900px) { .grid { grid-template-columns: 1fr !important; } #files .row { flex-direction: column; flex-wrap: nowrap; } #files .panel, #files .note { flex: 1 1 auto; width: 100%; } } -@media (max-width: 900px) { .metrics-grid { grid-template-columns: repeat(2, 1fr); } } -@media (max-width: 480px) { .metrics-grid { grid-template-columns: 1fr; } .file-row { grid-template-columns: 1fr; } .file-actions { justify-content: flex-start; } .file-row { gap: 0.65rem; } .file-size { text-align: left; } } +@media (max-width: 900px) { .grid { grid-template-columns: repeat(2, 1fr) !important; } #files .row { flex-direction: column; flex-wrap: nowrap; } #files .panel, #files .note { flex: 1 1 auto; width: 100%; } } +@media (max-width: 640px) { .grid { grid-template-columns: 1fr !important; } .file-row { grid-template-columns: 1fr; } .file-actions { justify-content: flex-start; } .file-row { gap: 0.65rem; } .file-size { text-align: left; } } @@ -389,7 +379,7 @@ a, a:visited, a:link { text-decoration: none; }
-
+
Temperature
@@ -431,6 +421,12 @@ a, a:visited, a:link { text-decoration: none; } ok
+
@@ -635,7 +631,7 @@ a, a:visited, a:link { text-decoration: none; } Mongoose includes built-in firmware update support for many architectures, such as STM32, RP2040, and ESP32. See - ota.h + ota.h for the full list of supported MCUs. If you are using STM32, it works out of the box with no additional setup. diff --git a/tutorials/device-dashboard/full/main.c b/tutorials/device-dashboard/full/main.c deleted file mode 100644 index 13db0c87..00000000 --- a/tutorials/device-dashboard/full/main.c +++ /dev/null @@ -1,18 +0,0 @@ -// 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; -} diff --git a/tutorials/device-dashboard/minimal/Makefile b/tutorials/device-dashboard/minimal/Makefile index ddfa86cc..f28dc096 100644 --- a/tutorials/device-dashboard/minimal/Makefile +++ b/tutorials/device-dashboard/minimal/Makefile @@ -2,10 +2,10 @@ PROG ?= example TOP ?= ../../.. -SOURCES = main.c dashboard.c file_data.c $(TOP)/mongoose.c +SOURCES = dashboard.c file_data.c $(TOP)/mongoose.c CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -O0 $(CFLAGS_EXTRA) -PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_ENABLE_LINES=1 +PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_ENABLE_LINES=1 -DMAIN AFLAGS ?= -fsanitize=signed-integer-overflow,address,undefined,alignment all: $(PROG) @@ -17,13 +17,12 @@ $(PROG): $(SOURCES) $(TOP)/mongoose.h # 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 + node $(TOP)/resources/html2c.js dashboard.html -o file_data.c @echo "On-flash size: `grep -oE '[0-9]+' file_data.c | wc -l`" # 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 + node $(TOP)/resources/watch.js -w dashboard.c -w $(TOP)/mongoose.h -w dashboard.html -- make clean: - rm -rf $(PROG) *.exe *.obj *.dSYM mongoose out.* file_data.c + rm -rf $(PROG) *.exe *.obj *.dSYM mongoose file_data.c diff --git a/tutorials/device-dashboard/minimal/ai.spec.md b/tutorials/device-dashboard/minimal/ai.spec.md deleted file mode 100644 index a90dc8e3..00000000 --- a/tutorials/device-dashboard/minimal/ai.spec.md +++ /dev/null @@ -1,36 +0,0 @@ -# Device dashboard - -This is the AI specification file for the Web device dashboard. - -## General rules - -Your goal is to produce a dashboard.c and dashboard.html files - -Fetch the initial implementation of these two files from: - -https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/dashboard.c -https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/dashboard.html - -The C API for dashboard.c is described at https://mongoose.ws/docs/api/dash/ - -The C API header file is at https://github.com/cesanta/mongoose/blob/master/src/dash.h - -The minimal reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/ - -The full reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/full/ - -For editing dashboard.html, use only Mongoose `data-bind` and `${...}` template syntax - -No vanilla JS event listeners, no fetch calls, no custom reactive logic. - -## dashboard.html - -On a page, create single control panel titled "LED control" - -On that control, add 3 LED toggles: LED1, LED2, LED3 - -Update Dashboard.init call accordingly. Update dashboard.c accordingly - have 3 LEDs in the "leds" data set. - -## dashboard.c - -Don't touch it diff --git a/tutorials/device-dashboard/minimal/dashboard.c b/tutorials/device-dashboard/minimal/dashboard.c index 9876ea6e..70aad5e5 100644 --- a/tutorials/device-dashboard/minimal/dashboard.c +++ b/tutorials/device-dashboard/minimal/dashboard.c @@ -45,3 +45,21 @@ void mg_dash_init(struct mg_mgr *mgr) { void mg_dash_poll(struct mg_mgr *mgr) { (void) mgr; } + +// On desktop, build with -DMAIN flag to include main(). +// On embedded system, run this code in your main() function +#ifdef MAIN +int main(void) { + struct mg_mgr mgr; + + mg_mgr_init(&mgr); + mg_dash_init(&mgr); + + for (;;) { + mg_mgr_poll(&mgr, 1); + mg_dash_poll(&mgr); + } + + return 0; +} +#endif diff --git a/tutorials/device-dashboard/minimal/dashboard.html b/tutorials/device-dashboard/minimal/dashboard.html index d4b98b75..668abff1 100644 --- a/tutorials/device-dashboard/minimal/dashboard.html +++ b/tutorials/device-dashboard/minimal/dashboard.html @@ -54,6 +54,11 @@ Open this page in two or more browser windows and see how the dashboards stay synchronised when changes are made in any of them.
+ +
+ To customize this dashboard, follow + https://mongoose.ws/docs/guides/web-dashboard-guide/ +
diff --git a/tutorials/device-dashboard/minimal/main.c b/tutorials/device-dashboard/minimal/main.c deleted file mode 100644 index 13db0c87..00000000 --- a/tutorials/device-dashboard/minimal/main.c +++ /dev/null @@ -1,18 +0,0 @@ -// 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; -}