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.

733 lines
28 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
{
/// <summary>
/// 三菱PLC的计算机链接协议适用的PLC型号参考备注
/// </summary>
/// <remarks>
/// 支持的通讯的系列如下参考
/// <list type="table">
/// <listheader>
/// <term>系列</term>
/// <term>是否支持</term>
/// <term>备注</term>
/// </listheader>
/// <item>
/// <description>FX3UC系列</description>
/// <description>支持</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX3U系列</description>
/// <description>支持</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX3GC系列</description>
/// <description>支持</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX3G系列</description>
/// <description>支持</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX3S系列</description>
/// <description>支持</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX2NC系列</description>
/// <description>支持</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX2N系列</description>
/// <description>部分支持(v1.06+)</description>
/// <description>通过监控D8001来确认版本号</description>
/// </item>
/// <item>
/// <description>FX1NC系列</description>
/// <description>支持</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX1N系列</description>
/// <description>支持</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX1S系列</description>
/// <description>支持</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX0N系列</description>
/// <description>部分支持(v1.20+)</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX0S系列</description>
/// <description>不支持</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX0系列</description>
/// <description>不支持</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX2C系列</description>
/// <description>部分支持(v3.30+)</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX2(FX)系列</description>
/// <description>部分支持(v3.30+)</description>
/// <description></description>
/// </item>
/// <item>
/// <description>FX1系列</description>
/// <description>不支持</description>
/// <description></description>
/// </item>
/// </list>
/// 数据地址支持的格式如下:
/// <list type="table">
/// <listheader>
/// <term>地址名称</term>
/// <term>地址代号</term>
/// <term>示例</term>
/// <term>地址进制</term>
/// <term>字操作</term>
/// <term>位操作</term>
/// <term>备注</term>
/// </listheader>
/// <item>
/// <term>内部继电器</term>
/// <term>M</term>
/// <term>M100,M200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// <item>
/// <term>输入继电器</term>
/// <term>X</term>
/// <term>X10,X20</term>
/// <term>8</term>
/// <term>√</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// <item>
/// <term>输出继电器</term>
/// <term>Y</term>
/// <term>Y10,Y20</term>
/// <term>8</term>
/// <term>√</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// <item>
/// <term>步进继电器</term>
/// <term>S</term>
/// <term>S100,S200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// <item>
/// <term>定时器的触点</term>
/// <term>TS</term>
/// <term>TS100,TS200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// <item>
/// <term>定时器的当前值</term>
/// <term>TN</term>
/// <term>TN100,TN200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term></term>
/// </item>
/// <item>
/// <term>计数器的触点</term>
/// <term>CS</term>
/// <term>CS100,CS200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// <item>
/// <term>计数器的当前</term>
/// <term>CN</term>
/// <term>CN100,CN200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term></term>
/// </item>
/// <item>
/// <term>数据寄存器</term>
/// <term>D</term>
/// <term>D1000,D2000</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term></term>
/// </item>
/// <item>
/// <term>文件寄存器</term>
/// <term>R</term>
/// <term>R100,R200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term></term>
/// </item>
/// </list>
/// </remarks>
public class MelsecFxLinks : SerialDeviceBase<RegularByteTransform>
{
#region Constructor
/// <summary>
/// 实例化默认的构造方法
/// </summary>
public MelsecFxLinks( )
{
WordLength = 1;
}
#endregion
#region Public Member
/// <summary>
/// PLC的站号信息
/// </summary>
public byte Station { get => station; set => station = value; }
/// <summary>
/// 报文等待时间单位10ms设置范围为0-15
/// </summary>
public byte WaittingTime
{
get => watiingTime;
set
{
if (watiingTime > 0x0F)
{
watiingTime = 0x0F;
}
else
{
watiingTime = value;
}
}
}
/// <summary>
/// 是否启动和校验
/// </summary>
public bool SumCheck { get => sumCheck; set => sumCheck = value; }
#endregion
#region Read Write Support
/// <summary>
/// 批量读取PLC的数据以字为单位支持读取X,Y,M,S,D,T,C具体的地址范围需要根据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, sumCheck, watiingTime );
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] != 0x02) return new OperateResult<byte[]>( 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 );
}
/// <summary>
/// 批量写入PLC的数据以字为单位也就是说最少2个字节信息支持X,Y,M,S,D,T,C具体的地址范围需要根据PLC型号来确认
/// </summary>
/// <param name="address">地址信息</param>
/// <param name="value">数据值</param>
/// <returns>是否写入成功</returns>
public override OperateResult Write( string address, byte[] value )
{
// 解析指令
OperateResult<byte[]> command = BuildWriteByteCommand( this.station, address, value, sumCheck, watiingTime );
if (!command.IsSuccess) return command;
// 核心交互
OperateResult<byte[]> 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
/// <summary>
/// 批量读取bool类型数据支持的类型为X,Y,S,T,C具体的地址范围取决于PLC的类型
/// </summary>
/// <param name="address">地址信息比如X10,Y17注意XY的地址是8进制的</param>
/// <param name="length">读取的长度</param>
/// <returns>读取结果信息</returns>
public OperateResult<bool[]> ReadBool( string address, ushort length )
{
// 解析指令
OperateResult<byte[]> command = BuildReadCommand( this.station, address, length, true, sumCheck, watiingTime );
if (!command.IsSuccess) return OperateResult.CreateFailedResult<bool[]>( command );
// 核心交互
OperateResult<byte[]> read = ReadBase( command.Content );
if(!read.IsSuccess) return OperateResult.CreateFailedResult<bool[]>( read );
// 结果验证
if (read.Content[0] != 0x02) return new OperateResult<bool[]>( 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( ) );
}
/// <summary>
/// 批量读取bool类型数据支持的类型为X,Y,S,T,C具体的地址范围取决于PLC的类型
/// </summary>
/// <param name="address">地址信息比如X10,Y17注意XY的地址是8进制的</param>
/// <returns>读取结果信息</returns>
public OperateResult<bool> ReadBool( string address )
{
OperateResult<bool[]> read = ReadBool( address, 1 );
if (!read.IsSuccess) return OperateResult.CreateFailedResult<bool>( read );
return OperateResult.CreateSuccessResult( read.Content[0] );
}
/// <summary>
/// 批量写入bool类型的数值支持的类型为X,Y,S,T,C具体的地址范围取决于PLC的类型
/// </summary>
/// <param name="address">PLC的地址信息</param>
/// <param name="value">数据信息</param>
/// <returns>是否写入成功</returns>
public OperateResult Write(string address, bool value )
{
return Write( address, new bool[] { value } );
}
/// <summary>
/// 批量写入bool类型的数组支持的类型为X,Y,S,T,C具体的地址范围取决于PLC的类型
/// </summary>
/// <param name="address">PLC的地址信息</param>
/// <param name="value">数据信息</param>
/// <returns>是否写入成功</returns>
public OperateResult Write( string address, bool[] value )
{
// 解析指令
OperateResult<byte[]> command = BuildWriteBoolCommand( this.station, address, value, sumCheck, watiingTime );
if (!command.IsSuccess) return command;
// 核心交互
OperateResult<byte[]> 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
/// <summary>
/// 启动PLC
/// </summary>
/// <returns>是否启动成功</returns>
public OperateResult StartPLC( )
{
// 解析指令
OperateResult<byte[]> command = BuildStart( this.station, sumCheck, watiingTime );
if (!command.IsSuccess) return command;
// 核心交互
OperateResult<byte[]> 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( );
}
/// <summary>
/// 停止PLC
/// </summary>
/// <returns>是否停止成功</returns>
public OperateResult StopPLC( )
{
// 解析指令
OperateResult<byte[]> command = BuildStop( this.station, sumCheck, watiingTime );
if (!command.IsSuccess) return command;
// 核心交互
OperateResult<byte[]> 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
/// <summary>
/// 解析数据地址成不同的三菱地址类型
/// </summary>
/// <param name="address">数据地址</param>
/// <returns>地址结果对象</returns>
private static OperateResult<string> FxAnalysisAddress( string address )
{
var result = new OperateResult<string>( );
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;
}
/// <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 data + count.ToString( "X4" ).Substring( 2 );
}
/// <summary>
/// 创建一条读取的指令信息,需要指定一些参数
/// </summary>
/// <param name="station">PLCd的站号</param>
/// <param name="address">地址信息</param>
/// <param name="length">数据长度</param>
/// <param name="isBool">是否位读取</param>
/// <param name="sumCheck">是否和校验</param>
/// <param name="waitTime">等待时间</param>
/// <returns>是否成功的结果对象</returns>
public static OperateResult<byte[]> BuildReadCommand( byte station, string address, ushort length, bool isBool, bool sumCheck = true, byte waitTime = 0x00 )
{
OperateResult<string> addressAnalysis = FxAnalysisAddress( address );
if (!addressAnalysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( 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 );
}
/// <summary>
/// 创建一条别入bool数据的指令信息需要指定一些参数
/// </summary>
/// <param name="station">站号</param>
/// <param name="address">地址</param>
/// <param name="value">数组值</param>
/// <param name="sumCheck">是否和校验</param>
/// <param name="waitTime">等待时间</param>
/// <returns>是否创建成功</returns>
public static OperateResult<byte[]> BuildWriteBoolCommand( byte station, string address, bool[] value, bool sumCheck = true, byte waitTime = 0x00 )
{
OperateResult<string> addressAnalysis = FxAnalysisAddress( address );
if (!addressAnalysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( 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 );
}
/// <summary>
/// 创建一条别入byte数据的指令信息需要指定一些参数按照字单位
/// </summary>
/// <param name="station">站号</param>
/// <param name="address">地址</param>
/// <param name="value">数组值</param>
/// <param name="sumCheck">是否和校验</param>
/// <param name="waitTime">等待时间</param>
/// <returns>是否创建成功</returns>
public static OperateResult<byte[]> BuildWriteByteCommand( byte station, string address, byte[] value, bool sumCheck = true, byte waitTime = 0x00 )
{
OperateResult<string> addressAnalysis = FxAnalysisAddress( address );
if (!addressAnalysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( 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 );
}
/// <summary>
/// 创建启动PLC的报文信息
/// </summary>
/// <param name="station">站号信息</param>
/// <param name="sumCheck">是否和校验</param>
/// <param name="waitTime">等待时间</param>
/// <returns>是否创建成功</returns>
public static OperateResult<byte[]> 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 );
}
/// <summary>
/// 创建启动PLC的报文信息
/// </summary>
/// <param name="station">站号信息</param>
/// <param name="sumCheck">是否和校验</param>
/// <param name="waitTime">等待时间</param>
/// <returns>是否创建成功</returns>
public static OperateResult<byte[]> 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
}
}