Files
2026-06-14 11:19:15 +08:00

548 lines
21 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// Azowie 呼叫器按钮盒实现
/// 基于 Modbus TCP 协议
/// </summary>
public class AzowieButtonBox : BasicButtonBox
{
/// <summary>
/// Modbus TCP 客户端
/// </summary>
private ModbusRtu _modbusClient;
/// <summary>
/// 同步锁
/// </summary>
private readonly object _syncLock = new object();
/// <summary>
/// 是否已启动
/// </summary>
private bool _isStarted = false;
/// <summary>
/// 定时读取任务取消令牌
/// </summary>
private CancellationTokenSource _cancellationTokenSource;
/// <summary>
/// 定时读取任务
/// </summary>
private Task _readTask;
/// <summary>
/// 读取间隔(毫秒),默认500ms
/// </summary>
public int ReadInterval { get; set; } = 500;
/// <summary>
/// 重连间隔(毫秒),默认3000ms,避免过于频繁的重连
/// </summary>
public int ReconnectInterval { get; set; } = 3000;
/// <summary>
/// 上次重连尝试时间
/// </summary>
private DateTime _lastReconnectAttempt = DateTime.MinValue;
/// <summary>
/// Modbus 从站地址,默认1
/// </summary>
public byte SlaveAddress { get; set; } = 1;
/// <summary>
/// 呼叫器编号(只读)
/// </summary>
public int DeviceId { get; private set; } = 0;
/// <summary>
/// 电池电量(0-100
/// </summary>
public int BatteryLevel { get; private set; } = 0;
/// <summary>
/// 按钮灯状态字典
/// </summary>
public Dictionary<int, ButtonLightState> ButtonLightStates { get; private set; } = new Dictionary<int, ButtonLightState>();
/// <summary>
/// 按钮灯状态枚举
/// </summary>
public enum ButtonLightState
{
/// <summary>
/// 常灭
/// </summary>
Off = 0,
/// <summary>
/// 常亮
/// </summary>
On = 1,
/// <summary>
/// 快闪,间隔0.5秒
/// </summary>
FastBlink = 2,
/// <summary>
/// 慢闪,间隔2秒
/// </summary>
SlowBlink = 3
}
/// <summary>
/// 连接按钮盒
/// </summary>
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<int> { 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);
}
}
}
/// <summary>
/// 断开连接
/// </summary>
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);
}
}
}
/// <summary>
/// 定时读取按钮状态循环
/// </summary>
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);
}
}
/// <summary>
/// 尝试重连 Modbus TCP 连接
/// </summary>
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);
}
}
/// <summary>
/// 读取按钮状态
/// 根据配置的按钮数量和编号读取对应的寄存器
/// </summary>
private void ReadButtonStates()
{
try
{
// 获取配置的按钮索引列表
var configuredButtons = ButtonConfigs.Keys.OrderBy(k => k).ToList();
if (configuredButtons.Count == 0)
{
// 如果没有配置,默认读取按钮1-8
configuredButtons = new List<int> { 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);
}
}
/// <summary>
/// 更新按钮配置信息
/// 当配置变化时,同步更新按钮状态和按钮灯状态字典
/// </summary>
/// <param name="buttonConfigs">按钮配置列表</param>
public override void UpdateButtonConfigs(List<ButtonModel> buttonConfigs)
{
lock (_syncLock)
{
// 保存旧的按钮索引列表
var oldButtonIndices = new HashSet<int>(ButtonConfigs.Keys);
// 调用基类方法更新配置
base.UpdateButtonConfigs(buttonConfigs);
// 获取新的按钮索引列表
var newButtonIndices = new HashSet<int>(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<int> { 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);
}
}
}
/// <summary>
/// 清零指定按钮的寄存器状态
/// </summary>
/// <param name="buttonIndex">按钮索引(1-8</param>
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();
}
}
}
}
/// <summary>
/// 析构函数,确保资源释放
/// </summary>
~AzowieButtonBox()
{
Disconnect();
}
}
}