using EasyModbus;
using GSModbus.Config;
using GSModbus.Database;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace GSModbus
{
///
/// 通用Modbus TCP通信管理器 - 支持配置驱动的动态通信
///
public class UniversalModbusManager : IDisposable
{
#region 私有字段
///
/// Modbus配置
///
private ModbusConfiguration? _configuration;
///
/// EasyModbus TCP客户端实例
///
private ModbusClient? _modbusClient;
///
/// 心跳定时器
///
private System.Timers.Timer? _heartbeatTimer;
///
/// 数据轮询定时器
///
private System.Timers.Timer? _dataPollingTimer;
///
/// 心跳包状态
///
private byte _heartbeatState = 0;
///
/// 连接状态标志
///
private bool _isConnected = false;
///
/// 取消令牌源
///
private CancellationTokenSource? _cancellationTokenSource;
///
/// 重试计数器
///
private int _retryCount = 0;
///
/// 数据库管理器
///
private DatabaseManager? _databaseManager;
#endregion
#region 事件定义
///
/// 连接状态改变事件
///
public event EventHandler? ConnectionStatusChanged;
///
/// 接收到动态数据事件
///
public event EventHandler? DataReceived;
///
/// 错误发生事件
///
public event EventHandler? ErrorOccurred;
///
/// 通信统计更新事件
///
public event EventHandler? StatsUpdated;
///
/// 数据库日志事件
///
public event EventHandler? DatabaseLogOccurred;
///
/// 数据库错误事件
///
public event EventHandler? DatabaseErrorOccurred;
#endregion
#region 公共属性
///
/// 获取当前连接状态
///
public bool IsConnected => _isConnected;
///
/// 获取当前配置
///
public ModbusConfiguration? Configuration => _configuration;
///
/// 通信统计信息
///
public CommunicationStats Stats { get; private set; } = new();
#endregion
#region 构造函数
///
/// 初始化通用Modbus管理器
///
/// Modbus配置
public UniversalModbusManager(ModbusConfiguration? configuration = null)
{
_cancellationTokenSource = new CancellationTokenSource();
if (configuration != null)
{
SetConfiguration(configuration);
}
}
#endregion
#region 配置管理方法
///
/// 设置新的配置
///
/// 新配置
public void SetConfiguration(ModbusConfiguration configuration)
{
// 如果当前已连接,需要先断开
if (_isConnected)
{
LogWarning("配置更改时自动断开连接");
Disconnect();
}
// 释放现有数据库管理器
_databaseManager?.Dispose();
_databaseManager = null;
_configuration = configuration;
// 释放现有资源
DisposeTimers();
// 初始化新配置
InitializeWithConfiguration();
LogInfo($"配置已更新: {configuration.ProjectName}");
}
///
/// 根据配置初始化组件
///
private void InitializeWithConfiguration()
{
if (_configuration == null)
{
LogError("无法初始化:配置为空");
return;
}
try
{
// 创建Modbus客户端
_modbusClient = new ModbusClient(_configuration.Connection.IpAddress, _configuration.Connection.Port);
_modbusClient.ConnectionTimeout = _configuration.Connection.ConnectionTimeoutMs;
// 初始化定时器
InitializeTimers();
// 初始化数据库管理器
InitializeDatabaseManager();
LogInfo($"已初始化Modbus客户端: {_configuration.Connection.IpAddress}:{_configuration.Connection.Port}");
}
catch (Exception ex)
{
LogError($"初始化配置时发生错误: {ex.Message}");
throw;
}
}
///
/// 初始化定时器
///
private void InitializeTimers()
{
if (_configuration == null) return;
// 心跳定时器
_heartbeatTimer = new System.Timers.Timer(_configuration.Communication.HeartbeatIntervalMs);
_heartbeatTimer.Elapsed += OnHeartbeatTimerElapsed;
_heartbeatTimer.AutoReset = true;
// 数据轮询定时器
_dataPollingTimer = new System.Timers.Timer(_configuration.Communication.DataPollingIntervalMs);
_dataPollingTimer.Elapsed += OnDataPollingTimerElapsed;
_dataPollingTimer.AutoReset = true;
LogInfo($"定时器已初始化 - 心跳:{_configuration.Communication.HeartbeatIntervalMs}ms, 轮询:{_configuration.Communication.DataPollingIntervalMs}ms");
}
///
/// 初始化数据库管理器
///
private void InitializeDatabaseManager()
{
if (_configuration?.Database?.Enabled == true)
{
try
{
_databaseManager = new DatabaseManager(
_configuration.Database,
_configuration.ProjectName,
_configuration.Connection.IpAddress
);
// 订阅数据库事件
_databaseManager.DatabaseLogOccurred += (sender, message) => DatabaseLogOccurred?.Invoke(this, message);
_databaseManager.DatabaseErrorOccurred += (sender, error) => DatabaseErrorOccurred?.Invoke(this, error);
LogInfo("数据库管理器初始化成功");
}
catch (Exception ex)
{
LogError($"初始化数据库管理器失败: {ex.Message}");
}
}
else
{
LogInfo("数据库功能未启用");
}
}
#endregion
#region 连接管理方法
///
/// 连接到PLC服务器
///
/// 连接是否成功
public async Task ConnectAsync()
{
if (_configuration == null)
{
LogError("无法连接:配置未设置");
return false;
}
if (_modbusClient == null)
{
LogError("无法连接:Modbus客户端未初始化");
return false;
}
try
{
LogInfo($"正在连接到PLC服务器 {_configuration.Connection.IpAddress}:{_configuration.Connection.Port}...");
// 尝试连接到PLC
await Task.Run(() => _modbusClient.Connect());
_isConnected = true;
_retryCount = 0;
Stats.ConnectionAttempts++;
Stats.LastConnectedTime = DateTime.Now;
LogInfo("成功连接到PLC服务器");
// 记录数据库日志
await _databaseManager?.LogCommunicationAsync(EventTypes.Connected, "成功连接到PLC服务器", true)!;
// 启动定时器
_heartbeatTimer?.Start();
_dataPollingTimer?.Start();
// 触发连接状态改变事件
ConnectionStatusChanged?.Invoke(this, true);
return true;
}
catch (Exception ex)
{
_isConnected = false;
Stats.ConnectionFailures++;
LogError($"连接PLC失败: {ex.Message}");
// 记录数据库错误日志
await _databaseManager?.LogErrorAsync("ConnectionError", $"连接PLC失败: {ex.Message}", ex, ErrorSeverity.High, _retryCount)!;
await _databaseManager?.LogCommunicationAsync(EventTypes.ConnectionError, ex.Message, false)!;
ConnectionStatusChanged?.Invoke(this, false);
// 如果启用自动重连,安排重连
if (_configuration.Communication.EnableAutoReconnect &&
_retryCount < _configuration.Communication.MaxRetryCount)
{
ScheduleReconnect();
}
return false;
}
}
///
/// 断开与PLC的连接
///
public async void Disconnect()
{
try
{
// 停止定时器
_heartbeatTimer?.Stop();
_dataPollingTimer?.Stop();
// 断开Modbus连接
if (_modbusClient?.Connected == true)
{
_modbusClient.Disconnect();
}
_isConnected = false;
Stats.LastDisconnectedTime = DateTime.Now;
LogInfo("已断开与PLC的连接");
// 记录数据库日志
await _databaseManager?.LogCommunicationAsync(EventTypes.Disconnected, "手动断开PLC连接", true)!;
// 触发连接状态改变事件
ConnectionStatusChanged?.Invoke(this, false);
}
catch (Exception ex)
{
LogError($"断开连接时发生错误: {ex.Message}");
}
}
///
/// 安排自动重连
///
private void ScheduleReconnect()
{
if (_configuration == null || _cancellationTokenSource?.Token.IsCancellationRequested == true)
return;
_retryCount++;
var delay = _configuration.Communication.ReconnectDelayMs;
LogWarning($"将在 {delay}ms 后尝试第 {_retryCount} 次重连");
Task.Delay(delay, _cancellationTokenSource?.Token ?? CancellationToken.None).ContinueWith(async _ =>
{
if (!(_cancellationTokenSource?.Token.IsCancellationRequested ?? true))
{
await ConnectAsync();
}
}, _cancellationTokenSource?.Token ?? CancellationToken.None);
}
#endregion
#region 定时器事件处理
///
/// 心跳定时器事件
///
private async void OnHeartbeatTimerElapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
if (!_isConnected || _configuration == null || _modbusClient == null ||
_cancellationTokenSource?.Token.IsCancellationRequested == true)
return;
try
{
// 在0和1之间交替心跳状态
_heartbeatState = (byte)(_heartbeatState == 0 ? 1 : 0);
// 向PLC发送心跳包
var heartbeatAddress = _configuration.OutputAddresses.HeartbeatAddress.Address;
await Task.Run(() => _modbusClient.WriteSingleRegister(heartbeatAddress, _heartbeatState));
Stats.HeartbeatsSent++;
LogDebug($"发送心跳包到地址 {heartbeatAddress}: {_heartbeatState}");
}
catch (Exception ex)
{
Stats.CommunicationErrors++;
LogError($"发送心跳包失败: {ex.Message}");
await HandleConnectionLoss();
}
}
///
/// 数据轮询定时器事件
///
private async void OnDataPollingTimerElapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
if (!_isConnected || _configuration == null || _modbusClient == null ||
_cancellationTokenSource?.Token.IsCancellationRequested == true)
return;
try
{
// 读取PLC数据
var dynamicData = await ReadDynamicDataAsync();
if (dynamicData != null)
{
// 检查是否需要发送确认信号
var dataReadySignal = GetDataReadySignal(dynamicData);
if (dataReadySignal == 1)
{
await SendDataReadConfirmationAsync();
// 保存到数据库
await _databaseManager?.SaveModbusDataAsync(dynamicData)!;
await _databaseManager?.LogCommunicationAsync(EventTypes.DataReceived, "成功读取PLC数据", true)!;
// 触发数据接收事件
DataReceived?.Invoke(this, dynamicData);
Stats.SuccessfulReads++;
LogDebug("成功读取PLC数据并发送确认信号");
}
}
}
catch (Exception ex)
{
Stats.CommunicationErrors++;
LogError($"读取PLC数据失败: {ex.Message}");
// 记录数据库错误日志
await _databaseManager?.LogErrorAsync("DataReadError", ex.Message, ex, ErrorSeverity.Medium)!;
await _databaseManager?.LogCommunicationAsync(EventTypes.DataReadError, ex.Message, false)!;
}
finally
{
// 更新统计信息
StatsUpdated?.Invoke(this, Stats);
}
}
#endregion
#region 数据读取方法
///
/// 读取动态配置的数据
///
/// 动态数据对象
private async Task ReadDynamicDataAsync()
{
if (_configuration?.InputAddresses == null || _modbusClient == null)
return null;
var dynamicData = new DynamicModbusData
{
SourceConfiguration = _configuration,
ReadTime = DateTime.Now
};
try
{
// 读取控制信号
await ReadFieldGroup(dynamicData, _configuration.InputAddresses.ControlSignals, "控制信号");
// 读取产品数据
await ReadFieldGroup(dynamicData, _configuration.InputAddresses.ProductData, "产品数据");
// 读取测量数据
await ReadFieldGroup(dynamicData, _configuration.InputAddresses.MeasurementData, "测量数据");
return dynamicData;
}
catch (Exception ex)
{
LogError($"读取动态数据时发生错误: {ex.Message}");
return null;
}
}
///
/// 读取字段组数据
///
/// 动态数据容器
/// 字段组配置
/// 组名称(用于日志)
private async Task ReadFieldGroup(DynamicModbusData dynamicData, Dictionary fieldGroup, string groupName)
{
if (_modbusClient == null)
{
LogWarning($"Modbus客户端为null,无法读取 {groupName}");
return;
}
if (!_isConnected)
{
LogWarning($"Modbus未连接,无法读取 {groupName}");
return;
}
if (fieldGroup == null || fieldGroup.Count == 0)
{
LogDebug($"{groupName} 没有配置字段,跳过读取");
return;
}
foreach (var kvp in fieldGroup)
{
var fieldName = kvp.Key;
var fieldConfig = kvp.Value;
try
{
// 检查Modbus客户端状态
if (_modbusClient == null || !_modbusClient.Connected)
{
LogWarning($"Modbus客户端未连接,跳过字段 '{fieldName}'");
continue;
}
// 读取原始寄存器数据
var registers = await Task.Run(() =>
_modbusClient.ReadHoldingRegisters(fieldConfig.Address, fieldConfig.Length));
// 验证寄存器读取结果的有效性
ValidateRegisterData(fieldName, fieldConfig, registers);
// 检查读取结果是否为null
if (registers == null)
{
LogWarning($"字段 '{fieldName}' 读取结果为null,地址: {fieldConfig.Address}, 长度: {fieldConfig.Length}");
continue;
}
// 记录原始寄存器数据用于调试
LogDebug($"[RAW-DATA] 字段 '{fieldName}' 原始寄存器: [{string.Join(", ", registers)}] (十六进制: [{string.Join(", ", registers.Select(r => $"0x{r:X4}"))}])");
// 详细记录每个寄存器的字节分解
for (int i = 0; i < registers.Length; i++)
{
var reg = registers[i];
var highByte = (byte)((reg >> 8) & 0xFF);
var lowByte = (byte)(reg & 0xFF);
LogDebug($"[RAW-DATA] 寄存器{i}: {reg} (0x{reg:X4}) → 高字节:{highByte}('{(char)highByte}') 低字节:{lowByte}('{(char)lowByte}')");
}
// 验证数据长度
if (!ModbusDataParser.ValidateRegisterLength(registers, fieldConfig))
{
LogWarning($"字段 '{fieldName}' 的数据长度不足:期望{fieldConfig.Length},实际{registers?.Length ?? 0}");
continue;
}
// 解析数据
var parsedValue = ModbusDataParser.ParseFieldData(registers, fieldConfig);
// 存储到动态数据容器
dynamicData.SetFieldData(fieldName, parsedValue, registers);
LogDebug($"成功读取字段 '{fieldName}': {parsedValue}");
}
catch (Exception ex)
{
LogError($"读取字段 '{fieldName}' 失败: {ex.Message}");
// 记录详细错误信息
await _databaseManager?.LogErrorAsync(
"FieldReadError",
$"读取字段 '{fieldName}' 失败: {ex.Message}",
ex,
ErrorSeverity.Medium)!;
// 继续读取其他字段
}
}
}
///
/// 获取数据就绪信号
///
private byte GetDataReadySignal(DynamicModbusData data)
{
// 查找数据就绪信号字段
var controlSignals = _configuration?.InputAddresses.ControlSignals;
if (controlSignals != null)
{
foreach (var kvp in controlSignals)
{
var fieldConfig = kvp.Value;
if (fieldConfig.Description?.Contains("数据") == true &&
fieldConfig.Description?.Contains("就绪") == true)
{
return data.GetByte(kvp.Key);
}
}
}
// 如果没有找到明确的数据就绪信号,返回1(假设数据总是就绪)
return 1;
}
#endregion
#region 数据验证方法
///
/// 验证寄存器数据的有效性
///
/// 字段名
/// 字段配置
/// 读取的寄存器数据
private void ValidateRegisterData(string fieldName, DataField fieldConfig, int[]? registers)
{
if (registers == null)
{
LogWarning($"[VALIDATE] 字段 '{fieldName}' 寄存器数据为null");
return;
}
LogDebug($"[VALIDATE] 字段 '{fieldName}' 原始数据验证:");
LogDebug($"[VALIDATE] - 地址: {fieldConfig.Address}, 期望长度: {fieldConfig.Length}, 实际长度: {registers.Length}");
LogDebug($"[VALIDATE] - 数据类型: {fieldConfig.DataType}");
// 检查数据长度
if (registers.Length != fieldConfig.Length)
{
LogWarning($"[VALIDATE] 字段 '{fieldName}' 数据长度不匹配: 期望{fieldConfig.Length}, 实际{registers.Length}");
}
// 检查是否包含有效数据
var hasNonZeroData = registers.Any(r => r != 0);
if (!hasNonZeroData)
{
LogWarning($"[VALIDATE] 字段 '{fieldName}' 所有寄存器都为0,可能是无效数据");
}
// 检查数据范围(16位有符号整数范围)
var outOfRangeCount = registers.Count(r => r < -32768 || r > 32767);
if (outOfRangeCount > 0)
{
LogWarning($"[VALIDATE] 字段 '{fieldName}' 有{outOfRangeCount}个寄存器值超出16位有符号整数范围");
}
// 特殊数据模式检测
DetectSpecialDataPatterns(fieldName, registers);
}
///
/// 检测特殊的数据模式
///
/// 字段名
/// 寄存器数据
private void DetectSpecialDataPatterns(string fieldName, int[] registers)
{
// 检测全FF模式(通信错误)
if (registers.All(r => r == -1 || r == 0xFFFF))
{
LogWarning($"[PATTERN] 字段 '{fieldName}' 检测到全FF模式,可能是通信错误");
}
// 检测常见的错误码模式
if (registers.Length >= 2)
{
var combined = ((long)((uint)registers[0] & 0xFFFF) << 16) | ((uint)registers[1] & 0xFFFF);
if (combined == 0xE2400001)
{
LogInfo($"[PATTERN] 字段 '{fieldName}' 检测到已知的无效数据标识 0xE2400001");
}
}
// 检测ASCII字符模式(用于字符串和时间戳字段)
if (registers.Any(r => IsLikelyAsciiData(r)))
{
LogDebug($"[PATTERN] 字段 '{fieldName}' 包含可能的ASCII字符数据");
}
}
///
/// 检查寄存器值是否可能包含ASCII字符
///
/// 寄存器值
/// 是否可能是ASCII数据
private bool IsLikelyAsciiData(int register)
{
if (register == 0) return false;
var highByte = (byte)((register >> 8) & 0xFF);
var lowByte = (byte)(register & 0xFF);
// 检查是否为可打印ASCII字符范围(32-126)
return (highByte >= 32 && highByte <= 126) || (lowByte >= 32 && lowByte <= 126);
}
#endregion
#region 数据发送方法
///
/// 向PLC发送数据读取确认信号
///
private async Task SendDataReadConfirmationAsync()
{
if (_configuration == null || _modbusClient == null) return;
try
{
var confirmAddress = _configuration.OutputAddresses.DataConfirmationAddress.Address;
await Task.Run(() => _modbusClient.WriteSingleRegister(confirmAddress, 1));
Stats.ConfirmationsSent++;
LogDebug($"已向地址 {confirmAddress} 发送数据读取确认信号");
}
catch (Exception ex)
{
LogError($"发送数据确认信号失败: {ex.Message}");
throw;
}
}
#endregion
#region 错误处理方法
///
/// 处理连接丢失
///
private Task HandleConnectionLoss()
{
if (_isConnected)
{
LogWarning("检测到连接丢失");
Disconnect();
// 如果启用自动重连
if (_configuration?.Communication.EnableAutoReconnect == true &&
_retryCount < _configuration.Communication.MaxRetryCount)
{
ScheduleReconnect();
}
}
return Task.CompletedTask;
}
#endregion
#region 资源清理方法
///
/// 释放定时器资源
///
private void DisposeTimers()
{
_heartbeatTimer?.Stop();
_heartbeatTimer?.Dispose();
_heartbeatTimer = null;
_dataPollingTimer?.Stop();
_dataPollingTimer?.Dispose();
_dataPollingTimer = null;
}
public void Dispose()
{
_cancellationTokenSource?.Cancel();
DisposeTimers();
Disconnect();
_modbusClient = null;
_databaseManager?.Dispose();
_databaseManager = null;
_cancellationTokenSource?.Dispose();
}
#endregion
#region 日志方法
private void LogInfo(string message)
{
Console.WriteLine($"[UNIVERSAL-INFO] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
}
private void LogWarning(string message)
{
Console.WriteLine($"[UNIVERSAL-WARN] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
}
private void LogError(string message)
{
Console.WriteLine($"[UNIVERSAL-ERROR] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
ErrorOccurred?.Invoke(this, message);
}
private void LogDebug(string message)
{
// 只在Debug模式下输出详细日志
#if DEBUG
Console.WriteLine($"[UNIVERSAL-DEBUG] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
#endif
}
#endregion
}
#region 通信统计类
///
/// 通信统计信息
///
public class CommunicationStats
{
///
/// 连接尝试次数
///
public long ConnectionAttempts { get; set; }
///
/// 连接失败次数
///
public long ConnectionFailures { get; set; }
///
/// 发送的心跳包数量
///
public long HeartbeatsSent { get; set; }
///
/// 发送的确认信号数量
///
public long ConfirmationsSent { get; set; }
///
/// 成功读取次数
///
public long SuccessfulReads { get; set; }
///
/// 通信错误次数
///
public long CommunicationErrors { get; set; }
///
/// 最后连接时间
///
public DateTime? LastConnectedTime { get; set; }
///
/// 最后断开时间
///
public DateTime? LastDisconnectedTime { get; set; }
///
/// 获取连接成功率
///
public double ConnectionSuccessRate =>
ConnectionAttempts > 0 ? (double)(ConnectionAttempts - ConnectionFailures) / ConnectionAttempts * 100 : 0;
///
/// 获取连接成功率
///
public double TotalConnectionAttempts => ConnectionAttempts;
///
/// 获取成功连接数
///
public double SuccessfulConnections => ConnectionAttempts - ConnectionFailures;
///
/// 获取总读取数
///
public double TotalDataReads => SuccessfulReads + CommunicationErrors;
///
/// 获取数据读取成功率
///
public double DataReadSuccessRate =>
TotalDataReads > 0 ? SuccessfulReads / TotalDataReads * 100 : 0;
///
/// 获取总错误数
///
public double TotalErrors => CommunicationErrors + ConnectionFailures;
///
/// 平均响应时间(毫秒)
///
public double AverageResponseTimeMs { get; set; } = 0;
///
/// 最大响应时间(毫秒)
///
public int MaxResponseTimeMs { get; set; } = 0;
///
/// 最小响应时间(毫秒)
///
public int MinResponseTimeMs { get; set; } = int.MaxValue;
///
/// 获取统计摘要字符串
///
public string GetSummary()
{
return $"连接: {ConnectionAttempts - ConnectionFailures}/{ConnectionAttempts} " +
$"({ConnectionSuccessRate:F1}%), " +
$"读取: {SuccessfulReads}, " +
$"错误: {CommunicationErrors}";
}
}
#endregion
}