Historian provisioning skips Float/Double/Byte/SByte tags: DataType is an OPC UA name, the applier parses DriverDataType #898

Closed
opened 2026-11-13 00:43:29 -05:00 by dohertj2 · 1 comment
Owner

AddressSpaceApplier.ProvisionHistorizedTags sends an EnsureTags request only when the raw tag's DataType string parses as DriverDataType: Enum.TryParse<DriverDataType>(tag.DataType), members Boolean, Int16..UInt64, Float32, Float64, String, DateTime, Reference.

But Tag.DataType holds OPC UA built-in names:

  • the AdminUI writes them: RawTagModal.DataTypes, manual entry and CSV default to "Float", and RawBrowseCommitMapper maps Float32 → "Float" and Float64 → "Double";
  • OtOpcUaNodeManager.ResolveBuiltInDataType only understands those names ("Float32" would become BaseDataType).

Effect: a historized Float, Double, Byte or SByte tag is never provisioned in the historian. The skip is logged at Debug only, and no tally line is written because the request list stays empty. On the Gateway backend a brand-new Float/Double tag therefore never gets its AVEVA tag created, and continuous historization of it fails downstream. AddressSpaceApplierProvisioningTests asserts this behaviour ("Float is NOT a DriverDataType member … it must be skipped") and uses Float32 for the positive cases, a spelling the AdminUI never produces.

Seen live on DEV vd03 fea7de0a, 2026-11-13, #700 gate: four historized Float/Byte/Double tags were deployed and the gateway's metrics showed no EnsureTags call (docs/plans/evidence/2026-11-13-historian-700-vd03/vd03-endpoint-gate.md). The gate relied on this skip deliberately to stay zero-write.

Fix direction: map the OPC UA names to DriverDataType (Float→Float32, Double→Float64, Byte/SByte→a widened integer) in one shared helper, used by both provisioning and the continuous-historization recorder.

⚠️ Coordinate with #899. Once this is fixed, any node that binds history to an existing AVEVA tag through historianTagname with a Float/Double type will start re-ensuring (rewriting) that tag on every add.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi

`AddressSpaceApplier.ProvisionHistorizedTags` sends an `EnsureTags` request only when the raw tag's `DataType` string parses as `DriverDataType`: `Enum.TryParse<DriverDataType>(tag.DataType)`, members `Boolean, Int16..UInt64, Float32, Float64, String, DateTime, Reference`. But `Tag.DataType` holds **OPC UA built-in names**: - the AdminUI writes them: `RawTagModal.DataTypes`, manual entry and CSV default to `"Float"`, and `RawBrowseCommitMapper` maps `Float32` → `"Float"` and `Float64` → `"Double"`; - `OtOpcUaNodeManager.ResolveBuiltInDataType` only understands those names (`"Float32"` would become `BaseDataType`). **Effect:** a historized **Float**, **Double**, **Byte** or **SByte** tag is never provisioned in the historian. The skip is logged at Debug only, and no tally line is written because the request list stays empty. On the Gateway backend a brand-new Float/Double tag therefore never gets its AVEVA tag created, and continuous historization of it fails downstream. `AddressSpaceApplierProvisioningTests` asserts this behaviour ("`Float` is NOT a DriverDataType member … it must be skipped") and uses `Float32` for the positive cases, a spelling the AdminUI never produces. **Seen live** on DEV vd03 `fea7de0a`, 2026-11-13, #700 gate: four historized Float/Byte/Double tags were deployed and the gateway's metrics showed no `EnsureTags` call (`docs/plans/evidence/2026-11-13-historian-700-vd03/vd03-endpoint-gate.md`). The gate relied on this skip deliberately to stay zero-write. **Fix direction:** map the OPC UA names to `DriverDataType` (`Float`→Float32, `Double`→Float64, `Byte`/`SByte`→a widened integer) in one shared helper, used by both provisioning and the continuous-historization recorder. ⚠️ **Coordinate with #899.** Once this is fixed, any node that binds history to an *existing* AVEVA tag through `historianTagname` with a Float/Double type will start re-ensuring (rewriting) that tag on every add. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
dohertj2 added the type:bugpriority:p2 labels 2026-11-13 00:43:29 -05:00
Author
Owner

Fixed (same change as #899)

Mapping. Type resolution now goes through one shared helper, HistorianDataTypeNames.TryResolve in Core.Abstractions. The provisioning hook in AddressSpaceApplier now uses it instead of Enum.TryParse<DriverDataType>. The recorder never parsed the type string (it classifies by value kind), so it needed no change.

Tag.DataType (OPC UA name) resolved Gateway provisions as
Float Float32 Float
Double Float64 Double
Byte (0..255) Int16 (lossless widening) Int2
SByte (−128..127) Int16 (lossless widening) Int2 (Int1 creation is server-degenerate, #441)
Boolean, Int16…UInt64, String, DateTime the same-named member unchanged
Float32 / Float64 / Reference (DriverDataType spellings) still accepted unchanged
Guid, ByteString, a number, blank unresolved: not provisioned, logged at Information (it was Debug) n/a

HistorianGateway's lossless-cast-or-reject rule. Its client cannot create Int8/UInt8, so HistorianTypeMapper now marks Int64/UInt64 as not historizable on the Gateway backend. They are skipped (counted skipped) instead of being sent to fail with FailedPrecondition (counted failed). For Byte, I chose Int2 over HG's UInt1→UInt2 path. Int2 is the type already proven for Boolean, and OtOpcUa still widens away from the historian's UInt2 write path (UInt16→Uint4).

Tests. The ones that encoded the bug were rewritten:

  • Apply_skips_added_tag_with_unparseable_datatype became Apply_provisions_opc_ua_type_names_and_skips_unmapped_ones. A Float, Double, Byte and SByte tag now reach EnsureTags with the mapped types, and Guid is skipped.
  • HistorianTypeMapperTests no longer expects Int64→Int8 / UInt64→Uint8.

New tests: HistorianDataTypeNamesTests covers every name RawTagModal offers, plus the number and comma-list cases Enum.TryParse would have accepted. GatewayTagProvisionerTests.Sixty_four_bit_integers_are_skipped_not_sent covers the 64-bit skip. All of these were mutation-checked.

⚠️ Upgrade warning (also in docs/Historian.md §Tag auto-provisioning)

Before this release, historized tags with DataType Float, Double, Byte or SByte were never provisioned. On the first host start after the upgrade, every existing historized Float, Double, Byte and SByte tag is provisioned. On the Gateway backend that is an EnsureTags upsert:

  • It creates the AVEVA tag if it is missing.
  • If the tag exists, it re-types and re-describes it: description = the OtOpcUa tag name, storage rate 1000 ms, integral divisor 1.

That is the intended fix for tags OtOpcUa owns. But a tag whose historianTagname names someone else's AVEVA tag (a System Platform attribute, SysTimeSec, …) will be rewritten. Before upgrading, do one of these:

  • mark each such tag "historianReadOnly": true (#899);
  • set ServerHistorian:ProvisionTags=CreateOnly, which leaves existing tags alone and still creates missing ones;
  • set ServerHistorian:ProvisionTags=Off.

On the SqlServer and QuestDB backends the upgrade only upserts rows in OtOpcUa's own tag table, which is harmless.

On deployed DEV vd03 nothing changes until this build is deployed there. The #700 gate's temporary Float/Byte/Double tags were removed (configuration revision fa9a2e28… is the original), so no gate tag would provision.

## Fixed (same change as #899) **Mapping.** Type resolution now goes through one shared helper, `HistorianDataTypeNames.TryResolve` in Core.Abstractions. The provisioning hook in `AddressSpaceApplier` now uses it instead of `Enum.TryParse<DriverDataType>`. The recorder never parsed the type string (it classifies by value kind), so it needed no change. | `Tag.DataType` (OPC UA name) | resolved | Gateway provisions as | |---|---|---| | `Float` | Float32 | `Float` | | `Double` | Float64 | `Double` | | `Byte` (0..255) | Int16 (lossless widening) | `Int2` | | `SByte` (−128..127) | Int16 (lossless widening) | `Int2` (`Int1` creation is server-degenerate, #441) | | `Boolean`, `Int16`…`UInt64`, `String`, `DateTime` | the same-named member | unchanged | | `Float32` / `Float64` / `Reference` (DriverDataType spellings) | still accepted | unchanged | | `Guid`, `ByteString`, a number, blank | unresolved: not provisioned, logged at **Information** (it was Debug) | n/a | **HistorianGateway's lossless-cast-or-reject rule.** Its client cannot create `Int8`/`UInt8`, so `HistorianTypeMapper` now marks `Int64`/`UInt64` as not historizable on the Gateway backend. They are skipped (counted `skipped`) instead of being sent to fail with `FailedPrecondition` (counted `failed`). For Byte, I chose `Int2` over HG's `UInt1`→`UInt2` path. `Int2` is the type already proven for Boolean, and OtOpcUa still widens away from the historian's `UInt2` write path (UInt16→`Uint4`). **Tests.** The ones that encoded the bug were rewritten: - `Apply_skips_added_tag_with_unparseable_datatype` became `Apply_provisions_opc_ua_type_names_and_skips_unmapped_ones`. A Float, Double, Byte and SByte tag now reach EnsureTags with the mapped types, and Guid is skipped. - `HistorianTypeMapperTests` no longer expects Int64→Int8 / UInt64→Uint8. New tests: `HistorianDataTypeNamesTests` covers every name `RawTagModal` offers, plus the number and comma-list cases `Enum.TryParse` would have accepted. `GatewayTagProvisionerTests.Sixty_four_bit_integers_are_skipped_not_sent` covers the 64-bit skip. All of these were mutation-checked. ### ⚠️ Upgrade warning (also in `docs/Historian.md` §Tag auto-provisioning) Before this release, historized tags with `DataType` **`Float`, `Double`, `Byte` or `SByte`** were never provisioned. **On the first host start after the upgrade, every existing historized Float, Double, Byte and SByte tag is provisioned.** On the Gateway backend that is an `EnsureTags` upsert: - It creates the AVEVA tag if it is missing. - If the tag exists, it re-types and re-describes it: description = the OtOpcUa tag name, storage rate 1000 ms, integral divisor 1. That is the intended fix for tags OtOpcUa owns. But a tag whose `historianTagname` names someone else's AVEVA tag (a System Platform attribute, `SysTimeSec`, …) will be rewritten. **Before upgrading**, do one of these: - mark each such tag `"historianReadOnly": true` (#899); - set `ServerHistorian:ProvisionTags=CreateOnly`, which leaves existing tags alone and still creates missing ones; - set `ServerHistorian:ProvisionTags=Off`. On the SqlServer and QuestDB backends the upgrade only upserts rows in OtOpcUa's own tag table, which is harmless. On deployed DEV vd03 nothing changes until this build is deployed there. The #700 gate's temporary Float/Byte/Double tags were removed (configuration revision `fa9a2e28…` is the original), so no gate tag would provision.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#898