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; } } }