# 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)