fix(historian): stamp a positive StorageRateMs on provisioned tags
v2-ci / build (pull_request) Successful in 4m7s
v2-ci / unit-tests (pull_request) Failing after 10m50s

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:
Joseph Doherty
2026-07-13 19:29:49 -04:00
parent 7154ec8f04
commit 4da2d62b9a
3 changed files with 34 additions and 0 deletions
@@ -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 &gt; 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,
});
}