using HslCommunication.Serial; using HslCommunication.Core; using System; using System.Collections.Generic; using System.Linq; using System.Text; using HslCommunication.BasicFramework; namespace HslCommunication.Profinet.Melsec { /// /// 三菱PLC的计算机链接协议,适用的PLC型号参考备注 /// /// /// 支持的通讯的系列如下参考 /// /// /// 系列 /// 是否支持 /// 备注 /// /// /// FX3UC系列 /// 支持 /// /// /// /// FX3U系列 /// 支持 /// /// /// /// FX3GC系列 /// 支持 /// /// /// /// FX3G系列 /// 支持 /// /// /// /// FX3S系列 /// 支持 /// /// /// /// FX2NC系列 /// 支持 /// /// /// /// FX2N系列 /// 部分支持(v1.06+) /// 通过监控D8001来确认版本号 /// /// /// FX1NC系列 /// 支持 /// /// /// /// FX1N系列 /// 支持 /// /// /// /// FX1S系列 /// 支持 /// /// /// /// FX0N系列 /// 部分支持(v1.20+) /// /// /// /// FX0S系列 /// 不支持 /// /// /// /// FX0系列 /// 不支持 /// /// /// /// FX2C系列 /// 部分支持(v3.30+) /// /// /// /// FX2(FX)系列 /// 部分支持(v3.30+) /// /// /// /// FX1系列 /// 不支持 /// /// /// /// 数据地址支持的格式如下: /// /// /// 地址名称 /// 地址代号 /// 示例 /// 地址进制 /// 字操作 /// 位操作 /// 备注 /// /// /// 内部继电器 /// M /// M100,M200 /// 10 /// /// /// /// /// /// 输入继电器 /// X /// X10,X20 /// 8 /// /// /// /// /// /// 输出继电器 /// Y /// Y10,Y20 /// 8 /// /// /// /// /// /// 步进继电器 /// S /// S100,S200 /// 10 /// /// /// /// /// /// 定时器的触点 /// TS /// TS100,TS200 /// 10 /// /// /// /// /// /// 定时器的当前值 /// TN /// TN100,TN200 /// 10 /// /// × /// /// /// /// 计数器的触点 /// CS /// CS100,CS200 /// 10 /// /// /// /// /// /// 计数器的当前 /// CN /// CN100,CN200 /// 10 /// /// × /// /// /// /// 数据寄存器 /// D /// D1000,D2000 /// 10 /// /// × /// /// /// /// 文件寄存器 /// R /// R100,R200 /// 10 /// /// × /// /// /// /// public class MelsecFxLinks : SerialDeviceBase { #region Constructor /// /// 实例化默认的构造方法 /// public MelsecFxLinks( ) { WordLength = 1; } #endregion #region Public Member /// /// PLC的站号信息 /// public byte Station { get => station; set => station = value; } /// /// 报文等待时间,单位10ms,设置范围为0-15 /// public byte WaittingTime { get => watiingTime; set { if (watiingTime > 0x0F) { watiingTime = 0x0F; } else { watiingTime = value; } } } /// /// 是否启动和校验 /// public bool SumCheck { get => sumCheck; set => sumCheck = value; } #endregion #region Read Write Support /// /// 批量读取PLC的数据,以字为单位,支持读取X,Y,M,S,D,T,C,具体的地址范围需要根据PLC型号来确认 /// /// 地址信息 /// 数据长度 /// 读取结果信息 public override OperateResult Read( string address, ushort length ) { // 解析指令 OperateResult command = BuildReadCommand( this.station, address, length, false, sumCheck, watiingTime ); if (!command.IsSuccess) return OperateResult.CreateFailedResult( command ); // 核心交互 OperateResult read = ReadBase( command.Content ); if (!read.IsSuccess) return OperateResult.CreateFailedResult( read ); // 结果验证 if (read.Content[0] != 0x02) return new OperateResult( read.Content[0], "Read Faild:" + BasicFramework.SoftBasic.ByteToHexString( read.Content, ' ' ) ); // 提取结果 byte[] Content = new byte[length * 2]; for (int i = 0; i < Content.Length / 2; i++) { ushort tmp = Convert.ToUInt16( Encoding.ASCII.GetString( read.Content, i * 4 + 5, 4 ), 16 ); BitConverter.GetBytes( tmp ).CopyTo( Content, i * 2 ); } return OperateResult.CreateSuccessResult( Content ); } /// /// 批量写入PLC的数据,以字为单位,也就是说最少2个字节信息,支持X,Y,M,S,D,T,C,具体的地址范围需要根据PLC型号来确认 /// /// 地址信息 /// 数据值 /// 是否写入成功 public override OperateResult Write( string address, byte[] value ) { // 解析指令 OperateResult command = BuildWriteByteCommand( this.station, address, value, sumCheck, watiingTime ); if (!command.IsSuccess) return command; // 核心交互 OperateResult read = ReadBase( command.Content ); if (!read.IsSuccess) return read; // 结果验证 if (read.Content[0] != 0x06) return new OperateResult( read.Content[0], "Write Faild:" + BasicFramework.SoftBasic.ByteToHexString( read.Content, ' ' ) ); // 提取结果 return OperateResult.CreateSuccessResult( ); } #endregion #region Bool Read Write /// /// 批量读取bool类型数据,支持的类型为X,Y,S,T,C,具体的地址范围取决于PLC的类型 /// /// 地址信息,比如X10,Y17,注意X,Y的地址是8进制的 /// 读取的长度 /// 读取结果信息 public OperateResult ReadBool( string address, ushort length ) { // 解析指令 OperateResult command = BuildReadCommand( this.station, address, length, true, sumCheck, watiingTime ); if (!command.IsSuccess) return OperateResult.CreateFailedResult( command ); // 核心交互 OperateResult read = ReadBase( command.Content ); if(!read.IsSuccess) return OperateResult.CreateFailedResult( read ); // 结果验证 if (read.Content[0] != 0x02) return new OperateResult( read.Content[0], "Read Faild:" + BasicFramework.SoftBasic.ByteToHexString( read.Content, ' ' ) ); // 提取结果 byte[] buffer = new byte[length]; Array.Copy( read.Content, 5, buffer, 0, length ); return OperateResult.CreateSuccessResult( buffer.Select( m => m == 0x31 ).ToArray( ) ); } /// /// 批量读取bool类型数据,支持的类型为X,Y,S,T,C,具体的地址范围取决于PLC的类型 /// /// 地址信息,比如X10,Y17,注意X,Y的地址是8进制的 /// 读取结果信息 public OperateResult ReadBool( string address ) { OperateResult read = ReadBool( address, 1 ); if (!read.IsSuccess) return OperateResult.CreateFailedResult( read ); return OperateResult.CreateSuccessResult( read.Content[0] ); } /// /// 批量写入bool类型的数值,支持的类型为X,Y,S,T,C,具体的地址范围取决于PLC的类型 /// /// PLC的地址信息 /// 数据信息 /// 是否写入成功 public OperateResult Write(string address, bool value ) { return Write( address, new bool[] { value } ); } /// /// 批量写入bool类型的数组,支持的类型为X,Y,S,T,C,具体的地址范围取决于PLC的类型 /// /// PLC的地址信息 /// 数据信息 /// 是否写入成功 public OperateResult Write( string address, bool[] value ) { // 解析指令 OperateResult command = BuildWriteBoolCommand( this.station, address, value, sumCheck, watiingTime ); if (!command.IsSuccess) return command; // 核心交互 OperateResult read = ReadBase( command.Content ); if (!read.IsSuccess) return read; // 结果验证 if (read.Content[0] != 0x06) return new OperateResult( read.Content[0], "Write Faild:" + BasicFramework.SoftBasic.ByteToHexString( read.Content, ' ' ) ); // 提取结果 return OperateResult.CreateSuccessResult( ); } #endregion #region Start Stop /// /// 启动PLC /// /// 是否启动成功 public OperateResult StartPLC( ) { // 解析指令 OperateResult command = BuildStart( this.station, sumCheck, watiingTime ); if (!command.IsSuccess) return command; // 核心交互 OperateResult read = ReadBase( command.Content ); if (!read.IsSuccess) return read; // 结果验证 if (read.Content[0] != 0x06) return new OperateResult( read.Content[0], "Start Faild:" + BasicFramework.SoftBasic.ByteToHexString( read.Content, ' ' ) ); // 提取结果 return OperateResult.CreateSuccessResult( ); } /// /// 停止PLC /// /// 是否停止成功 public OperateResult StopPLC( ) { // 解析指令 OperateResult command = BuildStop( this.station, sumCheck, watiingTime ); if (!command.IsSuccess) return command; // 核心交互 OperateResult read = ReadBase( command.Content ); if (!read.IsSuccess) return read; // 结果验证 if (read.Content[0] != 0x06) return new OperateResult( read.Content[0], "Stop Faild:" + BasicFramework.SoftBasic.ByteToHexString( read.Content, ' ' ) ); // 提取结果 return OperateResult.CreateSuccessResult( ); } #endregion #region Private Member private byte station = 0x00; // PLC的站号信息 private byte watiingTime = 0x00; // 报文的等待时间,设置为0-15 private bool sumCheck = true; // 是否启用和校验 #endregion #region Static Helper /// /// 解析数据地址成不同的三菱地址类型 /// /// 数据地址 /// 地址结果对象 private static OperateResult FxAnalysisAddress( string address ) { var result = new OperateResult( ); try { switch (address[0]) { case 'X': case 'x': { ushort tmp = Convert.ToUInt16( address.Substring( 1 ), 8 ); result.Content = "X" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D4" ); break; } case 'Y': case 'y': { ushort tmp = Convert.ToUInt16( address.Substring( 1 ), 8 ); result.Content = "Y" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D4" ); break; } case 'M': case 'm': { result.Content = "M" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D4" ); break; } case 'S': case 's': { result.Content = "S" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D4" ); break; } case 'T': case 't': { if (address[1] == 'S' || address[1] == 's') { result.Content = "TS" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D3" ); break; } else if (address[1] == 'N' || address[1] == 'n') { result.Content = "TN" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D3" ); break; } else { throw new Exception( StringResources.Language.NotSupportedDataType ); } } case 'C': case 'c': { if (address[1] == 'S' || address[1] == 's') { result.Content = "CS" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D3" ); break; } else if (address[1] == 'N' || address[1] == 'n') { result.Content = "CN" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D3" ); break; } else { throw new Exception( StringResources.Language.NotSupportedDataType ); } } case 'D': case 'd': { result.Content = "D" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D4" ); break; } case 'R': case 'r': { result.Content = "R" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D4" ); break; } default: throw new Exception( StringResources.Language.NotSupportedDataType ); } } catch (Exception ex) { result.Message = ex.Message; return result; } result.IsSuccess = true; return result; } /// /// 计算指令的和校验码 /// /// 指令 /// 校验之后的信息 public static string CalculateAcc( string data ) { byte[] buffer = Encoding.ASCII.GetBytes( data ); int count = 0; for (int i = 0; i < buffer.Length; i++) { count += buffer[i]; } return data + count.ToString( "X4" ).Substring( 2 ); } /// /// 创建一条读取的指令信息,需要指定一些参数 /// /// PLCd的站号 /// 地址信息 /// 数据长度 /// 是否位读取 /// 是否和校验 /// 等待时间 /// 是否成功的结果对象 public static OperateResult BuildReadCommand( byte station, string address, ushort length, bool isBool, bool sumCheck = true, byte waitTime = 0x00 ) { OperateResult addressAnalysis = FxAnalysisAddress( address ); if (!addressAnalysis.IsSuccess) return OperateResult.CreateFailedResult( addressAnalysis ); StringBuilder stringBuilder = new StringBuilder( ); stringBuilder.Append( station.ToString( "D2" ) ); stringBuilder.Append( "FF" ); if (isBool) stringBuilder.Append( "BR" ); else stringBuilder.Append( "WR" ); stringBuilder.Append( waitTime.ToString( "X" ) ); stringBuilder.Append( addressAnalysis.Content ); stringBuilder.Append( length.ToString( "D2" ) ); byte[] core = null; if (sumCheck) core = Encoding.ASCII.GetBytes( CalculateAcc( stringBuilder.ToString( ) ) ); else core = Encoding.ASCII.GetBytes( stringBuilder.ToString( ) ); core = BasicFramework.SoftBasic.SpliceTwoByteArray( new byte[] { 0x05 }, core ); return OperateResult.CreateSuccessResult( core ); } /// /// 创建一条别入bool数据的指令信息,需要指定一些参数 /// /// 站号 /// 地址 /// 数组值 /// 是否和校验 /// 等待时间 /// 是否创建成功 public static OperateResult BuildWriteBoolCommand( byte station, string address, bool[] value, bool sumCheck = true, byte waitTime = 0x00 ) { OperateResult addressAnalysis = FxAnalysisAddress( address ); if (!addressAnalysis.IsSuccess) return OperateResult.CreateFailedResult( addressAnalysis ); StringBuilder stringBuilder = new StringBuilder( ); stringBuilder.Append( station.ToString( "D2" ) ); stringBuilder.Append( "FF" ); stringBuilder.Append( "BW" ); stringBuilder.Append( waitTime.ToString( "X" ) ); stringBuilder.Append( addressAnalysis.Content ); stringBuilder.Append( value.Length.ToString( "D2" ) ); for (int i = 0; i < value.Length; i++) { stringBuilder.Append( value[i] ? "1" : "0" ); } byte[] core = null; if (sumCheck) core = Encoding.ASCII.GetBytes( CalculateAcc( stringBuilder.ToString( ) ) ); else core = Encoding.ASCII.GetBytes( stringBuilder.ToString( ) ); core = BasicFramework.SoftBasic.SpliceTwoByteArray( new byte[] { 0x05 }, core ); return OperateResult.CreateSuccessResult( core ); } /// /// 创建一条别入byte数据的指令信息,需要指定一些参数,按照字单位 /// /// 站号 /// 地址 /// 数组值 /// 是否和校验 /// 等待时间 /// 是否创建成功 public static OperateResult BuildWriteByteCommand( byte station, string address, byte[] value, bool sumCheck = true, byte waitTime = 0x00 ) { OperateResult addressAnalysis = FxAnalysisAddress( address ); if (!addressAnalysis.IsSuccess) return OperateResult.CreateFailedResult( addressAnalysis ); StringBuilder stringBuilder = new StringBuilder( ); stringBuilder.Append( station.ToString( "D2" ) ); stringBuilder.Append( "FF" ); stringBuilder.Append( "WW" ); stringBuilder.Append( waitTime.ToString( "X" ) ); stringBuilder.Append( addressAnalysis.Content ); stringBuilder.Append( (value.Length / 2).ToString( "D2" ) ); // 字写入 byte[] buffer = new byte[value.Length * 2]; for (int i = 0; i < value.Length / 2; i++) { SoftBasic.BuildAsciiBytesFrom( BitConverter.ToUInt16( value, i * 2 ) ).CopyTo( buffer, 4 * i ); } stringBuilder.Append( Encoding.ASCII.GetString( buffer ) ); byte[] core = null; if (sumCheck) core = Encoding.ASCII.GetBytes( CalculateAcc( stringBuilder.ToString( ) ) ); else core = Encoding.ASCII.GetBytes( stringBuilder.ToString( ) ); core = BasicFramework.SoftBasic.SpliceTwoByteArray( new byte[] { 0x05 }, core ); return OperateResult.CreateSuccessResult( core ); } /// /// 创建启动PLC的报文信息 /// /// 站号信息 /// 是否和校验 /// 等待时间 /// 是否创建成功 public static OperateResult BuildStart( byte station, bool sumCheck = true, byte waitTime = 0x00 ) { StringBuilder stringBuilder = new StringBuilder( ); stringBuilder.Append( station.ToString( "D2" ) ); stringBuilder.Append( "FF" ); stringBuilder.Append( "RR" ); stringBuilder.Append( waitTime.ToString( "X" ) ); byte[] core = null; if (sumCheck) core = Encoding.ASCII.GetBytes( CalculateAcc( stringBuilder.ToString( ) ) ); else core = Encoding.ASCII.GetBytes( stringBuilder.ToString( ) ); core = BasicFramework.SoftBasic.SpliceTwoByteArray( new byte[] { 0x05 }, core ); return OperateResult.CreateSuccessResult( core ); } /// /// 创建启动PLC的报文信息 /// /// 站号信息 /// 是否和校验 /// 等待时间 /// 是否创建成功 public static OperateResult BuildStop( byte station, bool sumCheck = true, byte waitTime = 0x00 ) { StringBuilder stringBuilder = new StringBuilder( ); stringBuilder.Append( station.ToString( "D2" ) ); stringBuilder.Append( "FF" ); stringBuilder.Append( "RS" ); stringBuilder.Append( waitTime.ToString( "X" ) ); byte[] core = null; if (sumCheck) core = Encoding.ASCII.GetBytes( CalculateAcc( stringBuilder.ToString( ) ) ); else core = Encoding.ASCII.GetBytes( stringBuilder.ToString( ) ); core = BasicFramework.SoftBasic.SpliceTwoByteArray( new byte[] { 0x05 }, core ); return OperateResult.CreateSuccessResult( core ); } #endregion } }