feat(lmxproxy): phase 2 — host core (MxAccessClient, SessionManager, SubscriptionManager)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-21 23:58:17 -04:00
parent 0d63fb1105
commit 64c92c63e5
15 changed files with 1834 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using ArchestrA.MxAccess;
using Serilog;
using ZB.MOM.WW.LmxProxy.Host.Domain;
namespace ZB.MOM.WW.LmxProxy.Host.MxAccess
{
public sealed partial class MxAccessClient
{
/// <summary>
/// Callback invoked by the SubscriptionManager when it needs to deliver
/// data change events. Set by the SubscriptionManager during initialization.
/// </summary>
public Action<string, Vtq>? OnTagValueChanged { get; set; }
/// <summary>
/// COM event handler for MxAccess OnDataChange events.
/// Called on the STA thread when a subscribed tag value changes.
/// Signature matches the ArchestrA.MxAccess ILMXProxyServerEvents interface.
/// </summary>
private void OnDataChange(
int hLMXServerHandle,
int phItemHandle,
object pvItemValue,
int pwItemQuality,
object pftItemTimeStamp,
ref MXSTATUS_PROXY[] ItemStatus)
{
try
{
var quality = MapQuality(pwItemQuality);
var timestamp = ConvertTimestamp(pftItemTimeStamp);
var vtq = new Vtq(pvItemValue, timestamp, quality);
// We don't have the address from the COM callback — the reference code
// looks it up from _subscriptionsByHandle. For the v2 design, the
// SubscriptionManager's global handler receives (address, vtq) via
// OnTagValueChanged. The actual address resolution will be implemented
// when the full subscription tracking is wired up on windev.
// Route to the SubscriptionManager's global handler
OnTagValueChanged?.Invoke(phItemHandle.ToString(), vtq);
}
catch (Exception ex)
{
Log.Error(ex, "Error processing OnDataChange event for handle {Handle}", phItemHandle);
}
}
/// <summary>
/// COM event handler for MxAccess OnWriteComplete events.
/// Signature matches the ArchestrA.MxAccess ILMXProxyServerEvents interface.
/// </summary>
private void OnWriteComplete(
int hLMXServerHandle,
int phItemHandle,
ref MXSTATUS_PROXY[] ItemStatus)
{
// Write completion is currently fire-and-forget.
// Log for diagnostics.
try
{
Log.Debug("WriteCompleted: handle {Handle}", phItemHandle);
}
catch (Exception ex)
{
Log.Error(ex, "Error processing OnWriteComplete event for handle {Handle}", phItemHandle);
}
}
/// <summary>
/// Converts a timestamp object to DateTime in UTC.
/// </summary>
private static DateTime ConvertTimestamp(object timestamp)
{
if (timestamp is DateTime dt)
{
return dt.Kind == DateTimeKind.Utc ? dt : dt.ToUniversalTime();
}
return DateTime.UtcNow;
}
}
}