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.
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user