[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