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 # 程序集元数据 |
全部代码
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); |
| } |
| } |
| } |