跳到主要内容

叶帆物联平台通信

概述

将YF3300-ESP32S3设备数据上传到叶帆物联网平台需要使用 YFLink 协议通过 MQTT 进行通信。本章以完整项目 YeFanIoTTest 为例,介绍从设备初始化到数据上传的完整实现。

技术栈
  • 运行环境:.NET nanoFramework
  • 硬件平台:ESP32-S3 (YF3300)
  • 通信协议:MQTT + YFLink 1.3.0
  • 认证方式:HMAC-SHA1(项目/产品/设备三元组)

NuGet 软件包

包名版本用途
nanoFramework.CoreLibrary1.17.11基础运行时(System 命名空间)
nanoFramework.Hardware.Esp321.6.37ESP32 引脚功能配置(I2C/SPI/UART)
nanoFramework.Iot.Device.DhcpServer1.2.938AP 模式 DHCP 服务器
nanoFramework.Logging1.1.161日志记录(ILogger)
nanoFramework.M2Mqtt5.1.212MQTT 客户端(连接叶帆物联平台)
nanoFramework.Networking.Sntp1.6.42NTP 时间同步
nanoFramework.System.Collections1.5.67Hashtable / ArrayList(属性上传)
nanoFramework.System.Device.Gpio1.1.57GPIO 控制(LED/按钮/继电器/数字输入)
nanoFramework.System.Device.I2c1.1.29I2C 通信(SHT30 温湿度传感器)
nanoFramework.System.Device.Wifi1.5.141WiFi STA/AP 管理
nanoFramework.System.Math1.5.116数学运算(指数退避重连)
nanoFramework.System.Net1.11.50DNS 解析(网络可达性检测)
nanoFramework.System.Net.Http.Server1.5.204Web 服务器(配网页面)
nanoFramework.System.Text1.3.42StringBuilder / UTF-8 编码
nanoFramework.System.Threading1.1.52线程/定时器
nanoFramework.WebServer1.2.140WebServer 路由框架(属性路由)

系统架构

┌─────────────────────────────────────────────────────────┐
│ YF3300-ESP32S3 设备 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ SHT30 │ │ 继电器 │ │ 数字输入 │ │ BOOT │ │
│ │ 温湿度 │ │ GPIO48 │ │ GPIO21/47│ │ GPIO0 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬────┘ │
│ │ I2C │ GPIO │ GPIO │ GPIO │
│ ┌────▼──────────────▼────────────▼──────────────▼─────┐ │
│ │ 驱动层 (Drivers/) │ │
│ │ Sht30Sensor │ RelayDriver │ DigitalInputDriver │ │
│ │ ButtonDriver│ LedManager │ │
│ └────────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌────────────────────────▼────────────────────────────┐ │
│ │ 管理层 (Managers/) │ │
│ │ MqttClientManager ←→ WifiManager ←→ APConfigManager│ │
│ │ NtpTimeManager │ ConfigurationManager │ │
│ └────────────────────────┬────────────────────────────┘ │
│ │ MQTT (YFLink协议) │
└───────────────────────────┼──────────────────────────────┘

┌────────▼────────┐
│ 叶帆物联网平台 │
│ iot.yfios.net │
│ MQTT:1883 │
└─────────────────┘

启动流程(12步初始化)

程序在 Main() 中按顺序执行12个步骤完成系统初始化:

步骤组件GPIO/接口说明
1Logger-初始化 DebugLoggerFactory 日志系统
2GPIO-创建 GpioController 实例
3LEDGPIO39/40黄色(网络状态) + 绿色(配网状态)
4RelayGPIO481路继电器输出驱动
5Digital InputGPIO21/472路开关量输入,回调模式
6SHT30I2C(17/18)温湿度传感器,地址0x44
7ButtonGPIO0BOOT按钮,短按切换继电器/长按配网
8Configuration-WiFi凭证读写的配置管理器
9WiFi Manager-STA/AP双模式WiFi管理
10WiFi Connect-加载配置连接WiFi,成功后初始化NTP
11AP Config-AP配网管理器,含Web服务器
12MQTT Connect-连接叶帆物联网平台

主循环逻辑

┌──────────────┐
│ counter++ │
└──────┬───────┘


┌──────────────┐ 每5秒读取一次
│ 读取传感器 │◄── NTP时间 / 温湿度 / 数字输入 / 继电器状态
└──────┬───────┘


┌──────────────┐ counter % 6 == 0 (约30秒)
│ 是否上传? │─── NO ──► Sleep(5000ms) ──► 循环
└──────┬───────┘
│ YES

┌──────────────┐
│ UploadData │ 构建属性Hashtable → MQTT发布
│ ToCloud() │ 属性: H(湿度) T(温度) I1 I2 Q1
└──────────────┘

核心代码详解

1. 硬件引脚定义 (Hardware/YF3300_ESP32S3.cs)

命名空间规范

硬件定义使用独立的命名空间 YFSoft.Hardware.YF3300_ESP32S3,方便在其他项目中复用。

Hardware/YF3300_ESP32S3.cs
using System;

namespace YFSoft.Hardware.YF3300_ESP32S3
{
public static class CPU
{
public static class Pins
{
// GPIO 0-48 全部定义
public const int GPIO0 = 0;
public const int GPIO9 = 9; // RS485 TX
public const int GPIO10 = 10; // RS485 RX
public const int GPIO11 = 11; // RS232 TX
public const int GPIO12 = 12; // RS232 RX
public const int GPIO17 = 17; // I2C SDA
public const int GPIO18 = 18; // I2C SCL
public const int GPIO21 = 21; // 开关量输入1
public const int GPIO39 = 39; // 绿色LED
public const int GPIO40 = 40; // 黄色LED
public const int GPIO47 = 47; // 开关量输入2
public const int GPIO48 = 48; // 继电器1
}
}

public static class Mainboard
{
public static class Pins
{
public const int YellowLED = CPU.Pins.GPIO40; // 网络状态
public const int GreenLED = CPU.Pins.GPIO39; // 配网状态
public const int BOOT = CPU.Pins.GPIO0;
public const int I1 = CPU.Pins.GPIO21;
public const int I2 = CPU.Pins.GPIO47;
public const int Q1 = CPU.Pins.GPIO48;
}

public static class RS485
{
public const string PortName = "COM1";
public const int TxPin = CPU.Pins.GPIO9;
public const int RxPin = CPU.Pins.GPIO10;
}

public static class I2C
{
public const int BusId = 1;
public const int SdaPin = CPU.Pins.GPIO17;
public const int SclPin = CPU.Pins.GPIO18;
}
}

// LED闪烁时序定义(毫秒)
public static class LEDTiming
{
// 黄色LED - 网络状态
public const int NetworkConnecting_On = 0; // 常亮
public const int NetworkNormal_On = 500; // 慢闪
public const int NetworkNormal_Off = 1500;
public const int NetworkError_On = 200; // 快闪
public const int NetworkError_Off = 200;

// 绿色LED - 配网状态
public const int ConfigAP_On = 0; // 常亮(配网中)
public const int ConfigSuccess_On = 500; // 慢闪(成功)
public const int ConfigFailed_On = 200; // 快闪(失败)
}

// 系统配置常量
public static class SystemConfig
{
public const int WiFiConnectTimeout = 15000; // WiFi连接超时(ms)
public const int WiFiReconnectInterval = 5000; // 重连间隔(ms)
public const int MqttKeepAliveInterval = 60; // MQTT心跳(s)
public const string APSSID = "YF3300_ESP32S3";
public const string APPassword = "yf123456";
public const string APIP = "192.168.4.1";
public const int APConfigTimeout = 600000; // 配网超时(10分钟)
public const int SensorReadInterval = 30000; // 传感器读取间隔
public const int DataUploadInterval = 30000; // 数据上传间隔
}
}

YFLink协议使用JSON格式通过MQTT通信,所有请求都包含 idver(版本号1.3.0)、timestamp 三个基础字段。

Models/YFLinkModels.cs
using System.Collections;

namespace YeFanIoTTest.Models
{
// 基础请求/响应
public class YFLinkRequest
{
public int id { get; set; } // 消息ID
public string ver { get; set; } = "1.3.0"; // 协议版本
public long timestamp { get; set; } // Unix毫秒时间戳
}

public class YFLinkResponse
{
public int id { get; set; }
public int code { get; set; } // 200=成功
public string message { get; set; }
}

// ★ 属性上传
public class PropertyPostRequest : YFLinkRequest
{
public Hashtable parameters { get; set; } // 属性键值对
}

// 事件上传
public class EventPostRequest : YFLinkRequest
{
public ArrayList parameters { get; set; } // 事件数据列表
}

public class EventData
{
public int type { get; set; } // 0-信息 1-告警 2-故障
public int code { get; set; } // 事件编码
public string content { get; set; } // 事件内容(≤1024字节)
public long time { get; set; } // 事件时间戳
}

// 服务下发(云端→设备)
public class ServiceSendRequest : YFLinkRequest
{
public int serviceType { get; set; } // 0-命令 1-参数
public ServiceParams parameters { get; set; }
}

public class ServiceParams
{
public string command { get; set; }
public string parameter { get; set; }
}

// NTP校时
public class NtpRequest : YFLinkRequest
{
public NtpParams parameters { get; set; }
}

public class NtpParams
{
public long deviceSendTime { get; set; }
}

public class NtpResponse : YFLinkResponse
{
public NtpResponseParams parameters { get; set; }
}

public class NtpResponseParams
{
public long deviceSendTime { get; set; }
public long serverRecvTime { get; set; }
public long serverSendTime { get; set; }
}
}

3. MQTT 连接与YFLink认证 (Managers/MqttClientManager.cs)

这是与叶帆物联网平台通信的核心代码,包含YFLink协议的三元组认证(HMAC-SHA1签名)。

3.1 MQTT连接参数

// MQTT服务器
private const string MqttServer = "iot.yfios.net";
private const int MqttPort = 1883;

// YFLink三元组
private const string ProjectId = "YFIoT_TEST";
private const string ProductId = "YF3300_ESP32S3";
private const string DeviceId = "YF3300_ESP32S301";
private const string DeviceKey = "dxR99LCS7Uldc7KUnurFBeBi";

// MQTT主题(V1.3.0格式)
private const string PropertyPostTopic = "{0}/{1}/{2}/property/post"; // 属性上传
private const string EventPostTopic = "{0}/{1}/{2}/event/post"; // 事件上传
private const string ServiceSendTopic = "{0}/{1}/{2}/service/send"; // 服务下发

3.2 HMAC-SHA1 认证算法

重要

nanoFramework的 System.Security.Cryptography 只支持 HMACSHA256/HMACSHA512,不支持 HMACSHA1。因此本项目从零实现了完整的 SHA1 + HMAC-SHA1 算法。

认证公式

clientId = "{项目ID}-{产品ID}-{设备ID}"
userName = "{项目ID}&{产品ID}&{设备ID}"
password = HMAC-SHA1(DeviceKey, clientId + userName).toLowerCase(hex)
Managers/MqttClientManager.cs - Connect方法
public bool Connect()
{
// clientId: YFIoT_TEST-YF3300_ESP32S3-YF3300_ESP32S301
// userName: YFIoT_TEST&YF3300_ESP32S3&YF3300_ESP32S301
// password: HMAC-SHA1签名 → 小写十六进制
string clientId = $"{ProjectId}-{ProductId}-{DeviceId}";
string username = $"{ProjectId}&{ProductId}&{DeviceId}";
string password = CalculateHmacSha1(clientId + username, DeviceKey).ToLower();

_mqttClient = new MqttClient(MqttServer, MqttPort, false, null, null, MqttSslProtocols.None);

// 注册回调
_mqttClient.MqttMsgPublishReceived += OnMessageReceived;
_mqttClient.ConnectionClosed += OnConnectionClosed;

var result = _mqttClient.Connect(clientId, username, password, false, 60);
if (result == MqttReasonCode.Success)
{
SubscribeServiceTopic(); // 订阅服务下发主题
return true;
}
return false;
}

3.3 属性上传

属性上传使用自定义JSON序列化(nanoFramework的 System.Text.Json 功能有限):

public bool PublishProperties(Hashtable properties)
{
var request = new PropertyPostRequest
{
id = GenerateMessageId(),
timestamp = GetCurrentTimestamp(),
parameters = properties // { "H":36.2, "T":29.3, "I1":0, "I2":0, "Q1":0 }
};

string json = SerializeToJson(request);
string topic = string.Format(PropertyPostTopic, ProjectId, ProductId, DeviceId);
_mqttClient.Publish(topic, Encoding.UTF8.GetBytes(json), null, null,
MqttQoSLevel.AtLeastOnce, false);
return true;
}

上传的JSON格式

{
"id": 1234578,
"timestamp": 1716355200000,
"params": {
"H": 36.2,
"T": 29.3,
"I1": 0,
"I2": 0,
"Q1": 0
}
}

3.4 HMAC-SHA1 完整实现

Managers/MqttClientManager.cs - HMAC-SHA1自实现
// HMAC-SHA1算法(RFC 2104)
private byte[] HmacSha1(byte[] key, byte[] message)
{
const int blockSize = 64; // SHA1块大小

// 1. 规范化密钥(>64字节则先SHA1哈希)
byte[] normalizedKey = new byte[blockSize];
if (key.Length > blockSize)
Array.Copy(Sha1(key), normalizedKey, 20);
else
Array.Copy(key, normalizedKey, key.Length);

// 2. 计算inner/outer padding
byte[] innerPadding = new byte[blockSize];
byte[] outerPadding = new byte[blockSize];
for (int i = 0; i < blockSize; i++)
{
innerPadding[i] = (byte)(normalizedKey[i] ^ 0x36);
outerPadding[i] = (byte)(normalizedKey[i] ^ 0x5C);
}

// 3. HMAC = SHA1(outer_padding + SHA1(inner_padding + message))
byte[] innerHash = Sha1(Concat(innerPadding, message));
return Sha1(Concat(outerPadding, innerHash));
}

// SHA1算法(FIPS 180-4)
private byte[] Sha1(byte[] data)
{
uint h0 = 0x67452301, h1 = 0xEFCDAB89, h2 = 0x98BADCFE,
h3 = 0x10325476, h4 = 0xC3D2E1F0;

// 填充 → 分块(64字节) → 80轮压缩 → 输出20字节
// ... (完整实现见项目源码,共130行)
return hash; // 20字节
}

4. SHT30 温湿度传感器 (Drivers/Sht30Sensor.cs)

纯I2C通信实现,不依赖外部传感器库。

Drivers/Sht30Sensor.cs - 核心读取
public class Sht30Sensor : IDisposable
{
private I2cDevice _i2cDevice;
private const byte CMD_MEASURE_HIGH_REP = 0x2C; // 单次测量(高重复性)
private const byte CMD_MEASURE_HIGH_REP_2 = 0x06;

public Sht30Sensor()
{
// 配置I2C引脚:SDA=GPIO17, SCL=GPIO18
Configuration.SetPinFunction(17, DeviceFunction.I2C1_DATA);
Configuration.SetPinFunction(18, DeviceFunction.I2C1_CLOCK);

var settings = new I2cConnectionSettings(1, 0x44); // BusId=1, 地址0x44
_i2cDevice = I2cDevice.Create(settings);
}

public Sht30Data ReadMeasurement()
{
// 1. 发送测量命令
_i2cDevice.Write(new byte[] { 0x2C, 0x06 });

// 2. 等待测量完成(高重复性≈15ms)
Thread.Sleep(20);

// 3. 读取6字节:[温度高, 温度低, CRC, 湿度高, 湿度低, CRC]
byte[] buf = new byte[6];
_i2cDevice.Read(buf);

// 4. 计算公式
int rawT = (buf[0] << 8) | buf[1];
int rawH = (buf[3] << 8) | buf[4];

double temperature = -45.0 + (175.0 * rawT / 65535.0);
double humidity = 100.0 * rawH / 65535.0;

// 5. CRC-8校验(多项式0x31)
CheckCRC(buf[0], buf[1], buf[2]);
CheckCRC(buf[3], buf[4], buf[5]);

return new Sht30Data { Temperature = temperature, Humidity = humidity };
}

// CRC-8校验 (x^8 + x^5 + x^4 + 1)
private bool CheckCRC(byte d1, byte d2, byte crc)
{
byte val = 0xFF;
val ^= d1;
for (int i = 0; i < 8; i++)
val = (byte)((val & 0x80) != 0 ? (val << 1) ^ 0x31 : val << 1);
val ^= d2;
for (int i = 0; i < 8; i++)
val = (byte)((val & 0x80) != 0 ? (val << 1) ^ 0x31 : val << 1);
return val == crc;
}
}

温度公式T = -45 + 175 × (raw / 65535) °C

湿度公式RH = 100 × (raw / 65535) %


5. 数据采集与上传 (Program.cs 主循环)

Program.cs - UploadDataToCloud()
private static void UploadDataToCloud()
{
if (_mqttClientManager == null || !_mqttClientManager.IsConnected) return;

// 采集数据
double temperature = 0, humidity = 0;
bool relayState = false;

if (_sht30Sensor != null)
{
var data = _sht30Sensor.ReadMeasurement();
if (data != null)
{
// 四舍五入保留一位小数
temperature = (int)(data.Temperature * 10 + 0.5) / 10.0;
humidity = (int)(data.Humidity * 10 + 0.5) / 10.0;
}
}

if (_relayDriver != null)
relayState = _relayDriver.GetState(0);

// 构建YFLink属性(属性ID需与云端物模型一致)
var properties = new Hashtable
{
{ "H", humidity }, // 湿度
{ "T", temperature }, // 温度
{ "I1", 0 }, // 开关量输入1
{ "I2", 0 }, // 开关量输入2
{ "Q1", relayState ? 1 : 0 } // 继电器输出1
};

_mqttClientManager.PublishProperties(properties);
}

6. 数据模型 (Models/DeviceModels.cs)

namespace YeFanIoTTest.Models
{
public class DeviceConfig
{
public string ProjectID { get; set; } // 项目ID
public string ProductID { get; set; } // 产品ID
public string DeviceID { get; set; } // 设备ID
public string DeviceKey { get; set; } // 设备密钥(32位)
public string MqttServer { get; set; } // MQTT服务器
public int MqttPort { get; set; } // MQTT端口
}

public class WifiConfig
{
public string SSID { get; set; }
public string Password { get; set; }
}

public class SensorData
{
public double Temperature { get; set; }
public double Humidity { get; set; }
public DateTime Timestamp { get; set; }
}
}

7. 枚举定义 (Enums/Enums.cs)

namespace YeFanIoTTest.Enums
{
public enum DeviceState { Initializing, CheckingConfig, APConfiguring,
ConnectingWifi, ConnectingCloud, NormalRunning, Error }
public enum NetworkStatus { Connecting, Connected, Disconnected, Error }
public enum ConfigStatus { Configuring, Success, Failed, Normal }
public enum LedBlinkMode { Off, On, SlowBlink, FastBlink }
public enum EventType { Info = 0, Warning = 1, Fault = 2 }
public enum ServiceType { Command = 0, Parameter = 1 }
}

8. 按钮与配网交互

Program.cs - 按钮事件处理
_buttonDriver.OnButtonEvent += (sender, e) =>
{
if (e.EventType == ButtonEventType.ShortPress)
{
// 短按:切换继电器
_relayDriver.Toggle(0);
}
else if (e.EventType == ButtonEventType.LongPress)
{
// 长按(3秒):启动AP配网模式
// SSID: YF3300_ESP32S3 密码: yf123456
// 配网页面: http://192.168.4.1
new Thread(() => StartAPConfigMode()).Start();
}
};

// 配网完成回调
_apConfigManager.OnConfigCompleted += (sender, e) =>
{
if (e.Success)
{
_ledManager.SetNetworkStatus(NetworkStatus.Connected);
InitializeNtpTime(); // 配网成功后同步时间
}
};

项目文件结构

YeFanIoTTest/
├── Program.cs # 主入口,12步初始化 + 主循环
├── YeFanIoTTest.nfproj # nanoFramework项目文件
├── packages.config # NuGet依赖(22个包)

├── Hardware/
│ └── YF3300_ESP32S3.cs # 硬件引脚映射 + 系统配置常量

├── Drivers/ # 硬件驱动层
│ ├── Sht30Sensor.cs # SHT30温湿度传感器(I2C + CRC-8校验)
│ ├── ButtonDriver.cs # BOOT按钮(短按/长按检测)
│ ├── DigitalInputDriver.cs # 2路开关量输入(回调模式)
│ ├── RelayDriver.cs # 1路继电器输出
│ ├── LedManager.cs # 双色LED状态指示
│ └── SerialPortManager.cs # [预留] 串口管理器

├── Managers/ # 业务逻辑层
│ ├── MqttClientManager.cs # ★ YFLink协议MQTT通信(含HMAC-SHA1自实现)
│ ├── WifiManager.cs # WiFi STA/AP模式管理
│ ├── APConfigManager.cs # AP配网流程控制
│ ├── WebServer.cs # 配网Web页面服务
│ ├── NtpTimeManager.cs # NTP时间同步(5个服务器)
│ ├── ConfigurationManager.cs # WiFi凭证持久化
│ ├── CloudCommunicationManager.cs # [预留] 云端通信管理器
│ └── DataCollectorManager.cs # [预留] 数据采集管理器

├── Models/ # 数据模型层
│ ├── YFLinkModels.cs # YFLink协议请求/响应模型
│ └── DeviceModels.cs # 设备配置/传感器数据模型

├── Enums/
│ └── Enums.cs # 6个枚举定义

├── Core/
│ └── DeviceManager.cs # [预留] 设备管理器

└── Properties/
└── AssemblyInfo.cs # 程序集元数据
备注

Core/DeviceManager.csCloudCommunicationManager.csDataCollectorManager.csSerialPortManager.cs 为预留文件,当前不参与编译。


全部代码

Program.cs
using System;
using System.Device.Gpio;
using System.Threading;
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using nanoFramework.Logging.Debug;
using YeFanIoTTest.Drivers;
using YeFanIoTTest.Enums;
using YeFanIoTTest.Managers;
using YeFanIoTTest.Models;

namespace YeFanIoTTest
{
public class Program
{
private static LedManager _ledManager;
private static GpioController _gpioController;
private static ButtonDriver _buttonDriver;
private static WifiManager _wifiManager;
private static APConfigManager _apConfigManager;
private static RelayDriver _relayDriver;
private static DigitalInputDriver _digitalInputDriver;
private static Sht30Sensor _sht30Sensor;
private static NtpTimeManager _ntpTimeManager;
private static ConfigurationManager _configurationManager;
private static MqttClientManager _mqttClientManager;

public static void Main()
{
Console.WriteLine("========================================");
Console.WriteLine(" YF3300-ESP32S3 - Iteration 4 Test");
Console.WriteLine(" NTP Time Sync + Cloud Communication");
Console.WriteLine("========================================");

Console.WriteLine("\n[Step 1] Initializing Logger...");
try
{
var factory = new DebugLoggerFactory();
if (factory != null)
{
LogDispatcher.LoggerFactory = factory;
Console.WriteLine("[Step 1] Logger - PASS");
}
}
catch (Exception ex)
{
Console.WriteLine($"[Step 1] Logger - FAIL: {ex.Message}");
}
Thread.Sleep(500);

Console.WriteLine("\n[Step 2] Initializing GPIO...");
try
{
_gpioController = new GpioController();
Console.WriteLine("[Step 2] GPIO Controller - PASS");
}
catch (Exception ex)
{
Console.WriteLine($"[Step 2] GPIO - FAIL: {ex.Message}");
Thread.Sleep(Timeout.Infinite);
}
Thread.Sleep(500);

Console.WriteLine("\n[Step 3] Initializing LED...");
try
{
_ledManager = new LedManager(_gpioController);
_ledManager.SetNetworkStatus(NetworkStatus.Connecting);
_ledManager.SetConfigStatus(ConfigStatus.Normal); // 绿色LED默认熄灭(正常运行)
Console.WriteLine("[Step 3] LED Manager - PASS");
}
catch (Exception ex)
{
Console.WriteLine($"[Step 3] LED - FAIL: {ex.Message}");
}
Thread.Sleep(500);

Console.WriteLine("\n[Step 4] Initializing Relay...");
try
{
_relayDriver = new RelayDriver(_gpioController);
Console.WriteLine("[Step 4] Relay Driver - PASS");
}
catch (Exception ex)
{
Console.WriteLine($"[Step 4] Relay - FAIL: {ex.Message}");
}
Thread.Sleep(500);

Console.WriteLine("\n[Step 5] Initializing Digital Input...");
try
{
// 使用回调委托,实时响应数字输入变化
_digitalInputDriver = new DigitalInputDriver(_gpioController, OnDigitalInputChanged);
Console.WriteLine("[Step 5] Digital Input Driver - PASS");
Console.WriteLine("[Step 5] Callback mode enabled (real-time response)");
}
catch (Exception ex)
{
Console.WriteLine($"[Step 5] Digital Input - FAIL: {ex.Message}");
}
Thread.Sleep(500);

Console.WriteLine("\n[Step 6] Initializing SHT30 Sensor...");
try
{
_sht30Sensor = new Sht30Sensor();
Console.WriteLine("[Step 6] SHT30 Sensor - PASS");
}
catch (Exception ex)
{
Console.WriteLine($"[Step 6] SHT30 - FAIL: {ex.Message}");
}
Thread.Sleep(500);

Console.WriteLine("\n[Step 7] Initializing Button...");
try
{
_buttonDriver = new ButtonDriver(
_gpioController,
YFSoft.Hardware.YF3300_ESP32S3.Mainboard.Pins.BOOT,
debounceTime: 20,
longPressTime: 3000
);
Console.WriteLine("[Step 7] Button Driver - PASS");

_buttonDriver.OnButtonEvent += (sender, e) =>
{
Console.WriteLine($"[Button] Event: {e.EventType}, Pin: {e.PinNumber}");

if (e.EventType == ButtonEventType.ShortPress)
{
if (_relayDriver != null)
{
_relayDriver.Toggle(0);
}
Console.WriteLine("[Button] Toggled Relay 1");
}
else if (e.EventType == ButtonEventType.LongPress)
{
Console.WriteLine("[Button] Long press detected - Starting AP config mode...");

Thread apThread = new Thread(() =>
{
StartAPConfigMode();
});
apThread.Start();
}
};
Console.WriteLine("[Step 7] Event Handler Registered");
}
catch (Exception ex)
{
Console.WriteLine($"[Step 7] Button - FAIL: {ex.Message}");
}
Thread.Sleep(500);

Console.WriteLine("\n[Step 8] Initializing Configuration Manager...");
try
{
_configurationManager = new ConfigurationManager();
Console.WriteLine("[Step 8] Configuration Manager - PASS");
}
catch (Exception ex)
{
Console.WriteLine($"[Step 8] Configuration Manager - FAIL: {ex.Message}");
}
Thread.Sleep(500);

Console.WriteLine("\n[Step 9] Initializing WiFi Manager...");
try
{
_wifiManager = new WifiManager();
Console.WriteLine("[Step 9] WiFi Manager - PASS");
}
catch (Exception ex)
{
Console.WriteLine($"[Step 9] WiFi Manager - FAIL: {ex.Message}");
}
Thread.Sleep(500);

Console.WriteLine("\n[Step 10] Checking WiFi Configuration...");
bool hasWifiConfig = false;
try
{
hasWifiConfig = _configurationManager.HasWifiConfig();
if (hasWifiConfig)
{
Console.WriteLine("[Step 10] WiFi configuration found");

// 加载WiFi配置
WifiConfig wifiConfig;
if (_configurationManager.LoadWifiConfig(out wifiConfig))
{
Console.WriteLine($"[Step 10] SSID: {wifiConfig.SSID}");

// 连接WiFi
Console.WriteLine("[Step 10] Connecting to WiFi...");
bool connected = _wifiManager.ConnectSTA(wifiConfig.SSID, wifiConfig.Password);
if (connected)
{
Console.WriteLine("[Step 10] WiFi connected successfully");
_ledManager.SetNetworkStatus(NetworkStatus.Connected);

// 初始化NTP并同步时间
InitializeNtpTime();
}
else
{
Console.WriteLine("[Step 10] WiFi connection failed");
_ledManager.SetNetworkStatus(NetworkStatus.Disconnected);
}
}
}
else
{
Console.WriteLine("[Step 10] No WiFi configuration found");
Console.WriteLine("[Step 10] Long press BOOT button to start AP config mode");
_ledManager.SetNetworkStatus(NetworkStatus.Disconnected);
}
}
catch (Exception ex)
{
Console.WriteLine($"[Step 10] WiFi Check - FAIL: {ex.Message}");
}
Thread.Sleep(500);

Console.WriteLine("\n[Step 11] Initializing AP Config Manager...");
try
{
_apConfigManager = new APConfigManager(_wifiManager);

// 设置WebServerController的APConfigManager实例引用
WebServerController.SetAPConfigManager(_apConfigManager);

Console.WriteLine("[Step 11] AP Config Manager - PASS");

_apConfigManager.OnConfigCompleted += (sender, e) =>
{
Console.WriteLine($"[AP Config] Result: {(e.Success ? "SUCCESS" : "FAILED")}");
Console.WriteLine($"[AP Config] SSID: {e.SSID}");
Console.WriteLine($"[AP Config] Message: {e.Message}");

// 检查是否网络可达
bool networkReachable = e.Message.Contains("网络可达");

if (e.Success && networkReachable)
{
// ========== 网络可达:配网真正成功 ==========
Console.WriteLine("[AP Config] Network is reachable - Configuration successful!");

// ========== 暂时注释:不保存WiFi配置 ==========
// if (_configurationManager != null)
// {
// var wifiConfig = new WifiConfig { SSID = e.SSID, Password = e.Password };
// _configurationManager.SaveWifiConfig(wifiConfig);
// Console.WriteLine("[AP Config] WiFi configuration saved");
// }

if (_ledManager != null)
{
_ledManager.SetNetworkStatus(NetworkStatus.Connected);
}

// 配网成功后,初始化NTP并同步时间
InitializeNtpTime();
}
else if (e.Success && !networkReachable)
{
// ========== 网络不可达:配网失败 ==========
Console.WriteLine("[AP Config] Network is NOT reachable - Configuration failed!");
Console.WriteLine("[AP Config] Please check your router internet connection");

if (_ledManager != null)
{
_ledManager.SetNetworkStatus(NetworkStatus.Disconnected);
}
}
else
{
// ========== WiFi连接失败 ==========
Console.WriteLine("[AP Config] WiFi connection failed");

if (_ledManager != null)
{
_ledManager.SetNetworkStatus(NetworkStatus.Disconnected);
}
}
};
Console.WriteLine("[Step 11] Event Handler Registered");
}
catch (Exception ex)
{
Console.WriteLine($"[Step 11] AP Config Manager - FAIL: {ex.Message}");
}

Console.WriteLine("\n========================================");
Console.WriteLine(" Iteration 4 Test Complete!");
Console.WriteLine("========================================");
Console.WriteLine("\nTest Instructions:");
Console.WriteLine("1. Short press BOOT button to toggle relay");
Console.WriteLine("2. Long press BOOT button to start AP config mode");
Console.WriteLine("3. Digital inputs use real-time callback mode");
Console.WriteLine("4. Monitor temperature, humidity and time every 5 seconds");
Console.WriteLine("5. Upload data to cloud every 30 seconds");
Console.WriteLine("\nPress CTRL+C to exit\n");

// ========== 步骤12:连接MQTT服务器 ==========
Console.WriteLine("\n[Step 12] Connecting to MQTT Server...");
try
{
_mqttClientManager = new MqttClientManager();
bool mqttConnected = _mqttClientManager.Connect();

if (mqttConnected)
{
Console.WriteLine("[Step 12] MQTT Connected successfully!");
}
else
{
Console.WriteLine("[Step 12] MQTT Connection failed!");
}
}
catch (Exception ex)
{
Console.WriteLine($"[Step 12] MQTT Error: {ex.Message}");
}
Thread.Sleep(500);

if (_ledManager != null)
{
_ledManager.SetNetworkStatus(NetworkStatus.Connected);
}

int counter = 0;
while (true)
{
try
{
counter++;
Console.WriteLine($"\n--- Reading #{counter} ---");

// 显示时间
if (_ntpTimeManager != null)
{
try
{
var localTime = _ntpTimeManager.GetLocalTime();
Console.WriteLine($"Time: {localTime:yyyy-MM-dd HH:mm:ss}");
}
catch (Exception ex)
{
Console.WriteLine($"Time: Error - {ex.Message}");
}
}
else
{
Console.WriteLine("Time: NTP not initialized");
}

// 读取温湿度
if (_sht30Sensor != null)
{
try
{
var sht30Data = _sht30Sensor.ReadMeasurement();
if (sht30Data != null)
{
Console.WriteLine($"Temperature: {sht30Data.Temperature:F1}°C, Humidity: {sht30Data.Humidity:F1}%");
}
else
{
Console.WriteLine("Temperature: N/A, Humidity: N/A");
}
}
catch (Exception ex)
{
Console.WriteLine($"Temperature: Error - {ex.Message}");
}
}
else
{
Console.WriteLine("Temperature: Sensor not initialized");
}

// 读取数字输入状态(轮询模式,作为备份)
if (_digitalInputDriver != null)
{
try
{
for (int i = 0; i < 2; i++)
{
var state = _digitalInputDriver.ReadState(i);
Console.WriteLine($"Digital Input {i + 1}: {(state ? "Triggered" : "Not triggered")}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Digital Input: Error - {ex.Message}");
}
}
else
{
Console.WriteLine("Digital Input: Driver not initialized");
}

// 读取继电器状态
if (_relayDriver != null)
{
try
{
var relayState = _relayDriver.GetState(0);
Console.WriteLine($"Relay 1: {(relayState ? "ON" : "OFF")}");
}
catch (Exception ex)
{
Console.WriteLine($"Relay: Error - {ex.Message}");
}
}
else
{
Console.WriteLine("Relay: Driver not initialized");
}

Thread.Sleep(5000);

// ========== 上传数据到云端(每30秒) ==========
if (counter % 6 == 0) // 每6次读取(约30秒)上传一次
{
UploadDataToCloud();
}
}
catch (Exception ex)
{
Console.WriteLine($"Error in monitoring loop: {ex.Message}");
Thread.Sleep(1000);
}
}
}

// 初始化NTP并同步时间
private static void InitializeNtpTime()
{
try
{
Console.WriteLine("\n[NTP] Initializing NTP Time Manager...");
_ntpTimeManager = new NtpTimeManager();

Console.WriteLine("[NTP] Starting NTP client...");
bool ntpInitSuccess = _ntpTimeManager.Initialize();
if (ntpInitSuccess)
{
Console.WriteLine("[NTP] NTP Client started");

Console.WriteLine("[NTP] Syncing time...");
Thread.Sleep(2000); // 等待2秒让NTP同步

bool syncSuccess = _ntpTimeManager.SyncNow();
if (syncSuccess)
{
var utcTime = _ntpTimeManager.GetCurrentTime();
var localTime = _ntpTimeManager.GetLocalTime();
Console.WriteLine($"[NTP] UTC Time: {utcTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"[NTP] Local Time: {localTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine("[NTP] Time sync successful");
}
else
{
Console.WriteLine("[NTP] Time sync failed");
}
}
else
{
Console.WriteLine("[NTP] NTP Client start failed");
}
}
catch (Exception ex)
{
Console.WriteLine($"[NTP] Error: {ex.Message}");
}
}

// 上传数据到云端
private static void UploadDataToCloud()
{
if (_mqttClientManager == null || !_mqttClientManager.IsConnected)
{
Console.WriteLine("[Cloud] MQTT not connected, skipping data upload");
return;
}

try
{
// 收集数据
double temperature = 0;
double humidity = 0;
bool relayState = false;

// 读取温湿度(保留一位小数)
if (_sht30Sensor != null)
{
var sht30Data = _sht30Sensor.ReadMeasurement();
if (sht30Data != null)
{
// 使用整数运算实现四舍五入
temperature = (int)(sht30Data.Temperature * 10 + 0.5) / 10.0;
humidity = (int)(sht30Data.Humidity * 10 + 0.5) / 10.0;
}
}

// 读取继电器状态
if (_relayDriver != null)
{
relayState = _relayDriver.GetState(0);
}

// 构建属性数据(使用YFLink协议规定的属性ID)
// H - 湿度(百分比)
// T - 温度(摄氏度)
// I1 - 开关量输入1
// I2 - 开关量输入2
// Q1 - 继电器输出1
var properties = new System.Collections.Hashtable
{
{ "H", humidity }, // 湿度
{ "T", temperature }, // 温度
{ "I1", 0 }, // 开关量输入1(待实现)
{ "I2", 0 }, // 开关量输入2(待实现)
{ "Q1", relayState ? 1 : 0 } // 继电器输出1
};

// 上传属性
bool uploadSuccess = _mqttClientManager.PublishProperties(properties);
if (uploadSuccess)
{
Console.WriteLine($"[Cloud] Data uploaded: H={humidity:F1}%, T={temperature:F1}°C, I1=0, I2=0, Q1={(relayState ? 1 : 0)}");
}
else
{
Console.WriteLine("[Cloud] Data upload failed");
}
}
catch (Exception ex)
{
Console.WriteLine($"[Cloud] Upload error: {ex.Message}");
}
}

// 数字输入状态变化回调
// 【重要】此方法在GPIO中断上下文中执行,必须遵循BUG1.md的规则
// channel: 通道号(从0开始)
// isTriggered: 是否触发(true=低电平触发,false=高电平未触发)
// pinValue: 引脚电平值(0=低电平,1=高电平)
private static void OnDigitalInputChanged(int channel, bool isTriggered, int pinValue)
{
// 在中断上下文中,只能做最简单的操作
// 使用Console.WriteLine输出,不创建对象
Console.WriteLine($"[Digital Input] Channel {channel + 1}: {(isTriggered ? "Triggered" : "Not triggered")} (Pin: {pinValue})");
}

private static void StartAPConfigMode()
{
try
{
Console.WriteLine("\n[AP Config] Starting AP configuration mode...");
if (_ledManager != null)
{
_ledManager.SetNetworkStatus(NetworkStatus.Connecting);
}

Thread.Sleep(100);

bool success = _apConfigManager.StartAPConfig();

if (success)
{
Console.WriteLine("[AP Config] AP mode started successfully");
Console.WriteLine("[AP Config] SSID: YF3300_ESP32S3");
Console.WriteLine("[AP Config] Password: yf123456");
Console.WriteLine("[AP Config] IP: 192.168.4.1");
}
else
{
Console.WriteLine("[AP Config] Failed to start AP mode");
if (_ledManager != null)
{
_ledManager.SetNetworkStatus(NetworkStatus.Disconnected);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[AP Config] Error: {ex.Message}");
Console.WriteLine($"[AP Config] Exception Type: {ex.GetType().Name}");
if (_ledManager != null)
{
_ledManager.SetNetworkStatus(NetworkStatus.Disconnected);
}
}
}
}
}
Hardware/YF3300_ESP32S3.cs
using System;


namespace YFSoft.Hardware.YF3300_ESP32S3
{
public static class CPU
{
public static class Pins
{
// GPIO 0-47
public const int GPIO0 = 0;
public const int GPIO1 = 1;
public const int GPIO2 = 2;
public const int GPIO3 = 3;
public const int GPIO4 = 4;
public const int GPIO5 = 5;
public const int GPIO6 = 6;
public const int GPIO7 = 7;
public const int GPIO8 = 8;
public const int GPIO9 = 9;
public const int GPIO10 = 10;
public const int GPIO11 = 11;
public const int GPIO12 = 12;
public const int GPIO13 = 13;
public const int GPIO14 = 14;
public const int GPIO15 = 15;
public const int GPIO16 = 16;
public const int GPIO17 = 17;
public const int GPIO18 = 18;
public const int GPIO19 = 19;
public const int GPIO20 = 20;
public const int GPIO21 = 21;
public const int GPIO22 = 22;
public const int GPIO23 = 23;
public const int GPIO24 = 24;
public const int GPIO25 = 25;
public const int GPIO26 = 26;
public const int GPIO27 = 27;
public const int GPIO28 = 28;
public const int GPIO29 = 29;
public const int GPIO30 = 30;
public const int GPIO31 = 31;
public const int GPIO32 = 32;
public const int GPIO33 = 33;
public const int GPIO34 = 34;
public const int GPIO35 = 35;
public const int GPIO36 = 36;
public const int GPIO37 = 37;
public const int GPIO38 = 38;
public const int GPIO39 = 39;
public const int GPIO40 = 40;
public const int GPIO41 = 41;
public const int GPIO42 = 42;
public const int GPIO43 = 43;
public const int GPIO44 = 44;
public const int GPIO45 = 45;
public const int GPIO46 = 46;
public const int GPIO47 = 47;
public const int GPIO48 = 48;
}
}

public static class Mainboard
{
// 主板引脚定义
public static class Pins
{
// LED 指示灯(根据实际硬件连接)
public const int YellowLED = CPU.Pins.GPIO40; // 黄色LED - 网络状态指示
public const int GreenLED = CPU.Pins.GPIO39; // 绿色LED - 配网状态指示

// 兼容旧命名
public const int CommLED = YellowLED; // 通信指示灯(黄色)
public const int UserLED = GreenLED; // 用户指示灯(绿色)

// 按钮
public const int BOOT = CPU.Pins.GPIO0;

// 开关量输入
public const int I1 = CPU.Pins.GPIO21; // 输入1
public const int I2 = CPU.Pins.GPIO47; // 输入2

// 继电器输出
public const int Q1 = CPU.Pins.GPIO48; // 继电器1

}

// RS485 串口定义
public static class RS485
{
public const string PortName = "COM1";
public const int DefaultBaudRate = 9600;
public const int TxPin = CPU.Pins.GPIO9; // UART1 TX (电路图: TX1=IO9)
public const int RxPin = CPU.Pins.GPIO10; // UART1 RX (电路图: RX1=IO10)
}

// RS232 串口定义
public static class RS232
{
public const string PortName = "COM2";
public const int DefaultBaudRate = 9600;
public const int TxPin = CPU.Pins.GPIO11; // UART2 TX (电路图: TX2=IO11)
public const int RxPin = CPU.Pins.GPIO12; // UART2 RX (电路图: RX2=IO12)
}

// I2C 总线定义
public static class I2C
{
public const int BusId = 1;
public const int SdaPin = CPU.Pins.GPIO17;
public const int SclPin = CPU.Pins.GPIO18;
public const int DefaultSpeed = 100000; // 100kHz
}

// 开关量输入通道
public static class DigitalInputs
{
public const int Count = 2;
public static readonly int[] Channels = { Mainboard.Pins.I1, Mainboard.Pins.I2 };
}

// 继电器输出通道
public static class Relays
{
public const int Count = 1;
public static readonly int[] Channels = { Mainboard.Pins.Q1 };
}

// LED 闪烁时间定义(毫秒)
public static class LEDTiming
{
// 黄色LED - 网络状态指示
public const int NetworkConnecting_On = 0; // 常亮(正在连接)
public const int NetworkConnecting_Off = int.MaxValue;

public const int NetworkNormal_On = 500; // 慢闪(正常)
public const int NetworkNormal_Off = 1500;

public const int NetworkError_On = 200; // 快闪(异常)
public const int NetworkError_Off = 200;

// 绿色LED - 配网状态指示
public const int ConfigAP_On = 0; // 常亮(配网中)
public const int ConfigAP_Off = int.MaxValue;

public const int ConfigSuccess_On = 500; // 慢闪(配网成功)
public const int ConfigSuccess_Off = 1500;

public const int ConfigFailed_On = 200; // 快闪(配网失败)
public const int ConfigFailed_Off = 200;

public const int ConfigNormal_On = 0; // 熄灭(正常运行)
public const int ConfigNormal_Off = 0;
}
}

// 设备信息定义
public static class DeviceInfo
{
public const string DeviceName = "YF3300-ESP32S3"; // 设备名称
public const string Manufacturer = "YFSoft"; // 制造商
public const string HardwareVersion = "1.0.0"; // 硬件版本
public const string FirmwareVersion = "1.0.0"; // 固件版本
public const string Model = "YF3300-ESP32S3"; // 设备型号
}

// 系统配置常量
public static class SystemConfig
{
// WiFi 配置
public const int WiFiConnectTimeout = 15000; // WiFi连接超时(毫秒)
public const int WiFiReconnectInterval = 5000; // WiFi重连间隔(毫秒)

// MQTT 配置
public const string DefaultMqttServer = "mqtt.yfiot.com"; // 默认MQTT服务器
public const int DefaultMqttPort = 1883; // 默认MQTT端口
public const int MqttKeepAliveInterval = 60; // MQTT心跳间隔(秒)
public const int MqttReconnectInterval = 5000; // MQTT重连间隔(毫秒)

// AP 配网配置
public const string APSSID = "YF3300_ESP32S3"; // AP热点名称
public const string APPassword = "yf123456"; // AP热点密码
public const string APIP = "192.168.4.1"; // AP网关IP
public const int APConfigTimeout = 600000; // 配网超时(10分钟)

// 数据采集配置
public const int SensorReadInterval = 30000; // 传感器读取间隔(毫秒)
public const int DataUploadInterval = 30000; // 数据上传间隔(毫秒)

// 看门狗配置
public const int WatchdogTimeout = 30000; // 看门狗超时(毫秒)

// NTP 配置
public const string DefaultNtpServer = "ntp.aliyun.com"; // 默认NTP服务器
public const int NtpSyncInterval = 3600000; // NTP同步间隔(1小时)

// 按钮配置
public const int ButtonLongPressDuration = 5000; // 长按时间(毫秒)
public const int ButtonDebounceTime = 50; // 按钮防抖时间(毫秒)
}
}
Models/YFLinkModels.cs
using System;
using System.Collections;

namespace YeFanIoTTest.Models
{
// YFLink协议基础请求模型
public class YFLinkRequest
{
public int id { get; set; } // 消息ID(32位整数)
public string ver { get; set; } = "1.3.0"; // 协议版本
public long timestamp { get; set; } // 时间戳(1970年1月1日以来的毫秒数)
}

// YFLink协议基础响应模型
public class YFLinkResponse
{
public int id { get; set; } // 消息ID(与请求ID对应)
public int code { get; set; } // 返回结果编码(200表示成功)
public string message { get; set; } // 返回结果描述
}

// 属性上传请求模型
public class PropertyPostRequest : YFLinkRequest
{
public Hashtable parameters { get; set; } // 属性键值对集合
}

// 属性上传响应模型
public class PropertyPostResponse : YFLinkResponse
{
public ArrayList data { get; set; } // 验证不通过的属性标识列表
}

// 事件上传请求模型
public class EventPostRequest : YFLinkRequest
{
public ArrayList parameters { get; set; } // 事件数据列表
}

// 事件数据模型
public class EventData
{
public int type { get; set; } // 事件类型(0-信息,1-告警,2-故障)
public int code { get; set; } // 事件编码(32位整数)
public string content { get; set; } // 事件内容(不超过1024字节)
public long time { get; set; } // 事件时间戳
}

// 服务下发请求模型
public class ServiceSendRequest : YFLinkRequest
{
public int serviceType { get; set; } // 服务类型(0-命令,1-参数)
public ServiceParams parameters { get; set; } // 服务参数
}

// 服务参数模型
public class ServiceParams
{
public string command { get; set; } // 服务命令
public string parameter { get; set; } // 服务参数
}

// 服务响应模型
public class ServiceSendResponse : YFLinkRequest
{
public ServiceResultParams parameters { get; set; } // 服务响应参数
}

// 服务响应参数模型
public class ServiceResultParams
{
public int code { get; set; } // 服务响应标识符
public string content { get; set; } // 服务响应内容
}

// NTP校时请求模型
public class NtpRequest : YFLinkRequest
{
public NtpParams parameters { get; set; } // NTP参数
}

// NTP参数模型
public class NtpParams
{
public long deviceSendTime { get; set; } // 设备发送请求的时间
}

// NTP校时响应模型
public class NtpResponse : YFLinkResponse
{
public NtpResponseParams parameters { get; set; } // NTP响应参数
}

// NTP响应参数模型
public class NtpResponseParams
{
public long deviceSendTime { get; set; } // 设备发送请求的时间
public long serverRecvTime { get; set; } // 服务器接收到该请求的时间
public long serverSendTime { get; set; } // 服务器发起发送该响应的时间
}
}

Models/DeviceModels.cs
using System;

namespace YeFanIoTTest.Models
{
// 设备配置模型 - 包含YFLink协议四元组及MQTT服务器配置
public class DeviceConfig
{
public string ProjectID { get; set; } // 项目ID
public string ProductID { get; set; } // 产品ID
public string DeviceID { get; set; } // 设备ID
public string DeviceKey { get; set; } // 设备密钥(32位随机字符)
public string MqttServer { get; set; } // MQTT服务器地址
public int MqttPort { get; set; } // MQTT服务器端口

// 验证设备配置是否有效
public bool IsValid()
{
return !string.IsNullOrEmpty(ProjectID) &&
!string.IsNullOrEmpty(ProductID) &&
!string.IsNullOrEmpty(DeviceID) &&
!string.IsNullOrEmpty(DeviceKey);
}
}

// WiFi配置模型
public class WifiConfig
{
public string SSID { get; set; } // WiFi名称
public string Password { get; set; } // WiFi密码

// 验证WiFi配置是否有效
public bool IsValid()
{
return !string.IsNullOrEmpty(SSID) && !string.IsNullOrEmpty(Password);
}
}

// 传感器数据模型 - 存储温湿度传感器采集的数据
public class SensorData
{
public double Temperature { get; set; } // 温度值(摄氏度)
public double Humidity { get; set; } // 湿度值(百分比)
public DateTime Timestamp { get; set; } // 数据采集时间戳

// 格式化输出传感器数据
public override string ToString()
{
return $"Temperature: {Temperature:F1}°C, Humidity: {Humidity:F1}%";
}
}

// 继电器状态模型
public class RelayState
{
public int RelayId { get; set; } // 继电器ID(1, 2, 3...)
public bool IsOn { get; set; } // 继电器状态(true=打开,false=关闭)
public DateTime Timestamp { get; set; } // 状态更新时间戳
}

// 开关量输入状态模型
public class DigitalInputState
{
public int InputId { get; set; } // 输入通道ID(1, 2, 3...)
public bool IsHigh { get; set; } // 输入状态(true=高电平,false=低电平)
public DateTime Timestamp { get; set; } // 状态更新时间戳
}
}

Enums/Enums.cs
using System;

namespace YeFanIoTTest.Enums
{
// 设备状态枚举 - 用于表示设备的生命周期状态
public enum DeviceState
{
Initializing, // 初始化中
CheckingConfig, // 检查配置
APConfiguring, // AP配网中
ConnectingWifi, // 连接WiFi中
ConnectingCloud, // 连接云端中
NormalRunning, // 正常运行
Error // 错误状态
}

// 网络状态枚举 - 用于表示WiFi和MQTT连接状态
public enum NetworkStatus
{
Connecting, // 连接中
Connected, // 已连接
Disconnected, // 已断开
Error // 错误
}

// 配网状态枚举 - 用于表示AP配网过程的状态
public enum ConfigStatus
{
Configuring, // 配网中
Success, // 配网成功
Failed, // 配网失败
Normal // 正常运行
}

// LED闪烁模式枚举
public enum LedBlinkMode
{
Off, // 关闭
On, // 常亮
SlowBlink, // 慢闪
FastBlink // 快闪
}

// 事件类型枚举 - 对应YFLink协议中的事件类型定义
public enum EventType
{
Info = 0, // 信息事件
Warning = 1, // 告警事件
Fault = 2 // 故障事件
}

// 服务类型枚举 - 对应YFLink协议中的服务类型定义
public enum ServiceType
{
Command = 0, // 命令服务
Parameter = 1 // 参数服务
}
}

Drivers/Sht30Sensor.cs
using System;
using System.Device.I2c;
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using nanoFramework.Hardware.Esp32;
using YFSoft.Hardware.YF3300_ESP32S3;

namespace YeFanIoTTest.Drivers
{
// SHT30传感器数据模型
public class Sht30Data
{
public double Temperature { get; set; } // 温度值(摄氏度)
public double Humidity { get; set; } // 湿度值(百分比)
public DateTime Timestamp { get; set; } // 数据读取时间戳

// 格式化输出传感器数据
public override string ToString()
{
return $"Temperature: {Temperature:F1}°C, Humidity: {Humidity:F1}%, Time: {Timestamp:HH:mm:ss}";
}
}

// SHT30温湿度传感器驱动类
// 使用纯I2C通信协议实现,不依赖外部库
public class Sht30Sensor : IDisposable
{
private readonly ILogger _logger;
private I2cDevice _i2cDevice; // I2C 设备实例
private bool _disposed = false;

// SHT30 I2C命令定义
private const byte CMD_MEASURE_HIGH_REP = 0x2C; // 单次测量命令(高重复性)
private const byte CMD_MEASURE_HIGH_REP_2 = 0x06; // 单次测量命令第二字节
private const byte CMD_SOFT_RESET = 0x30; // 软复位命令
private const byte CMD_SOFT_RESET_2 = 0xA2; // 软复位命令第二字节
private const byte CMD_READ_STATUS = 0xF3; // 读状态寄存器命令
private const byte CMD_READ_STATUS_2 = 0x2D; // 读状态寄存器命令第二字节

// 默认构造函数:使用默认I2C配置
public Sht30Sensor()
{
_logger = LogDispatcher.LoggerFactory.CreateLogger("Sht30Sensor");

try
{
// 配置I2C引脚
Configuration.SetPinFunction(Mainboard.I2C.SdaPin, DeviceFunction.I2C1_DATA);
Configuration.SetPinFunction(Mainboard.I2C.SclPin, DeviceFunction.I2C1_CLOCK);

// 创建I2C设备(默认地址0x44)
var i2cSettings = new I2cConnectionSettings(Mainboard.I2C.BusId, 0x44);
_i2cDevice = I2cDevice.Create(i2cSettings);

_logger.LogInformation($"SHT30 sensor initialized on I2C bus {Mainboard.I2C.BusId}, address: 0x44");
}
catch (Exception ex)
{
_logger.LogError($"Failed to initialize SHT30 sensor: {ex.Message}");
throw;
}
}

// 带参数构造函数
// i2cBusId: I2C总线ID
// sensorAddress: 传感器地址(0x44或0x45)
public Sht30Sensor(int i2cBusId, byte sensorAddress)
{
_logger = LogDispatcher.LoggerFactory.CreateLogger("Sht30Sensor");

try
{
// 配置I2C引脚
Configuration.SetPinFunction(Mainboard.I2C.SdaPin, DeviceFunction.I2C1_DATA);
Configuration.SetPinFunction(Mainboard.I2C.SclPin, DeviceFunction.I2C1_CLOCK);

// 创建I2C设备
var i2cSettings = new I2cConnectionSettings(i2cBusId, sensorAddress);
_i2cDevice = I2cDevice.Create(i2cSettings);

_logger.LogInformation($"SHT30 sensor initialized on I2C bus {i2cBusId}, address: 0x{sensorAddress:X2}");
}
catch (Exception ex)
{
_logger.LogError($"Failed to initialize SHT30 sensor: {ex.Message}");
throw;
}
}

// 读取传感器数据
// 返回:Sht30Data对象,读取失败返回null
public Sht30Data ReadMeasurement()
{
if (_disposed)
{
_logger.LogWarning("Sensor has been disposed");
return null;
}

try
{
// 发送测量命令(单次测量,高重复性)
byte[] writeBuffer = new byte[] { CMD_MEASURE_HIGH_REP, CMD_MEASURE_HIGH_REP_2 };
_i2cDevice.Write(writeBuffer);

// 等待测量完成(高重复性测量需要约15ms)
System.Threading.Thread.Sleep(20);

// 读取6字节数据:温度高字节、温度低字节、温度CRC、湿度高字节、湿度低字节、湿度CRC
byte[] readBuffer = new byte[6];
_i2cDevice.Read(readBuffer);

// 解析温度数据(前2字节)
int rawTemperature = (readBuffer[0] << 8) | readBuffer[1];

// 解析湿度数据(后2字节,跳过CRC)
int rawHumidity = (readBuffer[3] << 8) | readBuffer[4];

// 计算实际温度和湿度
// 温度公式:T = -45 + 175 * (raw / 65535.0)
// 湿度公式:RH = 100 * (raw / 65535.0)
double temperature = -45.0 + (175.0 * rawTemperature / 65535.0);
double humidity = 100.0 * rawHumidity / 65535.0;

// CRC校验(可选)
if (!CheckCRC(readBuffer[0], readBuffer[1], readBuffer[2]))
{
_logger.LogWarning("Temperature CRC check failed");
}

if (!CheckCRC(readBuffer[3], readBuffer[4], readBuffer[5]))
{
_logger.LogWarning("Humidity CRC check failed");
}

return new Sht30Data
{
Temperature = temperature,
Humidity = humidity,
Timestamp = DateTime.UtcNow
};
}
catch (Exception ex)
{
_logger.LogError($"Failed to read SHT30 sensor: {ex.Message}");
return null;
}
}

// 重置传感器
public void Reset()
{
if (_disposed)
{
_logger.LogWarning("Sensor has been disposed");
return;
}

try
{
// 发送软复位命令
byte[] writeBuffer = new byte[] { CMD_SOFT_RESET, CMD_SOFT_RESET_2 };
_i2cDevice.Write(writeBuffer);

// 等待复位完成
System.Threading.Thread.Sleep(10);

_logger.LogInformation("SHT30 sensor reset successfully");
}
catch (Exception ex)
{
_logger.LogError($"Failed to reset SHT30 sensor: {ex.Message}");
}
}

// 读取状态寄存器
public ushort ReadStatus()
{
if (_disposed)
{
_logger.LogWarning("Sensor has been disposed");
return 0;
}

try
{
// 发送读状态命令
byte[] writeBuffer = new byte[] { CMD_READ_STATUS, CMD_READ_STATUS_2 };
_i2cDevice.Write(writeBuffer);

// 读取3字节:状态高字节、状态低字节、CRC
byte[] readBuffer = new byte[3];
_i2cDevice.Read(readBuffer);

// 组合状态字
ushort status = (ushort)((readBuffer[0] << 8) | readBuffer[1]);

return status;
}
catch (Exception ex)
{
_logger.LogError($"Failed to read SHT30 status: {ex.Message}");
return 0;
}
}

// CRC校验函数
// data1: 数据字节1
// data2: 数据字节2
// crc: CRC校验字节
// 返回:校验是否通过
private bool CheckCRC(byte data1, byte data2, byte crc)
{
// SHT30使用CRC-8校验
// 多项式:0x31 (x^8 + x^5 + x^4 + 1)
// 初始值:0xFF
byte crcValue = 0xFF;

// 计算第一个字节的CRC
crcValue ^= data1;
for (int i = 0; i < 8; i++)
{
if ((crcValue & 0x80) != 0)
{
crcValue = (byte)((crcValue << 1) ^ 0x31);
}
else
{
crcValue = (byte)(crcValue << 1);
}
}

// 计算第二个字节的CRC
crcValue ^= data2;
for (int i = 0; i < 8; i++)
{
if ((crcValue & 0x80) != 0)
{
crcValue = (byte)((crcValue << 1) ^ 0x31);
}
else
{
crcValue = (byte)(crcValue << 1);
}
}

return crcValue == crc;
}

// 释放资源
public void Dispose()
{
if (_disposed) return;

try
{
if (_i2cDevice != null)
{
_i2cDevice.Dispose();
_i2cDevice = null;
}

_disposed = true;
_logger.LogInformation("SHT30 sensor disposed");
}
catch (Exception ex)
{
_logger.LogError($"Error disposing SHT30 sensor: {ex.Message}");
}
}
}
}

Drivers/ButtonDriver.cs
using System;
using System.Device.Gpio;
using System.Threading;
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;

namespace YeFanIoTTest.Drivers
{
// 按钮事件类型
public enum ButtonEventType
{
ShortPress, // 短按(按下并释放)
LongPress // 长按(按住超过指定时间)
}

// 按钮事件参数
public class ButtonEventArgs : EventArgs
{
public ButtonEventType EventType { get; set; }
public int PinNumber { get; set; }
public TimeSpan PressDuration { get; set; }
}

// 按钮事件委托
public delegate void ButtonEventHandler(object sender, ButtonEventArgs e);

// 按钮驱动类(安全稳定版本)
// 支持短按、长按检测,带软件防抖
internal class ButtonDriver : IDisposable
{
// 日志记录器
private readonly ILogger _logger;

// GPIO引脚
private readonly GpioPin _pin;
private readonly bool _activeLow; // 是否低电平有效
private bool _disposed; // 是否已释放资源

// 防抖和长按检测
private Timer _initTimer; // 初始化延迟定时器
private Timer _debounceTimer; // 防抖定时器
private Timer _longPressTimer; // 长按检测定时器
private DateTime _pressStartTime; // 按下时间
private bool _isPressed; // 当前是否按下
private bool _longPressTriggered; // 长按是否已触发
private bool _isInitialized; // 是否已初始化完成(稳定期结束)
private DateTime _initCompleteTime; // 初始化完成时间

// 配置参数
private readonly int _debounceTime; // 防抖时间(毫秒)
private readonly int _longPressTime; // 长按触发时间(毫秒)

// 事件
public event ButtonEventHandler OnButtonEvent;

// 属性
public int PinNumber => _pin.PinNumber;
public bool IsPressed => _activeLow ? _pin.Read() == PinValue.Low
: _pin.Read() == PinValue.High;

// 构造函数
public ButtonDriver(GpioController controller, int pinNumber,
int debounceTime = 20, int longPressTime = 3000,
bool activeLow = true, PinMode pinMode = PinMode.InputPullUp)
{
_logger = LogDispatcher.LoggerFactory.CreateLogger("ButtonDriver");
_activeLow = activeLow;
_debounceTime = debounceTime;
_longPressTime = longPressTime;

// 打开GPIO引脚
_pin = controller.OpenPin(pinNumber, pinMode);

// 延迟订阅事件,等待引脚状态稳定(避免初始化时触发误事件)
_initTimer = new Timer(InitCallback, null, 100, Timeout.Infinite);

_logger.LogInformation($"Button driver initialized on pin {pinNumber}");
}

// 延迟初始化回调(等待引脚状态稳定)
private void InitCallback(object state)
{
if (_disposed) return;

try
{
// 订阅引脚值变化事件
_pin.ValueChanged += OnPinValueChanged;
_initTimer?.Dispose();
_initTimer = null;

// 标记初始化完成时间
_initCompleteTime = DateTime.UtcNow;
_isInitialized = true;

_logger.LogInformation("Button event subscription enabled");
}
catch (Exception ex)
{
_logger.LogError($"Failed to subscribe button event: {ex.Message}");
}
}

// GPIO引脚值变化事件处理(中断回调)
// 【重要】不要在此方法中执行耗时操作,避免阻塞中断
private void OnPinValueChanged(object sender, PinValueChangedEventArgs e)
{
if (_disposed || !_isInitialized) return;

// 判断按下/释放状态
bool isPressed = _activeLow ? (e.ChangeType == PinEventTypes.Falling)
: (e.ChangeType == PinEventTypes.Rising);

// 软件防抖:使用定时器延迟处理
if (_debounceTimer != null)
{
_debounceTimer.Dispose();
_debounceTimer = null;
}

// 防抖延迟后处理
_debounceTimer = new Timer(DebounceCallback, isPressed, _debounceTime, Timeout.Infinite);
}

// 防抖回调(在防抖时间后执行实际逻辑)
private void DebounceCallback(object state)
{
if (_disposed) return;

bool isPressed = (bool)state;

try
{
if (isPressed)
{
// 按钮按下
_isPressed = true;
_longPressTriggered = false;
_pressStartTime = DateTime.UtcNow;

// 启动长按检测定时器
if (_longPressTimer != null)
{
_longPressTimer.Dispose();
}
_longPressTimer = new Timer(LongPressCallback, null, _longPressTime, Timeout.Infinite);

_logger.LogDebug($"Button pressed on pin {PinNumber}");
}
else
{
// 按钮释放
_isPressed = false;

// 停止长按检测定时器
if (_longPressTimer != null)
{
_longPressTimer.Dispose();
_longPressTimer = null;
}

// 如果长按未触发,则触发短按事件
if (!_longPressTriggered)
{
var duration = DateTime.UtcNow - _pressStartTime;

// 合理性检查:过滤异常的持续时间(超过10秒的短按视为异常)
if (duration.TotalMilliseconds > 10000)
{
_logger.LogWarning($"Ignored abnormal button press, duration: {duration.TotalMilliseconds}ms");
return;
}

_logger.LogInformation($"Button short press detected, duration: {duration.TotalMilliseconds}ms");

// 触发短按事件
OnButtonEvent?.Invoke(this, new ButtonEventArgs
{
EventType = ButtonEventType.ShortPress,
PinNumber = PinNumber,
PressDuration = duration
});
}
}
}
catch (Exception ex)
{
_logger.LogError($"Error in button debounce callback: {ex.Message}");
}
}

// 长按检测回调
private void LongPressCallback(object state)
{
if (_disposed || !_isPressed) return;

try
{
_longPressTriggered = true;
var duration = DateTime.UtcNow - _pressStartTime;

_logger.LogInformation($"Button long press detected, duration: {duration.TotalMilliseconds}ms");

// 触发长按事件
OnButtonEvent?.Invoke(this, new ButtonEventArgs
{
EventType = ButtonEventType.LongPress,
PinNumber = PinNumber,
PressDuration = duration
});
}
catch (Exception ex)
{
_logger.LogError($"Error in long press callback: {ex.Message}");
}
}

// 释放资源
public void Dispose()
{
if (_disposed) return;
_disposed = true;

try
{
// 停止所有定时器
_initTimer?.Dispose();
_debounceTimer?.Dispose();
_longPressTimer?.Dispose();

// 取消订阅事件
_pin.ValueChanged -= OnPinValueChanged;

// 释放GPIO引脚
_pin.Dispose();

_logger.LogInformation("Button driver disposed");
}
catch (Exception ex)
{
_logger.LogError($"Error disposing button driver: {ex.Message}");
}
}
}
}

Drivers/RelayDriver.cs
using System;
using System.Device.Gpio;
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using YFSoft.Hardware.YF3300_ESP32S3;

namespace YeFanIoTTest.Drivers
{
// 继电器驱动类
// 支持多路继电器控制,提供开关、切换、状态查询等功能
public class RelayDriver : IDisposable
{
private readonly ILogger _logger;
private readonly GpioController _gpioController;
private readonly GpioPin[] _relayPins; // 继电器引脚数组
private readonly bool[] _relayStates; // 继电器状态数组
private readonly int _relayCount; // 继电器数量
private bool _disposed = false;

// 构造函数:使用默认继电器配置
public RelayDriver(GpioController gpioController)
{
_logger = LogDispatcher.LoggerFactory.CreateLogger("RelayDriver");
_gpioController = gpioController ?? throw new ArgumentNullException(nameof(gpioController));

_relayCount = Mainboard.Relays.Count;
_relayPins = new GpioPin[_relayCount];
_relayStates = new bool[_relayCount];

InitializeRelays();
_logger.LogInformation($"RelayDriver initialized with {_relayCount} relay(s)");
}

// 初始化所有继电器引脚
private void InitializeRelays()
{
for (int i = 0; i < _relayCount; i++)
{
int pinNumber = Mainboard.Relays.Channels[i];
_relayPins[i] = _gpioController.OpenPin(pinNumber, PinMode.Output);
_relayPins[i].Write(PinValue.Low); // 默认关闭
_relayStates[i] = false;
_logger.LogInformation($"Relay {i + 1} initialized on GPIO{pinNumber}, initial state: OFF");
}
}

// 打开指定继电器
// channel: 继电器通道号(从0开始)
public void TurnOn(int channel)
{
if (!IsValidChannel(channel)) return;

_relayPins[channel].Write(PinValue.High);
_relayStates[channel] = true;
_logger.LogInformation($"Relay {channel + 1} turned ON");
}

// 关闭指定继电器
// channel: 继电器通道号(从0开始)
public void TurnOff(int channel)
{
if (!IsValidChannel(channel)) return;

_relayPins[channel].Write(PinValue.Low);
_relayStates[channel] = false;
_logger.LogInformation($"Relay {channel + 1} turned OFF");
}

// 切换指定继电器状态
// channel: 继电器通道号(从0开始)
public void Toggle(int channel)
{
if (!IsValidChannel(channel)) return;

if (_relayStates[channel])
{
TurnOff(channel);
}
else
{
TurnOn(channel);
}
}

// 获取指定继电器状态
// channel: 继电器通道号(从0开始)
// 返回:true=吸合,false=释放
public bool GetState(int channel)
{
if (!IsValidChannel(channel)) return false;
return _relayStates[channel];
}

// 获取所有继电器状态
// 返回:状态数组,true=吸合,false=释放
public bool[] GetAllStates()
{
bool[] states = new bool[_relayCount];
Array.Copy(_relayStates, states, _relayCount);
return states;
}

// 打开所有继电器
public void TurnOnAll()
{
for (int i = 0; i < _relayCount; i++)
{
TurnOn(i);
}
}

// 关闭所有继电器
public void TurnOffAll()
{
for (int i = 0; i < _relayCount; i++)
{
TurnOff(i);
}
}

// 验证通道号是否有效
private bool IsValidChannel(int channel)
{
if (channel < 0 || channel >= _relayCount)
{
_logger.LogWarning($"Invalid relay channel: {channel}, valid range: 0-{_relayCount - 1}");
return false;
}
return true;
}

// 释放资源
public void Dispose()
{
if (_disposed) return;

// 关闭所有继电器
TurnOffAll();

// 释放引脚资源
for (int i = 0; i < _relayCount; i++)
{
if (_relayPins[i] != null)
{
_relayPins[i].Dispose();
_relayPins[i] = null;
}
}

_disposed = true;
_logger.LogInformation("RelayDriver disposed");
}
}
}

Drivers/DigitalInputDriver.cs
using System;
using System.Device.Gpio;
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using YFSoft.Hardware.YF3300_ESP32S3;

namespace YeFanIoTTest.Drivers
{
// 数字输入回调委托 - 只传递基本类型参数
// channel: 通道号(从0开始)
// isTriggered: 是否触发(true=低电平触发,false=高电平未触发)
// pinValue: 引脚电平值(0=低电平,1=高电平)
public delegate void DigitalInputCallback(int channel, bool isTriggered, int pinValue);

// 数字输入驱动类
// 支持多路数字输入检测,使用回调委托代替事件
public class DigitalInputDriver : IDisposable
{
private readonly ILogger _logger;
private readonly GpioController _gpioController;
private readonly GpioPin[] _inputPins; // 输入引脚数组
private readonly int _inputCount; // 输入数量
private readonly DigitalInputCallback _callback; // 回调委托
private bool _disposed = false;

// 构造函数:使用默认数字输入配置
// gpioController: GPIO控制器
// callback: 状态变化回调委托(可选)
public DigitalInputDriver(GpioController gpioController, DigitalInputCallback callback = null)
{
// 构造函数中可以使用Logger
_logger = LogDispatcher.LoggerFactory.CreateLogger("DigitalInputDriver");

_gpioController = gpioController ?? throw new ArgumentNullException(nameof(gpioController));
_callback = callback;

_inputCount = Mainboard.DigitalInputs.Count;
_inputPins = new GpioPin[_inputCount];

InitializeInputs();

_logger.LogInformation($"DigitalInputDriver initialized with {_inputCount} input(s)");
}

// 初始化所有数字输入引脚
private void InitializeInputs()
{
for (int i = 0; i < _inputCount; i++)
{
int pinNumber = Mainboard.DigitalInputs.Channels[i];

// 采用参考项目07的方式:先OpenPin,再SetPinMode
_inputPins[i] = _gpioController.OpenPin(pinNumber);
_inputPins[i].SetPinMode(PinMode.Input);

// 如果提供了回调委托,订阅ValueChanged事件
if (_callback != null)
{
// 捕获变量,避免闭包问题
int channel = i;

// 订阅事件
_inputPins[i].ValueChanged += (sender, e) =>
{
// 事件回调 - 只传递基本类型,不创建对象
OnPinValueChanged(channel);
};
}

_logger.LogInformation($"Digital input {i + 1} initialized on GPIO{pinNumber}");
}
}

// 引脚值变化事件处理
// 【重要】此方法在GPIO中断上下文中执行,必须遵循以下规则:
// 1. 不能创建托管对象(不使用new关键字)
// 2. 不能使用DateTime.UtcNow(会创建对象)
// 3. 只传递基本类型参数
// 4. 只调用简单方法
private void OnPinValueChanged(int channel)
{
if (_disposed) return;

try
{
// 读取引脚状态
PinValue value = _inputPins[channel].Read();
bool isTriggered = (value == PinValue.Low);
int pinValueInt = (int)value;

// 调用回调,只传递基本类型
// 不创建任何对象,不使用DateTime.UtcNow
_callback?.Invoke(channel, isTriggered, pinValueInt);
}
catch
{
// 中断上下文中不能使用Logger,只能忽略错误
}
}

// 读取指定通道状态
// channel: 通道号(从0开始)
// 返回:true=触发(低电平),false=未触发(高电平)
public bool ReadState(int channel)
{
if (channel < 0 || channel >= _inputCount)
{
_logger.LogWarning($"Invalid input channel: {channel}, valid range: 0-{_inputCount - 1}");
return false;
}

PinValue value = _inputPins[channel].Read();
return (value == PinValue.Low);
}

// 读取所有通道状态
// 返回:状态数组,true=触发,false=未触发
public bool[] ReadAllStates()
{
bool[] states = new bool[_inputCount];
for (int i = 0; i < _inputCount; i++)
{
states[i] = ReadState(i);
}
return states;
}

// 释放资源
public void Dispose()
{
if (_disposed) return;

// 释放引脚资源
for (int i = 0; i < _inputCount; i++)
{
if (_inputPins[i] != null)
{
_inputPins[i].Dispose();
_inputPins[i] = null;
}
}

_disposed = true;
_logger.LogInformation("DigitalInputDriver disposed");
}
}
}

Drivers/LedManager.cs
using System;
using System.Device.Gpio;
using System.Threading;
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using YeFanIoTTest.Enums;
using YFSoft.Hardware.YF3300_ESP32S3;

namespace YeFanIoTTest.Drivers
{
// LED管理器 - 管理黄色LED(网络状态)和绿色LED(配网状态)
public class LedManager : IDisposable
{
// 日志记录器
private readonly ILogger _logger;

// GPIO控制器(外部注入)
private readonly GpioController _gpioController;

// LED引脚
private readonly GpioPin _yellowLedPin; // 黄色LED引脚(网络状态指示)
private readonly GpioPin _greenLedPin; // 绿色LED引脚(配网状态指示)

// 闪烁定时器
private Timer _yellowLedTimer; // 黄色LED闪烁定时器
private Timer _greenLedTimer; // 绿色LED闪烁定时器

// 资源释放标志
private bool _disposed;

// 构造函数:注入GPIO控制器
public LedManager(GpioController gpioController)
{
_gpioController = gpioController ?? throw new ArgumentNullException(nameof(gpioController));
_logger = LogDispatcher.LoggerFactory.CreateLogger("LedManager");

// 初始化LED引脚
_yellowLedPin = _gpioController.OpenPin(Mainboard.Pins.YellowLED, PinMode.Output);
_greenLedPin = _gpioController.OpenPin(Mainboard.Pins.GreenLED, PinMode.Output);

// 初始化时关闭所有LED
TurnOffAll();
_logger.LogInformation("LED manager initialized");
}

// 设置网络状态指示(黄色LED)
public void SetNetworkStatus(NetworkStatus status)
{
StopYellowBlink();

switch (status)
{
case NetworkStatus.Connecting:
// 常亮 - 正在连接网络
_logger.LogDebug("Yellow LED: ON - Connecting to network");
_yellowLedPin.Write(PinValue.High);
break;

case NetworkStatus.Connected:
// 慢闪 - 网络正常
_logger.LogDebug("Yellow LED: Slow blink - Network normal");
StartYellowBlink(
Mainboard.LEDTiming.NetworkNormal_On,
Mainboard.LEDTiming.NetworkNormal_Off);
break;

case NetworkStatus.Disconnected:
case NetworkStatus.Error:
// 快闪 - 网络异常
_logger.LogDebug("Yellow LED: Fast blink - Network error");
StartYellowBlink(
Mainboard.LEDTiming.NetworkError_On,
Mainboard.LEDTiming.NetworkError_Off);
break;
}
}

// 设置配网状态指示(绿色LED)
public void SetConfigStatus(ConfigStatus status)
{
StopGreenBlink();

switch (status)
{
case ConfigStatus.Configuring:
// 常亮 - 正在配网
_logger.LogDebug("Green LED: ON - Configuring");
_greenLedPin.Write(PinValue.High);
break;

case ConfigStatus.Success:
// 慢闪 - 配网成功
_logger.LogDebug("Green LED: Slow blink - Config success");
StartGreenBlink(
Mainboard.LEDTiming.ConfigSuccess_On,
Mainboard.LEDTiming.ConfigSuccess_Off);
break;

case ConfigStatus.Failed:
// 快闪 - 配网失败
_logger.LogDebug("Green LED: Fast blink - Config failed");
StartGreenBlink(
Mainboard.LEDTiming.ConfigFailed_On,
Mainboard.LEDTiming.ConfigFailed_Off);
break;

case ConfigStatus.Normal:
// 熄灭 - 正常运行
_logger.LogDebug("Green LED: OFF - Normal operation");
_greenLedPin.Write(PinValue.Low);
break;
}
}

// 启动黄色LED闪烁
private void StartYellowBlink(int onMs, int offMs)
{
StopYellowBlink();
bool isOn = false;
_yellowLedTimer = new Timer(_ =>
{
isOn = !isOn;
_yellowLedPin.Write(isOn ? PinValue.High : PinValue.Low);
}, null, 0, isOn ? onMs : offMs);
}

// 停止黄色LED闪烁
private void StopYellowBlink()
{
if (_yellowLedTimer != null)
{
_yellowLedTimer.Dispose();
_yellowLedTimer = null;
}
}

// 启动绿色LED闪烁
private void StartGreenBlink(int onMs, int offMs)
{
StopGreenBlink();
bool isOn = false;
_greenLedTimer = new Timer(_ =>
{
isOn = !isOn;
_greenLedPin.Write(isOn ? PinValue.High : PinValue.Low);
}, null, 0, isOn ? onMs : offMs);
}

// 停止绿色LED闪烁
private void StopGreenBlink()
{
if (_greenLedTimer != null)
{
_greenLedTimer.Dispose();
_greenLedTimer = null;
}
}

// 关闭所有LED
public void TurnOffAll()
{
_yellowLedPin.Write(PinValue.Low);
_greenLedPin.Write(PinValue.Low);
}

// 释放资源
public void Dispose()
{
if (!_disposed)
{
StopYellowBlink();
StopGreenBlink();
_yellowLedPin?.Dispose();
_greenLedPin?.Dispose();
_disposed = true;
_logger.LogInformation("LED manager disposed");
}
}
}
}

Managers/MqttClientManager.cs
using System;
using System.Collections;
using System.Text;
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using nanoFramework.M2Mqtt;
using nanoFramework.M2Mqtt.Messages;
using YeFanIoTTest.Models;

namespace YeFanIoTTest.Managers
{
// MQTT客户端管理器
// 负责与叶帆物联网平台通信,实现YFLink协议
internal class MqttClientManager
{
private readonly ILogger _logger;
private MqttClient _mqttClient;
private bool _isConnected = false;

// MQTT连接参数
private const string MqttServer = "iot.yfios.net";
private const int MqttPort = 1883;
private const string ProjectId = "YFIoT_TEST";
private const string ProductId = "YF3300_ESP32S3";
private const string DeviceId = "YF3300_ESP32S301";
private const string DeviceKey = "dxR99LCS7Uldc7KUnurFBeBi";

// MQTT主题(V1.3.0 去掉前导"/"以支持共享订阅)
private const string PropertyPostTopic = "{0}/{1}/{2}/property/post"; // 属性上传
private const string EventPostTopic = "{0}/{1}/{2}/event/post"; // 事件上传
private const string ServiceSendTopic = "{0}/{1}/{2}/service/send"; // 服务下发
private const string ServiceResultTopic = "{0}/{1}/{2}/service/result"; // 服务响应

// 事件:收到服务下发
public event ServiceReceivedEventHandler OnServiceReceived;

// 服务下发事件委托
public delegate void ServiceReceivedEventHandler(object sender, ServiceSendRequest request);

// 属性:是否已连接
public bool IsConnected => _isConnected;

// 构造函数
public MqttClientManager()
{
_logger = LogDispatcher.LoggerFactory.CreateLogger("MqttClientManager");
}

// 连接到MQTT服务器
public bool Connect()
{
try
{
_logger.LogInformation("Connecting to MQTT server...");

// 创建MQTT客户端
_mqttClient = new MqttClient(MqttServer, MqttPort, false, null, null, MqttSslProtocols.None);

// 设置回调
_mqttClient.MqttMsgPublishReceived += OnMessageReceived;
_mqttClient.MqttMsgSubscribed += OnSubscribed;
_mqttClient.ConnectionClosed += OnConnectionClosed;

// 连接参数(YFLink协议格式)
// clientId - 项目ID + "-" + 产品ID + "-" + 设备ID
// userName - 项目ID + "&" + 产品ID + "&" + 设备ID
// password - HMACSHA1(DeviceKey, clientId + userName) 转为小写十六进制
string clientId = $"{ProjectId}-{ProductId}-{DeviceId}";
string username = $"{ProjectId}&{ProductId}&{DeviceId}";

// 计算HMACSHA1密码
string content = clientId + username;
string password = CalculateHmacSha1(content, DeviceKey).ToLower();

_logger.LogInformation($"MQTT ClientId: {clientId}");
_logger.LogInformation($"MQTT Username: {username}");
_logger.LogInformation($"MQTT Password: {password}");

// 连接服务器
var result = _mqttClient.Connect(clientId, username, password, false, 60);

if (result == MqttReasonCode.Success)
{
_isConnected = true;
_logger.LogInformation($"MQTT connected successfully, ClientId: {clientId}");

// 订阅服务下发主题
SubscribeServiceTopic();

return true;
}
else
{
_logger.LogError($"MQTT connection failed, reason: {result}");
return false;
}
}
catch (Exception ex)
{
_logger.LogError($"Failed to connect MQTT: {ex.Message}");
return false;
}
}

// 断开连接
public void Disconnect()
{
try
{
if (_mqttClient != null && _mqttClient.IsConnected)
{
_mqttClient.Disconnect();
_logger.LogInformation("MQTT disconnected");
}

_isConnected = false;
}
catch (Exception ex)
{
_logger.LogError($"Failed to disconnect MQTT: {ex.Message}");
}
}

// 上传属性
public bool PublishProperties(Hashtable properties)
{
if (!_isConnected)
{
_logger.LogWarning("MQTT not connected, cannot publish properties");
return false;
}

try
{
// 构建属性上传请求
var request = new PropertyPostRequest
{
id = GenerateMessageId(),
timestamp = GetCurrentTimestamp(),
parameters = properties
};

// 序列化为JSON
string json = SerializeToJson(request);

// 发布消息
string topic = string.Format(PropertyPostTopic, ProjectId, ProductId, DeviceId);
_mqttClient.Publish(topic, Encoding.UTF8.GetBytes(json), null, null, MqttQoSLevel.AtLeastOnce, false);

_logger.LogInformation($"Properties published: {json}");
return true;
}
catch (Exception ex)
{
_logger.LogError($"Failed to publish properties: {ex.Message}");
return false;
}
}

// 上传事件
public bool PublishEvent(int eventType, int eventCode, string content)
{
if (!_isConnected)
{
_logger.LogWarning("MQTT not connected, cannot publish event");
return false;
}

try
{
// 构建事件数据
var eventData = new EventData
{
type = eventType,
code = eventCode,
content = content,
time = GetCurrentTimestamp()
};

// 构建事件上传请求
var request = new EventPostRequest
{
id = GenerateMessageId(),
timestamp = GetCurrentTimestamp(),
parameters = new ArrayList { eventData }
};

// 序列化为JSON
string json = SerializeToJson(request);

// 发布消息
string topic = string.Format(EventPostTopic, ProjectId, ProductId, DeviceId);
_mqttClient.Publish(topic, Encoding.UTF8.GetBytes(json), null, null, MqttQoSLevel.AtLeastOnce, false);

_logger.LogInformation($"Event published: {json}");
return true;
}
catch (Exception ex)
{
_logger.LogError($"Failed to publish event: {ex.Message}");
return false;
}
}

// 订阅服务下发主题
private void SubscribeServiceTopic()
{
try
{
string topic = string.Format(ServiceSendTopic, ProjectId, ProductId, DeviceId);
_mqttClient.Subscribe(new string[] { topic }, new MqttQoSLevel[] { MqttQoSLevel.AtLeastOnce });
_logger.LogInformation($"Subscribed to service topic: {topic}");
}
catch (Exception ex)
{
_logger.LogError($"Failed to subscribe service topic: {ex.Message}");
}
}

// 消息接收回调
private void OnMessageReceived(object sender, MqttMsgPublishEventArgs e)
{
try
{
string topic = e.Topic;
string message = Encoding.UTF8.GetString(e.Message, 0, e.Message.Length);

_logger.LogInformation($"Message received, Topic: {topic}, Message: {message}");

// 检查是否为服务下发主题
string serviceTopic = string.Format(ServiceSendTopic, ProjectId, ProductId, DeviceId);
if (topic == serviceTopic)
{
// 解析服务下发请求
var request = DeserializeFromJson<ServiceSendRequest>(message);
if (request != null)
{
// 触发事件
OnServiceReceived?.Invoke(this, request);
}
}
}
catch (Exception ex)
{
_logger.LogError($"Error processing received message: {ex.Message}");
}
}

// 订阅成功回调
private void OnSubscribed(object sender, MqttMsgSubscribedEventArgs e)
{
_logger.LogInformation($"Subscribed successfully, MessageId: {e.MessageId}");
}

// 连接关闭回调
private void OnConnectionClosed(object sender, EventArgs e)
{
_isConnected = false;
_logger.LogWarning("MQTT connection closed");
}

// 生成消息ID
private int GenerateMessageId()
{
var random = new Random();
return random.Next();
}

// 获取当前时间戳(毫秒)
private long GetCurrentTimestamp()
{
return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds;
}

// 序列化为JSON(YFLink协议格式)
private string SerializeToJson(object obj)
{
// YFLink属性上传格式:
// {
// "id": 1234578,
// "timestamp": xxxxx,
// "params": {
// "H": 36.2,
// "T": 29.3,
// "I1": 0,
// "I2": 0,
// "Q1": 0
// }
// }
if (obj is PropertyPostRequest propReq)
{
var sb = new StringBuilder();
sb.Append("{");
sb.Append($"\"id\":{propReq.id},");
sb.Append($"\"timestamp\":{propReq.timestamp},");
sb.Append("\"params\":{");

bool first = true;
foreach (DictionaryEntry entry in propReq.parameters)
{
if (!first) sb.Append(",");
// 根据值类型决定是否需要引号和格式
if (entry.Value is string)
{
sb.Append($"\"{entry.Key}\":\"{entry.Value}\"");
}
else if (entry.Value is double)
{
// 对double类型保留一位小数
double value = (double)entry.Value;
sb.Append($"\"{entry.Key}\":{value:F1}");
}
else
{
sb.Append($"\"{entry.Key}\":{entry.Value}");
}
first = false;
}

sb.Append("}}");
return sb.ToString();
}

return "{}";
}

// 从JSON反序列化(简化版)
private T DeserializeFromJson<T>(string json) where T : class
{
// 这里使用简化的JSON反序列化
// 实际项目中应使用nanoFramework.Json库
return null;
}

// 计算HMACSHA1(YFLink协议要求的密码计算方法)
// content: ClientID + UserName
// key: DeviceKey
// 注意:nanoFramework只支持HMACSHA256 和 HMACSHA512,这里使用自定义HMACSHA1实现
private string CalculateHmacSha1(string content, string key)
{
try
{
// 将key和content转换为字节数组
byte[] keyBytes = Encoding.UTF8.GetBytes(key);
byte[] contentBytes = Encoding.UTF8.GetBytes(content);

// 使用自定义HMACSHA1实现
byte[] hashBytes = HmacSha1(keyBytes, contentBytes);

// 转换为十六进制字符串
return BytesToHexString(hashBytes);
}
catch (Exception ex)
{
_logger.LogError($"HMACSHA1 calculation failed: {ex.Message}");
return string.Empty;
}
}

// 自定义HMACSHA1实现(因为nanoFramework不支持HMACSHA1)
private byte[] HmacSha1(byte[] key, byte[] message)
{
// HMAC算法步骤:
// 1. 如果key长度大于64字节,先对key进行SHA1哈希
// 2. 如果key长度小于64字节,用0填充到64字节
// 3. 计算inner padding: key XOR 0x36
// 4. 计算outer padding: key XOR 0x5C
// 5. 计算hash = SHA1(outer_padding + SHA1(inner_padding + message))

const int blockSize = 64; // SHA1的块大小是64字节

// 处理key
byte[] normalizedKey = new byte[blockSize];
if (key.Length > blockSize)
{
// Key太长,先进行SHA1哈希
byte[] keyHash = Sha1(key);
Array.Copy(keyHash, normalizedKey, keyHash.Length);
}
else
{
Array.Copy(key, normalizedKey, key.Length);
}

// 创建inner padding (key XOR 0x36)
byte[] innerPadding = new byte[blockSize];
for (int i = 0; i < blockSize; i++)
{
innerPadding[i] = (byte)(normalizedKey[i] ^ 0x36);
}

// 创建outer padding (key XOR 0x5C)
byte[] outerPadding = new byte[blockSize];
for (int i = 0; i < blockSize; i++)
{
outerPadding[i] = (byte)(normalizedKey[i] ^ 0x5C);
}

// 计算inner hash: SHA1(inner_padding + message)
byte[] innerData = new byte[blockSize + message.Length];
Array.Copy(innerPadding, innerData, blockSize);
Array.Copy(message, 0, innerData, blockSize, message.Length);
byte[] innerHash = Sha1(innerData);

// 计算outer hash: SHA1(outer_padding + inner_hash)
byte[] outerData = new byte[blockSize + innerHash.Length];
Array.Copy(outerPadding, outerData, blockSize);
Array.Copy(innerHash, 0, outerData, blockSize, innerHash.Length);
byte[] outerHash = Sha1(outerData);

return outerHash;
}

// 自定义SHA1实现
private byte[] Sha1(byte[] data)
{
// SHA1算法实现
// 初始化哈希值
uint h0 = 0x67452301;
uint h1 = 0xEFCDAB89;
uint h2 = 0x98BADCFE;
uint h3 = 0x10325476;
uint h4 = 0xC3D2E1F0;

// 预处理:填充数据
int originalLength = data.Length;
int paddedLength = ((originalLength + 8) / 64 + 1) * 64;
byte[] paddedData = new byte[paddedLength];
Array.Copy(data, paddedData, originalLength);
paddedData[originalLength] = 0x80; // 添加1后面跟着0

// 添加原始长度(位)
ulong bitLength = (ulong)originalLength * 8;
for (int i = 0; i < 8; i++)
{
paddedData[paddedLength - 8 + i] = (byte)(bitLength >> (56 - i * 8));
}

// 处理每个512位(64字节)块
for (int blockStart = 0; blockStart < paddedLength; blockStart += 64)
{
// 将块分成16个32位字
uint[] w = new uint[80];
for (int i = 0; i < 16; i++)
{
w[i] = (uint)(paddedData[blockStart + i * 4] << 24 |
paddedData[blockStart + i * 4 + 1] << 16 |
paddedData[blockStart + i * 4 + 2] << 8 |
paddedData[blockStart + i * 4 + 3]);
}

// 扩展为80个字
for (int i = 16; i < 80; i++)
{
w[i] = LeftRotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
}

// 初始化工作变量
uint a = h0;
uint b = h1;
uint c = h2;
uint d = h3;
uint e = h4;

// 主循环
for (int i = 0; i < 80; i++)
{
uint f, k;
if (i < 20)
{
f = (b & c) | ((~b) & d);
k = 0x5A827999;
}
else if (i < 40)
{
f = b ^ c ^ d;
k = 0x6ED9EBA1;
}
else if (i < 60)
{
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
}
else
{
f = b ^ c ^ d;
k = 0xCA62C1D6;
}

uint temp = LeftRotate(a, 5) + f + e + k + w[i];
e = d;
d = c;
c = LeftRotate(b, 30);
b = a;
a = temp;
}

// 添加工作变量到哈希值
h0 += a;
h1 += b;
h2 += c;
h3 += d;
h4 += e;
}

// 生成最终哈希值(20字节)
byte[] hash = new byte[20];
hash[0] = (byte)(h0 >> 24);
hash[1] = (byte)(h0 >> 16);
hash[2] = (byte)(h0 >> 8);
hash[3] = (byte)h0;
hash[4] = (byte)(h1 >> 24);
hash[5] = (byte)(h1 >> 16);
hash[6] = (byte)(h1 >> 8);
hash[7] = (byte)h1;
hash[8] = (byte)(h2 >> 24);
hash[9] = (byte)(h2 >> 16);
hash[10] = (byte)(h2 >> 8);
hash[11] = (byte)h2;
hash[12] = (byte)(h3 >> 24);
hash[13] = (byte)(h3 >> 16);
hash[14] = (byte)(h3 >> 8);
hash[15] = (byte)h3;
hash[16] = (byte)(h4 >> 24);
hash[17] = (byte)(h4 >> 16);
hash[18] = (byte)(h4 >> 8);
hash[19] = (byte)h4;

return hash;
}

// 32位左循环移位
private uint LeftRotate(uint value, int bits)
{
return (value << bits) | (value >> (32 - bits));
}

// 字节数组转十六进制字符串
private string BytesToHexString(byte[] bytes)
{
var sb = new StringBuilder();
foreach (byte b in bytes)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
}
}

Managers/WifiManager.cs
using System;
using System.Device.Wifi;
using System.Net;
using System.Net.NetworkInformation;
using System.Threading;
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using nanoFramework.Networking;
using Iot.Device.DhcpServer;

namespace YeFanIoTTest.Managers
{
public enum WifiState
{
Idle, // 空闲状态
Connecting, // 连接中
Connected, // 已连接
Reconnecting, // 重连中
APMode // AP模式
}

// internal:仅本程序集可见,封装内部实现
// 不对外暴露具体实现细节,保证API稳定性
internal class WifiManager
{
// 日志记录器
private ILogger _logger;

// WIFI硬件适配器(系统唯一)
private WifiAdapter _wifiAdapter;

// 当前连接的网络凭证
private string _currentSSID = null;
private string _currentPassword = null;

// ========== 状态机与同步工具 ==========
// 当前WIFI状态(线程安全的状态机)
public WifiState CurrentState { get; private set; } = WifiState.Idle;

// 是否已连接(封装IsSTAConnected方法)
public bool IsConnected => IsSTAConnected();

// 云端通信唤醒事件(连接成功时通知上云线程)
public AutoResetEvent CloudWakeUpEvent { get; private set; }

// 重连任务取消令牌(用于安全终止重连线程)
private CancellationTokenSource _reconnectCts;

// 网络状态防抖定时器(避免频繁触发重连)
private Timer _debounceTimer;

// DHCP服务器实例(AP模式下使用)
private DhcpServer _dhcpServer;

// 构造函数:初始化WIFI管理器
// 1. 创建日志记录器
// 2. 初始化同步事件
// 3. 获取系统WIFI适配器
public WifiManager()
{
// 初始化云端唤醒事件(非信号状态)
CloudWakeUpEvent = new AutoResetEvent(false);

// 获取系统中的第一个WIFI适配器
var adapters = WifiAdapter.FindAllAdapters();
if (adapters == null || adapters.Length == 0)
{
throw new Exception("未找到 WIFI 硬件适配器");
}
_wifiAdapter = adapters[0];

// 创建日志记录器(放在最后,因为需要确保其他初始化完成)
try
{
_logger = LogDispatcher.LoggerFactory.CreateLogger("WifiManager");
}
catch
{
// 如果日志创建失败,不影响程序运行
//Debug.WriteLine("WifiManager: 日志记录器创建失败");
_logger = null;
return;
}
}

#region STA模式连接

// 连接到指定的WIFI网络(STA模式)
public bool ConnectSTA(string ssid, string password, bool enableReconnect = true)
{
// 保存连接凭证
_currentSSID = ssid;
_currentPassword = password;

// ========== 连接前检查 ==========
// 检查是否已经连接(避免重复连接)
if (IsSTAConnected())
{
_logger.LogInformation($"[WifiManager] 已连接到网络,无需重复连接");
CurrentState = WifiState.Connected;

if (enableReconnect)
{
RegisterNetworkEvents();
}
return true;
}

// 更新状态为连接中
CurrentState = WifiState.Connecting;

// 首次连接,超时15秒
var firstTryCts = new CancellationTokenSource(15000);
bool connectResult = ConnectWithHelper(ssid, password, firstTryCts.Token);

// 根据连接结果更新状态
CurrentState = connectResult ? WifiState.Connected : WifiState.Reconnecting;

// 如果启用自动重连
if (enableReconnect)
{
// 注册网络状态变化事件
RegisterNetworkEvents();

// 如果首次连接失败,立即启动重连任务
if (!connectResult)
{
StartReconnectTask();
}
}

return connectResult;
}

// 重新连接WiFi
public bool Reconnect()
{
if (string.IsNullOrEmpty(_currentSSID) || string.IsNullOrEmpty(_currentPassword))
{
_logger.LogWarning("[WifiManager] No WiFi credentials saved, cannot reconnect");
return false;
}

_logger.LogInformation($"[WifiManager] Reconnecting to {_currentSSID}...");

// 使用保存的凭证重新连接
return ConnectSTA(_currentSSID, _currentPassword, enableReconnect: true);
}

// 获取IP地址
public string GetIPAddress()
{
try
{
foreach (var nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
{
string ip = nic.IPv4Address.ToString();
if (ip != "0.0.0.0" && ip != "127.0.0.1")
{
return ip;
}
}
}
}
catch (Exception ex)
{
_logger?.LogError($"[WifiManager] Failed to get IP address: {ex.Message}");
}

return "0.0.0.0";
}

// 注册网络状态变化事件(事件驱动)
private void RegisterNetworkEvents()
{
// 订阅网络地址变化事件
NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged;

// 初始化防抖定时器(3秒防抖窗口)
if (_debounceTimer == null)
{
_debounceTimer = new Timer(DebounceAction, null, Timeout.Infinite, Timeout.Infinite);
}

_logger.LogInformation("[WifiManager] 已注册防抖网络事件");
}

// 取消注册网络事件
public void UnregisterNetworkEvents()
{
NetworkChange.NetworkAddressChanged -= NetworkChange_NetworkAddressChanged;
_logger.LogInformation("[WifiManager] 已取消网络事件");
}
#endregion

#region 防抖核心逻辑
// 网络状态变化事件处理函数(防抖入口)
// 当检测到网络波动时,重置防抖计时器
private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
{
// AP模式下忽略网络事件
if (CurrentState == WifiState.APMode)
{
return;
}

_logger.LogInformation("[WifiManager] 检测到波动,重置3秒计时...");

// 重置防抖定时器,3秒后执行实际检测
// 如果3秒内再次触发事件,计时器将被重新设置
if (_debounceTimer != null)
{
_debounceTimer.Change(3000, Timeout.Infinite);
}
}

// 防抖动作 - 防抖窗口结束后执行的实际检测逻辑
private void DebounceAction(object state)
{
// 获取实际连接状态
bool actuallyConnected = IsSTAConnected();

// 状态流转判断
// 场景1:已连接 -> 断开
if (CurrentState == WifiState.Connected && !actuallyConnected)
{
CurrentState = WifiState.Reconnecting;
_logger.LogWarning("[WifiManager] 确认断开,触发重连...");
StartReconnectTask();
}
// 场景2:连接中/重连中 -> 已连接
else if ((CurrentState == WifiState.Reconnecting || CurrentState == WifiState.Connecting) && actuallyConnected)
{
CurrentState = WifiState.Connected;
_logger.LogInformation("[WifiManager] 确认恢复!唤醒上云线程...");
CloudWakeUpEvent.Set(); // 唤醒等待中的云端通信线程
}
}
#endregion

#region 安全重连核心逻辑
// 启动安全重连任务
// 取消之前的重连任务(如果存在),启动新的重连任务
private void StartReconnectTask()
{
// 取消之前的重连任务(如果正在运行)
if (_reconnectCts != null)
{
_reconnectCts.Cancel();
}

// 创建新的取消令牌源
_reconnectCts = new CancellationTokenSource();

// 在新线程中启动重连任务(nanoFramework不支持ThreadPool和IsBackground)
Thread reconnectThread = new Thread(() => AttemptReconnect(_reconnectCts.Token));
reconnectThread.Start();
}

// 执行重连尝试(带指数退避策略)
private void AttemptReconnect(CancellationToken ct)
{
int retryCount = 0; // 重试次数
const int maxRetryDelay = 60000; // 最大重试延迟(60秒)

// 循环重连,直到连接成功或被取消
while (CurrentState == WifiState.Reconnecting)
{
// 检查是否被取消
if (ct.IsCancellationRequested)
{
_logger.LogInformation("[WifiManager] 重连任务已被取消");
break;
}

// 尝试连接
if (ConnectWithHelper(_currentSSID, _currentPassword, ct))
{
// 连接成功,更新状态并退出循环
CurrentState = WifiState.Connected;
_logger.LogInformation("[WifiManager] 重连成功!");
CloudWakeUpEvent.Set(); // 唤醒云端通信线程
break;
}

// 计算指数退避延迟:2^n * 2000ms,最大60秒
int delay = Math.Min((int)Math.Pow(2, retryCount) * 2000, maxRetryDelay);

_logger.LogWarning($"[WifiManager] 第 {retryCount + 1} 次失败,等待 {delay / 1000} 秒后重试...");

// 分段等待,每500ms检查一次取消信号
for (int elapsed = 0; elapsed < delay; elapsed += 500)
{
if (ct.IsCancellationRequested)
{
_logger.LogInformation("[WifiManager] 等待期间收到取消信号");
return;
}
Thread.Sleep(500);
}

retryCount++;
}
}
#endregion

#region 底层连接辅助
// 底层连接辅助函数 使用 nanoFramework 内置的 WifiNetworkHelper 进行连接
private bool ConnectWithHelper(string ssid, string password, CancellationToken ct)
{
try
{
// 使用内置助手类连接,禁用日期时间检查(嵌入式设备可能没有RTC)
bool success = WifiNetworkHelper.ConnectDhcp(
ssid,
password,
token: ct // 取消令牌
);

if (success)
{
// 获取并记录分配的IP地址
var ip = NetworkInterface.GetAllNetworkInterfaces()[0].IPv4Address;
_logger.LogInformation($"[WifiManager] 连接成功,IP: {ip}");
}

return success;
}
catch (OperationCanceledException)
{
// 连接被取消
_logger.LogInformation("[WifiManager] 连接操作已取消");
return false;
}
catch (Exception ex)
{
// ConnectDhcp可能在物理连接成功后因DNS/NTP检查失败而抛异常
// 需要检查实际连接状态
_logger.LogWarning($"[WifiManager] ConnectDhcp异常: {ex.Message}");

// 检查实际连接状态
if (IsSTAConnected())
{
var ip = NetworkInterface.GetAllNetworkInterfaces()[0].IPv4Address;
_logger.LogInformation($"[WifiManager] 实际已连接,IP: {ip}");
return true;
}

return false;
}
}

// 检查STA模式是否已连接(带网络可达性检测)
// 判断条件:
// 1. 无线STA接口 + 非零IP + 接口状态正常(基本连接)
// 2. DNS解析成功(网络可达性,确保NTP/MQTT能正常工作)
private bool IsSTAConnected()
{
try
{
string deviceIp = null;

// 步骤1:检查基本连接状态
foreach (var nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
{
string ip = nic.IPv4Address.ToString();
// nanoFramework中NetworkInterface没有OperationalStatus属性
// 仅通过IP地址判断连接状态
bool hasValidIP = (ip != "0.0.0.0" && ip != "127.0.0.1");

if (hasValidIP)
{
deviceIp = ip;
break;
}
}
}

// 如果基本连接都不满足,直接返回false
if (string.IsNullOrEmpty(deviceIp))
{
return false;
}

// 步骤2:网络可达性检测(DNS解析)
// 通过解析域名验证网络是否真正可达
// 这确保后续NTP校时、MQTT等网络操作能正常工作
if (IsNetworkReachable())
{
_logger.LogInformation($"[WifiManager] STA已连接,IP: {deviceIp},网络可达");
return true;
}
else
{
_logger.LogWarning($"[WifiManager] STA有IP但网络不可达");
return false;
}
}
catch (Exception ex)
{
_logger.LogWarning($"[WifiManager] 检查连接状态异常: {ex.Message}");
}
return false;
}

// 网络可达性检测(通过DNS解析验证)
// 返回true表示网络可达,可以进行NTP校时、MQTT等操作
public bool IsNetworkReachable()
{
try
{
// 尝试解析公共DNS域名
// 使用多个域名提高可靠性
string[] testDomains = { "www.baidu.com", "time.windows.com", "pool.ntp.org" , "ntp.ntsc.ac.cn", "ntp.aliyun.com" };

foreach (string domain in testDomains)
{
try
{
_logger.LogInformation($"[WifiManager] 正在检测网络可达性: {domain}");
var hostEntry = System.Net.Dns.GetHostEntry(domain);

if (hostEntry != null && hostEntry.AddressList != null && hostEntry.AddressList.Length > 0)
{
_logger.LogInformation($"[WifiManager] DNS解析成功: {domain} -> {hostEntry.AddressList[0]}");
return true;
}
}
catch { /* 继续尝试下一个域名 */ }
}

_logger.LogWarning("[WifiManager] 所有域名解析失败,网络不可达");
return false;
}
catch (Exception ex)
{
_logger.LogWarning($"[WifiManager] 网络可达性检测异常: {ex.Message}");
return false;
}
}
#endregion

#region AP模式(热点模式)
// 启动AP热点模式(参考官方WifiAP示例实现)
// 【配置AP必需的7个关键步骤】:
// 1. 取消STA相关任务和事件(STA/AP互斥)
// 2. 彻底关闭STA模式(使用Wireless80211Configuration.Options = None | SmartConfig)
// 3. 查找WirelessAP网络接口
// 4. 配置静态IP地址
// 5. 通过SpecificConfigId获取正确的配置槽
// 6. 设置AP参数(SSID/密码/认证类型/Options)
// 7. 启动DHCP服务器(关键!否则AP不会广播SSID)
// 热点密码(WPA2模式下最少8位)
// AP静态IP地址(默认192.168.4.1)
public bool StartAP(string ssid, string password, string ipAddress = "192.168.4.1")
{
_logger.LogInformation($"[WifiManager] AP准备启动...");

// ========== 步骤1:互斥保护 - 取消STA相关任务 ==========
// STA模式与AP模式互斥,必须先清理STA相关资源
if (_reconnectCts != null) _reconnectCts.Cancel();
Thread.Sleep(200); // 等待任务取消完成
UnregisterNetworkEvents(); // 取消网络状态监听
CurrentState = WifiState.APMode; // 更新状态机

// ========== 步骤2:彻底关闭STA模式(关键!否则射频冲突) ==========
// 参考官方Wireless80211.Disable()实现
// 使用 None | SmartConfig 确保STA完全禁用
DisableSTAMode();
Thread.Sleep(300); // 等待射频切换完成

// ========== 步骤3:查找WirelessAP网络接口 ==========
NetworkInterface apInterface = null;
foreach (var nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.NetworkInterfaceType == NetworkInterfaceType.WirelessAP)
{
apInterface = nic;
break;
}
}
if (apInterface == null)
{
_logger.LogError("[WifiManager] 未找到AP网络接口");
return false;
}

// ========== 步骤4:配置AP静态IP地址 ==========
// 参数:IP地址、子网掩码、网关(AP自身作为网关)
apInterface.EnableStaticIPv4(ipAddress, "255.255.255.0", ipAddress);
_logger.LogInformation($"[WifiManager] AP接口IP配置完成: {ipAddress}");

// 【关键】等待网络接口完全初始化
// 配置IP后需要等待一段时间,否则后续Socket操作会失败
Thread.Sleep(1000);

// ========== 步骤5:获取AP配置对象(关键!使用SpecificConfigId) ==========
// 【重要】必须通过接口的SpecificConfigId获取配置对象
// 直接new WirelessAPConfiguration(1)可能修改错误的配置槽
WirelessAPConfiguration apConfig = null;
try
{
WirelessAPConfiguration[] apConfigs = WirelessAPConfiguration.GetAllWirelessAPConfigurations();
if (apConfigs != null && apConfigs.Length > 0)
{
uint configIdUInt = apInterface.SpecificConfigId;
int configId = (int)configIdUInt;
apConfig = (configId >= 0 && configId < apConfigs.Length) ? apConfigs[configId] : apConfigs[0];
}
}
catch (Exception ex)
{
_logger.LogWarning($"[WifiManager] 获取AP配置失败: {ex.Message}");
}
if (apConfig == null) apConfig = new WirelessAPConfiguration(0);

// ========== 步骤6:配置AP热点参数 ==========
apConfig.Ssid = ssid; // 热点名称
apConfig.Password = password; // 热点密码(WPA2需≥8位)
apConfig.Authentication = System.Net.NetworkInformation.AuthenticationType.WPA2; // 认证类型:WPA2
apConfig.Encryption = EncryptionType.WPA2_PSK; // 加密类型:WPA2-PSK
apConfig.Channel = 6; // WIFI信道(信道6兼容性最好)
apConfig.MaxConnections = 8; // 最大连接数
// 【关键】Options必须同时包含AutoStart和Enable
// AutoStart: 配置保存后立即启动AP
// Enable: 启用AP模式
apConfig.Options = WirelessAPConfiguration.ConfigurationOptions.AutoStart
| WirelessAPConfiguration.ConfigurationOptions.Enable;

// 保存配置到Flash
apConfig.SaveConfiguration();
_logger.LogInformation($"[WifiManager] AP配置已保存,SSID: {ssid}");

// ========== 步骤7:(关键)启动DHCP服务器 ==========
// DHCP服务器为连接的设备分配IP地址
// 同时配置Captive Portal URL、DNS服务器和网关
try
{
_logger.LogInformation("[WifiManager] 正在启动DHCP服务器...");
_dhcpServer = new DhcpServer
{
CaptivePortalUrl = "http://" + ipAddress,
DnsServer = IPAddress.Parse(ipAddress),
Gateway = IPAddress.Parse(ipAddress)
};
bool dhcpStarted = _dhcpServer.Start(IPAddress.Parse(ipAddress), new IPAddress(new byte[] { 255, 255, 255, 0 }));
if (dhcpStarted)
{
_logger.LogInformation("[WifiManager] DHCP服务器启动成功");
}
else
{
_logger.LogError("[WifiManager] DHCP服务器启动失败!");
_logger.LogError("[WifiManager] 可能原因:网络接口未就绪或端口被占用");
}
}
catch (Exception ex)
{
_logger.LogError($"[WifiManager] 启动DHCP服务器异常: {ex.Message}");
_logger.LogError($"[WifiManager] 异常类型: {ex.GetType().Name}");
}

// 等待AP启动(增加等待时间确保射频模块初始化完成)
Thread.Sleep(2000);

// 验证AP是否真正启动
bool apStarted = VerifyAPStarted(apInterface, ipAddress);
if (!apStarted)
{
// nanoFramework.Runtime.Native.Power.RebootDevice();
_logger.LogWarning("[WifiManager] AP配置已保存,但射频可能未立即启动");
_logger.LogWarning("[WifiManager] 建议重启设备以确保AP完全生效");
// 不强制重启,让用户决定是否重启
}

_logger.LogInformation($"[WifiManager] AP启动成功,IP: {ipAddress}");
return true;

// 验证AP是否真正启动
bool VerifyAPStarted(NetworkInterface ni, string expectedIP)
{
try
{
// 检查IP是否配置正确
if (ni.IPv4Address != expectedIP)
{
_logger.LogWarning($"[WifiManager] AP IP不匹配: 期望 {expectedIP}, 实际 {ni.IPv4Address}");
return false;
}

// 获取当前AP配置检查Options
var currentConfigs = WirelessAPConfiguration.GetAllWirelessAPConfigurations();
if (currentConfigs != null && currentConfigs.Length > 0)
{
uint configId = ni.SpecificConfigId;
if (configId < currentConfigs.Length)
{
var currentConfig = currentConfigs[configId];
bool hasAutoStart = (currentConfig.Options & WirelessAPConfiguration.ConfigurationOptions.AutoStart) != 0;
bool hasEnable = (currentConfig.Options & WirelessAPConfiguration.ConfigurationOptions.Enable) != 0;

if (!hasAutoStart || !hasEnable)
{
_logger.LogWarning("[WifiManager] AP Options未正确设置");
return false;
}
}
}
return true;
}
catch (Exception ex)
{
_logger.LogWarning($"[WifiManager] 验证AP状态失败: {ex.Message}");
return false;
}
}
}

// 禁用STA模式(参考官方示例Wireless80211.Disable())
// 使用System.Net.NetworkInformation中的Wireless80211Configuration
private void DisableSTAMode()
{
try
{
// 获取STA网络接口
NetworkInterface staInterface = null;
foreach (var nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
{
staInterface = nic;
break;
}
}

if (staInterface != null)
{
// 通过接口的SpecificConfigId获取STA配置(与官方一致)
var staConfigs = Wireless80211Configuration.GetAllWireless80211Configurations();
if (staConfigs != null && staConfigs.Length > 0)
{
uint configId = staInterface.SpecificConfigId;
Wireless80211Configuration staConfig = staConfigs[configId];

// 禁用STA模式(与官方一致:None | SmartConfig)
staConfig.Options = Wireless80211Configuration.ConfigurationOptions.None |
Wireless80211Configuration.ConfigurationOptions.SmartConfig;
staConfig.SaveConfiguration();
_logger.LogInformation("[WifiManager] STA模式已禁用");
}
}
}
catch (Exception ex)
{
_logger.LogWarning($"[WifiManager] 禁用STA模式失败: {ex.Message}");
// 降级方案:断开连接
try { _wifiAdapter.Disconnect(); } catch { }
}
}

// 关闭AP热点模式(参考官方示例WirelessAP.Disable())
public void StopAP()
{
// 获取当前AP配置
var configs = WirelessAPConfiguration.GetAllWirelessAPConfigurations();

if (configs != null && configs.Length > 0)
{
// 禁用AP模式(与官方一致:使用None而非Disable)
configs[0].Options = WirelessAPConfiguration.ConfigurationOptions.None;
// 保存配置以应用更改
configs[0].SaveConfiguration();
}

// 更新状态为空闲
CurrentState = WifiState.Idle;
_logger.LogInformation("[WifiManager] AP 已关闭");
}
#endregion

#region 断开连接
// 断开STA连接并安全地取消所有相关任务
public void DisconnectSTA()
{
// 取消重连任务
if (_reconnectCts != null)
{
_reconnectCts.Cancel();
}

// 取消注册网络事件
UnregisterNetworkEvents();

// 断开WIFI连接
_wifiAdapter.Disconnect();

// 更新状态为空闲
CurrentState = WifiState.Idle;

_logger.LogInformation("[WifiManager] STA 已断开连接");
}
#endregion

#region WiFi凭证存储(官方方式)

// 获取无线网络接口(私有方法)
private NetworkInterface GetWirelessInterface()
{
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var iface in interfaces)
{
if (iface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
{
return iface;
}
}
return null;
}

// 保存WiFi凭证到Flash(使用官方Wireless80211Configuration)
// 官方推荐方式:WiFi配置存储在设备的Configuration分区,重启后固件自动连接
public bool SaveCredentials()
{
try
{
// 检查当前是否有凭证
if (string.IsNullOrEmpty(_currentSSID))
{
_logger.LogWarning("[WifiManager] 没有WiFi凭证可保存");
return false;
}

// 获取无线网络接口
var iface = GetWirelessInterface();
if (iface == null)
{
_logger.LogError("[WifiManager] 未找到无线网络接口");
return false;
}

// 获取WiFi配置
var configs = Wireless80211Configuration.GetAllWireless80211Configurations();
var config = configs[iface.SpecificConfigId];

// 设置WiFi参数
config.Ssid = _currentSSID;
config.Password = _currentPassword;
config.Authentication = System.Net.NetworkInformation.AuthenticationType.WPA2;
config.Encryption = EncryptionType.WPA2_PSK;
config.Options = Wireless80211Configuration.ConfigurationOptions.Enable;

// 保存配置(重启后固件会自动连接)
config.SaveConfiguration();
_logger.LogInformation($"[WifiManager] WiFi凭证已保存: {_currentSSID}");
return true;
}
catch (Exception ex)
{
_logger.LogError($"[WifiManager] 保存凭证失败: {ex.Message}");
return false;
}
}

// 保存指定的WiFi凭证到Flash
// 参数:ssid - WiFi名称,password - WiFi密码
public bool SaveCredentials(string ssid, string password)
{
// 更新当前凭证
_currentSSID = ssid;
_currentPassword = password;

// 调用无参版本保存
return SaveCredentials();
}

// 从Flash读取WiFi凭证(使用官方Wireless80211Configuration)
// 返回:true表示读取成功,false表示读取失败或无配置
public bool LoadCredentials(out string ssid, out string password)
{
ssid = null;
password = null;

try
{
// 获取所有WiFi配置
var configs = Wireless80211Configuration.GetAllWireless80211Configurations();

foreach (var config in configs)
{
// 查找第一个有效的配置
if (!string.IsNullOrEmpty(config.Ssid))
{
ssid = config.Ssid;
password = config.Password;

// 更新当前凭证
_currentSSID = ssid;
_currentPassword = password;

_logger.LogInformation($"[WifiManager] 读取凭证成功: {ssid}");
return true;
}
}

_logger.LogInformation("[WifiManager] 未找到已保存的WiFi配置");
return false;
}
catch (Exception ex)
{
_logger.LogError($"[WifiManager] 读取凭证失败: {ex.Message}");
return false;
}
}

// 检查是否有已保存的WiFi配置
public bool HasSavedConfig()
{
try
{
var configs = Wireless80211Configuration.GetAllWireless80211Configurations();
foreach (var config in configs)
{
if (!string.IsNullOrEmpty(config.Ssid))
{
return true;
}
}
return false;
}
catch
{
return false;
}
}

// 清除保存的WiFi配置
public bool ClearCredentials()
{
try
{
// 获取无线网络接口
var iface = GetWirelessInterface();
if (iface == null)
{
_logger.LogError("[WifiManager] 未找到无线网络接口");
return false;
}

// 获取WiFi配置并清除
var configs = Wireless80211Configuration.GetAllWireless80211Configurations();
var config = configs[iface.SpecificConfigId];

config.Ssid = "";
config.Password = "";
config.Options = Wireless80211Configuration.ConfigurationOptions.None;
config.SaveConfiguration();

// 清空内存中的凭证
_currentSSID = null;
_currentPassword = null;

_logger.LogInformation("[WifiManager] WiFi凭证已清除");
return true;
}
catch (Exception ex)
{
_logger.LogError($"[WifiManager] 清除凭证失败: {ex.Message}");
return false;
}
}

#endregion
}
}

Managers/APConfigManager.cs
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using nanoFramework.WebServer;
using System;
using System.Net;
using System.Threading;

namespace YeFanIoTTest.Managers
{
// AP配网状态枚举
public enum APConfigState
{
Idle, // 空闲
APConfiguring, // 正在配网
Connecting, // 正在连接
Configured, // 配网成功
Failed // 配网失败
}

// 配网完成事件参数
public class APConfigResultEventArgs : EventArgs
{
public bool Success { get; set; }
public string SSID { get; set; }
public string Message { get; set; }
}

internal class APConfigManager
{
// 日志记录器
private ILogger _logger;

// WIFI管理器(外部注入,不内部创建)
private WifiManager _wifiManager;

// Web服务器实例
private WebServer _webServer;

// 当前配网状态
public APConfigState CurrentState { get; private set; } = APConfigState.Idle;

// 配网完成事件(通知外部配网结果)
public event APConfigResultEventHandler OnConfigCompleted;
public delegate void APConfigResultEventHandler(object sender, APConfigResultEventArgs e);

// AP配网参数
private const string AP_SSID = "YF3300_ESP32S3"; // 热点名称
private const string AP_PASSWORD = "yf123456"; // 热点密码(WPA2需>=8)
private const string AP_IP = "192.168.4.1"; // AP网关IP

// Web服务器参数
private const int WEB_PORT = 80; // Web服务器端口
private const int CONFIG_TIMEOUT = 600000; // 配网超时时间(10分钟)

// 配网任务取消令牌
private CancellationTokenSource _configCts;

// 配网超时定时器
private Timer _timeoutTimer;

// 当前配网的SSID和密码(用于传递给WebServerController)
public static string PendingSSID { get; set; }
public static string PendingPassword { get; set; }
public static bool ConfigSubmitted { get; set; } = false;

// WifiManager实例(供WebServerController访问)
private static WifiManager _staticWifiManager;

// 获取WifiManager实例
public static WifiManager GetWifiManager()
{
return _staticWifiManager;
}

// 触发配网完成事件(由外部调用)
public void RaiseConfigCompleted(bool success, string ssid, string message)
{
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = success,
SSID = ssid,
Message = message
});
}
}

// 构造函数:注入WIFI管理器实例
public APConfigManager(WifiManager wifiManager)
{
if (wifiManager == null)
{
throw new ArgumentNullException(nameof(wifiManager));
}
_wifiManager = wifiManager;
_staticWifiManager = wifiManager; // 保存到静态字段
_logger = LogDispatcher.LoggerFactory.CreateLogger("APConfigManager");
}

#region 公共API

// 检查是否已有保存的WIFI配置
public bool HasSavedWifiConfig()
{
return _wifiManager.HasSavedConfig();
}

// 尝试使用已保存的配置连接WIFI
public bool TryConnectSavedWifiConfig()
{
if (_wifiManager.LoadCredentials(out string ssid, out string password))
{
_logger.LogInformation($"[APConfigManager] 尝试使用已保存的WIFI配置进行连接: {ssid}");
return _wifiManager.ConnectSTA(ssid, password, enableReconnect: true);
}
return false;
}

// 开始配网
public bool StartAPConfig()
{
if (CurrentState != APConfigState.Idle)
{
_logger.LogWarning("[APConfigManager] 当前AP状态不是空闲状态,无法开始配网");
return false;
}

_logger.LogInformation("[APConfigManager] 开始配网");

// 步骤一:启动AP热点
bool apStarted = _wifiManager.StartAP(AP_SSID, AP_PASSWORD, AP_IP);
if (!apStarted)
{
_logger.LogError("[APConfigManager] 启动AP热点失败");
CurrentState = APConfigState.Failed;
return false;
}

CurrentState = APConfigState.APConfiguring;
_logger.LogInformation($"[APConfigManager] AP热点已启动,热点名称:{AP_SSID}, IP:{AP_IP}");

// 步骤二:启动Web服务器
// 【重要】等待网络接口完全初始化后再启动Web服务器
Thread.Sleep(1000);
try
{
// 重置配网提交标志
ConfigSubmitted = false;

// 创建Web服务器实例
// 【重要】在AP模式下,必须显式指定绑定的IP地址,否则会绑定到错误的网络接口
_webServer = new WebServer(WEB_PORT, HttpProtocol.Http, IPAddress.Parse(AP_IP), new Type[] { typeof(WebServerController) });
_webServer.Start();
_logger.LogInformation($"[APConfigManager] Web服务器已启动,端口:{WEB_PORT},绑定IP:{AP_IP}");

// ========== 诊断信息:验证网络接口状态 ==========
try
{
var interfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();
foreach (var ni in interfaces)
{
if (ni.NetworkInterfaceType == System.Net.NetworkInformation.NetworkInterfaceType.WirelessAP)
{
_logger.LogInformation($"[APConfigManager] AP接口状态: {ni.IPv4Address}");
_logger.LogInformation($"[APConfigManager] AP接口类型: {ni.NetworkInterfaceType}");
}
}
}
catch (Exception ex)
{
_logger.LogWarning($"[APConfigManager] 获取网络接口信息失败: {ex.Message}");
}
}
catch (Exception e)
{
_logger.LogError($"[APConfigManager] 启动Web服务器失败: {e.Message}");
StopAPConfig();
return false;
}

// 步骤三:启动配网超时定时器
_configCts = new CancellationTokenSource();
_timeoutTimer = new Timer(OnTimeoutCallback, null, CONFIG_TIMEOUT, Timeout.Infinite);

_logger.LogInformation($"[APConfigManager] 请连接WIFI {AP_SSID},并访问 http://{AP_IP}");
_logger.LogInformation($"[APConfigManager] 配网超时定时器已启动,超时时间:{CONFIG_TIMEOUT / 1000} 秒");

return true;
}

// 停止AP配网(清理资源)
public void StopAPConfig()
{
_logger.LogInformation("[APConfigManager] 停止AP配网");

// 停止超时定时器
if (_timeoutTimer != null)
{
_timeoutTimer.Dispose();
_timeoutTimer = null;
}

// 取消配网任务
if (_configCts != null)
{
_configCts.Cancel();
_configCts.Dispose();
_configCts = null;
}

// 关闭Web服务器
if (_webServer != null)
{
_webServer.Stop();
_webServer.Dispose();
_webServer = null;
}

// 关闭AP热点
_wifiManager.StopAP();

CurrentState = APConfigState.Idle;
_logger.LogInformation("[APConfigManager] AP配网已停止");
}

// 处理配网提交(由WebServerController调用)
public void HandleConfigSubmit(string ssid, string password)
{
_logger.LogInformation($"[APConfigManager] 收到配网请求:{ssid}");

// 停止超时定时器
if (_timeoutTimer != null)
{
_timeoutTimer.Change(Timeout.Infinite, Timeout.Infinite);
}

// 停止Web服务器
if (_webServer != null)
{
_webServer.Stop();
}

CurrentState = APConfigState.Connecting;

// 尝试连接WIFI
bool connected = _wifiManager.ConnectSTA(ssid, password, enableReconnect: true);

if (connected)
{
// ========== 验证网络可达性 ==========
// 检查WiFi是否真的可以上网(通过DNS解析验证)
_logger.LogInformation("[APConfigManager] 正在验证网络可达性...");
bool networkReachable = _wifiManager.IsNetworkReachable();

if (!networkReachable)
{
_logger.LogWarning("[APConfigManager] WiFi已连接但网络不可达,请检查路由器是否连接外网");
// 仍然保存凭证,但提示用户网络可能不可用
}
else
{
_logger.LogInformation("[APConfigManager] 网络可达性验证成功,可以正常上网");
}

// ========== 保存WIFI凭证到Flash ==========
// 【测试时可注释以下2行,关闭持久化存储功能】
// _wifiManager.SaveCredentials(ssid, password);
_logger.LogInformation($"[APConfigManager] 已保存WIFI凭证:{ssid}");

CurrentState = APConfigState.Configured;
_logger.LogInformation($"[APConfigManager] 配网成功!SSID:{ssid}");

// 触发配网完成事件
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = true,
SSID = ssid,
Message = networkReachable ? "配网成功!网络可达" : "配网成功!但网络不可达,请检查路由器"
});
}
}
else
{
CurrentState = APConfigState.Failed;
_logger.LogError($"[APConfigManager] 配网失败!SSID:{ssid}");

// 触发配网失败事件
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = false,
SSID = ssid,
Message = "配网失败!"
});
}

// 重新启动AP配网
Thread.Sleep(2000);
CurrentState = APConfigState.Idle;
StartAPConfig();
}
}

#endregion

#region 私有方法

// 超时回调
private void OnTimeoutCallback(object state)
{
_logger.LogWarning("[APConfigManager] 配网超时!");

// 触发配网超时事件
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = false,
SSID = null,
Message = "配网超时!"
});
}

// 停止AP配网
StopAPConfig();
}

#endregion
}
}

Managers/WebServer.cs
using nanoFramework.WebServer;
using System;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace YeFanIoTTest.Managers
{
// Web服务器控制器
// 处理AP配网的HTTP请求
// 【重要】必须是public类,否则WebServer无法实例化
public class WebServerController
{
// 静态APConfigManager实例引用
private static APConfigManager _apConfigManager;

// 设置APConfigManager实例(internal,因为APConfigManager是internal)
internal static void SetAPConfigManager(APConfigManager apConfigManager)
{
_apConfigManager = apConfigManager;
}

// ========== 配网页面路由 ==========

// GET / - 配网主页
[Route("")]
[Method("GET")]
public void GetIndex(WebServerEventArgs e)
{
Debug.WriteLine("[WebServer] ========== Received GET request ==========");
Debug.WriteLine($"[WebServer] Remote endpoint: {e.Context.Request.RemoteEndPoint}");
Debug.WriteLine($"[WebServer] Request URL: {e.Context.Request.RawUrl}");

// 直接输出HTML,不进行任何检查
string html = GetConfigPageHtml();
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, html);

Debug.WriteLine("[WebServer] Response sent successfully");
}

// POST /config - 处理配网表单提交
[Route("config")]
[Method("POST")]
public void PostConfig(WebServerEventArgs e)
{
try
{
// 解析表单数据
string body = GetRequestBody(e);

// 解析SSID和密码
string ssid = ParseFormField(body, "ssid");
string password = ParseFormField(body, "password");

if (string.IsNullOrEmpty(ssid) || string.IsNullOrEmpty(password))
{
string errorHtml = GetErrorPageHtml("SSID和密码不能为空!");
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, errorHtml);
return;
}

// ========== 调用APConfigManager处理配网 ==========
if (_apConfigManager == null)
{
string errorHtml = GetErrorPageHtml("系统错误:APConfigManager未初始化");
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, errorHtml);
return;
}

// 调用HandleConfigSubmit方法处理配网
// 该方法会:连接WiFi、检查网络可达性、触发OnConfigCompleted事件
_apConfigManager.HandleConfigSubmit(ssid, password);

// 根据配网结果显示页面
// 注意:HandleConfigSubmit已经处理了所有逻辑,这里只需要显示结果页面
// 由于HandleConfigSubmit是同步的,我们可以直接检查CurrentState

// 等待配网完成(最多等待10秒)
int waitCount = 0;
while (_apConfigManager.CurrentState == APConfigState.Connecting && waitCount < 100)
{
System.Threading.Thread.Sleep(100);
waitCount++;
}

// 根据配网结果显示页面
if (_apConfigManager.CurrentState == APConfigState.Configured)
{
// 配网成功
string successHtml = GetSuccessPageHtml(ssid);
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, successHtml);
}
else if (_apConfigManager.CurrentState == APConfigState.Failed)
{
// 配网失败
string errorHtml = GetErrorPageHtml("WiFi连接失败,请检查SSID和密码是否正确");
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, errorHtml);
}
else
{
// 其他状态(超时等)
string errorHtml = GetErrorPageHtml("配网超时,请重试");
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, errorHtml);
}
}
catch (Exception ex)
{
string errorHtml = GetErrorPageHtml("配网失败:" + ex.Message);
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, errorHtml);
}
}

// GET /scan - 扫描WiFi网络(AP模式下不支持)
[Route("scan")]
[Method("GET")]
public void GetScan(WebServerEventArgs e)
{
// AP模式下无法扫描WiFi网络
string jsonResult = "{\"success\":false,\"message\":\"AP模式下不支持WiFi扫描,请手动输入WiFi名称\"}";
e.Context.Response.ContentType = "application/json";
WebServer.OutputAsStream(e.Context.Response, jsonResult);
}

// ========== 辅助方法 ==========

// 获取请求体
private string GetRequestBody(WebServerEventArgs e)
{
try
{
var request = e.Context.Request;

// 获取请求体长度
long contentLength = request.ContentLength64;
if (contentLength == 0)
{
return string.Empty;
}

// 读取请求体
byte[] buffer = new byte[(int)contentLength];
request.InputStream.Read(buffer, 0, (int)contentLength);
return Encoding.UTF8.GetString(buffer, 0, (int)contentLength);
}
catch (Exception)
{
return string.Empty;
}
}

// 解析表单字段
private static string ParseFormField(string body, string fieldName)
{
try
{
// 格式: ssid=MyWiFi&password=12345678
string searchPattern = fieldName + "=";
int startIndex = body.IndexOf(searchPattern);
if (startIndex < 0)
{
return string.Empty;
}

startIndex += searchPattern.Length;
int endIndex = body.IndexOf('&', startIndex);
if (endIndex < 0)
{
endIndex = body.Length;
}

string value = body.Substring(startIndex, endIndex - startIndex);

// URL解码
return UrlDecode(value);
}
catch
{
return string.Empty;
}
}

// URL解码
private static string UrlDecode(string value)
{
// 处理 %XX 格式的编码和 + 号
StringBuilder result = new StringBuilder();
for (int i = 0; i < value.Length; i++)
{
if (value[i] == '+')
{
// + 号转换为空格
result.Append(' ');
}
else if (value[i] == '%' && i + 2 < value.Length)
{
// 解析十六进制值
string hex = value.Substring(i + 1, 2);
byte b = HexToByte(hex);
if (b > 0)
{
result.Append((char)b);
i += 2;
}
else
{
result.Append(value[i]);
}
}
else
{
result.Append(value[i]);
}
}
return result.ToString();
}

// 十六进制字符串转字节
private static byte HexToByte(string hex)
{
try
{
byte high = HexCharToValue(hex[0]);
byte low = HexCharToValue(hex[1]);
return (byte)((high << 4) | low);
}
catch
{
return 0;
}
}

// 十六进制字符转数值
private static byte HexCharToValue(char c)
{
if (c >= '0' && c <= '9')
{
return (byte)(c - '0');
}
else if (c >= 'A' && c <= 'F')
{
return (byte)(c - 'A' + 10);
}
else if (c >= 'a' && c <= 'f')
{
return (byte)(c - 'a' + 10);
}
return 0;
}

// ========== HTML页面模板 ==========

// 配网主页HTML
private static string GetConfigPageHtml()
{
string html = "<!DOCTYPE html>";
html += "<html><head>";
html += "<meta charset=\"UTF-8\">";
html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
html += "<title>YF3300-ESP32S3 WiFi配网</title>";
html += "<style>";
html += "body{font-family:Arial;margin:20px;background:#f0f0f0;}";
html += ".container{max-width:400px;margin:0 auto;background:white;padding:20px;border-radius:10px;}";
html += "h1{color:#333;text-align:center;}";
html += ".form-group{margin-bottom:15px;}";
html += "label{display:block;margin-bottom:5px;color:#666;}";
html += "input{width:100%;padding:10px;border:1px solid #ddd;border-radius:5px;box-sizing:border-box;}";
html += "button{width:100%;padding:12px;background:#007bff;color:white;border:none;border-radius:5px;font-size:16px;cursor:pointer;}";
html += ".footer{text-align:center;margin-top:20px;color:#999;font-size:12px;}";
html += ".tip{background:#fff3cd;border:1px solid #ffc107;padding:10px;border-radius:5px;margin-bottom:15px;font-size:14px;}";
html += "</style></head><body>";
html += "<div class=\"container\">";
html += "<h1>YF3300-ESP32S3 WiFi配网</h1>";
html += "<div class=\"tip\">请输入您要连接的WiFi名称和密码</div>";
html += "<form action=\"/config\" method=\"POST\">";
html += "<div class=\"form-group\">";
html += "<label>WiFi名称 (SSID)</label>";
html += "<input type=\"text\" name=\"ssid\" placeholder=\"请输入WiFi名称\" required>";
html += "</div>";
html += "<div class=\"form-group\">";
html += "<label>WiFi密码</label>";
html += "<input type=\"password\" name=\"password\" placeholder=\"请输入WiFi密码\" required>";
html += "</div>";
html += "<button type=\"submit\">开始配网</button>";
html += "</form>";
html += "<div class=\"footer\">YF3300-ESP32S3 v1.0</div>";
html += "</div></body></html>";
return html;
}

// 成功页面HTML
private static string GetSuccessPageHtml(string ssid)
{
string html = "<!DOCTYPE html>";
html += "<html><head>";
html += "<meta charset=\"UTF-8\">";
html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
html += "<title>配网成功</title>";
html += "<style>";
html += "body{font-family:Arial;margin:20px;background:#f0f0f0;}";
html += ".container{max-width:400px;margin:0 auto;background:white;padding:20px;border-radius:10px;text-align:center;}";
html += "h1{color:#28a745;}";
html += ".info{margin:20px 0;padding:15px;background:#f8f9fa;border-radius:5px;}";
html += "</style></head><body>";
html += "<div class=\"container\">";
html += "<h1>配网成功!</h1>";
html += "<div class=\"info\">";
html += "<p>WiFi: <strong>" + ssid + "</strong></p>";
html += "<p>网络连接正常,可以访问互联网</p>";
html += "</div>";
html += "<p>设备已成功同步时间</p>";
html += "</div></body></html>";
return html;
}

// 错误页面HTML
private static string GetErrorPageHtml(string message)
{
string html = "<!DOCTYPE html>";
html += "<html><head>";
html += "<meta charset=\"UTF-8\">";
html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
html += "<title>配网失败</title>";
html += "<style>";
html += "body{font-family:Arial;margin:20px;background:#f0f0f0;}";
html += ".container{max-width:400px;margin:0 auto;background:white;padding:20px;border-radius:10px;text-align:center;}";
html += "h1{color:#dc3545;}";
html += ".error{margin:20px 0;padding:15px;background:#f8d7da;border-radius:5px;color:#721c24;}";
html += "a{display:inline-block;margin-top:20px;padding:10px 20px;background:#007bff;color:white;text-decoration:none;border-radius:5px;}";
html += "</style></head><body>";
html += "<div class=\"container\">";
html += "<h1>配网失败</h1>";
html += "<div class=\"error\">";
html += "<p>" + message + "</p>";
html += "</div>";
html += "<a href=\"/\">重新配网</a>";
html += "</div></body></html>";
return html;
}

// 警告页面HTML(WiFi连接成功但网络不可达)
private static string GetWarningPageHtml(string ssid, string message)
{
string html = "<!DOCTYPE html>";
html += "<html><head>";
html += "<meta charset=\"UTF-8\">";
html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
html += "<title>配网警告</title>";
html += "<style>";
html += "body{font-family:Arial;margin:20px;background:#f0f0f0;}";
html += ".container{max-width:400px;margin:0 auto;background:white;padding:20px;border-radius:10px;text-align:center;}";
html += "h1{color:#ffc107;}";
html += ".warning{margin:20px 0;padding:15px;background:#fff3cd;border-radius:5px;color:#856404;}";
html += ".info{margin:10px 0;color:#666;}";
html += "</style></head><body>";
html += "<div class=\"container\">";
html += "<h1>⚠️ 配网警告</h1>";
html += "<div class=\"info\">";
html += "<p>已连接WiFi: " + ssid + "</p>";
html += "</div>";
html += "<div class=\"warning\">";
html += "<p>" + message + "</p>";
html += "</div>";
html += "<p style=\"color:#999;font-size:12px;\">设备将自动重启...</p>";
html += "</div></body></html>";
return html;
}
}
}
Managers/NtpTimeManager.cs
using System;
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using nanoFramework.Networking;

namespace YeFanIoTTest.Managers
{
// NTP时间同步管理器
// 负责与NTP服务器同步系统时间
internal class NtpTimeManager
{
private readonly ILogger _logger;
private bool _isSynced = false;

// NTP服务器列表(按优先级排序)
private static readonly string[] NtpServers =
{
"cn.pool.ntp.org", // 中国NTP池
"ntp.aliyun.com", // 阿里云NTP(推荐)
"ntp.ntsc.ac.cn", // 国家授时中心(推荐)
"time.windows.com", // Windows时间服务器
"pool.ntp.org", // NTP池
};

private int _currentServerIndex = 0; // 当前使用的服务器索引

// 属性:是否已同步
public bool IsSynced => _isSynced;

// 构造函数
public NtpTimeManager()
{
_logger = LogDispatcher.LoggerFactory.CreateLogger("NtpTimeManager");
}

// 初始化并启动NTP客户端
public bool Initialize()
{
try
{
_logger.LogInformation("Initializing NTP time manager");

// 使用第一个NTP服务器
ConfigureNtpServer(_currentServerIndex);

// 启动SNTP客户端
Sntp.Start();

_logger.LogInformation("SNTP client started");

return true;
}
catch (Exception ex)
{
_logger.LogError($"Failed to initialize NTP manager: {ex.Message}");
return false;
}
}

// 配置NTP服务器
private void ConfigureNtpServer(int index)
{
if (index < NtpServers.Length)
{
// 使用局部变量存储服务器地址
string server1 = NtpServers[index];
string server2 = (index + 1 < NtpServers.Length) ? NtpServers[index + 1] : NtpServers[0];

// 设置服务器
Sntp.Server1 = server1;
Sntp.Server2 = server2;

// 使用变量记录日志,避免读取可能为null的属性
_logger.LogInformation($"NTP servers configured: {server1}, {server2}");
}
}

// 立即同步时间(带重试机制)
public bool SyncNow()
{
int maxRetries = NtpServers.Length; // 最多尝试所有服务器

for (int retry = 0; retry < maxRetries; retry++)
{
try
{
_logger.LogInformation($"Requesting time sync (attempt {retry + 1}/{maxRetries})");
_logger.LogInformation($"Using NTP server: {NtpServers[_currentServerIndex]}");

// 执行立即同步
Sntp.UpdateNow();

// 检查时间是否合理(大于2020年)
var now = DateTime.UtcNow;
if (now.Year > 2020)
{
_isSynced = true;
_logger.LogInformation($"Time synchronized successfully: {now.ToString()}");
return true;
}
else
{
_logger.LogWarning($"Time sync failed, invalid time: {now.ToString()}");

// 尝试下一个服务器
if (retry < maxRetries - 1)
{
_logger.LogInformation("Trying next NTP server...");
_currentServerIndex = (_currentServerIndex + 1) % NtpServers.Length;

// 重新配置NTP服务器
ConfigureNtpServer(_currentServerIndex);

// 等待1秒再重试
System.Threading.Thread.Sleep(1000);
}
}
}
catch (Exception ex)
{
_logger.LogError($"Failed to sync time: {ex.Message}");

// 尝试下一个服务器
if (retry < maxRetries - 1)
{
_logger.LogInformation("Trying next NTP server...");
_currentServerIndex = (_currentServerIndex + 1) % NtpServers.Length;

// 重新配置NTP服务器
ConfigureNtpServer(_currentServerIndex);

// 等待1秒再重试
System.Threading.Thread.Sleep(1000);
}
}
}

_logger.LogError("All NTP servers failed");
return false;
}

// 获取当前UTC时间
public DateTime GetCurrentTime()
{
return DateTime.UtcNow;
}

// 获取当前本地时间(UTC+8)
public DateTime GetLocalTime()
{
return DateTime.UtcNow.AddHours(8);
}

// 停止NTP客户端
public void Stop()
{
try
{
if (Sntp.IsStarted)
{
Sntp.Stop();
_logger.LogInformation("SNTP client stopped");
}
}
catch (Exception ex)
{
_logger.LogError($"Failed to stop SNTP client: {ex.Message}");
}
}

// 检查SNTP客户端状态
public bool IsRunning()
{
return Sntp.IsStarted;
}
}
}

Managers/ConfigurationManager.cs
using System;
using System.Net.NetworkInformation;
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using YeFanIoTTest.Models;

namespace YeFanIoTTest.Managers
{
// 配置管理器 - 负责设备配置和WiFi配置的加载、保存和持久化
public class ConfigurationManager
{
// 日志记录器
private readonly ILogger _logger;

// 内存中的设备配置(临时存储)
private DeviceConfig _deviceConfig;

// 构造函数
public ConfigurationManager()
{
_logger = LogDispatcher.LoggerFactory.CreateLogger("ConfigurationManager");
}

// 加载设备配置(从内存)
public bool LoadDeviceConfig(out DeviceConfig config)
{
config = _deviceConfig ?? new DeviceConfig();

if (_deviceConfig != null && _deviceConfig.IsValid())
{
_logger.LogInformation($"Device config loaded successfully: {_deviceConfig.DeviceID}");
return true;
}

_logger.LogWarning("Device config not found in memory");
return false;
}

// 保存设备配置(到内存)
public bool SaveDeviceConfig(DeviceConfig config)
{
try
{
_deviceConfig = config;
_logger.LogInformation("Device config saved to memory successfully");
return true;
}
catch (Exception ex)
{
_logger.LogError($"Failed to save device config: {ex.Message}");
return false;
}
}

// 加载WiFi配置(使用官方Wireless80211Configuration)
public bool LoadWifiConfig(out WifiConfig config)
{
config = new WifiConfig();
try
{
// 获取所有WiFi配置
var configs = Wireless80211Configuration.GetAllWireless80211Configurations();

foreach (var cfg in configs)
{
// 查找第一个有效的配置
if (!string.IsNullOrEmpty(cfg.Ssid))
{
config.SSID = cfg.Ssid;
config.Password = cfg.Password;

_logger.LogInformation($"WiFi config loaded successfully: {cfg.Ssid}");
return config.IsValid();
}
}

_logger.LogWarning("WiFi config not found");
return false;
}
catch (Exception ex)
{
_logger.LogError($"Failed to load WiFi config: {ex.Message}");
return false;
}
}

// 保存WiFi配置(使用官方Wireless80211Configuration)
public bool SaveWifiConfig(WifiConfig config)
{
try
{
// 获取所有WiFi配置
var configs = Wireless80211Configuration.GetAllWireless80211Configurations();

// 使用第一个配置槽位
var cfg = configs[0];

// 设置WiFi参数
cfg.Ssid = config.SSID;
cfg.Password = config.Password;
cfg.Authentication = AuthenticationType.WPA2;
cfg.Encryption = EncryptionType.WPA2_PSK;
cfg.Options = Wireless80211Configuration.ConfigurationOptions.Enable;

// 保存配置(重启后固件会自动连接)
cfg.SaveConfiguration();

_logger.LogInformation($"WiFi config saved successfully: {config.SSID}");
return true;
}
catch (Exception ex)
{
_logger.LogError($"Failed to save WiFi config: {ex.Message}");
return false;
}
}

// 检查设备配置是否存在
public bool HasDeviceConfig()
{
return _deviceConfig != null && _deviceConfig.IsValid();
}

// 检查WiFi配置是否存在
public bool HasWifiConfig()
{
try
{
var configs = Wireless80211Configuration.GetAllWireless80211Configurations();
foreach (var cfg in configs)
{
if (!string.IsNullOrEmpty(cfg.Ssid))
{
return true;
}
}
return false;
}
catch
{
return false;
}
}
}
}

Properties/AssemblyInfo.cs
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("CSharp.BlankApplication")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CSharp.BlankApplication")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]