Drivers/ButtonDriver.cs

发布时间:2026/7/8 17:55:44
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.csusing 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.csusing 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);}}}