thingsboard-gateway icon indicating copy to clipboard operation
thingsboard-gateway copied to clipboard

feat(opcua): support constant datapoints (attributes + one-time telemetry)

Open palandri opened this issue 2 months ago • 3 comments

feat(opcua): support constant datapoints (attributes + one-time telemetry)

Summary

Adds first-class support for constant datapoints in the OPC-UA connector:

  • Attributes with type: "constant" are sent when a device is created and on every reconnection.
  • Telemetry with type: "constant" is sent once per device per gateway process (idempotent).
  • Values are auto-cast (bool/int/float) when possible; otherwise kept as string.
  • Telemetry is timestamped with the gateway time.
  • Report strategy is respected at datapoint level via TBUtility.

Motivation

Previously, OPC-UA mapping handled datapoints backed by NodeId or path expressions but ignored constants. This feature enables:

  • Populating device attributes with fixed metadata (e.g., Customer, Location).
  • Emitting one-time constant telemetry (e.g., initial setup values) without requiring OPC-UA nodes.

Implementation Details

  • Parsing/modeling on device side:
    • Added in-memory collections (not polled/subscribed from OPC-UA):
      • constant_attributes and constant_timeseries, collected during device config parsing in python.Device.load_values().
  • Sending pipeline on connector:
    • New routine to emit constants: python.OpcUaConnector.__send_constants_for_device().
    • Invoked right after device creation in python.OpcUaConnector._create_new_devices().
    • Idempotence for constant telemetry via in-memory set created in thingsboard_gateway/connectors/opcua/opcua_connector.py.
    • Key building preserves report strategy: python.TBUtility.convert_key_to_datapoint_key().
    • Auto type inference for constant values: python.OpcUaConnector.__guess_type_and_cast().
    • Telemetry timestamp uses gateway time by default: python.TelemetryEntry.__init__().

Behavior

  • Attributes (type: "constant")
    • Sent on device creation and every reconnection (devices are recreated on reconnect).
  • Telemetry (type: "constant")
    • Sent exactly once per device per process (not re-sent on reconnects).

Configuration Example (new mapping format)

{
  "mapping": [
    {
      "deviceNodePattern": "Root\\.Objects\\.Device1",
      "deviceInfo": {
        "deviceNameExpressionSource": "path",
        "deviceNameExpression": "Device ${Root\\.Objects\\.Device1\\.serialNumber}",
        "deviceProfileExpressionSource": "constant",
        "deviceProfileExpression": "default"
      },
      "attributes": [
        { "key": "Customer", "type": "constant", "value": "ACME Corp" }
      ],
      "timeseries": [
        { "key": "InitialBatchSize", "type": "constant", "value": "12" }
      ]
    }
  ]
}

Casting Rules

  • "true"/"false" (case-insensitive) → boolean
  • -123 → integer
  • 45.67 → float
  • Anything else → string

Backward Compatibility

  • No breaking changes. Existing path/identifier-based datapoints remain unchanged.
  • The backward-compatibility adapter continues to set type: "constant" where applicable; the new pipeline now handles those entries.

Tests / Verification

  • Unit tests (added in this PR):
    • tests/unit/connectors/opcua/test_constants.py
      • Validates parsing buckets for constants and idempotent behavior of one-time constant telemetry.
    • Fixture config:
      • tests/unit/connectors/opcua/data/constants/opcua_config_constants.json
  • Blackbox (optional, requires TB test environment):
    • Example configuration to validate runtime behavior:
      • tests/blackbox/data/opcua/configs/uplink_configs/constants_config.json
    • Integrates with existing OPC-UA blackbox harness. Not executed as part of this PR by default.

Documentation Impact

  • Update OPC-UA connector mapping docs to include:
    • Support for type: "constant" in attributes and timeseries.
    • Semantics (attributes on create/reconnect, telemetry one-time).
    • Casting rules and gateway timestamp.
    • Example mapping similar to the snippet above.

Touched Code

  • Device parsing and storage:
    • thingsboard_gateway/connectors/opcua/device.py
    • python.Device.load_values()
  • Connector logic:
    • python.OpcUaConnector.__send_constants_for_device()
    • python.OpcUaConnector._create_new_devices()
    • python.OpcUaConnector.__guess_type_and_cast()
  • Utilities and entities (used as-is):
    • python.TBUtility.convert_key_to_datapoint_key()
    • python.TelemetryEntry.__init__()
  • Tests:
    • tests/unit/connectors/opcua/test_constants.py
    • tests/unit/connectors/opcua/data/constants/opcua_config_constants.json
    • tests/blackbox/data/opcua/configs/uplink_configs/constants_config.json

Checklist

  • [x] Parse/store constant datapoints (no OPC-UA read required)
  • [x] Send constant attributes on create/reconnect
  • [x] Send constant telemetry once per device per process
  • [x] Type inference and gateway timestamp
  • [x] Datapoint-level report strategy
  • [x] Unit tests
  • [ ] Blackbox validation on TB test environment
  • [ ] Documentation and CHANGELOG updates

palandri avatar Oct 10 '25 18:10 palandri

Hi @palandri, thanks for your contribution! We really appreciate it. I will take a look at your PR soon.

samson0v avatar Oct 14 '25 07:10 samson0v

Hello @samson0v. Do not worry, there is no hurry. I'm looking forward your opinion on this.

Cheers.

palandri avatar Oct 18 '25 17:10 palandri

Hi @palandri, I am reviewing your PR and have a question about this:

Attributes (type: "constant")
    Sent on device creation and every reconnection (devices are recreated on reconnect).
Telemetry (type: "constant")
    Sent exactly once per device per process (not re-sent on reconnects).

Why did you decide to use different strategies for sending?

samson0v avatar Nov 18 '25 08:11 samson0v