| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613 | 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{    /// <summary>    /// 三菱PLC通讯类,采用Qna兼容3E帧协议实现,需要在PLC侧先的以太网模块先进行配置,必须为二进制通讯    /// </summary>    /// <remarks>    /// 目前组件测试通过的PLC型号列表     /// <list type="number">    /// <item>Q06UDV PLC  感谢hwdq0012</item>    /// <item>fx5u PLC  感谢山楂</item>    /// <item>Q02CPU PLC </item>    /// <item>L02CPU PLC </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>X100,X1A0</term>    ///     <term>16</term>    ///     <term>√</term>    ///     <term>√</term>    ///     <term></term>    ///   </item>    ///   <item>    ///     <term>输出继电器</term>    ///     <term>Y</term>    ///     <term>Y100,Y1A0</term>    ///     <term>16</term>    ///     <term>√</term>    ///     <term>√</term>    ///     <term></term>    ///   </item>    ///    <item>    ///     <term>锁存继电器</term>    ///     <term>L</term>    ///     <term>L100,L200</term>    ///     <term>10</term>    ///     <term>√</term>    ///     <term>√</term>    ///     <term></term>    ///   </item>    ///   <item>    ///     <term>报警器</term>    ///     <term>F</term>    ///     <term>F100,F200</term>    ///     <term>10</term>    ///     <term>√</term>    ///     <term>√</term>    ///     <term></term>    ///   </item>    ///   <item>    ///     <term>边沿继电器</term>    ///     <term>V</term>    ///     <term>V100,V200</term>    ///     <term>10</term>    ///     <term>√</term>    ///     <term>√</term>    ///     <term></term>    ///   </item>    ///   <item>    ///     <term>链接继电器</term>    ///     <term>B</term>    ///     <term>B100,B1A0</term>    ///     <term>16</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>D</term>    ///     <term>D1000,D2000</term>    ///     <term>10</term>    ///     <term>√</term>    ///     <term>×</term>    ///     <term></term>    ///   </item>    ///   <item>    ///     <term>链接寄存器</term>    ///     <term>W</term>    ///     <term>W100,W1A0</term>    ///     <term>16</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>    ///   <item>    ///     <term>ZR文件寄存器</term>    ///     <term>ZR</term>    ///     <term>ZR100,ZR2A0</term>    ///     <term>16</term>    ///     <term>√</term>    ///     <term>×</term>    ///     <term></term>    ///   </item>    ///   <item>    ///     <term>变址寄存器</term>    ///     <term>Z</term>    ///     <term>Z100,Z200</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>TC</term>    ///     <term>TC100,TC200</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>SS</term>    ///     <term>SS100,SS200</term>    ///     <term>10</term>    ///     <term>√</term>    ///     <term>√</term>    ///     <term></term>    ///   </item>    ///   <item>    ///     <term>累计定时器的线圈</term>    ///     <term>SC</term>    ///     <term>SC100,SC200</term>    ///     <term>10</term>    ///     <term>√</term>    ///     <term>√</term>    ///     <term></term>    ///   </item>    ///   <item>    ///     <term>累计定时器的当前值</term>    ///     <term>SN</term>    ///     <term>SN100,SN200</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>CC</term>    ///     <term>CC100,CC200</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>    /// </list>    /// </remarks>    /// <example>    /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\melsecTest.cs" region="Usage" title="简单的短连接使用" />    /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\melsecTest.cs" region="Usage2" title="简单的长连接使用" />    /// </example>    public class MelsecMcNet : NetworkDeviceBase<MelsecQnA3EBinaryMessage, RegularByteTransform>    {        #region Constructor        /// <summary>        /// 实例化三菱的Qna兼容3E帧协议的通讯对象        /// </summary>        public MelsecMcNet( )        {            WordLength = 1;        }        /// <summary>        /// 实例化一个三菱的Qna兼容3E帧协议的通讯对象        /// </summary>        /// <param name="ipAddress">PLC的Ip地址</param>        /// <param name="port">PLC的端口</param>        public MelsecMcNet( string ipAddress, int port )        {            WordLength = 1;            IpAddress = ipAddress;            Port = port;        }        #endregion        #region Public Member        /// <summary>        /// 网络号,通常为0        /// </summary>        /// <remarks>        /// 依据PLC的配置而配置,如果PLC配置了1,那么此处也填0,如果PLC配置了2,此处就填2,测试不通的话,继续测试0        /// </remarks>        public byte NetworkNumber { get; set; } = 0x00;        /// <summary>        /// 网络站号,通常为0        /// </summary>        /// <remarks>        /// 依据PLC的配置而配置,如果PLC配置了1,那么此处也填0,如果PLC配置了2,此处就填2,测试不通的话,继续测试0        /// </remarks>        public byte NetworkStationNumber { get; set; } = 0x00;        #endregion        #region Virtual Address Analysis        /// <summary>        /// 分析地址的方法,允许派生类里进行重写操作        /// </summary>        /// <param name="address">地址信息</param>        /// <returns>解析后的数据信息</returns>        protected virtual OperateResult<MelsecMcDataType, int> McAnalysisAddress( string address )        {            return MelsecHelper.McAnalysisAddress( address );        }        #endregion        #region Read Write Support        /// <summary>        /// 从三菱PLC中读取想要的数据,输入地址,按照字单位读取,返回读取结果        /// </summary>        /// <param name="address">读取地址,格式为"M100","D100","W1A0"</param>        /// <param name="length">读取的数据长度,字最大值960,位最大值7168</param>        /// <returns>带成功标志的结果数据对象</returns>        /// <remarks>        /// 地址支持的列表参考 <seealso cref="MelsecMcNet"/> 的备注说明        /// </remarks>        /// <example>        /// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,读取如下:        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\melsecTest.cs" region="ReadExample2" title="Read示例" />        /// 以下是读取不同类型数据的示例        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\melsecTest.cs" region="ReadExample1" title="Read示例" />        /// </example>        public override OperateResult<byte[]> Read( string address, ushort length )        {            // 分析地址            OperateResult<byte[]> 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<byte[]>( read );            // 错误代码验证            ushort errorCode = BitConverter.ToUInt16( read.Content, 9 );            if (errorCode != 0) return new OperateResult<byte[]>( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );            // 数据解析,需要传入是否使用位的参数            return ExtractActualData( SoftBasic.BytesArrayRemoveBegin( read.Content, 11 ), false );        }        /// <summary>        /// 向PLC写入数据,数据格式为原始的字节类型        /// </summary>        /// <param name="address">初始地址</param>        /// <param name="value">原始的字节数据</param>        /// <example>        /// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,写入如下:        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\melsecTest.cs" region="WriteExample2" title="Write示例" />        /// 以下是写入不同类型数据的示例        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\melsecTest.cs" region="WriteExample1" title="Write示例" />        /// </example>        /// <returns>结果</returns>        public override OperateResult Write( string address, byte[] value )        {            // 分析地址            OperateResult<byte[]> coreResult = MelsecHelper.BuildWriteWordCoreCommand( address, value, McAnalysisAddress );            if (!coreResult.IsSuccess) return coreResult;            // 核心交互            OperateResult<byte[]> 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<byte[]>( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );            // 成功            return OperateResult.CreateSuccessResult( );        }        #endregion        #region Bool Operate Support        /// <summary>        /// 从三菱PLC中批量读取位软元件,返回读取结果        /// </summary>        /// <param name="address">起始地址</param>        /// <param name="length">读取的长度</param>        /// <returns>带成功标志的结果数据对象</returns>        /// <remarks>        /// 地址支持的列表参考 <seealso cref="MelsecMcNet"/> 的备注说明        /// </remarks>        /// <example>        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\melsecTest.cs" region="ReadBool" title="Bool类型示例" />        /// </example>        public OperateResult<bool[]> ReadBool( string address, ushort length )        {            // 获取指令            OperateResult<byte[]> coreResult = MelsecHelper.BuildReadMcCoreCommand( address, length, true, McAnalysisAddress );            if (!coreResult.IsSuccess) return OperateResult.CreateFailedResult<bool[]>( coreResult );            // 核心交互            var read = ReadFromCoreServer( PackMcCommand( coreResult.Content, NetworkNumber, NetworkStationNumber ) );			if (!read.IsSuccess) return OperateResult.CreateFailedResult<bool[]>(read);			// 错误代码验证			ushort errorCode = BitConverter.ToUInt16( read.Content, 9 );            if (errorCode != 0) return new OperateResult<bool[]>( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );            // 数据解析,需要传入是否使用位的参数            var extract = ExtractActualData( SoftBasic.BytesArrayRemoveBegin( read.Content, 11 ), true );            if(!extract.IsSuccess) return OperateResult.CreateFailedResult<bool[]>( extract );            // 转化bool数组            return OperateResult.CreateSuccessResult( extract.Content.Select( m => m == 0x01 ).Take( length ).ToArray( ) );        }        /// <summary>        /// 从三菱PLC中批量读取位软元件,返回读取结果        /// </summary>        /// <param name="address">起始地址</param>        /// <returns>带成功标志的结果数据对象</returns>        /// <example>参照 <see cref="ReadBool(string, ushort)"/> 方法 </example>        public OperateResult<bool> ReadBool( string address )        {            OperateResult<bool[]> read = ReadBool( address, 1 );            if (!read.IsSuccess) return OperateResult.CreateFailedResult<bool>( read );            return OperateResult.CreateSuccessResult<bool>( read.Content[0] );        }                /// <summary>        /// 向PLC中位软元件写入bool数组,返回值说明,比如你写入M100,values[0]对应M100        /// </summary>        /// <param name="address">要写入的数据地址</param>        /// <param name="value">要写入的实际数据,长度为8的倍数</param>        /// <example>        /// 详细请查看<see cref="Write(string, bool[])"/>方法的示例        /// </example>        /// <returns>返回写入结果</returns>        public OperateResult Write( string address, bool value )        {            return Write( address, new bool[] { value} );        }        /// <summary>        /// 向PLC中位软元件写入bool数组,返回值说明,比如你写入M100,values[0]对应M100        /// </summary>        /// <param name="address">要写入的数据地址</param>        /// <param name="values">要写入的实际数据,可以指定任意的长度</param>        /// <example>        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\melsecTest.cs" region="WriteBool" title="Write示例" />        /// </example>        /// <returns>返回写入结果</returns>        public OperateResult Write( string address, bool[] values )        {            OperateResult<byte[]> coreResult = MelsecHelper.BuildWriteBitCoreCommand( address, values, McAnalysisAddress );            if (!coreResult.IsSuccess) return coreResult;            // 核心交互            OperateResult<byte[]> 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<byte[]>( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );            // 成功            return OperateResult.CreateSuccessResult( );        }        #endregion        #region Remote Operate        /// <summary>        /// 远程Run操作        /// </summary>        /// <returns>是否成功</returns>        public OperateResult RemoteRun( )        {            // 核心交互            OperateResult<byte[]> 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( );        }                /// <summary>        /// 远程Stop操作        /// </summary>        /// <returns>是否成功</returns>        public OperateResult RemoteStop( )        {            // 核心交互            OperateResult<byte[]> 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( );        }        /// <summary>        /// 远程Reset操作        /// </summary>        /// <returns>是否成功</returns>        public OperateResult RemoteReset()        {            // 核心交互            OperateResult<byte[]> 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( );        }        /// <summary>        /// 读取PLC的型号信息        /// </summary>        /// <returns>返回型号的结果对象</returns>        public OperateResult<string> ReadPlcType( )        {            // 核心交互            OperateResult<byte[]> read = ReadFromCoreServer( PackMcCommand( new byte[] { 0x01, 0x01, 0x00, 0x00 }, NetworkNumber, NetworkStationNumber ) );            if (!read.IsSuccess) return OperateResult.CreateFailedResult<string>( read );            // 错误码校验            ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );            if (ErrorCode != 0) return new OperateResult<string>( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );            // 成功            return OperateResult.CreateSuccessResult( Encoding.ASCII.GetString( read.Content, 11, 16 ).TrimEnd( ) );        }        #endregion        #region Object Override        /// <summary>        /// 获取当前对象的字符串标识形式        /// </summary>        /// <returns>字符串信息</returns>        public override string ToString()        {            return $"MelsecMcNet[{IpAddress}:{Port}]";        }        #endregion        #region Static Method Helper        /// <summary>        /// 将MC协议的核心报文打包成一个可以直接对PLC进行发送的原始报文        /// </summary>        /// <param name="mcCore">MC协议的核心报文</param>        /// <param name="networkNumber">网络号</param>        /// <param name="networkStationNumber">网络站号</param>        /// <returns>原始报文信息</returns>        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;        }        /// <summary>        /// 从PLC反馈的数据中提取出实际的数据内容,需要传入反馈数据,是否位读取        /// </summary>        /// <param name="response">反馈的数据内容</param>        /// <param name="isBit">是否位读取</param>        /// <returns>解析后的结果对象</returns>        public static OperateResult<byte[]> 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    }}
 |