Files
AMWD.Protocols.Modbus/test/AMWD.Protocols.Modbus.Tests/Common/Protocols/RtuOverTcpProtocolTest.cs
Andreas Müller 5f3f934b89
Some checks failed
Branch Build / build-test-deploy (push) Has been cancelled
Updated to .NET 10
2026-01-12 18:45:33 +01:00

1181 lines
32 KiB
C#

using System.Reflection;
using AMWD.Protocols.Modbus.Common.Protocols;
namespace AMWD.Protocols.Modbus.Tests.Common.Protocols
{
[TestClass]
public class RtuOverTcpProtocolTest
{
private const byte UNIT_ID = 0x2A; // 42
#region Read Coils
[TestMethod]
public void ShouldSerializeReadCoils()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act
var bytes = protocol.SerializeReadCoils(UNIT_ID, 19, 19);
// Assert
Assert.IsNotNull(bytes);
Assert.HasCount(14, bytes);
// Transaction id
Assert.AreEqual(0x00, bytes[0]);
Assert.AreEqual(0x01, bytes[1]);
// Protocol identifier
Assert.AreEqual(0x00, bytes[2]);
Assert.AreEqual(0x00, bytes[3]);
// Following bytes
Assert.AreEqual(0x00, bytes[4]);
Assert.AreEqual(0x08, bytes[5]);
// Unit id
Assert.AreEqual(UNIT_ID, bytes[6]);
// Function code
Assert.AreEqual(0x01, bytes[7]);
// Starting address
Assert.AreEqual(0x00, bytes[8]);
Assert.AreEqual(0x13, bytes[9]);
// Quantity
Assert.AreEqual(0x00, bytes[10]);
Assert.AreEqual(0x13, bytes[11]);
// CRC check will be ignored
}
[TestMethod]
[DataRow(0)]
[DataRow(2001)]
public void ShouldThrowOutOfRangeForCountOnSerializeReadCoils(int count)
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => protocol.SerializeReadCoils(UNIT_ID, 19, (ushort)count));
}
[TestMethod]
public void ShouldThrowOutOfRangeForStartingAddressOnSerializeReadCoils()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => protocol.SerializeReadCoils(UNIT_ID, ushort.MaxValue, 2));
}
[TestMethod]
public void ShouldDeserializeReadCoils()
{
// Arrange
int[] setValues = [0, 2, 3, 6, 7, 8, 9, 11, 13, 14, 16, 18];
var protocol = new RtuOverTcpProtocol();
// Act
var coils = protocol.DeserializeReadCoils([0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x01, 0x03, 0xCD, 0x6B, 0x05, 0x00, 0x00]);
// Assert
Assert.IsNotNull(coils);
Assert.HasCount(24, coils);
for (int i = 0; i < 24; i++)
{
Assert.AreEqual(i, coils[i].Address);
if (setValues.Contains(i))
Assert.IsTrue(coils[i].Value);
else
Assert.IsFalse(coils[i].Value);
}
}
[TestMethod]
public void ShouldThrowExceptionOnDeserializeReadCoils()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.DeserializeReadCoils([0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x01, 0x02, 0xCD, 0x6B, 0x05, 0x00, 0x00]));
}
#endregion Read Coils
#region Read Discrete Inputs
[TestMethod]
public void ShouldSerializeReadDiscreteInputs()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act
var bytes = protocol.SerializeReadDiscreteInputs(UNIT_ID, 19, 19);
// Assert
Assert.IsNotNull(bytes);
Assert.HasCount(14, bytes);
// Transaction id
Assert.AreEqual(0x00, bytes[0]);
Assert.AreEqual(0x01, bytes[1]);
// Protocol identifier
Assert.AreEqual(0x00, bytes[2]);
Assert.AreEqual(0x00, bytes[3]);
// Following bytes
Assert.AreEqual(0x00, bytes[4]);
Assert.AreEqual(0x08, bytes[5]);
// Unit id
Assert.AreEqual(UNIT_ID, bytes[6]);
// Function code
Assert.AreEqual(0x02, bytes[7]);
// Starting address
Assert.AreEqual(0x00, bytes[8]);
Assert.AreEqual(0x13, bytes[9]);
// Quantity
Assert.AreEqual(0x00, bytes[10]);
Assert.AreEqual(0x13, bytes[11]);
// CRC check will be ignored
}
[TestMethod]
[DataRow(0)]
[DataRow(2001)]
public void ShouldThrowOutOfRangeForCountOnSerializeReadDiscreteInputs(int count)
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => protocol.SerializeReadDiscreteInputs(UNIT_ID, 19, (ushort)count));
}
[TestMethod]
public void ShouldThrowOutOfRangeForStartingAddressOnSerializeReadDiscreteInputs()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => protocol.SerializeReadDiscreteInputs(UNIT_ID, ushort.MaxValue, 2));
}
[TestMethod]
public void ShouldDeserializeReadDiscreteInputs()
{
// Arrange
int[] setValues = [0, 2, 3, 6, 7, 8, 9, 11, 13, 14, 16, 18];
var protocol = new RtuOverTcpProtocol();
// Act
var discreteInputs = protocol.DeserializeReadDiscreteInputs([0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x02, 0x03, 0xCD, 0x6B, 0x05, 0x00, 0x00]);
// Assert
Assert.IsNotNull(discreteInputs);
Assert.HasCount(24, discreteInputs);
for (int i = 0; i < 24; i++)
{
Assert.AreEqual(i, discreteInputs[i].Address);
if (setValues.Contains(i))
Assert.IsTrue(discreteInputs[i].Value);
else
Assert.IsFalse(discreteInputs[i].Value);
}
}
[TestMethod]
public void ShouldThrowExceptionOnDeserializeReadDiscreteInputs()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.DeserializeReadDiscreteInputs([0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x02, 0x02, 0xCD, 0x6B, 0x05, 0x00, 0x00]));
}
#endregion Read Discrete Inputs
#region Read Holding Registers
[TestMethod]
public void ShouldSerializeReadHoldingRegisters()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act
var bytes = protocol.SerializeReadHoldingRegisters(UNIT_ID, 107, 2);
// Assert
Assert.IsNotNull(bytes);
Assert.HasCount(14, bytes);
// Transaction id
Assert.AreEqual(0x00, bytes[0]);
Assert.AreEqual(0x01, bytes[1]);
// Protocol identifier
Assert.AreEqual(0x00, bytes[2]);
Assert.AreEqual(0x00, bytes[3]);
// Following bytes
Assert.AreEqual(0x00, bytes[4]);
Assert.AreEqual(0x08, bytes[5]);
// Unit id
Assert.AreEqual(UNIT_ID, bytes[6]);
// Function code
Assert.AreEqual(0x03, bytes[7]);
// Starting address
Assert.AreEqual(0x00, bytes[8]);
Assert.AreEqual(0x6B, bytes[9]);
// Quantity
Assert.AreEqual(0x00, bytes[10]);
Assert.AreEqual(0x02, bytes[11]);
// CRC check will be ignored
}
[TestMethod]
[DataRow(0)]
[DataRow(126)]
public void ShouldThrowOutOfRangeForCountOnSerializeReadHoldingRegisters(int count)
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => protocol.SerializeReadHoldingRegisters(UNIT_ID, 19, (ushort)count));
}
[TestMethod]
public void ShouldThrowOutOfRangeForStartingAddressOnSerializeReadHoldingRegisters()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => protocol.SerializeReadHoldingRegisters(UNIT_ID, ushort.MaxValue, 2));
}
[TestMethod]
public void ShouldDeserializeReadHoldingRegisters()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act
var registers = protocol.DeserializeReadHoldingRegisters([0x00, 0x01, 0x00, 0x00, 0x00, 0x09, UNIT_ID, 0x03, 0x04, 0x02, 0x2B, 0x00, 0x64, 0x00, 0x00]);
// Assert
Assert.IsNotNull(registers);
Assert.HasCount(2, registers);
Assert.AreEqual(0, registers[0].Address);
Assert.AreEqual(555, registers[0].Value);
Assert.AreEqual(1, registers[1].Address);
Assert.AreEqual(100, registers[1].Value);
}
[TestMethod]
public void ShouldThrowExceptionOnDeserializeReadHoldingRegisters()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.DeserializeReadHoldingRegisters([0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x03, 0x04, 0x02, 0x2B, 0x00, 0x00]));
}
#endregion Read Holding Registers
#region Read Input Registers
[TestMethod]
public void ShouldSerializeReadInputRegisters()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act
var bytes = protocol.SerializeReadInputRegisters(UNIT_ID, 107, 2);
// Assert
Assert.IsNotNull(bytes);
Assert.HasCount(14, bytes);
// Transaction id
Assert.AreEqual(0x00, bytes[0]);
Assert.AreEqual(0x01, bytes[1]);
// Protocol identifier
Assert.AreEqual(0x00, bytes[2]);
Assert.AreEqual(0x00, bytes[3]);
// Following bytes
Assert.AreEqual(0x00, bytes[4]);
Assert.AreEqual(0x08, bytes[5]);
// Unit id
Assert.AreEqual(UNIT_ID, bytes[6]);
// Function code
Assert.AreEqual(0x04, bytes[7]);
// Starting address
Assert.AreEqual(0x00, bytes[8]);
Assert.AreEqual(0x6B, bytes[9]);
// Quantity
Assert.AreEqual(0x00, bytes[10]);
Assert.AreEqual(0x02, bytes[11]);
// CRC check will be ignored
}
[TestMethod]
[DataRow(0)]
[DataRow(126)]
public void ShouldThrowOutOfRangeForCountOnSerializeReadInputRegisters(int count)
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => protocol.SerializeReadInputRegisters(UNIT_ID, 19, (ushort)count));
}
[TestMethod]
public void ShouldThrowOutOfRangeForStartingAddressOnSerializeReadInputRegisters()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => protocol.SerializeReadInputRegisters(UNIT_ID, ushort.MaxValue, 2));
}
[TestMethod]
public void ShouldDeserializeReadInputRegisters()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act
var registers = protocol.DeserializeReadInputRegisters([0x00, 0x01, 0x00, 0x00, 0x00, 0x09, UNIT_ID, 0x04, 0x04, 0x02, 0x2B, 0x00, 0x64, 0x00, 0x00]);
// Assert
Assert.IsNotNull(registers);
Assert.HasCount(2, registers);
Assert.AreEqual(0, registers[0].Address);
Assert.AreEqual(555, registers[0].Value);
Assert.AreEqual(1, registers[1].Address);
Assert.AreEqual(100, registers[1].Value);
}
[TestMethod]
public void ShouldThrowExceptionOnDeserializeReadInputRegisters()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.DeserializeReadInputRegisters([0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x04, 0x04, 0x02, 0x2B, 0x00, 0x00]));
}
#endregion Read Input Registers
#region Read Device Identification
[TestMethod]
[DataRow(ModbusDeviceIdentificationCategory.Basic)]
[DataRow(ModbusDeviceIdentificationCategory.Regular)]
[DataRow(ModbusDeviceIdentificationCategory.Extended)]
[DataRow(ModbusDeviceIdentificationCategory.Individual)]
public void ShouldSerializeReadDeviceIdentification(ModbusDeviceIdentificationCategory category)
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act
var bytes = protocol.SerializeReadDeviceIdentification(UNIT_ID, category, ModbusDeviceIdentificationObject.ProductCode);
// Assert
Assert.IsNotNull(bytes);
Assert.HasCount(13, bytes);
// Transaction id
Assert.AreEqual(0x00, bytes[0]);
Assert.AreEqual(0x01, bytes[1]);
// Protocol identifier
Assert.AreEqual(0x00, bytes[2]);
Assert.AreEqual(0x00, bytes[3]);
// Following bytes
Assert.AreEqual(0x00, bytes[4]);
Assert.AreEqual(0x07, bytes[5]);
// Unit id
Assert.AreEqual(UNIT_ID, bytes[6]);
// Function code
Assert.AreEqual(0x2B, bytes[7]);
// MEI Type
Assert.AreEqual(0x0E, bytes[8]);
// Category
Assert.AreEqual((byte)category, bytes[9]);
// Object Id
Assert.AreEqual((byte)ModbusDeviceIdentificationObject.ProductCode, bytes[10]);
// CRC check will be ignored
}
[TestMethod]
public void ShouldThrowOutOfRangeExceptionForCategoryOnSerializeReadDeviceIdentification()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => protocol.SerializeReadDeviceIdentification(UNIT_ID, (ModbusDeviceIdentificationCategory)10, ModbusDeviceIdentificationObject.ProductCode));
}
[TestMethod]
[DataRow(false)]
[DataRow(true)]
public void ShouldDeserializeReadDeviceIdentification(bool moreAndIndividual)
{
// Arrange
byte[] response = [0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, UNIT_ID, 0x2B, 0x0E, 0x02, (byte)(moreAndIndividual ? 0x82 : 0x02), (byte)(moreAndIndividual ? 0xFF : 0x00), (byte)(moreAndIndividual ? 0x05 : 0x00), 0x01, 0x04, 0x02, 0x41, 0x4D, 0x00, 0x00];
var protocol = new RtuOverTcpProtocol();
// Act
var result = protocol.DeserializeReadDeviceIdentification(response);
// Assert
Assert.IsNotNull(result);
Assert.AreEqual(moreAndIndividual, result.AllowsIndividualAccess);
Assert.AreEqual(moreAndIndividual, result.MoreRequestsNeeded);
Assert.AreEqual(moreAndIndividual ? 0x05 : 0x00, result.NextObjectIdToRequest);
Assert.HasCount(1, result.Objects);
Assert.AreEqual(4, result.Objects.First().Key);
CollectionAssert.AreEqual("AM"u8.ToArray(), result.Objects.First().Value);
}
[TestMethod]
public void ShouldThrowExceptionOnDeserializeReadDeviceIdentificationForMeiType()
{
// Arrange
byte[] response = [0x00, 0x01, 0x00, 0x00, 0x00, 0x05, UNIT_ID, 0x2B, 0x0D, 0x00, 0x00];
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.DeserializeReadDeviceIdentification(response));
}
[TestMethod]
public void ShouldThrowExceptionOnDeserializeReadDeviceIdentificationForCategory()
{
// Arrange
byte[] response = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, UNIT_ID, 0x2B, 0x0E, 0x08, 0x00, 0x00];
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.DeserializeReadDeviceIdentification(response));
}
#endregion Read Device Identification
#region Write Single Coil
[TestMethod]
public void ShouldSerializeWriteSingleCoil()
{
// Arrange
var coil = new Coil { Address = 109, Value = true };
var protocol = new RtuOverTcpProtocol();
// Act
var result = protocol.SerializeWriteSingleCoil(UNIT_ID, coil);
// Assert
Assert.IsNotNull(result);
Assert.HasCount(14, result);
// Transaction id
Assert.AreEqual(0x00, result[0]);
Assert.AreEqual(0x01, result[1]);
// Protocol identifier
Assert.AreEqual(0x00, result[2]);
Assert.AreEqual(0x00, result[3]);
// Following bytes
Assert.AreEqual(0x00, result[4]);
Assert.AreEqual(0x08, result[5]);
// Unit id
Assert.AreEqual(UNIT_ID, result[6]);
// Function code
Assert.AreEqual(0x05, result[7]);
// Starting address
Assert.AreEqual(0x00, result[8]);
Assert.AreEqual(0x6D, result[9]);
// Value
Assert.AreEqual(0xFF, result[10]);
Assert.AreEqual(0x00, result[11]);
// CRC check will be ignored
}
[TestMethod]
public void ShouldThrowArgumentNullOnSerializeWriteSingleCoil()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentNullException>(() => protocol.SerializeWriteSingleCoil(UNIT_ID, null));
}
[TestMethod]
public void ShouldDeserializeWriteSingleCoil()
{
// Arrange
byte[] bytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x05, 0x01, 0x0A, 0xFF, 0x00, 0x00, 0x00];
var protocol = new RtuOverTcpProtocol();
// Act
var coil = protocol.DeserializeWriteSingleCoil(bytes);
// Assert
Assert.IsNotNull(coil);
Assert.AreEqual(266, coil.Address);
Assert.IsTrue(coil.Value);
}
#endregion Write Single Coil
#region Write Single Register
[TestMethod]
public void ShouldSerializeWriteSingleHoldingRegister()
{
// Arrange
var register = new HoldingRegister { Address = 109, Value = 123 };
var protocol = new RtuOverTcpProtocol();
// Act
var result = protocol.SerializeWriteSingleHoldingRegister(UNIT_ID, register);
// Assert
Assert.IsNotNull(result);
Assert.HasCount(14, result);
// Transaction id
Assert.AreEqual(0x00, result[0]);
Assert.AreEqual(0x01, result[1]);
// Protocol identifier
Assert.AreEqual(0x00, result[2]);
Assert.AreEqual(0x00, result[3]);
// Following bytes
Assert.AreEqual(0x00, result[4]);
Assert.AreEqual(0x08, result[5]);
// Unit id
Assert.AreEqual(UNIT_ID, result[6]);
// Function code
Assert.AreEqual(0x06, result[7]);
// Starting address
Assert.AreEqual(0x00, result[8]);
Assert.AreEqual(0x6D, result[9]);
// Value
Assert.AreEqual(0x00, result[10]);
Assert.AreEqual(0x7B, result[11]);
// CRC check will be ignored
}
[TestMethod]
public void ShouldThrowArgumentNullOnSerializeWriteSingleHoldingRegister()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentNullException>(() => protocol.SerializeWriteSingleHoldingRegister(UNIT_ID, null));
}
[TestMethod]
public void ShouldDeserializeWriteSingleHoldingRegister()
{
// Arrange
byte[] bytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x2A, 0x06, 0x02, 0x02, 0x01, 0x23, 0x00, 0x00];
var protocol = new RtuOverTcpProtocol();
// Act
var register = protocol.DeserializeWriteSingleHoldingRegister(bytes);
// Assert
Assert.IsNotNull(register);
Assert.AreEqual(514, register.Address);
Assert.AreEqual(291, register.Value);
}
#endregion Write Single Register
#region Write Multiple Coils
[TestMethod]
public void ShouldSerializeWriteMultipleCoils()
{
// Arrange
var coils = new Coil[]
{
new() { Address = 10, Value = true },
new() { Address = 11, Value = false },
new() { Address = 12, Value = true },
new() { Address = 13, Value = false },
new() { Address = 14, Value = true },
};
var protocol = new RtuOverTcpProtocol();
// Act
var result = protocol.SerializeWriteMultipleCoils(UNIT_ID, coils);
// Assert
Assert.IsNotNull(result);
Assert.HasCount(16, result);
// Transaction id
Assert.AreEqual(0x00, result[0]);
Assert.AreEqual(0x01, result[1]);
// Protocol identifier
Assert.AreEqual(0x00, result[2]);
Assert.AreEqual(0x00, result[3]);
// Following bytes
Assert.AreEqual(0x00, result[4]);
Assert.AreEqual(0x0A, result[5]);
// Unit id
Assert.AreEqual(UNIT_ID, result[6]);
// Function code
Assert.AreEqual(0x0F, result[7]);
// Starting address
Assert.AreEqual(0x00, result[8]);
Assert.AreEqual(0x0A, result[9]);
// Quantity
Assert.AreEqual(0x00, result[10]);
Assert.AreEqual(0x05, result[11]);
// Byte count
Assert.AreEqual(0x01, result[12]);
// Values
Assert.AreEqual(0x15, result[13]);
// CRC check will be ignored
}
[TestMethod]
public void ShouldThrowArgumentNullOnSerializeWriteMultipleCoils()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentNullException>(() => protocol.SerializeWriteMultipleCoils(UNIT_ID, null));
}
[TestMethod]
[DataRow(0)]
[DataRow(1969)]
public void ShouldThrowOutOfRangeForCountOnSerializeWriteMultipleCoils(int count)
{
// Arrange
var coils = new List<Coil>();
for (int i = 0; i < count; i++)
coils.Add(new() { Address = (ushort)i });
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => protocol.SerializeWriteMultipleCoils(UNIT_ID, coils));
}
[TestMethod]
public void ShouldThrowArgumentExceptionForDuplicateEntryOnSerializeMultipleCoils()
{
// Arrange
var coils = new Coil[]
{
new() { Address = 10, Value = true },
new() { Address = 10, Value = false },
};
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentException>(() => protocol.SerializeWriteMultipleCoils(UNIT_ID, coils));
}
[TestMethod]
public void ShouldThrowArgumentExceptionForGapInAddressOnSerializeMultipleCoils()
{
// Arrange
var coils = new Coil[]
{
new() { Address = 10, Value = true },
new() { Address = 12, Value = false },
};
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentException>(() => protocol.SerializeWriteMultipleCoils(UNIT_ID, coils));
}
[TestMethod]
public void ShouldDeserializeWriteMultipleCoils()
{
// Arrange
byte[] bytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x2A, 0x0F, 0x01, 0x0A, 0x00, 0x0B, 0x00, 0x00];
var protocol = new RtuOverTcpProtocol();
// Act
var (firstAddress, numberOfCoils) = protocol.DeserializeWriteMultipleCoils(bytes);
// Assert
Assert.AreEqual(266, firstAddress);
Assert.AreEqual(11, numberOfCoils);
}
#endregion Write Multiple Coils
#region Write Multiple Holding Registers
[TestMethod]
public void ShouldSerializeWriteMultipleHoldingRegisters()
{
// Arrange
var registers = new HoldingRegister[]
{
new() { Address = 10, Value = 10 },
new() { Address = 11, Value = 11 }
};
var protocol = new RtuOverTcpProtocol();
// Act
var result = protocol.SerializeWriteMultipleHoldingRegisters(UNIT_ID, registers);
// Assert
Assert.IsNotNull(result);
Assert.HasCount(19, result);
// Transaction id
Assert.AreEqual(0x00, result[0]);
Assert.AreEqual(0x01, result[1]);
// Protocol identifier
Assert.AreEqual(0x00, result[2]);
Assert.AreEqual(0x00, result[3]);
// Following bytes
Assert.AreEqual(0x00, result[4]);
Assert.AreEqual(0x0D, result[5]);
// Unit id
Assert.AreEqual(UNIT_ID, result[6]);
// Function code
Assert.AreEqual(0x10, result[7]);
// Starting address
Assert.AreEqual(0x00, result[8]);
Assert.AreEqual(0x0A, result[9]);
// Quantity
Assert.AreEqual(0x00, result[10]);
Assert.AreEqual(0x02, result[11]);
// Byte count
Assert.AreEqual(0x04, result[12]);
// Values
Assert.AreEqual(0x00, result[13]);
Assert.AreEqual(0x0A, result[14]);
Assert.AreEqual(0x00, result[15]);
Assert.AreEqual(0x0B, result[16]);
// CRC check will be ignored
}
[TestMethod]
public void ShouldThrowArgumentNullOnSerializeWriteMultipleHoldingRegisters()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentNullException>(() => protocol.SerializeWriteMultipleHoldingRegisters(UNIT_ID, null));
}
[TestMethod]
[DataRow(0)]
[DataRow(124)]
public void ShouldThrowOutOfRangeForCountOnSerializeWriteMultipleHoldingRegisters(int count)
{
// Arrange
var registers = new List<HoldingRegister>();
for (int i = 0; i < count; i++)
registers.Add(new() { Address = (ushort)i });
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => protocol.SerializeWriteMultipleHoldingRegisters(UNIT_ID, registers));
}
[TestMethod]
public void ShouldThrowArgumentExceptionForDuplicateEntryOnSerializeMultipleHoldingRegisters()
{
// Arrange
var registers = new HoldingRegister[]
{
new() { Address = 10 },
new() { Address = 10 },
};
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentException>(() => protocol.SerializeWriteMultipleHoldingRegisters(UNIT_ID, registers));
}
[TestMethod]
public void ShouldThrowArgumentExceptionForGapInAddressOnSerializeMultipleHoldingRegisters()
{
// Arrange
var registers = new HoldingRegister[]
{
new() { Address = 10 },
new() { Address = 12 },
};
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ArgumentException>(() => protocol.SerializeWriteMultipleHoldingRegisters(UNIT_ID, registers));
}
[TestMethod]
public void ShouldDeserializeWriteMultipleHoldingRegisters()
{
// Arrange
byte[] bytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x10, 0x02, 0x0A, 0x00, 0x0A, 0x00, 0x00];
var protocol = new RtuOverTcpProtocol();
// Act
var (firstAddress, numberOfCoils) = protocol.DeserializeWriteMultipleHoldingRegisters(bytes);
// Assert
Assert.AreEqual(522, firstAddress);
Assert.AreEqual(10, numberOfCoils);
}
#endregion Write Multiple Holding Registers
#region Validation
[TestMethod]
public void ShouldReturnFalseForMinLengthOnCheckResponseComplete()
{
// Arrange
byte[] bytes = [0x00, 0x01, 0x00];
var protocol = new RtuOverTcpProtocol();
// Act
bool complete = protocol.CheckResponseComplete(bytes);
// Assert
Assert.IsFalse(complete);
}
[TestMethod]
public void ShouldReturnFalseForFollowingBytesOnCheckResponseComplete()
{
// Arrange
byte[] bytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x81];
var protocol = new RtuOverTcpProtocol();
// Act
bool complete = protocol.CheckResponseComplete(bytes);
// Assert
Assert.IsFalse(complete);
}
[TestMethod]
public void ShouldReturnTrueOnCheckResponseComplete()
{
// Arrange
byte[] bytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x81, 0x01];
var protocol = new RtuOverTcpProtocol();
// Act
bool complete = protocol.CheckResponseComplete(bytes);
// Assert
Assert.IsTrue(complete);
}
[TestMethod]
public void ShouldValidateResponse()
{
// Arrange
byte[] request = [0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00];
byte[] response = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, UNIT_ID, 0x01, 0x01, 0x00, 0x00, 0x00];
SetCrc(response);
var protocol = new RtuOverTcpProtocol();
// Act
protocol.ValidateResponse(request, response);
}
[TestMethod]
public void ShouldValidateResponseIgnoringTransactionId()
{
// Arrange
byte[] request = [0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00];
byte[] response = [0x00, 0x00, 0x00, 0x00, 0x00, 0x06, UNIT_ID, 0x01, 0x01, 0x00, 0x00, 0x00];
SetCrc(response);
var protocol = new RtuOverTcpProtocol { DisableTransactionId = true };
// Act
protocol.ValidateResponse(request, response);
}
[TestMethod]
[DataRow(0x00, 0x00)]
[DataRow(0x01, 0x01)]
public void ShouldThrowForTransactionIdOnValidateResponse(int hi, int lo)
{
// Arrange
byte[] request = [(byte)hi, (byte)lo, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00];
byte[] response = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, UNIT_ID, 0x01, 0x01, 0x00, 0x00, 0x00];
SetCrc(response);
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.ValidateResponse(request, response));
}
[TestMethod]
[DataRow(0x00, 0x01)]
[DataRow(0x01, 0x00)]
public void ShouldThrowForProtocolIdOnValidateResponse(int hi, int lo)
{
// Arrange
byte[] request = [0x00, 0x01, (byte)hi, (byte)lo, 0x00, 0x08, UNIT_ID, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00];
byte[] response = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, UNIT_ID, 0x01, 0x01, 0x00, 0x00, 0x00];
SetCrc(response);
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.ValidateResponse(request, response));
}
[TestMethod]
public void ShouldThrowForFollowingBytesOnValidateResponse()
{
// Arrange
byte[] request = [0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00];
byte[] response = [0x00, 0x01, 0x00, 0x00, 0x00, 0x07, UNIT_ID, 0x01, 0x01, 0x00, 0x00, 0x00];
SetCrc(response);
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.ValidateResponse(request, response));
}
[TestMethod]
public void ShouldThrowForUnitIdOnValidateResponse()
{
// Arrange
byte[] request = [0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00];
byte[] response = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, UNIT_ID + 1, 0x01, 0x01, 0x00, 0x00, 0x00];
SetCrc(response);
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.ValidateResponse(request, response));
}
[TestMethod]
public void ShouldThrowForFunctionCodeOnValidateResponse()
{
// Arrange
byte[] request = [0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00];
byte[] response = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, UNIT_ID, 0x02, 0x01, 0x00, 0x00, 0x00];
SetCrc(response);
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.ValidateResponse(request, response));
}
[TestMethod]
public void ShouldThrowForModbusErrorOnValidateResponse()
{
// Arrange
byte[] request = [0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00];
byte[] response = [0x00, 0x01, 0x00, 0x00, 0x00, 0x05, UNIT_ID, 0x81, 0x01, 0x00, 0x00];
SetCrc(response);
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.ValidateResponse(request, response));
}
[TestMethod]
[DataRow(0x59, 0x6C)]
[DataRow(0x58, 0x6B)]
public void ShouldThrowForCrcOnValidateResponse(int hi, int lo)
{
// Arrange
byte[] request = [0x00, 0x01, 0x00, 0x00, 0x00, 0x08, UNIT_ID, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00];
byte[] response = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, UNIT_ID, 0x01, 0x01, 0x00, (byte)hi, (byte)lo];
var protocol = new RtuOverTcpProtocol();
// Act + Assert
Assert.ThrowsExactly<ModbusException>(() => protocol.ValidateResponse(request, response));
}
#endregion Validation
#region Helper
[TestMethod]
public void ShouldIncreaseTransactionId()
{
// Arrange
var list = new List<byte[]>();
var protocol = new RtuOverTcpProtocol();
// Act
list.Add([.. protocol.SerializeReadCoils(UNIT_ID, 10, 10)]);
list.Add([.. protocol.SerializeReadCoils(UNIT_ID, 10, 10)]);
list.Add([.. protocol.SerializeReadCoils(UNIT_ID, 10, 10)]);
// Assert
for (int i = 0; i < list.Count; i++)
{
Assert.AreEqual(0x00, list[i][0]);
Assert.AreEqual((byte)(i + 1), list[i][1]);
// Other asserts already done
}
}
[TestMethod]
public void ShouldNotIncreaseTransactionId()
{
// Arrange
var list = new List<byte[]>();
var protocol = new RtuOverTcpProtocol { DisableTransactionId = true };
// Act
list.Add([.. protocol.SerializeReadCoils(UNIT_ID, 10, 10)]);
list.Add([.. protocol.SerializeReadCoils(UNIT_ID, 10, 10)]);
list.Add([.. protocol.SerializeReadCoils(UNIT_ID, 10, 10)]);
// Assert
for (int i = 0; i < list.Count; i++)
{
Assert.AreEqual(0x00, list[i][0]);
Assert.AreEqual(0x00, list[i][1]);
// Other asserts already done
}
}
[TestMethod]
public void ShouldResetTransactionIdOnMaxValue()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
protocol.GetType()
.GetField("_transactionId", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(protocol, (ushort)(ushort.MaxValue - 1));
// Act
var result1 = protocol.SerializeReadCoils(UNIT_ID, 10, 10);
var result2 = protocol.SerializeReadCoils(UNIT_ID, 10, 10);
// Assert
Assert.AreEqual(0xFF, result1[0]);
Assert.AreEqual(0xFF, result1[1]);
Assert.AreEqual(0x00, result2[0]);
Assert.AreEqual(0x00, result2[1]);
}
#endregion Helper
[TestMethod]
public void ShouldNameRtuOverTcp()
{
// Arrange
var protocol = new RtuOverTcpProtocol();
// Act
string result = protocol.Name;
// Assert
Assert.AreEqual("RTU over TCP", result);
}
private static void SetCrc(byte[] bytes)
{
byte[] crc = RtuProtocol.CRC16(bytes, 6, bytes.Length - 8);
bytes[^2] = crc[0];
bytes[^1] = crc[1];
}
}
}