[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (13) 實作 AsciiModbusResponse

[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (13) 實作 AsciiModbusResponse

續上篇,接下來,來實作做後一個 Response 模型 Modbus ASCII Response,這個 AsciiModbusResponse 類別主要是驗証資料的正確性

如下圖紅框:

image

 

AsciiModbusResponse  實作 AbsModbusResponse 抽像類別:

  1. 指派 FunctionCodePosition ,它表示 Function Code 的位置
  2. CheckDataValidate 方法裡驗證了Response 的 LRC 是否符合期望結果。
  3. GetResultArray 方法則是取出結果。

 

程式碼如下:

internal class AsciiModbusResponse : AbsModbusResponse
{
    private byte _functionCodePosition = 3;

    protected override byte FunctionCodePosition
    {
        get { return _functionCodePosition; }
        set { _functionCodePosition = value; }
    }

    protected override void ExceptionValidate(byte[] ResponseArray)
    {
        if (ResponseArray == null | ResponseArray.Length <= this.FunctionCodePosition)
        {
            throw new ModbusException("No Response or Data Fail");
        }

        var functionCodeArray = new byte[2];
        Array.Copy(ResponseArray, this.FunctionCodePosition, functionCodeArray, 0, functionCodeArray.Length);
        var functionCode = Utility.HexStringToBytes(Encoding.ASCII.GetString(functionCodeArray))[0];

        EnumModbusFunctionCode resultCode;
        if (!EnumModbusFunctionCode.TryParse(functionCode.ToString(), out resultCode))
        {
            throw ModbusException.GetModbusException(0x01);
        }

        if (functionCode >= 80)
        {
            var errorCodeArray = new byte[2];
            Array.Copy(ResponseArray, this.FunctionCodePosition + 2, errorCodeArray, 0, errorCodeArray.Length);

            var exceptionCode = byte.Parse(Encoding.ASCII.GetString(errorCodeArray));
            throw ModbusException.GetModbusException(exceptionCode);
        }
    }

    protected override void FunctionCodeValidate(byte[] RequestArray, byte[] ResponseArray, EnumModbusFunctionCode FunctionCodeFlag)
    {
        var resFunctionCodeArray = new byte[2];
        Array.Copy(ResponseArray, FunctionCodePosition, resFunctionCodeArray, 0, resFunctionCodeArray.Length);
        byte resFunctionCode = Utility.HexStringToBytes(Encoding.ASCII.GetString(resFunctionCodeArray))[0];

        EnumModbusFunctionCode resFunctionCodeMode;
        if (!EnumModbusFunctionCode.TryParse(resFunctionCode.ToString(), out resFunctionCodeMode))
        {
            throw ModbusException.GetModbusException(0x01);
        }

        var reqFunctionCodeArray = new byte[2];
        Array.Copy(RequestArray, FunctionCodePosition, reqFunctionCodeArray, 0, reqFunctionCodeArray.Length);
        byte reqFunctionCode = Utility.HexStringToBytes(Encoding.ASCII.GetString(reqFunctionCodeArray))[0];

        EnumModbusFunctionCode reqFunctionCodeMode;
        if (!EnumModbusFunctionCode.TryParse(reqFunctionCode.ToString(), out reqFunctionCodeMode))
        {
            throw ModbusException.GetModbusException(0x01);
        }

        if ((byte)FunctionCodeFlag != resFunctionCode)
        {
            throw ModbusException.GetModbusException(0x01);
        }
        if ((byte)FunctionCodeFlag != reqFunctionCode)
        {
            throw ModbusException.GetModbusException(0x01);
        }
    }

    protected override void CheckDataValidate(byte[] ResponseArray)
    {
        //start symbol
        var startSymbolArrayFlag = Encoding.ASCII.GetBytes(ModbusUtility.ASCII_START_SYMBOL);
        if (ResponseArray[0] != startSymbolArrayFlag[0])
        {
            throw new ModbusException("Start Symbol Format Fail");
        }

        //end symbol
        var endSymbolArrayFlag = Encoding.ASCII.GetBytes(ModbusUtility.ASCII_END_SYMBOL);
        var endSymbolArray = new byte[2];
        Array.Copy(ResponseArray, ResponseArray.Length - 2, endSymbolArray, 0, endSymbolArray.Length);
        if (!endSymbolArrayFlag.SequenceEqual(endSymbolArray))
        {
            throw new ModbusException("End Symbol Format Fail");
        }

        // lrc validate
        var sourceLrcArray = new byte[2];
        Array.Copy(ResponseArray, ResponseArray.Length - 4, sourceLrcArray, 0, sourceLrcArray.Length);

        byte[] sourceDataArray = null;
        using (MemoryStream memory = new MemoryStream())
        {
            for (int i = 1; i < ResponseArray.Length - 4; i++)
            {
                memory.WriteByte(ResponseArray[i]);
            }
            sourceDataArray = memory.ToArray();
        }
        var sourceDataHex = Encoding.ASCII.GetString(sourceDataArray);
        var hexArray = Utility.HexStringToBytes(sourceDataHex);
        var destinationLrcHex = Utility.CalculateLRC(hexArray);
        var destinationLrcArray = Encoding.ASCII.GetBytes(destinationLrcHex);
        if (!sourceLrcArray.SequenceEqual(destinationLrcArray))
        {
            throw new ModbusException("LRC Validate Fail");
        }
    }

    protected override byte[] GetResultArray(byte[] ResponseArray)
    {
        using (MemoryStream memory = new MemoryStream())
        {
            memory.Write(ResponseArray, 7, ResponseArray.Length - 7 - 4);
            var resultArray = memory.ToArray();
            return resultArray;
        }
    }
}

 


 

建立單元測試,同樣的我們需要驗証資料處理的正確性,在這裡我用到了驅動測試

下圖為測試檔案。

image

驅動測試詳細作法請參考:[Visual Studio 2012 ] 建立資料驅動單元測試

 

測試程式碼如下:

[TestMethod()]
[ExpectedException(typeof(ModbusException))]
public void ReadCoils_Exception_Test()
{
    AsciiModbusResponse target = new AsciiModbusResponse();
    byte[] RequestArray = _modbusUtility.HexStringToBytes("3A 30 31 30 31 30 30 30 30 30 30 43 38 33 36 0D 0A");
    byte[] ResponseArray = _modbusUtility.HexStringToBytes("3A 30 31 38 31 30 32 37 43 0D 0A");
    byte[] expected = ResponseArray;
    byte[] actual;
    actual = target.ReadCoils(RequestArray, ResponseArray);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
[DeploymentItem("TestDoc\\AsciiReadFunc1Test.csv")]
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", "|DataDirectory|\\TestDoc\\AsciiReadFunc1Test.csv", "AsciiReadFunc1Test#csv", DataAccessMethod.Sequential)]
public void ReadCoilsTest()
{
    AsciiModbusResponse target = new AsciiModbusResponse();

    byte[] RequestArray = _modbusUtility.HexStringToBytes(TestContext.DataRow[0].ToString());
    byte[] ResponseArray = _modbusUtility.HexStringToBytes(TestContext.DataRow[1].ToString());
    byte[] expected = _modbusUtility.HexStringToBytes(TestContext.DataRow[2].ToString());

    byte[] actual;
    actual = target.ReadCoils(RequestArray, ResponseArray);

    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
[DeploymentItem("TestDoc\\AsciiReadFunc2Test.csv")]
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", "|DataDirectory|\\TestDoc\\AsciiReadFunc2Test.csv", "AsciiReadFunc2Test#csv", DataAccessMethod.Sequential)]
public void ReadDiscreteInputsTest()
{
    AsciiModbusResponse target = new AsciiModbusResponse();

    byte[] RequestArray = _modbusUtility.HexStringToBytes(TestContext.DataRow[0].ToString());
    byte[] ResponseArray = _modbusUtility.HexStringToBytes(TestContext.DataRow[1].ToString());
    byte[] expected = _modbusUtility.HexStringToBytes(TestContext.DataRow[2].ToString());
    byte[] actual;
    actual = target.ReadDiscreteInputs(RequestArray, ResponseArray);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

/// <summary>
///A test for ReadHoldingRegisters
///</summary>
[TestMethod()]
[DeploymentItem("TestDoc\\AsciiReadFunc3Test.csv")]
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", "|DataDirectory|\\TestDoc\\AsciiReadFunc3Test.csv", "AsciiReadFunc3Test#csv", DataAccessMethod.Sequential)]
public void ReadHoldingRegistersTest()
{
    AsciiModbusResponse target = new AsciiModbusResponse();

    byte[] RequestArray = _modbusUtility.HexStringToBytes(TestContext.DataRow[0].ToString());
    byte[] ResponseArray = _modbusUtility.HexStringToBytes(TestContext.DataRow[1].ToString());
    byte[] expected = _modbusUtility.HexStringToBytes(TestContext.DataRow[2].ToString());
    byte[] actual;
    actual = target.ReadHoldingRegisters(RequestArray, ResponseArray);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
[DeploymentItem("TestDoc\\AsciiReadFunc4Test.csv")]
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", "|DataDirectory|\\TestDoc\\AsciiReadFunc4Test.csv", "AsciiReadFunc4Test#csv", DataAccessMethod.Sequential)]
public void ReadInputRegistersTest()
{
    AsciiModbusResponse target = new AsciiModbusResponse();

    byte[] RequestArray = _modbusUtility.HexStringToBytes(TestContext.DataRow[0].ToString());
    byte[] ResponseArray = _modbusUtility.HexStringToBytes(TestContext.DataRow[1].ToString());
    byte[] expected = _modbusUtility.HexStringToBytes(TestContext.DataRow[2].ToString());
    byte[] actual;
    actual = target.ReadInputRegisters(RequestArray, ResponseArray);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
public void WriteSingleCoilTest()
{
    AsciiModbusResponse target = new AsciiModbusResponse();
    byte[] RequestArray = _modbusUtility.HexStringToBytes("3A 30 31 30 35 30 30 30 31 46 46 30 30 46 41 0D 0A");
    byte[] ResponseArray = _modbusUtility.HexStringToBytes("3A 30 31 30 35 30 30 30 31 46 46 30 30 46 41 0D 0A");
    byte[] expected = ResponseArray;
    byte[] actual;
    actual = target.WriteSingleCoil(RequestArray, ResponseArray);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
public void WriteSingleRegisterTest()
{
    AsciiModbusResponse target = new AsciiModbusResponse();
    byte[] RequestArray = _modbusUtility.HexStringToBytes("3A 30 31 30 36 30 30 30 35 30 32 33 33 42 46 0D 0A");
    byte[] ResponseArray = _modbusUtility.HexStringToBytes("3A 30 31 30 36 30 30 30 35 30 32 33 33 42 46 0D 0A");
    byte[] expected = ResponseArray;
    byte[] actual;
    actual = target.WriteSingleRegister(RequestArray, ResponseArray);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
public void WriteMultipleCoilsTest()
{
    AsciiModbusResponse target = new AsciiModbusResponse();
    byte[] RequestArray = _modbusUtility.HexStringToBytes("3A 30 31 30 46 30 30 30 30 30 30 30 41 30 32 32 37 30 30 42 44 0D 0A");
    byte[] ResponseArray = _modbusUtility.HexStringToBytes("3A 30 31 30 46 30 30 30 30 30 30 30 41 45 36 0D 0A");
    byte[] expected = ResponseArray;
    byte[] actual;
    actual = target.WriteMultipleCoils(RequestArray, ResponseArray);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
public void WriteMultipleRegistersTest()
{
    AsciiModbusResponse target = new AsciiModbusResponse();
    byte[] RequestArray = _modbusUtility.HexStringToBytes("3A 30 31 31 30 30 30 30 30 30 30 30 41 31 34 30 30 30 30 30 30 30 43 30 30 30 30 46 46 42 45 30 30 37 33 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 39 35 0D 0A");
    byte[] ResponseArray = _modbusUtility.HexStringToBytes("3A 30 31 31 30 30 30 30 30 30 30 30 41 45 35 0D 0A");
    byte[] expected = ResponseArray;
    byte[] actual;
    actual = target.WriteMultipleRegisters(RequestArray, ResponseArray);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo