Commit 2351a773 authored by Andreas Müller's avatar Andreas Müller
Browse files

Refactoring of Modbus TCP

parent 130da16c
* -text
*.dll filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.exe filter=lfs diff=lfs merge=lfs -text
*.bmp filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text
\ No newline at end of file
# Project-specific files
setup/bin/
# User-specific files
*.suo
*.user
......
......@@ -9,6 +9,7 @@ build:
tags:
- docker
script:
- cat $NUGET_CONFIG > nuget.config
- dotnet restore
- dotnet build --no-restore -c Release
- dotnet nuget push -s amget -k $AMGET_API_KEY --skip-duplicate src/Modbus.Common/bin/Release/*.nupkg
......@@ -38,6 +39,7 @@ deploy:
only:
- tags
script:
- cat $NUGET_CONFIG > nuget.config
- dotnet restore
- dotnet build --no-restore -c Release
- dotnet nuget push -s nuget -k $NUGET_API_KEY --skip-duplicate src/Modbus.Common/bin/Release/*.nupkg
......
......@@ -6,12 +6,10 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0A7CE6D2-374F-4EBC-BFD3-DE2587A6701C}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitattributes = .gitattributes
.gitignore = .gitignore
.gitlab-ci.yml = .gitlab-ci.yml
CodeMaid.config = CodeMaid.config
LICENSE.txt = LICENSE.txt
nuget.config = nuget.config
README.md = README.md
EndProjectSection
EndProject
......
......@@ -32,7 +32,7 @@ float voltage = registers.GetSingle();
Console.WriteLine($"The voltage between L1 and N is: {voltage:N2}V");
```
For the people who have seen some devices; yes, it is a request to a Janitza device ;-).
For the people who have seen some devices: yes, it's a request to a Janitza device ;-).
## License
......
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
<add key="amget" value="https://nuget.am-wd.de/v3/index.json" />
</packageSources>
</configuration>
using System;
using Microsoft.Extensions.Logging;
namespace ConsoleDemo.Logger
{
// Inspired by https://github.com/aspnet/Logging/blob/master/src/Microsoft.Extensions.Logging.Console/ConsoleLogger.cs
internal class ConsoleLogger : ILogger
{
private readonly object syncObj = new object();
public ConsoleLogger()
{
}
public ConsoleLogger(string name)
{
Name = name;
}
public ConsoleLogger(string name, ConsoleLogger parentLogger)
{
Name = name;
ParentLogger = parentLogger;
}
public string Name { get; }
public ConsoleLogger ParentLogger { get; }
public bool DisableColors { get; set; }
public string TimestampFormat { get; set; }
public LogLevel MinLevel { get; set; }
internal IExternalScopeProvider ScopeProvider { get; }
public IDisposable BeginScope<TState>(TState state) => ScopeProvider?.Push(state) ?? NullScope.Instance;
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= MinLevel;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
if (formatter == null)
{
throw new ArgumentNullException(nameof(formatter));
}
string message = formatter(state, exception);
if (!string.IsNullOrEmpty(message) || exception != null)
{
if (ParentLogger != null)
{
ParentLogger.WriteMessage(Name, logLevel, eventId.Id, message, exception);
}
else
{
WriteMessage(Name, logLevel, eventId.Id, message, exception);
}
}
}
private void WriteMessage(string name, LogLevel logLevel, int _, string message, Exception exception)
{
if (exception != null)
{
if (!string.IsNullOrEmpty(message))
{
message += Environment.NewLine + exception.ToString();
}
else
{
message = exception.ToString();
}
}
bool changedColor;
string timestampPadding = "";
lock (syncObj)
{
if (!string.IsNullOrEmpty(TimestampFormat))
{
changedColor = false;
if (!DisableColors)
{
switch (logLevel)
{
case LogLevel.Trace:
Console.ForegroundColor = ConsoleColor.DarkGray;
changedColor = true;
break;
}
}
string timestamp = DateTime.Now.ToString(TimestampFormat) + " ";
Console.Write(timestamp);
timestampPadding = new string(' ', timestamp.Length);
if (changedColor)
{
Console.ResetColor();
}
}
changedColor = false;
if (!DisableColors)
{
switch (logLevel)
{
case LogLevel.Trace:
Console.ForegroundColor = ConsoleColor.DarkGray;
changedColor = true;
break;
case LogLevel.Information:
Console.ForegroundColor = ConsoleColor.DarkGreen;
changedColor = true;
break;
case LogLevel.Warning:
Console.ForegroundColor = ConsoleColor.Yellow;
changedColor = true;
break;
case LogLevel.Error:
Console.ForegroundColor = ConsoleColor.Black;
Console.BackgroundColor = ConsoleColor.Red;
changedColor = true;
break;
case LogLevel.Critical:
Console.ForegroundColor = ConsoleColor.White;
Console.BackgroundColor = ConsoleColor.Red;
changedColor = true;
break;
}
}
Console.Write(GetLogLevelString(logLevel));
if (changedColor)
{
Console.ResetColor();
}
changedColor = false;
if (!DisableColors)
{
switch (logLevel)
{
case LogLevel.Trace:
Console.ForegroundColor = ConsoleColor.DarkGray;
changedColor = true;
break;
}
}
Console.WriteLine(": " + (!string.IsNullOrEmpty(name) ? "[" + name + "] " : "") + message.Replace("\n", "\n " + timestampPadding));
if (changedColor)
{
Console.ResetColor();
}
}
}
private static string GetLogLevelString(LogLevel logLevel)
{
return logLevel switch
{
LogLevel.Trace => "trce",
LogLevel.Debug => "dbug",
LogLevel.Information => "info",
LogLevel.Warning => "warn",
LogLevel.Error => "fail",
LogLevel.Critical => "crit",
_ => throw new ArgumentOutOfRangeException(nameof(logLevel)),
};
}
}
public class ConsoleLoggerOptions
{
public bool DisableColors { get; set; } = false;
public string TimestampFormat { get; set; } = "yyyy-MM-dd HH:mm:ss.fff";
public LogLevel MinLevel { get; set; } = LogLevel.Information;
}
public class ConsoleLoggerProvider : ILoggerProvider
{
private readonly ConsoleLoggerOptions consoleLoggerOptions;
public ConsoleLoggerProvider(Action<ConsoleLoggerOptions> configure = null)
{
var options = new ConsoleLoggerOptions();
configure?.Invoke(options);
consoleLoggerOptions = options;
}
public ILogger CreateLogger(string categoryName)
{
return new ConsoleLogger(categoryName)
{
DisableColors = consoleLoggerOptions.DisableColors,
MinLevel = consoleLoggerOptions.MinLevel,
TimestampFormat = consoleLoggerOptions.TimestampFormat
};
}
public void Dispose()
{ }
}
/// <summary>
/// An empty scope without any logic
/// </summary>
public class NullScope : IDisposable
{
public static NullScope Instance { get; } = new NullScope();
private NullScope()
{
}
/// <inheritdoc />
public void Dispose()
{
}
}
}
......@@ -2,59 +2,34 @@
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AMWD.Modbus.Common;
using AMWD.Modbus.Common.Interfaces;
using AMWD.Modbus.Common.Structures;
using AMWD.Modbus.Common.Util;
using AMWD.Modbus.Serial;
using ConsoleDemo.Logger;
using Microsoft.Extensions.Logging;
using SerialClient = AMWD.Modbus.Serial.Client.ModbusClient;
using TcpClient = AMWD.Modbus.Tcp.Client.ModbusClient;
using SerialServer = AMWD.Modbus.Serial.Server.ModbusServer;
using TcpClient = AMWD.Modbus.Tcp.Client.ModbusClient;
using TcpServer = AMWD.Modbus.Tcp.Server.ModbusServer;
using System.Threading;
using System.Net;
using System.Net.Sockets;
namespace ConsoleDemo
{
internal class Program
{
private static bool run = true;
private static void Main(string[] args)
{
bool run = true;
Console.CancelKeyPress += (s, e) =>
{
run = false;
e.Cancel = true;
};
try
{
var client = new TcpClient("am-pi-dev", 502);
client.Connected += (s, e) =>
{
Console.WriteLine("Connected");
};
client.Disconnected += (s, e) =>
{
Console.WriteLine("Disconnected");
};
client.Connect().Wait();
while (run)
{
Console.WriteLine(client);
Thread.Sleep(1000);
}
client.Disconnect().Wait();
//ClientMainAsync(args).GetAwaiter().GetResult();
ClientMainAsync(args)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
//ServerMain(args);
}
catch (Exception ex)
......@@ -65,6 +40,19 @@ namespace ConsoleDemo
private static async Task ClientMainAsync(string[] _)
{
bool run = true;
Console.CancelKeyPress += (object _, ConsoleCancelEventArgs evArgs) =>
{
evArgs.Cancel = true;
run = false;
};
var logger = new ConsoleLogger()
{
MinLevel = LogLevel.Trace,
TimestampFormat = "HH:mm:ss.fff"
};
Console.WriteLine("Console Demo Modbus Client");
Console.WriteLine();
......@@ -83,7 +71,7 @@ namespace ConsoleDemo
Console.Write("Port: ");
int port = Convert.ToInt32(Console.ReadLine().Trim());
client = new TcpClient(host, port);
client = new TcpClient(host, port, logger);
}
break;
case 2:
......@@ -103,7 +91,7 @@ namespace ConsoleDemo
Console.Write("Handshake [0] None [1] X-On/Off [2] RTS [3] RTS+X-On/Off: ");
int handshake = Convert.ToInt32(Console.ReadLine().Trim());
Console.Write("Timeout: ");
Console.Write("Timeout (ms): ");
int timeout = Convert.ToInt32(Console.ReadLine().Trim());
Console.Write("Set Driver to RS485 [0] No [1] Yes: ");
......@@ -116,8 +104,8 @@ namespace ConsoleDemo
StopBits = (StopBits)stopBits,
Parity = (Parity)parity,
Handshake = (Handshake)handshake,
SendTimeout = timeout,
ReceiveTimeout = timeout
SendTimeout = TimeSpan.FromMilliseconds(timeout),
ReceiveTimeout = TimeSpan.FromMilliseconds(timeout)
};
if (setDriver == 1)
......@@ -306,6 +294,19 @@ namespace ConsoleDemo
private static void ServerMain(string[] _)
{
bool run = true;
Console.CancelKeyPress += (object _, ConsoleCancelEventArgs evArgs) =>
{
evArgs.Cancel = true;
run = false;
};
var logger = new ConsoleLogger()
{
MinLevel = LogLevel.Trace,
TimestampFormat = "HH:mm:ss.fff"
};
Console.WriteLine("Demo Modbus Server");
Console.WriteLine();
......@@ -315,14 +316,7 @@ namespace ConsoleDemo
IModbusServer server = null;
try
{
bool run = true;
Console.CancelKeyPress += (object _, ConsoleCancelEventArgs evArgs) =>
{
evArgs.Cancel = true;
run = false;
};
switch(cType)
switch (cType)
{
case 1:
{
......@@ -332,7 +326,7 @@ namespace ConsoleDemo
Console.Write("Port: ");
int port = Convert.ToInt32(Console.ReadLine().Trim());
var tcp = new TcpServer(port, ip)
var tcp = new TcpServer(port, ip, logger)
{
Timeout = TimeSpan.FromSeconds(3)
};
......
......@@ -31,12 +31,12 @@ namespace AMWD.Modbus.Common.Interfaces
/// <summary>
/// Gets or sets the send timeout in milliseconds. Default: 1000.
/// </summary>
int SendTimeout { get; set; }
TimeSpan SendTimeout { get; set; }
/// <summary>
/// Gets ors sets the receive timeout in milliseconds. Default: 1000;
/// </summary>
int ReceiveTimeout { get; set; }
TimeSpan ReceiveTimeout { get; set; }
#endregion Properties
......
......@@ -13,7 +13,7 @@ namespace AMWD.Modbus.Common.Util
/// </summary>
public static class Extensions
{
#region Register handling
#region Public extensions
#region To unsigned data types
......@@ -277,16 +277,10 @@ namespace AMWD.Modbus.Common.Util
#endregion To string
#endregion Register handling
#endregion Public extensions
#region Enums
#region Internal extensions
/// <summary>
/// Tries to return an attribute of an enum value.
/// </summary>
/// <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>
internal static T GetAttribute<T>(this Enum enumValue)
where T : Attribute
{
......@@ -299,20 +293,11 @@ namespace AMWD.Modbus.Common.Util
return default;
}
/// <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>
internal static string GetDescription(this Enum enumValue)
{
return enumValue.GetAttribute<DescriptionAttribute>()?.Description ?? enumValue.ToString();
}
#endregion Enums
#region ReaderWriterLockSlim
internal static IDisposable GetReadLock(this ReaderWriterLockSlim rwLock, int millisecondsTimeout = -1)
{
if (!rwLock.TryEnterReadLock(millisecondsTimeout))
......@@ -401,6 +386,6 @@ namespace AMWD.Modbus.Common.Util
}
}
#endregion ReaderWriterLockSlim
#endregion Internal extensions
}
}
......@@ -35,8 +35,8 @@ namespace AMWD.Modbus.Serial.Client
private Parity parity = Parity.None;
private StopBits stopBits = StopBits.None;
private Handshake handshake = Handshake.None;
private int sendTimeout = 1000;
private int receiveTimeout = 1000;
private TimeSpan sendTimeout = TimeSpan.FromSeconds(1);
private TimeSpan receiveTimeout = TimeSpan.FromSeconds(1);
private int bufferSize = 0;
// driver switch
......@@ -207,7 +207,7 @@ namespace AMWD.Modbus.Serial.Client
/// <summary>
/// Gets or sets the send timeout in milliseconds. Default 1000 (recommended).
/// </summary>
public int SendTimeout
public TimeSpan SendTimeout
{
get
{
......@@ -218,14 +218,14 @@ namespace AMWD.Modbus.Serial.Client
sendTimeout = value;
if (serialPort != null)
serialPort.WriteTimeout = value;
serialPort.WriteTimeout = (int)value.TotalMilliseconds;
}
}
/// <summary>
/// Gets or sets the receive timeout in milliseconds. Default 1000 (recommended).
/// </summary>
public int ReceiveTimeout
public TimeSpan ReceiveTimeout
{
get
{
......@@ -236,7 +236,7 @@ namespace AMWD.Modbus.Serial.Client
receiveTimeout = value;
if (serialPort != null)
serialPort.ReadTimeout = value;
serialPort.ReadTimeout = (int)value.TotalMilliseconds;
}
}
......@@ -1079,8 +1079,8 @@ namespace AMWD.Modbus.Serial.Client
Parity = Parity,
StopBits = StopBits,
Handshake = Handshake,
ReadTimeout = ReceiveTimeout,
WriteTimeout = SendTimeout
ReadTimeout = (int)ReceiveTimeout.TotalMilliseconds,
WriteTimeout = (int)SendTimeout.TotalMilliseconds
};
if (bufferSize > 0)
......
This diff is collapsed.
......@@ -182,16 +182,16 @@ namespace AMWD.Modbus.Tcp.Protocol
TransactionId = buffer.GetUInt16(0);
ushort ident = buffer.GetUInt16(2);
if (ident != 0)
throw new ArgumentException("Protocol ident not valid");
throw new ArgumentException("Protocol identifier not valid.");
ushort length = buffer.GetUInt16(4);
if (buffer.Length < length + 6)
throw new ArgumentException("Too less data");
throw new ArgumentException("Too less data.");
if (buffer.Length > length + 6)
{
if (buffer.Buffer.Skip(length + 6).Any(b => b != 0))
throw new ArgumentException("Too many data");
throw new ArgumentException("Too many data.");
buffer = new DataBuffer(bytes.Take(length + 6));
}
......@@ -231,11 +231,11 @@ namespace AMWD.Modbus.Tcp.Protocol
MEIObject = (DeviceIDObject)buffer.GetByte(10);
break;
default:
throw new NotImplementedException();
throw new NotImplementedException($"Unknown MEI type: {MEIType}.");
}
break;
default:
throw new NotImplementedException();
throw new NotImplementedException($"Unknown function code: {Function}.");
}
}
......