using HslCommunication.BasicFramework;
using HslCommunication.Core;
using HslCommunication.Core.Address;
using HslCommunication.Core.IMessage;
using HslCommunication.Core.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace HslCommunication.Profinet.Melsec
{
///
/// 三菱PLC通讯类,采用Qna兼容3E帧协议实现,需要在PLC侧先的以太网模块先进行配置,必须为ASCII通讯格式
///
///
/// 地址的输入的格式说明如下:
///
///
/// 地址名称
/// 地址代号
/// 示例
/// 地址进制
/// 字操作
/// 位操作
/// 备注
///
/// -
/// 内部继电器
/// M
/// M100,M200
/// 10
/// √
/// √
///
///
/// -
/// 输入继电器
/// X
/// X100,X1A0
/// 16
/// √
/// √
///
///
/// -
/// 输出继电器
/// Y
/// Y100,Y1A0
/// 16
/// √
/// √
///
///
/// -
/// 锁存继电器
/// L
/// L100,L200
/// 10
/// √
/// √
///
///
/// -
/// 报警器
/// F
/// F100,F200
/// 10
/// √
/// √
///
///
/// -
/// 边沿继电器
/// V
/// V100,V200
/// 10
/// √
/// √
///
///
/// -
/// 链接继电器
/// B
/// B100,B1A0
/// 16
/// √
/// √
///
///
/// -
/// 步进继电器
/// S
/// S100,S200
/// 10
/// √
/// √
///
///
/// -
/// 数据寄存器
/// D
/// D1000,D2000
/// 10
/// √
/// ×
///
///
/// -
/// 链接寄存器
/// W
/// W100,W1A0
/// 16
/// √
/// ×
///
///
/// -
/// 文件寄存器
/// R
/// R100,R200
/// 10
/// √
/// ×
///
///
/// -
/// ZR文件寄存器
/// ZR
/// ZR100,ZR2A0
/// 16
/// √
/// ×
///
///
/// -
/// 变址寄存器
/// Z
/// Z100,Z200
/// 10
/// √
/// ×
///
///
/// -
/// 定时器的触点
/// TS
/// TS100,TS200
/// 10
/// √
/// √
///
///
/// -
/// 定时器的线圈
/// TC
/// TC100,TC200
/// 10
/// √
/// √
///
///
/// -
/// 定时器的当前值
/// TN
/// TN100,TN200
/// 10
/// √
/// ×
///
///
/// -
/// 累计定时器的触点
/// SS
/// SS100,SS200
/// 10
/// √
/// √
///
///
/// -
/// 累计定时器的线圈
/// SC
/// SC100,SC200
/// 10
/// √
/// √
///
///
/// -
/// 累计定时器的当前值
/// SN
/// SN100,SN200
/// 10
/// √
/// ×
///
///
/// -
/// 计数器的触点
/// CS
/// CS100,CS200
/// 10
/// √
/// √
///
///
/// -
/// 计数器的线圈
/// CC
/// CC100,CC200
/// 10
/// √
/// √
///
///
/// -
/// 计数器的当前值
/// CN
/// CN100,CN200
/// 10
/// √
/// ×
///
///
///
///
///
///
///
///
public class MelsecMcAsciiNet : NetworkDeviceBase
{
#region Constructor
///
/// 实例化三菱的Qna兼容3E帧协议的通讯对象
///
public MelsecMcAsciiNet( )
{
WordLength = 1;
}
///
/// 实例化一个三菱的Qna兼容3E帧协议的通讯对象
///
/// PLC的Ip地址
/// PLC的端口
public MelsecMcAsciiNet( string ipAddress, int port )
{
WordLength = 1;
IpAddress = ipAddress;
Port = port;
}
#endregion
#region Public Member
///
/// 网络号
///
public byte NetworkNumber { get; set; } = 0x00;
///
/// 网络站号
///
public byte NetworkStationNumber { get; set; } = 0x00;
#endregion
#region Address Analysis
///
/// 分析地址的方法,允许派生类里进行重写操作
///
/// 地址信息
/// 数据长度
/// 解析后的数据信息
protected virtual OperateResult McAnalysisAddress( string address, ushort length )
{
return McAddressData.ParseMelsecFrom( address, length );
}
#endregion
#region Read Write Override
///
/// 从三菱PLC中读取想要的数据,返回读取结果,读取的单位为字
///
/// 读取地址,格式为"M100","D100","W1A0"
/// 读取的数据长度,字最大值960,位最大值7168
/// 带成功标志的结果数据对象
///
/// 地址支持的列表参考 的备注说明
///
///
/// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,读取如下:
///
/// 以下是读取不同类型数据的示例
///
///
public override OperateResult Read( string address, ushort length )
{
// 分析地址
OperateResult addressResult = McAnalysisAddress( address, length );
if (!addressResult.IsSuccess) return OperateResult.CreateFailedResult( addressResult );
List bytesContent = new List( );
ushort alreadyFinished = 0;
while (alreadyFinished < length)
{
ushort readLength = (ushort)Math.Min( length - alreadyFinished, 450 );
addressResult.Content.Length = readLength;
OperateResult read = ReadAddressData( addressResult.Content );
if (!read.IsSuccess) return read;
bytesContent.AddRange( read.Content );
alreadyFinished += readLength;
// 字的话就是正常的偏移位置,如果是位的话,就转到位的数据
if (addressResult.Content.McDataType.DataType == 0)
addressResult.Content.AddressStart += readLength;
else
addressResult.Content.AddressStart += readLength * 16;
}
return OperateResult.CreateSuccessResult( bytesContent.ToArray( ) );
}
private OperateResult ReadAddressData( McAddressData addressData )
{
// 地址分析
byte[] coreResult = MelsecHelper.BuildAsciiReadMcCoreCommand( addressData, false );
// 核心交互
var read = ReadFromCoreServer( PackMcCommand( coreResult, NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
// 错误代码验证
ushort errorCode = Convert.ToUInt16( Encoding.ASCII.GetString( read.Content, 18, 4 ), 16 );
if (errorCode != 0) return new OperateResult( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 数据解析,需要传入是否使用位的参数
return ExtractActualData( read.Content, false );
}
///
/// 向PLC写入数据,数据格式为原始的字节类型
///
/// 初始地址
/// 原始的字节数据
///
/// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,写入如下:
///
/// 以下是读取不同类型数据的示例
///
///
/// 结果
public override OperateResult Write( string address, byte[] value )
{
// 分析地址
OperateResult addressResult = McAnalysisAddress( address, 0 );
if (!addressResult.IsSuccess) return OperateResult.CreateFailedResult( addressResult );
// 地址分析
byte[] coreResult = MelsecHelper.BuildAsciiWriteWordCoreCommand( addressResult.Content, value );
// 核心交互
OperateResult read = ReadFromCoreServer( PackMcCommand( coreResult, NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return read;
// 错误码验证
ushort errorCode = Convert.ToUInt16( Encoding.ASCII.GetString( read.Content, 18, 4 ), 16 );
if (errorCode != 0) return new OperateResult( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 写入成功
return OperateResult.CreateSuccessResult( );
}
#endregion
#region Bool Operate Support
///
/// 从三菱PLC中批量读取位软元件,返回读取结果
///
/// 起始地址
/// 读取的长度
/// 带成功标志的结果数据对象
///
/// 地址支持的列表参考 的备注说明
///
///
///
///
public virtual OperateResult ReadBool( string address, ushort length )
{
// 分析地址
OperateResult addressResult = McAnalysisAddress( address, length );
if (!addressResult.IsSuccess) return OperateResult.CreateFailedResult( addressResult );
// 地址分析
byte[] coreResult = MelsecHelper.BuildAsciiReadMcCoreCommand( addressResult.Content, true );
// 核心交互
var read = ReadFromCoreServer( PackMcCommand( coreResult, NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
// 错误代码验证
ushort errorCode = Convert.ToUInt16( Encoding.ASCII.GetString( read.Content, 18, 4 ), 16 );
if (errorCode != 0) return new OperateResult( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 数据解析,需要传入是否使用位的参数
var extract = ExtractActualData( read.Content, true );
if(!extract.IsSuccess) return OperateResult.CreateFailedResult( extract );
// 转化bool数组
return OperateResult.CreateSuccessResult( extract.Content.Select( m => m == 0x01 ).Take( length ).ToArray( ) );
}
///
/// 从三菱PLC中批量读取位软元件,返回读取结果
///
/// 起始地址
/// 带成功标志的结果数据对象
/// 参照 方法
public OperateResult ReadBool( string address )
{
OperateResult read = ReadBool( address, 1 );
if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
return OperateResult.CreateSuccessResult( read.Content[0] );
}
///
/// 向PLC中位软元件写入bool数组,返回值说明,比如你写入M100,values[0]对应M100
///
/// 要写入的数据地址
/// 要写入的实际数据,长度为8的倍数
///
/// 详细请查看方法的示例
///
/// 返回写入结果
public OperateResult Write( string address, bool value )
{
return Write( address, new bool[] { value } );
}
///
/// 向PLC中位软元件写入bool数组,返回值说明,比如你写入M100,values[0]对应M100
///
/// 要写入的数据地址
/// 要写入的实际数据,可以指定任意的长度
///
///
///
/// 返回写入结果
public virtual OperateResult Write( string address, bool[] values )
{
// 分析地址
OperateResult addressResult = McAnalysisAddress( address, 0 );
if (!addressResult.IsSuccess) return addressResult;
// 解析指令
byte[] coreResult = MelsecHelper.BuildAsciiWriteBitCoreCommand( addressResult.Content, values );
// 核心交互
OperateResult read = ReadFromCoreServer( PackMcCommand( coreResult, NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return read;
// 错误码验证
ushort errorCode = Convert.ToUInt16( Encoding.ASCII.GetString( read.Content, 18, 4 ), 16 );
if (errorCode != 0) return new OperateResult( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 写入成功
return OperateResult.CreateSuccessResult( );
}
#endregion
#region Remote Operate
///
/// 远程Run操作
///
/// 是否成功
public OperateResult RemoteRun()
{
// 核心交互
OperateResult read = ReadFromCoreServer( PackMcCommand( Encoding.ASCII.GetBytes("1001000000010000"), NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return read;
// 错误码校验
ushort errorCode = Convert.ToUInt16( Encoding.ASCII.GetString( read.Content, 18, 4 ), 16 );
if (errorCode != 0) return new OperateResult( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 成功
return OperateResult.CreateSuccessResult( );
}
///
/// 远程Stop操作
///
/// 是否成功
public OperateResult RemoteStop()
{
// 核心交互
OperateResult read = ReadFromCoreServer( PackMcCommand( Encoding.ASCII.GetBytes( "100200000001" ), NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return read;
// 错误码校验
ushort errorCode = Convert.ToUInt16( Encoding.ASCII.GetString( read.Content, 18, 4 ), 16 );
if (errorCode != 0) return new OperateResult( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 成功
return OperateResult.CreateSuccessResult( );
}
///
/// 读取PLC的型号信息
///
/// 返回型号的结果对象
public OperateResult ReadPlcType()
{
// 核心交互
OperateResult read = ReadFromCoreServer( PackMcCommand( Encoding.ASCII.GetBytes( "01010000" ), NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
// 错误码校验
ushort errorCode = Convert.ToUInt16( Encoding.ASCII.GetString( read.Content, 18, 4 ), 16 );
if (errorCode != 0) return new OperateResult( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 成功
return OperateResult.CreateSuccessResult( Encoding.ASCII.GetString( read.Content, 22, 16 ).TrimEnd( ) );
}
#endregion
#region Object Override
///
/// 获取当前对象的字符串标识形式
///
/// 字符串信息
public override string ToString( )
{
return $"MelsecMcAsciiNet[{IpAddress}:{Port}]";
}
#endregion
#region Static Method Helper
///
/// 将MC协议的核心报文打包成一个可以直接对PLC进行发送的原始报文
///
/// MC协议的核心报文
/// 网络号
/// 网络站号
/// 原始报文信息
public static byte[] PackMcCommand( byte[] mcCore, byte networkNumber = 0, byte networkStationNumber = 0 )
{
byte[] plcCommand = new byte[22 + mcCore.Length];
plcCommand[ 0] = 0x35; // 副标题
plcCommand[ 1] = 0x30;
plcCommand[ 2] = 0x30;
plcCommand[ 3] = 0x30;
plcCommand[ 4] = SoftBasic.BuildAsciiBytesFrom( networkNumber )[0]; // 网络号
plcCommand[ 5] = SoftBasic.BuildAsciiBytesFrom( networkNumber )[1];
plcCommand[ 6] = 0x46; // PLC编号
plcCommand[ 7] = 0x46;
plcCommand[ 8] = 0x30; // 目标模块IO编号
plcCommand[ 9] = 0x33;
plcCommand[10] = 0x46;
plcCommand[11] = 0x46;
plcCommand[12] = SoftBasic.BuildAsciiBytesFrom( networkStationNumber )[0]; // 目标模块站号
plcCommand[13] = SoftBasic.BuildAsciiBytesFrom( networkStationNumber )[1];
plcCommand[14] = SoftBasic.BuildAsciiBytesFrom( (ushort)(plcCommand.Length - 18) )[0]; // 请求数据长度
plcCommand[15] = SoftBasic.BuildAsciiBytesFrom( (ushort)(plcCommand.Length - 18) )[1];
plcCommand[16] = SoftBasic.BuildAsciiBytesFrom( (ushort)(plcCommand.Length - 18) )[2];
plcCommand[17] = SoftBasic.BuildAsciiBytesFrom( (ushort)(plcCommand.Length - 18) )[3];
plcCommand[18] = 0x30; // CPU监视定时器
plcCommand[19] = 0x30;
plcCommand[20] = 0x31;
plcCommand[21] = 0x30;
mcCore.CopyTo( plcCommand, 22 );
return plcCommand;
}
///
/// 从PLC反馈的数据中提取出实际的数据内容,需要传入反馈数据,是否位读取
///
/// 反馈的数据内容
/// 是否位读取
/// 解析后的结果对象
public static OperateResult ExtractActualData( byte[] response, bool isBit )
{
if (isBit)
{
// 位读取
byte[] Content = new byte[response.Length - 22];
for (int i = 22; i < response.Length; i++)
{
Content[i - 22] = response[i] == 0x30 ? (byte)0x00 : (byte)0x01;
}
return OperateResult.CreateSuccessResult( Content );
}
else
{
// 字读取
byte[] Content = new byte[(response.Length - 22) / 2];
for (int i = 0; i < Content.Length / 2; i++)
{
ushort tmp = Convert.ToUInt16( Encoding.ASCII.GetString( response, i * 4 + 22, 4 ), 16 );
BitConverter.GetBytes( tmp ).CopyTo( Content, i * 2 );
}
return OperateResult.CreateSuccessResult( Content );
}
}
#endregion
}
}