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_PartDry : Form
{
AutoSizeFormClass asc = new AutoSizeFormClass();
DataHelper dh;
DataTable dt;
// ============ 零件烘烤批次相关 ============
// 工序类型:烘烤页面固定写DRY,写入SB_STEPCODE用于和零件清洗区分
const string StepCode = "DRY";
// 当前批次号:结束烘烤后置空,下一个录入的SN会生成新的批次号
string currentBatch = "";
// ============ PLC温度采集相关(Modbus) ============
// Modbus 连接对象(保持长连接,断线自动重连)
TcpClient plcClient;
NetworkStream plcStream;
bool plcConnected = 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#舱各一条)
const string TempLogTable = "TEMPLOG";
const string TempLogSeq = "TEMPLOG_SEQ";
public Make_PartDry()
{
InitializeComponent();
}
private void Make_PartDry_Load(object sender, EventArgs e)
{
asc.controllInitializeSize(this);
dh = SystemInf.dh;
// PLC连接参数默认值(ETH-DVP-2P模块默认IP,端口固定502)
ip.Text = "192.168.1.150";
port.Value = 502;
unitid.Value = 1;
interval.Value = 2;
decimals.Value = 1;
SetStatus("未连接PLC,请录入SN或先连接设备");
// 恢复未结束的批次(比如中途退出页面的情况)
object b = dh.getFieldDataByCondition("SINGLEBOARD", "max(SB_BATCHCODE)", "SB_STEPCODE='" + StepCode + "' and SB_ENDTIME is null");
currentBatch = b == null ? "" : b.ToString();
if (currentBatch == "")
{
lblBatchCode.Text = "(暂无,录入SN时自动生成)";
}
else
{
lblBatchCode.Text = currentBatch;
LoadBatchData(currentBatch);
}
sncode.Focus();
}
#region 零件烘烤:SN录入与批次管理
///
/// 录入框回车:插入SN到SINGLEBOARD,本批次共用同一个批次号
///
private void sncode_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
InsertSN();
e.Handled = true;
e.SuppressKeyPress = true;
}
}
private void InsertSN()
{
string sn = sncode.Text.Trim();
if (sn == "")
{
SetStatus("SN不能为空!");
return;
}
try
{
// 没有进行中的批次时生成新批次号:PD+年月日时分秒
if (currentBatch == "")
{
currentBatch = GenBatchCode();
lblBatchCode.Text = currentBatch;
}
// 同一批次内SN不允许重复录入
dt = (DataTable)dh.ExecuteSql("select SB_ID from SINGLEBOARD where SB_SNCODE='" + sn + "' and SB_BATCHCODE='" + currentBatch + "' and SB_STEPCODE='" + StepCode + "'", "select");
if (dt.Rows.Count > 0)
{
SetStatus("SN【" + sn + "】在本批次已录入,请勿重复!");
sncode.Clear();
sncode.Focus();
return;
}
dh.ExecuteSql("insert into SINGLEBOARD(SB_ID,SB_SNCODE,SB_INDATE,SB_STATUS,SB_STEPCODE,SB_BATCHCODE,SB_INMAN)values(SINGLEBOARD_SEQ.nextval,'" + sn + "',sysdate,'在录入','" + StepCode + "','" + currentBatch + "','" + User.UserName + "')", "insert");
SetStatus("SN【" + sn + "】录入成功,批次号" + currentBatch);
sncode.Clear();
sncode.Focus();
LoadBatchData(currentBatch);
}
catch (Exception ex)
{
SetStatus("录入失败:" + ex.Message);
}
}
///
/// 生成批次号:PD+年月日时分秒,长度16在SB_BATCHCODE(20)范围内
///
private string GenBatchCode()
{
return "PD" + DateTime.Now.ToString("yyyyMMddHHmmss");
}
///
/// 开始烘烤:给当前批次所有未开始的记录写开始时间
///
private void btnStart_Click(object sender, EventArgs e)
{
if (currentBatch == "")
{
MessageBox.Show("当前没有进行中的批次,请先录入SN!");
return;
}
try
{
dh.ExecuteSql("update SINGLEBOARD set SB_STARTTIME='" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "',SB_STATUS='烘烤中' where SB_BATCHCODE='" + currentBatch + "' and SB_STEPCODE='" + StepCode + "' and SB_STARTTIME is null", "update");
SetStatus("批次【" + currentBatch + "】烘烤已开始");
LoadBatchData(currentBatch);
}
catch (Exception ex)
{
SetStatus("开始烘烤失败:" + ex.Message);
}
}
///
/// 结束烘烤:给当前批次所有未结束的记录写结束时间,之后录入的SN生成新批次
///
private void btnEnd_Click(object sender, EventArgs e)
{
if (currentBatch == "")
{
MessageBox.Show("当前没有进行中的批次!");
return;
}
try
{
dh.ExecuteSql("update SINGLEBOARD set SB_ENDTIME='" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "',SB_STATUS='已完成' where SB_BATCHCODE='" + currentBatch + "' and SB_STEPCODE='" + StepCode + "' and SB_ENDTIME is null", "update");
SetStatus("批次【" + currentBatch + "】已结束,下一批录入SN时生成新批次号");
// 保留显示刚结束批次的数据,批次号置空
LoadBatchData(currentBatch);
currentBatch = "";
lblBatchCode.Text = "(暂无,录入SN时自动生成)";
}
catch (Exception ex)
{
SetStatus("结束烘烤失败:" + ex.Message);
}
}
///
/// 按批次号查询
///
private void btnQuery_Click(object sender, EventArgs e)
{
string b = txtQueryBatch.Text.Trim();
if (b == "")
{
MessageBox.Show("请输入要查询的批次号!");
return;
}
LoadBatchData(b);
}
///
/// 加载指定批次的数据显示在表格里
///
private void LoadBatchData(string batch)
{
dt = (DataTable)dh.ExecuteSql("select SB_SNCODE \"SN\",SB_INDATE \"录入时间\",SB_STARTTIME \"开始烘烤时间\",SB_INMAN \"录入人\",SB_STATUS \"状态\" from SINGLEBOARD where SB_BATCHCODE='" + batch + "' and SB_STEPCODE='" + StepCode + "' order by SB_ID", "select");
DGV.DataSource = dt;
}
#endregion
#region PLC温度采集(Modbus)
///
/// 连接串口转网口模块(ETH-DVP-2P,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("PLC连接超时");
MessageBox.Show("连接PLC超时,请检查IP和端口!");
return;
}
plcClient.EndConnect(ar);
plcClient.ReceiveTimeout = 5000;
plcClient.SendTimeout = 5000;
plcStream = plcClient.GetStream();
plcConnected = true;
OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 已连接 " + ip.Text.Trim() + ":" + port.Value + ",站号" + unitid.Value + "\n");
SetStatus("已连接PLC " + ip.Text.Trim() + ":" + port.Value);
timer1.Interval = (int)interval.Value * 1000;
timer1.Start();
// 连接成功后立即读一次
ReadNow_Click(null, null);
}
catch (Exception ex)
{
ClosePlc();
SetStatus("PLC连接失败:" + ex.Message);
MessageBox.Show("连接失败:" + ex.Message);
}
}
///
/// 断开PLC连接
///
private void Disconnect_Click(object sender, EventArgs e)
{
timer1.Stop();
ClosePlc();
SetStatus("PLC已断开");
OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 已断开连接\n");
}
///
/// 立即读取一次温度
///
private void ReadNow_Click(object sender, EventArgs e)
{
if (!plcConnected)
{
SetStatus("未连接PLC");
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();
plcConnected = 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 (!plcConnected)
{
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("PLC重连超时,继续重试...");
return;
}
plcClient.EndConnect(ar);
plcClient.ReceiveTimeout = 5000;
plcClient.SendTimeout = 5000;
plcStream = plcClient.GetStream();
plcConnected = true;
SetStatus("PLC重连成功 " + DateTime.Now.ToString("HH:mm:ss"));
OperatResult.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] 重连成功\n");
}
catch
{
ClosePlc();
SetStatus("PLC重连失败,继续重试...");
}
}
///
/// 把本次采集的温度写入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");
}
///
/// 读保持寄存器(功能码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 (!plcConnected || 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 (!plcConnected || 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 { }
plcConnected = false;
}
#endregion
private void SetStatus(string text)
{
lblStatus.Text = "状态:" + text;
}
private void Make_PartDry_FormClosing(object sender, FormClosingEventArgs e)
{
timer1.Stop();
ClosePlc();
}
private void Make_PartDry_SizeChanged(object sender, EventArgs e)
{
asc.controlAutoSize(this);
}
}
}