332 lines
10 KiB
C#
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;
|
|
}
|
|
}
|