Use bracketless OPC UA node IDs for arrays
This commit is contained in:
@@ -130,6 +130,34 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Helpers
|
||||
return Session.ReadValue(nodeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a specific OPC UA attribute from a node.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node whose attribute should be read.</param>
|
||||
/// <param name="attributeId">The OPC UA attribute identifier to read.</param>
|
||||
/// <returns>The attribute value returned by the server.</returns>
|
||||
public DataValue ReadAttribute(NodeId nodeId, uint attributeId)
|
||||
{
|
||||
var nodesToRead = new ReadValueIdCollection
|
||||
{
|
||||
new ReadValueId
|
||||
{
|
||||
NodeId = nodeId,
|
||||
AttributeId = attributeId
|
||||
}
|
||||
};
|
||||
|
||||
Session.Read(
|
||||
null,
|
||||
0,
|
||||
TimestampsToReturn.Neither,
|
||||
nodesToRead,
|
||||
out var results,
|
||||
out _);
|
||||
|
||||
return results[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a node's value, optionally using an OPC UA index range for array element writes.
|
||||
/// Returns the server status code for the write.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Client;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.LmxOpcUa.Host.Domain;
|
||||
@@ -30,7 +32,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Integration
|
||||
await client.ConnectAsync(fixture.EndpointUrl);
|
||||
|
||||
// Browse path: TestMachine_001 -> MESReceiver -> MoveInPartNumbers
|
||||
var nodeId = client.MakeNodeId("MESReceiver_001.MoveInPartNumbers[]");
|
||||
var nodeId = client.MakeNodeId("MESReceiver_001.MoveInPartNumbers");
|
||||
|
||||
var before = client.Read(nodeId).Value as string[];
|
||||
before.ShouldNotBeNull();
|
||||
@@ -52,5 +54,87 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Integration
|
||||
await fixture.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Confirms that array nodes use bracketless OPC UA node identifiers while still exposing one-dimensional array metadata.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ArrayNode_UsesBracketlessNodeId_AndPublishesArrayDimensions()
|
||||
{
|
||||
var fixture = OpcUaServerFixture.WithFakeMxAccessClient();
|
||||
fixture.MxAccessClient!.TagValues["MESReceiver_001.MoveInPartNumbers[]"] = Vtq.Good(
|
||||
Enumerable.Range(0, 50).Select(i => $"PART-{i:00}").ToArray());
|
||||
|
||||
await fixture.InitializeAsync();
|
||||
try
|
||||
{
|
||||
using var client = new OpcUaTestClient();
|
||||
await client.ConnectAsync(fixture.EndpointUrl);
|
||||
|
||||
var nodeId = client.MakeNodeId("MESReceiver_001.MoveInPartNumbers");
|
||||
|
||||
var value = client.Read(nodeId).Value as string[];
|
||||
value.ShouldNotBeNull();
|
||||
value.Length.ShouldBe(50);
|
||||
|
||||
var valueRank = client.ReadAttribute(nodeId, Attributes.ValueRank).Value;
|
||||
valueRank.ShouldBe(ValueRanks.OneDimension);
|
||||
|
||||
var dimensions = client.ReadAttribute(nodeId, Attributes.ArrayDimensions).Value as uint[];
|
||||
dimensions.ShouldNotBeNull();
|
||||
dimensions.ShouldBe(new uint[] { 50 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
await fixture.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Confirms that an indexed write also updates the published OPC UA value seen by subscribed clients.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Write_SingleArrayElement_PublishesUpdatedArrayToSubscribers()
|
||||
{
|
||||
var fixture = OpcUaServerFixture.WithFakeMxAccessClient();
|
||||
fixture.MxAccessClient!.TagValues["MESReceiver_001.MoveInPartNumbers[]"] = Vtq.Good(
|
||||
Enumerable.Range(0, 50).Select(i => $"PART-{i:00}").ToArray());
|
||||
|
||||
await fixture.InitializeAsync();
|
||||
try
|
||||
{
|
||||
using var client = new OpcUaTestClient();
|
||||
await client.ConnectAsync(fixture.EndpointUrl);
|
||||
|
||||
var nodeId = client.MakeNodeId("MESReceiver_001.MoveInPartNumbers");
|
||||
var notifications = new ConcurrentBag<MonitoredItemNotification>();
|
||||
var (sub, item) = await client.SubscribeAsync(nodeId, intervalMs: 100);
|
||||
item.Notification += (_, e) =>
|
||||
{
|
||||
if (e.NotificationValue is MonitoredItemNotification notification)
|
||||
notifications.Add(notification);
|
||||
};
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
var status = client.Write(nodeId, new[] { "UPDATED-PART" }, indexRange: "1");
|
||||
StatusCode.IsGood(status).ShouldBe(true);
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
notifications.Any(n =>
|
||||
n.Value.Value is string[] values &&
|
||||
values.Length == 50 &&
|
||||
values[0] == "PART-00" &&
|
||||
values[1] == "UPDATED-PART" &&
|
||||
values[2] == "PART-02").ShouldBe(true);
|
||||
|
||||
await sub.DeleteAsync(true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await fixture.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.OpcUa
|
||||
model.NodeIdToTagReference.ContainsKey("TestMachine_001.MachineID").ShouldBe(true);
|
||||
model.NodeIdToTagReference.ContainsKey("DelmiaReceiver_001.DownloadPath").ShouldBe(true);
|
||||
model.NodeIdToTagReference.ContainsKey("DelmiaReceiver_001.JobStepNumber").ShouldBe(true);
|
||||
model.NodeIdToTagReference.ContainsKey("TestMachine_001.BatchItems[]").ShouldBe(true);
|
||||
model.NodeIdToTagReference.ContainsKey("TestMachine_001.BatchItems").ShouldBe(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -73,7 +73,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.OpcUa
|
||||
var (hierarchy, attributes) = CreateTestData();
|
||||
var model = AddressSpaceBuilder.Build(hierarchy, attributes);
|
||||
|
||||
model.NodeIdToTagReference.ContainsKey("TestMachine_001.BatchItems[]").ShouldBe(true);
|
||||
model.NodeIdToTagReference.ContainsKey("TestMachine_001.BatchItems").ShouldBe(true);
|
||||
model.NodeIdToTagReference["TestMachine_001.BatchItems"].ShouldBe("TestMachine_001.BatchItems[]");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user