You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

320 lines
13 KiB
C#

using HslCommunication.BasicFramework;
using HslCommunication.Core;
using HslCommunication.Serial;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace HslCommunication.Profinet.Fuji
{
/// <summary>
/// 富士PLC的SPB协议
/// </summary>
public class FujiSPB : SerialDeviceBase<RegularByteTransform>
{
#region Constructor
/// <summary>
/// 使用默认的构造方法实例化对象
/// </summary>
public FujiSPB( )
{
WordLength = 1;
}
#endregion
#region Public Member
/// <summary>
/// PLC的站号信息
/// </summary>
public byte Station { get => station; set => station = value; }
#endregion
#region Read Write Support
/// <summary>
/// 批量读取PLC的数据以字为单位支持读取X,Y,L,M,D,TN,CN,TC,CC,R具体的地址范围需要根据PLC型号来确认
/// </summary>
/// <param name="address">地址信息</param>
/// <param name="length">数据长度</param>
/// <returns>读取结果信息</returns>
public override OperateResult<byte[]> Read( string address, ushort length )
{
// 解析指令
OperateResult<byte[]> command = BuildReadCommand( this.station, address, length, false );
if (!command.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( command );
// 核心交互
OperateResult<byte[]> read = ReadBase( command.Content );
if (!read.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( read );
// 结果验证
if (read.Content[0] != ':') return new OperateResult<byte[]>( read.Content[0], "Read Faild:" + BasicFramework.SoftBasic.ByteToHexString( read.Content, ' ' ) );
if (Encoding.ASCII.GetString(read.Content, 9, 2) != "00") return new OperateResult<byte[]>( read.Content[5], GetErrorDescriptionFromCode( Encoding.ASCII.GetString( read.Content, 9, 2 ) ) );
// 提取结果
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 );
}
/// <summary>
/// 批量写入PLC的数据以字为单位也就是说最少2个字节信息支持读取X,Y,L,M,D,TN,CN,TC,CC,R具体的地址范围需要根据PLC型号来确认
/// </summary>
/// <param name="address">地址信息举例D100R200RC100RT200</param>
/// <param name="value">数据值</param>
/// <returns>是否写入成功</returns>
public override OperateResult Write( string address, byte[] value )
{
// 解析指令
OperateResult<byte[]> command = BuildWriteByteCommand( this.station, address, value );
if (!command.IsSuccess) return command;
// 核心交互
OperateResult<byte[]> read = ReadBase( command.Content );
if (!read.IsSuccess) return read;
// 结果验证
if (read.Content[0] != ':') return new OperateResult<byte[]>( read.Content[0], "Read Faild:" + BasicFramework.SoftBasic.ByteToHexString( read.Content, ' ' ) );
if (Encoding.ASCII.GetString( read.Content, 9, 2 ) != "00") return new OperateResult<byte[]>( read.Content[5], GetErrorDescriptionFromCode( Encoding.ASCII.GetString( read.Content, 9, 2 ) ) );
// 提取结果
return OperateResult.CreateSuccessResult( );
}
#endregion
#region Private Member
private byte station = 0x01; // PLC的站号信息
#endregion
#region Object Override
/// <summary>
/// 返回表示当前对象的字符串
/// </summary>
/// <returns>字符串</returns>
public override string ToString( )
{
return $"FujiSPB[{PortName}:{BaudRate}]";
}
#endregion
#region Static Helper
private static string AnalysisIntegerAddress( int address )
{
string tmp = address.ToString( "D4" );
return tmp.Substring( 2 ) + tmp.Substring( 0, 2 );
}
/// <summary>
/// 解析数据地址成不同的三菱地址类型
/// </summary>
/// <param name="address">数据地址</param>
/// <returns>地址结果对象</returns>
private static OperateResult<string> FujikAnalysisAddress( string address )
{
var result = new OperateResult<string>( );
try
{
switch (address[0])
{
case 'X':
case 'x':
{
result.Content = "01" + AnalysisIntegerAddress( Convert.ToUInt16( address.Substring( 1 ), 10 ) );
break;
}
case 'Y':
case 'y':
{
result.Content = "00" + AnalysisIntegerAddress( Convert.ToUInt16( address.Substring( 1 ), 10 ) );
break;
}
case 'M':
case 'm':
{
result.Content = "02" + AnalysisIntegerAddress( Convert.ToUInt16( address.Substring( 1 ), 10 ) );
break;
}
case 'L':
case 'l':
{
result.Content = "03" + AnalysisIntegerAddress( Convert.ToUInt16( address.Substring( 1 ), 10 ) );
break;
}
case 'T':
case 't':
{
if (address[1] == 'N' || address[1] == 'n')
{
result.Content = "0A" + AnalysisIntegerAddress( Convert.ToUInt16( address.Substring( 1 ), 10 ) );
break;
}
else if (address[1] == 'C' || address[1] == 'c')
{
result.Content = "04" + AnalysisIntegerAddress( Convert.ToUInt16( address.Substring( 1 ), 10 ) );
break;
}
else
{
throw new Exception( StringResources.Language.NotSupportedDataType );
}
}
case 'C':
case 'c':
{
if (address[1] == 'N' || address[1] == 'n')
{
result.Content = "0B" + AnalysisIntegerAddress( Convert.ToUInt16( address.Substring( 1 ), 10 ) );
break;
}
else if (address[1] == 'C' || address[1] == 'c')
{
result.Content = "05" + AnalysisIntegerAddress( Convert.ToUInt16( address.Substring( 1 ), 10 ) );
break;
}
else
{
throw new Exception( StringResources.Language.NotSupportedDataType );
}
}
case 'D':
case 'd':
{
result.Content = "0C" + AnalysisIntegerAddress( Convert.ToUInt16( address.Substring( 1 ), 10 ) );
break;
}
case 'R':
case 'r':
{
result.Content = "0D" + AnalysisIntegerAddress( Convert.ToUInt16( address.Substring( 1 ), 10 ) );
break;
}
default: throw new Exception( StringResources.Language.NotSupportedDataType );
}
}
catch (Exception ex)
{
result.Message = ex.Message;
return result;
}
result.IsSuccess = true;
return result;
}
/// <summary>
/// 计算指令的和校验码
/// </summary>
/// <param name="data">指令</param>
/// <returns>校验之后的信息</returns>
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 );
}
/// <summary>
/// 创建一条读取的指令信息,需要指定一些参数
/// </summary>
/// <param name="station">PLCd的站号</param>
/// <param name="address">地址信息</param>
/// <param name="length">数据长度</param>
/// <param name="isBool">是否位读取</param>
/// <returns>是否成功的结果对象</returns>
public static OperateResult<byte[]> BuildReadCommand( byte station, string address, ushort length, bool isBool )
{
OperateResult<string> addressAnalysis = FujikAnalysisAddress( address );
if (!addressAnalysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( addressAnalysis );
StringBuilder stringBuilder = new StringBuilder( );
stringBuilder.Append( ':' );
stringBuilder.Append( station.ToString( "X2" ) );
stringBuilder.Append( "09" );
stringBuilder.Append( "FFFF" );
stringBuilder.Append( "00" );
stringBuilder.Append( "00" );
stringBuilder.Append( addressAnalysis.Content );
stringBuilder.Append( length.ToString( "D4" ) );
stringBuilder.Append( "\r\n" );
return OperateResult.CreateSuccessResult( Encoding.ASCII.GetBytes( stringBuilder.ToString( ) ) );
}
/// <summary>
/// 创建一条别入byte数据的指令信息需要指定一些参数按照字单位
/// </summary>
/// <param name="station">站号</param>
/// <param name="address">地址</param>
/// <param name="value">数组值</param>
/// <returns>是否创建成功</returns>
public static OperateResult<byte[]> BuildWriteByteCommand( byte station, string address, byte[] value )
{
OperateResult<string> addressAnalysis = FujikAnalysisAddress( address );
if (!addressAnalysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( addressAnalysis );
StringBuilder stringBuilder = new StringBuilder( );
stringBuilder.Append( ':' );
stringBuilder.Append( station.ToString( "X2" ) );
stringBuilder.Append( (9 + value.Length / 2).ToString( "X2" ) );
stringBuilder.Append( "FFFF" );
stringBuilder.Append( "01" );
stringBuilder.Append( "00" );
stringBuilder.Append( addressAnalysis.Content );
stringBuilder.Append( (value.Length / 2).ToString( "D4" ) );
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( "\r\n" );
return OperateResult.CreateSuccessResult( Encoding.ASCII.GetBytes( stringBuilder.ToString( ) ) );
}
/// <summary>
/// 根据错误码获取到真实的文本信息
/// </summary>
/// <param name="code">错误码</param>
/// <returns>错误的文本描述</returns>
public static string GetErrorDescriptionFromCode( string code )
{
switch (code)
{
case "01": return StringResources.Language.FujiSpbStatus01;
case "02": return StringResources.Language.FujiSpbStatus02;
case "03": return StringResources.Language.FujiSpbStatus03;
case "04": return StringResources.Language.FujiSpbStatus04;
case "05": return StringResources.Language.FujiSpbStatus05;
case "06": return StringResources.Language.FujiSpbStatus06;
case "07": return StringResources.Language.FujiSpbStatus07;
case "09": return StringResources.Language.FujiSpbStatus09;
case "0C": return StringResources.Language.FujiSpbStatus0C;
default: return StringResources.Language.UnknownError;
}
}
#endregion
}
}