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 eventHandler); public void Deregister(string variableName, EventHandler 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>> _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(text) ?? new AdsSettings(); } return settings; } // Register a callback on variable change public void Register(string variableName, EventHandler eventHandler) { List> 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 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 eventHandler) { List>? tempList; bool eventRemoved = false; if (_test.TryGetValue(variableName, out tempList)) { foreach (EventHandler 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>> entry in _test) { symbol = (Symbol)_symbolLoader.Symbols[entry.Key]; foreach (EventHandler 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; } }