You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
501 lines
17 KiB
501 lines
17 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace FINSTCPIP
|
|
{
|
|
//用来解析FINS包的数据和将数据打包
|
|
public class FINSParse
|
|
{
|
|
public FINSParse ()
|
|
{
|
|
_socketConnected = false;
|
|
_finsConnected = false;
|
|
_ifIniting = true;
|
|
}
|
|
|
|
readonly byte[] HEADER_FINS = new byte[4] { FINS_F, FINS_I, FINS_N, FINS_S }; //FINS 的 ASCII
|
|
|
|
public byte[] _clientaddr = new byte[4]; //客户端IP第四节(程序)
|
|
public byte[] _serveraddr = new byte[4]; //服务端IP第四节(PLC)
|
|
public bool _socketConnected; //socket连接
|
|
public bool _finsConnected; //FINS连接
|
|
public bool _ifIniting; //正在初始化
|
|
public int connectTimes;
|
|
|
|
|
|
string _errname = string.Empty;
|
|
public string CommLayerError
|
|
{
|
|
get { return _errname; }
|
|
set { _errname = value; }
|
|
}
|
|
public const byte SID = 255;
|
|
|
|
#region 『FINS/TCP Header 』
|
|
// Header
|
|
public const byte FINS_F = 0x46;
|
|
public const byte FINS_I = 0x49;
|
|
public const byte FINS_N = 0x4e;
|
|
public const byte FINS_S = 0x53;
|
|
|
|
// Length 报文中的字段长度,从 Command 开始,到报文结束(不包括Header的长度和Length的长度)
|
|
public const byte SHAKEHAND_SEND_LENGTH = 12; //FINS 握手请求报文中 Length 的值
|
|
public const byte SHAKEHAND_RECV_LENGTH = 16; //FINS 握手回复报文中 Length 的值
|
|
public const byte FRAM_NODATA_SEND_LENGTH = 26; //FINS 发送读报文中无数据的 Length 的值
|
|
public const byte FRAM_NODATA_RECV_LENGTH = 22; //FINS 收到读报文中无数据的 Length 的值
|
|
|
|
// Command
|
|
public const byte CLIENT_SERVER = 0;
|
|
public const byte SERVER_CLIENT = 1;
|
|
public const byte FINS_FRAME = 2;
|
|
public const byte ERROR_FRAME = 3;
|
|
public const byte CONNECT_RIGHT = 6;
|
|
|
|
// ErrorCode
|
|
// 0 正常
|
|
// 01 Header不正确
|
|
// 02 数据过长
|
|
// 03 命令Command错误
|
|
// 20 连接/通道被占用
|
|
#endregion
|
|
|
|
|
|
/// <summary>
|
|
/// 返回 FINS 握手需要的数据
|
|
/// </summary>
|
|
public byte[] GetConnectInfo(byte clientAddr = 0)
|
|
{
|
|
byte[] SendByte = new byte[8 + SHAKEHAND_SEND_LENGTH];
|
|
|
|
SendByte[0] = HEADER_FINS[0]; //header
|
|
SendByte[1] = HEADER_FINS[1];
|
|
SendByte[2] = HEADER_FINS[2];
|
|
SendByte[3] = HEADER_FINS[3];
|
|
|
|
SendByte[4] = 0; //cmd length
|
|
SendByte[5] = 0;
|
|
SendByte[6] = 0;
|
|
SendByte[7] = SHAKEHAND_SEND_LENGTH;
|
|
|
|
SendByte[8] = 0; //frame command
|
|
SendByte[9] = 0;
|
|
SendByte[10] = 0;
|
|
SendByte[11] = CLIENT_SERVER;
|
|
|
|
SendByte[12] = 0;//err
|
|
SendByte[13] = 0;
|
|
SendByte[14] = 0;
|
|
SendByte[15] = 0;
|
|
|
|
SendByte[16] = 0;//FINS Frame (本机节点)(0为自动,PLC返回)
|
|
SendByte[17] = 0;
|
|
SendByte[18] = 0;
|
|
SendByte[19] = clientAddr;
|
|
|
|
return SendByte;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 读取 DM 内存时所需的信息。由上层定时调用
|
|
/// </summary>
|
|
/// <param name="startAdress">要读取的起始地址</param>
|
|
/// <param name="lenth">要读的长度</param>
|
|
/// <param name="isbit">是否按位读取</param>
|
|
public byte[] GetInfo_DMRead(ushort startAdress, ushort lenth, bool isbit = false)
|
|
{
|
|
try
|
|
{
|
|
byte[] SendByte = new byte[FRAM_NODATA_SEND_LENGTH + 8];
|
|
|
|
SendByte[0] = HEADER_FINS[0]; //header
|
|
SendByte[1] = HEADER_FINS[1];
|
|
SendByte[2] = HEADER_FINS[2];
|
|
SendByte[3] = HEADER_FINS[3];
|
|
|
|
SendByte[4] = 0; //cmd length
|
|
SendByte[5] = 0;
|
|
SendByte[6] = 0;
|
|
SendByte[7] = FRAM_NODATA_SEND_LENGTH;
|
|
|
|
SendByte[8] = 0;//frame command
|
|
SendByte[9] = 0;
|
|
SendByte[10] = 0;
|
|
SendByte[11] = FINS_FRAME;
|
|
|
|
SendByte[12] = 0;//err
|
|
SendByte[13] = 0;
|
|
SendByte[14] = 0;
|
|
SendByte[15] = 0;
|
|
|
|
//command frame header
|
|
SendByte[16] = 0x80;//ICF
|
|
SendByte[17] = 0x00;//RSV
|
|
SendByte[18] = 0x02;//GCT, less than 8 network layers
|
|
SendByte[19] = 0x00;//DNA, local network
|
|
SendByte[20] = _serveraddr[3];//DA1
|
|
SendByte[21] = 0x00;//DA2, CPU unit
|
|
SendByte[22] = 0x00;//SNA, local network
|
|
SendByte[23] = _clientaddr[3];//SA1
|
|
SendByte[24] = 0x00;//SA2, CPU unit
|
|
SendByte[25] = Convert.ToByte(SID);//SID
|
|
|
|
SendByte[26] = 0x01; //0101 Read
|
|
SendByte[27] = 0x01;
|
|
|
|
|
|
byte[] head = AddrToByte("DM", startAdress, lenth, isbit);
|
|
SendByte[28] = head[0];
|
|
SendByte[29] = head[1];
|
|
SendByte[30] = head[2];
|
|
SendByte[31] = head[3];
|
|
SendByte[32] = head[4];
|
|
SendByte[33] = head[5];
|
|
|
|
return SendByte;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 写入 DM 内存时所需的信息。由上层定时调用.(lenth好像不太对,目前只保证写16位的变量时没问题)
|
|
/// </summary>
|
|
/// <param name="startAdress">要写入的起始地址</param>
|
|
/// <param name="lenth">要写的长度</param>
|
|
/// <param name="data">要写数据</param>
|
|
/// <param name="isbit">是否按位写入</param>
|
|
public byte[] GetInfo_DMWrite(ushort startAdress, ushort lenth, byte[] data, bool isbit = false)
|
|
{
|
|
try
|
|
{
|
|
byte[] SendByte = new byte[8 + data.Length + FRAM_NODATA_SEND_LENGTH];
|
|
SendByte[0] = HEADER_FINS[0];
|
|
SendByte[1] = HEADER_FINS[1];
|
|
SendByte[2] = HEADER_FINS[2];
|
|
SendByte[3] = HEADER_FINS[3];
|
|
|
|
|
|
SendByte[4] = 0;//cmd length
|
|
SendByte[5] = 0;
|
|
SendByte[6] = 0;
|
|
SendByte[7] = Convert.ToByte(FRAM_NODATA_SEND_LENGTH + data.Length);
|
|
|
|
SendByte[8] = 0;//frame command
|
|
SendByte[9] = 0;
|
|
SendByte[10] = 0;
|
|
SendByte[11] = FINS_FRAME;
|
|
|
|
SendByte[12] = 0;//err
|
|
SendByte[13] = 0;
|
|
SendByte[14] = 0;
|
|
SendByte[15] = 0;
|
|
|
|
//command frame header
|
|
SendByte[16] = 0x80;//ICF
|
|
SendByte[17] = 0x00;//RSV
|
|
SendByte[18] = 0x02;//GCT, less than 8 network layers
|
|
SendByte[19] = 0x00;//DNA, local network
|
|
SendByte[20] = _serveraddr[3];//DA1
|
|
SendByte[21] = 0x00;//DA2, CPU unit
|
|
SendByte[22] = 0x00;//SNA, local network
|
|
SendByte[23] = _clientaddr[3];//SA1
|
|
SendByte[24] = 0x00;//SA2, CPU unit
|
|
SendByte[25] = Convert.ToByte(SID);//SID
|
|
|
|
SendByte[26] = 0x01; //0102 Write
|
|
SendByte[27] = 0x02;
|
|
|
|
|
|
byte[] head = AddrToByte("DM", startAdress, lenth, isbit);
|
|
SendByte[28] = head[0];
|
|
SendByte[29] = head[1];
|
|
SendByte[30] = head[2];
|
|
SendByte[31] = head[3];
|
|
SendByte[32] = head[4];
|
|
SendByte[33] = head[5];
|
|
|
|
Array.Copy(data, 0, SendByte, 34, data.Length);
|
|
|
|
return SendByte;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw ex; //理论上不会报错
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 地址格式转换.PLC的内存地址(区域.地址.字长度 如:WR.10.4)
|
|
/// </summary>
|
|
/// <param name="memory">内存区域</param>
|
|
/// <param name="startaddr">起始地址</param>
|
|
/// <param name="len">读写长度</param>
|
|
/// <param name="isBit">数据类型为位</param>
|
|
private byte[] AddrToByte(string memory, int startaddr, int len, bool isBit)
|
|
{
|
|
|
|
byte[] CH = BitConverter.GetBytes(startaddr);
|
|
byte[] Count = BitConverter.GetBytes(len);
|
|
byte[] Addrs = new byte[6];
|
|
|
|
if (!isBit) //字处理
|
|
{
|
|
switch (memory)
|
|
{
|
|
case "CIO":
|
|
Addrs[0] = 0xB0;
|
|
break;
|
|
case "WR":
|
|
Addrs[0] = 0xB1;
|
|
break;
|
|
case "DM":
|
|
Addrs[0] = 0x82;
|
|
break;
|
|
case "HR":
|
|
Addrs[0] = 0xB2;
|
|
break;
|
|
case "TIM":
|
|
Addrs[0] = 0x89;
|
|
break;
|
|
case "AR":
|
|
Addrs[0] = 0xB3;
|
|
break;
|
|
case "CNT":
|
|
Addrs[0] = 0x89;
|
|
break;
|
|
default:
|
|
Addrs[0] = 0x00;
|
|
break;
|
|
}
|
|
|
|
Addrs[1] = CH[1];
|
|
Addrs[2] = CH[0];
|
|
Addrs[3] = 0x00;
|
|
Addrs[4] = Count[1];
|
|
Addrs[5] = Count[0];//读写字的长度
|
|
}
|
|
else //位处理
|
|
{
|
|
switch (memory)
|
|
{
|
|
case "CIO":
|
|
Addrs[0] = 0x30;
|
|
break;
|
|
case "WR":
|
|
Addrs[0] = 0x31;
|
|
break;
|
|
case "DM":
|
|
Addrs[0] = 0x02;
|
|
break;
|
|
case "HR":
|
|
Addrs[0] = 0x32;
|
|
break;
|
|
case "TIM":
|
|
Addrs[0] = 0x09;
|
|
break;
|
|
case "AR":
|
|
Addrs[0] = 0x33;
|
|
break;
|
|
case "CNT":
|
|
Addrs[0] = 0x09;
|
|
break;
|
|
default:
|
|
Addrs[0] = 0x00;
|
|
break;
|
|
}
|
|
|
|
Addrs[1] = CH[1];
|
|
Addrs[2] = CH[0];
|
|
Addrs[3] = Count[0];
|
|
Addrs[4] = 0x00;
|
|
Addrs[5] = 0x01;//每次读写一位
|
|
}
|
|
|
|
return Addrs;
|
|
}
|
|
|
|
//前四位是 'FINS'
|
|
public bool IsHeader(byte[] recv)
|
|
{
|
|
if (recv.Length < HEADER_FINS.Length)
|
|
{
|
|
CommLayerError = "Header is too short, not enough four!";
|
|
return false;
|
|
}
|
|
if (recv[0] != HEADER_FINS[0] || recv[1] != HEADER_FINS[1] || recv[2] != HEADER_FINS[2] || recv[3] != HEADER_FINS[3])
|
|
{
|
|
CommLayerError = "Header is not 'FINS'!";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//根据接收到的包,来判断FINS连接是否成功,并更新_clientaddr和_serveraddr
|
|
public bool IsConnectRight(List<byte> recv)
|
|
{
|
|
if (recv.Count < SHAKEHAND_RECV_LENGTH + 8)
|
|
{
|
|
CommLayerError = "建立连接时,收到的数据长度不够。";
|
|
return false;
|
|
}
|
|
int err = 0;
|
|
if (recv[11] == ERROR_FRAME || recv[14] != 0 || recv[15] != 0)
|
|
{
|
|
switch (recv[15])
|
|
{
|
|
case 0x01:
|
|
err = 2;
|
|
CommLayerError = "ERROR CODE:2——header is not 'FINS'";
|
|
break;//the head is not 'FINS'
|
|
case 0x02:
|
|
err = 3;
|
|
CommLayerError = "ERROR CODE:4——the data length is too long";
|
|
break;//the data length is too long
|
|
case 0x03:
|
|
err = 4;
|
|
CommLayerError = "ERROR CODE:8——the command is not supported";
|
|
break;//the command is not supported
|
|
case 0x20:
|
|
err = 32;
|
|
CommLayerError = "ERROR CODE:32——PLC 拒绝访问,请重启 PLC!";
|
|
break;//the command is not supported
|
|
default:
|
|
err = 1;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if (recv[7] == SHAKEHAND_RECV_LENGTH && recv[11] == SERVER_CLIENT && recv[15] == 0)
|
|
{
|
|
_clientaddr[0] = recv[16];
|
|
_clientaddr[1] = recv[17];
|
|
_clientaddr[2] = recv[18];
|
|
_clientaddr[3] = recv[19];
|
|
|
|
_serveraddr[0] = recv[20];
|
|
_serveraddr[1] = recv[21];
|
|
_serveraddr[2] = recv[22];
|
|
_serveraddr[3] = recv[23];
|
|
}
|
|
|
|
|
|
if (err == 0)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
//需要返回错误类型(未完成)
|
|
private bool IsFrameHeaderRight(byte[] recv)
|
|
{
|
|
int err = 0;
|
|
if (recv[0] != 128 && recv[0] != 192)
|
|
{
|
|
err = 1;// ICF错误
|
|
}
|
|
if (recv[1] != 0)
|
|
{
|
|
err = 1;
|
|
}
|
|
if (recv[2] != 2)
|
|
{
|
|
err = 1;
|
|
}
|
|
if (recv[4] != _clientaddr[3])
|
|
{
|
|
err = 1;
|
|
}
|
|
if (recv[7] != _serveraddr[3])
|
|
{
|
|
err = 1;
|
|
}
|
|
if (recv[9] != SID)
|
|
{
|
|
//err = 1;
|
|
}
|
|
if (err == 0)
|
|
return true;
|
|
else
|
|
return false;
|
|
|
|
}
|
|
//
|
|
private bool IsFrameCommandRight(byte[] recv)
|
|
{
|
|
int err = -1;
|
|
if (recv[0] == 1 && recv[1] == 1 && recv[2] == 0 && recv[3] == 0)
|
|
{//读内存
|
|
err = 0;
|
|
}
|
|
if (recv[0] == 1 && recv[1] == 2 && recv[2] == 0 && recv[3] == 0)
|
|
{//写内存
|
|
err = 0;
|
|
}
|
|
|
|
if (err == 0)
|
|
return true;
|
|
else
|
|
return false;
|
|
|
|
}
|
|
|
|
//
|
|
public bool IsRecvRight(List<byte> recv)
|
|
{
|
|
if (recv.Count < FRAM_NODATA_RECV_LENGTH + 8)
|
|
{
|
|
CommLayerError = "数据长度不够。";
|
|
return false;
|
|
}
|
|
int err = 0;
|
|
if (recv[11] == ERROR_FRAME)
|
|
{
|
|
switch (recv[15])
|
|
{
|
|
case 0x01: err = 2; CommLayerError = "ERROR CODE:2——the head is not 'FINS'。"; break;//the head is not 'FINS'
|
|
case 0x02: err = 3; CommLayerError = "ERROR CODE:4——the data length is too long。"; break;//the data length is too long
|
|
case 0x03: err = 4; CommLayerError = "ERROR CODE:8——the command is not supported。"; break;//the command is not supported
|
|
}
|
|
|
|
}
|
|
|
|
if (recv[7] >= FRAM_NODATA_RECV_LENGTH && recv[11] == FINS_FRAME && recv[15] == 0)
|
|
{
|
|
|
|
byte[] header = new byte[10];
|
|
recv.CopyTo(SHAKEHAND_RECV_LENGTH, header, 0, 10);
|
|
if (IsFrameHeaderRight(header) == false)
|
|
{
|
|
err = 1;
|
|
CommLayerError = "FrameHeader不正确。";
|
|
}
|
|
byte[] commad = new byte[4];
|
|
recv.CopyTo(FRAM_NODATA_SEND_LENGTH, commad, 0, 4);
|
|
if (IsFrameCommandRight(commad) == false)
|
|
{
|
|
err = 1;
|
|
CommLayerError = "FrameCommand不正确。";
|
|
}
|
|
}
|
|
|
|
if (err == 0)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|