[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (10) 實作 RtuModbusClient
續上篇,接下來要實作 RtuModbusClient,在這個類別裡主要是處理 Serial 類別中的 Send/Receive 方法,也就是要將 RtuModbusRequest 以及 RtuMuodbusResponse 組合起來
如下圖紅框:
RtuModbusClient 類別實作AbsModbusClient 抽像類別:
- 檢查資料格式的動作都交給 RtuModbusRequest 還有 RtuModbusResponse 了,在這裡就不需要考慮資料檢查的問題,只需要專心對付真實設備的響應情況,比如 Retry次數的設定、Timeout 的設定,更清楚的分工,可以讓程式碼看起來更簡單。
- 這個類別的 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