using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Net.Sockets; using System.Text; using System.Windows.Forms; using UAS_MES_NEW.DataOperate; using UAS_MES_NEW.Entity; using UAS_MES_NEW.PublicMethod; namespace UAS_MES_NEW.Make { public partial class Make_PLCMonitor : Form { AutoSizeFormClass asc = new AutoSizeFormClass(); DataHelper dh; // Modbus TCP 连接对象(保持长连接,断线自动重连) TcpClient plcClient; NetworkStream plcStream; bool connected = false; ushort transactionId = 0; readonly object readLock = new object(); // 台达PLC数据寄存器:D寄存器对应Modbus保持寄存器(功能码0x03) // ETH-DVP-2P模块手册寄存器表:D0~4095 的Modbus地址 = 0x1000 + D编号 // 如 D141 → 0x1000+141 = 0x108D;D4096以上是另一段(0x9000起),当前用不到 const int D141_ADDR = 141; const int DVP_D_OFFSET = 0x1000; const int READ_COUNT = 2; // 温度记录表:每次采集往里插一条(1#舱、2#舱各一条) // TL_ID 用序列 TEMPLOG_SEQ,TL_DATE 取sysdate,TL_DEVICEID 存舱号,TL_TP 存换算后的温度值 const string TempLogTable = "TEMPLOG"; const string TempLogSeq = "TEMPLOG_SEQ"; public Make_PLCMonitor() { InitializeComponent(); } private void Make_PLCMonitor_Load(object sender, EventArgs e) { asc.controllInitializeSize(this); dh = SystemInf.dh; ip.Text = "192.168.1.150"; port.Value = 502; unitid.Value = 1; interval.Value = 2; decimals.Value = 1; SetStatus("未连接"); } /// /// 连接串口转网口模块(Modbus TCP) /// private void Connect_Click(object sender, EventArgs e) { try { ClosePlc(); plcClient = new TcpClient(); IAsyncResult ar = plcClient.BeginConnect(ip.Text.Trim(), (int)port.Value, null, null); if (!ar.AsyncWaitHandle.WaitOne(3000)) { ClosePlc(); SetStatus("连接超时"); MessageBox.Show("连接PLC超时,请检查IP和端口!"); return; } plcClient.EndConnect(ar); plcClient.ReceiveTimeout = 5000; plcClient.SendTimeout = 5000; plcStream = plcClient.GetStream(); connected = true; SetStatus("已连接 " + ip.Text.Trim() + ":" + port.Value); OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 已连接 " + ip.Text.Trim() + ":" + port.Value + ",站号" + unitid.Value + "\n"); timer1.Interval = (int)interval.Value * 1000; timer1.Start(); // 连接成功后立即读一次 ReadNow_Click(null, null); } catch (Exception ex) { ClosePlc(); SetStatus("连接失败:" + ex.Message); MessageBox.Show("连接失败:" + ex.Message); } } /// /// 断开连接 /// private void Disconnect_Click(object sender, EventArgs e) { timer1.Stop(); ClosePlc(); SetStatus("未连接"); OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 已断开连接\n"); } /// /// 立即读取一次 /// private void ReadNow_Click(object sender, EventArgs e) { if (!connected) { SetStatus("未连接"); return; } try { // D寄存器地址要加台达DVP的0x1000偏移:D141→0x108D, D142→0x108E int[] values = ReadRegisters((ushort)(DVP_D_OFFSET + D141_ADDR), READ_COUNT); ShowValues(values); InsertTempLog(values); SetStatus("读取成功 " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); } catch (Exception ex) { // 读取失败:断开连接,等待下一次重连 timer1.Stop(); ClosePlc(); connected = false; string mode = rdoTCP.Checked ? "ModbusTCP" : "ModbusRTU(透传)"; SetStatus("读取失败:" + ex.Message); OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 读取失败(" + mode + "):" + ex.Message + "\n"); if (ex.Message.Contains("超时") || ex.Message.Contains("没有正确答复") || ex.Message.Contains("连接已断开")) { OperatResult.AppendText("[提示] 如果RTU(透传)模式一直超时,请尝试切换到ModbusTCP模式;反之亦然。同时确认模块透传设置、PLC串口参数(9600/8/N/1)和站号。\n"); } // 自动重连 TryReconnect(); } } /// /// 定时获取数据 /// private void timer1_Tick(object sender, EventArgs e) { if (!connected) { TryReconnect(); return; } ReadNow_Click(null, null); } /// /// 尝试重新连接(不打断界面,仅更新状态) /// private void TryReconnect() { try { ClosePlc(); plcClient = new TcpClient(); IAsyncResult ar = plcClient.BeginConnect(ip.Text.Trim(), (int)port.Value, null, null); if (!ar.AsyncWaitHandle.WaitOne(3000)) { ClosePlc(); SetStatus("重连超时,继续重试..."); return; } plcClient.EndConnect(ar); plcClient.ReceiveTimeout = 5000; plcClient.SendTimeout = 5000; plcStream = plcClient.GetStream(); connected = true; SetStatus("重连成功 " + DateTime.Now.ToString("HH:mm:ss")); OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 重连成功\n"); } catch { ClosePlc(); SetStatus("重连失败,继续重试..."); } } /// /// 把本次采集的温度写入TEMPLOG表,1#舱、2#舱各一条 /// TL_ID=序列TEMPLOG_SEQ,TL_DATE=sysdate,TL_DEVICEID=舱号,TL_TP=换算后的温度 /// private void InsertTempLog(int[] values) { if (values == null || values.Length < 2) return; try { int d = (int)decimals.Value; double v1 = values[0] / Math.Pow(10, d); double v2 = values[1] / Math.Pow(10, d); string v1s = v1.ToString("0.###"); string v2s = v2.ToString("0.###"); dh.ExecuteSql("insert into " + TempLogTable + "(TL_ID,TL_DATE,TL_DEVICEID,TL_TP)values(" + TempLogSeq + ".nextval,sysdate,'1#'," + v1s + ")", "insert"); dh.ExecuteSql("insert into " + TempLogTable + "(TL_ID,TL_DATE,TL_DEVICEID,TL_TP)values(" + TempLogSeq + ".nextval,sysdate,'2#'," + v2s + ")", "insert"); } catch (Exception ex) { // 插入失败不影响采集显示,只记录 OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 写入" + TempLogTable + "失败:" + ex.Message + "\n"); } } /// /// 显示温度值,并根据小数位设置换算(台达一般存实际值x10) /// private void ShowValues(int[] values) { if (values == null || values.Length < 2) return; int d = (int)decimals.Value; double v1 = values[0] / Math.Pow(10, d); double v2 = values[1] / Math.Pow(10, d); string fmt = d > 0 ? "F" + d : "F0"; lblD141Value.Text = v1.ToString(fmt); lblD142Value.Text = v2.ToString(fmt); OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 1#舱(D141)=" + values[0] + " 2#舱(D142)=" + values[1] + "\n"); } /// /// Modbus TCP 读保持寄存器(功能码0x03) /// /// /// 读保持寄存器(功能码0x03),按界面选择的协议分发: /// ModbusRTU(串口透传):模块把TCP数据原样转发到串口,要发带CRC16的RTU裸帧 /// ModbusTCP:模块/PLC本身是标准Modbus TCP服务端,发带MBAP头的帧 /// private int[] ReadRegisters(ushort startAddr, ushort count) { if (rdoTCP.Checked) { return ReadTcpRegisters(startAddr, count); } return ReadRtuRegisters(startAddr, count); } /// /// Modbus RTU over TCP(串口透传模式):站号+功能码+数据+CRC16 /// private int[] ReadRtuRegisters(ushort startAddr, ushort count) { lock (readLock) { if (!connected || plcStream == null) { throw new Exception("未连接PLC"); } byte unit = (byte)unitid.Value; byte[] req = new byte[8]; req[0] = unit; // 功能码0x03 读保持寄存器 req[1] = 0x03; req[2] = (byte)(startAddr >> 8); req[3] = (byte)(startAddr & 0xFF); req[4] = (byte)(count >> 8); req[5] = (byte)(count & 0xFF); ushort crc = CalcCRC16(req, 6); // Modbus RTU CRC低字节在前 req[6] = (byte)(crc & 0xFF); req[7] = (byte)((crc >> 8) & 0xFF); plcStream.Write(req, 0, req.Length); // 响应:站号+功能码+字节数 byte[] head = ReadBytes(plcStream, 3); if (head[0] != unit) { throw new Exception("响应站号不匹配(收到0x" + head[0].ToString("X2") + "),请检查站号设置"); } if ((head[1] & 0x80) != 0) { byte[] rest = ReadBytes(plcStream, 3); throw new Exception("PLC返回异常码 0x" + rest[0].ToString("X2")); } int byteCount = head[2]; byte[] rest2 = ReadBytes(plcStream, byteCount + 2); // CRC校验:响应最后2字节是CRC byte[] full = new byte[3 + byteCount + 2]; Array.Copy(head, 0, full, 0, 3); Array.Copy(rest2, 0, full, 3, byteCount + 2); ushort calcCrc = CalcCRC16(full, full.Length - 2); ushort recvCrc = (ushort)(full[full.Length - 2] | (full[full.Length - 1] << 8)); if (calcCrc != recvCrc) { throw new Exception("响应CRC校验失败,串口参数(波特率/校验位)可能与PLC不一致"); } int[] values = new int[count]; for (int i = 0; i < count; i++) { values[i] = (rest2[i * 2] << 8) | rest2[i * 2 + 1]; } return values; } } /// /// Modbus TCP 读保持寄存器(功能码0x03),带MBAP头 /// private int[] ReadTcpRegisters(ushort startAddr, ushort count) { lock (readLock) { if (!connected || plcStream == null) { throw new Exception("未连接PLC"); } transactionId++; byte[] req = new byte[12]; // MBAP头:事务标识、协议标识0、长度6、单元标识符 req[0] = (byte)(transactionId >> 8); req[1] = (byte)(transactionId & 0xFF); req[2] = 0x00; req[3] = 0x00; req[4] = 0x00; req[5] = 0x06; req[6] = (byte)unitid.Value; // 功能码0x03 读保持寄存器 req[7] = 0x03; req[8] = (byte)(startAddr >> 8); req[9] = (byte)(startAddr & 0xFF); req[10] = (byte)(count >> 8); req[11] = (byte)(count & 0xFF); plcStream.Write(req, 0, req.Length); byte[] head = ReadBytes(plcStream, 9); if ((head[7] & 0x80) != 0) { byte[] ec = ReadBytes(plcStream, 1); throw new Exception("PLC返回异常码 0x" + (ec.Length > 0 ? ec[0] : 0).ToString("X2")); } int byteCount = head[8]; byte[] data = ReadBytes(plcStream, byteCount); int[] values = new int[count]; for (int i = 0; i < count; i++) { values[i] = (data[i * 2] << 8) | data[i * 2 + 1]; } return values; } } /// /// Modbus RTU CRC16校验 /// private ushort CalcCRC16(byte[] data, int length) { ushort crc = 0xFFFF; for (int i = 0; i < length; i++) { crc ^= data[i]; for (int j = 0; j < 8; j++) { if ((crc & 0x0001) != 0) { crc = (ushort)((crc >> 1) ^ 0xA001); } else { crc = (ushort)(crc >> 1); } } } return crc; } /// /// 从流中读满指定长度,读不满视为连接异常 /// private byte[] ReadBytes(NetworkStream stream, int length) { byte[] buffer = new byte[length]; int read = 0; while (read < length) { int n = stream.Read(buffer, read, length - read); if (n <= 0) { throw new Exception("连接已断开"); } read += n; } return buffer; } /// /// 关闭PLC连接 /// private void ClosePlc() { try { if (plcStream != null) { plcStream.Close(); plcStream = null; } if (plcClient != null) { plcClient.Close(); plcClient = null; } } catch { } connected = false; } private void SetStatus(string text) { lblStatus.Text = "状态:" + text; } private void Make_PLCMonitor_FormClosing(object sender, FormClosingEventArgs e) { timer1.Stop(); ClosePlc(); } private void Make_PLCMonitor_SizeChanged(object sender, EventArgs e) { asc.controlAutoSize(this); } } }