fix(historian): stamp a positive StorageRateMs on provisioned tags
GatewayTagProvisioner built HistorianTagDefinition without a StorageRateMs, so
the proto uint32 defaulted to 0. The gateway's EnsureTags maps StorageRateMs
straight to the AVEVA SDK's storageRateMs, which requires a positive value —
0 makes the SDK throw ArgumentOutOfRangeException ('Storage rate must be > 0 ms'),
so EVERY historized-tag auto-provision (the deploy-time IHistorianProvisioning
hook) silently failed at the gateway with failed=N.
Stamp DefaultStorageRateMs=1000 (matches the gateway's own live provisioner
convention; quantized 1000/5000/10000). Add a regression unit test asserting the
definition carries a positive rate, and set StorageRateMs on the live-suite's
inline definitions so the retype probe reaches a real EnsureTags.
Found via the R2-06 live gate against a real 0.2.0 gateway + AVEVA historian:
with this fixed, Float/Int2/UInt2/Int4/UInt4/Double all provision and the live
suite goes 3/6 -> 5/6. (Int1/Boolean remains blocked by the gateway histsdk's
ProtocolEvidenceMissingException for Int1 — a gateway-side type-support gap, not
this bug.)
Gateway driver unit 103/103.
This commit is contained in:
@@ -19,6 +19,16 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway;
|
||||
/// </remarks>
|
||||
public sealed class GatewayTagProvisioner : IHistorianProvisioning
|
||||
{
|
||||
/// <summary>
|
||||
/// Storage rate (ms) stamped on every provisioned analog tag. The gateway's <c>EnsureTags</c> path
|
||||
/// maps this straight to the AVEVA SDK's <c>storageRateMs</c>, which requires a positive, quantized
|
||||
/// value (1000/5000/10000/…); the proto's <c>uint32</c> default of 0 makes the SDK throw
|
||||
/// <see cref="ArgumentOutOfRangeException"/> ("Storage rate must be > 0 ms"), failing the whole
|
||||
/// provision. 1000 ms (1 s) matches the gateway's own live provisioner default and suits
|
||||
/// continuous-historization analog tags.
|
||||
/// </summary>
|
||||
private const uint DefaultStorageRateMs = 1000;
|
||||
|
||||
private readonly IHistorianGatewayClient _client;
|
||||
private readonly ILogger<GatewayTagProvisioner> _logger;
|
||||
|
||||
@@ -58,6 +68,8 @@ public sealed class GatewayTagProvisioner : IHistorianProvisioning
|
||||
// Proto string fields are non-nullable — coalesce absent metadata to empty.
|
||||
EngineeringUnit = request.EngineeringUnit ?? string.Empty,
|
||||
Description = request.Description ?? string.Empty,
|
||||
// Required: a 0 storage rate makes the gateway's EnsureTags throw (see the constant).
|
||||
StorageRateMs = DefaultStorageRateMs,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+19
@@ -60,6 +60,25 @@ public sealed class GatewayTagProvisionerTests
|
||||
Assert.True(defs[0].HasDataType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Definition_carries_a_positive_storage_rate()
|
||||
{
|
||||
// Regression guard (R2-06 live gate): the gateway maps StorageRateMs straight to the AVEVA SDK's
|
||||
// storageRateMs, which requires a positive value — the proto uint32 default of 0 makes EnsureTags
|
||||
// throw ArgumentOutOfRangeException ("Storage rate must be > 0 ms"), silently failing EVERY
|
||||
// historized-tag provision. A non-zero rate must always be stamped.
|
||||
var fake = new FakeHistorianGatewayClient { EnsureTagsResult = new TagOperationResults() };
|
||||
var p = Provisioner(fake);
|
||||
|
||||
await p.EnsureTagsAsync(
|
||||
new[] { new HistorianTagProvisionRequest("Pump1.Temp", DriverDataType.Float32, null, null) },
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
var defs = fake.LastEnsureDefinitions!;
|
||||
Assert.Single(defs);
|
||||
Assert.True(defs[0].StorageRateMs > 0, "provisioned tags must carry a positive storage rate (0 fails EnsureTags).");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Maps_metadata_and_coalesces_null_metadata_to_empty()
|
||||
{
|
||||
|
||||
+3
@@ -103,6 +103,7 @@ public sealed class GatewayLiveIntegrationTests(GatewayLiveFixture fixture) : IC
|
||||
DataType = HistorianDataType.Float,
|
||||
EngineeringUnit = string.Empty,
|
||||
Description = "OtOpcUa live validation sandbox",
|
||||
StorageRateMs = 1000, // 0 makes the gateway's EnsureTags throw (storageRateMs must be > 0).
|
||||
},
|
||||
},
|
||||
ct);
|
||||
@@ -345,6 +346,7 @@ public sealed class GatewayLiveIntegrationTests(GatewayLiveFixture fixture) : IC
|
||||
DataType = HistorianDataType.Float,
|
||||
EngineeringUnit = string.Empty,
|
||||
Description = "OtOpcUa live validation retype probe (stage 1: Float)",
|
||||
StorageRateMs = 1000, // 0 makes the gateway's EnsureTags throw (storageRateMs must be > 0).
|
||||
},
|
||||
},
|
||||
ct);
|
||||
@@ -358,6 +360,7 @@ public sealed class GatewayLiveIntegrationTests(GatewayLiveFixture fixture) : IC
|
||||
DataType = HistorianDataType.Int1,
|
||||
EngineeringUnit = string.Empty,
|
||||
Description = "OtOpcUa live validation retype probe (stage 2: Int1)",
|
||||
StorageRateMs = 1000, // 0 makes the gateway's EnsureTags throw (storageRateMs must be > 0).
|
||||
},
|
||||
},
|
||||
ct);
|
||||
|
||||
Reference in New Issue
Block a user