Compare commits

...

2 Commits

Author SHA1 Message Date
Maier Stephan SI
b7055bc31c Sicherung 2024-08-27 07:31:12 +02:00
Maier Stephan SI
bc23a8514f Tagessicherung 2024-07-22 17:01:21 +03:00
10 changed files with 290 additions and 60 deletions

View File

@@ -8,6 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Config.Net" Version="5.2.0" /> <PackageReference Include="Config.Net" Version="5.2.0" />
<PackageReference Include="GuerrillaNtp" Version="3.1.0" /> <PackageReference Include="GuerrillaNtp" Version="3.1.0" />
<PackageReference Include="Json.Net" Version="1.0.33" /> <PackageReference Include="Json.Net" Version="1.0.33" />

View File

@@ -1,5 +1,4 @@
using FSI.BT.IR.Plc.TimeSync; using FSI.BT.IR.Plc.TimeSync;
using FSI.BT.IR.Plc.TimeSync.Settings;
using static FSI.BT.IR.Plc.TimeSync.Settings.Context; using static FSI.BT.IR.Plc.TimeSync.Settings.Context;
var builder = Host.CreateApplicationBuilder(args); var builder = Host.CreateApplicationBuilder(args);

View File

@@ -1,5 +1,6 @@
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using CommandLine;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using static FSI.BT.IR.Plc.TimeSync.Settings.Context; using static FSI.BT.IR.Plc.TimeSync.Settings.Context;
@@ -7,27 +8,23 @@ namespace FSI.BT.IR.Plc.TimeSync.Settings
{ {
public class AppContext : ISettings public class AppContext : ISettings
{ {
private Cfg _cfg; private Cfg _cfg;
private string[] _args;
public AppContext() public AppContext()
{ {
_args = new string[0];
LoadSettings(); LoadSettings();
LoadCfg(); LoadCfg();
} }
private void LoadSettings() private void LoadSettings()
{ {
var values = new ConfigurationBuilder() var values = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.Build(); .Build();
Settings = values.Get<Context.Settings>(); Settings = values.Get<Context.Settings>();
var tmo = Settings.Logging.LogLevel.MicrosoftHostingLifetime;
var verstion = Settings.Version.ToString();
} }
private void LoadCfg() private void LoadCfg()

View File

@@ -0,0 +1,12 @@
using CommandLine;
namespace FSI.BT.IR.Plc.TimeSync.Settings
{
public class ConsoleArgs
{
[Option('v', "version", Required = false, HelpText = "Version")]
public bool Version { get; set; }
}
}

View File

@@ -1,41 +1,106 @@
using System.ComponentModel; using System.ComponentModel;
using System.Configuration;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace FSI.BT.IR.Plc.TimeSync.Settings namespace FSI.BT.IR.Plc.TimeSync.Settings
{ {
/// <summary>
/// Datenbasis von config.json und appsettings.json
/// Einstellungen und Konfigurationsdaten
/// </summary>
public class Context public class Context
{ {
public interface ISettings : INotifyPropertyChanged public interface ISettings : INotifyPropertyChanged
{ {
/// <summary>
/// Daten von appsettings.json
/// </summary>
public Settings Settings { get; set; } public Settings Settings { get; set; }
/// <summary>
/// Daten von config.json
/// </summary>
public Cfg Cfg { get; set; } public Cfg Cfg { get; set; }
} }
/// <summary>
/// Daten von appsettings.json
/// </summary>
public record Settings public record Settings
{ {
/// <summary>
/// Versions-Informationen
/// </summary>
public Version Version { get; set; } public Version Version { get; set; }
/// <summary>
/// Build-Informationen
/// </summary>
public Build Build { get; set; }
/// <summary>
/// Logging-Einstellungen
/// </summary>
public Logging Logging { get; set; } public Logging Logging { get; set; }
} }
/// <summary>
/// Versions-Informationen
/// </summary>
public record Version public record Version
{ {
/// <summary>
/// Haupt-Versionsnummer
/// </summary>
public uint Major { get; set; } = 0; public uint Major { get; set; } = 0;
/// <summary>
/// Unter-Versionsnummer
/// </summary>
public uint Minor { get; set; } = 0; public uint Minor { get; set; } = 0;
/// <summary>
/// Patch/Hotfix
/// </summary>
public uint Patch { get; set; } = 0; public uint Patch { get; set; } = 0;
/// <summary>
/// optinoale Versionsinformationen
/// </summary>
public string? Optional { get; set; } public string? Optional { get; set; }
/// <summary>
/// gibt die Versions-Nummer zurück
/// </summary>
/// <returns>Versionsnummer</returns>
public override string ToString() public override string ToString()
{ {
return Major.ToString() + "." + Minor.ToString() + "." + Patch.ToString() + ((Optional == string.Empty || Optional == null) ? "" : "-" + Optional); return Major.ToString() + "." + Minor.ToString() + "." + Patch.ToString() + ((Optional == string.Empty || Optional == null) ? "" : "-" + Optional);
} }
}
/// <summary>
/// Build-Informationen
/// </summary>
public record Build
{
/// <summary>
/// Ersteller
/// </summary>
public string Creator { get; set; }
/// <summary>
/// Organisation
/// </summary>
public string Organization { get; set; }
/// <summary>
/// Erstellungsjahr
/// </summary>
public int CreationYear { get; set; }
/// <summary>
/// Beschreibung
/// </summary>
public string Description { get; set; }
} }
public class Logging public class Logging
@@ -43,6 +108,9 @@ namespace FSI.BT.IR.Plc.TimeSync.Settings
public Loglevel LogLevel { get; set; } public Loglevel LogLevel { get; set; }
} }
/// <summary>
/// Logging-Einstellungen
/// </summary>
public class Loglevel public class Loglevel
{ {
public string Default { get; set; } public string Default { get; set; }
@@ -51,45 +119,119 @@ namespace FSI.BT.IR.Plc.TimeSync.Settings
public string MicrosoftHostingLifetime { get; set; } public string MicrosoftHostingLifetime { get; set; }
} }
/// <summary>
/// Daten von config.json
/// </summary>
public record Cfg public record Cfg
{ {
/// <summary>
/// Spsen, deren Zeit mit NTP-Server syncronsiert werden sollen
/// </summary>
public List<Plc> Plcs { get; set; } public List<Plc> Plcs { get; set; }
/// <summary>
/// NTP-Server Adresse
/// </summary>
public string NtpServer { get; set; } public string NtpServer { get; set; }
} }
/// <summary>
/// SPS-Daten
/// </summary>
public record Plc : IPlc public record Plc : IPlc
{ {
/// <summary>
/// Name
/// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// Beschreibung
/// </summary>
public string Description { get; set; } public string Description { get; set; }
/// <summary>
/// IP-Adresse
/// </summary>
public string Adress { get; set; } public string Adress { get; set; }
/// <summary>
/// Rack-Nummer
/// </summary>
public int Rack { get; set; } public int Rack { get; set; }
/// <summary>
/// Slot-Nummer
/// </summary>
public int Slot { get; set; } public int Slot { get; set; }
/// <summary>
/// Update-Intervall, in der die Zeit überprüft werden soll.
/// </summary>
public int UpdateIntervall { get; set; } public int UpdateIntervall { get; set; }
/// <summary>
/// Zeitdifferenz, ab der die SPS-Zeit angepasst werden soll.
/// </summary>
public int TimeDifference { get; set; } public int TimeDifference { get; set; }
/// <summary>
/// Locale Zeit wird an die SPS gesendet - nicht UTC-Zeit
/// </summary>
public bool LocalTime { get; set; } public bool LocalTime { get; set; }
/// <summary>
/// Soll SPS-Zeit synchronisiert werden
/// </summary>
public bool Enable { get; set; } public bool Enable { get; set; }
} }
public interface IPlc public interface IPlc
{ {
/// <summary>
/// Name
/// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// Beschreibung
/// </summary>
public string Description { get; set; } public string Description { get; set; }
/// <summary>
/// IP-Adresse
/// </summary>
public string Adress { get; set; } public string Adress { get; set; }
/// <summary>
/// Rack-Nummer
/// </summary>
public int Rack { get; set; } public int Rack { get; set; }
/// <summary>
/// Slot-Nummer
/// </summary>
public int Slot { get; set; } public int Slot { get; set; }
/// <summary>
/// Update-Intervall, in der die Zeit überprüft werden soll.
/// </summary>
public int UpdateIntervall { get; set; } public int UpdateIntervall { get; set; }
/// <summary>
/// Zeitdifferenz, ab der die SPS-Zeit angepasst werden soll.
/// </summary>
public int TimeDifference { get; set; } public int TimeDifference { get; set; }
/// <summary>
/// Locale Zeit wird an die SPS gesendet - nicht UTC-Zeit
/// </summary>
public bool LocalTime { get; set; } public bool LocalTime { get; set; }
/// <summary>
/// Soll SPS-Zeit synchronisiert werden
/// </summary>
public bool Enable { get; set; } public bool Enable { get; set; }
} }
} }

View File

@@ -3,13 +3,17 @@ using Sharp7;
using GuerrillaNtp; using GuerrillaNtp;
using static FSI.BT.IR.Plc.TimeSync.Settings.Context; using static FSI.BT.IR.Plc.TimeSync.Settings.Context;
namespace FSI.BT.IR.Plc.TimeSync namespace FSI.BT.IR.Plc.TimeSync
{ {
internal class SyncPlcTime : IPlc internal class SyncPlcTime : IPlc
{ {
private Logger _log = LogManager.GetCurrentClassLogger(); #region Constants
private const string DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm:ss.fff";
private const string DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm:ss.fff"; // Zeitformat
#endregion
private Logger _log = LogManager.GetCurrentClassLogger(); // Nlog
public SyncPlcTime(IPlc plc) public SyncPlcTime(IPlc plc)
{ {
@@ -24,80 +28,137 @@ namespace FSI.BT.IR.Plc.TimeSync
Enable = plc.Enable; Enable = plc.Enable;
} }
/// <summary>
/// Name
/// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// Beschreibung
/// </summary>
public string Description { get; set; } public string Description { get; set; }
/// <summary>
/// IP-Adresse
/// </summary>
public string Adress { get; set; } public string Adress { get; set; }
/// <summary>
/// Rack-Nummer
/// </summary>
public int Rack { get; set; } public int Rack { get; set; }
/// <summary>
/// Slot-Nummer
/// </summary>
public int Slot { get; set; } public int Slot { get; set; }
/// <summary>
/// Update-Intervall, in der die Zeit überprüft werden soll.
/// </summary>
public int UpdateIntervall { get; set; } public int UpdateIntervall { get; set; }
/// <summary>
/// Zeitdifferenz, ab der die SPS-Zeit angepasst werden soll.
/// </summary>
public int TimeDifference { get; set; } public int TimeDifference { get; set; }
/// <summary>
/// Locale Zeit wird an die SPS gesendet - nicht UTC-Zeit
/// </summary>
public bool LocalTime { get; set; } public bool LocalTime { get; set; }
/// <summary>
/// Soll SPS-Zeit synchronisiert werden
/// </summary>
public bool Enable { get; set; } public bool Enable { get; set; }
/// <summary>
/// NTP-Server Adresse
/// </summary>
public string NtpServer { get; set; } public string NtpServer { get; set; }
public async Task Snyc(CancellationToken cancellationToken) => public async Task Snyc(CancellationToken cancellationToken) =>
await Task.Run(async () => await Task.Run(async () =>
{ {
var plc = new S7Client(); // SPS-Verbindung
var client = new NtpClient(NtpServer); // NTP-Client
NtpClock clock = client.Query(); // NTP-Client Uhrzeit
var plcDateTime = new DateTime(); // Uhrzeit SPS
DateTime ntpDateTime; // Uhrzeit NTP-Client
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var plc = new S7Client(); try
var connectionRslt = plc.ConnectTo(Adress, Rack, Slot);
if (connectionRslt == 0)
{ {
_log.Debug(Name + " Verbindung hergestellt."); // Verbindung mit SPS-Aufbauen
var connectionRslt = plc.ConnectTo(Adress, Rack, Slot);
// Verbindungsstatus überprüfen
if (connectionRslt == 0) // Verbindung i.O.
{
_log.Debug(Name + " Verbindung hergestellt.");
}
else // Verbindung n.i.O.
{
_log.Error(Name + " Verbindung nicht hergestellt.");
_log.Error(Name + " Fehler: " + plc.ErrorText(connectionRslt));
await Task.Delay(UpdateIntervall, cancellationToken); // Warten bis zum nächsten Verbindungsversuch (Zeiten aus config.json)
continue;
}
} }
else catch (Exception ex)
{ {
_log.Error(Name + " Verbindung nicht hergestellt."); _log.Error(Name + " " + ex.Message);
_log.Error(Name + " Fehler: " + plc.ErrorText(connectionRslt));
return;
} }
var plcDateTime = new DateTime(); plc.GetPlcDateTime(ref plcDateTime); // Uhrzeit aus SPS auslesen
plc.GetPlcDateTime(ref plcDateTime);
_log.Debug(Name + " SPS Zeit (aktuell): " + plcDateTime.ToString(DATE_TIME_FORMAT)); _log.Debug(Name + " SPS Zeit (aktuell): " + plcDateTime.ToString(DATE_TIME_FORMAT));
var client = new NtpClient(NtpServer); if (LocalTime) // lokale Zeit/Ortszeit
NtpClock clock = client.Query();
DateTime ntpDateTime;
if (LocalTime)
{ {
_log.Debug(Name + " UTC Zeit: " + clock.Now.ToString(DATE_TIME_FORMAT)); _log.Debug(Name + " Ortszeit: " + clock.Now.ToString(DATE_TIME_FORMAT));
ntpDateTime = clock.Now.DateTime; ntpDateTime = clock.Now.DateTime; // lokale Zeit/Ortszeit von NTP-Server
} }
else else // UTC - Zeit
{ {
_log.Debug(Name + " Ortszeit: " + clock.UtcNow.ToString(DATE_TIME_FORMAT)); _log.Debug(Name + " UTC-Zeit: " + clock.UtcNow.ToString(DATE_TIME_FORMAT));
ntpDateTime = clock.UtcNow.DateTime; ntpDateTime = clock.UtcNow.DateTime; // UTC-Zeit von NTP-Server
} }
// Zeitdiffernz zwischen SPS-Zeit und Zeit von NTP-Server berechnen
var timeSpan = Math.Abs((plcDateTime - ntpDateTime).TotalMilliseconds); var timeSpan = Math.Abs((plcDateTime - ntpDateTime).TotalMilliseconds);
if (TimeDifference > 0 && timeSpan >= TimeDifference)
if (
TimeDifference > 0 // Zeitdifferenz aus Einstellungen > 0 ms
&& timeSpan >= TimeDifference // Zeitdifferenz überprüfung
)
{ {
_log.Debug(Name + " Zeitdifferenz " + timeSpan + " ms überschritten"); _log.Debug(Name + " Zeitdifferenz " + timeSpan + " ms überschritten");
var temp = plc.SetPlcDateTime(ntpDateTime); // Zeit an SPS senden
_log.Info(Name + " neue Zeit: " + ntpDateTime.ToString(DATE_TIME_FORMAT)); _log.Info(Name + " neue Zeit: " + ntpDateTime.ToString(DATE_TIME_FORMAT));
var temp = plc.SetPlcDateTime(ntpDateTime);
} }
else if (TimeDifference == 0) else if (TimeDifference == 0)
{ {
var temp = plc.SetPlcDateTime(ntpDateTime); // Zeit an SPS senden
_log.Info(Name + " neue Zeit: " + ntpDateTime.ToString(DATE_TIME_FORMAT)); _log.Info(Name + " neue Zeit: " + ntpDateTime.ToString(DATE_TIME_FORMAT));
var temp = plc.SetPlcDateTime(ntpDateTime);
} }
plc.Disconnect(); plc.Disconnect(); // Verbindung zur SPS trennen
_log.Debug(Name + " Verbindung getrennt."); _log.Debug(Name + " Verbindung getrennt.");
await Task.Delay(UpdateIntervall, cancellationToken); _log.Debug(Name + " Start Task-Wartezeit");
try
{
await Task.Delay(UpdateIntervall, cancellationToken); // Warten bis zum nächsten Verbindungsversuch (Zeiten aus config.json)
}
catch (Exception ex)
{
_log.Error(Name + " " + ex.Message);
}
_log.Debug(Name + " Ende Task-Wartezeit");
} }
}, cancellationToken); }, cancellationToken);
} }
} }

View File

@@ -10,22 +10,34 @@ namespace FSI.BT.IR.Plc.TimeSync
private CancellationTokenSource _tokenSource; private CancellationTokenSource _tokenSource;
private CancellationToken _stoppingToken; private CancellationToken _stoppingToken;
/// <summary>
/// Standard Taks
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
settings.PropertyChanged += Settings_PropertyChanged; settings.PropertyChanged += Settings_PropertyChanged; // Event, bei <20>nderungen an der Config-Datei
StartTasks(); StartTasks(); // Tasks, die beim Start ausgef<65>hrt werden
} }
/// <summary>
/// Event, bei <20>nderungen an der config.json.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Settings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) private void Settings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{ {
StopTasks(); StopTasks();
StartTasks(); StartTasks();
} }
/// <summary>
/// Tasks, die beim Start ausgef<65>hrt werden sollen.
/// </summary>
private void StartTasks() private void StartTasks()
{ {
@@ -36,10 +48,10 @@ namespace FSI.BT.IR.Plc.TimeSync
_tokenSource = new CancellationTokenSource(); _tokenSource = new CancellationTokenSource();
_stoppingToken = _tokenSource.Token; _stoppingToken = _tokenSource.Token;
// Schleife <20>ber alle Spsen
foreach (var plc in settings.Cfg.Plcs) foreach (var plc in settings.Cfg.Plcs)
{ {
if (plc.Enable)
if (plc.Enable)
{ {
var timeSync = new SyncPlcTime(plc); var timeSync = new SyncPlcTime(plc);
timeSync.NtpServer = settings.Cfg.NtpServer; timeSync.NtpServer = settings.Cfg.NtpServer;
@@ -51,11 +63,15 @@ namespace FSI.BT.IR.Plc.TimeSync
} }
/// <summary>
/// alle Taks werden gestoppt.
/// </summary>
private async void StopTasks() private async void StopTasks()
{ {
_tokenSource.Cancel(); _tokenSource.Cancel();
_stoppingToken = _tokenSource.Token; _stoppingToken = _tokenSource.Token;
// Schleife <20>ber alle Spsen
foreach (var task in _taskList) foreach (var task in _taskList)
{ {
task.Snyc(_stoppingToken); task.Snyc(_stoppingToken);
@@ -65,7 +81,5 @@ namespace FSI.BT.IR.Plc.TimeSync
_taskList?.Clear(); _taskList?.Clear();
_taskList = null; _taskList = null;
} }
} }
} }

View File

@@ -3,7 +3,13 @@
"Major": 0, "Major": 0,
"Minor": 0, "Minor": 0,
"Patch": 0, "Patch": 0,
"Optional": "alpha" "Optional": "alpha"
},
"Build": {
"Creator": "Stephan Maier",
"Organization": "Fondium Singen GmbH",
"CreationYear": "2024",
"Description": ""
}, },
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {

View File

@@ -1,5 +1,4 @@
{ {
"Plcs": [ "Plcs": [
{ {
"Name": "PL1 FA", "Name": "PL1 FA",
@@ -25,5 +24,4 @@
} }
], ],
"NtpServer": "10.10.199.41" "NtpServer": "10.10.199.41"
} }

View File

@@ -12,7 +12,7 @@
<target xsi:type="File" <target xsi:type="File"
name="logfile" name="logfile"
fileName="d:/logs/${appName}/${appName}.log" fileName="d:/logs/${appName}/${appName}.log"
archiveFileName ="d:/logs/{#}_${appName}.log" archiveFileName ="d:/logs/${appName}/{#}_${appName}.log"
archiveNumbering ="Date" archiveEvery="Day" archiveNumbering ="Date" archiveEvery="Day"
archiveDateFormat="yyyyMMdd"/> archiveDateFormat="yyyyMMdd"/>