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

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

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

如下圖紅框:

image

RtuModbusClient 類別實作AbsModbusClient 抽像類別:

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

程式碼如下:


{
    private AbsModbusRequest _modbusRequest = new RtuModbusRequest();
    private AbsModbusResponse _modbusResponse = new RtuModbusResponse();
    private AbsModbusDataConvert _modbusDataConvert = new HexModbusDataConvert();

    public SerialPort ModbusSerialPort { get; set; }

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

        SerialModbusConnectConifg connectConfig = ConnectConfig as SerialModbusConnectConifg;
        if (connectConfig == null)
        {
            throw new NotSupportedException("ConnectConfig");
        }
        if (this.ModbusSerialPort == null)
        {
            this.ModbusSerialPort = new SerialPort(connectConfig.PortName, connectConfig.BaudRate, connectConfig.Parity, connectConfig.DataBits, connectConfig.StopBits);
        }

        if (this.ModbusSerialPort.IsOpen)
        {
            this.ModbusSerialPort.Close();
            Thread.Sleep(100);
        }

        this.ModbusSerialPort.Open();
        this.IsConnected = this.ModbusSerialPort.IsOpen;
        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.ModbusSerialPort.Close();
        this.IsConnected = this.ModbusSerialPort.IsOpen;
        return !this.IsConnected;
    }

    public override byte[] Receive()
    {
        if (!this.IsConnected)
        {
            throw new ModbusException("No Connect");
        }
        var timeoutTemp = this.ModbusSerialPort.ReadTimeout;
        this.ModbusSerialPort.ReadTimeout = this.ReceiveTimeout;
        byte[] bufferArray = new byte[256];
        byte[] resultArray = null;
        var retryCount = 0;

        using (MemoryStream stream = new MemoryStream())
        {
            while (true)
            {
                if (this.ModbusSerialPort.BytesToRead > 0)
                {
                    var receiveCount = this.ModbusSerialPort.Read(bufferArray, 0, bufferArray.Length);
                    stream.Write(bufferArray, 0, receiveCount);
                    resultArray = stream.ToArray();
                    if (receiveCount <= 0)
                    {
                        break;
                    }

                    retryCount = 0;
                }
                retryCount++;

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

        if (resultArray == null || resultArray.Length == 0)
        {
            throw new ModbusException("Receive Timeout");
        }
        this.ModbusSerialPort.ReadTimeout = timeoutTemp;
        return resultArray;
    }

    public override bool Send(byte[] RequestArray)
    {
        this.ModbusSerialPort.Write(RequestArray, 0, RequestArray.Length);
        return true;
    }

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

    ~RtuModbusClient()
    {
        Dispose(false);
    }

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

        this.IsConnected = false;

        if (disposing)
        {
            //clean management resource
            if (this.ModbusSerialPort != null)
            {
                this.ModbusSerialPort.Dispose();
                this.ModbusSerialPort = null;
            }
        }

        //clean unmanagement resource

        //change flag
        this.Disposed = true;
    }
}

定義連線參數。

程式碼如下:


public class SerialModbusConnectConifg : INotifyPropertyChanged
{
    private string _portName = "COM1";
    private int _baudRate = 115200;
    private Parity _parity = Parity.None;
    private int _dataBits = 8;
    private StopBits _stopBits = StopBits.One;
    private int _receiveTimeout;
    private int _sendTimeout;
    private int _retryTime;

    public event PropertyChangedEventHandler PropertyChanged;

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

    public virtual string PortName
    {
        get { return _portName; }
        set
        {
            _portName = value;
            OnPropertyChanged("PortName");
        }
    }

    public virtual int BaudRate
    {
        get { return _baudRate; }
        set
        {
            _baudRate = value;
            OnPropertyChanged("BaudRate");
        }
    }

    public virtual Parity Parity
    {
        get { return _parity; }
        set
        {
            _parity = value;
            OnPropertyChanged("Parity");
        }
    }

    public virtual int DataBits
    {
        get { return _dataBits; }
        set
        {
            _dataBits = value;
            OnPropertyChanged("DataBits");
        }
    }

    public virtual StopBits StopBits
    {
        get { return _stopBits; }
        set
        {
            _stopBits = value;
            OnPropertyChanged("StopBits");
        }
    }
}


調用方式如下:


{
    ModbusClientAdpater adpater = new ModbusClientAdpater();

    AbsModbusClient client = adpater.CreateModbusClient(EnumModbusFraming.RTU);
    client.Connect(new SerialModbusConnectConifg() { PortName = "COM6", BaudRate = 115200, Parity = Parity.None, StopBits = StopBits.One });
    var result = client.ReadCoilsToDecimal(1, 0, 10);
}

 

再在撐一下下,還差一個ASCII…

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


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

Image result for microsoft+mvp+logo