- MxStatusMapper: maps all 40+ MxStatusDetail codes, MxStatusCategory, and MxStatusSource to human-readable names and client messages - OnDataChange: checks MXSTATUS_PROXY.success and overrides quality with specific OPC UA code when MxAccess reports a failure (e.g., CommFailure, ConfigError, WaitingForInitialData) - OnWriteComplete: uses MxStatusMapper.FormatStatus for structured logging - Write errors: catches COMException separately with HRESULT in message - Read errors: distinguishes COM, timeout, and generic failures in logging Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
129 lines
4.9 KiB
C#
129 lines
4.9 KiB
C#
using System;
|
|
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.
|
|
/// 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);
|
|
|
|
// Check MXSTATUS_PROXY — if success is false, override quality
|
|
// with a more specific code derived from the MxAccess status fields
|
|
if (ItemStatus != null && ItemStatus.Length > 0 && ItemStatus[0].success == 0)
|
|
{
|
|
var status = ItemStatus[0];
|
|
quality = MxStatusMapper.CategoryToQuality((int)status.category, status.detail);
|
|
Log.Debug("OnDataChange status failure for handle {Handle}: {Status}",
|
|
phItemHandle, MxStatusMapper.FormatStatus(status.detail, (int)status.category, (int)status.detectedBy));
|
|
}
|
|
|
|
var vtq = new Vtq(pvItemValue, timestamp, quality);
|
|
|
|
// Resolve address from handle map
|
|
string address;
|
|
lock (_lock)
|
|
{
|
|
if (!_handleToAddress.TryGetValue(phItemHandle, out address))
|
|
{
|
|
Log.Debug("OnDataChange for unknown handle {Handle}, ignoring", phItemHandle);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Invoke the stored subscription callback
|
|
Action<string, Vtq> callback;
|
|
lock (_lock)
|
|
{
|
|
if (!_storedSubscriptions.TryGetValue(address, out callback))
|
|
{
|
|
Log.Debug("OnDataChange for {Address} but no callback registered", address);
|
|
return;
|
|
}
|
|
}
|
|
|
|
callback.Invoke(address, vtq);
|
|
|
|
// Also route to the SubscriptionManager's global handler
|
|
OnTagValueChanged?.Invoke(address, 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.
|
|
/// Kept wired for diagnostic logging only — writes are resolved synchronously
|
|
/// when the Write() COM call returns without throwing.
|
|
/// </summary>
|
|
private void OnWriteComplete(
|
|
int hLMXServerHandle,
|
|
int phItemHandle,
|
|
ref MXSTATUS_PROXY[] ItemStatus)
|
|
{
|
|
try
|
|
{
|
|
if (ItemStatus != null && ItemStatus.Length > 0)
|
|
{
|
|
var status = ItemStatus[0];
|
|
if (status.success == 0)
|
|
{
|
|
Log.Warning("OnWriteComplete callback: write failed for handle {Handle}: {Status}",
|
|
phItemHandle, MxStatusMapper.FormatStatus(status.detail, (int)status.category, (int)status.detectedBy));
|
|
}
|
|
else
|
|
{
|
|
Log.Debug("OnWriteComplete callback: write succeeded for handle {Handle}", phItemHandle);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log.Debug("OnWriteComplete callback: no status for 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;
|
|
}
|
|
}
|
|
}
|