feat(lmxproxy): phase 1 — v2 protocol types and domain model
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using ArchestrA.MxAccess;
|
||||
using ZB.MOM.WW.LmxProxy.Host.Domain;
|
||||
|
||||
namespace ZB.MOM.WW.LmxProxy.Host.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Event handlers for MxAccessClient to process data changes, write completions, and operation completions.
|
||||
/// </summary>
|
||||
public sealed partial class MxAccessClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles data change events from the MxAccess server.
|
||||
/// </summary>
|
||||
/// <param name="hLMXServerHandle">Server handle.</param>
|
||||
/// <param name="phItemHandle">Item handle.</param>
|
||||
/// <param name="pvItemValue">Item value.</param>
|
||||
/// <param name="pwItemQuality">Item quality code.</param>
|
||||
/// <param name="pftItemTimeStamp">Item timestamp.</param>
|
||||
/// <param name="ItemStatus">Status array.</param>
|
||||
private void OnDataChange(int hLMXServerHandle, int phItemHandle, object pvItemValue,
|
||||
int pwItemQuality, object pftItemTimeStamp, ref MXSTATUS_PROXY[] ItemStatus)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_subscriptionsByHandle.TryGetValue(phItemHandle, out SubscriptionInfo? subscription))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert quality from integer
|
||||
Quality quality = ConvertQuality(pwItemQuality);
|
||||
DateTime timestamp = ConvertTimestamp(pftItemTimeStamp);
|
||||
var vtq = new Vtq(pvItemValue, timestamp, quality);
|
||||
|
||||
// Invoke callback
|
||||
subscription.Callback?.Invoke(subscription.Address, vtq);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Error processing data change for handle {Handle}", phItemHandle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles write completion events from the MxAccess server.
|
||||
/// </summary>
|
||||
/// <param name="hLMXServerHandle">Server handle.</param>
|
||||
/// <param name="phItemHandle">Item handle.</param>
|
||||
/// <param name="ItemStatus">Status array.</param>
|
||||
private void OnWriteComplete(int hLMXServerHandle, int phItemHandle, ref MXSTATUS_PROXY[] ItemStatus)
|
||||
{
|
||||
try
|
||||
{
|
||||
WriteOperation? writeOp;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_pendingWrites.TryGetValue(phItemHandle, out writeOp))
|
||||
{
|
||||
_pendingWrites.Remove(phItemHandle);
|
||||
}
|
||||
}
|
||||
|
||||
if (writeOp != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ItemStatus is { Length: > 0 })
|
||||
{
|
||||
var status = ItemStatus[0];
|
||||
if (status.success == 0)
|
||||
{
|
||||
string errorMsg = GetWriteErrorMessage(status.detail);
|
||||
Logger.Warning(
|
||||
"Write failed for {Address} (handle {Handle}): {Error} (Category={Category}, Detail={Detail})",
|
||||
writeOp.Address, phItemHandle, errorMsg, status.category, status.detail);
|
||||
|
||||
writeOp.CompletionSource.TrySetException(new InvalidOperationException(
|
||||
$"Write failed: {errorMsg}"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("Write completed successfully for {Address} (handle {Handle})",
|
||||
writeOp.Address, phItemHandle);
|
||||
writeOp.CompletionSource.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("Write completed for {Address} (handle {Handle}) with no status",
|
||||
writeOp.Address, phItemHandle);
|
||||
writeOp.CompletionSource.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Clean up the item after write completes
|
||||
lock (_lock)
|
||||
{
|
||||
if (_lmxProxy != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_lmxProxy.UnAdvise(_connectionHandle, phItemHandle);
|
||||
_lmxProxy.RemoveItem(_connectionHandle, phItemHandle);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug(ex, "Error cleaning up after write for handle {Handle}", phItemHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ItemStatus is { Length: > 0 })
|
||||
{
|
||||
var status = ItemStatus[0];
|
||||
if (status.success == 0)
|
||||
{
|
||||
Logger.Warning("Write failed for unknown handle {Handle}: Category={Category}, Detail={Detail}",
|
||||
phItemHandle, status.category, status.detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Error processing write complete for handle {Handle}", phItemHandle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles operation completion events from the MxAccess server.
|
||||
/// </summary>
|
||||
/// <param name="hLMXServerHandle">Server handle.</param>
|
||||
/// <param name="phItemHandle">Item handle.</param>
|
||||
/// <param name="ItemStatus">Status array.</param>
|
||||
private void OnOperationComplete(int hLMXServerHandle, int phItemHandle, ref MXSTATUS_PROXY[] ItemStatus)
|
||||
{
|
||||
// Log operation completion
|
||||
Logger.Debug("Operation complete for handle {Handle}", phItemHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an integer MxAccess quality code to <see cref="Quality" />.
|
||||
/// </summary>
|
||||
/// <param name="mxQuality">The MxAccess quality code.</param>
|
||||
/// <returns>The corresponding <see cref="Quality" /> value.</returns>
|
||||
private Quality ConvertQuality(int mxQuality) => (Quality)mxQuality;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a timestamp object to <see cref="DateTime" /> in UTC.
|
||||
/// </summary>
|
||||
/// <param name="timestamp">The timestamp object.</param>
|
||||
/// <returns>The UTC <see cref="DateTime" /> value.</returns>
|
||||
private DateTime ConvertTimestamp(object timestamp)
|
||||
{
|
||||
if (timestamp is DateTime dt)
|
||||
{
|
||||
return dt.Kind == DateTimeKind.Utc ? dt : dt.ToUniversalTime();
|
||||
}
|
||||
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user