Add ModbusDevice as preparation for server implementations.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
220
AMWD.Protocols.Modbus.Common/Models/ModbusDevice.cs
Normal file
220
AMWD.Protocols.Modbus.Common/Models/ModbusDevice.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
/// <inheritdoc/>
|
||||
public (ushort FirstAddress, ushort NumberOfCoils) DeserializeWriteMultipleCoils(IReadOnlyList<byte> 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
|
||||
/// <inheritdoc/>
|
||||
public (ushort FirstAddress, ushort NumberOfRegisters) DeserializeWriteMultipleHoldingRegisters(IReadOnlyList<byte> 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T>
|
||||
|
||||
288
AMWD.Protocols.Modbus.Tests/Common/Models/ModbusDeviceTest.cs
Normal file
288
AMWD.Protocols.Modbus.Tests/Common/Models/ModbusDeviceTest.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
|
||||
<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>
|
||||
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
|
||||
Reference in New Issue
Block a user