a25593a9c6
Group all 69 projects into category subfolders under src/ and tests/ so the Rider Solution Explorer mirrors the module structure. Folders: Core, Server, Drivers (with a nested Driver CLIs subfolder), Client, Tooling. - Move every project folder on disk with git mv (history preserved as renames). - Recompute relative paths in 57 .csproj files: cross-category ProjectReferences, the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external mxaccessgw refs in Driver.Galaxy and its test project. - Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders. - Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL, integration, install). Build green (0 errors); unit tests pass. Docs left for a separate pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92 lines
4.4 KiB
C#
92 lines
4.4 KiB
C#
using MxGateway.Contracts.Proto.Galaxy;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;
|
|
|
|
/// <summary>
|
|
/// Translates a Galaxy object hierarchy (from <see cref="IGalaxyHierarchySource"/>) into
|
|
/// <see cref="IAddressSpaceBuilder"/> calls — folders for each gobject, variables for
|
|
/// each dynamic attribute. Alarm-bearing attributes get all five sub-attribute refs
|
|
/// populated via <see cref="AlarmRefBuilder"/> so the server-level alarm subsystem
|
|
/// (PR 2.2) can subscribe + ack without help from the driver.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Hierarchy materialisation rules (mirror legacy <c>MxAccessGalaxyBackend.DiscoverAsync</c>):
|
|
/// <list type="bullet">
|
|
/// <item>Browse name = <c>contained_name</c> when present; falls back to <c>tag_name</c>.</item>
|
|
/// <item>Folder per gobject; variables placed inside their owner folder.</item>
|
|
/// <item>Variable's full reference = <c>tag_name.attribute_name</c> — the format MXAccess
|
|
/// expects for read/write addressing (translated from the contained-name browse path).</item>
|
|
/// <item>Hierarchy is rendered flat (one folder per gobject under the driver root) for
|
|
/// this PR. PR 4.W's address-space wiring revisits whether to nest under
|
|
/// <c>parent_gobject_id</c> for a true tree shape.</item>
|
|
/// </list>
|
|
/// </remarks>
|
|
public sealed class GalaxyDiscoverer
|
|
{
|
|
private readonly IGalaxyHierarchySource _source;
|
|
|
|
public GalaxyDiscoverer(IGalaxyHierarchySource source)
|
|
{
|
|
_source = source ?? throw new ArgumentNullException(nameof(source));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Drive the supplied builder with one folder + N variables per Galaxy object the
|
|
/// gateway returns. Idempotent — caller can re-invoke after a redeploy event.
|
|
/// </summary>
|
|
public async Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(builder);
|
|
var objects = await _source.GetHierarchyAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
foreach (var obj in objects)
|
|
{
|
|
var browseName = string.IsNullOrEmpty(obj.ContainedName) ? obj.TagName : obj.ContainedName;
|
|
if (string.IsNullOrEmpty(browseName)) continue; // skip objects with no usable identity
|
|
|
|
var folder = builder.Folder(browseName, browseName);
|
|
|
|
foreach (var attr in obj.Attributes)
|
|
{
|
|
if (string.IsNullOrEmpty(attr.AttributeName)) continue;
|
|
|
|
var fullReference = !string.IsNullOrEmpty(attr.FullTagReference)
|
|
? StripArraySuffix(attr.FullTagReference)
|
|
: obj.TagName + "." + attr.AttributeName;
|
|
|
|
var info = new DriverAttributeInfo(
|
|
FullName: fullReference,
|
|
DriverDataType: DataTypeMap.Map(attr.MxDataType),
|
|
IsArray: attr.IsArray,
|
|
ArrayDim: attr.IsArray && attr.ArrayDimensionPresent && attr.ArrayDimension > 0
|
|
? (uint)attr.ArrayDimension
|
|
: null,
|
|
SecurityClass: SecurityMap.Map(attr.SecurityClassification),
|
|
IsHistorized: attr.IsHistorized,
|
|
IsAlarm: attr.IsAlarm);
|
|
|
|
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.
|
|
if (attr.IsAlarm)
|
|
{
|
|
handle.MarkAsAlarmCondition(AlarmRefBuilder.Build(fullReference));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// PR 5.W workaround for mxaccessgw GalaxyRepository.cs:173-175 — the gateway's
|
|
// SQL appends `[]` to array-typed `full_tag_reference` values, but MxAccess COM
|
|
// `IInstance.AddItem` doesn't accept `[]`-suffixed addresses (so any downstream
|
|
// Subscribe/Read/Write through the worker would fail with the suffixed form).
|
|
// Strip defensively here so the parity matrix can run today; remove once the
|
|
// gw fix (mxaccessgw/requirements-array-suffix-fix.md) lands.
|
|
private static string StripArraySuffix(string fullReference) =>
|
|
fullReference.EndsWith("[]", StringComparison.Ordinal)
|
|
? fullReference[..^2]
|
|
: fullReference;
|
|
}
|