fix(historian-gateway): historize under the historian name, not the mux ref, when HistorianTagname overrides (FU-3)
The continuous-historization recorder conflated two identifiers into one string: the dependency mux fans DependencyValueChanged keyed by the driver FullName (the mux ref), but a value must be historized under the resolved historian name (HistorianTagname override, else FullName). In the common no-override case the two are equal, so it worked; with an override they diverge and the recorder registered mux interest under a key the mux never fans — that tag's values were never captured (and, had they been, would have been written under the mux ref). Carry BOTH identifiers through the seam: a new HistorizedTagRef(MuxRef, HistorianName) record on IHistorizedTagSubscriptionSink. The applier resolves MuxRef = FullName and HistorianName = override-or-FullName. The recorder now keeps a muxRef->historianName map: it registers/filters mux interest by MuxRef but writes the outbox entry (and drains) under HistorianName. The convergence handler re-registers the mux only when the registered key-set changes, so an override-only rename (same FullName) updates the write target without mux churn. Tests: a divergent-override recorder test (interest by mux ref, value written under the override name, never the mux ref) + an override-rename no-churn test; the applier feed tests now assert the full (mux ref, historian name) pairs. Runtime 348/0, OpcUaServer 327/0; 0 warnings. Closes FU-3. Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
+38
-7
@@ -1,5 +1,35 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// A single historized tag the recorder tracks, carrying BOTH identifiers it needs — kept distinct
|
||||
/// because a <c>HistorianTagname</c> override makes them diverge:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <see cref="MuxRef"/> — the driver-published reference the per-node dependency mux fans its
|
||||
/// <c>DependencyValueChanged</c> by (the tag's driver-side <c>FullName</c>). The recorder
|
||||
/// registers mux interest and matches incoming values by THIS.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <see cref="HistorianName"/> — the resolved historian tag name the value is written under (a
|
||||
/// non-alarm historized value variable's <c>HistorianTagname</c> override, else its
|
||||
/// <c>FullName</c>) — the SAME name the EnsureTags provisioning hook ensures.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// In the common (no-override) case the two are the same string; an override is the only case they
|
||||
/// diverge, and conflating them would silently drop that tag's values (interest registered under a
|
||||
/// key the mux never fans).
|
||||
/// </summary>
|
||||
/// <param name="MuxRef">The driver ref the mux fans by (and the key the recorder registers interest under).</param>
|
||||
/// <param name="HistorianName">The resolved historian tag name the value is historized under.</param>
|
||||
public sealed record HistorizedTagRef(string MuxRef, string HistorianName)
|
||||
{
|
||||
/// <summary>The no-override identity: the mux ref and historian name are the same string (the tag has
|
||||
/// no <c>HistorianTagname</c> override, so it historizes under its own driver <c>FullName</c>).</summary>
|
||||
/// <param name="reference">The driver ref that serves as both the mux key and the historian name.</param>
|
||||
/// <returns>A ref whose <see cref="MuxRef"/> and <see cref="HistorianName"/> are equal.</returns>
|
||||
public static HistorizedTagRef ForSelf(string reference) => new(reference, reference);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-side feed that keeps the continuous-historization recorder's set of historized tag refs
|
||||
/// in step with the deployed address space. The <c>AddressSpaceApplier</c> (in the
|
||||
@@ -18,15 +48,16 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
public interface IHistorizedTagSubscriptionSink
|
||||
{
|
||||
/// <summary>
|
||||
/// Converge the recorder's historized-ref interest by an add/remove delta. The refs are
|
||||
/// resolved EXACTLY as the EnsureTags provisioning hook resolves them (a non-alarm historized
|
||||
/// value variable's <c>HistorianTagname</c> override, else its driver-side <c>FullName</c>).
|
||||
/// The recorder applies the delta to its tracked full set and re-registers mux interest only
|
||||
/// when the set actually changes.
|
||||
/// Converge the recorder's historized-ref interest by an add/remove delta. Each ref carries both
|
||||
/// its <see cref="HistorizedTagRef.MuxRef"/> (the driver ref the mux fans by) and its
|
||||
/// <see cref="HistorizedTagRef.HistorianName"/> (the resolved override-or-FullName the value is
|
||||
/// historized under) — the same name the EnsureTags provisioning hook ensures. The recorder
|
||||
/// applies the delta to its tracked full set and re-registers mux interest (keyed by
|
||||
/// <see cref="HistorizedTagRef.MuxRef"/>) only when the registered key-set actually changes.
|
||||
/// </summary>
|
||||
/// <param name="added">Historized refs newly historized by this deploy (added/changed-into tags).</param>
|
||||
/// <param name="removed">Historized refs no longer historized by this deploy (removed/changed-out tags).</param>
|
||||
void UpdateHistorizedRefs(IReadOnlyList<string> added, IReadOnlyList<string> removed);
|
||||
void UpdateHistorizedRefs(IReadOnlyList<HistorizedTagRef> added, IReadOnlyList<HistorizedTagRef> removed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -42,7 +73,7 @@ public sealed class NullHistorizedTagSubscriptionSink : IHistorizedTagSubscripti
|
||||
private NullHistorizedTagSubscriptionSink() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateHistorizedRefs(IReadOnlyList<string> added, IReadOnlyList<string> removed)
|
||||
public void UpdateHistorizedRefs(IReadOnlyList<HistorizedTagRef> added, IReadOnlyList<HistorizedTagRef> removed)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user