Skip to main content
APSHANKAR Sagar
Seasoned ⭐️⭐️
Seasoned ⭐️⭐️
August 4, 2026
Question

Carrying metadata from OPC-UA into CogniteTimeSeries — units, isStep, and state sets

  • August 4, 2026
  • 4 replies
  • 83 views

 We're migrating a production line from classic time series to CogniteTimeSeries in the data model, ingested by the OPC-UA extractor from a Siemens IIH server, and we'd like to understand how much of the metadata we hold at the source can travel with the signal rather than being reapplied afterwards. Three things, in increasing order of interest to us:

  1.  Engineering units — the UnitId is discarded. Our server sends the full EUInformation: NamespaceUri = http://www.opcfoundation.org/UA/units/un/cefact, UnitId = 4937544, DisplayName = kW·h, Description = kilowatt hour. What lands in CDF is only the composed display string, in classic unit and now in sourceUnit. The typed reference is never populated: across 2,772 OPC-UA-sourced classic series in our production project, unitExternalId is set on zero, and on the data-model side the unit direct relation to CogniteUnit is null on all 854 series of the migrated line. We fill it afterwards with our own script, but the UNECE common code in UnitId is exactly the deterministic key that would make resolution automatic, and it's dropped. Is UnitId-based resolution to CogniteUnit supported or planned? If post-processing is the intended pattern, is there a published UNECE-code → CDF unit-catalogue mapping we should use instead of matching on display strings?
  2. . isStep is derived solely from the OPC-UA DataType. In our line, exactly the 24 Boolean nodes come out isStep = true and all 830 others false — including Cur_State, Cur_Mode, Cur_Prog (UInt32 enums), the alarm/warning history codes (Int16), and every setpoint, all of which are step-wise by nature. Is there any way to declare this at the source — e.g. does the extractor read the standard HistoricalDataConfiguration.Stepped property, or can a custom node property be mapped onto isStep the way EngineeringUnits maps onto unit? (For contrast, the numeric/string distinction does arrive correctly, derived from DataType, so the mechanism clearly exists for some fields.)
  3. State time series — can they be assigned at ingestion, and may we join the private preview? Our machines publish genuine state signals with a documented code registry (Cur_State, Cur_Mode, Cur_Prog; e.g. 8 = Lack, 16 = Tailback, 128 = Operating), which is precisely what a state time series expresses. We can see stateSet as a direct relation on CogniteTimeSeries in our project, with no target view resolvable — so we assume StateTimeSeries is gated. We'd like to be enrolled in the private beta. Beyond access, the question that matters for us architecturally: can a state set be assigned during ingestion — from an OPC-UA node property, an extractor mapping, or MultiStateStateSet-style configuration — or is it strictly a post-ingestion assignment? We're generating our OPC-UA models ourselves from a canonical definition, so if any of this can be declared in the model we would rather put it there once than reapply it per line, per migration.

4 replies

APSHANKAR Sagar
Seasoned ⭐️⭐️
Seasoned ⭐️⭐️
August 4, 2026

Follow-up: we tested this end-to-end. The DM target drops all node metadata, and isStep is silently unmappable.

The data-model target drops node metadata. With classic time series, every OPC-UA property landed in metadata (for us EngineeringUnits and ValuePrecision on 511 of 687 series, EURange on a few). cdf_cdm:CogniteTimeSeries/v1 has no free-form metadata property, so once metadata-targets writes data-model instances, anything not explicitly mapped to a named field is simply dropped. Is that intended, and is there a supported way to carry the property bag into CDM?

 isStep isn't a valid metadata-mapping target, and fails silently. By default it looks derived from the OPC-UA DataType alone — only our 24 Boolean nodes are stepped, while state registers, alarm codes and setpoints all land non-stepped. So we emitted a boolean Stepped property on 346 signals and mapped it:

 - "Stepped": "isStep" → no change, neither on update nor when we deleted series so they'd be recreated from scratch.
- "Stepped": "description" → all 346 took the value immediately.

So the property is read and correctly shaped; the target isn't supported, and nothing is logged about it. Could you confirm the complete set of valid metadata-mapping targets for timeseries? And is there any route to update unit/isStep on series that already exist — update only exposes name, description, context, metadata?

vikram.chawan
Practitioner ⭐️⭐️⭐️
Practitioner ⭐️⭐️⭐️
August 5, 2026

Follow-up: CDM metadata and updating existing series

"The DM target drops all node metadata" - worth double-checking before treating this as a hard limitation. The extractor's CDM write path does populate a general-purpose unstructured-metadata field on every CogniteTimeSeries write (the same property bag that fills classic Metadata) - so the extractor is attempting to carry the bag over, not dropping it outright. The likely explanation for what you're seeing is that this field lives on a different (extension) view than the one your query is reading through, so it's present on the instance but absent from your view projection. Before concluding it's unsupported, try retrieving the raw instance directly (rather than through your current view) to confirm whether the data is there but unqueried instance but absent from your view projection. Before concluding it's unsupported, try retrieving the raw
instance directly (rather than through your current view) to confirm whether the data is there but unqueried versus genuinely absent.

"Any route to update unit/isStep on series that already exist?" - this differs by target, which I should be clear about:
- Classic timeseries: confirmed no route exists. Updates only ever touch name, description, context (asset/parent), and metadata - unit and isStep are write-once at creation. Recreating the series, as you did, is the only way today.

- CDM/data-model target: likely different, and worth testing on your end. The data-model writer performs a full property upsert on every browse/rebrowse cycle rather than a partial update, so isStep and sourceUnit should refresh automatically the next time a rebrowse runs - without deleting the instance. Try changing the source-side property and triggering a rebrowse (or restart) to see if the DM series picks up the new value in place. If it doesn't, that's a genuine bug worth a repro-backed report, since the underlying write behavior suggests it should work.

vikram.chawan
Practitioner ⭐️⭐️⭐️
Practitioner ⭐️⭐️⭐️
August 5, 2026

@APSHANKAR Sagar 

Thanks for the detailed writeup and for testing this end-to-end - that's exactly the kind of signal that helps us prioritise. Answering all three original questions plus the follow-up together below.

1. Engineering units - UnitId is discarded, and not recoverable today

Confirmed: the extractor never reads NamespaceUri or UnitId into any output path, mapped or not. What you're seeing - only the composed DisplayName: Description string - is the entire value the extractor extracts from EUInformation; the UNECE code and namespace are dropped before they reach any config surface, including metadata-mapping.

So to be precise about your question: this isn't a case of a structured field being flattened into a string that you could reconstruct - the UnitId simply never enters the pipeline. unitExternalId/unit direct-relation resolution to CogniteUnit from the source UnitId is not supported and not currently planned in the shipped extractor. There is no published UNECE-code → CDF-unit-catalogue mapping either; your post-processing script matching against the catalogue is the only path today. If deterministic resolution matters to you, please file it as a feature request - matching on display strings (kW·h: kilowatt hour) is exactly the brittleness that a UnitId-keyed lookup would avoid, so this is a reasonable ask.

Config for what does work today (gets you the composed string into unit/sourceUnit, not the UnitId):
 

cognite:
  metadata-mapping:
   timeseries:
    "EngineeringUnits": "unit"

2. isStep - confirmed not configurable via metadata-mapping; here's why and what is

Your test result is correct and expected: isStep is not, and cannot currently be, a metadata-mapping target. The complete set of valid targets for cognite.metadata-mapping.timeseries is:

- name
- description
- unit
- parentId

That's the full list. Mapping anything else - isStep included - is silently ignored: the extractor has no fallback branch for unrecognized targets, so nothing is applied and nothing is logged. That silent failure is a gap on our end (an unrecognized mapping key should at minimum log a warning at startup), and I'd encourage you to file it as a bug - it cost you real debugging time to discover the target simply doesn't exist.

On why your 830 non-Boolean nodes default to non-stepped: isStep today is derived only from the OPC-UA DataType hierarchy - Boolean is always stepped, custom Enumeration DataType subtypes are stepped by default (unless enums-as-strings: true), and that's it. Importantly, MultiStateDiscreteType and MultiStateValueDiscreteType are OPC-UA VariableTypes, not subtypes of the Enumeration DataType - so the "enum → stepped" rule never applies to Cur_State/Cur_Mode/Cur_Prog, alarm codes, or your setpoints, regardless of auto-identify-types. Their underlying DataType is plain UInt32/Int16, and nothing marks that stepped
automatically. There's also no reading of HistoricalDataConfiguration.Stepped today.

The supported route is extraction.data-types.custom-numeric-types, with one caveat: it's keyed by the DataType's NodeId, not the variable's. If your state/alarm nodes use the plain built-in UInt32/Int16 DataType directly, setting is-step: true there would mark every variable of that built-in type as stepped across your whole model - not just the state registers. Since you generate your OPC-UA models from a canonical definition, the clean fix is to give each state/alarm class its own custom DataType in the model (e.g. MachineStateType, AlarmCodeType), then key off that specific NodeId:

 extraction:
  data-types:
   auto-identify-types: true
   custom-numeric-types:
    - node-id: {namespace-uri: "your-namespace", node-id: "MachineStateType"}
     is-step: true
    - node-id: {namespace-uri: "your-namespace", node-id: "AlarmCodeType"}
     is-step: true


This lets you declare it once in the model, per state/alarm class, rather than reapplying it per line or per migration - which matches what you're after.

3. State time series

Enrollment: I can't grant private beta access from here - please raise that specifically through your account team or a support ticket for enrollment.

Architecture: a first-class CogniteStateSet write path does not exist in the extractor today, and it's earlier-stage than "in progress" might suggest. There's unreleased internal scaffolding (an extraction.data-types.states config block) that builds an in-memory state map from EnumStrings/EnumValues for Enumeration-subtype DataTypes and for MultiStateDiscrete/MultiStateValueDiscrete variables - but nothing currently consumes that map to write anything to CDF: no metadata field, no CDF resource. It's unmerged into any released version, and the SDK layer the extractor depends on has no state-set API surface yet either.

To your architectural question - whether a state set could be declared at the source (a node property, an extractor mapping, or MultiStateStateSet-style config) rather than assigned post-ingestion - that's the direction the groundwork above is heading, since it already keys off DataType/VariableType (i.e. the model, not per-instance data). But there's no committed design or timeline yet. I'd recommend filing a feature request specifically asking for MultiStateDiscreteType/MultiStateValueDiscreteType → CogniteStateSet mapping at browse time - the node-identification logic already exists internally; the write path is what's missing.
 

vikram.chawan
Practitioner ⭐️⭐️⭐️
Practitioner ⭐️⭐️⭐️
August 5, 2026

Replied here: