|
|
@@ -0,0 +1,872 @@
|
|
|
+using System.Collections.Concurrent;
|
|
|
+using System.Reflection;
|
|
|
+using YSAI.Core.attribute;
|
|
|
+using YSAI.Core.communication.net.tcp.client;
|
|
|
+using YSAI.Core.data;
|
|
|
+using YSAI.Core.@interface;
|
|
|
+using YSAI.Unility;
|
|
|
+using static YSAI.Mitsubishi.MitsubishiData;
|
|
|
+
|
|
|
+namespace YSAI.Mitsubishi
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// 三菱PLC通信客户端
|
|
|
+ /// </summary>
|
|
|
+ public class MitsubishiOperate : IBaseAbstract, IDaq
|
|
|
+ {
|
|
|
+ protected override string TAG => "MitsubishiOperate";
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 锁
|
|
|
+ /// </summary>
|
|
|
+ private static readonly object Lock = new object();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 自身对象集合
|
|
|
+ /// </summary>
|
|
|
+ private static List<MitsubishiOperate> ThisObjList = new List<MitsubishiOperate>();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 单例模式
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static MitsubishiOperate Instance(MitsubishiData.Basics basics)
|
|
|
+ {
|
|
|
+ if (ThisObjList.Count >= MaxInstanceCount)
|
|
|
+ {
|
|
|
+ throw new Exception(ExceedMaxInstanceCountTips);
|
|
|
+ }
|
|
|
+ MitsubishiOperate? exp = ThisObjList.FirstOrDefault(c => c.basics.Comparer(basics).result);
|
|
|
+ if (exp == null)
|
|
|
+ {
|
|
|
+ lock (Lock)
|
|
|
+ {
|
|
|
+ if (ThisObjList.Count(c => c.basics.Comparer(basics).result) > 0)
|
|
|
+ {
|
|
|
+ return ThisObjList.First(c => c.basics.Comparer(basics).result);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ MitsubishiOperate exp2 = new MitsubishiOperate(basics);
|
|
|
+ ThisObjList.Add(exp2);
|
|
|
+ return exp2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return exp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 构造函数
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="param">参数</param>
|
|
|
+ public MitsubishiOperate(MitsubishiData.Basics basics)
|
|
|
+ {
|
|
|
+ this.basics = basics;
|
|
|
+ }
|
|
|
+ public MitsubishiOperate()
|
|
|
+ { }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 基础数据
|
|
|
+ /// </summary>
|
|
|
+ private MitsubishiData.Basics basics;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ ///SOCKET TCP Client
|
|
|
+ /// </summary>
|
|
|
+ private TcpClientOperate tcpClientOperate;
|
|
|
+
|
|
|
+ #region 基础
|
|
|
+ /// <summary>
|
|
|
+ /// Qna_3E地址解析
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="address">地址名</param>
|
|
|
+ /// <param name="toUpper">转换成大写</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private McAddressDetails ParseQNA3E(string address, McDT dataType = McDT.None, bool toUpper = true)
|
|
|
+ {
|
|
|
+ if (toUpper) address = address.ToUpper();
|
|
|
+ var addressInfo = new McAddressDetails()
|
|
|
+ {
|
|
|
+ DataType = dataType
|
|
|
+ };
|
|
|
+ switch (address[0])
|
|
|
+ {
|
|
|
+ case 'M'://M中间继电器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x90 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'X':// X输入继电器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x9C };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 16;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'Y'://Y输出继电器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x9D };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 16;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'D'://D数据寄存器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xA8 };
|
|
|
+ addressInfo.BitType = 0x00;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'W'://W链接寄存器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xB4 };
|
|
|
+ addressInfo.BitType = 0x00;
|
|
|
+ addressInfo.Format = 16;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'L'://L锁存继电器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x92 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'F'://F报警器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x93 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'V'://V边沿继电器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x94 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'B'://B链接继电器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xA0 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 16;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'R'://R文件寄存器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xAF };
|
|
|
+ addressInfo.BitType = 0x00;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'S':
|
|
|
+ {
|
|
|
+ //累计定时器的线圈
|
|
|
+ if (address[1] == 'C')
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xC6 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 2);
|
|
|
+ }
|
|
|
+ //累计定时器的触点
|
|
|
+ else if (address[1] == 'S')
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xC7 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 2);
|
|
|
+ }
|
|
|
+ //累计定时器的当前值
|
|
|
+ else if (address[1] == 'N')
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xC8 };
|
|
|
+ addressInfo.BitType = 0x00;
|
|
|
+ addressInfo.Format = 100;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 2);
|
|
|
+ }
|
|
|
+ // S步进继电器
|
|
|
+ else
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x98 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case 'Z':
|
|
|
+ {
|
|
|
+ //文件寄存器ZR区
|
|
|
+ if (address[1] == 'R')
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xB0 };
|
|
|
+ addressInfo.BitType = 0x00;
|
|
|
+ addressInfo.Format = 16;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 2);
|
|
|
+ }
|
|
|
+ //变址寄存器
|
|
|
+ else
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xCC };
|
|
|
+ addressInfo.BitType = 0x00;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case 'T':
|
|
|
+ {
|
|
|
+ // 定时器的当前值
|
|
|
+ if (address[1] == 'N')
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xC2 };
|
|
|
+ addressInfo.BitType = 0x00;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 2);
|
|
|
+ }
|
|
|
+ //定时器的触点
|
|
|
+ else if (address[1] == 'S')
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xC1 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 2);
|
|
|
+ }
|
|
|
+ //定时器的线圈
|
|
|
+ else if (address[1] == 'C')
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xC0 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 2);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case 'C':
|
|
|
+ {
|
|
|
+ //计数器的当前值
|
|
|
+ if (address[1] == 'N')
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xC5 };
|
|
|
+ addressInfo.BitType = 0x00;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 2);
|
|
|
+ }
|
|
|
+ //计数器的触点
|
|
|
+ else if (address[1] == 'S')
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xC4 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 2);
|
|
|
+ }
|
|
|
+ //计数器的线圈
|
|
|
+ else if (address[1] == 'C')
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0xC3 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 2);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return addressInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A_1E地址解析
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="address">地址名</param>
|
|
|
+ /// <param name="toUpper">转换成大写</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private McAddressDetails ParseA1E(string address, bool toUpper = true)
|
|
|
+ {
|
|
|
+ if (toUpper) address = address.ToUpper();
|
|
|
+ var addressInfo = new McAddressDetails();
|
|
|
+ switch (address[0])
|
|
|
+ {
|
|
|
+ case 'X'://X输入寄存器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x58, 0x20 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 8;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'Y'://Y输出寄存器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x59, 0x20 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 8;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'M'://M中间寄存器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x4D, 0x20 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'S'://S状态寄存器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x53, 0x20 };
|
|
|
+ addressInfo.BitType = 0x01;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'D'://D数据寄存器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x44, 0x20 };
|
|
|
+ addressInfo.BitType = 0x00;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'R'://R文件寄存器
|
|
|
+ {
|
|
|
+ addressInfo.TypeCode = new byte[] { 0x52, 0x20 };
|
|
|
+ addressInfo.BitType = 0x00;
|
|
|
+ addressInfo.Format = 10;
|
|
|
+ addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format);
|
|
|
+ addressInfo.TypeChar = address.Substring(0, 1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return addressInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// QNA3E读取命令
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="beginAddress">地址</param>
|
|
|
+ /// <param name="typeCode">类型代码</param>
|
|
|
+ /// <param name="length">长度</param>
|
|
|
+ /// <param name="isBit">是不是布尔类型</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private byte[] ReadCommand_QNA3E(int beginAddress, byte[] typeCode, ushort length, bool isBit)
|
|
|
+ {
|
|
|
+ if (!isBit) length = (ushort)(length / 2);
|
|
|
+
|
|
|
+ byte[] command = new byte[21];
|
|
|
+ command[0] = 0x50;
|
|
|
+ command[1] = 0x00; //副头部
|
|
|
+ command[2] = 0x00; //网络编号
|
|
|
+ command[3] = 0xFF; //PLC编号
|
|
|
+ command[4] = 0xFF;
|
|
|
+ command[5] = 0x03; //IO编号
|
|
|
+ command[6] = 0x00; //模块站号
|
|
|
+ command[7] = (byte)((command.Length - 9) % 256);
|
|
|
+ command[8] = (byte)((command.Length - 9) / 256); // 请求数据长度
|
|
|
+ command[9] = 0x0A;
|
|
|
+ command[10] = 0x00; //时钟
|
|
|
+ command[11] = 0x01;
|
|
|
+ command[12] = 0x04;//指令(0x01 0x04读 0x01 0x14写)
|
|
|
+ command[13] = isBit ? (byte)0x01 : (byte)0x00;//子指令(位 或 字节为单位)
|
|
|
+ command[14] = 0x00;
|
|
|
+ command[15] = BitConverter.GetBytes(beginAddress)[0];// 起始地址的地位
|
|
|
+ command[16] = BitConverter.GetBytes(beginAddress)[1];
|
|
|
+ command[17] = BitConverter.GetBytes(beginAddress)[2];
|
|
|
+ command[18] = typeCode[0]; //数据类型
|
|
|
+ command[19] = (byte)(length % 256);
|
|
|
+ command[20] = (byte)(length / 256); //长度
|
|
|
+ return command;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A1E读取命令
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="beginAddress">地址</param>
|
|
|
+ /// <param name="typeCode">类型代码</param>
|
|
|
+ /// <param name="length">长度</param>
|
|
|
+ /// <param name="isBit">是不是布尔类型</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private byte[] ReadCommand_A1E(int beginAddress, byte[] typeCode, ushort length, bool isBit)
|
|
|
+ {
|
|
|
+ if (!isBit)
|
|
|
+ length = (ushort)(length / 2);
|
|
|
+ byte[] command = new byte[12];
|
|
|
+ command[0] = isBit ? (byte)0x00 : (byte)0x01;//副头部
|
|
|
+ command[1] = 0xFF; //PLC编号
|
|
|
+ command[2] = 0x0A;
|
|
|
+ command[3] = 0x00;
|
|
|
+ command[4] = BitConverter.GetBytes(beginAddress)[0]; //
|
|
|
+ command[5] = BitConverter.GetBytes(beginAddress)[1]; // 开始读取的地址
|
|
|
+ command[6] = 0x00;
|
|
|
+ command[7] = 0x00;
|
|
|
+ command[8] = typeCode[1];
|
|
|
+ command[9] = typeCode[0];
|
|
|
+ command[10] = (byte)(length % 256);//长度
|
|
|
+ command[11] = (byte)(length / 256);
|
|
|
+ return command;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// QNA3E写入命令
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="beginAddress">地址</param>
|
|
|
+ /// <param name="typeCode">类型代码</param>
|
|
|
+ /// <param name="data">数据</param>
|
|
|
+ /// <param name="isBit">是不是布尔类型</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private byte[] WriteCommand_QNA3E(int beginAddress, byte[] typeCode, byte[] data, bool isBit)
|
|
|
+ {
|
|
|
+ var length = data.Length / 2;
|
|
|
+ if (isBit) length = 1;
|
|
|
+
|
|
|
+ byte[] command = new byte[21 + data.Length];
|
|
|
+ command[0] = 0x50;
|
|
|
+ command[1] = 0x00; //副头部
|
|
|
+ command[2] = 0x00; //网络编号
|
|
|
+ command[3] = 0xFF; //PLC编号
|
|
|
+ command[4] = 0xFF;
|
|
|
+ command[5] = 0x03; //IO编号
|
|
|
+ command[6] = 0x00; //模块站号
|
|
|
+ command[7] = (byte)((command.Length - 9) % 256);// 请求数据长度
|
|
|
+ command[8] = (byte)((command.Length - 9) / 256);
|
|
|
+ command[9] = 0x0A;
|
|
|
+ command[10] = 0x00; //时钟
|
|
|
+ command[11] = 0x01;
|
|
|
+ command[12] = 0x14;//指令(0x01 0x04读 0x01 0x14写)
|
|
|
+ command[13] = isBit ? (byte)0x01 : (byte)0x00;//子指令(位 或 字节为单位)
|
|
|
+ command[14] = 0x00;
|
|
|
+ command[15] = BitConverter.GetBytes(beginAddress)[0];// 起始地址的地位
|
|
|
+ command[16] = BitConverter.GetBytes(beginAddress)[1];
|
|
|
+ command[17] = BitConverter.GetBytes(beginAddress)[2];
|
|
|
+ command[18] = typeCode[0];//数据类型
|
|
|
+ command[19] = (byte)(length % 256);
|
|
|
+ command[20] = (byte)(length / 256); //长度
|
|
|
+ data.Reverse().ToArray().CopyTo(command, 21);
|
|
|
+ return command;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A1E写入命令
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="beginAddress">地址</param>
|
|
|
+ /// <param name="typeCode">类型代码</param>
|
|
|
+ /// <param name="data">数据</param>
|
|
|
+ /// <param name="isBit">是不是布尔类型</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private byte[] WriteCommand_A1E(int beginAddress, byte[] typeCode, byte[] data, bool isBit)
|
|
|
+ {
|
|
|
+ var length = data.Length / 2;
|
|
|
+ if (isBit) length = data.Length;
|
|
|
+
|
|
|
+ byte[] command = new byte[12 + data.Length];
|
|
|
+ command[0] = isBit ? (byte)0x02 : (byte)0x03; //副标题
|
|
|
+ command[1] = 0xFF; // PLC号
|
|
|
+ command[2] = 0x0A;
|
|
|
+ command[3] = 0x00;
|
|
|
+ command[4] = BitConverter.GetBytes(beginAddress)[0]; //
|
|
|
+ command[5] = BitConverter.GetBytes(beginAddress)[1]; //起始地址的地位
|
|
|
+ command[6] = 0x00;
|
|
|
+ command[7] = 0x00;
|
|
|
+ command[8] = typeCode[1]; //
|
|
|
+ command[9] = typeCode[0]; //数据类型
|
|
|
+ command[10] = (byte)(length % 256);
|
|
|
+ command[11] = (byte)(length / 256);
|
|
|
+ data.Reverse().ToArray().CopyTo(command, 12);
|
|
|
+ return command;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 读取数据
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="address">地址</param>
|
|
|
+ /// <param name="length">长度</param>
|
|
|
+ /// <param name="isBit">是不是布尔类型</param>
|
|
|
+ /// <returns>统一返回(byte[])</returns>
|
|
|
+ private byte[] R(string address, ushort length, bool isBit = false)
|
|
|
+ {
|
|
|
+ //地址详情
|
|
|
+ McAddressDetails details = null;
|
|
|
+ //命令字节
|
|
|
+ byte[]? command = null;
|
|
|
+ //类型判断
|
|
|
+ switch (basics.DType)
|
|
|
+ {
|
|
|
+ case DevType.A1E:
|
|
|
+ details = ParseA1E(address);
|
|
|
+ command = ReadCommand_A1E(details.BeginAddress, details.TypeCode, length, isBit);
|
|
|
+ break;
|
|
|
+ case DevType.QNA3E:
|
|
|
+ details = ParseQNA3E(address);
|
|
|
+ command = ReadCommand_QNA3E(details.BeginAddress, details.TypeCode, length, isBit);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ //发送等待结果
|
|
|
+ OperateResult operateResult = null;
|
|
|
+ switch (basics.DType)
|
|
|
+ {
|
|
|
+ case DevType.A1E:
|
|
|
+ var lenght = command[10] + command[11] * 256;
|
|
|
+ if (isBit)
|
|
|
+ {
|
|
|
+ operateResult = tcpClientOperate.SendWait(command, (int)Math.Ceiling(lenght * 0.5) + 2, 4096);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ operateResult = tcpClientOperate.SendWait(command);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case DevType.QNA3E:
|
|
|
+ operateResult = tcpClientOperate.SendWait(command);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!operateResult.State)
|
|
|
+ {
|
|
|
+ //读取失败返回
|
|
|
+ return new byte[] { };
|
|
|
+ }
|
|
|
+ //字节数组转换
|
|
|
+ byte[]? bytes = operateResult.GetRData<byte[]>();
|
|
|
+ if (bytes == null)
|
|
|
+ {
|
|
|
+ //读取失败返回
|
|
|
+ return new byte[] { };
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 写入数据
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="address">地址</param>
|
|
|
+ /// <param name="data">数据</param>
|
|
|
+ /// <param name="isBit">是不是布尔类型</param>
|
|
|
+ /// <returns>统一返回(写入状态)</returns>
|
|
|
+ private bool W(string address, byte[] data, bool isBit = false)
|
|
|
+ {
|
|
|
+ //数据反转
|
|
|
+ Array.Reverse(data);
|
|
|
+ //地址详情
|
|
|
+ McAddressDetails details = null;
|
|
|
+ //命令字节
|
|
|
+ byte[]? command = null;
|
|
|
+ //类型判断
|
|
|
+ switch (basics.DType)
|
|
|
+ {
|
|
|
+ case DevType.A1E:
|
|
|
+ details = ParseA1E(address);
|
|
|
+ command = WriteCommand_A1E(details.BeginAddress, details.TypeCode, data, isBit);
|
|
|
+ break;
|
|
|
+ case DevType.QNA3E:
|
|
|
+ details = ParseQNA3E(address);
|
|
|
+ command = WriteCommand_QNA3E(details.BeginAddress, details.TypeCode, data, isBit);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ //发送等待结果,只要有响应说明写入成功
|
|
|
+ return tcpClientOperate.SendWait(command).State;
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ public void Dispose()
|
|
|
+ {
|
|
|
+ Off();
|
|
|
+ GC.Collect();
|
|
|
+ GC.SuppressFinalize(this);
|
|
|
+ ThisObjList.Remove(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult GetStatus()
|
|
|
+ {
|
|
|
+ string SN = Depart("GetStatus");
|
|
|
+ if (tcpClientOperate == null)
|
|
|
+ {
|
|
|
+ return Break(SN, false, "未连接");
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return Break(SN, tcpClientOperate.GetStatus().State, tcpClientOperate.GetStatus().State ? "已连接" : "未连接");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> GetStatusAsync()
|
|
|
+ {
|
|
|
+ return Task.Run(() => GetStatus());
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult Off()
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> OffAsync()
|
|
|
+ {
|
|
|
+ return Task.Run(() => Off());
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult On()
|
|
|
+ {
|
|
|
+ string SN = Depart("On");
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (tcpClientOperate != null && tcpClientOperate.GetStatus().State)
|
|
|
+ {
|
|
|
+ return Break(SN, false, "已连接");
|
|
|
+ }
|
|
|
+ if (tcpClientOperate != null && !tcpClientOperate.GetStatus().State)
|
|
|
+ {
|
|
|
+ tcpClientOperate.Dispose();
|
|
|
+ }
|
|
|
+ //先实例化底层 socket 通信
|
|
|
+ tcpClientOperate = TcpClientOperate.Instance(new TcpClientData.Basics
|
|
|
+ {
|
|
|
+ InterruptReconnection = basics.InterruptReconnection,
|
|
|
+ Ip = basics.Ip,
|
|
|
+ Port = basics.Port,
|
|
|
+ ReconnectionInterval = basics.ReconnectionInterval,
|
|
|
+ SN = basics.SN,
|
|
|
+ Timeout = basics.Timeout,
|
|
|
+ SendWait = true,
|
|
|
+ SendWaitInterval = basics.Timeout
|
|
|
+ });
|
|
|
+ OperateResult operateResult = tcpClientOperate.On();
|
|
|
+ if (operateResult.State)
|
|
|
+ {
|
|
|
+ return Break(SN, true);
|
|
|
+ }
|
|
|
+ return Break(SN, false, operateResult.Message);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return Break(SN, false, ex.Message, Exception: ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> OnAsync()
|
|
|
+ {
|
|
|
+ return Task.Run(() => On());
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult Read(Address address)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> ReadAsync(Address address)
|
|
|
+ {
|
|
|
+ return Task.Run(() => Read(address));
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult Subscribe(Address address)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> SubscribeAsync(Address address)
|
|
|
+ {
|
|
|
+ return Task.Run(() => Subscribe(address));
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult UnSubscribe(Address address)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> UnSubscribeAsync(Address address)
|
|
|
+ {
|
|
|
+ return Task.Run(() => UnSubscribe(address));
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult Write<V>(ConcurrentDictionary<string, V> Values)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> WriteAsync<V>(ConcurrentDictionary<string, V> Values)
|
|
|
+ {
|
|
|
+ return Task.Run(() => Write(Values));
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult GetParam()
|
|
|
+ {
|
|
|
+ string SN = Depart("GetParam");
|
|
|
+ try
|
|
|
+ {
|
|
|
+ //通过反射得到参数信息
|
|
|
+ List<ReflexTool.LibInstanceParam>? libInstanceParams = ReflexTool.GetClassAllPropertyData<MitsubishiData.Basics>();
|
|
|
+ //名称
|
|
|
+ string name = TAG.Replace("Operate", string.Empty).Replace("Client", string.Empty);
|
|
|
+ //命名空间
|
|
|
+ string nameSpace = "YSAI.Mitsubishi.MitsubishiOperate";
|
|
|
+ //对象实例
|
|
|
+ MitsubishiData.Basics basics = new MitsubishiData.Basics();
|
|
|
+ //参数结构体
|
|
|
+ ParamStructure? paramStructure = new ParamStructure()
|
|
|
+ {
|
|
|
+ Name = name,
|
|
|
+ Description = name,
|
|
|
+ Subset = new List<ParamStructure.subset>
|
|
|
+ {
|
|
|
+ new ParamStructure.subset
|
|
|
+ {
|
|
|
+ Description = name,
|
|
|
+ Name = name,
|
|
|
+ Propertie = new List<ParamStructure.subset.propertie>()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ paramStructure.Subset[0].Propertie.Add(new ParamStructure.subset.propertie
|
|
|
+ {
|
|
|
+ PropertyName = "ServiceName",
|
|
|
+ Description = "命名空间",
|
|
|
+ Show = false,
|
|
|
+ Use = false,
|
|
|
+ Default = nameSpace,
|
|
|
+ DataCate = null
|
|
|
+ });
|
|
|
+ foreach (var lib in libInstanceParams)
|
|
|
+ {
|
|
|
+ //默认值
|
|
|
+ string Default = ReflexTool.GetModelValue(lib.Name, basics);
|
|
|
+ //前端展示特性
|
|
|
+ DisplayAttribute? displayAttribute = typeof(MitsubishiData.Basics).GetProperty(lib.Name).GetCustomAttribute<DisplayAttribute>();
|
|
|
+ //验证特性
|
|
|
+ VerifyAttribute? verifyAttribute = typeof(MitsubishiData.Basics).GetProperty(lib.Name).GetCustomAttribute<VerifyAttribute>();
|
|
|
+ //单位特性
|
|
|
+ UnitAttribute? unitAttribute = typeof(MitsubishiData.Basics).GetProperty(lib.Name).GetCustomAttribute<UnitAttribute>();
|
|
|
+ //描述上加单位
|
|
|
+ string Describe = lib.Describe;
|
|
|
+ if (unitAttribute != null && !string.IsNullOrWhiteSpace(unitAttribute.Unit))
|
|
|
+ {
|
|
|
+ Describe += $"({unitAttribute.Unit})";
|
|
|
+ }
|
|
|
+
|
|
|
+ ParamStructure.subset.propertie propertie = new ParamStructure.subset.propertie
|
|
|
+ {
|
|
|
+ PropertyName = lib.Name,
|
|
|
+ Description = Describe,
|
|
|
+
|
|
|
+ Show = displayAttribute?.Show ?? false,
|
|
|
+ Use = displayAttribute?.Use ?? false,
|
|
|
+ MustFillIn = displayAttribute?.MustFillIn ?? false,
|
|
|
+ DataCate = displayAttribute?.DataCate ?? null,
|
|
|
+
|
|
|
+ Regex = verifyAttribute?.Regex ?? null,
|
|
|
+ FailTips = verifyAttribute?.FailTips ?? null,
|
|
|
+
|
|
|
+ Default = Default
|
|
|
+ };
|
|
|
+ switch (displayAttribute?.DataCate)
|
|
|
+ {
|
|
|
+ case Core.data.ParamStructure.dataCate.select:
|
|
|
+ propertie.Options = new List<ParamStructure.subset.propertie.options>();
|
|
|
+ foreach (var val in lib.EnumArray as List<dynamic>)
|
|
|
+ {
|
|
|
+ string des = val.Describe;
|
|
|
+ if (!string.IsNullOrEmpty(des))
|
|
|
+ {
|
|
|
+ des = $"({val.Describe})";
|
|
|
+ }
|
|
|
+ propertie.Options.Add(new ParamStructure.subset.propertie.options
|
|
|
+ {
|
|
|
+ Key = val.Name + des,
|
|
|
+ Value = val.Value,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case Core.data.ParamStructure.dataCate.radio:
|
|
|
+ propertie.Options = new List<ParamStructure.subset.propertie.options>();
|
|
|
+ propertie.Options.Add(new ParamStructure.subset.propertie.options
|
|
|
+ {
|
|
|
+ Key = "是",
|
|
|
+ Value = true,
|
|
|
+ });
|
|
|
+ propertie.Options.Add(new ParamStructure.subset.propertie.options
|
|
|
+ {
|
|
|
+ Key = "否",
|
|
|
+ Value = false,
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ paramStructure.Subset[0].Propertie.Add(propertie);
|
|
|
+ }
|
|
|
+ return Break(SN, true, paramStructure.ToJson().JsonFormatting(), paramStructure, Core.@enum.ResultType.Object);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return Break(SN, false, ex.Message, Exception: ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> GetParamAsync()
|
|
|
+ {
|
|
|
+ return Task.Run(() => GetParam());
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult CreateInstance<T>(T Basics)
|
|
|
+ {
|
|
|
+ string SN = Depart("CreateInstance");
|
|
|
+ try
|
|
|
+ {
|
|
|
+ //先判断对象类型是否一致
|
|
|
+ if (typeof(T).FullName.Equals(typeof(MitsubishiData.Basics).FullName))
|
|
|
+ {
|
|
|
+ return Break(SN, true, RData: Instance(Basics as MitsubishiData.Basics));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return Break(SN, false, "对象类型错误,无法创建实例");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return Break(SN, false, ex.Message, Exception: ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> CreateInstanceAsync<T>(T Basics)
|
|
|
+ {
|
|
|
+ return Task.Run(() => CreateInstance(Basics));
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|