using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using StandardScene.Utils; using SimpleCore; using SimpleCore.Library; namespace StandardScene.ExtendDevice.ButtonBox { /// /// Azowie 呼叫器按钮盒实现 /// 基于 Modbus TCP 协议 /// public class AzowieButtonBox : BasicButtonBox { /// /// Modbus TCP 客户端 /// private ModbusRtu _modbusClient; /// /// 同步锁 /// private readonly object _syncLock = new object(); /// /// 是否已启动 /// private bool _isStarted = false; /// /// 定时读取任务取消令牌 /// private CancellationTokenSource _cancellationTokenSource; /// /// 定时读取任务 /// private Task _readTask; /// /// 读取间隔(毫秒),默认500ms /// public int ReadInterval { get; set; } = 500; /// /// 重连间隔(毫秒),默认3000ms,避免过于频繁的重连 /// public int ReconnectInterval { get; set; } = 3000; /// /// 上次重连尝试时间 /// private DateTime _lastReconnectAttempt = DateTime.MinValue; /// /// Modbus 从站地址,默认1 /// public byte SlaveAddress { get; set; } = 1; /// /// 呼叫器编号(只读) /// public int DeviceId { get; private set; } = 0; /// /// 电池电量(0-100) /// public int BatteryLevel { get; private set; } = 0; /// /// 按钮灯状态字典 /// public Dictionary ButtonLightStates { get; private set; } = new Dictionary(); /// /// 按钮灯状态枚举 /// public enum ButtonLightState { /// /// 常灭 /// Off = 0, /// /// 常亮 /// On = 1, /// /// 快闪,间隔0.5秒 /// FastBlink = 2, /// /// 慢闪,间隔2秒 /// SlowBlink = 3 } /// /// 连接按钮盒 /// public override void Connect() { lock (_syncLock) { if (_isStarted) { return; } try { UpdateState(ButtonBoxState.Connecting); // 根据配置的按钮初始化按钮状态 var buttonIndices = ButtonConfigs.Keys.OrderBy(k => k).ToList(); if (buttonIndices.Count == 0) { // 如果没有配置,默认初始化按钮1-8 buttonIndices = new List { 1, 2, 3, 4, 5, 6, 7, 8 }; } InitializeButtons(buttonIndices); // 初始化按钮灯状态 foreach (var index in buttonIndices) { ButtonLightStates[index] = ButtonLightState.Off; } // 尝试连接 Modbus TCP 客户端 try { _modbusClient = new ModbusRtu(); _modbusClient.StartTcpRtu(Ip, Port); UpdateState(ButtonBoxState.Online); } catch (Exception connectEx) { // 初次连接失败,但不阻止启动读取循环,循环中会持续重连 UpdateState(ButtonBoxState.Connecting); Diagnosis.Log($"AzowieButtonBox[{Index}] 初次连接失败,将在后台持续重连: {ExceptionFormatter.FormatEx(connectEx)}", "AzowieButtonBox", true); } // 启动定时读取任务(即使连接失败也会启动,循环中会持续重连) _cancellationTokenSource = new CancellationTokenSource(); _readTask = Task.Run(() => ReadButtonStatesLoop(_cancellationTokenSource.Token)); _isStarted = true; } catch (Exception ex) { UpdateState(ButtonBoxState.Error, $"初始化失败: {ex.Message}"); _isStarted = false; Diagnosis.Log($"AzowieButtonBox[{Index}] 初始化失败: {ExceptionFormatter.FormatEx(ex)}", "AzowieButtonBox", true); } } } /// /// 断开连接 /// public override void Disconnect() { lock (_syncLock) { if (!_isStarted || _modbusClient == null) { return; } try { // 停止读取任务 _cancellationTokenSource?.Cancel(); _readTask?.Wait(1000); // 关闭 Modbus 连接 _modbusClient?.Close(); _modbusClient = null; _isStarted = false; UpdateState(ButtonBoxState.Offline); } catch (Exception ex) { UpdateState(ButtonBoxState.Error, $"断开连接失败: {ex.Message}"); Diagnosis.Log($"AzowieButtonBox[{Index}] 断开连接失败: {ExceptionFormatter.FormatEx(ex)}", "AzowieButtonBox", true); } } } /// /// 定时读取按钮状态循环 /// private void ReadButtonStatesLoop(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { try { if (!_isStarted) { break; } // 检查连接状态,如果未连接或连接断开,尝试重连 bool isConnected = _modbusClient?.modbusRtu?.Connected ?? false; if (_modbusClient == null || !isConnected) { UpdateState(ButtonBoxState.Connecting); // 控制重连频率,避免过于频繁的重连导致资源浪费 var timeSinceLastReconnect = (DateTime.Now - _lastReconnectAttempt).TotalMilliseconds; if (timeSinceLastReconnect >= ReconnectInterval) { _lastReconnectAttempt = DateTime.Now; TryReconnect(); } // 如果重连后仍然未连接,等待后继续下一次循环 isConnected = _modbusClient?.modbusRtu?.Connected ?? false; if (_modbusClient == null || !isConnected) { Thread.Sleep(ReadInterval); continue; } } // 读取按钮状态(寄存器 01-04 对应按钮1-4,21-24 对应按钮5-8) ReadButtonStates(); // 读取设备信息(每5秒读取一次) //if (DateTime.Now.Second % 5 == 0) //{ // ReadDeviceInfo(); //} // 更新在线状态 UpdateState(ButtonBoxState.Online); } catch (Exception ex) { Diagnosis.Log($"AzowieButtonBox[{Index}] 读取状态失败: {ExceptionFormatter.FormatEx(ex)}", "AzowieButtonBox", true); UpdateState(ButtonBoxState.Error, $"读取状态失败: {ex.Message}"); // 如果连接失败,尝试重连 TryReconnect(); } // 等待指定间隔 Thread.Sleep(ReadInterval); } } /// /// 尝试重连 Modbus TCP 连接 /// private void TryReconnect() { ModbusRtu oldClient = null; try { // 安全关闭旧连接 if (_modbusClient != null) { oldClient = _modbusClient; _modbusClient = null; // 先置空,避免并发访问 try { oldClient.Close(); } catch { // 忽略关闭时的异常,继续创建新连接 } finally { oldClient = null; // 确保引用释放 } } // 创建新连接 _modbusClient = new ModbusRtu(); _modbusClient.StartTcpRtu(Ip, Port); Diagnosis.Log($"AzowieButtonBox[{Index}] 重连成功", "AzowieButtonBox", false); } catch (Exception ex) { // 重连失败,确保资源释放 if (_modbusClient != null) { try { _modbusClient.Close(); } catch { // 忽略关闭异常 } _modbusClient = null; } // 记录日志但不抛出异常,等待下次循环继续尝试 Diagnosis.Log($"AzowieButtonBox[{Index}] 重连失败: {ExceptionFormatter.FormatEx(ex)}", "AzowieButtonBox", false); } } /// /// 读取按钮状态 /// 根据配置的按钮数量和编号读取对应的寄存器 /// private void ReadButtonStates() { try { // 获取配置的按钮索引列表 var configuredButtons = ButtonConfigs.Keys.OrderBy(k => k).ToList(); if (configuredButtons.Count == 0) { // 如果没有配置,默认读取按钮1-8 configuredButtons = new List { 1, 2, 3, 4, 5, 6, 7, 8 }; } // 将按钮分为两组:按钮1-4(寄存器01-04)和按钮5-8(寄存器21-24) var buttons1_4 = configuredButtons.Where(b => b >= 1 && b <= 4).OrderBy(b => b).ToList(); var buttons5_8 = configuredButtons.Where(b => b >= 5 && b <= 8).OrderBy(b => b).ToList(); // 读取按钮1-4的状态(寄存器地址 01-04) // 按钮索引直接对应寄存器地址:按钮1->寄存器01,按钮2->寄存器02,按钮3->寄存器03,按钮4->寄存器04 if (buttons1_4.Count > 0) { // 计算需要读取的寄存器范围(从最小按钮索引到最大按钮索引) var minButton = buttons1_4.Min(); var maxButton = buttons1_4.Max(); var startReg = (ushort)minButton; // 按钮索引直接对应寄存器地址 var count = maxButton - minButton + 1; var buttonStates1_4 = _modbusClient.ReadRegisterBuffer_03(SlaveAddress, startReg, (ushort)count); // 将读取结果映射到对应的按钮索引 foreach (var buttonIndex in buttons1_4) { // 计算该按钮在读取结果数组中的位置 var arrayIndex = buttonIndex - minButton; if (arrayIndex >= 0 && arrayIndex < buttonStates1_4.Length) { var state = buttonStates1_4[arrayIndex]; UpdateButtonState(buttonIndex, state == 1 ? ButtonState.Pressed : ButtonState.Released); } } } // 读取按钮5-8的状态(寄存器地址 21-24) // 按钮索引对应寄存器地址:按钮5->寄存器21,按钮6->寄存器22,按钮7->寄存器23,按钮8->寄存器24 // 寄存器地址 = 20 + 按钮索引 if (buttons5_8.Count > 0) { var minButton = buttons5_8.Min(); var maxButton = buttons5_8.Max(); var startReg = (ushort)(20 + minButton); // 按钮5对应寄存器21 var count = maxButton - minButton + 1; var buttonStates5_8 = _modbusClient.ReadRegisterBuffer_03(SlaveAddress, startReg, (ushort)count); // 将读取结果映射到对应的按钮索引 foreach (var buttonIndex in buttons5_8) { // 计算该按钮在读取结果数组中的位置 var arrayIndex = buttonIndex - minButton; if (arrayIndex >= 0 && arrayIndex < buttonStates5_8.Length) { var state = buttonStates5_8[arrayIndex]; UpdateButtonState(buttonIndex, state == 1 ? ButtonState.Pressed : ButtonState.Released); } } } } catch (Exception ex) { throw new Exception($"读取按钮状态失败: {ex.Message}", ex); } } /// /// 更新按钮配置信息 /// 当配置变化时,同步更新按钮状态和按钮灯状态字典 /// /// 按钮配置列表 public override void UpdateButtonConfigs(List buttonConfigs) { lock (_syncLock) { // 保存旧的按钮索引列表 var oldButtonIndices = new HashSet(ButtonConfigs.Keys); // 调用基类方法更新配置 base.UpdateButtonConfigs(buttonConfigs); // 获取新的按钮索引列表 var newButtonIndices = new HashSet(ButtonConfigs.Keys); // 如果配置发生变化,更新按钮状态和按钮灯状态字典 if (!oldButtonIndices.SetEquals(newButtonIndices)) { // 移除已删除的按钮状态 var toRemove = oldButtonIndices.Where(k => !newButtonIndices.Contains(k)).ToList(); foreach (var index in toRemove) { ButtonStates.Remove(index); ButtonLightStates.Remove(index); } // 添加新按钮的状态(初始化为未按下和常灭) var toAdd = newButtonIndices.Where(k => !oldButtonIndices.Contains(k)).ToList(); foreach (var index in toAdd) { if (!ButtonStates.ContainsKey(index)) { ButtonStates[index] = ButtonState.Released; } if (!ButtonLightStates.ContainsKey(index)) { ButtonLightStates[index] = ButtonLightState.Off; } } // 如果没有配置,默认初始化按钮1-8 if (newButtonIndices.Count == 0) { var defaultButtons = new List { 1, 2, 3, 4, 5, 6, 7, 8 }; InitializeButtons(defaultButtons); foreach (var index in defaultButtons) { if (!ButtonLightStates.ContainsKey(index)) { ButtonLightStates[index] = ButtonLightState.Off; } } } Diagnosis.Log($"AzowieButtonBox[{Index}] 按钮配置已更新: 旧配置{oldButtonIndices.Count}个按钮, 新配置{newButtonIndices.Count}个按钮", "AzowieButtonBox", false); } } } /// /// 清零指定按钮的寄存器状态 /// /// 按钮索引(1-8) public override void ClearButtonRegister(int buttonIndex) { lock (_syncLock) { if (!_isStarted) { return; } if (buttonIndex < 1 || buttonIndex > 8) { Diagnosis.Log($"AzowieButtonBox[{Index}] 按钮索引超出范围: {buttonIndex}", "AzowieButtonBox", true); return; } try { // 检查连接状态,如果未连接则尝试重连 bool isConnected = _modbusClient?.modbusRtu?.Connected ?? false; if (!isConnected) { // 尝试重连 TryReconnect(); isConnected = _modbusClient?.modbusRtu?.Connected ?? false; // 如果重连失败,记录日志并返回 if (!isConnected) { Diagnosis.Log($"AzowieButtonBox[{Index}] 清零按钮{buttonIndex}寄存器失败: 连接未建立", "AzowieButtonBox", true); return; } } // 根据按钮索引确定寄存器地址 // 按钮1-4对应寄存器01-04,按钮5-8对应寄存器21-24 ushort buttonRegisterAddress; if (buttonIndex >= 1 && buttonIndex <= 4) { buttonRegisterAddress = (ushort)buttonIndex; // 按钮索引直接对应寄存器地址 } else { buttonRegisterAddress = (ushort)(20 + buttonIndex); // 按钮5-8对应寄存器21-24 } // 清零按钮状态寄存器 _modbusClient.WriteSingleRegister_06(SlaveAddress, buttonRegisterAddress, 0); // 清零对应的按钮灯状态寄存器 // 按钮灯1-4对应寄存器05-08(按钮索引 + 4),按钮灯5-8对应寄存器25-28(按钮索引 + 20) ushort lightRegisterAddress; if (buttonIndex is >= 1 and <= 4) { lightRegisterAddress = (ushort)(4 + buttonIndex); // 按钮灯寄存器 = 按钮索引 + 4 } else { lightRegisterAddress = (ushort)(20 + buttonIndex); // 按钮灯寄存器 = 按钮索引 + 20 } _modbusClient.WriteSingleRegister_06(SlaveAddress, lightRegisterAddress, 0); // 更新本地按钮灯状态 if (ButtonLightStates.ContainsKey(buttonIndex)) { ButtonLightStates[buttonIndex] = ButtonLightState.Off; } Diagnosis.Log($"AzowieButtonBox[{Index}] 清零按钮{buttonIndex}状态和灯光寄存器成功", "AzowieButtonBox", false); } catch (Exception ex) { Diagnosis.Log($"AzowieButtonBox[{Index}] 清零按钮{buttonIndex}寄存器失败: {ExceptionFormatter.FormatEx(ex)}", "AzowieButtonBox", true); // 如果是因为连接问题导致的异常,尝试重连 bool isConnected = _modbusClient?.modbusRtu?.Connected ?? false; if (!isConnected) { TryReconnect(); } } } } /// /// 析构函数,确保资源释放 /// ~AzowieButtonBox() { Disconnect(); } } }