diff --git a/AMWD.Protocols.Modbus.Common/Extensions/ArrayExtensions.cs b/AMWD.Protocols.Modbus.Common/Extensions/ArrayExtensions.cs index de7604d..d3e0f69 100644 --- a/AMWD.Protocols.Modbus.Common/Extensions/ArrayExtensions.cs +++ b/AMWD.Protocols.Modbus.Common/Extensions/ArrayExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace AMWD.Protocols.Modbus.Common { @@ -10,5 +11,19 @@ namespace AMWD.Protocols.Modbus.Common if (BitConverter.IsLittleEndian) 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; + } } } diff --git a/AMWD.Protocols.Modbus.Common/Extensions/EnumExtensions.cs b/AMWD.Protocols.Modbus.Common/Extensions/EnumExtensions.cs index d8e0288..b15eca9 100644 --- a/AMWD.Protocols.Modbus.Common/Extensions/EnumExtensions.cs +++ b/AMWD.Protocols.Modbus.Common/Extensions/EnumExtensions.cs @@ -5,7 +5,7 @@ using System.Linq; 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] internal static class EnumExtensions diff --git a/AMWD.Protocols.Modbus.Common/Extensions/ReaderWriterLockSlimExtensions.cs b/AMWD.Protocols.Modbus.Common/Extensions/ReaderWriterLockSlimExtensions.cs new file mode 100644 index 0000000..e0fcec6 --- /dev/null +++ b/AMWD.Protocols.Modbus.Common/Extensions/ReaderWriterLockSlimExtensions.cs @@ -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 + { + /// + /// Acquires a read lock on a lock object that can be released with an + /// instance. + /// + /// The lock object. + /// The number of milliseconds to wait, or -1 + /// () to wait indefinitely. + /// An instance to release the lock. + 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); + } + + /// + /// Acquires a upgradeable read lock on a lock object that can be released with an + /// instance. The lock can be upgraded to a write lock temporarily + /// with or until the lock is released with + /// alone. + /// + /// The lock object. + /// The number of milliseconds to wait, or -1 + /// () to wait indefinitely. + /// An instance to release the lock. If the lock was + /// upgraded to a write lock, that will be released as well. + 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); + } + + /// + /// Acquires a write lock on a lock object that can be released with an + /// instance. + /// + /// The lock object. + /// The number of milliseconds to wait, or -1 + /// () to wait indefinitely. + /// An instance to release the lock. + 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, + } + } +} diff --git a/AMWD.Protocols.Modbus.Common/Models/ModbusDevice.cs b/AMWD.Protocols.Modbus.Common/Models/ModbusDevice.cs new file mode 100644 index 0000000..b7b23a2 --- /dev/null +++ b/AMWD.Protocols.Modbus.Common/Models/ModbusDevice.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace AMWD.Protocols.Modbus.Common.Models +{ + /// + /// Represents a Modbus device used in a Modbus server implementation. + /// + /// + /// Initializes a new instance of the class. + /// + /// The ID. + 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 _coils = []; + private readonly HashSet _discreteInputs = []; + private readonly Dictionary _holdingRegisters = []; + private readonly Dictionary _inputRegisters = []; + + private bool _isDisposed; + + /// + /// Gets the ID of the . + /// + public byte Id { get; } = id; + + /// + /// Releases the unmanaged resources used by the + /// and optionally also discards the managed resources. + /// + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + + _rwLockCoils.Dispose(); + _rwLockDiscreteInputs.Dispose(); + _rwLockHoldingRegisters.Dispose(); + _rwLockInputRegisters.Dispose(); + + _coils.Clear(); + _discreteInputs.Clear(); + _holdingRegisters.Clear(); + _inputRegisters.Clear(); + } + + /// + /// Gets a from the . + /// + /// The address of the . + public Coil GetCoil(ushort address) + { + Assertions(); + using (_rwLockCoils.GetReadLock()) + { + return new Coil + { + Address = address, + HighByte = (byte)(_coils.Contains(address) ? 0xFF : 0x00) + }; + } + } + + /// + /// Sets a to the . + /// + /// The to set. + public void SetCoil(Coil coil) + { + Assertions(); + using (_rwLockCoils.GetWriteLock()) + { + if (coil.Value) + _coils.Add(coil.Address); + else + _coils.Remove(coil.Address); + } + } + + /// + /// Gets a from the . + /// + /// The address of the . + public DiscreteInput GetDiscreteInput(ushort address) + { + Assertions(); + using (_rwLockDiscreteInputs.GetReadLock()) + { + return new DiscreteInput + { + Address = address, + HighByte = (byte)(_discreteInputs.Contains(address) ? 0xFF : 0x00) + }; + } + } + + /// + /// Sets a to the . + /// + /// The to set. + public void SetDiscreteInput(DiscreteInput input) + { + using (_rwLockDiscreteInputs.GetWriteLock()) + { + if (input.Value) + _discreteInputs.Add(input.Address); + else + _discreteInputs.Remove(input.Address); + } + } + + /// + /// Gets a from the . + /// + /// The address of the . + 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] + }; + } + } + + /// + /// Sets a to the . + /// + /// The to set. + 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); + } + } + + /// + /// Gets a from the . + /// + /// The address of the . + 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] + }; + } + } + + /// + /// Sets a to the . + /// + /// The to set. + 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 + } + } +} diff --git a/AMWD.Protocols.Modbus.Common/Protocols/TcpProtocol.cs b/AMWD.Protocols.Modbus.Common/Protocols/TcpProtocol.cs index 0107cc7..868f8c8 100644 --- a/AMWD.Protocols.Modbus.Common/Protocols/TcpProtocol.cs +++ b/AMWD.Protocols.Modbus.Common/Protocols/TcpProtocol.cs @@ -89,12 +89,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols request[7] = (byte)ModbusFunctionCode.ReadCoils; // Starting address - byte[] addrBytes = ToNetworkBytes(startAddress); + byte[] addrBytes = startAddress.ToNetworkBytes(); request[8] = addrBytes[0]; request[9] = addrBytes[1]; // Quantity - byte[] countBytes = ToNetworkBytes(count); + byte[] countBytes = count.ToNetworkBytes(); request[10] = countBytes[0]; request[11] = countBytes[1]; @@ -144,12 +144,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols request[7] = (byte)ModbusFunctionCode.ReadDiscreteInputs; // Starting address - byte[] addrBytes = ToNetworkBytes(startAddress); + byte[] addrBytes = startAddress.ToNetworkBytes(); request[8] = addrBytes[0]; request[9] = addrBytes[1]; // Quantity - byte[] countBytes = ToNetworkBytes(count); + byte[] countBytes = count.ToNetworkBytes(); request[10] = countBytes[0]; request[11] = countBytes[1]; @@ -199,12 +199,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols request[7] = (byte)ModbusFunctionCode.ReadHoldingRegisters; // Starting address - byte[] addrBytes = ToNetworkBytes(startAddress); + byte[] addrBytes = startAddress.ToNetworkBytes(); request[8] = addrBytes[0]; request[9] = addrBytes[1]; // Quantity - byte[] countBytes = ToNetworkBytes(count); + byte[] countBytes = count.ToNetworkBytes(); request[10] = countBytes[0]; request[11] = countBytes[1]; @@ -251,12 +251,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols request[7] = (byte)ModbusFunctionCode.ReadInputRegisters; // Starting address - byte[] addrBytes = ToNetworkBytes(startAddress); + byte[] addrBytes = startAddress.ToNetworkBytes(); request[8] = addrBytes[0]; request[9] = addrBytes[1]; // Quantity - byte[] countBytes = ToNetworkBytes(count); + byte[] countBytes = count.ToNetworkBytes(); request[10] = countBytes[0]; request[11] = countBytes[1]; @@ -362,7 +362,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols // Function code request[7] = (byte)ModbusFunctionCode.WriteSingleCoil; - byte[] addrBytes = ToNetworkBytes(coil.Address); + byte[] addrBytes = coil.Address.ToNetworkBytes(); request[8] = addrBytes[0]; request[9] = addrBytes[1]; @@ -377,7 +377,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols { return new Coil { - Address = ToNetworkUInt16(response.Skip(8).Take(2).ToArray()), + Address = response.ToArray().NetworkUInt16(8), HighByte = response[10], LowByte = response[11] }; @@ -401,7 +401,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols // Function code request[7] = (byte)ModbusFunctionCode.WriteSingleRegister; - byte[] addrBytes = ToNetworkBytes(register.Address); + byte[] addrBytes = register.Address.ToNetworkBytes(); request[8] = addrBytes[0]; request[9] = addrBytes[1]; @@ -416,7 +416,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols { return new HoldingRegister { - Address = ToNetworkUInt16(response.Skip(8).Take(2).ToArray()), + Address = response.ToArray().NetworkUInt16(8), HighByte = response[10], LowByte = response[11] }; @@ -454,11 +454,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols request[7] = (byte)ModbusFunctionCode.WriteMultipleCoils; - byte[] addrBytes = ToNetworkBytes(firstAddress); + byte[] addrBytes = firstAddress.ToNetworkBytes(); request[8] = addrBytes[0]; request[9] = addrBytes[1]; - byte[] countBytes = ToNetworkBytes((ushort)orderedList.Count); + byte[] countBytes = ((ushort)orderedList.Count).ToNetworkBytes(); request[10] = countBytes[0]; request[11] = countBytes[1]; @@ -483,8 +483,8 @@ namespace AMWD.Protocols.Modbus.Common.Protocols /// public (ushort FirstAddress, ushort NumberOfCoils) DeserializeWriteMultipleCoils(IReadOnlyList response) { - ushort firstAddress = ToNetworkUInt16(response.Skip(8).Take(2).ToArray()); - ushort numberOfCoils = ToNetworkUInt16(response.Skip(10).Take(2).ToArray()); + ushort firstAddress = response.ToArray().NetworkUInt16(8); + ushort numberOfCoils = response.ToArray().NetworkUInt16(10); return (firstAddress, numberOfCoils); } @@ -521,11 +521,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols request[7] = (byte)ModbusFunctionCode.WriteMultipleRegisters; - byte[] addrBytes = ToNetworkBytes(firstAddress); + byte[] addrBytes = firstAddress.ToNetworkBytes(); request[8] = addrBytes[0]; request[9] = addrBytes[1]; - byte[] countBytes = ToNetworkBytes((ushort)orderedList.Count); + byte[] countBytes = ((ushort)orderedList.Count).ToNetworkBytes(); request[10] = countBytes[0]; request[11] = countBytes[1]; @@ -544,8 +544,8 @@ namespace AMWD.Protocols.Modbus.Common.Protocols /// public (ushort FirstAddress, ushort NumberOfRegisters) DeserializeWriteMultipleHoldingRegisters(IReadOnlyList response) { - ushort firstAddress = ToNetworkUInt16(response.Skip(8).Take(2).ToArray()); - ushort numberOfRegisters = ToNetworkUInt16(response.Skip(10).Take(2).ToArray()); + ushort firstAddress = response.ToArray().NetworkUInt16(8); + ushort numberOfRegisters = response.ToArray().NetworkUInt16(10); return (firstAddress, numberOfRegisters); } @@ -563,7 +563,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols if (responseBytes.Count < 6) return false; - ushort followingBytes = ToNetworkUInt16(responseBytes.Skip(4).Take(2).ToArray()); + ushort followingBytes = responseBytes.ToArray().NetworkUInt16(4); if (responseBytes.Count < followingBytes + 6) return false; @@ -582,7 +582,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols if (request[2] != response[2] || request[3] != response[3]) 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) throw new ModbusException("Number of following bytes does not match."); @@ -636,7 +636,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols // Transaction id ushort txId = GetNextTransacitonId(); - byte[] txBytes = ToNetworkBytes(txId); + byte[] txBytes = txId.ToNetworkBytes(); header[0] = txBytes[0]; header[1] = txBytes[1]; @@ -645,7 +645,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols header[3] = 0x00; // Number of following bytes - byte[] countBytes = ToNetworkBytes((ushort)followingBytes); + byte[] countBytes = ((ushort)followingBytes).ToNetworkBytes(); header[4] = countBytes[0]; header[5] = countBytes[1]; @@ -655,24 +655,6 @@ namespace AMWD.Protocols.Modbus.Common.Protocols 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 } } diff --git a/AMWD.Protocols.Modbus.Tcp/Utils/ModbusTcpConnection.cs b/AMWD.Protocols.Modbus.Tcp/ModbusTcpConnection.cs similarity index 100% rename from AMWD.Protocols.Modbus.Tcp/Utils/ModbusTcpConnection.cs rename to AMWD.Protocols.Modbus.Tcp/ModbusTcpConnection.cs diff --git a/AMWD.Protocols.Modbus.Tcp/Utils/AsyncQueue.cs b/AMWD.Protocols.Modbus.Tcp/Utils/AsyncQueue.cs index af686e1..836e028 100644 --- a/AMWD.Protocols.Modbus.Tcp/Utils/AsyncQueue.cs +++ b/AMWD.Protocols.Modbus.Tcp/Utils/AsyncQueue.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; 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] internal class AsyncQueue diff --git a/AMWD.Protocols.Modbus.Tests/Common/Models/ModbusDeviceTest.cs b/AMWD.Protocols.Modbus.Tests/Common/Models/ModbusDeviceTest.cs new file mode 100644 index 0000000..d01ace4 --- /dev/null +++ b/AMWD.Protocols.Modbus.Tests/Common/Models/ModbusDeviceTest.cs @@ -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)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)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)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)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)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)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)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)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)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)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)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)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); + } + } +} diff --git a/Directory.Build.props b/Directory.Build.props index 0a18424..3efe4ee 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,7 +8,7 @@ false git - https://github.com/AndreasAmMueller/AMWD.Protocols.Modbus.git + https://github.com/AM-WD/AMWD.Protocols.Modbus.git true true