Tagessicherung

This commit is contained in:
Maier Stephan SI
2024-07-22 09:01:44 +03:00
parent 7fb7fce332
commit fb47ce09ab
12 changed files with 516 additions and 0 deletions

View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34723.18
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSI.BT.IR.Plc.TimeSync", "FSI.BT.IR.Plc.TimeSync\FSI.BT.IR.Plc.TimeSync.csproj", "{23DFF9D8-7A25-4465-865B-3D1834AF5725}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{23DFF9D8-7A25-4465-865B-3D1834AF5725}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{23DFF9D8-7A25-4465-865B-3D1834AF5725}.Debug|Any CPU.Build.0 = Debug|Any CPU
{23DFF9D8-7A25-4465-865B-3D1834AF5725}.Release|Any CPU.ActiveCfg = Release|Any CPU
{23DFF9D8-7A25-4465-865B-3D1834AF5725}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {539E3901-0BD7-4435-8501-3C27CC52E614}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>dotnet-FSI.BT.IR.Plc.TimeSync-965c3cf3-0f37-484a-865d-4762bb9fe30e</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Config.Net" Version="5.2.0" />
<PackageReference Include="GuerrillaNtp" Version="3.1.0" />
<PackageReference Include="Json.Net" Version="1.0.33" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.11" />
<PackageReference Include="Sharp7" Version="1.1.84" />
</ItemGroup>
<ItemGroup>
<Compile Update="Settings\AppContext.cs">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Compile>
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="nlog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,11 @@
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);
builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<ISettings, FSI.BT.IR.Plc.TimeSync.Settings.AppContext>();
using IHost host = builder.Build();
host.Run();

View File

@@ -0,0 +1,12 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"FSI.BT.IR.Plc.TimeSync": {
"commandName": "Project",
"dotnetRunMessages": true,
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,74 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Primitives;
using static FSI.BT.IR.Plc.TimeSync.Settings.Context;
namespace FSI.BT.IR.Plc.TimeSync.Settings
{
public class AppContext : ISettings
{
private Cfg _cfg;
public AppContext()
{
LoadSettings();
LoadCfg();
}
private void LoadSettings()
{
var values = new ConfigurationBuilder()
.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()
{
var values = new ConfigurationBuilder()
.AddJsonFile("config.json", optional: true, reloadOnChange: true)
.Build();
Cfg = values.Get<Context.Cfg>();
Action onChange = () =>
{
Cfg = values.Get<Context.Cfg>();
};
ChangeToken.OnChange(() => values.GetReloadToken(), onChange);
}
public Context.Settings Settings { get; set; }
public Cfg Cfg
{
get { return _cfg; }
set
{
_cfg = value;
RaisePropertyChanged();
}
}
private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
// Null means no subscribers to the event
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

View File

@@ -0,0 +1,97 @@
using System.ComponentModel;
using System.Configuration;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace FSI.BT.IR.Plc.TimeSync.Settings
{
public class Context
{
public interface ISettings : INotifyPropertyChanged
{
public Settings Settings { get; set; }
public Cfg Cfg { get; set; }
}
public record Settings
{
public Version Version { get; set; }
public Logging Logging { get; set; }
}
public record Version
{
public uint Major { get; set; } = 0;
public uint Minor { get; set; } = 0;
public uint Patch { get; set; } = 0;
public string? Optional { get; set; }
public override string ToString()
{
return Major.ToString() + "." + Minor.ToString() + "." + Patch.ToString() + ((Optional == string.Empty || Optional == null) ? "" : "-" + Optional);
}
}
public class Logging
{
public Loglevel LogLevel { get; set; }
}
public class Loglevel
{
public string Default { get; set; }
[ConfigurationKeyName("Microsoft.Hosting.Lifetime")]
public string MicrosoftHostingLifetime { get; set; }
}
public record Cfg
{
public List<Plc> Plcs { get; set; }
public string NtpServer { get; set; }
}
public record Plc : IPlc
{
public string Name { get; set; }
public string Description { get; set; }
public string Adress { get; set; }
public int Rack { get; set; }
public int Slot { get; set; }
public int UpdateIntervall { get; set; }
public int TimeDifference { get; set; }
public bool LocalTime { get; set; }
public bool Enable { get; set; }
}
public interface IPlc
{
public string Name { get; set; }
public string Description { get; set; }
public string Adress { get; set; }
public int Rack { get; set; }
public int Slot { get; set; }
public int UpdateIntervall { get; set; }
public int TimeDifference { get; set; }
public bool LocalTime { get; set; }
public bool Enable { get; set; }
}
}
}

View File

@@ -0,0 +1,103 @@
using NLog;
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";
public SyncPlcTime(IPlc plc)
{
Name = plc.Name;
Description = plc.Description;
Adress = plc.Adress;
Rack = plc.Rack;
Slot = plc.Slot;
UpdateIntervall = plc.UpdateIntervall;
TimeDifference = plc.TimeDifference;
LocalTime = plc.LocalTime;
Enable = plc.Enable;
}
public string Name { get; set; }
public string Description { get; set; }
public string Adress { get; set; }
public int Rack { get; set; }
public int Slot { get; set; }
public int UpdateIntervall { get; set; }
public int TimeDifference { get; set; }
public bool LocalTime { get; set; }
public bool Enable { get; set; }
public string NtpServer { get; set; }
public async Task Snyc(CancellationToken cancellationToken) =>
await Task.Run(async () =>
{
while (!cancellationToken.IsCancellationRequested)
{
var plc = new S7Client();
var connectionRslt = plc.ConnectTo(Adress, Rack, Slot);
if (connectionRslt == 0)
{
_log.Debug(Name + " Verbindung hergestellt.");
}
else
{
_log.Error(Name + " Verbindung nicht hergestellt.");
_log.Error(Name + " Fehler: " + plc.ErrorText(connectionRslt));
return;
}
var plcDateTime = new DateTime();
plc.GetPlcDateTime(ref plcDateTime);
_log.Debug(Name + " SPS Zeit (aktuell): " + plcDateTime.ToString(DATE_TIME_FORMAT));
var client = new NtpClient(NtpServer);
NtpClock clock = client.Query();
DateTime ntpDateTime;
if (LocalTime)
{
_log.Debug(Name + " UTC Zeit: " + clock.Now.ToString(DATE_TIME_FORMAT));
ntpDateTime = clock.Now.DateTime;
}
else
{
_log.Debug(Name + " Ortszeit: " + clock.UtcNow.ToString(DATE_TIME_FORMAT));
ntpDateTime = clock.UtcNow.DateTime;
}
var timeSpan = Math.Abs((plcDateTime - ntpDateTime).TotalMilliseconds);
if (TimeDifference > 0 && timeSpan >= TimeDifference)
{
_log.Debug(Name + " Zeitdifferenz " + timeSpan + " ms überschritten");
_log.Info(Name + " neue Zeit: " + ntpDateTime.ToString(DATE_TIME_FORMAT));
var temp = plc.SetPlcDateTime(ntpDateTime);
}
else if (TimeDifference == 0)
{
_log.Info(Name + " neue Zeit: " + ntpDateTime.ToString(DATE_TIME_FORMAT));
var temp = plc.SetPlcDateTime(ntpDateTime);
}
plc.Disconnect();
_log.Debug(Name + " Verbindung getrennt.");
await Task.Delay(UpdateIntervall, cancellationToken);
}
}, cancellationToken);
}
}

View File

@@ -0,0 +1,71 @@
using NLog;
using static FSI.BT.IR.Plc.TimeSync.Settings.Context;
namespace FSI.BT.IR.Plc.TimeSync
{
public class Worker(ISettings settings) : BackgroundService
{
private Logger _log = LogManager.GetCurrentClassLogger();
private List<SyncPlcTime> _taskList;
private CancellationTokenSource _tokenSource;
private CancellationToken _stoppingToken;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
settings.PropertyChanged += Settings_PropertyChanged;
StartTasks();
}
private void Settings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
StopTasks();
StartTasks();
}
private void StartTasks()
{
if (_taskList != null)
return;
_taskList = new List<SyncPlcTime>();
_tokenSource = new CancellationTokenSource();
_stoppingToken = _tokenSource.Token;
foreach (var plc in settings.Cfg.Plcs)
{
if (plc.Enable)
{
var timeSync = new SyncPlcTime(plc);
timeSync.NtpServer = settings.Cfg.NtpServer;
timeSync.Snyc(_stoppingToken);
_taskList.Add(timeSync);
_log.Info(plc.Name + " Task gestartet.");
}
}
}
private async void StopTasks()
{
_tokenSource.Cancel();
_stoppingToken = _tokenSource.Token;
foreach (var task in _taskList)
{
task.Snyc(_stoppingToken);
_log.Info(task.Name + " Task beendet.");
}
_taskList?.Clear();
_taskList = null;
}
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@@ -0,0 +1,14 @@
{
"Version": {
"Major": 0,
"Minor": 0,
"Patch": 0,
"Optional": "alpha"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@@ -0,0 +1,29 @@
{
"Plcs": [
{
"Name": "PL1 FA",
"Description": "Beschreibung",
"Adress": "10.10.199.95",
"Rack": 0,
"Slot": 2,
"UpdateIntervall": 10000,
"TimeDifference": 10,
"LocalTime": false,
"Enable": true
},
{
"Name": "PL1 FA 123",
"Description": "PL1 Formanlage",
"Adress": "10.10.199.95",
"Rack": 0,
"Slot": 2,
"UpdateIntervall": 60000,
"TimeDifference": 0,
"LocalTime": true,
"Enable": false
}
],
"NtpServer": "10.10.199.41"
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
throwExceptions="false">
<variable name="appName" value="FSI.BT.IR.Plc.TimeSync" />
<!-- the targets to write to -->
<targets>
<!-- write logs to file -->
<target xsi:type="File"
name="logfile"
fileName="d:/logs/${appName}/${appName}.log"
archiveFileName ="d:/logs/{#}_${appName}.log"
archiveNumbering ="Date" archiveEvery="Day"
archiveDateFormat="yyyyMMdd"/>
<target name="viewer"
xsi:type="NLogViewer"
includeSourceInfo="true"
address="udp://FDESINB0166:9999"/>
</targets>
<!-- rules to map from logger name to target -->
<rules>
<logger name="*"
minlevel="Trace"
writeTo="logfile,logconsole,console" />
<logger name="*"
minlevel="Debug"
writeTo="viewer" />
</rules>
</nlog>