你可以依据 MQTT API参考文档 来开发设备固件中用于执行配置请求的功能。
如前所述,设备可以在注册过程中由服务器生成访问凭据或提供其自己的访问凭据。请参阅下面每个请求/响应和示例代码:

由服务端创建访问凭据

Parameter Example value 描述
deviceName DEVICE_NAME ThingsBoard中的设备名称。
provisionDeviceKey PUT_PROVISION_KEY_HERE 设备批量化的键, 从已配置的设备配置文件中获取。
provisionDeviceSecret PUT_PROVISION_SECRET_HERE 设备批量化的密钥的值,从已配置的设备配置文件中获取。

请求参数示例:

  1. {
  2. "deviceName": "DEVICE_NAME",
  3. "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  4. "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE"
  5. }

响应示例:

  1. {
  2. "status":"SUCCESS",
  3. "credentialsType":"ACCESS_TOKEN",
  4. "credentialsValue":"sLzc0gDAZPkGMzFVTyUY"
  5. }

示例脚本

为了与 ThingsBoard 通信,我们将使用 Paho MQTT 模块,因此我们应该安装它:

  1. pip3 install paho-mqtt --user

下面提供了脚本源代码。您可以将其复制粘贴到文件中,例如:

  1. device-provision-example.py

现在,按照以下步骤运行脚本。
您可以使用 python 3 启动脚本:

  1. python3 device-provision-example.py

脚本源代码:

  1. from paho.mqtt.client import Client
  2. from json import dumps, loads
  3. RESULT_CODES = {
  4. 1: "incorrect protocol version",
  5. 2: "invalid client identifier",
  6. 3: "server unavailable",
  7. 4: "bad username or password",
  8. 5: "not authorised",
  9. }
  10. def collect_required_data():
  11. config = {}
  12. print("\n\n", "="*80, sep="")
  13. print(" "*10, "\033[1m\033[94mThingsBoard device provisioning with basic authorization example script.\033[0m", sep="")
  14. print("="*80, "\n\n", sep="")
  15. host = input("Please write your ThingsBoard \033[93mhost\033[0m or leave it blank to use default (cloud.thingsboard.io): ")
  16. config["host"] = host if host else "cloud.thingsboard.io"
  17. port = input("Please write your ThingsBoard \033[93mport\033[0m or leave it blank to use default (1883): ")
  18. config["port"] = int(port) if port else 1883
  19. config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
  20. config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
  21. device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
  22. if device_name:
  23. config["device_name"] = device_name
  24. print("\n", "="*80, "\n", sep="")
  25. return config
  26. class ProvisionClient(Client):
  27. PROVISION_REQUEST_TOPIC = "/provision/request"
  28. PROVISION_RESPONSE_TOPIC = "/provision/response"
  29. def __init__(self, host, port, provision_request):
  30. super().__init__()
  31. self._host = host
  32. self._port = port
  33. self._username = "provision"
  34. self.on_connect = self.__on_connect
  35. self.on_message = self.__on_message
  36. self.__provision_request = provision_request
  37. def __on_connect(self, client, userdata, flags, rc): # Callback for connect
  38. if rc == 0:
  39. print("[Provisioning client] Connected to ThingsBoard ")
  40. client.subscribe(self.PROVISION_RESPONSE_TOPIC) # Subscribe to provisioning response topic
  41. provision_request = dumps(self.__provision_request)
  42. print("[Provisioning client] Sending provisioning request %s" % provision_request)
  43. client.publish(self.PROVISION_REQUEST_TOPIC, provision_request) # Publishing provisioning request topic
  44. else:
  45. print("[Provisioning client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])
  46. def __on_message(self, client, userdata, msg):
  47. decoded_payload = msg.payload.decode("UTF-8")
  48. print("[Provisioning client] Received data from ThingsBoard: %s" % decoded_payload)
  49. decoded_message = loads(decoded_payload)
  50. provision_device_status = decoded_message.get("status")
  51. if provision_device_status == "SUCCESS":
  52. self.__save_credentials(decoded_message["credentialsValue"])
  53. else:
  54. print("[Provisioning client] Provisioning was unsuccessful with status %s and message: %s" % (provision_device_status, decoded_message["errorMsg"]))
  55. self.disconnect()
  56. def provision(self):
  57. print("[Provisioning client] Connecting to ThingsBoard (provisioning client)")
  58. self.__clean_credentials()
  59. self.connect(self._host, self._port, 60)
  60. self.loop_forever()
  61. def get_new_client(self):
  62. client_credentials = self.__get_credentials()
  63. new_client = None
  64. if client_credentials:
  65. new_client = Client()
  66. new_client.username_pw_set(client_credentials)
  67. print("[Provisioning client] Read credentials from file.")
  68. else:
  69. print("[Provisioning client] Cannot read credentials from file!")
  70. return new_client
  71. @staticmethod
  72. def __get_credentials():
  73. new_credentials = None
  74. try:
  75. with open("credentials", "r") as credentials_file:
  76. new_credentials = credentials_file.read()
  77. except Exception as e:
  78. print(e)
  79. return new_credentials
  80. @staticmethod
  81. def __save_credentials(credentials):
  82. with open("credentials", "w") as credentials_file:
  83. credentials_file.write(credentials)
  84. @staticmethod
  85. def __clean_credentials():
  86. open("credentials", "w").close()
  87. def on_tb_connected(client, userdata, flags, rc): # Callback for connect with received credentials
  88. if rc == 0:
  89. print("[ThingsBoard client] Connected to ThingsBoard with credentials: %s" % client._username.decode())
  90. else:
  91. print("[ThingsBoard client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])
  92. if __name__ == '__main__':
  93. config = collect_required_data()
  94. THINGSBOARD_HOST = config["host"] # ThingsBoard instance host
  95. THINGSBOARD_PORT = config["port"] # ThingsBoard instance MQTT port
  96. PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"], # Provision device key, replace this value with your value from device profile.
  97. "provisionDeviceSecret": config["provision_device_secret"], # Provision device secret, replace this value with your value from device profile.
  98. }
  99. if config.get("device_name") is not None:
  100. PROVISION_REQUEST["deviceName"] = config["device_name"]
  101. provision_client = ProvisionClient(THINGSBOARD_HOST, THINGSBOARD_PORT, PROVISION_REQUEST)
  102. provision_client.provision() # Request provisioned data
  103. tb_client = provision_client.get_new_client() # Getting client with provisioned data
  104. if tb_client:
  105. tb_client.on_connect = on_tb_connected # Setting callback for connect
  106. tb_client.connect(THINGSBOARD_HOST, THINGSBOARD_PORT, 60)
  107. tb_client.loop_forever() # Starting infinity loop
  108. else:
  109. print("Client was not created!")

由设备提供访问令牌

Parameter Example value Description
deviceName DEVICE_NAME Thingsboard中的设备名称。
provisionDeviceKey PUT_PROVISION_KEY_HERE 设备批量化的键, 从已配置的设备配置文件中获取。
provisionDeviceSecret PUT_PROVISION_SECRET_HERE 设备批量化的密钥,从已配置的设备配置文件中获取。
credentialsType ACCESS_TOKEN 凭证类型参数。
token DEVICE_ACCESS_TOKEN 设备访问 Thingsboard 的令牌

请求参数示例:

  1. {
  2. "deviceName": "DEVICE_NAME",
  3. "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  4. "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE",
  5. "credentialsType": "ACCESS_TOKEN",
  6. "token": "DEVICE_ACCESS_TOKEN"
  7. }

响应示例:

  1. {
  2. "credentialsType":"ACCESS_TOKEN",
  3. "credentialsValue":"DEVICE_ACCESS_TOKEN",
  4. "status":"SUCCESS"
  5. }

示例脚本

为了与 ThingsBoard 通信,我们将使用 Paho MQTT 模块,因此我们应该安装它:

  1. pip3 install paho-mqtt --user

下面提供了脚本源代码。您可以将其复制粘贴到文件中,例如:

  1. device-provision-example.py

现在,按照以下步骤运行脚本。
您可以使用 python 3 启动脚本:

  1. python3 device-provision-example.py

脚本源代码:

  1. from paho.mqtt.client import Client
  2. from json import dumps, loads
  3. RESULT_CODES = {
  4. 1: "incorrect protocol version",
  5. 2: "invalid client identifier",
  6. 3: "server unavailable",
  7. 4: "bad username or password",
  8. 5: "not authorised",
  9. }
  10. def collect_required_data():
  11. config = {}
  12. print("\n\n", "="*80, sep="")
  13. print(" "*10, "\033[1m\033[94mThingsBoard device provisioning with access token authorization example script. MQTT API\033[0m", sep="")
  14. print("="*80, "\n\n", sep="")
  15. host = input("Please write your ThingsBoard \033[93mhost\033[0m or leave it blank to use default (cloud.thingsboard.io): ")
  16. config["host"] = host if host else "cloud.thingsboard.io"
  17. port = input("Please write your ThingsBoard \033[93mport\033[0m or leave it blank to use default (1883): ")
  18. config["port"] = int(port) if port else 1883
  19. config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
  20. config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
  21. config["token"] = input("Please write \033[93mdevice access token\033[0m: ")
  22. device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
  23. if device_name:
  24. config["device_name"] = device_name
  25. print("\n", "="*80, "\n", sep="")
  26. return config
  27. class ProvisionClient(Client):
  28. PROVISION_REQUEST_TOPIC = "/provision/request"
  29. PROVISION_RESPONSE_TOPIC = "/provision/response"
  30. def __init__(self, host, port, provision_request):
  31. super().__init__()
  32. self._host = host
  33. self._port = port
  34. self._username = "provision"
  35. self.on_connect = self.__on_connect
  36. self.on_message = self.__on_message
  37. self.__provision_request = provision_request
  38. def __on_connect(self, client, userdata, flags, rc): # Callback for connect
  39. if rc == 0:
  40. print("[Provisioning client] Connected to ThingsBoard ")
  41. client.subscribe(self.PROVISION_RESPONSE_TOPIC) # Subscribe to provisioning response topic
  42. provision_request = dumps(self.__provision_request)
  43. print("[Provisioning client] Sending provisioning request %s" % provision_request)
  44. client.publish(self.PROVISION_REQUEST_TOPIC, provision_request) # Publishing provisioning request topic
  45. else:
  46. print("[Provisioning client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])
  47. def __on_message(self, client, userdata, msg):
  48. decoded_payload = msg.payload.decode("UTF-8")
  49. print("[Provisioning client] Received data from ThingsBoard: %s" % decoded_payload)
  50. decoded_message = loads(decoded_payload)
  51. provision_device_status = decoded_message.get("status")
  52. if provision_device_status == "SUCCESS":
  53. self.__save_credentials(decoded_message["credentialsValue"])
  54. else:
  55. print("[Provisioning client] Provisioning was unsuccessful with status %s and message: %s" % (provision_device_status, decoded_message["errorMsg"]))
  56. self.disconnect()
  57. def provision(self):
  58. print("[Provisioning client] Connecting to ThingsBoard (provisioning client)")
  59. self.__clean_credentials()
  60. self.connect(self._host, self._port, 60)
  61. self.loop_forever()
  62. def get_new_client(self):
  63. client_credentials = self.__get_credentials()
  64. new_client = None
  65. if client_credentials:
  66. new_client = Client()
  67. new_client.username_pw_set(client_credentials)
  68. print("[Provisioning client] Read credentials from file.")
  69. else:
  70. print("[Provisioning client] Cannot read credentials from file!")
  71. return new_client
  72. @staticmethod
  73. def __get_credentials():
  74. new_credentials = None
  75. try:
  76. with open("credentials", "r") as credentials_file:
  77. new_credentials = credentials_file.read()
  78. except Exception as e:
  79. print(e)
  80. return new_credentials
  81. @staticmethod
  82. def __save_credentials(credentials):
  83. with open("credentials", "w") as credentials_file:
  84. credentials_file.write(credentials)
  85. @staticmethod
  86. def __clean_credentials():
  87. open("credentials", "w").close()
  88. def on_tb_connected(client, userdata, flags, rc): # Callback for connect with received credentials
  89. if rc == 0:
  90. print("[ThingsBoard client] Connected to ThingsBoard with credentials: %s" % client._username.decode())
  91. else:
  92. print("[ThingsBoard client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])
  93. if __name__ == '__main__':
  94. config = collect_required_data()
  95. THINGSBOARD_HOST = config["host"] # ThingsBoard instance host
  96. THINGSBOARD_PORT = config["port"] # ThingsBoard instance MQTT port
  97. PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"], # Provision device key, replace this value with your value from device profile.
  98. "provisionDeviceSecret": config["provision_device_secret"], # Provision device secret, replace this value with your value from device profile.
  99. "credentialsType": "ACCESS_TOKEN",
  100. "token": config["token"],
  101. }
  102. if config.get("device_name") is not None:
  103. PROVISION_REQUEST["deviceName"] = config["device_name"]
  104. provision_client = ProvisionClient(THINGSBOARD_HOST, THINGSBOARD_PORT, PROVISION_REQUEST)
  105. provision_client.provision() # Request provisioned data
  106. tb_client = provision_client.get_new_client() # Getting client with provisioned data
  107. if tb_client:
  108. tb_client.on_connect = on_tb_connected # Setting callback for connect
  109. tb_client.connect(THINGSBOARD_HOST, THINGSBOARD_PORT, 60)
  110. tb_client.loop_forever() # Starting infinity loop
  111. else:
  112. print("Client was not created!")

设备提供基本的 MQTT 凭证

Parameter Example value Description
deviceName DEVICE_NAME Thingsboard中的设备名称
provisionDeviceKey PUT_PROVISION_KEY_HERE 设备批量化的键, 从已配置的设备配置文件中获取。
provisionDeviceSecret PUT_PROVISION_SECRET_HERE 设备批量化的密钥,从已配置的设备配置文件中获取。
credentialsType MQTT_BASIC 凭证类型参数。
username DEVICE_USERNAME_HERE Thingsboard中设备的用户名。
password DEVICE_PASSWORD_HERE Thingsboard中设备的密码。
clientId DEVICE_CLIENT_ID_HERE Client id for device in ThingsBoard.设备在 Thingsboard 中的客户端 ID

请求参数示例:

  1. {
  2. "deviceName": "DEVICE_NAME",
  3. "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  4. "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE",
  5. "credentialsType": "MQTT_BASIC",
  6. "username": "DEVICE_USERNAME_HERE",
  7. "password": "DEVICE_PASSWORD_HERE",
  8. "clientId": "DEVICE_CLIENT_ID_HERE"
  9. }

响应示例:

  1. {
  2. "credentialsType":"MQTT_BASIC",
  3. "credentialsValue": {
  4. "clientId":"DEVICE_CLIENT_ID_HERE",
  5. "userName":"DEVICE_USERNAME_HERE",
  6. "password":"DEVICE_PASSWORD_HERE"
  7. },
  8. "status":"SUCCESS"
  9. }

示例脚本

为了与 ThingsBoard 通信,我们将使用 Paho MQTT 模块,因此我们应该安装它:

  1. pip3 install paho-mqtt --user

下面提供了脚本源代码。您可以将其复制粘贴到文件中,例如:

  1. device-provision-example.py

现在,按照以下步骤运行脚本。
您可以使用python 3启动脚本:

  1. python3 device-provision-example.py

脚本源代码:

  1. from paho.mqtt.client import Client
  2. from json import dumps, loads
  3. RESULT_CODES = {
  4. 1: "incorrect protocol version",
  5. 2: "invalid client identifier",
  6. 3: "server unavailable",
  7. 4: "bad username or password",
  8. 5: "not authorised",
  9. }
  10. def collect_required_data():
  11. config = {}
  12. print("\n\n", "="*80, sep="")
  13. print(" "*10, "\033[1m\033[94mThingsBoard device provisioning with basic authorization example script.\033[0m", sep="")
  14. print("="*80, "\n\n", sep="")
  15. host = input("Please write your ThingsBoard \033[93mhost\033[0m or leave it blank to use default (cloud.thingsboard.io): ")
  16. config["host"] = host if host else "cloud.thingsboard.io"
  17. port = input("Please write your ThingsBoard \033[93mport\033[0m or leave it blank to use default (1883): ")
  18. config["port"] = int(port) if port else 1883
  19. config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
  20. config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
  21. device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
  22. if device_name:
  23. config["device_name"] = device_name
  24. config["clientId"] = input("Please write \033[93mclient Id\033[0m: ")
  25. config["username"] = input("Please write \033[93musername\033[0m: ")
  26. config["password"] = input("Please write \033[93mpassword\033[0m: ")
  27. print("\n", "="*80, "\n", sep="")
  28. return config
  29. class ProvisionClient(Client):
  30. PROVISION_REQUEST_TOPIC = "/provision/request"
  31. PROVISION_RESPONSE_TOPIC = "/provision/response"
  32. def __init__(self, host, port, provision_request):
  33. super().__init__()
  34. self._host = host
  35. self._port = port
  36. self._username = "provision"
  37. self.on_connect = self.__on_connect
  38. self.on_message = self.__on_message
  39. self.__provision_request = provision_request
  40. def __on_connect(self, client, userdata, flags, rc): # Callback for connect
  41. if rc == 0:
  42. print("[Provisioning client] Connected to ThingsBoard ")
  43. client.subscribe(self.PROVISION_RESPONSE_TOPIC) # Subscribe to provisioning response topic
  44. provision_request = dumps(self.__provision_request)
  45. print("[Provisioning client] Sending provisioning request %s" % provision_request)
  46. client.publish(self.PROVISION_REQUEST_TOPIC, provision_request) # Publishing provisioning request topic
  47. else:
  48. print("[Provisioning client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])
  49. def __on_message(self, client, userdata, msg):
  50. decoded_payload = msg.payload.decode("UTF-8")
  51. print("[Provisioning client] Received data from ThingsBoard: %s" % decoded_payload)
  52. decoded_message = loads(decoded_payload)
  53. provision_device_status = decoded_message.get("status")
  54. if provision_device_status == "SUCCESS":
  55. self.__save_credentials(decoded_message["credentialsValue"])
  56. else:
  57. print("[Provisioning client] Provisioning was unsuccessful with status %s and message: %s" % (provision_device_status, decoded_message["errorMsg"]))
  58. self.disconnect()
  59. def provision(self):
  60. print("[Provisioning client] Connecting to ThingsBoard (provisioning client)")
  61. self.__clean_credentials()
  62. self.connect(self._host, self._port, 60)
  63. self.loop_forever()
  64. def get_new_client(self):
  65. client_credentials = loads(self.__get_credentials())
  66. new_client = None
  67. if client_credentials:
  68. new_client = Client(client_id=client_credentials["clientId"]) # Setting client id
  69. new_client.username_pw_set(client_credentials["userName"], client_credentials["password"]) # Setting username and password for ThingsBoard client
  70. print("[Provisioning client] Read credentials from file.")
  71. else:
  72. print("[Provisioning client] Cannot read credentials from file!")
  73. return new_client
  74. @staticmethod
  75. def __get_credentials():
  76. new_credentials = None
  77. try:
  78. with open("credentials", "r") as credentials_file:
  79. new_credentials = credentials_file.read()
  80. except Exception as e:
  81. print(e)
  82. return new_credentials
  83. @staticmethod
  84. def __save_credentials(credentials):
  85. with open("credentials", "w") as credentials_file:
  86. credentials_file.write(dumps(credentials))
  87. @staticmethod
  88. def __clean_credentials():
  89. open("credentials", "w").close()
  90. def on_tb_connected(client, userdata, flags, rc): # Callback for connect with received credentials
  91. if rc == 0:
  92. 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()))
  93. else:
  94. print("[ThingsBoard client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])
  95. if __name__ == '__main__':
  96. config = collect_required_data()
  97. THINGSBOARD_HOST = config["host"] # ThingsBoard instance host
  98. THINGSBOARD_PORT = config["port"] # ThingsBoard instance MQTT port
  99. PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"],
  100. # Provision device key, replace this value with your value from device profile.
  101. "provisionDeviceSecret": config["provision_device_secret"],
  102. # Provision device secret, replace this value with your value from device profile.
  103. "credentialsType": "MQTT_BASIC",
  104. "username": config["username"],
  105. "password": config["password"],
  106. "clientId": config["clientId"],
  107. }
  108. if config.get("device_name") is not None:
  109. PROVISION_REQUEST["deviceName"] = config["device_name"]
  110. provision_client = ProvisionClient(THINGSBOARD_HOST, THINGSBOARD_PORT, PROVISION_REQUEST)
  111. provision_client.provision() # Request provisioned data
  112. tb_client = provision_client.get_new_client() # Getting client with provisioned data
  113. if tb_client:
  114. tb_client.on_connect = on_tb_connected # Setting callback for connect
  115. tb_client.connect(THINGSBOARD_HOST, THINGSBOARD_PORT, 60)
  116. tb_client.loop_forever() # Starting infinity loop
  117. else:
  118. print("Client was not created!")

设备提供X.509证书

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

Parameter Example value Description
deviceName DEVICE_NAME 设备在Thingsboard中的名称。
provisionDeviceKey PUT_PROVISION_KEY_HERE 设备批量化的键, 从已配置的设备配置文件中获取。
provisionDeviceSecret PUT_PROVISION_SECRET_HERE 设备批量化的密钥,从已配置的设备配置文件中获取。
credentialsType X509_CERTIFICATE 凭证类型参数。
hash MIIB……..AQAB 设备提供X.509证书

预配请求数据示例:

  1. {
  2. "deviceName": "DEVICE_NAME",
  3. "provisionDeviceKey": "PUT_PROVISION_KEY_HERE",
  4. "provisionDeviceSecret": "PUT_PROVISION_SECRET_HERE",
  5. "credentialsType": "X509_CERTIFICATE",
  6. "hash": "MIIB........AQAB"
  7. }

预配响应示例:

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

MQTT示例脚本

要使用此脚本,请将 mqttserver.pub.pem (服务器的公钥) 放入带有脚本的文件夹中。

  1. import ssl
  2. from datetime import datetime, timedelta
  3. from cryptography import x509
  4. from cryptography.x509.oid import NameOID
  5. from cryptography.hazmat.primitives import hashes
  6. from cryptography.hazmat.backends import default_backend
  7. from cryptography.hazmat.primitives import serialization
  8. from cryptography.hazmat.primitives.asymmetric import rsa
  9. from paho.mqtt.client import Client
  10. from json import dumps, loads
  11. RESULT_CODES = {
  12. 1: "incorrect protocol version",
  13. 2: "invalid client identifier",
  14. 3: "server unavailable",
  15. 4: "bad username or password",
  16. 5: "not authorised",
  17. }
  18. def collect_required_data():
  19. config = {}
  20. print("\n\n", "="*80, sep="")
  21. print(" "*10, "\033[1m\033[94mThingsBoard device provisioning with X509 certificate authorization example script. MQTT API\033[0m", sep="")
  22. print("="*80, "\n\n", sep="")
  23. host = input("Please write your ThingsBoard \033[93mhost\033[0m or leave it blank to use default (cloud.thingsboard.io): ")
  24. config["host"] = host if host else "cloud.thingsboard.io"
  25. port = input("Please write your ThingsBoard \033[93mSSL port\033[0m or leave it blank to use default (8883): ")
  26. config["port"] = int(port) if port else 8883
  27. config["provision_device_key"] = input("Please write \033[93mprovision device key\033[0m: ")
  28. config["provision_device_secret"] = input("Please write \033[93mprovision device secret\033[0m: ")
  29. device_name = input("Please write \033[93mdevice name\033[0m or leave it blank to generate: ")
  30. if device_name:
  31. config["device_name"] = device_name
  32. print("\n", "="*80, "\n", sep="")
  33. return config
  34. def generate_certs(ca_certfile="mqttserver.pub.pem"):
  35. root_cert = None
  36. try:
  37. with open(ca_certfile, "r") as ca_file:
  38. root_cert = x509.load_pem_x509_certificate(str.encode(ca_file.read()), default_backend())
  39. except Exception as e:
  40. print("Failed to load CA certificate: %r" % e)
  41. if root_cert is not None:
  42. private_key = rsa.generate_private_key(
  43. public_exponent=65537, key_size=2048, backend=default_backend()
  44. )
  45. new_subject = x509.Name([
  46. x509.NameAttribute(NameOID.COMMON_NAME, "localhost")
  47. ])
  48. certificate = (
  49. x509.CertificateBuilder()
  50. .subject_name(new_subject)
  51. .issuer_name(new_subject)
  52. .public_key(private_key.public_key())
  53. .serial_number(x509.random_serial_number())
  54. .not_valid_before(datetime.utcnow())
  55. .not_valid_after(datetime.utcnow() + timedelta(days=365*10))
  56. .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
  57. .sign(private_key=private_key, algorithm=hashes.SHA256(), backend=default_backend())
  58. )
  59. with open("cert.pem", "wb") as cert_file:
  60. cert_file.write(certificate.public_bytes(encoding=serialization.Encoding.PEM))
  61. with open("key.pem", "wb") as key_file:
  62. key_file.write(private_key.private_bytes(encoding=serialization.Encoding.PEM,
  63. format=serialization.PrivateFormat.TraditionalOpenSSL,
  64. encryption_algorithm=serialization.NoEncryption(),
  65. ))
  66. def read_cert():
  67. cert = None
  68. key = None
  69. try:
  70. with open("cert.pem", "r") as cert_file:
  71. cert = cert_file.read()
  72. with open("key.pem", "r") as key_file:
  73. key = key_file.read()
  74. except Exception as e:
  75. print("Cannot read certificate with error: %r" % e)
  76. return cert, key
  77. class ProvisionClient(Client):
  78. PROVISION_REQUEST_TOPIC = "/provision/request"
  79. PROVISION_RESPONSE_TOPIC = "/provision/response"
  80. def __init__(self, host, port, provision_request):
  81. super().__init__()
  82. self._host = host
  83. self._port = port
  84. self._username = "provision"
  85. self.tls_set(ca_certs="mqttserver.pub.pem", tls_version=ssl.PROTOCOL_TLSv1_2)
  86. self.on_connect = self.__on_connect
  87. self.on_message = self.__on_message
  88. self.__provision_request = provision_request
  89. def __on_connect(self, client, userdata, flags, rc): # Callback for connect
  90. if rc == 0:
  91. print("[Provisioning client] Connected to ThingsBoard ")
  92. client.subscribe(self.PROVISION_RESPONSE_TOPIC) # Subscribe to provisioning response topic
  93. provision_request = dumps(self.__provision_request)
  94. print("[Provisioning client] Sending provisioning request %s" % provision_request)
  95. client.publish(self.PROVISION_REQUEST_TOPIC, provision_request) # Publishing provisioning request topic
  96. else:
  97. print("[Provisioning client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])
  98. def __on_message(self, client, userdata, msg):
  99. decoded_payload = msg.payload.decode("UTF-8")
  100. print("[Provisioning client] Received data from ThingsBoard: %s" % decoded_payload)
  101. decoded_message = loads(decoded_payload)
  102. provision_device_status = decoded_message.get("status")
  103. if provision_device_status == "SUCCESS":
  104. if decoded_message["credentialsValue"] == cert.replace("-----BEGIN CERTIFICATE-----\n", "")\
  105. .replace("-----END CERTIFICATE-----\n", "")\
  106. .replace("\n", ""):
  107. print("[Provisioning client] Provisioning success! Certificates are saved.")
  108. self.__save_credentials(cert)
  109. else:
  110. print("[Provisioning client] Returned certificate is not equal to sent one.")
  111. else:
  112. print("[Provisioning client] Provisioning was unsuccessful with status %s and message: %s" % (provision_device_status, decoded_message["errorMsg"]))
  113. self.disconnect()
  114. def provision(self):
  115. print("[Provisioning client] Connecting to ThingsBoard (provisioning client)")
  116. self.__clean_credentials()
  117. self.connect(self._host, self._port, 60)
  118. self.loop_forever()
  119. def get_new_client(self):
  120. client_credentials = self.__get_credentials()
  121. new_client = None
  122. if client_credentials:
  123. new_client = Client()
  124. new_client.tls_set(ca_certs="mqttserver.pub.pem", certfile="cert.pem", keyfile="key.pem", cert_reqs=ssl.CERT_REQUIRED,
  125. tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)
  126. new_client.tls_insecure_set(False)
  127. print("[Provisioning client] Read credentials from file.")
  128. else:
  129. print("[Provisioning client] Cannot read credentials from file!")
  130. return new_client
  131. @staticmethod
  132. def __get_credentials():
  133. new_credentials = None
  134. try:
  135. with open("credentials", "r") as credentials_file:
  136. new_credentials = credentials_file.read()
  137. except Exception as e:
  138. print(e)
  139. return new_credentials
  140. @staticmethod
  141. def __save_credentials(credentials):
  142. with open("credentials", "w") as credentials_file:
  143. credentials_file.write(credentials)
  144. @staticmethod
  145. def __clean_credentials():
  146. open("credentials", "w").close()
  147. def on_tb_connected(client, userdata, flags, rc): # Callback for connect with received credentials
  148. if rc == 0:
  149. print("[ThingsBoard client] Connected to ThingsBoard with credentials: username: %s, password: %s, client id: %s" % (client._username, client._password, client._client_id))
  150. else:
  151. print("[ThingsBoard client] Cannot connect to ThingsBoard!, result: %s" % RESULT_CODES[rc])
  152. if __name__ == '__main__':
  153. config = collect_required_data()
  154. THINGSBOARD_HOST = config["host"] # ThingsBoard instance host
  155. THINGSBOARD_PORT = config["port"] # ThingsBoard instance MQTT port
  156. PROVISION_REQUEST = {"provisionDeviceKey": config["provision_device_key"], # Provision device key, replace this value with your value from device profile.
  157. "provisionDeviceSecret": config["provision_device_secret"], # Provision device secret, replace this value with your value from device profile.
  158. "credentialsType": "X509_CERTIFICATE",
  159. }
  160. if config.get("device_name") is not None:
  161. PROVISION_REQUEST["deviceName"] = config["device_name"]
  162. generate_certs() # Generate certificate and key
  163. cert, key = read_cert() # Read certificate and key
  164. PROVISION_REQUEST["hash"] = cert
  165. if PROVISION_REQUEST.get("hash") is not None:
  166. provision_client = ProvisionClient(THINGSBOARD_HOST, THINGSBOARD_PORT, PROVISION_REQUEST)
  167. provision_client.provision() # Request provisioned data
  168. tb_client = provision_client.get_new_client() # Getting client with provisioned data
  169. if tb_client:
  170. tb_client.on_connect = on_tb_connected # Setting callback for connect
  171. tb_client.connect(THINGSBOARD_HOST, THINGSBOARD_PORT, 60)
  172. tb_client.loop_forever() # Starting infinity loop
  173. else:
  174. print("Client was not created!")
  175. else:
  176. print("Cannot read certificate.")