feat(opcua): remove SystemPlatform mirror producer + Galaxy exception from Phase7Composer
This commit is contained in:
@@ -335,43 +335,18 @@ public static class Phase7Composer
|
|||||||
.Select(a => new ScriptedAlarmPlan(a.ScriptedAlarmId, a.EquipmentId, a.PredicateScriptId, a.MessageTemplate))
|
.Select(a => new ScriptedAlarmPlan(a.ScriptedAlarmId, a.EquipmentId, a.PredicateScriptId, a.MessageTemplate))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// SystemPlatform tags = Galaxy tags. Match each tag to its DriverInstance and that
|
|
||||||
// driver's Namespace; emit only when the namespace kind is SystemPlatform AND the tag
|
|
||||||
// has no EquipmentId (per the entity invariant for SystemPlatform).
|
|
||||||
var driversById = driverInstances.ToDictionary(d => d.DriverInstanceId, StringComparer.Ordinal);
|
var driversById = driverInstances.ToDictionary(d => d.DriverInstanceId, StringComparer.Ordinal);
|
||||||
var namespacesById = namespaces.ToDictionary(n => n.NamespaceId, StringComparer.Ordinal);
|
var namespacesById = namespaces.ToDictionary(n => n.NamespaceId, StringComparer.Ordinal);
|
||||||
|
|
||||||
var galaxyTags = tags
|
// Equipment tags = a Tag bound to an Equipment (non-null EquipmentId) whose driver's namespace
|
||||||
.Where(t => t.EquipmentId is null)
|
// is Equipment-kind. FullName is the driver-side wire reference pulled from TagConfig — it
|
||||||
.Where(t => driversById.TryGetValue(t.DriverInstanceId, out var di)
|
// becomes the variable's NodeId + read/write routing key. Galaxy points are ordinary equipment
|
||||||
&& namespacesById.TryGetValue(di.NamespaceId, out var ns)
|
// tags now (GalaxyMxGateway is a standard Equipment-kind driver), so no driver-type exception.
|
||||||
&& ns.Kind == NamespaceKind.SystemPlatform)
|
|
||||||
.OrderBy(t => t.DriverInstanceId, StringComparer.Ordinal)
|
|
||||||
.ThenBy(t => t.FolderPath, StringComparer.Ordinal)
|
|
||||||
.ThenBy(t => t.Name, StringComparer.Ordinal)
|
|
||||||
.Select(t => new GalaxyTagPlan(
|
|
||||||
TagId: t.TagId,
|
|
||||||
DriverInstanceId: t.DriverInstanceId,
|
|
||||||
FolderPath: t.FolderPath ?? string.Empty,
|
|
||||||
DisplayName: t.Name,
|
|
||||||
DataType: t.DataType,
|
|
||||||
// MXAccess reference: "FolderPath.Name" when FolderPath is set, else just "Name".
|
|
||||||
MxAccessRef: string.IsNullOrWhiteSpace(t.FolderPath) ? t.Name : $"{t.FolderPath}.{t.Name}"))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Equipment tags = the inverse filter: a Tag bound to an Equipment (non-null EquipmentId)
|
|
||||||
// whose driver's namespace is Equipment-kind. FullName is the driver-side wire reference
|
|
||||||
// pulled from TagConfig — it becomes the variable's NodeId + read/write routing key.
|
|
||||||
// A Galaxy alias is the one exception to the Equipment-kind namespace rule: a GalaxyMxGateway
|
|
||||||
// driver lives in a SystemPlatform-kind namespace, yet an equipment-scoped alias Tag bound to
|
|
||||||
// it must still surface as an equipment tag — so admit those by DriverType. The galaxyTags
|
|
||||||
// producer keeps its `t.EquipmentId is null` guard, so an alias (EquipmentId set) never
|
|
||||||
// double-counts there.
|
|
||||||
var equipmentTags = tags
|
var equipmentTags = tags
|
||||||
.Where(t => t.EquipmentId is not null)
|
.Where(t => t.EquipmentId is not null)
|
||||||
.Where(t => driversById.TryGetValue(t.DriverInstanceId, out var di)
|
.Where(t => driversById.TryGetValue(t.DriverInstanceId, out var di)
|
||||||
&& namespacesById.TryGetValue(di.NamespaceId, out var ns)
|
&& namespacesById.TryGetValue(di.NamespaceId, out var ns)
|
||||||
&& (ns.Kind == NamespaceKind.Equipment || di.DriverType == "GalaxyMxGateway"))
|
&& ns.Kind == NamespaceKind.Equipment)
|
||||||
.OrderBy(t => t.EquipmentId, StringComparer.Ordinal)
|
.OrderBy(t => t.EquipmentId, StringComparer.Ordinal)
|
||||||
.ThenBy(t => t.FolderPath ?? string.Empty, StringComparer.Ordinal) // coalesce so the sort matches the artifact-decode side exactly
|
.ThenBy(t => t.FolderPath ?? string.Empty, StringComparer.Ordinal) // coalesce so the sort matches the artifact-decode side exactly
|
||||||
.ThenBy(t => t.Name, StringComparer.Ordinal)
|
.ThenBy(t => t.Name, StringComparer.Ordinal)
|
||||||
@@ -465,7 +440,7 @@ public static class Phase7Composer
|
|||||||
Enabled: a.Enabled));
|
Enabled: a.Enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Phase7CompositionResult(areas, lines, nodes, plans, alarms, galaxyTags)
|
return new Phase7CompositionResult(areas, lines, nodes, plans, alarms, Array.Empty<GalaxyTagPlan>())
|
||||||
{
|
{
|
||||||
EquipmentTags = equipmentTags,
|
EquipmentTags = equipmentTags,
|
||||||
EquipmentVirtualTags = equipmentVirtualTags,
|
EquipmentVirtualTags = equipmentVirtualTags,
|
||||||
|
|||||||
@@ -6,35 +6,36 @@ using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
|||||||
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
|
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verifies the live-edit compose seam (<see cref="Phase7Composer.Compose"/>) admits a Galaxy
|
/// Verifies the live-edit compose seam (<see cref="Phase7Composer.Compose"/>) treats a Galaxy
|
||||||
/// alias tag — an equipment-scoped <see cref="Tag"/> (non-null <see cref="Tag.EquipmentId"/>)
|
/// point as an ordinary equipment tag now that <c>GalaxyMxGateway</c> is a standard
|
||||||
/// bound to a <c>GalaxyMxGateway</c> driver that lives in a <c>SystemPlatform</c>-kind namespace.
|
/// Equipment-kind driver. An equipment-scoped <see cref="Tag"/> (non-null
|
||||||
/// The broadened equipment-tag filter must surface the alias under
|
/// <see cref="Tag.EquipmentId"/>) bound to a <c>GalaxyMxGateway</c> driver living in an
|
||||||
/// <see cref="Phase7CompositionResult.EquipmentTags"/> (carrying its driver-side FullName) while a
|
/// <c>Equipment</c>-kind namespace must surface under
|
||||||
/// sibling SystemPlatform mirror tag (null EquipmentId) on the same driver stays in
|
/// <see cref="Phase7CompositionResult.EquipmentTags"/> (carrying its driver-side FullName), and
|
||||||
/// <see cref="Phase7CompositionResult.GalaxyTags"/> and never double-counts.
|
/// the retired SystemPlatform-mirror producer means <see cref="Phase7CompositionResult.GalaxyTags"/>
|
||||||
|
/// is always empty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class Phase7ComposerAliasTagTests
|
public sealed class Phase7ComposerAliasTagTests
|
||||||
{
|
{
|
||||||
/// <summary>A <c>GalaxyMxGateway</c> driver in a SystemPlatform namespace carries two tags: an
|
/// <summary>A <c>GalaxyMxGateway</c> driver in an Equipment-kind namespace carries an
|
||||||
/// equipment-scoped alias (EquipmentId set, FolderPath null, TagConfig FullName = the Galaxy ref)
|
/// equipment-scoped Galaxy tag (EquipmentId set, FolderPath null, TagConfig FullName = the Galaxy
|
||||||
/// and a SystemPlatform mirror (EquipmentId null, FolderPath set). Compose must put the alias in
|
/// ref). Compose must put it in EquipmentTags with its FullName, and GalaxyTags must be empty
|
||||||
/// EquipmentTags with its FullName and keep the mirror in GalaxyTags only.</summary>
|
/// (the SystemPlatform mirror producer is gone).</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Compose_admits_galaxy_alias_tag_in_equipment_tags()
|
public void Compose_admits_galaxy_equipment_tag_in_equipment_tags()
|
||||||
{
|
{
|
||||||
var ns = new Namespace
|
var ns = new Namespace
|
||||||
{
|
{
|
||||||
NamespaceId = "ns-sp",
|
NamespaceId = "ns-eq",
|
||||||
ClusterId = "c1",
|
ClusterId = "c1",
|
||||||
Kind = NamespaceKind.SystemPlatform,
|
Kind = NamespaceKind.Equipment,
|
||||||
NamespaceUri = "urn:sp",
|
NamespaceUri = "urn:eq",
|
||||||
};
|
};
|
||||||
var driver = new DriverInstance
|
var driver = new DriverInstance
|
||||||
{
|
{
|
||||||
DriverInstanceId = "drv-galaxy",
|
DriverInstanceId = "drv-galaxy",
|
||||||
ClusterId = "c1",
|
ClusterId = "c1",
|
||||||
NamespaceId = "ns-sp",
|
NamespaceId = "ns-eq",
|
||||||
Name = "Galaxy1",
|
Name = "Galaxy1",
|
||||||
DriverType = "GalaxyMxGateway",
|
DriverType = "GalaxyMxGateway",
|
||||||
DriverConfig = "{}",
|
DriverConfig = "{}",
|
||||||
@@ -50,10 +51,10 @@ public sealed class Phase7ComposerAliasTagTests
|
|||||||
MachineCode = "TESTMACHINE_020",
|
MachineCode = "TESTMACHINE_020",
|
||||||
};
|
};
|
||||||
|
|
||||||
// (a) Equipment-scoped alias: EquipmentId set, FolderPath null, FullName = Galaxy ref.
|
// Equipment-scoped Galaxy tag: EquipmentId set, FolderPath null, FullName = Galaxy ref.
|
||||||
var aliasTag = new Tag
|
var galaxyTag = new Tag
|
||||||
{
|
{
|
||||||
TagId = "tag-alias",
|
TagId = "tag-galaxy",
|
||||||
DriverInstanceId = "drv-galaxy",
|
DriverInstanceId = "drv-galaxy",
|
||||||
EquipmentId = "eq-1",
|
EquipmentId = "eq-1",
|
||||||
FolderPath = null,
|
FolderPath = null,
|
||||||
@@ -63,36 +64,21 @@ public sealed class Phase7ComposerAliasTagTests
|
|||||||
TagConfig = "{\"FullName\":\"TestMachine_020.TestChangingInt\"}",
|
TagConfig = "{\"FullName\":\"TestMachine_020.TestChangingInt\"}",
|
||||||
};
|
};
|
||||||
|
|
||||||
// (b) SystemPlatform mirror: EquipmentId null, FolderPath set → stays a Galaxy tag.
|
|
||||||
var mirrorTag = new Tag
|
|
||||||
{
|
|
||||||
TagId = "tag-mirror",
|
|
||||||
DriverInstanceId = "drv-galaxy",
|
|
||||||
EquipmentId = null,
|
|
||||||
FolderPath = "TestMachine_020",
|
|
||||||
Name = "Speed",
|
|
||||||
DataType = "Float",
|
|
||||||
AccessLevel = TagAccessLevel.Read,
|
|
||||||
TagConfig = "{\"FullName\":\"TestMachine_020.Speed\"}",
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = Phase7Composer.Compose(
|
var result = Phase7Composer.Compose(
|
||||||
new[] { area }, new[] { line }, new[] { equip },
|
new[] { area }, new[] { line }, new[] { equip },
|
||||||
new[] { driver }, Array.Empty<ScriptedAlarm>(),
|
new[] { driver }, Array.Empty<ScriptedAlarm>(),
|
||||||
new[] { aliasTag, mirrorTag }, new[] { ns });
|
new[] { galaxyTag }, new[] { ns });
|
||||||
|
|
||||||
// (a) the alias survives composition as an equipment tag, carrying its Galaxy FullName.
|
// The Galaxy point survives composition as an equipment tag, carrying its Galaxy FullName.
|
||||||
var alias = result.EquipmentTags.ShouldHaveSingleItem();
|
var tag = result.EquipmentTags.ShouldHaveSingleItem();
|
||||||
alias.TagId.ShouldBe("tag-alias");
|
tag.TagId.ShouldBe("tag-galaxy");
|
||||||
alias.EquipmentId.ShouldBe("eq-1");
|
tag.EquipmentId.ShouldBe("eq-1");
|
||||||
alias.DriverInstanceId.ShouldBe("drv-galaxy");
|
tag.DriverInstanceId.ShouldBe("drv-galaxy");
|
||||||
alias.Name.ShouldBe("TestChangingInt");
|
tag.Name.ShouldBe("TestChangingInt");
|
||||||
alias.DataType.ShouldBe("Int32");
|
tag.DataType.ShouldBe("Int32");
|
||||||
alias.FullName.ShouldBe("TestMachine_020.TestChangingInt");
|
tag.FullName.ShouldBe("TestMachine_020.TestChangingInt");
|
||||||
|
|
||||||
// (b) the SystemPlatform mirror stays in GalaxyTags and is NOT double-counted as equipment.
|
// The SystemPlatform-mirror producer is retired → GalaxyTags is always empty.
|
||||||
result.GalaxyTags.ShouldContain(g => g.TagId == "tag-mirror");
|
result.GalaxyTags.ShouldBeEmpty();
|
||||||
result.GalaxyTags.ShouldNotContain(g => g.TagId == "tag-alias");
|
|
||||||
result.EquipmentTags.ShouldNotContain(t => t.TagId == "tag-mirror");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user