Files
scadalink-design/deprecated/lmxproxy/src/ZB.MOM.WW.LmxProxy.Client/LmxProxyClient.Connection.cs
Joseph Doherty 9dccf8e72f deprecate(lmxproxy): move all LmxProxy code, tests, and docs to deprecated/
LmxProxy is no longer needed. Moved the entire lmxproxy/ workspace, DCL
adapter files, and related docs to deprecated/. Removed LmxProxy registration
from DataConnectionFactory, project reference from DCL, protocol option from
UI, and cleaned up all requirement docs.
2026-04-08 15:56:23 -04:00

220 lines
5.8 KiB
C#

using Grpc.Net.Client;
using Microsoft.Extensions.Logging;
using ProtoBuf.Grpc.Client;
using ZB.MOM.WW.LmxProxy.Client.Domain;
using ZB.MOM.WW.LmxProxy.Client.Security;
namespace ZB.MOM.WW.LmxProxy.Client;
public partial class LmxProxyClient
{
/// <inheritdoc />
public async Task ConnectAsync(CancellationToken cancellationToken = default)
{
await _connectionLock.WaitAsync(cancellationToken);
try
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (IsConnected)
return;
var endpoint = BuildEndpointUri();
_logger.LogInformation("Connecting to LmxProxy at {Endpoint}", endpoint);
GrpcChannel channel = GrpcChannelFactory.CreateChannel(endpoint, _tlsConfiguration, _logger, _apiKey);
IScadaService client;
try
{
client = channel.CreateGrpcService<IScadaService>();
}
catch
{
channel.Dispose();
throw;
}
ConnectResponse response;
try
{
var request = new ConnectRequest
{
ClientId = $"ScadaBridge-{Guid.NewGuid():N}",
ApiKey = _apiKey ?? string.Empty
};
response = await client.ConnectAsync(request);
}
catch
{
channel.Dispose();
throw;
}
if (!response.Success)
{
channel.Dispose();
throw new InvalidOperationException($"Connect failed: {response.Message}");
}
_channel = channel;
_client = client;
_sessionId = response.SessionId;
_isConnected = true;
StartKeepAlive();
_logger.LogInformation("Connected to LmxProxy, session={SessionId}", _sessionId);
}
catch (Exception ex)
{
_channel = null;
_client = null;
_sessionId = string.Empty;
_isConnected = false;
_logger.LogError(ex, "Failed to connect to LmxProxy");
throw;
}
finally
{
_connectionLock.Release();
}
}
/// <inheritdoc />
public async Task DisconnectAsync()
{
await _connectionLock.WaitAsync();
try
{
StopKeepAlive();
if (_client is not null && !string.IsNullOrEmpty(_sessionId))
{
try
{
await _client.DisconnectAsync(new DisconnectRequest { SessionId = _sessionId });
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error sending disconnect request");
}
}
_client = null;
_sessionId = string.Empty;
_isConnected = false;
_channel?.Dispose();
_channel = null;
}
finally
{
_connectionLock.Release();
}
}
/// <inheritdoc cref="SubscribeAsync"/>
public async Task<ISubscription> SubscribeAsync(
IEnumerable<string> addresses,
Action<string, Vtq> onUpdate,
Action<Exception>? onStreamError = null,
CancellationToken cancellationToken = default)
{
EnsureConnected();
var subscription = new CodeFirstSubscription(
_client!,
_sessionId,
addresses.ToList(),
onUpdate,
onStreamError,
_logger,
sub =>
{
lock (_subscriptionLock)
{
_activeSubscriptions.Remove(sub);
}
});
lock (_subscriptionLock)
{
_activeSubscriptions.Add(subscription);
}
await subscription.StartAsync(cancellationToken);
return subscription;
}
private void StartKeepAlive()
{
_keepAliveTimer = new Timer(
async _ => await KeepAliveCallback(),
null,
_keepAliveInterval,
_keepAliveInterval);
}
private async Task KeepAliveCallback()
{
try
{
if (_client is null || string.IsNullOrEmpty(_sessionId))
return;
await _client.GetConnectionStateAsync(new GetConnectionStateRequest { SessionId = _sessionId });
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Keep-alive failed, marking disconnected");
StopKeepAlive();
await MarkDisconnectedAsync(ex);
}
}
private void StopKeepAlive()
{
_keepAliveTimer?.Dispose();
_keepAliveTimer = null;
}
internal async Task MarkDisconnectedAsync(Exception ex)
{
if (_disposed) return;
await _connectionLock.WaitAsync();
try
{
_isConnected = false;
_client = null;
_sessionId = string.Empty;
_channel?.Dispose();
_channel = null;
}
finally
{
_connectionLock.Release();
}
List<ISubscription> subscriptions;
lock (_subscriptionLock)
{
subscriptions = [.. _activeSubscriptions];
_activeSubscriptions.Clear();
}
foreach (var sub in subscriptions)
{
try { sub.Dispose(); }
catch { /* swallow */ }
}
_logger.LogWarning(ex, "Client marked as disconnected");
}
private Uri BuildEndpointUri()
{
string scheme = _tlsConfiguration?.UseTls == true ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
return new UriBuilder { Scheme = scheme, Host = _host, Port = _port }.Uri;
}
}