[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