Python Edge ML solution for ThingWorx
Running a machine-learning model right at the edge — next to the machine, without a round-trip to the cloud — is a common ask. This post shows a ready-made Python solution that reads live data from an OPC UA server, scores a PMML model locally, and pushes the predictions into ThingWorx as Thing properties. It builds on the thingworx-python wrapper, so the ThingWorx side is only a few lines of code and everything is driven by one config.json.
What it does
The service edge_inference.py wires up a small pipeline:
OPC UA (asyncua) → assemble a feature row → PMML model → ThingWorx properties
- subscribes to / polls OPC UA nodes (e.g. temperature, pressure, humidity);
- optionally reads extra inputs from ThingWorx properties (setpoints, limits);
- runs the model when the trigger condition is met;
- writes each prediction back to a ThingWorx property.
Where to get it
The Edge ML solution is on GitHub:
https://github.com/thingworx-field-work/Python-Edge-Machine-Learning
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 solution needs three groups of dependencies: OPC UA, data/ML, and the ThingWorx wrapper. They are listed in requirements.txt:
# OPC UA client
asyncua
# data + PMML scoring
pandas
numpy
sklearn-pmml-model # pure-Python backend (recommended, no Java)
# pypmml # optional: full PMML 4.4, needs a Java 8+ runtime
# ThingWorx C SDK Python wrapper (installed from this repo)
# pip install -e "../ThingWorx Python wrapper"Install them into a virtual environment:
pip install -r requirements.txt
pip install -e "../ThingWorx Python wrapper" # the thingworx packageThe repo also ships helper scripts — install.ps1 / install.sh set up the venv and install everything in one step, and run.ps1 / run.sh launch the service.
Importing the libraries in Python
Inside edge_inference.py the key imports are:
import json
import pandas as pd
from asyncua import Client as OpcUaClient # OPC UA
from thingworx import ThingWorxClient, Thing, BaseType # ThingWorx wrapper
# The PMML backend (sklearn-pmml-model or pypmml) is imported internally,
# chosen by the "backend" field in config.json.The ThingWorx wrapper loads the native C SDK (twCSdk.dll / libtwCSdk.so) lazily when the client is created — the same mechanism described in the Python SDK post. On Windows keep the OpenSSL DLLs (libcrypto-3-x64.dll, libssl-3-x64.dll) next to twCSdk.dll.
Configuring the app: config.json
You normally do not edit the Python at all — everything lives in config.json, which has four sections:
{
"thingworx": {
"host": "localhost", "port": 8016,
"app_key": "your-app-key", "thing_name": "EdgeMLThing",
"encryption": false, "self_signed_ok": true,
"input_properties": [ // read FROM ThingWorx, feed to model
{ "name": "OperatingMode", "type": "STRING",
"model_input_field": "operating_mode" }
],
"output_properties": [ // write predictions TO ThingWorx
{ "name": "ElectricalOutput", "type": "NUMBER",
"model_output_field": "ElectricalOutput", "push_type": "ALWAYS" }
]
},
"opcua": {
"server_url": "opc.tcp://your-kepware-host:49320",
"security": { "mode": "username",
"username": "OPCUAClient", "password": "secret" },
"nodes": [
{ "node_id": "ns=2;s=SIM_ML.Machine1.AmbientTemperature",
"name": "AmbientTemperature", "type": "NUMBER" }
]
},
"model": {
"pmml_file": "my_model.xml",
"backend": "auto", // auto | sklearn-pmml-model | pypmml
"trigger_on": "all", // run when all/any nodes reported
"input_mapping": { "AmbientTemperature": "AmbientTemperature" },
"output_mapping": { "ElectricalOutput": "ElectricalOutput" }
},
"logging": { "level": "INFO", "file": null }
}The two mappings are the glue: input_mapping connects each OPC UA node name to a PMML input field, and output_mapping connects each PMML output field to a ThingWorx property. Use trigger_on: "all" to score only once every subscribed node has reported at least once, or "any" to score on every change.
Running the app
Point config.json at your OPC UA server, ThingWorx host, and PMML file, then start it:
# Windows
.\run.ps1
# or explicitly
python edge_inference.py --config config.json
# Linux / macOS
./run.shOn start it connects to ThingWorx, binds the Thing, connects to the OPC UA server, and begins scoring. Predictions appear as properties on your Thing in Composer. Stop it with Ctrl+C.
Not sure which fields your model expects? Inspect it first — this lists the model's inputs and outputs without connecting to anything:
.\run.ps1 inspect my_model.xml # or: ./run.sh inspect my_model.xmlTroubleshooting
- PMML scoring fails only with
pypmml— that backend needs a Java 8+ runtime. Use the defaultsklearn-pmml-model(pure Python) if you have no JVM. - Model field mismatch — run
inspectand make the names ininput_mapping/output_mappingmatch the PMML exactly. - OPC UA connect errors — check
server_urland thesecurityblock (mode/username/password) against your server. 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
With one config file you get a complete edge-ML loop: OPC UA in, PMML scoring on the device, ThingWorx out — no cloud round-trip and no custom transport code. Swap the pmml_file and the two mappings to deploy a different model against different tags.
Prerequisites: Python 3.11+ (for sklearn-pmml-model), a twCSdk build for your OS, a reachable OPC UA server, and a ThingWorx platform with an Application Key and a RemoteThing to bind to.

