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": "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": "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": "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"] },
|
||||
|
||||
@@ -68,7 +68,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
_logger = logger ?? NullLogger<FocasDriver>.Instance;
|
||||
_resolver = new EquipmentTagRefResolver<FocasTagDefinition>(
|
||||
r => _tagsByName.TryGetValue(r, out var t) ? t : null,
|
||||
r => FocasEquipmentTagParser.TryParse(r, out var d) ? d : null);
|
||||
ResolveEquipmentTagRef);
|
||||
_poll = new PollGroupEngine(
|
||||
reader: ReadAsync,
|
||||
onChange: (handle, tagRef, snapshot) =>
|
||||
@@ -234,6 +234,20 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
internal bool IsParsedAddressCached(string 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
|
||||
// equipment tags (resolver-produced, not seeded at InitializeAsync) don't re-parse the
|
||||
// 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