v3(galaxy): re-key GalaxyDriver to RawPath identity (dial attributeRef)
Wave-B driver re-keying. Under v3 the server addresses Galaxy nodes by RawPath and the driver dials the Galaxy attributeRef (tag_name.AttributeName). The dialled address moved into TagConfig under the camelCase key `attributeRef` (renamed from the pre-v3 PascalCase FullName) and rides on RawTagEntry. Options + factory: - GalaxyDriverOptions gains `IReadOnlyList<RawTagEntry> RawTags` (Contracts now references the zero-dep Core.Abstractions leaf for RawTagEntry — same as Modbus.Contracts). Factory binds `List<RawTagEntry>? RawTags` from DriverConfig. Boundary translation (built once from options.RawTags): - RawPath -> attributeRef (forward dial) on Read / Write / Subscribe / Ack. - attributeRef -> RawPath (reverse) on the value fan-out (OnDataChange) and the alarm ConditionId — so the alarm resolves back to the same RawPath the value path surfaces. Both legs fall back to identity on a miss, so the live-browse discovery path (no authored tags) and synthetic alarm sub-refs (attributeRef.InAlarm) dial straight through unchanged. - WriteAsync dials the attributeRef but resolves security via the RawPath-keyed map the discoverer captured (reverse-maps in the resolver closure). Discovery + alarms: - GalaxyDiscoverer emits FullName = RawPath for authored attributes (falls back to attributeRef), threads RawTagEntry.WriteIdempotent onto DriverAttributeInfo, and gains an optional attributeRef -> RawTagEntry resolver ctor param. - AlarmRefBuilder.Build(conditionReference, dialReference): SourceName/ConditionId = RawPath identity; the five live-state/ack sub-refs dial off the attributeRef. ReinitializeAsync equivalence now compares the session-shape sections field-wise, excluding RawTags (address-space state re-applied via rediscovery, and a record's synthesized equality would compare the lists by reference). Tests: +9 (discoverer RawPath/WriteIdempotent + alarm identity; factory RawTags binding; driver read/write/subscribe/alarm boundary translation). Existing 304 preserved via identity fallback. 313 pass, 5 live-gated skips.
This commit is contained in:
@@ -32,24 +32,36 @@ internal static class AlarmRefBuilder
|
||||
|
||||
/// <summary>
|
||||
/// Build an <see cref="AlarmConditionInfo"/> for an alarm-bearing attribute with all
|
||||
/// five sub-attribute references populated. <paramref name="fullReference"/> is the
|
||||
/// attribute's full reference (e.g. <c>"Tank1.Level.HiHi"</c>); the convention prefixes
|
||||
/// each suffix to it.
|
||||
/// five sub-attribute references populated.
|
||||
/// <para>
|
||||
/// v3 splits identity from dialling: <paramref name="conditionReference"/> is the alarm's
|
||||
/// <b>RawPath</b> — the condition's stable identity, surfaced as
|
||||
/// <see cref="AlarmConditionInfo.SourceName"/> and matched against the
|
||||
/// <c>ConditionId</c> the driver reports on every transition and against the value
|
||||
/// fan-out key. <paramref name="dialReference"/> is the Galaxy <b>attributeRef</b>
|
||||
/// (<c>tag_name.AttributeName</c>, e.g. <c>"Tank1.Level.HiHi"</c>) — the address MXAccess
|
||||
/// understands; the five live-state / ack sub-refs prefix each suffix onto it so the
|
||||
/// server's <c>AlarmConditionService</c> subscribes + acks the dialled attributes. In the
|
||||
/// identity case (no RawPath mapping) both arguments are the same string, reproducing the
|
||||
/// pre-v3 shape exactly.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="fullReference">The full reference of the alarm-bearing attribute.</param>
|
||||
/// <param name="conditionReference">The alarm condition's RawPath identity (= <see cref="AlarmConditionInfo.SourceName"/>).</param>
|
||||
/// <param name="dialReference">The Galaxy attributeRef the five sub-refs are dialled from.</param>
|
||||
/// <param name="initialSeverity">The initial alarm severity level.</param>
|
||||
/// <param name="initialDescription">The initial alarm description.</param>
|
||||
/// <returns>The populated <see cref="AlarmConditionInfo"/> with all five sub-attribute references.</returns>
|
||||
public static AlarmConditionInfo Build(
|
||||
string fullReference,
|
||||
string conditionReference,
|
||||
string dialReference,
|
||||
AlarmSeverity initialSeverity = AlarmSeverity.Medium,
|
||||
string? initialDescription = null) => new(
|
||||
SourceName: fullReference,
|
||||
SourceName: conditionReference,
|
||||
InitialSeverity: initialSeverity,
|
||||
InitialDescription: initialDescription,
|
||||
InAlarmRef: fullReference + InAlarmSuffix,
|
||||
PriorityRef: fullReference + PrioritySuffix,
|
||||
DescAttrNameRef: fullReference + DescAttrNameSuffix,
|
||||
AckedRef: fullReference + AckedSuffix,
|
||||
AckMsgWriteRef: fullReference + AckMsgSuffix);
|
||||
InAlarmRef: dialReference + InAlarmSuffix,
|
||||
PriorityRef: dialReference + PrioritySuffix,
|
||||
DescAttrNameRef: dialReference + DescAttrNameSuffix,
|
||||
AckedRef: dialReference + AckedSuffix,
|
||||
AckMsgWriteRef: dialReference + AckMsgSuffix);
|
||||
}
|
||||
|
||||
@@ -31,11 +31,28 @@ public sealed class GalaxyDiscoverer
|
||||
{
|
||||
private readonly IGalaxyHierarchySource _source;
|
||||
|
||||
// v3 RawPath keying. Given a discovered Galaxy attributeRef (tag_name.AttributeName), this
|
||||
// resolves the authored RawTagEntry the deploy artifact delivered for it (keyed by attributeRef),
|
||||
// or null when the attribute is not authored. When present, the emitted node reference
|
||||
// (DriverAttributeInfo.FullName) becomes the entry's RawPath — the v3 wire identity the server
|
||||
// hands back on read/write/subscribe — and WriteIdempotent flows off the entry. When absent (no
|
||||
// mapping, or the live-browse path with no authored tags) the reference falls back to the
|
||||
// attributeRef, reproducing the pre-v3 shape exactly.
|
||||
private readonly Func<string, RawTagEntry?>? _rawTagByAttributeRef;
|
||||
|
||||
/// <summary>Initializes a new GalaxyDiscoverer with the specified hierarchy source.</summary>
|
||||
/// <param name="source">The Galaxy hierarchy source to use for discovery.</param>
|
||||
public GalaxyDiscoverer(IGalaxyHierarchySource source)
|
||||
/// <param name="rawTagByAttributeRef">
|
||||
/// Optional v3 resolver mapping a discovered Galaxy attributeRef to its authored
|
||||
/// <see cref="RawTagEntry"/> (RawPath + WriteIdempotent). Null (or a null return) leaves the
|
||||
/// emitted reference as the attributeRef — the identity / live-browse fallback.
|
||||
/// </param>
|
||||
public GalaxyDiscoverer(
|
||||
IGalaxyHierarchySource source,
|
||||
Func<string, RawTagEntry?>? rawTagByAttributeRef = null)
|
||||
{
|
||||
_source = source ?? throw new ArgumentNullException(nameof(source));
|
||||
_rawTagByAttributeRef = rawTagByAttributeRef;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -79,12 +96,20 @@ public sealed class GalaxyDiscoverer
|
||||
{
|
||||
if (string.IsNullOrEmpty(attr.AttributeName)) continue;
|
||||
|
||||
var fullReference = !string.IsNullOrEmpty(attr.FullTagReference)
|
||||
// attributeRef = the Galaxy dialled address (tag_name.AttributeName) MXAccess reads/writes.
|
||||
var attributeRef = !string.IsNullOrEmpty(attr.FullTagReference)
|
||||
? StripArraySuffix(attr.FullTagReference)
|
||||
: obj.TagName + "." + attr.AttributeName;
|
||||
|
||||
// v3: the node's driver reference (FullName) is the authored RawPath when this
|
||||
// attribute is authored; otherwise it degrades to the attributeRef (identity). The
|
||||
// WriteIdempotent platform flag travels on the RawTagEntry, not inside the blob.
|
||||
var entry = _rawTagByAttributeRef?.Invoke(attributeRef);
|
||||
var rawPath = entry?.RawPath ?? attributeRef;
|
||||
var writeIdempotent = entry?.WriteIdempotent ?? false;
|
||||
|
||||
var info = new DriverAttributeInfo(
|
||||
FullName: fullReference,
|
||||
FullName: rawPath,
|
||||
DriverDataType: DataTypeMap.Map(attr.MxDataType),
|
||||
IsArray: attr.IsArray,
|
||||
ArrayDim: attr.IsArray && attr.ArrayDimensionPresent && attr.ArrayDimension > 0
|
||||
@@ -92,15 +117,18 @@ public sealed class GalaxyDiscoverer
|
||||
: null,
|
||||
SecurityClass: SecurityMap.Map(attr.SecurityClassification),
|
||||
IsHistorized: attr.IsHistorized,
|
||||
IsAlarm: attr.IsAlarm);
|
||||
IsAlarm: attr.IsAlarm,
|
||||
WriteIdempotent: writeIdempotent);
|
||||
|
||||
var handle = folder.Variable(attr.AttributeName, attr.AttributeName, info);
|
||||
|
||||
// Alarm-bearing attributes ship the full sub-attribute ref set so the server's
|
||||
// AlarmConditionService can subscribe + ack-write without re-deriving the names.
|
||||
// AlarmConditionService can subscribe + ack-write without re-deriving the names. The
|
||||
// condition identity (SourceName / ConditionId) is the RawPath — matching the value
|
||||
// fan-out key — while the five live-state sub-refs dial off the Galaxy attributeRef.
|
||||
if (attr.IsAlarm)
|
||||
{
|
||||
handle.MarkAsAlarmCondition(AlarmRefBuilder.Build(fullReference));
|
||||
handle.MarkAsAlarmCondition(AlarmRefBuilder.Build(rawPath, attributeRef));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user