Commit 1b12bd7d authored by Andreas Müller's avatar Andreas Müller

Updated References, Added ILogger to TCP-client. Other following.

parent fa6cf8c9
Pipeline #19 passed with stage
in 2 minutes and 38 seconds
......@@ -5,6 +5,10 @@
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Modbus.Tcp\Modbus.Tcp.csproj" />
</ItemGroup>
......
......@@ -52,7 +52,8 @@
<DocumentationFile>$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.2.2-beta" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.2.5" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
......
using System;
using AMWD.Modbus.Common.Util;
using System;
namespace AMWD.Modbus.Common.Structures
{
......@@ -32,5 +33,15 @@ namespace AMWD.Modbus.Common.Structures
public ModbusException(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Gets or sets the error/exception code.
/// </summary>
public ErrorCode ErrorCode { get; set; }
/// <summary>
/// Gets the error message.
/// </summary>
public string ErrorMessage => ErrorCode.GetDescription();
}
}
......@@ -53,8 +53,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="System.IO.Ports" Version="4.5.0" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.2.2-beta" PrivateAssets="all" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.2.5" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
......
......@@ -3,6 +3,7 @@ using AMWD.Modbus.Common.Interfaces;
using AMWD.Modbus.Common.Structures;
using AMWD.Modbus.Common.Util;
using AMWD.Modbus.Tcp.Protocol;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
......@@ -20,6 +21,8 @@ namespace AMWD.Modbus.Tcp.Client
{
#region Fields
private ILogger<ModbusClient> logger;
private volatile bool isReconnecting;
private volatile bool isStarted;
......@@ -55,8 +58,11 @@ namespace AMWD.Modbus.Tcp.Client
/// </summary>
/// <param name="host">The remote host name or ip.</param>
/// <param name="port">The remote port.</param>
public ModbusClient(string host, int port = 502)
/// <param name="logger"><see cref="ILogger"/> instance to write log entries.</param>
public ModbusClient(string host, int port = 502, ILogger<ModbusClient> logger = null)
{
this.logger = logger;
if (string.IsNullOrWhiteSpace(host))
{
throw new ArgumentNullException(nameof(host));
......@@ -149,6 +155,7 @@ namespace AMWD.Modbus.Tcp.Client
/// <returns>An awaitable task.</returns>
public Task Connect()
{
logger?.LogTrace("ModbusClient.Connect");
if (isDisposed)
{
throw new ObjectDisposedException(GetType().FullName);
......@@ -156,6 +163,7 @@ namespace AMWD.Modbus.Tcp.Client
if (!isStarted)
{
logger?.LogInformation("ModbusClient starting");
isStarted = true;
cts = new CancellationTokenSource();
ConnectingTask = Task.Run(() => Reconnect());
......@@ -169,6 +177,7 @@ namespace AMWD.Modbus.Tcp.Client
/// <returns>An awaitable task.</returns>
public async Task Disconnect()
{
logger?.LogTrace("ModbusClient.Disconnect");
if (isDisposed)
{
throw new ObjectDisposedException(GetType().FullName);
......@@ -176,18 +185,26 @@ namespace AMWD.Modbus.Tcp.Client
if (isStarted)
{
logger?.LogInformation("ModbusClient stopping");
var connected = IsConnected;
try
{
cts.Cancel();
cts.Cancel();
await ConnectingTask;
await ConnectingTask;
tcpClient.Dispose();
tcpClient = null;
tcpClient.Dispose();
tcpClient = null;
if (connected)
if (connected)
{
logger?.LogTrace("ModbusClient.Disconnect fire disconnected event.");
Task.Run(() => Disconnected?.Invoke(this, EventArgs.Empty)).Forget();
}
}
catch (OperationCanceledException ex)
{
Task.Run(() => Disconnected?.Invoke(this, EventArgs.Empty)).Forget();
logger?.LogDebug(ex, "ModbusClient.Disconnect was (re)connecting?");
}
isStarted = false;
......@@ -242,7 +259,10 @@ namespace AMWD.Modbus.Tcp.Client
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
throw new ModbusException(response.ErrorMessage)
{
ErrorCode = response.ErrorCode
};
}
if (request.TransactionId != response.TransactionId)
{
......@@ -264,12 +284,14 @@ namespace AMWD.Modbus.Tcp.Client
});
}
}
catch (SocketException)
catch (SocketException sex)
{
logger?.LogWarning(sex, "Reading coils. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
catch (IOException)
catch (IOException ioex)
{
logger?.LogWarning(ioex, "Reading coils. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
......@@ -320,7 +342,10 @@ namespace AMWD.Modbus.Tcp.Client
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
throw new ModbusException(response.ErrorMessage)
{
ErrorCode = response.ErrorCode
};
}
if (request.TransactionId != response.TransactionId)
{
......@@ -342,12 +367,14 @@ namespace AMWD.Modbus.Tcp.Client
});
}
}
catch (SocketException)
catch (SocketException sex)
{
logger?.LogWarning(sex, "Reading discrete inputs. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
catch (IOException)
catch (IOException ioex)
{
logger?.LogWarning(ioex, "Reading discrete inputs. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
......@@ -398,7 +425,10 @@ namespace AMWD.Modbus.Tcp.Client
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
throw new ModbusException(response.ErrorMessage)
{
ErrorCode = response.ErrorCode
};
}
if (request.TransactionId != response.TransactionId)
{
......@@ -416,12 +446,14 @@ namespace AMWD.Modbus.Tcp.Client
});
}
}
catch (SocketException)
catch (SocketException sex)
{
logger?.LogWarning(sex, "Reading holding registers. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
catch (IOException)
catch (IOException ioex)
{
logger?.LogWarning(ioex, "Reading holding registers. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
......@@ -472,7 +504,10 @@ namespace AMWD.Modbus.Tcp.Client
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
throw new ModbusException(response.ErrorMessage)
{
ErrorCode = response.ErrorCode
};
}
if (request.TransactionId != response.TransactionId)
{
......@@ -490,12 +525,14 @@ namespace AMWD.Modbus.Tcp.Client
});
}
}
catch (SocketException)
catch (SocketException sex)
{
logger?.LogWarning(sex, "Reading input registers. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
catch (IOException)
catch (IOException ioex)
{
logger?.LogWarning(ioex, "Reading input registers. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
......@@ -546,11 +583,14 @@ namespace AMWD.Modbus.Tcp.Client
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
throw new SocketException((int)SocketError.TimedOut);
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
throw new ModbusException(response.ErrorMessage)
{
ErrorCode = response.ErrorCode
};
}
if (request.TransactionId != response.TransactionId)
{
......@@ -563,12 +603,14 @@ namespace AMWD.Modbus.Tcp.Client
request.Address == response.Address &&
request.Data.Equals(response.Data);
}
catch (SocketException)
catch (SocketException sex)
{
logger?.LogWarning(sex, "Writing single coil. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
catch (IOException)
catch (IOException ioex)
{
logger?.LogWarning(ioex, "Writing single coil. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
......@@ -613,11 +655,14 @@ namespace AMWD.Modbus.Tcp.Client
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
throw new SocketException((int)SocketError.TimedOut);
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
throw new ModbusException(response.ErrorMessage)
{
ErrorCode = response.ErrorCode
};
}
if (request.TransactionId != response.TransactionId)
{
......@@ -630,12 +675,14 @@ namespace AMWD.Modbus.Tcp.Client
request.Address == response.Address &&
request.Data.Equals(response.Data);
}
catch (SocketException)
catch (SocketException sex)
{
logger?.LogWarning(sex, "Writing single register. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
catch (IOException)
catch (IOException ioex)
{
logger?.LogWarning(ioex, "Writing single register. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
......@@ -709,11 +756,14 @@ namespace AMWD.Modbus.Tcp.Client
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
throw new SocketException((int)SocketError.TimedOut);
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
throw new ModbusException(response.ErrorMessage)
{
ErrorCode = response.ErrorCode
};
}
if (request.TransactionId != response.TransactionId)
{
......@@ -724,12 +774,14 @@ namespace AMWD.Modbus.Tcp.Client
request.Address == response.Address &&
request.Count == response.Count;
}
catch (SocketException)
catch (SocketException sex)
{
logger?.LogWarning(sex, "Writing multiple coils. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
catch (IOException)
catch (IOException ioex)
{
logger?.LogWarning(ioex, "Writing multiple coils. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
......@@ -795,11 +847,14 @@ namespace AMWD.Modbus.Tcp.Client
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
throw new SocketException((int)SocketError.TimedOut);
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
throw new ModbusException(response.ErrorMessage)
{
ErrorCode = response.ErrorCode
};
}
if (request.TransactionId != response.TransactionId)
{
......@@ -810,12 +865,14 @@ namespace AMWD.Modbus.Tcp.Client
request.Address == response.Address &&
request.Count == response.Count;
}
catch (SocketException)
catch (SocketException sex)
{
logger?.LogWarning(sex, "Writing multiple registers. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
catch (IOException)
catch (IOException ioex)
{
logger?.LogWarning(ioex, "Writing multiple registers. Reconnecting.");
ConnectingTask = Task.Run(() => Reconnect());
}
......@@ -848,6 +905,7 @@ namespace AMWD.Modbus.Tcp.Client
}
if (wasConnected)
{
logger?.LogDebug("ModbusClient.Reconnect fire disconnected event.");
Task.Run(() => Disconnected?.Invoke(this, EventArgs.Empty)).Forget();
}
......@@ -864,15 +922,18 @@ namespace AMWD.Modbus.Tcp.Client
var connectTask = tcpClient.ConnectAsync(Host, Port);
if (await Task.WhenAny(connectTask, Task.Delay(TimeSpan.FromSeconds(timeout), cts.Token)) == connectTask)
{
logger?.LogInformation("ModbusClient.Reconnect connected.");
tcpClient.SendTimeout = SendTimeout;
tcpClient.ReceiveTimeout = ReceiveTimeout;
}
else if (cts.Token.IsCancellationRequested)
{
logger?.LogWarning("ModbusClient.Reconnect was cancelled.");
return;
}
else
{
logger?.LogWarning($"ModbusClient.Reconnect failed to connect within {timeout} seconds.");
timeout += 2;
if (timeout > maxTimeout)
{
......@@ -888,6 +949,7 @@ namespace AMWD.Modbus.Tcp.Client
}
catch (Exception ex)
{
logger?.LogError(ex, "ModbusClient.Reconnect failed.");
reconnectFailed = true;
if (isDisposed)
{
......@@ -904,6 +966,7 @@ namespace AMWD.Modbus.Tcp.Client
}
wasConnected = true;
logger?.LogDebug("ModbusClient.Reconnect fire connected event.");
Task.Run(() => Connected?.Invoke(this, EventArgs.Empty)).Forget();
break;
}
......@@ -913,16 +976,20 @@ namespace AMWD.Modbus.Tcp.Client
private async Task<Response> SendRequest(Request request)
{
logger?.LogTrace("ModbusClient.SendRequest");
if (!IsConnected)
{
throw new InvalidOperationException("No connection");
}
logger?.LogTrace(request.ToString());
var stream = tcpClient.GetStream();
var bytes = request.Serialize();
var writeTask = stream.WriteAsync(bytes, 0, bytes.Length, cts.Token);
if (await Task.WhenAny(writeTask, Task.Delay(SendTimeout, cts.Token)) == writeTask && !cts.Token.IsCancellationRequested)
{
logger?.LogTrace($"{bytes.Length} bytes sent");
var responseBytes = new List<byte>();
var buffer = new byte[6];
var readTask = stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
......@@ -930,6 +997,7 @@ namespace AMWD.Modbus.Tcp.Client
{
var count = await readTask;
responseBytes.AddRange(buffer.Take(count));
logger?.LogTrace($"{count} bytes received at first");
bytes = new byte[2];
Array.Copy(buffer, 4, bytes, 0, 2);
......@@ -938,20 +1006,25 @@ namespace AMWD.Modbus.Tcp.Client
Array.Reverse(bytes);
}
int following = BitConverter.ToUInt16(bytes, 0);
logger?.LogTrace($"{following} bytes following");
do
{
buffer = new byte[following];
count = await stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
logger?.LogTrace($"{count} following bytes received");
following -= count;
responseBytes.AddRange(buffer.Take(count));
}
while (following > 0 && !cts.Token.IsCancellationRequested);
logger?.LogTrace($"Response received");
return new Response(responseBytes.ToArray());
}
}
logger?.LogWarning("ModbusClient.SendRequest failed to send");
return new Response(new byte[] { 0, 0, 0, 0, 0, 0 });
}
......
......@@ -52,7 +52,8 @@
<DocumentationFile>$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.2.2-beta" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.2.5" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
......
......@@ -541,7 +541,7 @@ namespace UnitTests
Console.WriteLine($"Server response: {response?.Length ?? -1}");
if (response != null)
{
await stream.WriteAsync(response, ct);
await stream.WriteAsync(response, 0, response.Length, ct);
Console.WriteLine("Server response written");
}
}
......
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>netcoreapp2.0</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" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
</ItemGroup>
<ItemGroup>
......
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