using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ZB.MOM.WW.LmxProxy.Host.Domain;
namespace ZB.MOM.WW.LmxProxy.Host.Implementation
{
///
/// Subscription management for MxAccessClient to handle SCADA tag updates.
///
public sealed partial class MxAccessClient
{
///
/// Subscribes to a set of addresses and registers a callback for value changes.
///
/// The collection of addresses to subscribe to.
///
/// The callback to invoke when a value changes.
/// The callback receives the address and the new value.
///
/// An optional to cancel the operation.
///
/// A that completes with a handle to the subscription.
/// Disposing the handle will unsubscribe from all addresses.
///
/// Thrown if not connected to MxAccess.
/// Thrown if subscription fails for any address.
public Task SubscribeAsync(IEnumerable addresses, Action callback,
CancellationToken ct = default) => SubscribeInternalAsync(addresses, callback, true, ct);
///
/// Internal subscription method that allows control over whether to store the subscription for recreation.
///
private Task SubscribeInternalAsync(IEnumerable addresses,
Action callback, bool storeForRecreation, CancellationToken ct = default)
{
return Task.Run(() =>
{
lock (_lock)
{
if (!IsConnected || _lmxProxy == null)
{
throw new InvalidOperationException("Not connected to MxAccess");
}
var subscriptionIds = new List();
try
{
var addressList = addresses.ToList();
foreach (string? address in addressList)
{
// Add the item
var itemHandle = _lmxProxy.AddItem(_connectionHandle, address);
// Create subscription info
string subscriptionId = Guid.NewGuid().ToString();
var subscription = new SubscriptionInfo
{
Address = address,
ItemHandle = itemHandle,
Callback = callback,
SubscriptionId = subscriptionId
};
// Store subscription
_subscriptions[subscriptionId] = subscription;
_subscriptionsByHandle[itemHandle] = subscription;
subscriptionIds.Add(subscriptionId);
// Advise the item
_lmxProxy.AdviseSupervisory(_connectionHandle, itemHandle);
Logger.Debug("Subscribed to {Address} with handle {Handle}", address, itemHandle);
}
// Store subscription group for automatic recreation after reconnect
string groupId = Guid.NewGuid().ToString();
if (storeForRecreation)
{
_storedSubscriptions.Add(new StoredSubscription
{
Addresses = addressList,
Callback = callback,
GroupId = groupId
});
Logger.Debug(
"Stored subscription group {GroupId} with {Count} addresses for automatic recreation",
groupId, addressList.Count);
}
return new SubscriptionHandle(this, subscriptionIds, groupId);
}
catch (Exception ex)
{
// Clean up any subscriptions that were created
foreach (string? id in subscriptionIds)
{
UnsubscribeInternalAsync(id).Wait();
}
Logger.Error(ex, "Failed to subscribe to addresses");
throw;
}
}
}, ct);
}
///
/// Unsubscribes from a subscription by its ID.
///
/// The subscription identifier.
///
/// A representing the asynchronous operation.
///
private Task UnsubscribeInternalAsync(string subscriptionId)
{
return Task.Run(() =>
{
lock (_lock)
{
if (!_subscriptions.TryGetValue(subscriptionId, out SubscriptionInfo? subscription))
{
return;
}
try
{
if (_lmxProxy != null && _connectionHandle > 0)
{
_lmxProxy.UnAdvise(_connectionHandle, subscription.ItemHandle);
_lmxProxy.RemoveItem(_connectionHandle, subscription.ItemHandle);
}
_subscriptions.Remove(subscriptionId);
_subscriptionsByHandle.Remove(subscription.ItemHandle);
Logger.Debug("Unsubscribed from {Address}", subscription.Address);
}
catch (Exception ex)
{
Logger.Warning(ex, "Error unsubscribing from {Address}", subscription.Address);
}
}
});
}
}
}