HslCommunication庫的二次協議擴展,適配第三方通訊協議開發,基礎框架支持長短連接模式


本文將使用一個gitHub開源的項目來擴展實現二次協議的開發,該項目已經搭建好了基礎層架構,並實現了三菱,西門子,歐姆龍,MODBUS-TCP的通訊示例,也可以參照這些示例開發其他的通訊協議,並Pull request到這個項目中來實現這個項目的最終目標

github地址:https://github.com/dathlin/HslCommunication 如果喜歡可以star或是fork,還可以打賞支持。

聯系作者及加群方式(激活碼在群里發放):http://www.hslcommunication.cn/Cooperation

在Visual Studio 中的NuGet管理器中可以下載安裝,也可以直接在NuGet控制台輸入下面的指令安裝

Install-Package HslCommunication

 如果需要教程:Nuget安裝教程:http://www.cnblogs.com/dathlin/p/7705014.html

 

 

組件的完整信息和其他API介紹參照:http://www.cnblogs.com/dathlin/p/7703805.html   組件的授權協議,更新日志,都在該頁面里面。

 

本文將展示如果進行二次擴展通訊協議,來進行遠程交互,可以是PLC協議,自定義協議等等。以一個示例為切入點,根據這個示例來深入講解

此處使用到了2個命名空間:

using HslCommunication;
using HslCommunication.Core.Net;

 

 

關於兩種模式


本組件所提供的所有客戶端類,包括三菱,西門子,歐姆龍,modbus-tcp,以及SimplifyNet都是繼承自雙模式基類,雙模式包含了短連接和長連接,下面就具體介紹下兩個模式的區別

短連接:每次讀寫都是一個單獨的請求,請求完畢也就關閉了,如果服務器的端口僅僅支持單連接,那么關閉后這個端口可以被其他連接復用,但是在頻繁的網絡請求下,容易發生異常,會有其他的請求不成功,尤其是多線程的情況下。

長連接:創建一個公用的連接通道,所有的讀寫請求都利用這個通道來完成,這樣的話,讀寫性能更快速,即時多線程調用也不會影響,內部有同步機制。如果服務器的端口僅僅支持單連接,那么這個端口就被占用了,比如三菱的端口機制,西門子的Modbus tcp端口機制也是這樣的。以下代碼默認使用長連接,性能更高,還支持多線程同步。

在短連接的模式下,每次請求都是單獨的訪問,所以沒有重連的困擾,在長連接的模式下,如果本次請求失敗了,在下次請求的時候,會自動重新連接服務器,直到請求成功為止。另外,盡量所有的讀寫都對結果的成功進行判斷。

 

關於日志記錄


不管是三菱的數據訪問類,還是西門子的,還是Modbus tcp訪問類,都有一個LogNet屬性用來記錄日志,該屬性是一個接口類,ILogNet,凡事繼承該接口的都可以用來記錄日志,該日志會在訪問失敗時,尤其是因為網絡的原因導致訪問失敗時會進行日志記錄(如果你為這個 LogNet 屬性配置了真實的日志記錄器的話):如果你想使用該記錄日志的功能,請參照如下的博客進行實例化:

http://www.cnblogs.com/dathlin/p/7691693.html

 

關於基類:public class NetworkDoubleBase<TNetMessage, TTransform> : NetworkBase where TNetMessage : INetMessage, new() where TTransform : IByteTransform, new()


該基類定義了連接方法,單次的數據請求方法,但是需要指定消息類型,TNetMessage指示了該消息類型必須繼承自接口INetMessage,至於TTransform指示了一些數據類型的變換規則,這兩個類型指定完成后,后面的事情就是定義地址解析器,定義讀寫指令創建,定義基礎的讀寫方法,然后擴展不同類型的數據讀寫。

 

開始二次開發:


先定義消息:消息的接口指示了如果去接收一條完整的消息,通常都是byte[]數據,我們看一下這個接口的定義

    /// <summary>
    /// 本系統的消息類,包含了各種解析規則,數據信息提取規則
    /// </summary>
    public interface INetMessage
    {
        /// <summary>
        /// 消息頭的指令長度
        /// </summary>
        int ProtocolHeadBytesLength { get; }


        /// <summary>
        /// 從當前的頭子節文件中提取出接下來需要接收的數據長度
        /// </summary>
        /// <returns>返回接下來的數據內容長度</returns>
        int GetContentLengthByHeadBytes();


        /// <summary>
        /// 檢查頭子節的合法性
        /// </summary>
        /// <param name="token">特殊的令牌,有些特殊消息的驗證</param>
        /// <returns></returns>
        bool CheckHeadBytesLegal(byte[] token);


        /// <summary>
        /// 獲取頭子節里的消息標識
        /// </summary>
        /// <returns></returns>
        int GetHeadBytesIdentity();


        /// <summary>
        /// 消息頭字節
        /// </summary>
        byte[] HeadBytes { get; set; }


        /// <summary>
        /// 消息內容字節
        /// </summary>
        byte[] ContentBytes { get; set; }


        /// <summary>
        /// 發送的字節信息
        /// </summary>
        byte[] SendBytes { get; set; }
    }
    

}

 

舉例來說明:例子一:Modbus-Tcp消息,通常如下:

byte[0] byte[1]  消息頭 byte[0]*256+byte[1]

byte[2] byte[3] 必須都是0,否則不是Modbus協議

byte[4] byte[5] 后面跟着的消息長度,長度為byte[4]*256 + byte[5]

byte[6] 站號

byte[7] 功能碼

byte[8] byte[9] 地址

...

...

等等,不管后面是什么了

 

OK,現在已經可以寫TNetMessage了,主要思路是先接收6個長度的頭子節,接收完后 HeadBytes 就是6個長度的字節,如果需要驗證,就判斷byte[2],byte[3]是不是都為0,然后寫一個方法,從這個頭子節數據里分析出接下來的數據長度, 然后就可以按照下面寫。

下面的驗證消息接收的合法性,還需要根據發送消息的消息號,接收的消息號要一致。

    /// <summary>
    /// Modbus-Tcp協議支持的消息解析類
    /// </summary>
    public class ModbusTcpMessage : INetMessage
    {
        /// <summary>
        /// 消息頭的指令長度
        /// </summary>
        public int ProtocolHeadBytesLength
        {
            get { return 6; }
        }


        /// <summary>
        /// 從當前的頭子節文件中提取出接下來需要接收的數據長度
        /// </summary>
        /// <returns>返回接下來的數據內容長度</returns>
        public int GetContentLengthByHeadBytes( )
        {
               return = HeadBytes[4] * 256 + HeadBytes[5];
        }


        /// <summary>
        /// 檢查頭子節的合法性
        /// </summary>
        /// <param name="token">特殊的令牌,有些特殊消息的驗證</param>
        /// <returns></returns>
        public bool CheckHeadBytesLegal( byte[] token )
        {
            if (SendBytes[0] != HeadBytes[0] || SendBytes[1] != HeadBytes[1]) return false;
            return HeadBytes[2] == 0x00 && HeadBytes[3] == 0x00;
        }


        /// <summary>
        /// 獲取頭子節里的消息標識
        /// </summary>
        /// <returns></returns>
        public int GetHeadBytesIdentity( )
        {
            return HeadBytes[0] * 256 + HeadBytes[1];// 有些協議沒有標識就返回0
        }


        /// <summary>
        /// 消息頭字節
        /// </summary>
        public byte[] HeadBytes { get; set; }


        /// <summary>
        /// 消息內容字節
        /// </summary>
        public byte[] ContentBytes { get; set; }


        /// <summary>
        /// 發送的字節信息
        /// </summary>
        public byte[] SendBytes { get; set; }


    }

消息類寫好 ,接下來就選取IByteTransform接口的類,這個接口定義了什么呢?定義了常用的數據類型和byte[]數組之間的轉換方法。為什么要實現這個接口呢,因為不同設備的數據定義規則是不一樣的,比如C#的類庫,地位在前,高位在后,三菱PLC中也是類似的,西門子確實地位在后,高位在前,但是modbus-tcp和fins協議卻以雙字節為單位。

所以本系統系統三個常用的數據轉換類,如果有其他的機制,后面可以擴展,這三個類如下:

  • RegularByteTransform 常規的數據轉換,低位在前,高位在后
  • ReverseBytesTransform 高地位反轉的數據轉換類,高位在前,地位在后
  • ReverseWordTransform 以字節為單位進行反轉的數據類

那么我們就選擇好了類型,然后通訊類已經基本成型了

public class ModbusTcpNet : NetworkDoubleBase<ModbusTcpMessage, ReverseWordTransform>
{

}

 

然后創建基礎的讀取指令方法,和寫入指令方法,此處簡便處理,只針對寄存器進行操作

        /// <summary>
        /// 讀取數據的基礎指令,需要指定指令碼,地址,長度
        /// </summary>
        /// <param name="code"></param>
        /// <param name="address"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        private OperateResult<byte[]> BuildReadCommandBase( byte code, string address, ushort count )
        {
            ushort add = ushort.Parse( address );
            ushort messageId = (ushort)softIncrementCount.GetCurrentValue( );
            byte[] buffer = new byte[12];
            buffer[0] = (byte)(messageId / 256);
            buffer[1] = (byte)(messageId % 256);
            buffer[2] = 0x00;
            buffer[3] = 0x00;
            buffer[4] = 0x00;
            buffer[5] = 0x06;
            buffer[6] = station;
            buffer[7] = code;
            buffer[8] = (byte)(add / 256);
            buffer[9] = (byte)(add % 256);
            buffer[10] = (byte)(count / 256);
            buffer[11] = (byte)(count % 256);

            return OperateResult.CreateSuccessResult( buffer );
        }

然后讀取寄存器的基礎方法是這樣設計,基類里有個方法:

        /// <summary>
        /// 使用底層的數據報文來通訊,傳入需要發送的消息,返回一條完整的數據指令
        /// </summary>
        /// <param name="send">發送的完整的報文信息</param>
        /// <returns>接收的完整的報文信息</returns>
        public OperateResult<byte[]> ReadFromCoreServer( byte[] send );

這個方法是一次數據交互的成功與否,所以我們要封裝一個二次方法,不僅僅是進行數據交互,進行消息的二次驗證,如果驗證失敗,就返回錯誤還有相關的消息

        private OperateResult<byte[]> CheckModbusTcpResponse( byte[] send )
        {
            OperateResult<byte[]> result = ReadFromCoreServer( send );
            if (result.IsSuccess)
            {
                if ((send[7] + 0x80) == result.Content[7])
                {
                    // 發生了錯誤
                    result.IsSuccess = false;
                    result.Message = GetDescriptionByErrorCode( result.Content[8] );
                    result.ErrorCode = result.Content[8];
                }
            }
            return result;
        }

然后在封裝一層基礎的通信方法,在讀取到數據並且驗證成功之后,把讀取到的數據內容單獨提取出來,好讓后續進行更加方便的處理。

        /// <summary>
        /// 讀取服務器的數據,需要指定不同的功能碼
        /// </summary>
        /// <param name="code">指令</param>
        /// <param name="address">地址</param>
        /// <param name="length">長度</param>
        /// <returns></returns>
        private OperateResult<byte[]> ReadModBusBase( byte code, string address, ushort length )
        {
            OperateResult<byte[]> command = BuildReadCommandBase( code, address, length );
            if (!command.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( command );

            OperateResult<byte[]> resultBytes = CheckModbusTcpResponse( command.Content );
            if (resultBytes.IsSuccess)
            {
                // 二次數據處理
                if (resultBytes.Content?.Length >= 9)
                {
                    byte[] buffer = new byte[resultBytes.Content.Length - 9];
                    Array.Copy( resultBytes.Content, 9, buffer, 0, buffer.Length );
                    resultBytes.Content = buffer;
                }
            }
            return resultBytes;
        }

有了上面兩層的基礎,最終提供了一個讀取寄存器的基礎方法,也就是第三層的方法

        /// <summary>
        /// 從Modbus服務器批量讀取寄存器的信息,需要指定起始地址,讀取長度
        /// </summary>
        /// <param name="address">起始地址,格式為"1234"</param>
        /// <param name="length">讀取的數量</param>
        /// <returns>帶有成功標志的字節信息</returns>
        public OperateResult<byte[]> Read( string address, ushort length )
        {
            OperateResult<byte[]> read = ReadModBusBase( ModbusInfo.ReadRegister, address, length );
            if (!read.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( read );
            return read;
        }

 

有了上面的讀取寄存器的方法,那么我們可以方便的擴展其他基礎類型的數據讀取了。

        /// <summary>
        /// 讀取指定地址的short數據
        /// </summary>
        /// <param name="address">起始地址,格式為"1234"</param>
        /// <returns>帶有成功標志的short數據</returns>
        public OperateResult<short> ReadInt16( string address )
        {
            return GetInt16ResultFromBytes( Read( address, 1 ) );
        }


        /// <summary>
        /// 讀取指定地址的ushort數據
        /// </summary>
        /// <param name="address">起始地址,格式為"1234"</param>
        /// <returns>帶有成功標志的ushort數據</returns>
        public OperateResult<ushort> ReadUInt16( string address )
        {
            return GetUInt16ResultFromBytes( Read( address, 1 ) );
        }

        /// <summary>
        /// 讀取指定地址的int數據
        /// </summary>
        /// <param name="address">起始地址,格式為"1234"</param>
        /// <returns>帶有成功標志的int數據</returns>
        public OperateResult<int> ReadInt32( string address )
        {
            return GetInt32ResultFromBytes( Read( address, 2 ) );
        }

        /// <summary>
        /// 讀取指定地址的uint數據
        /// </summary>
        /// <param name="address">起始地址,格式為"1234"</param>
        /// <returns>帶有成功標志的uint數據</returns>
        public OperateResult<uint> ReadUInt32( string address )
        {
            return GetUInt32ResultFromBytes( Read( address, 2 ) );
        }

        /// <summary>
        /// 讀取指定地址的float數據
        /// </summary>
        /// <param name="address">起始地址,格式為"1234"</param>
        /// <returns>帶有成功標志的float數據</returns>
        public OperateResult<float> ReadFloat( string address )
        {
            return GetSingleResultFromBytes( Read( address, 2 ) );
        }

        /// <summary>
        /// 讀取指定地址的long數據
        /// </summary>
        /// <param name="address">起始地址,格式為"1234"</param>
        /// <returns>帶有成功標志的long數據</returns>
        public OperateResult<long> ReadInt64( string address )
        {
            return GetInt64ResultFromBytes( Read( address, 4 ) );
        }

        /// <summary>
        /// 讀取指定地址的ulong數據
        /// </summary>
        /// <param name="address">起始地址,格式為"1234"</param>
        /// <returns>帶有成功標志的ulong數據</returns>
        public OperateResult<ulong> ReadUInt64( string address )
        {
            return GetUInt64ResultFromBytes( Read( address, 4 ) );
        }

        /// <summary>
        /// 讀取指定地址的double數據
        /// </summary>
        /// <param name="address">起始地址,格式為"1234"</param>
        /// <returns>帶有成功標志的double數據</returns>
        public OperateResult<double> ReadDouble( string address )
        {
            return GetDoubleResultFromBytes( Read( address, 4 ) );
        }

        /// <summary>
        /// 讀取地址地址的String數據,字符串編碼為ASCII
        /// </summary>
        /// <param name="address">起始地址,格式為"1234"</param>
        /// <param name="length">字符串長度</param>
        /// <returns>帶有成功標志的string數據</returns>
        public OperateResult<string> ReadString( string address, ushort length )
        {
            return GetStringResultFromBytes( Read( address, length ) );
        }

到這里為止,就寫完了寄存器的讀取方法,實際上會更加復雜點,會把地址解析專門拿出來做成地址解析器,因為有些PLC的地址是比較復雜,例如西門子的"M100.2",就需要寫個專門的解析器來解析,針對單次讀取上限,也可以支持更具地址來多次訪問等等操作。

 

寫入數據的例子:

寫入的操作通常不會返回數據,只要驗證完指令的邏輯性即可,我們把地址解析器拿出來看看,先寫地址解析器

        /// <summary>
        /// 解析數據地址,解析出地址類型,起始地址
        /// </summary>
        /// <param name="address">數據地址</param>
        /// <returns>解析出地址類型,起始地址,DB塊的地址</returns>
        private OperateResult<int> AnalysisAddress( string address )
        {
            try
            {
                return OperateResult.CreateSuccessResult( Convert.ToInt32( address ) );
            }
            catch (Exception ex)
            {
                return new OperateResult<int>( )
                {
                    Message = ex.Message
                };
            }
        }

解析完地址后,就創建寫入的基礎指令,需要指定字節數組,如下的創建方式是針對了多個寄存器寫入的代碼

        private OperateResult<byte[]> BuildWriteRegisterCommand( string address, byte[] data )
        {
            OperateResult<int> analysis = AnalysisAddress( address );
            if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( analysis );

            ushort messageId = (ushort)softIncrementCount.GetCurrentValue( );
            byte[] buffer = new byte[13 + data.Length];
            buffer[0] = (byte)(messageId / 256);
            buffer[1] = (byte)(messageId % 256);
            buffer[2] = 0x00;
            buffer[3] = 0x00;
            buffer[4] = (byte)((buffer.Length - 6) / 256);
            buffer[5] = (byte)((buffer.Length - 6) % 256);
            buffer[6] = station;
            buffer[7] = ModbusInfo.WriteRegister;
            buffer[8] = (byte)(analysis.Content / 256);
            buffer[9] = (byte)(analysis.Content % 256);
            buffer[10] = (byte)(data.Length / 2 / 256);
            buffer[11] = (byte)(data.Length / 2 % 256);

            buffer[12] = (byte)(data.Length);
            data.CopyTo( buffer, 13 );
            return OperateResult.CreateSuccessResult( buffer );
        }

那么寫入數據基礎方法就是

        /// <summary>
        /// 將數據寫入到Modbus的寄存器上去,需要指定起始地址和數據內容
        /// </summary>
        /// <param name="address">起始地址,格式為"1234"</param>
        /// <param name="value">寫入的數據,長度根據data的長度來指示</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, byte[] value )
        {
            OperateResult<byte[]> command = BuildWriteRegisterCommand( address, value );
            if (!command.IsSuccess)
            {
                return command;
            }

            return CheckModbusTcpResponse( command.Content );
        }

然后我們再想支持其他的數據類型,就好辦很多了

        #region Write Short

        /// <summary>
        /// 向寄存器中寫入short數組,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="values">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, short[] values )
        {
            return Write( address, ByteTransform.TransByte( values ) );
        }

        /// <summary>
        /// 向寄存器中寫入short數據,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="value">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, short value )
        {
            return Write( address, new short[] { value } );
        }

        #endregion

        #region Write UShort


        /// <summary>
        /// 向寄存器中寫入ushort數組,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="values">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, ushort[] values )
        {
            return Write( address, ByteTransform.TransByte( values ) );
        }


        /// <summary>
        /// 向寄存器中寫入ushort數據,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="value">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, ushort value )
        {
            return Write( address, new ushort[] { value } );
        }


        #endregion

        #region Write Int

        /// <summary>
        /// 向寄存器中寫入int數組,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="values">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, int[] values )
        {
            return Write( address, ByteTransform.TransByte( values ) );
        }

        /// <summary>
        /// 向寄存器中寫入int數據,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="value">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, int value )
        {
            return Write( address, new int[] { value } );
        }

        #endregion

        #region Write UInt

        /// <summary>
        /// 向寄存器中寫入uint數組,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="values">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, uint[] values )
        {
            return Write( address, ByteTransform.TransByte( values ) );
        }

        /// <summary>
        /// 向寄存器中寫入uint數據,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="value">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, uint value )
        {
            return Write( address, new uint[] { value } );
        }

        #endregion

        #region Write Float

        /// <summary>
        /// 向寄存器中寫入float數組,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="values">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, float[] values )
        {
            return Write( address, ByteTransform.TransByte( values ) );
        }

        /// <summary>
        /// 向寄存器中寫入float數據,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="value">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, float value )
        {
            return Write( address, new float[] { value } );
        }


        #endregion

        #region Write Long

        /// <summary>
        /// 向寄存器中寫入long數組,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="values">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, long[] values )
        {
            return Write( address, ByteTransform.TransByte( values ) );
        }

        /// <summary>
        /// 向寄存器中寫入long數據,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="value">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, long value )
        {
            return Write( address, new long[] { value } );
        }

        #endregion

        #region Write ULong

        /// <summary>
        /// 向寄存器中寫入ulong數組,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="values">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, ulong[] values )
        {
            return Write( address, ByteTransform.TransByte( values ) );
        }

        /// <summary>
        /// 向寄存器中寫入ulong數據,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="value">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, ulong value )
        {
            return Write( address, new ulong[] { value } );
        }

        #endregion

        #region Write Double

        /// <summary>
        /// 向寄存器中寫入double數組,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="values">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, double[] values )
        {
            return Write( address, ByteTransform.TransByte( values ) );
        }

        /// <summary>
        /// 向寄存器中寫入double數據,返回值說明
        /// </summary>
        /// <param name="address">要寫入的數據地址</param>
        /// <param name="value">要寫入的實際數據</param>
        /// <returns>返回寫入結果</returns>
        public OperateResult Write( string address, double value )
        {
            return Write( address, new double[] { value } );
        }

        #endregion

 

 

到這里為止,基礎的操作和擴展講的差不多了。接下來就要針對某些特殊的設備進行適配,比如我在實際的開發中,發現西門子,歐姆龍的通信協議中,沒有一個握手信號交互的過程,在西門子里還要進行2次握手,在歐姆龍里要進行一次握手,這些握手信息在網絡連接上之后就需要進行交互,不然無法現在讀取。在上述的MODBUS協議了就不需要握手信號,如果想支持握手信號,那么就要重寫一個方法

        /// <summary>
        /// 在連接上歐姆龍PLC后,需要進行一步握手協議
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        protected override OperateResult InitilizationOnConnect( Socket socket )
        {
            // handSingle就是握手信號字節
            OperateResult<byte[], byte[]> read = ReadFromCoreServerBase( socket, handSingle );
            if (!read.IsSuccess) return read;
            
            // 檢查返回的狀態
            byte[] buffer = new byte[4];
            buffer[0] = read.Content2[7];
            buffer[1] = read.Content2[6];
            buffer[2] = read.Content2[5];
            buffer[3] = read.Content2[4];
            int status = BitConverter.ToInt32( buffer, 0 );
            if(status != 0)
            {
                return new OperateResult( )
                {
                    ErrorCode = status,
                    Message = "初始化失敗,具體原因請根據錯誤碼查找"
                };
            }

            // 提取PLC的節點地址
            if (read.Content2.Length >= 16)
            {
                DA1 = read.Content2[15];
            }
            return OperateResult.CreateSuccessResult( ) ;
        }

上面的代碼所示就是,歐姆龍協議的握手信號的處理方式,處理成功就返回為真的Result對象,處理失敗就返回假的結果對象。

注意:握手信號使用的方法必須是ReadFromCoreServerBase方法。

 

更復雜的實際開發例子,可以參見項目的源代碼,歡迎大家完善開發其他的通訊協議。

 

創作不易,感謝打賞


 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM