Compare commits
2 Commits
fb47ce09ab
...
b7055bc31c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7055bc31c | ||
|
|
bc23a8514f |
@@ -8,6 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageReference Include="Config.Net" Version="5.2.0" />
|
||||
<PackageReference Include="GuerrillaNtp" Version="3.1.0" />
|
||||
<PackageReference Include="Json.Net" Version="1.0.33" />
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using FSI.BT.IR.Plc.TimeSync;
|
||||
using FSI.BT.IR.Plc.TimeSync.Settings;
|
||||
using static FSI.BT.IR.Plc.TimeSync.Settings.Context;
|
||||
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using CommandLine;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using static FSI.BT.IR.Plc.TimeSync.Settings.Context;
|
||||
|
||||
@@ -7,27 +8,23 @@ namespace FSI.BT.IR.Plc.TimeSync.Settings
|
||||
{
|
||||
public class AppContext : ISettings
|
||||
{
|
||||
|
||||
private Cfg _cfg;
|
||||
|
||||
private string[] _args;
|
||||
|
||||
public AppContext()
|
||||
{
|
||||
_args = new string[0];
|
||||
LoadSettings();
|
||||
LoadCfg();
|
||||
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
var values = new ConfigurationBuilder()
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
|
||||
.Build();
|
||||
|
||||
Settings = values.Get<Context.Settings>();
|
||||
|
||||
var tmo = Settings.Logging.LogLevel.MicrosoftHostingLifetime;
|
||||
var verstion = Settings.Version.ToString();
|
||||
}
|
||||
|
||||
private void LoadCfg()
|
||||
|
||||
12
scr/FSI.BT.IR.Plc.TimeSync/Settings/ConsoleArgs.cs
Normal file
12
scr/FSI.BT.IR.Plc.TimeSync/Settings/ConsoleArgs.cs
Normal 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; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,106 @@
|
||||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace FSI.BT.IR.Plc.TimeSync.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Datenbasis von config.json und appsettings.json
|
||||
/// Einstellungen und Konfigurationsdaten
|
||||
/// </summary>
|
||||
public class Context
|
||||
{
|
||||
public interface ISettings : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Daten von appsettings.json
|
||||
/// </summary>
|
||||
public Settings Settings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Daten von config.json
|
||||
/// </summary>
|
||||
public Cfg Cfg { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Daten von appsettings.json
|
||||
/// </summary>
|
||||
public record Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Versions-Informationen
|
||||
/// </summary>
|
||||
public Version Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Build-Informationen
|
||||
/// </summary>
|
||||
public Build Build { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logging-Einstellungen
|
||||
/// </summary>
|
||||
public Logging Logging { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Versions-Informationen
|
||||
/// </summary>
|
||||
public record Version
|
||||
{
|
||||
/// <summary>
|
||||
/// Haupt-Versionsnummer
|
||||
/// </summary>
|
||||
public uint Major { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Unter-Versionsnummer
|
||||
/// </summary>
|
||||
public uint Minor { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Patch/Hotfix
|
||||
/// </summary>
|
||||
public uint Patch { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// optinoale Versionsinformationen
|
||||
/// </summary>
|
||||
public string? Optional { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// gibt die Versions-Nummer zurück
|
||||
/// </summary>
|
||||
/// <returns>Versionsnummer</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
{
|
||||
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
|
||||
@@ -43,6 +108,9 @@ namespace FSI.BT.IR.Plc.TimeSync.Settings
|
||||
public Loglevel LogLevel { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logging-Einstellungen
|
||||
/// </summary>
|
||||
public class Loglevel
|
||||
{
|
||||
public string Default { get; set; }
|
||||
@@ -51,45 +119,119 @@ namespace FSI.BT.IR.Plc.TimeSync.Settings
|
||||
public string MicrosoftHostingLifetime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Daten von config.json
|
||||
/// </summary>
|
||||
public record Cfg
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Spsen, deren Zeit mit NTP-Server syncronsiert werden sollen
|
||||
/// </summary>
|
||||
public List<Plc> Plcs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NTP-Server Adresse
|
||||
/// </summary>
|
||||
public string NtpServer { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SPS-Daten
|
||||
/// </summary>
|
||||
public record Plc : IPlc
|
||||
{
|
||||
/// <summary>
|
||||
/// Name
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Beschreibung
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IP-Adresse
|
||||
/// </summary>
|
||||
public string Adress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rack-Nummer
|
||||
/// </summary>
|
||||
public int Rack { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Slot-Nummer
|
||||
/// </summary>
|
||||
public int Slot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Update-Intervall, in der die Zeit überprüft werden soll.
|
||||
/// </summary>
|
||||
public int UpdateIntervall { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Zeitdifferenz, ab der die SPS-Zeit angepasst werden soll.
|
||||
/// </summary>
|
||||
public int TimeDifference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Locale Zeit wird an die SPS gesendet - nicht UTC-Zeit
|
||||
/// </summary>
|
||||
public bool LocalTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Soll SPS-Zeit synchronisiert werden
|
||||
/// </summary>
|
||||
public bool Enable { get; set; }
|
||||
}
|
||||
|
||||
public interface IPlc
|
||||
{
|
||||
/// <summary>
|
||||
/// Name
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Beschreibung
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IP-Adresse
|
||||
/// </summary>
|
||||
public string Adress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rack-Nummer
|
||||
/// </summary>
|
||||
public int Rack { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Slot-Nummer
|
||||
/// </summary>
|
||||
public int Slot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Update-Intervall, in der die Zeit überprüft werden soll.
|
||||
/// </summary>
|
||||
public int UpdateIntervall { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Zeitdifferenz, ab der die SPS-Zeit angepasst werden soll.
|
||||
/// </summary>
|
||||
public int TimeDifference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Locale Zeit wird an die SPS gesendet - nicht UTC-Zeit
|
||||
/// </summary>
|
||||
public bool LocalTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Soll SPS-Zeit synchronisiert werden
|
||||
/// </summary>
|
||||
public bool Enable { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,17 @@ using Sharp7;
|
||||
using GuerrillaNtp;
|
||||
using static FSI.BT.IR.Plc.TimeSync.Settings.Context;
|
||||
|
||||
|
||||
namespace FSI.BT.IR.Plc.TimeSync
|
||||
{
|
||||
internal class SyncPlcTime : IPlc
|
||||
{
|
||||
private Logger _log = LogManager.GetCurrentClassLogger();
|
||||
private const string DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm:ss.fff";
|
||||
#region Constants
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -24,80 +28,137 @@ namespace FSI.BT.IR.Plc.TimeSync
|
||||
Enable = plc.Enable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Beschreibung
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IP-Adresse
|
||||
/// </summary>
|
||||
public string Adress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rack-Nummer
|
||||
/// </summary>
|
||||
public int Rack { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Slot-Nummer
|
||||
/// </summary>
|
||||
public int Slot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Update-Intervall, in der die Zeit überprüft werden soll.
|
||||
/// </summary>
|
||||
public int UpdateIntervall { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Zeitdifferenz, ab der die SPS-Zeit angepasst werden soll.
|
||||
/// </summary>
|
||||
public int TimeDifference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Locale Zeit wird an die SPS gesendet - nicht UTC-Zeit
|
||||
/// </summary>
|
||||
public bool LocalTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Soll SPS-Zeit synchronisiert werden
|
||||
/// </summary>
|
||||
public bool Enable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NTP-Server Adresse
|
||||
/// </summary>
|
||||
public string NtpServer { get; set; }
|
||||
|
||||
|
||||
public async Task Snyc(CancellationToken cancellationToken) =>
|
||||
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)
|
||||
{
|
||||
var plc = new S7Client();
|
||||
|
||||
var connectionRslt = plc.ConnectTo(Adress, Rack, Slot);
|
||||
|
||||
if (connectionRslt == 0)
|
||||
try
|
||||
{
|
||||
_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 + " Fehler: " + plc.ErrorText(connectionRslt));
|
||||
return;
|
||||
_log.Error(Name + " " + ex.Message);
|
||||
}
|
||||
|
||||
var plcDateTime = new DateTime();
|
||||
plc.GetPlcDateTime(ref plcDateTime);
|
||||
|
||||
plc.GetPlcDateTime(ref plcDateTime); // Uhrzeit aus SPS auslesen
|
||||
_log.Debug(Name + " SPS Zeit (aktuell): " + plcDateTime.ToString(DATE_TIME_FORMAT));
|
||||
|
||||
var client = new NtpClient(NtpServer);
|
||||
NtpClock clock = client.Query();
|
||||
|
||||
DateTime ntpDateTime;
|
||||
|
||||
if (LocalTime)
|
||||
if (LocalTime) // lokale Zeit/Ortszeit
|
||||
{
|
||||
_log.Debug(Name + " UTC Zeit: " + clock.Now.ToString(DATE_TIME_FORMAT));
|
||||
ntpDateTime = clock.Now.DateTime;
|
||||
_log.Debug(Name + " Ortszeit: " + clock.Now.ToString(DATE_TIME_FORMAT));
|
||||
ntpDateTime = clock.Now.DateTime; // lokale Zeit/Ortszeit von NTP-Server
|
||||
}
|
||||
else
|
||||
else // UTC - Zeit
|
||||
{
|
||||
_log.Debug(Name + " Ortszeit: " + clock.UtcNow.ToString(DATE_TIME_FORMAT));
|
||||
ntpDateTime = clock.UtcNow.DateTime;
|
||||
_log.Debug(Name + " UTC-Zeit: " + clock.UtcNow.ToString(DATE_TIME_FORMAT));
|
||||
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);
|
||||
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");
|
||||
var temp = plc.SetPlcDateTime(ntpDateTime); // Zeit an SPS senden
|
||||
_log.Info(Name + " neue Zeit: " + ntpDateTime.ToString(DATE_TIME_FORMAT));
|
||||
var temp = plc.SetPlcDateTime(ntpDateTime);
|
||||
}
|
||||
else if (TimeDifference == 0)
|
||||
{
|
||||
var temp = plc.SetPlcDateTime(ntpDateTime); // Zeit an SPS senden
|
||||
_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.");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,22 +10,34 @@ namespace FSI.BT.IR.Plc.TimeSync
|
||||
private CancellationTokenSource _tokenSource;
|
||||
private CancellationToken _stoppingToken;
|
||||
|
||||
/// <summary>
|
||||
/// Standard Taks
|
||||
/// </summary>
|
||||
/// <param name="stoppingToken"></param>
|
||||
/// <returns></returns>
|
||||
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)
|
||||
{
|
||||
StopTasks();
|
||||
StartTasks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tasks, die beim Start ausgef<65>hrt werden sollen.
|
||||
/// </summary>
|
||||
private void StartTasks()
|
||||
{
|
||||
|
||||
@@ -36,10 +48,10 @@ namespace FSI.BT.IR.Plc.TimeSync
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
_stoppingToken = _tokenSource.Token;
|
||||
|
||||
// Schleife <20>ber alle Spsen
|
||||
foreach (var plc in settings.Cfg.Plcs)
|
||||
{
|
||||
|
||||
if (plc.Enable)
|
||||
if (plc.Enable)
|
||||
{
|
||||
var timeSync = new SyncPlcTime(plc);
|
||||
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()
|
||||
{
|
||||
_tokenSource.Cancel();
|
||||
_stoppingToken = _tokenSource.Token;
|
||||
|
||||
// Schleife <20>ber alle Spsen
|
||||
foreach (var task in _taskList)
|
||||
{
|
||||
task.Snyc(_stoppingToken);
|
||||
@@ -65,7 +81,5 @@ namespace FSI.BT.IR.Plc.TimeSync
|
||||
_taskList?.Clear();
|
||||
_taskList = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
"Major": 0,
|
||||
"Minor": 0,
|
||||
"Patch": 0,
|
||||
"Optional": "alpha"
|
||||
"Optional": "alpha"
|
||||
},
|
||||
"Build": {
|
||||
"Creator": "Stephan Maier",
|
||||
"Organization": "Fondium Singen GmbH",
|
||||
"CreationYear": "2024",
|
||||
"Description": ""
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
|
||||
"Plcs": [
|
||||
{
|
||||
"Name": "PL1 FA",
|
||||
@@ -25,5 +24,4 @@
|
||||
}
|
||||
],
|
||||
"NtpServer": "10.10.199.41"
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<target xsi:type="File"
|
||||
name="logfile"
|
||||
fileName="d:/logs/${appName}/${appName}.log"
|
||||
archiveFileName ="d:/logs/{#}_${appName}.log"
|
||||
archiveFileName ="d:/logs/${appName}/{#}_${appName}.log"
|
||||
archiveNumbering ="Date" archiveEvery="Day"
|
||||
archiveDateFormat="yyyyMMdd"/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user