用 Mongoose Library 打造完整设备仪表盘Modbus TCP 主站控制台实战解析【免费下载链接】mongooseEmbedded web server, with TCP/IP network stack, MQTT and Websocket项目地址: https://gitcode.com/gh_mirrors/mon/mongoose导读本指南以 tutorials/tcp/modbus-dashboard 示例为核心讲解如何将 Mongoose Library 集成进嵌入式设备构建一个集Web UI、REST API、Modbus TCP 主站与 MQTT 转发于一体的完整设备仪表盘。读完本文你将掌握如何用 Mongoose 的多连接事件驱动模型同时服务 HTTP/HTTPS 与 Modbus TCP 请求、如何把 Web 前端整体打包进固件二进制无需文件系统、如何实现设备设置的持久化与在线修改以及如何通过 JSON 指令驱动 Modbus 读写操作。示例定位一个完整的设备仪表盘根据 README.md该示例演示了 Mongoose Library 集成进嵌入式设备后所能提供的完整设备仪表盘能力目标特性包括认证登录保护的仪表盘login-protected dashboard多用户登录支持多个登录账号并可能拥有不同权限Web UI 完全内嵌固件前端资源打包进固件二进制运行时无需文件系统即可服务对文件系统故障具备弹性变更实时传播所有更改都会同步到所有已连接的客户端。需要说明的是登录认证与实时推送属于该系列教程tutorials/device-dashboard的目标能力框架而在本 modbus-dashboard 示例的落地代码中可实际运行验证的核心能力是Modbus 主站控制台 设置持久化 MQTT 转发配置 内嵌 Web UI。下文将基于仓库真实源码逐一剖析这些能力的实现。目录结构与构建方式该示例的完整文件布局如下tutorials/tcp/modbus-dashboard/ ├── certs/ # TLS 证书server_cert.pem / server_key.pem ├── web_root/ # Web 前端资源 │ ├── bundle.js # Preact htm preact-router 打包产物 │ ├── history.min.js # hash 路由依赖 │ ├── index.html # 页面入口 │ ├── main.js # 前端逻辑ES Module │ └── tailwind.config.js # Tailwind 样式配置 ├── Makefile # 构建脚本 ├── main.c # 程序入口事件循环 ├── mongoose.c / mongoose.h # Mongoose 单文件库 ├── net.c / net.h # 仪表盘核心实现HTTP API Modbus ├── pack.js # 打包文件系统生成脚本 └── packed_fs.c # 由 pack.js 生成的 C 资源文件在 Makefile 中示例由main.c mongoose.c net.c packed_fs.c四个源文件编译而成默认构建命令为make # 构建并运行默认编译为 ./example make TLSmbedtls # 启用 mbedTLS 的 HTTPS 支持自动 clone mbedtls v2.28.2 make clean # 清理构建产物其中两个关键生成目标值得注意# Bundle JS libraries (preact, preact-router, ...) into a single file web_root/bundle.js: curl -s https://npm.reversehttp.com/preact,preact/hooks,htm/preact,preact-router -o $ # Generate packed filesystem for serving Web UI packed_fs.c: $(wildcard web_root/*) $(wildcard certs/*) Makefile web_root/bundle.js node pack.js $(addsuffix ::gzip, $(wildcard web_root/*)) certs/* $也就是说前端依赖Preact、preact/hooks、htm/preact、preact-router被在线打包为单个bundle.jspacked_fs.c则由 pack.js 把web_root下所有资源并标注::gzip进行 gzip 压缩及certs证书一起生成为 C 源码。mbedtls目标则用于拉取 TLS 实现当make TLSmbedtls时 Makefile 会追加-DMG_TLSMG_TLS_MBED编译选项并编译 mbedtls 库源码。程序入口与事件循环main.c 展示了典型 Mongoose 应用的骨架int main(void) { struct mg_mgr mgr; signal(SIGPIPE, SIG_IGN); signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); mg_log_set(MG_LL_DEBUG); // Set debug log level mg_mgr_init(mgr); web_init(mgr); while (s_sig_num 0) { mg_mgr_poll(mgr, 50); } mg_mgr_free(mgr); return 0; }流程非常简洁初始化事件管理器mg_mgr_init→ 调用web_init注册 HTTP/HTTPS 监听器与定时器 → 以 50ms 周期循环mg_mgr_poll驱动所有事件直到收到 SIGINT/SIGTERM。全部业务逻辑被收敛在web_init与net.c的各个事件回调中。网络层初始化HTTP HTTPS SNTP 内嵌文件系统net.c 的web_init是整个仪表盘的后端入口void web_init(struct mg_mgr *mgr) { set_default_settings(s_settings); web_load_settings(s_settings, sizeof(s_settings)); mg_http_listen(mgr, HTTP_URL, fn, NULL); mg_http_listen(mgr, HTTPS_URL, fn, (void *) 1); mg_timer_add(mgr, 10 * 60 * 1000, MG_TIMER_RUN_NOW | MG_TIMER_REPEAT, timer_sntp_fn, mgr); mg_mem_files mg_packed_files; // Use generated packed filesystem }监听地址定义在 net.h并允许在编译期覆盖#if !defined(HTTP_URL) #define HTTP_URL http://0.0.0.0:8000 #endif #if !defined(HTTPS_URL) #define HTTPS_URL https://0.0.0.0:8443 #endif默认同时监听HTTP 8000与HTTPS 8443。此外还注册了一个每 10 分钟执行一次、启动立即运行的 SNTP 定时器通过mg_sntp_connect连接udp://time.google.com:123同步设备时间——这为 Modbus 响应的超时判断提供了可靠的时间基准。REST API 设计设置读写与设备控制HTTP 请求统一由fn回调处理net.c通过mg_match分发路由API 端点方法功能/api/settings/getGET读取设备设置并返回 JSON/api/settings/setPOST接收 JSON 并更新、持久化设置/api/modbus/execPOST执行一次 Modbus TCP 请求JSON 参数/api/device/resetPOST设备复位返回true非 API 路径一律交给mg_http_serve_dir提供静态资源服务struct mg_http_serve_opts opts; memset(opts, 0, sizeof(opts)); #if MG_ARCH MG_ARCH_UNIX || MG_ARCH MG_ARCH_WIN32 opts.root_dir web_root; // On workstations, use filesystem #else opts.root_dir /web_root; // On embedded, use packed files opts.fs mg_fs_packed; #endif mg_http_serve_dir(c, ev_data, opts);这里体现了 README 强调的双模式设计在桌面工作站UNIX/Win32上直接读磁盘目录web_root便于开发调试在嵌入式平台上则从mg_fs_packed即打包进固件的资源中读取/web_root下的文件完全不需要文件系统。设备设置的数据模型net.c定义了设备设置结构体struct device_settings { uint32_t magic; int log_level; bool mqtt_enabled; char mqtt_server_url[64]; char mqtt_topic_tx[16]; char mqtt_topic_rx[16]; };set_default_settings提供出厂默认值magic 为SETTINGS_MAGIC即0xaabbccdd日志级别MG_LL_DEBUGMQTT 服务器mqtt://broker.hivemq.com:1883收发主题modbus1/tx与modbus1/rx。/api/settings/set处理函数使用 Mongoose 的 JSON 解析 APImg_json_get_bool、mg_json_get_long、mg_json_get_str从请求体中提取mqtt_enabled、log_level、mqtt_server_url、mqtt_topic_rx、mqtt_topic_tx等字段更新后调用web_save_settings持久化并以{%m:%s,%m:%m}格式回写{status:...,message:...}。设置持久化web_load_settings与web_save_settings在 main.c 中实现落盘文件为settings.binbool web_load_settings(void *buf, size_t len) { struct mg_str data mg_file_read(mg_fs_posix, CONFIG_FILE); // 读取失败或长度不匹配时记录错误否则 memcpy 到缓冲区 ... } bool web_save_settings(void *buf, size_t len) { MG_INFO((Saving to %s, CONFIG_FILE)); return mg_file_write(mg_fs_posix, CONFIG_FILE, buf, len); }web_load_settings要求文件大小与结构体完全一致data.len ! len即报错这是嵌入式场景常用的固定长度二进制配置块模式magic字段则用于校验配置块是否有效。net.h中导出的这套接口web_init/web_load_settings/web_save_settings也正是 README 所说的可将同一套 Web 框架移植到其他硬件平台的抽象边界——换平台只需重新实现读写两个函数。Modbus TCP 主站的实现细节这是本示例最有技术含量的部分Mongoose 的事件驱动模型天然适合同时扮演HTTP 服务器与Modbus TCP 客户端两种角色。发起请求从 JSON 参数到 Modbus 帧前端点击 Execute 后/api/modbus/exec收到形如{url: tcp://192.168.0.100:502, id: 1, reg: 0, func: 3, nregs: 1, timeout: 1500}的 JSON。handle_modbus_exec调用start_modbus_requestnet.c解析参数并建立到从站的 TCP 连接char *url mg_json_get_str(json, $.url); long timeout mg_json_get_long(json, $.timeout, 750); uint8_t id (uint8_t) mg_json_get_long(json, $.id, 1); uint16_t reg (uint16_t) mg_json_get_long(json, $.reg, 1); uint8_t func (uint8_t) mg_json_get_long(json, $.func, 0); uint16_t nregs (uint16_t) mg_json_get_long(json, $.nregs, 1);随后逐字节拼装 Modbus TCP 应用数据单元ADUsend16(c, 1); // TID: 事务标识 send16(c, 0); // Protocol identifier: 0 (modbus) send16(c, 0); // Length: 稍后回填 send8(c, id); // Client ID从站地址 send8(c, func); // 功能码对于功能码 3/4读保持/输入寄存器、6写单寄存器、16写多寄存器追加起始寄存器号与寄存器数量写多寄存器16还会按$.values[i]逐个填入要写入的寄存器值对于功能码 43读设备标识则追加 MEI 类型 14、基本设备 ID 1、对象 ID 0厂商名。最后回填长度字段*lp mg_htons((uint16_t) (c-send.len - len)); // Set length field响应处理帧组装、超时与事件回传Modbus 连接的回调mfn处理两类事件MG_EV_READ先判断最小长度 8继续攒数据从第 4 字节取出 PDU 长度不足则继续缓冲凑齐一帧后通过mg_call(t, MG_EV_USER, c-recv)把收到的数据以用户自定义事件MG_EV_USER转发给发起请求的 HTTP 连接然后关闭 Modbus 连接MG_EV_POLL若设置了expiration_time且已超时mg_millis()比较关闭连接。HTTP 侧则维护两个超时发起请求后cd-expiration_time mg_millis() 15001.5 秒内未收到响应即回false收到MG_EV_USER后清除超时并调用print_mb_resp把原始响应解析成 JSON 回给浏览器功能码 3/4解析为寄存器值数组[...]功能码 43解析为{mei:..,devid:..,conformity:..,more:..,next:..,objects:[{...}]}结构其他功能码返回null。同时响应中还附带raw字段mg_print_hex十六进制原始字节方便调试。这套HTTP 连接等待 Modbus 连接结果的联动完全依靠 Mongoose 的mg_call用户事件机制实现连接间无需共享任何锁或全局队列。内嵌文件系统Web UI 与固件零依赖README 特别强调Web UI 可完全嵌入固件二进制无需文件系统。支撑这一能力的是 pack.js它读取命令行传入的每个文件生成static const unsigned char vN[] {...}字节数组末尾补 0 使其成为 asciz 字符串并附带{/path, vN, sizeof(vN)-1, mtime}元信息最终输出mg_packed_files[]数组。配合 Mongoose 提供的打包文件系统接口见 mongoose.h 与 src/fs_packed.cextern const struct mg_mem_file *mg_mem_files; // 激活打包 FS 的挂载点 struct mg_str mg_unpacked(const char *path); // 按路径取打包资源 extern const struct mg_mem_file mg_packed_files[]; // 打包资源表示例通过mg_mem_files mg_packed_files激活打包 FSTLS 证书也走同一通道加载opts.cert mg_unpacked(/certs/server_cert.pem); opts.key mg_unpacked(/certs/server_key.pem); mg_tls_init(c, opts);由于所有 Web 资源HTML/JS/CSS/证书都以 gzip 压缩后的二进制形式躺在固件里设备即便完全没有外部存储也能完整提供 HTTPS 页面服务——这正是 README 所说对 FS 问题具有弹性的含义。前端Preact 单页应用与两个核心页面前端入口 index.html 通过 CDN Tailwind 与history.min.js提供样式与 hash 路由主逻辑在 main.jsES Module从bundle.js导入h, render, useState, useEffect, useRef, html, Router。应用基于Preact htm编写包含两个页面Modbus Master Console默认页表单控件与后端 JSON 参数一一对应Slave URLtcp://192.168.0.100:502默认即从站 TCP 地址Connect timeout毫秒范围 50–5000默认 1500Slave ID1–253默认 1Starting Register0–65535默认 0Register Count1–125默认 1改变数量时自动把 Values 重置为零填充数组Values写寄存器功能码 16/6时使用格式如1或1,2,3Function下拉选择 3读多保持寄存器、4读多输入寄存器、16写多保持寄存器、43读设备标识。点击 Execute 后发起fetch(api/modbus/exec, {method: post, body: JSON.stringify({...})})响应 JSON 格式化显示在右侧pre面板中。Settings 页设备设置包含两个区块MQTT Forwarding开关mqtt_enabled、mqtt_server_url、mqtt_topic_rx、mqtt_topic_tx点击 Save Settings 通过api/settings/set保存成功后弹出 Notification 提示Console Loglog_level下拉Disable/Error/Info/Debug 对应 0–3。页面上还以 Developer Note 形式说明了 MQTT 转发协议每次成功查询到 Modbus 从站后设备会向 TX 主题发送一条 MQTT 消息同时RX 主题可接收格式如下的 JSON 命令来远程触发 Modbus 请求{url: tcp://IP:PORT, reg: NUM, id: NUM, func: NUM}这使得仪表盘不仅能在本地浏览器操作还能经 MQTT 进行云侧或远程控制构成浏览器 → HTTP API → Modbus TCP 从站与MQTT RX → Modbus TCP 从站两条控制路径。小结通过 modbus-dashboard 示例可以清楚看到 Mongoose Library 的核心设计哲学单一事件循环、多连接协作。同一个mg_mgr里HTTP 监听连接fn回调、Modbus 客户端连接mfn回调、SNTP 定时器彼此独立又通过mg_call/MG_EV_USER协同——设备仪表盘所需的前端服务、后端 API、现场总线通信与云端桥接仅凭数 KB 的 C 代码即可全部承载。如果你打算为嵌入式设备做一个自带 Web 界面、支持现场总线、可远程管理的完整方案这个示例的结构net.h的抽象接口 pack.js的资源打包 JSON API 路由是一个极佳的参考起点。更完整的图文教程可进一步参考同仓库的 tutorials/device-dashboard 系列array/full/minimal 三个变体与 tutorials/mqtt 下的 MQTT 相关示例。【免费下载链接】mongooseEmbedded web server, with TCP/IP network stack, MQTT and Websocket项目地址: https://gitcode.com/gh_mirrors/mon/mongoose创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考