Connecting OPC UA Publisher to Azure IoT Central with MQTT

Azure IoT Central is an easily scalable application platform as a service (aPaaS) that streamlines the creation and maintenance of IoT devices. It lowers the entry barrier for clients with limited technological knowledge. It also has built-in visualization, which does not require Power BI.

While Azure IoT Central has some distinct features compared to Azure IoT Hub, we recommend reading our IoT Hub demo, as well. IoT Central uses an underlying IoT Hub, and as there is no IoT Central SDK, the user needs to use IoT Hub SDK for programmatic implementations.

OPC UA SDK for Java - Azure IoT Central diagram

Even though IoT Central is not a fully compliant MQTT broker, it can still act as an OPC UA subscriber and receive OPC UA messages. Device-to-cloud messaging works normally, but there are some connection errors occurring when trying to send messages from another MQTT broker to Azure IoT Central i.e. cloud-to-cloud messages.

Azure IoT Central application and device

To start using IoT Central, you need to create an Azure IoT Central application and create a device in the application. We created a blank custom application with a blank device without any device template. The template will be assigned in a later step.

Provisioning the device

Connecting a device to Azure IoT Central requires you to provision the device to IoT Hub using Azure IoT Hub Device Provisioning Service (DPS). This process allocates a position in IoT Hub for the device. Note that the IoT Hub position is not permanent and the provisioning process should be repeated every time device is turned on. After the device owner/user has added the device registration information to the DPS device enrollment list in the Azure IoT Central, the provisioning process can be done on device boot automatically and requires no effort from the user.

In order to connect to the IoT Central with MQTT, we need to generate connection strings <iot_hub_hostname> and <sas_token> using the variables <id_scope>, <device_id>, <symmetric_key> and <model_id>. First three of these variables can be found from the connect page of the previously created IoT Central device. <model_id> is the name of a device template that defines how the device interacts with IoT Central application.

Device connection groups light window

In our demo we used a small Python program and config.json to generate all necessary data to connect the device to IoT Central.

Azure IoT Hub SDKs offers some plug and play -samples that our code was based on. As our device only needs to send temperature value as telemetry. We used simple example thermostat device template example. <port> and <provisioning_host> are normally default values that can not be changed. Default port is 8883 and default provisioning server address is If you can not use the MQTT default port, you will have to use MQTT over Web Sockets protocol and port 443.


	"model_id": "dtmi:com:example:Thermostat;1",
	"port": 8883,
	"provisioning_host": "",
	"id_scope": "xxxx",
	"device_id": "xxxx",
	"symmetric_key": "xxxx"
}' );

IoT Hub hostname

.create_from_symmetric_key() or .create_from_x509_certificate() is a function provided by Azure Python library that is used to provision the IoT Hub for the given device. The only difference between these functions is the authentication method. In our demo we use The Shared Access Signature (SAS) authentication. X509 is commonly used as well but it does not offer all the same functionality as SAS in Azure.

					async def provision_device(provisioning_host, id_scope, registration_id, symmetric_key, model_id):
    provisioning_device_client = ProvisioningDeviceClient.create_from_symmetric_key(

    provisioning_device_client.provisioning_payload = {"modelId": model_id}
    return await provisioning_device_client.register()

hostname = (await provision_device(provisioning_host, id_scope, device_id, symmetric_key, model_id)).registration_state.assigned_hub

SAS token

<sas_token> can be generated with (resource) <uri>, <symmetric_key>, <policy_name> which in our case is None and <expiry> in seconds which is set by default to approximately one month. The resource URI is in format <iot_hub_hostname>/devices/<device_id>. <sas_token> is used as the password for the publisher. The function returns the generated SAS string as: <sas_token>
= "SharedAccessSignature
time in epoch}"
When using the default settings, the only variables we need to provide are <symmetric_key> and <uri>, where: <uri> = "<hostname>/devices/<device_id>"

					def generate_sas_token(uri, symmetric_key, policy_name=None, expiry=2700000):
    ttl = time() + expiry
    sign_key = "%s\n%d" % ((parse.quote_plus(uri)), int(ttl))
    signature = b64encode(HMAC(b64decode(symmetric_key), sign_key.encode('utf-8'), sha256).digest())

    rawtoken = {
        'sr' :  uri,
        'sig': signature,
        'se' : str(int(ttl))
    if policy_name is not None:
        rawtoken['skn'] = policy_name

    return 'SharedAccessSignature ' + parse.urlencode(rawtoken)

sas_token = generate_sas_token(string(hostname+"/devices/"+device_id), symmetric_key)

While the launch command can be typed to console or terminal in a single line as it is, we recommend creating a new .bat or .sh -file to launch the SamplePublisherServer. There might be some issues due to the complexity of the arguments. For example, the argument --password has {space}, & and % -characters included.

Windows Command Prompt (cmd) and .bat-files has some issues that are caused by characters {space} and %. These problems can be fixed by adding " before and after the --username and --password as seen on the code snippet above. The problem with % can be fixed by adding a second % next to it. This issue is caused by the fact that there are two %-characters in a string, which makes the cmd think that the string between these two is a variable, which breaks the argument.

Unix-based systems, for example the Raspberry Pi we used for this demo had some similar issues. Almost all the issues were caused by the {space} in the --password. We had to rewrite part of the with the following lines to call --username and --password from variables.


--username "$USER" --password "$PASS"

--queue-name and --metadata-queue-name arguments define the MQTT topics the publisher sends messages to. We use the same MQTT topics in the demo, but you could define metadata to another MQTT topic or leave it empty to save messages to IoT Central as it is billed per message.

--non-reversible-json is an optional launch commandline argument that removes the network headers that the publisher normally sends. When the argument is given, the publisher sends plain name/value pairs in the following format:


SamplePublisherServer also sends some event and header messages to the metadata queue that the IoT Central does not need in our case.

Data mapping

IoT Central uses JSONPath expression to map the values from telemetry to corresponding aliases. In our demo JSONPath $["MyLevel"]["Value"] has been mapped to variable temperature, which allows the dashboards to search for the corresponding variable from the telemetry messages.


IoT Central uses dashboards to internally visualise the data for quick use. We made a simple dashboard that shows data from the last hour in a linechart and heatmap. Additionally it shows some keyvalues from a longer timespan. The dashboard can be shared to other users of your organization but it can not be published outside.

IoT Central dashboard

Device management

Devices connected to IoT Central can be managed in bulk with Jobs. Jobs include management of device cloud properties, properties, templates or calling command to a device or group of devices. Devices can also be imported or exported in bulk with csv.

Extending IoT Central


Rules are customized response to a trigger on a monitored events in the connected devices. For example, if our thermostat measured temperature over 100°C, we could send email or POST request to other application. There are also advanced rules to expand the triggers further inside and outside of Azure.

Data export

IoT Central is used to connect a large number of devices to Azure. It can be stand-alone but normally its just the first step in Azure IoT System. Data can be exported from IoT Central. For example, we have digital distribution center, which uses different devices and Azure applications that use data exported from devices in IoT Central.


The maximum size of an Azure IoT Central message is 4KB, our average message size was 0.1KB and the service is billed per message. If you would want to reduce cost of running large scale IoT Central application, you could send messages without headers and compress key/value pairs list to single JSON array and send it as a single message. For example, if you would have 300 different sensors in a single automation system, you could first locally gather all the data to single “Azure IoT device” and send it in compressed format:

	"Values": {
		"T0": 2,
		"T1": 20,
		"T2": 10,
		"T300": 1
	"SourceTimestamp": "2022-07-07T09:17:26.890Z"

This is just a sample message, whereas a real message could also include some header data. The size of this single message is still only around 4KB. This compressed format would need complex Azure IoT Central model template for all the data mapping, but it reduces the price per message cost to 1/300 of the original proposed message style. Using float values with two decimals would still allow use of around 150-200 variables.


We have shown that our Prosys OPC UA SDK for Java is capable of publishing OPC UA data directly to Azure IoT Central. Our implementation demo only uses one device but the IoT Central can easily be up-scaled to support multitude of devices.

More Information and Testing

If you are interested in developing your own Azure or other cloud-based systems with OPC UA connectivity, don’t hesitate to contact us by email at or through our website contact form. We will provide you with software tools and professional services that enable fast development in this rapidly growing new market.

A generic vector graphic of an author avatar

Elias Nykänen

Software Engineer


Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top