using EasyModbus;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
namespace GSModbus
{
///
/// Modbus TCP通信管理器 - 作为MES客户端与PLC服务器通信
///
public class ModbusManager : IDisposable
{
#region 常量定义 - Modbus地址映射
// MES发送给PLC的地址 (MES → PLC)
///
/// MES发送给PLC的心跳包地址 - 每秒交替发送0/1来维持连接
///
private const int MES_HEARTBEAT_ADDRESS = 6000;
///
/// MES发送的数据读取确认信号 - 当MES成功读取PLC数据后置1确认
///
private const int MES_DATA_OK_ADDRESS = 6001;
// PLC发送给MES的地址 (PLC → MES) - 这些是我们需要读取的地址
///
/// PLC发送的心跳包地址 - PLC每秒交替发送0/1
///
private const int PLC_HEARTBEAT_ADDRESS = 6002;
///
/// PLC数据准备就绪信号 - PLC置1表示数据已准备好供MES读取
///
private const int PLC_DATA_OK_ADDRESS = 6003;
///
/// 产品型号起始地址 - 10个字节存储产品型号(D6004-D6013)
///
private const int PRODUCT_MODEL_START_ADDRESS = 6004;
///
/// 测试工位地址 - 存储当前测试的工位号
///
private const int TEST_STATION_ADDRESS = 6014;
///
/// 产品码起始地址 - 10个字节存储产品码(D6015-D6024)
///
private const int PRODUCT_CODE_START_ADDRESS = 6015;
///
/// 测试结果地址 - 存储测试通过/失败状态
///
private const int TEST_RESULT_ADDRESS = 6025;
///
/// 记录时间起始地址 - 7个字节存储时间戳(D6026-D6032)
///
private const int TIMESTAMP_START_ADDRESS = 6026;
///
/// 测量数据起始地址 - 各种传感器测量值(D6033-D6048)
///
private const int MEASUREMENT_DATA_START_ADDRESS = 6033;
// PLC服务器连接信息
private const string PLC_IP_ADDRESS = "192.168.3.250";
private const int PLC_PORT = 502;
#endregion
#region 私有字段
///
/// EasyModbus TCP客户端实例
///
private ModbusClient _modbusClient;
///
/// 心跳定时器 - 用于定期发送心跳包
///
private System.Timers.Timer _heartbeatTimer;
///
/// 数据轮询定时器 - 用于定期读取PLC数据
///
private System.Timers.Timer _dataPollingTimer;
///
/// 心跳包状态 - 在0和1之间交替
///
private byte _heartbeatState = 0;
///
/// 连接状态标志
///
private bool _isConnected = false;
///
/// 取消令牌源 - 用于优雅停止异步操作
///
private CancellationTokenSource _cancellationTokenSource;
#endregion
#region 事件定义
///
/// 连接状态改变事件
///
public event EventHandler ConnectionStatusChanged;
///
/// 接收到PLC数据事件
///
public event EventHandler DataReceived;
///
/// 错误发生事件
///
public event EventHandler ErrorOccurred;
#endregion
#region 公共属性
///
/// 获取当前连接状态
///
public bool IsConnected => _isConnected;
#endregion
#region 构造函数
///
/// 初始化Modbus管理器
///
public ModbusManager()
{
_cancellationTokenSource = new CancellationTokenSource();
InitializeModbusClient();
InitializeTimers();
}
#endregion
#region 初始化方法
///
/// 初始化Modbus TCP客户端
///
private void InitializeModbusClient()
{
try
{
// 创建Modbus TCP客户端实例
_modbusClient = new ModbusClient(PLC_IP_ADDRESS, PLC_PORT);
// 设置连接超时时间(毫秒)
_modbusClient.ConnectionTimeout = 5000;
LogInfo($"Modbus客户端初始化完成,目标PLC地址: {PLC_IP_ADDRESS}:{PLC_PORT}");
}
catch (Exception ex)
{
LogError($"初始化Modbus客户端失败: {ex.Message}");
throw;
}
}
///
/// 初始化定时器
///
private void InitializeTimers()
{
// 心跳定时器 - 每1秒发送一次心跳
_heartbeatTimer = new System.Timers.Timer(1000);
_heartbeatTimer.Elapsed += OnHeartbeatTimerElapsed;
_heartbeatTimer.AutoReset = true;
// 数据轮询定时器 - 每500毫秒读取一次PLC数据
_dataPollingTimer = new System.Timers.Timer(500);
_dataPollingTimer.Elapsed += OnDataPollingTimerElapsed;
_dataPollingTimer.AutoReset = true;
LogInfo("定时器初始化完成 - 心跳周期:1秒, 数据轮询周期:500毫秒");
}
#endregion
#region 连接管理方法
///
/// 连接到PLC服务器
///
/// 连接是否成功
public async Task ConnectAsync()
{
try
{
LogInfo($"正在连接到PLC服务器 {PLC_IP_ADDRESS}:{PLC_PORT}...");
// 尝试连接到PLC
await Task.Run(() => _modbusClient.Connect());
_isConnected = true;
LogInfo("成功连接到PLC服务器");
// 启动定时器
_heartbeatTimer.Start();
_dataPollingTimer.Start();
// 触发连接状态改变事件
ConnectionStatusChanged?.Invoke(this, true);
return true;
}
catch (Exception ex)
{
_isConnected = false;
LogError($"连接PLC失败: {ex.Message}");
ConnectionStatusChanged?.Invoke(this, false);
return false;
}
}
///
/// 断开与PLC的连接
///
public void Disconnect()
{
try
{
// 停止定时器
_heartbeatTimer?.Stop();
_dataPollingTimer?.Stop();
// 断开Modbus连接
if (_modbusClient?.Connected == true)
{
_modbusClient.Disconnect();
}
_isConnected = false;
LogInfo("已断开与PLC的连接");
// 触发连接状态改变事件
ConnectionStatusChanged?.Invoke(this, false);
}
catch (Exception ex)
{
LogError($"断开连接时发生错误: {ex.Message}");
}
}
#endregion
#region 定时器事件处理
///
/// 心跳定时器事件 - 每秒向PLC发送心跳包
///
private async void OnHeartbeatTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (!_isConnected || _cancellationTokenSource.Token.IsCancellationRequested)
return;
try
{
// 在0和1之间交替心跳状态
_heartbeatState = (byte)(_heartbeatState == 0 ? 1 : 0);
// 向PLC发送心跳包
await Task.Run(() =>
{
_modbusClient.WriteSingleRegister(MES_HEARTBEAT_ADDRESS, _heartbeatState);
});
LogDebug($"发送心跳包到PLC: {_heartbeatState}");
}
catch (Exception ex)
{
LogError($"发送心跳包失败: {ex.Message}");
// 心跳失败可能表示连接已断开
await HandleConnectionLoss();
}
}
///
/// 数据轮询定时器事件 - 定期从PLC读取数据
///
private async void OnDataPollingTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (!_isConnected || _cancellationTokenSource.Token.IsCancellationRequested)
return;
try
{
// 读取PLC数据
var plcData = await ReadPlcDataAsync();
if (plcData != null)
{
// 如果PLC表示数据已准备好,则发送确认信号
if (plcData.DataOkSignal == 1)
{
await SendDataReadConfirmationAsync();
// 触发数据接收事件
DataReceived?.Invoke(this, plcData);
LogInfo("成功读取PLC数据并发送确认信号");
}
}
}
catch (Exception ex)
{
LogError($"读取PLC数据失败: {ex.Message}");
}
}
#endregion
#region 数据读取方法
///
/// 从PLC读取所有数据
///
/// PLC数据结构
private async Task ReadPlcDataAsync()
{
try
{
// 读取控制信号
var controlSignals = await Task.Run(() =>
_modbusClient.ReadHoldingRegisters(PLC_HEARTBEAT_ADDRESS, 2));
// 检查PLC数据是否准备就绪
if (controlSignals[1] == 0) // DataOK信号为0表示数据未准备好
{
return null;
}
// 读取产品型号 (10个寄存器)
var productModelData = await Task.Run(() =>
_modbusClient.ReadHoldingRegisters(PRODUCT_MODEL_START_ADDRESS, 10));
// 读取测试工位
var testStationData = await Task.Run(() =>
_modbusClient.ReadHoldingRegisters(TEST_STATION_ADDRESS, 1));
// 读取产品码 (10个寄存器)
var productCodeData = await Task.Run(() =>
_modbusClient.ReadHoldingRegisters(PRODUCT_CODE_START_ADDRESS, 10));
// 读取测试结果
var testResultData = await Task.Run(() =>
_modbusClient.ReadHoldingRegisters(TEST_RESULT_ADDRESS, 1));
// 读取时间戳 (7个寄存器)
var timestampData = await Task.Run(() =>
_modbusClient.ReadHoldingRegisters(TIMESTAMP_START_ADDRESS, 7));
// 读取测量数据 (16个寄存器,从D6033到D6048)
var measurementData = await Task.Run(() =>
_modbusClient.ReadHoldingRegisters(MEASUREMENT_DATA_START_ADDRESS, 16));
// 解析并返回数据
return new PlcData
{
// 控制信号
PlcHeartbeat = (byte)controlSignals[0],
DataOkSignal = (byte)controlSignals[1],
// 产品信息
ProductModel = ParseStringFromRegisters(productModelData),
TestStation = (byte)testStationData[0],
ProductCode = ParseStringFromRegisters(productCodeData),
TestResult = (byte)testResultData[0],
// 时间戳
Timestamp = ParseTimestampFromRegisters(timestampData),
// 测量数据 (需要除以100来获得实际值,因为PLC以整数*100方式存储小数)
MinInstallSize = measurementData[0] / 100.0,
MaxInstallSize = measurementData[2] / 100.0,
Stroke = measurementData[4] / 100.0,
Speed = measurementData[6] / 100.0,
WorkingVoltage = measurementData[8] / 100.0,
WorkingCurrent = measurementData[10] / 100.0,
WorkingPressure = measurementData[12] / 100.0,
UpHallSensor = measurementData[14] / 100.0,
ReadTime = DateTime.Now
};
}
catch (Exception ex)
{
LogError($"读取PLC数据时发生错误: {ex.Message}");
throw;
}
}
#endregion
#region 数据解析方法
///
/// 从寄存器数组解析字符串 - 大端模式,每个寄存器包含2个字符
///
/// 寄存器数组
/// 解析出的字符串
private string ParseStringFromRegisters(int[] registers)
{
var bytes = new List();
foreach (var register in registers)
{
// 大端模式:高字节在前,低字节在后
bytes.Add((byte)((register >> 8) & 0xFF)); // 高字节
bytes.Add((byte)(register & 0xFF)); // 低字节
}
// 移除空字符并转换为字符串
var validBytes = bytes.TakeWhile(b => b != 0).ToArray();
return Encoding.ASCII.GetString(validBytes);
}
///
/// 从寄存器数组解析时间戳 - 格式: YYYYMMDDHHMMSS
///
/// 时间戳寄存器数组(7个寄存器)
/// 解析出的DateTime
private DateTime ParseTimestampFromRegisters(int[] registers)
{
try
{
// 将寄存器数据转换为字符串
var timestampString = ParseStringFromRegisters(registers);
// 解析时间戳字符串 (格式: YYYYMMDDHHMMSS)
if (timestampString.Length >= 14)
{
var year = int.Parse(timestampString.Substring(0, 4));
var month = int.Parse(timestampString.Substring(4, 2));
var day = int.Parse(timestampString.Substring(6, 2));
var hour = int.Parse(timestampString.Substring(8, 2));
var minute = int.Parse(timestampString.Substring(10, 2));
var second = int.Parse(timestampString.Substring(12, 2));
return new DateTime(year, month, day, hour, minute, second);
}
LogWarning($"时间戳格式无效: {timestampString}");
return DateTime.MinValue;
}
catch (Exception ex)
{
LogError($"解析时间戳失败: {ex.Message}");
return DateTime.MinValue;
}
}
#endregion
#region 数据发送方法
///
/// 向PLC发送数据读取确认信号
///
private async Task SendDataReadConfirmationAsync()
{
try
{
// 向PLC发送确认信号 (置1表示MES已成功读取数据)
await Task.Run(() =>
_modbusClient.WriteSingleRegister(MES_DATA_OK_ADDRESS, 1));
LogDebug("已向PLC发送数据读取确认信号");
}
catch (Exception ex)
{
LogError($"发送数据确认信号失败: {ex.Message}");
throw;
}
}
#endregion
#region 错误处理方法
///
/// 处理连接丢失
///
private async Task HandleConnectionLoss()
{
if (_isConnected)
{
LogWarning("检测到连接丢失,正在尝试重新连接...");
Disconnect();
// 等待3秒后重试连接
await Task.Delay(3000);
if (!_cancellationTokenSource.Token.IsCancellationRequested)
{
await ConnectAsync();
}
}
}
#endregion
#region 日志方法
private void LogInfo(string message)
{
Console.WriteLine($"[INFO] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
}
private void LogWarning(string message)
{
Console.WriteLine($"[WARN] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
}
private void LogError(string message)
{
Console.WriteLine($"[ERROR] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
ErrorOccurred?.Invoke(this, message);
}
private void LogDebug(string message)
{
Console.WriteLine($"[DEBUG] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
}
#endregion
#region IDisposable实现
public void Dispose()
{
_cancellationTokenSource?.Cancel();
_heartbeatTimer?.Stop();
_heartbeatTimer?.Dispose();
_dataPollingTimer?.Stop();
_dataPollingTimer?.Dispose();
Disconnect();
// EasyModbus的ModbusClient没有实现IDisposable接口
_modbusClient = null;
_cancellationTokenSource?.Dispose();
}
#endregion
}
///
/// PLC数据结构 - 包含从PLC读取的所有数据
///
public class PlcData
{
///
/// PLC心跳包状态 (0或1)
///
public byte PlcHeartbeat { get; set; }
///
/// PLC数据准备就绪信号 (1表示数据已准备好)
///
public byte DataOkSignal { get; set; }
///
/// 产品型号 (10字节字符串)
///
public string ProductModel { get; set; } = string.Empty;
///
/// 测试工位号
///
public byte TestStation { get; set; }
///
/// 产品码 (10字节字符串)
///
public string ProductCode { get; set; } = string.Empty;
///
/// 测试结果 (1表示通过,0表示失败)
///
public byte TestResult { get; set; }
///
/// 记录时间戳
///
public DateTime Timestamp { get; set; }
///
/// 最小安装尺寸 (毫米,保留2位小数)
///
public double MinInstallSize { get; set; }
///
/// 最大安装尺寸 (毫米,保留2位小数)
///
public double MaxInstallSize { get; set; }
///
/// 行程 (毫米,保留2位小数)
///
public double Stroke { get; set; }
///
/// 速度 (单位根据具体需求确定,保留2位小数)
///
public double Speed { get; set; }
///
/// 工作电压 (伏特,保留2位小数)
///
public double WorkingVoltage { get; set; }
///
/// 工作电流 (安培,保留2位小数)
///
public double WorkingCurrent { get; set; }
///
/// 工作压力 (压力单位,保留2位小数)
///
public double WorkingPressure { get; set; }
///
/// 上升霍尔传感器读数 (保留2位小数)
///
public double UpHallSensor { get; set; }
///
/// 数据读取时间 (本地时间)
///
public DateTime ReadTime { get; set; }
}
}