[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (03) 建立 Modbus Client 架構
續上篇,程式開發前,我需要ModelingProject 來幫助我釐清架構,在這個開發架構裡,設備端表示 Slave ,我把它看成 Server 端,然而 Master 我把它看成 Client 端,現在我要實作 Modbus TCP Client,為了方便測試我把 Request 以及 Response 隔開來,所以建立四個主要類別,來完成工作。
- AbsModbusClient:負責 Socket 物件的調用,比如Socket.Send/Receive
- AbsModbusRequest:負責 Request 的驗證及建立
- AbsModbusResponse:負責 Response 的驗證
- AbsModbusDataConvert:負責 Response 的資料格式轉換,把 byte[] 轉成10進制、16進制、2進制…等等
下圖為原型架構:
IModbusTransport 介面:
定義功能
{
byte[] ReadCoils(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<long> ReadCoilsToDecimal(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<string> ReadCoilsToBinary(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<long> ReadCoilsToOctal(byte Unit, ushort StartAddress, ushort Quantity);
byte[] ReadDiscreteInputs(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<long> ReadDiscreteInputsToDecimal(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<string> ReadDiscreteInputsToBinary(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<long> ReadDiscreteInputsToOctal(byte Unit, ushort StartAddress, ushort Quantity);
byte[] ReadHoldingRegisters(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<long> ReadHoldingRegistersToDecimal(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<string> ReadHoldingRegistersToBinary(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<long> ReadHoldingRegistersToOctal(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<float> ReadHoldingRegistersToFloat(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<double> ReadHoldingRegistersToDouble(byte Unit, ushort StartAddress, ushort Quantity);
byte[] ReadInputRegisters(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<long> ReadInputRegistersToDecimal(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<string> ReadInputRegistersToBinary(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<long> ReadInputRegistersToOctal(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<float> ReadInputRegistersToFloat(byte Unit, ushort StartAddress, ushort Quantity);
IEnumerable<double> ReadInputRegistersTDouble(byte Unit, ushort StartAddress, ushort Quantity);
bool WriteSingleCoil(byte Unit, ushort OutputAddress, bool OutputValue);
bool WriteSingleRegister(byte Unit, ushort OutputAddress, short OutputValue);
bool WriteMultipleCoils(byte Unit, ushort StartAddress, ushort Quantity, byte[] OutputValues);
bool WriteMultipleRegisters(byte Unit, ushort StartAddress, ushort Quantity, short[] OutputValues);
}
AbsModbusRequest 抽像類別:
- QuantityValidate 方法驗証 Quantity 是否合法。
- GetByteCount 方法,以 Quantity 數量,計算需要有多少個 Byte。
{
private ModbusUtility _modbusUtility = new ModbusUtility();
protected ModbusUtility ModbusUtility
{
get { return _modbusUtility; }
set { _modbusUtility = value; }
}
public virtual ushort? TransactionID { get; set; }
protected virtual void QuantityValidate(ushort StartAddress, ushort Quantity, ushort MinQuantity, ushort MaxQuantity)
{
if (Quantity < MinQuantity || Quantity > MaxQuantity)
{
throw ModbusException.GetModbusException(0x03);
}
}
protected virtual byte GetByteCount(ushort Quantity)
{
byte i = (byte)(Quantity / 8);
byte j = (byte)(Quantity - (i * 8));
byte counter = 0;
if (j == 0)
{
counter = i;
}
else
{
counter = (byte)(i + 1);
}
return counter;
}
protected virtual byte[] GetByteCount(short[] OutputValues)
{
byte counter = 0;
byte[] outputArray = null;
using (MemoryStream stream = new MemoryStream())
{
foreach (var output in OutputValues)
{
var tempArray = BitConverter.GetBytes((short)output);
Array.Reverse(tempArray);
stream.Write(tempArray, 0, tempArray.Length);
counter += (byte)tempArray.Length;
}
outputArray = stream.ToArray();
}
return outputArray;
}
public abstract byte[] ReadCoils(byte Unit, ushort StartAddress, ushort Quantity);
public abstract byte[] ReadDiscreteInputs(byte Unit, ushort StartAddress, ushort Quantity);
public abstract byte[] ReadHoldingRegisters(byte Unit, ushort StartAddress, ushort Quantity);
public abstract byte[] ReadInputRegisters(byte Unit, ushort StartAddress, ushort Quantity);
public abstract byte[] WriteSingleCoil(byte Unit, ushort OutputAddress, bool OutputValue);
public abstract byte[] WriteSingleRegister(byte Unit, ushort OutputAddress, short OutputValue);
public abstract byte[] WriteMultipleCoils(byte Unit, ushort StartAddress, ushort Quantity, byte[] OutputValues);
public abstract byte[] WriteMultipleRegisters(byte Unit, ushort StartAddress, ushort Quantity, short[] OutputValues);
}
AbsModbusResponse 抽像類別
- ExceptionValidate 方法驗証 Response 資料是否正確
- FunctionCodeValidate 方法驗証 Response 資料是 Function Code 是否相符
{
private ModbusUtility _utility = new ModbusUtility();
protected abstract byte FunctionCodePosition { get; set; }
protected virtual ModbusUtility Utility
{
get { return _utility; }
set { _utility = value; }
}
protected virtual void ExceptionValidate(byte[] ResponseArray)
{
if (ResponseArray == null | ResponseArray.Length <= FunctionCodePosition)
{
throw new ModbusException("No Response or Miss response data");
}
//exception
var functionCode = ResponseArray[FunctionCodePosition];
if (functionCode >= 80)
{
var exceptionCode = ResponseArray[FunctionCodePosition + 1];
throw ModbusException.GetModbusException(exceptionCode);
}
}
protected virtual void FunctionCodeValidate(byte[] RequestArray, byte[] ResponseArray, EnumModbusFunctionCode FunctionCodeFlag)
{
//function code valid
if (RequestArray[FunctionCodePosition] != ResponseArray[FunctionCodePosition])
{
throw ModbusException.GetModbusException(0x01);
}
if ((byte)FunctionCodeFlag != RequestArray[FunctionCodePosition])
{
throw ModbusException.GetModbusException(0x01);
}
if ((byte)FunctionCodeFlag != ResponseArray[FunctionCodePosition])
{
throw ModbusException.GetModbusException(0x01);
}
}
protected abstract void CheckDataValidate(byte[] ResponseArray);
protected abstract byte[] GetResultArray(byte[] ResponseArray);
public virtual byte[] ReadCoils(byte[] RequestArray, byte[] ResponseArray)
{
this.ExceptionValidate(ResponseArray);
this.FunctionCodeValidate(RequestArray, ResponseArray, EnumModbusFunctionCode.ReadCoils);
this.CheckDataValidate(ResponseArray);
var resultArray = GetResultArray(ResponseArray);
return resultArray;
}
public virtual byte[] ReadDiscreteInputs(byte[] RequestArray, byte[] ResponseArray)
{
this.ExceptionValidate(ResponseArray);
this.FunctionCodeValidate(RequestArray, ResponseArray, EnumModbusFunctionCode.ReadDiscreteInputs);
this.CheckDataValidate(ResponseArray);
var resultArray = GetResultArray(ResponseArray);
return resultArray;
}
public virtual byte[] ReadHoldingRegisters(byte[] RequestArray, byte[] ResponseArray)
{
this.ExceptionValidate(ResponseArray);
this.FunctionCodeValidate(RequestArray, ResponseArray, EnumModbusFunctionCode.ReadHoldingRegisters);
this.CheckDataValidate(ResponseArray);
var resultArray = GetResultArray(ResponseArray);
return resultArray;
}
public virtual byte[] ReadInputRegisters(byte[] RequestArray, byte[] ResponseArray)
{
this.ExceptionValidate(ResponseArray);
this.FunctionCodeValidate(RequestArray, ResponseArray, EnumModbusFunctionCode.ReadInputRegisters);
this.CheckDataValidate(ResponseArray);
var resultArray = GetResultArray(ResponseArray);
return resultArray;
}
public virtual byte[] WriteSingleCoil(byte[] RequestArray, byte[] ResponseArray)
{
this.ExceptionValidate(ResponseArray);
this.FunctionCodeValidate(RequestArray, ResponseArray, EnumModbusFunctionCode.WriteSingleCoil);
this.CheckDataValidate(ResponseArray);
return ResponseArray;
}
public virtual byte[] WriteSingleRegister(byte[] RequestArray, byte[] ResponseArray)
{
this.ExceptionValidate(ResponseArray);
this.FunctionCodeValidate(RequestArray, ResponseArray, EnumModbusFunctionCode.WriteSingleRegister);
this.CheckDataValidate(ResponseArray);
return ResponseArray;
}
public virtual byte[] WriteMultipleCoils(byte[] RequestArray, byte[] ResponseArray)
{
this.ExceptionValidate(ResponseArray);
this.FunctionCodeValidate(RequestArray, ResponseArray, EnumModbusFunctionCode.WriteMultipleCoils);
this.CheckDataValidate(ResponseArray);
return ResponseArray;
}
public virtual byte[] WriteMultipleRegisters(byte[] RequestArray, byte[] ResponseArray)
{
this.ExceptionValidate(ResponseArray);
this.FunctionCodeValidate(RequestArray, ResponseArray, EnumModbusFunctionCode.WriteMultipleRegisters);
this.CheckDataValidate(ResponseArray);
return ResponseArray;
}
}
AbsModbusDataConvert 抽象類別:
定義資料格式轉換的方法
{
private ModbusUtility _modbusUtility = new ModbusUtility();
protected ModbusUtility ModbusUtility
{
get { return _modbusUtility; }
set { _modbusUtility = value; }
}
public abstract byte[] ResultArray { get; internal set; }
public abstract IEnumerable<long> ToDecimal(byte[] ResultArray, EnumModbusIntegralUnit Unit);
public abstract IEnumerable<long> ToOctal(byte[] ResultArray, EnumModbusIntegralUnit Unit);
public abstract IEnumerable<string> ToHexadecimal(byte[] ResultArray, EnumModbusIntegralUnit Unit);
public abstract IEnumerable<string> ToBinary(byte[] ResultArray, EnumModbusIntegralUnit Unit);
public abstract IEnumerable<float> ToFloat(byte[] ResultArray);
public abstract IEnumerable<double> ToDouble(byte[] ResultArray);
}
AbsModbusClient 抽像類別:
處理現場設備的響應狀況,適情況調整 Retry 的次數、Timeout 的時間
{
//fields
private int _receiveTimeout = 1000;
private int _sendTimeout = 1000;
private bool _isConnected = false;
private int _retryTime = 10;
private ushort? _transactionID;
//virtual properties
public virtual bool IsConnected
{
get { return _isConnected; }
set { _isConnected = value; }
}
public virtual int ReceiveTimeout
{
get { return _receiveTimeout; }
set { _receiveTimeout = value; }
}
public virtual int SendTimeout
{
get { return _sendTimeout; }
set { _sendTimeout = value; }
}
public virtual int RetryTime
{
get { return _retryTime; }
set { _retryTime = value; }
}
public virtual ushort? TransactionID
{
get { return _transactionID; }
set
{
_transactionID = value;
this.ModbusRequest.TransactionID = value;
}
}
protected virtual bool Disposed
{
get;
set;
}
//abstract properties
internal abstract AbsModbusRequest ModbusRequest { get; set; }
internal abstract AbsModbusResponse ModbusResponse { get; set; }
internal abstract AbsModbusDataConvert ModbusDataConvert { get; set; }
//virtual method
public virtual byte[] SendAndReceive(byte[] RequestArray)
{
if (RequestArray == null)
{
throw new ArgumentNullException("RequestArray");
}
Send(RequestArray);
var resultArray = Receive();
return resultArray;
}
//abstract method
public abstract bool Disconnect();
public abstract byte[] Receive();
public abstract bool Send(byte[] RequestArray);
public abstract bool Connect<T>(T ConnectConfig);
public abstract void Dispose();
public virtual byte[] ReadCoils(byte Unit, ushort StartAddress, ushort Quantity)
{
var requestArray = this.ModbusRequest.ReadCoils(Unit, StartAddress, Quantity);
var responseArray = SendAndReceive(requestArray);
var result = this.ModbusResponse.ReadCoils(requestArray, responseArray);
return result;
}
public virtual IEnumerable<long> ReadCoilsToDecimal(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadCoils(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToDecimal(resultArray, EnumModbusIntegralUnit.Byte);
}
public virtual IEnumerable<string> ReadCoilsToBinary(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadCoils(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToBinary(resultArray, EnumModbusIntegralUnit.Byte);
}
public virtual IEnumerable<long> ReadCoilsToOctal(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadCoils(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToOctal(resultArray, EnumModbusIntegralUnit.Byte);
}
public virtual byte[] ReadDiscreteInputs(byte Unit, ushort StartAddress, ushort Quantity)
{
var requestArray = this.ModbusRequest.ReadDiscreteInputs(Unit, StartAddress, Quantity);
var responseArray = SendAndReceive(requestArray);
var result = this.ModbusResponse.ReadCoils(requestArray, responseArray);
return result;
}
public virtual IEnumerable<long> ReadDiscreteInputsToDecimal(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadDiscreteInputs(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToDecimal(resultArray, EnumModbusIntegralUnit.Byte);
}
public virtual IEnumerable<string> ReadDiscreteInputsToBinary(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadDiscreteInputs(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToBinary(resultArray, EnumModbusIntegralUnit.Byte);
}
public virtual IEnumerable<long> ReadDiscreteInputsToOctal(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadDiscreteInputs(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToOctal(resultArray, EnumModbusIntegralUnit.Byte);
}
public virtual byte[] ReadHoldingRegisters(byte Unit, ushort StartAddress, ushort Quantity)
{
var requestArray = this.ModbusRequest.ReadHoldingRegisters(Unit, StartAddress, Quantity);
var responseArray = SendAndReceive(requestArray);
var result = this.ModbusResponse.ReadHoldingRegisters(requestArray, responseArray);
return result;
}
public virtual IEnumerable<long> ReadHoldingRegistersToDecimal(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadHoldingRegisters(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToDecimal(resultArray, EnumModbusIntegralUnit.Word);
}
public virtual IEnumerable<string> ReadHoldingRegistersToBinary(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadHoldingRegisters(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToBinary(resultArray, EnumModbusIntegralUnit.Word);
}
public virtual IEnumerable<long> ReadHoldingRegistersToOctal(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadHoldingRegisters(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToOctal(resultArray, EnumModbusIntegralUnit.Word);
}
public virtual IEnumerable<float> ReadHoldingRegistersToFloat(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadHoldingRegisters(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToFloat(resultArray);
}
public virtual IEnumerable<double> ReadHoldingRegistersToDouble(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadHoldingRegisters(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToDouble(resultArray);
}
public virtual byte[] ReadInputRegisters(byte Unit, ushort StartAddress, ushort Quantity)
{
var requestArray = this.ModbusRequest.ReadInputRegisters(Unit, StartAddress, Quantity);
var responseArray = SendAndReceive(requestArray);
var result = this.ModbusResponse.ReadInputRegisters(requestArray, responseArray);
return result;
}
public virtual IEnumerable<long> ReadInputRegistersToDecimal(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadInputRegisters(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToDecimal(resultArray, EnumModbusIntegralUnit.Word);
}
public virtual IEnumerable<string> ReadInputRegistersToBinary(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadInputRegisters(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToBinary(resultArray, EnumModbusIntegralUnit.Word);
}
public virtual IEnumerable<long> ReadInputRegistersToOctal(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadInputRegisters(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToOctal(resultArray, EnumModbusIntegralUnit.Word);
}
public virtual IEnumerable<float> ReadInputRegistersToFloat(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadInputRegisters(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToFloat(resultArray);
}
public virtual IEnumerable<double> ReadInputRegistersTDouble(byte Unit, ushort StartAddress, ushort Quantity)
{
var resultArray = this.ReadInputRegisters(Unit, StartAddress, Quantity);
return this.ModbusDataConvert.ToDouble(resultArray);
}
public virtual bool WriteSingleCoil(byte Unit, ushort OutputAddress, bool OutputValue)
{
var requestArray = this.ModbusRequest.WriteSingleCoil(Unit, OutputAddress, OutputValue);
var responseArray = SendAndReceive(requestArray);
var result = this.ModbusResponse.WriteSingleCoil(requestArray, responseArray);
return result != null;
}
public virtual bool WriteSingleRegister(byte Unit, ushort OutputAddress, short OutputValue)
{
var requestArray = this.ModbusRequest.WriteSingleRegister(Unit, OutputAddress, OutputValue);
var responseArray = SendAndReceive(requestArray);
var result = this.ModbusResponse.WriteSingleRegister(requestArray, responseArray);
return result != null;
}
public virtual bool WriteMultipleCoils(byte Unit, ushort StartAddress, ushort Quantity, byte[] OutputValues)
{
var requestArray = this.ModbusRequest.WriteMultipleCoils(Unit, StartAddress, Quantity, OutputValues);
var responseArray = SendAndReceive(requestArray);
var result = this.ModbusResponse.WriteMultipleCoils(requestArray, responseArray);
return result != null;
}
public virtual bool WriteMultipleRegisters(byte Unit, ushort StartAddress, ushort Quantity, short[] OutputValues)
{
var requestArray = this.ModbusRequest.WriteMultipleRegisters(Unit, StartAddress, Quantity, OutputValues);
var responseArray = SendAndReceive(requestArray);
var result = this.ModbusResponse.WriteMultipleRegisters(requestArray, responseArray);
return result != null;
}
}
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET