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
}
}