Star

ThingsBoard Documentation

Documentation for using ThingsBoard IoT Platform.

Provisioning devices

Use Case description

As a device manufacturer or firmware developer, I would like my devices to automatically provision themselves in ThingsBoard. During the automatic provisioning, the device may either generate unique credentials or ask the server to provide unique credentials for the device.

How it works?

The device may send a device provisioning request (Request) to the ThingsBoard. The Request should always contain a Provision key and secret. The Request may optionally include the device name and credentials generated by the device. If those credentials are absent, the Server will generate an Access Token to be used by the device.

Provisioning request example:

{
  "deviceName": "DEVICE_NAME",
  "provisionDeviceKey": "YOUR_PROVISION_KEY_HERE",
  "provisionDeviceSecret": "YOUR_PROVISION_SECRET_HERE"
}

The ThingsBoard validates the Request and replies with the device provisioning response (Response). The successful response contains device id, credentials type, and body. If the validation was not successful, the Response will contain only the status.

Provisioning response example:

{
  "provisionDeviceStatus":"SUCCESS",  
  "credentialsType":"ACCESS_TOKEN",
  "accessToken":"sLzc0gDAZPkGMzFVTyUY"
}

During the validation of the Request, ThingsBoard will first check the supplied provisionDeviceKey and provisionDeviceSecret to find the corresponding Device Profile. Once the profile is found, the platform will use the configured provision strategy to validate the device name. There are two provision strategies available:

Once the provisioning is complete, ThingsBoard will update provisionState server attribute of the device and will set it to provisioned value.

Device profile configuration

You should configure the device profile to enable provisioning feature, collect provision device key and provision device secret.

Provision Device APIs

MQTT Device APIs

You can use the MQTT API reference to develop your device firmware that will perform the provision request.
As mentioned earlier, a device can request the server to generate the credentials or to provide its own credentials during the registration process. See request/response and code examples for each option below:

Parameter Example value Description
deviceName DEVICE_NAME Device name in ThingsBoard.
provisionDeviceKey PUT_PROVISION_KEY_HERE Provisioning device key, you should take it from configured device profile.
provisionDeviceSecret PUT_PROVISION_SECRET_HERE Provisioning device secret, you should take it from configured device profile.

Provisioning request data example:

{
  "deviceName": "DEVICE_NAME",
  "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE"
}

Provisioning response example:

{
  "status":"SUCCESS",
  "credentialsType":"ACCESS_TOKEN",
  "credentialsValue":"sLzc0gDAZPkGMzFVTyUY"
}

Sample script

To communicate with ThingsBoard we will use Paho MQTT module, so we should install it:

pip3 install paho-mqtt --user

The script source code is available below. You may copy-paste it to a file, for example:

device-provision-example.py

Now you should run the script and follow the steps inside.
You may launch the script using python 3:

python3 device-provision-example.py

The script source code:


from paho.mqtt.client import Client
from json import dumps, loads

RESULT_CODES = {
    1: "incorrect protocol version",
    2: "invalid client identifier",
    3: "server unavailable",
    4: "bad username or password",
    5: "not authorised",
    }



def collect_required_data():
    config = {}
    print("\n\n", "="*80, sep="")
    print(" "*10, "\033[1m\033[94mThingsBoard device provisioning with basic authorization example script.\033[0m", sep="")
    print("="*80, "\n\n", sep="")
    host = input("Please write your ThingsBoard \033[93mhost\033[0m or leave it blank to use default (thingsboard.cloud): ")
    config["host"] = host if host else "thingsboard.cloud"
    port = input("Please write your ThingsBoard \033[93mport\033[0m or leave it blank to use default (1883): ")
    config["port"] = int(port) if port else 1883
    config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
    config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
    device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
    if device_name:
        config["device_name"] = device_name
    print("\n", "="*80, "\n", sep="")
    return config


class ProvisionClient(Client):
    PROVISION_REQUEST_TOPIC = "/provision/request"
    PROVISION_RESPONSE_TOPIC = "/provision/response"

    def __init__(self, host, port, provision_request):
        super().__init__()
        self._host = host
        self._port = port
        self._username = "provision"
        self.on_connect = self.__on_connect
        self.on_message = self.__on_message
        self.__provision_request = provision_request

    def __on_connect(self, client, userdata, flags, rc):  # Callback for connect
        if rc == 0:
            print("[Provisioning client] Connected to ThingsBoard ")
            client.subscribe(self.PROVISION_RESPONSE_TOPIC)  # Subscribe to provisioning response topic
            provision_request = dumps(self.__provision_request)
            print("[Provisioning client] Sending provisioning request %s" % provision_request)
            client.publish(self.PROVISION_REQUEST_TOPIC, provision_request)  # Publishing provisioning request topic
        else:
            print("[Provisioning client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])

    def __on_message(self, client, userdata, msg):
        decoded_payload = msg.payload.decode("UTF-8")
        print("[Provisioning client] Received data from ThingsBoard: %s" % decoded_payload)
        decoded_message = loads(decoded_payload)
        provision_device_status = decoded_message.get("status")
        if provision_device_status == "SUCCESS":
            self.__save_credentials(decoded_message["credentialsValue"])
        else:
            print("[Provisioning client] Provisioning was unsuccessful with status %s and message: %s" % (provision_device_status, decoded_message["errorMsg"]))
        self.disconnect()

    def provision(self):
        print("[Provisioning client] Connecting to ThingsBoard (provisioning client)")
        self.__clean_credentials()
        self.connect(self._host, self._port, 60)
        self.loop_forever()

    def get_new_client(self):
        client_credentials = self.__get_credentials()
        new_client = None
        if client_credentials:
            new_client = Client()
            new_client.username_pw_set(client_credentials)
            print("[Provisioning client] Read credentials from file.")
        else:
            print("[Provisioning client] Cannot read credentials from file!")
        return new_client

    @staticmethod
    def __get_credentials():
        new_credentials = None
        try:
            with open("credentials", "r") as credentials_file:
                new_credentials = credentials_file.read()
        except Exception as e:
            print(e)
        return new_credentials

    @staticmethod
    def __save_credentials(credentials):
        with open("credentials", "w") as credentials_file:
            credentials_file.write(credentials)

    @staticmethod
    def __clean_credentials():
        open("credentials", "w").close()


def on_tb_connected(client, userdata, flags, rc):  # Callback for connect with received credentials
    if rc == 0:
        print("[ThingsBoard client] Connected to ThingsBoard with credentials: %s" % client._username.decode())
    else:
        print("[ThingsBoard client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])


if __name__ == '__main__':

    config = collect_required_data()

    THINGSBOARD_HOST = config["host"]  # ThingsBoard instance host
    THINGSBOARD_PORT = config["port"]  # ThingsBoard instance MQTT port

    PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"],  # Provision device key, replace this value with your value from device profile.
                         "provisionDeviceSecret": config["provision_device_secret"],  # Provision device secret, replace this value with your value from device profile.
                         }
    if config.get("device_name") is not None:
        PROVISION_REQUEST["deviceName"] = config["device_name"]
    provision_client = ProvisionClient(THINGSBOARD_HOST, THINGSBOARD_PORT, PROVISION_REQUEST)
    provision_client.provision()  # Request provisioned data
    tb_client = provision_client.get_new_client()  # Getting client with provisioned data
    if tb_client:
        tb_client.on_connect = on_tb_connected  # Setting callback for connect
        tb_client.connect(THINGSBOARD_HOST, THINGSBOARD_PORT, 60)
        tb_client.loop_forever()  # Starting infinity loop
    else:
        print("Client was not created!")

Parameter Example value Description
deviceName DEVICE_NAME Device name in ThingsBoard.
provisionDeviceKey PUT_PROVISION_KEY_HERE Provisioning device key, you should take it from configured device profile.
provisionDeviceSecret PUT_PROVISION_SECRET_HERE Provisioning device secret, you should take it from configured device profile.
credentialsType ACCESS_TOKEN Credentials type parameter.
token DEVICE_ACCESS_TOKEN Access token for device in ThingsBoard.

Provisioning request data example:

{
  "deviceName": "DEVICE_NAME",
  "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE",
  "credentialsType": "ACCESS_TOKEN",
  "token": "DEVICE_ACCESS_TOKEN"
}

Provisioning response example:

{
  "credentialsType":"ACCESS_TOKEN",
  "credentialsValue":"DEVICE_ACCESS_TOKEN",
  "status":"SUCCESS"
}

Sample script

To communicate with ThingsBoard we will use Paho MQTT module, so we should install it:

pip3 install paho-mqtt --user

The script source code is available below. You may copy-paste it to a file, for example:

device-provision-example.py

Now you should run the script and follow the steps inside.
You may launch the script using python 3:

python3 device-provision-example.py

The script source code:


from paho.mqtt.client import Client
from json import dumps, loads

RESULT_CODES = {
    1: "incorrect protocol version",
    2: "invalid client identifier",
    3: "server unavailable",
    4: "bad username or password",
    5: "not authorised",
    }



def collect_required_data():
    config = {}
    print("\n\n", "="*80, sep="")
    print(" "*10, "\033[1m\033[94mThingsBoard device provisioning with access token authorization example script. MQTT API\033[0m", sep="")
    print("="*80, "\n\n", sep="")
    host = input("Please write your ThingsBoard \033[93mhost\033[0m or leave it blank to use default (thingsboard.cloud): ")
    config["host"] = host if host else "thingsboard.cloud"
    port = input("Please write your ThingsBoard \033[93mport\033[0m or leave it blank to use default (1883): ")
    config["port"] = int(port) if port else 1883
    config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
    config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
    config["token"] = input("Please write \033[93mdevice access token\033[0m: ")
    device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
    if device_name:
        config["device_name"] = device_name
    print("\n", "="*80, "\n", sep="")
    return config


class ProvisionClient(Client):
    PROVISION_REQUEST_TOPIC = "/provision/request"
    PROVISION_RESPONSE_TOPIC = "/provision/response"

    def __init__(self, host, port, provision_request):
        super().__init__()
        self._host = host
        self._port = port
        self._username = "provision"
        self.on_connect = self.__on_connect
        self.on_message = self.__on_message
        self.__provision_request = provision_request

    def __on_connect(self, client, userdata, flags, rc):  # Callback for connect
        if rc == 0:
            print("[Provisioning client] Connected to ThingsBoard ")
            client.subscribe(self.PROVISION_RESPONSE_TOPIC)  # Subscribe to provisioning response topic
            provision_request = dumps(self.__provision_request)
            print("[Provisioning client] Sending provisioning request %s" % provision_request)
            client.publish(self.PROVISION_REQUEST_TOPIC, provision_request)  # Publishing provisioning request topic
        else:
            print("[Provisioning client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])

    def __on_message(self, client, userdata, msg):
        decoded_payload = msg.payload.decode("UTF-8")
        print("[Provisioning client] Received data from ThingsBoard: %s" % decoded_payload)
        decoded_message = loads(decoded_payload)
        provision_device_status = decoded_message.get("status")
        if provision_device_status == "SUCCESS":
            self.__save_credentials(decoded_message["credentialsValue"])
        else:
            print("[Provisioning client] Provisioning was unsuccessful with status %s and message: %s" % (provision_device_status, decoded_message["errorMsg"]))
        self.disconnect()

    def provision(self):
        print("[Provisioning client] Connecting to ThingsBoard (provisioning client)")
        self.__clean_credentials()
        self.connect(self._host, self._port, 60)
        self.loop_forever()

    def get_new_client(self):
        client_credentials = self.__get_credentials()
        new_client = None
        if client_credentials:
            new_client = Client()
            new_client.username_pw_set(client_credentials)
            print("[Provisioning client] Read credentials from file.")
        else:
            print("[Provisioning client] Cannot read credentials from file!")
        return new_client

    @staticmethod
    def __get_credentials():
        new_credentials = None
        try:
            with open("credentials", "r") as credentials_file:
                new_credentials = credentials_file.read()
        except Exception as e:
            print(e)
        return new_credentials

    @staticmethod
    def __save_credentials(credentials):
        with open("credentials", "w") as credentials_file:
            credentials_file.write(credentials)

    @staticmethod
    def __clean_credentials():
        open("credentials", "w").close()


def on_tb_connected(client, userdata, flags, rc):  # Callback for connect with received credentials
    if rc == 0:
        print("[ThingsBoard client] Connected to ThingsBoard with credentials: %s" % client._username.decode())
    else:
        print("[ThingsBoard client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])


if __name__ == '__main__':

    config = collect_required_data()

    THINGSBOARD_HOST = config["host"]  # ThingsBoard instance host
    THINGSBOARD_PORT = config["port"]  # ThingsBoard instance MQTT port

    PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"],  # Provision device key, replace this value with your value from device profile.
                         "provisionDeviceSecret": config["provision_device_secret"],  # Provision device secret, replace this value with your value from device profile.
                         "credentialsType": "ACCESS_TOKEN",
                         "token": config["token"],
                         }
    if config.get("device_name") is not None:
        PROVISION_REQUEST["deviceName"] = config["device_name"]
    provision_client = ProvisionClient(THINGSBOARD_HOST, THINGSBOARD_PORT, PROVISION_REQUEST)
    provision_client.provision()  # Request provisioned data
    tb_client = provision_client.get_new_client()  # Getting client with provisioned data
    if tb_client:
        tb_client.on_connect = on_tb_connected  # Setting callback for connect
        tb_client.connect(THINGSBOARD_HOST, THINGSBOARD_PORT, 60)
        tb_client.loop_forever()  # Starting infinity loop
    else:
        print("Client was not created!")

Parameter Example value Description
deviceName DEVICE_NAME Device name in ThingsBoard.
provisionDeviceKey PUT_PROVISION_KEY_HERE Provisioning device key, you should take it from configured device profile.
provisionDeviceSecret PUT_PROVISION_SECRET_HERE Provisioning device secret, you should take it from configured device profile.
credentialsType MQTT_BASIC Credentials type parameter.
username DEVICE_USERNAME_HERE Username for device in ThingsBoard.
password DEVICE_PASSWORD_HERE Password for device in ThingsBoard.
clientId DEVICE_CLIENT_ID_HERE Client id for device in ThingsBoard.

Provisioning request data example:

{
  "deviceName": "DEVICE_NAME",
  "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE",
  "credentialsType": "MQTT_BASIC",
  "username": "DEVICE_USERNAME_HERE",
  "password": "DEVICE_PASSWORD_HERE",
  "clientId": "DEVICE_CLIENT_ID_HERE"
}

Provisioning response example:

{
  "credentialsType":"MQTT_BASIC",
  "credentialsValue": {
    "clientId":"DEVICE_CLIENT_ID_HERE",
    "userName":"DEVICE_USERNAME_HERE",
    "password":"DEVICE_PASSWORD_HERE"
    },
  "status":"SUCCESS"
}

Sample script

To communicate with ThingsBoard we will use Paho MQTT module, so we should install it:

pip3 install paho-mqtt --user

The script source code is available below. You may copy-paste it to a file, for example:

device-provision-example.py

Now you should run the script and follow the steps inside.
You may launch the script using python 3:

python3 device-provision-example.py

The script source code:


from paho.mqtt.client import Client
from json import dumps, loads

RESULT_CODES = {
    1: "incorrect protocol version",
    2: "invalid client identifier",
    3: "server unavailable",
    4: "bad username or password",
    5: "not authorised",
    }



def collect_required_data():
    config = {}
    print("\n\n", "="*80, sep="")
    print(" "*10, "\033[1m\033[94mThingsBoard device provisioning with basic authorization example script.\033[0m", sep="")
    print("="*80, "\n\n", sep="")
    host = input("Please write your ThingsBoard \033[93mhost\033[0m or leave it blank to use default (thingsboard.cloud): ")
    config["host"] = host if host else "thingsboard.cloud"
    port = input("Please write your ThingsBoard \033[93mport\033[0m or leave it blank to use default (1883): ")
    config["port"] = int(port) if port else 1883
    config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
    config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
    device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
    if device_name:
        config["device_name"] = device_name
    config["clientId"] = input("Please write \033[93mclient Id\033[0m: ")
    config["username"] = input("Please write \033[93musername\033[0m: ")
    config["password"] = input("Please write \033[93mpassword\033[0m: ")
    print("\n", "="*80, "\n", sep="")
    return config


class ProvisionClient(Client):
    PROVISION_REQUEST_TOPIC = "/provision/request"
    PROVISION_RESPONSE_TOPIC = "/provision/response"

    def __init__(self, host, port, provision_request):
        super().__init__()
        self._host = host
        self._port = port
        self._username = "provision"
        self.on_connect = self.__on_connect
        self.on_message = self.__on_message
        self.__provision_request = provision_request

    def __on_connect(self, client, userdata, flags, rc):  # Callback for connect
        if rc == 0:
            print("[Provisioning client] Connected to ThingsBoard ")
            client.subscribe(self.PROVISION_RESPONSE_TOPIC)  # Subscribe to provisioning response topic
            provision_request = dumps(self.__provision_request)
            print("[Provisioning client] Sending provisioning request %s" % provision_request)
            client.publish(self.PROVISION_REQUEST_TOPIC, provision_request)  # Publishing provisioning request topic
        else:
            print("[Provisioning client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])

    def __on_message(self, client, userdata, msg):
        decoded_payload = msg.payload.decode("UTF-8")
        print("[Provisioning client] Received data from ThingsBoard: %s" % decoded_payload)
        decoded_message = loads(decoded_payload)
        provision_device_status = decoded_message.get("status")
        if provision_device_status == "SUCCESS":
            self.__save_credentials(decoded_message["credentialsValue"])
        else:
            print("[Provisioning client] Provisioning was unsuccessful with status %s and message: %s" % (provision_device_status, decoded_message["errorMsg"]))
        self.disconnect()

    def provision(self):
        print("[Provisioning client] Connecting to ThingsBoard (provisioning client)")
        self.__clean_credentials()
        self.connect(self._host, self._port, 60)
        self.loop_forever()

    def get_new_client(self):
        client_credentials = loads(self.__get_credentials())
        new_client = None
        if client_credentials:
            new_client = Client(client_id=client_credentials["clientId"])  # Setting client id
            new_client.username_pw_set(client_credentials["userName"], client_credentials["password"])  # Setting username and password for ThingsBoard client
            print("[Provisioning client] Read credentials from file.")
        else:
            print("[Provisioning client] Cannot read credentials from file!")
        return new_client

    @staticmethod
    def __get_credentials():
        new_credentials = None
        try:
            with open("credentials", "r") as credentials_file:
                new_credentials = credentials_file.read()
        except Exception as e:
            print(e)
        return new_credentials

    @staticmethod
    def __save_credentials(credentials):
        with open("credentials", "w") as credentials_file:
            credentials_file.write(dumps(credentials))

    @staticmethod
    def __clean_credentials():
        open("credentials", "w").close()


def on_tb_connected(client, userdata, flags, rc):  # Callback for connect with received credentials
    if rc == 0:
        print("[ThingsBoard client] Connected to ThingsBoard with credentials: username: %s, password: %s, client id: %s" % (client._username.decode(), client._password.decode(), client._client_id.decode()))
    else:
        print("[ThingsBoard client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])


if __name__ == '__main__':
    config = collect_required_data()

    THINGSBOARD_HOST = config["host"]  # ThingsBoard instance host
    THINGSBOARD_PORT = config["port"]  # ThingsBoard instance MQTT port

    PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"],
                         # Provision device key, replace this value with your value from device profile.
                         "provisionDeviceSecret": config["provision_device_secret"],
                         # Provision device secret, replace this value with your value from device profile.
                         "credentialsType": "MQTT_BASIC",
                         "username": config["username"],
                         "password": config["password"],
                         "clientId": config["clientId"],
                         }
    if config.get("device_name") is not None:
        PROVISION_REQUEST["deviceName"] = config["device_name"]
    provision_client = ProvisionClient(THINGSBOARD_HOST, THINGSBOARD_PORT, PROVISION_REQUEST)
    provision_client.provision()  # Request provisioned data
    tb_client = provision_client.get_new_client()  # Getting client with provisioned data
    if tb_client:
        tb_client.on_connect = on_tb_connected  # Setting callback for connect
        tb_client.connect(THINGSBOARD_HOST, THINGSBOARD_PORT, 60)
        tb_client.loop_forever()  # Starting infinity loop
    else:
        print("Client was not created!")

INFO: To use this feature, you should configure MQTT over SSL in ThingsBoard

Parameter Example value Description
deviceName DEVICE_NAME Device name in ThingsBoard.
provisionDeviceKey PUT_PROVISION_KEY_HERE Provisioning device key, you should take it from configured device profile.
provisionDeviceSecret PUT_PROVISION_SECRET_HERE Provisioning device secret, you should take it from configured device profile.
credentialsType X509_CERTIFICATE Credentials type parameter.
hash MIIB……..AQAB Public key X509 hash for device in ThingsBoard.

Provisioning request data example:

{
  "deviceName": "DEVICE_NAME",
  "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE",
  "credentialsType": "X509_CERTIFICATE",
  "hash": "MIIB........AQAB"
}

Provisioning response example:

{
  "deviceId":"3b829220-232f-11eb-9d5c-e9ed3235dff8",
  "credentialsType":"X509_CERTIFICATE",
  "credentialsId":"f307a1f717a12b32c27203cf77728d305d29f64694a8311be921070dd1259b3a",
  "credentialsValue":"MIIB........AQAB",
  "provisionDeviceStatus":"SUCCESS"
}

MQTT Example script

To use this script put your mqttserver.pub.pem (public key of the server) into the folder with script.


import ssl
from datetime import datetime, timedelta
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from paho.mqtt.client import Client
from json import dumps, loads

RESULT_CODES = {
    1: "incorrect protocol version",
    2: "invalid client identifier",
    3: "server unavailable",
    4: "bad username or password",
    5: "not authorised",
    }


def collect_required_data():
    config = {}
    print("\n\n", "="*80, sep="")
    print(" "*10, "\033[1m\033[94mThingsBoard device provisioning with X509 certificate authorization example script. MQTT API\033[0m", sep="")
    print("="*80, "\n\n", sep="")
    host = input("Please write your ThingsBoard \033[93mhost\033[0m or leave it blank to use default (thingsboard.cloud): ")
    config["host"] = host if host else "thingsboard.cloud"
    port = input("Please write your ThingsBoard \033[93mSSL port\033[0m or leave it blank to use default (8883): ")
    config["port"] = int(port) if port else 8883
    config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
    config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
    device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
    if device_name:
        config["device_name"] = device_name
    print("\n", "="*80, "\n", sep="")
    return config

def generate_certs(ca_certfile="mqttserver.pub.pem"):
    root_cert = None
    try:
        with open(ca_certfile, "r") as ca_file:
            root_cert = x509.load_pem_x509_certificate(str.encode(ca_file.read()), default_backend())
    except Exception as e:
        print("Failed to load CA certificate: %r" % e)
    if root_cert is not None:
        private_key = rsa.generate_private_key(
            public_exponent=65537, key_size=2048, backend=default_backend()
            )
        new_subject = x509.Name([
        x509.NameAttribute(NameOID.COMMON_NAME, "localhost")
    ])
        certificate = (
            x509.CertificateBuilder()
            .subject_name(new_subject)
            .issuer_name(new_subject)
            .public_key(private_key.public_key())
            .serial_number(x509.random_serial_number())
            .not_valid_before(datetime.utcnow())
            .not_valid_after(datetime.utcnow() + timedelta(days=365*10))
            .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
            .sign(private_key=private_key, algorithm=hashes.SHA256(), backend=default_backend())
        )

        with open("cert.pem", "wb") as cert_file:
            cert_file.write(certificate.public_bytes(encoding=serialization.Encoding.PEM))

        with open("key.pem", "wb") as key_file:
            key_file.write(private_key.private_bytes(encoding=serialization.Encoding.PEM,
                                                     format=serialization.PrivateFormat.TraditionalOpenSSL,
                                                     encryption_algorithm=serialization.NoEncryption(),
                                                ))


def read_cert():
    cert = None
    key = None
    try:
        with open("cert.pem", "r") as cert_file:
            cert = cert_file.read()
        with open("key.pem", "r") as key_file:
            key = key_file.read()
    except Exception as e:
        print("Cannot read certificate with error: %r" % e)
    return cert, key


class ProvisionClient(Client):
    PROVISION_REQUEST_TOPIC = "/provision/request"
    PROVISION_RESPONSE_TOPIC = "/provision/response"

    def __init__(self, host, port, provision_request):
        super().__init__()
        self._host = host
        self._port = port
        self._username = "provision"
        self.tls_set(ca_certs="mqttserver.pub.pem", tls_version=ssl.PROTOCOL_TLSv1_2)
        self.on_connect = self.__on_connect
        self.on_message = self.__on_message
        self.__provision_request = provision_request

    def __on_connect(self, client, userdata, flags, rc):  # Callback for connect
        if rc == 0:
            print("[Provisioning client] Connected to ThingsBoard ")
            client.subscribe(self.PROVISION_RESPONSE_TOPIC)  # Subscribe to provisioning response topic
            provision_request = dumps(self.__provision_request)
            print("[Provisioning client] Sending provisioning request %s" % provision_request)
            client.publish(self.PROVISION_REQUEST_TOPIC, provision_request)  # Publishing provisioning request topic
        else:
            print("[Provisioning client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])

    def __on_message(self, client, userdata, msg):
        decoded_payload = msg.payload.decode("UTF-8")
        print("[Provisioning client] Received data from ThingsBoard: %s" % decoded_payload)
        decoded_message = loads(decoded_payload)
        provision_device_status = decoded_message.get("status")
        if provision_device_status == "SUCCESS":
            if decoded_message["credentialsValue"] == cert.replace("-----BEGIN CERTIFICATE-----\n", "")\
                                                          .replace("-----END CERTIFICATE-----\n", "")\
                                                          .replace("\n", ""):
                print("[Provisioning client] Provisioning success! Certificates are saved.")
                self.__save_credentials(cert)
            else:
                print("[Provisioning client] Returned certificate is not equal to sent one.")
        else:
            print("[Provisioning client] Provisioning was unsuccessful with status %s and message: %s" % (provision_device_status, decoded_message["errorMsg"]))
        self.disconnect()

    def provision(self):
        print("[Provisioning client] Connecting to ThingsBoard (provisioning client)")
        self.__clean_credentials()
        self.connect(self._host, self._port, 60)
        self.loop_forever()

    def get_new_client(self):
        client_credentials = self.__get_credentials()
        new_client = None
        if client_credentials:
            new_client = Client()
            new_client.tls_set(ca_certs="mqttserver.pub.pem", certfile="cert.pem", keyfile="key.pem", cert_reqs=ssl.CERT_REQUIRED,
                               tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)
            new_client.tls_insecure_set(False)
            print("[Provisioning client] Read credentials from file.")
        else:
            print("[Provisioning client] Cannot read credentials from file!")
        return new_client

    @staticmethod
    def __get_credentials():
        new_credentials = None
        try:
            with open("credentials", "r") as credentials_file:
                new_credentials = credentials_file.read()
        except Exception as e:
            print(e)
        return new_credentials

    @staticmethod
    def __save_credentials(credentials):
        with open("credentials", "w") as credentials_file:
            credentials_file.write(credentials)

    @staticmethod
    def __clean_credentials():
        open("credentials", "w").close()


def on_tb_connected(client, userdata, flags, rc):  # Callback for connect with received credentials
    if rc == 0:
        print("[ThingsBoard client] Connected to ThingsBoard with credentials: username: %s, password: %s, client id: %s" % (client._username, client._password, client._client_id))
    else:
        print("[ThingsBoard client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])


if __name__ == '__main__':

    config = collect_required_data()

    THINGSBOARD_HOST = config["host"]  # ThingsBoard instance host
    THINGSBOARD_PORT = config["port"]  # ThingsBoard instance MQTT port

    PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"],  # Provision device key, replace this value with your value from device profile.
                         "provisionDeviceSecret": config["provision_device_secret"],  # Provision device secret, replace this value with your value from device profile.
                         "credentialsType": "X509_CERTIFICATE",
                         }
    if config.get("device_name") is not None:
        PROVISION_REQUEST["deviceName"] = config["device_name"]
    generate_certs()  # Generate certificate and key
    cert, key = read_cert()  # Read certificate and key
    PROVISION_REQUEST["hash"] = cert
    if PROVISION_REQUEST.get("hash") is not None:
        provision_client = ProvisionClient(THINGSBOARD_HOST, THINGSBOARD_PORT, PROVISION_REQUEST)
        provision_client.provision()  # Request provisioned data
        tb_client = provision_client.get_new_client()  # Getting client with provisioned data
        if tb_client:
            tb_client.on_connect = on_tb_connected  # Setting callback for connect
            tb_client.connect(THINGSBOARD_HOST, THINGSBOARD_PORT, 60)
            tb_client.loop_forever()  # Starting infinity loop
        else:
            print("Client was not created!")
    else:
        print("Cannot read certificate.")

HTTP Device APIs

You may use the HTTP API reference to develop your device firmware that will perform the provision request.
As mentioned earlier, the device may request server to generate the credentials or provide its own credentials during the registration process. See request/response and code examples for each option below:

Parameter Example value Description
deviceName DEVICE_NAME Device name in ThingsBoard.
provisionDeviceKey PUT_PROVISION_KEY_HERE Provisioning device key, you should take it from configured device profile.
provisionDeviceSecret PUT_PROVISION_SECRET_HERE Provisioning device secret, you should take it from configured device profile.

Provisioning request data example:

{
  "deviceName": "DEVICE_NAME",
  "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE"
}

Provisioning response example:

{
  "status":"SUCCESS",
  "credentialsType":"ACCESS_TOKEN",
  "credentialsValue":"sLzc0gDAZPkGMzFVTyUY"
}

Sample script

The script source code is available below. You may copy-paste it to a file, for example:

device-provision-example.py

Now you should run the script and follow the steps inside.
You may launch the script using python 3:

python3 device-provision-example.py

The script source code:



from requests import post
from json import dumps


def collect_required_data():
    config = {}
    print("\n\n", "="*80, sep="")
    print(" "*10, "\033[1m\033[94mThingsBoard device provisioning without authorization example script. HTTP API\033[0m", sep="")
    print("="*80, "\n\n", sep="")
    host = input("Please write your ThingsBoard \033[93mhost\033[0m or leave it blank to use default (https://thingsboard.cloud): ")
    config["host"] = host if host else "https://thingsboard.cloud"
    port = input("Please write your ThingsBoard \033[93mHTTP port\033[0m or leave it blank to use default (80): ")
    config["port"] = int(port) if port else 80
    config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
    config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
    device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
    if device_name:
        config["device_name"] = device_name
    print("\n", "="*80, "\n", sep="")
    return config


# Example for message to ThingsBoard
to_publish = {
  "stringKey": "value1",
  "booleanKey": True,
  "doubleKey": 42.0,
  "longKey": 73,
  "jsonKey": {
    "someNumber": 42,
    "someArray": [1, 2, 3],
    "someNestedObject": {"key": "value"}
  }
}

if __name__ == '__main__':

    config = collect_required_data()

    THINGSBOARD_HOST = config["host"]  # ThingsBoard instance host
    THINGSBOARD_PORT = config["port"]  # ThingsBoard instance MQTT port

    PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"],  # Provision device key, replace this value with your value from device profile.
                         "provisionDeviceSecret": config["provision_device_secret"],  # Provision device secret, replace this value with your value from device profile.
                         }
    if config.get("device_name") is not None:
        PROVISION_REQUEST["deviceName"] = config["device_name"]
    response = post("%s:%i/api/v1/provision" % (THINGSBOARD_HOST, THINGSBOARD_PORT), json=PROVISION_REQUEST)
    decoded_response = response.json()
    print("Received response: ")
    print(decoded_response)
    received_token = decoded_response.get("credentialsValue")
    if received_token is not None:
        response = post('%s:%i/api/v1/%s/telemetry' % (THINGSBOARD_HOST, THINGSBOARD_PORT, received_token,), dumps(to_publish))
        print("[THINGSBOARD CLIENT] Response code from Thingsboard.")
        print(response.status_code)
    else:
        print("Failed to get access token from response.")
        print(decoded_response.get("errorMsg"))


Parameter Example value Description
deviceName DEVICE_NAME Device name in ThingsBoard.
provisionDeviceKey PUT_PROVISION_KEY_HERE Provisioning device key, you should take it from configured device profile.
provisionDeviceSecret PUT_PROVISION_SECRET_HERE Provisioning device secret, you should take it from configured device profile.
credentialsType ACCESS_TOKEN Credentials type parameter.
token DEVICE_ACCESS_TOKEN Access token for device in ThingsBoard.

Provisioning request data example:

{
  "deviceName": "DEVICE_NAME",
  "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE",
  "credentialsType": "ACCESS_TOKEN",
  "token": "DEVICE_ACCESS_TOKEN"
}

Provisioning response example:

{
  "credentialsType":"ACCESS_TOKEN",
  "credentialsValue":"DEVICE_ACCESS_TOKEN",
  "status":"SUCCESS"
}

Sample script

The script source code is available below. You may copy-paste it to a file, for example:

device-provision-example.py

Now you should run the script and follow the steps inside.
You may launch the script using python 3:

python3 device-provision-example.py

The script source code:



from requests import post
from json import dumps


def collect_required_data():
    config = {}
    print("\n\n", "="*80, sep="")
    print(" "*10, "\033[1m\033[94mThingsBoard device provisioning with access token authorization example script. HTTP API\033[0m", sep="")
    print("="*80, "\n\n", sep="")
    host = input("Please write your ThingsBoard \033[93murl\033[0m or leave it blank to use default (https://thingsboard.cloud): ")
    config["host"] = host if host else "https://thingsboard.cloud"
    port = input("Please write your ThingsBoard \033[93mHTTP port\033[0m or leave it blank to use default (80): ")
    config["port"] = int(port) if port else 80
    config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
    config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
    config["token"] = input("Please write \033[93mdevice access token\033[0m: ")
    device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
    if device_name:
        config["device_name"] = device_name
    print("\n", "="*80, "\n", sep="")
    return config


# Example for message to ThingsBoard
to_publish = {
  "stringKey": "value1",
  "booleanKey": True,
  "doubleKey": 42.0,
  "longKey": 73,
  "jsonKey": {
    "someNumber": 42,
    "someArray": [1, 2, 3],
    "someNestedObject": {"key": "value"}
  }
}

if __name__ == '__main__':

    config = collect_required_data()

    THINGSBOARD_HOST = config["host"]  # ThingsBoard instance host
    THINGSBOARD_PORT = config["port"]  # ThingsBoard instance MQTT port

    PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"],  # Provision device key, replace this value with your value from device profile.
                         "provisionDeviceSecret": config["provision_device_secret"],  # Provision device secret, replace this value with your value from device profile.
                         "credentialsType": "ACCESS_TOKEN",
                         "token": config["token"],
                         }
    if config.get("device_name") is not None:
        PROVISION_REQUEST["deviceName"] = config["device_name"]
    response = post("%s:%i/api/v1/provision" % (THINGSBOARD_HOST, THINGSBOARD_PORT), json=PROVISION_REQUEST)
    decoded_response = response.json()
    print("Received response: ")
    print(decoded_response)
    received_token = decoded_response.get("credentialsValue")
    if received_token is not None:
        response = post('%s:%i/api/v1/%s/telemetry' % (THINGSBOARD_HOST, THINGSBOARD_PORT, received_token,), dumps(to_publish))
        print("[THINGSBOARD CLIENT] Response code from Thingsboard.")
        print(response.status_code)
    else:
        print("Failed to get access token from response.")
        print(decoded_response.get("errorMsg"))


CoAP Device APIs

You may use the CoAP API reference to develop your device firmware that will perform the provision request.
As mentioned earlier, the device may request server to generate the credentials or provide its own credentials during the registration process. See request/response and code examples for each option below:

Parameter Example value Description
deviceName DEVICE_NAME Device name in ThingsBoard.
provisionDeviceKey PUT_PROVISION_KEY_HERE Provisioning device key, you should take it from configured device profile.
provisionDeviceSecret PUT_PROVISION_SECRET_HERE Provisioning device secret, you should take it from configured device profile.

Provisioning request data example:

{
  "deviceName": "DEVICE_NAME",
  "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE"
}

Provisioning response example:

{
  "status":"SUCCESS",
  "credentialsType":"ACCESS_TOKEN",
  "credentialsValue":"sLzc0gDAZPkGMzFVTyUY"
}

Sample script

To communicate with ThingsBoard we will use CoAPthon3 module, so we should install it:

pip3 install coapthon3 --user

The script source code is available below. You may copy-paste it to a file, for example:

device-provision-example.py

Now you should run the script and follow the steps inside.
You may launch the script using python 3:

python3 device-provision-example.py

The script source code:


from coapthon.client.helperclient import HelperClient
from json import loads, dumps


def collect_required_data():
    config = {}
    print("\n\n", "="*80, sep="")
    print(" "*10, "\033[1m\033[94mThingsBoard device provisioning without authorization example script. CoAP API\033[0m", sep="")
    print("="*80, "\n\n", sep="")
    host = input("Please write your ThingsBoard \033[93mhost\033[0m or leave it blank to use default (thingsboard.cloud): ")
    config["host"] = host if host else "thingsboard.cloud"
    port = input("Please write your ThingsBoard \033[93mCoAP port\033[0m or leave it blank to use default (5683): ")
    config["port"] = int(port) if port else 5683
    config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
    config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
    device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
    if device_name:
        config["device_name"] = device_name
    print("\n", "="*80, "\n", sep="")
    return config


# Example for message to ThingsBoard
to_publish = {
  "stringKey": "value1",
  "booleanKey": True,
  "doubleKey": 42.0,
  "longKey": 73,
  "jsonKey": {
    "someNumber": 42,
    "someArray": [1, 2, 3],
    "someNestedObject": {"key": "value"}
  }
}

if __name__ == '__main__':

    config = collect_required_data()

    THINGSBOARD_HOST = config["host"]  # ThingsBoard instance host
    THINGSBOARD_PORT = config["port"]  # ThingsBoard instance MQTT port

    PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"],  # Provision device key, replace this value with your value from device profile.
                         "provisionDeviceSecret": config["provision_device_secret"],  # Provision device secret, replace this value with your value from device profile.
                         }
    if config.get("device_name") is not None:
        PROVISION_REQUEST["deviceName"] = config["device_name"]
    client = HelperClient(server=(THINGSBOARD_HOST, THINGSBOARD_PORT))
    response = client.post('/api/v1/provision', dumps(PROVISION_REQUEST))
    client.stop()
    decoded_response = loads(response.payload)
    print("Received response: ")
    print(decoded_response)
    received_token = decoded_response.get("credentialsValue")
    if received_token is not None:
        thingsboardClient = HelperClient(server=(THINGSBOARD_HOST, THINGSBOARD_PORT))
        response = thingsboardClient.post('/api/v1/%s/telemetry' % (received_token,), dumps(to_publish))
        print("[THINGSBOARD CLIENT] Response from Thingsboard.")
        print(response)
        thingsboardClient.stop()
    else:
        print("Failed to get access token from response.")
        print(decoded_response.get("errorMsg"))

Parameter Example value Description
deviceName DEVICE_NAME Device name in ThingsBoard.
provisionDeviceKey PUT_PROVISION_KEY_HERE Provisioning device key, you should take it from configured device profile.
provisionDeviceSecret PUT_PROVISION_SECRET_HERE Provisioning device secret, you should take it from configured device profile.
credentialsType ACCESS_TOKEN Credentials type parameter.
token DEVICE_ACCESS_TOKEN Access token for device in ThingsBoard.

Provisioning request data example:

{
  "deviceName": "DEVICE_NAME",
  "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE",
  "credentialsType": "ACCESS_TOKEN",
  "token": "DEVICE_ACCESS_TOKEN"
}

Provisioning response example:

{
  "credentialsType":"ACCESS_TOKEN",
  "credentialsValue":"DEVICE_ACCESS_TOKEN",
  "status":"SUCCESS"
}

Sample script

To communicate with ThingsBoard we will use CoAPthon3 module, so we should install it:

pip3 install coapthon3 --user

The script source code is available below. You may copy-paste it to a file, for example:

device-provision-example.py

Now you should run the script and follow the steps inside.
You may launch the script using python 3:

python3 device-provision-example.py

The script source code:


from coapthon.client.helperclient import HelperClient
from json import loads, dumps


def collect_required_data():
    config = {}
    print("\n\n", "="*80, sep="")
    print(" "*10, "\033[1m\033[94mThingsBoard device provisioning with access token authorization example script. CoAP API\033[0m", sep="")
    print("="*80, "\n\n", sep="")
    host = input("Please write your ThingsBoard \033[93mhost\033[0m or leave it blank to use default (thingsboard.cloud): ")
    config["host"] = host if host else "thingsboard.cloud"
    port = input("Please write your ThingsBoard \033[93mCoAP port\033[0m or leave it blank to use default (5683): ")
    config["port"] = int(port) if port else 5683
    config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
    config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
    config["token"] = input("Please write \033[93mdevice access token\033[0m: ")
    device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
    if device_name:
        config["device_name"] = device_name
    print("\n", "="*80, "\n", sep="")
    return config


# Example for message to ThingsBoard
to_publish = {
  "stringKey": "value1",
  "booleanKey": True,
  "doubleKey": 42.0,
  "longKey": 73,
  "jsonKey": {
    "someNumber": 42,
    "someArray": [1, 2, 3],
    "someNestedObject": {"key": "value"}
  }
}

if __name__ == '__main__':

    config = collect_required_data()

    THINGSBOARD_HOST = config["host"]  # ThingsBoard instance host
    THINGSBOARD_PORT = config["port"]  # ThingsBoard instance MQTT port

    PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"],  # Provision device key, replace this value with your value from device profile.
                         "provisionDeviceSecret": config["provision_device_secret"],  # Provision device secret, replace this value with your value from device profile.
                         "credentialsType": "ACCESS_TOKEN",
                         "token": config["token"],
                         }
    if config.get("device_name") is not None:
        PROVISION_REQUEST["deviceName"] = config["device_name"]
    client = HelperClient(server=(THINGSBOARD_HOST, THINGSBOARD_PORT))
    response = client.post('/api/v1/provision', dumps(PROVISION_REQUEST))
    client.stop()
    decoded_response = loads(response.payload)
    print("Received response: ")
    print(decoded_response)
    received_token = decoded_response.get("credentialsValue")
    if received_token is not None:
        thingsboardClient = HelperClient(server=(THINGSBOARD_HOST, THINGSBOARD_PORT))
        response = thingsboardClient.post('/api/v1/%s/telemetry' % (received_token,), dumps(to_publish))
        print("[THINGSBOARD CLIENT] Response from Thingsboard.")
        print(response)
        thingsboardClient.stop()
    else:
        print("Failed to get access token from response.")
        print(decoded_response.get("errorMsg"))

Next steps