Commit 27b35672 authored by Andreas Müller's avatar Andreas Müller

Added UnitTests

- UnitTests implemented for ModbusClient

Closes Issues #1, #2, #3
parent 881313e6
Pipeline #17 passed with stage
in 3 minutes and 6 seconds
......@@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Common", "src\Modbus
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Serial", "src\Modbus.Serial\Modbus.Serial.csproj", "{30629B0B-B815-4951-AE56-9A0038210ECB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "test\UnitTests\UnitTests.csproj", "{33EB4212-B2B4-4CFA-A374-1DD9D2675822}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -46,6 +48,10 @@ Global
{30629B0B-B815-4951-AE56-9A0038210ECB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30629B0B-B815-4951-AE56-9A0038210ECB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30629B0B-B815-4951-AE56-9A0038210ECB}.Release|Any CPU.Build.0 = Release|Any CPU
{33EB4212-B2B4-4CFA-A374-1DD9D2675822}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{33EB4212-B2B4-4CFA-A374-1DD9D2675822}.Debug|Any CPU.Build.0 = Debug|Any CPU
{33EB4212-B2B4-4CFA-A374-1DD9D2675822}.Release|Any CPU.ActiveCfg = Release|Any CPU
{33EB4212-B2B4-4CFA-A374-1DD9D2675822}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -55,6 +61,7 @@ Global
{2E6A165D-23DE-40D2-8A43-FE6EFA86F076} = {D4E6650D-2156-4660-B531-0B2AAD475BA1}
{71D10922-FE10-4A77-B4AC-E947797E9CFE} = {D4E6650D-2156-4660-B531-0B2AAD475BA1}
{30629B0B-B815-4951-AE56-9A0038210ECB} = {D4E6650D-2156-4660-B531-0B2AAD475BA1}
{33EB4212-B2B4-4CFA-A374-1DD9D2675822} = {3B6130C0-9168-4208-A677-29DD20301220}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FC7F1581-0E3C-4E35-9DBC-CC7952DE19C1}
......
......@@ -2,7 +2,7 @@
Implements the Modbus communication protocol, written as a .NET Standard 2.0 library.
[![NuGet](https://img.shields.io/nuget/v/AMWD.Modbus.Tcp.svg)](https://www.nuget.org/packages/AMWD.Modbus.Tcp)
[![NuGet](https://img.shields.io/nuget/v/AMWD.Modbus.Tcp.svg?style=flat-square)](https://www.nuget.org/packages/AMWD.Modbus.Tcp)
## Repositories
......
......@@ -93,7 +93,7 @@ namespace AMWD.Modbus.Common
[Description("Illegal data address")]
IllegalDataAddress = 2,
/// <summary>
/// The data value to set not valid.
/// The data value to set is not valid.
/// </summary>
[Description("Illegal data value")]
IllegalDataValue = 3,
......
......@@ -15,7 +15,7 @@ namespace AMWD.Modbus.Common.Interfaces
/// <summary>
/// Gets the result of the asynchronous initialization of this instance.
/// </summary>
Task Initialization { get; }
Task ConnectingTask { get; }
/// <summary>
/// Gets a value indicating whether the connection is established.
......@@ -39,6 +39,22 @@ namespace AMWD.Modbus.Common.Interfaces
#endregion Properties
#region Control
/// <summary>
/// Connects the client to the server.
/// </summary>
/// <returns>An awaitable task.</returns>
Task Connect();
/// <summary>
/// Disconnects the client.
/// </summary>
/// <returns>An awaitable task.</returns>
Task Disconnect();
#endregion Control
#region Read methods
/// <summary>
......
using AMWD.Modbus.Common.Structures;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace AMWD.Modbus.Common.Interfaces
{
......@@ -25,6 +26,11 @@ namespace AMWD.Modbus.Common.Interfaces
#region Properties
/// <summary>
/// Gets the result of the asynchronous initialization of this instance.
/// </summary>
Task Initialization { get; }
/// <summary>
/// Gets the UTC timestamp of the server start.
/// </summary>
......@@ -44,6 +50,10 @@ namespace AMWD.Modbus.Common.Interfaces
#region Public methods
//void Start();
//void Stop();
#region Coils
/// <summary>
......
......@@ -20,5 +20,25 @@
{
return $"Coil#{Address} | {Value}";
}
/// <inheritdoc/>
public override int GetHashCode()
{
return base.GetHashCode() ^
Address.GetHashCode() ^
Value.GetHashCode();
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (!(obj is Coil coil))
{
return false;
}
return Address == coil.Address &&
Value == coil.Value;
}
}
}
......@@ -20,5 +20,25 @@
{
return $"DiscreteInput#{Address} | {Value}";
}
/// <inheritdoc/>
public override int GetHashCode()
{
return base.GetHashCode() ^
Address.GetHashCode() ^
Value.GetHashCode();
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (!(obj is DiscreteInput input))
{
return false;
}
return Address == input.Address &&
Value == input.Value;
}
}
}
......@@ -5,16 +5,16 @@ namespace AMWD.Modbus.Common.Util
/// <summary>
/// Helper class for checksums.
/// </summary>
public class Checksum
public static class Checksum
{
/// <summary>
/// Calculates the CRC checksum with 16 bits of an array.
/// </summary>
/// <param name="array">The array with data.</param>
/// <returns>CRC16 Checksum as byte array. [0] = low byte, [1] = high byte.</returns>
public static byte[] CRC16(byte[] array)
public static byte[] CRC16(this byte[] array)
{
return CRC16(array, 0, array.Length);
return array.CRC16(0, array.Length);
}
/// <summary>
......@@ -24,7 +24,7 @@ namespace AMWD.Modbus.Common.Util
/// <param name="start">The first byte to use.</param>
/// <param name="length">The number of bytes to use.</param>
/// <returns>CRC16 Checksum as byte array. [0] = low byte, [1] = high byte.</returns>
public static byte[] CRC16(byte[] array, int start, int length)
public static byte[] CRC16(this byte[] array, int start, int length)
{
if (array == null || array.Length == 0)
{
......
......@@ -259,15 +259,32 @@ namespace AMWD.Modbus.Common.Util
#region Enums
/// <summary>
/// Tries to read the description of an enum-value.
/// Tries to return an attribute of an enum value.
/// </summary>
/// <param name="value">The enum value.</param>
/// <typeparam name="T">The attribute type.</typeparam>
/// <param name="enumValue">The enum value.</param>
/// <returns>The first attribute of the type present or null.</returns>
public static T GetAttribute<T>(this Enum enumValue)
where T : Attribute
{
if (enumValue != null)
{
var fi = enumValue.GetType().GetField(enumValue.ToString());
var attrs = (T[])fi?.GetCustomAttributes(typeof(T), inherit: false);
return attrs?.FirstOrDefault();
}
return default(T);
}
/// <summary>
/// Tries to read the description of an enum value.
/// </summary>
/// <param name="enumValue">The enum value.</param>
/// <returns>The description or the <see cref="Enum.ToString()"/></returns>
public static string GetDescription(this Enum value)
public static string GetDescription(this Enum enumValue)
{
var fi = value.GetType().GetField(value.ToString());
var attrs = (DescriptionAttribute[])fi?.GetCustomAttributes(typeof(DescriptionAttribute), inherit: false);
return attrs?.FirstOrDefault()?.Description ?? value.ToString();
return enumValue.GetAttribute<DescriptionAttribute>()?.Description ?? enumValue.ToString();
}
#endregion
......
......@@ -20,6 +20,8 @@ namespace AMWD.Modbus.Serial.Client
{
#region Fields
private volatile bool isStarted;
private readonly object reconnectLock = new object();
private readonly object sendLock = new object();
private SerialPort serialPort;
......@@ -65,8 +67,6 @@ namespace AMWD.Modbus.Serial.Client
}
PortName = portName;
Initialization = Connect();
}
#endregion Constructors
......@@ -76,7 +76,7 @@ namespace AMWD.Modbus.Serial.Client
/// <summary>
/// Gets the result of the asynchronous initialization of this instance.
/// </summary>
public Task Initialization { get; }
public Task ConnectingTask { get; private set; }
/// <summary>
/// Gets the serial port name.
......@@ -250,6 +250,49 @@ namespace AMWD.Modbus.Serial.Client
#region Public methods
#region Control
/// <summary>
/// Connects the client to the device.
/// </summary>
/// <returns>An awaitable task.</returns>
public Task Connect()
{
if (isDisposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
if (!isStarted)
{
isStarted = true;
ConnectingTask = Task.Run((Action)Reconnect);
}
return ConnectingTask;
}
/// <summary>
/// Disconnects the client.
/// </summary>
/// <returns>An awaitable task.</returns>
public Task Disconnect()
{
if (isDisposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
if (isStarted)
{
serialPort?.Dispose();
serialPort = null;
}
return Task.CompletedTask;
}
#endregion Control
#region Read methods
/// <summary>
......@@ -316,7 +359,7 @@ namespace AMWD.Modbus.Serial.Client
}
catch (IOException)
{
Task.Run((Action)Reconnect).Forget();
ConnectingTask = Task.Run((Action)Reconnect);
}
return list;
......@@ -386,7 +429,7 @@ namespace AMWD.Modbus.Serial.Client
}
catch (IOException)
{
Task.Run((Action)Reconnect).Forget();
ConnectingTask = Task.Run((Action)Reconnect);
}
return list;
......@@ -452,7 +495,7 @@ namespace AMWD.Modbus.Serial.Client
}
catch (IOException)
{
Task.Run((Action)Reconnect).Forget();
ConnectingTask = Task.Run((Action)Reconnect);
}
return list;
......@@ -518,7 +561,7 @@ namespace AMWD.Modbus.Serial.Client
}
catch (IOException)
{
Task.Run((Action)Reconnect).Forget();
ConnectingTask = Task.Run((Action)Reconnect);
}
return list;
......@@ -582,7 +625,7 @@ namespace AMWD.Modbus.Serial.Client
}
catch (IOException)
{
Task.Run((Action)Reconnect).Forget();
ConnectingTask = Task.Run((Action)Reconnect);
}
return false;
......@@ -640,7 +683,7 @@ namespace AMWD.Modbus.Serial.Client
}
catch (IOException)
{
Task.Run((Action)Reconnect).Forget();
ConnectingTask = Task.Run((Action)Reconnect);
}
return false;
......@@ -725,7 +768,7 @@ namespace AMWD.Modbus.Serial.Client
}
catch (IOException)
{
Task.Run((Action)Reconnect).Forget();
ConnectingTask = Task.Run((Action)Reconnect);
}
return false;
......@@ -802,7 +845,7 @@ namespace AMWD.Modbus.Serial.Client
}
catch (IOException)
{
Task.Run((Action)Reconnect).Forget();
ConnectingTask = Task.Run((Action)Reconnect);
}
return false;
......@@ -814,16 +857,6 @@ namespace AMWD.Modbus.Serial.Client
#region Private methods
private async Task Connect()
{
if (isDisposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
await Task.Run((Action)Reconnect);
}
private void Reconnect()
{
if (isDisposed)
......@@ -947,12 +980,11 @@ namespace AMWD.Modbus.Serial.Client
private void Dispose(bool disposing)
{
if (disposing)
if (isDisposed)
{
//tcpClient?.Dispose();
//tcpClient = null;
return;
}
Disconnect().Wait();
isDisposed = true;
}
......
......@@ -8,6 +8,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Threading.Tasks;
namespace AMWD.Modbus.Serial.Server
{
......@@ -66,13 +67,18 @@ namespace AMWD.Modbus.Serial.Server
PortName = portName;
Initialize();
Initialization = Task.Run((Action)Initialize);
}
#endregion Constructors
#region Properties
/// <summary>
/// Gets the result of the asynchronous initialization of this instance.
/// </summary>
public Task Initialization { get; }
/// <summary>
/// Gets the serial port name.
/// </summary>
......@@ -234,7 +240,7 @@ namespace AMWD.Modbus.Serial.Server
/// <summary>
/// Gets the UTC timestamp of the server start.
/// </summary>
public DateTime StartTime { get; set; }
public DateTime StartTime { get; private set; }
/// <summary>
/// Gets a value indicating whether the server is running.
......
This diff is collapsed.
......@@ -120,6 +120,7 @@ namespace AMWD.Modbus.Tcp.Protocol
case FunctionCode.WriteMultipleCoils:
case FunctionCode.WriteMultipleRegisters:
buffer.AddUInt16(Count);
buffer.AddByte((byte)(Data?.Length ?? 0));
if (Data?.Length > 0)
{
buffer.AddBytes(Data.Buffer);
......
......@@ -2,6 +2,7 @@
using AMWD.Modbus.Common.Util;
using System;
using System.Linq;
using System.Text;
namespace AMWD.Modbus.Tcp.Protocol
{
......@@ -207,7 +208,20 @@ namespace AMWD.Modbus.Tcp.Protocol
/// <inheritdoc/>
public override string ToString()
{
return $"Response#{TransactionId} | Device#{DeviceId}, Fn: {Function}, Error: {IsError}, Address: {Address}, Count: {Count} | {string.Join(" ", Data.Buffer.Select(b => b.ToString("X2")).ToArray())}";
var sb = new StringBuilder();
if (Data != null)
{
foreach (var b in Data.Buffer)
{
if (sb.Length > 0)
{
sb.Append(" ");
}
sb.Append(b.ToString("X2"));
}
}
return $"Response#{TransactionId} | Device#{DeviceId}, Fn: {Function}, Error: {IsError}, Address: {Address}, Count: {Count} | {sb}";
}
/// <inheritdoc/>
......
......@@ -64,13 +64,18 @@ namespace AMWD.Modbus.Tcp.Server
/// <param name="port">The port to listen. (Default: 502)</param>
public ModbusServer(int port = 502)
{
Initialize(port);
Initialization = Task.Run(() => Initialize(port));
}
#endregion Constructors
#region Properties
/// <summary>
/// Gets the result of the asynchronous initialization of this instance.
/// </summary>
public Task Initialization { get; }
/// <summary>
/// Gets the UTC timestamp of the server start.
/// </summary>
......@@ -436,7 +441,9 @@ namespace AMWD.Modbus.Tcp.Server
}
if (!isDisposed)
{
ClientDisconnected?.Invoke(this, new ClientEventArgs((IPEndPoint)client.Client.RemoteEndPoint));
}
lock (tcpClients)
{
......
using System;
using AMWD.Modbus.Common.Util;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Text;
namespace UnitTests
{
[TestClass]
public class ChecksumTests
{
[TestMethod]
public void Crc16Test()
{
var bytes = Encoding.ASCII.GetBytes("0123456789");
var expected = new byte[] { 77, 67 };
var crc = bytes.CRC16();
CollectionAssert.AreEqual(expected, crc);
}
}
}
using System;
namespace UnitTests
{
public class DataBufferTests
{
public void Test1()
{
}
}
}
This diff is collapsed.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
namespace UnitTests
{
[TestClass]
public class StructureTests
{
public void CoilTest()
{
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.3.2" />
<PackageReference Include="MSTest.TestFramework" Version="1.3.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Modbus.Common\Modbus.Common.csproj" />
<ProjectReference Include="..\..\src\Modbus.Tcp\Modbus.Tcp.csproj" />
</ItemGroup>
</Project>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment