Make_PLCMonitor.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Net.Sockets;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. using UAS_MES_NEW.DataOperate;
  10. using UAS_MES_NEW.Entity;
  11. using UAS_MES_NEW.PublicMethod;
  12. namespace UAS_MES_NEW.Make
  13. {
  14. public partial class Make_PLCMonitor : Form
  15. {
  16. AutoSizeFormClass asc = new AutoSizeFormClass();
  17. DataHelper dh;
  18. // Modbus TCP 连接对象(保持长连接,断线自动重连)
  19. TcpClient plcClient;
  20. NetworkStream plcStream;
  21. bool connected = false;
  22. ushort transactionId = 0;
  23. readonly object readLock = new object();
  24. // 台达PLC数据寄存器:D寄存器对应Modbus保持寄存器(功能码0x03)
  25. // ETH-DVP-2P模块手册寄存器表:D0~4095 的Modbus地址 = 0x1000 + D编号
  26. // 如 D141 → 0x1000+141 = 0x108D;D4096以上是另一段(0x9000起),当前用不到
  27. const int D141_ADDR = 141;
  28. const int DVP_D_OFFSET = 0x1000;
  29. const int READ_COUNT = 2;
  30. // 温度记录表:每次采集往里插一条(1#舱、2#舱各一条)
  31. // TL_ID 用序列 TEMPLOG_SEQ,TL_DATE 取sysdate,TL_DEVICEID 存舱号,TL_TP 存换算后的温度值
  32. const string TempLogTable = "TEMPLOG";
  33. const string TempLogSeq = "TEMPLOG_SEQ";
  34. public Make_PLCMonitor()
  35. {
  36. InitializeComponent();
  37. }
  38. private void Make_PLCMonitor_Load(object sender, EventArgs e)
  39. {
  40. asc.controllInitializeSize(this);
  41. dh = SystemInf.dh;
  42. ip.Text = "192.168.1.150";
  43. port.Value = 502;
  44. unitid.Value = 1;
  45. interval.Value = 2;
  46. decimals.Value = 1;
  47. SetStatus("未连接");
  48. }
  49. /// <summary>
  50. /// 连接串口转网口模块(Modbus TCP)
  51. /// </summary>
  52. private void Connect_Click(object sender, EventArgs e)
  53. {
  54. try
  55. {
  56. ClosePlc();
  57. plcClient = new TcpClient();
  58. IAsyncResult ar = plcClient.BeginConnect(ip.Text.Trim(), (int)port.Value, null, null);
  59. if (!ar.AsyncWaitHandle.WaitOne(3000))
  60. {
  61. ClosePlc();
  62. SetStatus("连接超时");
  63. MessageBox.Show("连接PLC超时,请检查IP和端口!");
  64. return;
  65. }
  66. plcClient.EndConnect(ar);
  67. plcClient.ReceiveTimeout = 5000;
  68. plcClient.SendTimeout = 5000;
  69. plcStream = plcClient.GetStream();
  70. connected = true;
  71. SetStatus("已连接 " + ip.Text.Trim() + ":" + port.Value);
  72. OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 已连接 " + ip.Text.Trim() + ":" + port.Value + ",站号" + unitid.Value + "\n");
  73. timer1.Interval = (int)interval.Value * 1000;
  74. timer1.Start();
  75. // 连接成功后立即读一次
  76. ReadNow_Click(null, null);
  77. }
  78. catch (Exception ex)
  79. {
  80. ClosePlc();
  81. SetStatus("连接失败:" + ex.Message);
  82. MessageBox.Show("连接失败:" + ex.Message);
  83. }
  84. }
  85. /// <summary>
  86. /// 断开连接
  87. /// </summary>
  88. private void Disconnect_Click(object sender, EventArgs e)
  89. {
  90. timer1.Stop();
  91. ClosePlc();
  92. SetStatus("未连接");
  93. OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 已断开连接\n");
  94. }
  95. /// <summary>
  96. /// 立即读取一次
  97. /// </summary>
  98. private void ReadNow_Click(object sender, EventArgs e)
  99. {
  100. if (!connected)
  101. {
  102. SetStatus("未连接");
  103. return;
  104. }
  105. try
  106. {
  107. // D寄存器地址要加台达DVP的0x1000偏移:D141→0x108D, D142→0x108E
  108. int[] values = ReadRegisters((ushort)(DVP_D_OFFSET + D141_ADDR), READ_COUNT);
  109. ShowValues(values);
  110. InsertTempLog(values);
  111. SetStatus("读取成功 " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
  112. }
  113. catch (Exception ex)
  114. {
  115. // 读取失败:断开连接,等待下一次重连
  116. timer1.Stop();
  117. ClosePlc();
  118. connected = false;
  119. string mode = rdoTCP.Checked ? "ModbusTCP" : "ModbusRTU(透传)";
  120. SetStatus("读取失败:" + ex.Message);
  121. OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 读取失败(" + mode + "):" + ex.Message + "\n");
  122. if (ex.Message.Contains("超时") || ex.Message.Contains("没有正确答复") || ex.Message.Contains("连接已断开"))
  123. {
  124. OperatResult.AppendText("[提示] 如果RTU(透传)模式一直超时,请尝试切换到ModbusTCP模式;反之亦然。同时确认模块透传设置、PLC串口参数(9600/8/N/1)和站号。\n");
  125. }
  126. // 自动重连
  127. TryReconnect();
  128. }
  129. }
  130. /// <summary>
  131. /// 定时获取数据
  132. /// </summary>
  133. private void timer1_Tick(object sender, EventArgs e)
  134. {
  135. if (!connected)
  136. {
  137. TryReconnect();
  138. return;
  139. }
  140. ReadNow_Click(null, null);
  141. }
  142. /// <summary>
  143. /// 尝试重新连接(不打断界面,仅更新状态)
  144. /// </summary>
  145. private void TryReconnect()
  146. {
  147. try
  148. {
  149. ClosePlc();
  150. plcClient = new TcpClient();
  151. IAsyncResult ar = plcClient.BeginConnect(ip.Text.Trim(), (int)port.Value, null, null);
  152. if (!ar.AsyncWaitHandle.WaitOne(3000))
  153. {
  154. ClosePlc();
  155. SetStatus("重连超时,继续重试...");
  156. return;
  157. }
  158. plcClient.EndConnect(ar);
  159. plcClient.ReceiveTimeout = 5000;
  160. plcClient.SendTimeout = 5000;
  161. plcStream = plcClient.GetStream();
  162. connected = true;
  163. SetStatus("重连成功 " + DateTime.Now.ToString("HH:mm:ss"));
  164. OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 重连成功\n");
  165. }
  166. catch
  167. {
  168. ClosePlc();
  169. SetStatus("重连失败,继续重试...");
  170. }
  171. }
  172. /// <summary>
  173. /// 把本次采集的温度写入TEMPLOG表,1#舱、2#舱各一条
  174. /// TL_ID=序列TEMPLOG_SEQ,TL_DATE=sysdate,TL_DEVICEID=舱号,TL_TP=换算后的温度
  175. /// </summary>
  176. private void InsertTempLog(int[] values)
  177. {
  178. if (values == null || values.Length < 2) return;
  179. try
  180. {
  181. int d = (int)decimals.Value;
  182. double v1 = values[0] / Math.Pow(10, d);
  183. double v2 = values[1] / Math.Pow(10, d);
  184. string v1s = v1.ToString("0.###");
  185. string v2s = v2.ToString("0.###");
  186. dh.ExecuteSql("insert into " + TempLogTable + "(TL_ID,TL_DATE,TL_DEVICEID,TL_TP)values(" + TempLogSeq + ".nextval,sysdate,'1#'," + v1s + ")", "insert");
  187. dh.ExecuteSql("insert into " + TempLogTable + "(TL_ID,TL_DATE,TL_DEVICEID,TL_TP)values(" + TempLogSeq + ".nextval,sysdate,'2#'," + v2s + ")", "insert");
  188. }
  189. catch (Exception ex)
  190. {
  191. // 插入失败不影响采集显示,只记录
  192. OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 写入" + TempLogTable + "失败:" + ex.Message + "\n");
  193. }
  194. }
  195. /// <summary>
  196. /// 显示温度值,并根据小数位设置换算(台达一般存实际值x10)
  197. /// </summary>
  198. private void ShowValues(int[] values)
  199. {
  200. if (values == null || values.Length < 2) return;
  201. int d = (int)decimals.Value;
  202. double v1 = values[0] / Math.Pow(10, d);
  203. double v2 = values[1] / Math.Pow(10, d);
  204. string fmt = d > 0 ? "F" + d : "F0";
  205. lblD141Value.Text = v1.ToString(fmt);
  206. lblD142Value.Text = v2.ToString(fmt);
  207. OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 1#舱(D141)=" + values[0] + " 2#舱(D142)=" + values[1] + "\n");
  208. }
  209. /// <summary>
  210. /// Modbus TCP 读保持寄存器(功能码0x03)
  211. /// </summary>
  212. /// <summary>
  213. /// 读保持寄存器(功能码0x03),按界面选择的协议分发:
  214. /// ModbusRTU(串口透传):模块把TCP数据原样转发到串口,要发带CRC16的RTU裸帧
  215. /// ModbusTCP:模块/PLC本身是标准Modbus TCP服务端,发带MBAP头的帧
  216. /// </summary>
  217. private int[] ReadRegisters(ushort startAddr, ushort count)
  218. {
  219. if (rdoTCP.Checked)
  220. {
  221. return ReadTcpRegisters(startAddr, count);
  222. }
  223. return ReadRtuRegisters(startAddr, count);
  224. }
  225. /// <summary>
  226. /// Modbus RTU over TCP(串口透传模式):站号+功能码+数据+CRC16
  227. /// </summary>
  228. private int[] ReadRtuRegisters(ushort startAddr, ushort count)
  229. {
  230. lock (readLock)
  231. {
  232. if (!connected || plcStream == null)
  233. {
  234. throw new Exception("未连接PLC");
  235. }
  236. byte unit = (byte)unitid.Value;
  237. byte[] req = new byte[8];
  238. req[0] = unit;
  239. // 功能码0x03 读保持寄存器
  240. req[1] = 0x03;
  241. req[2] = (byte)(startAddr >> 8);
  242. req[3] = (byte)(startAddr & 0xFF);
  243. req[4] = (byte)(count >> 8);
  244. req[5] = (byte)(count & 0xFF);
  245. ushort crc = CalcCRC16(req, 6);
  246. // Modbus RTU CRC低字节在前
  247. req[6] = (byte)(crc & 0xFF);
  248. req[7] = (byte)((crc >> 8) & 0xFF);
  249. plcStream.Write(req, 0, req.Length);
  250. // 响应:站号+功能码+字节数
  251. byte[] head = ReadBytes(plcStream, 3);
  252. if (head[0] != unit)
  253. {
  254. throw new Exception("响应站号不匹配(收到0x" + head[0].ToString("X2") + "),请检查站号设置");
  255. }
  256. if ((head[1] & 0x80) != 0)
  257. {
  258. byte[] rest = ReadBytes(plcStream, 3);
  259. throw new Exception("PLC返回异常码 0x" + rest[0].ToString("X2"));
  260. }
  261. int byteCount = head[2];
  262. byte[] rest2 = ReadBytes(plcStream, byteCount + 2);
  263. // CRC校验:响应最后2字节是CRC
  264. byte[] full = new byte[3 + byteCount + 2];
  265. Array.Copy(head, 0, full, 0, 3);
  266. Array.Copy(rest2, 0, full, 3, byteCount + 2);
  267. ushort calcCrc = CalcCRC16(full, full.Length - 2);
  268. ushort recvCrc = (ushort)(full[full.Length - 2] | (full[full.Length - 1] << 8));
  269. if (calcCrc != recvCrc)
  270. {
  271. throw new Exception("响应CRC校验失败,串口参数(波特率/校验位)可能与PLC不一致");
  272. }
  273. int[] values = new int[count];
  274. for (int i = 0; i < count; i++)
  275. {
  276. values[i] = (rest2[i * 2] << 8) | rest2[i * 2 + 1];
  277. }
  278. return values;
  279. }
  280. }
  281. /// <summary>
  282. /// Modbus TCP 读保持寄存器(功能码0x03),带MBAP头
  283. /// </summary>
  284. private int[] ReadTcpRegisters(ushort startAddr, ushort count)
  285. {
  286. lock (readLock)
  287. {
  288. if (!connected || plcStream == null)
  289. {
  290. throw new Exception("未连接PLC");
  291. }
  292. transactionId++;
  293. byte[] req = new byte[12];
  294. // MBAP头:事务标识、协议标识0、长度6、单元标识符
  295. req[0] = (byte)(transactionId >> 8);
  296. req[1] = (byte)(transactionId & 0xFF);
  297. req[2] = 0x00;
  298. req[3] = 0x00;
  299. req[4] = 0x00;
  300. req[5] = 0x06;
  301. req[6] = (byte)unitid.Value;
  302. // 功能码0x03 读保持寄存器
  303. req[7] = 0x03;
  304. req[8] = (byte)(startAddr >> 8);
  305. req[9] = (byte)(startAddr & 0xFF);
  306. req[10] = (byte)(count >> 8);
  307. req[11] = (byte)(count & 0xFF);
  308. plcStream.Write(req, 0, req.Length);
  309. byte[] head = ReadBytes(plcStream, 9);
  310. if ((head[7] & 0x80) != 0)
  311. {
  312. byte[] ec = ReadBytes(plcStream, 1);
  313. throw new Exception("PLC返回异常码 0x" + (ec.Length > 0 ? ec[0] : 0).ToString("X2"));
  314. }
  315. int byteCount = head[8];
  316. byte[] data = ReadBytes(plcStream, byteCount);
  317. int[] values = new int[count];
  318. for (int i = 0; i < count; i++)
  319. {
  320. values[i] = (data[i * 2] << 8) | data[i * 2 + 1];
  321. }
  322. return values;
  323. }
  324. }
  325. /// <summary>
  326. /// Modbus RTU CRC16校验
  327. /// </summary>
  328. private ushort CalcCRC16(byte[] data, int length)
  329. {
  330. ushort crc = 0xFFFF;
  331. for (int i = 0; i < length; i++)
  332. {
  333. crc ^= data[i];
  334. for (int j = 0; j < 8; j++)
  335. {
  336. if ((crc & 0x0001) != 0)
  337. {
  338. crc = (ushort)((crc >> 1) ^ 0xA001);
  339. }
  340. else
  341. {
  342. crc = (ushort)(crc >> 1);
  343. }
  344. }
  345. }
  346. return crc;
  347. }
  348. /// <summary>
  349. /// 从流中读满指定长度,读不满视为连接异常
  350. /// </summary>
  351. private byte[] ReadBytes(NetworkStream stream, int length)
  352. {
  353. byte[] buffer = new byte[length];
  354. int read = 0;
  355. while (read < length)
  356. {
  357. int n = stream.Read(buffer, read, length - read);
  358. if (n <= 0)
  359. {
  360. throw new Exception("连接已断开");
  361. }
  362. read += n;
  363. }
  364. return buffer;
  365. }
  366. /// <summary>
  367. /// 关闭PLC连接
  368. /// </summary>
  369. private void ClosePlc()
  370. {
  371. try
  372. {
  373. if (plcStream != null)
  374. {
  375. plcStream.Close();
  376. plcStream = null;
  377. }
  378. if (plcClient != null)
  379. {
  380. plcClient.Close();
  381. plcClient = null;
  382. }
  383. }
  384. catch { }
  385. connected = false;
  386. }
  387. private void SetStatus(string text)
  388. {
  389. lblStatus.Text = "状态:" + text;
  390. }
  391. private void Make_PLCMonitor_FormClosing(object sender, FormClosingEventArgs e)
  392. {
  393. timer1.Stop();
  394. ClosePlc();
  395. }
  396. private void Make_PLCMonitor_SizeChanged(object sender, EventArgs e)
  397. {
  398. asc.controlAutoSize(this);
  399. }
  400. }
  401. }