[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (13) 實作 AsciiModbusResponse
續上篇,接下來,來實作做後一個 Response 模型 Modbus ASCII Response,這個 AsciiModbusResponse 類別主要是驗証資料的正確性
如下圖紅框:
AsciiModbusResponse 實作 AbsModbusResponse 抽像類別:
- 指派 FunctionCodePosition ,它表示 Function Code 的位置
- CheckDataValidate 方法裡驗證了Response 的 LRC 是否符合期望結果。
- 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; } } }
建立單元測試,同樣的我們需要驗証資料處理的正確性,在這裡我用到了驅動測試
下圖為測試檔案。
驅動測試詳細作法請參考:[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