-
Notifications
You must be signed in to change notification settings - Fork 76
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
imbeacon
wants to merge
78
commits into
master
Choose a base branch
from
develop-2.0
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
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 fa614d0
Updated processing rate limitations, data sending and messages delive…
imbeacon 7e9f191
Added basic handlers for device, updated copyright
imbeacon 5442199
Added ability to use client-side RPC and rate limits retrieval refact…
imbeacon e25c785
Added values validation, improved entities representation
imbeacon 5b98ce4
Added wait for publish option to send_attributes and timeseries and c…
imbeacon efe94d2
Fix for qos in message queue and added ability to send errors while R…
imbeacon cd23b2f
Changed typing for callbacks
imbeacon cb853f3
Added device provisioning
samson0v 0e91491
Added firmware update
samson0v 2914484
Updated device provisioning
samson0v 92c9236
Refactored device provisioning
samson0v 4490374
Updated firmware updater
samson0v f93f78c
Updated firmware updater due to comments
samson0v 1b6f117
Device provisioning fixes
samson0v 78d6f1a
Merge pull request #93 from samson0v/develop-2.0
imbeacon 35d89c1
Fixed on_unsubscribe callback
samson0v 23322cc
Added properies argument to on_unsubscribe callback
samson0v d6e6066
Merge pull request #94 from samson0v/develop-2.0
imbeacon 603fc32
Added CONACK handler
imbeacon bd47806
Updated async functions to wait for responses by default
imbeacon db545f2
Updated example due to changes in device client
imbeacon c45148f
Updated examples for device
imbeacon 7fda91e
Added description for configuration objects
imbeacon 14af70b
Added provisioning and firmware update examples
samson0v bde61b0
Fix for provisioning request with x509 credentials and added tests fo…
imbeacon f2d977e
Added tests for message splitter
imbeacon f1209fe
Added error handling for provisioning, added timeout for firmware update
samson0v bdfcee0
Merge pull request #95 from samson0v/develop-2.0
imbeacon 5995131
Removed deprecated examples
imbeacon 10f22ba
Adjusted configuration for tests
imbeacon a157f0a
Added tests for MQTTManager
imbeacon 4088ff8
Added rate limits tests
imbeacon c098239
Moved provisioning client to common
imbeacon 6a53f0a
Fix for message queue and added tests
imbeacon f78fa2e
Added tests
imbeacon 36eacc6
Added fixes for found bugs
imbeacon 7a6c43a
Refactoring
imbeacon ccc2032
Adjusted claiming for current implementation on the platform
imbeacon bf8a6b2
Removed deprecated example
imbeacon 4c8b94f
Renamed telemetry to time series
imbeacon 14c4a53
Added new example for connection over SSL
imbeacon 30e61e4
Added send_telemetry method with warning, to use send_timeseries instead
imbeacon d38e031
Example refactoring
imbeacon 8911dd0
Merge remote-tracking branch 'origin/master' into develop-2.0
imbeacon d4e1900
Examples refactoring
imbeacon 0ceecf2
Refactored device client and added basic entities for gateway client
imbeacon febccf6
Removed device name from device uplink message, updated gateway messa…
imbeacon bc1b34a
Returned device name to device uplink message to improve data grouping
imbeacon 2b4ec62
Added send attribbutes functionality
imbeacon 72c5d4f
Added example and processing for device attribute requests for device…
imbeacon 5704f71
Refactored example callback assignment and device events handling
imbeacon e128d81
Added processing RPC requests and example to gateway part
imbeacon 983bec9
Updated loggers for examples
imbeacon 0f6a649
Added processing for device disconnection and adjusted example
imbeacon 35cfaa6
Added attribute updates handling and example
imbeacon 3fb01d5
Added claiming request processing for devices connected through the g…
imbeacon 17a35e2
Fix for timeseries without ts
imbeacon 96d6309
Added shared future for splitted and grouped messages for confirmation
imbeacon 8674be1
Adjusted entities and services to use mqtt publish message, changes t…
imbeacon aeeae21
Message queue refactoring, added additional tests
imbeacon 4e54c79
Added main ts to initial message, splitted and grouped messages, to s…
imbeacon 94c2c57
Improved examples and fixed double call of connect callback and subsc…
imbeacon 7e5ea8c
Adjusted gateway part of SDK to use MqttPublishMessage entity, refact…
imbeacon 18af003
Adjusted grouping timeseries by ts in uplink messages for gateway par…
imbeacon 45298b2
Optimized message service, rate limits processing, sending retry for …
imbeacon f8f13f7
Removed old implementation, added tests
imbeacon c930d21
Updated gateway operational example and attribute response processing
imbeacon d01e25b
Added tls example for gateway client
imbeacon 93957df
Refactiong, imports optimization, formatting
imbeacon 6b18fdd
Updated README and dev notes
imbeacon 2f34578
Updated license
imbeacon 4e37259
Fixed reconnection on session taken over and missing device at init
imbeacon 1012629
Added blackbox tests
imbeacon 8b18e25
Added fix for checksum verification and blackbox tests for firmware u…
imbeacon 404b6a3
Optimized tests and added blackbox test for client side RPC
imbeacon 36e498b
Added additional tests
imbeacon 665fc95
Added check for ThingsBoard availability before running blackbox test…
imbeacon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.