# NanoFrameworkAPP **Repository Path**: tonmyn/nano-framework-app ## Basic Information - **Project Name**: NanoFrameworkAPP - **Description**: 存放基于nanoframework 固件开发的 单片机程序例程 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-29 - **Last Updated**: 2026-10-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # NanoFrameworkAPP > 存放基于 nanoFramework 固件开发的单片机程序例程 [nanoFramework](https://nanoframework.net/) 让 **C# 跑在 MCU 上**。本仓库是我在 ESP32 / RP2040 上学习和实践的例程合集, 其中 **ESP32-S3 多入口控制 WS2812** 是当前主线项目,已经沉淀出一套可复用的分层框架。 --- ## 项目一览 | 项目 | 目标平台 | 内容 | 定位 | |---|---|---|---| | **`NFApp_html_esp32s3`** | ESP32-S3 | **网页 + MCP + A2A Skills + UDP 带外 + 看护** 五入口控制板载 WS2812,分层框架 | ⭐ **当前主线** | | `NFApp_html_esp32c3` | ESP32-C3 | 网页控制板载 LED(单文件版本) | 框架的起点 / 参考 | | `NFApp1`(`NFApp_Esp32.nfproj`) | ESP32 | 基础例程 + 风扇 PWM + WiFi 管理器 | 学习例程 | | `NFApp_Pico` | RP2040 | GPIO / 按键例程 | 学习例程 | | `Pico` | RP2040 | GPIO / 按键例程(**未加入解决方案**,需单独打开) | 学习例程 | --- ## 目录结构 ``` NanoFrameworkAPP/ ├── NFApp1.sln 解决方案(含 4 个项目) ├── NFApp1/ ESP32 基础例程 │ ├── Examples/ GPIO / Button / PWM / WIFI_helper / WIFI_scan / WS28xx │ └── Programs/ PWM 风扇、WiFi 管理器 ├── NFApp_Pico/ ├── Pico/ RP2040 例程(独立项目) ├── NFApp_html_esp32c3/ C3 网页控制 LED(单文件) └── NFApp_html_esp32s3/ ★ S3 五入口控制 WS2812(分层框架) ├── Framework/ 框架:契约与机制 │ ├── IModule.cs IModule / IModulePage / IModuleMcpTool / IModuleSkill │ ├── ModuleBase.cs 模块基类(构造即自注册) │ ├── ModuleController.cs 模块总账 + InitAll + 只读入口 │ ├── ModuleHub/ ★ 三个能力台账(各管各的) │ │ ├── PageHub.cs 网页路由(含框架基础路由 /ping) │ │ ├── McpToolHub.cs MCP 工具 + 提示词 │ │ └── SkillHub.cs A2A Skills + Agent Card 配置 │ ├── WatchdogService.cs 看护(事件驱动 + 主动探活) │ └── DeviceTool.cs 设备级工具(FreeHeap / Reboot) ├── Modules/ │ └── Ws2812Led/ ★ 一个模块一个文件夹 │ ├── Ws2812LedFunc.cs 业务本体(实现 IModule)——与文件夹同名 │ ├── Ws2812LedPage.cs 网页能力 │ ├── index.html 网页本体 │ ├── Ws2812LedMcpTool.cs MCP 能力(+ LedSetArgs / Doc) │ └── Ws2812LedSkill.cs A2A Skill 能力(适配器 + 静态动作类) ├── Web/ 网络基础设施(不认识任何业务) │ ├── WebServerService.cs 连 WiFi / 起 WebServer / 挂控制器 │ ├── UdpControlService.cs 带外通道 │ └── WebRouteTable.cs / WebReply.cs / WebQuery.cs └── Program.cs 装配清单 ``` --- ## 环境要求 - **Visual Studio 2022** + [.NET nanoFramework 扩展](https://marketplace.visualstudio.com/items?itemName=nanoframework.nanoframework-vs2022-extension) - **nanoff**(刷固件):`dotnet tool install -g nanoff` - ⚠️ **固件版本线必须与 NuGet 包同线**(本项目使用 **1.17.0.x**),否则会报 `Version mismatch` | 板子 | nanoff target | 备注 | |---|---|---| | ESP32-S3 | `ESP32_S3_QUAD` | ⚠️ **不要用 `ESP32_S3`** —— 它属于旧的 1.16 线,会和 1.17 的 NuGet 包冲突 | | ESP32-C3 | `ESP32_C3` | | | Raspberry Pi Pico | `PICO_RP2040` | | --- ## 主线:ESP32-S3 多入口控制 WS2812 ### 五条入口,同一份能力层 | 入口 | 端点 / 用法 | 面向 | |---|---|---| | **网页** | `http://<设备IP>/` | 人 | | **MCP** | `POST /mcp`(JSON-RPC) | AI agent —— **客户端生态最广** | | **A2A Skills** | `GET /.well-known/agent-card.json`
`POST /skills/invoke` | AI agent —— **可自我描述** | | **UDP 带外** | 端口 4210 | 救急通道(独立于 WebServer) | | **看护探针** | 内部 `GET /ping`(每 30 秒) | 设备自己 | **⇒ 五条入口操作的是同一份状态** —— 网页改的值 AI 读得到,AI 改的值刷新网页就能看见。 ### 架构分层 ``` Program.cs 装配清单(只做"谁来 new、谁来注册") │ ├── ModuleController 模块总账:CreateAll / InitAll / 命令分发 / 状态汇总 │ ├── IModule · ModuleBase 业务契约(构造即自注册) │ │ └── Ws2812LedFunc 业务实现:硬件能力 + 命令词 │ └── ModuleHub/ 三个能力台账(构造时统计一次,之后只回答问题) │ ├── PageHub 网页路由(含框架基础路由 /ping) │ ├── McpToolHub MCP 工具 + 提示词 │ └── SkillHub A2A Skills + Agent Card 配置 │ ├── WebServerService 连 WiFi + 起 WebServer + 挂各台账声明的控制器 ├── UdpControlService 带外通道(PING/STATUS/REBOOT/HELP/GC,其余转发给模块) └── WatchdogService 两级判据:Stopped 事件 + 主动探活 ``` **四条铁律** 1. **台账只统计,不做动作** —— 它只回答"我这儿有几个","要怎么办"归调用方 2. **网络层自己判断要不要联网** —— "哪些能力算网络能力"是网络层的知识 3. **`Program` 里不出现 Page / Mcp / Skill / Hub 任何一个词** —— 它就是装配清单 4. **协议层的文案与配置,各 Hub 自己写** —— 总管只提供台账数据 ### 加新东西要改哪里 | 我要做的事 | 改哪 | 框架要动吗 | |---|---|---| | **加一个新模块** | 建 `Modules/XxxModule/`;`ModuleController.CreateAll()` 加一行 | ❌ | | **给模块加网页 / MCP / Skill** | 各写一个适配器;模块构造函数里 `new` 一行 | ❌ | | **禁用一个能力** | 模块构造函数里注释掉那一行 | ❌ | | **加一种全新能力**(如配网入口) | ① 建一个新 Hub ② `ModuleController` 加字段 + `InitAll` 里构造 ③ `WebServerService` 加一段挂控制器 | ✅ 就这三处 | ### 硬件定案 | 项 | 值 | |---|---| | 板载 WS2812 驱动 | `Ws2808`(RMT) | | 数据引脚 | **GPIO21** | | 灯珠数 | 1 | | 状态模型 | **使能**(开/关)与**设定值**(RGB + 亮度)**两者正交** —— 关灯不丢设定值,开灯按当前设定值呈现 | ### 怎么用 | 入口 | 用法 | |---|---| | **网页** | 浏览器打开 `http://<设备IP>/` —— 3 个颜色滑条 + 亮度滑条 + 开/关/Toggle | | **MCP** | 在支持 MCP 的 agent 里添加 `http://<设备IP>/mcp`;传输类型选 **Streamable HTTP**(**不是 SSE**)。5 个工具:`led_on` / `led_off` / `led_toggle` / `led_status` / `led_set` | | **A2A Skills** | agent 先读 `http://<设备IP>/.well-known/agent-card.json` 自我发现,再 `POST /skills/invoke` 调用 | | **UDP 带外**(端口 4210) | `PING` / `STATUS` / `LED ON` / `LED OFF` / `LED TOGGLE` / `COLOR r g b` / `BRIGHT v` / `GC` / `REBOOT` | | **看护探针** | 内部 `GET /ping`,无需人工干预 | **手工验证(不需要 agent):** ```bash # MCP:列出工具 curl -X POST http://<设备IP>/mcp -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' # MCP:一次设置颜色 + 亮度 curl -X POST http://<设备IP>/mcp -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"led_set","arguments":{"R":255,"G":100,"B":0,"Brightness":80}}}' # A2A:读 Agent Card(agent 的自我发现入口) curl http://<设备IP>/.well-known/agent-card.json # A2A:同样一次设置颜色 + 亮度 curl -X POST http://<设备IP>/skills/invoke -H "Content-Type: application/json" \ -d '{"skill":"ws2812-led","action":"led_set","arguments":{"R":255,"G":100,"B":0,"Brightness":80}}' # 健康检查 curl http://<设备IP>/ping ``` > ⚠️ **参数键名区分大小写**(`R` / `G` / `B` / `Brightness`)—— 传小写会被**静默忽略**。 > 两个协议的实现里都做了防御:四个字段全都收不到时返回明确的 `ERR` 提示,方便 AI 自我纠正。 > **UDP 通道**是**独立于 WebServer** 的管理通道(类比服务器的 IPMI):网页彻底打不开时用它救回来。 > 同理,看护者的主动探活也用 **设备自己的局域网 IP**(不是 `127.0.0.1` —— 该固件的 lwIP 环回不一定可用)。 --- ## ⚠️ 凭据纪律(重要,务必遵守) **本仓库是公开仓库,源码中绝不出现任何真实凭据。** | | 规则 | |---|---| | ❌ **禁止** | 真实 WiFi SSID / 密码、Token、API Key、私钥、账号、真实内网 IP | | ✅ **WiFi 凭据** | **存设备 NVS**,不进源码:
VS → **Device Explorer → Network Configuration → Wi-Fi profiles** 填一次 | | ✅ **代码怎么连** | `WifiNetworkHelper.Reconnect(...)` —— 消费设备里已存的配置,**不需要凭据参数** | | ✅ **必须写死时** | 放进 `Secrets.cs`(**已在 `.gitignore` 中**),仓库里只放 `Secrets.sample.cs` 模板 | > ### 历史教训 > 曾因把 WiFi 密码写进 `Program.cs` 并提交到公开仓库,最后不得不**删库重建**。 > > **凭据一旦推上公开仓库,就必须视为已泄露** —— 改文件、删提交、清历史都**不能撤销**它已经被抓取的可能性。 > **唯一的补救是更换凭据。** --- ## 构建与部署 ```bash # 1. 拉代码 git clone https://gitee.com/tonmyn/nano-framework-app.git # 2. 刷固件(按板子选 target) nanoff --update --target ESP32_S3_QUAD ``` 3. 用 VS 打开 `NFApp1.sln` 4. 首次需要还原 NuGet 包(`packages.config` 风格) > 💡 若"引用"节点里是空的,说明**只还原、没重建引用** —— 用 NuGet 管理器**卸载再安装**,或程序包管理器控制台执行 `Update-Package -Reinstall` 5. 选中项目 → **部署**(F5) 6. 首次运行若提示"设备里没有已保存的 WiFi 配置",在 **Device Explorer → Network Configuration → Wi-Fi profiles** 里填一次,**不用重新烧录**,10 秒内会自动连上 > ⚠️ **改了 `Modules/` 下 `` 文件的路径后要 `--masserase` 清一次** —— > 设备端路径是**项目相对路径的镜像**(`Modules\Ws2812Led\index.html` → `I:\Modules\Ws2812Led\index.html`), > 不清的话设备里会同时留着新旧两份。 --- ## 参考 - nanoFramework 官网 - API 文档 - 官方样例 - 固件下载(Cloudsmith) - A2A 协议 ## License [MIT](LICENSE.txt)