| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- 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("未连接");
- }
- /// <summary>
- /// 连接串口转网口模块(Modbus TCP)
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// 断开连接
- /// </summary>
- private void Disconnect_Click(object sender, EventArgs e)
- {
- timer1.Stop();
- ClosePlc();
- SetStatus("未连接");
- OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 已断开连接\n");
- }
- /// <summary>
- /// 立即读取一次
- /// </summary>
- 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();
- }
- }
- /// <summary>
- /// 定时获取数据
- /// </summary>
- private void timer1_Tick(object sender, EventArgs e)
- {
- if (!connected)
- {
- TryReconnect();
- return;
- }
- ReadNow_Click(null, null);
- }
- /// <summary>
- /// 尝试重新连接(不打断界面,仅更新状态)
- /// </summary>
- 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("重连失败,继续重试...");
- }
- }
- /// <summary>
- /// 把本次采集的温度写入TEMPLOG表,1#舱、2#舱各一条
- /// TL_ID=序列TEMPLOG_SEQ,TL_DATE=sysdate,TL_DEVICEID=舱号,TL_TP=换算后的温度
- /// </summary>
- 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");
- }
- }
- /// <summary>
- /// 显示温度值,并根据小数位设置换算(台达一般存实际值x10)
- /// </summary>
- 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");
- }
- /// <summary>
- /// Modbus TCP 读保持寄存器(功能码0x03)
- /// </summary>
- /// <summary>
- /// 读保持寄存器(功能码0x03),按界面选择的协议分发:
- /// ModbusRTU(串口透传):模块把TCP数据原样转发到串口,要发带CRC16的RTU裸帧
- /// ModbusTCP:模块/PLC本身是标准Modbus TCP服务端,发带MBAP头的帧
- /// </summary>
- private int[] ReadRegisters(ushort startAddr, ushort count)
- {
- if (rdoTCP.Checked)
- {
- return ReadTcpRegisters(startAddr, count);
- }
- return ReadRtuRegisters(startAddr, count);
- }
- /// <summary>
- /// Modbus RTU over TCP(串口透传模式):站号+功能码+数据+CRC16
- /// </summary>
- 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;
- }
- }
- /// <summary>
- /// Modbus TCP 读保持寄存器(功能码0x03),带MBAP头
- /// </summary>
- 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;
- }
- }
- /// <summary>
- /// Modbus RTU CRC16校验
- /// </summary>
- 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;
- }
- /// <summary>
- /// 从流中读满指定长度,读不满视为连接异常
- /// </summary>
- 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;
- }
- /// <summary>
- /// 关闭PLC连接
- /// </summary>
- 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);
- }
- }
- }
|