Skip to content

ThingsBoard Python Client SDK 2.0 – Complete Architectural Rewrite Based on gmqtt #99

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 78 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 70 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
a5d6be5
Initial commit, structure and basic data sending
imbeacon May 16, 2025
fa614d0
Updated processing rate limitations, data sending and messages delive…
imbeacon May 28, 2025
7e9f191
Added basic handlers for device, updated copyright
imbeacon May 29, 2025
5442199
Added ability to use client-side RPC and rate limits retrieval refact…
imbeacon May 30, 2025
e25c785
Added values validation, improved entities representation
imbeacon May 30, 2025
5b98ce4
Added wait for publish option to send_attributes and timeseries and c…
imbeacon Jun 4, 2025
efe94d2
Fix for qos in message queue and added ability to send errors while R…
imbeacon Jun 4, 2025
cd23b2f
Changed typing for callbacks
imbeacon Jun 4, 2025
cb853f3
Added device provisioning
samson0v Jun 11, 2025
0e91491
Added firmware update
samson0v Jun 12, 2025
2914484
Updated device provisioning
samson0v Jun 17, 2025
92c9236
Refactored device provisioning
samson0v Jun 17, 2025
4490374
Updated firmware updater
samson0v Jun 18, 2025
f93f78c
Updated firmware updater due to comments
samson0v Jun 18, 2025
1b6f117
Device provisioning fixes
samson0v Jun 19, 2025
78d6f1a
Merge pull request #93 from samson0v/develop-2.0
imbeacon Jun 19, 2025
35d89c1
Fixed on_unsubscribe callback
samson0v Jun 19, 2025
23322cc
Added properies argument to on_unsubscribe callback
samson0v Jun 19, 2025
d6e6066
Merge pull request #94 from samson0v/develop-2.0
imbeacon Jun 19, 2025
603fc32
Added CONACK handler
imbeacon Jun 23, 2025
bd47806
Updated async functions to wait for responses by default
imbeacon Jun 23, 2025
db545f2
Updated example due to changes in device client
imbeacon Jun 23, 2025
c45148f
Updated examples for device
imbeacon Jun 24, 2025
7fda91e
Added description for configuration objects
imbeacon Jun 24, 2025
14af70b
Added provisioning and firmware update examples
samson0v Jun 24, 2025
bde61b0
Fix for provisioning request with x509 credentials and added tests fo…
imbeacon Jun 25, 2025
f2d977e
Added tests for message splitter
imbeacon Jun 25, 2025
f1209fe
Added error handling for provisioning, added timeout for firmware update
samson0v Jun 25, 2025
bdfcee0
Merge pull request #95 from samson0v/develop-2.0
imbeacon Jun 25, 2025
5995131
Removed deprecated examples
imbeacon Jun 25, 2025
10f22ba
Adjusted configuration for tests
imbeacon Jun 25, 2025
a157f0a
Added tests for MQTTManager
imbeacon Jun 25, 2025
4088ff8
Added rate limits tests
imbeacon Jun 25, 2025
c098239
Moved provisioning client to common
imbeacon Jun 26, 2025
6a53f0a
Fix for message queue and added tests
imbeacon Jun 26, 2025
f78fa2e
Added tests
imbeacon Jun 27, 2025
36eacc6
Added fixes for found bugs
imbeacon Jun 27, 2025
7a6c43a
Refactoring
imbeacon Jun 27, 2025
ccc2032
Adjusted claiming for current implementation on the platform
imbeacon Jun 30, 2025
bf8a6b2
Removed deprecated example
imbeacon Jun 30, 2025
4c8b94f
Renamed telemetry to time series
imbeacon Jun 30, 2025
14c4a53
Added new example for connection over SSL
imbeacon Jun 30, 2025
30e61e4
Added send_telemetry method with warning, to use send_timeseries instead
imbeacon Jun 30, 2025
d38e031
Example refactoring
imbeacon Jun 30, 2025
8911dd0
Merge remote-tracking branch 'origin/master' into develop-2.0
imbeacon Jul 3, 2025
d4e1900
Examples refactoring
imbeacon Jul 3, 2025
0ceecf2
Refactored device client and added basic entities for gateway client
imbeacon Jul 11, 2025
febccf6
Removed device name from device uplink message, updated gateway messa…
imbeacon Jul 15, 2025
bc1b34a
Returned device name to device uplink message to improve data grouping
imbeacon Jul 16, 2025
2b4ec62
Added send attribbutes functionality
imbeacon Jul 16, 2025
72c5d4f
Added example and processing for device attribute requests for device…
imbeacon Jul 16, 2025
5704f71
Refactored example callback assignment and device events handling
imbeacon Jul 16, 2025
e128d81
Added processing RPC requests and example to gateway part
imbeacon Jul 16, 2025
983bec9
Updated loggers for examples
imbeacon Jul 16, 2025
0f6a649
Added processing for device disconnection and adjusted example
imbeacon Jul 16, 2025
35cfaa6
Added attribute updates handling and example
imbeacon Jul 16, 2025
3fb01d5
Added claiming request processing for devices connected through the g…
imbeacon Jul 17, 2025
17a35e2
Fix for timeseries without ts
imbeacon Jul 17, 2025
96d6309
Added shared future for splitted and grouped messages for confirmation
imbeacon Jul 22, 2025
8674be1
Adjusted entities and services to use mqtt publish message, changes t…
imbeacon Jul 24, 2025
aeeae21
Message queue refactoring, added additional tests
imbeacon Jul 24, 2025
4e54c79
Added main ts to initial message, splitted and grouped messages, to s…
imbeacon Jul 24, 2025
94c2c57
Improved examples and fixed double call of connect callback and subsc…
imbeacon Jul 24, 2025
7e5ea8c
Adjusted gateway part of SDK to use MqttPublishMessage entity, refact…
imbeacon Jul 25, 2025
18af003
Adjusted grouping timeseries by ts in uplink messages for gateway par…
imbeacon Jul 25, 2025
45298b2
Optimized message service, rate limits processing, sending retry for …
imbeacon Jul 31, 2025
f8f13f7
Removed old implementation, added tests
imbeacon Aug 1, 2025
c930d21
Updated gateway operational example and attribute response processing
imbeacon Aug 1, 2025
d01e25b
Added tls example for gateway client
imbeacon Aug 4, 2025
93957df
Refactiong, imports optimization, formatting
imbeacon Aug 5, 2025
6b18fdd
Updated README and dev notes
imbeacon Aug 5, 2025
2f34578
Updated license
imbeacon Aug 6, 2025
4e37259
Fixed reconnection on session taken over and missing device at init
imbeacon Aug 6, 2025
1012629
Added blackbox tests
imbeacon Aug 7, 2025
8b18e25
Added fix for checksum verification and blackbox tests for firmware u…
imbeacon Aug 7, 2025
404b6a3
Optimized tests and added blackbox test for client side RPC
imbeacon Aug 8, 2025
36e498b
Added additional tests
imbeacon Aug 8, 2025
665fc95
Added check for ThingsBoard availability before running blackbox test…
imbeacon Aug 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,3 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#


__name__ = "tb_mqtt_client"
68 changes: 68 additions & 0 deletions examples/device/claim_device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright 2025 ThingsBoard
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Example script to claim a device using ThingsBoard DeviceClient

import asyncio
import logging

from tb_mqtt_client.common.config_loader import DeviceConfig
from tb_mqtt_client.common.logging_utils import configure_logging, get_logger
from tb_mqtt_client.common.publish_result import PublishResult
from tb_mqtt_client.entities.data.claim_request import ClaimRequest
from tb_mqtt_client.service.device.client import DeviceClient

configure_logging()
logger = get_logger(__name__)
logger.setLevel(logging.INFO)
logging.getLogger("tb_mqtt_client").setLevel(logging.INFO)

# Constants for connection
PLATFORM_HOST = "localhost" # Replace with your ThingsBoard host
DEVICE_ACCESS_TOKEN = "YOUR_ACCESS_TOKEN" # Replace with your device's access token

# Constants for claiming
CLAIMING_DURATION = 120 # Default claiming duration in seconds
CLAIMING_SECRET_KEY = "YOUR_SECRET_KEY" # Replace with your actual secret key


async def main():
# Create device config
config = DeviceConfig()
config.host = PLATFORM_HOST
config.access_token = DEVICE_ACCESS_TOKEN

# Create device client
client = DeviceClient(config)
await client.connect()

# Build claim request with secret key and optional duration (in seconds)
claim_request = ClaimRequest.build(secret_key=CLAIMING_SECRET_KEY, duration=CLAIMING_DURATION)

# Send claim request
result: PublishResult = await client.claim_device(claim_request,
wait_for_publish=True,
timeout=CLAIMING_DURATION + 10)
if result.is_successful():
logger.info(
f"Claiming request was sent successfully. "
f"Please use the secret key '{CLAIMING_SECRET_KEY}' to claim the device from the dashboard.")
else:
logger.error(f"Failed to send claiming request. Result: {result}")

await client.stop()


if __name__ == "__main__":
asyncio.run(main())
37 changes: 0 additions & 37 deletions examples/device/claiming_device_pe_only.py

This file was deleted.

93 changes: 40 additions & 53 deletions examples/device/client_provisioning.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,57 @@
# Copyright 2025. ThingsBoard
# Copyright 2025 ThingsBoard
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Example script to device provisioning using the DeviceClient

import asyncio
import logging
from tb_device_mqtt import TBDeviceMqttClient, TBPublishInfo
logging.basicConfig(level=logging.DEBUG)
from random import randint

from tb_mqtt_client.common.logging_utils import configure_logging, get_logger
from tb_mqtt_client.entities.data.provisioning_request import AccessTokenProvisioningCredentials, ProvisioningRequest
from tb_mqtt_client.entities.data.timeseries_entry import TimeseriesEntry
from tb_mqtt_client.service.device.client import DeviceClient

def main():
"""
We can provide the following parameters to provisioning function:
host - required - Host of ThingsBoard
provision_device_key - required - device provision key from device profile
provision_device_secret - required - device provision secret from device profile
port=1883 - not required - MQTT port of ThingsBoard instance
device_name=None - may be generated on ThingsBoard - You may pass here name for device, if this parameter is not assigned, the name will be generated
configure_logging()
logger = get_logger(__name__)
logger.setLevel(logging.INFO)
logging.getLogger("tb_mqtt_client").setLevel(logging.INFO)

### Credentials type = ACCESS_TOKEN

access_token=None - may be generated on ThingsBoard - You may pass here some access token and it will be saved as accessToken for device on ThingsBoard.
async def main():
provisioning_credentials = AccessTokenProvisioningCredentials(
provision_device_key='YOUR_PROVISION_DEVICE_KEY',
provision_device_secret='YOUR_PROVISION_DEVICE_SECRET',
)
provisioning_request = ProvisioningRequest('localhost', credentials=provisioning_credentials)
provisioning_response = await DeviceClient.provision(provisioning_request)

### Credentials type = MQTT_BASIC
if provisioning_response.error is not None:
logger.error(f"Provisioning failed: {provisioning_response.error}")
return

client_id=None - not required (if username is not None) - You may pass here client Id for your device and use it later for connecting
username=None - not required (if client id is not None) - You may pass here username for your client and use it later for connecting
password=None - not required - You may pass here password and use it later for connecting
logger.info('Provisioned device configuration: ', provisioning_response)

### Credentials type = X509_CERTIFICATE
hash=None - required (If you want to use this credentials type) - You should pass here public key of the device, generated from mqttserver.jks
# Create a DeviceClient instance with the provisioned device configuration
client = DeviceClient(provisioning_response.result)
await client.connect()

"""
# Send single telemetry entry to the provisioned device
await client.send_timeseries(TimeseriesEntry("batteryLevel", randint(0, 100)))

# Call device provisioning, to do this we don't need an instance of the TBDeviceMqttClient to provision device
await client.stop()

THINGSBOARD_HOST = "mqtt.thingsboard.cloud"

credentials = TBDeviceMqttClient.provision(THINGSBOARD_HOST, "PROVISION_DEVICE_KEY", "PROVISION_DEVICE_SECRET")

if credentials is not None and credentials.get("status") == "SUCCESS":
username = None
password = None
client_id = None
if credentials["credentialsType"] == "ACCESS_TOKEN":
username = credentials["credentialsValue"]
elif credentials["credentialsType"] == "MQTT_BASIC":
username = credentials["credentialsValue"]["userName"]
password = credentials["credentialsValue"]["password"]
client_id = credentials["credentialsValue"]["clientId"]

client = TBDeviceMqttClient(THINGSBOARD_HOST, username=username, password=password, client_id=client_id)
client.connect()
# Other code

client.stop()


if __name__ == '__main__':
main()
if __name__ == "__main__":
asyncio.run(main())
44 changes: 0 additions & 44 deletions examples/device/client_rpc_request.py

This file was deleted.

68 changes: 44 additions & 24 deletions examples/device/firmware_update.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,57 @@
# Copyright 2025. ThingsBoard
# Copyright 2025 ThingsBoard
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import time
# Example script to update firmware using the DeviceClient

import asyncio
import logging
from tb_device_mqtt import TBDeviceMqttClient, FW_STATE_ATTR
from time import monotonic

from tb_mqtt_client.common.config_loader import DeviceConfig
from tb_mqtt_client.common.logging_utils import configure_logging, get_logger
from tb_mqtt_client.service.device.client import DeviceClient

configure_logging()
logger = get_logger(__name__)
logger.setLevel(logging.INFO)
logging.getLogger("tb_mqtt_client").setLevel(logging.INFO)

firmware_received = asyncio.Event()
firmware_update_timeout = 30


async def firmware_update_callback(_, payload):
logger.info(f"Firmware update payload received: {payload}")
firmware_received.set()

logging.basicConfig(level=logging.INFO)

async def main():
config = DeviceConfig()
config.host = "localhost"
config.access_token = "YOUR_ACCESS_TOKEN"

def main():
client = TBDeviceMqttClient("127.0.0.1", username="A2_TEST_TOKEN")
client.connect()
client = DeviceClient(config)
await client.connect()

client.get_firmware_update()
await client.update_firmware(on_received_callback=firmware_update_callback)

# Waiting for firmware to be delivered
while not client.current_firmware_info[FW_STATE_ATTR] == 'UPDATED':
time.sleep(1)
update_started = monotonic()
while not firmware_received.is_set() and monotonic() - update_started < firmware_update_timeout:
await asyncio.sleep(1)

client.disconnect()
client.stop()
await client.stop()


if __name__ == '__main__':
main()
if __name__ == "__main__":
asyncio.run(main())
Loading
Loading