using TwinCAT; using TwinCAT.Ads; using TwinCAT.Ads.TypeSystem; namespace PLCIOPointTool.Services; public class AdsClientService : IClientService { private readonly ILogService _logService; private readonly AdsClient _adsClient; private readonly Timer _timer; private bool _previousCheckState = false; private bool disposedValue; public AdsClientService(ILogService logService) { _logService = logService; _adsClient = new AdsClient(); _adsClient.ConnectionStateChanged += OnAdsClientConnectionStateChanged; _timer = new Timer(OnHealthCheck, null, Timeout.Infinite, Timeout.Infinite); } public event EventHandler? ConnectionStateChanged; public event EventHandler? HealthCheckingChanged; public async Task ConnectAsync(string netId, int port) { if (string.IsNullOrWhiteSpace(netId) || port < 0) { _logService.Warning("Invalid connection parameters"); return; } try { await _adsClient.ConnectAsync(new AmsAddress(netId, port), new CancellationToken()); if (_adsClient.TryReadState(out _) != AdsErrorCode.NoError) { OnAdsClientConnectionStateChanged( null, new ConnectionStateChangedEventArgs( ConnectionStateChangedReason.Established, ConnectionState.Disconnected, ConnectionState.Disconnected)); _logService.Warning("Failed to connect ADS server, please try later"); return; } _logService.Info("Succeeded to connect ADS server"); _timer.Change(3000, 3000); } catch (Exception ex) { _logService.Error($"Got an exception while connecting; {ex}"); } } public async Task DisconnectAsync() { try { await _adsClient.DisconnectAsync(new CancellationToken()); _logService.Info("Succeeded to disconnect ADS server"); } catch (Exception ex) { _logService.Error($"Got an exception while disconnecting; {ex}"); } finally { _timer.Change(Timeout.Infinite, Timeout.Infinite); } } public async Task ReadValue(string symbolName) { if (string.IsNullOrWhiteSpace(symbolName)) { _logService.Warning("Invalid variable path"); return null; } try { var symbol = await _adsClient.ReadSymbolAsync(symbolName, new CancellationToken()); if (symbol is null || symbol.Value is null || symbol.Succeeded == false) { _logService.Warning($"Failed to get symbol of {symbolName}"); return null; } var symbolValue = await _adsClient.ReadValueAsync(symbol.Value, new CancellationToken()); if (symbolValue is null || symbolValue.Value is null || symbolValue.Succeeded == false) { _logService.Warning($"Failed to get value of {symbolName}"); return null; } _logService.Info($"Succeeded to get value of {symbolName}"); return symbolValue.Value; } catch (Exception ex) { _logService.Error($"Got an exception while reading; {ex}"); return null; } } public async Task WriteValue(string symbolName, object value) { if (string.IsNullOrWhiteSpace(symbolName) || value is null) { _logService.Warning("Invalid variable path or value"); return; } try { var resultValue = await _adsClient.WriteSymbolAsync(symbolName, value, new CancellationToken()); if (resultValue is null || resultValue.Succeeded == false) { _logService.Warning($"Failed to set value of {symbolName}"); return; } _logService.Info($"Succeeded to set value of {symbolName}"); } catch (Exception ex) { _logService.Error($"Got an exception while reading; {ex}"); } } private void OnAdsClientConnectionStateChanged(object? sender, TwinCAT.ConnectionStateChangedEventArgs e) { try { ConnectionStateChanged?.Invoke(null, e.NewState); } catch (Exception ex) { _logService.Error($"Got an exception; {ex}"); } } private void OnHealthCheck(object? state) { var errorCode = _adsClient.TryReadState(out _); if (errorCode != AdsErrorCode.NoError) { _logService.Warning("Health Checking Failed"); if (_previousCheckState == true) { _previousCheckState = false; HealthCheckingChanged?.Invoke(null, false); } return; } if (_previousCheckState == false) { _previousCheckState = true; HealthCheckingChanged?.Invoke(null, true); } //_logService.Info("Health Checking succeeded"); } #region Dispose protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects) } // TODO: free unmanaged resources (unmanaged objects) and override finalizer // TODO: set large fields to null _timer.Change(Timeout.Infinite, Timeout.Infinite); _timer.Dispose(); _adsClient.Dispose(); _adsClient.ConnectionStateChanged -= OnAdsClientConnectionStateChanged; disposedValue = true; } } // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources // ~AdsClientService() // { // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method // Dispose(disposing: false); // } public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } #endregion }