using HslCommunication.Core; using HslCommunication.Serial; using System; using System.Collections.Generic; using System.Linq; using System.Text; using HslCommunication.BasicFramework; namespace HslCommunication.Profinet.FATEK { /// /// 台湾永宏公司的编程口协议 /// /// /// 其所支持的地址形式如下: /// /// /// 地址名称 /// 地址代号 /// 示例 /// 地址进制 /// 字操作 /// 位操作 /// 备注 /// /// /// 内部继电器 /// M /// M100,M200 /// 10 /// /// /// /// /// /// 输入继电器 /// X /// X10,X20 /// 10 /// /// /// /// /// /// 输出继电器 /// Y /// Y10,Y20 /// 10 /// /// /// /// /// /// 步进继电器 /// S /// S100,S200 /// 10 /// /// /// /// /// /// 定时器的触点 /// T /// T100,T200 /// 10 /// /// /// /// /// /// 定时器的当前值 /// RT /// RT100,RT200 /// 10 /// /// × /// /// /// /// 计数器的触点 /// C /// C100,C200 /// 10 /// /// /// /// /// /// 计数器的当前 /// RC /// RC100,RC200 /// 10 /// /// × /// /// /// /// 数据寄存器 /// D /// D1000,D2000 /// 10 /// /// × /// /// /// /// 文件寄存器 /// R /// R100,R200 /// 10 /// /// × /// /// /// /// public class FatekProgram : SerialDeviceBase { #region Constructor /// /// 实例化默认的构造方法 /// public FatekProgram( ) { WordLength = 1; } #endregion #region Public Member /// /// PLC的站号信息 /// public byte Station { get => station; set => station = value; } #endregion #region Read Write Support /// /// 批量读取PLC的数据,以字为单位,支持读取X,Y,M,S,D,T,C,R,RT,RC具体的地址范围需要根据PLC型号来确认 /// /// 地址信息 /// 数据长度 /// 读取结果信息 public override OperateResult Read( string address, ushort length ) { // 解析指令 OperateResult command = BuildReadCommand( this.station, address, length, false ); 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, ' ' ) ); if (read.Content[5] != 0x30) return new OperateResult( read.Content[5], GetErrorDescriptionFromCode( (char)read.Content[5] ) ); // 提取结果 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 + 6, 4 ), 16 ); BitConverter.GetBytes( tmp ).CopyTo( Content, i * 2 ); } return OperateResult.CreateSuccessResult( Content ); } /// /// 批量写入PLC的数据,以字为单位,也就是说最少2个字节信息,支持X,Y,M,S,D,T,C,R,RT,RC具体的地址范围需要根据PLC型号来确认 /// /// 地址信息,举例,D100,R200,RC100,RT200 /// 数据值 /// 是否写入成功 public override OperateResult Write( string address, byte[] value ) { // 解析指令 OperateResult command = BuildWriteByteCommand( this.station, address, value ); if (!command.IsSuccess) return command; // 核心交互 OperateResult read = ReadBase( command.Content ); if (!read.IsSuccess) return read; // 结果验证 if (read.Content[0] != 0x02) return new OperateResult( read.Content[0], "Write Faild:" + BasicFramework.SoftBasic.ByteToHexString( read.Content, ' ' ) ); if (read.Content[5] != 0x30) return new OperateResult( read.Content[5], GetErrorDescriptionFromCode( (char)read.Content[5] ) ); // 提取结果 return OperateResult.CreateSuccessResult( ); } #endregion #region Bool Read Write /// /// 批量读取bool类型数据,支持的类型为X,Y,M,S,T,C,具体的地址范围取决于PLC的类型 /// /// 地址信息,比如X10,Y17,M100 /// 读取的长度 /// 读取结果信息 public OperateResult ReadBool( string address, ushort length ) { // 解析指令 OperateResult command = BuildReadCommand( this.station, address, length, true ); 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, ' ' ) ); if (read.Content[5] != 0x30) return new OperateResult( read.Content[5], GetErrorDescriptionFromCode( (char)read.Content[5] ) ); // 提取结果 byte[] buffer = new byte[length]; Array.Copy( read.Content, 6, buffer, 0, length ); return OperateResult.CreateSuccessResult( buffer.Select( m => m == 0x31 ).ToArray( ) ); } /// /// 批量读取bool类型数据,支持的类型为X,Y,M,S,T,C,具体的地址范围取决于PLC的类型 /// /// 地址信息,比如X10,Y17,M100 /// 读取结果信息 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,M,S,T,C,具体的地址范围取决于PLC的类型 /// /// PLC的地址信息 /// 数据信息 /// 是否写入成功 public OperateResult Write( string address, bool value ) { return Write( address, new bool[] { value } ); } /// /// 批量写入bool类型的数组,支持的类型为X,Y,M,S,T,C,具体的地址范围取决于PLC的类型 /// /// PLC的地址信息 /// 数据信息 /// 是否写入成功 public OperateResult Write( string address, bool[] value ) { // 解析指令 OperateResult command = BuildWriteBoolCommand( this.station, address, value ); if (!command.IsSuccess) return command; // 核心交互 OperateResult read = ReadBase( command.Content ); if (!read.IsSuccess) return read; // 结果验证 if (read.Content[0] != 0x02) return new OperateResult( read.Content[0], "Write Faild:" + BasicFramework.SoftBasic.ByteToHexString( read.Content, ' ' ) ); if (read.Content[5] != 0x30) return new OperateResult( read.Content[5], GetErrorDescriptionFromCode( (char)read.Content[5] ) ); // 提取结果 return OperateResult.CreateSuccessResult( ); } #endregion #region Object Override /// /// 返回表示当前对象的字符串 /// /// 字符串 public override string ToString( ) { return $"FatekProgram[{PortName}:{BaudRate}]"; } #endregion #region Private Member private byte station = 0x01; // PLC的站号信息 #endregion #region Static Helper /// /// 解析数据地址成不同的三菱地址类型 /// /// 数据地址 /// 地址结果对象 private static OperateResult FatekAnalysisAddress( string address ) { var result = new OperateResult( ); try { switch (address[0]) { case 'X': case 'x': { result.Content = "X" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D4" ); break; } case 'Y': case 'y': { 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': { result.Content = "T" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D4" ); break; } case 'C': case 'c': { result.Content = "C" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D4" ); break; } case 'D': case 'd': { result.Content = "D" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D5" ); break; } case 'R': case 'r': { if(address[1] == 'T' || address[1] == 't') { result.Content = "RT" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D4" ); } else if(address[1] == 'C' || address[1] == 'c') { result.Content = "RC" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D4" ); } else { result.Content = "R" + Convert.ToUInt16( address.Substring( 1 ), 10 ).ToString( "D5" ); } 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 count.ToString( "X4" ).Substring( 2 ); } /// /// 创建一条读取的指令信息,需要指定一些参数 /// /// PLCd的站号 /// 地址信息 /// 数据长度 /// 是否位读取 /// 是否成功的结果对象 public static OperateResult BuildReadCommand( byte station, string address, ushort length, bool isBool ) { OperateResult addressAnalysis = FatekAnalysisAddress( address ); if (!addressAnalysis.IsSuccess) return OperateResult.CreateFailedResult( addressAnalysis ); StringBuilder stringBuilder = new StringBuilder( ); stringBuilder.Append( (char)0x02 ); stringBuilder.Append( station.ToString( "X2" ) ); if (isBool) { stringBuilder.Append( "44" ); stringBuilder.Append( length.ToString( "X2" ) ); } else { stringBuilder.Append( "46" ); stringBuilder.Append( length.ToString( "X2" ) ); if (addressAnalysis.Content.StartsWith( "X" ) || addressAnalysis.Content.StartsWith( "Y" ) || addressAnalysis.Content.StartsWith( "M" ) || addressAnalysis.Content.StartsWith( "S" ) || addressAnalysis.Content.StartsWith( "T" ) || addressAnalysis.Content.StartsWith( "C" )) { stringBuilder.Append( "W" ); } } stringBuilder.Append( addressAnalysis.Content ); stringBuilder.Append( CalculateAcc( stringBuilder.ToString( ) ) ); stringBuilder.Append( (char)0x03 ); return OperateResult.CreateSuccessResult( Encoding.ASCII.GetBytes( stringBuilder.ToString( ) ) ); } /// /// 创建一条别入bool数据的指令信息,需要指定一些参数 /// /// 站号 /// 地址 /// 数组值 /// 是否创建成功 public static OperateResult BuildWriteBoolCommand( byte station, string address, bool[] value ) { OperateResult addressAnalysis = FatekAnalysisAddress( address ); if (!addressAnalysis.IsSuccess) return OperateResult.CreateFailedResult( addressAnalysis ); StringBuilder stringBuilder = new StringBuilder( ); stringBuilder.Append( (char)0x02 ); stringBuilder.Append( station.ToString( "X2" ) ); stringBuilder.Append( "45" ); stringBuilder.Append( value.Length.ToString( "X2" ) ); stringBuilder.Append( addressAnalysis.Content ); for (int i = 0; i < value.Length; i++) { stringBuilder.Append( value[i] ? "1" : "0" ); } stringBuilder.Append( CalculateAcc( stringBuilder.ToString( ) ) ); stringBuilder.Append( (char)0x03 ); return OperateResult.CreateSuccessResult( Encoding.ASCII.GetBytes( stringBuilder.ToString( ) ) ); } /// /// 创建一条别入byte数据的指令信息,需要指定一些参数,按照字单位 /// /// 站号 /// 地址 /// 数组值 /// 是否创建成功 public static OperateResult BuildWriteByteCommand( byte station, string address, byte[] value ) { OperateResult addressAnalysis = FatekAnalysisAddress( address ); if (!addressAnalysis.IsSuccess) return OperateResult.CreateFailedResult( addressAnalysis ); StringBuilder stringBuilder = new StringBuilder( ); stringBuilder.Append( (char)0x02 ); stringBuilder.Append( station.ToString( "X2" ) ); stringBuilder.Append( "47" ); stringBuilder.Append( (value.Length / 2).ToString( "X2" ) ); if (addressAnalysis.Content.StartsWith( "X" ) || addressAnalysis.Content.StartsWith( "Y" ) || addressAnalysis.Content.StartsWith( "M" ) || addressAnalysis.Content.StartsWith( "S" ) || addressAnalysis.Content.StartsWith( "T" ) || addressAnalysis.Content.StartsWith( "C" )) { stringBuilder.Append( "W" ); } stringBuilder.Append( addressAnalysis.Content ); 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 ) ); stringBuilder.Append( CalculateAcc( stringBuilder.ToString( ) ) ); stringBuilder.Append( (char)0x03 ); return OperateResult.CreateSuccessResult( Encoding.ASCII.GetBytes( stringBuilder.ToString( ) ) ); } /// /// 根据错误码获取到真实的文本信息 /// /// 错误码 /// 错误的文本描述 public static string GetErrorDescriptionFromCode( char code ) { switch (code) { case '2': return StringResources.Language.FatekStatus02; case '3': return StringResources.Language.FatekStatus03; case '4': return StringResources.Language.FatekStatus04; case '5': return StringResources.Language.FatekStatus05; case '6': return StringResources.Language.FatekStatus06; case '7': return StringResources.Language.FatekStatus07; case '9': return StringResources.Language.FatekStatus09; case 'A': return StringResources.Language.FatekStatus10; default: return StringResources.Language.UnknownError; } } #endregion } }