Renames all 11 projects (5 src + 6 tests), the .slnx solution file, all source-file namespaces, all axaml namespace references, and all v1 documentation references in CLAUDE.md and docs/*.md (excluding docs/v2/ which is already in OtOpcUa form). Also updates the TopShelf service registration name from "LmxOpcUa" to "OtOpcUa" per Phase 0 Task 0.6.
Preserves runtime identifiers per Phase 0 Out-of-Scope rules to avoid breaking v1/v2 client trust during coexistence: OPC UA `ApplicationUri` defaults (`urn:{GalaxyName}:LmxOpcUa`), server `EndpointPath` (`/LmxOpcUa`), `ServerName` default (feeds cert subject CN), `MxAccessConfiguration.ClientName` default (defensive — stays "LmxOpcUa" for MxAccess audit-trail consistency), client OPC UA identifiers (`ApplicationName = "LmxOpcUaClient"`, `ApplicationUri = "urn:localhost:LmxOpcUaClient"`, cert directory `%LocalAppData%\LmxOpcUaClient\pki\`), and the `LmxOpcUaServer` class name (class rename out of Phase 0 scope per Task 0.5 sed pattern; happens in Phase 1 alongside `LmxNodeManager → GenericDriverNodeManager` Core extraction). 23 LmxOpcUa references retained, all enumerated and justified in `docs/v2/implementation/exit-gate-phase-0.md`.
Build clean: 0 errors, 30 warnings (lower than baseline 167). Tests at strict improvement over baseline: 821 passing / 1 failing vs baseline 820 / 2 (one flaky pre-existing failure passed this run; the other still fails — both pre-existing and unrelated to the rename). `Client.UI.Tests`, `Historian.Aveva.Tests`, `Client.Shared.Tests`, `IntegrationTests` all match baseline exactly. Exit gate compliance results recorded in `docs/v2/implementation/exit-gate-phase-0.md` with all 7 checks PASS or DEFERRED-to-PR-review (#7 service install verification needs Windows service permissions on the reviewer's box).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
97 lines
3.6 KiB
C#
97 lines
3.6 KiB
C#
using System;
|
|
using ArchestrA.MxAccess;
|
|
using ZB.MOM.WW.OtOpcUa.Host.Domain;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Host.MxAccess
|
|
{
|
|
public sealed partial class MxAccessClient
|
|
{
|
|
/// <summary>
|
|
/// COM event handler for MxAccess OnDataChange events.
|
|
/// Signature matches the ArchestrA.MxAccess ILMXProxyServerEvents interface.
|
|
/// </summary>
|
|
private void HandleOnDataChange(
|
|
int hLMXServerHandle,
|
|
int phItemHandle,
|
|
object pvItemValue,
|
|
int pwItemQuality,
|
|
object pftItemTimeStamp,
|
|
ref MXSTATUS_PROXY[] ItemStatus)
|
|
{
|
|
try
|
|
{
|
|
if (!_handleToAddress.TryGetValue(phItemHandle, out var address))
|
|
{
|
|
Log.Debug("OnDataChange for unknown handle {Handle}", phItemHandle);
|
|
return;
|
|
}
|
|
|
|
var quality = QualityMapper.MapFromMxAccessQuality(pwItemQuality);
|
|
|
|
// Check MXSTATUS_PROXY — if success is false, use more specific quality
|
|
if (ItemStatus != null && ItemStatus.Length > 0 && ItemStatus[0].success == 0)
|
|
quality = MxErrorCodes.MapToQuality(ItemStatus[0].detail);
|
|
|
|
var timestamp = ConvertTimestamp(pftItemTimeStamp);
|
|
var vtq = new Vtq(pvItemValue, timestamp, quality);
|
|
|
|
// Update probe timestamp
|
|
if (string.Equals(address, _probeTag, StringComparison.OrdinalIgnoreCase))
|
|
_lastProbeValueTime = DateTime.UtcNow;
|
|
|
|
// Invoke stored subscription callback
|
|
if (_storedSubscriptions.TryGetValue(address, out var callback)) callback(address, vtq);
|
|
|
|
if (_pendingReadsByAddress.TryGetValue(address, out var pendingReads))
|
|
foreach (var pendingRead in pendingReads.Values)
|
|
pendingRead.TrySetResult(vtq);
|
|
|
|
// Global handler
|
|
OnTagValueChanged?.Invoke(address, vtq);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "Error processing OnDataChange for handle {Handle}", phItemHandle);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// COM event handler for MxAccess OnWriteComplete events.
|
|
/// </summary>
|
|
private void HandleOnWriteComplete(
|
|
int hLMXServerHandle,
|
|
int phItemHandle,
|
|
ref MXSTATUS_PROXY[] ItemStatus)
|
|
{
|
|
try
|
|
{
|
|
if (_pendingWrites.TryRemove(phItemHandle, out var tcs))
|
|
{
|
|
var success = ItemStatus == null || ItemStatus.Length == 0 || ItemStatus[0].success != 0;
|
|
if (success)
|
|
{
|
|
tcs.TrySetResult(true);
|
|
}
|
|
else
|
|
{
|
|
var detail = ItemStatus![0].detail;
|
|
var message = MxErrorCodes.GetMessage(detail);
|
|
Log.Warning("Write failed for handle {Handle}: {Message}", phItemHandle, message);
|
|
tcs.TrySetResult(false);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "Error processing OnWriteComplete for handle {Handle}", phItemHandle);
|
|
}
|
|
}
|
|
|
|
private static DateTime ConvertTimestamp(object pftItemTimeStamp)
|
|
{
|
|
if (pftItemTimeStamp is DateTime dt)
|
|
return dt.Kind == DateTimeKind.Utc ? dt : dt.ToUniversalTime();
|
|
return DateTime.UtcNow;
|
|
}
|
|
}
|
|
} |