using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MECF.Framework.RT.Core.IoProviders.Common;
using MECF.Framework.RT.Core.IoProviders.Common.Net.NetworkBase;
using MECF.Framework.RT.Core.IoProviders.Common.Transfer;
namespace MECF.Framework.RT.Core.IoProviders.Melsec
{
///
/// 三菱PLC通讯类,采用Qna兼容3E帧协议实现,需要在PLC侧先的以太网模块先进行配置,必须为二进制通讯
///
///
/// 目前组件测试通过的PLC型号列表
///
/// - Q06UDV PLC 感谢hwdq0012
/// - fx5u PLC 感谢山楂
/// - Q02CPU PLC
/// - L02CPU PLC
///
/// 地址的输入的格式说明如下:
///
///
/// 地址名称
/// 地址代号
/// 示例
/// 地址进制
/// 字操作
/// 位操作
/// 备注
///
/// -
/// 内部继电器
/// 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 MelsecMcNet : NetworkDeviceBase
{
#region Constructor
///
/// 实例化三菱的Qna兼容3E帧协议的通讯对象
///
public MelsecMcNet( )
{
WordLength = 1;
}
///
/// 实例化一个三菱的Qna兼容3E帧协议的通讯对象
///
/// PLC的Ip地址
/// PLC的端口
public MelsecMcNet( string ipAddress, int port )
{
WordLength = 1;
IpAddress = ipAddress;
Port = port;
}
#endregion
#region Public Member
///
/// 网络号,通常为0
///
///
/// 依据PLC的配置而配置,如果PLC配置了1,那么此处也填0,如果PLC配置了2,此处就填2,测试不通的话,继续测试0
///
public byte NetworkNumber { get; set; } = 0x00;
///
/// 网络站号,通常为0
///
///
/// 依据PLC的配置而配置,如果PLC配置了1,那么此处也填0,如果PLC配置了2,此处就填2,测试不通的话,继续测试0
///
public byte NetworkStationNumber { get; set; } = 0x00;
#endregion
#region Virtual Address Analysis
///
/// 分析地址的方法,允许派生类里进行重写操作
///
/// 地址信息
/// 解析后的数据信息
protected virtual OperateResult McAnalysisAddress( string address )
{
return MelsecHelper.McAnalysisAddress( address );
}
#endregion
#region Read Write Support
///
/// 从三菱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 coreResult = MelsecHelper.BuildReadMcCoreCommand( address, length, false, McAnalysisAddress );
if (!coreResult.IsSuccess) return coreResult;
// 核心交互
var read = ReadFromCoreServer( PackMcCommand( coreResult.Content, this.NetworkNumber, this.NetworkStationNumber ) );
if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
// 错误代码验证
ushort errorCode = BitConverter.ToUInt16( read.Content, 9 );
if (errorCode != 0) return new OperateResult( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 数据解析,需要传入是否使用位的参数
return ExtractActualData( SoftBasic.BytesArrayRemoveBegin( read.Content, 11 ), false );
}
///
/// 向PLC写入数据,数据格式为原始的字节类型
///
/// 初始地址
/// 原始的字节数据
///
/// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,写入如下:
///
/// 以下是写入不同类型数据的示例
///
///
/// 结果
public override OperateResult Write( string address, byte[] value )
{
// 分析地址
OperateResult coreResult = MelsecHelper.BuildWriteWordCoreCommand( address, value, McAnalysisAddress );
if (!coreResult.IsSuccess) return coreResult;
// 核心交互
OperateResult read = ReadFromCoreServer( PackMcCommand( coreResult.Content, NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return read;
// 错误码校验
ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
if (ErrorCode != 0) return new OperateResult( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 成功
return OperateResult.CreateSuccessResult( );
}
#endregion
#region Bool Operate Support
///
/// 从三菱PLC中批量读取位软元件,返回读取结果
///
/// 起始地址
/// 读取的长度
/// 带成功标志的结果数据对象
///
/// 地址支持的列表参考 的备注说明
///
///
///
///
public OperateResult ReadBool( string address, ushort length )
{
// 获取指令
OperateResult coreResult = MelsecHelper.BuildReadMcCoreCommand( address, length, true, McAnalysisAddress );
if (!coreResult.IsSuccess) return OperateResult.CreateFailedResult( coreResult );
// 核心交互
var read = ReadFromCoreServer( PackMcCommand( coreResult.Content, NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return OperateResult.CreateFailedResult(read);
// 错误代码验证
ushort errorCode = BitConverter.ToUInt16( read.Content, 9 );
if (errorCode != 0) return new OperateResult( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 数据解析,需要传入是否使用位的参数
var extract = ExtractActualData( SoftBasic.BytesArrayRemoveBegin( read.Content, 11 ), 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 OperateResult Write( string address, bool[] values )
{
OperateResult coreResult = MelsecHelper.BuildWriteBitCoreCommand( address, values, McAnalysisAddress );
if (!coreResult.IsSuccess) return coreResult;
// 核心交互
OperateResult read = ReadFromCoreServer( PackMcCommand( coreResult.Content, NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return read;
// 错误码校验
ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
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( new byte[] { 0x01, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }, NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return read;
// 错误码校验
ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
if (ErrorCode != 0) return new OperateResult( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 成功
return OperateResult.CreateSuccessResult( );
}
///
/// 远程Stop操作
///
/// 是否成功
public OperateResult RemoteStop( )
{
// 核心交互
OperateResult read = ReadFromCoreServer( PackMcCommand( new byte[] { 0x02, 0x10, 0x00, 0x00, 0x01, 0x00 }, NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return read;
// 错误码校验
ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
if (ErrorCode != 0) return new OperateResult( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 成功
return OperateResult.CreateSuccessResult( );
}
///
/// 远程Reset操作
///
/// 是否成功
public OperateResult RemoteReset()
{
// 核心交互
OperateResult read = ReadFromCoreServer( PackMcCommand( new byte[] { 0x06, 0x10, 0x00, 0x00, 0x01, 0x00 }, NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return read;
// 错误码校验
ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
if (ErrorCode != 0) return new OperateResult( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 成功
return OperateResult.CreateSuccessResult( );
}
///
/// 读取PLC的型号信息
///
/// 返回型号的结果对象
public OperateResult ReadPlcType( )
{
// 核心交互
OperateResult read = ReadFromCoreServer( PackMcCommand( new byte[] { 0x01, 0x01, 0x00, 0x00 }, NetworkNumber, NetworkStationNumber ) );
if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
// 错误码校验
ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
if (ErrorCode != 0) return new OperateResult( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
// 成功
return OperateResult.CreateSuccessResult( Encoding.ASCII.GetString( read.Content, 11, 16 ).TrimEnd( ) );
}
#endregion
#region Object Override
///
/// 获取当前对象的字符串标识形式
///
/// 字符串信息
public override string ToString()
{
return $"MelsecMcNet[{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[11 + mcCore.Length];
_PLCCommand[0] = 0x50; // 副标题
_PLCCommand[1] = 0x00;
_PLCCommand[2] = networkNumber; // 网络号
_PLCCommand[3] = 0xFF; // PLC编号
_PLCCommand[4] = 0xFF; // 目标模块IO编号
_PLCCommand[5] = 0x03;
_PLCCommand[6] = networkStationNumber; // 目标模块站号
_PLCCommand[7] = (byte)((_PLCCommand.Length - 9) % 256); // 请求数据长度
_PLCCommand[8] = (byte)((_PLCCommand.Length - 9) / 256);
_PLCCommand[9] = 0x0A; // CPU监视定时器
_PLCCommand[10] = 0x00;
mcCore.CopyTo( _PLCCommand, 11 );
return _PLCCommand;
}
///
/// 从PLC反馈的数据中提取出实际的数据内容,需要传入反馈数据,是否位读取
///
/// 反馈的数据内容
/// 是否位读取
/// 解析后的结果对象
public static OperateResult ExtractActualData( byte[] response, bool isBit )
{
if (isBit)
{
// 位读取
byte[] Content = new byte[response.Length * 2];
for (int i = 0; i < response.Length; i++)
{
if ((response[i] & 0x10) == 0x10)
{
Content[i * 2 + 0] = 0x01;
}
if ((response[i] & 0x01) == 0x01)
{
Content[i * 2 + 1] = 0x01;
}
}
return OperateResult.CreateSuccessResult( Content );
}
else
{
// 字读取
return OperateResult.CreateSuccessResult( response );
}
}
#endregion
}
}