An standards-based and containerized industrial connectivity edge application translating from many proprietary protocols to OPC UA leveraging the W3C Web of Things (WoT) thing descriptions via the WoT-Connectivity specification, version 1.02. Data transformation into OPC UA Companion Specs is also supported. Thing Descriptions can be easily edited using the Eclipse Foundation's edi{TD}or, or automatically generated using AI. UA Edge Translator runs on both ARM and X64 architectures, it runs on both Windows and Linux and it runs in both Docker and Kubernetes environments.
UA Edge Translator solves the common "brownfield" use case of connecting disparate industrial assets with many different interfaces and translates their data into an OPC UA information model (ideally to one of the standardized companion specifications from the UA Cloud Library), enabling processing of the assets' data either on the edge or in the cloud leveraging a normalized, IEC standard (OPC UA) data format. This accelerates Industrial IoT projects and saves cost since the data doesn't need to be normalized in the cloud and makes use of the OT expertise often only found on-premises. For defining a mapping from the proprietary data format to OPC UA, the Web of Things (WoT) Thing Description schema (JSON-LD-based) is used. Additionally, the mechanism to provide the schema to the UA Edge Translator is also leveraging OPC UA. Therefore, for the first time, OPC UA is used for both the control and data plane for industrial connectivity, while previous solutions only used OPC UA for the data plane and a proprietary REST interface for the control plane.
The following southbound asset interfaces (a.k.a. protocol drivers) are supported:
- Modbus TCP
- Modbus RTU
- OPC UA
- OPC DA (a.k.a. OPC Classic)
- HTTP
- Siemens S7 Comm
- Rockwell CIP (Ethernet/IP)
- Beckhoff ADS (TwinCAT)
- LoRaWAN
- Matter
- OCPP (Open Charge Point Protocol) V1.6J
- OCPP (Open Charge Point Protocol) V2.1 (experimental)
- Mitsubishi MC Protocol (experimental)
- Aveva PI (experimental)
- BACNet (experimental)
- IEC61850 (experimental)
- DMTF Redfish (experimental)
Other southbound asset interfaces can easily be added by implementing the IAsset interface (for runtime interaction with the asset) as well as the IProtocolDriver interface (for asset onboarding).
There is also a tool provided (UA-WoTGenerator) that can convert from an OPC UA nodeset file (with instance variable nodes defined in it), an AutomationML file, a Beckhoff TwinCAT module class file, a Rockwell Studio 5000 tag CSV export, an Asset Admin Shell file, or a Siemens TIA Portal project file (via TIA Openness) to a WoT Thing Model file. See Generating WoT Thing Descriptions from PLC Engineering Tools below for details.
UA Edge Translator is available as a pre-built Docker container (supporting both AMD64 and ARM64 CPUs) directly from GitHub and will run on any Docker- or Kubernetes-enabled edge device. See "Packages" in this repo for details.
Note: Since BACNet uses UDP messages, BACNet support is limited to running UA Edge Translator natively or with the --net=host argument within a Docker container!
Note: Network discovery for Rockwell PLCs only works when running UA Edge Translator natively or with the --net=host argument within a Docker container!
Note: The LoRaWAN Network Server is available on port 5000 (not secure) and port 5001 (secure), which needs to be mapped to the Docker host for access. If you need a LoRaWAN Gateway, you can use the open-source Basic Station together with a LoRaWAN HAT for Raspberry Pi.
Note: The OCPP Central System is available on port 19520 (not secure) and on port 19521 (secure), which needs to be mapped to the Docker host for access.
Note: Since Matter uses BluetoothLE and mDNS as the underlying network protocol for commissioning, Matter support is limited to running UA Edge Translator natively or with the --network=host argument as well as with the
-v /run/dbus:/run/dbus:roargument and, depending on your Linux distro,--cap-add=NET_ADMIN, within a Docker container! Also, if you are using the BlueZ stack on Linux, make sure that experimental features are enabled since Matter uses some Bluetooth features that are not enabled by default in this stack.
Note: OPC DA (OLE for Process Control Data Access) is a legacy protocol that relies on COM/DCOM and its support is limited to running UA Edge Translator natively on Windows on x86 CPUs and the OPC DA server must be located on the same machine as UA Edge Translator (i.e. no DCOM support).
Note: For testing the Matter asset interface, you will also need to create a Thread network using an OpenThread Border Router (OTBR). An open-source OTBR is available here and runs on a Raspberry Pi equipped with a Thread radio USB dongle, the setup instructions are here. If you need a Matter commissioning QR-code scanner/decoder, there is an online one here.
Note: The Modbus RTU interface requires access to a serial port on the host system. When running UA Edge Translator in a Docker container, make sure to map the serial port device into the container using the --device argument, e.g.
-v /dev/ttyUSB1:/dev/ttyUSB1.
Note: Avoid
--privilegedin production deployments — it disables the user namespace, capability set, and seccomp/AppArmor confinement that the rest of the hardening relies on.
# 1) Create a named volume for drivers
docker volume create translator_drivers
# 2) Copy drivers from the driver-pack image into the volume
docker run --rm -v translator_drivers:/out ghcr.io/opcfoundation/ua-edgetranslator-drivers:main /bin/sh -c 'cp -a /drivers/. /out/'
# 3) Run UA Edge Translator with the drivers volume mounted to /app/drivers
docker run -d --name ua-edge-translator -v translator_drivers:/app/drivers -e OPCUA_USERNAME="REPLACE_ME" -e OPCUA_PASSWORD="REPLACE_ME" -p 4840:4840 ghcr.io/opcfoundation/ua-edgetranslator:main
In addition, the following folders within the Docker container store certificates, secrets and settings and should be mapped and persisted (-v argument in Docker command line) to the Docker host to encrypted folders, e.g. protected folders using BitLocker:
/app/logs(log files)/app/pki(certificates and keys)/app/settings(WoT Thing Descriptions)/app/nodesets(OPC UA nodesets for referenced companion specifications)/app/drivers(protocol driver DLLs, see above)
E.g. -v c:/uaedgetranslator/pki:/app/pki, etc.
Client certificates need to be manually moved from the /pki/rejected/certs folder to the /pki/trusted/certs folder to trust an OPC UA client trying to connect.
A ready-to-use Kubernetes deployment manifest is provided in this repository: UA-EdgeTranslator.yaml. It creates a dedicated ua-edgetranslator-namespace, deploys UA Edge Translator with an init container that copies the protocol drivers from the driver-pack image into a shared volume, and exposes the OPC UA, LoRaWAN and OCPP ports via a LoadBalancer service.
Apply it to your cluster directly from this repository with:
kubectl apply -f https://raw.githubusercontent.com/OPCFoundation/UA-EdgeTranslator/main/UA-EdgeTranslator.yaml
Before applying, review the manifest and consider adjusting the following to suit your environment:
- The
OPCUA_USERNAMEandOPCUA_PASSWORDenvironment variables (consider using a KubernetesSecretinstead of inline values for production). - The
hostPathentries for thesettings,pki,logsandnodesetsvolumes — these default to paths under/mnt/c/K3s/UAEdgeTranslator/(suitable for K3s on WSL) and should be changed to persistent locations on your nodes (or replaced withPersistentVolumeClaims). - The exposed service ports if you do not need LoRaWAN (5000/5001) or OCPP (19520/19521).
UA Edge Translator can be deployed to an Azure IoT Edge device managed by an Azure IoT Hub. A ready-to-use deployment manifest is provided in this repository: deployment.template.json. It deploys:
- The standard Azure IoT Edge system modules (
$edgeAgentand$edgeHub, both pinned to the 1.5 LTS tag). - An
uaedgetranslatordriversmodule that runs once (restartPolicy: never) and copies the signed protocol drivers from theghcr.io/opcfoundation/ua-edgetranslator-drivers:mainimage into a shared Docker named volume. - The
uaedgetranslatormodule itself, which mounts that shared drivers volume into/app/driversand exposes the OPC UA (4840), LoRaWAN (5000/5001) and OCPP (19520/19521) ports on the host. - The Microsoft OPC Publisher module (
mcr.microsoft.com/iotedge/opc-publisher:2.9), which connects to the OPC UA server exposed by UA Edge Translator atopc.tcp://uaedgetranslator:4840and forwards the translated OPC UA telemetry to the local$edgeHubmodule (not directly to IoT Hub).$edgeHubthen routes it upstream viaFROM /messages/modules/opcpublisher/* INTO $upstreamand applies its store-and-forward buffer (timeToLiveSecs = 7200) when the cloud link is down. The manifest does not set--mqc/--ec/ any other external broker connection string, so the IoT Hub SDKModuleClientinside OPC Publisher uses the auto-injectedEdgeHubConnectionStringand goes throughedgeHub.
- An Azure IoT Hub instance.
- An IoT Edge device registered with that IoT Hub and running the Azure IoT Edge 1.5 LTS runtime on a Linux host (AMD64 or ARM64).
- The Azure CLI with the
azure-iotextension installed (az extension add --name azure-iot).
- Edit deployment.template.json and replace the
OPCUA_USERNAME/OPCUA_PASSWORDplaceholder values with the credentials you want OPC UA clients to use when connecting to UA Edge Translator. For production, store these in Azure Key Vault or use the IoT Edge secret injection pattern instead of inlining them in the manifest. - Optionally add any of the Optional Environment Variables (for example
UACLURL,LICENSE_KEYorWOT_MAX_FILE_BYTES) to theenvblock of theuaedgetranslatormodule. - Apply the deployment to your IoT Edge device:
az iot edge set-modules `
--hub-name <your-iot-hub-name> `
--device-id <your-iot-edge-device-id> `
--content ./deployment.template.json
- Verify on the IoT Edge device that all four modules are reported as
running(note thatuaedgetranslatordriverswill report as exited / stopped once it has copied the drivers — this is expected because itsrestartPolicyisnever):
sudo iotedge list
You can also apply the manifest from the portal:
- Navigate to your IoT Hub → Devices → select your IoT Edge device → Set Modules.
- Choose Review + create → Load from a JSON file and pick deployment.template.json.
- Update the
OPCUA_USERNAME/OPCUA_PASSWORDenvironment variables under theuaedgetranslatormodule before submitting.
The manifest creates Docker named volumes (uaedgetranslator-drivers, uaedgetranslator-settings, uaedgetranslator-pki, uaedgetranslator-logs, uaedgetranslator-nodesets, opcpublisher-pn, opcpublisher-pki, opcpublisher-logs) that survive container restarts and module updates. For production deployments, consider the following:
- Replace the named volumes with bind mounts to encrypted folders on the host (e.g. LUKS-encrypted partitions) by adjusting the
Mountsentries in thecreateOptionsfield — the/app/pkifolders (for both UA Edge Translator and OPC Publisher) in particular contain private keys and must be protected. - Client certificates need to be manually moved from
/app/pki/rejected/certsto/app/pki/trusted/certsinside the container (docker execinto theuaedgetranslatormodule) to trust an OPC UA client trying to connect. Because OPC Publisher acts as an OPC UA client against UA Edge Translator, its certificate fromopcpublisher-pki/own/certsmust also be copied into UA Edge Translator's trusted store before subscriptions can be established.
OPC Publisher reads its subscription configuration from /app/pn/publishednodes.json. Before applying the manifest, populate the opcpublisher-pn volume on the IoT Edge host with a publishednodes.json file describing which OPC UA nodes to subscribe to on UA Edge Translator. A minimal example:
[
{
"EndpointUrl": "opc.tcp://uaedgetranslator:4840",
"UseSecurity": true,
"OpcAuthenticationMode": "UsernamePassword",
"OpcAuthenticationUsername": "REPLACE_ME",
"OpcAuthenticationPassword": "REPLACE_ME",
"OpcNodes": [
{
"Id": "nsu=http://opcfoundation.org/UA/WoTCon/;s=YourAssetVariable",
"OpcSamplingInterval": 1000,
"OpcPublishingInterval": 1000
}
]
}
]The EndpointUrl uses the module's hostname (uaedgetranslator) because all IoT Edge modules on the same device share a Docker bridge network and can resolve each other by module name. The credentials must match the OPCUA_USERNAME / OPCUA_PASSWORD configured for UA Edge Translator. See the OPC Publisher configuration schema and publishednodes.json reference for the full schema.
To populate the volume, SSH to the IoT Edge device and run:
sudo docker volume create opcpublisher-pn
sudo cp publishednodes.json /var/lib/docker/volumes/opcpublisher-pn/_data/publishednodes.json
sudo chown 1000:1000 /var/lib/docker/volumes/opcpublisher-pn/_data/publishednodes.json
Alternatively, OPC Publisher exposes IoT Hub direct methods (PublishNodes_V1, UnpublishNodes_V1, GetConfiguredNodesOnEndpoint_V1, …) so the configuration can be managed from the cloud after the module starts. See the OPC Publisher direct methods API reference and command-line options reference.
- The
uaedgetranslatordriversmodule hasrestartPolicy: neverandstartupOrder: 0so it runs once at deployment time, copies the signed drivers into the shared volume, and then exits cleanly. Theuaedgetranslatormodule starts afterwards (startupOrder: 1) and consumes the drivers from the same volume. OPC Publisher starts last (startupOrder: 2) so the OPC UA endpoint is reachable when it tries to connect. - Because the images on
ghcr.io/opcfoundationandmcr.microsoft.comare public, noregistryCredentialsentry is required. If you mirror them into Azure Container Registry or another private registry, add the credentials under$edgeAgent → settings → registryCredentials. - The same caveats that apply to the Docker deployment apply here too — see the notes at the top of the README about BACNet, Matter, Rockwell discovery and Modbus RTU, which all require
--network=host(which IoT Edge does not expose by default) or device pass-through. To enable host networking for those protocols, add"NetworkMode": "host"to the module'screateOptions.HostConfig. - The OPC Publisher command line in the manifest uses
--aa(auto-accept untrusted certificates) for ease of first-time setup. Remove--aafor production and instead exchange certificates manually between OPC Publisher (opcpublisher-pki) and UA Edge Translator (uaedgetranslator-pki). - The manifest sets
IGNORE_PROVISIONING_MODE=1on theuaedgetranslatormodule because OPC Publisher does not support the OPC UA GDS Server Push provisioning mechanism, so it cannot inject an issuer certificate to take UA Edge Translator out of provisioning mode. Without this flag, OPC Publisher would be unable to browse or subscribe to the WoT-Connectivity-related OPC UA nodes. RemoveIGNORE_PROVISIONING_MODEfor production if you have another way to provision UA Edge Translator (e.g. manually copying an issuer certificate into theuaedgetranslator-pkivolume atissuer/certs).
OPCUA_USERNAME- OPC UA username to connect to UA Edge Translator. The server refuses to start if this is missing or empty.OPCUA_PASSWORD- OPC UA password to connect to UA Edge Translator. The server refuses to start if this is missing or empty.
APP_NAME- OPC UA application name to use. Default is UAEdgeTranslator.UACLURL- UA Cloud Library URL (e.g. https://uacloudlibrary.opcfoundation.org or https://cloudlib.cesmii.net).UACLUsername- UA Cloud Library username.UACLPassword- UA Cloud Library password.OPCUA_CLIENT_USERNAME- OPC UA client username to connect to an OPC UA asset.OPCUA_CLIENT_PASSWORD- OPC UA client password to connect to an OPC UA asset.DISABLE_ASSET_CONNECTION_TEST- Set to1to disable the connection test when mapping an asset to OPC UA.IGNORE_PROVISIONING_MODE- Set to1to ignore provisioning mode and allow access to WoT-Connectivity-related OPC UA nodes in the address space.OPC_UA_GDS_ENDPOINT_URL- The endpoint URL of an OPC UA Global Discovery Server on the network, which will then be used during network discovery.DISABLE_TLS- Set to1to turn off TLS for OCPP and LoRaWAN connections.LICENSE_KEY- Activation key required by the OPC UALicensewrite method. If unset, license activation requests are rejected withBadNotSupported. The configured value is compared in constant time against the value written by the OPC UA client.WOT_MAX_FILE_BYTES- Maximum size (in bytes) accepted per upload through the OPC UA File API (WoT Thing Descriptions and nodeset uploads). Defaults to5242880(5 MB). Uploads exceeding the cap are rejected withBadOutOfMemoryso a malicious or misconfigured client cannot exhaust server memory.DRIVER_ALLOWLIST_OIDC_ISSUER- Fulcio OIDC issuer the driver allow-list signing certificate must come from. Defaults tohttps://token.actions.githubusercontent.com. See Protocol driver allow-list (trust manifest).DRIVER_ALLOWLIST_OIDC_REPO- GitHubowner/repowhose.github/workflows/driver-pack.ymlis allowed to sign the driver allow-list manifest. Defaults toOPCFoundation/UA-EdgeTranslator. The loader accepts bothrefs/heads/mainandrefs/tags/v*runs of that workflow file. Override this when you publish your own driver pack from a fork.DRIVER_ALLOWLIST_OFFLINE_MODE- Set toallow-hash-onlyto permit hash-only enforcement of the driver allow-list when the Sigstore bundle is missing or cannot be verified (intended for air-gapped deployments). Unset by default, meaning the loader is fail-closed and refuses to load any driver if the manifest is not signed and verified.- REDFISH_USERNAME - Username for authentication to Redfish assets.
- REDFISH_PASSWORD - Password for authentication to Redfish assets.
UA Edge Translator supports provisioning via GDS Server Push functionality as described in part 12 of the OPC UA specification. Until an issuer certificate is provided in the issuer certificate store of UA Edge Translator, it is in provisioning mode and access to the WoT-Connectivity-related OPC UA nodes in its address space is restricted. An issuer certificate can be provided as part of the GDS Server Push mechanism or by manually copying a certificate into the issuer certificate store found in the /app/pki/issuer/certs directory. During provisioning, all client certificates are auto-approved by UA Edge Translator, but afterwards they need to be manually trusted by copying them from the rejected certificate store to the trusted certificate store, unless of course the certificates were already trusted (for example because they were provided by the GDS Server Push mechanism). These stores can also be found in the /app/pki/ folder.
UA Edge Translator can be controlled through the use of just 2 OPC UA methods (and OPC UA file transfer functionality) readily available through the OPC UA server interface built in. The methods are:
- CreateAsset(assetName) - Creates an asset node and an OPC UA File API node below the asset node (which can be used to upload the WoT Thing Description), returning the node ID of the newly created asset node on success.
- DeleteAsset(assetNodeId) - deletes a configured asset.
Asset names supplied to CreateAsset / CreateAssetForEndpoint, the name field of an uploaded Thing Description, and *.jsonld filenames placed in settings/ must:
- be 1-128 characters long,
- contain only ASCII letters, digits,
.,_or-, - not start with
., and - not contain path separators or relative-path segments (
..,/,\, etc.).
Names that don't satisfy these rules are rejected with BadInvalidArgument and logged. The restriction prevents a client from writing outside settings/ or hijacking another asset's polling state through a path-traversal name.
UA Edge Translator loads protocol drivers as DLLs from the /app/drivers folder at runtime.
The following will get you to a state you can modify with your own driver and WoT files.
-
Publish the HttpClient driver. This will copy the httpclient.dll and its debug file in the "drivers" folder under "UAServer"
-
Copy the WoT File "SimpleHTTPClient.td.jsonld" in the "settings" folder under "UAServer"
-
Load the UA-EdgeTranslator project and run it.
-
Connect to the opc server of the UA-EdgeTranslator using your favorite OPC UA Client (i.e. UAExpert).
You must specify the credentials to connect to UA-EdgeTranslator in the launchSettings.json file under "Properties" of the UA-EdgeTranslator project:
"OPCUA_USERNAME": "REPLACE_ME",
"OPCUA_PASSWORD": "REPLACE_ME",
To test your setup before provisioning the UA-EdgeTranslator with the proper certificates you can also set this in the launchSettings.json:
"IGNORE_PROVISIONING_MODE": "1"
Once connected, you will see the OPC UA address space with a node called "WoTAssetConnectionManagement"
- Open this node and you will find another node called "SimpleHTTPClient.td"
In this branch you will find a variable "IPAddress" that was defined in the "SimpleHTTPClient.td.jsonld". The variable is read every 60 seconds, although it probably does not change since it just calls a service on the internet determining your external IP address.
For more details on the Web of Things file format and description see https://www.w3.org/TR/wot-thing-description-2.0/
To build your own protocol driver, create a new .NET10 Class Library project and add a project reference to the UA-EdgeTranslator, making sure that only the protocol driver DLL is published:
<ItemGroup>
<ProjectReference Include="..\..\UAServer\UaEdgeTranslator.csproj">
<Private>true</Private>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
Then implement the IProtocolDriver and IAsset interface and publish your project into the ..\..\UAServer\drivers\<yourdrivername> folder and restart UA Edge Translator to load your new protocol driver.
Protocol drivers are loaded as in-process .NET assemblies and therefore run with the same privileges as UA Edge Translator itself. To prevent an attacker (or a misconfigured deployment) from dropping an arbitrary DLL into /app/drivers and having it executed, the loader supports an opt-in SHA-256 allow-list manifest that is itself signed with Sigstore (cosign keyless) and verified at startup against the GitHub Actions identity that produced the driver pack.
The driver-pack image ships two files next to the drivers:
| Path | Purpose |
|---|---|
/drivers/drivers.allowlist.json |
SHA-256 hashes of every *.dll we are willing to load. |
/drivers/drivers.allowlist.sigstore.json |
cosign keyless Sigstore bundle that signs the manifest above. |
On startup, DriverLoadContext first verifies the bundle, pins the signing identity to the GitHub Actions workflow that produced the driver pack, and only then uses the manifest to decide which DLLs to load. If the bundle is missing or fails verification the loader is fail-closed (no drivers are loaded) unless the operator explicitly opts in to a hash-only fallback for air-gapped deployments.
| Manifest | Bundle | Behavior |
|---|---|---|
| Missing | n/a | Loads every *.dll under drivers/** (legacy mode, backwards compatible). A warning is written on every startup recommending that the manifest be added. |
| Present, signature verified against the pinned identity | present + valid | SHA-256 enforcement using the manifest. Refused DLLs are logged with their offending path and computed hash. |
| Present, bundle missing or signature/identity mismatch | — | Refuses to load any protocol driver. Set DRIVER_ALLOWLIST_OFFLINE_MODE=allow-hash-only to downgrade to hash-only enforcement (with a loud warning) for air-gapped sites. |
| Present but empty / unparseable | n/a | Refuses to load any protocol driver and logs an error. UA Edge Translator continues to start, but no southbound assets will work until the manifest is fixed. |
For production / enterprise deployments the signed manifest is mandatory — without it there is no trust boundary between the host and a third-party driver.
The signing identity defaults to the OPC Foundation driver-pack workflow. Override the defaults via environment variables when you publish your own driver pack from a fork:
| Env var | Default | Purpose |
|---|---|---|
DRIVER_ALLOWLIST_OIDC_ISSUER |
https://token.actions.githubusercontent.com |
Fulcio OIDC issuer the signing certificate must come from. |
DRIVER_ALLOWLIST_OIDC_REPO |
OPCFoundation/UA-EdgeTranslator |
GitHub owner/repo whose .github/workflows/driver-pack.yml is allowed to sign the manifest. The loader accepts both refs/heads/main and refs/tags/v* runs of that workflow file. |
DRIVER_ALLOWLIST_OFFLINE_MODE |
unset (fail-closed) | Set to allow-hash-only to permit hash-only enforcement when the Sigstore bundle is missing or cannot be verified (air-gapped deployments). The loader logs a loud warning when this fallback is taken. |
The file is a small JSON document at drivers/drivers.allowlist.json (UTF-8, no BOM):
{
"allowed": [
{ "name": "Modbus.dll", "sha256": "9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08" },
{ "name": "Siemens.dll", "sha256": "2C26B46B68FFC68FF99B453C1D30413413422D706483BFA0F98A5E886266E7AE" },
{ "name": "RockwellEIP.dll", "sha256": "FCDE2B2EDBA56BF408601FB721FE9B5C338D10EE429EA04FAE5511B68FBF8FB9" }
]
}Notes:
- The
namefield is for human readability only — matching is done by hash, not by file name, so renaming a DLL does not bypass the check. - Hashes are uppercase hexadecimal SHA-256 of the raw DLL bytes; case is ignored when matching.
- Every
*.dllin a driver folder is hashed before the loader decides what to do with it, so transitive managed dependencies and native pInvoke DLLs that ship alongside a driver must both be listed. Files that are not*.dll(.so,.pdb, config, etc.) are not enumerated and do not need an entry. - Neither the manifest nor its
.sigstore.jsonbundle need to be listed.
To compute the SHA-256 of a driver from PowerShell:
Get-FileHash -Algorithm SHA256 .\drivers\Modbus\Modbus.dll | Select-Object HashOr from bash on Linux:
sha256sum drivers/Modbus/Modbus.dllThe published driver-pack image (ghcr.io/opcfoundation/ua-edgetranslator-drivers:main) ships its own /drivers/drivers.allowlist.json and /drivers/drivers.allowlist.sigstore.json that cover every DLL in every built-in driver folder. Both are produced by .github/workflows/driver-pack.yml from the exact bytes that get baked into the image; the bundle is created by cosign sign-blob --bundle using the workflow's GitHub Actions OIDC identity (no long-lived signing keys). If you only use the built-in drivers, you do not need to generate or sign anything yourself — just mount the driver-pack contents (manifest and bundle) into /app/drivers.
If you ship your own driver alongside the built-in ones, you must produce your own signed manifest covering both your DLLs and the built-in ones, and point DRIVER_ALLOWLIST_OIDC_REPO (and optionally DRIVER_ALLOWLIST_OIDC_ISSUER) at the workflow that signs it. Otherwise the loader will refuse to honour an unsigned manifest in fail-closed mode, or ignore your additional driver in allow-hash-only mode.
When the manifest is enforced you will see one of the following on startup:
Protocol driver allow-list /app/drivers/drivers.allowlist.json verified against Sigstore bundle /app/drivers/drivers.allowlist.sigstore.json (signer: issuer=..., san=...).
Protocol driver allow-list loaded from /app/drivers/drivers.allowlist.json with N entries; only matching SHA-256 hashes will be loaded.
and, for any DLL that does not match:
Refusing to load protocol driver assembly /app/drivers/Foo/Foo.dll: SHA-256 <hash> not in allow-list (/app/drivers/drivers.allowlist.json).
If the manifest is missing you will instead see:
Protocol driver allow-list /app/drivers/drivers.allowlist.json not found; loading every *.dll under /app/drivers. Production deployments should ship a signed allow-list manifest — see README.
The UA-WoTGenerator tool in this repository converts data exported from common PLC engineering tools into WoT Thing Model files (*.tm.jsonld) that UA Edge Translator can consume after the placeholders (e.g. {{address}}, {{port}}, {{name}}) have been filled in.
It currently supports input from:
| Vendor / Source | Input file | Produced binding |
|---|---|---|
| Beckhoff TwinCAT | *.tmc (TwinCAT Module Class) |
ADS / GenericForm |
| Rockwell Studio 5000 / RSLogix 5000 | *.csv (tag / UDT export) |
EtherNet/IP (EIPForm) |
| Generic Modbus point list (Azure IoT format) | *.csv |
Modbus TCP (ModbusForm) |
| Siemens TIA Portal V15.1..V21 | *.ap15_1, *.ap16 .. *.ap21 (project file) |
S7Comm (S7Form) |
| OPC UA | *.NodeSet2.xml |
OPC UA (GenericForm) |
| AutomationML | *.aml |
GenericForm |
| Asset Administration Shell — Asset Interface Description | *.aas.json |
Modbus or GenericForm |
The tool scans its current working directory, processes every recognised file it finds, and writes a <inputName>.tm.jsonld next to it (Siemens projects emit one file per PLC: <projectName>_<plcName>.td.jsonld).
UA-WoTGenerator targets net8.0-windows / x64 because the Siemens TIA Openness API is x64‑only. The other importers also run on the same build.
cd UA-EdgeTranslator
dotnet build UA-WoTGenerator\UA-WoTGenerator.csproj -c Release /p:Platform=x64Run UA-WoTGenerator from any directory containing input files:
cd <folder containing your engineering exports>
& "<repo>\UA-WoTGenerator\bin\x64\Release\net8.0-windows\UA-WoTGenerator.exe"Each generated *.td.jsonld can then be uploaded to UA Edge Translator via the OPC UA File API exposed under the asset node, or copied into /app/settings for it to be picked up at start‑up (after replacing the {{...}} placeholders with the real values for your asset).
- Open the project in TwinCAT XAE / Visual Studio.
- In the Solution Explorer, expand the PLC project node.
- Right‑click the PLC project → Properties → TMC File (or Build → TwinCAT Build → Build TMC File) — the
*.tmcis regenerated on every PLC build and lives next to the*.tsprojor under<project>\_Boot\TwinCAT RT (x64)\Plc\. - Copy that
*.tmcnext toUA-WoTGenerator.exeand run the tool. - The tool emits
<plcName>.tm.jsonldwith one Property per published symbol (those exposed in the ADS data area).
Only symbols that appear in a TwinCAT data area (
<DataArea>) are exported. Variables you want to read over ADS must therefore have the{attribute 'TcLinkTo'}/ publish flag set in TwinCAT.
- Open the controller project in Studio 5000 Logix Designer (or RSLogix 5000).
- Open the Tags editor for the controller / program scope you want to expose.
- Use Tools → Export → Tags and Logic Comments… and choose CSV as the output format. Make sure both Tags and Comments are included — the tool reads
COMMENTrows to infer UDT field names. - Drop the resulting
*.csvnext toUA-WoTGenerator.exeand run it. - The tool emits
<csvName>.tm.jsonldcontaining one Property per primitive tag and one structured Property per UDT‑typed tag (the field offsets inside the UDT are resolved at runtime by the Rockwell driver).
The Rockwell driver also implements
BrowseAndGenerateTD, so you can alternatively let UA Edge Translator browse a connected controller live (no CSV needed) when the controller is reachable on the network.
The Siemens importer drives the TIA Portal Openness API to walk the project's PlcSoftware → BlockGroup → DataBlock hierarchy and emit one Property per leaf interface member of every standard‑access (non‑optimized) data block, including byte and bit offsets.
- TIA Portal V15.1, V16, V17, V18, V19, V20 or V21 installed locally. The project must be openable in that TIA version (older STEP 7 Classic projects must be migrated into TIA first).
- For TIA Portal V15.1 / V16, install TIA Portal Openness (setup option in V15.1 / V16). V17+ ships Openness with the base install.
- The current Windows user must be a member of the local
Siemens TIA Opennessgroup. Add the user (e.g. vialusrmgr.msc) and sign out / in. - Open your project.
- For S7-1200 and 1500 PLCs, in TIA Portal, on every FB / DB you want to read:
- Properties → Attributes → uncheck "Optimized block access" — without this there are no stable byte offsets and S7Comm classic cannot address individual variables. Optimized blocks are skipped by the importer with a warning.
- For S7-1200 and 1500 PLCs, on the CPU itself:
- Properties → Protection & Security → Connection mechanisms → enable "Permit access with PUT/GET communication from remote partner" (this is a runtime requirement for the S7 driver, not for the import).
Siemens names the V15.1 install folder
Portal V15_1(with an underscore) but the API sub-folder underneath isPublicAPI\V15.1(with a dot). The build script normalizes the underscore to a dot automatically; pass/p:SiemensTIAPortalPath="C:\Program Files\Siemens\Automation\Portal V15_1"if you need to target V15.1 explicitly.
By default the project file references TIA V21 at:
C:\Program Files\Siemens\Automation\Portal V21\PublicAPI\V21\net48\Siemens.Engineering.Base.dll
If you have a different version installed, e.g. V20, override the path on the command line:
dotnet build UA-WoTGenerator\UA-WoTGenerator.csproj `
-c Release /p:Platform=x64 `
/p:SiemensTIAPortalPath="C:\Program Files\Siemens\Automation\Portal V20"For V15.1 (note the underscore in the install-folder name), use:
dotnet build UA-WoTGenerator\UA-WoTGenerator.csproj `
-c Release /p:Platform=x64 `
/p:SiemensTIAPortalPath="C:\Program Files\Siemens\Automation\Portal V15_1"The Openness assemblies are referenced from the local TIA install with <Private>false</Private> and never copied into the output (Siemens forbids redistribution). At runtime the tool resolves them from the same install path; override with the SIEMENS_TIA_PATH environment variable if needed.
If the TIA project has Project User Management (UMAC) enabled, the importer needs valid credentials to open it — there is no way to remove the password once it has been set. Two paths are supported, in this order:
The importer first calls TiaPortal.GetProcesses() and looks for a TIA Portal instance that already has the target project file open. If one is found, it attaches to that session via TiaPortalProcess.Attach() (the version-portable entry point exposed by every supported Openness build from V15.1 onwards) and reuses the already-loaded Project. Because the user has already authenticated against UMAC interactively in the TIA UI, Openness never invokes the credential callback, so this path works even on Openness builds (e.g. TIA V15.1 / V16) that don't expose the UMAC-aware Projects.Open(FileInfo, UmacDelegate) overload required by Option 2 at all.
Workflow:
-
Launch TIA Portal interactively.
-
Open the protected project and log in at the UMAC prompt when TIA asks.
-
Leave TIA running and start the UA-WoTGenerator tool. Look for this line in the console output:
Attaching to running TIA Portal process (PID …) that already has the project open. -
The importer reuses your authenticated session and does not close the project or dispose the TIA instance when it finishes, so your editor session is left intact.
The Windows user running UA-WoTGenerator must still be a member of the Siemens TIA Openness group; that requirement is enforced by Siemens at attach time.
If no running TIA Portal instance has the project open, the importer falls back to launching a headless TiaPortalMode.WithoutUserInterface instance and opening the file itself. For UMAC-protected projects, supply the credentials via environment variables before running the tool:
$env:SIEMENS_TIA_USERNAME = "<user defined in the TIA project>"
$env:SIEMENS_TIA_PASSWORD = "<password for that user>"
.\UA-WoTGenerator.exeWhen both variables are set the importer invokes the Openness UmacDelegate overload of Projects.Open and populates the credentials object handed back to it. Because the credentials API changed between Openness versions (V17..V20 expose UmacUserCredentials with Name + Conceal(SecureString); V21+ exposes UmacCredentials with Name + Type + SetPassword(SecureString) on a split-assembly layout), the importer discovers the overload and its members reflectively at runtime — a single build therefore works against every supported Openness version without per-version build flags. When either variable is empty or missing the importer falls back to the unprotected open and behaves exactly as before, so leaving these variables unset is the right choice for projects without User Management.
Note: The UMAC-aware
Projects.Open(FileInfo, UmacDelegate)overload was introduced in Openness V17 and is not present in V15.1 / V16. Because the importer discovers and invokes that overload reflectively, no per-version build flag is required — the same binary adapts at runtime to whichever Openness version is installed. When the resolved Openness build does not expose the overload (TIA V15.1 / V16) the headless code path prints a warning, ignoresSIEMENS_TIA_USERNAME/SIEMENS_TIA_PASSWORD, and falls back to the unauthenticatedProjects.Open(FileInfo). On TIA V15.1 / V16, Option 1 above is therefore the only working route for UMAC-protected projects — open the project in TIA first, log in, and let the importer attach to that session.
-
Copy the entire contents of the TIA project folder (e.g. the files and folders containing the e.g. *.ap21 file) into the folder
<repo root>\UA-WoTGenerator\bin\x64\Release\net48\, so the path to the project file is<repo root>\UA-WoTGenerator\bin\x64\Release\net48\<projectName>.ap21, for example. -
Run the tool:
cd UA-WoTGenerator\bin\x64\Release\net48 .\UA-WoTGenerator.exe
-
For every PLC in the project, the tool emits
<projectName>_<plcName>.td.jsonldcontaining one Property per leaf data block member, addressed byS7DBNumber,S7Start,S7Pos,S7SizeandS7MaxLen. The PLC's IPv4 address (read from the PROFINET interface) is baked into thebasefield ass7://<ip>:0:1.
Files with extensions
.ap15_1,.ap16,.ap17,.ap18,.ap19,.ap20and.ap21are all recognised; pick the one that matches your installed TIA version.
Many industrial assets — power meters, drives, gateways, sensors, scanners, RFID readers, soft starters, IO‑Link masters, weighing terminals, etc. — are fixed‑function: their data model is hard‑wired by the vendor and shipped as a Modbus / EtherNet/IP / S7 / HTTP register or object map in the user manual. There is no engineering project to export, so the two practical paths to a Thing Description are:
Always check whether the vendor already publishes a machine‑readable description before generating one yourself. In order of preference:
- A WoT Thing Description (
*.td.jsonld/*.tm.jsonld) on the product page or GitHub. - An Asset Administration Shell (AAS) submodel Asset Interface Description (AID) package (
*.aas.json,*.aasx). UA Edge Translator'sUA-WoTGeneratorcan convert AID JSON files directly to WoT Thing Models — see the table above. - An OPC UA companion specification NodeSet2 for the device class (
*.NodeSet2.xml), e.g. from the UA Cloud Library. Also supported byUA-WoTGenerator. - A vendor‑provided register / point list (CSV, TMC (XML) for TwinCAT). For a generic Modbus point list in the Azure IoT format, the tool already imports it directly. For other CSV layouts, a small adapter in
UA-WoTGeneratoris usually a few minutes' work.
A vendor‑provided file is authoritative, has correct register addresses and scaling factors, and removes the risk of hallucinated fields. It also tends to be re‑usable across every customer of that device.
When no machine‑readable description is available, the asset's user / reference manual PDF almost always contains the full register or object map — Modbus tables, EtherNet/IP assembly definitions, OPC UA NodeIds, HTTP endpoints — together with data types, units and scaling factors. A modern multimodal LLM (ChatGPT, Microsoft Copilot, Claude, Gemini, etc.) can read the PDF and emit a WoT Thing Model in one step.
Recommended workflow:
-
Download the official user manual / reference manual PDF for your specific firmware revision from the vendor's website. Manuals labelled "Modbus reference", "Communication manual", "EDS file documentation" or similar are best — they contain the register tables you need.
-
Open a chat session with an LLM that supports file upload (e.g. ChatGPT, Microsoft 365 Copilot, Claude). Upload the PDF as an attachment.
-
Send the prompt below, replacing the angle‑bracketed values. Treat the prompt as a starting point — for unusual devices you may need to clarify which register table the LLM should focus on (some manuals contain several).
You are an industrial connectivity engineer. From the attached user manual for <vendor> <product> <firmware/rev> generate a single WoT 1.1 Thing Model JSON document for use with the OPC Foundation UA Edge Translator.
Use the
<protocol>binding (one of:modbus+tcp,modbus,eip,s7,http,opc.tcp).Requirements:
- Output only the JSON, no prose.
@contextmust be["https://www.w3.org/2022/wot/td/v1.1"].@typemust be["tm:ThingModel"].securityDefinitionsmust be{ "nosec_sc": { "scheme": "nosec" } }andsecuritymust be["nosec_sc"].name="{{name}}",base="<protocol>://{{address}}:{{port}}",title= product name.- For each variable in the manual's register / object table, emit one entry under
propertieswith:type(number/integer/boolean/string),readOnly,observable: true, and one form whose binding fields match the protocol. - For Modbus use
ModbusFormfields:href(e.g."40001?quantity=2"),op: ["readproperty","observeproperty"],modv:type(xsd:float/xsd:integer/xsd:boolean/xsd:string),modv:entity(HoldingRegister/InputRegister),modv:pollingTime(ms),modv:mostSignificantByte,modv:mostSignificantWord, andmodv:multiplierif the manual specifies a scaling factor. - For EtherNet/IP use
EIPFormfields:href(tag name),op,type(xsd:REAL,xsd:DINT, …),pollingTime. - For Siemens S7 use
S7Formfields:href,op,s7:target(DB/MB/EB/AB),s7:dbnumber,s7:start,s7:pos,s7:size,s7:maxlen(for STRING),type,pollingTime. - Do not invent registers that are not in the manual. If a value in the manual is unclear, omit it rather than guessing.
- Use the original variable / register names from the manual as property keys, replacing spaces with underscores.
-
Save the LLM's response as
<assetName>.tm.jsonldand review it manually against the manual:- Spot‑check a handful of register addresses, data types, byte/word order and scaling factors.
- Confirm that read‑only registers are flagged
readOnly: true. - Trim out anything you don't actually need to expose.
-
Replace the
{{name}},{{address}}and{{port}}placeholders with the real values for your asset (Eclipse's Edi{td}or WoT-file editor does this automatically for you). -
Upload the file to UA Edge Translator using the OPC UA File API exposed under the asset node (or drop it into
/app/settings) and let the matching protocol driver onboard the asset.
Important: an LLM can misread tables, especially in scanned PDFs, multi‑column layouts or manuals with several variants of the same register map. Always validate the produced Thing Description against the manual and against a live test read from the asset before deploying it to production.
UA Edge Translator uses a zero trust security model and implements the following security features:
- UA Edge Translator runs within a Docker container in a restricted network environment and with limited permissions to the host system.
- UA Edge Translator comes with extensive logging to the console and to disk, but does not log any sensitive information such as passwords or private keys.
- OPC UA SHA256 sign & encrypt server security policy and username/passowrd user authentication for secure communication between clients and the UA Edge Translator OPC UA server as well as between the UA Edge Translator OPC UA client protocol driver and OPC UA assets.
- OPC UA GDS Server Push provisioning mechanism for secure provisioning of the UA Edge Translator with issuer certificates and client certificates.
- Secure Websockets using TLS for secure communication with LoRaWAN Network Server and OCPP Central System.
- Matter Fabric persistency of certificates and keys in the /app/pki folder for secure communication with Matter assets.
- Protocol drivers are loaded as DLLs at runtime and drivers considered insecure can be easily turned off by removing the respective DLL from the "drivers" folder.
Note: If the /app/pki folder is mapped to a folder on the Docker host, make sure to protect this folder since it contains private keys and certificates. For example, you can use BitLocker to encrypt the folder on the Docker host.
- Spoofing: Mitigated by OPC UA username/password authentication and client certificate authentication.
- Tampering: Mitigated by OPC UA message signing and encryption.
- Repudiation: Mitigated by OPC UA message signing and encryption, as well as append-only logging.
- Information Disclosure: Mitigated by OPC UA message encryption.
- Denial of Service: Mitigated by OPC UA secure channels and session management with maximums set for sessions, subscriptions, monitored items and message size limits.
- Elevation of Privilege: Mitigated by OPC UA user authentication as well as a provisioning mode preventing read/write access to variables before GDS Push is carried out.
- Spoofing: Mitigated by TLS client certificate authentication for the LoRaWAN Network and communication for OCPP Central System.
- Tampering: Mitigated by TLS encryption for the LoRaWAN Network and OCPP Central System.
- Repudiation: Mitigated by TLS encryption for the LoRaWAN Network and OCPP Central System, as well as append-only logging.
- Information Disclosure: Mitigated by TLS encryption for the LoRaWAN Network and OCPP Central System.
- Denial of Service: Mitigated by secure Websocket communication and retry/backoff mechanisms in the code.
- Elevation of Privilege: Mitigated by TLS client certificate authentication for the LoRaWAN Network and secure Websocket communication for OCPP.