fix(driver-focas): equipment tags run the capability-matrix pre-flight authored tags get (R2-11, 05/UNDER-6)
This commit is contained in:
@@ -19,7 +19,7 @@
|
|||||||
{ "id": "T15", "subject": "AbLegacy equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings", "status": "completed", "blockedBy": ["T11"] },
|
{ "id": "T15", "subject": "AbLegacy equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings", "status": "completed", "blockedBy": ["T11"] },
|
||||||
{ "id": "T16", "subject": "TwinCAT equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings", "status": "completed", "blockedBy": ["T11"] },
|
{ "id": "T16", "subject": "TwinCAT equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings", "status": "completed", "blockedBy": ["T11"] },
|
||||||
{ "id": "T17", "subject": "FOCAS equipment-tag parser: force Writable:false (05/UNDER-1 correction), shared readers, Inspect warnings (+ amend Contracts banner)", "status": "completed", "blockedBy": ["T11"] },
|
{ "id": "T17", "subject": "FOCAS equipment-tag parser: force Writable:false (05/UNDER-1 correction), shared readers, Inspect warnings (+ amend Contracts banner)", "status": "completed", "blockedBy": ["T11"] },
|
||||||
{ "id": "T18", "subject": "FOCAS capability-matrix pre-flight on the equipment-tag _parseRef resolve path (BadNodeIdUnknown on rejection)", "status": "pending", "blockedBy": ["T17"] },
|
{ "id": "T18", "subject": "FOCAS capability-matrix pre-flight on the equipment-tag _parseRef resolve path (BadNodeIdUnknown on rejection)", "status": "completed", "blockedBy": ["T17"] },
|
||||||
{ "id": "T19", "subject": "Modbus probe parses the factory DTO shape (timeoutMs round-trip; OpcUaClient parity rule)", "status": "pending", "blockedBy": [] },
|
{ "id": "T19", "subject": "Modbus probe parses the factory DTO shape (timeoutMs round-trip; OpcUaClient parity rule)", "status": "pending", "blockedBy": [] },
|
||||||
{ "id": "T20", "subject": "EquipmentTagConfigInspector DriverType-dispatch map in ControlPlane (+6 Contracts references)", "status": "pending", "blockedBy": ["T12", "T13", "T14", "T15", "T16", "T17"] },
|
{ "id": "T20", "subject": "EquipmentTagConfigInspector DriverType-dispatch map in ControlPlane (+6 Contracts references)", "status": "pending", "blockedBy": ["T12", "T13", "T14", "T15", "T16", "T17"] },
|
||||||
{ "id": "T21", "subject": "AdminOperationsActor deploy-gate wiring + Deployment:TagConfigValidationMode (Warn default | Error opt-in); actor-level consuming test", "status": "pending", "blockedBy": ["T20"] },
|
{ "id": "T21", "subject": "AdminOperationsActor deploy-gate wiring + Deployment:TagConfigValidationMode (Warn default | Error opt-in); actor-level consuming test", "status": "pending", "blockedBy": ["T20"] },
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
|||||||
_logger = logger ?? NullLogger<FocasDriver>.Instance;
|
_logger = logger ?? NullLogger<FocasDriver>.Instance;
|
||||||
_resolver = new EquipmentTagRefResolver<FocasTagDefinition>(
|
_resolver = new EquipmentTagRefResolver<FocasTagDefinition>(
|
||||||
r => _tagsByName.TryGetValue(r, out var t) ? t : null,
|
r => _tagsByName.TryGetValue(r, out var t) ? t : null,
|
||||||
r => FocasEquipmentTagParser.TryParse(r, out var d) ? d : null);
|
ResolveEquipmentTagRef);
|
||||||
_poll = new PollGroupEngine(
|
_poll = new PollGroupEngine(
|
||||||
reader: ReadAsync,
|
reader: ReadAsync,
|
||||||
onChange: (handle, tagRef, snapshot) =>
|
onChange: (handle, tagRef, snapshot) =>
|
||||||
@@ -234,6 +234,20 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
|||||||
internal bool IsParsedAddressCached(string reference) =>
|
internal bool IsParsedAddressCached(string reference) =>
|
||||||
_parsedAddressesByTagName.ContainsKey(reference);
|
_parsedAddressesByTagName.ContainsKey(reference);
|
||||||
|
|
||||||
|
// Resolves an equipment-tag TagConfig reference into a FocasTagDefinition, applying the SAME
|
||||||
|
// capability pre-flight authored tags get at InitializeAsync (:105-119) — R2-11 (05/UNDER-6). A
|
||||||
|
// reference whose address is unparseable, whose bound device is unknown, or whose address violates
|
||||||
|
// the device series' capability matrix returns null ⇒ resolver miss ⇒ BadNodeIdUnknown at read time,
|
||||||
|
// instead of a wrong-status read reaching the wire. The negative is cached by the resolver.
|
||||||
|
private FocasTagDefinition? ResolveEquipmentTagRef(string reference)
|
||||||
|
{
|
||||||
|
if (!FocasEquipmentTagParser.TryParse(reference, out var def)) return null;
|
||||||
|
var parsed = FocasAddress.TryParse(def.Address);
|
||||||
|
if (parsed is null) return null;
|
||||||
|
if (!_devices.TryGetValue(def.DeviceHostAddress, out var device)) return null;
|
||||||
|
return FocasCapabilityMatrix.Validate(device.Options.Series, parsed) is null ? def : null;
|
||||||
|
}
|
||||||
|
|
||||||
// Resolves a tag definition to its parsed FocasAddress, caching the result so that
|
// Resolves a tag definition to its parsed FocasAddress, caching the result so that
|
||||||
// equipment tags (resolver-produced, not seeded at InitializeAsync) don't re-parse the
|
// equipment tags (resolver-produced, not seeded at InitializeAsync) don't re-parse the
|
||||||
// address string on every ReadAsync / WriteAsync hot-path call.
|
// address string on every ReadAsync / WriteAsync hot-path call.
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// R2-11 (05/UNDER-6): an equipment-tag reference whose address violates the device series'
|
||||||
|
/// capability matrix must fail to RESOLVE (surfacing <c>BadNodeIdUnknown</c>) — the same pre-flight
|
||||||
|
/// authored tags get at <c>InitializeAsync</c> — instead of reaching the wire and failing later with a
|
||||||
|
/// wire error. A capability-valid equipment tag still resolves + reads.
|
||||||
|
/// </summary>
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public sealed class FocasEquipmentTagCapabilityGateTests
|
||||||
|
{
|
||||||
|
private const string Host = "focas://10.0.0.5:8193";
|
||||||
|
|
||||||
|
private static FocasDriver NewDriver() => new(new FocasDriverOptions
|
||||||
|
{
|
||||||
|
// Series 16i: macro range 0..999 — MACRO:9500 is out of range.
|
||||||
|
Devices = [new FocasDeviceOptions(Host, Series: FocasCncSeries.Sixteen_i)],
|
||||||
|
Probe = new FocasProbeOptions { Enabled = false },
|
||||||
|
}, "drv-1", new FakeFocasClientFactory());
|
||||||
|
|
||||||
|
private static string EquipTag(string address) =>
|
||||||
|
$"{{\"address\":\"{address}\",\"dataType\":\"Int32\",\"deviceHostAddress\":\"{Host}\"}}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Capability_violating_equipment_tag_does_not_resolve()
|
||||||
|
{
|
||||||
|
var drv = NewDriver();
|
||||||
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||||
|
|
||||||
|
var results = await drv.ReadAsync([EquipTag("MACRO:9500")], CancellationToken.None);
|
||||||
|
|
||||||
|
results.Single().StatusCode.ShouldBe(FocasStatusMapper.BadNodeIdUnknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Capability_valid_equipment_tag_resolves_and_reads()
|
||||||
|
{
|
||||||
|
var drv = NewDriver();
|
||||||
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||||
|
|
||||||
|
var results = await drv.ReadAsync([EquipTag("MACRO:100")], CancellationToken.None);
|
||||||
|
|
||||||
|
results.Single().StatusCode.ShouldNotBe(FocasStatusMapper.BadNodeIdUnknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Equipment_tag_for_unknown_device_does_not_resolve()
|
||||||
|
{
|
||||||
|
var drv = NewDriver();
|
||||||
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||||
|
|
||||||
|
var badDevice = "{\"address\":\"MACRO:100\",\"dataType\":\"Int32\",\"deviceHostAddress\":\"focas://10.9.9.9:8193\"}";
|
||||||
|
var results = await drv.ReadAsync([badDevice], CancellationToken.None);
|
||||||
|
|
||||||
|
results.Single().StatusCode.ShouldBe(FocasStatusMapper.BadNodeIdUnknown);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user