using NetWorkHelper; using NetWorkHelper.IModels; using NetWorkHelper.TCP; using SLC1_N; using Sunny.UI; using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using System.Xml.Linq; using static System.Net.Mime.MediaTypeNames; namespace SLC1_N { public class ModbusTCP_28 // 28仪器线程类 { private Thread RunThread; // 线程 public bool[] ArrCoil = new bool[10]; public uint[] ArrRegister = new uint[100]; public OriginClient chxclient; public string IP; // ip public bool isthread = true; // 线程标志位 private bool disposed = false; // 添加IDisposable接口 public bool isRunning { get; set; } // 运行标志位 private string CHX4; // 记录站号的4位十六进制 //public bool isReadRegister = false; // 读取寄存器开关标志位 public ModbusTCP_28(int CH) { isRunning = false; CHX4 = CH.ToString("X4"); //Console.WriteLine($"将地址转为4位十六进制: {CHX4}和{CHX4.Substring(2, 2)}01"); // 初始化数组 for (int i = 0; i < ArrCoil.Length; i++) { ArrCoil[i] = false; } // 初始化数组 for (int i = 0; i < ArrRegister.Length; i++) { ArrRegister[i] = 0; } chxclient = new OriginClient(CH); } // 连接 public void Connect(string ip) { try { IP = ip; // 先取消事件订阅 if (chxclient != null) { chxclient.OnDataReceived -= Slot_CH1DataReceived; chxclient.Disconnect(); } // 新连接 chxclient.Connect("2000", IP, "9999"); chxclient.OnDataReceived += Slot_CH1DataReceived; } catch (Exception ex) { mxlLog.Instance.Error($"ModbusTCP_28[{CHX4}]重连失败: {ex.Message}"); } } // 断开 public void Disconnect() { if (chxclient.IsConnect) { chxclient.Disconnect(); } } /* * 连接标志位仅检查TCP连接是否建立 * 但Modbus设备可能还未准备好通信(设备启动延迟、端口未完全初始化等 * 加点延时 */ // 启动通信线程 public void Start() { if (RunThread == null || !RunThread.IsAlive) { isthread = true; RunThread = new Thread(() => { Thread.Sleep(500); // 首次启动延迟(重要) run(); }); RunThread.IsBackground = true; // 设为后台线程 RunThread.Start(); } } ~ModbusTCP_28() { Dispose(false); } // 通信主循环 private void run() { int sleepcount = 2; while (isthread) { try { if (chxclient.IsConnect) { //Console.WriteLine($"{CHX4}-[读取]主循环 T: {chxclient.IsConnect}"); if (sleepcount > 0) // 防止刚开始连接 不稳定 加延时 { sleepcount--; Thread.Sleep(250); } Thread.Sleep(250); readCoil(); Thread.Sleep(250); readRegister(); } else { //Console.WriteLine($"{CHX4}-[读取]主循环 F: {chxclient.IsConnect}"); isRunning = false; } } catch { mxlLog.Instance.Error($"ModbusTCP_28[{CHX4}]-[读取] 异常: {chxclient.IsConnect}"); isRunning = false; Thread.Sleep(1000); } } } // 停止方法 public void Stop() { isthread = false; if (RunThread != null && RunThread.IsAlive) { RunThread.Join(1000); // 等待线程结束,最多1秒 } } // 实现IDisposable public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } // 释放资源 protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { Stop(); Disconnect(); chxclient?.Dispose(); } disposed = true; } } // 接收函数 private void Slot_CH1DataReceived(string str, int idata) { //Console.WriteLine($"{CHX4}-[接收]数据:{str} idata:{idata}"); isRunning = true; // 收到数据才算通讯正常。可以排除ip正确,但通道不正确的情况 if (str.Length > 6 && str.Substring(0, 4) == $"{CHX4.Substring(2, 2)}01") { ParseCoilResponse(str, ref ArrCoil); } else if (str.Length > 6 && str.Substring(0, 4) == $"{CHX4.Substring(2, 2)}03") { ParseRegisterResponse(str, ref ArrRegister); } } // 解析线圈状态响应 private void ParseCoilResponse(string response, ref bool[] targetArray) { try { if (response.Length < 6 || !response.StartsWith($"{CHX4.Substring(2, 2)}01")) return; int byteCount = Convert.ToInt32(response.Substring(4, 2), 16); // 提取字节数(第3字节) int dataIndex = 6; // 提取线圈数据(从第4字节开始) for (int i = 0; i < byteCount; i++) { if (dataIndex + 2 > response.Length) break; byte coilData = Convert.ToByte(response.Substring(dataIndex, 2), 16); dataIndex += 2; // 解析每个Bit(8个线圈) for (int bit = 0; bit < 8; bit++) { int coilIndex = i * 8 + bit; // 计算线圈全局索引 if (coilIndex >= targetArray.Length) break; targetArray[coilIndex] = (coilData & (1 << bit)) != 0 ? true : false; } } } catch (Exception ex) { Console.WriteLine($"解析线圈状态出错: {ex.Message}"); mxlLog.Instance.Error($"ModbusTCP_28[{CHX4}] 解析线圈状态出错", ex); } } // 解析寄存器响应 private void ParseRegisterResponse(string response, ref uint[] targetArray) { try { if (response.Length < 250 || !response.StartsWith($"{CHX4.Substring(2, 2)}03")) // 实际正常收到长度254 return; int dataIndex = 6; // 数据开始位置 int registerIndex = 0; while (dataIndex + 4 <= response.Length && registerIndex < targetArray.Length) { // 每2字节(4个十六进制字符)表示一个寄存器值 targetArray[registerIndex++] = Convert.ToUInt32(response.Substring(dataIndex, 4), 16); dataIndex += 4; } } catch (Exception ex) { Console.WriteLine($"解析寄存器值出错: {ex.Message}"); mxlLog.Instance.Error($"ModbusTCP_28[{CHX4}] 解析寄存器值出错", ex); } } // 读取线圈 public void readCoil() { if (!chxclient.IsConnect) return; chxclient?.SendData($"{CHX4.Substring(2, 2)} 01 00 00 00 07"); // 0开始读7个 } // 读取寄存器 private void readRegister() { if (!chxclient.IsConnect) return; chxclient?.SendData($"{CHX4.Substring(2, 2)} 03 03 E8 00 3D "); // 读取 1000 - 1061 } // 读取浮点 public string readFloat(int index) // addr 是十进制地址(如 1013) { try { ushort low = (ushort)ArrRegister[index]; // 低位 ushort high = (ushort)ArrRegister[index + 1]; // 高位 float fdata = TwoUInt16ToFloat(high, low); return fdata.ToString(); // 返回字符串形式的浮点数 } catch (Exception ex) { mxlLog.Instance.Error($"ModbusTCP_28[{CHX4}] 解析浮点数出错", ex); } return "0"; } // 读取浮点 public string readFloatF2(int index) // addr 是十进制地址(如 1013) { try { ushort low = (ushort)ArrRegister[index]; // 低位 ushort high = (ushort)ArrRegister[index + 1]; // 高位 float fdata = TwoUInt16ToFloat(high, low); return fdata.ToString("F2"); // 返回字符串形式的浮点数 } catch (Exception ex) { mxlLog.Instance.Error($"ModbusTCP_28[{CHX4}] 解析浮点数出错", ex); } return "0"; } // 写线圈 public void writeCoil(string straddress, bool bdata) { if (!chxclient.IsConnect) return; try { int address = Convert.ToInt32(straddress); string hexAddress = address.ToString("X4"); // 将地址转为4位十六进制(如 "1" → "0001") if(bdata) { chxclient?.SendData($"{CHX4.Substring(2, 2)} 05 00 {hexAddress.Substring(2, 2)} FF 00"); } else { chxclient?.SendData($"{CHX4.Substring(2, 2)} 05 00 {hexAddress.Substring(2, 2)} 00 00"); } } catch (Exception ex) { MessageBox.Show($"输入错误: {ex.Message}"); mxlLog.Instance.Error($"ModbusTCP_28[{CHX4}] writeCoil输入错误", ex); } } // 写入单个保持寄存器 public void writeRegister(int address, int value) { if (!chxclient.IsConnect) return; try { string hexAddress = address.ToString("X4"); // 将地址转为4位十六进制(如 "1" → "0001") string hexValue = value.ToString("X4"); string message = $"{CHX4.Substring(2, 2)} 06 {hexAddress.Substring(0, 2)} {hexAddress.Substring(2, 2)} {hexValue.Substring(0, 2)} {hexValue.Substring(2, 2)}"; chxclient?.SendData(message); } catch (Exception ex) { MessageBox.Show($"输入错误: {ex.Message}"); mxlLog.Instance.Error($"ModbusTCP_28[{CHX4}] writeRegister输入错误", ex); } } //// 写入多个保持寄存器(功能码16),每个int占用2个寄存器(32位) //public void writeRegisters(int address, int[] values) //{ // if (!chxclient.IsConnect || values == null || values.Length == 0) // return; // try // { // string hexAddress = address.ToString("X4"); // int regCount = values.Length * 2; // 每个int占2个寄存器 // string message = $"{CHX4.Substring(2, 2)} 10 " + // 站号 + 功能码 // $"{hexAddress.Substring(0, 2)} {hexAddress.Substring(2, 2)} " + // 地址 // //$"{(regCount >> 8):X2} {(regCount & 0xFF):X2} " + // 寄存器数量 // $"{(regCount * 2):X2}"; // 字节数 // foreach (int val in values) // { // byte[] bytes = BitConverter.GetBytes(val); // if (BitConverter.IsLittleEndian) // Array.Reverse(bytes); // 转为大端序 // message += $" {bytes[0]:X2}{bytes[1]:X2}{bytes[2]:X2}{bytes[3]:X2}"; // } // chxclient.SendData(message); // } // catch (Exception ex) // { // Console.WriteLine($"写入多寄存器(int)失败: {ex.Message}"); // } //} // 修改为:写入多个保持寄存器(功能码16),每个int对应1个寄存器 public void writeRegisters(int address, int[] values) { if (!chxclient.IsConnect || values == null || values.Length == 0) return; try { string hexAddress = address.ToString("X4"); int regCount = values.Length; // 每个int对应1个寄存器 int byteCount = regCount * 2; // 构建报文头 string message = $"{CHX4.Substring(2, 2)} 10 " + // 站号 + 功能码 $"{hexAddress.Substring(0, 2)} {hexAddress.Substring(2, 2)} " + // 地址 $"{(regCount >> 8):X2} {(regCount & 0xFF):X2} " + // 寄存器数量(关键修复!) $"{byteCount:X2} "; // 字节数 // 添加所有数据 foreach (int val in values) { // 将int拆分为两个字节(大端序) ushort value = (ushort)val; // 只取低16位 message += $"{(value >> 8):X2} {(value & 0xFF):X2} "; } Console.WriteLine($"多个保持寄存器 writeRegisters(): {message}"); chxclient?.SendData(message.Replace(" ", "")); // 移除空格 } catch (Exception ex) { Console.WriteLine($"写入多寄存器失败: {ex.Message}"); mxlLog.Instance.Error($"ModbusTCP_28[{CHX4}] 写入多寄存器失败", ex); } } /* 高16位(1014): 0x4048 低16位(1013): 0xF5C3 完整32位: 0x4048F5C3 */ // 写入浮点 public void writeFloat(int address, float value) { if (!chxclient.IsConnect) return; try { byte[] floatBytes = BitConverter.GetBytes(value); // float转换为4字节(IEEE 754格式) ushort highRegister = BitConverter.ToUInt16(floatBytes, 2); // 高16位 ushort lowRegister = BitConverter.ToUInt16(floatBytes, 0); // 低16位 string hexAddress = address.ToString("X4"); // 4位十六进制地址 string message = string.Format( "{0} 10 {1} {2} 00 02 04 {3:X2}{4:X2} {5:X2}{6:X2}", CHX4.Substring(2, 2), // 站号 hexAddress.Substring(0, 2), // 起始地址高字节 hexAddress.Substring(2, 2), // 起始地址低字节 (byte)(lowRegister >> 8), // 低寄存器高字节 (byte)lowRegister, // 低寄存器低字节 (byte)(highRegister >> 8), // 高寄存器高字节 (byte)highRegister // 高寄存器低字节 ); // 4. 自动添加CRC校验(假设SendData会自动添加) chxclient?.SendData(message); //Console.WriteLine($"writeFloat(): {message}"); //chxclient.SendData("01 10 03 F7 00 02 04 F5 C3 40 48"); // 数据是3.14 站号1功能10,1015写两个字节 1015-F5C3 1016-4048 } catch (Exception ex) { MessageBox.Show($"写入浮点数失败: {ex.Message}"); mxlLog.Instance.Error($"ModbusTCP_28[{CHX4}] 写入浮点数失败", ex); } } // 写入多个浮点数(功能码16),每个float占用2个寄存器(32位) public void writeFloats(int address, float[] values) { if (!chxclient.IsConnect || values == null || values.Length == 0) return; try { // 构造报文头 string hexAddress = address.ToString("X4"); int regCount = values.Length * 2; // 每个float占2个寄存器 string message = $"{CHX4.Substring(2, 2)} 10 " + // 站号 + 功能码 $"{hexAddress.Substring(0, 2)} {hexAddress.Substring(2, 2)} " + // 地址 $"{(regCount >> 8):X2} {(regCount & 0xFF):X2} " + // 寄存器数量 $"{(regCount * 2):X2}"; // 字节数 foreach (float val in values) { byte[] bytes = BitConverter.GetBytes(val); if (BitConverter.IsLittleEndian) Array.Reverse(bytes); // 转为大端序 // 拆分为两个16位寄存器(高16位在前) ushort high = BitConverter.ToUInt16(bytes, 0); ushort low = BitConverter.ToUInt16(bytes, 2); message += $" {(high >> 8):X2}{(high & 0xFF):X2} {(low >> 8):X2}{(low & 0xFF):X2}"; } chxclient?.SendData(message); // 假设自动添加CRC } catch (Exception ex) { Console.WriteLine($"写入多浮点数失败: {ex.Message}"); mxlLog.Instance.Error($"ModbusTCP_28[{CHX4}] 写入多浮点数失败", ex); } } //UInt16转float public static float TwoUInt16ToFloat(UInt16 high, UInt16 low) { Int32 sum = (high << 16) + (low & 0xFFFF); byte[] bs = BitConverter.GetBytes(sum); float fs = BitConverter.ToSingle(bs, 0); return fs; } } public enum _28Addr { [Description("参数编号")] ParameterNumber = 1000, // 03E8 [Description("当前参数名称")] CurrentParameterName1 = 1001, // 3E9 [Description("当前参数名称")] CurrentParameterName2 = 1002, // 3EA [Description("当前参数名称")] CurrentParameterName3 = 1003, // 3EB [Description("当前参数名称")] CurrentParameterName4 = 1004, // 3EC [Description("当前参数名称")] CurrentParameterName5 = 1005, // 3ED [Description("充气时间")] InflationTime = 1006, // 3EE [Description("平衡时间")] BalanceTime = 1007, // 3EF [Description("检测时间")] DetectionTime = 1008, // 3F0 [Description("排气时间")] ExhaustTime = 1009, // 3F1 [Description("解除输出2时间")] ReleaseOutput2Time = 1010, // 3F2 [Description("延时1")] Delay1 = 1011, // 3F3 [Description("延时2")] Delay2 = 1012, // 3F4 [Description("充气压力上限低位")] InflationPressureUpperLimitLow = 1013, // 3F5 [Description("充气压力上限高位")] InflationPressureUpperLimitHigh = 1014, // 3F6 [Description("充气压力下限低位")] InflationPressureLowerLimitLow = 1015, // 3F7 [Description("充气压力下限高位")] InflationPressureLowerLimitHigh = 1016, // 3F8 [Description("平衡压差上限低位")] BalancePressureDiffUpperLimitLow = 1017, // 3F9 [Description("平衡压差上限高位")] BalancePressureDiffUpperLimitHigh = 1018, // 3FA [Description("平衡压差下限低位")] BalancePressureDiffLowerLimitLow = 1019, // 3FB [Description("平衡压差下限高位")] BalancePressureDiffLowerLimitHigh = 1020, // 3FC [Description("允许泄漏量上限低位")] AllowableLeakageUpperLimitLow = 1021, // 3FD [Description("允许泄漏量上限高位")] AllowableLeakageUpperLimitHigh = 1022, // 3FE [Description("允许泄漏量下限低位")] AllowableLeakageLowerLimitLow = 1023, // 3FF [Description("允许泄漏量下限高位")] AllowableLeakageLowerLimitHigh = 1024, // 400 [Description("等效容积低位")] EquivalentVolumeLow = 1025, // 401 [Description("等效容积高位")] EquivalentVolumeHigh = 1026, // 402 [Description("压力单位")] PressureUnit = 1027, // 403 [Description("泄漏量单位")] LeakageUnit = 1028, // 404 [Description("仪器状态")] DeviceStatus = 1034, // 40A [Description("大漏泄漏量低位(平衡压差)")] LargeLeakageLow = 1035, // 40B [Description("大漏泄漏量高位(平衡压差)")] LargeLeakageHigh = 1036, // 40C [Description("大漏泄漏量单位(压差单位)")] LargeLeakageUnit = 1037, // 40D [Description("微漏泄漏量低位(泄漏量)")] MicroLeakageLow = 1038, // 40E [Description("微漏泄漏量高位(泄漏量)")] MicroLeakageHigh = 1039, // 40F [Description("微漏泄漏量单位(泄漏量)")] MicroLeakageUnit = 1040, // 410 [Description("测试结果")] TestResult = 1041, // 411 [Description("后台压力低位")] BackgroundPressureLow = 1048, // 418 [Description("后台压力高位")] BackgroundPressureHigh = 1049, // 419 [Description("后台压差低位")] BackgroundPressureDiffLow = 1050, // 41A [Description("后台压差高位")] BackgroundPressureDiffHigh = 1051, // 41B [Description("记录-测试压力低位")] RecordTestPressureLow = 1057, // 421 [Description("记录-测试压力高位")] RecordTestPressureHigh = 1058, // 422 [Description("记录-压力单位")] RecordPressureUnit1 = 1059, // 423 [Description("记录-压力单位")] RecordPressureUnit2 = 1060, // 424 [Description("记录-压力单位")] RecordPressureUnit3 = 1061, // 0425 // 下面地址无法读取 [Description("记录-测试泄漏量低位")] RecordTestLeakageLow = 1062, // 426 [Description("记录-测试泄漏量高位")] RecordTestLeakageHigh = 1063, // 427 [Description("记录-泄漏量单位")] RecordLeakageUnit1 = 1064, // 428 [Description("记录-泄漏量单位")] RecordLeakageUnit2 = 1065, // 429 [Description("记录-泄漏量单位")] RecordLeakageUnit3 = 1066, // 42A [Description("记录-泄漏量单位")] RecordLeakageUnit4 = 1067 // 42B } // 原FrmClient类 #region 原FrmClient类 public class OriginClient { private SocketState _connectState { get; set; }// 连接状态 private ITcpClient _client { get; set; }// 连接对象 private System.Timers.Timer _timer { get; set; } // 定时器 public bool IsConnect { get; private set; } = false;// 连接状态标志 public int ChannelId { get; private set; }// 通道标识 public event Action OnDataReceived;// 数据接收事件 public event Action OnConnectionStateChanged;// 连接状态变化事件 /// /// 初始化客户端 /// /// 通道标识 public OriginClient(int channelId) { ChannelId = channelId; InitializeClient(); } private void InitializeClient() { _client = new ITcpClient { IsReconnection = true }; _client.OnStateInfo += Client_OnStateInfo; _client.OnRecevice += Client_OnRecevice; _client.OnErrorMsg += Client_OnErrorMsg; } /// /// 将字符串转换为十六进制字节数组 /// private static byte[] StrtoHexbyte(string hexstring) { int i; hexstring = hexstring.Replace(" ", ""); if ((hexstring.Length % 2) != 0) { byte[] returnbytes = new byte[(hexstring.Length + 1) / 2]; for (i = 0; i < (hexstring.Length - 1) / 2; i++) { returnbytes[i] = Convert.ToByte(hexstring.Substring(i * 2, 2), 16); } returnbytes[returnbytes.Length - 1] = Convert.ToByte(hexstring.Substring(hexstring.Length - 1, 1).PadLeft(2, '0'), 16); return returnbytes; } else { byte[] returnBytes = new byte[hexstring.Length / 2]; for (i = 0; i < returnBytes.Length; i++) { returnBytes[i] = Convert.ToByte(hexstring.Substring(i * 2, 2), 16); } return returnBytes; } } /// /// 计算CRC冗余码 /// private int Crc16_Modbus(byte[] modbusdata, int length) { int i, j; int crc = 0xffff; // 0xffff or 0 for (i = 0; i < length; i++) { crc ^= modbusdata[i] & 0xff; for (j = 0; j < 8; j++) { if ((crc & 0x01) == 1) { crc = (crc >> 1) ^ 0xa001; } else { crc >>= 1; } } } return crc; } /// /// 连接服务器 /// connectinterval:心跳包发送间隔 0不发 /// public void Connect(string connectinterval, string ipaddress, string port) { try { if (!_client.IsStart) { _client.IsReconnection = true; int interval = int.Parse(connectinterval); _timer = new System.Timers.Timer { Interval = 1000 * interval }; _timer.Elapsed += Timer_Elapsed; _timer.Start(); _client.ServerIp = ipaddress; _client.ServerPort = int.Parse(port); _client.StartConnect(); } } catch (Exception ex) { MessageBox.Show(ex.Message); MessageBox.Show(ex.StackTrace); Disconnect(); throw; } } private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { // 可以在这里实现定时发送逻辑 } /// /// 连接错误处理 /// private void Client_OnErrorMsg(object sender, NetWorkHelper.ICommond.TcpClientErrorEventArgs e) { IsConnect = false; OnConnectionStateChanged?.Invoke(IsConnect, ChannelId); } /// /// 接收数据处理 /// private void Client_OnRecevice(object sender, NetWorkHelper.ICommond.TcpClientReceviceEventArgs e) { var len = e.Data.Length; if (len > 0) { string returnStr = ""; for (int i = 0; i < len; i++) { returnStr += e.Data[i].ToString("X2"); } var message = returnStr; string value = message.Replace(" ", ""); OnDataReceived?.Invoke(message, ChannelId); } } /// /// 连接状态处理 /// private void Client_OnStateInfo(object sender, NetWorkHelper.ICommond.TcpClientStateEventArgs e) { _connectState = e.State; if (e.StateInfo.Contains("已断开服务器连接")) { Disconnect(); } else if (e.StateInfo.Contains("已连接")) { IsConnect = true; OnConnectionStateChanged?.Invoke(IsConnect, ChannelId); } else { IsConnect = false; OnConnectionStateChanged?.Invoke(IsConnect, ChannelId); } } object sendLock = new object(); /// /// 发送数据 /// public void SendData(string sendMsg) { if (!string.IsNullOrWhiteSpace(sendMsg) && IsConnect) { try { byte[] byt = StrtoHexbyte(sendMsg); int str2 = Crc16_Modbus(byt, byt.Length); string str3 = Convert.ToString((str2 >> 8) & 0xff, 16); string str4 = Convert.ToString(str2 & 0xff, 16); if (str3.Length == 1) { str3 = "0" + str3; } if (str4.Length == 1) { str4 = "0" + str4; } sendMsg = sendMsg + str4 + str3; var message = StrtoHexbyte(sendMsg); if (_client != null) { lock (sendLock) { _client?.SendData(message); } } } catch (Exception ex) { MessageBox.Show(ex.Message); MessageBox.Show(ex.StackTrace); throw; } } } /// /// 断开连接 /// public void Disconnect() { if (_timer != null) { _timer.Stop(); _timer.Dispose(); _timer = null; } if (_client != null) { _client.IsReconnection = false; _client.StopConnect(); _client.Dispose(); } IsConnect = false; OnConnectionStateChanged?.Invoke(IsConnect, ChannelId); } /// /// 释放资源 /// public void Dispose() { Disconnect(); } } #endregion }