[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (03) 建立 Modbus Client 架構

[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進制…等等

 

下圖為原型架構:

image

 


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 抽像類別:

  1. QuantityValidate 方法驗証 Quantity 是否合法。
  2. 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 抽像類別

  1. ExceptionValidate 方法驗証 Response 資料是否正確
  2. 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

Image result for microsoft+mvp+logo