Enhance cube package

This commit is contained in:
cpq
2026-05-10 12:48:00 +01:00
parent 91715b380d
commit 961a6f0a46
16 changed files with 378 additions and 138 deletions
+86
View File
@@ -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
```
@@ -8,6 +8,7 @@
<license>License.txt</license>
<supportContact>https://mongoose.ws/contact/</supportContact>
<releases>
<release date="2026-05-09" version="7.21.6">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
<release date="2026-05-05" version="7.21.5">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
<release date="2026-04-24" version="7.21.4">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
<release date="2026-04-08" version="7.21.0">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
@@ -23,24 +24,26 @@
</condition>
</conditions>
<components>
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.21.5" maxInstances="1">
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.21.6" maxInstances="1">
<description>Networking Core</description>
<files>
<file category="doc" name="README.md"/>
<file category="header" name="mongoose.h" />
<file category="sourceC" name="mongoose.c" />
<file category="header" name="mongoose_config.h"/>
<file category="utility" name="README.md" public="false"/>
</files>
</component>
<component Cclass="Mongoose" Cgroup="Web Device Dashboard" Cversion="7.21.5" condition="MongooseCore" maxInstances="1">
<component Cclass="Mongoose" Cgroup="Web Device Dashboard" Cversion="7.21.6" condition="MongooseCore" maxInstances="1" generator="copyassets" >
<description>Web Device Dashboard</description>
<files>
<file category="sourceC" name="dashboard.c"/>
<file category="sourceC" name="file_data.c"/>
<file category="other" name="dashboard.html"/>
<file category="utility" name="dashboard.html" public="false"/>
<file category="utility" name="html2c.js" public="false"/>
</files>
</component>
<component Cclass="Mongoose" Cgroup="MQTT Client" Cversion="7.21.5" condition="MongooseCore" maxInstances="1">
<component Cclass="Mongoose" Cgroup="MQTT Client" Cversion="7.21.6" condition="MongooseCore" maxInstances="1">
<description>MQTT Client</description>
<files>
<file category="sourceC" name="mongoose_mqtt.c"/>
+27 -4
View File
@@ -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
+18
View File
@@ -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.
+1 -1
View File
@@ -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)
+179
View File
@@ -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 `<style>${css}</style>`;
}
async function inlineJs(match) {
const src = attr(match, 'src');
if (!src) return match;
const js = await readAsset(src, 'utf8');
return `<script>${js}</script>`;
}
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 = /<link[^>]+rel=["']stylesheet["'][^>]*>/g;
const jsRegex = /<script[^>]+src=["'][^"']+["'][^>]*><\/script>/g;
const imgRegex = /<img[^>]+src=["'][^"']+["'][^>]*>/g;
const svgRegex = /<link[^>]+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);
});
+4 -5
View File
@@ -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
@@ -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
@@ -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
+9 -13
View File
@@ -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; } }
</style>
<!-- uPlot graph library -->
@@ -389,7 +379,7 @@ a, a:visited, a:link { text-decoration: none; }
</div>
<main class="tab-stage">
<section id="overview" class="tab-panel tab-panel-active" role="tabpanel" aria-labelledby="tab-overview">
<div class="metrics-grid" aria-label="Device metrics overview">
<div class="grid" style="grid-template-columns: repeat(5, 1fr); gap: 1rem;">
<article class="metric-tile">
<div class="metric-label">Temperature</div>
<div class="metric-value-row">
@@ -431,6 +421,12 @@ a, a:visited, a:link { text-decoration: none; }
<span class="metric-status metric-status-good">ok</span>
</div>
</article>
<article class="metric-tile note">
<div>
To customize this dashboard, follow
<a target="_blank" href="https://mongoose.ws/docs/guides/web-dashboard-guide/">https://mongoose.ws/docs/guides/web-dashboard-guide/</a>
</div>
</article>
</div>
<div class="grid" style="grid-template-columns: 1fr 1fr;">
-18
View File
@@ -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;
}
+5 -6
View File
@@ -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
@@ -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
@@ -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
@@ -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.
</div>
<div style="text-align: left; margin-top: 6px;">
To customize this dashboard, follow
<a target="_blank" href="https://mongoose.ws/docs/guides/web-dashboard-guide/">https://mongoose.ws/docs/guides/web-dashboard-guide/</a>
</div>
</div>
</div>
-18
View File
@@ -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;
}