赞
踩
本人近十年的工作都与工业软件相关、其中工控系统开发过程中有一个必要环节就是跟各大厂商的PLC进行通讯,而对于从互联网行业跨入工业互联网行业的从业人员来说要实现各型号PLC通讯还是需要一个过程的,本人在此对主流型号PLC通讯实现进行总结以便大家参考。
首先我们要进行一下抽象设计,先设计一个抽象类(接口也可以,此处因为还有其他业务使用了抽象类)BaseEquip,对PLC的常规操作进行定义,即Open、Read、Write、Close,业务代码调用BaseEquip进行PLC的读写,然后在实现各型号的Equip类,对Open、Read、Write、Close进行实现,根据配置在业务代码中对BaseEquip进行实例化,这样后期更改PLC型号后,只需修改配置即可,不用修改业务代码。
实现语言C#
抽象基类BaseEquip
public class BaseEquip
{
/// <summary>
/// 打开设备
/// </summary>
/// <returns>成功返回true,失败返回false</returns>
public abstract bool Open();
/// <summary>
/// 读取信息
/// </summary>
/// <param name="block">数据块</param>
/// <param name="start">起始地址</param>
/// <param name="len">长度</param>
/// <param name="buff">读取返回信息</param>
/// <returns>成功返回true,失败返回false</returns>
public abstract bool Read(string block, int start, int len, out object[] buff);
/// <summary>
/// 写入信息
/// </summary>
/// <param name="block">数据块</param>
/// <param name="start">起始地址</param>
/// <param name="buff">要写入的数据</param>
/// <returns>成功返回true,失败返回false</returns>
public abstract bool Write(int block, int start, object[] buff);
/// <summary>
/// 关闭设备
/// </summary>
public abstract void Close();
}
设备实现类Equip实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using Mesnac.Equips;
namespace Mesnac.Equip.OMRON.FINS.UDP
{
public class Equip : BaseEquip
{
#region 字段定义
private string _plcIp = "192.168.1.50";
private int _plcPort = 9600;
private string _pcIp = "192.168.1.131";
private Socket server = null; //Socket Server
IPEndPoint plcIpEndPoint = null;
IPEndPoint receiveEndPoint = null;
EndPoint remote = null;
private bool _isOpen = false; //是否打开连接
#endregion
#region 属性定义
/// <summary>
/// PLC的IP地址
/// </summary>
public string PlcIp
{
get
{
return _plcIp;
}
}
/// <summary>
/// PLC的端口号
/// </summary>
public int PlcPort
{
get
{
return _plcPort;
}
}
public string PcIp
{
get
{
return _pcIp;
}
}
#endregion
#region 实现BaseEquip成员
public override bool Open()
{
try
{
if (this._isOpen == true && (server != null))
{
return true;
}
this.State = false;
this.server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
this.plcIpEndPoint = new IPEndPoint(IPAddress.Parse(this.PlcIp), 9600);
this.receiveEndPoint = new IPEndPoint(IPAddress.Any, 0);
this.remote = (EndPoint)this.receiveEndPoint;
this.State = this.Connection();
if (!this.State)
{
return this.State;
}
else
{
this.State = true;
this._isOpen = true;
Console.WriteLine("连接成功!");
return this.State;
}
}
catch (Exception ex)
{
this.State = false;
this._isOpen = false;
Console.WriteLine(ex.Message);
return this.State;
}
}
public override bool Read(string block, int start, int len, out object[] buff)
{
buff = new object[len];
try
{
if (len > 256)
{
for (int i = 0; i < len; i++)
{
buff[i] = 0;
}
base.State = false;
return false;
}
int maxOneLen = 100; //单次允许读取的最大长度,欧姆龙限制为100个字
int count = len / maxOneLen; //要读取的次数
int mod = len % maxOneLen; //剩余的长度
bool flag = true; //保存读取标志
for (int i = 0; i < count; i++)
{
object[] _buff = new object[maxOneLen];
flag = this.ReadByLen(block, start + i * maxOneLen, maxOneLen, out _buff);
if (flag == false)
{
base.State = flag;
return false;
}
for (int k = i * maxOneLen; k < (i + 1) * maxOneLen; k++)
{
buff[k] = _buff[k - i * maxOneLen];
}
}
if (mod > 0)
{
object[] _buff = new object[mod];
flag = this.ReadByLen(block, start + count * maxOneLen, mod, out _buff);
if (flag == false)
{
base.State = flag;
return false;
}
for (int k = count * maxOneLen; k < count * maxOneLen + mod; k++)
{
buff[k] = _buff[k - count * maxOneLen];
}
}
base.State = flag;
return flag;
}
catch (Exception ex)
{
ICSharpCode.Core.LoggingService.Error(String.Format("读取PLC(OMRON)设备失败-({0})!", ex.Message));
base.State = false;
return false;
}
}
/// <summary>
/// 单次读取最长100个字的方法
/// </summary>
/// <param name="block">块号</param>
/// <param name="start">起始字</param>
/// <param name="len">长度,最长不超过100</param>
/// <param name="buff">数据缓冲区,存放读取的数据</param>
/// <returns>读取成功返回true,读取失败返回false</returns>
private bool ReadByLen(string block, int start, int len, out object[] buff)
{
lock (this)
{
buff = new object[len];
if (!this.Open())
{
return false;
}
int state = len;
object[] _buff = new object[len];
int iblock = Convert.ToInt32(block);
//iblock = iblock + start;
byte[] readCmd = this.GetReadCmd(iblock + start, len);
int size = this.server.SendTo(readCmd, readCmd.Length, SocketFlags.None, this.plcIpEndPoint);
byte[] buffer = new byte[len * 2 + 14];
if (size < readCmd.Length)
{
//ICSharpCode.Core.LoggingService.Warn("PLC读取失败:" + this.GetErrInfo(result));
this.State = false;
return false;
}
else
{
int recv = server.ReceiveFrom(buffer, ref this.remote);//返回收到的字节数
if (recv != 0)
{
for(int i = 0; i < len; i++)
{
byte[] a = new byte[2];
a[0] = buffer[15 + i * 2];
a[1] = buffer[14 + i * 2];
_buff[i] = BitConverter.ToInt16(a, 0);
}
}
this.State = true;
}
int iReadLen = len;
if (iReadLen > state)
{
iReadLen = state;
}
for (int i = 0; i < iReadLen; i++)
{
int value = 0;
int.TryParse(_buff[i].ToString(), out value);
if (value > ushort.MaxValue)
{
value = ushort.MaxValue - value;
}
buff[i] = value;
}
return true;
}
}
public override bool Write(int block, int start, object[] buff)
{
lock(this)
{
byte[] writeCmd = this.GetWriteCmd(block + start, buff);
try
{
int size = this.server.SendTo(writeCmd, writeCmd.Length, SocketFlags.None, this.plcIpEndPoint);
if (size < writeCmd.Length)
{
return false;
}
this.server.ReceiveTimeout = 1000;
byte[] buffer = new byte[20];
int recv = server.ReceiveFrom(buffer, ref remote);
if (recv != 0)
{
return true;
}
else
{
return false;
}
}
catch(Exception ex)
{
ICSharpCode.Core.LoggingService.Error(String.Format("block={0}, start={1}, len={2} 写入PLC失败,{3}", block, start, buff.Length, ex.Message), ex);
return false;
}
}
}
public override void Close()
{
try
{
if (this.server != null)
{
this.server.Close();
this.server = null;
}
}
catch(Exception ex)
{
ICSharpCode.Core.LoggingService.Error("断开PLC连接异常:" + ex.Message, ex);
}
}
#endregion
#region 辅助方法
/// <summary>
/// 获取IP地址中最后一部分的值,比如:192.168.1.50,则返回50
/// </summary>
/// <param name="ip">ip地址</param>
/// <returns>返回IP地址中最后一部分的值</returns>
private byte GetLastIpScopeValue(string ip)
{
try
{
IPAddress ipAddress = IPAddress.Parse(ip);
byte[] ipValues = ipAddress.GetAddressBytes();
return ipValues[ipValues.Length - 1];
}
catch(Exception ex)
{
ICSharpCode.Core.LoggingService.Error(String.Format("IP地址格式不合法,IP = [{0}]", ip), ex);
return 0;
}
}
/// <summary>
/// 获取读取数据的指令
/// </summary>
/// <param name="start">起始地址</param>
/// <param name="len">要读取的长度</param>
/// <returns>返回合法的读取指令</returns>
private byte[] GetReadCmd(int start, int len)
{
byte[] starts = BitConverter.GetBytes((short)start);
byte[] lens = BitConverter.GetBytes((short)len);
byte[] readCmd = new byte[18];
readCmd[0] = 0X80;
readCmd[1] = 0X00;
readCmd[2] = 0X02;
readCmd[3] = 0X00;
//下标从0开始,第4个字节表示PLC的IP地址(IP地址4部分的最后一部分的值)
readCmd[4] = this.GetLastIpScopeValue(this.PlcIp); //对方IP地址,即PLC
readCmd[5] = 0X00;
readCmd[6] = 0X00;
//下标从0开始,第7个字节表示PC的IP地址(IP地址4部分的最后一部分的值)
readCmd[7] = 0X01; // this.GetLastIpScopeValue(this.PcIp); //本机IP地址,即上位机电脑,只要是大于0的值就行
readCmd[8] = 0X00;
readCmd[9] = 0X00;
readCmd[10] = 0X01;
readCmd[11] = 0X01; //0101 读取数据
readCmd[12] = 0X82; //82 地址区域DM区
//下标从0开始,第13和14这2个字节表示起始地址
readCmd[13] = starts[1]; //读/写数据区的起始地址
readCmd[14] = starts[0]; //读/写数据区的起始地址
readCmd[15] = 0X00;
//下标从0开始,第16和17这2个字节表示读取数据的长度
readCmd[16] = lens[1]; //读/写数据个数
readCmd[17] = lens[0]; //读/写数据个数
Console.WriteLine(BitConverter.ToString(readCmd).Replace("-", String.Empty));
return readCmd;
}
/// <summary>
/// 获取写入PLC的指令
/// </summary>
/// <param name="start"></param>
/// <param name="buff"></param>
/// <returns></returns>
private byte[] GetWriteCmd(int start, object[] buff)
{
Random ra = new Random(unchecked((int)DateTime.Now.Ticks));
int SID = ra.Next(1, 100);
int num = buff.Length;
int amount = 18 + num * 2;
byte[] writeCmd = new byte[amount];
byte[] startBytes = new byte[2];
startBytes = BitConverter.GetBytes((short)start);
byte[] lenBytes = new byte[2];
lenBytes = BitConverter.GetBytes((short)num);
writeCmd[0] = 0X80; //ICF
writeCmd[1] = 0X00; //RSV
writeCmd[2] = 0X02; //GCT
writeCmd[3] = 0X00; //DNA
//下标从0开始,第4个字节表示PLC的IP地址(IP地址4部分的最后一部分的值)
writeCmd[4] = this.GetLastIpScopeValue(this.PlcIp); //DA1,对方IP地址,即PLC
writeCmd[5] = 0X00; //DA2
writeCmd[6] = 0X00; //SNA
//下标从0开始,第7个字节表示PC的IP地址(IP地址4部分的最后一部分的值)
writeCmd[7] = 0X01; // this.GetLastIpScopeValue(this.PcIp); //SA1,本机IP地址,即上位机电脑,只要是大于0的值就行
writeCmd[8] = 0X00; //SA2
writeCmd[9] = Convert.ToByte(SID.ToString(), 16);//SID //0x00;
writeCmd[10] = 0X01; //Command code
writeCmd[11] = 0X02; //Command code,0101表示读取,0102 写入数据
writeCmd[12] = 0X82; //82 地址区域DM区
//下标从0开始,第13和14这2个字节表示起始地址
writeCmd[13] = startBytes[1];
writeCmd[14] = startBytes[0];
writeCmd[15] = 0x00;
writeCmd[16] = lenBytes[1];
writeCmd[17] = lenBytes[0];
for (int i = 0; i < buff.Length; i++)
{
int value = 0;
if (buff[i] != null)
{
int.TryParse(buff[i].ToString(), out value);
}
byte[] byteValues = BitConverter.GetBytes((short)value);
writeCmd[18 + i * 2] = byteValues[1];
writeCmd[18 + i * 2 + 1] = byteValues[0];
}
Console.WriteLine(BitConverter.ToString(writeCmd).Replace("-", String.Empty));
return writeCmd;
}
/// <summary>
/// 连接测试
/// </summary>
/// <returns></returns>
private bool Connection()
{
byte[] readCmd = this.GetReadCmd(0, 1);
int size = this.server.SendTo(readCmd, readCmd.Length, SocketFlags.None, this.plcIpEndPoint);
if (size < readCmd.Length)
{
this.State = false;
return this.State;
}
byte[] buffer = new byte[256 + 14];
int recv = server.ReceiveFrom(buffer, ref this.remote);//返回收到的字节数
if (recv != 0)
{
return true;
}
return false;
}
#endregion
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。