Add ModbusDevice as preparation for server implementations.

This commit is contained in:
2024-03-18 13:49:06 +01:00
parent 946614b86c
commit fbc9f9e429
9 changed files with 642 additions and 45 deletions

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
namespace AMWD.Protocols.Modbus.Common namespace AMWD.Protocols.Modbus.Common
{ {
@@ -10,5 +11,19 @@ namespace AMWD.Protocols.Modbus.Common
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
Array.Reverse(bytes); Array.Reverse(bytes);
} }
public static ushort NetworkUInt16(this byte[] bytes, int offset = 0)
{
byte[] b = bytes.Skip(offset).Take(2).ToArray();
b.SwapNetworkOrder();
return BitConverter.ToUInt16(b, 0);
}
public static byte[] ToNetworkBytes(this ushort value)
{
byte[] b = BitConverter.GetBytes(value);
b.SwapNetworkOrder();
return b;
}
} }
} }

View File

@@ -5,7 +5,7 @@ using System.Linq;
namespace System namespace System
{ {
// ================================================================================================================================== // // ================================================================================================================================== //
// Source: https://git.am-wd.de/am.wd/common/-/blob/d4b390ad911ce302cc371bb2121fa9c31db1674a/AMWD.Common/Extensions/EnumExtensions.cs // // Source: https://git.am-wd.de/am-wd/common/-/blob/d4b390ad911ce302cc371bb2121fa9c31db1674a/AMWD.Common/Extensions/EnumExtensions.cs //
// ================================================================================================================================== // // ================================================================================================================================== //
[Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static class EnumExtensions internal static class EnumExtensions

View File

@@ -0,0 +1,92 @@
namespace System.Threading
{
// ================================================================================================================================================== //
// Source: https://git.am-wd.de/am-wd/common/-/blob/d4b390ad911ce302cc371bb2121fa9c31db1674a/AMWD.Common/Extensions/ReaderWriterLockSlimExtensions.cs //
// ================================================================================================================================================== //
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static class ReaderWriterLockSlimExtensions
{
/// <summary>
/// Acquires a read lock on a lock object that can be released with an
/// <see cref="IDisposable"/> instance.
/// </summary>
/// <param name="rwLock">The lock object.</param>
/// <param name="timeoutMilliseconds">The number of milliseconds to wait, or -1
/// (<see cref="Timeout.Infinite"/>) to wait indefinitely.</param>
/// <returns>An <see cref="IDisposable"/> instance to release the lock.</returns>
public static IDisposable GetReadLock(this ReaderWriterLockSlim rwLock, int timeoutMilliseconds = -1)
{
if (!rwLock.TryEnterReadLock(timeoutMilliseconds))
throw new TimeoutException("The read lock could not be acquired.");
return new DisposableReadWriteLock(rwLock, LockMode.Read);
}
/// <summary>
/// Acquires a upgradeable read lock on a lock object that can be released with an
/// <see cref="IDisposable"/> instance. The lock can be upgraded to a write lock temporarily
/// with <see cref="GetWriteLock"/> or until the lock is released with
/// <see cref="ReaderWriterLockSlim.EnterWriteLock"/> alone.
/// </summary>
/// <param name="rwLock">The lock object.</param>
/// <param name="timeoutMilliseconds">The number of milliseconds to wait, or -1
/// (<see cref="Timeout.Infinite"/>) to wait indefinitely.</param>
/// <returns>An <see cref="IDisposable"/> instance to release the lock. If the lock was
/// upgraded to a write lock, that will be released as well.</returns>
public static IDisposable GetUpgradeableReadLock(this ReaderWriterLockSlim rwLock, int timeoutMilliseconds = -1)
{
if (!rwLock.TryEnterUpgradeableReadLock(timeoutMilliseconds))
throw new TimeoutException("The upgradeable read lock could not be acquired.");
return new DisposableReadWriteLock(rwLock, LockMode.Upgradable);
}
/// <summary>
/// Acquires a write lock on a lock object that can be released with an
/// <see cref="IDisposable"/> instance.
/// </summary>
/// <param name="rwLock">The lock object.</param>
/// <param name="timeoutMilliseconds">The number of milliseconds to wait, or -1
/// (<see cref="Timeout.Infinite"/>) to wait indefinitely.</param>
/// <returns>An <see cref="IDisposable"/> instance to release the lock.</returns>
public static IDisposable GetWriteLock(this ReaderWriterLockSlim rwLock, int timeoutMilliseconds = -1)
{
if (!rwLock.TryEnterWriteLock(timeoutMilliseconds))
throw new TimeoutException("The write lock could not be acquired.");
return new DisposableReadWriteLock(rwLock, LockMode.Write);
}
private struct DisposableReadWriteLock(ReaderWriterLockSlim rwLock, LockMode lockMode)
: IDisposable
{
private readonly ReaderWriterLockSlim _rwLock = rwLock;
private LockMode _lockMode = lockMode;
public void Dispose()
{
if (_lockMode == LockMode.Read)
_rwLock.ExitReadLock();
if (_lockMode == LockMode.Upgradable && _rwLock.IsWriteLockHeld) // Upgraded with EnterWriteLock alone
_rwLock.ExitWriteLock();
if (_lockMode == LockMode.Upgradable)
_rwLock.ExitUpgradeableReadLock();
if (_lockMode == LockMode.Write)
_rwLock.ExitWriteLock();
_lockMode = LockMode.None;
}
}
private enum LockMode
{
None = 0,
Read = 1,
Upgradable = 2,
Write = 3,
}
}
}

View File

@@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.Threading;
namespace AMWD.Protocols.Modbus.Common.Models
{
/// <summary>
/// Represents a Modbus device used in a Modbus server implementation.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="ModbusDevice"/> class.
/// </remarks>
/// <param name="id">The <see cref="ModbusDevice"/> ID.</param>
public class ModbusDevice(byte id) : IDisposable
{
private readonly ReaderWriterLockSlim _rwLockCoils = new();
private readonly ReaderWriterLockSlim _rwLockDiscreteInputs = new();
private readonly ReaderWriterLockSlim _rwLockHoldingRegisters = new();
private readonly ReaderWriterLockSlim _rwLockInputRegisters = new();
private readonly HashSet<ushort> _coils = [];
private readonly HashSet<ushort> _discreteInputs = [];
private readonly Dictionary<ushort, ushort> _holdingRegisters = [];
private readonly Dictionary<ushort, ushort> _inputRegisters = [];
private bool _isDisposed;
/// <summary>
/// Gets the ID of the <see cref="ModbusDevice"/>.
/// </summary>
public byte Id { get; } = id;
/// <summary>
/// Releases the unmanaged resources used by the <see cref="ModbusDevice"/>
/// and optionally also discards the managed resources.
/// </summary>
public void Dispose()
{
if (_isDisposed)
return;
_isDisposed = true;
_rwLockCoils.Dispose();
_rwLockDiscreteInputs.Dispose();
_rwLockHoldingRegisters.Dispose();
_rwLockInputRegisters.Dispose();
_coils.Clear();
_discreteInputs.Clear();
_holdingRegisters.Clear();
_inputRegisters.Clear();
}
/// <summary>
/// Gets a <see cref="Coil"/> from the <see cref="ModbusDevice"/>.
/// </summary>
/// <param name="address">The address of the <see cref="Coil"/>.</param>
public Coil GetCoil(ushort address)
{
Assertions();
using (_rwLockCoils.GetReadLock())
{
return new Coil
{
Address = address,
HighByte = (byte)(_coils.Contains(address) ? 0xFF : 0x00)
};
}
}
/// <summary>
/// Sets a <see cref="Coil"/> to the <see cref="ModbusDevice"/>.
/// </summary>
/// <param name="coil">The <see cref="Coil"/> to set.</param>
public void SetCoil(Coil coil)
{
Assertions();
using (_rwLockCoils.GetWriteLock())
{
if (coil.Value)
_coils.Add(coil.Address);
else
_coils.Remove(coil.Address);
}
}
/// <summary>
/// Gets a <see cref="DiscreteInput"/> from the <see cref="ModbusDevice"/>.
/// </summary>
/// <param name="address">The address of the <see cref="DiscreteInput"/>.</param>
public DiscreteInput GetDiscreteInput(ushort address)
{
Assertions();
using (_rwLockDiscreteInputs.GetReadLock())
{
return new DiscreteInput
{
Address = address,
HighByte = (byte)(_discreteInputs.Contains(address) ? 0xFF : 0x00)
};
}
}
/// <summary>
/// Sets a <see cref="DiscreteInput"/> to the <see cref="ModbusDevice"/>.
/// </summary>
/// <param name="input">The <see cref="DiscreteInput"/> to set.</param>
public void SetDiscreteInput(DiscreteInput input)
{
using (_rwLockDiscreteInputs.GetWriteLock())
{
if (input.Value)
_discreteInputs.Add(input.Address);
else
_discreteInputs.Remove(input.Address);
}
}
/// <summary>
/// Gets a <see cref="HoldingRegister"/> from the <see cref="ModbusDevice"/>.
/// </summary>
/// <param name="address">The address of the <see cref="HoldingRegister"/>.</param>
public HoldingRegister GetHoldingRegister(ushort address)
{
Assertions();
using (_rwLockHoldingRegisters.GetReadLock())
{
if (!_holdingRegisters.TryGetValue(address, out ushort value))
value = 0x0000;
byte[] blob = BitConverter.GetBytes(value);
blob.SwapNetworkOrder();
return new HoldingRegister
{
Address = address,
HighByte = blob[0],
LowByte = blob[1]
};
}
}
/// <summary>
/// Sets a <see cref="HoldingRegister"/> to the <see cref="ModbusDevice"/>.
/// </summary>
/// <param name="register">The <see cref="HoldingRegister"/> to set.</param>
public void SetHoldingRegister(HoldingRegister register)
{
Assertions();
using (_rwLockHoldingRegisters.GetWriteLock())
{
if (register.Value == 0)
{
_holdingRegisters.Remove(register.Address);
return;
}
byte[] blob = [register.HighByte, register.LowByte];
blob.SwapNetworkOrder();
_holdingRegisters[register.Address] = BitConverter.ToUInt16(blob, 0);
}
}
/// <summary>
/// Gets a <see cref="InputRegister"/> from the <see cref="ModbusDevice"/>.
/// </summary>
/// <param name="address">The address of the <see cref="InputRegister"/>.</param>
public InputRegister GetInputRegister(ushort address)
{
Assertions();
using (_rwLockInputRegisters.GetReadLock())
{
if (!_inputRegisters.TryGetValue(address, out ushort value))
value = 0x0000;
byte[] blob = BitConverter.GetBytes(value);
blob.SwapNetworkOrder();
return new InputRegister
{
Address = address,
HighByte = blob[0],
LowByte = blob[1]
};
}
}
/// <summary>
/// Sets a <see cref="InputRegister"/> to the <see cref="ModbusDevice"/>.
/// </summary>
/// <param name="register">The <see cref="InputRegister"/> to set.</param>
public void SetInputRegister(InputRegister register)
{
Assertions();
using (_rwLockInputRegisters.GetWriteLock())
{
if (register.Value == 0)
{
_inputRegisters.Remove(register.Address);
return;
}
byte[] blob = [register.HighByte, register.LowByte];
blob.SwapNetworkOrder();
_inputRegisters[register.Address] = BitConverter.ToUInt16(blob, 0);
}
}
private void Assertions()
{
#if NET8_0_OR_GREATER
ObjectDisposedException.ThrowIf(_isDisposed, this);
#else
if (_isDisposed)
throw new ObjectDisposedException(GetType().FullName);
#endif
}
}
}

View File

@@ -89,12 +89,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadCoils; request[7] = (byte)ModbusFunctionCode.ReadCoils;
// Starting address // Starting address
byte[] addrBytes = ToNetworkBytes(startAddress); byte[] addrBytes = startAddress.ToNetworkBytes();
request[8] = addrBytes[0]; request[8] = addrBytes[0];
request[9] = addrBytes[1]; request[9] = addrBytes[1];
// Quantity // Quantity
byte[] countBytes = ToNetworkBytes(count); byte[] countBytes = count.ToNetworkBytes();
request[10] = countBytes[0]; request[10] = countBytes[0];
request[11] = countBytes[1]; request[11] = countBytes[1];
@@ -144,12 +144,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadDiscreteInputs; request[7] = (byte)ModbusFunctionCode.ReadDiscreteInputs;
// Starting address // Starting address
byte[] addrBytes = ToNetworkBytes(startAddress); byte[] addrBytes = startAddress.ToNetworkBytes();
request[8] = addrBytes[0]; request[8] = addrBytes[0];
request[9] = addrBytes[1]; request[9] = addrBytes[1];
// Quantity // Quantity
byte[] countBytes = ToNetworkBytes(count); byte[] countBytes = count.ToNetworkBytes();
request[10] = countBytes[0]; request[10] = countBytes[0];
request[11] = countBytes[1]; request[11] = countBytes[1];
@@ -199,12 +199,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadHoldingRegisters; request[7] = (byte)ModbusFunctionCode.ReadHoldingRegisters;
// Starting address // Starting address
byte[] addrBytes = ToNetworkBytes(startAddress); byte[] addrBytes = startAddress.ToNetworkBytes();
request[8] = addrBytes[0]; request[8] = addrBytes[0];
request[9] = addrBytes[1]; request[9] = addrBytes[1];
// Quantity // Quantity
byte[] countBytes = ToNetworkBytes(count); byte[] countBytes = count.ToNetworkBytes();
request[10] = countBytes[0]; request[10] = countBytes[0];
request[11] = countBytes[1]; request[11] = countBytes[1];
@@ -251,12 +251,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadInputRegisters; request[7] = (byte)ModbusFunctionCode.ReadInputRegisters;
// Starting address // Starting address
byte[] addrBytes = ToNetworkBytes(startAddress); byte[] addrBytes = startAddress.ToNetworkBytes();
request[8] = addrBytes[0]; request[8] = addrBytes[0];
request[9] = addrBytes[1]; request[9] = addrBytes[1];
// Quantity // Quantity
byte[] countBytes = ToNetworkBytes(count); byte[] countBytes = count.ToNetworkBytes();
request[10] = countBytes[0]; request[10] = countBytes[0];
request[11] = countBytes[1]; request[11] = countBytes[1];
@@ -362,7 +362,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
// Function code // Function code
request[7] = (byte)ModbusFunctionCode.WriteSingleCoil; request[7] = (byte)ModbusFunctionCode.WriteSingleCoil;
byte[] addrBytes = ToNetworkBytes(coil.Address); byte[] addrBytes = coil.Address.ToNetworkBytes();
request[8] = addrBytes[0]; request[8] = addrBytes[0];
request[9] = addrBytes[1]; request[9] = addrBytes[1];
@@ -377,7 +377,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
{ {
return new Coil return new Coil
{ {
Address = ToNetworkUInt16(response.Skip(8).Take(2).ToArray()), Address = response.ToArray().NetworkUInt16(8),
HighByte = response[10], HighByte = response[10],
LowByte = response[11] LowByte = response[11]
}; };
@@ -401,7 +401,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
// Function code // Function code
request[7] = (byte)ModbusFunctionCode.WriteSingleRegister; request[7] = (byte)ModbusFunctionCode.WriteSingleRegister;
byte[] addrBytes = ToNetworkBytes(register.Address); byte[] addrBytes = register.Address.ToNetworkBytes();
request[8] = addrBytes[0]; request[8] = addrBytes[0];
request[9] = addrBytes[1]; request[9] = addrBytes[1];
@@ -416,7 +416,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
{ {
return new HoldingRegister return new HoldingRegister
{ {
Address = ToNetworkUInt16(response.Skip(8).Take(2).ToArray()), Address = response.ToArray().NetworkUInt16(8),
HighByte = response[10], HighByte = response[10],
LowByte = response[11] LowByte = response[11]
}; };
@@ -454,11 +454,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.WriteMultipleCoils; request[7] = (byte)ModbusFunctionCode.WriteMultipleCoils;
byte[] addrBytes = ToNetworkBytes(firstAddress); byte[] addrBytes = firstAddress.ToNetworkBytes();
request[8] = addrBytes[0]; request[8] = addrBytes[0];
request[9] = addrBytes[1]; request[9] = addrBytes[1];
byte[] countBytes = ToNetworkBytes((ushort)orderedList.Count); byte[] countBytes = ((ushort)orderedList.Count).ToNetworkBytes();
request[10] = countBytes[0]; request[10] = countBytes[0];
request[11] = countBytes[1]; request[11] = countBytes[1];
@@ -483,8 +483,8 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
/// <inheritdoc/> /// <inheritdoc/>
public (ushort FirstAddress, ushort NumberOfCoils) DeserializeWriteMultipleCoils(IReadOnlyList<byte> response) public (ushort FirstAddress, ushort NumberOfCoils) DeserializeWriteMultipleCoils(IReadOnlyList<byte> response)
{ {
ushort firstAddress = ToNetworkUInt16(response.Skip(8).Take(2).ToArray()); ushort firstAddress = response.ToArray().NetworkUInt16(8);
ushort numberOfCoils = ToNetworkUInt16(response.Skip(10).Take(2).ToArray()); ushort numberOfCoils = response.ToArray().NetworkUInt16(10);
return (firstAddress, numberOfCoils); return (firstAddress, numberOfCoils);
} }
@@ -521,11 +521,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.WriteMultipleRegisters; request[7] = (byte)ModbusFunctionCode.WriteMultipleRegisters;
byte[] addrBytes = ToNetworkBytes(firstAddress); byte[] addrBytes = firstAddress.ToNetworkBytes();
request[8] = addrBytes[0]; request[8] = addrBytes[0];
request[9] = addrBytes[1]; request[9] = addrBytes[1];
byte[] countBytes = ToNetworkBytes((ushort)orderedList.Count); byte[] countBytes = ((ushort)orderedList.Count).ToNetworkBytes();
request[10] = countBytes[0]; request[10] = countBytes[0];
request[11] = countBytes[1]; request[11] = countBytes[1];
@@ -544,8 +544,8 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
/// <inheritdoc/> /// <inheritdoc/>
public (ushort FirstAddress, ushort NumberOfRegisters) DeserializeWriteMultipleHoldingRegisters(IReadOnlyList<byte> response) public (ushort FirstAddress, ushort NumberOfRegisters) DeserializeWriteMultipleHoldingRegisters(IReadOnlyList<byte> response)
{ {
ushort firstAddress = ToNetworkUInt16(response.Skip(8).Take(2).ToArray()); ushort firstAddress = response.ToArray().NetworkUInt16(8);
ushort numberOfRegisters = ToNetworkUInt16(response.Skip(10).Take(2).ToArray()); ushort numberOfRegisters = response.ToArray().NetworkUInt16(10);
return (firstAddress, numberOfRegisters); return (firstAddress, numberOfRegisters);
} }
@@ -563,7 +563,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
if (responseBytes.Count < 6) if (responseBytes.Count < 6)
return false; return false;
ushort followingBytes = ToNetworkUInt16(responseBytes.Skip(4).Take(2).ToArray()); ushort followingBytes = responseBytes.ToArray().NetworkUInt16(4);
if (responseBytes.Count < followingBytes + 6) if (responseBytes.Count < followingBytes + 6)
return false; return false;
@@ -582,7 +582,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
if (request[2] != response[2] || request[3] != response[3]) if (request[2] != response[2] || request[3] != response[3])
throw new ModbusException("Protocol Identifier does not match."); throw new ModbusException("Protocol Identifier does not match.");
ushort count = ToNetworkUInt16(response.Skip(4).Take(2).ToArray()); ushort count = response.ToArray().NetworkUInt16(4);
if (count != response.Count - 6) if (count != response.Count - 6)
throw new ModbusException("Number of following bytes does not match."); throw new ModbusException("Number of following bytes does not match.");
@@ -636,7 +636,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
// Transaction id // Transaction id
ushort txId = GetNextTransacitonId(); ushort txId = GetNextTransacitonId();
byte[] txBytes = ToNetworkBytes(txId); byte[] txBytes = txId.ToNetworkBytes();
header[0] = txBytes[0]; header[0] = txBytes[0];
header[1] = txBytes[1]; header[1] = txBytes[1];
@@ -645,7 +645,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
header[3] = 0x00; header[3] = 0x00;
// Number of following bytes // Number of following bytes
byte[] countBytes = ToNetworkBytes((ushort)followingBytes); byte[] countBytes = ((ushort)followingBytes).ToNetworkBytes();
header[4] = countBytes[0]; header[4] = countBytes[0];
header[5] = countBytes[1]; header[5] = countBytes[1];
@@ -655,24 +655,6 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
return header; return header;
} }
private static byte[] ToNetworkBytes(ushort value)
{
byte[] bytes = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
return bytes;
}
private static ushort ToNetworkUInt16(byte[] bytes)
{
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
return BitConverter.ToUInt16(bytes, 0);
}
#endregion Private helpers #endregion Private helpers
} }
} }

View File

@@ -4,7 +4,7 @@ using System.Threading.Tasks;
namespace System.Collections.Generic namespace System.Collections.Generic
{ {
// ============================================================================================================================= // // ============================================================================================================================= //
// Source: https://git.am-wd.de/am.wd/common/-/blob/d4b390ad911ce302cc371bb2121fa9c31db1674a/AMWD.Common/Utilities/AsyncQueue.cs // // Source: https://git.am-wd.de/am-wd/common/-/blob/d4b390ad911ce302cc371bb2121fa9c31db1674a/AMWD.Common/Utilities/AsyncQueue.cs //
// ============================================================================================================================= // // ============================================================================================================================= //
[Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal class AsyncQueue<T> internal class AsyncQueue<T>

View File

@@ -0,0 +1,288 @@
using System.Collections.Generic;
using System.Reflection;
using AMWD.Protocols.Modbus.Common.Models;
namespace AMWD.Protocols.Modbus.Tests.Common.Models
{
[TestClass]
public class ModbusDeviceTest
{
[TestMethod]
public void ShouldAllowMultipleDispose()
{
// Arrange
var device = new ModbusDevice(123);
// Act
device.Dispose();
device.Dispose();
// Assert - no exception
}
[TestMethod]
public void ShouldAssertDisposed()
{
// Arrange
var device = new ModbusDevice(123);
device.Dispose();
// Act
try
{
device.GetCoil(111);
Assert.Fail();
}
catch (ObjectDisposedException)
{ }
try
{
device.SetCoil(new Coil { Address = 222 });
Assert.Fail();
}
catch (ObjectDisposedException)
{ }
try
{
device.GetDiscreteInput(111);
Assert.Fail();
}
catch (ObjectDisposedException)
{ }
try
{
device.SetDiscreteInput(new DiscreteInput { Address = 222 });
Assert.Fail();
}
catch (ObjectDisposedException)
{ }
try
{
device.GetHoldingRegister(111);
Assert.Fail();
}
catch (ObjectDisposedException)
{ }
try
{
device.SetHoldingRegister(new HoldingRegister { Address = 222 });
Assert.Fail();
}
catch (ObjectDisposedException)
{ }
try
{
device.GetInputRegister(111);
Assert.Fail();
}
catch (ObjectDisposedException)
{ }
try
{
device.SetInputRegister(new InputRegister { Address = 222 });
Assert.Fail();
}
catch (ObjectDisposedException)
{ }
}
[TestMethod]
public void ShouldGetCoil()
{
// Arrange
var device = new ModbusDevice(123);
((HashSet<ushort>)device.GetType()
.GetField("_coils", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device))
.Add(333);
// Act
var coilFalse = device.GetCoil(111);
var coilTrue = device.GetCoil(333);
// Assert
Assert.AreEqual(111, coilFalse.Address);
Assert.IsFalse(coilFalse.Value);
Assert.AreEqual(333, coilTrue.Address);
Assert.IsTrue(coilTrue.Value);
}
[TestMethod]
public void ShouldSetCoil()
{
// Arrange
var device = new ModbusDevice(123);
((HashSet<ushort>)device.GetType()
.GetField("_coils", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device))
.Add(333);
// Act
device.SetCoil(new Coil { Address = 111, Value = true });
device.SetCoil(new Coil { Address = 333, Value = false });
// Assert
ushort[] coils = ((HashSet<ushort>)device.GetType()
.GetField("_coils", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device)).ToArray();
Assert.AreEqual(1, coils.Length);
Assert.AreEqual(111, coils.First());
}
[TestMethod]
public void ShouldGetDiscreteInput()
{
// Arrange
var device = new ModbusDevice(123);
((HashSet<ushort>)device.GetType()
.GetField("_discreteInputs", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device))
.Add(333);
// Act
var inputFalse = device.GetDiscreteInput(111);
var inputTrue = device.GetDiscreteInput(333);
// Assert
Assert.AreEqual(111, inputFalse.Address);
Assert.IsFalse(inputFalse.Value);
Assert.AreEqual(333, inputTrue.Address);
Assert.IsTrue(inputTrue.Value);
}
[TestMethod]
public void ShouldSetDiscreteInput()
{
// Arrange
var device = new ModbusDevice(123);
((HashSet<ushort>)device.GetType()
.GetField("_discreteInputs", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device))
.Add(333);
// Act
device.SetDiscreteInput(new DiscreteInput { Address = 111, HighByte = 0xFF });
device.SetDiscreteInput(new DiscreteInput { Address = 333, HighByte = 0x00 });
// Assert
ushort[] discreteInputs = ((HashSet<ushort>)device.GetType()
.GetField("_discreteInputs", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device)).ToArray();
Assert.AreEqual(1, discreteInputs.Length);
Assert.AreEqual(111, discreteInputs.First());
}
[TestMethod]
public void ShouldGetHoldingRegister()
{
// Arrange
var device = new ModbusDevice(123);
((Dictionary<ushort, ushort>)device.GetType()
.GetField("_holdingRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device))
.Add(333, 42);
// Act
var zeroRegister = device.GetHoldingRegister(111);
var valueRegister = device.GetHoldingRegister(333);
// Assert
Assert.AreEqual(111, zeroRegister.Address);
Assert.AreEqual(0, zeroRegister.Value);
Assert.AreEqual(0x00, zeroRegister.HighByte);
Assert.AreEqual(0x00, zeroRegister.LowByte);
Assert.AreEqual(333, valueRegister.Address);
Assert.AreEqual(42, valueRegister.Value);
Assert.AreEqual(0x00, valueRegister.HighByte);
Assert.AreEqual(0x2A, valueRegister.LowByte);
}
[TestMethod]
public void ShouldSetHoldingRegister()
{
// Arrange
var device = new ModbusDevice(123);
((Dictionary<ushort, ushort>)device.GetType()
.GetField("_holdingRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device))
.Add(333, 42);
// Act
device.SetHoldingRegister(new HoldingRegister { Address = 333, Value = 0 });
device.SetHoldingRegister(new HoldingRegister { Address = 111, Value = 42 });
// Assert
var registers = ((Dictionary<ushort, ushort>)device.GetType()
.GetField("_holdingRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device))
.ToDictionary(x => x.Key, x => x.Value);
Assert.AreEqual(1, registers.Count);
Assert.AreEqual(111, registers.First().Key);
Assert.AreEqual(42, registers.First().Value);
}
[TestMethod]
public void ShouldGetInputRegister()
{
// Arrange
var device = new ModbusDevice(123);
((Dictionary<ushort, ushort>)device.GetType()
.GetField("_inputRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device))
.Add(333, 42);
// Act
var zeroRegister = device.GetInputRegister(111);
var valueRegister = device.GetInputRegister(333);
// Assert
Assert.AreEqual(111, zeroRegister.Address);
Assert.AreEqual(0, zeroRegister.Value);
Assert.AreEqual(0x00, zeroRegister.HighByte);
Assert.AreEqual(0x00, zeroRegister.LowByte);
Assert.AreEqual(333, valueRegister.Address);
Assert.AreEqual(42, valueRegister.Value);
Assert.AreEqual(0x00, valueRegister.HighByte);
Assert.AreEqual(0x2A, valueRegister.LowByte);
}
[TestMethod]
public void ShouldSetInputRegister()
{
// Arrange
var device = new ModbusDevice(123);
((Dictionary<ushort, ushort>)device.GetType()
.GetField("_inputRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device))
.Add(333, 42);
// Act
device.SetInputRegister(new InputRegister { Address = 333, LowByte = 0 });
device.SetInputRegister(new InputRegister { Address = 111, LowByte = 42 });
// Assert
var registers = ((Dictionary<ushort, ushort>)device.GetType()
.GetField("_inputRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(device))
.ToDictionary(x => x.Key, x => x.Value);
Assert.AreEqual(1, registers.Count);
Assert.AreEqual(111, registers.First().Key);
Assert.AreEqual(42, registers.First().Value);
}
}
}

View File

@@ -8,7 +8,7 @@
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/AndreasAmMueller/AMWD.Protocols.Modbus.git</RepositoryUrl> <RepositoryUrl>https://github.com/AM-WD/AMWD.Protocols.Modbus.git</RepositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>