OPC UA → ThingWorx bridge Python
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
INFOTABLEproperties 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:
- GitHub: https://github.com/thingworx-field-work/ThingWorx-Python-wrapper
- Community post: Python wrapper for C SDK for ThingWorx
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.dll, libssl-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_bridgeImport 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 DEBUGIt 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, thesecuritypolicy/mode, andauth. For dev servers with self-signed certs settrust_server_cert: true. Failed to load ThingWorx C SDK library— make suretwCSdk.dlland the OpenSSL DLLs are in the run folder (the launcher setsTWCSDK_LIB_PATHfor 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.

