[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