[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (07) 實作 TcpModbusClient

[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (07) 實作 TcpModbusClient

續上篇,接下來要實作 TcpModbusClient,在這個類別裡主要是處理 Socket 類別中的 Send/Receive 方法,也就是要將 TcpModbusRequest 以及 TcpModbusResponse 組合起來

如下圖紅框:

image

 

TcpModbusClient 類別實作AbsModbusClient 抽像類別

  1. 檢查資料格式的動作都交給 TcpModbusRequest 還有 TcpModbusResponse 了,在這裡就不需要考慮資料檢查的問題,只需要專心對付真實設備的響應情況,比如 Retry次數的設定、Timeout 的設定,更清楚的分工,可以讓程式碼看起來更簡單。
  2. 這個類別的 Connect 泛型方法,主要是吃 TcpModbusConnectConfig 類別

程式碼如下:

{
    private AbsModbusRequest _modbusRequest = new TcpModbusRequest();
    private AbsModbusResponse _modbusResponse = new TcpModbusResponse();
    private AbsModbusDataConvert _modbusDataConvert = new HexModbusDataConvert();

    private Socket ModbusClient { get; set; }

    private IPEndPoint ModbusEndPoint { get; set; }

    public override bool Connect<T>(T ConnectConfig)
    {
        if (ConnectConfig == null)
        {
            throw new ArgumentNullException("ConnectConfig");
        }

        TcpModbusConnectConfig connectConfig = ConnectConfig as TcpModbusConnectConfig;
        if (connectConfig == null)
        {
            throw new NotSupportedException("ConnectConfig");
        }
        if (this.ModbusClient == null)
        {
            this.ModbusClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        this.ModbusEndPoint = new IPEndPoint(IPAddress.Parse(connectConfig.IpAddress), connectConfig.Port);

        this.ModbusClient.Connect((EndPoint)ModbusEndPoint);
        this.IsConnected = this.ModbusClient.Connected;
        return this.IsConnected;
    }

    internal override AbsModbusRequest ModbusRequest
    {
        get { return _modbusRequest; }
        set { _modbusRequest = value; }
    }

    internal override AbsModbusResponse ModbusResponse
    {
        get { return _modbusResponse; }
        set { _modbusResponse = value; }
    }

    internal override AbsModbusDataConvert ModbusDataConvert
    {
        get { return _modbusDataConvert; }
        set { _modbusDataConvert = value; }
    }

    public override bool Disconnect()
    {
        if (!this.IsConnected)
        {
            return false;
        }

        this.ModbusClient.Shutdown(SocketShutdown.Both);
        this.ModbusClient.Disconnect(false);
        this.IsConnected = this.ModbusClient.Connected;
        return !this.IsConnected;
    }

    public override bool Send(byte[] RequestArray)
    {
        if (!this.connectVerify())
        {
            return false;
        }
        this.ModbusClient.Send(RequestArray);
        return true;
    }

    public override byte[] Receive()
    {
        if (!this.connectVerify())
        {
            return null;
        }

        var tempTimeOut = this.ModbusClient.ReceiveTimeout;
        this.ModbusClient.ReceiveTimeout = this.ReceiveTimeout;

        var bufferArray = new byte[256];
        var socketError = new SocketError();
        byte[] responseArray = null;

        var retryCount = 0;
        Stopwatch sw = new Stopwatch();
        using (MemoryStream memory = new MemoryStream())
        {
            while (true)
            {
                if (this.ModbusClient.Available > 0)
                {
                    var receiveCount = this.ModbusClient.Receive(bufferArray, 0, bufferArray.Length, SocketFlags.None, out socketError);
                    if (receiveCount == 0 || socketError != SocketError.Success)
                    {
                        break;
                    }
                    memory.Write(bufferArray, 0, receiveCount);
                    sw.Restart();
                    retryCount = 0;
                }

                //if (retryCount > this.RetryTime)
                //{
                //    break;
                //}
                //if (sw.ElapsedMilliseconds >= this.ReceiveTimeout)
                //{
                //    break;
                //}

                retryCount++;

                //空轉
                SpinWait.SpinUntil(() => retryCount > this.RetryTime, this.ReceiveTimeout);
            }

            this.ModbusClient.ReceiveTimeout = tempTimeOut;
            responseArray = memory.ToArray();
        }

        if (responseArray == null || responseArray.Length == 0)
        {
            throw new ModbusException("Receive reponse timeout");
        }
        return responseArray;
    }

    public override void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~TcpModbusClient()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (this.Disposed)
            return;

        this.IsConnected = false;

        if (disposing)
        {
            //clean management resource
            if (this.ModbusClient != null)
            {
                this.ModbusClient.Shutdown(SocketShutdown.Both);
                this.ModbusClient.Disconnect(false);
                this.ModbusClient.Dispose();
                this.ModbusClient = null;
            }
        }

        //clean unmanagement resource

        //change flag
        this.Disposed = true;
    }

    private bool connectVerify()
    {
        if (!this.IsConnected)
        {
            throw new Exception("No Connect");
        }
        if (this.Disposed)
        {
            return false;
        }
        return true;
    }
}

 


 

定義連線參數。

public class TcpModbusConnectConfig : INotifyPropertyChanged
{
    private string _ipAddress = "127.0.0.1";
    private int _port = 502;

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

    public string IpAddress
    {
        get { return _ipAddress; }
        set
        {
            if (string.IsNullOrEmpty(value))
            {
                throw new ArgumentNullException();
            }
            IPAddress result;
            if (IPAddress.TryParse(value, out result))
            {
                _ipAddress = value;
            }
            OnPropertyChanged("IpAddress");
        }
    }

    public int Port
    {
        get { return _port; }
        set
        {
            _port = value;
            OnPropertyChanged("Port");
        }
    }
}

調用方法

client.Connect(new TcpModbusConnectConfig() { IpAddress = "127.0.0.1", Port = 502 });
var result = client.ReadCoilsToDecimal(1, 0, 10);

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo