Skip to main content
6-Contributor
July 6, 2026
Question

OPC UA → ThingWorx bridge Python

  • July 6, 2026
  • 0 replies
  • 9 views

How to use the OPC UA → ThingWorx bridge (Python)

Getting OPC UA data into ThingWorx usually means Kepware plus a Connection Server, and complex/structured tags are often the hardest part. This post shows a config-driven Python bridge that connects to any OPC UA server, including complex structures (structs, arrays, method arguments), maps the tags to ThingWorx Things and properties, and pushes live values through the thingworx-python wrapper. It even generates the matching ThingWorx entities for you, so you write no code — just YAML.

What it does

The bridge is a Python package (opcua_bridge) with a four-step workflow and a runtime pipeline:

OPC UA server → reader (asyncua) → map/coerce → ThingWorx properties

  • walks the OPC UA address space and writes a mapping skeleton for you;
  • generates importable ThingWorx XML (Project, DataShapes, Things, Network);
  • at runtime, decodes OPC UA values on an asyncio thread, coerces them, and pushes them up through the wrapper;
  • turns complex OPC UA types (structs, struct arrays, primitive arrays) into ThingWorx INFOTABLE properties automatically.

Where to get it

The bridge is on GitHub:
https://github.com/thingworx-field-work/OPC-UA-ThingWorx-Wrapper

It builds on the ThingWorx Python SDK wrapper. See:

Installing the libraries

The bridge needs only the OPC UA client and YAML, plus the ThingWorx wrapper. Install with the helper script, or by hand:

# one-step helper (sets up the venv + installs everything)
.\install.ps1 # Windows
./install.sh # Linux / macOS

# or manually
pip install asyncua pyyaml
pip install -e "../ThingWorx Python wrapper" # the thingworx package (with the [opc] extra)

The ThingWorx wrapper loads the native C SDK (twCSdk.dll / libtwCSdk.so) lazily when the client is created. On Windows keep the OpenSSL DLLs (libcrypto-3-x64.dlllibssl-3-x64.dll) next to twCSdk.dll.

The workflow: discover → export → import → run

You run the bridge as a Python module (or via run.ps1 / run.sh, which pass a friendly command through):

.\run.ps1 discover     # 1) walk the OPC UA server  -> config/mapping.yaml
.\run.ps1 export # 2) mapping.yaml -> importable TWX XML (out/)
.\run.ps1 import # 3) push the XML into ThingWorx (ordered)
.\run.ps1 # 4) run the live bridge

# equivalent module form:
python -m opcua_bridge.discover
python -m opcua_bridge.export_twx
python -m opcua_bridge.import_to_twx
python -m opcua_bridge

Import order matters and the importer enforces it: _project.xml → _datashapes.xml → things/*.xml → _network.xml. Each step validates against entities already in the platform.

Configuring the app

At runtime the bridge reads exactly two files: config/bridge.yaml (connections + scan rates) and config/mapping.yaml (the OPC-UA→TWX rules, auto-generated by discover).

opcua:
endpoint: "opc.tcp://your-server:48020"
security: { policy: "None", mode: "None", trust_server_cert: true }
auth: { mode: "anonymous" } # or username / certificate

thingworx:
host: "localhost"
port: 8016 # 8016 = HTTP/WS, 443/8443 = HTTPS/WSS
app_key: "your-app-key"
gateway_name: "OpcUaBridgeGateway"
encryption: false
self_signed_ok: true

mapping: "opcua_bridge/config/mapping.yaml"

scan_groups: # named tag-rate classes
fast: { mode: subscription, publishing_interval_ms: 250 }
normal: { mode: subscription, publishing_interval_ms: 1000 }
audit: { mode: poll, poll_interval_s: 60 }

logging: { bridge: "INFO", asyncua: "WARNING", file: "INFO" }

scan_groups let you assign different update rates per Thing or even per field (an OT pattern familiar from KEPServerEX, Matrikon, ABB, Honeywell). Rules in mapping.yaml reference a group by name; resolution is field > rule > default.

Complex types become InfoTables

A scalar tag maps to a scalar property. But a struct, a struct array (e.g. a method's InputArguments), or a primitive array (Double[]Int32[], …) maps to a ThingWorx INFOTABLE property. discover emits one DataShape per such property (named <Thing>.<Property>_DS), and export renders them into _datashapes.xml — so the structured data lands in ThingWorx as a proper table, not a flattened string.

Running the bridge

Once the entities are imported, point bridge.yaml at your server and platform and start it:

.\run.ps1               # Windows
./run.sh # Linux / macOS
# or: python -m opcua_bridge --log-level DEBUG

It connects to ThingWorx, binds the Things, subscribes to (or polls) the OPC UA nodes, and streams values up. Stop it with Ctrl+C, or type q + Enter.

Troubleshooting

  • Import stops partway — the ordered import validates each file; fix the entity it names, then re-run import. Order is project → datashapes → things → network.
  • A setpoint pushes once then never updates — constant OPC UA tags are delivered only once; the bridge re-asserts every property on a timer (republish_interval_s) so nothing is lost to a race at bind time.
  • OPC UA connect / security errors — check the endpoint, the security policy/mode, and auth. For dev servers with self-signed certs set trust_server_cert: true.
  • Failed to load ThingWorx C SDK library — make sure twCSdk.dll and the OpenSSL DLLs are in the run folder (the launcher sets TWCSDK_LIB_PATH for you).

Takeaways

The bridge turns an OPC UA server into ThingWorx Things with a repeatable discover → export → import → run workflow — scalars and complex structures alike, driven entirely by YAML. It is a lightweight, scriptable alternative when you want full control over the mapping and no extra connection server.

Tested with: the OPC UA server from Unified Automation (ANSI C UA Server SDK) — https://www.unified-automation.com/products/server-sdk/ansi-c-ua-server-sdk.html.

Prerequisites: Python 3.7+, a twCSdk build for your OS, a reachable OPC UA server, and a ThingWorx platform with an Application Key.