Files
infineon_cs_hmi/ads-wrapper/AdsManager.cs
2026-02-11 08:38:36 +01:00

332 lines
10 KiB
C#

using System.Text.Json;
using System.Timers;
using TwinCAT;
using TwinCAT.Ads;
using TwinCAT.Ads.TypeSystem;
using TwinCAT.TypeSystem;
namespace Heisig.HMI.AdsManager;
// AdsManager Interface for dependency injection
public interface IAdsManager
{
public void Register(string variableName, EventHandler<ValueChangedEventArgs> eventHandler);
public void Deregister(string variableName, EventHandler<ValueChangedEventArgs> eventHandler);
public void WriteValue(string variableName, object value);
public object? ReadValue(string variableName);
public ISymbolLoader? GetSymbolLoader();
}
public sealed class AdsManager : IDisposable, IAdsManager
{
enum ConnectionState
{
DISCONNECTED,
CONNECTED
}
// Ads client instance
private readonly AdsClient _adsClient = new();
// Ads symbol loader
private ISymbolLoader? _symbolLoader;
// Last value of online change count
private uint? _lastOnlineChangeCount = null;
// Online change detected
private bool _onlineChangeDetected = false;
// Dictionary with symbols and events for variable changing events
private readonly Dictionary<string, List<EventHandler<ValueChangedEventArgs>>> _test = [];
// Timer to test cyclically if we are still connected
private readonly System.Timers.Timer _timer = new();
// Internal connection state
private ConnectionState _state = ConnectionState.DISCONNECTED;
// Filename with the connection settings
private const string _filename = "AdsSettings.json";
// Settings for the connection
private readonly AdsSettings _settings = new();
// Thread safety lock
private static readonly object _lock = new();
// Json serializer options
private readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true };
public AdsManager()
{
// Read settings file
_settings = ReadSettings();
// Start cyclic connection test
_timer.Interval = _settings.ReconnectIntervalMS;
_timer.Elapsed += CheckConnection;
_timer.Start();
}
public void Dispose()
{
_timer.Stop();
_timer.Dispose();
_adsClient.Dispose();
}
private void WriteSettings(AdsSettings settings)
{
string text = JsonSerializer.Serialize(settings, _jsonOptions);
File.WriteAllText(_filename, text);
}
private AdsSettings ReadSettings()
{
AdsSettings settings = new();
string text;
if (!File.Exists(_filename))
WriteSettings(settings);
else
{
text = File.ReadAllText(_filename);
settings = JsonSerializer.Deserialize<AdsSettings>(text) ?? new AdsSettings();
}
return settings;
}
// Register a callback on variable change
public void Register(string variableName, EventHandler<ValueChangedEventArgs> eventHandler)
{
List<EventHandler<ValueChangedEventArgs>> tempList;
// Add new variable entry
#pragma warning disable CA1854 // Methode „IDictionary.TryGetValue(TKey, out TValue)“ bevorzugen
if (!_test.ContainsKey(variableName))
#pragma warning restore CA1854 // Methode „IDictionary.TryGetValue(TKey, out TValue)“ bevorzugen
{
// Add new entry
tempList = [eventHandler];
_test.Add(variableName, tempList);
}
else
{
// Append event to variable
tempList = _test[variableName];
foreach (EventHandler<ValueChangedEventArgs> handler in tempList)
{
if (handler == eventHandler)
return;
}
tempList.Add(eventHandler);
_test[variableName] = tempList;
}
// If we are already connected, directly register variable
if (_state == ConnectionState.CONNECTED)
{
try
{
Symbol? symbol = (Symbol?)_symbolLoader?.Symbols[variableName];
if (symbol != null)
symbol.ValueChanged += eventHandler;
}
catch (Exception)
{
Console.WriteLine($"Variable {variableName} could not be found");
}
Console.WriteLine($"Variable {variableName} registered");
}
}
// Remove a registration from the dictionary
public void Deregister(string variableName, EventHandler<ValueChangedEventArgs> eventHandler)
{
List<EventHandler<ValueChangedEventArgs>>? tempList;
bool eventRemoved = false;
if (_test.TryGetValue(variableName, out tempList))
{
foreach (EventHandler<ValueChangedEventArgs> handler in tempList)
{
if (handler == eventHandler)
{
tempList.Remove(eventHandler);
// If we are connected remove directly remove variable changed event
if (_state == ConnectionState.CONNECTED)
{
try
{
Symbol? symbol = (Symbol?)_symbolLoader?.Symbols[variableName];
if (symbol != null)
symbol.ValueChanged -= eventHandler;
}
catch(Exception)
{
Console.WriteLine($"Variable {variableName} could not be found and deregistered");
}
Console.WriteLine("Variable {0} deregistered", variableName);
}
break;
}
if (eventRemoved)
_test[variableName] = tempList;
}
}
}
private void RegisterValueChangedEvents()
{
if (_symbolLoader == null)
return;
Symbol symbol;
foreach(KeyValuePair<string, List<EventHandler<ValueChangedEventArgs>>> entry in _test)
{
symbol = (Symbol)_symbolLoader.Symbols[entry.Key];
foreach (EventHandler<ValueChangedEventArgs> handler in entry.Value)
symbol.ValueChanged += handler;
}
}
public void WriteValue(string variableName, object value)
{
if (_state == ConnectionState.CONNECTED)
{
_adsClient.TryWriteValue(variableName, value);
}
}
public object? ReadValue(string variableName)
{
if (_state == ConnectionState.CONNECTED)
{
_adsClient.TryReadValue(variableName, out object? value);
return value;
}
else
return null;
}
// Cyclic check if we are still connected to the ads server
private void CheckConnection(object? sender, ElapsedEventArgs e)
{
lock(_lock)
{
// Stop timer
_timer.Stop();
}
switch (_state)
{
case ConnectionState.DISCONNECTED:
// Connect if not already connected
if (!_adsClient.IsConnected)
_adsClient.Connect(_settings.AdsAdress, _settings.AdsPort);
// Check if we have a connection
if (TestIfConnected())
{
Console.WriteLine("Connected");
Console.WriteLine("Reading Symbols ...");
// Load ads symbols
_symbolLoader = SymbolLoaderFactory.Create(_adsClient, SymbolLoaderSettings.Default);
Console.WriteLine("{0} Symbols read", _symbolLoader.Symbols.Count);
// Read last online change count
lock(_lock)
{
_lastOnlineChangeCount = (uint)((Symbol)_symbolLoader.Symbols[_settings.OnlineChangeCntVar]).ReadValue();
// Change state to connected
_state = ConnectionState.CONNECTED;
}
// Register value changed events
RegisterValueChangedEvents();
}
break;
case ConnectionState.CONNECTED:
if (!TestIfConnected())
{
Console.WriteLine("Connection lost");
lock (_lock)
_state = ConnectionState.DISCONNECTED;
}
else
{
try
{
uint onlineChangeCnt = (uint)((Symbol)_symbolLoader!.Symbols[_settings.OnlineChangeCntVar]).ReadValue();
lock (_lock)
{
_onlineChangeDetected = false;
if (onlineChangeCnt != _lastOnlineChangeCount)
_onlineChangeDetected = true;
_lastOnlineChangeCount = (uint)((Symbol)_symbolLoader.Symbols[_settings.OnlineChangeCntVar]).ReadValue();
}
// Reread symbols
if (_onlineChangeDetected)
{
Console.WriteLine("Online change detected. Rereading symbols ...");
_symbolLoader = SymbolLoaderFactory.Create(_adsClient, SymbolLoaderSettings.Default);
Console.WriteLine("{0} Symbols read", _symbolLoader.Symbols.Count);
RegisterValueChangedEvents();
}
}
catch(Exception)
{
Console.WriteLine("Could not read online change count");
}
}
break;
}
lock (_lock)
{
// Restart timer
_timer.Start();
}
}
private bool TestIfConnected()
{
bool connected;
// Test if connection could be established and is still alive
try
{
// Read ads server status
StateInfo state = _adsClient.ReadState();
connected = true;
}
catch (AdsErrorException)
{
connected = false;
}
return connected;
}
public ISymbolLoader? GetSymbolLoader()
{
return _symbolLoader;
}
}