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