init commit
This commit is contained in:
@@ -0,0 +1,547 @@
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user