Initial Push
This commit is contained in:
331
ads-wrapper/AdsManager.cs
Normal file
331
ads-wrapper/AdsManager.cs
Normal file
@@ -0,0 +1,331 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user