硬件采用RS232串口,通讯协议使用ModusbusRTU。以前跟PLC通讯,为求简单,一般直接使用无协议,使用倒没问题,但偶尔出现数据丢失也是烦人,要确保对方接收到数据还要双方写返回数据。使用modbus协议,PLC基本不用写通讯程序,而上位机可以直接自己确认对方是否收到数据,再确认是否给对方重发一次。这个项目只要求采集数据,因为读取的频率高于数据变化,所以偶尔读不到都不影响,使用modbus就更合适了。
///
/// 通过Modbus读取PLC,获取相应数据
///
private void ScanPLC()
{
//创建字节数组
byte[] result = null;
//从站号
ushort DevAdd = 1;
//起始地址
ushort Address = 100;
//长度
ushort Length = 5;
while (running)
{
if (plcConnected)
{
result = objModbus.ReadKeepReg(DevAdd, Address, Length);
DataProcessing(result, Length);
}
else
{
try
{
objModbus.OpenMyCom(9600, comName1, 8, Parity.Odd, StopBits.One);
//objModbus.ReadTimeOut = 500;
plcConnected = true;
textReceive.Invoke(new ExecuteWork(RecRunData), "重连PLC成功");
}
catch (Exception ex)
{
textReceive.Invoke(new ExecuteWork(RecRunData), "重连PLC失败:" + ex.Message);
plcConnected = false;
}
}
Thread.Sleep(1000);
}
}
硬件也是采用RS232串口,因为测试仪的说明书不完善,modbus协议没有测试成功,而无协议测试成功了,所以直接采用无协议接收数据。采取的采集数据模式是,测试仪有测试数据马上主动通过串口发送,而我的程序只要一直处于接收状态,有数据就记下。所以建了一个线程一直扫描串口数据。
关于串口无协议通讯,以前也有个烦恼。就是接收数据有时会把一条数据拆成两条或者两条数据合成一条接收,要进行一些处理判断很是麻烦。这次发现Serial自带的读取功能很全面(可以在Serialport这个类下找到)。于是我直接采用了下面这个函数,再也不用担心老是读错数据了~
//
// 摘要:
// 一直读取到输入缓冲区中的指定 value 的字符串。
//
// 参数:
// value:
// 指示读取操作停止位置的值。
//
// 返回结果:
// 输入缓冲区中直到指定 value 的内容。
//
// 异常:
// T:System.ArgumentException:
// 长度 value 参数为 0。
//
// T:System.ArgumentNullException:
// value 参数为 null。
//
// T:System.InvalidOperationException:
// 指定的端口未打开。
//
// T:System.TimeoutException:
// 该操作未完成之前超时期限已结束。
public string ReadTo(string value);
这个之前有用过读写TXT文本,有读写过配置文件,也用过自带的应用程序设置。这次使用的是当前机种存在应用程序设置中,具体机种数据则存在ini文件中。应用程序设置只要配置一下读取和保存,很简单。ini文件的读写麻烦些,将机种名设为Section,将具体的参数作为key的值。
///
/// 读取机种参数
///
public void ReadFiles()
{
try
{
IniHelper iniHelp = new IniHelper(@".\config.ini", JiZhong);
GongDan = iniHelp.ReadIniData(JiZhong, "GongDan" );
LiHao = iniHelp.ReadIniData(JiZhong, "LiHao" );
LiHaoZhouQi = iniHelp.ReadIniData(JiZhong,"LiHaoZhouQi");
DianRongGuiGe = iniHelp.ReadIniData(JiZhong,"DianRongGuiGe");
WeiZhi = iniHelp.ReadIniData(JiZhong,"WeiZhi");
PiLiang = iniHelp.ReadIniData(JiZhong,"PiLiang");
YiChangCiShu = iniHelp.ReadIniData(JiZhong,"YiChangCiShu");
ChanLiang = iniHelp.ReadIniData(JiZhong, "ChanLiang");
JiZhongs = iniHelp.ReadIniData("JiZhongMing", "JiZhongs");
}
catch (Exception)
{
MessageBox.Show("未找到配置文件,将使用默认配置");
}
}
///
/// 写入机种参数
///
public void SaveFiles()
{
string path = @".\config.ini";
if(!File.Exists(path))
{
FileStream fs = new FileStream(path, FileMode.CreateNew);
fs.Close();
}
IniHelper iniHelp = new IniHelper(path, JiZhong);
iniHelp.WriteIniData("GongDan" , GongDan);
iniHelp.WriteIniData( "LiHao" , LiHao );
iniHelp.WriteIniData("LiHaoZhouQi", LiHaoZhouQi );
iniHelp.WriteIniData("DianRongGuiGe", DianRongGuiGe);
iniHelp.WriteIniData("WeiZhi", WeiZhi );
iniHelp.WriteIniData("PiLiang", PiLiang );
iniHelp.WriteIniData("YiChangCiShu", YiChangCiShu );
iniHelp.WriteIniData( "ChanLiang" , ChanLiang );
IniHelper iniHelp1 = new IniHelper(path, "JiZhongMing");
iniHelp1.WriteIniData( "JiZhongs", JiZhongs );
}
这涉及到Excel的读写,之前了解过,但没使用过。网上找到了好几种方案,有使用Office自带库的,有使用各种开源软件的。测试了几种开源工具,都不得其法,后来看用NPOI的很多,而且不需要安装Office,功能也很强大,遂决定采用。通过参考案例,发现还是比较容易使用的。
///
/// 向Excel中插入行
///
/// 在第几行插入
/// 文件路径
/// 插入的数据<一维数组>
public void InsertRow(int n = 1, string filePath = "D:\\RunData\\test.xlsx", string[] RecordData =null)
{
string fileExt = Path.GetExtension(filePath).ToLower();//获取扩展名
IWorkbook workbook;
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
if (fileExt == ".xlsx") { workbook = new XSSFWorkbook(fs); }
else if (fileExt == ".xls") { workbook = new HSSFWorkbook(fs); }
else { workbook = null; }
if (workbook == null) { return; }
ISheet sheet = workbook.GetSheetAt(0);
IRow row = sheet.GetRow(n);//获取第一行
if (row != null)//檢查是否有數據,有數據就下移,沒有就直接賦值
{
//将表格内容整体下移
sheet.ShiftRows(n, sheet.LastRowNum, 1);
}
var newrow = sheet.CreateRow(n);
int i = 0;
foreach (string v in RecordData)
{
ICell cell = newrow.GetCell(i);
if (cell == null)
{
cell = newrow.CreateCell(i);
}
cell.SetCellValue(v);
i++;
}
FileStream out2 = new FileStream(filePath, FileMode.Create);
workbook.Write(out2);
out2.Close();
}
}
当时测试其它开源工具的时候,发现往数据后面添加行很容易,但想把最新数据插入第一行却不容易,后面才转入NPOI的怀抱,因为它有下面这个函数,很合适。
// 摘要:
// Shifts rows between startRow and endRow n number of rows. If you use a negative
// number, it will shift rows up. Code ensures that rows don't wrap around. Calls
// shiftRows(startRow, endRow, n, false, false); Additionally shifts merged regions
// that are completely defined in these rows (ie. merged 2 cells on a row to be
// shifted).
//
// 参数:
// startRow:
// the row to start shifting
//
// endRow:
// the row to end shifting
//
// n:
// the number of rows to shift
void ShiftRows(int startRow, int endRow, int n);
这个项目虽然不大,但相比我以前做的项目,我在尽可能将代码写得规范容易理解。
private delegate void ExecuteWork(string str);
private void test()
{
this.textReceive.Invoke(new ExecuteWork(RecRunData), "运行异常");
}
private void RecRunData(string reda)
{
if (reda != lastData&&reda.Length>0)
{
string tim = DateTime.Now.ToLongTimeString() + "." + DateTime.Now.Millisecond.ToString() + "--";
textReceive.Text = tim + reda + "\n" + textReceive.Text;
lastData = reda;
}
}
而且还一种更简单的,甚至不用先声明委托方法,直接一行就可以搞定:
this.textYield.Invoke(new Action(s => { this.textYield.Text = s; }), "运行情况");