diff --git a/.github/files/explorer/device_info.json.asc b/.github/files/explorer/device_info.json.asc
new file mode 100644
index 0000000..eae0605
--- /dev/null
+++ b/.github/files/explorer/device_info.json.asc
@@ -0,0 +1,13 @@
+-----BEGIN PGP MESSAGE-----
+
+jA0EBwMCQEdzKXolxHrr0sDDAS/p+lpyNU18OpGIntyFt6WTktOe7QBFOCa2zzKJ
+0PLJKVs6IK+AqSsWU/ASJhWf7x1MCLywgnXt3Kmo2iZQlP0HFqHD5CZ/AW83DNVU
+rFVuoUPot6YYmlMqbLsZp8NVGxZ+Zev5wp7GiadYFmPQsVR/IoLdUikUOoUpWeyI
+4aVRk6vgsMZzotAYI6DcdCxEOQdUS7vO5B8ZfEx7ehzWf+giR4PGlH+aQJevaT7X
+qHovtcNOK5XPf8hk+QafOYkCDdJP5QaHdfxrDwn7TJHUiLiUvciIYSLOeM16+psP
+XevcTYOhMCy44eLMGejFbT3YPqcEK7j+522x9mpPhDlXtA9FLnLYufv5hUNRQ85Q
+6sMfGgKSd+AoY6Qbfkk2OhM/wqjM7bpr9wGwzurpxvdYXFZ1kBi78zC/T/HqyOQf
+R3LGhW1DijI7Ufo22/+uioaM66S6JVyfBMxVH8n774js2fzh43xOJw1rdgoITSEw
+FOW2a7N/O5ZbxLnTctH1K34NA3cr
+=rSwV
+-----END PGP MESSAGE-----
diff --git a/.github/files/hub/device_info.json.asc b/.github/files/hub/device_info.json.asc
new file mode 100644
index 0000000..d048b6e
--- /dev/null
+++ b/.github/files/hub/device_info.json.asc
@@ -0,0 +1,13 @@
+-----BEGIN PGP MESSAGE-----
+
+jA0EBwMCDiAOe23XImfr0sC4AWnWsJkzpMVE7w0aZqHyjIa3pEXxWBJlZcxbSHGr
+MyjGqM1sYP4MBQ2aMY9jO2xgsbQbAKDONyhkfdhjw72M7Z5QIiPADwfW+MnTJ96P
+Pw8k0riMYjM1sAWM0odPB6Pn7/F0wAW93020+rTNfiqHewlZ2aPxBBMTS1y9uejE
+eJ/WgZr+QQm2ZJJOGNi/ncEM4qNwSgnduDCuZn3v8kCSXIoVXZuJ6Xp9kH/9Sl2/
+nAz6pKm/YY59yoQrcmxiBCc+44pTG3VfgIs6OAs0t8oZRiwLQNyB3aoAg8kIDJXK
+O1pbroSXq7cdC7YvAhHgTu0lfrv1Kkbwc8rn+VLsB4wgAQH32v2uBJniluUiCf7G
+KtoNFmjR0HeAbOpyKMM7Z4MbDQPbxaQ3YsZCYwwwnMwBeDoG3ScuNV7lD+mYL4xP
+dVaCtjzbnsHgjaqDnmgwfgimAIDbwONa40ZS1YppSvd6+ENRLDu3b+mWqutPE4I+
+jQlLu4BDMnTZeA==
+=skJN
+-----END PGP MESSAGE-----
diff --git a/.github/scripts/send_result_to_wecom.sh b/.github/scripts/send_result_to_wecom.sh
new file mode 100644
index 0000000..0a8cb10
--- /dev/null
+++ b/.github/scripts/send_result_to_wecom.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+color=""
+if [ $2 == "success" ]
+then
+ echo "success"
+ color="info"
+else
+ echo "fail"
+ color="warning"
+fi
+
+curl "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=$IOT_WECOM_CID_ROBOT_KEY" \
+ -H 'Content-Type: application/json' \
+ -d '
+ {
+ "msgtype": "markdown",
+ "markdown": {
+ "content": "**repo: \"'"$1"'\"**\n
+ >result:\"'"$2"'\"\n
+ >[action](https://github.com/tencentyun/iot-device-python/actions)"\n
+ }
+ }'
diff --git a/.github/workflows/pypi_publish.yml b/.github/workflows/pypi_publish.yml
index 68dc077..95cf171 100644
--- a/.github/workflows/pypi_publish.yml
+++ b/.github/workflows/pypi_publish.yml
@@ -7,33 +7,43 @@ on:
- '**.md'
- 'LICENSE'
+env:
+ IOT_WECOM_CID_ROBOT_KEY: ${{ secrets.IOT_WECOM_CID_ROBOT_KEY }}
+
jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v4
- name: Set up Python 3.x
- uses: actions/setup-python@v1
+ uses: actions/setup-python@v5
with:
- python-version: 3.7
+ python-version: '3.13'
- name: Install dependencies
run: |
+ pip install setuptools wheel twine
python setup.py check
python -m pip install --upgrade pip
- pip install setuptools wheel twine
- name: Unittest TestCase
+ env:
+ PROVISIONING_PASSWORD: ${{ secrets.GPG_DECRYPT_PASSPHRASE }}
run: |
python -m venv test_env
source test_env/bin/activate
+ pip install setuptools
python setup.py install
pip install -r requirements.txt
- python sample/test.py
+ gpg --quiet -d --passphrase "$PROVISIONING_PASSWORD" --batch .github/files/explorer/device_info.json.asc > explorer/sample/device_info.json
+ gpg --quiet -d --passphrase "$PROVISIONING_PASSWORD" --batch .github/files/hub/device_info.json.asc > hub/sample/device_info.json
+ python explorer/sample/test.py
+ python hub/sample/test.py
- name: Publish to Test PyPI
+ if: startsWith(github.event.ref, 'refs/heads')
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.IOT_TEST_PYPI_UPLOAD_TOKEN }}
@@ -47,8 +57,20 @@ jobs:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.IOT_PYPI_UPLOAD_TOKEN }}
run: |
+ VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
+ echo $VERSION
rm -rf dist/
sed -i "s#resp = urllib.request.*#resp = urllib.request.urlopen(f\'https://pypi.org/pypi/{name}/json\')#g" setup.py
-
+ sed -i "s#version=.*#version=\"$VERSION\",#g" setup.py
python setup.py sdist bdist_wheel
twine upload dist/*
+ - name: Report success result to wecom
+ if: ${{ success() }}
+ run: |
+ echo success!!!
+ bash .github/scripts/send_result_to_wecom.sh ${{ github.event.repository.name }} success
+ - name: Report fail result to wecom
+ if: ${{ failure() }}
+ run: |
+ echo fail!!!
+ bash .github/scripts/send_result_to_wecom.sh ${{ github.event.repository.name }} fail
diff --git a/.gitignore b/.gitignore
index 75cffec..89f8d18 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,5 +5,6 @@ dist/
test_env/
samples/
build.sh
-TIoTExploreSDK.egg-info/
-*.pyc
\ No newline at end of file
+*.egg-info/
+*.pyc
+logs/log
\ No newline at end of file
diff --git a/README.md b/README.md
index 016e031..94aa5d6 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+简体中文 | [English](./README_EN-US.md)
+
# 产品介绍
本仓库中包含两个产品:腾讯云物联网通信(IoT Hub) 和 腾讯云物联网开发平台(IoT Explorer) ,本仓库提供 Python 接入方式 。
diff --git a/README_EN-US.md b/README_EN-US.md
new file mode 100644
index 0000000..6b52cfc
--- /dev/null
+++ b/README_EN-US.md
@@ -0,0 +1,27 @@
+[简体中文](https://github.com/tencentyun/iot-device-python) | English
+
+# Product Overview
+
+This repository contains two products: IoT Hub and IoT Explorer. It provides the Python connection method.
+
+## IoT Hub SDK for Python
+
+Tencent Cloud Internet of Things Hub (IoT Hub) aims to provide a secure, stable, and efficient connection platform that helps developers quickly achieve stable, high-concurrency, and omnidirectional data communications among devices, user applications, and cloud services at low costs. It can implement cross-device interaction, device data reporting, and configuration distribution. Further, by opening up the linkage between device data and Tencent Cloud services based on the rule engine, it allows for the storage, real-time computation, and smart processing and analysis of massive amounts of data with speed and ease. For more information, please see [IoT Hub](https://cloud.tencent.com/document/product/634).
+
+This repository provides the IoT Hub device SDK. You can securely connect Python-enabled devices to IoT Hub by integrating the SDK into them.
+
+* [IoT Hub device SDK for Python](hub/)
+
+
+## IoT Explorer SDK for Python
+
+Tencent Cloud Internet of Things Explorer (IoT Explorer) provides device manufacturers, solution providers, and application developers in various industries with one-stop device automation services. It offers the capabilities to connect and manage high numbers of devices and develop mini programs and is interconnected with the basic products and AI capabilities of Tencent Cloud, which helps improve the device automation efficiency in traditional industries, reduce the development and OPS costs, and empower the business growth. For more information, please see [IoT Explorer](https://cloud.tencent.com/document/product/1081).
+
+This repository provides the IoT Explorer device SDK. You can securely connect Python-enabled devices to IoT Explorer by connecting their drivers or integrating the SDK into them.
+
+* [IoT Explorer device SDK for Python](explorer/)
+
+
+## Domain names involved in SDKs
+
+For the domain names involved in the corresponding SDKs of the two products, please see [Domain Names Involved in Device SDKs](https://github.com/tencentyun/iot-device-java/wiki/Device-SDK涉及的域名).
diff --git a/explorer/README.md b/explorer/README.md
index 7d418ca..8cb9368 100644
--- a/explorer/README.md
+++ b/explorer/README.md
@@ -1,9 +1,12 @@
+简体中文 | [English](doc/en)
+
* [腾讯云物联网开发平台设备端 IoT Explorer Python-SDK](#腾讯云物联网开发平台设备端-IoT-Explorer-Python-SDK)
* [前提条件](#前提条件)
* [工程配置](#工程配置)
* [下载IoT Explorer Python-SDK Demo示例代码](#下载IoT-Explorer-Python-SDK-Demo示例代码)
* [功能文档](#功能文档)
* [SDK API 说明](#SDK-API-说明)
+ * [常见问题](#常见问题)
# 腾讯云物联网开发平台设备端 IoT Explorer Python-SDK
欢迎使用腾讯云物联网开发平台设备端 IoT Explorer Python-SDK 。
@@ -33,3 +36,4 @@ SDK支持pip安装,以及本地源码安装,详细接入步骤请参考 [SDK
* [事件上报以及多事件上报](doc/事件上报以及多事件上报.md)
* [检查固件更新](doc/检查固件更新.md)
* [网关使用示例](doc/网关使用示例.md)
+* [常见问题](doc/常见问题.md)
diff --git "a/explorer/doc/SDK\346\216\245\345\205\245\350\257\264\346\230\216.md" "b/explorer/doc/SDK\346\216\245\345\205\245\350\257\264\346\230\216.md"
index 3f3503e..b0170ce 100755
--- "a/explorer/doc/SDK\346\216\245\345\205\245\350\257\264\346\230\216.md"
+++ "b/explorer/doc/SDK\346\216\245\345\205\245\350\257\264\346\230\216.md"
@@ -7,7 +7,7 @@
- Install from pip
```
- pip install TIoTExploreSDK
+ pip install tencent-iot-device
```
diff --git "a/explorer/doc/SDK\346\216\245\345\217\243\350\257\264\346\230\216.md" "b/explorer/doc/SDK\346\216\245\345\217\243\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..7e98116
--- /dev/null
+++ "b/explorer/doc/SDK\346\216\245\345\217\243\350\257\264\346\230\216.md"
@@ -0,0 +1,83 @@
+* [API接口说明](#API接口说明)
+ * [MQTT接口](#MQTT接口)
+ * [网关接口](#网关接口)
+ * [数据模板接口](#设备影子接口)
+ * [动态注册接口](#动态注册接口)
+ * [OTA接口](#OTA接口)
+ * [LOG接口](#LOG接口)
+
+# API接口说明
+## MQTT接口
+
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| connect | MQTT连接 |
+| disconnect | 断开MQTT连接 |
+| subscribe | MQTT订阅 |
+| unsubscribe | MQTT取消订阅 |
+| publish | MQTT发布消息 |
+| registerMqttCallback | 注册MQTT回调函数 |
+| registerUserCallback | 注册用户回调函数 |
+| isMqttConnected | MQTT是否正常连接 |
+| getConnectState | 获取MQTT连接状态 |
+| setReconnectInterval | 设置MQTT重连尝试间隔 |
+| setMessageTimout | 设置消息发送超时时间 |
+| setKeepaliveInterval | 设置MQTT保活间隔 |
+
+## 网关接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| gatewayInit | 网关初始化 |
+| isSubdevStatusOnline | 判断子设备是否在线 |
+| updateSubdevStatus | 更新子设备在线状态 |
+| gatewaySubdevGetConfigList | 获取配置文件中子设备列表 |
+| gatewaySubdevOnline | 代理子设备上线 |
+| gatewaySubdevOffline | 代理子设备下线 |
+| gatewaySubdevBind | 绑定子设备 |
+| gatewaySubdevUnbind | 解绑子设备 |
+| gatewaySubdevSubscribe | 子设备订阅 |
+
+## 数据模板接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| templateInit | 数据模板初始化 |
+| getEventsList | 获取设备event列表 |
+| getActionList | 获取设备action列表 |
+| getPropertyList | 获取设备property列表 |
+| templateSetup | 解析数据模板 |
+| templateEventPost | events上报 |
+| templateJsonConstructReportArray | 构建上报的json结构 |
+| templateReportSysInfo | 设备信息上报 |
+| templateControlReply | 控制消息应答 |
+| templateActionReply | action消息应答 |
+| templateGetStatus | 获取设备最新状态 |
+| templateReport | 设备属性上报 |
+| clearControl | 清除控制 |
+| templateDeinit | 数据模板销毁 |
+
+## 动态注册接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| dynregDevice | 获取设备动态注册的信息 |
+
+## OTA接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| otaInit | OTA初始化 |
+| otaIsFetching | 判断是否正在下载 |
+| otaIsFetchFinished | 判断是否下载完成 |
+| otaReportUpgradeSuccess | 上报升级成功消息 |
+| otaReportUpgradeFail | 上报升级失败消息 |
+| otaIoctlNumber | 获取下载固件大小等int类型信息 |
+| otaIoctlString | 获取下载固件md5等string类型信息 |
+| otaResetMd5 | 重置md5信息 |
+| otaMd5Update | 更新md5信息 |
+| httpInit | 初始化http |
+| otaReportVersion | 上报当前固件版本信息 |
+| otaDownloadStart | 开始固件下载 |
+| otaFetchYield | 读取固件 |
+
+## LOG接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| logInit | 日志初始化 |
\ No newline at end of file
diff --git "a/explorer/doc/en/PRELIM__SDK\346\216\245\345\205\245\350\257\264\346\230\216_EN-US.md" "b/explorer/doc/en/PRELIM__SDK\346\216\245\345\205\245\350\257\264\346\230\216_EN-US.md"
new file mode 100644
index 0000000..bc03df0
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__SDK\346\216\245\345\205\245\350\257\264\346\230\216_EN-US.md"
@@ -0,0 +1,28 @@
+ * [How to Import](#How-to-Import)
+
+## How to Import
+
+**How to import**
+
+- Install from pip
+
+ ```
+ pip install tencent-iot-device
+ ```
+
+
+- Build from source
+
+ ```
+ git clone https://github.com/tencentyun/iot-device-python.git
+ cd iot-device-python
+ python setup.py install
+ ```
+
+
+**SDK for Python source code**
+
+- If you want to develop a project through code integration, you can download the SDK for Python source code from [Github](../).
+- If you want to develop a project by importing the source code, you can refer to [Latest release](https://github.com/tencentyun/iot-device-python/releases) for the specific version number.
+
+
diff --git "a/explorer/doc/en/PRELIM__SDK\346\216\245\345\217\243\350\257\264\346\230\216_EN-US.md" "b/explorer/doc/en/PRELIM__SDK\346\216\245\345\217\243\350\257\264\346\230\216_EN-US.md"
new file mode 100644
index 0000000..8a504bf
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__SDK\346\216\245\345\217\243\350\257\264\346\230\216_EN-US.md"
@@ -0,0 +1,83 @@
+* [API Description](#API-Description)
+ * [MQTT APIs](#MQTT-APIs)
+ * [Gateway APIs](#Gateway-APIs)
+ * [Data template APIs](#Data-template-APIs)
+ * [Dynamic registration APIs](#Dynamic-registration-APIs)
+ * [OTA APIs](#OTA-APIs)
+ * [Log APIs](#Log-APIs)
+
+# API Description
+## MQTT APIs
+
+| API | Description |
+| :-: | :-: |
+| connect | Establishes MQTT connection |
+| disconnect | Closes MQTT connection |
+| subscribe | Subscribes to MQTT |
+| unsubscribe | Unsubscribes from MQTT |
+| publish | Publishes message over MQTT |
+| registerMqttCallback | Registers MQTT callback function |
+| registerUserCallback | Registers user callback function |
+| isMqttConnected | Checks whether MQTT is normally connected |
+| getConnectState | Gets MQTT connection status |
+| setReconnectInterval | Sets MQTT reconnection attempt interval |
+| setMessageTimout | Sets message sending timeout period |
+| setKeepaliveInterval | Sets MQTT keepalive interval |
+
+## Gateway APIs
+| API | Description |
+| :-: | :-: |
+| gatewayInit | Initializes gateway |
+| isSubdevStatusOnline | Determines whether subdevice is online |
+| updateSubdevStatus | Updates subdevice's online status |
+| gatewaySubdevGetConfigList | Gets subdevice list from configuration file |
+| gatewaySubdevOnline | Proxies subdevice connection |
+| gatewaySubdevOffline | Proxies subdevice disconnection |
+| gatewaySubdevBind | Binds subdevice |
+| gatewaySubdevUnbind | Unbinds subdevice |
+| gatewaySubdevSubscribe | Proxies subdevice subscription |
+
+## Data template APIs
+| API | Description |
+| :-: | :-: |
+| templateInit | Initializes data template |
+| getEventsList | Gets device event list |
+| getActionList | Gets device action list |
+| getPropertyList | Gets device attribute list |
+| templateSetup | Parses data template |
+| templateEventPost | Reports event |
+| templateJsonConstructReportArray | Constructs JSON structure for reporting |
+| templateReportSysInfo | Reports device information |
+| templateControlReply | Replies to control message |
+| templateActionReply | Replies to action message |
+| templateGetStatus | Gets latest device status |
+| templateReport | Reports device attribute |
+| clearControl | Clears control |
+| templateDeinit | Terminates data template |
+
+## Dynamic registration APIs
+| API | Description |
+| :-: | :-: |
+| dynregDevice | Gets the dynamic registration information of device |
+
+## OTA APIs
+| API | Description |
+| :-: | :-: |
+| otaInit | Initializes OTA |
+| otaIsFetching | Determines whether the download is in progress |
+| otaIsFetchFinished | Determines whether the download is completed |
+| otaReportUpgradeSuccess | Reports update success message |
+| otaReportUpgradeFail | Reports update failure message |
+| otaIoctlNumber | Gets the information of the downloaded firmware in `int` type, such as the size |
+| otaIoctlString | Gets the information of the downloaded firmware in `String` type, such as MD5 |
+| otaResetMd5 | Resets MD5 information |
+| otaMd5Update | Updates MD5 information |
+| httpInit | Initializes HTTP |
+| otaReportVersion | Reports the information of current firmware version |
+| otaDownloadStart | Starts firmware download |
+| otaFetchYield | Reads firmware |
+
+## Log APIs
+| API | Description |
+| :-: | :-: |
+| logInit | Initializes log |
\ No newline at end of file
diff --git "a/explorer/doc/en/PRELIM__\344\272\213\344\273\266\344\270\212\346\212\245\344\273\245\345\217\212\345\244\232\344\272\213\344\273\266\344\270\212\346\212\245_EN-US.md" "b/explorer/doc/en/PRELIM__\344\272\213\344\273\266\344\270\212\346\212\245\344\273\245\345\217\212\345\244\232\344\272\213\344\273\266\344\270\212\346\212\245_EN-US.md"
new file mode 100644
index 0000000..052e0c8
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__\344\272\213\344\273\266\344\270\212\346\212\245\344\273\245\345\217\212\345\244\232\344\272\213\344\273\266\344\270\212\346\212\245_EN-US.md"
@@ -0,0 +1,162 @@
+* [Event Reporting and Multi-Event Reporting](#Event-Reporting-and-Multi-Event-Reporting)
+ * [Publishing to topic for reporting event](#Publishing-to-topic-for-reporting-event)
+ * [Publishing to topic for reporting multiple events](#Publishing-to-topic-for-reporting-multiple-events)
+
+# Event Reporting and Multi-Event Reporting
+
+This document describes how a device publishes to topics for event reporting and multi-event reporting.
+
+## Publishing to topic for reporting event
+
+Run [TemplateSample.py](../../explorer/sample/template/example_template.py). After the device is connected successfully, it will initialize the data template, call the `templateEventPost()` API for event reporting, and publish to the event topic:
+`$thing/up/event/{ProductID}/{DeviceName}`
+
+Below is the sample code:
+```python
+# Construct QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# Initialize the log
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# Register the MQTT callback
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# Get the product ID and device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# Connect to MQTT
+qcloud.connect()
+
+# Initialize the data template
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# Construct an event
+timestamp = int(round(time.time() * 1000))
+event = {
+ "events": [
+ {
+ "eventId": "status_report",
+ "type": "info",
+ "timestamp": timestamp,
+ "params": {
+ "status":0,
+ "message":"event test"
+ }
+ }
+ ]
+}
+# Report the event
+qcloud.templateEventPost(product_id, device_name, event)
+
+# Disconnect from MQTT
+qcloud.disconnect()
+```
+
+Observe the output log.
+```
+2021-07-21 16:59:37,208.208 [log.py:35] - DEBUG - [event post] {'events': [{'eventId': 'status_report', 'type': 'info', 'timestamp': 1626857977209, 'params': {'status': 0, 'message': 'event test'}}]}
+2021-07-21 16:59:37,209.209 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m5), 'b'$thing/up/event/xxx/dev1'', ... (182 bytes)
+2021-07-21 16:59:37,209.209 [log.py:35] - DEBUG - publish success
+2021-07-21 16:59:37,261.261 [client.py:2165] - DEBUG - Received PUBACK (Mid: 5)
+2021-07-21 16:59:37,262.262 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 16:59:37,286.286 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/event/xxx/dev1', ... (85 bytes)
+2021-07-21 16:59:37,287.287 [log.py:35] - DEBUG - product_1:on_template_event:payload:{'method': 'events_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': '', 'data': {}},userdata:None
+```
+The above log represents the process in which the device publishes to the topic for reporting a single event successfully. As can be seen, the device publishes an event successfully and receives the `event_reply` from the cloud. In the information of the device created in the console, you can view the corresponding device event. If the `type` passed in is `info`, the event is of the information type. For more information on how to view device events in the console, please see [Device Debugging](https://cloud.tencent.com/document/product/1081/34741).
+
+## Publishing to topic for reporting multiple events
+
+Run [TemplateSample.py](../../explorer/sample/template/example_template.py). After the device is connected successfully, it will initialize the data template, call the `templateEventPost()` API for event reporting, and publish to the event topic:
+`$thing/up/event/{ProductID}/{DeviceName}`
+
+Below is the sample code:
+```python
+# Construct a JSON message
+def report_json_construct_events(event_list):
+ # deal events and add your real value
+ status = 1
+ message = "test"
+ voltage = 20.0
+ name = "memory"
+ error_code = 0
+ timestamp = int(round(time.time() * 1000))
+
+ format_string = '"%s":"%s",'
+ format_int = '"%s":%d,'
+ events = []
+ for event in event_list:
+ string = '{'
+ string += format_string % ("eventId", event.event_name)
+ string += format_string % ("type", event.type)
+ string += format_int % ("timestamp", timestamp)
+ string += '"params":{'
+ for prop in event.events_prop:
+ if (prop.type == "int" or prop.type == "float"
+ or prop.type == "bool" or prop.type == "enum"):
+ if prop.key == "status":
+ string += format_int % (prop.key, status)
+ elif prop.key == "voltage":
+ string += format_int % (prop.key, voltage)
+ elif prop.key == "error_code":
+ string += format_int % (prop.key, error_code)
+ elif prop.type == "string":
+ if prop.key == "message":
+ string += format_string % (prop.key, message)
+ elif prop.key == "name":
+ string += format_string % (prop.key, name)
+
+ string = string[:len(string) - 1]
+ string += "}}"
+ events.append(json.loads(string))
+
+ json_out = '{"events":%s}' % json.dumps(events)
+
+ return json.loads(json_out)
+
+# Construct QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# Initialize the log
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# Register the MQTT callback
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# Get the product ID and device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# Connect to MQTT
+qcloud.connect()
+
+# Initialize the data template
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# Get the event list from the configuration file
+event_list = qcloud.getEventsList(product_id, device_name)
+# Construct a JSON event structure based on the event list
+events = report_json_construct_events(event_list)
+# Report the event
+qcloud.templateEventPost(product_id, device_name, events)
+
+# Disconnect from MQTT
+qcloud.disconnect()
+```
+
+Observe the output log.
+```
+2021-07-21 16:48:28,464.464 [log.py:35] - DEBUG - [event post] {'events': [{'eventId': 'status_report', 'type': 'info', 'timestamp': 1626857308464, 'params': {'status': 1, 'message': 'test'}}, {'eventId': 'low_voltage', 'type': 'alert', 'timestamp': 1626857308464, 'params': {'voltage': 20}}, {'eventId': 'hardware_fault', 'type': 'fault', 'timestamp': 1626857308464, 'params': {'name': 'memory', 'error_code': 0}}]}
+2021-07-21 16:48:28,464.464 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m5), 'b'$thing/up/event/xxx/dev1'', ... (409 bytes)
+2021-07-21 16:48:28,464.464 [log.py:35] - DEBUG - publish success
+2021-07-21 16:48:28,508.508 [client.py:2165] - DEBUG - Received PUBACK (Mid: 5)
+2021-07-21 16:48:28,508.508 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 16:48:28,538.538 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/event/xxx/dev1', ... (85 bytes)
+2021-07-21 16:48:28,539.539 [log.py:35] - DEBUG - on_template_event:payload:{'method': 'events_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': '', 'data': {}},userdata:None
+```
+The above log represents the process in which the device publishes to the topic for reporting multiple events successfully. As can be seen, the device publishes events successfully and receives the `events_reply` from the cloud. In the information of the device created in the console, you can view the corresponding device events. If the `type` passed in is `info`, the event is of the information type; if `alert`, the alarm type; if `fault`, the fault type. For more information on how to view device events in the console, please see [Device Debugging](https://cloud.tencent.com/document/product/1081/34741).
diff --git "a/explorer/doc/en/PRELIM__\345\212\250\346\200\201\346\263\250\345\206\214_EN-US.md" "b/explorer/doc/en/PRELIM__\345\212\250\346\200\201\346\263\250\345\206\214_EN-US.md"
new file mode 100644
index 0000000..5be625a
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__\345\212\250\346\200\201\346\263\250\345\206\214_EN-US.md"
@@ -0,0 +1,57 @@
+* [Dynamic Registration Authentication](#Dynamic-Registration-Authentication)
+ * [Overview](#Overview)
+ * [Enabling dynamic registration in console](#Enabling-dynamic-registration-in-console)
+ * [Running demo for dynamic registration](#Running-demo-for-dynamic-registration)
+
+# Dynamic Registration Authentication
+## Overview
+This feature assigns a unified key to all devices under the same product, and a device gets a device certificate/key through a registration request for authentication. You can burn the same configuration information for the same batch of devices. For more information on the dynamic registration request, please see [Dynamic Registration API Description](https://cloud.tencent.com/document/product/1081/47612).
+
+If you enable automatic device creation in the console, you don't need to create devices in advance, but you must guarantee that device names are unique under the same product ID, which are generally unique device identifiers (such as MAC address). This method is more flexible. If you disable it in the console, you must create devices in advance and enter the same device names during dynamic registration, which is more secure but less convenient.
+
+## Enabling dynamic registration in console
+To use the dynamic registration feature, you need to enable it when creating a product in the console and save the `productSecret` information of the product. The settings in the console are as shown below:
+
+
+## Running demo for dynamic registration
+Before running the demo, you need to enter the product information obtained in the console in the [device_info.json](../../explorer/sample/device_info.json) file, with the device name to be generated in the `deviceName` field, `YOUR_DEVICE_SECRET` in the `deviceSecret` field, and the `productSecret` information generated during product creation in the console in the `productSecret` field.
+
+You can run [DynregSample.py](../../explorer/sample/dynreg/example_dynreg.py) to call the `dynregDevice()` API for dynamic registration authentication. After the dynamic registration callback gets the key or certificate information of the corresponding device, it will be returned through the returned value of the API. Below is the sample code:
+
+```
+from explorer.explorer import QcloudExplorer
+
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+ret, msg = qcloud.dynregDevice()
+if ret == 0:
+ logger.debug('dynamic register success, psk: {}'.format(msg))
+else:
+ logger.error('dynamic register fail, msg: {}'.format(msg))
+```
+
+The following is the log of successful authentication for dynamic device registration.
+```
+dynamic register test success, psk: xxxxxxx
+2021-07-15 15:41:24,838.838 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-15 15:41:24,838.838 [log.py:35] - DEBUG - connect_async...8883
+2021-07-15 15:41:25,275.275 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxxx'
+2021-07-15 15:41:25,332.332 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-15 15:41:25,332.332 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-15 15:41:25,840.840 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxxx/xxxx', 0)]
+2021-07-15 15:41:25,840.840 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/xxx
+2021-07-15 15:41:25,840.840 [log.py:35] - DEBUG - pub topic:$sys/operation/xxx/xxx,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-15 15:41:25,841.841 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/xxx'', ... (37 bytes)
+2021-07-15 15:41:25,841.841 [log.py:35] - DEBUG - publish success
+2021-07-15 15:41:25,841.841 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-15 15:41:25,893.893 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-15 15:41:25,893.893 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-15 15:41:25,910.910 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/xxx/xxx', ... (82 bytes)
+2021-07-15 15:41:26,042.042 [log.py:35] - DEBUG - current time:2021-07-15 15:41:25
+2021-07-15 15:41:26,042.042 [log.py:35] - DEBUG - disconnect
+2021-07-15 15:41:26,042.042 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-15 15:41:26,043.043 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
+2021-07-15 15:41:26,043.043 [log.py:35] - DEBUG - LoopThread thread exit
+```
+As can be seen, the dynamic registration is successful, the device key is obtained, the MQTT is connected successfully, the NTP time is synced, and the `deviceSecret` field is updated in the [device_info.json](../../explorer/sample/device_info.json) configuration file.
\ No newline at end of file
diff --git "a/explorer/doc/en/PRELIM__\345\261\236\346\200\247\344\270\212\346\212\245_EN-US.md" "b/explorer/doc/en/PRELIM__\345\261\236\346\200\247\344\270\212\346\212\245_EN-US.md"
new file mode 100644
index 0000000..965cd11
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__\345\261\236\346\200\247\344\270\212\346\212\245_EN-US.md"
@@ -0,0 +1,113 @@
+* [Attribute Reporting](#Attribute-Reporting)
+ * [Publishing to topic for reporting attribute](#Publishing-to-topic-for-reporting-attribute)
+
+# Attribute Reporting
+
+When you create a product in the IoT Explorer console, a data template and some standard features will be generated for it by default. You can also customize the features. Such features are divided into three categories: attribute, event, and action. For more information on how to use a data template in the console, please see [Data Template](https://cloud.tencent.com/document/product/1081/44921).
+
+After a data template is defined for a product, the device can report attributes and events according to the definitions in the data template, and you can also deliver remote control instructions to the device to modify its writable attributes. For more information on how to manage a data template, please see Product Definition. The data template protocol includes device attribute reporting, remote device control, device-reported latest information acquisition, device event reporting, and device action triggering. For more information on the corresponding definitions and the topics used by the cloud to deliver control instructions, please see [Thing Model Protocol](https://cloud.tencent.com/document/product/1081/34916).
+
+This document describes how to report the values of the associated attributes in the data template.
+
+## Publishing to topic for reporting attribute
+
+Run [TemplateSample.py](../../explorer/sample/template/example_template.py). After the device is connected successfully, it will initialize the data template, call the `templateReport()` API to report attributes, and publish to the attribute topic:
+`$thing/up/property/{ProductID}/{DeviceName}`
+
+Below is the sample code:
+```python
+# Construct a JSON message
+def report_json_construct_property(thing_list):
+
+ format_string = '"%s":"%s"'
+ format_int = '"%s":%d'
+ report_string = '{'
+ arg_cnt = 0
+
+ for arg in thing_list:
+ arg_cnt += 1
+ if arg.type == "int" or arg.type == "float" or arg.type == "bool" or arg.type == "enum":
+ report_string += format_int % (arg.key, arg.data)
+ elif arg.type == "string":
+ report_string += format_string % (arg.key, arg.data)
+ else:
+ logger.err_code("type[%s] not support" % arg.type)
+ arg.data = " "
+ if arg_cnt < len(thing_list):
+ report_string += ","
+ pass
+ report_string += '}'
+
+ json_out = json.loads(report_string)
+
+ return json_out
+
+# Construct QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# Initialize the log
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# Register the MQTT callback
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# Get the product ID and device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# Connect to MQTT
+qcloud.connect()
+
+# Initialize the data template
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# Get the attribute list from the configuration file
+prop_list = qcloud.getPropertyList(product_id, device_name)
+# Construct a JSON attribute structure based on the attribute list
+reports = report_json_construct_property(prop_list)
+# Construct an attribute message
+params_in = qcloud.templateJsonConstructReportArray(product_id, device_name, reports)
+# Report the attributes
+qcloud.templateReport(product_id, device_name, params_in)
+
+# Disconnect from MQTT
+qcloud.disconnect()
+```
+
+Observe the output log.
+```
+2021-07-21 15:41:23,702.702 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-21 15:41:23,703.703 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-21 15:41:24,117.117 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-21 15:41:24,176.176 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-21 15:41:24,176.176 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-21 15:41:24,704.704 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$thing/down/property/xxx/dev1', 0)]
+2021-07-21 15:41:24,705.705 [log.py:35] - DEBUG - subscribe success topic:$thing/down/property/xxx/dev1
+2021-07-21 15:41:24,705.705 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m2) [(b'$thing/down/action/xxx/dev1', 0)]
+2021-07-21 15:41:24,705.705 [log.py:35] - DEBUG - subscribe success topic:$thing/down/action/xxx/dev1
+2021-07-21 15:41:24,705.705 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'$thing/down/event/xxx/dev1', 0)]
+2021-07-21 15:41:24,706.706 [log.py:35] - DEBUG - subscribe success topic:$thing/down/event/xxx/dev1
+2021-07-21 15:41:24,706.706 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m4) [(b'$thing/down/service/xxx/dev1', 0)]
+2021-07-21 15:41:24,706.706 [log.py:35] - DEBUG - subscribe success topic:$thing/down/service/xxx/dev1
+2021-07-21 15:41:24,754.754 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 15:41:24,755.755 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 15:41:24,755.755 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-21 15:41:24,755.755 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:4,userdata:None
+2021-07-21 15:41:24,755.755 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 15:41:24,756.756 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:2,userdata:None
+2021-07-21 15:41:24,756.756 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 15:41:24,756.756 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:3,userdata:None
+2021-07-21 15:41:26,068.068 [log.py:35] - DEBUG - [template report] {'method': 'report', 'clientToken': 'xxx-0', 'params': {'power_switch': 0, 'color': 0, 'brightness': 0, 'name': ''}}
+2021-07-21 15:41:26,068.068 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$thing/up/property/xxx/dev1'', ... (123 bytes)
+2021-07-21 15:41:26,068.068 [log.py:35] - DEBUG - publish success
+2021-07-21 15:41:26,069.069 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 15:41:26,149.149 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (82 bytes)
+2021-07-21 15:41:26,149.149 [log.py:35] - DEBUG - product_1:on_template_property:params:{'method': 'report_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': 'success'},userdata:None
+2021-07-21 15:41:26,151.151 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$thing/up/property/xxx/dev1'', ... (52 bytes)
+2021-07-21 15:41:26,151.151 [log.py:35] - DEBUG - publish success
+2021-07-21 15:41:26,151.151 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+```
+As can be seen from the log, after the demo is started, it subscribes to the topics related to the data template, reports the attribute message through the `$thing/up/property/{ProductID}/{DeviceName}` topic, and receives the `report_reply` message from the cloud. You can view the log of the device created in the console. In the online debugging section, you can see that the attribute values of the device have changed to the reported ones. For more information on how to view the device logs and debug devices online, please see [Device Debugging](https://cloud.tencent.com/document/product/1081/34741).
+
diff --git "a/explorer/doc/en/PRELIM__\346\216\247\345\210\266\350\256\276\345\244\207\344\270\212\344\270\213\347\272\277_EN-US.md" "b/explorer/doc/en/PRELIM__\346\216\247\345\210\266\350\256\276\345\244\207\344\270\212\344\270\213\347\272\277_EN-US.md"
new file mode 100644
index 0000000..9af0eeb
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__\346\216\247\345\210\266\350\256\276\345\244\207\344\270\212\344\270\213\347\272\277_EN-US.md"
@@ -0,0 +1,171 @@
+* [Getting Started](#Getting-Started)
+ * [Creating device in console](#Creating-device-in-console)
+ * [Running demo](#Running-demo)
+ * [Key authentication for connection](#Key-authentication-for-connection)
+ * [Certificate authentication for connection](#Certificate-authentication-for-connection)
+ * [Device connection](#Device-connection)
+ * [Device disconnection](#Device-disconnection)
+
+# Getting Started
+This document describes how to create a device in the IoT Explorer console and quickly try out how it connects to Tencent Cloud over MQTT and disconnects from MQTT with the aid of the demo.
+
+## Creating device in console
+
+Before connecting devices to the SDK, you need to create project products and devices in the console and get the product ID, device name, device certificate (for certificate authentication), device private key (for certificate authentication), and device key (for key authentication), which are required for authentication of the devices when you connect them to the cloud. For more information, please see [Project Management](https://cloud.tencent.com/document/product/1081/40290), [Product Definition](https://cloud.tencent.com/document/product/1081/34739), and [Device Debugging](https://cloud.tencent.com/document/product/1081/34741).
+
+## Running demo
+
+You can run the [MqttSample.py](../../explorer/sample/mqtt/example_mqtt.py) demo to try out how a device connects and disconnects through key authentication and certificate authentication.
+
+#### Key authentication for connection
+Edit the parameter configuration information in the [device_info.json](../../explorer/sample/device_info.json) file in the demo.
+```
+{
+ "auth_mode":"KEY",
+
+ "productId":"xxx",
+ "productSecret":"YOUR_PRODUCT_SECRET",
+ "deviceName":"xxx",
+
+ "key_deviceinfo":{
+ "deviceSecret":"xxx"
+ },
+
+ "cert_deviceinfo":{
+ "devCaFile":"YOUR_DEVICE_CA_FILE_NAME",
+ "devCertFile":"YOUR_DEVICE_CERT_FILE_NAME",
+ "devPrivateKeyFile":"YOUR_DEVICE_PRIVATE_KEY_FILE_NAME"
+ },
+
+ "subDev":{
+ "subdev_num":4,
+ "subdev_list":
+ [
+ {"sub_productId": "", "sub_devName": ""},
+ {"sub_productId": "", "sub_devName": ""},
+ {"sub_productId": "", "sub_devName": ""},
+ {"sub_productId": "", "sub_devName": ""}
+ ]
+ },
+
+ "region":"china"
+}
+```
+To use key authentication, you need to enter `productId` (product ID), `deviceName`(device name), and `deviceSecret` (device key) in `device_info.json` and specify the value of the `auth_mode` field as `KEY`. Key authentication is used in the demo.
+
+#### Certificate authentication for connection
+
+To use certificate authentication, you need to download the device certificate from the console, save it on the device, enter the `devCaFile` (`ca` certificate), `devCertFile` (`cert` certificate), and `devPrivateKeyFile` (`private` certificate) fields in the `device_info.json` configuration file to specify the absolute path of the certificate, and specify the value of the `auth_mode` field as `CERT`.
+```
+{
+ "auth_mode":"CERT",
+
+ "productId":"xxx",
+ "productSecret":"YOUR_PRODUCT_SECRET",
+ "deviceName":"xxx",
+
+ "key_deviceinfo":{
+ "deviceSecret":"YOUR_DEVICE_SECRET"
+ },
+
+ "cert_deviceinfo":{
+ "devCaFile":"CA_FILE_PATH",
+ "devCertFile":"CERT_FILE_PATH",
+ "devPrivateKeyFile":"PRIVATEKEY_FILE_PATH"
+ },
+
+ "subDev":{
+ "subdev_num":4,
+ "subdev_list":
+ [
+ {"sub_productId": "", "sub_devName": ""},
+ {"sub_productId": "", "sub_devName": ""},
+ {"sub_productId": "", "sub_devName": ""},
+ {"sub_productId": "", "sub_devName": ""}
+ ]
+ },
+
+ "region":"china"
+}
+```
+
+#### Device connection
+
+Below is the sample code:
+```python
+# Construct QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# Initialize the log
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# Register the MQTT callback
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# Connect to MQTT
+qcloud.connect()
+```
+
+Observe the log.
+```
+2021-07-22 10:49:52,302.302 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-22 10:49:52,302.302 [log.py:43] - INFO - connect with key...
+2021-07-22 10:49:52,302.302 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-22 10:49:53,068.068 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-22 10:49:53,179.179 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-22 10:49:53,179.179 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-22 10:49:53,304.304 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxx/dev1', 0)]
+2021-07-22 10:49:53,306.306 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/dev1
+2021-07-22 10:49:53,307.307 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/dev1'', ... (37 bytes)
+2021-07-22 10:49:53,308.308 [log.py:35] - DEBUG - publish success
+2021-07-22 10:49:53,310.310 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-22 10:49:53,416.416 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-22 10:49:53,416.416 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-22 10:49:53,424.424 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/xxx/dev1', ... (82 bytes)
+2021-07-22 10:49:53,511.511 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m3) [b'$sys/operation/result/xxx/dev1']
+2021-07-22 10:49:53,512.512 [log.py:35] - DEBUG - current time:2021-07-22 10:49:53
+```
+The above log represents the process in which the device connects to the cloud over MQTT through key authentication successfully and requests the NTP time. In the console, you can see that the status of the device has been updated to `online`.
+
+```
+2021-07-22 10:47:33,080.080 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-22 10:47:33,080.080 [log.py:43] - INFO - connect with certificate...
+2021-07-22 10:47:33,081.081 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-22 10:47:33,609.609 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-22 10:47:33,693.693 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-22 10:47:33,694.694 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-22 10:47:34,081.081 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxx/dev001', 0)]
+2021-07-22 10:47:34,082.082 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/dev001
+2021-07-22 10:47:34,082.082 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/dev001'', ... (37 bytes)
+2021-07-22 10:47:34,082.082 [log.py:35] - DEBUG - publish success
+2021-07-22 10:47:34,083.083 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-22 10:47:34,170.170 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-22 10:47:34,170.170 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-22 10:47:34,181.181 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/xxx/dev001', ... (82 bytes)
+2021-07-22 10:47:34,283.283 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m3) [b'$sys/operation/result/xxx/dev001']
+2021-07-22 10:47:34,284.284 [log.py:35] - DEBUG - current time:2021-07-22 10:47:34
+```
+The above log represents the process in which the device connects to the cloud over MQTT through certificate authentication successfully and requests the NTP time. In the console, you can see that the status of the device has been updated to `online`.
+
+#### Device disconnection
+
+Below is the sample code:
+```python
+# Disconnect from MQTT
+qcloud.disconnect()
+```
+
+Observe the output log.
+```
+2021-07-21 17:40:42,080.080 [log.py:35] - DEBUG - disconnect
+2021-07-21 17:40:42,081.081 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-21 17:40:42,081.081 [log.py:35] - DEBUG - LoopThread thread exit
+```
+The above log represents the process in which the device disconnects from MQTT through key authentication successfully. In the console, you can see that the status of the device has been updated to `offline`.
+
+```
+2021-07-22 10:47:34,285.285 [log.py:35] - DEBUG - disconnect
+2021-07-22 10:47:34,285.285 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-22 10:47:34,286.286 [log.py:35] - DEBUG - LoopThread thread exit
+```
+The above log represents the process in which the device disconnects from MQTT through certificate authentication successfully. In the console, you can see that the status of the device has been updated to `offline`.
\ No newline at end of file
diff --git "a/explorer/doc/en/PRELIM__\346\243\200\346\237\245\345\233\272\344\273\266\346\233\264\346\226\260_EN-US.md" "b/explorer/doc/en/PRELIM__\346\243\200\346\237\245\345\233\272\344\273\266\346\233\264\346\226\260_EN-US.md"
new file mode 100644
index 0000000..481bb41
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__\346\243\200\346\237\245\345\233\272\344\273\266\346\233\264\346\226\260_EN-US.md"
@@ -0,0 +1,81 @@
+* [Checking for Firmware Update](#Checking-for-Firmware-Update)
+ * [Subscribing and publishing to topic for checking for firmware update](#Subscribing-and-publishing-to-topic-for-checking-for-firmware-update)
+ * [Updating firmware](#Updating-firmware)
+
+# Checking for Firmware Update
+Device firmware update (aka OTA) is an important part of the IoT Hub service. When a device has new features available or vulnerabilities that need to be fixed, firmware update can be quickly performed for it through the OTA service. For more information, please see [Firmware Update](https://cloud.tencent.com/document/product/634/14673).
+
+To try out firmware update, you need to add a new firmware file in the console. For more information, please see [Device Firmware Update](https://cloud.tencent.com/document/product/634/14674).
+
+This document describes how to subscribe and publish to the topic for checking for firmware update.
+
+## Subscribing and publishing to topic for checking for firmware update
+
+Run [OtaSample.py](../../explorer/sample/ota/example_ota.py). After the device is connected, it will subscribe to the relevant topic, check the current firmware version, and report it.
+
+The relevant topics are as follows:
+* Subscribe to the topic for checking for firmware update: `$ota/update/${productID}/${deviceName}`
+* Publish to the topic for checking for firmware update: `$ota/report/${productID}/${deviceName}`
+
+Observe the log:
+```
+2021-07-19 10:54:26,800.800 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-19 10:54:26,800.800 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-19 10:54:28,159.159 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-19 10:54:28,384.384 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-19 10:54:28,385.385 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-19 10:54:28,803.803 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$ota/update/xxx/xxx', 1)]
+2021-07-19 10:54:28,803.803 [log.py:35] - DEBUG - subscribe success topic:$ota/update/xxx/xxx
+2021-07-19 10:54:28,932.932 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 10:54:28,932.932 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:1,userdata:None
+2021-07-19 10:54:29,004.004 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m2), 'b'$ota/report/xxx/xxx'', ... (58 bytes)
+2021-07-19 10:54:29,005.005 [log.py:35] - DEBUG - publish success
+2021-07-19 10:54:29,144.144 [client.py:2165] - DEBUG - Received PUBACK (Mid: 2)
+2021-07-19 10:54:29,145.145 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (86 bytes)
+2021-07-19 10:54:29,147.147 [log.py:35] - DEBUG - on_ota_report:payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'},userdata:None
+2021-07-19 10:54:30,006.006 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:31,008.008 [log.py:35] - DEBUG - wait for ota upgrade command
+```
+As can be seen from the above log, after the demo goes online, it subscribes to the `$ota/update/xxx/xxx` topic (`xxx/xxx` is the real `product_id` and `device_name`) to receive commands delivered by the cloud, checks the current local firmware version, reports the version number (`0.1.0` here) to the cloud through the `$ota/report/xxx/xxx` topic, receives a reply from the cloud, and waits for the update command.
+
+## Updating firmware
+
+In the firmware update module of the IoT Explorer console, you can upload a new version of the firmware for a product, update the firmware of a specified device, and update the firmware of devices in batches. For more information, please see [Firmware Update](https://cloud.tencent.com/document/product/1081/40296).
+
+After the firmware update operation is triggered in the console, the device will receive a firmware update message through the subscribed `$ota/update/${productID}/${deviceName}` topic, start downloading the firmware, and regularly report the download progress.
+```
+2021-07-19 10:54:44,032.032 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:45,034.034 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:45,409.409 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (468 bytes)
+2021-07-19 10:54:46,036.036 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:46,036.036 [log.py:47] - ERROR - info file not exists
+2021-07-19 10:54:46,036.036 [log.py:47] - ERROR - local_size:0,local_ver:None,re_ver:1.0.0
+__ota_http_deinit do nothing
+2021-07-19 10:54:47,052.052 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:54:47,052.052 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$ota/report/xxx/xxx'', ... (151 bytes)
+2021-07-19 10:54:47,053.053 [log.py:35] - DEBUG - publish success
+2021-07-19 10:54:48,032.032 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:54:48,032.032 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m4), 'b'$ota/report/xxx/xxx'', ... (151 bytes)
+2021-07-19 10:54:48,032.032 [log.py:35] - DEBUG - publish success
+2021-07-19 10:54:49,118.118 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '1', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:54:49,119.119 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$ota/report/xxx/xxx'', ... (151 bytes)
+2021-07-19 10:54:49,119.119 [log.py:35] - DEBUG - publish success
+```
+The above log represents the process in which the device receives the firmware version 1.0.0 update message successfully, and the SDK calls back the firmware download progress and reports it. At this point, you can see that the new firmware OTA update package is already at the download path passed in.
+
+After the update is completed, the device will report the update result and wait for a reply from the cloud.
+```
+2021-07-19 10:57:25,537.537 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '100', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:57:25,537.537 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m117), 'b'$ota/report/xxx/xxx'', ... (153 bytes)
+2021-07-19 10:57:25,537.537 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - The firmware download success
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - burning firmware...
+2021-07-19 10:57:25,538.538 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m118), 'b'$ota/report/xxx/xxx'', ... (128 bytes)
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:25,539.539 [log.py:35] - DEBUG - wait for ack...
+2021-07-19 10:57:25,641.641 [client.py:2165] - DEBUG - Received PUBACK (Mid: 118)
+2021-07-19 10:57:25,642.642 [log.py:35] - DEBUG - publish ack id 118
+2021-07-19 10:57:28,042.042 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m119), 'b'$ota/report/xxx/xxx'', ... (58 bytes)
+2021-07-19 10:57:28,043.043 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:28,128.128 [client.py:2165] - DEBUG - Received PUBACK (Mid: 119)
+```
diff --git "a/explorer/doc/en/PRELIM__\346\270\205\351\231\244\346\216\247\345\210\266_EN-US.md" "b/explorer/doc/en/PRELIM__\346\270\205\351\231\244\346\216\247\345\210\266_EN-US.md"
new file mode 100644
index 0000000..abb5731
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__\346\270\205\351\231\244\346\216\247\345\210\266_EN-US.md"
@@ -0,0 +1,64 @@
+* [Clearing Control](#Clearing-Control)
+ * [Publishing to topic for clearing control](#Publishing-to-topic-for-clearing-control)
+
+# Clearing Control
+
+This document describes how a device delivers an instruction to clear control.
+
+## Publishing to topic for clearing control
+
+Run [TemplateSample.py](../../explorer/sample/template/example_template.py). After the device is connected successfully, it will initialize the data template and call `clearControl()` to clear control when needed. The topic for control clearing is:
+`$thing/up/property/{ProductID}/{DeviceName}`
+
+Below is the sample code:
+```python
+# Construct QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# Initialize the log
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# Register the MQTT callback
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# Get the product ID and device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# Connect to MQTT
+qcloud.connect()
+
+# Initialize the data template
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# Clear control
+qcloud.clearControl(product_id, device_name)
+
+# Disconnect from MQTT
+qcloud.disconnect()
+```
+
+Observe the output log.
+```
+2021-07-21 15:57:20,128.128 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$thing/up/property/xxx/dev1'', ... (55 bytes)
+2021-07-21 15:57:20,129.129 [log.py:35] - DEBUG - publish success
+2021-07-21 15:57:20,129.129 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 15:57:20,204.204 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (160 bytes)
+2021-07-21 15:57:20,205.205 [log.py:35] - DEBUG - on_template_property:params:{'method': 'get_status_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': 'success', 'data': {'reported': {'name': '', 'power_switch': 0, 'color': 0, 'brightness': 0}}},userdata:None
+2021-07-21 15:57:20,205.205 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$thing/up/property/xxx/dev1'', ... (62 bytes)
+2021-07-21 15:57:20,205.205 [log.py:35] - DEBUG - publish success
+2021-07-21 15:57:20,205.205 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+7
+2021-07-21 15:57:21,965.965 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m7), 'b'$thing/up/property/xxx/dev1'', ... (58 bytes)
+2021-07-21 15:57:21,966.966 [log.py:35] - DEBUG - publish success
+2021-07-21 15:57:21,966.966 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-21 15:57:22,038.038 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (89 bytes)
+2021-07-21 15:57:22,038.038 [log.py:35] - DEBUG - on_template_property:params:{'method': 'clear_control_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': 'success'},userdata:None
+2021-07-21 15:57:22,038.038 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m8), 'b'$thing/up/property/xxx/dev1'', ... (62 bytes)
+2021-07-21 15:57:22,038.038 [log.py:35] - DEBUG - publish success
+2021-07-21 15:57:22,039.039 [log.py:35] - DEBUG - on_publish:mid:8,userdata:None
+```
+As can been seen from the log, the demo publishes a control clearing message successfully and receives the `clear_control_reply` reply from the cloud.
+
diff --git "a/explorer/doc/en/PRELIM__\347\275\221\345\205\263\344\275\277\347\224\250\347\244\272\344\276\213_EN-US.md" "b/explorer/doc/en/PRELIM__\347\275\221\345\205\263\344\275\277\347\224\250\347\244\272\344\276\213_EN-US.md"
new file mode 100644
index 0000000..8590ac7
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__\347\275\221\345\205\263\344\275\277\347\224\250\347\244\272\344\276\213_EN-US.md"
@@ -0,0 +1,183 @@
+* [Gateway Use Cases](#Gateway-Use-Cases)
+ * [Creating gateway device in console](#Creating-gateway-device-in-console)
+ * [Creating gateway product and device](#Creating-gateway-product-and-device)
+ * [Defining subdevice data template](#Defining-subdevice-data-template)
+ * [Running demo](#Running-demo)
+ * [Entering parameters for authenticating device for connection](#Entering-parameters-for-authenticating-device-for-connection)
+ * [Proxying subdevice connection and disconnection](#Proxying-subdevice-connection-and-disconnection)
+ * [Binding and unbinding subdevice](#Binding-and-unbinding-subdevice)
+ * [Proxying subdevice communication based on data template](#Proxying-subdevice-communication-based-on-data-template)
+
+# Gateway Use Cases
+
+This document describes how to apply for a gateway device and bind a subdevice to it in the IoT Explorer console, and quickly try out proxying subdevice connection/disconnection for the subdevice to send/receive messages based on the data template protocol or custom data with the aid of the [GatewaySample.py](../../explorer/sample/gateway/example_gateway.py) demo of the SDK.
+
+## Creating gateway device in console
+#### Creating gateway product and device
+To use the gateway demo, you need to create a gateway device and a general device in the IoT Explorer console and bind the latter to the former as a subdevice. For more information, please see [Gateway Device Connection](https://cloud.tencent.com/document/product/1081/43417).
+
+#### Defining subdevice data template
+After creating a subdevice, you need to define its data template. You can use the default data template when trying out the demo. For more information, please see [Guide to Connecting Smart Light](https://cloud.tencent.com/document/product/1081/41155).
+
+
+## Running demo
+You can run the [GatewaySample.py](../../explorer/sample/gateway/example_gateway.py) demo to try out how a gateway proxies subdevice connection/disconnection, binds/unbinds subdevices, and proxies subdevices' message communication based on their respective data templates.
+
+#### Entering parameters for authenticating device for connection
+Enter the information of the device created in the console in [device_info.json](../../explorer/sample/device_info.json), such as the `auth_mode`, `productId`, `deviceName`, and `deviceSecret` fields of a key-authenticated device as well as certain fields of a subdevice (`subDev`), as shown below:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"test02",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+"subDev":{
+ "subdev_num":2,
+ "subdev_list":
+ [
+ {"sub_productId": "xxxx", "sub_devName": "dev1"},
+ {"sub_productId": "xxxx", "sub_devName": "dev001"}
+ ]
+}
+```
+
+#### Proxying subdevice connection and disconnection
+In the configuration file of the demo, two subdevices are bound to the gateway device `test02`, with their `device_name` being `dev1` and `dev001` respectively.
+* Proxied subdevice disconnection through a gateway
+Offline subdevices can be connected through the gateway:
+```
+2021-07-20 14:12:29,913.913 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$gateway/operation/xxx/test02'', ... (97 bytes)
+2021-07-20 14:12:29,913.913 [log.py:35] - DEBUG - publish success
+2021-07-20 14:12:29,913.913 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-20 14:12:30,029.029 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/NCUL2VSYG6/test02', ... (101 bytes)
+2021-07-20 14:12:30,114.114 [log.py:35] - DEBUG - client:xxx/dev1 online success
+2021-07-20 14:12:30,114.114 [log.py:35] - DEBUG - online success
+2021-07-20 14:12:30,114.114 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$gateway/operation/xxx/test02'', ... (99 bytes)
+2021-07-20 14:12:30,115.115 [log.py:35] - DEBUG - publish success
+2021-07-20 14:12:30,115.115 [log.py:35] - DEBUG - on_publish:mid:3,userdata:None
+2021-07-20 14:12:30,249.249 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test02', ... (103 bytes)
+2021-07-20 14:12:30,315.315 [log.py:35] - DEBUG - client:xxx/dev001 online success
+2021-07-20 14:12:30,315.315 [log.py:35] - DEBUG - online success
+```
+As can be seen from the log, the two subdevices `dev1` and `dev001` are connected successfully (`online success`). At this point, you can see in the console that the subdevices are online.
+
+* Proxied subdevice disconnection through a gateway
+Online subdevices can be disconnected through the gateway:
+```
+2021-07-20 14:14:50,962.962 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m4), 'b'$gateway/operation/xxx/test02'', ... (98 bytes)
+2021-07-20 14:14:50,963.963 [log.py:35] - DEBUG - publish success
+2021-07-20 14:14:50,963.963 [log.py:35] - DEBUG - on_publish:mid:4,userdata:None
+2021-07-20 14:14:51,037.037 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test02', ... (102 bytes)
+2021-07-20 14:14:51,163.163 [log.py:35] - DEBUG - client:xxx/dev1 offline success
+2021-07-20 14:14:51,164.164 [log.py:35] - DEBUG - offline success
+2021-07-20 14:14:51,165.165 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$gateway/operation/xxx/test02'', ... (100 bytes)
+2021-07-20 14:14:51,166.166 [log.py:35] - DEBUG - publish success
+2021-07-20 14:14:51,167.167 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-20 14:14:51,247.247 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test02', ... (104 bytes)
+2021-07-20 14:14:51,368.368 [log.py:35] - DEBUG - client:xxx/dev001 offline success
+2021-07-20 14:14:51,368.368 [log.py:35] - DEBUG - offline success
+```
+As can be seen from the log, the two subdevices just connected are disconnected successfully (`offline success`) through the gateway. At this point, you can see that they are offline in the console.
+
+
+#### Binding and unbinding subdevice
+* Bind a subdevice
+Subdevices not bound to a gateway can be bound on the device side.
+```
+2021-07-20 14:18:28,801.801 [log.py:35] - DEBUG - sign base64 ********************
+2021-07-20 14:18:28,801.801 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$gateway/operation/xxx/test02'', ... (233 bytes)
+2021-07-20 14:18:28,802.802 [log.py:35] - DEBUG - publish success
+2021-07-20 14:18:28,802.802 [log.py:35] - DEBUG - on_publish:mid:3,userdata:None
+2021-07-20 14:18:28,873.873 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test02', ... (101 bytes)
+2021-07-20 14:18:29,003.003 [log.py:35] - DEBUG - client:xxx/dev001 bind success
+2021-07-20 14:18:29,003.003 [log.py:35] - DEBUG - bind success
+```
+As can be seen from the log, the `dev001` subdevice is bound to the gateway successfully. At this point, you can see in the console that `dev001` is already in the subdevice list.
+
+* Unbind a subdevice
+Subdevices bound to a gateway can be unbound on the device side.
+```
+2021-07-20 14:17:04,807.807 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$gateway/operation/xxx/test02'', ... (99 bytes)
+2021-07-20 14:17:04,807.807 [log.py:35] - DEBUG - publish success
+2021-07-20 14:17:04,808.808 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-20 14:17:04,914.914 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test02', ... (103 bytes)
+2021-07-20 14:17:05,009.009 [log.py:35] - DEBUG - client:xxx/dev001 unbind success
+2021-07-20 14:17:05,009.009 [log.py:35] - DEBUG - unbind success
+```
+As can be seen from the log, the `dev001` subdevice is unbound from the gateway successfully. At this point, you can see in the console that `dev001` is not in the subdevice list.
+。
+
+#### Proxying subdevice communication based on data template
+A data template defines a general method for describing and controlling devices in a unified manner and thus provides data flow and computing services to enable data interconnection, flow, and fusion between different devices and help with application implementation. For the specific protocol, please see [Thing Model Protocol](https://cloud.tencent.com/document/product/1081/34916).
+
+A gateway device processes multiple subdevice transactions through multiple threads and initializes the subdevice data template in the subdevice transaction processing threads. This process will subscribe to the data template topics of the subdevices. The message communication between the subdevices and the cloud should be sent to the gateway device to proxy subdevices' message sending/receiving.
+
+* Proxy the subdevice's topic subscription
+```
+2021-07-20 14:34:16,975.975 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m2) [(b'xxx/dev001/data', 0)]
+2021-07-20 14:34:16,976.976 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'xxx/dev1/data', 0)]
+2021-07-20 14:34:16,977.977 [log.py:35] - DEBUG - subscribe success topic:xxx/dev001/data
+2021-07-20 14:34:16,977.977 [log.py:35] - DEBUG - gateway subdev subscribe success
+2021-07-20 14:34:16,977.977 [log.py:35] - DEBUG - subscribe success topic:xxx/dev1/data
+2021-07-20 14:34:16,977.977 [log.py:35] - DEBUG - gateway subdev subscribe success
+2021-07-20 14:34:16,977.977 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m4) [(b'$thing/down/property/xxx/dev001', 0)]
+2021-07-20 14:34:16,978.978 [log.py:35] - DEBUG - subscribe success topic:$thing/down/property/xxx/dev001
+2021-07-20 14:34:16,978.978 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m5) [(b'$thing/down/action/xxx/dev001', 0)]
+2021-07-20 14:34:16,978.978 [log.py:35] - DEBUG - subscribe success topic:$thing/down/action/xxx/dev001
+2021-07-20 14:34:16,978.978 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m6) [(b'$thing/down/event/xxx/dev001', 0)]
+2021-07-20 14:34:16,978.978 [log.py:35] - DEBUG - subscribe success topic:$thing/down/event/xxx/dev001
+2021-07-20 14:34:16,979.979 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m7) [(b'$thing/down/service/xxx/dev001', 0)]
+2021-07-20 14:34:16,979.979 [log.py:35] - DEBUG - subscribe success topic:$thing/down/service/xxx/dev001
+2021-07-20 14:34:16,979.979 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m9) [(b'$thing/down/property/xxx/dev1', 0)]
+2021-07-20 14:34:16,980.980 [log.py:35] - DEBUG - subscribe success topic:$thing/down/property/xxx/dev1
+2021-07-20 14:34:16,981.981 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m11) [(b'$thing/down/action/xxx/dev1', 0)]
+2021-07-20 14:34:16,981.981 [log.py:35] - DEBUG - subscribe success topic:$thing/down/action/xxx/dev1
+2021-07-20 14:34:16,982.982 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m13) [(b'$thing/down/event/xxx/dev1', 0)]
+2021-07-20 14:34:16,983.983 [log.py:35] - DEBUG - subscribe success topic:$thing/down/event/xxx/dev1
+2021-07-20 14:34:16,983.983 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m14) [(b'$thing/down/service/xxx/dev1', 0)]
+2021-07-20 14:34:16,983.983 [log.py:35] - DEBUG - subscribe success topic:$thing/down/service/xxx/dev1
+2021-07-20 14:34:17,063.063 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,063.063 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:4,userdata:None
+2021-07-20 14:34:17,065.065 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,065.065 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,065.065 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,065.065 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:6,userdata:None
+2021-07-20 14:34:17,066.066 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:5,userdata:None
+2021-07-20 14:34:17,066.066 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:3,userdata:None
+2021-07-20 14:34:17,066.066 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,066.066 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:2,userdata:None
+2021-07-20 14:34:17,070.070 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,070.070 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:13,userdata:None
+2021-07-20 14:34:17,070.070 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,070.070 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:7,userdata:None
+2021-07-20 14:34:17,070.070 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,071.071 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:14,userdata:None
+2021-07-20 14:34:17,071.071 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,071.071 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:11,userdata:None
+2021-07-20 14:34:17,071.071 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,071.071 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:9,userdata:None
+```
+As can be seen from the above log, the gateway device subscribes to the `${product_id}/${device_name}/data` topic of the two subdevices to receive the messages delivered by the cloud and then subscribes to the topics related to their data templates after initializing the data templates to receive event notifications and report attributes.
+
+* Receive the control message from the subdevice
+Debug the subdevice by modifying its attributes in **Device Debugging** > **Online Debugging** > **Attribute Debugging** in the console and deliver them to the device.
+```
+2021-07-20 14:34:22,861.861 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (152 bytes)
+2021-07-20 14:34:22,862.862 [log.py:35] - DEBUG - on_template_property:params:{'method': 'control', 'clientToken': 'clientToken-2842aa2d-a9c8-48e3-9160-e13aafa338b3', 'params': {'power_switch': 1, 'color': 2, 'brightness': 5, 'name': 'dev1'}},userdata:None
+2021-07-20 14:34:20,194.194 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev001', ... (155 bytes)
+2021-07-20 14:34:20,194.194 [log.py:35] - DEBUG - product_1:on_template_property:params:{'method': 'control', 'clientToken': 'clientToken-502eae4d-642f-47c0-b6a9-47b6e15c8f4f', 'params': {'power_switch': 1, 'color': 1, 'brightness': 10, 'name': 'dev001'}},userdata:None
+```
+As can be seen from the log, the gateway device receives the delivered control message successfully. At this point, it should deliver the message to the corresponding subdevice.
+
+* Receive the action message from the subdevice
+Debug the subdevice by selecting the action of turning light on in **Device Debugging** > **Online Debugging** > **Action Invocation** in the console and deliver the information to the subdevice.
+```
+2021-07-20 14:34:29,164.164 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/action/xxx/dev1', ... (143 bytes)
+2021-07-20 14:34:29,164.164 [log.py:35] - DEBUG - on_template_action:payload:{'method': 'action', 'clientToken': '146761673::81d315f7-17d9-4d00-9ce0-1ff782c25666', 'actionId': 'c_sw', 'timestamp': 1626762869, 'params': {'sw': 1}},userdata:None
+2021-07-20 14:34:35,394.394 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/action/xxx/dev001', ... (150 bytes)
+2021-07-20 14:34:35,394.394 [log.py:35] - DEBUG - on_template_action:payload:{'method': 'action', 'clientToken': '146761676::a27e4649-d94e-4acb-b636-5bfe7f4a592d', 'actionId': 'light_on', 'timestamp': 1626762875, 'params': {'is_on': 1}},userdata:None
+```
+As can be seen from the log, the gateway receives the turn-light-on message successfully. At this point, it should deliver the message to the corresponding subdevice.
\ No newline at end of file
diff --git "a/explorer/doc/en/PRELIM__\350\216\267\345\217\226\350\256\276\345\244\207\346\234\200\346\226\260\344\270\212\346\212\245\344\277\241\346\201\257_EN-US.md" "b/explorer/doc/en/PRELIM__\350\216\267\345\217\226\350\256\276\345\244\207\346\234\200\346\226\260\344\270\212\346\212\245\344\277\241\346\201\257_EN-US.md"
new file mode 100644
index 0000000..ecef9f5
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__\350\216\267\345\217\226\350\256\276\345\244\207\346\234\200\346\226\260\344\270\212\346\212\245\344\277\241\346\201\257_EN-US.md"
@@ -0,0 +1,88 @@
+* [Getting Latest Information Reported by Device](#Getting-Latest-Information-Reported-by-Device)
+ * [Publishing to topic for getting latest information reported by device](#Publishing-to-topic-for-getting-latest-information-reported-by-device)
+
+# Getting Latest Information Reported by Device
+
+When you create a product in the IoT Explorer console, a data template and some standard features will be generated for it by default. You can also customize the features. Such features are divided into three categories: attribute, event, and action. For more information on how to use a data template in the console, please see [Data Template](https://cloud.tencent.com/document/product/1081/44921).
+
+After a data template is defined for a product, the device can report attributes and events according to the definitions in the data template, and you can also deliver remote control instructions to the device to modify its writable attributes. For more information on how to manage a data template, please see Product Definition. The data template protocol includes device attribute reporting, remote device control, device-reported latest information acquisition, device event reporting, and device action triggering. For more information on the corresponding definitions and the topics used by the cloud to deliver control instructions, please see [Thing Model Protocol](https://cloud.tencent.com/document/product/1081/34916).
+
+This document describes how to get the latest information reported by a device in a data template.
+
+## Publishing to topic for getting latest information reported by device
+
+Run [TemplateSample.py](../../explorer/sample/template/example_template.py). After the device is connected successfully, it will initialize the data template and then call the async `templatGetStatus()` API to get the latest information. The obtained information will be notified to the registered callback function.
+
+Below is the sample code:
+```python
+# Call back message receipt
+def on_template_property(topic, qos, payload, userdata):
+ logger.debug("%s:params:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+
+ # save changed propertys
+ global g_property_params
+ g_property_params = payload
+
+ global g_control_msg_arrived
+ g_control_msg_arrived = True
+
+ # deal down stream and add your real value
+
+ reply_param = qcloud.ReplyPara()
+ reply_param.code = 0
+ reply_param.timeout_ms = 5 * 1000
+ reply_param.status_msg = '\0'
+
+ qcloud.templateControlReply(product_id, device_name, reply_param)
+ pass
+
+# Construct QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# Initialize the log
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# Register the MQTT callback
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# Get the product ID and device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# Connect to MQTT
+qcloud.connect()
+
+# Initialize the data template
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# Get the latest information
+qcloud.templateGetStatus(product_id, device_name)
+
+# Disconnect from MQTT
+qcloud.disconnect()
+```
+
+Observe the output log.
+```
+2021-07-21 16:16:46,271.271 [log.py:35] - DEBUG - [template report] {'method': 'report', 'clientToken': 'xxx-0', 'params': {'power_switch': 1, 'color': 1, 'brightness': 1, 'name': 'test'}}
+2021-07-21 16:16:46,272.272 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$thing/up/property/xxx/dev1'', ... (127 bytes)
+2021-07-21 16:16:46,272.272 [log.py:35] - DEBUG - publish success
+2021-07-21 16:16:46,272.272 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 16:16:46,373.373 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (82 bytes)
+2021-07-21 16:16:46,373.373 [log.py:35] - DEBUG - on_template_property:params:{'method': 'report_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': 'success'},userdata:None
+2021-07-21 16:16:46,373.373 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$thing/up/property/xxx/dev1'', ... (52 bytes)
+2021-07-21 16:16:46,374.374 [log.py:35] - DEBUG - publish success
+2021-07-21 16:16:46,374.374 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+2021-07-21 16:16:48,930.930 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m7), 'b'$thing/up/property/xxx/dev1'', ... (55 bytes)
+2021-07-21 16:16:48,931.931 [log.py:35] - DEBUG - publish success
+2021-07-21 16:16:48,931.931 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-21 16:16:49,023.023 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (164 bytes)
+2021-07-21 16:16:49,023.023 [log.py:35] - DEBUG - on_template_property:params:{'method': 'get_status_reply', 'clientToken': 'xxx-1', 'code': 0, 'status': 'success', 'data': {'reported': {'name': 'test', 'power_switch': 1, 'color': 1, 'brightness': 1}}},userdata:None
+2021-07-21 16:16:49,023.023 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m8), 'b'$thing/up/property/xxx/dev1'', ... (62 bytes)
+2021-07-21 16:16:49,024.024 [log.py:35] - DEBUG - publish success
+2021-07-21 16:16:49,024.024 [log.py:35] - DEBUG - on_publish:mid:8,userdata:None
+```
+As can be seen from the log, after the demo is started, it first reports an attribute message, where the value of the `name` field is `test` and the values of the `power_switch` and other fields are all `1`. The field values obtained by getting the latest information reported by the device are exactly the same as those reported. In addition, you can also view the corresponding latest values of the device attributes in the console, which you will find the same as the attribute values in the `data` parameter of the received subscription message. For more information on how to view the device attributes and debug devices online, please see [Device Debugging](https://cloud.tencent.com/document/product/1081/34741).
+
diff --git "a/explorer/doc/en/PRELIM__\350\256\242\351\230\205\344\270\216\345\217\226\346\266\210\350\256\242\351\230\205Topic_EN-US.md" "b/explorer/doc/en/PRELIM__\350\256\242\351\230\205\344\270\216\345\217\226\346\266\210\350\256\242\351\230\205Topic_EN-US.md"
new file mode 100644
index 0000000..340af20
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__\350\256\242\351\230\205\344\270\216\345\217\226\346\266\210\350\256\242\351\230\205Topic_EN-US.md"
@@ -0,0 +1,159 @@
+* [Subscribing and Unsubscribing](#Subscribing-and-Unsubscribing)
+ * [Subscribing to topic associated with data template](#Subscribing-to-topic-associated-with-data-template)
+ * [Receiving device binding/unbinding notification messages](#Receiving-device-binding/unbinding-notification-messages)
+ * [Unsubscribing from topic](#Unsubscribing-from-topic)
+
+# Subscribing and Unsubscribing
+
+When you create a product in the IoT Explorer console, a data template and some standard features will be generated for it by default. You can also customize the features. Such features are divided into three categories: attribute, event, and action. For more information on how to use a data template in the console, please see [Data Template](https://cloud.tencent.com/document/product/1081/44921).
+
+After a data template is defined for a product, the device can report attributes and events according to the definitions in the data template, and you can also deliver remote control instructions to the device to modify its writable attributes. For more information on how to manage a data template, please see Product Definition. The data template protocol includes device attribute reporting, remote device control, device-reported latest information acquisition, device event reporting, and device action triggering. For more information on the corresponding definitions and the topics used by the cloud to deliver control instructions, please see [Thing Model Protocol](https://cloud.tencent.com/document/product/1081/34916).
+
+This document describes how to subscribe to/unsubscribe from a topic associated with a data template.
+
+## Subscribing to topic associated with data template
+
+Run [TemplateSample.py](../../explorer/sample/template/example_template.py), and the initialized data template will automatically subscribe to the attribute, event, and action topics associated with it:
+```
+$thing/down/property/{ProductID}/{DeviceName}
+$thing/down/event/{ProductID}/{DeviceName}
+$thing/down/action/{ProductID}/{DeviceName}
+$thing/down/service/{ProductID}/{DeviceName}
+```
+After topic subscription, the corresponding downstream messages will be provided by the callback function registered during the initialization of the data template, which is defined as follows:
+```python
+def on_template_property(topic, qos, payload, userdata):
+ """Attribute callback
+ Receive the downstream message from `$thing/down/property/{ProductID}/{DeviceName}`
+ Args:
+ topic: downstream topic
+ qos: qos
+ payload: downstream message content
+ userdata: any structure registered by user
+ """
+ pass
+
+def on_template_service(topic, qos, payload, userdata):
+ """Service callback
+ Receive the downstream message from `$thing/down/service/{ProductID}/{DeviceName}`
+ Args:
+ topic: downstream topic
+ qos: qos
+ payload: downstream message content
+ userdata: any structure registered by user
+ """
+ pass
+
+def on_template_event(topic, qos, payload, userdata):
+ """Event callback
+ Receive the downstream message from `$thing/down/event/{ProductID}/{DeviceName}`
+ Args:
+ topic: downstream topic
+ qos: qos
+ payload: downstream message content
+ userdata: any structure registered by user
+ """
+ pass
+
+def on_template_action(topic, qos, payload, userdata):
+ """Action callback
+ Receive the downstream message from `$thing/down/action/{ProductID}/{DeviceName}`
+ Args:
+ topic: downstream topic
+ qos: qos
+ payload: downstream message content
+ userdata: any structure registered by user
+ """
+ pass
+```
+
+Below is the sample code:
+```python
+# Construct QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# Initialize the log
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# Register the MQTT callback
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# Get the product ID and device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# Connect to MQTT
+qcloud.connect()
+
+# Initialize the data template and automatically subscribe to the relevant topics
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+```
+
+Observe the log.
+```
+2021-07-21 16:59:34,956.956 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-21 16:59:34,956.956 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-21 16:59:35,432.432 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-21 16:59:35,491.491 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-21 16:59:35,491.491 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-21 16:59:35,958.958 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$thing/down/property/xxx/dev1', 0)]
+2021-07-21 16:59:35,958.958 [log.py:35] - DEBUG - subscribe success topic:$thing/down/property/xxx/dev1
+2021-07-21 16:59:35,959.959 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m2) [(b'$thing/down/action/xxx/dev1', 0)]
+2021-07-21 16:59:35,959.959 [log.py:35] - DEBUG - subscribe success topic:$thing/down/action/xxx/dev1
+2021-07-21 16:59:35,960.960 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'$thing/down/event/xxx/dev1', 0)]
+2021-07-21 16:59:35,960.960 [log.py:35] - DEBUG - subscribe success topic:$thing/down/event/xxx/dev1
+2021-07-21 16:59:35,960.960 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m4) [(b'$thing/down/service/xxx/dev1', 0)]
+2021-07-21 16:59:35,960.960 [log.py:35] - DEBUG - subscribe success topic:$thing/down/service/xxx/dev1
+2021-07-21 16:59:36,006.006 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 16:59:36,006.006 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-21 16:59:36,009.009 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 16:59:36,010.010 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 16:59:36,010.010 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:2,userdata:None
+2021-07-21 16:59:36,010.010 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:4,userdata:None
+2021-07-21 16:59:36,016.016 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 16:59:36,016.016 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:3,userdata:None
+```
+As can be seen from the log, the device subscribes to the topics successfully.
+
+## Receiving device binding/unbinding notification messages
+After the data template is initialized, you can run the demo and use Tencent IoT Link to try out the downstream message receiving feature.
+* Receive device binding notification
+Display the device QR code in the console, scan it with Tencent IoT Link to bind the device, and the device will receive the `bind_device` message. The log is as follows:
+```
+2021-07-29 15:09:09,407.407 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/service/xxx/xxx', ... (86 bytes)
+2021-07-29 15:09:09,407.407 [log.py:35] - DEBUG - on_template_service:payload:{'method': 'bind_device', 'clientToken': 'clientToken-8l1b8SX3cw', 'timestamp': 1627542549},userdata:None
+```
+
+* Receive device unbinding notification
+After you choose to delete the device on Tencent IoT Link, it will receive the `unbind_device` message. The log is as follows:
+```
+2021-07-29 15:09:28,343.343 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/service/xxx/xxx', ... (118 bytes)
+2021-07-29 15:09:28,345.345 [log.py:35] - DEBUG - on_template_service:payload:{'method': 'unbind_device', 'DeviceId': 'xxx/xxx', 'clientToken': 'clientToken-Bcjwl8Io0', 'timestamp': 1627542568},userdata:None
+```
+
+## Unsubscribing from topic
+
+Run [TemplateSample.py](../../explorer/sample/template/example_template.py) and call the `templateDeinit()` API to unsubscribe from the topic when exiting the demo.
+
+Below is the sample code:
+```python
+# Delete the data template
+qcloud.templateDeinit(product_id, device_name)
+
+# Disconnect from MQTT
+qcloud.disconnect()
+```
+
+Observe the output log.
+```
+2021-07-21 17:21:33,833.833 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m5) [b'$thing/down/property/xxx/dev1', b'$thing/down/event/xxx/dev1', b'$thing/down/action/xxx/dev1', b'$thing/down/service/xxx/dev1']
+2021-07-21 17:21:33,913.913 [client.py:2165] - DEBUG - Received UNSUBACK (Mid: 5)
+2021-07-21 17:21:33,914.914 [log.py:35] - DEBUG - on_unsubscribe:mid:5,userdata:None
+2021-07-21 17:21:35,218.218 [log.py:35] - DEBUG - disconnect
+2021-07-21 17:21:35,218.218 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-21 17:21:35,219.219 [log.py:35] - DEBUG - LoopThread thread exit
+2021-07-21 17:21:35,219.219 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
+```
+As can be seen from the log, the device unsubscribes from the topic successfully.
diff --git "a/explorer/doc/en/PRELIM__\350\256\276\345\244\207\344\277\241\346\201\257\344\270\212\346\212\245_EN-US.md" "b/explorer/doc/en/PRELIM__\350\256\276\345\244\207\344\277\241\346\201\257\344\270\212\346\212\245_EN-US.md"
new file mode 100644
index 0000000..948b881
--- /dev/null
+++ "b/explorer/doc/en/PRELIM__\350\256\276\345\244\207\344\277\241\346\201\257\344\270\212\346\212\245_EN-US.md"
@@ -0,0 +1,88 @@
+* [Device Information Reporting](#Device-Information-Reporting)
+ * [Publishing to topic for reporting device information](#Publishing-to-topic-for-reporting-device-information)
+
+# Device Information Reporting
+
+This document describes how to report device information to the cloud.
+
+## Publishing to topic for reporting device information
+
+Run [TemplateSample.py](../../explorer/sample/template/example_template.py). After the device is connected successfully, it will initialize the data template and then call the `templateReportSysInfo()` API to report the device information. The topic for device information reporting is:
+`$thing/up/property/{ProductID}/{DeviceName}`
+
+Below is the sample code:
+```python
+# Call back message receipt
+def on_template_property(topic, qos, payload, userdata):
+ logger.debug("%s:params:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+
+ # save changed propertys
+ global g_property_params
+ g_property_params = payload
+
+ global g_control_msg_arrived
+ g_control_msg_arrived = True
+
+ # deal down stream and add your real value
+
+ reply_param = qcloud.ReplyPara()
+ reply_param.code = 0
+ reply_param.timeout_ms = 5 * 1000
+ reply_param.status_msg = '\0'
+
+ qcloud.templateControlReply(product_id, device_name, reply_param)
+ pass
+
+# Construct QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# Initialize the log
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# Register the MQTT callback
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# Get the product ID and device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# Connect to MQTT
+qcloud.connect()
+
+# Initialize the data template
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# Simulate the device information
+sys_info = {
+ "module_hardinfo": "ESP8266",
+ "module_softinfo": "V1.0",
+ "fw_ver": "3.1.4",
+ "imei": "11-22-33-44",
+ "lat": "22.546015",
+ "lon": "113.941125",
+ "device_label": {
+ "append_info": "your self define info"
+ }
+}
+# Report the device information
+qcloud.templateReportSysInfo(product_id, device_name, sys_info)
+
+# Disconnect from MQTT
+qcloud.disconnect()
+```
+
+Observe the output log.
+```
+2021-07-21 16:39:33,894.894 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$thing/up/property/xxx/dev1'', ... (266 bytes)
+2021-07-21 16:39:33,895.895 [log.py:35] - DEBUG - publish success
+2021-07-21 16:39:33,896.896 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 16:39:33,965.965 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (87 bytes)
+2021-07-21 16:39:33,966.966 [log.py:35] - DEBUG - on_template_property:params:{'method': 'report_info_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': 'success'},userdata:None
+2021-07-21 16:39:33,966.966 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$thing/up/property/xxx/dev1'', ... (52 bytes)
+2021-07-21 16:39:33,966.966 [log.py:35] - DEBUG - publish success
+2021-07-21 16:39:33,966.966 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+```
+As can be seen from the log, the device reports the information successfully and receives a reply from the cloud.
+
diff --git a/explorer/doc/en/README.md b/explorer/doc/en/README.md
new file mode 100644
index 0000000..f5f70b1
--- /dev/null
+++ b/explorer/doc/en/README.md
@@ -0,0 +1,37 @@
+[简体中文](../../../explorer) | English
+
+* [IoT Explorer Device SDK for Python](#IoT-Explorer-Device-SDK-for-Python)
+ * [Prerequisites](#Prerequisites)
+ * [Project configuration](#Project-configuration)
+ * [Downloading the sample code of IoT Explorer SDK for Python demo](#Downloading-the-sample-code-of-IoT-Explorer-SDK-for-Python-demo)
+ * [Feature documentation](#Feature-documentation)
+ * [SDK API description](#SDK-API-description)
+
+# IoT Explorer Device SDK for Python
+Welcome to the IoT Explorer device SDK for Python.
+
+The IoT Explorer device SDK for Python works with the device data template defined by the platform to implement a framework for data interaction between devices and the cloud based on the data template protocol. You can quickly implement data interaction between devices and the platform as well as between devices and applications based on the framework. This document describes how to get and call the IoT Explorer SDK for Python. If you encounter any issues when using it, please [feel free to submit them at GitHub](https://github.com/tencentyun/iot-device-python/issues/new).
+
+## Prerequisites
+* Create a Tencent Cloud account and activate IoT Explorer in the Tencent Cloud console.
+* Create project products and devices in the console and get the product ID, device name, device certificate (for certificate authentication), device private key (for certificate authentication), and device key (for key authentication), which are required for authentication of the devices when you connect them to the cloud. For detailed directions, please see [Project Management](https://cloud.tencent.com/document/product/1081/40290), [Product Definition](https://cloud.tencent.com/document/product/1081/34739), and [Device Debugging](https://cloud.tencent.com/document/product/1081/34741).
+
+## Project configuration
+The SDK can be installed through pip or through source code locally. For more information on how to connect, please see [SDK Connection Description](doc/SDK-Connection-Description.md).
+
+## Downloading the code of IoT Explorer SDK for Python demo
+Download the complete code in the [repository](../../../). The sample code of the IoT Explorer SDK for Python demo is in the [iot-device-python/sample](../../../tree/master/sample) directory.
+
+## Feature documentation
+For more information on how to call the APIs, please see the demos of the following corresponding features.
+
+* [Controlling Device Connection and Disconnection](doc/Controlling-Device-Connection-and-Disconnection.md)
+* [Dynamic Registration](doc/Dynamic-Registration.md)
+* [Subscribing to and Unsubscribing from Topic](doc/Subscribing-to-and-Unsubscribing-from-Topic.md)
+* [Attribute Reporting](doc/Attribute-Reporting.md)
+* [Getting Latest Information Reported by Device](doc/Getting-Latest-Information-Reported-by-Device.md)
+* [Device Information Reporting](doc/Device-Information-Reporting.md)
+* [Clearing Control](doc/Clearing-Control.md)
+* [Event Reporting and Multi-Event Reporting](doc/Event-Reporting-and-Multi-Event-Reporting.md)
+* [Checking for Firmware Update](doc/Checking-for-Firmware-Update.md)
+* [Gateway Use Cases](doc/Gateway-Use-Cases.md)
diff --git "a/explorer/doc/\344\272\213\344\273\266\344\270\212\346\212\245\344\273\245\345\217\212\345\244\232\344\272\213\344\273\266\344\270\212\346\212\245.md" "b/explorer/doc/\344\272\213\344\273\266\344\270\212\346\212\245\344\273\245\345\217\212\345\244\232\344\272\213\344\273\266\344\270\212\346\212\245.md"
index 613eff2..c22814d 100644
--- "a/explorer/doc/\344\272\213\344\273\266\344\270\212\346\212\245\344\273\245\345\217\212\345\244\232\344\272\213\344\273\266\344\270\212\346\212\245.md"
+++ "b/explorer/doc/\344\272\213\344\273\266\344\270\212\346\212\245\344\273\245\345\217\212\345\244\232\344\272\213\344\273\266\344\270\212\346\212\245.md"
@@ -8,12 +8,33 @@
## 发布事件上报的 Topic
-运行 [MqttSample.py](../sample/MqttSample.py) 的main函数,设备成功上线后,成功订阅过Topic后,调用eventSinglePost(),发布事件类型的 Topic:
+运行 [TemplateSample.py](../../explorer/sample/template/example_template.py) ,设备成功上线后,初始化数据模板,之后调用`templateEventPost()`接口进行事件上报,发布事件类型的 Topic:
`$thing/up/event/{ProductID}/{DeviceName}`
示例代码如下:
-
-```
+```python
+# 构造QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# 初始化日志
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# 注册mqtt回调
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# 获取设备product id和device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# mqtt连接
+qcloud.connect()
+
+# 数据模板初始化
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# 构建事件
timestamp = int(round(time.time() * 1000))
event = {
"events": [
@@ -23,93 +44,119 @@ event = {
"timestamp": timestamp,
"params": {
"status":0,
- "message":""
+ "message":"event test"
}
- }
+ }
]
}
-te.template_event_post(event)
+# 上报事件
+qcloud.templateEventPost(product_id, device_name, event)
+
+# 断开mqtt连接
+qcloud.disconnect()
```
观察输出日志。
-
```
-2021-03-16 15:01:05,841.841 [explorer.py:198] - DEBUG - pub topic:$thing/up/event/ZPHBLEB4J5/dev001,payload:{'method': 'events_post', 'clientToken': 'ZPHBLEB4J5-0', 'events': [{'eventId': 'status_report', 'type': 'info', 'timestamp': 1615878065841, 'params': {'status': 1, 'message': 'message'}}, {'eventId': 'low_voltage', 'type': 'alert', 'timestamp': 1615878065841, 'params': {'voltage': 20.0}}, {'eventId': 'hardware_fault', 'type': 'fault', 'timestamp': 1615878065841, 'params': {'name': 'memory', 'error_code': 0}}]},qos:1
-2021-03-16 15:01:05,841.841 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m6), 'b'$thing/up/event/ZPHBLEB4J5/dev001'', ... (414 bytes)
-2021-03-16 15:01:05,841.841 [explorer.py:198] - DEBUG - publish success
-2021-03-16 15:01:05,841.841 [explorer.py:198] - DEBUG - mid:6
-2021-03-16 15:01:05,965.965 [client.py:2165] - DEBUG - Received PUBACK (Mid: 6)
-on_publish:mid:6,userdata:None
-2021-03-16 15:01:05,966.966 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/event/ZPHBLEB4J5/dev001', ... (85 bytes)
-2021-03-16 15:01:05,966.966 [explorer.py:206] - INFO - __user_thread_on_message_callback,topic:$thing/down/event/ZPHBLEB4J5/dev001,payload:{'method': 'events_reply', 'clientToken': 'ZPHBLEB4J5-0', 'code': 0, 'status': '', 'data': {}},mid:0
-on_template_event_post:payload:{'method': 'events_reply', 'clientToken': 'ZPHBLEB4J5-0', 'code': 0, 'status': '', 'data': {}},userdata:None
+2021-07-21 16:59:37,208.208 [log.py:35] - DEBUG - [event post] {'events': [{'eventId': 'status_report', 'type': 'info', 'timestamp': 1626857977209, 'params': {'status': 0, 'message': 'event test'}}]}
+2021-07-21 16:59:37,209.209 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m5), 'b'$thing/up/event/xxx/dev1'', ... (182 bytes)
+2021-07-21 16:59:37,209.209 [log.py:35] - DEBUG - publish success
+2021-07-21 16:59:37,261.261 [client.py:2165] - DEBUG - Received PUBACK (Mid: 5)
+2021-07-21 16:59:37,262.262 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 16:59:37,286.286 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/event/xxx/dev1', ... (85 bytes)
+2021-07-21 16:59:37,287.287 [log.py:35] - DEBUG - product_1:on_template_event:payload:{'method': 'events_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': '', 'data': {}},userdata:None
```
-以上是设备成功发布单个事件上报Topic的日志。如果已订阅 Topic,设备会接收到如上日志中的event_reply消息。在控制台创建的对应设备中,可查看到对应的设备事件,若传入的type为info时,代表信息类型的事件。控制台中查看设备事件,请参考 [设备调试](https://cloud.tencent.com/document/product/1081/34741) 章节。
+以上是设备成功发布单个事件上报Topic的日志。可以看到设备成功发布事件并收到云端响应`event_reply`。在控制台创建的对应设备中,可查看到对应的设备事件,若传入的type为info时,代表信息类型的事件。控制台中查看设备事件,请参考 [设备调试](https://cloud.tencent.com/document/product/1081/34741) 章节。
## 发布多事件上报的 Topic
-运行 [MqttSample.py](../sample/MqttSample.py) 的main函数,设备成功上线后,成功订阅过Topic后,调用eventsPost(),发布事件类型的 Topic:
+运行 [TemplateSample.py](../../explorer/sample/template/example_template.py) ,设备成功上线后,初始化数据模板,之后调用`templateEventPost()`接口进行事件上报,发布事件类型的 Topic:
`$thing/up/event/{ProductID}/{DeviceName}`
示例代码如下:
-
-```
-
-event_list = te.template_events_list
-
-# deal events and add your real value
-status = 1
-message = "message"
-voltage = 20.0
-name = "memory"
-error_code = 0
-timestamp = int(round(time.time() * 1000))
-
-events = {
- "events": [
- {
- "eventId": event_list[0].event_name,
- "type": event_list[0].type,
- "timestamp": timestamp,
- "params": {
- event_list[0].events_prop[0].key:status,
- event_list[0].events_prop[1].key:message
- }
- },
- {
- "eventId": event_list[1].event_name,
- "type": event_list[1].type,
- "timestamp": timestamp,
- "params": {
- event_list[1].events_prop[0].key:voltage
- }
- },
- {
- "eventId": event_list[2].event_name,
- "type": event_list[2].type,
- "timestamp": timestamp,
- "params": {
- event_list[2].events_prop[0].key:name,
- event_list[2].events_prop[1].key:error_code
- }
- }
- ]
-}
-te.template_event_post(events)
+```python
+# 构建json message
+def report_json_construct_events(event_list):
+ # deal events and add your real value
+ status = 1
+ message = "test"
+ voltage = 20.0
+ name = "memory"
+ error_code = 0
+ timestamp = int(round(time.time() * 1000))
+
+ format_string = '"%s":"%s",'
+ format_int = '"%s":%d,'
+ events = []
+ for event in event_list:
+ string = '{'
+ string += format_string % ("eventId", event.event_name)
+ string += format_string % ("type", event.type)
+ string += format_int % ("timestamp", timestamp)
+ string += '"params":{'
+ for prop in event.events_prop:
+ if (prop.type == "int" or prop.type == "float"
+ or prop.type == "bool" or prop.type == "enum"):
+ if prop.key == "status":
+ string += format_int % (prop.key, status)
+ elif prop.key == "voltage":
+ string += format_int % (prop.key, voltage)
+ elif prop.key == "error_code":
+ string += format_int % (prop.key, error_code)
+ elif prop.type == "string":
+ if prop.key == "message":
+ string += format_string % (prop.key, message)
+ elif prop.key == "name":
+ string += format_string % (prop.key, name)
+
+ string = string[:len(string) - 1]
+ string += "}}"
+ events.append(json.loads(string))
+
+ json_out = '{"events":%s}' % json.dumps(events)
+
+ return json.loads(json_out)
+
+# 构造QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# 初始化日志
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# 注册mqtt回调
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# 获取设备product id和device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# mqtt连接
+qcloud.connect()
+
+# 数据模板初始化
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# 获取配置文件中事件列表
+event_list = qcloud.getEventsList(product_id, device_name)
+# 基于事件列表构建事件json结构
+events = report_json_construct_events(event_list)
+# 事件上报
+qcloud.templateEventPost(product_id, device_name, events)
+
+# 断开mqtt连接
+qcloud.disconnect()
```
观察输出日志。
-
```
-2021-03-16 15:01:05,841.841 [explorer.py:198] - DEBUG - pub topic:$thing/up/event/ZPHBLEB4J5/dev001,payload:{'method': 'events_post', 'clientToken': 'ZPHBLEB4J5-0', 'events': [{'eventId': 'status_report', 'type': 'info', 'timestamp': 1615878065841, 'params': {'status': 1, 'message': 'message'}}, {'eventId': 'low_voltage', 'type': 'alert', 'timestamp': 1615878065841, 'params': {'voltage': 20.0}}, {'eventId': 'hardware_fault', 'type': 'fault', 'timestamp': 1615878065841, 'params': {'name': 'memory', 'error_code': 0}}]},qos:1
-2021-03-16 15:01:05,841.841 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m6), 'b'$thing/up/event/ZPHBLEB4J5/dev001'', ... (414 bytes)
-2021-03-16 15:01:05,841.841 [explorer.py:198] - DEBUG - publish success
-2021-03-16 15:01:05,841.841 [explorer.py:198] - DEBUG - mid:6
-2021-03-16 15:01:05,965.965 [client.py:2165] - DEBUG - Received PUBACK (Mid: 6)
-on_publish:mid:6,userdata:None
-2021-03-16 15:01:05,966.966 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/event/ZPHBLEB4J5/dev001', ... (85 bytes)
-2021-03-16 15:01:05,966.966 [explorer.py:206] - INFO - __user_thread_on_message_callback,topic:$thing/down/event/ZPHBLEB4J5/dev001,payload:{'method': 'events_reply', 'clientToken': 'ZPHBLEB4J5-0', 'code': 0, 'status': '', 'data': {}},mid:0
-on_template_event_post:payload:{'method': 'events_reply', 'clientToken': 'ZPHBLEB4J5-0', 'code': 0, 'status': '', 'data': {}},userdata:None
+2021-07-21 16:48:28,464.464 [log.py:35] - DEBUG - [event post] {'events': [{'eventId': 'status_report', 'type': 'info', 'timestamp': 1626857308464, 'params': {'status': 1, 'message': 'test'}}, {'eventId': 'low_voltage', 'type': 'alert', 'timestamp': 1626857308464, 'params': {'voltage': 20}}, {'eventId': 'hardware_fault', 'type': 'fault', 'timestamp': 1626857308464, 'params': {'name': 'memory', 'error_code': 0}}]}
+2021-07-21 16:48:28,464.464 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m5), 'b'$thing/up/event/xxx/dev1'', ... (409 bytes)
+2021-07-21 16:48:28,464.464 [log.py:35] - DEBUG - publish success
+2021-07-21 16:48:28,508.508 [client.py:2165] - DEBUG - Received PUBACK (Mid: 5)
+2021-07-21 16:48:28,508.508 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 16:48:28,538.538 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/event/xxx/dev1', ... (85 bytes)
+2021-07-21 16:48:28,539.539 [log.py:35] - DEBUG - on_template_event:payload:{'method': 'events_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': '', 'data': {}},userdata:None
```
-
-以上是设备成功发布多个事件上报Topic的日志。如果已订阅 Topic,设备会接收到如上日志中的events_reply消息。在控制台创建的对应设备中,可查看到对应的设备事件,若传入的type为info时,代表信息类型的事件;若传入的type为alert时,代表告警类型的事件;若传入的type为fault时,代表故障类型的事件。控制台中查看设备事件,请参考 [设备调试](https://cloud.tencent.com/document/product/1081/34741) 章节。
+以上是设备成功发布多个事件上报Topic的日志。可以看到设备成功发布事件并收到云端响应`events_reply`。在控制台创建的对应设备中,可查看到对应的设备事件,若传入的type为info时,代表信息类型的事件;若传入的type为alert时,代表告警类型的事件;若传入的type为fault时,代表故障类型的事件。控制台中查看设备事件,请参考 [设备调试](https://cloud.tencent.com/document/product/1081/34741) 章节。
diff --git "a/explorer/doc/\345\212\250\346\200\201\346\263\250\345\206\214.md" "b/explorer/doc/\345\212\250\346\200\201\346\263\250\345\206\214.md"
index 5519d93..d8b9b1e 100755
--- "a/explorer/doc/\345\212\250\346\200\201\346\263\250\345\206\214.md"
+++ "b/explorer/doc/\345\212\250\346\200\201\346\263\250\345\206\214.md"
@@ -1,5 +1,6 @@
* [动态注册认证](#动态注册认证)
* [动态注册认证简介](#动态注册认证简介)
+ * [控制台使能动态注册](#控制台使能动态注册)
* [运行示例程序进行动态注册](#运行示例程序进行动态注册)
# 动态注册认证
@@ -8,29 +9,49 @@
若用户在控制台上开启了自动创建设备,则无需在控制台预先创建设备,但需保证同一产品下设备名称无重复,一般可以取设备的唯一信息,比如MAC地址,此种方式更加灵活。若用户在控制台上关闭了自动创建设备,则必须要预先创建设备,动态注册时的设备要与录入的设备名称一致,此种方式更加安全,但便利性有所下降。
+## 控制台使能动态注册
+要使用动态注册功能,在控制台创建产品时需要打开该产品动态注册开关,并将产品`productSecret`信息保存,控制台设置如下图所示:
+
+
## 运行示例程序进行动态注册
-运行示例程序前,需要配置 [device_info.json](../../sample/device_info.json) 文件中设备上下线所需的参数外,还需要填写PRODUCT_KEY(控制台中ProductSecret)
+运行示例程序前,需要将控制台获取到的产品信息填写到 [device_info.json](../../explorer/sample/device_info.json) 文件中,其中`deviceName`字段填写要生成的设备名字,`deviceSecret`字段保持为`YOUR_DEVICE_SECRET`,`productSecret`字段填写在控制台创建产品时生成的`productSecret`信息.
-运行 [DynregSample.py](../../sample/dynreg/example_dynreg.py) ,调用dynreg_device(),调用动态注册认证,动态注册回调获取了对应设备的密钥或证书相关信息后,再调用 MQTT 上线。示例代码如下:
+运行 [DynregSample.py](../../explorer/sample/dynreg/example_dynreg.py)会调用`dynregDevice()`接口进行动态注册认证,动态注册回调获取了对应设备的密钥或证书相关信息后,通过接口返回值返回。示例代码如下:
```
-dyn_explorer = explorer.QcloudExplorer('./device_info.json')
-ret, msg = dyn_explorer.dynreg_device()
+from explorer.explorer import QcloudExplorer
+
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+ret, msg = qcloud.dynregDevice()
if ret == 0:
- print('dynamic register success, psk: {}'.format(msg))
+ logger.debug('dynamic register success, psk: {}'.format(msg))
else:
- print('dynamic register fail, msg: {}'.format(msg))
+ logger.error('dynamic register fail, msg: {}'.format(msg))
```
-以下是设备动态注册认证成功和失败的日志。
-
+以下是设备动态注册认证成功的日志。
+```
+dynamic register test success, psk: xxxxxxx
+2021-07-15 15:41:24,838.838 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-15 15:41:24,838.838 [log.py:35] - DEBUG - connect_async...8883
+2021-07-15 15:41:25,275.275 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxxx'
+2021-07-15 15:41:25,332.332 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-15 15:41:25,332.332 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-15 15:41:25,840.840 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxxx/xxxx', 0)]
+2021-07-15 15:41:25,840.840 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/xxx
+2021-07-15 15:41:25,840.840 [log.py:35] - DEBUG - pub topic:$sys/operation/xxx/xxx,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-15 15:41:25,841.841 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/xxx'', ... (37 bytes)
+2021-07-15 15:41:25,841.841 [log.py:35] - DEBUG - publish success
+2021-07-15 15:41:25,841.841 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-15 15:41:25,893.893 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-15 15:41:25,893.893 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-15 15:41:25,910.910 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/xxx/xxx', ... (82 bytes)
+2021-07-15 15:41:26,042.042 [log.py:35] - DEBUG - current time:2021-07-15 15:41:25
+2021-07-15 15:41:26,042.042 [log.py:35] - DEBUG - disconnect
+2021-07-15 15:41:26,042.042 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-15 15:41:26,043.043 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
+2021-07-15 15:41:26,043.043 [log.py:35] - DEBUG - LoopThread thread exit
```
-2021-03-16 10:43:28,371.371 [explorer.py:206] - INFO - dynreg url https://gateway.tencentdevices.com/register/dev
-2021-03-16 10:43:33,328.328 [explorer.py:210] - INFO - encrypt type psk
-dynamic register success
-
-2021-03-16 10:43:28,371.371 [explorer.py:206] - INFO - dynreg url https://gateway.tencentdevices.com/register/dev
-2021-03-16 10:43:33,328.328 [explorer.py:210] - ERROR - code: 1021, error message: Device has been activated
-dynamic register fail, msg: Device has been activated
-```
\ No newline at end of file
+可以看到动态注册成功获取到了设备密钥且mqtt连接成功并同步了ntp时间,此时查看配置文件[device_info.json](../../explorer/sample/device_info.json)会发现`deviceSecret`字段已被更新。
\ No newline at end of file
diff --git "a/explorer/doc/\345\261\236\346\200\247\344\270\212\346\212\245.md" "b/explorer/doc/\345\261\236\346\200\247\344\270\212\346\212\245.md"
index 192a12d..f503b95 100644
--- "a/explorer/doc/\345\261\236\346\200\247\344\270\212\346\212\245.md"
+++ "b/explorer/doc/\345\261\236\346\200\247\344\270\212\346\212\245.md"
@@ -11,35 +11,103 @@
## 发布属性上报的 Topic
-运行 [MqttSample.py](../sample/MqttSample.py) 的main函数,设备成功上线后,订阅过Topic后,调用template_report(),发布属性类型的 Topic:
+运行 [TemplateSample.py](../../explorer/sample/template/example_template.py) ,设备成功上线后,初始化数据模板,之后调用`templateReport()`接口进行属性上报,发布属性类型的 Topic:
`$thing/up/property/{ProductID}/{DeviceName}`
示例代码如下:
+```python
+# 构建json message
+def report_json_construct_property(thing_list):
-```
-te = explorer.QcloudExplorer(device_file="./device_info.json")
-te.template_setup("./example_config.json")
-prop_list = te.template_property_list
- reports = {
- "power_switch": "1",
- "color": "2"
- }
-
-params_in = te.template_json_construct_report_array(reports)
-te.template_report(params_in)
-```
+ format_string = '"%s":"%s"'
+ format_int = '"%s":%d'
+ report_string = '{'
+ arg_cnt = 0
-观察输出日志。
+ for arg in thing_list:
+ arg_cnt += 1
+ if arg.type == "int" or arg.type == "float" or arg.type == "bool" or arg.type == "enum":
+ report_string += format_int % (arg.key, arg.data)
+ elif arg.type == "string":
+ report_string += format_string % (arg.key, arg.data)
+ else:
+ logger.err_code("type[%s] not support" % arg.type)
+ arg.data = " "
+ if arg_cnt < len(thing_list):
+ report_string += ","
+ pass
+ report_string += '}'
+
+ json_out = json.loads(report_string)
+
+ return json_out
+
+# 构造QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# 初始化日志
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+# 注册mqtt回调
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# 获取设备product id和device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# mqtt连接
+qcloud.connect()
+
+# 数据模板初始化
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# 获取配置文件中属性列表
+prop_list = qcloud.getPropertyList(product_id, device_name)
+# 基于属性列表构建属性json结构
+reports = report_json_construct_property(prop_list)
+# 构建属性报文
+params_in = qcloud.templateJsonConstructReportArray(product_id, device_name, reports)
+# 上报属性
+qcloud.templateReport(product_id, device_name, params_in)
+
+# 断开mqtt连接
+qcloud.disconnect()
```
-2021-03-16 14:45:01,454.454 [explorer.py:198] - DEBUG - pub topic:$thing/up/property/ZPHBLEB4J5/dev001,payload:{'method': 'report', 'clientToken': 'ZPHBLEB4J5-0', 'params': {'power_switch': 0, 'color': 0, 'brightness': 0, 'name': ''}},qos:0
-2021-03-16 14:45:01,455.455 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$thing/up/property/ZPHBLEB4J5/dev001'', ... (123 bytes)
-2021-03-16 14:45:01,455.455 [explorer.py:198] - DEBUG - publish success
-on_publish:mid:6,userdata:None
-2021-03-16 14:45:01,583.583 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/ZPHBLEB4J5/dev001', ... (82 bytes)
-2021-03-16 14:45:01,583.583 [explorer.py:206] - INFO - __user_thread_on_message_callback,topic:$thing/down/property/ZPHBLEB4J5/dev001,payload:{'method': 'report_reply', 'clientToken': 'ZPHBLEB4J5-0', 'code': 0, 'status': 'success'},mid:0
-2021-03-16 14:45:01,583.583 [explorer.py:198] - DEBUG - reply payload:{'method': 'report_reply', 'clientToken': 'ZPHBLEB4J5-0', 'code': 0, 'status': 'success'}
+观察输出日志。
+```
+2021-07-21 15:41:23,702.702 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-21 15:41:23,703.703 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-21 15:41:24,117.117 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-21 15:41:24,176.176 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-21 15:41:24,176.176 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-21 15:41:24,704.704 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$thing/down/property/xxx/dev1', 0)]
+2021-07-21 15:41:24,705.705 [log.py:35] - DEBUG - subscribe success topic:$thing/down/property/xxx/dev1
+2021-07-21 15:41:24,705.705 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m2) [(b'$thing/down/action/xxx/dev1', 0)]
+2021-07-21 15:41:24,705.705 [log.py:35] - DEBUG - subscribe success topic:$thing/down/action/xxx/dev1
+2021-07-21 15:41:24,705.705 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'$thing/down/event/xxx/dev1', 0)]
+2021-07-21 15:41:24,706.706 [log.py:35] - DEBUG - subscribe success topic:$thing/down/event/xxx/dev1
+2021-07-21 15:41:24,706.706 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m4) [(b'$thing/down/service/xxx/dev1', 0)]
+2021-07-21 15:41:24,706.706 [log.py:35] - DEBUG - subscribe success topic:$thing/down/service/xxx/dev1
+2021-07-21 15:41:24,754.754 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 15:41:24,755.755 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 15:41:24,755.755 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-21 15:41:24,755.755 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:4,userdata:None
+2021-07-21 15:41:24,755.755 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 15:41:24,756.756 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:2,userdata:None
+2021-07-21 15:41:24,756.756 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 15:41:24,756.756 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:3,userdata:None
+2021-07-21 15:41:26,068.068 [log.py:35] - DEBUG - [template report] {'method': 'report', 'clientToken': 'xxx-0', 'params': {'power_switch': 0, 'color': 0, 'brightness': 0, 'name': ''}}
+2021-07-21 15:41:26,068.068 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$thing/up/property/xxx/dev1'', ... (123 bytes)
+2021-07-21 15:41:26,068.068 [log.py:35] - DEBUG - publish success
+2021-07-21 15:41:26,069.069 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 15:41:26,149.149 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (82 bytes)
+2021-07-21 15:41:26,149.149 [log.py:35] - DEBUG - product_1:on_template_property:params:{'method': 'report_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': 'success'},userdata:None
+2021-07-21 15:41:26,151.151 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$thing/up/property/xxx/dev1'', ... (52 bytes)
+2021-07-21 15:41:26,151.151 [log.py:35] - DEBUG - publish success
+2021-07-21 15:41:26,151.151 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
```
-以上日志为 发布属性上报的 Topic 成功,如果已订阅 Topic,会接收到report_reply消息。同时在控制台创建的对应设备中,可以查看到对应的设备日志,在线调试中也可以看到设备的实时属性已更改为属性上报中对应设置的属性值。控制台中查看设备日志以及设备的在线调试,请参考 [设备调试](https://cloud.tencent.com/document/product/1081/34741) 章节。
+观察日志可以看到,程序启动后订阅了数据模板相关 Topic,之后通过 Topic:`$thing/up/property/{ProductID}/{DeviceName}`上报属性消息,成功后接收到云端的`report_reply`消息。同时在控制台创建的对应设备中,可以查看到对应的设备日志,在线调试中也可以看到设备的实时属性已更改为属性上报中对应设置的属性值。控制台中查看设备日志以及设备的在线调试,请参考 [设备调试](https://cloud.tencent.com/document/product/1081/34741) 章节。
diff --git "a/explorer/doc/\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/explorer/doc/\345\270\270\350\247\201\351\227\256\351\242\230.md"
new file mode 100644
index 0000000..574fa57
--- /dev/null
+++ "b/explorer/doc/\345\270\270\350\247\201\351\227\256\351\242\230.md"
@@ -0,0 +1,18 @@
+#### 海外设备或私有化设备接入服务器地址
+
+域名默认采用国内地址,对于海外或私有化设备的接入,需要修改对应的服务器地址,在对应各个功能模块中,初始化调用`QcloudExplorer`时,需要传入海外或私有化对应地域的服务器地址,域名相关参数说明:
+
+```
+domain :设备基于TCP接入方式下的地域服务器地址,海外或私有化设备接入,需传入对应设备域名或私有化完整URL
+
+```
+
+设备接入地域说明可参考:[官网文档](https://cloud.tencent.com/document/product/634/61228)
+
+#### 动态注册功能服务器域名
+动态注册功能默认为国内地址,如接入海外或私有化设备,需在`example_dynreg.py` 文件中调用`dynregDevice` 方法时,传入海外或私有化动态注册服务器地址,参数说明:
+##### 注意 创建产品时,控制台开启动态注册开关后才起作用
+```
+dynregDomain :设备动态注册地域服务器地址,海外或私有化设备接入,需传入对应设备域名或私有化完整URL
+
+```
\ No newline at end of file
diff --git "a/explorer/doc/\346\216\247\345\210\266\350\256\276\345\244\207\344\270\212\344\270\213\347\272\277.md" "b/explorer/doc/\346\216\247\345\210\266\350\256\276\345\244\207\344\270\212\344\270\213\347\272\277.md"
index 08993cd..a234a1e 100644
--- "a/explorer/doc/\346\216\247\345\210\266\350\256\276\345\244\207\344\270\212\344\270\213\347\272\277.md"
+++ "b/explorer/doc/\346\216\247\345\210\266\350\256\276\345\244\207\344\270\212\344\270\213\347\272\277.md"
@@ -1,9 +1,9 @@
* [快速开始](#快速开始)
* [控制台创建设备](#控制台创建设备)
- * [编译运行示例程序](#编译运行示例程序)
+ * [运行示例](#运行示例)
* [密钥认证接入](#密钥认证接入)
* [证书认证接入](#证书认证接入)
- * [运行示例程序进行 MQTT 认证连接使设备上线](#运行示例程序进行-MQTT-认证连接使设备上线)
+ * [设备上线](#设备上线)
* [设备下线](#设备下线)
# 快速开始
@@ -13,27 +13,26 @@
设备接入SDK前需要在控制台中创建项目产品设备,并获取产品ID、设备名称、设备证书(证书认证)、设备私钥(证书认证)、设备密钥(密钥认证),设备与云端认证连接时需要用到以上信息。请参考官网 [用户指南-项目管理](https://cloud.tencent.com/document/product/1081/40290)、 [用户指南-产品定义](https://cloud.tencent.com/document/product/1081/34739)、 [用户指南-设备调试](https://cloud.tencent.com/document/product/1081/34741)。
-## 编译运行示例程序
+## 运行示例
-[下载IoT Explorer Python-SDK Demo示例代码](../README.md#下载IoT-Explorer-Python-SDK-Demo示例代码)
+运行 [MqttSample.py](../../explorer/sample/mqtt/example_mqtt.py)示例体验设备使用密钥和证书认证方式进行上下线.
#### 密钥认证接入
-
-示例中编辑 [device_info.json](../src/test/resources/device_info.json) 文件中的参数配置信息
-
+示例中编辑 [device_info.json](../../explorer/sample/device_info.json) 文件中的参数配置信息
```
{
"auth_mode":"KEY",
- "productId":"YOUR_PRODUCT_ID",
+ "productId":"xxx",
"productSecret":"YOUR_PRODUCT_SECRET",
- "deviceName":"",
+ "deviceName":"xxx",
"key_deviceinfo":{
- "deviceSecret":""
+ "deviceSecret":"xxx"
},
"cert_deviceinfo":{
+ "devCaFile":"YOUR_DEVICE_CA_FILE_NAME",
"devCertFile":"YOUR_DEVICE_CERT_FILE_NAME",
"devPrivateKeyFile":"YOUR_DEVICE_PRIVATE_KEY_FILE_NAME"
},
@@ -52,66 +51,121 @@
"region":"china"
}
```
-如果控制台创建设备使用的是密钥认证方式,需要在 device_info.json 填写 PRODUCT_ID(产品ID)、DEVICE_NAME(设备名称)、DEVICE_PSK(设备密钥),示例中使用的是密钥认证。
+密钥认证方式需要在`device_info.json`填写`productId`(产品ID)、`deviceName`(设备名称)、`deviceSecret`(设备密钥),并且需要指定`auth_mode`字段为`KEY`,示例中使用的是密钥认证。
#### 证书认证接入
-将证书和私钥放到 [resources](../src/test/resources/)文件夹中。
+证书认证需要在控制台下载设备证书将其保存在设备上,并在配置文件`device_info.json`中填写`devCaFile`(ca证书),`devCertFile`(cert证书),`devPrivateKeyFile`(private证书)字段来指定证书绝对路径,并且需要指定`auth_mode`字段为`CERT`.
+```
+{
+ "auth_mode":"CERT",
-如果控制台创建设备使用的是证书认证方式,除了需要在 device_info.json 填写 PRODUCT_ID(产品ID)、DEVICE_NAME(设备名称),还需填写 DEVICE_CERT_FILE_NAME (设备证书文件名称)、DEVICE_PRIVATE_KEY_FILE_NAME(设备私钥文件名称)
+ "productId":"xxx",
+ "productSecret":"YOUR_PRODUCT_SECRET",
+ "deviceName":"xxx",
-#### 运行示例程序进行 MQTT 认证连接使设备上线
+ "key_deviceinfo":{
+ "deviceSecret":"YOUR_DEVICE_SECRET"
+ },
-运行 [MqttSample.py](../sample/MqttSample.py) 。示例代码如下:
+ "cert_deviceinfo":{
+ "devCaFile":"CA_FILE_PATH",
+ "devCertFile":"CERT_FILE_PATH",
+ "devPrivateKeyFile":"PRIVATEKEY_FILE_PATH"
+ },
+ "subDev":{
+ "subdev_num":4,
+ "subdev_list":
+ [
+ {"sub_productId": "", "sub_devName": ""},
+ {"sub_productId": "", "sub_devName": ""},
+ {"sub_productId": "", "sub_devName": ""},
+ {"sub_productId": "", "sub_devName": ""}
+ ]
+ },
+
+ "region":"china"
+}
```
-def on_connect(flags, rc, userdata):
- print("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
- pass
-
-def on_disconnect(rc, userdata):
- print("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
- pass
-
-te = explorer.QcloudExplorer(device_file="./device_info.json")
-te.user_on_connect = on_connect
-te.user_on_disconnect = on_disconnect
-
-te.mqtt_init(mqtt_domain="")
-te.connect_async()
+#### 设备上线
+
+示例代码如下:
+```python
+# 构造QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# 初始化日志
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# 注册mqtt回调
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# mqtt连接
+qcloud.connect()
```
观察日志。
-
```
-2021-03-16 10:18:31,470.470 [explorer.py:198] - DEBUG - sub topic:$sys/operation/result/ZPHBLEB4J5/dev001,qos:0
-2021-03-16 10:18:31,470.470 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/ZPHBLEB4J5/dev001', 0)]
-on_connect:flags:0,rc:0,userdata:None
-2021-03-16 10:18:31,470.470 [explorer.py:198] - DEBUG - subscribe success topic:$sys/operation/result/ZPHBLEB4J5/dev001
-2021-03-16 10:18:31,470.470 [explorer.py:198] - DEBUG - mid:1
-2021-03-16 10:18:31,470.470 [explorer.py:198] - DEBUG - pub topic:$sys/operation/ZPHBLEB4J5/dev001,payload:{'type': 'get', 'resource': ['time']},qos:0
-2021-03-16 10:18:31,471.471 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/ZPHBLEB4J5/dev001'', ... (37 bytes)
-2021-03-16 10:18:31,471.471 [explorer.py:198] - DEBUG - publish success
-on_publish:mid:2,userdata:None
+2021-07-22 10:49:52,302.302 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-22 10:49:52,302.302 [log.py:43] - INFO - connect with key...
+2021-07-22 10:49:52,302.302 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-22 10:49:53,068.068 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-22 10:49:53,179.179 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-22 10:49:53,179.179 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-22 10:49:53,304.304 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxx/dev1', 0)]
+2021-07-22 10:49:53,306.306 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/dev1
+2021-07-22 10:49:53,307.307 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/dev1'', ... (37 bytes)
+2021-07-22 10:49:53,308.308 [log.py:35] - DEBUG - publish success
+2021-07-22 10:49:53,310.310 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-22 10:49:53,416.416 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-22 10:49:53,416.416 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-22 10:49:53,424.424 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/xxx/dev1', ... (82 bytes)
+2021-07-22 10:49:53,511.511 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m3) [b'$sys/operation/result/xxx/dev1']
+2021-07-22 10:49:53,512.512 [log.py:35] - DEBUG - current time:2021-07-22 10:49:53
```
+以上是设备使用密钥认证方式通过MQTT成功连接至云端并请求到ntp时间的日志,在控制台可查看该设备的状态已更新为在线。
-以上是设备通过MQTT成功连接至云端的日志,在控制台可查看该设备的状态已更新为在线。
+```
+2021-07-22 10:47:33,080.080 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-22 10:47:33,080.080 [log.py:43] - INFO - connect with certificate...
+2021-07-22 10:47:33,081.081 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-22 10:47:33,609.609 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-22 10:47:33,693.693 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-22 10:47:33,694.694 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-22 10:47:34,081.081 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxx/dev001', 0)]
+2021-07-22 10:47:34,082.082 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/dev001
+2021-07-22 10:47:34,082.082 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/dev001'', ... (37 bytes)
+2021-07-22 10:47:34,082.082 [log.py:35] - DEBUG - publish success
+2021-07-22 10:47:34,083.083 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-22 10:47:34,170.170 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-22 10:47:34,170.170 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-22 10:47:34,181.181 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/xxx/dev001', ... (82 bytes)
+2021-07-22 10:47:34,283.283 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m3) [b'$sys/operation/result/xxx/dev001']
+2021-07-22 10:47:34,284.284 [log.py:35] - DEBUG - current time:2021-07-22 10:47:34
+```
+以上是设备使用证书认证方式通过MQTT成功连接至云端并请求到ntp时间的日志,在控制台可查看该设备的状态已更新为在线。
#### 设备下线
-运行 [MqttSample](../src) ,设备上线后调用disconnect()。示例代码如下:
-
-```
-te.disconnect()
+示例代码如下:
+```python
+# 断开mqtt连接
+qcloud.disconnect()
```
观察输出日志。
+```
+2021-07-21 17:40:42,080.080 [log.py:35] - DEBUG - disconnect
+2021-07-21 17:40:42,081.081 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-21 17:40:42,081.081 [log.py:35] - DEBUG - LoopThread thread exit
+```
+以上是设备使用密钥认证方式成功断开MQTT连接的日志,在控制台可查看该设备的状态已更新为离线。
```
-2021-03-16 10:20:50,446.446 [explorer.py:198] - DEBUG - disconnect
-2021-03-16 10:20:50,446.446 [client.py:2165] - DEBUG - Sending DISCONNECT
-2021-03-16 10:20:50,446.446 [explorer.py:206] - INFO - __on_disconnect,rc:0
-on_disconnect:rc:0,userdata:None
+2021-07-22 10:47:34,285.285 [log.py:35] - DEBUG - disconnect
+2021-07-22 10:47:34,285.285 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-22 10:47:34,286.286 [log.py:35] - DEBUG - LoopThread thread exit
```
-以上是设备成功断开MQTT连接的日志,在控制台可查看该设备的状态已更新为离线。
+以上是设备使用证书认证方式成功断开MQTT连接的日志,在控制台可查看该设备的状态已更新为离线。
\ No newline at end of file
diff --git "a/explorer/doc/\346\243\200\346\237\245\345\233\272\344\273\266\346\233\264\346\226\260.md" "b/explorer/doc/\346\243\200\346\237\245\345\233\272\344\273\266\346\233\264\346\226\260.md"
index 064d76a..20ec1b2 100755
--- "a/explorer/doc/\346\243\200\346\237\245\345\233\272\344\273\266\346\233\264\346\226\260.md"
+++ "b/explorer/doc/\346\243\200\346\237\245\345\233\272\344\273\266\346\233\264\346\226\260.md"
@@ -3,90 +3,79 @@
* [升级固件](#升级固件)
# 检查固件更新
+设备固件升级又称 OTA,是物联网通信服务的重要组成部分。当物联设备有新功能或者需要修复漏洞时,设备可以通过 OTA 服务快速进行固件升级。请参考官网文档控制台使用手册 [固件升级](https://cloud.tencent.com/document/product/634/14673)
+
+体验固件升级需要在控制台中添加新的固件,请参考官网文档开发者手册[设备固件升级](https://cloud.tencent.com/document/product/634/14674)
本文主要描述设备端如何订阅以及发布检查固件更新的 Topic 。
## 订阅以及发布检查固件更新的 Topic
-运行 [MqttSample.py](../sample/MqttSample.py),设备成功上线后,成功订阅过Topic后,调用ota_report_version(),订阅检查固件更新的 Topic`$ota/update/${productID}/${deviceName}` ,发布检查固件更新的 Topic `$ota/report/${productID}/${deviceName}` 。示例代码如下:
-
-```
-def on_subscribe(granted_qos, mid, userdata):
- print("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
- pass
-
-def on_ota_report(payload, userdata):
- print("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
- code = payload["result_code"]
- if code == 0:
- global g_report_res
- g_report_res = True
-
- pass
+运行 [OtaSample.py](../../explorer/sample/ota/example_ota.py) 设备上线后会订阅相关 Topic, 之后会检查当前固件版本然后进行版本上报。
-te = explorer.QcloudExplorer(device_file="./device_info.json")
-te.user_on_subscribe = on_subscribe
-te.user_on_ota_report = on_ota_report
+相关 Topic 如下:
+* 订阅检查固件更新的 Topic:`$ota/update/${productID}/${deviceName}`
+* 发布检查固件更新的 Topic:`$ota/report/${productID}/${deviceName}`
-te.mqtt_init(mqtt_domain="")
-te.connect_async()
-te.ota_init()
-te.ota_report_version(""0.0.1")
-rc = te.ota_download_start(download_size, file_size)
+观察日志:
```
-
-观察输出日志。
-```
-24/02/2021 09:15:36,653 [main] INFO TXMqttConnection subscribe 674 - Starting subscribe topic: $ota/update/LWVUL5SZ2L/light1
-24/02/2021 09:15:36,654 [main] INFO TXMqttConnection publish 492 - Starting publish topic: $ota/report/LWVUL5SZ2L/light1 Message: {"report":{"version":"0.0.1"},"type":"report_version"}
-24/02/2021 09:15:36,671 [MQTT Call: LWVUL5SZ2Llight1] DEBUG MqttSample onSubscribeCompleted 333 - onSubscribeCompleted, status[OK], topics[[$ota/update/LWVUL5SZ2L/light1]], userContext[], errMsg[subscribe success]
-24/02/2021 09:15:36,666 [MQTT Call: LWVUL5SZ2Llight1] DEBUG MqttSample onPublishCompleted 319 - onPublishCompleted, status[OK], topics[[$ota/report/LWVUL5SZ2L/light1]], userContext[], errMsg[publish success]
-24/02/2021 09:15:36,688 [MQTT Call: LWVUL5SZ2Llight1] INFO TXMqttConnection messageArrived 931 - Received topic: $ota/update/LWVUL5SZ2L/light1, id: 0, message: {"result_code":0,"result_msg":"success","type":"report_version_rsp","version":"0.0.1"}
-24/02/2021 09:15:50,329 [MQTT Call: LWVUL5SZ2Llight1] ERROR DataTemplateSample onReportFirmwareVersion 179 - onReportFirmwareVersion:0, version:0.0.1, resultMsg:success
+2021-07-19 10:54:26,800.800 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-19 10:54:26,800.800 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-19 10:54:28,159.159 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-19 10:54:28,384.384 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-19 10:54:28,385.385 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-19 10:54:28,803.803 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$ota/update/xxx/xxx', 1)]
+2021-07-19 10:54:28,803.803 [log.py:35] - DEBUG - subscribe success topic:$ota/update/xxx/xxx
+2021-07-19 10:54:28,932.932 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 10:54:28,932.932 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:1,userdata:None
+2021-07-19 10:54:29,004.004 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m2), 'b'$ota/report/xxx/xxx'', ... (58 bytes)
+2021-07-19 10:54:29,005.005 [log.py:35] - DEBUG - publish success
+2021-07-19 10:54:29,144.144 [client.py:2165] - DEBUG - Received PUBACK (Mid: 2)
+2021-07-19 10:54:29,145.145 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (86 bytes)
+2021-07-19 10:54:29,147.147 [log.py:35] - DEBUG - on_ota_report:payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'},userdata:None
+2021-07-19 10:54:30,006.006 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:31,008.008 [log.py:35] - DEBUG - wait for ota upgrade command
```
-以上是设备成功订阅以及发布检查固件更新 Topic 的日志。当前上报的设备固件的版本号为0.0.1。
+如上日志中程序上线后订阅了 Topic:`$ota/update/xxx/xxx` (`xxx/xxx`为实际product_id和device_name)用来接收云端下发的命令,之后检查当前本地固件版本并将版本号(本例为0.1.0)通过 Topic:`$ota/report/xxx/xxx`上报到云端且收到了云端响应,之后等待开始升级命令。
## 升级固件
-在物联网开发平台控制台的固件升级模块中,可以为产品上传新版本的固件,可以为指定设备升级固件,也可以批量升级固件。请参考官网 [固件升级](https://cloud.tencent.com/document/product/1081/40296) 章节。
+在物联网开发平台控制台的固件升级模块中, 可以为产品上传新版本的固件, 可以为指定设备升级固件, 也可以批量升级固件。请参考官网 [固件升级](https://cloud.tencent.com/document/product/1081/40296) 章节。
-设备在线并订阅过OTA相关的Topic,在物联网开发平台控制台创建对应的固件升级任务后,观察输出日志。
+在控制台中出发固件升级操作后,设备端会通过订阅的 Topic:`$ota/update/${productID}/${deviceName}`收到固件升级消息,之后开始下载固件并定时上报下载进度。
+```
+2021-07-19 10:54:44,032.032 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:45,034.034 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:45,409.409 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (468 bytes)
+2021-07-19 10:54:46,036.036 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:46,036.036 [log.py:47] - ERROR - info file not exists
+2021-07-19 10:54:46,036.036 [log.py:47] - ERROR - local_size:0,local_ver:None,re_ver:1.0.0
+__ota_http_deinit do nothing
+2021-07-19 10:54:47,052.052 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:54:47,052.052 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$ota/report/xxx/xxx'', ... (151 bytes)
+2021-07-19 10:54:47,053.053 [log.py:35] - DEBUG - publish success
+2021-07-19 10:54:48,032.032 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:54:48,032.032 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m4), 'b'$ota/report/xxx/xxx'', ... (151 bytes)
+2021-07-19 10:54:48,032.032 [log.py:35] - DEBUG - publish success
+2021-07-19 10:54:49,118.118 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '1', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:54:49,119.119 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$ota/report/xxx/xxx'', ... (151 bytes)
+2021-07-19 10:54:49,119.119 [log.py:35] - DEBUG - publish success
+```
+以上是设备成功收到1.0.0固件版本升级消息,SDK回调下载固件进度,并上报的日志,此时查看传入的OTA升级包的下载路径下已经有了新的固件升级包。
+升级完成后会上报升级记过并等待云端应答。
```
-2021-03-16 15:04:27,382.382 [explorer.py:198] - DEBUG - mqtt_init
-2021-03-16 15:04:27,383.383 [explorer.py:198] - DEBUG - LoopThread thread enter
-2021-03-16 15:04:27,383.383 [explorer.py:206] - INFO - __loop_forever
-2021-03-16 15:04:27,383.383 [explorer.py:198] - DEBUG - connect_async...
-2021-03-16 15:04:27,899.899 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'NCUL2VSYG6test02'
-2021-03-16 15:04:27,965.965 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
-2021-03-16 15:04:27,965.965 [explorer.py:198] - DEBUG - sub topic:$sys/operation/result/NCUL2VSYG6/test02,qos:0
-2021-03-16 15:04:27,965.965 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/NCUL2VSYG6/test02', 0)]
-on_connect:flags:0,rc:0,userdata:None
-2021-03-16 15:04:27,965.965 [explorer.py:198] - DEBUG - subscribe success topic:$sys/operation/result/NCUL2VSYG6/test02
-2021-03-16 15:04:27,965.965 [explorer.py:198] - DEBUG - mid:1
-2021-03-16 15:04:27,966.966 [explorer.py:198] - DEBUG - pub topic:$sys/operation/NCUL2VSYG6/test02,payload:{'type': 'get', 'resource': ['time']},qos:0
-2021-03-16 15:04:27,966.966 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/NCUL2VSYG6/test02'', ... (37 bytes)
-2021-03-16 15:04:27,966.966 [explorer.py:198] - DEBUG - publish success
-2021-03-16 15:04:28,039.039 [client.py:2165] - DEBUG - Received SUBACK
-2021-03-16 15:04:28,039.039 [explorer.py:206] - INFO - __on_subscribe:user_data:None,mid:1,qos:(0,)
-on_subscribe:mid:1,granted_qos:0,userdata:None
-2021-03-16 15:04:28,054.054 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/NCUL2VSYG6/test02', ... (82 bytes)
-2021-03-16 15:04:28,054.054 [explorer.py:206] - INFO - __user_thread_on_message_callback,topic:$sys/operation/result/NCUL2VSYG6/test02,payload:{'type': 'get', 'time': 1615878268, 'ntptime1': 1615878268442, 'ntptime2': 1615878268442},mid:0
-6
-2021-03-16 15:04:28,386.386 [explorer.py:198] - DEBUG - sub topic:$ota/update/NCUL2VSYG6/test02,qos:1
-2021-03-16 15:04:28,386.386 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'$ota/update/NCUL2VSYG6/test02', 1)]
-2021-03-16 15:04:28,386.386 [explorer.py:198] - DEBUG - subscribe success topic:$ota/update/NCUL2VSYG6/test02
-2021-03-16 15:04:28,495.495 [client.py:2165] - DEBUG - Received SUBACK
-2021-03-16 15:04:28,495.495 [explorer.py:206] - INFO - __on_subscribe:user_data:None,mid:3,qos:(1,)
-on_subscribe:mid:3,granted_qos:1,userdata:None
-2021-03-16 15:04:28,589.589 [explorer.py:198] - DEBUG - pub topic:$ota/report/NCUL2VSYG6/test02,payload:{'type': 'report_version', 'report': {'version': '0.1.0'}},qos:1
-2021-03-16 15:04:28,589.589 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m4), 'b'$ota/report/NCUL2VSYG6/test02'', ... (58 bytes)
-2021-03-16 15:04:28,589.589 [explorer.py:198] - DEBUG - publish success
-2021-03-16 15:04:28,639.639 [client.py:2165] - DEBUG - Received PUBACK (Mid: 4)
-2021-03-16 15:04:28,658.658 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/NCUL2VSYG6/test02', ... (86 bytes)
-2021-03-16 15:04:28,658.658 [explorer.py:206] - INFO - __user_thread_on_message_callback,topic:$ota/update/NCUL2VSYG6/test02,payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'},mid:0
-on_ota_report:payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'},userdata:None
-wait for ota upgrade command...
-wait for ota upgrade command...
+2021-07-19 10:57:25,537.537 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '100', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:57:25,537.537 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m117), 'b'$ota/report/xxx/xxx'', ... (153 bytes)
+2021-07-19 10:57:25,537.537 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - The firmware download success
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - burning firmware...
+2021-07-19 10:57:25,538.538 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m118), 'b'$ota/report/xxx/xxx'', ... (128 bytes)
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:25,539.539 [log.py:35] - DEBUG - wait for ack...
+2021-07-19 10:57:25,641.641 [client.py:2165] - DEBUG - Received PUBACK (Mid: 118)
+2021-07-19 10:57:25,642.642 [log.py:35] - DEBUG - publish ack id 118
+2021-07-19 10:57:28,042.042 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m119), 'b'$ota/report/xxx/xxx'', ... (58 bytes)
+2021-07-19 10:57:28,043.043 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:28,128.128 [client.py:2165] - DEBUG - Received PUBACK (Mid: 119)
```
-以上是设备成功收到0.0.2固件版本升级消息,SDK回调下载固件进度,并上报的日志,此时查看传入的OTA升级包的下载路径下已经有了新的固件升级包。
diff --git "a/explorer/doc/\346\270\205\351\231\244\346\216\247\345\210\266.md" "b/explorer/doc/\346\270\205\351\231\244\346\216\247\345\210\266.md"
index 2add186..147f312 100644
--- "a/explorer/doc/\346\270\205\351\231\244\346\216\247\345\210\266.md"
+++ "b/explorer/doc/\346\270\205\351\231\244\346\216\247\345\210\266.md"
@@ -1,5 +1,5 @@
* [清除控制](#清除控制)
- * [发布清除控制的 Topic ](#发布清除控制的-Topic)
+ * [发布清除控制的 Topic](#发布清除控制的-Topic)
# 清除控制
@@ -7,26 +7,58 @@
## 发布清除控制的 Topic
-运行 [MqttSample.py](../sample/MqttSample.py) 的main函数,设备成功上线后,订阅过Topic后,调用clear_control(),发布属性类型的 Topic:
+运行 [TemplateSample.py](../../explorer/sample/template/example_template.py),设备成功上线后,初始化数据模板,在需要清除控制时调用`clearControl()`进行控制,清除控制的 Topic:
`$thing/up/property/{ProductID}/{DeviceName}`
示例代码如下:
+```python
+# 构造QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# 初始化日志
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
-```
-te.clear_control()
+# 注册mqtt回调
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# 获取设备product id和device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# mqtt连接
+qcloud.connect()
+
+# 数据模板初始化
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# 清除控制
+qcloud.clearControl(product_id, device_name)
+
+# 断开mqtt连接
+qcloud.disconnect()
```
观察输出日志。
-
```
-2021-03-16 14:59:47,798.798 [explorer.py:198] - DEBUG - pub topic:$thing/up/property/ZPHBLEB4J5/dev001,payload:{'method': 'clear_control', 'clientToken': 'ZPHBLEB4J5-1'},qos:0
-2021-03-16 14:59:47,798.798 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m9), 'b'$thing/up/property/ZPHBLEB4J5/dev001'', ... (58 bytes)
-2021-03-16 14:59:47,798.798 [explorer.py:198] - DEBUG - publish success
-2021-03-16 14:59:47,798.798 [explorer.py:198] - DEBUG - mid:9
-on_publish:mid:9,userdata:None
-2021-03-16 14:59:47,901.901 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/ZPHBLEB4J5/dev001', ... (89 bytes)
-2021-03-16 14:59:47,901.901 [explorer.py:206] - INFO - __user_thread_on_message_callback,topic:$thing/down/property/ZPHBLEB4J5/dev001,payload:{'method': 'clear_control_reply', 'clientToken': 'ZPHBLEB4J5-1', 'code': 0, 'status': 'success'},mid:0
-2021-03-16 14:59:47,902.902 [explorer.py:198] - DEBUG - reply payload:{'method': 'clear_control_reply', 'clientToken': 'ZPHBLEB4J5-1', 'code': 0, 'status': 'success'}
+2021-07-21 15:57:20,128.128 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$thing/up/property/xxx/dev1'', ... (55 bytes)
+2021-07-21 15:57:20,129.129 [log.py:35] - DEBUG - publish success
+2021-07-21 15:57:20,129.129 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 15:57:20,204.204 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (160 bytes)
+2021-07-21 15:57:20,205.205 [log.py:35] - DEBUG - on_template_property:params:{'method': 'get_status_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': 'success', 'data': {'reported': {'name': '', 'power_switch': 0, 'color': 0, 'brightness': 0}}},userdata:None
+2021-07-21 15:57:20,205.205 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$thing/up/property/xxx/dev1'', ... (62 bytes)
+2021-07-21 15:57:20,205.205 [log.py:35] - DEBUG - publish success
+2021-07-21 15:57:20,205.205 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+7
+2021-07-21 15:57:21,965.965 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m7), 'b'$thing/up/property/xxx/dev1'', ... (58 bytes)
+2021-07-21 15:57:21,966.966 [log.py:35] - DEBUG - publish success
+2021-07-21 15:57:21,966.966 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-21 15:57:22,038.038 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (89 bytes)
+2021-07-21 15:57:22,038.038 [log.py:35] - DEBUG - on_template_property:params:{'method': 'clear_control_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': 'success'},userdata:None
+2021-07-21 15:57:22,038.038 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m8), 'b'$thing/up/property/xxx/dev1'', ... (62 bytes)
+2021-07-21 15:57:22,038.038 [log.py:35] - DEBUG - publish success
+2021-07-21 15:57:22,039.039 [log.py:35] - DEBUG - on_publish:mid:8,userdata:None
```
-以上是成功发布清除控制Topic的日志。
+观察日志可以看到程序成功发布了清除控制消息并收到了云端应答`clear_control_reply`.
diff --git "a/explorer/doc/\347\275\221\345\205\263\344\275\277\347\224\250\347\244\272\344\276\213.md" "b/explorer/doc/\347\275\221\345\205\263\344\275\277\347\224\250\347\244\272\344\276\213.md"
index feb2a20..9f52460 100755
--- "a/explorer/doc/\347\275\221\345\205\263\344\275\277\347\224\250\347\244\272\344\276\213.md"
+++ "b/explorer/doc/\347\275\221\345\205\263\344\275\277\347\224\250\347\244\272\344\276\213.md"
@@ -1,246 +1,183 @@
* [网关使用示例](#网关使用示例)
- * [控制台创建设备](#控制台创建设备)
- * [密钥认证接入](#密钥认证接入)
- * [证书认证接入](#证书认证接入)
- * [填写认证连接设备的参数](#填写认证连接设备的参数)
- * [运行示例程序使网关设备进行 MQTT 认证连接上线](#运行示例程序使网关设备进行-MQTT-认证连接上线)
- * [网关下线](#网关下线)
- * [绑定子设备](#绑定子设备)
- * [解绑子设备](#解绑子设备)
- * [添加智能灯设备](#添加智能灯设备)
- * [删除智能灯设备](#删除智能灯设备)
- * [智能灯设备上线](#智能灯设备上线)
- * [智能灯设备下线](#智能灯设备下线)
+ * [控制台创建网关设备](#创建网关设备)
+ * [创建网关产品和设备](#创建网关产品和设备)
+ * [定义子产品数据模板](#定义子产品数据模板)
+ * [运行示例](#运行示例)
+ * [填写认证连接设备的参数](#填写认证连接设备的参数)
+ * [代理子设备上下线](#代理子设备上下线)
+ * [绑定解绑子设备](#绑定解绑子设备)
+ * [代理子设备基于数据模板通信](#代理子设备基于数据模板通信)
# 网关使用示例
-本文主要描述 SDK Demo 中网关设备的使用示例。
+本文档将讲述如何在腾讯物联网开发平台(IoT Explorer)控制台申请网关设备并绑定子设备, 并结合 SDK 的[GatewaySample.py](../../explorer/sample/gateway/example_gateway.py) 快速体验网关设备代理子设备上下线,子设备基于数据模板协议或者自定义数据,发送和接收消息。
-## 控制台创建设备
+## 控制台创建网关设备
+#### 创建网关产品和设备
+体验网关示例 Demo 需要在腾讯云物联网开发平台控制台(以下简称控制台)创建一个网关设备,一个普通设备,并将普通设备绑定为网关的子设备,具体请参考官网 [用户指南-网关设备接入](https://cloud.tencent.com/document/product/1081/43417)。
-体验网关示例Demo需要在腾讯云物联网开发平台控制台(以下简称控制台)创建一个网关设备,一个智能灯设备,一个空调设备。请参考官网 [用户指南-网关设备接入](https://cloud.tencent.com/document/product/1081/43417)。 **注:需要将智能灯设备和空调设备绑定为网关设备的子设备**。
+#### 定义子产品数据模板
+创建完子设备需要定义子设备数据模板,体验 Demo 可以使用默认数据模板,具体参考官网 [快速入门-智能灯接入指引](https://cloud.tencent.com/document/product/1081/41155)。
-#### 密钥认证接入
-示例中编辑 [device_info.json](../sample/device_info.json) 文件中的参数配置信息
+## 运行示例
+运行 [GatewaySample.py](../../explorer/sample/gateway/example_gateway.py) 示例程序,可以体验网关代理子设备上下线、绑定/解绑子设备及网关代理子设备基于各自数据模板进行消息通信的过程。
+#### 填写认证连接设备的参数
+将在控制台创建设备时生成的设备信息填写到 [device_info.json](../../explorer/sample/device_info.json)中,以密钥认证方式为例,主要关注`auth_mode`,`productId`,`deviceName`,`deviceSecret`及网关子设备`subDev`部分字段,示例如下:
```
{
"auth_mode":"KEY",
-
- "productId":"YOUR_PRODUCT_ID",
- "productSecret":"YOUR_PRODUCT_SECRET",
- "deviceName":"",
-
- "key_deviceinfo":{
- "deviceSecret":""
- },
-
- "cert_deviceinfo":{
- "devCertFile":"YOUR_DEVICE_CERT_FILE_NAME",
- "devPrivateKeyFile":"YOUR_DEVICE_PRIVATE_KEY_FILE_NAME"
- },
-
- "subDev":{
- "subdev_num":4,
- "subdev_list":
- [
- {"sub_productId": "", "sub_devName": ""},
- {"sub_productId": "", "sub_devName": ""},
- {"sub_productId": "", "sub_devName": ""},
- {"sub_productId": "", "sub_devName": ""}
- ]
- },
-
- "region":"china"
+ "productId":"xxx",
+ "deviceName":"test02",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+"subDev":{
+ "subdev_num":2,
+ "subdev_list":
+ [
+ {"sub_productId": "xxxx", "sub_devName": "dev1"},
+ {"sub_productId": "xxxx", "sub_devName": "dev001"}
+ ]
}
```
-如果控制台创建设备使用的是密钥认证方式,需要在 device_info.json 填写网关设备的 PRODUCT_ID(产品ID)、DEVICE_NAME(设备名称)、DEVICE_PSK(设备密钥),示例中使用的是密钥认证,SUB_PRODUCT_ID (智能灯等子设备产品ID)、 SUB_DEV_NAME (智能灯等子设备名称)、 SUB_DEV_PSK (智能灯等子设备密钥,绑定子设备时会用到),SUB_PRODUCT_ID2 (空调设备等子设备产品ID)、 SUB_DEV_NAME2 (空调设备等子设备名称)、 SUB_DEV_PSK2 (空调设备等子设备密钥,绑定子设备时会用到)。
-
-#### 证书认证接入
-
-将证书和私钥放到 [resources](../sample)文件夹中。
-
-如果控制台创建设备使用的是证书认证方式,除了需要在 device_info.json 除了需要在 device_info.json 填写 PRODUCT_ID(产品ID)、DEVICE_NAME(设备名称),还需填写 DEVICE_CERT_FILE_NAME (设备证书文件名称)、DEVICE_PRIVATE_KEY_FILE_NAME(设备私钥文件名称)
-
-## 运行示例程序使网关设备进行 MQTT 认证连接上线
-
-运行 [IoTGateway.py](../sample/IoTGateway.py) ,进行认证连接,使网关设备上线。相关示例代码如下:
-
-```
-te = explorer.QcloudExplorer(device_file="./device_info.json")
-mGatewaySample.online();
-
-# 网关下所有产品名称列表
-g_product_list = ["", ""]
-
-te.user_on_publish = on_publish
-te.user_on_subscribe = on_subscribe
-te.mqtt_init(mqtt_domain="")
-te.connect_async()
-te.gateway_init()
-```
-
-观察输出日志。
-
-```
-2021-03-16 15:17:22,835.835 [explorer.py:198] - DEBUG - mqtt_init
-2021-03-16 15:17:22,836.836 [explorer.py:198] - DEBUG - LoopThread thread enter
-2021-03-16 15:17:22,836.836 [explorer.py:206] - INFO - __loop_forever
-2021-03-16 15:17:22,837.837 [explorer.py:198] - DEBUG - connect_async...
-2021-03-16 15:17:23,763.763 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'NCUL2VSYG6test02'
-2021-03-16 15:17:23,819.819 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
-2021-03-16 15:17:23,819.819 [explorer.py:198] - DEBUG - sub topic:$sys/operation/result/NCUL2VSYG6/test02,qos:0
-2021-03-16 15:17:23,820.820 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/NCUL2VSYG6/test02', 0)]
-2021-03-16 15:17:23,820.820 [explorer.py:198] - DEBUG - subscribe success topic:$sys/operation/result/NCUL2VSYG6/test02
-2021-03-16 15:17:23,820.820 [explorer.py:198] - DEBUG - mid:1
-2021-03-16 15:17:23,820.820 [explorer.py:198] - DEBUG - pub topic:$sys/operation/NCUL2VSYG6/test02,payload:{'type': 'get', 'resource': ['time']},qos:0
-on_connect:flags:0,rc:0,userdata:None
-2021-03-16 15:17:23,821.821 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/NCUL2VSYG6/test02'', ... (37 bytes)
-2021-03-16 15:17:23,821.821 [explorer.py:198] - DEBUG - publish success
-on_publish:mid:2,userdata:None
-2021-03-16 15:17:23,839.839 [explorer.py:198] - DEBUG - sub topic:$gateway/operation/result/NCUL2VSYG6/test02,qos:0
-2021-03-16 15:17:23,839.839 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'$gateway/operation/result/NCUL2VSYG6/test02', 0)]
-2021-03-16 15:17:23,840.840 [explorer.py:198] - DEBUG - subscribe success topic:$gateway/operation/result/NCUL2VSYG6/test02
-2021-03-16 15:17:23,840.840 [explorer.py:198] - DEBUG - mid:3
-2021-03-16 15:17:23,863.863 [client.py:2165] - DEBUG - Received SUBACK
-2021-03-16 15:17:23,863.863 [explorer.py:206] - INFO - __on_subscribe:user_data:None,mid:1,qos:(0,)
-on_subscribe:mid:0,granted_qos:1,userdata:None
-2021-03-16 15:17:23,894.894 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/NCUL2VSYG6/test02', ... (82 bytes)
-2021-03-16 15:17:23,894.894 [explorer.py:206] - INFO - __user_thread_on_message_callback,topic:$sys/operation/result/NCUL2VSYG6/test02,payload:{'type': 'get', 'time': 1615879044, 'ntptime1': 1615879044306, 'ntptime2': 1615879044306},mid:0
-6
-2021-03-16 15:17:23,944.944 [client.py:2165] - DEBUG - Received SUBACK
-2021-03-16 15:17:23,944.944 [explorer.py:206] - INFO - __on_subscribe:user_data:None,mid:3,qos:(0,)
-on_subscribe:mid:0,granted_qos:3,userdata:None
-```
-以上是设备通过 MQTT 成功连接至云端并订阅网关设备关联的[数据模板协议](https://cloud.tencent.com/document/product/1081/34916) Topic 消息的日志,在控制台可查看该网关设备的状态已更新为在线。
-
-## 网关下线
-
-运行 [IoTGateway.py](../sample/IoTGateway.py) 的main函数,设备成功上线后,订阅过Topic后,调用gatewayOffline(),使网关设备断开 MQTT 认证连接,网关设备下线。示例代码如下:
-
-```
-te.disconnect()
-```
-
-观察输出日志。
-
-```
-2021-03-16 15:18:20,259.259 [explorer.py:198] - DEBUG - disconnect
-2021-03-16 15:18:20,259.259 [client.py:2165] - DEBUG - Sending DISCONNECT
-2021-03-16 15:18:20,260.260 [explorer.py:206] - INFO - __on_disconnect,rc:0
-on_disconnect:rc:0,userdata:None
-```
-以上为网关设备成功断开 MQTT 认证连接并取消订阅网关设备关联的[数据模板协议](https://cloud.tencent.com/document/product/1081/34916) Topic 消息的日志,在控制台可查看该网关设备的状态已更新为离线。
-
-## 绑定子设备
-
-运行 [IoTGateway.py](../sample/IoTGateway.py) 的main函数,设备成功上线后,订阅过Topic后,将子设备绑定到指定的网关设备中。示例代码如下:
-
-```
-rc = te.gateway_subdev_bind_unbind("bind", "productid", "devicename", "secret")
-if rc == 0:
- print("bind success")
-else:
- print("bind fail")
-```
-
-以下是网关设备成功绑定子设备的输出日志,刷新观察控制台中的该网关设备下的子设备,选择对应绑定的子产品,即可查看到已绑定的子设备。
-
-```
-24/02/2021 17:06:47,974 [main] INFO TXMqttConnection publish 492 - Starting publish topic: $gateway/operation/VOY2UGD9HH/gateway1 Message: {"payload":{"devices":[{"random":592432,"device_name":"light1","signmethod":"hmacsha256","signature":"IA3zqP2BfedQ8Vb2dtVCRhfrV80u4kBBrhd5Ec2fgjQ=","product_id":"LWVUL5SZ2L","timestamp":1614157607,"authtype":"psk"}]},"type":"bind"}
-24/02/2021 17:06:47,987 [MQTT Call: VOY2UGD9HHgateway1] DEBUG GatewaySample onPublishCompleted 228 - onPublishCompleted, status[OK], topics[[$gateway/operation/VOY2UGD9HH/gateway1]], userContext[], errMsg[publish success]
-24/02/2021 17:06:48,014 [MQTT Call: VOY2UGD9HHgateway1] INFO TXMqttConnection messageArrived 931 - Received topic: $gateway/operation/result/VOY2UGD9HH/gateway1, id: 6, message: {"type":"bind","payload":{"devices":[{"product_id":"LWVUL5SZ2L","device_name":"light1","result":0}]}}
-```
-
-## 解绑子设备
-
-运行 [IoTGateway.py](../sample/IoTGateway.py) 的main函数,设备成功上线后,订阅过Topic后,调用gatewayUnbindSubdev(mSubDev1ProductId,mSubDev1DeviceName),将子设备和指定的网关设备解绑。示例代码如下:
-
-```
-rc = te.gateway_subdev_bind_unbind("unbind", "productid", "devicename", None)
-if rc == 0:
- print("unbind success")
-else:
- print("unbind fail")
-```
-
-以下是网关设备成功解绑子设备的输出日志,刷新观察控制台中的该网关设备下的子设备,选择对应绑定的子产品,之前已绑定的子设备已经不在子设备列表中,解绑成功。
-```
-24/02/2021 17:26:47,995 [main] INFO TXMqttConnection publish 492 - Starting publish topic: $gateway/operation/VOY2UGD9HH/gateway1 Message: {"payload":{"devices":[{"device_name":"light1","product_id":"LWVUL5SZ2L"}]},"type":"unbind"}
-24/02/2021 17:26:48,003 [MQTT Call: VOY2UGD9HHgateway1] DEBUG GatewaySample onPublishCompleted 228 - onPublishCompleted, status[OK], topics[[$gateway/operation/VOY2UGD9HH/gateway1]], userContext[], errMsg[publish success]
-24/02/2021 17:26:48,034 [MQTT Call: VOY2UGD9HHgateway1] INFO TXMqttConnection messageArrived 931 - Received topic: $gateway/operation/result/VOY2UGD9HH/gateway1, id: 8, message: {"type":"unbind","payload":{"devices":[{"product_id":"LWVUL5SZ2L","device_name":"light1","result":0}]}}
-```
-
-## 添加智能灯设备
-
-运行 [IoTGateway.py](../sample/IoTGateway.py) 的main函数,设备成功上线后,订阅过Topic后,调用gatewayAddSubDev(mSubDev1ProductId,mSubDev1DeviceName),使智能灯设备添加到网关设备的子设备中。示例代码如下:
-
-```
-# 产品下所有设备列表
-g_Z53CXC198M_subdev_list = []
-g_ZPHBLEB4J5_subdev_list.append(product_id, sub_devName)
-```
-
-观察输出日志。
-```
-24/02/2021 15:25:31,154 [main] DEBUG TXGatewayClient findSubdev 54 - input product id is LWVUL5SZ2L, input device name is light1
-```
-以上是网关设备成功将智能灯设备添加到子设备的日志。
-
-## 删除智能灯设备
-
-运行 [IoTGateway.py](../sample/IoTGateway.py) 的main函数,设备成功上线后,订阅过Topic后,调用gatewayDelSubDev(mSubDev1ProductId,mSubDev1DeviceName),智能灯设备将被从网关设备的子设备中移除。示例代码如下:
-
-```
-del g_Z53CXC198M_subdev_list[0]
-```
-
-## 智能灯设备上线
-
-运行 [IoTGateway.py](../sample/IoTGateway.py) 的main函数,设备成功上线后,订阅过Topic后,调用gatewayOnlineSubDev(mSubDev1ProductId,mSubDev1DeviceName),发布智能灯上线的 Topic 消息。示例代码如下:
-
-```
-te.gateway_init()
-subdev_list = te.gateway_subdev_list
-
-for subdev in subdev_list:
- if subdev.session_status is not te.SessionState.SUBDEV_SEESION_STATUS_ONLINE:
- rc = te.gateway_subdev_online_offline("online", subdev.sub_productId, subdev.sub_devName)
- if rc == 0:
- subdev.session_status = te.SessionState.SUBDEV_SEESION_STATUS_ONLINE
- print("online success")
- else:
- print("online fail")
-```
-
-观察输出日志。
-```
-24/02/2021 17:33:50,015 [main] DEBUG TXGatewayClient subdevOnline 183 - set LWVUL5SZ2L & light1 to Online
-24/02/2021 17:33:50,056 [MQTT Call: VOY2UGD9HHgateway1] DEBUG TXGatewayClient consumeGwOperationMsg 349 - got gate operation messga $gateway/operation/result/VOY2UGD9HH/gateway1{"type":"online","payload":{"devices":[{"product_id":"LWVUL5SZ2L","device_name":"light1","result":0}]}}
-```
-以上是网关设备成功发送智能灯上线 Topic 并且网关设备接收到了子设备上线的 Topic 消息的日志。网关设备代理子设备上下线的 Topic ,请参考官网 [代理子设备上下线](https://cloud.tencent.com/document/product/1081/47442)。
-
-## 智能灯设备下线
-
-运行 [IoTGateway.py](../sample/IoTGateway.py) 的main函数,设备成功上线后,订阅过Topic后,发布智能灯下线的 Topic 消息。示例代码如下:
-
-```
-for subdev in subdev_list:
- if subdev.session_status == te.SessionState.SUBDEV_SEESION_STATUS_ONLINE:
- rc = te.gateway_subdev_online_offline("offline", subdev.sub_productId, subdev.sub_devName)
- if rc == 0:
- subdev.session_status = te.SessionState.SUBDEV_SEESION_STATUS_OFFLINE
- print("offline success")
- else:
- print("offline fail")
-```
-
-观察输出日志。
-```
-24/02/2021 17:33:52,016 [main] DEBUG TXGatewayClient subdevOffline 135 - Try to find LWVUL5SZ2L & light1
-24/02/2021 17:33:52,016 [main] DEBUG TXGatewayClient findSubdev 54 - input product id is LWVUL5SZ2L, input device name is light1
-24/02/2021 17:33:52,041 [MQTT Call: VOY2UGD9HHgateway1] DEBUG TXGatewayClient consumeGwOperationMsg 349 - got gate operation messga $gateway/operation/result/VOY2UGD9HH/gateway1{"type":"offline","payload":{"devices":[{"product_id":"LWVUL5SZ2L","device_name":"light1","result":0}]}}
-```
-以上是网关设备成功发送智能灯下线 Topic 成功并且网关设备接收到了子设备下线的 Topic 消息的日志。网关设备代理子设备上下线的 Topic ,请参考官网 [代理子设备上下线](https://cloud.tencent.com/document/product/1081/47442)。
+#### 代理子设备上下线
+示例程序配置文件中网关设备`test02`的子设备有两个,devica_name分别为`dev1`和`dev001`。
+* 网关代理子设备下线
+处于离线状态的子设备可以由网关代理上线
+```
+2021-07-20 14:12:29,913.913 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$gateway/operation/xxx/test02'', ... (97 bytes)
+2021-07-20 14:12:29,913.913 [log.py:35] - DEBUG - publish success
+2021-07-20 14:12:29,913.913 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-20 14:12:30,029.029 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/NCUL2VSYG6/test02', ... (101 bytes)
+2021-07-20 14:12:30,114.114 [log.py:35] - DEBUG - client:xxx/dev1 online success
+2021-07-20 14:12:30,114.114 [log.py:35] - DEBUG - online success
+2021-07-20 14:12:30,114.114 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$gateway/operation/xxx/test02'', ... (99 bytes)
+2021-07-20 14:12:30,115.115 [log.py:35] - DEBUG - publish success
+2021-07-20 14:12:30,115.115 [log.py:35] - DEBUG - on_publish:mid:3,userdata:None
+2021-07-20 14:12:30,249.249 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test02', ... (103 bytes)
+2021-07-20 14:12:30,315.315 [log.py:35] - DEBUG - client:xxx/dev001 online success
+2021-07-20 14:12:30,315.315 [log.py:35] - DEBUG - online success
+```
+观察日志,可以看到两个子设备`dev1`和`dev001`都成功上线(online success)。此时查看控制台可以看到子设备为在线状态。
+
+* 网关代理子设备下线
+处于在线状态的子设备可以由网关代理下线
+```
+2021-07-20 14:14:50,962.962 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m4), 'b'$gateway/operation/xxx/test02'', ... (98 bytes)
+2021-07-20 14:14:50,963.963 [log.py:35] - DEBUG - publish success
+2021-07-20 14:14:50,963.963 [log.py:35] - DEBUG - on_publish:mid:4,userdata:None
+2021-07-20 14:14:51,037.037 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test02', ... (102 bytes)
+2021-07-20 14:14:51,163.163 [log.py:35] - DEBUG - client:xxx/dev1 offline success
+2021-07-20 14:14:51,164.164 [log.py:35] - DEBUG - offline success
+2021-07-20 14:14:51,165.165 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$gateway/operation/xxx/test02'', ... (100 bytes)
+2021-07-20 14:14:51,166.166 [log.py:35] - DEBUG - publish success
+2021-07-20 14:14:51,167.167 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-20 14:14:51,247.247 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test02', ... (104 bytes)
+2021-07-20 14:14:51,368.368 [log.py:35] - DEBUG - client:xxx/dev001 offline success
+2021-07-20 14:14:51,368.368 [log.py:35] - DEBUG - offline success
+```
+观察日志,可以看到刚上线的两个子设备通过网关代理成功下线(offline success)。此时查看控制台可以看到子设备为离线状态。
+
+
+#### 绑定解绑子设备
+* 绑定子设备
+未和网关绑定的子设备可以在设备端进行绑定操作
+```
+2021-07-20 14:18:28,801.801 [log.py:35] - DEBUG - sign base64 ********************
+2021-07-20 14:18:28,801.801 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$gateway/operation/xxx/test02'', ... (233 bytes)
+2021-07-20 14:18:28,802.802 [log.py:35] - DEBUG - publish success
+2021-07-20 14:18:28,802.802 [log.py:35] - DEBUG - on_publish:mid:3,userdata:None
+2021-07-20 14:18:28,873.873 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test02', ... (101 bytes)
+2021-07-20 14:18:29,003.003 [log.py:35] - DEBUG - client:xxx/dev001 bind success
+2021-07-20 14:18:29,003.003 [log.py:35] - DEBUG - bind success
+```
+以子设备`dev001`为例,观察日志可以看到与网关绑定成功,此时在控制台查看网关子设备会发现`dev001`已经存在于子设备列表中了。
+
+* 解绑子设备
+已经和网关绑定的子设备可以在设备端进行解绑操作
+```
+2021-07-20 14:17:04,807.807 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$gateway/operation/xxx/test02'', ... (99 bytes)
+2021-07-20 14:17:04,807.807 [log.py:35] - DEBUG - publish success
+2021-07-20 14:17:04,808.808 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-20 14:17:04,914.914 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test02', ... (103 bytes)
+2021-07-20 14:17:05,009.009 [log.py:35] - DEBUG - client:xxx/dev001 unbind success
+2021-07-20 14:17:05,009.009 [log.py:35] - DEBUG - unbind success
+```
+以子设备`dev001`为例,观察日志可以看到与网关解绑成功,此时在控制台查看网关子设备会发现`dev001`已经不在子设备列表中了。
+。
+
+#### 代理子设备基于数据模板通信
+数据模板义了一套通用的方法,实现设备的统一描述、统一控制,进而提供数据的流转和计算服务,实现不同设备的互联互通、数据的流转和融合,助力应用落地。具体协议请参考官网 [数据模板协议](https://cloud.tencent.com/document/product/1081/34916)。
+
+网关设备以多线程方式处理多个子设备事务,在子设备事务处理线程中初始化子设备数据模板,该过程会订阅子设备的数据模板 Topic,子设备与云端的消息通信应发往网关设备,有网关代理子设备进行消息收发。
+
+* 代理子设备订阅Topic
+```
+2021-07-20 14:34:16,975.975 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m2) [(b'xxx/dev001/data', 0)]
+2021-07-20 14:34:16,976.976 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'xxx/dev1/data', 0)]
+2021-07-20 14:34:16,977.977 [log.py:35] - DEBUG - subscribe success topic:xxx/dev001/data
+2021-07-20 14:34:16,977.977 [log.py:35] - DEBUG - gateway subdev subscribe success
+2021-07-20 14:34:16,977.977 [log.py:35] - DEBUG - subscribe success topic:xxx/dev1/data
+2021-07-20 14:34:16,977.977 [log.py:35] - DEBUG - gateway subdev subscribe success
+2021-07-20 14:34:16,977.977 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m4) [(b'$thing/down/property/xxx/dev001', 0)]
+2021-07-20 14:34:16,978.978 [log.py:35] - DEBUG - subscribe success topic:$thing/down/property/xxx/dev001
+2021-07-20 14:34:16,978.978 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m5) [(b'$thing/down/action/xxx/dev001', 0)]
+2021-07-20 14:34:16,978.978 [log.py:35] - DEBUG - subscribe success topic:$thing/down/action/xxx/dev001
+2021-07-20 14:34:16,978.978 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m6) [(b'$thing/down/event/xxx/dev001', 0)]
+2021-07-20 14:34:16,978.978 [log.py:35] - DEBUG - subscribe success topic:$thing/down/event/xxx/dev001
+2021-07-20 14:34:16,979.979 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m7) [(b'$thing/down/service/xxx/dev001', 0)]
+2021-07-20 14:34:16,979.979 [log.py:35] - DEBUG - subscribe success topic:$thing/down/service/xxx/dev001
+2021-07-20 14:34:16,979.979 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m9) [(b'$thing/down/property/xxx/dev1', 0)]
+2021-07-20 14:34:16,980.980 [log.py:35] - DEBUG - subscribe success topic:$thing/down/property/xxx/dev1
+2021-07-20 14:34:16,981.981 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m11) [(b'$thing/down/action/xxx/dev1', 0)]
+2021-07-20 14:34:16,981.981 [log.py:35] - DEBUG - subscribe success topic:$thing/down/action/xxx/dev1
+2021-07-20 14:34:16,982.982 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m13) [(b'$thing/down/event/xxx/dev1', 0)]
+2021-07-20 14:34:16,983.983 [log.py:35] - DEBUG - subscribe success topic:$thing/down/event/xxx/dev1
+2021-07-20 14:34:16,983.983 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m14) [(b'$thing/down/service/xxx/dev1', 0)]
+2021-07-20 14:34:16,983.983 [log.py:35] - DEBUG - subscribe success topic:$thing/down/service/xxx/dev1
+2021-07-20 14:34:17,063.063 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,063.063 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:4,userdata:None
+2021-07-20 14:34:17,065.065 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,065.065 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,065.065 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,065.065 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:6,userdata:None
+2021-07-20 14:34:17,066.066 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:5,userdata:None
+2021-07-20 14:34:17,066.066 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:3,userdata:None
+2021-07-20 14:34:17,066.066 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,066.066 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:2,userdata:None
+2021-07-20 14:34:17,070.070 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,070.070 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:13,userdata:None
+2021-07-20 14:34:17,070.070 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,070.070 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:7,userdata:None
+2021-07-20 14:34:17,070.070 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,071.071 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:14,userdata:None
+2021-07-20 14:34:17,071.071 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,071.071 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:11,userdata:None
+2021-07-20 14:34:17,071.071 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 14:34:17,071.071 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:9,userdata:None
+```
+观察以上日志,网关设备订阅了两个子设备的 Topic `${product_id}/${device_name}/data`用来接收云端下发的消息,在初始化子设备数据模板后又订阅了子设备的数据模板相关 Topic 用以接收事件通知及上报属性。
+
+* 接收子设备control消息
+调试子设备,在[控制台]-[设备调试]-[在线调试]-[属性调试]改变子设备属性,并下发到设备。
+```
+2021-07-20 14:34:22,861.861 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (152 bytes)
+2021-07-20 14:34:22,862.862 [log.py:35] - DEBUG - on_template_property:params:{'method': 'control', 'clientToken': 'clientToken-2842aa2d-a9c8-48e3-9160-e13aafa338b3', 'params': {'power_switch': 1, 'color': 2, 'brightness': 5, 'name': 'dev1'}},userdata:None
+2021-07-20 14:34:20,194.194 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev001', ... (155 bytes)
+2021-07-20 14:34:20,194.194 [log.py:35] - DEBUG - product_1:on_template_property:params:{'method': 'control', 'clientToken': 'clientToken-502eae4d-642f-47c0-b6a9-47b6e15c8f4f', 'params': {'power_switch': 1, 'color': 1, 'brightness': 10, 'name': 'dev001'}},userdata:None
+```
+观察日志可以看到网关设备成功接收到下发的控制消息,此时应将该消息分发到对于子设备中。
+
+* 接收子设备action消息
+调试子设备,在[控制台]-[设备调试]-[在线调试]-[行为调用]选择行为:开灯,并下发到设备。
+```
+2021-07-20 14:34:29,164.164 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/action/xxx/dev1', ... (143 bytes)
+2021-07-20 14:34:29,164.164 [log.py:35] - DEBUG - on_template_action:payload:{'method': 'action', 'clientToken': '146761673::81d315f7-17d9-4d00-9ce0-1ff782c25666', 'actionId': 'c_sw', 'timestamp': 1626762869, 'params': {'sw': 1}},userdata:None
+2021-07-20 14:34:35,394.394 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/action/xxx/dev001', ... (150 bytes)
+2021-07-20 14:34:35,394.394 [log.py:35] - DEBUG - on_template_action:payload:{'method': 'action', 'clientToken': '146761676::a27e4649-d94e-4acb-b636-5bfe7f4a592d', 'actionId': 'light_on', 'timestamp': 1626762875, 'params': {'is_on': 1}},userdata:None
+```
+观察日志可以看到网关设备成功接收到开灯消息,此时应将该消息分发到对于子设备中。
\ No newline at end of file
diff --git "a/explorer/doc/\350\216\267\345\217\226\350\256\276\345\244\207\346\234\200\346\226\260\344\270\212\346\212\245\344\277\241\346\201\257.md" "b/explorer/doc/\350\216\267\345\217\226\350\256\276\345\244\207\346\234\200\346\226\260\344\270\212\346\212\245\344\277\241\346\201\257.md"
index 3514e0f..04eb430 100644
--- "a/explorer/doc/\350\216\267\345\217\226\350\256\276\345\244\207\346\234\200\346\226\260\344\270\212\346\212\245\344\277\241\346\201\257.md"
+++ "b/explorer/doc/\350\216\267\345\217\226\350\256\276\345\244\207\346\234\200\346\226\260\344\270\212\346\212\245\344\277\241\346\201\257.md"
@@ -1,5 +1,5 @@
* [获取设备最新上报信息](#获取设备最新上报信息)
- * [发布获取设备最新上报信息的 Topic ](#发布获取设备最新上报信息的-Topic)
+ * [发布获取设备最新上报信息的 Topic](#发布获取设备最新上报信息的-Topic)
# 获取设备最新上报信息
@@ -11,35 +11,78 @@
## 发布获取设备最新上报信息的 Topic
-运行 [MqttSample.py](../sample/MqttSample.py) 的main函数,设备成功上线后,订阅过Topic后,调用template_get_status(),发布属性类型的 Topic:
-
-`$thing/up/property/{ProductID}/{DeviceName}`
+运行 [TemplateSample.py](../../explorer/sample/template/example_template.py),设备成功上线后,初始化数据模板,之后调用`templatGetStatus()`接口获取最新信息,该接口为异步接口,获取的信息会通知到注册的回调函数中.
示例代码如下:
+```python
+# 消息接收回调
+def on_template_property(topic, qos, payload, userdata):
+ logger.debug("%s:params:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
-```
-rc = te.template_get_status()
+ # save changed propertys
+ global g_property_params
+ g_property_params = payload
+
+ global g_control_msg_arrived
+ g_control_msg_arrived = True
+
+ # deal down stream and add your real value
+
+ reply_param = qcloud.ReplyPara()
+ reply_param.code = 0
+ reply_param.timeout_ms = 5 * 1000
+ reply_param.status_msg = '\0'
+
+ qcloud.templateControlReply(product_id, device_name, reply_param)
+ pass
+
+# 构造QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# 初始化日志
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# 注册mqtt回调
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# 获取设备product id和device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# mqtt连接
+qcloud.connect()
+
+# 数据模板初始化
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# 获取最新信息
+qcloud.templateGetStatus(product_id, device_name)
+
+# 断开mqtt连接
+qcloud.disconnect()
```
观察输出日志。
-
```
-2021-03-16 14:46:45,550.550 [explorer.py:198] - DEBUG - pub topic:$thing/up/property/ZPHBLEB4J5/dev001,payload:{'method': 'get_status', 'clientToken': 'ZPHBLEB4J5-0'},qos:0
-2021-03-16 14:46:45,551.551 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$thing/up/property/ZPHBLEB4J5/dev001'', ... (55 bytes)
-2021-03-16 14:46:45,551.551 [explorer.py:198] - DEBUG - publish success
-on_publish:mid:6,userdata:None
-2021-03-16 14:46:45,551.551 [explorer.py:198] - DEBUG - mid:6
-2021-03-16 14:46:45,632.632 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/ZPHBLEB4J5/dev001', ... (227 bytes)
-2021-03-16 14:46:45,633.633 [explorer.py:206] - INFO - __user_thread_on_message_callback,topic:$thing/down/property/ZPHBLEB4J5/dev001,payload:{'method': 'get_status_reply', 'clientToken': 'ZPHBLEB4J5-0', 'code': 0, 'status': 'success', 'data': {'reported': {'brightness': 0, 'name': '', 'power_switch': 0, 'color': 0}, 'control': {'color': 0, 'brightness': 1, 'name': '111', 'power_switch': 0}}},mid:0
-2021-03-16 14:46:45,633.633 [explorer.py:198] - DEBUG - reply payload:{'method': 'get_status_reply', 'clientToken': 'ZPHBLEB4J5-0', 'code': 0, 'status': 'success', 'data': {'reported': {'brightness': 0, 'name': '', 'power_switch': 0, 'color': 0}, 'control': {'color': 0, 'brightness': 1, 'name': '111', 'power_switch': 0}}}
-2021-03-16 14:46:45,633.633 [explorer.py:198] - DEBUG - pub topic:$thing/up/property/ZPHBLEB4J5/dev001,payload:{'method': 'clear_control', 'clientToken': 'ZPHBLEB4J5-0'},qos:0
-2021-03-16 14:46:45,633.633 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m7), 'b'$thing/up/property/ZPHBLEB4J5/dev001'', ... (58 bytes)
-2021-03-16 14:46:45,633.633 [explorer.py:198] - DEBUG - publish success
-2021-03-16 14:46:45,633.633 [explorer.py:198] - DEBUG - mid:7
-on_publish:mid:7,userdata:None
-2021-03-16 14:46:45,766.766 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/ZPHBLEB4J5/dev001', ... (89 bytes)
-2021-03-16 14:46:45,766.766 [explorer.py:206] - INFO - __user_thread_on_message_callback,topic:$thing/down/property/ZPHBLEB4J5/dev001,payload:{'method': 'clear_control_reply', 'clientToken': 'ZPHBLEB4J5-0', 'code': 0, 'status': 'success'},mid:0
-2021-03-16 14:46:45,766.766 [explorer.py:198] - DEBUG - reply payload:{'method': 'clear_control_reply', 'clientToken': 'ZPHBLEB4J5-0', 'code': 0, 'status': 'success'}
+2021-07-21 16:16:46,271.271 [log.py:35] - DEBUG - [template report] {'method': 'report', 'clientToken': 'xxx-0', 'params': {'power_switch': 1, 'color': 1, 'brightness': 1, 'name': 'test'}}
+2021-07-21 16:16:46,272.272 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$thing/up/property/xxx/dev1'', ... (127 bytes)
+2021-07-21 16:16:46,272.272 [log.py:35] - DEBUG - publish success
+2021-07-21 16:16:46,272.272 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 16:16:46,373.373 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (82 bytes)
+2021-07-21 16:16:46,373.373 [log.py:35] - DEBUG - on_template_property:params:{'method': 'report_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': 'success'},userdata:None
+2021-07-21 16:16:46,373.373 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$thing/up/property/xxx/dev1'', ... (52 bytes)
+2021-07-21 16:16:46,374.374 [log.py:35] - DEBUG - publish success
+2021-07-21 16:16:46,374.374 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+2021-07-21 16:16:48,930.930 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m7), 'b'$thing/up/property/xxx/dev1'', ... (55 bytes)
+2021-07-21 16:16:48,931.931 [log.py:35] - DEBUG - publish success
+2021-07-21 16:16:48,931.931 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-21 16:16:49,023.023 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (164 bytes)
+2021-07-21 16:16:49,023.023 [log.py:35] - DEBUG - on_template_property:params:{'method': 'get_status_reply', 'clientToken': 'xxx-1', 'code': 0, 'status': 'success', 'data': {'reported': {'name': 'test', 'power_switch': 1, 'color': 1, 'brightness': 1}}},userdata:None
+2021-07-21 16:16:49,023.023 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m8), 'b'$thing/up/property/xxx/dev1'', ... (62 bytes)
+2021-07-21 16:16:49,024.024 [log.py:35] - DEBUG - publish success
+2021-07-21 16:16:49,024.024 [log.py:35] - DEBUG - on_publish:mid:8,userdata:None
```
-以上是成功发布获取设备最新上报信息Topic的日志。如果已订阅 Topic,会接收到如上日志中的report和control消息。同时,在控制台中可以查看对应设备各个属性的最新值,对比可发现与接收到的订阅消息的data参数内各个属性值是一致的。在控制台中查看设备属性以及在线调试设备,请参考 [设备调试](https://cloud.tencent.com/document/product/1081/34741) 章节。
+观察日志可以看到,程序启动后先上报了一次属性消息,其中`name`字段为`test`,`power_switch`等值均为1,之后获取设备最新上报信息得到的字段与上报的完全一致。同时,在控制台中可以查看对应设备各个属性的最新值,对比可发现与接收到的订阅消息的data参数内各个属性值是一致的。在控制台中查看设备属性以及在线调试设备,请参考 [设备调试](https://cloud.tencent.com/document/product/1081/34741) 章节。
diff --git "a/explorer/doc/\350\256\242\351\230\205\344\270\216\345\217\226\346\266\210\350\256\242\351\230\205 Topic \344\270\273\351\242\230.md" "b/explorer/doc/\350\256\242\351\230\205\344\270\216\345\217\226\346\266\210\350\256\242\351\230\205 Topic \344\270\273\351\242\230.md"
deleted file mode 100644
index f4b9b45..0000000
--- "a/explorer/doc/\350\256\242\351\230\205\344\270\216\345\217\226\346\266\210\350\256\242\351\230\205 Topic \344\270\273\351\242\230.md"
+++ /dev/null
@@ -1,110 +0,0 @@
-* [订阅与取消订阅](#订阅与取消订阅)
- * [订阅 数据模板相关联 Topic 主题](#订阅-数据模板相关联-Topic-主题)
- * [取消订阅 Topic 主题](#取消订阅-Topic-主题)
-
-# 订阅与取消订阅
-
-在腾讯云物联网开发平台控制台(以下简称控制台)创建产品时,会默认生成一套产品的数据模板和一些标准功能,用户也可以自定义功能。数据模板对应的功能包含三大类:属性,事件和行为。控制台数据模板的使用,可参考官网 [数据模板](https://cloud.tencent.com/document/product/1081/44921) 章节。
-
-产品定义数据模板后,设备可以按照数据模板中的定义上报属性、事件,并可对设备下发远程控制指令,即对可写的设备属性进行修改。数据模板的管理详见 产品定义。数据模板协议包括设备属性上报、设备远程控制、获取设备最新上报信息、设备事件上报、设备行为。对应的定义和云端下发控制指令使用的 Topic 请参考官网 [数据模板协议](https://cloud.tencent.com/document/product/1081/34916) 章节。
-
-本文主要描述 如何对数据模板相关联 Topic 的订阅与取消订阅。
-
-## 订阅 数据模板相关联 Topic 主题
-
-运行 [MqttSample.py](../sample/MqttSample.py) 的main函数,设备成功上线后,调用template_init(),订阅数据模板相关联的属性、事件和行为类型的 Topic:
-
-```
-$thing/down/property/{ProductID}/{DeviceName}
-$thing/down/event/{ProductID}/{DeviceName}
-$thing/down/action/{ProductID}/{DeviceName}
-```
-示例代码如下:
-
-```
-def on_message(topic, payload, qos, userdata):
- print("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
- pass
-
-
-def on_publish(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-
-def on_subscribe(mid, granted_qos, userdata):
- print("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
- pass
-
-
-def on_unsubscribe(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-te = explorer.QcloudExplorer(device_file="./device_info.json")
-te.user_on_connect = on_connect
-te.user_on_disconnect = on_disconnect
-te.user_on_message = on_message
-te.user_on_publish = on_publish
-te.user_on_subscribe = on_subscribe
-te.user_on_unsubscribe = on_unsubscribe
-te.on_template_prop_changed = on_template_prop_changed
-te.on_template_event_post = on_template_event_post
-te.on_template_action = on_template_action
-
-te.template_setup("./example_config.json")
-te.mqtt_init(mqtt_domain="")
-te.connect_async()
-
-te.template_init()
-```
-
-观察日志。
-
-```
-2021-03-16 11:01:18,267.267 [explorer.py:198] - DEBUG - sub topic:$thing/down/property/ZPHBLEB4J5/dev001,qos:0
-2021-03-16 11:01:18,267.267 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'$thing/down/property/ZPHBLEB4J5/dev001', 0)]
-2021-03-16 11:01:18,268.268 [explorer.py:198] - DEBUG - subscribe success topic:$thing/down/property/ZPHBLEB4J5/dev001
-2021-03-16 11:01:18,268.268 [explorer.py:198] - DEBUG - mid:3
-2021-03-16 11:01:18,268.268 [explorer.py:198] - DEBUG - sub topic:$thing/down/event/ZPHBLEB4J5/dev001,qos:0
-2021-03-16 11:01:18,269.269 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m4) [(b'$thing/down/event/ZPHBLEB4J5/dev001', 0)]
-2021-03-16 11:01:18,269.269 [explorer.py:198] - DEBUG - subscribe success topic:$thing/down/event/ZPHBLEB4J5/dev001
-2021-03-16 11:01:18,269.269 [explorer.py:198] - DEBUG - mid:4
-2021-03-16 11:01:18,269.269 [explorer.py:198] - DEBUG - sub topic:$thing/down/action/ZPHBLEB4J5/dev001,qos:0
-2021-03-16 11:01:18,269.269 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m5) [(b'$thing/down/action/ZPHBLEB4J5/dev001', 0)]
-2021-03-16 11:01:18,270.270 [explorer.py:198] - DEBUG - subscribe success topic:$thing/down/action/ZPHBLEB4J5/dev001
-```
-以上日志为 订阅 Topic 主题 成功。
-
-## 取消订阅 Topic 主题
-
-运行 [MqttSample.py](../sample/MqttSample.py) ,设备成功上线后,订阅过Topic后,调用template_deinit(),取消订阅属性、事件和行为类型的 Topic:
-
-```
-$thing/down/property/{ProductID}/{DeviceName}
-$thing/down/event/{ProductID}/{DeviceName}
-$thing/down/action/{ProductID}/{DeviceName}
-```
-示例代码如下:
-
-```
-te.template_deinit()
-```
-
-观察输出日志。
-
-```
-2021-03-16 14:29:40,678.678 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m6) [b'$thing/down/property/ZPHBLEB4J5/dev001']
-2021-03-16 14:29:40,678.678 [explorer.py:198] - DEBUG - mid:6
-2021-03-16 14:29:40,678.678 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m7) [b'$thing/down/event/ZPHBLEB4J5/dev001']
-2021-03-16 14:29:40,679.679 [explorer.py:198] - DEBUG - mid:7
-2021-03-16 14:29:40,679.679 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m8) [b'$thing/down/action/ZPHBLEB4J5/dev001']
-2021-03-16 14:29:40,679.679 [explorer.py:198] - DEBUG - mid:8
-2021-03-16 14:29:40,726.726 [client.py:2165] - DEBUG - Received UNSUBACK (Mid: 8)
-on_unsubscribe:mid:8,userdata:None
-2021-03-16 14:29:40,726.726 [client.py:2165] - DEBUG - Received UNSUBACK (Mid: 6)
-on_unsubscribe:mid:6,userdata:None
-2021-03-16 14:29:40,727.727 [client.py:2165] - DEBUG - Received UNSUBACK (Mid: 7)
-on_unsubscribe:mid:7,userdata:None
-```
-以上日志为 取消订阅 Topic 主题 成功。
diff --git "a/explorer/doc/\350\256\242\351\230\205\344\270\216\345\217\226\346\266\210\350\256\242\351\230\205Topic.md" "b/explorer/doc/\350\256\242\351\230\205\344\270\216\345\217\226\346\266\210\350\256\242\351\230\205Topic.md"
new file mode 100644
index 0000000..2a7aa9d
--- /dev/null
+++ "b/explorer/doc/\350\256\242\351\230\205\344\270\216\345\217\226\346\266\210\350\256\242\351\230\205Topic.md"
@@ -0,0 +1,159 @@
+* [订阅与取消订阅](#订阅与取消订阅)
+ * [订阅数据模板相关联 Topic 主题](#订阅数据模板相关联-Topic-主题)
+ * [接收设备绑定解绑通知消息](#接收设备绑定解绑通知消息)
+ * [取消订阅 Topic 主题](#取消订阅-Topic-主题)
+
+# 订阅与取消订阅
+
+在腾讯云物联网开发平台控制台(以下简称控制台)创建产品时,会默认生成一套产品的数据模板和一些标准功能,用户也可以自定义功能。数据模板对应的功能包含三大类:属性,事件和行为。控制台数据模板的使用,可参考官网 [数据模板](https://cloud.tencent.com/document/product/1081/44921) 章节。
+
+产品定义数据模板后,设备可以按照数据模板中的定义上报属性、事件,并可对设备下发远程控制指令,即对可写的设备属性进行修改。数据模板的管理详见 产品定义。数据模板协议包括设备属性上报、设备远程控制、获取设备最新上报信息、设备事件上报、设备行为。对应的定义和云端下发控制指令使用的 Topic 请参考官网 [数据模板协议](https://cloud.tencent.com/document/product/1081/34916) 章节。
+
+本文主要描述 如何对数据模板相关联 Topic 的订阅与取消订阅。
+
+## 订阅数据模板相关联 Topic 主题
+
+运行 [TemplateSample.py](../../explorer/sample/template/example_template.py),初始化数据模板会自动订阅数据模板相关联的属性、事件和行为类型的 Topic:
+```
+$thing/down/property/{ProductID}/{DeviceName}
+$thing/down/event/{ProductID}/{DeviceName}
+$thing/down/action/{ProductID}/{DeviceName}
+$thing/down/service/{ProductID}/{DeviceName}
+```
+订阅Topic后对应的下行消息由初始化数据模板时注册的回调函数给出,回调函数定义如下:
+```python
+def on_template_property(topic, qos, payload, userdata):
+ """属性回调
+ 接受$thing/down/property/{ProductID}/{DeviceName}的下行消息
+ Args:
+ topic: 下行主题
+ qos: qos
+ payload: 下行消息内容
+ userdata: 用户注册的任意结构
+ """
+ pass
+
+def on_template_service(topic, qos, payload, userdata):
+ """服务回调
+ 接受$thing/down/service/{ProductID}/{DeviceName}的下行消息
+ Args:
+ topic: 下行主题
+ qos: qos
+ payload: 下行消息内容
+ userdata: 用户注册的任意结构
+ """
+ pass
+
+def on_template_event(topic, qos, payload, userdata):
+ """事件回调
+ 接受$thing/down/event/{ProductID}/{DeviceName}的下行消息
+ Args:
+ topic: 下行主题
+ qos: qos
+ payload: 下行消息内容
+ userdata: 用户注册的任意结构
+ """
+ pass
+
+def on_template_action(topic, qos, payload, userdata):
+ """行为回调
+ 接受$thing/down/action/{ProductID}/{DeviceName}的下行消息
+ Args:
+ topic: 下行主题
+ qos: qos
+ payload: 下行消息内容
+ userdata: 用户注册的任意结构
+ """
+ pass
+```
+
+示例代码如下:
+```python
+# 构造QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# 初始化日志
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# 注册mqtt回调
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# 获取设备product id和device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# mqtt连接
+qcloud.connect()
+
+# 数据模板初始化,自动订阅相关Topic
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+```
+
+观察日志。
+```
+2021-07-21 16:59:34,956.956 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-21 16:59:34,956.956 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-21 16:59:35,432.432 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-21 16:59:35,491.491 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-21 16:59:35,491.491 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-21 16:59:35,958.958 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$thing/down/property/xxx/dev1', 0)]
+2021-07-21 16:59:35,958.958 [log.py:35] - DEBUG - subscribe success topic:$thing/down/property/xxx/dev1
+2021-07-21 16:59:35,959.959 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m2) [(b'$thing/down/action/xxx/dev1', 0)]
+2021-07-21 16:59:35,959.959 [log.py:35] - DEBUG - subscribe success topic:$thing/down/action/xxx/dev1
+2021-07-21 16:59:35,960.960 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'$thing/down/event/xxx/dev1', 0)]
+2021-07-21 16:59:35,960.960 [log.py:35] - DEBUG - subscribe success topic:$thing/down/event/xxx/dev1
+2021-07-21 16:59:35,960.960 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m4) [(b'$thing/down/service/xxx/dev1', 0)]
+2021-07-21 16:59:35,960.960 [log.py:35] - DEBUG - subscribe success topic:$thing/down/service/xxx/dev1
+2021-07-21 16:59:36,006.006 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 16:59:36,006.006 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-21 16:59:36,009.009 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 16:59:36,010.010 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 16:59:36,010.010 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:2,userdata:None
+2021-07-21 16:59:36,010.010 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:4,userdata:None
+2021-07-21 16:59:36,016.016 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 16:59:36,016.016 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:3,userdata:None
+```
+观察日志可以看到成功设备订阅了Topic.
+
+## 接收设备绑定解绑通知消息
+数据模板初始化后程序配合腾讯连连可以体验接收下行消息功能.
+* 接收绑定设备通知
+在控制台打开设备二维码,使用腾讯连连小程序扫描绑定设备后设备端会收到`bind_device`消息,日志如下:
+```
+2021-07-29 15:09:09,407.407 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/service/xxx/xxx', ... (86 bytes)
+2021-07-29 15:09:09,407.407 [log.py:35] - DEBUG - on_template_service:payload:{'method': 'bind_device', 'clientToken': 'clientToken-8l1b8SX3cw', 'timestamp': 1627542549},userdata:None
+```
+
+* 接收解绑设备通知
+在腾讯连连中选择删除设备后设备端会收到`unbind_device`消息,日志如下:
+```
+2021-07-29 15:09:28,343.343 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/service/xxx/xxx', ... (118 bytes)
+2021-07-29 15:09:28,345.345 [log.py:35] - DEBUG - on_template_service:payload:{'method': 'unbind_device', 'DeviceId': 'xxx/xxx', 'clientToken': 'clientToken-Bcjwl8Io0', 'timestamp': 1627542568},userdata:None
+```
+
+## 取消订阅 Topic 主题
+
+运行 [TemplateSample.py](../../explorer/sample/template/example_template.py),退出时调用`templateDeinit()`接口取消订阅 Topic.
+
+示例代码如下:
+```python
+# 注销数据模板
+qcloud.templateDeinit(product_id, device_name)
+
+# 断开mqtt连接
+qcloud.disconnect()
+```
+
+观察输出日志。
+```
+2021-07-21 17:21:33,833.833 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m5) [b'$thing/down/property/xxx/dev1', b'$thing/down/event/xxx/dev1', b'$thing/down/action/xxx/dev1', b'$thing/down/service/xxx/dev1']
+2021-07-21 17:21:33,913.913 [client.py:2165] - DEBUG - Received UNSUBACK (Mid: 5)
+2021-07-21 17:21:33,914.914 [log.py:35] - DEBUG - on_unsubscribe:mid:5,userdata:None
+2021-07-21 17:21:35,218.218 [log.py:35] - DEBUG - disconnect
+2021-07-21 17:21:35,218.218 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-21 17:21:35,219.219 [log.py:35] - DEBUG - LoopThread thread exit
+2021-07-21 17:21:35,219.219 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
+```
+观察日志可以看到成功设备取消订阅了Topic.
diff --git "a/explorer/doc/\350\256\276\345\244\207\344\277\241\346\201\257\344\270\212\346\212\245.md" "b/explorer/doc/\350\256\276\345\244\207\344\277\241\346\201\257\344\270\212\346\212\245.md"
index 3a9d4af..a1ee2b1 100644
--- "a/explorer/doc/\350\256\276\345\244\207\344\277\241\346\201\257\344\270\212\346\212\245.md"
+++ "b/explorer/doc/\350\256\276\345\244\207\344\277\241\346\201\257\344\270\212\346\212\245.md"
@@ -7,37 +7,82 @@
## 发布设备信息上报的 Topic
-运行 [MqttSample.py](../sample/MqttSample.py) 的main函数,设备成功上线后,订阅过Topic后,调用template_report_sys_info(),发布属性类型的Topic:
-`$thing/up/property/{ProductID}/{DeviceName}`
+运行 [TemplateSample.py](../../explorer/sample/template/example_template.py) ,设备成功上线后,初始化数据模板,之后调用`templateReportSysInfo()`接口进行设备信息上报,设备信息上报的 Topic:
+`$thing/up/property/{ProductID}/{DeviceName}`
示例代码如下:
+```python
+# 消息接收回调
+def on_template_property(topic, qos, payload, userdata):
+ logger.debug("%s:params:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
-```
+ # save changed propertys
+ global g_property_params
+ g_property_params = payload
+
+ global g_control_msg_arrived
+ g_control_msg_arrived = True
+
+ # deal down stream and add your real value
+
+ reply_param = qcloud.ReplyPara()
+ reply_param.code = 0
+ reply_param.timeout_ms = 5 * 1000
+ reply_param.status_msg = '\0'
+
+ qcloud.templateControlReply(product_id, device_name, reply_param)
+ pass
+
+# 构造QcloudExplorer
+qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+# 初始化日志
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+# 注册mqtt回调
+qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+# 获取设备product id和device name
+product_id = qcloud.getProductID()
+device_name = qcloud.getDeviceName()
+
+# mqtt连接
+qcloud.connect()
+
+# 数据模板初始化
+qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+qcloud.templateSetup(product_id, device_name, "sample/template/template_config.json")
+
+# 模拟设备信息
sys_info = {
- "module_hardinfo": "ESP8266",
- "module_softinfo": "V1.0",
- "fw_ver": "3.1.4",
- "imei": "11-22-33-44",
- "lat": "22.546015",
- "lon": "113.941125",
- "device_label": {
- "append_info": "your self define info"
- }
- }
-te.template_report_sys_info(sys_info)
+ "module_hardinfo": "ESP8266",
+ "module_softinfo": "V1.0",
+ "fw_ver": "3.1.4",
+ "imei": "11-22-33-44",
+ "lat": "22.546015",
+ "lon": "113.941125",
+ "device_label": {
+ "append_info": "your self define info"
+ }
+}
+# 上报设备信息
+qcloud.templateReportSysInfo(product_id, device_name, sys_info)
+
+# 断开mqtt连接
+qcloud.disconnect()
```
观察输出日志。
-
```
-2021-03-16 14:47:49,032.032 [explorer.py:198] - DEBUG - pub topic:$thing/up/property/ZPHBLEB4J5/dev001,payload:{'method': 'report_info', 'clientToken': 'ZPHBLEB4J5-1', 'params': {'module_hardinfo': 'ESP8266', 'module_softinfo': 'V1.0', 'fw_ver': '3.1.4', 'imei': '11-22-33-44', 'lat': '22.546015', 'lon': '113.941125', 'device_label': {'append_info': 'your self define info'}}},qos:0
-2021-03-16 14:47:49,032.032 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m8), 'b'$thing/up/property/ZPHBLEB4J5/dev001'', ... (266 bytes)
-2021-03-16 14:47:49,032.032 [explorer.py:198] - DEBUG - publish success
-2021-03-16 14:47:49,033.033 [explorer.py:198] - DEBUG - mid:8
-on_publish:mid:8,userdata:None
-2021-03-16 14:47:49,139.139 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/ZPHBLEB4J5/dev001', ... (87 bytes)
-2021-03-16 14:47:49,140.140 [explorer.py:206] - INFO - __user_thread_on_message_callback,topic:$thing/down/property/ZPHBLEB4J5/dev001,payload:{'method': 'report_info_reply', 'clientToken': 'ZPHBLEB4J5-1', 'code': 0, 'status': 'success'},mid:0
-2021-03-16 14:47:49,140.140 [explorer.py:198] - DEBUG - reply payload:{'method': 'report_info_reply', 'clientToken': 'ZPHBLEB4J5-1', 'code': 0, 'status': 'success'}
+2021-07-21 16:39:33,894.894 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$thing/up/property/xxx/dev1'', ... (266 bytes)
+2021-07-21 16:39:33,895.895 [log.py:35] - DEBUG - publish success
+2021-07-21 16:39:33,896.896 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-21 16:39:33,965.965 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$thing/down/property/xxx/dev1', ... (87 bytes)
+2021-07-21 16:39:33,966.966 [log.py:35] - DEBUG - on_template_property:params:{'method': 'report_info_reply', 'clientToken': 'xxx-0', 'code': 0, 'status': 'success'},userdata:None
+2021-07-21 16:39:33,966.966 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$thing/up/property/xxx/dev1'', ... (52 bytes)
+2021-07-21 16:39:33,966.966 [log.py:35] - DEBUG - publish success
+2021-07-21 16:39:33,966.966 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
```
-以上是成功发布设备信息上报Topic的日志。
+观察日志可以看到,设备信息成功上报,并收到了云端响应.
diff --git a/explorer/explorer.py b/explorer/explorer.py
index c7d0de8..b4411c7 100644
--- a/explorer/explorer.py
+++ b/explorer/explorer.py
@@ -27,140 +27,43 @@
import socket
import string
import time
-# import re
import paho.mqtt.client as mqtt
from enum import Enum
from enum import IntEnum
-# from paho.mqtt.client import MQTTMessage
from Crypto.Cipher import AES
from hub.hub import QcloudHub
+from explorer.services.template.template import Template
class QcloudExplorer(object):
- __IOT_CA_CRT = "\
------BEGIN CERTIFICATE-----\n\
-MIIDxTCCAq2gAwIBAgIJALM1winYO2xzMA0GCSqGSIb3DQEBCwUAMHkxCzAJBgNV\n\
-BAYTAkNOMRIwEAYDVQQIDAlHdWFuZ0RvbmcxETAPBgNVBAcMCFNoZW5aaGVuMRAw\n\
-DgYDVQQKDAdUZW5jZW50MRcwFQYDVQQLDA5UZW5jZW50IElvdGh1YjEYMBYGA1UE\n\
-AwwPd3d3LnRlbmNlbnQuY29tMB4XDTE3MTEyNzA0MjA1OVoXDTMyMTEyMzA0MjA1\n\
-OVoweTELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUd1YW5nRG9uZzERMA8GA1UEBwwI\n\
-U2hlblpoZW4xEDAOBgNVBAoMB1RlbmNlbnQxFzAVBgNVBAsMDlRlbmNlbnQgSW90\n\
-aHViMRgwFgYDVQQDDA93d3cudGVuY2VudC5jb20wggEiMA0GCSqGSIb3DQEBAQUA\n\
-A4IBDwAwggEKAoIBAQDVxwDZRVkU5WexneBEkdaKs4ehgQbzpbufrWo5Lb5gJ3i0\n\
-eukbOB81yAaavb23oiNta4gmMTq2F6/hAFsRv4J2bdTs5SxwEYbiYU1teGHuUQHO\n\
-iQsZCdNTJgcikga9JYKWcBjFEnAxKycNsmqsq4AJ0CEyZbo//IYX3czEQtYWHjp7\n\
-FJOlPPd1idKtFMVNG6LGXEwS/TPElE+grYOxwB7Anx3iC5ZpE5lo5tTioFTHzqbT\n\
-qTN7rbFZRytAPk/JXMTLgO55fldm4JZTP3GQsPzwIh4wNNKhi4yWG1o2u3hAnZDv\n\
-UVFV7al2zFdOfuu0KMzuLzrWrK16SPadRDd9eT17AgMBAAGjUDBOMB0GA1UdDgQW\n\
-BBQrr48jv4FxdKs3r0BkmJO7zH4ALzAfBgNVHSMEGDAWgBQrr48jv4FxdKs3r0Bk\n\
-mJO7zH4ALzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDRSjXnBc3T\n\
-d9VmtTCuALXrQELY8KtM+cXYYNgtodHsxmrRMpJofsPGiqPfb82klvswpXxPK8Xx\n\
-SuUUo74Fo+AEyJxMrRKlbJvlEtnpSilKmG6rO9+bFq3nbeOAfat4lPl0DIscWUx3\n\
-ajXtvMCcSwTlF8rPgXbOaSXZidRYNqSyUjC2Q4m93Cv+KlyB+FgOke8x4aKAkf5p\n\
-XR8i1BN1OiMTIRYhGSfeZbVRq5kTdvtahiWFZu9DGO+hxDZObYGIxGHWPftrhBKz\n\
-RT16Amn780rQLWojr70q7o7QP5tO0wDPfCdFSc6CQFq/ngOzYag0kJ2F+O5U6+kS\n\
-QVrcRBDxzx/G\n\
------END CERTIFICATE-----"
-
- def __init__(self, device_file, tls=True, user_data=None):
- self.__explorer_log = QcloudHub.ExplorerLog()
- self.__PahoLog = logging.getLogger("Paho")
- self.__PahoLog.setLevel(logging.DEBUG)
- self.__device_file = QcloudHub.DeviceInfo(device_file, self.__explorer_log)
- self.__topic_info = None
-
- # set state initialized
- self.__explorer_state = QcloudHub.HubState.INITIALIZED
-
- self.__iot_ca_crt = self.__IOT_CA_CRT
+ def __init__(self, device_file, tls=True, userdata=None, domain=None, useWebsocket=False):
+ self.__device_file = device_file
self.__tls = tls
- # 默认使用密钥认证
- self.__key_mode = True
-
- self.__mqtt_client = None
- self.__host = None
-
- # 用户传参
- self.__user_data = user_data
-
- # mqtt set
- self.__set_mqtt_param()
- self.__useWebsocket = None
-
- # topic
- self.__user_topics_subscribe_request = {}
- self.__user_topics_unsubscribe_request = {}
- self.__user_topics_request_lock = threading.Lock()
- self.__user_topics_unsubscribe_request_lock = threading.Lock()
-
- self.__template_prop_report_reply_mid = {}
- self.__template_prop_report_reply_mid_lock = threading.Lock()
- self.__user_topics = {}
-
- # gateway
- self.__gateway_session_client_id = None
- self.__gateway_session_online_reply = {}
- self.__gateway_session_offline_reply = {}
- self.__gateway_session_bind_reply = {}
- self.__gateway_session_unbind_reply = {}
- self.__gateway_raply = False
-
- # 网关子设备property topic订阅的子设备列表
- self.__gateway_subdev_property_topic_list = []
- self.__gateway_subdev_action_topic_list = []
- self.__gateway_subdev_event_topic_list = []
-
- # 网关子设备property回调字典(["product_id":callback])
- self.__on_gateway_subdev_prop_cb_dict = {}
- self.__on_gateway_subdev_action_cb_dict = {}
- self.__on_gateway_subdev_event_cb_dict = {}
-
- self.__gateway_session_online_lock = threading.Lock()
- self.__gateway_session_offline_lock = threading.Lock()
- self.__gateway_session_bind_lock = threading.Lock()
- self.__gateway_session_unbind_lock = threading.Lock()
-
- # 防止多线程同时调用同一注册函数
- self.__register_property_cb_lock = threading.Lock()
- self.__register_action_cb_lock = threading.Lock()
- self.__register_event_cb_lock = threading.Lock()
-
- self.__gateway_subdev_append_lock = threading.Lock()
- self.__handle_topic_lock = threading.Lock()
+ """ 用户传参 """
+ self.__userdata = userdata
+ self.__domain = domain
+ self.__useWebsocket = useWebsocket
+ """ 存放用户注册的回调函数 """
+ self.__user_callback = {}
+ self.__provider = QcloudHub(device_file, userdata, tls, domain, useWebsocket)
+ self.__hub = self.__provider.hub
+ """
+ 向hub注册mqtt disconnect回调
+ explorer层清理相关资源
+ """
+ self.__hub.register_explorer_callback("$explorer/from/disconnect", self.__on_disconnect)
+
+ """ 用户回调注册到hub层 """
+ # self.__register_hub_event_callback()
+
+ self.__logger = self.__hub._logger
+
+ self.__template_map = {}
+
+ self.__topic = self.__hub._topic
# data template
- self.__template_setup_state = False
self.__is_subscribed_property_topic = False
- self.template_token_num = 0
-
- self.template_events_list = []
- self.template_action_list = []
- self.template_property_list = []
- self.gateway_subdev_list = []
-
- # data template reply
- self.__replyAck = -1
-
- # user data template callback
- # 做成回调函数字典,通过topic调用对应回调(针对网关有不同数据模板设备的情况)
- self.__on_template_prop_changed = None
- self.__on_template_action = None
- self.__on_template_event_post = None
- self.__on_subscribe_service_post = None
-
- # ota
- # 保存__on_subscribe()返回的mid和qos对,用以判断订阅是否成功
- self.__ota_subscribe_res = {}
- self.__ota_manager = None
- self.__ota_version_len_min = 1
- self.__ota_version_len_max = 32
- self.http_manager = None
-
- # connect with async
- self.__connect_async_req = False
- self.__worker_loop_exit_req = False
- self.__worker_loop_runing_state = False
- self.__worker_loop_exit_req_lock = threading.Lock()
# user mqtt callback
self.__user_on_connect = None
@@ -169,2063 +72,954 @@ def __init__(self, device_file, tls=True, user_data=None):
self.__user_on_subscribe = None
self.__user_on_unsubscribe = None
self.__user_on_message = None
-
- # ota
- self.__user_on_ota_report = None
-
- # rrpc callback
- self.__user_on_rrpc_message = None
- self.__process_id = None
-
- # shadow
- self._shadow_token_num = 0
-
- # construct thread handle
- self.__loop_thread = QcloudHub.LoopThread(self.__explorer_log)
- self.__user_thread = QcloudHub.UserCallBackTask(self.__explorer_log)
- self.__user_cmd_cb_init()
-
- pass
-
- @property
- def user_on_connect(self):
- return self.__user_on_connect
-
- @user_on_connect.setter
- def user_on_connect(self, value):
- self.__user_on_connect = value
- pass
-
- @property
- def user_on_disconnect(self):
- return self.__user_on_disconnect
-
- @user_on_disconnect.setter
- def user_on_disconnect(self, value):
- self.__user_on_disconnect = value
- pass
-
- @property
- def user_on_publish(self):
- return self.__user_on_publish
-
- @user_on_publish.setter
- def user_on_publish(self, value):
- self.__user_on_publish = value
- pass
-
- @property
- def user_on_subscribe(self):
- return self.__user_on_subscribe
-
- @user_on_subscribe.setter
- def user_on_subscribe(self, value):
- self.__user_on_subscribe = value
- pass
-
- @property
- def user_on_unsubscribe(self):
- return self.__user_on_unsubscribe
-
- @user_on_unsubscribe.setter
- def user_on_unsubscribe(self, value):
- self.__user_on_unsubscribe = value
- pass
-
- @property
- def user_on_message(self):
- return self.__user_on_message
-
- @user_on_message.setter
- def user_on_message(self, value):
- self.__user_on_message = value
- pass
-
- @property
- def user_on_ota_report(self):
- return self.__user_on_ota_report
-
- @user_on_ota_report.setter
- def user_on_ota_report(self, value):
- self.__user_on_ota_report = value
pass
- @property
- def user_on_rrpc_message(self):
- return self.__user_on_rrpc_message
-
- @user_on_rrpc_message.setter
- def user_on_rrpc_message(self, value):
- self.__user_on_rrpc_message = value
-
- @property
- def on_template_prop_changed(self):
- return self.__on_template_prop_changed
-
- @on_template_prop_changed.setter
- def on_template_prop_changed(self, value):
- self.__on_template_prop_changed = value
-
- @property
- def on_template_action(self):
- return self.__on_template_action
-
- @on_template_action.setter
- def on_template_action(self, value):
- self.__on_template_action = value
-
- @property
- def on_template_event_post(self):
- return self.__on_template_event_post
-
- @on_template_event_post.setter
- def on_template_event_post(self, value):
- self.__on_template_event_post = value
-
- @property
- def on_subscribe_service_post(self):
- return self.__on_subscribe_service_post
-
- @on_subscribe_service_post.setter
- def on_subscribe_service_post(self, value):
- self.__on_subscribe_service_post = value
-
- def __user_cmd_cb_init(self):
- self.__user_thread = QcloudHub.UserCallBackTask(self.__explorer_log)
- self.__user_cmd_on_connect = "user_on_connect"
- self.__user_cmd_on_disconnect = "user_on_disconnect"
- self.__user_cmd_on_message = "user_on_message"
- self.__user_cmd_on_publish = "user_on_publish"
- self.__user_cmd_on_subscribe = "user_on_subscribe"
- self.__user_cmd_on_unsubscribe = "user_on_unsubscribe"
- self.__user_thread.register_callback_with_cmd(self.__user_cmd_on_connect,
- self.__user_thread_on_connect_callback)
- self.__user_thread.register_callback_with_cmd(self.__user_cmd_on_disconnect,
- self.__user_thread_on_disconnect_callback)
- self.__user_thread.register_callback_with_cmd(self.__user_cmd_on_message,
- self.__user_thread_on_message_callback)
- self.__user_thread.register_callback_with_cmd(self.__user_cmd_on_publish,
- self.__user_thread_on_publish_callback)
- self.__user_thread.register_callback_with_cmd(self.__user_cmd_on_subscribe,
- self.__user_thread_on_subscribe_callback)
- self.__user_thread.register_callback_with_cmd(self.__user_cmd_on_unsubscribe,
- self.__user_thread_on_unsubscribe_callback)
- self.__user_thread.start()
+ class ReplyPara(object):
+ def __init__(self):
+ self.timeout_ms = 0
+ self.code = -1
+ self.status_msg = None
- pass
+ class LoggerLevel(Enum):
+ INFO = "info"
+ DEBUG = "debug"
+ WARNING = "warring"
+ ERROR = "error"
- def enableLogger(self, level):
- self.__explorer_log.set_level(level)
- self.__explorer_log.enable_logger()
- if self.__mqtt_client is not None:
- self.__mqtt_client.enable_logger(self.__PahoLog)
- self.__PahoLog.setLevel(level)
-
- def __set_mqtt_callback(self):
- self.__mqtt_client.on_connect = self.__on_connect
- self.__mqtt_client.on_disconnect = self.__on_disconnect
- self.__mqtt_client.on_message = self.__on_message
- self.__mqtt_client.on_publish = self.__on_publish
- self.__mqtt_client.on_subscribe = self.__on_subscribe
- self.__mqtt_client.on_unsubscribe = self.__on_unsubscribe
-
- def __set_mqtt_param(self):
- self.__mqtt_tls_port = 8883
- self.__mqtt_tcp_port = 1883
- self.__mqtt_socket_tls_port = 443
- self.__mqtt_socket_tcp_port = 80
- self.__mqtt_protocol = "MQTTv31"
- self.__mqtt_transport = "TCP"
- self.__mqtt_secure = "TLS"
- self.__mqtt_clean_session = True
- self.__mqtt_keep_alive = 60
-
- self.__mqtt_auto_reconnect_min_sec = 1
- self.__mqtt_auto_reconnect_max_sec = 60
- self.__mqtt_max_queued_message = 40
- self.__mqtt_max_inflight_message = 20
- self.__mqtt_auto_reconnect_sec = 0
- self.__mqtt_request_timeout = 10
-
- # default MQTT/CoAP timeout value when connect/pub/sub
- self.__mqtt_command_timeout = 5
+ def __on_disconnect(self, client, userdata, rc):
+ """
+ 清理数据模板资源
+ """
+ for template in self.__template_map.values():
+ if template is not None:
+ template.template_reset()
pass
- def dynregDevice(self, timeout=10):
- """
- dynamic device to tencent cloud
- :param timeout: http/https timeout
- :return: (code, msg): code 0 is success, msg is psk. Other is failed.
- """
- sign_format = '%s\n%s\n%s\n%s\n%s\n%d\n%d\n%s'
- url_format = '%s://ap-guangzhou.gateway.tencentdevices.com/device/register'
- request_format = "{\"ProductId\":\"%s\",\"DeviceName\":\"%s\"}"
-
- device_name = self.__device_file.device_name
- product_id = self.__device_file.product_id
- product_secret = self.__device_file.product_secret
-
- request_text = request_format % (product_id, device_name)
- request_hash = hashlib.sha256(request_text.encode("utf-8")).hexdigest()
-
- nonce = random.randrange(2147483647)
- timestamp = int(time.time())
- sign_content = sign_format % (
- "POST", "ap-guangzhou.gateway.tencentdevices.com",
- "/device/register", "", "hmacsha256", timestamp,
- nonce, request_hash)
- sign_base64 = base64.b64encode(hmac.new(product_secret.encode("utf-8"),
- sign_content.encode("utf-8"), hashlib.sha256).digest())
-
- # self.__explorer_log.debug('sign base64 {}'.format(sign_base64))
- header = {
- 'Content-Type': 'application/json; charset=utf-8',
- "X-TC-Algorithm": "hmacsha256",
- "X-TC-Timestamp": timestamp,
- "X-TC-Nonce": nonce,
- "X-TC-Signature": sign_base64
- }
- data = bytes(request_text, encoding='utf-8')
-
- context = None
- if self.__tls:
- request_url = url_format % 'https'
- context = ssl.create_default_context(
- ssl.Purpose.CLIENT_AUTH, cadata=self.__iot_ca_crt)
+ def __handle_subdev_topic(self, topic, qos, payload):
+ """ 回调用户处理 """
+ if self.__user_callback[topic] is not None:
+ self.__user_callback[topic](topic, qos, payload, self.__userdata)
else:
- request_url = url_format % 'http'
- self.__explorer_log.info('dynreg url {}'.format(request_url))
- req = urllib.request.Request(request_url, data=data, headers=header)
- with urllib.request.urlopen(req, timeout=timeout, context=context) as url_file:
- reply_data = url_file.read().decode('utf-8')
- reply_obj = json.loads(reply_data)
- resp = reply_obj['Response']
-
- if 'Len' in resp and resp['Len'] > 0:
- reply_obj_data = reply_obj['Response']["Payload"]
- if reply_obj_data is not None:
- psk = QcloudHub._AESUtil.decrypt(reply_obj_data.encode('UTF-8') , product_secret[:QcloudHub._AESUtil.BLOCK_SIZE_16].encode('UTF-8'),
- '0000000000000000'.encode('UTF-8'))
- psk = psk.decode('UTF-8', 'ignore').strip().strip(b'\x00'.decode())
- user_dict = json.loads(psk)
- self.__explorer_log.info('encrypt type: {}'.format(
- user_dict['encryptionType']))
- return 0, user_dict['psk']
- else:
- self.__explorer_log.warring('payload is null')
- return -1, 'payload is null'
- else:
- err_code = resp['Error']
- self.__explorer_log.error('code: {}, error message: {}'.format(
- err_code, err_code['Message']))
- return -1, err_code['Message']
-
- # 遍历逻辑待优化
- def __topic_match(self, payload, topic, plist, pdict):
- for tup in plist:
- tup_product = tup[0]
- tup_topic = tup[1]
- # 网关子设备的订阅
- if topic == tup_topic:
- if tup_product in pdict:
- # params = payload["params"]
- user_callback = pdict[tup_product]
- print("call user_callback")
- user_callback(payload, self.__user_data)
- return 0
- else:
- self.__explorer_log.warring('topic not registed')
- return 1
- else:
- continue
- return 2
-
- def __handle_nonStandard_topic(self, topic, payload):
- # 判断topic类型(property/action/event)
- with self.__handle_topic_lock:
- rc = self.__topic_match(payload,
- topic,
- self.__gateway_subdev_property_topic_list,
- self.__on_gateway_subdev_prop_cb_dict)
- if rc == 0:
- return 0
-
- rc = self.__topic_match(payload,
- topic,
- self.__gateway_subdev_action_topic_list,
- self.__on_gateway_subdev_action_cb_dict)
- if rc == 0:
- return 0
-
- rc = self.__topic_match(payload,
- topic,
- self.__gateway_subdev_event_topic_list,
- self.__on_gateway_subdev_event_cb_dict)
- if rc == 0:
- return 0
-
- return 1
-
- def __handle_gateway(self, message):
- self.__explorer_log.debug("gateway payload:%s" % message)
- ptype = message["type"]
- payload = message["payload"]
- devices = payload["devices"]
- result = devices[0]["result"]
- product_id = devices[0]["product_id"]
- device_name = devices[0]["device_name"]
- client_id = product_id + "/" + device_name
-
- self.__gateway_raply = True
- if ptype == "online":
- self.__gateway_session_online_reply[client_id] = result
- elif ptype == "offline":
- self.__gateway_session_offline_reply[client_id] = result
- elif ptype == "bind":
- self.__gateway_session_bind_reply[client_id] = result
- elif ptype == "unbind":
- self.__gateway_session_unbind_reply[client_id] = result
- pass
-
- def __handle_reply(self, method, payload):
- self.__explorer_log.debug("reply payload:%s" % payload)
-
- clientToken = payload["clientToken"]
- replyAck = payload["code"]
- if method == "get_status_reply":
- if replyAck == 0:
- topic_pub = self.__topic_info.template_property_topic_pub
- self.__topic_info.control_clientToken = clientToken
-
- # IOT_Template_ClearControl
- message = {
- "method": "clear_control",
- "clientToken": clientToken
- }
- rc, mid = self.publish(topic_pub, message, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, topic_pub))
- else:
- self.__replyAck = replyAck
- self.__explorer_log.debug("replyAck:%d" % replyAck)
+ self.__logger.error("no callback for topic %s" % topic)
- else:
- self.__replyAck = replyAck
+ def __handle_template(self, topic, qos, payload, userdate):
+ pos = topic.rfind("/")
+ device_name = topic[pos + 1:len(topic)]
+
+ topic_split = topic[0:pos]
+ pos = topic_split.rfind("/")
+ product_id = topic_split[pos + 1:len(topic_split)]
+
+ client = product_id + device_name
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return None
+
+ template = self.__template_map[client]
+ template.handle_template(topic, qos, payload, userdate)
+
+ # """ 回调用户处理 """
+ # if (topic in self.__user_callback.keys()
+ # and self.__user_callback[topic] is not None):
+ # self.__user_callback[topic](topic, qos, payload, self.__userdata)
+ # else:
+ # self.__logger.error("no callback for topic %s" % topic)
pass
- def __handle_control(self, payload):
- clientToken = payload["clientToken"]
- params = payload["params"]
- self.__topic_info.control_clientToken = clientToken
- # 调用用户回调,回调中应调用template_control_reply()
- self.__on_template_prop_changed(params, self.__user_data)
+ def setReconnectInterval(self, max_sec, min_sec):
+ """Set mqtt reconnect interval
- def __handle_action(self, payload):
+ Set mqtt reconnect interval
+ Args:
+ max_sec: reconnect max time
+ min_sec: reconnect min time
+ Returns:
+ success: default
+ fail: default
"""
- clientToken = payload["clientToken"]
- actionId = payload["actionId"]
- timestamp = payload["timestamp"]
- params = payload["params"]
- """
-
- # 调用用户回调,回调中应调用IOT_ACTION_REPLY()
- # self.__on_template_action(clientToken, actionId, timestamp, params, self.__user_data)
- self.__on_template_action(payload, self.__user_data)
- pass
+ self.__hub.setReconnectInterval(max_sec, min_sec)
- def __handle_ota(self, payload):
- ptype = payload["type"]
- if ptype == "report_version_rsp":
- self.__user_on_ota_report(payload, self.__user_data)
- elif ptype == "update_firmware":
- self.__ota_info_get(payload)
+ def setMessageTimout(self, timeout):
+ """Set message overtime time
- def __rrpc_get_process_id(self, topic):
- pos = topic.rfind("/")
- if pos > 0:
- self.__process_id = topic[pos + 1:len(topic)]
- return 0
- else:
- self.__explorer_log.error("cannot found process id from topic:%s" % topic)
- return -1
+ Set message overtime time
+ Args:
+ timeout: mqtt keepalive value
+ Returns:
+ success: default
+ fail: default
+ """
+ self.__hub.setMessageTimout(timeout)
- def __handle_rrpc(self, topic, payload):
- rc = self.__rrpc_get_process_id(topic)
- if rc < 0:
- raise QcloudHub.StateError("cannot found process id")
+ def setKeepaliveInterval(self, interval):
+ """Set mqtt keepalive interval
- # 调用用户注册的回调
- if self.__user_on_rrpc_message is not None:
- self.__user_on_rrpc_message(payload, self.__user_data)
+ Set mqtt keepalive interval
+ Args:
+ interval: mqtt keepalive interval
+ Returns:
+ success: default
+ fail: default
+ """
+ self.__hub.setKeepaliveInterval(interval)
def subscribe(self, topic, qos):
- self.__explorer_log.debug("sub topic:%s,qos:%d" % (topic, qos))
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
- if isinstance(topic, tuple):
- topic, qos = topic
- if isinstance(topic, str):
- if qos < 0:
- raise ValueError('Invalid QoS level.')
- if topic is None or len(topic) == 0:
- raise ValueError('Invalid topic.')
- pass
- self.__user_topics_request_lock.acquire()
- rc, mid = self.__mqtt_client.subscribe(topic, qos)
- if rc == mqtt.MQTT_ERR_SUCCESS:
- self.__explorer_log.debug("subscribe success topic:%s" % topic)
- self.__user_topics_subscribe_request[mid] = [(topic, qos)]
- self.__user_topics_request_lock.release()
- if rc == mqtt.MQTT_ERR_SUCCESS:
- return 0, mid
- if rc == mqtt.MQTT_ERR_NO_CONN:
- return 2, None
- else:
- self.__explorer_log.debug("subscribe error topic:%s" % topic)
- return -1, None
- # topic format [(topic1, qos),(topic2,qos)]
- if isinstance(topic, list):
- self.__user_topics_request_lock.acquire()
- sub_res, mid = self.__mqtt_client.subscribe(topic)
- if sub_res == mqtt.MQTT_ERR_SUCCESS:
- self.__user_topics_subscribe_request[mid] = [topic]
- self.__user_topics_request_lock.release()
- return 0, mid
- else:
- self.__user_topics_request_lock.release()
- return 1, mid
- pass
+ """Subscribe topic
+
+ Subscribe topic
+ Args:
+ topic: topic
+ qos: mqtt qos
+ Returns:
+ success: zero and subscribe mid
+ fail: negative number and subscribe mid
+ """
+ return self.__hub.subscribe(topic, qos)
def unsubscribe(self, topic):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
- unsubscribe_topics = []
- if topic is None or topic == "":
- raise ValueError('Invalid topic.')
- if isinstance(topic, str):
- # topic判断
- unsubscribe_topics.append(topic)
- with self.__user_topics_unsubscribe_request_lock:
- if len(unsubscribe_topics) == 0:
- return 2, None
- rc, mid = self.__mqtt_client.unsubscribe(unsubscribe_topics)
- if rc == mqtt.MQTT_ERR_SUCCESS:
- self.__user_topics_unsubscribe_request[mid] = unsubscribe_topics
- return 0, mid
- else:
- return 1, None
- pass
+ """Unsubscribe topic
+
+ Unsubscribe topic what is subscribed
+ Args:
+ topic: topic
+ Returns:
+ success: zero and unsubscribe mid
+ fail: negative number and unsubscribe mid
+ """
+ return self.__hub.unsubscribe(topic)
def publish(self, topic, payload, qos):
- self.__explorer_log.debug("pub topic:%s,payload:%s,qos:%d" % (topic, payload, qos))
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
- if topic is None or len(topic) == 0:
- raise ValueError('Invalid topic.')
- if qos < 0:
- raise ValueError('Invalid QoS level.')
- rc, mid = self.__mqtt_client.publish(topic, json.dumps(payload), qos)
- if rc == mqtt.MQTT_ERR_SUCCESS:
- self.__explorer_log.debug("publish success")
- return 0, mid
- else:
- self.__explorer_log.debug("publish failed")
- return 1, None
- pass
+ """Publish message
+
+ Publish message
+ Args:
+ topic: topic
+ payload: publish message
+ qos: mqtt qos
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ return self.__hub.publish(topic, payload, qos)
def isMqttConnected(self):
- if self.__explorer_state is QcloudHub.HubState.CONNECTED:
- return True
- else:
- return False
-
- def getConnectStatus(self):
- return self.__explorer_state
-
- def mqttInit(self, mqtt_domain, useWebsocket=False):
- self.__explorer_log.debug("mqttInit")
- timestamp = str(int(round(time.time() * 1000)))
-
- auth_mode = self.__device_file.auth_mode
- device_name = self.__device_file.device_name
- product_id = self.__device_file.product_id
- device_secret = self.__device_file.device_secret
- if auth_mode == "CERT":
- self.__key_mode = False
-
- self.__useWebsocket = useWebsocket
-
- self.__topic_info = QcloudHub.Topic(product_id, device_name)
-
- self.__psk = base64.b64decode(device_secret.encode("utf-8"))
- self.__psk_id = product_id + device_name
- sha1_key = self.__psk
-
- client_id = product_id + device_name
- conn_id = ''.join(random.sample(string.ascii_letters + string.digits, 5))
- username = client_id + ";21010406;" + conn_id + ";" + timestamp
- sign = hmac.new(sha1_key, username.encode("utf-8"), hashlib.sha1).hexdigest()
- password = "%s;hmacsha1" % (sign)
-
- if mqtt_domain is None or mqtt_domain == "":
- self.__host = product_id + ".iotcloud.tencentdevices.com"
- else:
- self.__host = product_id + mqtt_domain
- pass
-
- # c-sdk中sub_handles的设置待完成
-
- # AUTH_MODE_CERT 待添加
+ """Is mqtt connected
- # construct mqtt client
- mqtt_protocol_version = mqtt.MQTTv311
- if self.__mqtt_protocol == "MQTTv311":
- mqtt_protocol_version = mqtt.MQTTv311
- elif self.__mqtt_protocol == "MQTTv31":
- mqtt_protocol_version = mqtt.MQTTv31
-
- if self.__useWebsocket:
- if self.__tls:
- self.__host = "wss:" + product_id + ".ap-guangzhou.iothub.tencentdevices.com"
- else:
- self.__host = "ws:" + product_id + ".ap-guangzhou.iothub.tencentdevices.com"
- pass
-
- self.__mqtt_client = mqtt.Client(client_id=client_id,
- clean_session=self.__mqtt_clean_session,
- protocol=mqtt_protocol_version,
- transport="websockets")
- else:
- self.__mqtt_client = mqtt.Client(client_id=client_id,
- clean_session=self.__mqtt_clean_session,
- protocol=mqtt_protocol_version)
+ Is mqtt connected
+ Args: None
+ Returns:
+ success: True/False
+ """
+ return self.__hub.isMqttConnected()
- self.__explorer_log.debug("current_host: %s" % self.__host)
- if self.__explorer_log.is_enabled():
- self.__mqtt_client.enable_logger(self.__PahoLog)
+ def getConnectState(self):
+ """Get connect state
- # set username,password for connect()
- self.__mqtt_client.username_pw_set(username, password)
+ Get device current connect state
+ Args: None
+ Returns:
+ success: connect state
+ """
+ return self.__hub.getConnectState()
- # mqtt callback set
- self.__set_mqtt_callback()
+ def getNtpAccurateTime(self):
+ """Get NTP time
- self.__mqtt_client.reconnect_delay_set(self.__mqtt_auto_reconnect_min_sec, self.__mqtt_auto_reconnect_max_sec)
- self.__mqtt_client.max_queued_messages_set(self.__mqtt_max_queued_message)
- self.__mqtt_client.max_inflight_messages_set(self.__mqtt_max_inflight_message)
+ Get NTP time
+ Args: None
+ Returns:
+ success: thread start result
+ fail: thread start result
+ """
+ return self.__hub.getNtpAccurateTime()
# start thread to connect and loop
def connect(self):
- if self.__explorer_state not in (QcloudHub.HubState.INITIALIZED,
- QcloudHub.HubState.DISCONNECTED):
- raise QcloudHub.StateError("current state is not in INITIALIZED or DISCONNECTED")
- self.__connect_async_req = True
- with self.__worker_loop_exit_req_lock:
- self.__worker_loop_exit_req = False
- return self.__loop_thread.start(self.__loop_forever)
-
- def __ssl_init(self, key_mode):
- # 密钥认证
- if key_mode is True:
- context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cadata=self.__iot_ca_crt)
- self.__mqtt_client.tls_set_context(context)
- else:
- ca = self.__device_file.ca_file
- cert = self.__device_file.cert_file
- key = self.__device_file.private_key_file
- self.__mqtt_client.tls_set(ca_certs=ca, certfile=cert, keyfile=key,
- cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_SSLv23)
- pass
-
- def disconnect(self):
- self.__explorer_log.debug("disconnect")
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
- self.__explorer_state = QcloudHub.HubState.DISCONNECTING
- if self.__connect_async_req:
- with self.__worker_loop_exit_req_lock:
- self.__worker_loop_exit_req = True
-
- self.__mqtt_client.disconnect()
- self.__loop_thread.stop()
-
- def __on_connect(self, client, user_data, session_flag, rc):
- # self.__explorer_log.info("__on_connect:rc:%d" % (rc))
- if rc == 0:
- self.__reset_reconnect_wait()
- self.__explorer_state = QcloudHub.HubState.CONNECTED
-
- self.__user_thread.post_message(self.__user_cmd_on_connect, (session_flag, rc))
-
- sys_topic_sub = self.__topic_info.sys_topic_sub
- sys_topic_pub = self.__topic_info.sys_topic_pub
- qos = 0
- sub_res, mid = self.subscribe(sys_topic_sub, qos)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res == 0:
- payload = {
- "type": "get",
- "resource": [
- "time"
- ],
- }
- self.publish(sys_topic_pub, payload, qos)
- else:
- self.__explorer_log.error("topic_subscribe error:rc:%d" % (sub_res))
- pass
-
- def __on_disconnect(self, client, user_data, rc):
- self.__explorer_log.info("__on_disconnect,rc:%d" % (rc))
-
- if self.__explorer_state == QcloudHub.HubState.DISCONNECTING:
- self.__explorer_state = QcloudHub.HubState.DISCONNECTED
- elif self.__explorer_state == QcloudHub.HubState.DESTRUCTING:
- self.__explorer_state = QcloudHub.HubState.DESTRUCTED
- elif self.__explorer_state == QcloudHub.HubState.CONNECTED:
- self.__explorer_state = QcloudHub.HubState.DISCONNECTED
- else:
- self.__explorer_log.error("state error:%r" % self.__explorer_state)
- return
-
- self.__user_topics_subscribe_request.clear()
- self.__user_topics_unsubscribe_request.clear()
- self.__template_prop_report_reply_mid.clear()
- self.__user_topics.clear()
- self.__gateway_session_online_reply.clear()
- self.__gateway_session_offline_reply.clear()
- self.__gateway_session_bind_reply.clear()
- self.__gateway_session_unbind_reply.clear()
- self.__on_gateway_subdev_prop_cb_dict.clear()
- self.__on_gateway_subdev_action_cb_dict.clear()
- self.__on_gateway_subdev_event_cb_dict.clear()
-
- # self.__user_thread.post_message(self.__user_cmd_on_disconnect, (client, user_data, rc))
- self.__user_thread.post_message(self.__user_cmd_on_disconnect, (rc))
- if self.__explorer_state == QcloudHub.HubState.DESTRUCTED:
- self.__user_thread.stop()
- pass
-
- def __on_message(self, client, user_data, message):
- self.__user_thread.post_message(self.__user_cmd_on_message, (message))
- pass
-
- def __on_publish(self, client, user_data, mid):
- # self.__user_thread.post_message(self.__user_cmd_on_publish, (client, user_data, mid))
- self.__user_thread.post_message(self.__user_cmd_on_publish, (mid))
- pass
-
- def __on_subscribe(self, client, user_data, mid, granted_qos):
- # self.__explorer_log.info("__on_subscribe:user_data:%s,mid:%d,qos:%s" % (user_data, mid, granted_qos))
- qos = granted_qos[0]
- # __ota_subscribe_res可以用于所有订阅,mid必不相同
- self.__ota_subscribe_res[mid] = qos
- self.__user_thread.post_message(self.__user_cmd_on_subscribe, (qos, mid))
-
- pass
-
- def __on_unsubscribe(self, client, user_data, mid):
- self.__user_thread.post_message(self.__user_cmd_on_unsubscribe, (mid))
-
- pass
-
- # user callback
- def __user_thread_on_connect_callback(self, value):
- # client, user_data, session_flag, rc = value
- session_flag, rc = value
- if self.__user_on_connect is not None:
- try:
- self.__user_on_connect(session_flag['session present'], rc, self.__user_data)
- except Exception as e:
- self.__explorer_log.error("on_connect process raise exception:%r" % e)
- pass
-
- def __user_thread_on_disconnect_callback(self, value):
- self.__user_on_disconnect(value, self.__user_data)
- pass
-
- def __user_thread_on_publish_callback(self, value):
- self.__user_on_publish(value, self.__user_data)
- pass
-
- def __user_thread_on_subscribe_callback(self, value):
- qos, mid = value
- self.__user_on_subscribe(qos, mid, self.__user_data)
- pass
-
- def __user_thread_on_unsubscribe_callback(self, value):
- self.__user_on_unsubscribe(value, self.__user_data)
- pass
-
- #云端下发指令
- def __user_thread_on_message_callback(self, value):
- # client, user_data, message = value
- message = value
- topic = message.topic
- qos = message.qos
- mid = message.mid
- payload = json.loads(message.payload.decode('utf-8'))
-
- # self.__explorer_log.info("__user_thread_on_message_callback,topic:%s,payload:%s,mid:%d" % (topic, payload, mid))
-
- if topic == self.__topic_info.template_property_topic_sub:
- method = payload["method"]
- if method == "control":
- self.__handle_control(payload)
- else:
- self.__handle_reply(method, payload)
-
- elif topic == self.__topic_info.template_event_topic_sub:
- try:
- self.__on_template_event_post(payload, self.__user_data)
- except Exception as e:
- self.__explorer_log.error("on_template_event_post raise exception:%s" % e)
- pass
-
- elif topic == self.__topic_info.template_action_topic_sub:
- method = payload["method"]
-
- if method != "action":
- self.__explorer_log.error("method error:%s" % method)
- else:
- self.__handle_action(payload)
- pass
-
- elif topic == self.__topic_info.template_service_topic_sub:
- self.__explorer_log.info("--------Reserved: template service topic")
-
- try:
- self.__on_subscribe_service_post(payload, self.__user_data)
- except Exception as e:
- self.__explorer_log.error("__on_subscribe_service_post raise exception:%s" % e)
- pass
-
- elif topic == self.__topic_info.template_raw_topic_sub:
- self.__explorer_log.info("Reserved: template raw topic")
-
- elif topic in self.__user_topics and self.__user_on_message is not None:
- try:
- self.__user_on_message(topic, payload, qos, self.__user_data)
- except Exception as e:
- self.__explorer_log.error("user_on_message process raise exception:%s" % e)
- pass
- elif topic == self.__topic_info.template_topic_sub:
- self.__user_on_message(topic, payload, qos, self.__user_data)
- elif topic == self.__topic_info.sys_topic_sub:
- self.__user_on_message(topic, payload, qos, self.__user_data)
- elif topic == self.__topic_info.gateway_topic_sub:
- self.__handle_gateway(payload)
- elif topic == self.__topic_info.ota_update_topic_sub:
- self.__handle_ota(payload)
- elif self.__topic_info.rrpc_topic_sub_prefix in topic:
- self.__handle_rrpc(topic, payload)
- elif self.__topic_info.shadow_topic_sub in topic:
- self.__user_on_message(topic, payload, qos, self.__user_data)
- elif self.__topic_info.broadcast_topic_sub in topic:
- self.__user_on_message(topic, payload, qos, self.__user_data)
- else:
- rc = self.__handle_nonStandard_topic(topic, payload)
- if rc != 0:
- self.__explorer_log.error("unknow topic:%s" % topic)
- pass
-
- def __reconnect_wait(self):
- if self.__mqtt_auto_reconnect_sec == 0:
- self.__mqtt_auto_reconnect_sec = self.__mqtt_auto_reconnect_min_sec
- else:
- self.__mqtt_auto_reconnect_sec = min(self.__mqtt_auto_reconnect_sec * 2, self.__mqtt_auto_reconnect_max_sec)
- self.__mqtt_auto_reconnect_sec += random.randint(1, self.__mqtt_auto_reconnect_sec)
- time.sleep(self.__mqtt_auto_reconnect_sec)
- pass
+ """Connect
- def __reset_reconnect_wait(self):
- self.__mqtt_auto_reconnect_sec = 0
+ Device connect
+ Args: None
+ Returns:
+ success: thread start result
+ fail: thread start result
+ """
+ return self.__hub.connect()
- def __loop_forever(self):
- self.__explorer_log.info("__loop_forever")
- self.__explorer_state = QcloudHub.HubState.CONNECTING
+ def disconnect(self):
+ """Disconnect
- mqtt_port = self.__mqtt_tls_port
- if self.__tls:
- try:
- mqtt_port = self.__mqtt_tls_port
+ Device disconnect
+ Args: None
+ Returns:
+ success: default
+ fail: default
+ """
+ self.__hub.disconnect()
+
+ def registerMqttCallback(self, on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe):
+ """Register user mqtt callback
+
+ Register user mqtt callback for mqtt
+ Args:
+ on_connect: mqtt connect callback
+ on_disconnect: mqtt disconnect callback
+ on_message: mqtt message callback
+ on_publish: mqtt publish callback
+ on_subscribe: mqtt subscribe callback
+ on_unsubscribe: mqtt unsubscribe callback
+ Returns:
+ success: default
+ fail: default
+ """
+ self.__hub.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+
+ def registerUserCallback(self, topic, callback):
+ """Register user callback
+
+ Register user callback for a topic
+ Args:
+ topic: topic
+ callback: user callback
+ Returns:
+ success: default
+ fail: default
+ """
+ self.__user_callback[topic] = callback
+
+ def getEventsList(self, productId, deviceName):
+ """Get template event list
+
+ Get template event list from configuration file
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: template event list
+ fail: empty list
+ """
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return None
+
+ template = self.__template_map[client]
+ return template.get_events_list()
+
+ def getActionList(self, productId, deviceName):
+ """Get template action list
+
+ Get template action list from configuration file
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: template action list
+ fail: empty list
+ """
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return None
+
+ template = self.__template_map[client]
+ return template.get_action_list()
+
+ def getPropertyList(self, productId, deviceName):
+ """Get template property list
+
+ Get template property list from configuration file
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: template property list
+ fail: empty list
+ """
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return None
+
+ template = self.__template_map[client]
+ return template.get_property_list()
+
+ def getProductID(self):
+ """Get product id
+
+ Get product id
+ Args: None
+ Returns:
+ success: product id
+ fail: None
+ """
+ return self.__hub.getProductID()
- if self.__useWebsocket:
- mqtt_port = self.__mqtt_socket_tls_port
- pass
+ def getDeviceName(self):
+ """Get device name
- self.__ssl_init(self.__key_mode)
- except ssl.SSLError as e:
- self.__explorer_log.error("ssl init error:" + str(e))
- self.__explorer_state = QcloudHub.HubState.INITIALIZED
- # connect again 待添加
+ Get device name
+ Args: None
+ Returns:
+ success: device name
+ fail: None
+ """
+ return self.__hub.getDeviceName()
+
+ def templateSetup(self, productId, deviceName, config_file=None):
+ """Parse json configuration file
+
+ Parse json configuration file
+ Args:
+ productId: product id
+ deviceName: device name
+ config_file: configuration file path
+ Returns:
+ success: zero
+ fail: negative number
+ """
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return -1, -1
- return
- else:
- mqtt_port = self.__mqtt_tcp_port
-
- if self.__useWebsocket:
- mqtt_port = self.__mqtt_socket_tcp_port
- pass
-
- try:
- self.__explorer_log.debug("connect_async...%s", mqtt_port)
- self.__mqtt_client.connect_async(host=self.__host, port=mqtt_port, keepalive=self.__mqtt_keep_alive)
- except Exception as e:
- self.__explorer_log.error("mqtt connect with async error:" + str(e))
- self.__explorer_state = QcloudHub.HubState.INITIALIZED
- # connect again 待添加
-
- return
- while True:
- if self.__worker_loop_exit_req:
- if self.__explorer_state == QcloudHub.HubState.DESTRUCTING:
- # self.__handler_task.stop()
- self.__explorer_state = QcloudHub.HubState.DESTRUCTED
- break
- try:
- self.__explorer_state = QcloudHub.HubState.CONNECTING
- self.__mqtt_client.reconnect()
- except (socket.error, OSError) as e:
- self.__explorer_log.error("mqtt reconnect error:" + str(e))
- # 失败处理 待添加
- if self.__explorer_state == QcloudHub.HubState.CONNECTING:
- self.__explorer_state = QcloudHub.HubState.DISCONNECTED
- # self.__on__connect_safe(None, None, 0, 9)
- if self.__explorer_state == QcloudHub.HubState.DESTRUCTING:
- # self.__handler_task.stop()
- self.__explorer_state = QcloudHub.HubState.DESTRUCTED
- break
- self.__reconnect_wait()
-
- continue
-
- rc = mqtt.MQTT_ERR_SUCCESS
- while rc == mqtt.MQTT_ERR_SUCCESS:
- rc = self.__mqtt_client.loop(self.__mqtt_command_timeout, 1)
-
- # data template
- def __build_empty_json(self, info_in, method_in):
- if info_in is None or len(info_in) == 0:
- raise ValueError('Invalid topic.')
- client_token = info_in + "-" + str(self.template_token_num)
- self.template_token_num += 1
- if method_in is None or len(method_in) == 0:
- json_out = {
- "clientToken": client_token
- }
- else:
- json_out = {
- "method": method_in,
- "clientToken": client_token
- }
- return json_out
-
- def __build_control_reply(self, replyPara):
- token = self.__topic_info.control_clientToken
-
- json_out = None
- if len(replyPara.status_msg) > 0:
- json_out = {
- "code": replyPara.code,
- "clientToken": token,
- "status": replyPara.status_msg
- }
- else:
- json_out = {
- "code": replyPara.code,
- "clientToken": token
- }
- return json_out
-
- def __build_action_reply(self, clientToken, response, replyPara):
- json_out = None
- json_out = {
- "method": "action_reply",
- "code": replyPara.code,
- "clientToken": clientToken,
- "status": replyPara.status_msg,
- "response": response
- }
-
- return json_out
-
- # 构建系统信息上报的json消息
- def __json_construct_sysinfo(self, info_in):
- if info_in is None or len(info_in) == 0:
- raise ValueError('Invalid info.')
-
- json_token = self.__build_empty_json(self.__device_file.product_id, None)
- client_token = json_token["clientToken"]
- info_out = {
- "method": "report_info",
- "clientToken": client_token,
- "params": info_in
- }
-
- return 0, info_out
-
- def templateSetup(self, config_file=None):
- """
- if self.__explorer_state is not QcloudExplorer.HubState.INITIALIZED:
- raise QcloudExplorer.StateError("current state is not INITIALIZED")
- if self.__template_setup_state:
- return 1
- """
- try:
- with open(config_file, encoding='utf-8') as f:
- cfg = json.load(f)
- index = 0
- while index < len(cfg["events"]):
- # 解析events json
- params = cfg["events"][index]["params"]
-
- p_event = QcloudHub.template_event()
-
- p_event.event_name = cfg["events"][index]["id"]
- p_event.type = cfg["events"][index]["type"]
- p_event.timestamp = 0
- p_event.eventDataNum = len(params)
-
- i = 0
- while i < p_event.eventDataNum:
- event_prop = QcloudHub.template_property()
- event_prop.key = params[i]["id"]
- event_prop.type = params[i]["define"]["type"]
-
- if event_prop.type == "int" or event_prop.type == "bool":
- event_prop.data = 0
- elif event_prop.type == "float":
- event_prop.data = 0.0
- elif event_prop.type == "string":
- event_prop.data = ''
- else:
- self.__explorer_log.error("type not support")
- event_prop.data = None
-
- p_event.event_append(event_prop)
- i += 1
- pass
-
- self.template_events_list.append(p_event)
- index += 1
-
- '''
- for event in self.template_events_list:
- print("event_name:%s" % (event.event_name))
- for prop in event.events_prop:
- print("key:%s" % (prop.key))
- '''
-
- index = 0
- while index < len(cfg["actions"]):
- # 解析actions json
- inputs = cfg["actions"][index]["input"]
- outputs = cfg["actions"][index]["output"]
-
- p_action = QcloudHub.template_action()
- p_action.action_id = cfg["actions"][index]["id"]
- p_action.input_num = len(inputs)
- p_action.output_num = len(outputs)
- p_action.timestamp = 0
-
- i = 0
- while i < p_action.input_num:
- action_prop = QcloudHub.template_property()
- action_prop.key = inputs[i]["id"]
- action_prop.type = inputs[i]["define"]["type"]
-
- if action_prop.type == "int" or action_prop.type == "bool":
- action_prop.data = 0
- elif action_prop.type == "float":
- action_prop.data = 0.0
- elif action_prop.type == "string":
- action_prop.data = ''
- else:
- self.__explorer_log.error("type not support")
- action_prop.data = None
- p_action.action_input_append(action_prop)
- i += 1
- pass
-
- i = 0
- while i < p_action.output_num:
- action_prop = QcloudHub.template_property()
- action_prop.key = outputs[i]["id"]
- action_prop.type = outputs[i]["define"]["type"]
-
- if action_prop.type == "int" or action_prop.type == "bool":
- action_prop.data = 0
- elif action_prop.type == "float":
- action_prop.data = 0.0
- elif action_prop.type == "string":
- action_prop.data = ''
- else:
- self.__explorer_log.error("type not support")
- action_prop.data = None
- p_action.action_output_append(action_prop)
- i += 1
- pass
-
- self.template_action_list.append(p_action)
- index += 1
-
- pass
-
- '''
- for action in self.template_action_list:
- print("input_num:%s" % (action.input_num))
- for inp in action.actions_input_prop:
- print("key:%s" % (inp.key))
- print("output_num:%s" % (action.output_num))
- for out in action.actions_output_prop:
- print("key:%s" % (out.key))
- '''
-
- index = 0
- while index < len(cfg["properties"]):
- # 解析properties json
- p_prop = QcloudHub.template_property()
- p_prop.key = cfg["properties"][index]["id"]
- p_prop.type = cfg["properties"][index]["define"]["type"]
-
- if p_prop.type == "int" or p_prop.type == "bool" or p_prop.type == "enum":
- p_prop.data = 0
- elif p_prop.type == "float":
- p_prop.data = 0.0
- elif p_prop.type == "string":
- p_prop.data = ''
- else:
- self.__explorer_log.error("type not support")
- p_prop.data = None
-
- self.template_property_list.append(p_prop)
- index += 1
- pass
-
- '''
- for prop in self.template_property_list:
- print("key:%s" % (prop.key))
- '''
-
- except Exception as e:
- self.__explorer_log.error("config file open error:" + str(e))
- return 2
- self.__template_setup_state = True
- return 0
+ template = self.__template_map[client]
+ return template.template_setup(config_file)
# 暂定传入json格式
- def templateEventPost(self, message):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
- if message is None or len(message) == 0:
- raise ValueError('Invalid message.')
-
- json_token = self.__build_empty_json(self.__device_file.product_id, None)
- client_token = json_token["clientToken"]
- events = message["events"]
- json_out = {
- "method": "events_post",
- "clientToken": client_token,
- "events": events
- }
-
- template_topic_pub = self.__topic_info.template_event_topic_pub
- rc, mid = self.publish(template_topic_pub, json_out, 1)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if rc != 0:
- return 2
- else:
- return 0
- pass
-
- def __template_event_init(self):
- template_topic_sub = self.__topic_info.template_event_topic_sub
- sub_res, mid = self.subscribe(template_topic_sub, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, template_topic_sub))
- return 1
- else:
- return 0
- pass
-
- def __template_action_init(self):
- template_topic_sub = self.__topic_info.template_action_topic_sub
- sub_res, mid = self.subscribe(template_topic_sub, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, template_topic_sub))
- return 1
- else:
- return 0
- pass
-
-
- # 暂定传入的message为json格式(json/属性列表?)
- # 传入json格式时该函数应改为内部函数,由template_report()调用
- def templateJsonConstructReportArray(self, payload):
- if payload is None or len(payload) == 0:
- raise ValueError('Invalid payload.')
-
- json_token = self.__build_empty_json(self.__device_file.product_id, None)
- client_token = json_token["clientToken"]
- json_out = {
- "method": "report",
- "clientToken": client_token,
- "params": payload
- }
-
- return json_out
-
- def templateReportSysInfo(self, sysinfo):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
- if sysinfo is None or len(sysinfo) == 0:
- raise ValueError('Invalid sysinfo.')
-
- template_topic_pub = self.__topic_info.template_property_topic_pub
- rc, json_out = self.__json_construct_sysinfo(sysinfo)
- if rc != 0:
- self.__explorer_log.error("__json_construct_sysinfo error:rc:%d,topic:%s" % (rc, template_topic_pub))
- return 1
- rc, mid = self.publish(template_topic_pub, json_out, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, template_topic_pub))
- return 2
- return 0
-
- def templateControlReply(self, replyPara):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
-
- template_topic_pub = self.__topic_info.template_property_topic_pub
- json_out = self.__build_control_reply(replyPara)
- rc, mid = self.publish(template_topic_pub, json_out, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, template_topic_pub))
- return 2
- else:
- return 0
- pass
+ def templateEventPost(self, productId, deviceName, message):
+ """Report event/events infomation
+
+ Report device event/events infomation
+ Args:
+ productId: product id
+ deviceName: device name
+ message: device event/events infomation
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ if self.__hub.getConnectState() is not self.__hub.HubState.CONNECTED:
+ raise self.__hub.StateError("current state is not CONNECTED")
- def templateActionReply(self, clientToken, response, replyPara):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return -1, -1
- template_topic_pub = self.__topic_info.template_action_topic_pub
- json_out = self.__build_action_reply(clientToken, response, replyPara)
- rc, mid = self.publish(template_topic_pub, json_out, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
+ template = self.__template_map[client]
+ rc, mid = template.template_event_post(productId, message)
if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, template_topic_pub))
- return 2
- else:
- return 0
- pass
-
- # 回调中处理IOT_Template_ClearControl
- def templateGetStatus(self):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
+ self.__logger.error("[template] publish error:rc:%d" % (rc))
+ return rc, mid
- template_topic_pub = self.__topic_info.template_property_topic_pub
+ def templateJsonConstructReportArray(self, productId, deviceName, payload):
+ """Construct json array
+
+ Construct json array
+ Args:
+ productId: product id
+ deviceName: device name
+ payload: report message, json type
+ Returns:
+ success: json message
+ fail: None
+ """
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return -1, -1
+
+ template = self.__template_map[client]
+ return template.template_json_construct_report_array(productId, payload)
+
+ def templateReportSysInfo(self, productId, deviceName, sysInfo):
+ """Report system infomation
+
+ Report device system infomation
+ Args:
+ productId: product id
+ deviceName: device name
+ sysInfo: device system infomation
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ if self.__hub.getConnectState() is not self.__hub.HubState.CONNECTED:
+ raise self.__hub.StateError("current state is not CONNECTED")
- token = self.__build_empty_json(self.__device_file.product_id, "get_status")
- rc, mid = self.publish(template_topic_pub, token, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, template_topic_pub))
- return 2
- else:
- return 0
- pass
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return -1, -1
- def templateReport(self, message):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
- if message is None or len(message) == 0:
- raise ValueError('Invalid message.')
- # 判断下行topic是否订阅
- if self.__is_subscribed_property_topic is False:
- template_topic_sub = self.__topic_info.template_property_topic_sub
- sub_res, mid = self.subscribe(template_topic_sub, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, template_topic_sub))
- return 1
- self.__is_subscribed_property_topic = True
- pass
-
- template_topic_pub = self.__topic_info.template_property_topic_pub
- rc, mid = self.publish(template_topic_pub, message, 0)
+ template = self.__template_map[client]
+ rc, mid = template.template_report_sys_info(productId, sysInfo)
if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, template_topic_pub))
- return 2
- else:
- return 0
- pass
+ self.__logger.error("[template] publish error:rc:%d" % (rc))
+ return rc, mid
- def templateInit(self):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
+ def templateControlReply(self, productId, deviceName, replyPara):
+ """Report control reply
+
+ Report control reply after recvive control message
+ Args:
+ productId: product id
+ deviceName: device name
+ replyPara: description infomation, type is class ReplyPara()
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ if self.__hub.getConnectState() is not self.__hub.HubState.CONNECTED:
+ raise self.__hub.StateError("current state is not CONNECTED")
- template_topic_sub = self.__topic_info.template_property_topic_sub
- sub_res, mid = self.subscribe(template_topic_sub, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, template_topic_sub))
- return 1
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return -1, -1
- self.__is_subscribed_property_topic = True
- rc = self.__template_event_init()
- if rc != 0:
- return 1
- rc = self.__template_action_init()
+ template = self.__template_map[client]
+ rc, mid = template.template_control_reply(replyPara)
if rc != 0:
- return 1
- return 0
-
- def clearControl(self):
- topic_pub = self.__topic_info.template_property_topic_pub
- clientToken = self.__topic_info.control_clientToken
-
- # IOT_Template_ClearControl
- message = {
- "method": "clear_control",
- "clientToken": clientToken
- }
- rc, mid = self.publish(topic_pub, message, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, topic_pub))
- pass
-
- def templateDeinit(self):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
-
-
- template_topic_sub = self.__topic_info.template_property_topic_sub
- sub_res, mid = self.unsubscribe(template_topic_sub)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, template_topic_sub))
- return 1
-
- self.__is_subscribed_property_topic = False
-
-
- template_topic_sub = self.__topic_info.template_event_topic_sub
- sub_res, mid = self.unsubscribe(template_topic_sub)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, template_topic_sub))
- return 1
-
- template_topic_sub = self.__topic_info.template_action_topic_sub
- sub_res, mid = self.unsubscribe(template_topic_sub)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, template_topic_sub))
- return 1
- else:
- return 0
- pass
-
- # gateway
- # 网关子设备数据模板回调注册
- def registerUserPropertyCallback(self, product_id, callback):
- with self.__register_property_cb_lock:
- self.__on_gateway_subdev_prop_cb_dict[product_id] = callback
-
- def registerUserActionCallback(self, product_id, callback):
- with self.__register_action_cb_lock:
- self.__on_gateway_subdev_action_cb_dict[product_id] = callback
-
- def registerUserEventCallback(self, product_id, callback):
- with self.__register_event_cb_lock:
- self.__on_gateway_subdev_event_cb_dict[product_id] = callback
-
- def gatewaySubdevSubscribe(self, product_id, topic_prop, topic_action, topic_event):
- # 网关子设备数据模板
- sub_res, mid = self.subscribe(topic_prop, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, topic_prop))
- return 1
- else:
- # 将product id和topic对加到列表保存
- with self.__gateway_subdev_append_lock:
- for topic, qos in topic_prop:
- tup = (product_id, topic)
- self.__gateway_subdev_property_topic_list.append(tup)
-
- sub_res, mid = self.subscribe(topic_action, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, topic_action))
- return 1
- else:
- # 将product id和topic对加到列表保存
- with self.__gateway_subdev_append_lock:
- for topic, qos in topic_action:
- tup = (product_id, topic)
- self.__gateway_subdev_action_topic_list.append(tup)
-
- sub_res, mid = self.subscribe(topic_event, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, topic_event))
- return 1
- else:
- # 将product id和topic对加到列表保存
- with self.__gateway_subdev_append_lock:
- for topic, qos in topic_event:
- tup = (product_id, topic)
- self.__gateway_subdev_event_topic_list.append(tup)
-
- return 0
-
- def __wait_for_session_reply(self, client_id, session):
- if client_id is None or len(client_id) == 0:
- raise ValueError('Invalid client_id.')
- if session is None or len(session) == 0:
- raise ValueError('Invalid session.')
-
- if session == "online":
- cnt = 0
- while cnt < 3:
- with self.__gateway_session_online_lock:
- if client_id in self.__gateway_session_online_reply:
- if self.__gateway_session_online_reply[client_id] == 0:
- self.__gateway_session_online_reply.pop(client_id)
- return 0
- else:
- break
- pass
- time.sleep(0.2)
- cnt += 1
- return 1
- elif session == "offline":
- cnt = 0
- while cnt < 3:
- with self.__gateway_session_offline_lock:
- if client_id in self.__gateway_session_offline_reply:
- if self.__gateway_session_offline_reply[client_id] == 0:
- self.__gateway_session_offline_reply.pop(client_id)
- return 0
- else:
- break
- pass
- time.sleep(0.2)
- cnt += 1
- return 1
- elif session == "bind":
- cnt = 0
- while cnt < 3:
- with self.__gateway_session_bind_lock:
- if client_id in self.__gateway_session_bind_reply:
- if self.__gateway_session_bind_reply[client_id] == 0:
- self.__gateway_session_bind_reply.pop(client_id)
- return 0
- else:
- break
- pass
- time.sleep(0.2)
- cnt += 1
- return 1
- elif session == "unbind":
- cnt = 0
- while cnt < 3:
- with self.__gateway_session_unbind_lock:
- if client_id in self.__gateway_session_unbind_reply:
- if self.__gateway_session_unbind_reply[client_id] == 0:
- self.__gateway_session_unbind_reply.pop(client_id)
- return 0
- else:
- break
- pass
- time.sleep(0.2)
- cnt += 1
- return 1
- pass
-
- def __build_session_payload(self, ptype, pid, name, bind_secret):
- if ptype == "online" or ptype == "offline" or ptype == "unbind":
- payload = {
- "type": ptype,
- "payload": {
- "devices": [
- {
- "product_id": pid,
- "device_name": name
- }
- ]
- }
- }
- elif ptype == "bind":
- nonce = random.randrange(2147483647)
- timestamp = int(time.time())
- sign_format = '%s%s;%d;%d'
- sign_content = sign_format % (pid, name, nonce, timestamp)
-
- # 计算二进制
- sign = hmac.new(bind_secret.encode("utf-8"), sign_content.encode("utf-8"), hashlib.sha1).digest()
- sign_base64 = base64.b64encode(sign).decode('utf-8')
-
- self.__explorer_log.debug('sign base64 {}'.format(sign_base64))
- payload = {
- "type": ptype,
- "payload": {
- "devices": [{
- "product_id": pid,
- "device_name": name,
- "signature": sign_base64,
- "random": nonce,
- "timestamp": timestamp,
- "signmethod": "hmacsha1",
- "authtype": "psk"
- }]
- }
- }
- pass
-
- return payload
-
- def gatewayInit(self):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
-
- # 解析网关子设备信息,并添加到list
- json_data = self.__device_file.json_data
- subdev_num = json_data['subDev']['subdev_num']
- subdev_list = json_data['subDev']['subdev_list']
-
- index = 0
- while index < subdev_num:
- p_subdev = QcloudHub.gateway_subdev()
- p_subdev.sub_productId = subdev_list[index]['sub_productId']
- p_subdev.sub_devName = subdev_list[index]['sub_devName']
- p_subdev.session_status = QcloudHub.SessionState.SUBDEV_SEESION_STATUS_INIT
-
- self.gateway_subdev_list.append(p_subdev)
- index += 1
-
- gateway_topic_sub = self.__topic_info.gateway_topic_sub
- sub_res, mid = self.subscribe(gateway_topic_sub, 0)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, gateway_topic_sub))
- return 1
- return 0
-
- def gatewaySubdevOnline(self, sub_productId, sub_devName):
- if sub_productId is None or len(sub_productId) == 0:
- raise ValueError('Invalid sub_productId.')
- if sub_devName is None or len(sub_devName) == 0:
- raise ValueError('Invalid sub_devName.')
+ self.__logger.error("[template] publish error:rc:%d" % (rc))
+ return rc, mid
- # 保存当前会话的设备client_id
- # self.__gateway_session_client_id = sub_productId + "/" + sub_devName
- client_id = sub_productId + "/" + sub_devName
+ def templateActionReply(self, productId, deviceName, clientToken, response, replyPara):
+ """Report action reply
+
+ Report action reply after recvive action message
+ Args:
+ productId: product id
+ deviceName: device name
+ clientToken: client token
+ response: report message
+ replyPara: other description infomation, type is class ReplyPara()
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ if self.__hub.getConnectState() is not self.__hub.HubState.CONNECTED:
+ raise self.__hub.StateError("current state is not CONNECTED")
- gateway_topic_pub = self.__topic_info.gateway_topic_pub
- payload = self.__build_session_payload("online", sub_productId, sub_devName, None)
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return -1, -1
- rc, mid = self.publish(gateway_topic_pub, payload, 0)
+ template = self.__template_map[client]
+ rc, mid = template.template_action_reply(clientToken, response, replyPara)
if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, gateway_topic_pub))
- return 2
-
- rc = self.__wait_for_session_reply(client_id, "online")
- if rc == 0:
- self.__explorer_log.debug("client:%s %s success" % (client_id, "online"))
- else:
- self.__explorer_log.debug("client:%s %s fail" % (client_id, "online"))
-
- return rc
+ self.__logger.error("[template] publish error:rc:%d" % (rc))
+ return rc, mid
- def gatewaySubdevOffline(self, sub_productId, sub_devName):
- if sub_productId is None or len(sub_productId) == 0:
- raise ValueError('Invalid sub_productId.')
- if sub_devName is None or len(sub_devName) == 0:
- raise ValueError('Invalid sub_devName.')
+ # 回调中处理IOT_Template_ClearControl
+ def templateGetStatus(self, productId, deviceName):
+ """Get status
+
+ Get device status
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ if self.__hub.getConnectState() is not self.__hub.HubState.CONNECTED:
+ raise self.__hub.StateError("current state is not CONNECTED")
+
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return -1, -1
+
+ template = self.__template_map[client]
+ return template.template_get_status(productId)
+
+ def templateReport(self, productId, deviceName, message):
+ """Template message report
+
+ Report message to cloud
+ Args:
+ productId: product id
+ deviceName: device name
+ message: report message
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ if self.__hub.getConnectState() is not self.__hub.HubState.CONNECTED:
+ raise self.__hub.StateError("current state is not CONNECTED")
- # 保存当前会话的设备client_id
- # self.__gateway_session_client_id = sub_productId + "/" + sub_devName
- client_id = sub_productId + "/" + sub_devName
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return -1, -1
- gateway_topic_pub = self.__topic_info.gateway_topic_pub
- payload = self.__build_session_payload("offline", sub_productId, sub_devName, None)
+ # 判断下行topic是否订阅
+ if self.__is_subscribed_property_topic is False:
+ self.__logger.error("Template is not initialization, please do it!")
+ return -1, -1
- rc, mid = self.publish(gateway_topic_pub, payload, 0)
+ template = self.__template_map[client]
+ rc, mid = template.template_report(message)
if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, gateway_topic_pub))
- return 2
-
- rc = self.__wait_for_session_reply(client_id, "offline")
- if rc == 0:
- self.__explorer_log.debug("client:%s %s success" % (client_id, "offline"))
- else:
- self.__explorer_log.debug("client:%s %s fail" % (client_id, "offline"))
-
- return rc
+ self.__logger.error("template publish error:rc:%d" % (rc))
- def gatewaySubdevBind(self, sub_productId, sub_devName, sub_secret):
- if sub_productId is None or len(sub_productId) == 0:
- raise ValueError('Invalid sub_productId.')
- if sub_devName is None or len(sub_devName) == 0:
- raise ValueError('Invalid sub_devName.')
-
- if sub_secret is None or len(sub_secret) == 0:
- raise ValueError('Invalid sub_secret.')
+ return rc, mid
- client_id = sub_productId + "/" + sub_devName
+ def templateInit(self, productId, deviceName,
+ propertyCb, actionCb, eventCb, serviceCb):
+ """Template initialization
+
+ Template initialization
+ Args:
+ productId: product id
+ deviceName: device name
+ propertyCb: user received property message callback
+ actionCb: user received action message callback
+ eventCb: user received event message callback
+ serviceCb: user received service message callback
+ Returns:
+ success: zero and subscribe mid
+ fail: negative number and subscribe mid
+ """
+ if self.__hub.getConnectState() is not self.__hub.HubState.CONNECTED:
+ raise self.__hub.StateError("current state is not CONNECTED")
- gateway_topic_pub = self.__topic_info.gateway_topic_pub
- payload = self.__build_session_payload("bind", sub_productId, sub_devName, sub_secret)
+ """
+ 构造对应client的template对象并加入字典
+ """
+ client = productId + deviceName
+ template = Template(self.__device_file, self.__tls, productId, deviceName,
+ self.__userdata, self.__domain, self.__useWebsocket, self.__logger)
- rc, mid = self.publish(gateway_topic_pub, payload, 0)
+ """
+ 注册用户数据模板topic(property/action/event)对应回调
+ 注册后用户不用再关注相关topic
+ """
+ rc, mid = template.template_init(self.__handle_template,
+ propertyCb, actionCb, eventCb, serviceCb)
if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, gateway_topic_pub))
- return 2
+ self.__logger.error("[template] subscribe error:rc:%d" % (rc))
+ return rc, mid
- rc = self.__wait_for_session_reply(client_id, "bind")
- if rc == 0:
- self.__explorer_log.debug("client:%s %s success" % (client_id, "bind"))
- else:
- self.__explorer_log.debug("client:%s %s fail" % (client_id, "bind"))
+ # save template client
+ self.__template_map[client] = template
+ self.__is_subscribed_property_topic = True
+ return rc, mid
- return rc
-
- def gatewaySubdevUnbind(self, sub_productId, sub_devName, sub_secret):
- if sub_productId is None or len(sub_productId) == 0:
- raise ValueError('Invalid sub_productId.')
- if sub_devName is None or len(sub_devName) == 0:
- raise ValueError('Invalid sub_devName.')
+ def clearControl(self, productId, deviceName):
+ """Clear control
- client_id = sub_productId + "/" + sub_devName
+ Clear control message
+ Args:
+ productId: device product_id
+ deviceName: device device_name
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return -1, -1
- gateway_topic_pub = self.__topic_info.gateway_topic_pub
- payload = self.__build_session_payload("unbind", sub_productId, sub_devName, None)
+ template = self.__template_map[client]
- rc, mid = self.publish(gateway_topic_pub, payload, 0)
+ # clientToken = self.__topic.control_clientToken
+ rc, mid = template.template_clear_control()
if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, gateway_topic_pub))
- return 2
-
- rc = self.__wait_for_session_reply(client_id, "unbind")
- if rc == 0:
- self.__explorer_log.debug("client:%s %s success" % (client_id, "unbind"))
- else:
- self.__explorer_log.debug("client:%s %s fail" % (client_id, "unbind"))
-
- return rc
-
- # ota
- def __ota_publish(self, message, qos):
- topic = self.__topic_info.ota_report_topic_pub
- rc, mid = self.publish(topic, message, qos)
+ self.__logger.error("[template] publish error:rc:%d" % (rc))
return rc, mid
- def __ota_info_get(self, payload):
- size = payload["file_size"]
- if size > 0:
- self.__ota_manager.file_size = size
- version = payload["version"]
- if version is not None and len(version) > 0:
- self.__ota_manager.version = version
- url = payload["url"]
- if url is not None and len(url) > 0:
- self.__ota_manager.purl = url
- pos = url.find("https://")
- last_pos = url.rfind("/")
- if pos >= 0:
- self.__ota_manager.is_https = True
- host = url[8:last_pos]
- else:
- host = url[7:last_pos]
- self.__ota_manager.host = host
-
- md5sum = payload["md5sum"]
- if md5sum is not None and len(md5sum) > 0:
- self.__ota_manager.md5sum = md5sum
-
- self.__ota_manager.state = QcloudHub.OtaState.IOT_OTAS_FETCHING
-
- def otaInit(self):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
-
- self.__ota_manager = QcloudHub.ota_manage()
- self.__ota_manager.state = QcloudHub.OtaState.IOT_OTAS_UNINITED
-
- ota_topic_sub = self.__topic_info.ota_update_topic_sub
- sub_res, mid = self.subscribe(ota_topic_sub, 1)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, ota_topic_sub))
- return 1
-
- cnt = 0
- while cnt < 10:
- if mid in self.__ota_subscribe_res:
- # 收到该mid回调,且其qos>=0说明订阅完成,qos=0需另做判断
- if self.__ota_subscribe_res[mid] >= 1:
- break
-
- time.sleep(0.2)
- cnt += 1
- pass
- if cnt >= 10:
- return 1
-
- self.__ota_manager.state = QcloudHub.OtaState.IOT_OTAS_INITED
- self.__ota_manager.md5 = hashlib.md5()
-
- return 0
-
- # 是否应将ota句柄传入(支持多个下载进程?)
- def otaIsFetching(self):
- return (self.__ota_manager.state == QcloudHub.OtaState.IOT_OTAS_FETCHING)
-
- def otaIsFetchFinished(self):
- return (self.__ota_manager.state == QcloudHub.OtaState.IOT_OTAS_FETCHED)
-
- def __message_splice(self, state, progress, result_code, result_msg, version, ptype):
- message = None
- code = "%d" % (result_code)
- if ptype == 1:
- message = {
- "type": "report_progress",
- "report": {
- "progress": {
- "state": state,
- "percent": str(progress),
- "result_code": code,
- "result_msg": result_msg
- },
- "version": version
- }
- }
- elif ptype == 0:
- message = {
- "type": "report_progress",
- "report": {
- "progress": {
- "state": state,
- "result_code": code,
- "result_msg": result_msg
- },
- "version": version
- }
- }
-
- return message
-
- def __ota_gen_report_msg(self, version, progress, report_type):
- message = None
- if report_type == QcloudHub.OtaReportType.IOT_OTAR_DOWNLOAD_BEGIN:
- message = self.__message_splice("downloading", 0, 0, "", version, 1)
- elif report_type == QcloudHub.OtaReportType.IOT_OTAR_DOWNLOADING:
- message = self.__message_splice("downloading", progress, 0, "", version, 1)
- elif ((report_type == QcloudHub.OtaReportType.IOT_OTAR_DOWNLOAD_TIMEOUT)
- or (report_type == QcloudHub.OtaReportType.IOT_OTAR_FILE_NOT_EXIST)
- or (report_type == QcloudHub.OtaReportType.IOT_OTAR_MD5_NOT_MATCH)
- or (report_type == QcloudHub.OtaReportType.IOT_OTAR_AUTH_FAIL)
- or (report_type == QcloudHub.OtaReportType.IOT_OTAR_UPGRADE_FAIL)):
- message = self.__message_splice("fail", progress, report_type, "time_out", version, 0)
- elif report_type == QcloudHub.OtaReportType.IOT_OTAR_UPGRADE_BEGIN:
- message = self.__message_splice("burning", progress, 0, "", version, 0)
- elif report_type == QcloudHub.OtaReportType.IOT_OTAR_UPGRADE_SUCCESS:
- message = self.__message_splice("done", progress, 0, "", version, 0)
- else:
- self.__explorer_log.error("not support report_type:%d" % report_type)
- message = None
+ def templateDeinit(self, productId, deviceName):
+ """Template destroy
- return message
+ Template destroy
+ Args:
+ productId: device product_id
+ deviceName: device device_name
+ Returns:
+ success: zero and unsubscribe mid
+ fail: negative number and unsubscribe mid
+ """
+ if self.__hub.getConnectState() is not self.__hub.HubState.CONNECTED:
+ raise self.__hub.StateError("current state is not CONNECTED")
- def _ota_report_upgrade_result(self, version, report_type):
- if self.__ota_manager.state == QcloudHub.OtaState.IOT_OTAS_UNINITED:
- raise ValueError('ota handle is uninitialized')
- message = self.__ota_gen_report_msg(version, 1, report_type)
- if message is not None:
- return self.__ota_publish(message, 1)
- else:
- self.__explorer_log.error("message is none")
- return 1, -1
-
- def _ota_report_progress(self, progress, version, report_type):
- if self.__ota_manager.state == QcloudHub.OtaState.IOT_OTAS_UNINITED:
- raise ValueError('ota handle is uninitialized')
- message = self.__ota_gen_report_msg(version, progress, report_type)
- if message is not None:
- return self.__ota_publish(message, 0)
- else:
- self.__explorer_log.error("message is none")
- return 3
+ client = productId + deviceName
+ if (client not in self.__template_map.keys()
+ or self.__template_map[client] is None):
+ self.__logger.error("[template] not found template handle for client:%s" % (client))
+ return -1, -1
- def otaReportUpgradeSuccess(self, version):
- if version is None:
- rc, mid = self._ota_report_upgrade_result(self.__ota_manager.version,
- QcloudHub.OtaReportType.IOT_OTAR_UPGRADE_SUCCESS)
- else:
- rc, mid = self._ota_report_upgrade_result(version, QcloudHub.OtaReportType.IOT_OTAR_UPGRADE_SUCCESS)
- if rc != 0:
- self.__explorer_log.error("ota_report_upgrade_success fail")
- return -1
- return mid
-
- def otaReportUpgradeFail(self, version):
- if version is None:
- rc, mid = self._ota_report_upgrade_result(self.__ota_manager.version,
- QcloudHub.OtaReportType.IOT_OTAR_UPGRADE_FAIL)
- else:
- rc, mid = self._ota_report_upgrade_result(version, QcloudHub.OtaReportType.IOT_OTAR_UPGRADE_FAIL)
+ template = self.__template_map[client]
+ rc, mid = template.template_deinit()
if rc != 0:
- self.__explorer_log.error("ota_report_upgrade_success fail")
- return -1
- return mid
-
- def otaIoctlNumber(self, cmd_type):
- if ((self.__ota_manager.state == QcloudHub.OtaState.IOT_OTAS_INITED)
- or (self.__ota_manager.state == QcloudHub.OtaState.IOT_OTAS_UNINITED)):
- return -1, "state error"
-
- if cmd_type == QcloudHub.OtaCmdType.IOT_OTAG_FETCHED_SIZE:
- return self.__ota_manager.size_fetched, "success"
- elif cmd_type == QcloudHub.OtaCmdType.IOT_OTAG_FILE_SIZE:
- return self.__ota_manager.file_size, "success"
- elif cmd_type == QcloudHub.OtaCmdType.IOT_OTAG_CHECK_FIRMWARE:
- if self.__ota_manager.state is not QcloudHub.OtaState.IOT_OTAS_FETCHED:
- return -1, "state error"
- md5sum = self.__ota_manager.md5.hexdigest()
- if md5sum == self.__ota_manager.md5sum:
- return 0, "success"
- else:
- self._ota_report_upgrade_result(self.__ota_manager.version,
- QcloudHub.OtaReportType.IOT_OTAR_MD5_NOT_MATCH)
- return -1, "md5 error"
- pass
-
- return -1, "cmd type error"
-
- def otaIoctlString(self, cmd_type, length):
- if ((self.__ota_manager.state == QcloudHub.OtaState.IOT_OTAS_INITED)
- or (self.__ota_manager.state == QcloudHub.OtaState.IOT_OTAS_UNINITED)):
- return "nll", "state error"
-
- if cmd_type == QcloudHub.OtaCmdType.IOT_OTAG_VERSION:
- if len(self.__ota_manager.version) > length:
- return "null", "version length error"
- else:
- return self.__ota_manager.version, "success"
- elif cmd_type == QcloudHub.OtaCmdType.IOT_OTAG_MD5SUM:
- if len(self.__ota_manager.md5sum) > length:
- return "null", "md5sum length error"
- else:
- return self.__ota_manager.md5sum, "success"
-
- return "null", "cmd type error"
-
- def otaResetMd5(self):
- self.__ota_manager.md5 = None
- self.__ota_manager.md5 = hashlib.md5()
-
- def otaMd5Update(self, buf):
- if buf is None:
- self.__explorer_log.error("buf is none")
- return -1
- if self.__ota_manager.md5 is None:
- self.__explorer_log.error("md5 handle is uninitialized")
- return -1
-
- # self.__ota_manager.md5.update(buf.encode(encoding='utf-8'))
- self.__ota_manager.md5.update(buf)
-
- def __ota_http_deinit(self, http):
- print("__ota_http_deinit do nothing")
-
- def httpInit(self, host, url, offset, size, timeout_sec):
- range_format = "bytes=%d-%d"
- srange = range_format % (offset, size)
-
- header = {}
- header["Host"] = host
- header["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
- header["Accept-Encoding"] = "gzip, deflate"
- header["Range"] = srange
-
- self.http_manager = QcloudHub.http_manage()
- self.http_manager.header = header
- self.http_manager.host = host
-
- if self.__ota_manager.is_https:
- context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cadata=self.__iot_ca_crt)
- self.http_manager.https_context = context
- try:
- self.http_manager.request = urllib.request.Request(url=url, headers=header)
- self.http_manager.handle = urllib.request.urlopen(self.http_manager.request,
- context=context,
- timeout=timeout_sec)
- except urllib.error.HTTPError as e:
- self.__explorer_log.error("https connect error:%d" % e.code)
- self.http_manager.err_code = e.code
- return 1
- except urllib.error.URLError as e:
- self.__explorer_log.error("https connect error:%s" % e.reason)
- self.http_manager.err_reason = e.reason
- return 1
- else:
- try:
- self.http_manager.request = urllib.request.Request(url=url, headers=header)
- self.http_manager.handle = urllib.request.urlopen(self.http_manager.request,
- timeout=timeout_sec)
- except Exception as e:
- self.__explorer_log.error("http connect error:%s" % str(e))
- return 1
- return 0
-
- def httpFetch(self, buf_len):
- if self.http_manager.handle is None:
- return None, -1
- try:
- buf = self.http_manager.handle.read(buf_len)
- return buf, len(buf)
- except Exception as e:
- self.__explorer_log.error("http read error:%s" % str(e))
- return None, -2
-
- def otaReportVersion(self, version):
- if version is None or len(version) == 0:
- raise ValueError('Invalid version.')
- if len(version) < self.__ota_version_len_min or len(version) > self.__ota_version_len_max:
- raise ValueError('Invalid version length')
- if self.__ota_manager.state == QcloudHub.OtaState.IOT_OTAS_UNINITED:
- raise ValueError('ota handle is uninitialized')
- report = {
- "type": "report_version",
- "report": {
- "version": version
- }
- }
- rc, mid = self.__ota_publish(report, 1)
- if rc != 0:
- self.__explorer_log.error("__ota_publish fail")
- return 1, mid
- return 0, mid
-
- def otaDownloadStart(self, offset, size):
- if offset < 0 or size <= 0:
- raise ValueError('Invalid length.')
- if offset == 0:
- self.otaResetMd5()
- self.__ota_http_deinit(self.__ota_manager.http_manager)
- # 断点续传初始值不为0
- self.__ota_manager.size_fetched += offset
-
- rc = self.httpInit(self.__ota_manager.host, self.__ota_manager.purl, offset, size, 10000 / 1000)
- if rc != 0:
- if self.http_manager.err_code == 403:
- self._ota_report_upgrade_result(self.__ota_manager.version,
- QcloudHub.OtaReportType.IOT_OTAR_AUTH_FAIL)
- elif self.http_manager.err_code == 404:
- self._ota_report_upgrade_result(self.__ota_manager.version,
- QcloudHub.OtaReportType.IOT_OTAR_FILE_NOT_EXIST)
- elif self.http_manager.err_code == 408:
- self._ota_report_upgrade_result(self.__ota_manager.version,
- QcloudHub.OtaReportType.IOT_OTAR_DOWNLOAD_TIMEOUT)
- else:
- # 其他错误判断(error.reason)
- self.__explorer_log.error("http_init error:%d" % self.http_manager.err_code)
-
- return rc
-
- def otaFetchYield(self, buf_len):
- if self.__ota_manager.state != QcloudHub.OtaState.IOT_OTAS_FETCHING:
- self.__explorer_log.error("ota state is not fetching")
- return None, -1
- # http read
- buf, rv_len = self.httpFetch(buf_len)
- if rv_len < 0:
- if rv_len == -2:
- self._ota_report_upgrade_result(self.__ota_manager.version,
- QcloudHub.OtaReportType.IOT_OTAR_DOWNLOAD_TIMEOUT)
- return None, -2
- else:
- if self.__ota_manager.size_fetched == 0:
- self._ota_report_progress(QcloudHub.OtaProgressCode.IOT_OTAP_FETCH_PERCENTAGE_MIN,
- self.__ota_manager.version,
- QcloudHub.OtaReportType.IOT_OTAR_DOWNLOAD_BEGIN)
- self.__ota_manager.report_timestamp = int(time.time())
- pass
- self.__ota_manager.size_last_fetched = rv_len
- self.__ota_manager.size_fetched += rv_len
-
- percent = int((self.__ota_manager.size_fetched * 100) / self.__ota_manager.file_size)
- if percent == 100:
- self._ota_report_progress(percent, self.__ota_manager.version,
- QcloudHub.OtaReportType.IOT_OTAR_DOWNLOADING)
+ self.__logger.error("[template] unsubscribe error:rc:%d" % (rc))
else:
- timestamp = int(time.time())
- # 间隔1秒上报一次
- if (((timestamp - self.__ota_manager.report_timestamp) >= 1)
- and (self.__ota_manager.size_last_fetched > 0)):
- self.__ota_manager.report_timestamp = timestamp
- self._ota_report_progress(percent, self.__ota_manager.version,
- QcloudHub.OtaReportType.IOT_OTAR_DOWNLOADING)
-
- if self.__ota_manager.size_fetched >= self.__ota_manager.file_size:
- self.__ota_manager.state = QcloudHub.OtaState.IOT_OTAS_FETCHED
-
- self.__ota_manager.md5.update(buf)
-
- return buf, rv_len
-
-
- def rrpcInit(self):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
-
- rrpc_topic_sub = self.__topic_info.rrpc_topic_sub_prefix + "+"
- sub_res, mid = self.subscribe(rrpc_topic_sub, 0)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, rrpc_topic_sub))
- return 1
- # 判断订阅是否成功(qos0)
- return 0
-
- def rrpcReply(self, reply, length):
- if reply is None or length == 0:
- raise ValueError('Invalid length.')
- if self.__process_id is None:
- raise ValueError('no process id')
- topic = self.__topic_info.rrpc_topic_pub_prefix + self.__process_id
- rc, mid = self.publish(topic, reply, 0)
- if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, topic))
- return -1, mid
+ self.__is_subscribed_property_topic = False
+
+ self.__template_map.pop(client)
return rc, mid
- def shadowInit(self):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not connect")
+ def dynregDevice(self, timeout=10,dynregDomain=None):
+ """Dynamic register
- shadow_topic_sub = self.__topic_info.shadow_topic_sub
- sub_res, mid = self.subscribe(shadow_topic_sub, 0)
- if sub_res != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (sub_res, shadow_topic_sub))
- return -1
- return 0
+ Get the device secret from the Cloud
+ Args:
+ timeout: request timeout
+ Returns:
+ success: return zero and device secret
+ fail: -1 and error message
+ """
+ return self.__hub.dynregDevice(timeout, dynregDomain)
- def getShadow(self):
- topic_pub = self.__topic_info.shadow_topic_pub
+ def httpDevice(self,topicName=None,payload=None,qos=0,timeout=10,httpDomain=None):
+ """
+ 设备基于 HTTP 接入
+ :param topicName: 发布消息的 Topic 名称
+ :param payload:发布消息的内容
+ :param qos:消息 Qos 等级
+ :param timeout:超时时间
+ :param httpDomain: 域名,默认为国内,私有化和海外设备需要传入
+ :return:
+ success: return zero and device secret
+ fail: -1 and error message
+ """
+ return self.__hub.httpDevice(topicName,payload,qos,timeout,httpDomain)
- client_token = self.__device_file.product_id + "-" + str(self._shadow_token_num)
- self._shadow_token_num += 1
+ # gateway
+ def isSubdevStatusOnline(self, sub_productId, sub_devName):
+ """Sub-device status
+
+ Determine if the device is online
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ Returns:
+ success: if device is online
+ """
+ return self.__hub.isSubdevStatusOnline(sub_productId, sub_devName)
+
+ def updateSubdevStatus(self, sub_productId, sub_devName, status):
+ """Update device status
+
+ Update sub-device local status
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ status: new status
+ Returns:
+ success: None
+ """
+ return self.__hub.updateSubdevStatus(sub_productId, sub_devName, status)
- message = {
- "type": "get",
- "clientToken": client_token
- }
- rc, mid = self.publish(topic_pub, message, 0)
- if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, topic_pub))
- return -1, mid
- return rc, mid
-
- def shadowJsonConstructDesireNull(self):
- client_token = self.__device_file.product_id + "-" + str(self._shadow_token_num)
- self._shadow_token_num += 1
- json_out = {
- "type": "update",
- "state": {
- "desired": None
- },
- "clientToken": client_token
- }
- return json_out
-
- def shadowUpdate(self, shadow_docs, length):
- if shadow_docs is None or length == 0:
- raise ValueError('Invalid length.')
- topic = self.__topic_info.shadow_topic_pub
- rc, mid = self.publish(topic, shadow_docs, 0)
- if rc != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (rc, topic))
- return -1, mid
- return rc, mid
+ def gatewaySubdevGetConfigList(self):
+ """Get sub-device list
- def shadowJsonConstructReport(self, *args):
- format_string = '"%s":"%s"'
- format_int = '"%s":%d'
- report_string = '{"type": "update", "state": {"reported": {'
- arg_cnt = 0
-
- for arg in args:
- arg_cnt += 1
- if arg.type == "int" or arg.type == "float":
- report_string += format_int % (arg.key, arg.data)
- elif arg.type == "string":
- report_string += format_string % (arg.key, arg.data)
- else:
- self.__explorer_log.error("type not support")
- arg.data = " "
- if arg_cnt < len(args):
- report_string += ","
- pass
- report_string += '}}, "clientToken": "%s"}'
+ Get the list of sub-devices in the configuration file
+ Args: None
+ Returns:
+ success: sub-device list
+ """
+ return self.__hub.gatewaySubdevGetConfigList()
- client_token = self.__device_file.product_id + "-" + str(self._shadow_token_num)
- self._shadow_token_num += 1
+ def gatewaySubdevSubscribe(self, topic):
+ """Subscribe sub-device topic
- report_out = report_string % (client_token)
- json_out = json.loads(report_out)
+ Subscribe sub-device topic
+ Args: sub-device topic
+ Returns:
+ success: zero and subscribe mid
+ """
+ self.__hub.register_explorer_callback(topic, self.__handle_subdev_topic)
+ return self.__hub.gatewaySubdevSubscribe(topic)
- return json_out
+ def gatewaySubdevOnline(self, sub_productId, sub_devName):
+ """Make sub-device online
+
+ Make sub-device online
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ Returns:
+ success: zero and publish mid
+ """
+ return self.__hub.gatewaySubdevOnline(sub_productId, sub_devName)
- def broadcastInit(self):
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not connect")
+ def gatewaySubdevOffline(self, sub_productId, sub_devName):
+ """Make sub-device offline
+
+ Make sub-device offline
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ Returns:
+ success: zero and publish mid
+ """
+ return self.__hub.gatewaySubdevOffline(sub_productId, sub_devName)
- broadcast_topic_sub = self.__topic_info.broadcast_topic_sub
- sub_res, mid = self.subscribe(broadcast_topic_sub, 0)
- if sub_res != 0:
- self.__explorer_log.error("topic_publish error:rc:%d,topic:%s" % (sub_res, broadcast_topic_sub))
- return -1
- return 0
+ def gatewaySubdevBind(self, sub_productId, sub_devName, sub_secret):
+ """Bind sub-device
+
+ Gateway device bind sub-device
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ sub_secret: sub-device secret
+ Returns:
+ success: zero and publish mid
+ """
+ return self.__hub.gatewaySubdevBind(sub_productId, sub_devName, sub_secret)
+
+ def gatewaySubdevUnbind(self, sub_productId, sub_devName):
+ """Unbind sub-device
+
+ Gateway device unbind sub-device
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ sub_secret: sub-device secret
+ Returns:
+ success: zero and publish mid
+ """
+ return self.__hub.gatewaySubdevUnbind(sub_productId, sub_devName)
- def subscribeInit(self):
+ def gatewayInit(self):
+ """Gateway initialization
- if self.__explorer_state is not QcloudHub.HubState.CONNECTED:
- raise QcloudHub.StateError("current state is not CONNECTED")
+ Gateway initialization
+ Args: None
+ Returns:
+ success: zero and subscribe mid
+ """
+ return self.__hub.gatewayInit()
- subscribe_topic_sub = self.__topic_info.template_service_topic_sub
- sub_res, mid = self.subscribe(subscribe_topic_sub, 1)
- # should deal mid
- self.__explorer_log.debug("mid:%d" % mid)
- if sub_res != 0:
- self.__explorer_log.error("topic_subscribe error:rc:%d,topic:%s" % (sub_res, subscribe_topic_sub))
- return 1
- return 0
\ No newline at end of file
+ # ota
+ def otaInit(self, productId, deviceName, callback):
+ """Ota initialization
+
+ Ota initialization
+ Args:
+ productId: product id
+ deviceName: device name
+ callback: user received message callback
+ Returns:
+ success: zero and subscribe mid
+ """
+ return self.__hub.otaInit(productId, deviceName, callback)
+
+ def otaIsFetching(self, productId, deviceName):
+ """Is downloading
+
+ Is downloading
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: True
+ fail: False
+ """
+ return self.__hub.otaIsFetching(productId, deviceName)
+
+ def otaIsFetchFinished(self, productId, deviceName):
+ """Is download finished
+
+ Is download finished
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: True
+ fail: False
+ """
+ return self.__hub.otaIsFetchFinished(productId, deviceName)
+
+ def otaReportUpgradeSuccess(self, productId, deviceName, version):
+ """Report success message
+
+ Report upgrade success message to qcloud
+ Args:
+ productId: product id
+ deviceName: device name
+ version: firmware version
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ return self.__hub.otaReportUpgradeSuccess(productId, deviceName, version)
+
+ def otaReportUpgradeFail(self, productId, deviceName, version):
+ """Report fail message
+
+ Report upgrade fail message to qcloud
+ Args:
+ productId: product id
+ deviceName: device name
+ version: firmware version
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ return self.__hub.otaReportUpgradeFail(productId, deviceName, version)
+
+ def otaIoctlNumber(self, productId, deviceName, cmdType):
+ """User interaction
+
+ User interaction with SDK to get a number, like linux kernel ioctl
+ Args:
+ productId: product id
+ deviceName: device name
+ cmdType: interaction command
+ Returns:
+ success: the number you want and 'success' message
+ fail: negative number and error message
+ """
+ return self.__hub.otaIoctlNumber(productId, deviceName, cmdType)
+
+ def otaIoctlString(self, productId, deviceName, cmdType, length):
+ """User interaction
+
+ User interaction with SDK to get string, like linux kernel ioctl
+ Args:
+ productId: product id
+ deviceName: device name
+ cmdType: interaction command
+ length: command length
+ Returns:
+ success: the string you want and 'success' message
+ fail: negative number and error message
+ """
+ return self.__hub.otaIoctlString(productId, deviceName, cmdType, length)
+
+ def otaResetMd5(self, productId, deviceName):
+ """Reset md5 value
+
+ Reset md5 value
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: zero
+ fail: negative number
+ """
+ return self.__hub.otaResetMd5(productId, deviceName)
+
+ def otaMd5Update(self, productId, deviceName, buf):
+ """Update md5 value
+
+ Calculate new message md5 and update old
+ Args:
+ productId: product id
+ deviceName: device name
+ buf: new message
+ Returns:
+ success: zero
+ fail: negative number
+ """
+ return self.__hub.otaMd5Update(productId, deviceName, buf)
+
+ def httpInit(self, productId, deviceName, host, url, offset, size, timeoutSec):
+ """Http initialization
+
+ Http initialization
+ Args:
+ productId: product id
+ deviceName: device name
+ host: http server host
+ url: http url
+ offset: http parameter 'Range' minimum
+ size: http parameter 'Range' max
+ timeoutSec: http overtime time
+ Returns:
+ success: zero
+ fail: negative number
+ """
+ return self.__hub.httpInit(productId, deviceName, host, url, offset, size, timeoutSec)
+
+ def httpFetch(self, productId, deviceName, buf_len):
+ """Http download
+
+ Http download
+ Args:
+ productId: product id
+ deviceName: device name
+ buf_len: download max length
+ Returns:
+ success: downloaded content and length
+ fail: None and negative number
+ """
+ return self.__hub.httpFetch(productId, deviceName, buf_len)
+
+ def otaReportVersion(self, productId, deviceName, version):
+ """Report version
+
+ Report local firmware version
+ Args:
+ productId: product id
+ deviceName: device name
+ version: local firmware version
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ return self.__hub.otaReportVersion(productId, deviceName, version)
+
+ def otaDownloadStart(self, productId, deviceName, offset, size):
+ """Start download
+
+ Start download
+ Args:
+ productId: product id
+ deviceName: device name
+ offset: download offset
+ size: download size
+ Returns:
+ success: zero
+ fail: negative number
+ """
+ return self.__hub.otaDownloadStart(productId, deviceName, offset, size)
+
+ def otaFetchYield(self, productId, deviceName, buf_len):
+ """Http download
+
+ Perform an http download
+ Args:
+ productId: product id
+ deviceName: device name
+ buf_len: download max length
+ Returns:
+ success: downloaded content and length
+ fail: None and negative number
+ """
+ return self.__hub.otaFetchYield(productId, deviceName, buf_len)
+
+ def logInit(self, level, filePath, maxBytes, backupCount, enable=True):
+ """Log initialization
+
+ Log initialization
+ Args:
+ level: log level, type is class LoggerLevel()
+ enable: enable switch
+ Returns:
+ success: logger handle
+ fail: None
+ """
+ return self.__hub.logInit(level, filePath, maxBytes, backupCount, enable)
diff --git a/sample/device_info.json b/explorer/sample/device_info.json
similarity index 100%
rename from sample/device_info.json
rename to explorer/sample/device_info.json
diff --git a/explorer/sample/dynreg/example_dynreg.py b/explorer/sample/dynreg/example_dynreg.py
new file mode 100644
index 0000000..6d2b6a3
--- /dev/null
+++ b/explorer/sample/dynreg/example_dynreg.py
@@ -0,0 +1,87 @@
+import sys
+import time
+import logging
+from explorer.explorer import QcloudExplorer
+
+
+logger = None
+
+def on_connect(flags, rc, userdata):
+ global logger
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ global logger
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ global logger
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ global logger
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_subscribe(mid, granted_qos, userdata):
+ global logger
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ global logger
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def example_dynreg():
+ qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+ print("\033[1;36m dynreg test start...\033[0m")
+
+ """
+ start dynamic register
+ """
+ ret, msg = qcloud.dynregDevice()
+ if ret == 0:
+ # print("\033[1;36m dynamic register test success, psk: {}\033[0m".format(msg))
+ print("\033[1;36m dynamic register test success, psk = msg 内容 \033[0m")
+ else:
+ print("\033[1;31m dynamic register test fail, msg: {}\033[0m".format(msg))
+ return False
+
+ """
+ start mqtt connect
+ """
+ global logger
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024*1024*10, 5, enable=True)
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m mqtt test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ timestamp = qcloud.getNtpAccurateTime()
+ dt = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp/1000))
+ logger.debug("current time:%s" % dt)
+
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m dynamic register test success...\033[0m")
+
+ return True
\ No newline at end of file
diff --git a/explorer/sample/gateway/example_gateway.py b/explorer/sample/gateway/example_gateway.py
new file mode 100644
index 0000000..da79b08
--- /dev/null
+++ b/explorer/sample/gateway/example_gateway.py
@@ -0,0 +1,133 @@
+import sys
+import time
+import logging
+import threading
+from explorer.explorer import QcloudExplorer
+from gateway import product_1 as product_1
+from gateway import product_2 as product_2
+
+g_property_params = None
+g_control_msg_arrived = False
+
+product_list = []
+thread_list = []
+
+logger = None
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def example_gateway():
+ global logger
+ qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+ logger.debug("\033[1;36m gateway test start...\033[0m")
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m gateway test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ qcloud.gatewayInit()
+
+ """sub-device online"""
+ subdev_list = qcloud.gatewaySubdevGetConfigList()
+ for subdev in subdev_list:
+ if qcloud.isSubdevStatusOnline(subdev.product_id, subdev.device_name) is not True:
+ rc, mid = qcloud.gatewaySubdevOnline(subdev.product_id, subdev.device_name)
+ if rc == 0:
+ qcloud.updateSubdevStatus(subdev.product_id, subdev.device_name, "online")
+ logger.debug("online success")
+ else:
+ logger.error("online fail")
+ return False
+
+ """sub-device offline"""
+ for subdev in subdev_list:
+ if qcloud.isSubdevStatusOnline(subdev.product_id, subdev.device_name) is True:
+ rc, mid = qcloud.gatewaySubdevOffline(subdev.product_id, subdev.device_name)
+ if rc == 0:
+ qcloud.updateSubdevStatus(subdev.product_id, subdev.device_name, "offline")
+ logger.debug("offline success")
+ else:
+ logger.error("offline fail")
+ return False
+
+ # """sub-device bind"""
+ # rc, mid = qcloud.gatewaySubdevBind("SUBDEV_PRODUCT_ID", "SUBDEV_DEVICE_NAME", "SUBDEV_DEVICE_SECRET")
+ # if rc == 0:
+ # logger.debug("bind success")
+ # else:
+ # logger.error("bind fail")
+ # return False
+
+ # """sub-device unbind"""
+ # rc, mid = qcloud.gatewaySubdevUnbind("SUBDEV_PRODUCT_ID", "SUBDEV_DEVICE_NAME")
+ # if rc == 0:
+ # logger.debug("unbind success")
+ # else:
+ # logger.error("unbind fail")
+ # return False
+
+ product_list.append(product_1)
+ product_list.append(product_2)
+ index = 0
+ """sub-device affairs"""
+ for subdev in subdev_list:
+ if index >= len(product_list):
+ break
+ try:
+ thread = threading.Thread(target=product_list[index].product_init, args=(subdev.product_id, subdev.device_name, qcloud, logger))
+ global thread_list
+ thread_list.append(thread)
+ thread.start()
+ except:
+ logger.error("Error: unable to start thread")
+ return False
+ index += 1
+
+ for thread in thread_list:
+ thread.join()
+
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m gateway test success...\033[0m")
+
+ return True
\ No newline at end of file
diff --git a/explorer/sample/gateway/prdouct1_config.json b/explorer/sample/gateway/prdouct1_config.json
new file mode 100644
index 0000000..393dbbe
--- /dev/null
+++ b/explorer/sample/gateway/prdouct1_config.json
@@ -0,0 +1,153 @@
+{
+ "version":"1.0",
+ "properties":[
+ {
+ "id":"power_switch",
+ "name":"电灯开关",
+ "desc":"控制电灯开灭",
+ "required":true,
+ "mode":"rw",
+ "define":{
+ "type":"bool",
+ "mapping":{
+ "0":"关",
+ "1":"开"
+ }
+ }
+ },
+ {
+ "id":"color",
+ "name":"颜色",
+ "desc":"灯光颜色",
+ "mode":"rw",
+ "define":{
+ "type":"enum",
+ "mapping":{
+ "0":"Red",
+ "1":"Green",
+ "2":"Blue"
+ }
+ }
+ },
+ {
+ "id":"brightness",
+ "name":"亮度",
+ "desc":"灯光亮度",
+ "mode":"rw",
+ "define":{
+ "type":"int",
+ "unit":"%",
+ "step":"1",
+ "min":"0",
+ "max":"100",
+ "start":"1"
+ }
+ },
+ {
+ "id":"name",
+ "name":"灯位置名称",
+ "desc":"灯位置名称:书房、客厅等",
+ "mode":"rw",
+ "required":false,
+ "define":{
+ "type":"string",
+ "min":"0",
+ "max":"64"
+ }
+ }
+ ],
+ "events":[
+ {
+ "id":"status_report",
+ "name":"DeviceStatus",
+ "desc":"Report the device status",
+ "type":"info",
+ "required":false,
+ "params":[
+ {
+ "id":"status",
+ "name":"running_state",
+ "desc":"Report current device running state",
+ "define":{
+ "type":"bool",
+ "mapping":{
+ "0":"normal",
+ "1":"fault"
+ }
+ }
+ },
+ {
+ "id":"message",
+ "name":"Message",
+ "desc":"Some extra message",
+ "define":{
+ "type":"string",
+ "min":"0",
+ "max":"64"
+ }
+ }
+ ]
+ },
+ {
+ "id":"low_voltage",
+ "name":"LowVoltage",
+ "desc":"Alert for device voltage is low",
+ "type":"alert",
+ "required":false,
+ "params":[
+ {
+ "id":"voltage",
+ "name":"Voltage",
+ "desc":"Current voltage",
+ "define":{
+ "type":"float",
+ "unit":"V",
+ "step":"1",
+ "min":"0.0",
+ "max":"24.0",
+ "start":"1"
+ }
+ }
+ ]
+ },
+ {
+ "id":"hardware_fault",
+ "name":"Hardware_fault",
+ "desc":"Report hardware fault",
+ "type":"fault",
+ "required":false,
+ "params":[
+ {
+ "id":"name",
+ "name":"Name",
+ "desc":"Name like: memory,tf card, censors ...",
+ "define":{
+ "type":"string",
+ "min":"0",
+ "max":"64"
+ }
+ },
+ {
+ "id":"error_code",
+ "name":"Error_Code",
+ "desc":"Error code for fault",
+ "define":{
+ "type":"int",
+ "unit":"",
+ "step":"1",
+ "min":"0",
+ "max":"2000",
+ "start":"1"
+ }
+ }
+ ]
+ }
+ ],
+ "actions":[
+
+ ],
+ "profile":{
+ "ProductId":"SHP7NE6RXE",
+ "CategoryId":"3"
+ }
+}
diff --git a/explorer/sample/gateway/prdouct2_config.json b/explorer/sample/gateway/prdouct2_config.json
new file mode 100644
index 0000000..735861a
--- /dev/null
+++ b/explorer/sample/gateway/prdouct2_config.json
@@ -0,0 +1,153 @@
+{
+ "version":"1.0",
+ "properties":[
+ {
+ "id":"power_switch",
+ "name":"电灯开关",
+ "desc":"控制电灯开灭",
+ "required":true,
+ "mode":"rw",
+ "define":{
+ "type":"bool",
+ "mapping":{
+ "0":"关",
+ "1":"开"
+ }
+ }
+ },
+ {
+ "id":"color",
+ "name":"颜色",
+ "desc":"灯光颜色",
+ "mode":"rw",
+ "define":{
+ "type":"enum",
+ "mapping":{
+ "0":"Red",
+ "1":"Green",
+ "2":"Blue"
+ }
+ }
+ },
+ {
+ "id":"brightness",
+ "name":"亮度",
+ "desc":"灯光亮度",
+ "mode":"rw",
+ "define":{
+ "type":"int",
+ "unit":"%",
+ "step":"1",
+ "min":"0",
+ "max":"100",
+ "start":"1"
+ }
+ },
+ {
+ "id":"name",
+ "name":"灯位置名称",
+ "desc":"灯位置名称:书房、客厅等",
+ "mode":"rw",
+ "required":false,
+ "define":{
+ "type":"string",
+ "min":"0",
+ "max":"64"
+ }
+ }
+ ],
+ "events":[
+ {
+ "id":"status_report",
+ "name":"DeviceStatus",
+ "desc":"Report the device status",
+ "type":"info",
+ "required":false,
+ "params":[
+ {
+ "id":"status",
+ "name":"running_state",
+ "desc":"Report current device running state",
+ "define":{
+ "type":"bool",
+ "mapping":{
+ "0":"normal",
+ "1":"fault"
+ }
+ }
+ },
+ {
+ "id":"message",
+ "name":"Message",
+ "desc":"Some extra message",
+ "define":{
+ "type":"string",
+ "min":"0",
+ "max":"64"
+ }
+ }
+ ]
+ },
+ {
+ "id":"low_voltage",
+ "name":"LowVoltage",
+ "desc":"Alert for device voltage is low",
+ "type":"alert",
+ "required":false,
+ "params":[
+ {
+ "id":"voltage",
+ "name":"Voltage",
+ "desc":"Current voltage",
+ "define":{
+ "type":"float",
+ "unit":"V",
+ "step":"1",
+ "min":"0.0",
+ "max":"24.0",
+ "start":"1"
+ }
+ }
+ ]
+ },
+ {
+ "id":"hardware_fault",
+ "name":"Hardware_fault",
+ "desc":"Report hardware fault",
+ "type":"fault",
+ "required":false,
+ "params":[
+ {
+ "id":"name",
+ "name":"Name",
+ "desc":"Name like: memory,tf card, censors ...",
+ "define":{
+ "type":"string",
+ "min":"0",
+ "max":"64"
+ }
+ },
+ {
+ "id":"error_code",
+ "name":"Error_Code",
+ "desc":"Error code for fault",
+ "define":{
+ "type":"int",
+ "unit":"",
+ "step":"1",
+ "min":"0",
+ "max":"2000",
+ "start":"1"
+ }
+ }
+ ]
+ }
+ ],
+ "actions":[
+
+ ],
+ "profile":{
+ "ProductId":"HAVOCMDHTL",
+ "CategoryId":"3"
+ }
+}
diff --git a/explorer/sample/gateway/product_1.py b/explorer/sample/gateway/product_1.py
new file mode 100644
index 0000000..d4cb061
--- /dev/null
+++ b/explorer/sample/gateway/product_1.py
@@ -0,0 +1,189 @@
+import sys
+import time
+import json
+import logging
+
+qcloud = None
+logger = None
+g_property_params = None
+g_control_msg_arrived = False
+reply = False
+
+def on_subdev_cb(topic, qos, payload, userdata):
+ global reply
+ reply = True
+ pass
+
+def on_template_property(topic, qos, payload, userdata):
+ logger.debug("product_1:%s:params:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+
+ global reply
+ reply = True
+
+ global qcloud
+ # save changed propertys
+ global g_property_params
+ g_property_params = payload
+
+ global g_control_msg_arrived
+ g_control_msg_arrived = True
+
+ # deal down stream
+
+ # 测试,实际应发送用户属性数据
+ reply_param = qcloud.ReplyPara()
+ reply_param.code = 0
+ reply_param.timeout_ms = 5 * 1000
+ reply_param.status_msg = '\0'
+
+ qcloud.templateControlReply(product_id, device_name, reply_param)
+
+ pass
+
+def on_template_service(topic, qos, payload, userdata):
+ logger.debug("product_1:%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ global reply
+ reply = True
+ pass
+
+def on_template_event(topic, qos, payload, userdata):
+ logger.debug("product_1:%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ global reply
+ reply = True
+ pass
+
+
+def on_template_action(topic, qos, payload, userdata):
+ logger.debug("product_1:%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+
+ global reply
+ reply = True
+
+ global qcloud
+ clientToken = payload["clientToken"]
+ reply_param = qcloud.ReplyPara()
+ reply_param.code = 0
+ reply_param.timeout_ms = 5 * 1000
+ reply_param.status_msg = "action execute success!"
+ res = {
+ "err_code": 0
+ }
+
+ qcloud.templateActionReply(product_id, device_name, clientToken, res, reply_param)
+ pass
+
+def report_json_construct(thing_list):
+
+ format_string = '"%s":"%s"'
+ format_int = '"%s":%d'
+ report_string = '{'
+ arg_cnt = 0
+
+ for arg in thing_list:
+ arg_cnt += 1
+ if arg.type == "int" or arg.type == "float" or arg.type == "bool" or arg.type == "enum":
+ report_string += format_int % (arg.key, arg.data)
+ elif arg.type == "string":
+ report_string += format_string % (arg.key, arg.data)
+ else:
+ print("type[%s] not support" % arg.type)
+ arg.data = " "
+ if arg_cnt < len(thing_list):
+ report_string += ","
+ pass
+ report_string += '}'
+
+ json_out = json.loads(report_string)
+
+ return json_out
+
+def wait_for_reply():
+ cnt = 0
+ global reply
+ while cnt < 3:
+ if reply is True:
+ reply = False
+ return 0
+ time.sleep(0.5)
+ cnt += 1
+ return -1
+
+def product_init(pid, subdev_list, handle, log):
+ global qcloud
+ global logger
+ qcloud = handle
+ logger = log
+
+ subdev = subdev_list
+ global product_id
+ global device_name
+ product_id = pid
+ device_name = subdev
+
+ """
+ 订阅网关子设备topic
+ """
+ topic_list = []
+ topic_format = "%s/%s/%s"
+ topic_data = topic_format % (product_id, device_name, "data")
+ topic_list.append((topic_data, 0))
+ """ 注册topic对应回调 """
+ qcloud.registerUserCallback(topic_data, on_subdev_cb)
+
+ """ 订阅子设备topic,在此必须传入元组列表[(topic1,qos2),(topic2,qos2)] """
+ rc, mid = qcloud.gatewaySubdevSubscribe(topic_list)
+ if rc == 0:
+ logger.debug("gateway subdev subscribe success")
+ else:
+ logger.error("gateway subdev subscribe fail")
+ return -1
+
+ """
+ 注册数据模板topic回调,用户不再关注具体topic
+ """
+ qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+ qcloud.templateSetup(product_id, device_name, "explorer/sample/gateway/prdouct1_config.json")
+ # sysinfo report
+ sys_info = {
+ "module_hardinfo": "X86-64",
+ "module_softinfo": "V1.0",
+ "fw_ver": "0.0.0",
+ "imei": "11-22-33-44",
+ "lat": "22.546015",
+ "lon": "113.941125",
+ "device_label": {
+ "append_info": "just test"
+ }
+ }
+ rc, mid = qcloud.templateReportSysInfo(product_id, device_name, sys_info)
+ if rc != 0:
+ logger.error("sysinfo report fail")
+ return -1
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for report event reply timeout")
+ return -1
+
+ rc, mid = qcloud.templateGetStatus(product_id, device_name)
+ if rc != 0:
+ logger.error("get status fail")
+ return -1
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for report event reply timeout")
+ return -1
+
+ prop_list = qcloud.getPropertyList(product_id, device_name)
+ reports = report_json_construct(prop_list)
+ params_in = qcloud.templateJsonConstructReportArray(product_id, device_name, reports)
+ rc, mid = qcloud.templateReport(product_id, device_name, params_in)
+ if rc != 0:
+ logger.error("property report fail")
+ return -1
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for report event reply timeout")
+ return -1
+
+ return 0
diff --git a/explorer/sample/gateway/product_2.py b/explorer/sample/gateway/product_2.py
new file mode 100644
index 0000000..62fa7fa
--- /dev/null
+++ b/explorer/sample/gateway/product_2.py
@@ -0,0 +1,184 @@
+import sys
+import time
+import json
+import logging
+
+qcloud = None
+g_property_params = None
+g_control_msg_arrived = False
+reply = False
+
+def on_subdev_cb(topic, qos, payload, userdata):
+ global reply
+ reply = True
+ pass
+
+def on_template_property(topic, qos, payload, userdata):
+ logger.debug("product_2:%s:params:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ global reply
+ reply = True
+
+ global qcloud
+ # save changed propertys
+ global g_property_params
+ g_property_params = payload
+
+ global g_control_msg_arrived
+ g_control_msg_arrived = True
+
+ # deal down stream
+
+ # 测试,实际应发送用户属性数据
+ reply_param = qcloud.ReplyPara()
+ reply_param.code = 0
+ reply_param.timeout_ms = 5 * 1000
+ reply_param.status_msg = '\0'
+
+ qcloud.templateControlReply(product_id, device_name, reply_param)
+
+ pass
+
+def on_template_service(topic, qos, payload, userdata):
+ logger.debug("product_2:%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ global reply
+ reply = True
+ pass
+
+def on_template_event(topic, qos, payload, userdata):
+ logger.debug("product_2:%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ global reply
+ reply = True
+ pass
+
+
+def on_template_action(topic, qos, payload, userdata):
+ logger.debug("product_2:%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ global reply
+ reply = True
+
+ global qcloud
+ clientToken = payload["clientToken"]
+ reply_param = qcloud.ReplyPara()
+ reply_param.code = 0
+ reply_param.timeout_ms = 5 * 1000
+ reply_param.status_msg = "action execute success!"
+ res = {
+ "err_code": 0
+ }
+
+ qcloud.templateActionReply(product_id, device_name, clientToken, res, reply_param)
+ pass
+
+def report_json_construct(thing_list):
+
+ format_string = '"%s":"%s"'
+ format_int = '"%s":%d'
+ report_string = '{'
+ arg_cnt = 0
+
+ for arg in thing_list:
+ arg_cnt += 1
+ if arg.type == "int" or arg.type == "float" or arg.type == "bool" or arg.type == "enum":
+ report_string += format_int % (arg.key, arg.data)
+ elif arg.type == "string":
+ report_string += format_string % (arg.key, arg.data)
+ else:
+ logger.error("type[%s] not support" % arg.type)
+ arg.data = " "
+ if arg_cnt < len(thing_list):
+ report_string += ","
+ pass
+ report_string += '}'
+
+ json_out = json.loads(report_string)
+
+ return json_out
+
+def wait_for_reply():
+ cnt = 0
+ global reply
+ while cnt < 3:
+ if reply is True:
+ reply = False
+ return 0
+ time.sleep(0.5)
+ cnt += 1
+ return -1
+
+def product_init(pid, subdev_list, handle, log):
+ global qcloud
+ global logger
+ qcloud = handle
+ logger = log
+
+ subdev = subdev_list
+ global product_id
+ global device_name
+ product_id = pid
+ device_name = subdev
+
+ """
+ 订阅网关子设备topic
+ """
+ topic_list = []
+ topic_format = "%s/%s/%s"
+ topic_data = topic_format % (product_id, device_name, "data")
+ topic_list.append((topic_data, 0))
+ """ 注册topic对应回调 """
+ qcloud.registerUserCallback(topic_data, on_subdev_cb)
+
+ """ 订阅子设备topic,在此必须传入元组列表[(topic1,qos2),(topic2,qos2)] """
+ rc, mid = qcloud.gatewaySubdevSubscribe(topic_list)
+ if rc == 0:
+ logger.debug("gateway subdev subscribe success")
+ else:
+ logger.error("gateway subdev subscribe fail")
+ return -1
+
+ qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+ qcloud.templateSetup(product_id, device_name, "explorer/sample/gateway/prdouct2_config.json")
+
+ # sysinfo report
+ sys_info = {
+ "module_hardinfo": "ESP8266",
+ "module_softinfo": "V1.0",
+ "fw_ver": "3.1.4",
+ "imei": "11-22-33-44",
+ "lat": "22.546015",
+ "lon": "113.941125",
+ "device_label": {
+ "append_info": "your self define info"
+ }
+ }
+ rc, mid = qcloud.templateReportSysInfo(product_id, device_name, sys_info)
+ if rc != 0:
+ logger.error("sysinfo report fail")
+ return -1
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for report event reply timeout")
+ return -1
+
+ rc, mid = qcloud.templateGetStatus(product_id, device_name)
+ if rc != 0:
+ logger.error("get status fail")
+ return -1
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for report event reply timeout")
+ return -1
+
+ prop_list = qcloud.getPropertyList(product_id, device_name)
+ reports = report_json_construct(prop_list)
+ params_in = qcloud.templateJsonConstructReportArray(product_id, device_name, reports)
+ rc, mid = qcloud.templateReport(product_id, device_name, params_in)
+ if rc != 0:
+ logger.error("property report fail")
+ return -1
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for report event reply timeout")
+ return -1
+
+ return 0
diff --git a/explorer/sample/httpAccess/example_http.py b/explorer/sample/httpAccess/example_http.py
new file mode 100644
index 0000000..10ed354
--- /dev/null
+++ b/explorer/sample/httpAccess/example_http.py
@@ -0,0 +1,79 @@
+import sys
+import time
+import logging
+from explorer.explorer import QcloudExplorer
+
+logger = None
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def example_http():
+
+ global logger
+ qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+ logger.debug("\033[1;36m http test start...\033[0m")
+
+ """
+ start http request send
+ """
+ ret, msg = qcloud.httpDevice()
+ if ret == 0:
+ logger.debug("\033[1;36m http test success...\033[0m")
+ else:
+ print("\033[1;31m http request test fail, msg: {}\033[0m".format(msg))
+ return False
+
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m connect test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ timestamp = qcloud.getNtpAccurateTime()
+ dt = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp / 1000))
+ logger.debug("current time:%s" % dt)
+
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m connect test success...\033[0m")
+
+ return True
\ No newline at end of file
diff --git a/explorer/sample/mqtt/example_mqtt.py b/explorer/sample/mqtt/example_mqtt.py
new file mode 100644
index 0000000..91487b6
--- /dev/null
+++ b/explorer/sample/mqtt/example_mqtt.py
@@ -0,0 +1,67 @@
+import sys
+import time
+from explorer.explorer import QcloudExplorer
+
+logger = None
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def example_mqtt():
+ global logger
+ qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+ logger.debug("\033[1;36m mqtt test start...\033[0m")
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m mqtt test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ timestamp = qcloud.getNtpAccurateTime()
+ dt = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp/1000))
+ logger.debug("current time:%s" % dt)
+
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m mqtt test success...\033[0m")
+
+ return True
diff --git a/explorer/sample/ota/example_ota.py b/explorer/sample/ota/example_ota.py
new file mode 100644
index 0000000..6cfc291
--- /dev/null
+++ b/explorer/sample/ota/example_ota.py
@@ -0,0 +1,346 @@
+import sys
+import time
+import logging
+import json
+import os
+from enum import Enum
+from explorer.explorer import QcloudExplorer
+
+g_report_res = False
+g_packet_id = 0
+g_pub_ack = False
+product_id = None
+device_name = None
+
+logger = None
+
+class OtaContextData(object):
+ def __init__(self):
+ self.file_size = 0
+ self.version = None
+ self.file_path = None
+ self.info_file_path = None
+ self.remote_version = None
+ self.local_version = None
+ self.download_size = 0
+
+class OtaCmdType(Enum):
+ IOT_OTAG_FETCHED_SIZE = 0
+ IOT_OTAG_FILE_SIZE = 1
+ IOT_OTAG_MD5SUM = 2
+ IOT_OTAG_VERSION = 3
+ IOT_OTAG_CHECK_FIRMWARE = 4
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ global g_packet_id
+ if g_packet_id == mid:
+ global g_pub_ack
+ g_pub_ack = True
+ logger.debug("publish ack id %d" % g_packet_id)
+ pass
+
+
+def on_subscribe(granted_qos, mid, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_ota_report(topic, qos, payload, userdata):
+ logger.debug("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ code = payload["result_code"]
+ if code == 0:
+ global g_report_res
+ g_report_res = True
+ pass
+
+
+def _get_local_fw_running_version():
+ # you should get real version and return
+ return "0.1.0"
+
+
+def _get_local_fw_info(ota_cxt):
+ if ota_cxt.info_file_path is None:
+ logger.error("file name is none")
+ return 0
+ if not os.path.exists(ota_cxt.info_file_path):
+ logger.error("info file not exists")
+ return 0
+
+ f = open(ota_cxt.info_file_path, "r")
+ buf = f.read()
+ if len(buf) > 0:
+ file_buf = json.loads(buf)
+ version = file_buf["version"]
+ download_size = file_buf["downloaded_size"]
+ if version is not None:
+ ota_cxt.local_version = version
+ if download_size > 0:
+ return download_size
+ f.close()
+
+ return 0
+
+
+def _cal_exist_fw_md5(ota_cxt):
+ if ota_cxt.file_path is None:
+ logger.error("file name is none")
+ return -1
+
+ global product_id
+ global device_name
+ total_read = 0
+ qcloud.otaResetMd5(product_id, device_name)
+ size = ota_cxt.download_size
+ with open(ota_cxt.file_path, "rb") as f:
+ while size > 0:
+ rlen = 5000 if size > 5000 else size
+ buf = f.read(rlen)
+ if buf == "":
+ break
+ qcloud.otaMd5Update(product_id, device_name, buf)
+ size -= rlen
+ total_read += rlen
+ pass
+ f.close()
+ logger.debug("total read:%d" % total_read)
+
+ return 0
+
+
+def _update_fw_downloaded_size(ota_cxt):
+ local_size = _get_local_fw_info(ota_cxt)
+ logger.debug("local_size:%d,local_ver:%s,re_ver:%s" % (local_size, ota_cxt.local_version, ota_cxt.remote_version))
+ if ((ota_cxt.local_version != ota_cxt.remote_version)
+ or (ota_cxt.download_size > ota_cxt.file_size)):
+ ota_cxt.download_size = 0
+ return 0
+ ota_cxt.download_size = local_size
+ rc = _cal_exist_fw_md5(ota_cxt)
+ if rc != 0:
+ logger.error("cal md5 error")
+ os.remove(ota_cxt.info_file_path)
+ ota_cxt.download_size = 0
+ return 0
+
+ return local_size
+
+
+def _save_fw_data_to_file(ota_cxt, buf, buf_len):
+ if ota_cxt.file_path is None:
+ logger.error("file name is none")
+ return -1
+ f = None
+ wr_len = 0
+ if ota_cxt.download_size > 0:
+ f = open(ota_cxt.file_path, "ab+")
+ else:
+ f = open(ota_cxt.file_path, "wb+")
+
+ f.seek(ota_cxt.download_size, 0)
+ while True:
+ wr_len = f.write(buf)
+ if wr_len == buf_len:
+ break
+ else:
+ logger.error('write size error')
+ f.close()
+ return -1
+ f.flush()
+ f.close()
+
+ return 0
+
+
+def _update_local_fw_info(ota_cxt):
+ data_format = "{\"%s\":\"%s\", \"%s\":%d}"
+ data = data_format % ("version", ota_cxt.remote_version, "downloaded_size", ota_cxt.download_size)
+ with open(ota_cxt.info_file_path, "w") as f:
+ wr_len = f.write(data)
+ if wr_len != len(data):
+ return -1
+ return 0
+
+
+def _wait_for_pub_ack(packet_id):
+ wait_cnt = 10
+
+ global g_packet_id
+ g_packet_id = packet_id
+
+ global g_pub_ack
+ while (g_pub_ack is not True):
+ logger.debug("wait for ack...")
+ time.sleep(0.5)
+ if wait_cnt == 0:
+ logger.error("wait report pub ack timeout!")
+ break
+ wait_cnt -= 1
+ pass
+
+ g_pub_ack = False
+
+
+def _board_upgrade(fw_path):
+ logger.debug("burning firmware...")
+
+ return 0
+
+def example_ota():
+ global logger
+ qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+ logger.debug("\033[1;36m ota test start...\033[0m")
+
+ global product_id
+ global device_name
+ product_id = qcloud.getProductID()
+ device_name = qcloud.getDeviceName()
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m ota test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ qcloud.otaInit(product_id, device_name, on_ota_report)
+
+ cnt = 0
+ while True:
+ if not qcloud.isMqttConnected():
+ if cnt >= 10:
+ logger.error("mqtt disconnect")
+ break
+ time.sleep(1)
+ cnt += 1
+ continue
+ cnt = 0
+
+ upgrade_fetch_success = True
+ ota_cxt = OtaContextData()
+
+ qcloud.otaReportVersion(product_id, device_name, _get_local_fw_running_version())
+ # wait for ack
+ time.sleep(1)
+
+ global g_report_res
+ if g_report_res:
+ download_finished = False
+ while (download_finished is not True):
+ logger.debug("wait for ota upgrade command")
+ if qcloud.otaIsFetching(product_id, device_name):
+ file_size, state = qcloud.otaIoctlNumber(product_id, device_name, OtaCmdType.IOT_OTAG_FILE_SIZE)
+ if state == "success":
+ ota_cxt.file_size = file_size
+ else:
+ logger.error("ota_ioctl_number failed")
+ break
+ pass
+
+ version, state = qcloud.otaIoctlString(product_id, device_name, OtaCmdType.IOT_OTAG_VERSION, 32)
+ if state == "success":
+ ota_cxt.remote_version = version
+
+ ota_cxt.file_path = "./FW_%s.bin" % (ota_cxt.remote_version)
+ ota_cxt.info_file_path = "./FW_%s.json" % (ota_cxt.remote_version)
+
+ _update_fw_downloaded_size(ota_cxt)
+
+ rc = qcloud.otaDownloadStart(product_id, device_name, ota_cxt.download_size, ota_cxt.file_size)
+ if rc != 0:
+ upgrade_fetch_success = False
+ break
+
+ while (qcloud.otaIsFetchFinished(product_id, device_name, ) is not True):
+ buf, rv_len = qcloud.otaFetchYield(product_id, device_name, 5000)
+ if rv_len > 0:
+ rc = _save_fw_data_to_file(ota_cxt, buf, rv_len)
+ if rc != 0:
+ logger.error("save data to file fail")
+ upgrade_fetch_success = False
+ break
+ elif rv_len < 0:
+ logger.error("download fail rc:%d" % rv_len)
+ upgrade_fetch_success = False
+ break
+
+ fetched_size, state = qcloud.otaIoctlNumber(product_id, device_name, OtaCmdType.IOT_OTAG_FETCHED_SIZE)
+ if state == "success":
+ ota_cxt.download_size = fetched_size
+ else:
+ break
+
+ rc = _update_local_fw_info(ota_cxt)
+ if rc != 0:
+ logger.error("update local fw info error")
+ pass
+
+ #time.sleep(0.1)
+ # <> end
+
+ if upgrade_fetch_success:
+ os.remove(ota_cxt.info_file_path)
+ firmware_valid, state = qcloud.otaIoctlNumber(product_id, device_name, OtaCmdType.IOT_OTAG_CHECK_FIRMWARE)
+ if firmware_valid == 0:
+ logger.debug("The firmware download success")
+ upgrade_fetch_success = True
+ else:
+ logger.error("The firmware is invalid,state:%s" % state)
+ upgrade_fetch_success = False
+
+ download_finished = True
+ # <> end
+
+ if not download_finished:
+ time.sleep(1)
+ # <> end
+
+ _board_upgrade(ota_cxt.file_path)
+
+ # Report after confirming that the burning is successful or failed
+ packet_id = 0
+ if upgrade_fetch_success:
+ rc, packet_id = qcloud.otaReportUpgradeSuccess(product_id, device_name, None)
+ else:
+ rc, packet_id = qcloud.otaReportUpgradeFail(product_id, device_name, None)
+ if rc == 0:
+ _wait_for_pub_ack(packet_id)
+
+ g_report_res = False
+ time.sleep(2)
+
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m ota test success...\033[0m")
+
+ return True
\ No newline at end of file
diff --git a/explorer/sample/template/example_template.py b/explorer/sample/template/example_template.py
new file mode 100644
index 0000000..9324379
--- /dev/null
+++ b/explorer/sample/template/example_template.py
@@ -0,0 +1,298 @@
+import sys
+import time
+import json
+import logging
+from explorer.explorer import QcloudExplorer
+
+product_id = None
+device_name = None
+reply = False
+
+logger = None
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_subscribe(qos, mid, userdata):
+ logger.debug("%s:mid:%d,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_template_property(topic, qos, payload, userdata):
+ logger.debug("%s:params:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+
+ # save changed property
+ global reply
+ reply = True
+
+ # deal down stream and add your real value
+
+ reply_param = qcloud.ReplyPara()
+ reply_param.code = 0
+ reply_param.timeout_ms = 5 * 1000
+ reply_param.status_msg = '\0'
+
+ qcloud.templateControlReply(product_id, device_name, reply_param)
+ pass
+
+def on_template_service(topic, qos, payload, userdata):
+ logger.debug("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ pass
+
+def on_template_event(topic, qos, payload, userdata):
+ logger.debug("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ global reply
+ reply = True
+ pass
+
+
+def on_template_action(topic, qos, payload, userdata):
+ logger.debug("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ global reply
+ reply = True
+
+ clientToken = payload["clientToken"]
+ reply_param = qcloud.ReplyPara()
+ reply_param.code = 0
+ reply_param.timeout_ms = 5 * 1000
+ reply_param.status_msg = "action execute success!"
+ res = {
+ "err_code": 0
+ }
+
+ qcloud.templateActionReply(product_id, device_name, clientToken, res, reply_param)
+ pass
+
+def report_json_construct_property(thing_list):
+
+ format_string = '"%s":"%s"'
+ format_int = '"%s":%d'
+ report_string = '{'
+ arg_cnt = 0
+
+ for arg in thing_list:
+ arg_cnt += 1
+ if arg.type == "int" or arg.type == "float" or arg.type == "bool" or arg.type == "enum":
+ report_string += format_int % (arg.key, arg.data + 1)
+ elif arg.type == "string":
+ report_string += format_string % (arg.key, arg.data + "test")
+ else:
+ logger.err_code("type[%s] not support" % arg.type)
+ arg.data = " "
+ if arg_cnt < len(thing_list):
+ report_string += ","
+ pass
+ report_string += '}'
+
+ json_out = json.loads(report_string)
+
+ return json_out
+
+def report_json_construct_events(event_list):
+ # deal events and add your real value
+ status = 1
+ message = "test"
+ voltage = 20.0
+ name = "memory"
+ error_code = 0
+ timestamp = int(round(time.time() * 1000))
+
+ format_string = '"%s":"%s",'
+ format_int = '"%s":%d,'
+ events = []
+ for event in event_list:
+ string = '{'
+ string += format_string % ("eventId", event.event_name)
+ string += format_string % ("type", event.type)
+ string += format_int % ("timestamp", timestamp)
+ string += '"params":{'
+ for prop in event.events_prop:
+ if (prop.type == "int" or prop.type == "float"
+ or prop.type == "bool" or prop.type == "enum"):
+ if prop.key == "status":
+ string += format_int % (prop.key, status)
+ elif prop.key == "voltage":
+ string += format_int % (prop.key, voltage)
+ elif prop.key == "error_code":
+ string += format_int % (prop.key, error_code)
+ elif prop.type == "string":
+ if prop.key == "message":
+ string += format_string % (prop.key, message)
+ elif prop.key == "name":
+ string += format_string % (prop.key, name)
+
+ string = string[:len(string) - 1]
+ string += "}}"
+ events.append(json.loads(string))
+
+ json_out = '{"events":%s}' % json.dumps(events)
+
+ return json.loads(json_out)
+
+def wait_for_reply():
+ cnt = 0
+ global reply
+ while cnt < 3:
+ if reply is True:
+ reply = False
+ return 0
+ time.sleep(0.5)
+ cnt += 1
+ return -1
+
+def example_template():
+ global logger
+ qcloud = QcloudExplorer(device_file="explorer/sample/device_info.json", tls=True)
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+ logger.debug("\033[1;36m template test start...\033[0m")
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+
+ global product_id
+ global device_name
+ product_id = qcloud.getProductID()
+ device_name = qcloud.getDeviceName()
+
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m template test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ """template init"""
+ rc, mid = qcloud.templateInit(product_id, device_name, on_template_property,
+ on_template_action, on_template_event, on_template_service)
+ if rc != 0:
+ return False
+
+ qcloud.templateSetup(product_id, device_name, "explorer/sample/template/template_config.json")
+
+ """report sysinfo """
+ sys_info = {
+ "module_hardinfo": "ESP8266",
+ "module_softinfo": "V1.0",
+ "fw_ver": "3.1.4",
+ "imei": "11-22-33-44",
+ "lat": "22.546015",
+ "lon": "113.941125",
+ "device_label": {
+ "append_info": "your self define info"
+ }
+ }
+ rc, mid = qcloud.templateReportSysInfo(product_id, device_name, sys_info)
+ if rc != 0:
+ logger.error("report sysinfo error")
+ return False
+
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for report sysinfo reply timeout")
+ return False
+
+ """get status"""
+ rc, mid = qcloud.templateGetStatus(product_id, device_name)
+ if rc != 0:
+ logger.error("get status error")
+ return False
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for get status reply timeout")
+ return False
+
+ """report property"""
+ prop_list = qcloud.getPropertyList(product_id, device_name)
+ reports = report_json_construct_property(prop_list)
+ params_in = qcloud.templateJsonConstructReportArray(product_id, device_name, reports)
+ rc, mid = qcloud.templateReport(product_id, device_name, params_in)
+ if rc != 0:
+ logger.error("report property error")
+ return False
+
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for report property reply timeout")
+ return False
+
+ """events post"""
+ event_list = qcloud.getEventsList(product_id, device_name)
+ events = report_json_construct_events(event_list)
+ rc, mid = qcloud.templateEventPost(product_id, device_name, events)
+ if rc != 0:
+ logger.error("events post error")
+ return False
+
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for report events reply timeout")
+ return False
+
+ """report event"""
+ timestamp = int(round(time.time() * 1000))
+ event = {
+ "events": [
+ {
+ "eventId": "status_report",
+ "type": "info",
+ "timestamp": timestamp,
+ "params": {
+ "status":0,
+ "message":""
+ }
+ }
+ ]
+ }
+ rc, mid = qcloud.templateEventPost(product_id, device_name, event)
+ if rc != 0:
+ logger.error("report event error")
+ return False
+
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for report event reply timeout")
+ return False
+
+ # 阻塞测试控制台下发属性是否生效
+ # while True:
+ # time.sleep(3)
+ """clear control"""
+ qcloud.clearControl(product_id, device_name)
+
+ """template exit"""
+ qcloud.templateDeinit(product_id, device_name)
+ # qcloud.disconnect()
+
+ logger.debug("\033[1;36m template test success...\033[0m")
+ return True
+# example_template()
\ No newline at end of file
diff --git a/sample/template/template_config.json b/explorer/sample/template/template_config.json
similarity index 100%
rename from sample/template/template_config.json
rename to explorer/sample/template/template_config.json
diff --git a/sample/test.py b/explorer/sample/test.py
similarity index 85%
rename from sample/test.py
rename to explorer/sample/test.py
index f82e10e..af2196c 100644
--- a/sample/test.py
+++ b/explorer/sample/test.py
@@ -1,12 +1,11 @@
import unittest
-# from mqtt.example_mqtt import example_mqtt
from dynreg import example_dynreg as dynregtest
from gateway import example_gateway as gatewaytest
from mqtt import example_mqtt as mqtttest
from ota import example_ota as otatest
from template import example_template as templatetest
-from subscribe import example_subscribe as subscirbetest
+from httpAccess import example_http as httptest
class MyTestCase(unittest.TestCase):
@@ -32,6 +31,12 @@ def test_gateway(self):
self.assertEqual(ret, True)
pass
+ def test_http(self):
+ ret = httptest.example_http()
+ self.assertEqual(ret, True)
+ pass
+
+ @unittest.skip("skipping")
def test_ota(self):
ret = otatest.example_ota()
self.assertEqual(ret, True)
@@ -42,11 +47,6 @@ def test_template(self):
self.assertEqual(ret, True)
pass
- def test_subscribe(self):
- ret = subscirbetest.example_subscribe()
- self.assertEqual(ret, True)
- pass
-
if __name__ == '__main__':
unittest.main()
diff --git a/explorer/services/__init__.py b/explorer/services/__init__.py
new file mode 100644
index 0000000..20b6e13
--- /dev/null
+++ b/explorer/services/__init__.py
@@ -0,0 +1 @@
+name = "services"
diff --git a/explorer/services/gateway/__init__.py b/explorer/services/gateway/__init__.py
new file mode 100644
index 0000000..aa93a9c
--- /dev/null
+++ b/explorer/services/gateway/__init__.py
@@ -0,0 +1 @@
+name = "gateway"
diff --git a/explorer/services/gateway/gateway.py b/explorer/services/gateway/gateway.py
new file mode 100644
index 0000000..eec9aa3
--- /dev/null
+++ b/explorer/services/gateway/gateway.py
@@ -0,0 +1,12 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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.
\ No newline at end of file
diff --git a/explorer/services/template/__init__.py b/explorer/services/template/__init__.py
new file mode 100644
index 0000000..40edfae
--- /dev/null
+++ b/explorer/services/template/__init__.py
@@ -0,0 +1 @@
+name = "template"
diff --git a/explorer/services/template/template.py b/explorer/services/template/template.py
new file mode 100644
index 0000000..f125cde
--- /dev/null
+++ b/explorer/services/template/template.py
@@ -0,0 +1,414 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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 json
+# from explorer.providers.providers import Providers
+from hub.hub import QcloudHub
+from hub.utils.providers import TopicProvider
+
+class Template(object):
+ def __init__(self, device_file, tls, productId, deviceName, userdata=None, domain=None, useWebsocket=False, logger=None):
+ self.__logger = logger
+ # self.__provider = Providers(device_file, tls)
+ # self.__hub = self.__provider.hub
+ self.__provider = QcloudHub(device_file, userdata, tls, domain, useWebsocket)
+ self.__hub = self.__provider.hub
+
+ self.__topic = TopicProvider(productId, deviceName)
+ self._template_token_num = 0
+
+ self.__template_events_list = []
+ self.__template_action_list = []
+ self.__template_property_list = []
+ self.__template_setup_state = False
+
+ """
+ 保存用户注册的回调
+ """
+ self.__user_callback = {}
+
+ # data template reply
+ self.__replyAck = -1
+
+ # property结构
+ class template_property(object):
+ def __init__(self):
+ self.key = None
+ self.data = None
+ self.data_buff_len = 0
+ self.type = None
+
+ class template_action(object):
+ def __init__(self):
+ self.action_id = None
+ self.timestamp = 0
+ self.input_num = 0
+ self.output_num = 0
+ self.actions_input_prop = []
+ self.actions_output_prop = []
+
+ def action_input_append(self, prop):
+ self.actions_input_prop.append(prop)
+
+ def action_output_append(self, prop):
+ self.actions_output_prop.append(prop)
+
+ # event结构(sEvent)
+ class template_event(object):
+ def __init__(self):
+ self.event_name = None
+ self.type = None
+ self.timestamp = 0
+ self.eventDataNum = 0
+ self.events_prop = []
+
+ def event_append(self, prop):
+ self.events_prop.append(prop)
+
+ def __assert(self, param):
+ if param is None or len(param) == 0:
+ raise ValueError('Invalid param.')
+
+ def __build_empty_json(self, info_in, method_in):
+ client_token = info_in + "-" + str(self._template_token_num)
+ self._template_token_num += 1
+ if method_in is None or len(method_in) == 0:
+ json_out = {
+ "clientToken": client_token
+ }
+ else:
+ json_out = {
+ "method": method_in,
+ "clientToken": client_token
+ }
+ return json_out
+
+ def __build_action_reply(self, clientToken, response, replyPara):
+ json_out = None
+ json_out = {
+ "method": "action_reply",
+ "code": replyPara.code,
+ "clientToken": clientToken,
+ "status": replyPara.status_msg,
+ "response": response
+ }
+
+ return json_out
+
+ def __build_control_reply(self, token, replyPara):
+ json_out = None
+ if len(replyPara.status_msg) > 0:
+ json_out = {
+ "code": replyPara.code,
+ "clientToken": token,
+ "status": replyPara.status_msg
+ }
+ else:
+ json_out = {
+ "code": replyPara.code,
+ "clientToken": token
+ }
+ return json_out
+
+ # 构建系统信息上报的json消息
+ def __json_construct_sysinfo(self, id, info_in):
+ json_token = self.__build_empty_json(id, None)
+ client_token = json_token["clientToken"]
+ info_out = {
+ "method": "report_info",
+ "clientToken": client_token,
+ "params": info_in
+ }
+
+ return info_out
+
+ def __handle_reply(self, method, payload):
+ clientToken = payload["clientToken"]
+ replyAck = payload["code"]
+ if method == "get_status_reply":
+ if replyAck == 0:
+ # update client token
+ self.__topic.control_clientToken = clientToken
+ else:
+ self.__replyAck = replyAck
+ self.__logger.debug("replyAck:%d" % replyAck)
+ else:
+ self.__replyAck = replyAck
+ pass
+
+ def __handle_control(self, payload):
+ clientToken = payload["clientToken"]
+ self.__topic.control_clientToken = clientToken
+
+ def __handle_property(self, payload):
+ method = payload["method"]
+ if method == "control":
+ self.__handle_control(payload)
+ else:
+ self.__handle_reply(method, payload)
+
+ def get_events_list(self):
+ return self.__template_events_list
+
+ def get_action_list(self):
+ return self.__template_action_list
+
+ def get_property_list(self):
+ return self.__template_property_list
+
+ def handle_template(self, topic, qos, payload, userdata):
+ if topic == self.__topic.template_property_topic_sub:
+ # __handle_reply回调到用户,由用户调用clearContrl()
+ self.__handle_property(payload)
+
+ """
+ 回调用户数据模板topic对应回调
+ """
+ if self.__user_callback[topic] is not None:
+ self.__user_callback[topic](topic, qos, payload, userdata)
+ else:
+ self.__logger.error("no callback for topic %s" % topic)
+
+ def template_reset(self):
+ self._template_token_num = 0
+ self.__template_events_list.clear()
+ self.__template_action_list.clear()
+ self.__template_property_list.clear()
+ self.__template_setup_state = False
+ self.__replyAck = -1
+
+ def template_deinit(self):
+ # topic_list = []
+ # topic_list.append(self.__topic.template_property_topic_sub)
+ # topic_list.append(self.__topic.template_event_topic_sub)
+ # topic_list.append(self.__topic.template_action_topic_sub)
+ # topic_list.append(self.__topic.template_service_topic_sub)
+ # return self.__hub.unsubscribe(topic_list)
+
+ self.__hub.unsubscribe(self.__topic.template_property_topic_sub)
+ self.__hub.unsubscribe(self.__topic.template_event_topic_sub)
+ self.__hub.unsubscribe(self.__topic.template_action_topic_sub)
+ return self.__hub.unsubscribe(self.__topic.template_service_topic_sub)
+
+ def template_init(self, callback, peopery_cb, action_cb, event_cb, service_cb):
+ property_topic = self.__topic.template_property_topic_sub
+ action_topic = self.__topic.template_action_topic_sub
+ event_topic = self.__topic.template_event_topic_sub
+ service_topic = self.__topic.template_service_topic_sub
+ self.__user_callback[property_topic] = peopery_cb
+ self.__user_callback[action_topic] = action_cb
+ self.__user_callback[event_topic] = event_cb
+ self.__user_callback[service_topic] = service_cb
+
+ topic_list = []
+ topic_list.append(property_topic)
+ topic_list.append(action_topic)
+ topic_list.append(event_topic)
+ topic_list.append(service_topic)
+
+ self.__hub.register_explorer_callback(topic_list, callback)
+
+ self.__hub.subscribe(property_topic, 0)
+ self.__hub.subscribe(action_topic, 0)
+ self.__hub.subscribe(event_topic, 0)
+
+ return self.__hub.subscribe(service_topic, 0)
+
+ def template_report(self, message):
+ self.__assert(message)
+ return self.__hub.publish(self.__topic.template_property_topic_pub, message, 0)
+
+ def template_get_status(self, id):
+ token = self.__build_empty_json(id, "get_status")
+ return self.__hub.publish(self.__topic.template_property_topic_pub, token, 0)
+
+ def template_action_reply(self, clientToken, response, replyPara):
+ self.__assert(clientToken)
+ self.__assert(response)
+ json_out = self.__build_action_reply(clientToken, response, replyPara)
+ return self.__hub.publish(self.__topic.template_action_topic_pub, json_out, 0)
+
+ # IOT_Template_ClearControl
+ def template_clear_control(self):
+ clientToken = self.__topic.control_clientToken
+
+ message = {
+ "method": "clear_control",
+ "clientToken": clientToken
+ }
+ return self.__hub.publish(self.__topic.template_property_topic_pub, message, 0)
+
+ def template_control_reply(self, replyPara):
+ json_out = self.__build_control_reply(self.__topic.control_clientToken, replyPara)
+ return self.__hub.publish(self.__topic.template_property_topic_pub, json_out, 0)
+
+ def template_report_sys_info(self, pid, sysInfo):
+ self.__assert(sysInfo)
+
+ json_out = self.__json_construct_sysinfo(pid, sysInfo)
+ return self.__hub.publish(self.__topic.template_property_topic_pub, json_out, 0)
+
+ def template_json_construct_report_array(self, pid, payload):
+ self.__assert(pid)
+ self.__assert(payload)
+
+ json_token = self.__build_empty_json(pid, None)
+ client_token = json_token["clientToken"]
+ json_out = {
+ "method": "report",
+ "clientToken": client_token,
+ "params": payload
+ }
+
+ return json_out
+
+ def template_event_post(self, pid, message):
+ self.__assert(pid)
+ self.__assert(message)
+
+ json_token = self.__build_empty_json(pid, None)
+ client_token = json_token["clientToken"]
+ events = message["events"]
+
+ method = "event_post"
+ if len(events) > 1:
+ method = "events_post"
+ json_out = {
+ "method": method,
+ "clientToken": client_token,
+ "events": events
+ }
+ return self.__hub.publish(self.__topic.template_event_topic_pub, json_out, 1)
+
+ def template_setup(self, config_file=None):
+ if self.__template_setup_state:
+ return 0
+ try:
+ with open(config_file, encoding='utf-8') as f:
+ cfg = json.load(f)
+ index = 0
+ while index < len(cfg["events"]):
+ # 解析events json
+ params = cfg["events"][index]["params"]
+
+ p_event = self.template_event()
+
+ p_event.event_name = cfg["events"][index]["id"]
+ p_event.type = cfg["events"][index]["type"]
+ p_event.timestamp = 0
+ p_event.eventDataNum = len(params)
+
+ i = 0
+ while i < p_event.eventDataNum:
+ event_prop = self.template_property()
+ event_prop.key = params[i]["id"]
+ event_prop.type = params[i]["define"]["type"]
+
+ if event_prop.type == "int" or event_prop.type == "bool":
+ event_prop.data = 0
+ elif event_prop.type == "float":
+ event_prop.data = 0.0
+ elif event_prop.type == "string":
+ event_prop.data = ''
+ else:
+ self.__logger.error("type not support")
+ event_prop.data = None
+
+ p_event.event_append(event_prop)
+ i += 1
+ pass
+
+ self.__template_events_list.append(p_event)
+ index += 1
+
+ index = 0
+ while index < len(cfg["actions"]):
+ # 解析actions json
+ inputs = cfg["actions"][index]["input"]
+ outputs = cfg["actions"][index]["output"]
+
+ p_action = self.template_action()
+ p_action.action_id = cfg["actions"][index]["id"]
+ p_action.input_num = len(inputs)
+ p_action.output_num = len(outputs)
+ p_action.timestamp = 0
+
+ i = 0
+ while i < p_action.input_num:
+ action_prop = self.template_property()
+ action_prop.key = inputs[i]["id"]
+ action_prop.type = inputs[i]["define"]["type"]
+
+ if action_prop.type == "int" or action_prop.type == "bool":
+ action_prop.data = 0
+ elif action_prop.type == "float":
+ action_prop.data = 0.0
+ elif action_prop.type == "string":
+ action_prop.data = ''
+ else:
+ self.__logger.error("type not support")
+ action_prop.data = None
+ p_action.action_input_append(action_prop)
+ i += 1
+ pass
+
+ i = 0
+ while i < p_action.output_num:
+ action_prop = self.template_property()
+ action_prop.key = outputs[i]["id"]
+ action_prop.type = outputs[i]["define"]["type"]
+
+ if action_prop.type == "int" or action_prop.type == "bool":
+ action_prop.data = 0
+ elif action_prop.type == "float":
+ action_prop.data = 0.0
+ elif action_prop.type == "string":
+ action_prop.data = ''
+ else:
+ self.__logger.error("type not support")
+ action_prop.data = None
+ p_action.action_output_append(action_prop)
+ i += 1
+ pass
+
+ self.__template_action_list.append(p_action)
+ index += 1
+ pass
+
+ index = 0
+ while index < len(cfg["properties"]):
+ # 解析properties json
+ p_prop = self.template_property()
+ p_prop.key = cfg["properties"][index]["id"]
+ p_prop.type = cfg["properties"][index]["define"]["type"]
+
+ if p_prop.type == "int" or p_prop.type == "bool" or p_prop.type == "enum":
+ p_prop.data = 0
+ elif p_prop.type == "float":
+ p_prop.data = 0.0
+ elif p_prop.type == "string":
+ p_prop.data = ''
+ else:
+ self.__logger.error("type not support")
+ p_prop.data = None
+
+ self.__template_property_list.append(p_prop)
+ index += 1
+ pass
+
+ except Exception as e:
+ self.__logger.error("config file open error:" + str(e))
+ return 2
+ self.__template_setup_state = True
+ return 0
+
\ No newline at end of file
diff --git a/hub/README.md b/hub/README.md
index 1964a31..1ac7cbd 100644
--- a/hub/README.md
+++ b/hub/README.md
@@ -1,3 +1,5 @@
+简体中文 | [English](doc/en)
+
* [腾讯云物联网通信设备端 IoT Hub Python-SDK](#腾讯云物联网通信设备端-IoT-Hub-Python-SDK)
* [前提条件](#前提条件)
* [工程配置](#工程配置)
@@ -38,4 +40,6 @@ SDK支持远程pip依赖,以及本地源码依赖,详细接入步骤请参
* ~~[网关设备拓扑关系 待更新](doc/网关设备拓扑关系.md)~~
* ~~[设备互通 待更新](doc/设备互通.md)~~
* [设备影子](doc/设备影子.md)
-* ~~[设备状态上报与状态设置 待更新](doc/设备状态上报与状态设置.md)~~
\ No newline at end of file
+* [三方CA证书接入](doc/公有云接入三方CA证书.md)
+* ~~[设备状态上报与状态设置 待更新](doc/设备状态上报与状态设置.md)~~
+* [常见问题](doc/常见问题.md)
\ No newline at end of file
diff --git "a/hub/doc/RRPC\345\220\214\346\255\245\351\200\232\344\277\241.md" "b/hub/doc/RRPC\345\220\214\346\255\245\351\200\232\344\277\241.md"
new file mode 100644
index 0000000..9f5ecf4
--- /dev/null
+++ "b/hub/doc/RRPC\345\220\214\346\255\245\351\200\232\344\277\241.md"
@@ -0,0 +1,75 @@
+* [RRPC同步通信](#RRPC同步通信)
+ * [RRPC功能简介](#RRPC功能简介)
+ * [运行示例](#运行示例)
+ * [填写认证连接设备的参数](#填写认证连接设备的参数)
+ * [初始化RRPC](#初始化RRPC)
+ * [接收RRPC请求](#接收RRPC请求)
+ * [响应请求](#响应请求)
+
+# RRPC同步通信
+## RRPC功能简介
+MQTT协议是基于发布/订阅的异步通信模式,服务器无法控制设备同步返回结果。为解决此问题,物联网通信平台实现了一套同步通信机制,称为RRPC(Revert RPC)。
+即由服务器向客户端发起请求,客户端即时响应并同步给出答复。
+* 订阅消息Topic: `$rrpc/rxd/{productID}/{deviceName}/+`
+* 请求消息Topic: `$rrpc/rxd/{productID}/{deviceName}/{processID}`
+* 应答消息Topic: `$rrpc/txd/{productID}/{deviceName}/{processID}`
+* processID : 服务器生成的唯一的消息ID,用来标识不同RRPC消息。可以通过RRPC应答消息中携带的`processID`找到对应的RRPC请求消息。
+
+原理如下图:
+
+* **RRPC请求4s超时**,即4s内设备端没有应答就认为请求超时。
+
+## 运行示例
+运行 [RrpcSample.py](../../hub/sample/rrpc/example_rrpc.py) 示例程序,可以体验同步通信过程。
+
+#### 填写认证连接设备的参数
+将在控制台创建设备时生成的设备信息填写到 [device_info.json](../../hub/sample/device_info.json)中,以密钥认证方式为例,主要关注`auth_mode`,`productId`,`deviceName`,`deviceSecret`字段,示例如下:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"test01",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+#### 初始化RRPC
+示例程序运行后调用RRPC初始化接口进行相关Topic订阅,之后等待RRPC消息.
+```
+2021-07-20 17:43:30,612.612 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 17:43:30,622.622 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 17:43:31,111.111 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 17:43:31,164.164 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 17:43:31,164.164 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 17:43:31,613.613 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$rrpc/rxd/xxx/test01/+', 0)]
+2021-07-20 17:43:31,614.614 [log.py:35] - DEBUG - subscribe success topic:$rrpc/rxd/xxx/test01/+
+2021-07-20 17:43:31,614.614 [log.py:35] - DEBUG - rrpc while...
+2021-07-20 17:43:31,677.677 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 17:43:31,678.678 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-20 17:43:32,616.616 [log.py:35] - DEBUG - rrpc while...
+2021-07-20 17:43:33,618.618 [log.py:35] - DEBUG - rrpc while...
+2021-07-20 17:43:34,620.620 [log.py:35] - DEBUG - rrpc while...
+```
+
+#### 接收RRPC请求
+调用云API `PublishRRPCMessage` 发送RRPC请求消息.
+打开腾讯云[API控制台](https://console.cloud.tencent.com/api/explorer?Product=iotcloud&Version=2018-06-14&Action=PublishRRPCMessage&SignVersion=),填写个人密钥和设备参数信息,选择在线调用并发送请求.
+
+设备端成功接收到RRPC请求消息,`process id`为41440。
+```
+2021-07-20 19:07:31,220.220 [log.py:35] - DEBUG - rrpc while...
+2021-07-20 19:07:31,330.330 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$rrpc/rxd/xxx/test01/41440', ... (23 bytes)
+2021-07-20 19:07:31,330.330 [log.py:35] - DEBUG - on_rrpc_cb:payload:{'payload': 'rrpc test'},userdata:None
+```
+
+#### 响应请求
+设备收到RRPC请求后需要及时作出响应.
+```
+2021-07-20 19:07:31,330.330 [log.py:43] - INFO - [rrpc reply] ok
+2021-07-20 19:07:31,331.331 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$rrpc/txd/xxx/test01/41440'', ... (4 bytes)
+2021-07-20 19:07:31,331.331 [log.py:35] - DEBUG - publish success
+2021-07-20 19:07:31,331.331 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+```
+示例响应云端`ok`字符串,此时观察云API可以看到云端已经成功收到设备端的响应消息.
\ No newline at end of file
diff --git "a/hub/doc/SDK\346\216\245\345\205\245\350\257\264\346\230\216.md" "b/hub/doc/SDK\346\216\245\345\205\245\350\257\264\346\230\216.md"
index e660907..b0170ce 100755
--- "a/hub/doc/SDK\346\216\245\345\205\245\350\257\264\346\230\216.md"
+++ "b/hub/doc/SDK\346\216\245\345\205\245\350\257\264\346\230\216.md"
@@ -7,7 +7,7 @@
- Install from pip
```
- pip install TIotHubSDK
+ pip install tencent-iot-device
```
diff --git "a/hub/doc/SDK\346\216\245\345\217\243\350\257\264\346\230\216.md" "b/hub/doc/SDK\346\216\245\345\217\243\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..c846017
--- /dev/null
+++ "b/hub/doc/SDK\346\216\245\345\217\243\350\257\264\346\230\216.md"
@@ -0,0 +1,87 @@
+* [API接口说明](#API接口说明)
+ * [MQTT接口](#MQTT接口)
+ * [网关接口](#网关接口)
+ * [设备影子接口](#设备影子接口)
+ * [RRPC接口](#RRPC接口)
+ * [广播接口](#广播接口)
+ * [动态注册接口](#动态注册接口)
+ * [OTA接口](#OTA接口)
+ * [LOG接口](#LOG接口)
+
+# API接口说明
+## MQTT接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| connect | MQTT连接 |
+| disconnect | 断开MQTT连接 |
+| subscribe | MQTT订阅 |
+| unsubscribe | MQTT取消订阅 |
+| publish | MQTT发布消息 |
+| registerMqttCallback | 注册MQTT回调函数 |
+| registerUserCallback | 注册用户回调函数 |
+| isMqttConnected | MQTT是否正常连接 |
+| getConnectState | 获取MQTT连接状态 |
+| setReconnectInterval | 设置MQTT重连尝试间隔 |
+| setMessageTimout | 设置消息发送超时时间 |
+| setKeepaliveInterval | 设置MQTT保活间隔 |
+
+## 网关接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| gatewayInit | 网关初始化 |
+| isSubdevStatusOnline | 判断子设备是否在线 |
+| updateSubdevStatus | 更新子设备在线状态 |
+| gatewaySubdevGetConfigList | 获取配置文件中子设备列表 |
+| gatewaySubdevOnline | 代理子设备上线 |
+| gatewaySubdevOffline | 代理子设备下线 |
+| gatewaySubdevBind | 绑定子设备 |
+| gatewaySubdevUnbind | 解绑子设备 |
+| gatewaySubdevGetBindList | 获取绑定的子设备列表 |
+| gatewaySubdevSubscribe | 子设备订阅 |
+
+## 设备影子接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| shadowInit | 设备影子初始化 |
+| getShadow | 获取设备影子 |
+| shadowJsonConstructDesireAllNull | 构建json结构 |
+| shadowUpdate | 更新设备影子 |
+| shadowJsonConstructReport | 构建上报的json结构 |
+
+## RRPC接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| rrpcInit | RRPC初始化 |
+| rrpcReply | 消息回复 |
+
+## 广播接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| broadcastInit | 广播初始化 |
+
+## 动态注册接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| dynregDevice | 获取设备动态注册的信息 |
+
+## OTA接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| otaInit | OTA初始化 |
+| otaIsFetching | 判断是否正在下载 |
+| otaIsFetchFinished | 判断是否下载完成 |
+| otaReportUpgradeSuccess | 上报升级成功消息 |
+| otaReportUpgradeFail | 上报升级失败消息 |
+| otaIoctlNumber | 获取下载固件大小等int类型信息 |
+| otaIoctlString | 获取下载固件md5等string类型信息 |
+| otaResetMd5 | 重置md5信息 |
+| otaMd5Update | 更新md5信息 |
+| httpInit | 初始化http |
+| otaReportVersion | 上报当前固件版本信息 |
+| otaDownloadStart | 开始固件下载 |
+| otaFetchYield | 读取固件 |
+
+## LOG接口
+| 接口名称 | 接口描述 |
+| :-: | :-: |
+| logInit | 日志初始化 |
\ No newline at end of file
diff --git "a/hub/doc/en/PRELIM__RRPC\345\220\214\346\255\245\351\200\232\344\277\241_EN-US.md" "b/hub/doc/en/PRELIM__RRPC\345\220\214\346\255\245\351\200\232\344\277\241_EN-US.md"
new file mode 100644
index 0000000..2e0e97a
--- /dev/null
+++ "b/hub/doc/en/PRELIM__RRPC\345\220\214\346\255\245\351\200\232\344\277\241_EN-US.md"
@@ -0,0 +1,75 @@
+* [RRPC Sync Communication](#RRPC-Sync-Communication)
+ * [Overview](#Overview)
+ * [Running demo](#Running-demo)
+ * [Entering parameters for authenticating device for connection](#Entering-parameters-for-authenticating-device-for-connection)
+ * [Initializing RRPC](#Initializing-RRPC)
+ * [Receiving RRPC request](#Receiving-RRPC-request)
+ * [Responding to request](#Responding-to-request)
+
+# RRPC Sync Communication
+## Overview
+Because of the async communication mode of the MQTT protocol based on the publish/subscribe pattern, the server cannot control the device to synchronously return the result. To solve this problem, IoT Hub implements a sync communication mechanism called Revert RPC (RRPC).
+That is, the server initiates a request to the client, and the client responds immediately and replies synchronously.
+* Subscription message topic: `$rrpc/rxd/{productID}/{deviceName}/+`
+* Request message topic: `$rrpc/rxd/{productID}/{deviceName}/{processID}`
+* Response message topic: `$rrpc/txd/{productID}/{deviceName}/{processID}`
+* processID: unique message ID generated by the server to identify different RRPC messages. The corresponding RRPC request message can be found through the `processID` carried in the RRPC response message.
+
+The process is as shown below:
+
+* **RRPC requests time out in 4s**, that is, if the device doesn't respond within 4s, the request will be considered to have timed out.
+
+## Running demo
+You can run the [RrpcSample.py](../../hub/sample/rrpc/example_rrpc.py) demo to try out the sync communication process.
+
+#### Entering parameters for authenticating device for connection
+Enter the information of the device created in the console in [device_info.json](../../hub/sample/device_info.json), such as the `auth_mode`, `productId`, `deviceName`, and `deviceSecret` fields of a key-authenticated device, as shown below:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"test01",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+#### Initializing RRPC
+After running the demo, call the RRPC initialization API to subscribe to the relevant topic and then wait for the RRPC message.
+```
+2021-07-20 17:43:30,612.612 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 17:43:30,622.622 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 17:43:31,111.111 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 17:43:31,164.164 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 17:43:31,164.164 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 17:43:31,613.613 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$rrpc/rxd/xxx/test01/+', 0)]
+2021-07-20 17:43:31,614.614 [log.py:35] - DEBUG - subscribe success topic:$rrpc/rxd/xxx/test01/+
+2021-07-20 17:43:31,614.614 [log.py:35] - DEBUG - rrpc while...
+2021-07-20 17:43:31,677.677 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 17:43:31,678.678 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-20 17:43:32,616.616 [log.py:35] - DEBUG - rrpc while...
+2021-07-20 17:43:33,618.618 [log.py:35] - DEBUG - rrpc while...
+2021-07-20 17:43:34,620.620 [log.py:35] - DEBUG - rrpc while...
+```
+
+#### Receiving RRPC request
+Call TencentCloud API `PublishRRPCMessage` to send an RRPC request message.
+Go to [API Explorer](https://console.cloud.tencent.com/api/explorer?Product=iotcloud&Version=2018-06-14&Action=PublishRRPCMessage&SignVersion=), enter the personal key and device parameter information, select **Online Call**, and send the request.
+
+The device successfully receives the RRPC request message with the `process id` of `41440`.
+```
+2021-07-20 19:07:31,220.220 [log.py:35] - DEBUG - rrpc while...
+2021-07-20 19:07:31,330.330 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$rrpc/rxd/xxx/test01/41440', ... (23 bytes)
+2021-07-20 19:07:31,330.330 [log.py:35] - DEBUG - on_rrpc_cb:payload:{'payload': 'rrpc test'},userdata:None
+```
+
+#### Responding to request
+The device needs to respond promptly after receiving the RRPC request.
+```
+2021-07-20 19:07:31,330.330 [log.py:43] - INFO - [rrpc reply] ok
+2021-07-20 19:07:31,331.331 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$rrpc/txd/xxx/test01/41440'', ... (4 bytes)
+2021-07-20 19:07:31,331.331 [log.py:35] - DEBUG - publish success
+2021-07-20 19:07:31,331.331 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+```
+The demo responds with the `ok` string in the cloud. At this point, as can be seen from the TencentCloud API, the cloud successfully receives the response message from the device.
\ No newline at end of file
diff --git "a/hub/doc/en/PRELIM__SDK\346\216\245\345\205\245\350\257\264\346\230\216_EN-US.md" "b/hub/doc/en/PRELIM__SDK\346\216\245\345\205\245\350\257\264\346\230\216_EN-US.md"
new file mode 100644
index 0000000..29241e2
--- /dev/null
+++ "b/hub/doc/en/PRELIM__SDK\346\216\245\345\205\245\350\257\264\346\230\216_EN-US.md"
@@ -0,0 +1,28 @@
+ * [How to Import](#How-to-Import)
+
+## How to Import
+
+**How to import**
+
+- Install from pip
+
+ ```
+ pip install tencent-iot-device
+ ```
+
+
+- Build from source
+
+ ```
+ git clone https://github.com/tencentyun/iot-device-python.git
+ cd iot-device-python
+ python setup.py install
+ ```
+
+
+**SDK for Python source code**
+
+- If you want to develop a project through code integration, you can download the SDK for Python source code from [GitHub](../).
+- If you want to develop a project by importing the source code, you can refer to [Latest release](https://github.com/tencentyun/iot-device-python/releases) for the specific version number.
+
+
diff --git "a/hub/doc/en/PRELIM__SDK\346\216\245\345\217\243\350\257\264\346\230\216_EN-US.md" "b/hub/doc/en/PRELIM__SDK\346\216\245\345\217\243\350\257\264\346\230\216_EN-US.md"
new file mode 100644
index 0000000..ad176b3
--- /dev/null
+++ "b/hub/doc/en/PRELIM__SDK\346\216\245\345\217\243\350\257\264\346\230\216_EN-US.md"
@@ -0,0 +1,87 @@
+* [API Description](#API-Description)
+ * [MQTT APIs](#MQTT-APIs)
+ * [Gateway APIs](#Gateway-APIs)
+ * [Device shadow APIs](#Device-shadow-APIs)
+ * [RRPC APIs](#RRPC-APIs)
+ * [Broadcast APIs](#Broadcast-APIs)
+ * [Dynamic registration APIs](#Dynamic-registration-APIs)
+ * [OTA APIs](#OTA-APIs)
+ * [Log APIs](#Log-APIs)
+
+# API Description
+## MQTT APIs
+| API | Description |
+| :-: | :-: |
+| connect | Establishes MQTT connection |
+| disconnect | Closes MQTT connection |
+| subscribe | Subscribes to MQTT |
+| unsubscribe | Unsubscribes from MQTT |
+| publish | Publishes message over MQTT |
+| registerMqttCallback | Registers MQTT callback function |
+| registerUserCallback | Registers user callback function |
+| isMqttConnected | Checks whether MQTT is normally connected |
+| getConnectState | Gets MQTT connection status |
+| setReconnectInterval | Sets MQTT reconnection attempt interval |
+| setMessageTimout | Sets message sending timeout period |
+| setKeepaliveInterval | Sets MQTT keepalive interval |
+
+## Gateway APIs
+| API | Description |
+| :-: | :-: |
+| gatewayInit | Initializes gateway |
+| isSubdevStatusOnline | Determines whether subdevice is online |
+| updateSubdevStatus | Updates subdevice's online status |
+| gatewaySubdevGetConfigList | Gets subdevice list from configuration file |
+| gatewaySubdevOnline | Proxies subdevice connection |
+| gatewaySubdevOffline | Proxies subdevice disconnection |
+| gatewaySubdevBind | Binds subdevice |
+| gatewaySubdevUnbind | Unbinds subdevice |
+| gatewaySubdevGetBindList | Gets the list of bound subdevices |
+| gatewaySubdevSubscribe | Proxies subdevice subscription |
+
+## Device shadow APIs
+| API | Description |
+| :-: | :-: |
+| shadowInit | Initializes device shadow |
+| getShadow | Gets device shadow |
+| shadowJsonConstructDesireAllNull | Constructs JSON structure |
+| shadowUpdate | Updates device shadow |
+| shadowJsonConstructReport | Constructs JSON structure for reporting |
+
+## RRPC APIs
+| API | Description |
+| :-: | :-: |
+| rrpcInit | Initializes RRPC |
+| rrpcReply | Replies to message |
+
+## Broadcast APIs
+| API | Description |
+| :-: | :-: |
+| broadcastInit | Initializes broadcast |
+
+## Dynamic registration APIs
+| API | Description |
+| :-: | :-: |
+| dynregDevice | Gets the dynamic registration information of device |
+
+## OTA APIs
+| API | Description |
+| :-: | :-: |
+| otaInit | Initializes OTA |
+| otaIsFetching | Determines whether the download is in progress |
+| otaIsFetchFinished | Determines whether the download is completed |
+| otaReportUpgradeSuccess | Reports update success message |
+| otaReportUpgradeFail | Reports update failure message |
+| otaIoctlNumber | Gets the information of the downloaded firmware in `int` type, such as the size |
+| otaIoctlString | Gets the information of the downloaded firmware in `String` type, such as MD5 |
+| otaResetMd5 | Resets MD5 information |
+| otaMd5Update | Updates MD5 information |
+| httpInit | Initializes HTTP |
+| otaReportVersion | Reports the information of current firmware version |
+| otaDownloadStart | Starts firmware download |
+| otaFetchYield | Reads firmware |
+
+## Log APIs
+| API | Description |
+| :-: | :-: |
+| logInit | Initializes log |
\ No newline at end of file
diff --git "a/hub/doc/en/PRELIM__\345\212\250\346\200\201\346\263\250\345\206\214_EN-US.md" "b/hub/doc/en/PRELIM__\345\212\250\346\200\201\346\263\250\345\206\214_EN-US.md"
new file mode 100644
index 0000000..25d8385
--- /dev/null
+++ "b/hub/doc/en/PRELIM__\345\212\250\346\200\201\346\263\250\345\206\214_EN-US.md"
@@ -0,0 +1,57 @@
+* [Dynamic Registration Authentication](#Dynamic-Registration-Authentication)
+ * [Overview](#Overview)
+ * [Enabling dynamic registration in console](#Enabling-dynamic-registration-in-console)
+ * [Running demo for dynamic registration](#Running-demo-for-dynamic-registration)
+
+# Dynamic Registration Authentication
+## Overview
+This feature assigns a unified key to all devices under the same product, and a device gets a device certificate/key through a registration request for authentication. You can burn the same configuration information for the same batch of devices. For more information on the dynamic registration request, please see [Dynamic Registration API Description](https://cloud.tencent.com/document/product/1081/47612).
+
+If you enable automatic device creation in the console, you don't need to create devices in advance, but you must guarantee that device names are unique under the same product ID, which are generally unique device identifiers (such as MAC address). This method is more flexible. If you disable it in the console, you must create devices in advance and enter the same device names during dynamic registration, which is more secure but less convenient.
+
+## Enabling dynamic registration in console
+To use the dynamic registration feature, you need to enable it when creating a product in the console and save the `productSecret` information of the product. The settings in the console are as shown below:
+
+
+## Running demo for dynamic registration
+Before running the demo, you need to enter the product information obtained in the console in the [device_info.json](../../hub/sample/device_info.json) file, with the device name to be generated in the `deviceName` field, `YOUR_DEVICE_SECRET` in the `deviceSecret` field, and the `productSecret` information generated during product creation in the console in the `productSecret` field.
+
+You can run [DynregSample.py](../../hub/sample/dynreg/example_dynreg.py) to call the `dynregDevice()` API for dynamic registration authentication. After the dynamic registration callback gets the key or certificate information of the corresponding device, it will be returned through the returned value of the API. Below is the sample code:
+
+```
+from hub.hub import QcloudHub
+
+qcloud = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+
+ret, msg = qcloud.dynregDevice()
+if ret == 0:
+ logger.debug('dynamic register success, psk: {}'.format(msg))
+else:
+ logger.error('dynamic register fail, msg: {}'.format(msg))
+```
+
+The following is the log of successful authentication for dynamic device registration.
+```
+dynamic register test success, psk: xxxxxxx
+2021-07-15 15:00:17,500.500 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-15 15:00:17,500.500 [log.py:35] - DEBUG - connect_async...8883
+2021-07-15 15:00:18,185.185 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxxxxx'
+2021-07-15 15:00:18,284.284 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-15 15:00:18,285.285 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-15 15:00:18,502.502 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxx/xxx', 0)]
+2021-07-15 15:00:18,503.503 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/xxx
+2021-07-15 15:00:18,503.503 [log.py:35] - DEBUG - pub topic:$sys/operation/xxx/xxx,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-15 15:00:18,504.504 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/xxx'', ... (37 bytes)
+2021-07-15 15:00:18,505.505 [log.py:35] - DEBUG - publish success
+2021-07-15 15:00:18,505.505 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-15 15:00:18,584.584 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-15 15:00:18,585.585 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-15 15:00:18,588.588 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/xxx/xxx', ... (82 bytes)
+2021-07-15 15:00:18,706.706 [log.py:35] - DEBUG - current time:2021-07-15 15:00:18
+2021-07-15 15:00:18,707.707 [log.py:35] - DEBUG - disconnect
+2021-07-15 15:00:18,707.707 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-15 15:00:18,709.709 [log.py:35] - DEBUG - LoopThread thread exit
+2021-07-15 15:00:18,710.710 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
+```
+As can be seen, the dynamic registration is successful, the device key is obtained, the MQTT is connected successfully, the NTP time is synced, and the `deviceSecret` field is updated in the [device_info.json](../../hub/sample/device_info.json) configuration file.
diff --git "a/hub/doc/en/PRELIM__\345\233\272\344\273\266\345\215\207\347\272\247_EN-US.md" "b/hub/doc/en/PRELIM__\345\233\272\344\273\266\345\215\207\347\272\247_EN-US.md"
new file mode 100644
index 0000000..2cf6abc
--- /dev/null
+++ "b/hub/doc/en/PRELIM__\345\233\272\344\273\266\345\215\207\347\272\247_EN-US.md"
@@ -0,0 +1,165 @@
+* [OTA Device Firmware Update](#OTA-Device-Firmware-Update)
+ * [Overview](#Overview)
+ * [Running demo](#Running-demo)
+ * [Entering parameters for authenticating device for connection](#Entering-parameters-for-authenticating-device-for-connection)
+ * [Uploading firmware](#Uploading-firmware)
+ * [Reporting version](#Reporting-version)
+ * [Starting download](#Starting-download)
+ * [Reporting progress](#Reporting-progress)
+ * [Using checkpoint restart](#Using-checkpoint-restart)
+ * [Reporting result](#Reporting-result)
+
+
+# OTA Device Firmware Update
+## Overview
+Device firmware update (aka OTA) is an important part of the IoT Hub service. When a device has new features available or vulnerabilities that need to be fixed, firmware update can be quickly performed for it through the OTA service. For more information, please see [Firmware Update](https://cloud.tencent.com/document/product/634/14673).
+
+To try out firmware update, you need to add a new firmware file in the console. For more information, please see [Device Firmware Update](https://cloud.tencent.com/document/product/634/14674).
+
+
+## Running demo
+You can run the [OtaSample.py](../../hub/sample/ota/example_ota.py) demo to try out processes such as reporting the current version, downloading the firmware, and reporting the OTA progress.
+
+#### Entering parameters for authenticating device for connection
+Enter the information of the device created in the console in [device_info.json](../../hub/sample/device_info.json), such as the `auth_mode`, `productId`, `deviceName`, and `deviceSecret` fields of a key-authenticated device, as shown below:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"xxx",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+#### Uploading firmware
+To update the firmware, you first need to upload it to the IoT Hub backend in the console as shown below:
+
+
+#### Reporting version
+As can be seen from the output log, the demo reports the current firmware version (0.1.0) and starts waiting for the update command from the cloud.
+```
+2021-07-19 10:54:26,800.800 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-19 10:54:26,800.800 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-19 10:54:28,159.159 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-19 10:54:28,384.384 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-19 10:54:28,385.385 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-19 10:54:28,803.803 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$ota/update/xxx/xxx', 1)]
+2021-07-19 10:54:28,803.803 [log.py:35] - DEBUG - subscribe success topic:$ota/update/xxx/txest1
+2021-07-19 10:54:28,932.932 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 10:54:28,932.932 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:1,userdata:None
+2021-07-19 10:54:29,004.004 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m2), 'b'$ota/report/xxx/xxx'', ... (58 bytes)
+2021-07-19 10:54:29,005.005 [log.py:35] - DEBUG - publish success
+2021-07-19 10:54:29,144.144 [client.py:2165] - DEBUG - Received PUBACK (Mid: 2)
+2021-07-19 10:54:29,145.145 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (86 bytes)
+2021-07-19 10:54:29,147.147 [log.py:35] - DEBUG - on_ota_report:payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'},userdata:None
+2021-07-19 10:54:30,006.006 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:31,008.008 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:32,010.010 [log.py:35] - DEBUG - wait for ota upgrade command
+```
+
+#### Starting download
+After receiving the firmware version reported by the device, select the target new firmware and run the update command. You can update the firmware in the console in the following three methods:
+
+1. Update all devices by firmware version number. The selected version number is the version number for update, and you can select multiple version numbers for update.
+
+2. Batch update specified devices by firmware version number. You can select multiple version numbers and devices for update.
+
+3. Batch update the devices in the template file by device name. You can ignore the version number for update and directly update the devices in the template file to the selected firmware.
+
+
+***For methods 1 and 2, if the currently running firmware of the devices to be updated is not uploaded to the console, you cannot select their version number for update. In this case, you can upload this firmware to the console or select method 3 for update.***
+
+As can be seen from the output log, after the cloud delivers the update command, the demo starts downloading the firmware over HTTPS.
+```
+2021-07-19 10:54:44,032.032 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:45,034.034 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:45,409.409 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (468 bytes)
+2021-07-19 10:54:46,036.036 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:46,036.036 [log.py:47] - ERROR - info file not exists
+2021-07-19 10:54:46,036.036 [log.py:47] - ERROR - local_size:0,local_ver:None,re_ver:1.0.0
+2021-07-19 10:54:47,052.052 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:54:47,052.052 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$ota/report/xxx/xxx'', ... (151 bytes)
+2021-07-19 10:54:47,053.053 [log.py:35] - DEBUG - publish success
+```
+
+#### Reporting progress
+After the firmware download starts, the download progress (in percentages) will be continuously reported until the download is completed as shown in the following log:
+```
+2021-07-19 10:55:08,066.066 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '13', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:08,066.066 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m24), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:08,066.066 [log.py:35] - DEBUG - publish success
+2021-07-19 10:55:09,083.083 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '14', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:09,084.084 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m25), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:09,084.084 [log.py:35] - DEBUG - publish success
+2021-07-19 10:55:10,002.002 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '15', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:10,002.002 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m26), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:10,002.002 [log.py:35] - DEBUG - publish success
+2021-07-19 10:55:11,216.216 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '16', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:11,216.216 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m27), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:11,217.217 [log.py:35] - DEBUG - publish success
+```
+
+#### Using checkpoint restart
+Firmware update supports checkpoint restart; that is, if the firmware download process is interrupted due to any issues, it will resume from where interrupted. The log is as shown below:
+```
+2021-07-19 10:55:26,142.142 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '28', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:26,142.142 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m42), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:26,143.143 [log.py:35] - DEBUG - publish success
+```
+The download is interrupted when the firmware download progress reaches 28%, and then the demo is restarted for update.
+```
+2021-07-19 10:55:27,797.797 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-19 10:55:27,797.797 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-19 10:55:28,581.581 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-19 10:55:28,680.680 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-19 10:55:28,681.681 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-19 10:55:28,799.799 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$ota/update/xxx/xxx', 1)]
+2021-07-19 10:55:28,800.800 [log.py:35] - DEBUG - subscribe success topic:$ota/update/xxx/xxx
+2021-07-19 10:55:28,892.892 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 10:55:28,892.892 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:1,userdata:None
+2021-07-19 10:55:29,002.002 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m2), 'b'$ota/report/xxx/xxx'', ... (58 bytes)
+2021-07-19 10:55:29,003.003 [log.py:35] - DEBUG - publish success
+2021-07-19 10:55:29,079.079 [client.py:2165] - DEBUG - Received PUBACK (Mid: 2)
+2021-07-19 10:55:29,096.096 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (86 bytes)
+2021-07-19 10:55:29,096.096 [log.py:35] - DEBUG - on_ota_report:payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'},userdata:None
+2021-07-19 10:55:29,118.118 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (472 bytes)
+2021-07-19 10:55:30,005.005 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:55:30,006.006 [log.py:47] - ERROR - local_size:5620000,local_ver:1.0.0,re_ver:1.0.0
+2021-07-19 10:55:30,029.029 [log.py:47] - ERROR - total read:5620000
+__ota_http_deinit do nothing
+2021-07-19 10:55:31,221.221 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '28', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:31,222.222 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:31,223.223 [log.py:35] - DEBUG - publish success
+2021-07-19 10:55:32,358.358 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '29', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:32,359.359 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m4), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:32,359.359 [log.py:35] - DEBUG - publish success
+```
+As can be seen from the log, after the demo is restarted, the firmware download process resumes from where interrupted until it is completed.
+
+#### Reporting result
+After the firmware is downloaded, it is burnt and the result is reported to the cloud.
+```
+2021-07-19 10:57:25,228.228 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '99', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:57:25,228.228 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m116), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:57:25,229.229 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:25,537.537 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '100', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:57:25,537.537 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m117), 'b'$ota/report/xxx/xxx'', ... (153 bytes)
+2021-07-19 10:57:25,537.537 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - The firmware download success
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - burning firmware...
+2021-07-19 10:57:25,538.538 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m118), 'b'$ota/report/xxx/xxx'', ... (128 bytes)
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:25,539.539 [log.py:35] - DEBUG - wait for ack...
+2021-07-19 10:57:25,641.641 [client.py:2165] - DEBUG - Received PUBACK (Mid: 118)
+2021-07-19 10:57:25,642.642 [log.py:35] - DEBUG - publish ack id 118
+2021-07-19 10:57:28,042.042 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m119), 'b'$ota/report/xxx/xxx'', ... (58 bytes)
+2021-07-19 10:57:28,043.043 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:28,128.128 [client.py:2165] - DEBUG - Received PUBACK (Mid: 119)
+2021-07-19 10:57:28,175.175 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (86 bytes)
+2021-07-19 10:57:28,175.175 [log.py:35] - DEBUG - on_ota_report:payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'},userdata:None
+2021-07-19 10:57:29,045.045 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:57:30,048.048 [log.py:35] - DEBUG - wait for ota upgrade command
+```
+As can be seen from the log, after the firmware is downloaded, the demo verifies the firmware first and then simulates firmware burning. After that, the demo reports the update success to the cloud and waits for it to respond to ensure that it receives the reported result. Then, the demo starts waiting for the next update.
\ No newline at end of file
diff --git "a/hub/doc/en/PRELIM__\345\237\272\344\272\216TCP\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245_EN-US.md" "b/hub/doc/en/PRELIM__\345\237\272\344\272\216TCP\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245_EN-US.md"
new file mode 100644
index 0000000..6963f0f
--- /dev/null
+++ "b/hub/doc/en/PRELIM__\345\237\272\344\272\216TCP\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245_EN-US.md"
@@ -0,0 +1,110 @@
+* [Getting Started](#Getting-Started)
+ * [Creating device in console](#Creating-device-in-console)
+ * [Running demo](#Running-demo)
+ * [Entering parameters for authenticating device for connection](#Entering-parameters-for-authenticating-device-for-connection)
+ * [Running demo for authenticated MQTT connection](#Running-demo-for-authenticated-MQTT-connection)
+ * [Subscribing to topic](#Subscribing-to-topic)
+ * [Publishing to topic](#Publishing-to-topic)
+ * [Unsubscribing from topic](#Unsubscribing-from-topic)
+ * [Running demo to close MQTT connection](#Running-demo-to-close-MQTT-connection)
+
+
+# Getting Started
+This document describes how to create devices in the IoT Hub console and quickly try out device connection to IoT Hub over the MQTT protocol for message sending/receiving on the SDK demo.
+
+## Creating device in console
+
+Before connecting devices to the SDK, you need to create products and devices in the console and get the product ID, device name, device certificate (for certificate authentication), device private key (for certificate authentication), and device key (for key authentication), which are required for authentication of the devices when you connect them to the cloud. For more information, please see [Device Connection Preparations](https://cloud.tencent.com/document/product/634/14442).
+
+After a product is created successfully in the console, it has three permissions by default:
+
+```
+${productId}/${deviceName}/control // Subscribe
+${productId}/${deviceName}/data // Subscribe and publish
+${productId}/${deviceName}/event // Publish
+```
+For more information on how to manipulate the topic permissions, please see [Permission List](https://cloud.tencent.com/document/product/634/14444).
+
+## Running demo
+
+### Entering parameters for authenticating device for connection
+Enter the information of the device created in the console in [device_info.json](../../hub/sample/device_info.json), such as the `auth_mode`, `productId`, `deviceName`, and `deviceSecret` fields of a key-authenticated device, as shown below:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"xxx",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+#### Running demo for authenticated MQTT connection
+You can run the [MqttSample.py](../../hub/sample/mqtt/example_mqtt.py) demo to try out processes such as establishing an MQTT connection, subscribing to a topic, sending/receiving a message, and closing the connection. The log of running the demo is as follows:
+```
+2021-07-16 10:38:45,940.940 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-16 10:38:45,940.940 [log.py:35] - DEBUG - connect_async...8883
+2021-07-16 10:38:46,366.366 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-16 10:38:46,451.451 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-16 10:38:46,452.452 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-16 10:38:46,942.942 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxx/xxx', 0)]
+2021-07-16 10:38:46,943.943 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/xxx
+2021-07-16 10:38:46,944.944 [log.py:35] - DEBUG - pub topic:$sys/operation/xxx/xxx,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-16 10:38:46,945.945 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/xxx'', ... (37 bytes)
+2021-07-16 10:38:46,947.947 [log.py:35] - DEBUG - publish success
+2021-07-16 10:38:46,947.947 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-16 10:38:47,026.026 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-16 10:38:47,027.027 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-16 10:38:47,159.159 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/xxx/xxx', ... (82 bytes)
+2021-07-16 10:38:47,349.349 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m3) [b'$sys/operation/result/xxx/xxx']
+2021-07-16 10:38:47,350.350 [log.py:35] - DEBUG - current time:2021-07-16 10:38:47
+2021-07-16 10:38:47,433.433 [client.py:2165] - DEBUG - Received UNSUBACK (Mid: 3)
+2021-07-16 10:38:47,434.434 [log.py:35] - DEBUG - on_unsubscribe:mid:3,userdata:None
+2021-07-16 10:38:48,352.352 [log.py:35] - DEBUG - disconnect
+2021-07-16 10:38:48,352.352 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-16 10:38:48,354.354 [log.py:35] - DEBUG - LoopThread thread exit
+2021-07-16 10:38:48,355.355 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
+```
+As can be seen from the output log, the demo is connected over MQTT successfully.
+```
+2021-07-16 10:38:45,940.940 [log.py:35] - DEBUG - connect_async...8883
+2021-07-16 10:38:46,366.366 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-16 10:38:46,451.451 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-16 10:38:46,452.452 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+```
+
+#### Subscribing to topic
+As can be seen from the output log, the demo successfully subscribes to the system topic `$sys/operation/result/${productID}/${deviceName}` over MQTT.
+```
+2021-07-16 10:38:46,942.942 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxx/xxx', 0)]
+2021-07-16 10:38:46,943.943 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/xxx
+2021-07-16 10:38:47,027.027 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+```
+
+#### Publishing to topic
+As can be seen from the output log, the demo publishes a message over MQTT successfully to the `$sys/operation/${productID}/${deviceName}` topic, and the server responds after receiving and processing the message.
+```
+2021-07-16 10:38:46,944.944 [log.py:35] - DEBUG - pub topic:$sys/operation/xxx/xxx,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-16 10:38:46,945.945 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/xxx'', ... (37 bytes)
+2021-07-16 10:38:46,947.947 [log.py:35] - DEBUG - publish success
+2021-07-16 10:38:46,947.947 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-16 10:38:47,350.350 [log.py:35] - DEBUG - current time:2021-07-16 10:38:47
+```
+
+#### Unsubscribing from topic
+As can be seen from the output log, the demo unsubscribes from the topic after completing message publishing and receiving.
+```
+2021-07-16 10:38:47,349.349 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m3) [b'$sys/operation/result/xxx/xxx']
+2021-07-16 10:38:47,433.433 [client.py:2165] - DEBUG - Received UNSUBACK (Mid: 3)
+2021-07-16 10:38:47,434.434 [log.py:35] - DEBUG - on_unsubscribe:mid:3,userdata:None
+```
+
+#### Running demo to close MQTT connection
+As can be seen from the output log, the demo disconnects from MQTT after completing all the tasks.
+```
+2021-07-16 10:38:48,352.352 [log.py:35] - DEBUG - disconnect
+2021-07-16 10:38:48,352.352 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-16 10:38:48,354.354 [log.py:35] - DEBUG - LoopThread thread exit
+2021-07-16 10:38:48,355.355 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
+```
\ No newline at end of file
diff --git "a/hub/doc/en/PRELIM__\345\237\272\344\272\216Websocket\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245_EN-US.md" "b/hub/doc/en/PRELIM__\345\237\272\344\272\216Websocket\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245_EN-US.md"
new file mode 100644
index 0000000..ab64ac4
--- /dev/null
+++ "b/hub/doc/en/PRELIM__\345\237\272\344\272\216Websocket\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245_EN-US.md"
@@ -0,0 +1,92 @@
+* [Device Connection Through MQTT over WebSocket](#Device-Connection-Through-MQTT-over-WebSocket)
+ * [Overview](#Overview)
+ * [Entering parameters for authenticating device for connection](#Entering-parameters-for-authenticating-device-for-connection)
+ * [Running demo](#Running-demo)
+ * [Connecting to MQTT over WebSocket](#Connecting-to-MQTT-over-WebSocket)
+ * [Publishing and subscribing through MQTT over WebSocket](#Publishing-and-subscribing-through-MQTT-over-WebSocket)
+ * [Disconnecting from MQTT over WebSocket](#Disconnecting-from-MQTT-over-WebSocket)
+
+# Device Connection Through MQTT over WebSocket
+### Overview
+The IoT Hub platform supports MQTT communication over WebSocket, so that devices can use the MQTT protocol for message transfer on the basis of the WebSocket protocol. For more information, please see [Device Connection Through MQTT over WebSocket](https://cloud.tencent.com/document/product/634/46347).
+
+### Entering parameters for authenticating device for connection
+Enter the information of the device created in the console in [device_info.json](../../hub/sample/device_info.json), such as the `auth_mode`, `productId`, `deviceName`, and `deviceSecret` fields of a key-authenticated device, as shown below:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"xxx",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+### Running demo
+Change the connection method in the [MqttSample.py](../../hub/sample/mqtt/example_mqtt.py) demo to WebSocket.
+```
+qcloud = QcloudHub(device_file="hub/sample/device_info.json", tls=True, useWebsocket=True)
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+```
+
+#### Running demo for authenticated MQTT connection
+You can run the [MqttSample.py](../../hub/sample/mqtt/example_mqtt.py) demo to try out processes such as establishing an MQTT connection, subscribing to a topic, sending/receiving a message, and closing the connection. The log of running the demo is as follows:
+```
+2021-07-16 15:13:41,394.394 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-16 15:13:41,394.394 [log.py:35] - DEBUG - connect_async (UJDZES2SR2.ap-guangzhou.iothub.tencentdevices.com:443)
+2021-07-16 15:13:41,875.875 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'UJDZES2SR2test1'
+2021-07-16 15:13:41,952.952 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-16 15:13:41,952.952 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-16 15:13:42,396.396 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/UJDZES2SR2/test1', 0)]
+2021-07-16 15:13:42,396.396 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/UJDZES2SR2/test1
+2021-07-16 15:13:42,397.397 [log.py:35] - DEBUG - pub topic:$sys/operation/UJDZES2SR2/test1,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-16 15:13:42,397.397 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/UJDZES2SR2/test1'', ... (37 bytes)
+2021-07-16 15:13:42,397.397 [log.py:35] - DEBUG - publish success
+2021-07-16 15:13:42,398.398 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-16 15:13:42,498.498 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-16 15:13:42,499.499 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-16 15:13:42,627.627 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/UJDZES2SR2/test1', ... (82 bytes)
+2021-07-16 15:13:42,799.799 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m3) [b'$sys/operation/result/UJDZES2SR2/test1']
+2021-07-16 15:13:42,800.800 [log.py:35] - DEBUG - current time:2021-07-16 15:13:42
+2021-07-16 15:13:42,872.872 [client.py:2165] - DEBUG - Received UNSUBACK (Mid: 3)
+2021-07-16 15:13:42,872.872 [log.py:35] - DEBUG - on_unsubscribe:mid:3,userdata:None
+2021-07-16 15:13:43,802.802 [log.py:35] - DEBUG - disconnect
+2021-07-16 15:13:43,802.802 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-16 15:13:43,803.803 [log.py:35] - DEBUG - LoopThread thread exit
+```
+As can be seen from the output log, the demo is connected over MQTT successfully, and the connection domain name and port are the domain name and port of WebSocket.
+```
+2021-07-16 15:13:41,394.394 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-16 15:13:41,394.394 [log.py:35] - DEBUG - connect_async (UJDZES2SR2.ap-guangzhou.iothub.tencentdevices.com:443)
+2021-07-16 15:13:41,875.875 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'UJDZES2SR2test1'
+2021-07-16 15:13:41,952.952 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-16 15:13:41,952.952 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+```
+
+#### Subscribing through MQTT over WebSocket
+As can be seen from the output log, the demo successfully subscribes to the system topic `$sys/operation/result/${productID}/${deviceName}` over MQTT.
+```
+2021-07-16 15:13:42,396.396 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/UJDZES2SR2/test1', 0)]
+2021-07-16 15:13:42,396.396 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/UJDZES2SR2/test1
+2021-07-16 15:13:42,498.498 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-16 15:13:42,499.499 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+```
+
+#### Publishing through MQTT over WebSocket
+As can be seen from the output log, the demo publishes a message over MQTT successfully to the `$sys/operation/${productID}/${deviceName}` topic, and the server responds after receiving and processing the message.
+```
+2021-07-16 15:13:42,397.397 [log.py:35] - DEBUG - pub topic:$sys/operation/UJDZES2SR2/test1,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-16 15:13:42,397.397 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/UJDZES2SR2/test1'', ... (37 bytes)
+2021-07-16 15:13:42,397.397 [log.py:35] - DEBUG - publish success
+2021-07-16 15:13:42,398.398 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-16 15:13:42,627.627 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/UJDZES2SR2/test1', ... (82 bytes)
+2021-07-16 15:13:42,800.800 [log.py:35] - DEBUG - current time:2021-07-16 15:13:42
+```
+
+#### Disconnecting from MQTT over WebSocket
+As can be seen from the output log, the demo disconnects from MQTT after completing all the tasks.
+```
+2021-07-16 15:13:43,802.802 [log.py:35] - DEBUG - disconnect
+2021-07-16 15:13:43,802.802 [client.py:2165] - DEBUG - Sending DISCONNECT
+```
\ No newline at end of file
diff --git "a/hub/doc/en/PRELIM__\345\271\277\346\222\255\351\200\232\344\277\241_EN-US.md" "b/hub/doc/en/PRELIM__\345\271\277\346\222\255\351\200\232\344\277\241_EN-US.md"
new file mode 100644
index 0000000..207ff0d
--- /dev/null
+++ "b/hub/doc/en/PRELIM__\345\271\277\346\222\255\351\200\232\344\277\241_EN-US.md"
@@ -0,0 +1,89 @@
+* [Broadcast Communication](#Broadcast-Communication)
+ * [Overview](#Overview)
+ * [Broadcast topic](#Broadcast-topic)
+ * [Running demo](#Running-demo)
+ * [Entering parameters for authenticating device for connection](#Entering-parameters-for-authenticating-device-for-connection)
+ * [Initializing broadcast](#Initializing-broadcast)
+ * [Receiving broadcast message](#Receiving-broadcast-message)
+
+# Broadcast Communication
+## Overview
+The IoT Hub platform provides a broadcast communication topic. The server can publish a broadcast message by calling the broadcast communication API, which can be received by online devices that have subscribed to the broadcast topic under the same product. For more information, please see [Broadcast Communication](https://cloud.tencent.com/document/product/634/47333).
+
+## Broadcast topic
+* The broadcast communication topic is `$broadcast/rxd/${ProductId}/${DeviceName}`, where `ProductId` and `DeviceName` represent the product ID and device name respectively.
+
+
+## Running demo
+You can run [BroadcastSample.py](../../hub/sample/broadcast/example_broadcast.py) to try out broadcast message receiving by a device.
+
+#### Entering parameters for authenticating device for connection
+To try out broadcast message receiving, you need to create two devices and enter the information of the device created in the console in [device_info.json](../../hub/sample/device_info.json), such as the `auth_mode`, `productId`, `deviceName`, and `deviceSecret` fields of a key-authenticated device, as shown below:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"test01",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+You can create two configuration files, enter the information of the two created devices respectively, and modify the demo by importing their corresponding configuration files. Below is the sample code:
+```
+# Replace "hub/sample/device_info.json" with the configuration files of the created devices
+qcloud = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+```
+
+#### Initializing broadcast
+After running the demo, call the broadcast initialization API to subscribe to the relevant topic and then wait for the broadcast message.
+The `test01` device is initialized and waits for the broadcast message.
+```
+2021-07-20 19:43:09,109.109 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 19:43:09,119.119 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 19:43:09,516.516 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 19:43:09,573.573 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 19:43:09,573.573 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 19:43:10,112.112 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$broadcast/rxd/xxx/test01', 0)]
+2021-07-20 19:43:10,113.113 [log.py:35] - DEBUG - subscribe success topic:$broadcast/rxd/xxx/test01
+2021-07-20 19:43:10,113.113 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:43:10,156.156 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 19:43:10,157.157 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-20 19:43:11,115.115 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:43:12,118.118 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:43:13,119.119 [log.py:35] - DEBUG - broadcast wait
+```
+The `dev01` device is initialized and waits for the broadcast message.
+```
+2021-07-20 19:47:14,510.510 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 19:47:14,511.511 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 19:47:15,099.099 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 19:47:15,160.160 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 19:47:15,161.161 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 19:47:15,512.512 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$broadcast/rxd/xxx/dev01', 0)]
+2021-07-20 19:47:15,514.514 [log.py:35] - DEBUG - subscribe success topic:$broadcast/rxd/xxx/dev01
+2021-07-20 19:47:15,514.514 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:47:15,561.561 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 19:47:15,561.561 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-20 19:47:16,516.516 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:47:17,518.518 [log.py:35] - DEBUG - broadcast wait
+```
+
+#### Receiving broadcast message
+Call TencentCloud API `PublishBroadcastMessage` to send a broadcast message
+Go to [API Explorer](https://console.cloud.tencent.com/api/explorer?Product=iotcloud&Version=2018-06-14&Action=PublishBroadcastMessage&SignVersion=), enter the personal key and device parameter information, select **Online Call**, and send the request.
+
+The `test01` device successfully receives the broadcast message.
+```
+2021-07-20 19:48:32,693.693 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:48:33,003.003 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$broadcast/rxd/xxx/test01', ... (28 bytes)
+2021-07-20 19:48:33,013.013 [log.py:35] - DEBUG - on_broadcast_cb:payload:{'payload': 'broadcast test'},userdata:None
+```
+
+The `dev01` device successfully receives the broadcast message.
+```
+2021-07-20 19:48:32,657.657 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:48:33,002.002 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$broadcast/rxd/xxx/dev01', ... (28 bytes)
+2021-07-20 19:48:33,013.013 [log.py:35] - DEBUG - on_broadcast_cb:payload:{'payload': 'broadcast test'},userdata:None
+```
\ No newline at end of file
diff --git "a/hub/doc/en/PRELIM__\347\275\221\345\205\263\345\212\237\350\203\275_EN-US.md" "b/hub/doc/en/PRELIM__\347\275\221\345\205\263\345\212\237\350\203\275_EN-US.md"
new file mode 100644
index 0000000..bedeac1
--- /dev/null
+++ "b/hub/doc/en/PRELIM__\347\275\221\345\205\263\345\212\237\350\203\275_EN-US.md"
@@ -0,0 +1,221 @@
+* [Gateway Feature](#Gateway-Feature)
+ * [Overview](#Overview)
+ * [Running demo](#Running-demo)
+ * [Entering parameters for authenticating device for connection](#Entering-parameters-for-authenticating-device-for-connection)
+ * [Proxying subdevice connection and disconnection](#Proxying-subdevice-connection-and-disconnection)
+ * [Binding and unbinding subdevice](#Binding-and-unbinding-subdevice)
+ * [Querying the list of bound subdevices](#Querying-the-list-of-bound-subdevices)
+ * [Updating subdevice firmware](#Updating-subdevice-firmware)
+
+# Gateway Feature
+## Overview
+In addition to the basic features of general products, a gateway product can also be bound to products that cannot directly access the internet and used to exchange data with IoT Hub on behalf of such products (i.e., subdevices). This document describes how to connect a gateway product to IoT Hub over the MQTT protocol for proxied subdevice connection/disconnection and message sending/receiving.
+
+To try out the gateway feature, you need to create a gateway product in the console and bind a subproduct and a subdevice. For more information, please see [Device Connection Preparations](https://cloud.tencent.com/document/product/634/14442) and [Gateway Product Connection](https://cloud.tencent.com/document/product/634/32740).
+
+## Running demo
+You can run the [GatewaySample.py](../../hub/sample/gateway/example_gateway.py) demo to try out processes such as proxied subdevice connection/disconnection through the gateway, subdevice binding/unbinding, and subdevice firmware update.
+
+#### Entering parameters for authenticating device for connection
+Enter the information of the device created in the console in [device_info.json](../../hub/sample/device_info.json), such as the `auth_mode`, `productId`, `deviceName`, and `deviceSecret` fields of a key-authenticated device as well as certain fields of a subdevice (`subDev`), as shown below:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"test01",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+"subDev":{
+ "subdev_num":2,
+ "subdev_list":
+ [
+ {"sub_productId": "xxxx", "sub_devName": "test1"},
+ {"sub_productId": "xxxx", "sub_devName": "dev1"}
+ ]
+}
+```
+
+#### Proxying subdevice connection and disconnection
+In the configuration file of the demo, two subdevices are bound to the gateway device, with their `device_name` being `test1` and `dev1` respectively.
+* Proxied subdevice connection through a gateway
+Offline subdevices can be connected through the gateway:
+```
+2021-07-19 14:58:18,394.394 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-19 14:58:18,403.403 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-19 14:58:19,585.585 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxtest01'
+2021-07-19 14:58:19,678.678 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-19 14:58:19,678.678 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-19 14:58:20,398.398 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$gateway/operation/result/xxx/test01', 0)]
+2021-07-19 14:58:20,399.399 [log.py:35] - DEBUG - subscribe success topic:$gateway/operation/result/xxx/test01
+2021-07-19 14:58:20,459.459 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 14:58:20,460.460 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+1
+2021-07-19 14:58:24,178.178 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$gateway/operation/xxx/test01'', ... (98 bytes)
+2021-07-19 14:58:24,178.178 [log.py:35] - DEBUG - publish success
+2021-07-19 14:58:24,178.178 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-19 14:58:24,291.291 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (102 bytes)
+2021-07-19 14:58:24,379.379 [log.py:35] - DEBUG - client:xxx/test1 online success
+2021-07-19 14:58:24,379.379 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'xxx/test1/data', 0)]
+2021-07-19 14:58:24,379.379 [log.py:35] - DEBUG - subscribe success topic:xxx/test1/data
+2021-07-19 14:58:24,380.380 [log.py:35] - DEBUG - gateway subdev subscribe success
+2021-07-19 14:58:24,380.380 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m4), 'b'xxx/test1/data'', ... (36 bytes)
+2021-07-19 14:58:24,388.388 [log.py:35] - DEBUG - publish success
+2021-07-19 14:58:24,388.388 [log.py:35] - DEBUG - online success
+2021-07-19 14:58:24,389.389 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$gateway/operation/xxx/test01'', ... (97 bytes)
+2021-07-19 14:58:24,389.389 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-19 14:58:24,389.389 [log.py:35] - DEBUG - publish success
+2021-07-19 14:58:24,464.464 [client.py:2165] - DEBUG - Received PUBACK (Mid: 4)
+2021-07-19 14:58:24,465.465 [log.py:35] - DEBUG - on_publish:mid:4,userdata:None
+2021-07-19 14:58:24,467.467 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 14:58:24,467.467 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:3,userdata:None
+2021-07-19 14:58:24,499.499 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (101 bytes)
+2021-07-19 14:58:24,590.590 [log.py:35] - DEBUG - client:xxx/dev1 online success
+2021-07-19 14:58:24,590.590 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m6) [(b'xxx/dev1/data', 0)]
+2021-07-19 14:58:24,591.591 [log.py:35] - DEBUG - subscribe success topic:xxx/dev1/data
+2021-07-19 14:58:24,591.591 [log.py:35] - DEBUG - gateway subdev subscribe success
+2021-07-19 14:58:24,591.591 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m7), 'b'xxx/dev1/data'', ... (36 bytes)
+2021-07-19 14:58:24,591.591 [log.py:35] - DEBUG - publish success
+2021-07-19 14:58:24,591.591 [log.py:35] - DEBUG - online success
+2021-07-19 14:58:24,675.675 [client.py:2165] - DEBUG - Received PUBACK (Mid: 7)
+2021-07-19 14:58:24,676.676 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-19 14:58:24,686.686 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 14:58:24,686.686 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:6,userdata:None
+```
+As can be seen from the log, the two subdevices `test1` and `dev1` are connected successfully (`online success`). At this point, you can see in the console that the subdevices are online.
+
+* Proxied subdevice disconnection through a gateway
+Online subdevices can be disconnected through the gateway:
+```
+2021-07-19 15:12:31,923.923 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m8), 'b'$gateway/operation/xxx/test01'', ... (99 bytes)
+2021-07-19 15:12:31,925.925 [log.py:35] - DEBUG - publish success
+2021-07-19 15:12:31,926.926 [log.py:35] - DEBUG - on_publish:mid:8,userdata:None
+2021-07-19 15:12:31,995.995 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (103 bytes)
+2021-07-19 15:12:32,126.126 [log.py:35] - DEBUG - client:xxx/test1 offline success
+2021-07-19 15:12:32,126.126 [log.py:35] - DEBUG - offline success
+2021-07-19 15:12:32,126.126 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m9), 'b'$gateway/operation/xxx/test01'', ... (98 bytes)
+2021-07-19 15:12:32,127.127 [log.py:35] - DEBUG - publish success
+2021-07-19 15:12:32,127.127 [log.py:35] - DEBUG - on_publish:mid:9,userdata:None
+2021-07-19 15:12:32,219.219 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (102 bytes)
+2021-07-19 15:12:32,327.327 [log.py:35] - DEBUG - client:xxx/dev1 offline success
+2021-07-19 15:12:32,328.328 [log.py:35] - DEBUG - offline success
+```
+As can be seen from the log, the two subdevices just connected are disconnected successfully (`offline success`) through the gateway.
+
+#### Binding and unbinding subdevice
+* Bind a subdevice
+Subdevices not bound to a gateway can be bound on the device side.
+```
+2021-07-19 15:26:35,524.524 [log.py:35] - DEBUG - sign base64 *****************
+2021-07-19 15:26:35,524.524 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$gateway/operation/xxx/test01'', ... (231 bytes)
+2021-07-19 15:26:35,525.525 [log.py:35] - DEBUG - publish success
+2021-07-19 15:26:35,525.525 [log.py:35] - DEBUG - client:xxx/test1 bind success
+2021-07-19 15:26:35,525.525 [log.py:35] - DEBUG - bind success
+2021-07-19 15:26:35,525.525 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+2021-07-19 15:26:35,597.597 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (100 bytes)
+```
+As can be seen from the log, the `test1` subdevice is bound to the gateway successfully. At this point, you can see in the console that `test1` is already in the subdevice list.
+
+* Unbind a subdevice
+Subdevices bound to a gateway can be unbound on the device side.
+```
+2021-07-19 15:21:05,701.701 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$gateway/operation/xxx/test01'', ... (98 bytes)
+2021-07-19 15:21:05,701.701 [log.py:35] - DEBUG - publish success
+2021-07-19 15:21:05,701.701 [log.py:35] - DEBUG - on_publish:mid:3,userdata:None
+2021-07-19 15:21:05,786.786 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (102 bytes)
+2021-07-19 15:21:05,902.902 [log.py:35] - DEBUG - client:xxx/test1 unbind success
+2021-07-19 15:21:05,902.902 [log.py:35] - DEBUG - unbind success
+```
+As can be seen from the log, the `test1` subdevice is unbound from the gateway successfully. At this point, you can see in the console that `test1` is not in the subdevice list.
+
+#### Querying the list of bound subdevices
+You can query the list of subdevices bound to the current gateway on the device side.
+```
+2021-07-19 15:28:01,941.941 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m7), 'b'$gateway/operation/xxx/test01'', ... (32 bytes)
+2021-07-19 15:28:01,941.941 [log.py:35] - DEBUG - publish success
+2021-07-19 15:28:01,942.942 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-19 15:28:02,016.016 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (154 bytes)
+2021-07-19 15:28:02,142.142 [log.py:35] - DEBUG - client:xxx/test01 get bind list success
+2021-07-19 15:28:02,142.142 [log.py:35] - DEBUG - subdev id:xxx, name:dev1
+2021-07-19 15:28:02,142.142 [log.py:35] - DEBUG - subdev id:xxx, name:test1
+```
+As can be seen from the log, two subdevices are bound to the current gateway, with their `device_name` being `test1` and `dev1` respectively.
+
+#### Updating subdevice firmware
+Subdevice firmware update requires the gateway to download the firmware and then deliver it to the subdevice for update.
+```
+2021-07-20 10:14:15,311.311 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 10:14:15,312.312 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 10:14:16,976.976 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 10:14:17,057.057 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 10:14:17,057.057 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 10:14:17,315.315 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$gateway/operation/result/xxx/test01', 0)]
+2021-07-20 10:14:17,317.317 [log.py:35] - DEBUG - subscribe success topic:$gateway/operation/result/xxx/test01
+2021-07-20 10:14:17,401.401 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 10:14:17,401.401 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+6
+2021-07-20 10:14:18,791.791 [log.py:35] - DEBUG - ota test start...
+2021-07-20 10:14:18,791.791 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m2) [(b'$ota/update/xxx/test1', 1)]
+2021-07-20 10:14:18,791.791 [log.py:35] - DEBUG - subscribe success topic:$ota/update/xxx/test1
+2021-07-20 10:14:18,792.792 [log.py:35] - DEBUG - ota test start...
+2021-07-20 10:14:18,792.792 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'$ota/update/xxx/dev1', 1)]
+2021-07-20 10:14:18,793.793 [log.py:35] - DEBUG - subscribe success topic:$ota/update/xxx/dev1
+2021-07-20 10:14:18,868.868 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 10:14:18,868.868 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:2,userdata:None
+2021-07-20 10:14:18,993.993 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m4), 'b'$ota/report/xxx/test1'', ... (58 bytes)
+2021-07-20 10:14:18,993.993 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:19,718.718 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 10:14:19,718.718 [client.py:2165] - DEBUG - Received PUBACK (Mid: 4)
+2021-07-20 10:14:19,718.718 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/test1', ... (86 bytes)
+2021-07-20 10:14:19,718.718 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:3,userdata:None
+2021-07-20 10:14:19,719.719 [log.py:35] - DEBUG - on_publish:mid:4,userdata:None
+2021-07-20 10:14:19,719.719 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/test1', ... (472 bytes)
+2021-07-20 10:14:19,719.719 [log.py:35] - DEBUG - __on_ota_report:topic:$ota/update/xxx/test1,payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'}
+2021-07-20 10:14:19,796.796 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m5), 'b'$ota/report/xxx/dev1'', ... (58 bytes)
+2021-07-20 10:14:19,796.796 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:19,870.870 [client.py:2165] - DEBUG - Received PUBACK (Mid: 5)
+2021-07-20 10:14:19,870.870 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-20 10:14:19,887.887 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/dev1', ... (86 bytes)
+2021-07-20 10:14:19,888.888 [log.py:35] - DEBUG - __on_ota_report:topic:$ota/update/xxx/dev1,payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'}
+2021-07-20 10:14:19,994.994 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:19,994.994 [log.py:47] - ERROR - info file not exists
+2021-07-20 10:14:19,994.994 [log.py:35] - DEBUG - local_size:0,local_ver:None,re_ver:1.0.0
+__ota_http_deinit do nothing
+2021-07-20 10:14:20,799.799 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:20,991.991 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:20,991.991 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:20,991.991 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:20,991.991 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+2021-07-20 10:14:21,160.160 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:21,161.161 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m7), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:21,161.161 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:21,162.162 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-20 10:14:21,800.800 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:22,038.038 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:22,038.038 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m8), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:22,039.039 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:22,039.039 [log.py:35] - DEBUG - on_publish:mid:8,userdata:None
+2021-07-20 10:14:22,801.801 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:23,105.105 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '1', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:23,106.106 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m9), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:23,107.107 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:23,108.108 [log.py:35] - DEBUG - on_publish:mid:9,userdata:None
+2021-07-20 10:14:23,803.803 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:24,107.107 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '2', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:24,107.107 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m10), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:24,107.107 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:24,108.108 [log.py:35] - DEBUG - on_publish:mid:10,userdata:None
+2021-07-20 10:14:24,804.804 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:25,684.684 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '2', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:25,684.684 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m11), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:25,685.685 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:25,685.685 [log.py:35] - DEBUG - on_publish:mid:11,userdata:None
+2021-07-20 10:14:25,806.806 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:26,062.062 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '3', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:26,062.062 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m12), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:26,063.063 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:26,063.063 [log.py:35] - DEBUG - on_publish:mid:12,userdata:None
+2021-07-20 10:14:26,808.808 [log.py:35] - DEBUG - wait for ota upgrade command
+```
+The above log represents the process of subdevice firmware update through the gateway. The gateway is `xxx/test01`, and two subdevices are bound to it: `xxx/test1` and `xxx/dev1`. The console delivers a firmware update command to the `xxx/test1` subdevice for update, while the `xxx/dev1` device is in the waiting status. After downloading the firmware on behalf of the subdevice, the gateway needs to deliver the firmware to the subdevice and ask it to update. After the subdevice update is completed, the subdevice needs to notify the gateway of the update result, so that the gateway can report the update result on its behalf.
diff --git "a/hub/doc/en/PRELIM__\350\207\252\345\273\272\346\234\215\345\212\241\345\231\250\346\216\245\345\205\245_EN-US.md" "b/hub/doc/en/PRELIM__\350\207\252\345\273\272\346\234\215\345\212\241\345\231\250\346\216\245\345\205\245_EN-US.md"
new file mode 100644
index 0000000..7298246
--- /dev/null
+++ "b/hub/doc/en/PRELIM__\350\207\252\345\273\272\346\234\215\345\212\241\345\231\250\346\216\245\345\205\245_EN-US.md"
@@ -0,0 +1,20 @@
+* [Getting Started](#Getting-Started)
+ * [Setting the broker address of self-built server](#Setting-the-broker-address-of-self-built-server)
+ * [Setting the corresponding CA certificate of self-built server](#Setting-the-corresponding-CA-certificate-of-self-built-server)
+ * [Setting the corresponding domain name of self-built server for MQTT over WebSocket connection](#Setting-the-corresponding-domain-name-of-self-built-server-for-MQTT-over-WebSocket-connection)
+ * [Setting the corresponding dynamic registration URL of self-built server](#Setting-the-corresponding-dynamic-registration-URL-of-self-built-server)
+ * [Setting the connection domain name of self-built server for log reporting](#Setting-the-connection-domain-name-of-self-built-server-for-log-reporting)
+
+
+# Getting Started
+This document describes how to connect a self-built service based on the IoT Hub SDK.
+
+## Setting the broker address of self-built server
+
+## Setting the corresponding CA certificate of self-built server
+
+## Setting the corresponding domain name of self-built server for MQTT over WebSocket connection
+
+## Setting the corresponding dynamic registration URL of self-built server
+
+## Setting the connection domain name of self-built server for log reporting
\ No newline at end of file
diff --git "a/hub/doc/en/PRELIM__\350\256\276\345\244\207\344\272\222\351\200\232_EN-US.md" "b/hub/doc/en/PRELIM__\350\256\276\345\244\207\344\272\222\351\200\232_EN-US.md"
new file mode 100644
index 0000000..6695ca5
--- /dev/null
+++ "b/hub/doc/en/PRELIM__\350\256\276\345\244\207\344\272\222\351\200\232_EN-US.md"
@@ -0,0 +1,129 @@
+* [Device Interconnection](#Device-Interconnection)
+ * [Overview](#Overview)
+ * [Running demo](#Running-demo)
+ * [Entering parameters for authenticating device for connection](#Entering-parameters-for-authenticating-device-for-connection)
+ * [Trying out homecoming for door device](#Trying-out-homecoming-for-door-device)
+ * [Trying out homeleaving for door device](#Trying-out-homeleaving-for-door-device)
+
+# Device Interconnection
+## Overview
+This document describes how to try out device interconnection based on cross-device messaging and the rule engine in a smart home scenario with the aid of the IoT Hub device SDK for Python. For more information, please see [Scenario 1: Device Interconnection](https://cloud.tencent.com/document/product/634/11913).
+
+To try out device interconnection, you need to create two types of smart devices (`Door` and `AirConditioner`) as instructed in the documentation. You also need to configure the rule engine as instructed in [Overview](https://cloud.tencent.com/document/product/634/14446) and [forward the data to another topic](https://cloud.tencent.com/document/product/634/14449).
+
+## Running demo
+You can run [DoorSample.py](../../hub/sample/scenarized/example_door.py) to send a message to the air conditioner device upon homecoming and run [AircondSample.py](../../hub/sample/scenarized/example_aircond.py) to turn on/off the air conditioner device when messages are received from the door device.
+
+#### Entering parameters for authenticating device for connection
+To try out device interconnection, you need to create two devices. In the demo, an air conditioner device (AirConditioner) and a door device (door) are created, and the air conditioner will be turned on/off upon homecoming/homeleaving.
+Enter the information of the air conditioner device created in the console in ***aircond_device_info.json*** as shown below:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"AirConditioner1",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+Enter the information of the door device created in the console in ***door_device_info.json*** as shown below:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"door1",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+#### Trying out homecoming for door device
+The demo simulates the room temperature. The initial value is 25°C, the maximum value is 40°C, and the minimum value is -10°C. The temperature goes up by 1°C per second after the air conditioner is turned off and goes down by 1°C per second after it is turned on.
+The homecoming log of the `door1` device is as follows:
+```
+2021-07-21 14:59:53,618.618 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-21 14:59:53,618.618 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-21 14:59:54,000.000 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-21 14:59:54,061.061 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-21 14:59:54,061.061 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-21 14:59:54,619.619 [log.py:35] - DEBUG - publish {"action": "come_home", "targetDevice": "AirConditioner1"}
+2021-07-21 14:59:54,619.619 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m1), 'b'xxx/door1/event'', ... (68 bytes)
+2021-07-21 14:59:54,620.620 [log.py:35] - DEBUG - publish success
+2021-07-21 14:59:54,620.620 [log.py:35] - DEBUG - wait reply...
+2021-07-21 14:59:54,670.670 [client.py:2165] - DEBUG - Received PUBACK (Mid: 1)
+2021-07-21 14:59:54,670.670 [log.py:35] - DEBUG - on_publish:mid:1,userdata:None
+2021-07-21 14:59:55,621.621 [log.py:35] - DEBUG - disconnect
+2021-07-21 14:59:55,621.621 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-21 14:59:55,622.622 [log.py:35] - DEBUG - LoopThread thread exit
+```
+As can be seen, the device simulates homecoming and sends a `come_home` message to the air conditioner device.
+The log of the `AirConditioner1` device is as follows:
+```
+2021-07-21 14:59:48,270.270 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-21 14:59:48,270.270 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-21 14:59:48,650.650 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-21 14:59:48,715.715 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-21 14:59:48,715.715 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-21 14:59:49,272.272 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'xxx/AirConditioner1/control', 1)]
+2021-07-21 14:59:49,272.272 [log.py:35] - DEBUG - subscribe success topic:xxx/AirConditioner1/control
+2021-07-21 14:59:49,273.273 [log.py:35] - DEBUG - [air is close] temperature 25
+2021-07-21 14:59:49,320.320 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 14:59:49,320.320 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:1,userdata:None
+2021-07-21 14:59:50,274.274 [log.py:35] - DEBUG - [air is close] temperature 25
+2021-07-21 14:59:51,276.276 [log.py:35] - DEBUG - [air is close] temperature 26
+2021-07-21 14:59:52,278.278 [log.py:35] - DEBUG - [air is close] temperature 26
+2021-07-21 14:59:53,280.280 [log.py:35] - DEBUG - [air is close] temperature 27
+2021-07-21 14:59:54,283.283 [log.py:35] - DEBUG - [air is close] temperature 27
+2021-07-21 14:59:54,687.687 [client.py:2165] - DEBUG - Received PUBLISH (d0, q1, r0, m24), 'xxx/AirConditioner1/control', ... (68 bytes)
+2021-07-21 14:59:54,687.687 [client.py:2165] - DEBUG - Sending PUBACK (Mid: 24)
+2021-07-21 14:59:54,687.687 [log.py:35] - DEBUG - on_aircond_cb:payload:{"action": "come_home", "targetDevice": "AirConditioner1"},userdata:None
+2021-07-21 14:59:55,285.285 [log.py:35] - DEBUG - [air is open] temperature 28
+2021-07-21 14:59:56,288.288 [log.py:35] - DEBUG - [air is open] temperature 27
+2021-07-21 14:59:57,289.289 [log.py:35] - DEBUG - [air is open] temperature 27
+2021-07-21 14:59:58,291.291 [log.py:35] - DEBUG - [air is open] temperature 26
+2021-07-21 14:59:59,293.293 [log.py:35] - DEBUG - [air is open] temperature 26
+2021-07-21 15:00:00,299.299 [log.py:35] - DEBUG - [air is open] temperature 25
+2021-07-21 15:00:01,301.301 [log.py:35] - DEBUG - [air is open] temperature 25
+2021-07-21 15:00:02,311.311 [log.py:35] - DEBUG - [air is open] temperature 24
+2021-07-21 15:00:03,312.312 [log.py:35] - DEBUG - [air is open] temperature 24
+2021-07-21 15:00:04,314.314 [log.py:35] - DEBUG - [air is open] temperature 23
+2021-07-21 15:00:05,315.315 [log.py:35] - DEBUG - [air is open] temperature 23
+```
+As can be seen, the air conditioner is off and the air temperature goes up before it receives the homecoming message, and it is turned on and the air temperature gradually goes down after it receives the message.
+
+#### Trying out homeleaving for door device
+The homeleaving log of the `door1` device is as follows:
+```
+2021-07-21 15:00:04,962.962 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-21 15:00:04,963.963 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-21 15:00:05,288.288 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-21 15:00:05,355.355 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-21 15:00:05,356.356 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-21 15:00:05,964.964 [log.py:35] - DEBUG - publish {"action": "leave_home", "targetDevice": "AirConditioner1"}
+2021-07-21 15:00:05,964.964 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m1), 'b'xxx/door1/event'', ... (69 bytes)
+2021-07-21 15:00:05,964.964 [log.py:35] - DEBUG - publish success
+2021-07-21 15:00:05,965.965 [log.py:35] - DEBUG - wait reply...
+2021-07-21 15:00:06,011.011 [client.py:2165] - DEBUG - Received PUBACK (Mid: 1)
+2021-07-21 15:00:06,012.012 [log.py:35] - DEBUG - on_publish:mid:1,userdata:None
+2021-07-21 15:00:06,966.966 [log.py:35] - DEBUG - disconnect
+2021-07-21 15:00:06,966.966 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-21 15:00:06,967.967 [log.py:35] - DEBUG - LoopThread thread exit
+2021-07-21 15:00:06,967.967 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
+```
+As can be seen, the device simulates homeleaving and sends a `leave_home` message to the air conditioner device.
+The log of the `AirConditioner1` device is as follows:
+```
+2021-07-21 15:00:06,030.030 [client.py:2165] - DEBUG - Received PUBLISH (d0, q1, r0, m25), 'xxx/AirConditioner1/control', ... (69 bytes)
+2021-07-21 15:00:06,031.031 [client.py:2165] - DEBUG - Sending PUBACK (Mid: 25)
+2021-07-21 15:00:06,031.031 [log.py:35] - DEBUG - on_aircond_cb:payload:{"action": "leave_home", "targetDevice": "AirConditioner1"},userdata:None
+2021-07-21 15:00:06,318.318 [log.py:35] - DEBUG - [air is close] temperature 22
+2021-07-21 15:00:07,319.319 [log.py:35] - DEBUG - [air is close] temperature 23
+2021-07-21 15:00:08,321.321 [log.py:35] - DEBUG - [air is close] temperature 23
+2021-07-21 15:00:09,323.323 [log.py:35] - DEBUG - [air is close] temperature 24
+2021-07-21 15:00:10,325.325 [log.py:35] - DEBUG - [air is close] temperature 24
+2021-07-21 15:00:11,327.327 [log.py:35] - DEBUG - [air is close] temperature 25
+2021-07-21 15:00:12,329.329 [log.py:35] - DEBUG - [air is close] temperature 25
+```
+As can be seen, the air conditioner is turned off and the temperature gradually goes up again after it receives the homeleaving message.
diff --git "a/hub/doc/en/PRELIM__\350\256\276\345\244\207\345\275\261\345\255\220_EN-US.md" "b/hub/doc/en/PRELIM__\350\256\276\345\244\207\345\275\261\345\255\220_EN-US.md"
new file mode 100644
index 0000000..a1c357a
--- /dev/null
+++ "b/hub/doc/en/PRELIM__\350\256\276\345\244\207\345\275\261\345\255\220_EN-US.md"
@@ -0,0 +1,135 @@
+* [Device Shadow](#Device-Shadow)
+ * [Overview](#Overview)
+ * [Running demo](#Running-demo)
+ * [Entering parameters for authenticating device for connection](#Entering-parameters-for-authenticating-device-for-connection)
+ * [Getting status cached in cloud](#Getting-status-cached-in-cloud)
+ * [Modifying device shadow status](#Modifying-device-shadow-status)
+ * [Regularly updating device shadow](#Regularly-updating-device-shadow)
+
+# Device Shadow
+## Overview
+Device shadow is essentially a copy of device status and configuration data in JSON format cached by the server for the device. For more information, please see [Device Shadow Details](https://cloud.tencent.com/document/product/634/11918) and [Device Shadow Data Flow](https://cloud.tencent.com/document/product/634/14072).
+
+As an intermediary, device shadow can effectively implement two-way data sync between device and user application:
+
+* For device configuration, the user application does not need to directly modify the device; instead, it can modify the device shadow on the server, which will sync modifications to the device. In this way, if the device is offline at the time of modification, it will receive the latest configuration from the shadow once coming back online.
+* For device status, the device reports the status to the device shadow, and when users initiate queries, they can simply query the shadow. This can effectively reduce the network interactions between the device and the server, especially for low-power devices.
+
+## Running demo
+You can run the [ShadowSample.py](../../hub/sample/shadow/example_shadow.py) demo to try out operations related to device shadow.
+
+#### Entering parameters for authenticating device for connection
+Enter the information of the device created in the console in [device_info.json](../../hub/sample/device_info.json), such as the `auth_mode`, `productId`, `deviceName`, and `deviceSecret` fields of a key-authenticated device, as shown below:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"test01",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+#### Getting status cached in cloud
+After running the demo, subscribe to the `$shadow/operation/result/{productID}/{deviceName}` topic and get the cloud cache.
+```
+2021-07-20 16:44:56,611.611 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 16:44:56,612.612 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 16:44:57,010.010 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 16:44:57,069.069 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 16:44:57,069.069 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 16:44:57,613.613 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$shadow/operation/result/xxx/test01', 0)]
+2021-07-20 16:44:57,614.614 [log.py:35] - DEBUG - subscribe success topic:$shadow/operation/result/xxx/test01
+2021-07-20 16:44:57,661.661 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 16:44:57,662.662 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-20 16:44:57,670.670 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (192 bytes)
+2021-07-20 16:44:57,670.670 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-0', 'payload': {'state': {'reported': {'updateCount': 0, 'updateCount11': 'shadow'}}, 'timestamp': 1626770626087, 'version': 8}, 'result': 0, 'timestamp': 1626770697, 'type': 'get'},userdata:None
+```
+As can be seen from the log, the values of the `updateCount` and `updateCount11` fields are `0` and `shadow` respectively.
+
+#### Modifying device shadow status
+Send the `shadow GET` command to the `$shadow/operation/{productID}/{deviceName}` topic to get the device status cached in the cloud.
+```
+2021-07-20 16:44:57,615.615 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 12, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-1'}
+2021-07-20 16:44:57,615.615 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$shadow/operation/xxx/test01'', ... (120 bytes)
+2021-07-20 16:44:57,615.615 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-20 16:44:57,615.615 [log.py:35] - DEBUG - publish success
+2021-07-20 16:44:57,616.616 [log.py:35] - DEBUG - on_publish:mid:3,userdata:None
+```
+Observe the log, update the value of the `updateCount` field to `12`, add the new `updateCount12` field with the value `shadow` in the cloud, and get the cloud cache again. The log is as follows:
+```
+2021-07-20 16:44:58,688.688 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (219 bytes)
+2021-07-20 16:44:58,688.688 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-3', 'payload': {'state': {'reported': {'updateCount': 12, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626770698640, 'version': 10}, 'result': 0, 'timestamp': 1626770698, 'type': 'get'},userdata:None
+```
+As can be seen from the log, the value of the `updateCount` field is updated to `12`, the value of the `updateCount11` field is `shadow`, and the value of the new `updateCount12` field is also `shadow`.
+
+#### Regularly updating device shadow
+In the demo, the device shadow is updated once every 3 seconds by increasing the value of the `updateCount` field by 1, and the cloud cache is reset once every three updates. The log is as follows:
+```
+2021-07-20 16:58:40,724.724 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 16:58:40,724.724 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 16:58:41,185.185 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 16:58:41,245.245 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 16:58:41,245.245 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 16:58:41,726.726 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$shadow/operation/result/xxx/test01', 0)]
+2021-07-20 16:58:41,726.726 [log.py:35] - DEBUG - subscribe success topic:$shadow/operation/result/xxx/test01
+2021-07-20 16:58:41,726.726 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 1, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-0'}
+2021-07-20 16:58:41,727.727 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$shadow/operation/xxx/test01'', ... (119 bytes)
+2021-07-20 16:58:41,727.727 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:41,727.727 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$shadow/operation/xxx/test01'', ... (46 bytes)
+2021-07-20 16:58:41,728.728 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-20 16:58:41,728.728 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:41,728.728 [log.py:35] - DEBUG - on_publish:mid:3,userdata:None
+2021-07-20 16:58:41,778.778 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 16:58:41,779.779 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-20 16:58:41,795.795 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (174 bytes)
+2021-07-20 16:58:41,795.795 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-0', 'payload': {'state': {'reported': {'updateCount': 1}}, 'timestamp': 1626771521739, 'version': 21}, 'result': 0, 'timestamp': 1626771521739, 'type': 'update'},userdata:None
+2021-07-20 16:58:41,802.802 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (218 bytes)
+2021-07-20 16:58:41,802.802 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-1', 'payload': {'state': {'reported': {'updateCount': 1, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626771521739, 'version': 21}, 'result': 0, 'timestamp': 1626771521, 'type': 'get'},userdata:None
+2021-07-20 16:58:44,729.729 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 2, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-2'}
+2021-07-20 16:58:44,729.729 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m4), 'b'$shadow/operation/xxx/test01'', ... (119 bytes)
+2021-07-20 16:58:44,729.729 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:44,730.730 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$shadow/operation/xxx/test01'', ... (46 bytes)
+2021-07-20 16:58:44,730.730 [log.py:35] - DEBUG - on_publish:mid:4,userdata:None
+2021-07-20 16:58:44,730.730 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:44,730.730 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-20 16:58:44,804.804 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (174 bytes)
+2021-07-20 16:58:44,805.805 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-2', 'payload': {'state': {'reported': {'updateCount': 2}}, 'timestamp': 1626771524751, 'version': 22}, 'result': 0, 'timestamp': 1626771524751, 'type': 'update'},userdata:None
+2021-07-20 16:58:44,810.810 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (218 bytes)
+2021-07-20 16:58:44,811.811 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-3', 'payload': {'state': {'reported': {'updateCount': 2, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626771524751, 'version': 22}, 'result': 0, 'timestamp': 1626771524, 'type': 'get'},userdata:None
+2021-07-20 16:58:47,734.734 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 3, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-4'}
+2021-07-20 16:58:47,734.734 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$shadow/operation/xxx/test01'', ... (119 bytes)
+2021-07-20 16:58:47,734.734 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:47,735.735 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m7), 'b'$shadow/operation/xxx/test01'', ... (46 bytes)
+2021-07-20 16:58:47,735.735 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+2021-07-20 16:58:47,735.735 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:47,736.736 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-20 16:58:47,808.808 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (174 bytes)
+2021-07-20 16:58:47,809.809 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-4', 'payload': {'state': {'reported': {'updateCount': 3}}, 'timestamp': 1626771527749, 'version': 23}, 'result': 0, 'timestamp': 1626771527749, 'type': 'update'},userdata:None
+2021-07-20 16:58:47,816.816 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (218 bytes)
+2021-07-20 16:58:47,817.817 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-5', 'payload': {'state': {'reported': {'updateCount': 3, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626771527749, 'version': 23}, 'result': 0, 'timestamp': 1626771527, 'type': 'get'},userdata:None
+2021-07-20 16:58:50,739.739 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 4, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-6'}
+2021-07-20 16:58:50,739.739 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m8), 'b'$shadow/operation/xxx/test01'', ... (119 bytes)
+2021-07-20 16:58:50,740.740 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:50,740.740 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m9), 'b'$shadow/operation/xxx/test01'', ... (46 bytes)
+2021-07-20 16:58:50,740.740 [log.py:35] - DEBUG - on_publish:mid:8,userdata:None
+2021-07-20 16:58:50,741.741 [log.py:35] - DEBUG - on_publish:mid:9,userdata:None
+2021-07-20 16:58:50,741.741 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:50,801.801 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (174 bytes)
+2021-07-20 16:58:50,801.801 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-6', 'payload': {'state': {'reported': {'updateCount': 4}}, 'timestamp': 1626771530765, 'version': 24}, 'result': 0, 'timestamp': 1626771530765, 'type': 'update'},userdata:None
+2021-07-20 16:58:50,808.808 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (218 bytes)
+2021-07-20 16:58:50,808.808 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-7', 'payload': {'state': {'reported': {'updateCount': 4, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626771530765, 'version': 24}, 'result': 0, 'timestamp': 1626771530, 'type': 'get'},userdata:None
+2021-07-20 16:58:53,745.745 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 5, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-8'}
+2021-07-20 16:58:53,745.745 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m10), 'b'$shadow/operation/xxx/test01'', ... (119 bytes)
+2021-07-20 16:58:53,745.745 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:53,745.745 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m11), 'b'$shadow/operation/xxx/test01'', ... (46 bytes)
+2021-07-20 16:58:53,746.746 [log.py:35] - DEBUG - on_publish:mid:10,userdata:None
+2021-07-20 16:58:53,746.746 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:53,746.746 [log.py:35] - DEBUG - on_publish:mid:11,userdata:None
+2021-07-20 16:58:53,821.821 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (174 bytes)
+2021-07-20 16:58:53,821.821 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-8', 'payload': {'state': {'reported': {'updateCount': 5}}, 'timestamp': 1626771533769, 'version': 25}, 'result': 0, 'timestamp': 1626771533769, 'type': 'update'},userdata:None
+2021-07-20 16:58:53,827.827 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (218 bytes)
+2021-07-20 16:58:53,827.827 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-9', 'payload': {'state': {'reported': {'updateCount': 5, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626771533769, 'version': 25}, 'result': 0, 'timestamp': 1626771533, 'type': 'get'},userdata:None
+```
+As can be seen from the log, the value of the `updateCount` field is increased by 1 after each time of reporting from 1 to 3 after three times of reporting. Then, after the cloud cache is reset, it restores to its initial status (with the `updateCount11` and `updateCount12` fields cleared). After the `updateCount` field with a value of 4 and the `updateCount11` and `updateCount12` fields are reported again, the cloud cache is updated from the initial status again.
\ No newline at end of file
diff --git "a/hub/doc/en/PRELIM__\350\256\276\345\244\207\346\227\245\345\277\227\344\270\212\346\212\245_EN-US.md" "b/hub/doc/en/PRELIM__\350\256\276\345\244\207\346\227\245\345\277\227\344\270\212\346\212\245_EN-US.md"
new file mode 100644
index 0000000..1e2696e
--- /dev/null
+++ "b/hub/doc/en/PRELIM__\350\256\276\345\244\207\346\227\245\345\277\227\344\270\212\346\212\245_EN-US.md"
@@ -0,0 +1,10 @@
+* [Device Log Reporting](#Device-Log-Reporting)
+ * [Overview](#Overview)
+ * [Running demo to try out log testing](#Running-demo-to-try-out-log-testing)
+ * [Running demo to try out log upload](#Running-demo-to-try-out-log-upload)
+
+# Device Log Reporting
+## Overview
+The device log reporting feature can report device logs to the cloud over HTTP and display them in the console for you to remotely debug, diagnose, and monitor the device status. For more information on how to view device logs, please see [Cloud Log](https://cloud.tencent.com/document/product/634/14445).
+
+To try out the device log reporting feature, you need to enable **Device Log** and set the corresponding log level in **Device Information** > **Device Log Configuration** in the console.
\ No newline at end of file
diff --git a/hub/doc/en/README.md b/hub/doc/en/README.md
new file mode 100644
index 0000000..8665425
--- /dev/null
+++ b/hub/doc/en/README.md
@@ -0,0 +1,43 @@
+[简体中文](../../../hub) | English
+
+* [IoT Hub Device SDK for Python](#IoT-Hub-Device-SDK-for-Python)
+ * [Prerequisites](#Prerequisites)
+ * [Project configuration](#Project-configuration)
+ * [Downloading the sample code of IoT Hub SDK for Python demo](#Downloading-the-sample-code-of-IoT-Hub-SDK-for-Python-demo)
+ * [Feature documentation](#Feature-documentation)
+ * [SDK API and parameter descriptions](#SDK-API-and-parameter-descriptions)
+ * [FAQs](#FAQs)
+
+# IoT Hub Device SDK for Python
+Welcome to the IoT Hub device SDK for Python.
+
+The IoT Hub device SDK for Python relies on a secure and powerful data channel to enable IoT developers to connect devices (such as sensors, actuators, embedded devices, and smart home appliances) to the cloud for two-way communication. This document describes how to get and call the IoT Hub SDK for Python. If you encounter any issues when using it, please [feel free to submit them at GitHub](https://github.com/tencentyun/iot-device-python/issues/new).
+
+## Prerequisites
+* Create a Tencent Cloud account and activate IoT Hub in the Tencent Cloud console.
+* Create IoT products and devices in the console and get the product ID, device name, device certificate (for certificate authentication), device private key (for certificate authentication), and device key (for key authentication), which are required for authentication of the devices when you connect them to the cloud. For more information, please see [Device Connection Preparations](https://cloud.tencent.com/document/product/634/14442).
+* Understand the topic permissions. After a product is created successfully in the console, it has three permissions by default: subscribing to `${productId}/${deviceName}/control`, subscribing and publishing to `${productId}/${deviceName}/data`, and publishing to `${productId}/${deviceName}/event`. For more information on how to manipulate the topic permissions, please see [Permission List](https://cloud.tencent.com/document/product/634/14444).
+
+## Project configuration
+
+The SDK supports remote pip dependencies and local source code dependencies. For more information on how to connect, please see [SDK Connection Description](doc/SDK-Connection-Description.md).
+
+## Downloading the sample code of IoT Hub SDK for Python demo
+Download the complete code in the [repository](../../../). The sample code of the IoT Hub SDK for Python demo is in the [iot-device-python/sample](../../../tree/master/sample) module.
+
+
+## Feature documentation
+For more information on how to call the APIs, please see the demos of the following corresponding features.
+
+* [Device Connection Through MQTT over TCP](doc/Device-Connection-Through-MQTT-over-TCP.md)
+* [Device Connection Through MQTT over WebSocket](doc/Device-Connection-Through-MQTT-over-WebSocket.md)
+* [Dynamic Registration](doc/Dynamic-Registration.md)
+* ~~[Broadcast Communication](doc/Broadcast-Communication.md) (to be updated)~~
+* [Gateway Feature](doc/Gateway-Feature.md)
+* [Firmware Update](doc/Firmware-Update.md)
+* ~~[Gateway Subdevice Firmware Update](doc/Gateway-Subdevice-Firmware-Update.md) (to be updated)~~
+* ~~[Device Log Reporting](doc/Device-Log-Reporting.md) (to be updated)~~
+* ~~[Gateway Device Topological Relationship](doc/Gateway-Device-Topological-Relationship.md) (to be updated)~~
+* ~~[Device Interconnection](doc/Device-Interconnection.md) (to be updated)~~
+* [Device Shadow](doc/Device-Shadow.md)
+* ~~[Device Status Reporting and Setting](doc/Device-Status-Reporting-and-Setting.md) (to be updated)~~
\ No newline at end of file
diff --git "a/hub/doc/\345\205\254\346\234\211\344\272\221\346\216\245\345\205\245\344\270\211\346\226\271CA\350\257\201\344\271\246.md" "b/hub/doc/\345\205\254\346\234\211\344\272\221\346\216\245\345\205\245\344\270\211\346\226\271CA\350\257\201\344\271\246.md"
new file mode 100755
index 0000000..d4dd3ce
--- /dev/null
+++ "b/hub/doc/\345\205\254\346\234\211\344\272\221\346\216\245\345\205\245\344\270\211\346\226\271CA\350\257\201\344\271\246.md"
@@ -0,0 +1,64 @@
+## 接入三方CA证书流程
+
+**1.生成测试CA证书、验证证书、设备证书以及设备私钥**
+
+- [官网证书管理文档](https://cloud.tencent.com/document/product/634/59363),参考此链接文档将生成好的 CA 证书和设备证书上传至控制台,创建基于三方 CA 的产品和设备,此过程需注意生成证书的 Common Name 需正确填写,请按照文档顺序生成证书。测试 CA 证书可替换为厂商提供的 CA 证书。**注意**如需支持证书其他算法,如支持 SM2 算法,需同产品对接人沟通方案
+
+- 需注意生成过程中,如上传证书不支持格式,可将 openssl 命令中 crt 改为 cer
+
+- mac 环境 openssl.cnf 文件在系统目录中可找到 /System/Library/OpenSSL/openssl.cnf
+
+
+
+**2.配置Python SDK**
+
+- 在 demo [配置文件](https://github.com/tencentyun/iot-device-python/blob/master/hub/sample/device_info.json) 中配置公有云CA证书+设备证书+设备密钥(设备证书+设备密钥使用的是第三方ca签名过的, 需注意此处使用的CA证书非三方CA,而是官网腾讯云CA证书)
+
+ ```
+ 举例配置文件如下配置证书认证方式:
+ {
+ "auth_mode":"CERT",
+ "productId":"填写产品ID",
+ "productSecret":"",
+ "deviceName":"填写设备名称",
+
+ "key_deviceinfo":{
+ "deviceSecret":""
+ },
+
+ "cert_deviceinfo":{
+ "devCaFile":"/Users/xxx/Desktop/tencent/iot-device-python/hub/sample/ca.crt",
+ "devCertFile":"/Users/xxx/Desktop/tencent/iot-device-python/hub/sample/dev_01.crt",
+ "devPrivateKeyFile":"/Users/xxx/Desktop/tencent/iot-device-python/hub/sample/dev_01.key"
+ },
+ "region":"china"
+ }
+ ```
+
+
+- 运行 sample 事例 ``python3 hub/sample/test.py``
+
+ ```
+ init sdk
+ 2022-05-20 15:22:51,821.821 [log.py:46] - DEBUG - mqtt test start...
+ 2022-05-20 15:22:51,822.822 [log.py:46] - DEBUG - LoopThread thread enter
+ 2022-05-20 15:22:51,822.822 [log.py:54] - INFO - connect with certificate...
+ 2022-05-20 15:22:51,824.824 [log.py:46] - DEBUG - connect_async (productid.iotcloud.tencentdevices.com:8883)
+ 2022-05-20 15:22:52,085.085 [client.py:2404] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'productiddev_01'
+ 2022-05-20 15:22:52,154.154 [client.py:2404] - DEBUG - Received CONNACK (0, 0)
+ 2022-05-20 15:22:52,154.154 [log.py:46] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+ 2022-05-20 15:22:52,825.825 [client.py:2404] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/productid/dev_01', 0)]
+ 2022-05-20 15:22:52,826.826 [log.py:46] - DEBUG - subscribe success topic:$sys/operation/result/productid/dev_01
+ 2022-05-20 15:22:52,826.826 [client.py:2404] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/productid/dev_01'', ... (37 bytes)
+ 2022-05-20 15:22:52,827.827 [log.py:46] - DEBUG - publish success
+ 2022-05-20 15:22:52,827.827 [log.py:46] - DEBUG - on_publish:mid:2,userdata:None
+ 2022-05-20 15:22:52,885.885 [client.py:2404] - DEBUG - Received SUBACK
+ 2022-05-20 15:22:52,885.885 [log.py:46] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+ 2022-05-20 15:22:52,922.922 [client.py:2404] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/productid/dev_01', ... (82 bytes)
+ 2022-05-20 15:22:53,028.028 [client.py:2404] - DEBUG - Sending UNSUBSCRIBE (d0, m3) [b'$sys/operation/result/productid/dev_01']
+ 2022-05-20 15:22:53,029.029 [log.py:46] - DEBUG - current time:2022-05-20 15:22:53
+ 2022-05-20 15:22:53,029.029 [log.py:46] - DEBUG - mqtt test success...
+ deinit sdk
+ ```
+
+
diff --git "a/hub/doc/\345\212\250\346\200\201\346\263\250\345\206\214.md" "b/hub/doc/\345\212\250\346\200\201\346\263\250\345\206\214.md"
index 5519d93..b620059 100755
--- "a/hub/doc/\345\212\250\346\200\201\346\263\250\345\206\214.md"
+++ "b/hub/doc/\345\212\250\346\200\201\346\263\250\345\206\214.md"
@@ -1,5 +1,6 @@
* [动态注册认证](#动态注册认证)
* [动态注册认证简介](#动态注册认证简介)
+ * [控制台使能动态注册](#控制台使能动态注册)
* [运行示例程序进行动态注册](#运行示例程序进行动态注册)
# 动态注册认证
@@ -8,29 +9,49 @@
若用户在控制台上开启了自动创建设备,则无需在控制台预先创建设备,但需保证同一产品下设备名称无重复,一般可以取设备的唯一信息,比如MAC地址,此种方式更加灵活。若用户在控制台上关闭了自动创建设备,则必须要预先创建设备,动态注册时的设备要与录入的设备名称一致,此种方式更加安全,但便利性有所下降。
+## 控制台使能动态注册
+要使用动态注册功能,在控制台创建产品时需要打开该产品动态注册开关,并将产品`productSecret`信息保存,控制台设置如下图所示:
+
+
## 运行示例程序进行动态注册
-运行示例程序前,需要配置 [device_info.json](../../sample/device_info.json) 文件中设备上下线所需的参数外,还需要填写PRODUCT_KEY(控制台中ProductSecret)
+运行示例程序前,需要将控制台获取到的产品信息填写到 [device_info.json](../../hub/sample/device_info.json) 文件中,其中`deviceName`字段填写要生成的设备名字,`deviceSecret`字段保持为`YOUR_DEVICE_SECRET`,`productSecret`字段填写在控制台创建产品时生成的`productSecret`信息.
-运行 [DynregSample.py](../../sample/dynreg/example_dynreg.py) ,调用dynreg_device(),调用动态注册认证,动态注册回调获取了对应设备的密钥或证书相关信息后,再调用 MQTT 上线。示例代码如下:
+运行 [DynregSample.py](../../hub/sample/dynreg/example_dynreg.py)会调用`dynregDevice()`接口进行动态注册认证,动态注册回调获取了对应设备的密钥或证书相关信息后,通过接口返回值返回。示例代码如下:
```
-dyn_explorer = explorer.QcloudExplorer('./device_info.json')
-ret, msg = dyn_explorer.dynreg_device()
+from hub.hub import QcloudHub
+
+qcloud = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+ret, msg = qcloud.dynregDevice()
if ret == 0:
- print('dynamic register success, psk: {}'.format(msg))
+ logger.debug('dynamic register success, psk: {}'.format(msg))
else:
- print('dynamic register fail, msg: {}'.format(msg))
+ logger.error('dynamic register fail, msg: {}'.format(msg))
```
-以下是设备动态注册认证成功和失败的日志。
-
+以下是设备动态注册认证成功的日志。
+```
+dynamic register test success, psk: xxxxxxx
+2021-07-15 15:00:17,500.500 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-15 15:00:17,500.500 [log.py:35] - DEBUG - connect_async...8883
+2021-07-15 15:00:18,185.185 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxxxxx'
+2021-07-15 15:00:18,284.284 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-15 15:00:18,285.285 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-15 15:00:18,502.502 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxx/xxx', 0)]
+2021-07-15 15:00:18,503.503 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/xxx
+2021-07-15 15:00:18,503.503 [log.py:35] - DEBUG - pub topic:$sys/operation/xxx/xxx,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-15 15:00:18,504.504 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/xxx'', ... (37 bytes)
+2021-07-15 15:00:18,505.505 [log.py:35] - DEBUG - publish success
+2021-07-15 15:00:18,505.505 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-15 15:00:18,584.584 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-15 15:00:18,585.585 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-15 15:00:18,588.588 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/xxx/xxx', ... (82 bytes)
+2021-07-15 15:00:18,706.706 [log.py:35] - DEBUG - current time:2021-07-15 15:00:18
+2021-07-15 15:00:18,707.707 [log.py:35] - DEBUG - disconnect
+2021-07-15 15:00:18,707.707 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-15 15:00:18,709.709 [log.py:35] - DEBUG - LoopThread thread exit
+2021-07-15 15:00:18,710.710 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
```
-2021-03-16 10:43:28,371.371 [explorer.py:206] - INFO - dynreg url https://gateway.tencentdevices.com/register/dev
-2021-03-16 10:43:33,328.328 [explorer.py:210] - INFO - encrypt type psk
-dynamic register success
-
-2021-03-16 10:43:28,371.371 [explorer.py:206] - INFO - dynreg url https://gateway.tencentdevices.com/register/dev
-2021-03-16 10:43:33,328.328 [explorer.py:210] - ERROR - code: 1021, error message: Device has been activated
-dynamic register fail, msg: Device has been activated
-```
\ No newline at end of file
+可以看到动态注册成功获取到了设备密钥且mqtt连接成功并同步了ntp时间,此时查看配置文件[device_info.json](../../hub/sample/device_info.json)会发现`deviceSecret`字段已被更新。
diff --git "a/hub/doc/\345\233\272\344\273\266\345\215\207\347\272\247.md" "b/hub/doc/\345\233\272\344\273\266\345\215\207\347\272\247.md"
index 4fd703f..b40280f 100755
--- "a/hub/doc/\345\233\272\344\273\266\345\215\207\347\272\247.md"
+++ "b/hub/doc/\345\233\272\344\273\266\345\215\207\347\272\247.md"
@@ -1,9 +1,165 @@
* [OTA设备固件升级](#OTA设备固件升级)
* [固件升级简介](#固件升级简介)
- * [运行示例程序体验检查固件更新功能](#运行示例程序体验检查固件更新功能)
+ * [运行示例](#运行示例)
+ * [填写认证连接设备的参数](#填写认证连接设备的参数)
+ * [上传固件](#上传固件)
+ * [上报版本](#上报版本)
+ * [开始下载](#开始下载)
+ * [上报进度](#上报进度)
+ * [断点续传](#断点续传)
+ * [上报结果](#上报结果)
+
# OTA设备固件升级
## 固件升级简介
设备固件升级又称 OTA,是物联网通信服务的重要组成部分。当物联设备有新功能或者需要修复漏洞时,设备可以通过 OTA 服务快速进行固件升级。请参考官网文档 控制台使用手册 [固件升级](https://cloud.tencent.com/document/product/634/14673)
-体验固件升级需要在控制台中添加新的固件,请参考官网文档 开发者手册 [设备固件升级](https://cloud.tencent.com/document/product/634/14674)
\ No newline at end of file
+体验固件升级需要在控制台中添加新的固件,请参考官网文档 开发者手册 [设备固件升级](https://cloud.tencent.com/document/product/634/14674)
+
+
+## 运行示例
+运行 [OtaSample.py](../../hub/sample/ota/example_ota.py) 示例程序,会进行当前版本上报、固件下载、ota进度上报等过程。
+
+#### 填写认证连接设备的参数
+将在控制台创建设备时生成的设备信息填写到 [device_info.json](../../hub/sample/device_info.json)中,以密钥认证方式为例,主要关注`auth_mode`,`productId`,`deviceName`和`deviceSecret`字段,示例如下:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"xxx",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+#### 上传固件
+进行固件升级,首先要上传固件到物联网后台,可以通过控制台进行上传,如下图示:
+
+
+#### 上报版本
+观察日志输出,可以看到程序上报了当前固件版本(0.1.0),并开始等待云端的升级命令
+```
+2021-07-19 10:54:26,800.800 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-19 10:54:26,800.800 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-19 10:54:28,159.159 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-19 10:54:28,384.384 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-19 10:54:28,385.385 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-19 10:54:28,803.803 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$ota/update/xxx/xxx', 1)]
+2021-07-19 10:54:28,803.803 [log.py:35] - DEBUG - subscribe success topic:$ota/update/xxx/txest1
+2021-07-19 10:54:28,932.932 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 10:54:28,932.932 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:1,userdata:None
+2021-07-19 10:54:29,004.004 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m2), 'b'$ota/report/xxx/xxx'', ... (58 bytes)
+2021-07-19 10:54:29,005.005 [log.py:35] - DEBUG - publish success
+2021-07-19 10:54:29,144.144 [client.py:2165] - DEBUG - Received PUBACK (Mid: 2)
+2021-07-19 10:54:29,145.145 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (86 bytes)
+2021-07-19 10:54:29,147.147 [log.py:35] - DEBUG - on_ota_report:payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'},userdata:None
+2021-07-19 10:54:30,006.006 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:31,008.008 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:32,010.010 [log.py:35] - DEBUG - wait for ota upgrade command
+```
+
+#### 开始下载
+在收到设备上报的固件版本之后,可以选择要升级的新固件版本并执行升级命令,可以通过控制台进行操作,控制台有三种升级方式供选择,分别是:
+
+1. 按固件版本号升级所有设备。选择的版本号是待升级的版本号,可选多个待升级版本号。
+
+2. 按固件版本号批量升级指定设备。选择的版本号是待升级的版本号,可选多个待升级版本号;选择的设备是本次待升级的设备,可选多个待升级设备。
+
+3. 按设备名称批量升级模板文件中的设备。不关心待升级的版本号,直接将模板文件中的设备升级为选择的固件。
+
+
+***选择 1、2 方式如果待升级设备当前运行的固件没有上传到控制台,那么在选择待升级版本号时就不能选择待升级设备的版本号,此时可以通过在控制台上传待升级设备当前运行的固件版本或者选择方式 3 升级。***
+
+观察日志输出,可以看到在云端下发开始升级命令后,程序开始通过https下载固件
+```
+2021-07-19 10:54:44,032.032 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:45,034.034 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:45,409.409 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (468 bytes)
+2021-07-19 10:54:46,036.036 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:54:46,036.036 [log.py:47] - ERROR - info file not exists
+2021-07-19 10:54:46,036.036 [log.py:47] - ERROR - local_size:0,local_ver:None,re_ver:1.0.0
+2021-07-19 10:54:47,052.052 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:54:47,052.052 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$ota/report/xxx/xxx'', ... (151 bytes)
+2021-07-19 10:54:47,053.053 [log.py:35] - DEBUG - publish success
+```
+
+#### 上报进度
+固件开始下载后会持续上报下载进度(百分比)直至下载完成,如下日志所示
+```
+2021-07-19 10:55:08,066.066 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '13', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:08,066.066 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m24), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:08,066.066 [log.py:35] - DEBUG - publish success
+2021-07-19 10:55:09,083.083 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '14', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:09,084.084 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m25), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:09,084.084 [log.py:35] - DEBUG - publish success
+2021-07-19 10:55:10,002.002 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '15', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:10,002.002 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m26), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:10,002.002 [log.py:35] - DEBUG - publish success
+2021-07-19 10:55:11,216.216 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '16', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:11,216.216 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m27), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:11,217.217 [log.py:35] - DEBUG - publish success
+```
+
+#### 断点续传
+固件升级支持断点续传,若下载因故中断,回复后再次下载时会基于中断点继续下载,日志如下
+```
+2021-07-19 10:55:26,142.142 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '28', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:26,142.142 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m42), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:26,143.143 [log.py:35] - DEBUG - publish success
+```
+当前固件下载到28%时中断程序,然后重新启动程序进行升级
+```
+2021-07-19 10:55:27,797.797 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-19 10:55:27,797.797 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-19 10:55:28,581.581 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-19 10:55:28,680.680 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-19 10:55:28,681.681 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-19 10:55:28,799.799 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$ota/update/xxx/xxx', 1)]
+2021-07-19 10:55:28,800.800 [log.py:35] - DEBUG - subscribe success topic:$ota/update/xxx/xxx
+2021-07-19 10:55:28,892.892 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 10:55:28,892.892 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:1,userdata:None
+2021-07-19 10:55:29,002.002 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m2), 'b'$ota/report/xxx/xxx'', ... (58 bytes)
+2021-07-19 10:55:29,003.003 [log.py:35] - DEBUG - publish success
+2021-07-19 10:55:29,079.079 [client.py:2165] - DEBUG - Received PUBACK (Mid: 2)
+2021-07-19 10:55:29,096.096 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (86 bytes)
+2021-07-19 10:55:29,096.096 [log.py:35] - DEBUG - on_ota_report:payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'},userdata:None
+2021-07-19 10:55:29,118.118 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (472 bytes)
+2021-07-19 10:55:30,005.005 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:55:30,006.006 [log.py:47] - ERROR - local_size:5620000,local_ver:1.0.0,re_ver:1.0.0
+2021-07-19 10:55:30,029.029 [log.py:47] - ERROR - total read:5620000
+__ota_http_deinit do nothing
+2021-07-19 10:55:31,221.221 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '28', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:31,222.222 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:31,223.223 [log.py:35] - DEBUG - publish success
+2021-07-19 10:55:32,358.358 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '29', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:55:32,359.359 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m4), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:55:32,359.359 [log.py:35] - DEBUG - publish success
+```
+观察日志,可以看到再次启动程序后固件下载从上次的中断点开始下载直至完成
+
+#### 上报结果
+固件下载完成后进行烧录,并将结果上报云端
+```
+2021-07-19 10:57:25,228.228 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '99', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:57:25,228.228 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m116), 'b'$ota/report/xxx/xxx'', ... (152 bytes)
+2021-07-19 10:57:25,229.229 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:25,537.537 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '100', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-19 10:57:25,537.537 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m117), 'b'$ota/report/xxx/xxx'', ... (153 bytes)
+2021-07-19 10:57:25,537.537 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - The firmware download success
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - burning firmware...
+2021-07-19 10:57:25,538.538 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m118), 'b'$ota/report/xxx/xxx'', ... (128 bytes)
+2021-07-19 10:57:25,538.538 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:25,539.539 [log.py:35] - DEBUG - wait for ack...
+2021-07-19 10:57:25,641.641 [client.py:2165] - DEBUG - Received PUBACK (Mid: 118)
+2021-07-19 10:57:25,642.642 [log.py:35] - DEBUG - publish ack id 118
+2021-07-19 10:57:28,042.042 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m119), 'b'$ota/report/xxx/xxx'', ... (58 bytes)
+2021-07-19 10:57:28,043.043 [log.py:35] - DEBUG - publish success
+2021-07-19 10:57:28,128.128 [client.py:2165] - DEBUG - Received PUBACK (Mid: 119)
+2021-07-19 10:57:28,175.175 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/xxx', ... (86 bytes)
+2021-07-19 10:57:28,175.175 [log.py:35] - DEBUG - on_ota_report:payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'},userdata:None
+2021-07-19 10:57:29,045.045 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-19 10:57:30,048.048 [log.py:35] - DEBUG - wait for ota upgrade command
+```
+观察日志,可以看出固件下载完成后首先进行固件校验,确认无误后程序模拟固件烧录,完成后上报升级成功到云端并等待云端应答以确保云端收到上报结果,之后程序进入下一次的升级等待中。
\ No newline at end of file
diff --git "a/hub/doc/\345\237\272\344\272\216TCP\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245.md" "b/hub/doc/\345\237\272\344\272\216TCP\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245.md"
index b05d70d..f7347e4 100755
--- "a/hub/doc/\345\237\272\344\272\216TCP\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245.md"
+++ "b/hub/doc/\345\237\272\344\272\216TCP\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245.md"
@@ -1,12 +1,13 @@
* [快速开始](#快速开始)
* [控制台创建设备](#控制台创建设备)
- * [编译运行示例程序](#编译运行示例程序)
+ * [运行示例程序](#运行示例程序)
* [填写认证连接设备的参数](#填写认证连接设备的参数)
* [运行示例程序进行 MQTT 认证连接](#运行示例程序进行-MQTT-认证连接)
- * [运行示例程序进行断开 MQTT 连接](#运行示例程序进行断开-MQTT-连接)
* [订阅 Topic 主题](#订阅-Topic-主题)
- * [取消订阅 Topic 主题](#取消订阅-Topic-主题)
* [发布 Topic 主题](#发布-Topic-主题)
+ * [取消订阅 Topic 主题](#取消订阅-Topic-主题)
+ * [运行示例程序进行断开MQTT连接](#运行示例程序进行断开-MQTT-连接)
+
# 快速开始
本文将介绍如何在腾讯云物联网通信IoT Hub控制台创建设备, 并结合 SDK Demo 快速体验设备端通过 MQTT 协议连接腾讯云IoT Hub, 发送和接收消息。
@@ -24,6 +25,86 @@ ${productId}/${deviceName}/event // 发布
```
详情请参考官网 [控制台使用手册-权限列表](https://cloud.tencent.com/document/product/634/14444) 操作Topic权限。
-## 编译运行示例程序
+## 运行示例程序
+
+### 填写认证连接设备的参数
+将在控制台创建设备时生成的设备信息填写到 [device_info.json](../../hub/sample/device_info.json)中,以密钥认证方式为例,主要关注`auth_mode`,`productId`,`deviceName`和`deviceSecret`字段,示例如下:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"xxx",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+#### 运行示例程序进行MQTT认证连接
+运行 [MqttSample.py](../../hub/sample/mqtt/example_mqtt.py) 示例程序,会进行mqtt连接、topic订阅、消息发送、消息接收以及断开连接过程。运行sample日志如下如下:
+```
+2021-07-16 10:38:45,940.940 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-16 10:38:45,940.940 [log.py:35] - DEBUG - connect_async...8883
+2021-07-16 10:38:46,366.366 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-16 10:38:46,451.451 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-16 10:38:46,452.452 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-16 10:38:46,942.942 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxx/xxx', 0)]
+2021-07-16 10:38:46,943.943 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/xxx
+2021-07-16 10:38:46,944.944 [log.py:35] - DEBUG - pub topic:$sys/operation/xxx/xxx,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-16 10:38:46,945.945 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/xxx'', ... (37 bytes)
+2021-07-16 10:38:46,947.947 [log.py:35] - DEBUG - publish success
+2021-07-16 10:38:46,947.947 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-16 10:38:47,026.026 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-16 10:38:47,027.027 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-16 10:38:47,159.159 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/xxx/xxx', ... (82 bytes)
+2021-07-16 10:38:47,349.349 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m3) [b'$sys/operation/result/xxx/xxx']
+2021-07-16 10:38:47,350.350 [log.py:35] - DEBUG - current time:2021-07-16 10:38:47
+2021-07-16 10:38:47,433.433 [client.py:2165] - DEBUG - Received UNSUBACK (Mid: 3)
+2021-07-16 10:38:47,434.434 [log.py:35] - DEBUG - on_unsubscribe:mid:3,userdata:None
+2021-07-16 10:38:48,352.352 [log.py:35] - DEBUG - disconnect
+2021-07-16 10:38:48,352.352 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-16 10:38:48,354.354 [log.py:35] - DEBUG - LoopThread thread exit
+2021-07-16 10:38:48,355.355 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
+```
+观察日志输出,可以看到程序通过 MQTT 连接成功
+```
+2021-07-16 10:38:45,940.940 [log.py:35] - DEBUG - connect_async...8883
+2021-07-16 10:38:46,366.366 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-16 10:38:46,451.451 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-16 10:38:46,452.452 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+```
-[下载IoT Hub Python-SDK Demo示例代码](../README.md#下载IoT-Hub-Python-SDK-Demo示例代码)
\ No newline at end of file
+#### 订阅 Topic 主题
+观察日志输出,可以看出程序通过 MQTT 成功订阅了系统 Topic: `$sys/operation/result/${productID}/${deviceName}`
+```
+2021-07-16 10:38:46,942.942 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/xxx/xxx', 0)]
+2021-07-16 10:38:46,943.943 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/xxx/xxx
+2021-07-16 10:38:47,027.027 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+```
+
+#### 发布 Topic 主题
+观察日志输出,可以看出程序通过 MQTT 的 Publish 成功发布了消息到 Topic: `$sys/operation/${productID}/${deviceName}`,服务器收到了该消息并在处理后作出了响应
+```
+2021-07-16 10:38:46,944.944 [log.py:35] - DEBUG - pub topic:$sys/operation/xxx/xxx,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-16 10:38:46,945.945 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/xxx/xxx'', ... (37 bytes)
+2021-07-16 10:38:46,947.947 [log.py:35] - DEBUG - publish success
+2021-07-16 10:38:46,947.947 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-16 10:38:47,350.350 [log.py:35] - DEBUG - current time:2021-07-16 10:38:47
+```
+
+#### 取消订阅 Topic 主题
+观察日志输出,可以看出程序在完成消息的发布和接收后取消了对 Topic 的订阅
+```
+2021-07-16 10:38:47,349.349 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m3) [b'$sys/operation/result/xxx/xxx']
+2021-07-16 10:38:47,433.433 [client.py:2165] - DEBUG - Received UNSUBACK (Mid: 3)
+2021-07-16 10:38:47,434.434 [log.py:35] - DEBUG - on_unsubscribe:mid:3,userdata:None
+```
+
+#### 运行示例程序进行断开MQTT连接
+观察日志输出,可以看到程序在完成所有任务后断开了 MQTT 的连接
+```
+2021-07-16 10:38:48,352.352 [log.py:35] - DEBUG - disconnect
+2021-07-16 10:38:48,352.352 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-16 10:38:48,354.354 [log.py:35] - DEBUG - LoopThread thread exit
+2021-07-16 10:38:48,355.355 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
+```
\ No newline at end of file
diff --git "a/hub/doc/\345\237\272\344\272\216Websocket\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245.md" "b/hub/doc/\345\237\272\344\272\216Websocket\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245.md"
index cf93b15..cbc53f7 100644
--- "a/hub/doc/\345\237\272\344\272\216Websocket\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245.md"
+++ "b/hub/doc/\345\237\272\344\272\216Websocket\347\232\204MQTT\350\256\276\345\244\207\346\216\245\345\205\245.md"
@@ -1,10 +1,92 @@
* [基于Websocket的MQTT设备接入](#基于Websocket的MQTT设备接入)
* [基于Websocket的MQTT设备接入简介](#基于Websocket的MQTT设备接入简介)
* [填写认证连接设备的参数](#填写认证连接设备的参数)
- * [运行示例程序体验通过Websocket连接MQTT功能](#运行示例程序体验通过Websocket连接MQTT功能)
- * [运行示例程序体验通过Websocket断开MQTT连接功能](#运行示例程序体验通过Websocket断开MQTT连接功能)
- * [运行示例程序体验查看通过Websocket的MQTT连接状态](#运行示例程序体验查看通过Websocket的MQTT连接状态)
+ * [运行示例](#运行示例)
+ * [基于Websocket连接MQTT功能](#基于Websocket连接MQTT功能)
+ * [基于Websocket的MQTT发布订阅功能](#基于Websocket的MQTT发布订阅功能)
+ * [基于Websocket断开MQTT连接功能](#基于Websocket断开MQTT连接功能)
# 基于Websocket的MQTT设备接入
-## 基于Websocket的MQTT设备接入简介
-物联网平台支持基于 WebSocket 的 MQTT 通信,设备可以在 WebSocket 协议的基础之上使用 MQTT 协议进行消息的传输。请参考官网 [设备基于 WebSocket 的 MQTT 接入](https://cloud.tencent.com/document/product/634/46347)
\ No newline at end of file
+### 基于Websocket的MQTT设备接入简介
+物联网平台支持基于 WebSocket 的 MQTT 通信,设备可以在 WebSocket 协议的基础之上使用 MQTT 协议进行消息的传输。请参考官网 [设备基于 WebSocket 的 MQTT 接入](https://cloud.tencent.com/document/product/634/46347)
+
+### 填写认证连接设备的参数
+将在控制台创建设备时生成的设备信息填写到 [device_info.json](../../hub/sample/device_info.json)中,以密钥认证方式为例,主要关注`auth_mode`,`productId`,`deviceName`和`deviceSecret`字段,示例如下:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"xxx",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+### 运行示例程序
+修改 [MqttSample.py](../../hub/sample/mqtt/example_mqtt.py) 示例代码接入方式为websocket方式
+```
+qcloud = QcloudHub(device_file="hub/sample/device_info.json", tls=True, useWebsocket=True)
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
+```
+
+#### 运行示例程序进行MQTT认证连接
+运行 [MqttSample.py](../../hub/sample/mqtt/example_mqtt.py) 示例程序,会进行mqtt连接、topic订阅、消息发送、消息接收以及断开连接过程。运行sample日志如下如下:
+```
+2021-07-16 15:13:41,394.394 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-16 15:13:41,394.394 [log.py:35] - DEBUG - connect_async (UJDZES2SR2.ap-guangzhou.iothub.tencentdevices.com:443)
+2021-07-16 15:13:41,875.875 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'UJDZES2SR2test1'
+2021-07-16 15:13:41,952.952 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-16 15:13:41,952.952 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-16 15:13:42,396.396 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/UJDZES2SR2/test1', 0)]
+2021-07-16 15:13:42,396.396 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/UJDZES2SR2/test1
+2021-07-16 15:13:42,397.397 [log.py:35] - DEBUG - pub topic:$sys/operation/UJDZES2SR2/test1,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-16 15:13:42,397.397 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/UJDZES2SR2/test1'', ... (37 bytes)
+2021-07-16 15:13:42,397.397 [log.py:35] - DEBUG - publish success
+2021-07-16 15:13:42,398.398 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-16 15:13:42,498.498 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-16 15:13:42,499.499 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-16 15:13:42,627.627 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/UJDZES2SR2/test1', ... (82 bytes)
+2021-07-16 15:13:42,799.799 [client.py:2165] - DEBUG - Sending UNSUBSCRIBE (d0, m3) [b'$sys/operation/result/UJDZES2SR2/test1']
+2021-07-16 15:13:42,800.800 [log.py:35] - DEBUG - current time:2021-07-16 15:13:42
+2021-07-16 15:13:42,872.872 [client.py:2165] - DEBUG - Received UNSUBACK (Mid: 3)
+2021-07-16 15:13:42,872.872 [log.py:35] - DEBUG - on_unsubscribe:mid:3,userdata:None
+2021-07-16 15:13:43,802.802 [log.py:35] - DEBUG - disconnect
+2021-07-16 15:13:43,802.802 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-16 15:13:43,803.803 [log.py:35] - DEBUG - LoopThread thread exit
+```
+观察日志输出,可以看到程序通过 MQTT 连接成功,连接域名和端口都是websocket域名和端口
+```
+2021-07-16 15:13:41,394.394 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-16 15:13:41,394.394 [log.py:35] - DEBUG - connect_async (UJDZES2SR2.ap-guangzhou.iothub.tencentdevices.com:443)
+2021-07-16 15:13:41,875.875 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'UJDZES2SR2test1'
+2021-07-16 15:13:41,952.952 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-16 15:13:41,952.952 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+```
+
+#### 基于Websocket的MQTT订阅功能
+观察日志输出,可以看出程序通过 MQTT 成功订阅了系统 Topic: `$sys/operation/result/${productID}/${deviceName}`
+```
+2021-07-16 15:13:42,396.396 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$sys/operation/result/UJDZES2SR2/test1', 0)]
+2021-07-16 15:13:42,396.396 [log.py:35] - DEBUG - subscribe success topic:$sys/operation/result/UJDZES2SR2/test1
+2021-07-16 15:13:42,498.498 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-16 15:13:42,499.499 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+```
+
+#### 基于Websocket的MQTT发布功能
+观察日志输出,可以看出程序通过 MQTT 的 Publish 成功发布了消息到 Topic: `$sys/operation/${productID}/${deviceName}`,服务器收到了该消息并在处理后作出了响应
+```
+2021-07-16 15:13:42,397.397 [log.py:35] - DEBUG - pub topic:$sys/operation/UJDZES2SR2/test1,payload:{'type': 'get', 'resource': ['time']},qos:0
+2021-07-16 15:13:42,397.397 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$sys/operation/UJDZES2SR2/test1'', ... (37 bytes)
+2021-07-16 15:13:42,397.397 [log.py:35] - DEBUG - publish success
+2021-07-16 15:13:42,398.398 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-16 15:13:42,627.627 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$sys/operation/result/UJDZES2SR2/test1', ... (82 bytes)
+2021-07-16 15:13:42,800.800 [log.py:35] - DEBUG - current time:2021-07-16 15:13:42
+```
+
+#### 基于Websocket断开MQTT连接功能
+观察日志输出,可以看到程序在完成所有任务后断开了 MQTT 的连接
+```
+2021-07-16 15:13:43,802.802 [log.py:35] - DEBUG - disconnect
+2021-07-16 15:13:43,802.802 [client.py:2165] - DEBUG - Sending DISCONNECT
+```
\ No newline at end of file
diff --git "a/hub/doc/\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/hub/doc/\345\270\270\350\247\201\351\227\256\351\242\230.md"
new file mode 100644
index 0000000..8713cd7
--- /dev/null
+++ "b/hub/doc/\345\270\270\350\247\201\351\227\256\351\242\230.md"
@@ -0,0 +1,17 @@
+#### 海外设备或私有化设备接入服务器地址
+
+域名默认采用国内地址,对于海外或私有化设备的接入,需要修改对应的服务器地址,在对应各个功能模块中,初始化调用`QcloudHub`时,需要传入海外或私有化对应地域的服务器地址,域名相关参数说明:
+
+```
+domain :设备基于TCP接入方式下的地域服务器地址,默认为腾讯域名,海外或私有化设备接入,需传入对应设备域名或私有化完整URL
+```
+
+设备接入地域说明可参考:[官网文档](https://cloud.tencent.com/document/product/634/61228)
+
+#### 动态注册功能服务器域名
+动态注册功能默认为国内地址,如接入海外或私有化设备,需在`example_dynreg.py` 文件中调用`dynregDevice` 方法时,传入海外或私有化动态注册服务器地址,参数说明:
+##### 注意 创建产品时,控制台开启动态注册开关后才起作用
+```
+dynregDomain :设备动态注册地域服务器地址,海外或私有化设备接入,需传入对应设备域名或私有化完整URL
+
+```
\ No newline at end of file
diff --git "a/hub/doc/\345\271\277\346\222\255\351\200\232\344\277\241.md" "b/hub/doc/\345\271\277\346\222\255\351\200\232\344\277\241.md"
index 66ecc65..1abee3e 100644
--- "a/hub/doc/\345\271\277\346\222\255\351\200\232\344\277\241.md"
+++ "b/hub/doc/\345\271\277\346\222\255\351\200\232\344\277\241.md"
@@ -1,7 +1,10 @@
* [广播通信](#广播通信)
* [广播通信简介](#广播通信简介)
* [广播 Topic](#广播-Topic)
- * [运行示例程序进行广播通信](#运行示例程序进行广播通信)
+ * [运行示例](#运行示例)
+ * [填写认证连接设备的参数](#填写认证连接设备的参数)
+ * [广播初始化](#广播初始化)
+ * [接收广播消息](#接收广播消息)
# 广播通信
## 广播通信简介
@@ -11,28 +14,76 @@
* 广播通信的 Topic 内容为:$broadcast/rxd/${ProductId}/${DeviceName},其中 ProductId (产品ID) 、 DeviceName(设备名称)。
-运行 [MqttSample.py](../sample/MqttSample.py) 。示例代码如下:
+## 运行示例
+运行 [BroadcastSample.py](../../hub/sample/broadcast/example_broadcast.py) 可以体验设备接收广播消息.
+#### 填写认证连接设备的参数
+体验广播消息需要创建两个设备,将在控制台创建设备时生成的设备信息填写到 [device_info.json](../../hub/sample/device_info.json)中,以密钥认证方式为例,主要关注`auth_mode`,`productId`,`deviceName`,`deviceSecret`字段,示例如下:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"test01",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+体验时可以创建两个配置文件,分别填入创建的两个设备信息,并修改示例引入两个设备各自的配置文件,代码示例如下:
+```
+# 将"hub/sample/device_info.json"替换为创建的设备信息配置文件
+qcloud = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, enable=True)
```
-def on_connect(flags, rc, userdata):
- print("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
- pass
-def on_disconnect(rc, userdata):
- print("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
- pass
-
-te = explorer.QcloudExplorer(device_file="./device_info.json")
-te.user_on_connect = on_connect
-te.user_on_disconnect = on_disconnect
+#### 广播初始化
+示例程序运行后调用广播初始化接口进行相关Topic订阅,之后等待广播消息.
+设备`test01`初始化并等待广播消息
+```
+2021-07-20 19:43:09,109.109 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 19:43:09,119.119 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 19:43:09,516.516 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 19:43:09,573.573 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 19:43:09,573.573 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 19:43:10,112.112 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$broadcast/rxd/xxx/test01', 0)]
+2021-07-20 19:43:10,113.113 [log.py:35] - DEBUG - subscribe success topic:$broadcast/rxd/xxx/test01
+2021-07-20 19:43:10,113.113 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:43:10,156.156 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 19:43:10,157.157 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-20 19:43:11,115.115 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:43:12,118.118 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:43:13,119.119 [log.py:35] - DEBUG - broadcast wait
+```
+设备`dev01`初始化并等待广播消息
+```
+2021-07-20 19:47:14,510.510 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 19:47:14,511.511 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 19:47:15,099.099 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 19:47:15,160.160 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 19:47:15,161.161 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 19:47:15,512.512 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$broadcast/rxd/xxx/dev01', 0)]
+2021-07-20 19:47:15,514.514 [log.py:35] - DEBUG - subscribe success topic:$broadcast/rxd/xxx/dev01
+2021-07-20 19:47:15,514.514 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:47:15,561.561 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 19:47:15,561.561 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-20 19:47:16,516.516 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:47:17,518.518 [log.py:35] - DEBUG - broadcast wait
+```
-te.mqtt_init(mqtt_domain="")
-te.connect_async()
-te.broadcast_init()
+#### 接收广播消息
+调用云API `PublishBroadcastMessage` 发送广播消息
+打开腾讯云[API控制台](https://console.cloud.tencent.com/api/explorer?Product=iotcloud&Version=2018-06-14&Action=PublishBroadcastMessage&SignVersion=),填写个人密钥和设备参数信息,选择在线调用并发送请求
+
+设备`test01`成功接收到广播消息
+```
+2021-07-20 19:48:32,693.693 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:48:33,003.003 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$broadcast/rxd/xxx/test01', ... (28 bytes)
+2021-07-20 19:48:33,013.013 [log.py:35] - DEBUG - on_broadcast_cb:payload:{'payload': 'broadcast test'},userdata:None
```
-观察日志。
+设备`dev01`成功接收到广播消息
```
-2021-04-07 15:49:44,123.123 [client.py:2404] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$broadcast/rxd/0MG40UUX90/device_nname', ... (49 bytes)
-2021-04-07 15:49:44,124.124 [hub.py:188] - INFO - __user_thread_on_message_callback,topic:$broadcast/rxd/0MG40UUX90/device_nname,payload:{'state': 'wwww', 'result': 0, 'type': 'rerered'},mid:0
+2021-07-20 19:48:32,657.657 [log.py:35] - DEBUG - broadcast wait
+2021-07-20 19:48:33,002.002 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$broadcast/rxd/xxx/dev01', ... (28 bytes)
+2021-07-20 19:48:33,013.013 [log.py:35] - DEBUG - on_broadcast_cb:payload:{'payload': 'broadcast test'},userdata:None
```
\ No newline at end of file
diff --git "a/hub/doc/\347\275\221\345\205\263\345\212\237\350\203\275.md" "b/hub/doc/\347\275\221\345\205\263\345\212\237\350\203\275.md"
index af47815..2c543cc 100644
--- "a/hub/doc/\347\275\221\345\205\263\345\212\237\350\203\275.md"
+++ "b/hub/doc/\347\275\221\345\205\263\345\212\237\350\203\275.md"
@@ -1,12 +1,221 @@
* [网关功能](#网关功能)
* [网关功能简介](#网关功能简介)
- * [运行示例程序体验网关功能](#运行示例程序体验网关功能)
+ * [运行示例](#运行示例)
* [填写认证连接设备的参数](#填写认证连接设备的参数)
- * [体验子设备上线](#体验子设备上线)
- * [体验子设备下线](#体验子设备下线)
+ * [代理子设备上下线](#代理子设备上下线)
+ * [绑定解绑子设备](#绑定解绑子设备)
+ * [查询绑定的子设备列表](#查询绑定的子设备列表)
+ * [子设备固件升级](#子设备固件升级)
# 网关功能
## 网关功能简介
网关产品具备普通产品的基本功能,同时支持绑定不能直连 Internet 的产品,可用于代理子设备与腾讯云物联网通信 IoT Hub 进行数据交互。本文档将讲述网关产品通过 MQTT 协议连接到腾讯云IoT Hub以及代理子设备上下线、发送和接收消息的功能。
-体验网关功能需要在控制台创建网关产品,绑定其子产品,以及绑定子设备。请参考 [设备接入准备](https://cloud.tencent.com/document/product/634/14442) ,[网关产品接入](https://cloud.tencent.com/document/product/634/32740)
\ No newline at end of file
+体验网关功能需要在控制台创建网关产品,绑定其子产品,以及绑定子设备。请参考 [设备接入准备](https://cloud.tencent.com/document/product/634/14442) ,[网关产品接入](https://cloud.tencent.com/document/product/634/32740)
+
+## 运行示例
+运行 [GatewaySample.py](../../hub/sample/gateway/example_gateway.py) 示例程序,可以体验网关代理子设备上下线、绑定/解绑子设备及子设备固件升级等过程。
+
+#### 填写认证连接设备的参数
+将在控制台创建设备时生成的设备信息填写到 [device_info.json](../../hub/sample/device_info.json)中,以密钥认证方式为例,主要关注`auth_mode`,`productId`,`deviceName`,`deviceSecret`及网关子设备`subDev`部分字段,示例如下:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"test01",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+"subDev":{
+ "subdev_num":2,
+ "subdev_list":
+ [
+ {"sub_productId": "xxxx", "sub_devName": "test1"},
+ {"sub_productId": "xxxx", "sub_devName": "dev1"}
+ ]
+}
+```
+
+#### 代理子设备上下线
+示例程序配置文件中网关设备子设备有两个,devica_name分别为`test1`和`dev1`。
+* 网关代理子设备下线
+处于离线状态的子设备可以由网关代理上线
+```
+2021-07-19 14:58:18,394.394 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-19 14:58:18,403.403 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-19 14:58:19,585.585 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxtest01'
+2021-07-19 14:58:19,678.678 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-19 14:58:19,678.678 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-19 14:58:20,398.398 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$gateway/operation/result/xxx/test01', 0)]
+2021-07-19 14:58:20,399.399 [log.py:35] - DEBUG - subscribe success topic:$gateway/operation/result/xxx/test01
+2021-07-19 14:58:20,459.459 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 14:58:20,460.460 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+1
+2021-07-19 14:58:24,178.178 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$gateway/operation/xxx/test01'', ... (98 bytes)
+2021-07-19 14:58:24,178.178 [log.py:35] - DEBUG - publish success
+2021-07-19 14:58:24,178.178 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-19 14:58:24,291.291 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (102 bytes)
+2021-07-19 14:58:24,379.379 [log.py:35] - DEBUG - client:xxx/test1 online success
+2021-07-19 14:58:24,379.379 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'xxx/test1/data', 0)]
+2021-07-19 14:58:24,379.379 [log.py:35] - DEBUG - subscribe success topic:xxx/test1/data
+2021-07-19 14:58:24,380.380 [log.py:35] - DEBUG - gateway subdev subscribe success
+2021-07-19 14:58:24,380.380 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m4), 'b'xxx/test1/data'', ... (36 bytes)
+2021-07-19 14:58:24,388.388 [log.py:35] - DEBUG - publish success
+2021-07-19 14:58:24,388.388 [log.py:35] - DEBUG - online success
+2021-07-19 14:58:24,389.389 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$gateway/operation/xxx/test01'', ... (97 bytes)
+2021-07-19 14:58:24,389.389 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-19 14:58:24,389.389 [log.py:35] - DEBUG - publish success
+2021-07-19 14:58:24,464.464 [client.py:2165] - DEBUG - Received PUBACK (Mid: 4)
+2021-07-19 14:58:24,465.465 [log.py:35] - DEBUG - on_publish:mid:4,userdata:None
+2021-07-19 14:58:24,467.467 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 14:58:24,467.467 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:3,userdata:None
+2021-07-19 14:58:24,499.499 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (101 bytes)
+2021-07-19 14:58:24,590.590 [log.py:35] - DEBUG - client:xxx/dev1 online success
+2021-07-19 14:58:24,590.590 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m6) [(b'xxx/dev1/data', 0)]
+2021-07-19 14:58:24,591.591 [log.py:35] - DEBUG - subscribe success topic:xxx/dev1/data
+2021-07-19 14:58:24,591.591 [log.py:35] - DEBUG - gateway subdev subscribe success
+2021-07-19 14:58:24,591.591 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m7), 'b'xxx/dev1/data'', ... (36 bytes)
+2021-07-19 14:58:24,591.591 [log.py:35] - DEBUG - publish success
+2021-07-19 14:58:24,591.591 [log.py:35] - DEBUG - online success
+2021-07-19 14:58:24,675.675 [client.py:2165] - DEBUG - Received PUBACK (Mid: 7)
+2021-07-19 14:58:24,676.676 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-19 14:58:24,686.686 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-19 14:58:24,686.686 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:6,userdata:None
+```
+观察日志,可以看到两个子设备`test1`和`dev1`都成功上线(online success)。此时查看控制台可以看到子设备为在线状态。
+
+* 网关代理子设备下线
+处于在线状态的子设备可以由网关代理下线
+```
+2021-07-19 15:12:31,923.923 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m8), 'b'$gateway/operation/xxx/test01'', ... (99 bytes)
+2021-07-19 15:12:31,925.925 [log.py:35] - DEBUG - publish success
+2021-07-19 15:12:31,926.926 [log.py:35] - DEBUG - on_publish:mid:8,userdata:None
+2021-07-19 15:12:31,995.995 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (103 bytes)
+2021-07-19 15:12:32,126.126 [log.py:35] - DEBUG - client:xxx/test1 offline success
+2021-07-19 15:12:32,126.126 [log.py:35] - DEBUG - offline success
+2021-07-19 15:12:32,126.126 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m9), 'b'$gateway/operation/xxx/test01'', ... (98 bytes)
+2021-07-19 15:12:32,127.127 [log.py:35] - DEBUG - publish success
+2021-07-19 15:12:32,127.127 [log.py:35] - DEBUG - on_publish:mid:9,userdata:None
+2021-07-19 15:12:32,219.219 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (102 bytes)
+2021-07-19 15:12:32,327.327 [log.py:35] - DEBUG - client:xxx/dev1 offline success
+2021-07-19 15:12:32,328.328 [log.py:35] - DEBUG - offline success
+```
+观察日志,可以看到刚上线的两个子设备通过网关代理成功下线(offline success)。
+
+#### 绑定解绑子设备
+* 绑定子设备
+未和网关绑定的子设备可以在设备端进行绑定操作
+```
+2021-07-19 15:26:35,524.524 [log.py:35] - DEBUG - sign base64 *****************
+2021-07-19 15:26:35,524.524 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$gateway/operation/xxx/test01'', ... (231 bytes)
+2021-07-19 15:26:35,525.525 [log.py:35] - DEBUG - publish success
+2021-07-19 15:26:35,525.525 [log.py:35] - DEBUG - client:xxx/test1 bind success
+2021-07-19 15:26:35,525.525 [log.py:35] - DEBUG - bind success
+2021-07-19 15:26:35,525.525 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+2021-07-19 15:26:35,597.597 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (100 bytes)
+```
+以子设备`test1`为例,观察日志可以看到与网关绑定成功,此时在控制台查看网关子设备会发现`test1`已经存在于子设备列表中了。
+
+* 解绑子设备
+已经和网关绑定的子设备可以在设备端进行解绑操作
+```
+2021-07-19 15:21:05,701.701 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$gateway/operation/xxx/test01'', ... (98 bytes)
+2021-07-19 15:21:05,701.701 [log.py:35] - DEBUG - publish success
+2021-07-19 15:21:05,701.701 [log.py:35] - DEBUG - on_publish:mid:3,userdata:None
+2021-07-19 15:21:05,786.786 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (102 bytes)
+2021-07-19 15:21:05,902.902 [log.py:35] - DEBUG - client:xxx/test1 unbind success
+2021-07-19 15:21:05,902.902 [log.py:35] - DEBUG - unbind success
+```
+以子设备`test1`为例,观察日志可以看到与网关解绑成功,此时在控制台查看网关子设备会发现`test1`已经不在子设备列表中了。
+
+#### 查询绑定的子设备列表
+设备端可以查询当前网关绑定的子设备列表
+```
+2021-07-19 15:28:01,941.941 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m7), 'b'$gateway/operation/xxx/test01'', ... (32 bytes)
+2021-07-19 15:28:01,941.941 [log.py:35] - DEBUG - publish success
+2021-07-19 15:28:01,942.942 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-19 15:28:02,016.016 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$gateway/operation/result/xxx/test01', ... (154 bytes)
+2021-07-19 15:28:02,142.142 [log.py:35] - DEBUG - client:xxx/test01 get bind list success
+2021-07-19 15:28:02,142.142 [log.py:35] - DEBUG - subdev id:xxx, name:dev1
+2021-07-19 15:28:02,142.142 [log.py:35] - DEBUG - subdev id:xxx, name:test1
+```
+观察日志,可以看到当前网关绑定了两个子设备,`device_name`分别为`test1`和`dev1`
+
+#### 子设备固件升级
+子设备固件升级需要由网关代理下载固件,之后下发到子设备进行升级。
+```
+2021-07-20 10:14:15,311.311 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 10:14:15,312.312 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 10:14:16,976.976 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 10:14:17,057.057 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 10:14:17,057.057 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 10:14:17,315.315 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$gateway/operation/result/xxx/test01', 0)]
+2021-07-20 10:14:17,317.317 [log.py:35] - DEBUG - subscribe success topic:$gateway/operation/result/xxx/test01
+2021-07-20 10:14:17,401.401 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 10:14:17,401.401 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+6
+2021-07-20 10:14:18,791.791 [log.py:35] - DEBUG - ota test start...
+2021-07-20 10:14:18,791.791 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m2) [(b'$ota/update/xxx/test1', 1)]
+2021-07-20 10:14:18,791.791 [log.py:35] - DEBUG - subscribe success topic:$ota/update/xxx/test1
+2021-07-20 10:14:18,792.792 [log.py:35] - DEBUG - ota test start...
+2021-07-20 10:14:18,792.792 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m3) [(b'$ota/update/xxx/dev1', 1)]
+2021-07-20 10:14:18,793.793 [log.py:35] - DEBUG - subscribe success topic:$ota/update/xxx/dev1
+2021-07-20 10:14:18,868.868 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 10:14:18,868.868 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:2,userdata:None
+2021-07-20 10:14:18,993.993 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m4), 'b'$ota/report/xxx/test1'', ... (58 bytes)
+2021-07-20 10:14:18,993.993 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:19,718.718 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 10:14:19,718.718 [client.py:2165] - DEBUG - Received PUBACK (Mid: 4)
+2021-07-20 10:14:19,718.718 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/test1', ... (86 bytes)
+2021-07-20 10:14:19,718.718 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:3,userdata:None
+2021-07-20 10:14:19,719.719 [log.py:35] - DEBUG - on_publish:mid:4,userdata:None
+2021-07-20 10:14:19,719.719 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/test1', ... (472 bytes)
+2021-07-20 10:14:19,719.719 [log.py:35] - DEBUG - __on_ota_report:topic:$ota/update/xxx/test1,payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'}
+2021-07-20 10:14:19,796.796 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m5), 'b'$ota/report/xxx/dev1'', ... (58 bytes)
+2021-07-20 10:14:19,796.796 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:19,870.870 [client.py:2165] - DEBUG - Received PUBACK (Mid: 5)
+2021-07-20 10:14:19,870.870 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-20 10:14:19,887.887 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$ota/update/xxx/dev1', ... (86 bytes)
+2021-07-20 10:14:19,888.888 [log.py:35] - DEBUG - __on_ota_report:topic:$ota/update/xxx/dev1,payload:{'result_code': 0, 'result_msg': 'success', 'type': 'report_version_rsp', 'version': '0.1.0'}
+2021-07-20 10:14:19,994.994 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:19,994.994 [log.py:47] - ERROR - info file not exists
+2021-07-20 10:14:19,994.994 [log.py:35] - DEBUG - local_size:0,local_ver:None,re_ver:1.0.0
+__ota_http_deinit do nothing
+2021-07-20 10:14:20,799.799 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:20,991.991 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:20,991.991 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:20,991.991 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:20,991.991 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+2021-07-20 10:14:21,160.160 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:21,161.161 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m7), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:21,161.161 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:21,162.162 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-20 10:14:21,800.800 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:22,038.038 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '0', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:22,038.038 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m8), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:22,039.039 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:22,039.039 [log.py:35] - DEBUG - on_publish:mid:8,userdata:None
+2021-07-20 10:14:22,801.801 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:23,105.105 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '1', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:23,106.106 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m9), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:23,107.107 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:23,108.108 [log.py:35] - DEBUG - on_publish:mid:9,userdata:None
+2021-07-20 10:14:23,803.803 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:24,107.107 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '2', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:24,107.107 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m10), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:24,107.107 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:24,108.108 [log.py:35] - DEBUG - on_publish:mid:10,userdata:None
+2021-07-20 10:14:24,804.804 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:25,684.684 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '2', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:25,684.684 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m11), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:25,685.685 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:25,685.685 [log.py:35] - DEBUG - on_publish:mid:11,userdata:None
+2021-07-20 10:14:25,806.806 [log.py:35] - DEBUG - wait for ota upgrade command
+2021-07-20 10:14:26,062.062 [log.py:35] - DEBUG - [ota report] {'type': 'report_progress', 'report': {'progress': {'state': 'downloading', 'percent': '3', 'result_code': '0', 'result_msg': ''}, 'version': '1.0.0'}}
+2021-07-20 10:14:26,062.062 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m12), 'b'$ota/report/xxx/test1'', ... (151 bytes)
+2021-07-20 10:14:26,063.063 [log.py:35] - DEBUG - publish success
+2021-07-20 10:14:26,063.063 [log.py:35] - DEBUG - on_publish:mid:12,userdata:None
+2021-07-20 10:14:26,808.808 [log.py:35] - DEBUG - wait for ota upgrade command
+```
+以上日志是网关代理子设备下载固件进行升级过程,其中网关为`xxx/test01`,该网关下有两个子设备,分别为`xxx/test1`和`xxx/dev1`,其中子设备`xxx/test1`由控制台下发了固件升级命令进行升级,而子设备`xxx/dev1`没有下发,一直处于等待状态。网关代理子设备下载完固件后需要将固件下发到子设备并通知其进行升级,子设备升级完成需要将升级结果通知到网关,由网关代理子设备进行升级结果上报。
diff --git "a/hub/doc/\347\275\221\345\205\263\345\255\220\350\256\276\345\244\207\345\233\272\344\273\266\345\215\207\347\272\247.md" "b/hub/doc/\347\275\221\345\205\263\345\255\220\350\256\276\345\244\207\345\233\272\344\273\266\345\215\207\347\272\247.md"
deleted file mode 100644
index 5cd3f03..0000000
--- "a/hub/doc/\347\275\221\345\205\263\345\255\220\350\256\276\345\244\207\345\233\272\344\273\266\345\215\207\347\272\247.md"
+++ /dev/null
@@ -1,14 +0,0 @@
-* [OTA网关子设备固件升级](#OTA网关子设备固件升级)
- * [操网关子设备固件升级简介](#网关子设备固件升级简介)
- * [编译运行示例程序](#编译运行示例程序)
- * [填写认证连接设备的参数](#填写认证连接设备的参数)
- * [运行Demo认证连接并执行子设备固件升级](#运行Demo认证连接并执行子设备固件升级)
-
-# OTA网关子设备固件升级
-## 网关子设备固件升级简介
-
-设备固件升级又称 OTA,是物联网通信服务的重要组成部分。当物联设备有新功能或者需要修复漏洞时,设备可以通过 OTA 服务快速进行固件升级。请参考官网文档 控制台使用手册 [固件升级](https://cloud.tencent.com/document/product/634/14673)
-
-体验网关子设备固件升级需要在控制台中添加新的固件,请参考官网文档 开发者手册 [设备固件升级](https://cloud.tencent.com/document/product/634/14674)
-
-由于子设备无法直接和云端建立连接,网关设备延用设备OTA升级方式对子设备进行固件升级,支持网关子设备单台升级和批量升级。
\ No newline at end of file
diff --git "a/hub/doc/\347\275\221\345\205\263\350\256\276\345\244\207\346\213\223\346\211\221\345\205\263\347\263\273.md" "b/hub/doc/\347\275\221\345\205\263\350\256\276\345\244\207\346\213\223\346\211\221\345\205\263\347\263\273.md"
deleted file mode 100755
index 314855a..0000000
--- "a/hub/doc/\347\275\221\345\205\263\350\256\276\345\244\207\346\213\223\346\211\221\345\205\263\347\263\273.md"
+++ /dev/null
@@ -1,26 +0,0 @@
-* [网关设备拓扑关系](#网关设备拓扑关系)
- * [网关设备拓扑关系简介](#网关设备拓扑关系简介)
- * [运行示例程序体验绑定子设备功能](#运行示例程序体验绑定子设备功能)
- * [运行示例程序体验解绑子设备功能](#运行示例程序体验解绑子设备功能)
- * [运行示例程序体验查询设备拓扑关系功能](#运行示例程序体验查询设备拓扑关系功能)
-
-# 网关设备拓扑关系
-## 网关设备拓扑关系简介
-网关类型的设备可通过与云端进行数据通信,对其下的子设备进行绑定与解绑操作。
-
-实现此类功能需利用如下两个 Topic:
-
-* 数据上行 Topic(用于发布): `$gateway/operation/${productid}/${devicename}`
-* 数据下行 Topic(用于订阅): `$gateway/operation/result/${productid}/${devicename}`
-
-网关设备对其子设备的绑定与解绑的数据格式和参数说明,请参考官网 开发者手册[拓扑关系管理](https://cloud.tencent.com/document/product/634/45960)
-
-用户可通过网关设备查询网关子设备的拓扑关系。
-
-查询网关子设备也需要利用上面的两个Topic,请求的数据格式不同,如下:
-
-```
-{
- "type": "describe_sub_devices"
-}
-```
\ No newline at end of file
diff --git "a/hub/doc/\350\207\252\345\273\272\346\234\215\345\212\241\345\231\250\346\216\245\345\205\245.md" "b/hub/doc/\350\207\252\345\273\272\346\234\215\345\212\241\345\231\250\346\216\245\345\205\245.md"
new file mode 100755
index 0000000..db94c0d
--- /dev/null
+++ "b/hub/doc/\350\207\252\345\273\272\346\234\215\345\212\241\345\231\250\346\216\245\345\205\245.md"
@@ -0,0 +1,20 @@
+* [快速开始](#快速开始)
+ * [设置自建服务器的Broker地址](#设置自建服务器的Broker地址)
+ * [设置自建服务器对应的CA证书](#设置自建服务器对应的CA证书)
+ * [设置自建服务器对应的Websocket-MQTT连接域名](#设置自建服务器对应的Websocket-MQTT连接域名)
+ * [设置自建服务器对应的动态注册url](#设置自建服务器对应的动态注册url)
+ * [设置自建服务器的日志上报连接域名](#设置自建服务器的日志上报连接域名)
+
+
+# 快速开始
+本文将介绍如何基于腾讯云物联网通信IoT Hub SDK接入自建服务.
+
+## 设置自建服务器的Broker地址
+
+## 设置自建服务器对应的CA证书
+
+## 设置自建服务器对应的Websocket-MQTT连接域名
+
+## 设置自建服务器对应的动态注册url
+
+## 设置自建服务器的日志上报连接域名
\ No newline at end of file
diff --git "a/hub/doc/\350\256\276\345\244\207\344\272\222\351\200\232.md" "b/hub/doc/\350\256\276\345\244\207\344\272\222\351\200\232.md"
index 1605b72..24d3a0f 100644
--- "a/hub/doc/\350\256\276\345\244\207\344\272\222\351\200\232.md"
+++ "b/hub/doc/\350\256\276\345\244\207\344\272\222\351\200\232.md"
@@ -1,10 +1,9 @@
* [设备互通](#设备互通)
* [操作场景](#操作场景)
- * [编译运行示例程序](#编译运行示例程序)
- * [填写认证连接设备的参数](#填写认证连接设备的参数)
- * [连接认证介绍](#连接认证介绍)
- * [体验 Door 设备进门](#体验-Door-设备进门)
- * [体验 Door 设备出门](#体验-Door-设备出门)
+ * [运行示例](#运行示例)
+ * [填写认证连接设备的参数](#填写认证连接设备的参数)
+ * [体验 Door 设备进门](#体验-Door-设备进门)
+ * [体验 Door 设备出门](#体验-Door-设备出门)
# 设备互通
## 操作场景
@@ -12,3 +11,119 @@
体验设备互通,需要按照官网文档中创建两类智能设备(Door、AirConditioner)。 还需要配置规则引擎,请参考官网 [规则引擎概述](https://cloud.tencent.com/document/product/634/14446) 一章 , 将 [数据转发到另一Topic](https://cloud.tencent.com/document/product/634/14449)。
+## 运行示例
+运行 [DoorSample.py](../../hub/sample/scenarized/example_door.py) 可以体验进门时发送消息到空调设备.运行 [AircondSample.py](../../hub/sample/scenarized/example_aircond.py) 可以体验空调设备通过接收门设备的消息控制开关.
+
+#### 填写认证连接设备的参数
+体验设备互通需要创建两个设备,示例创建一个空调设备(AirConditioner)和一个门设备(door),体验进门时打开空调,出门时关闭空调.
+将在控制台创建的空调的设备信息填写到***aircond_device_info.json***,示例如下:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"AirConditioner1",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+将在控制台创建的门的设备信息填写到***door_device_info.json***,示例如下:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"door1",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+#### 体验 Door 设备进门
+示例程序模拟室温,初始值为25摄氏度,最高温为40摄氏度,最低温为-10摄氏度,空调关闭时室温每秒上升1摄氏度,打开后每秒下降1摄氏度.
+设备`door1`侧进门日志如下:
+```
+2021-07-21 14:59:53,618.618 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-21 14:59:53,618.618 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-21 14:59:54,000.000 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-21 14:59:54,061.061 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-21 14:59:54,061.061 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-21 14:59:54,619.619 [log.py:35] - DEBUG - publish {"action": "come_home", "targetDevice": "AirConditioner1"}
+2021-07-21 14:59:54,619.619 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m1), 'b'xxx/door1/event'', ... (68 bytes)
+2021-07-21 14:59:54,620.620 [log.py:35] - DEBUG - publish success
+2021-07-21 14:59:54,620.620 [log.py:35] - DEBUG - wait reply...
+2021-07-21 14:59:54,670.670 [client.py:2165] - DEBUG - Received PUBACK (Mid: 1)
+2021-07-21 14:59:54,670.670 [log.py:35] - DEBUG - on_publish:mid:1,userdata:None
+2021-07-21 14:59:55,621.621 [log.py:35] - DEBUG - disconnect
+2021-07-21 14:59:55,621.621 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-21 14:59:55,622.622 [log.py:35] - DEBUG - LoopThread thread exit
+```
+可以看到设备模拟进门,向空调设备发了一条`come_home`消息.
+设备`AirConditioner1`侧日志如下:
+```
+2021-07-21 14:59:48,270.270 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-21 14:59:48,270.270 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-21 14:59:48,650.650 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-21 14:59:48,715.715 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-21 14:59:48,715.715 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-21 14:59:49,272.272 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'xxx/AirConditioner1/control', 1)]
+2021-07-21 14:59:49,272.272 [log.py:35] - DEBUG - subscribe success topic:xxx/AirConditioner1/control
+2021-07-21 14:59:49,273.273 [log.py:35] - DEBUG - [air is close] temperature 25
+2021-07-21 14:59:49,320.320 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-21 14:59:49,320.320 [log.py:35] - DEBUG - on_subscribe:mid:1,granted_qos:1,userdata:None
+2021-07-21 14:59:50,274.274 [log.py:35] - DEBUG - [air is close] temperature 25
+2021-07-21 14:59:51,276.276 [log.py:35] - DEBUG - [air is close] temperature 26
+2021-07-21 14:59:52,278.278 [log.py:35] - DEBUG - [air is close] temperature 26
+2021-07-21 14:59:53,280.280 [log.py:35] - DEBUG - [air is close] temperature 27
+2021-07-21 14:59:54,283.283 [log.py:35] - DEBUG - [air is close] temperature 27
+2021-07-21 14:59:54,687.687 [client.py:2165] - DEBUG - Received PUBLISH (d0, q1, r0, m24), 'xxx/AirConditioner1/control', ... (68 bytes)
+2021-07-21 14:59:54,687.687 [client.py:2165] - DEBUG - Sending PUBACK (Mid: 24)
+2021-07-21 14:59:54,687.687 [log.py:35] - DEBUG - on_aircond_cb:payload:{"action": "come_home", "targetDevice": "AirConditioner1"},userdata:None
+2021-07-21 14:59:55,285.285 [log.py:35] - DEBUG - [air is open] temperature 28
+2021-07-21 14:59:56,288.288 [log.py:35] - DEBUG - [air is open] temperature 27
+2021-07-21 14:59:57,289.289 [log.py:35] - DEBUG - [air is open] temperature 27
+2021-07-21 14:59:58,291.291 [log.py:35] - DEBUG - [air is open] temperature 26
+2021-07-21 14:59:59,293.293 [log.py:35] - DEBUG - [air is open] temperature 26
+2021-07-21 15:00:00,299.299 [log.py:35] - DEBUG - [air is open] temperature 25
+2021-07-21 15:00:01,301.301 [log.py:35] - DEBUG - [air is open] temperature 25
+2021-07-21 15:00:02,311.311 [log.py:35] - DEBUG - [air is open] temperature 24
+2021-07-21 15:00:03,312.312 [log.py:35] - DEBUG - [air is open] temperature 24
+2021-07-21 15:00:04,314.314 [log.py:35] - DEBUG - [air is open] temperature 23
+2021-07-21 15:00:05,315.315 [log.py:35] - DEBUG - [air is open] temperature 23
+```
+可以看到空调收到进门消息前处于关闭状态,室温不断升高,收到进门消息后空调打开,室温逐渐下降.
+
+#### 体验 Door 设备出门
+设备`door1`侧出门日志如下:
+```
+2021-07-21 15:00:04,962.962 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-21 15:00:04,963.963 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-21 15:00:05,288.288 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-21 15:00:05,355.355 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-21 15:00:05,356.356 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-21 15:00:05,964.964 [log.py:35] - DEBUG - publish {"action": "leave_home", "targetDevice": "AirConditioner1"}
+2021-07-21 15:00:05,964.964 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q1, r0, m1), 'b'xxx/door1/event'', ... (69 bytes)
+2021-07-21 15:00:05,964.964 [log.py:35] - DEBUG - publish success
+2021-07-21 15:00:05,965.965 [log.py:35] - DEBUG - wait reply...
+2021-07-21 15:00:06,011.011 [client.py:2165] - DEBUG - Received PUBACK (Mid: 1)
+2021-07-21 15:00:06,012.012 [log.py:35] - DEBUG - on_publish:mid:1,userdata:None
+2021-07-21 15:00:06,966.966 [log.py:35] - DEBUG - disconnect
+2021-07-21 15:00:06,966.966 [client.py:2165] - DEBUG - Sending DISCONNECT
+2021-07-21 15:00:06,967.967 [log.py:35] - DEBUG - LoopThread thread exit
+2021-07-21 15:00:06,967.967 [log.py:35] - DEBUG - on_disconnect:rc:0,userdata:None
+```
+可以看到设备模拟出门,向空调设备发了一条`leave_home`消息.
+设备`AirConditioner1`侧日志如下:
+```
+2021-07-21 15:00:06,030.030 [client.py:2165] - DEBUG - Received PUBLISH (d0, q1, r0, m25), 'xxx/AirConditioner1/control', ... (69 bytes)
+2021-07-21 15:00:06,031.031 [client.py:2165] - DEBUG - Sending PUBACK (Mid: 25)
+2021-07-21 15:00:06,031.031 [log.py:35] - DEBUG - on_aircond_cb:payload:{"action": "leave_home", "targetDevice": "AirConditioner1"},userdata:None
+2021-07-21 15:00:06,318.318 [log.py:35] - DEBUG - [air is close] temperature 22
+2021-07-21 15:00:07,319.319 [log.py:35] - DEBUG - [air is close] temperature 23
+2021-07-21 15:00:08,321.321 [log.py:35] - DEBUG - [air is close] temperature 23
+2021-07-21 15:00:09,323.323 [log.py:35] - DEBUG - [air is close] temperature 24
+2021-07-21 15:00:10,325.325 [log.py:35] - DEBUG - [air is close] temperature 24
+2021-07-21 15:00:11,327.327 [log.py:35] - DEBUG - [air is close] temperature 25
+2021-07-21 15:00:12,329.329 [log.py:35] - DEBUG - [air is close] temperature 25
+```
+可以看到空调收到出门消息后关闭,室温逐渐又上升.
diff --git "a/hub/doc/\350\256\276\345\244\207\345\275\261\345\255\220.md" "b/hub/doc/\350\256\276\345\244\207\345\275\261\345\255\220.md"
index cccd4a1..6fd47bc 100755
--- "a/hub/doc/\350\256\276\345\244\207\345\275\261\345\255\220.md"
+++ "b/hub/doc/\350\256\276\345\244\207\345\275\261\345\255\220.md"
@@ -1,14 +1,10 @@
* [设备影子](#设备影子)
* [设备影子简介](#设备影子简介)
- * [填写认证连接设备的参数](#填写认证连接设备的参数)
- * [运行示例程序体验设备影子连接 IoT 云端](#运行示例程序体验设备影子连接-IoT-云端)
- * [体验设备影子断开连接](#体验设备影子断开连接)
- * [体验注册设备属性](#体验注册设备属性)
- * [体验定时更新设备影子](#体验定时更新设备影子)
- * [体验获取设备文档](#体验获取设备文档)
- * [体验订阅主题](#体验订阅主题)
- * [体验取消订阅主题](#体验取消订阅主题)
- * [体验发布主题](#体验发布主题)
+ * [运行示例](#运行示例)
+ * [填写认证连接设备的参数](#填写认证连接设备的参数)
+ * [获取云端缓存状态](#获取云端缓存状态)
+ * [修改设备影子状态](#修改设备影子状态)
+ * [定时更新设备影子](#定时更新设备影子)
# 设备影子
## 设备影子简介
@@ -17,4 +13,123 @@
作为中介,设备影子可以有效实现设备和用户应用之间的数据双向同步:
* 对于设备配置,用户应用不需要直接修改设备,只需要修改服务器端的设备影子,由设备影子同步到设备。即使当时设备不在线,设备上线后仍能从设备影子同步到最新配置。
-* 对于设备状态,设备将状态上报到设备影子,用户应用查询时,只需查询设备影子即可。这样可以有效减少设备和服务器端的网络交互,尤其是低功耗设备。
\ No newline at end of file
+* 对于设备状态,设备将状态上报到设备影子,用户应用查询时,只需查询设备影子即可。这样可以有效减少设备和服务器端的网络交互,尤其是低功耗设备。
+
+## 运行示例
+运行 [ShadowSample.py](../../hub/sample/shadow/example_shadow.py) 示例程序,可以体验设备影子相关操作。
+
+#### 填写认证连接设备的参数
+将在控制台创建设备时生成的设备信息填写到 [device_info.json](../../hub/sample/device_info.json)中,以密钥认证方式为例,主要关注`auth_mode`,`productId`,`deviceName`,`deviceSecret`字段,示例如下:
+```
+{
+ "auth_mode":"KEY",
+ "productId":"xxx",
+ "deviceName":"test01",
+ "key_deviceinfo":{
+ "deviceSecret":"xxxx"
+ }
+}
+```
+
+#### 获取云端缓存状态
+程序运行后订阅相关 Topic `$shadow/operation/result/{productID}/{deviceName}`,之后获取一次云端缓存.
+```
+2021-07-20 16:44:56,611.611 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 16:44:56,612.612 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 16:44:57,010.010 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 16:44:57,069.069 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 16:44:57,069.069 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 16:44:57,613.613 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$shadow/operation/result/xxx/test01', 0)]
+2021-07-20 16:44:57,614.614 [log.py:35] - DEBUG - subscribe success topic:$shadow/operation/result/xxx/test01
+2021-07-20 16:44:57,661.661 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 16:44:57,662.662 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-20 16:44:57,670.670 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (192 bytes)
+2021-07-20 16:44:57,670.670 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-0', 'payload': {'state': {'reported': {'updateCount': 0, 'updateCount11': 'shadow'}}, 'timestamp': 1626770626087, 'version': 8}, 'result': 0, 'timestamp': 1626770697, 'type': 'get'},userdata:None
+```
+观察日志,此时云端缓存中`updateCount`字段为0,`updateCount11`字段为`shadow`.
+
+#### 修改设备影子状态
+通过向 Topic `$shadow/operation/{productID}/{deviceName}`发送shadow GET命令来获取云端缓存的设备状态.
+```
+2021-07-20 16:44:57,615.615 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 12, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-1'}
+2021-07-20 16:44:57,615.615 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$shadow/operation/xxx/test01'', ... (120 bytes)
+2021-07-20 16:44:57,615.615 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-20 16:44:57,615.615 [log.py:35] - DEBUG - publish success
+2021-07-20 16:44:57,616.616 [log.py:35] - DEBUG - on_publish:mid:3,userdata:None
+```
+观察日志,更新云端缓存,将`updateCount`字段更新为12,新增字段`updateCount12`值为`shadow`.更新后再次获取云端缓存,日志如下
+```
+2021-07-20 16:44:58,688.688 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (219 bytes)
+2021-07-20 16:44:58,688.688 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-3', 'payload': {'state': {'reported': {'updateCount': 12, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626770698640, 'version': 10}, 'result': 0, 'timestamp': 1626770698, 'type': 'get'},userdata:None
+```
+观察日志,此时云端缓存的`updateCount`字段已更新为12,`updateCount11`字段为`shadow`,且新增字段`updateCount12`值为`shadow`.
+
+#### 定时更新设备影子
+示例中每隔3秒更新一次设备影子,更新内容为每次将`updateCount`字段值加1,并且每三次将云端缓存重置,日志如下
+```
+2021-07-20 16:58:40,724.724 [log.py:35] - DEBUG - LoopThread thread enter
+2021-07-20 16:58:40,724.724 [log.py:35] - DEBUG - connect_async (xxx.iotcloud.tencentdevices.com:8883)
+2021-07-20 16:58:41,185.185 [client.py:2165] - DEBUG - Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'xxxx'
+2021-07-20 16:58:41,245.245 [client.py:2165] - DEBUG - Received CONNACK (0, 0)
+2021-07-20 16:58:41,245.245 [log.py:35] - DEBUG - on_connect:flags:0,rc:0,userdata:None
+2021-07-20 16:58:41,726.726 [client.py:2165] - DEBUG - Sending SUBSCRIBE (d0, m1) [(b'$shadow/operation/result/xxx/test01', 0)]
+2021-07-20 16:58:41,726.726 [log.py:35] - DEBUG - subscribe success topic:$shadow/operation/result/xxx/test01
+2021-07-20 16:58:41,726.726 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 1, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-0'}
+2021-07-20 16:58:41,727.727 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m2), 'b'$shadow/operation/xxx/test01'', ... (119 bytes)
+2021-07-20 16:58:41,727.727 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:41,727.727 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m3), 'b'$shadow/operation/xxx/test01'', ... (46 bytes)
+2021-07-20 16:58:41,728.728 [log.py:35] - DEBUG - on_publish:mid:2,userdata:None
+2021-07-20 16:58:41,728.728 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:41,728.728 [log.py:35] - DEBUG - on_publish:mid:3,userdata:None
+2021-07-20 16:58:41,778.778 [client.py:2165] - DEBUG - Received SUBACK
+2021-07-20 16:58:41,779.779 [log.py:35] - DEBUG - on_subscribe:mid:0,granted_qos:1,userdata:None
+2021-07-20 16:58:41,795.795 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (174 bytes)
+2021-07-20 16:58:41,795.795 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-0', 'payload': {'state': {'reported': {'updateCount': 1}}, 'timestamp': 1626771521739, 'version': 21}, 'result': 0, 'timestamp': 1626771521739, 'type': 'update'},userdata:None
+2021-07-20 16:58:41,802.802 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (218 bytes)
+2021-07-20 16:58:41,802.802 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-1', 'payload': {'state': {'reported': {'updateCount': 1, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626771521739, 'version': 21}, 'result': 0, 'timestamp': 1626771521, 'type': 'get'},userdata:None
+2021-07-20 16:58:44,729.729 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 2, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-2'}
+2021-07-20 16:58:44,729.729 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m4), 'b'$shadow/operation/xxx/test01'', ... (119 bytes)
+2021-07-20 16:58:44,729.729 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:44,730.730 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m5), 'b'$shadow/operation/xxx/test01'', ... (46 bytes)
+2021-07-20 16:58:44,730.730 [log.py:35] - DEBUG - on_publish:mid:4,userdata:None
+2021-07-20 16:58:44,730.730 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:44,730.730 [log.py:35] - DEBUG - on_publish:mid:5,userdata:None
+2021-07-20 16:58:44,804.804 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (174 bytes)
+2021-07-20 16:58:44,805.805 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-2', 'payload': {'state': {'reported': {'updateCount': 2}}, 'timestamp': 1626771524751, 'version': 22}, 'result': 0, 'timestamp': 1626771524751, 'type': 'update'},userdata:None
+2021-07-20 16:58:44,810.810 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (218 bytes)
+2021-07-20 16:58:44,811.811 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-3', 'payload': {'state': {'reported': {'updateCount': 2, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626771524751, 'version': 22}, 'result': 0, 'timestamp': 1626771524, 'type': 'get'},userdata:None
+2021-07-20 16:58:47,734.734 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 3, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-4'}
+2021-07-20 16:58:47,734.734 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m6), 'b'$shadow/operation/xxx/test01'', ... (119 bytes)
+2021-07-20 16:58:47,734.734 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:47,735.735 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m7), 'b'$shadow/operation/xxx/test01'', ... (46 bytes)
+2021-07-20 16:58:47,735.735 [log.py:35] - DEBUG - on_publish:mid:6,userdata:None
+2021-07-20 16:58:47,735.735 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:47,736.736 [log.py:35] - DEBUG - on_publish:mid:7,userdata:None
+2021-07-20 16:58:47,808.808 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (174 bytes)
+2021-07-20 16:58:47,809.809 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-4', 'payload': {'state': {'reported': {'updateCount': 3}}, 'timestamp': 1626771527749, 'version': 23}, 'result': 0, 'timestamp': 1626771527749, 'type': 'update'},userdata:None
+2021-07-20 16:58:47,816.816 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (218 bytes)
+2021-07-20 16:58:47,817.817 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-5', 'payload': {'state': {'reported': {'updateCount': 3, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626771527749, 'version': 23}, 'result': 0, 'timestamp': 1626771527, 'type': 'get'},userdata:None
+2021-07-20 16:58:50,739.739 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 4, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-6'}
+2021-07-20 16:58:50,739.739 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m8), 'b'$shadow/operation/xxx/test01'', ... (119 bytes)
+2021-07-20 16:58:50,740.740 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:50,740.740 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m9), 'b'$shadow/operation/xxx/test01'', ... (46 bytes)
+2021-07-20 16:58:50,740.740 [log.py:35] - DEBUG - on_publish:mid:8,userdata:None
+2021-07-20 16:58:50,741.741 [log.py:35] - DEBUG - on_publish:mid:9,userdata:None
+2021-07-20 16:58:50,741.741 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:50,801.801 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (174 bytes)
+2021-07-20 16:58:50,801.801 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-6', 'payload': {'state': {'reported': {'updateCount': 4}}, 'timestamp': 1626771530765, 'version': 24}, 'result': 0, 'timestamp': 1626771530765, 'type': 'update'},userdata:None
+2021-07-20 16:58:50,808.808 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (218 bytes)
+2021-07-20 16:58:50,808.808 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-7', 'payload': {'state': {'reported': {'updateCount': 4, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626771530765, 'version': 24}, 'result': 0, 'timestamp': 1626771530, 'type': 'get'},userdata:None
+2021-07-20 16:58:53,745.745 [log.py:35] - DEBUG - [shadow update] {'type': 'update', 'state': {'reported': {'updateCount': 5, 'updateCount12': 'shadow'}}, 'clientToken': 'xxx-8'}
+2021-07-20 16:58:53,745.745 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m10), 'b'$shadow/operation/xxx/test01'', ... (119 bytes)
+2021-07-20 16:58:53,745.745 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:53,745.745 [client.py:2165] - DEBUG - Sending PUBLISH (d0, q0, r0, m11), 'b'$shadow/operation/xxx/test01'', ... (46 bytes)
+2021-07-20 16:58:53,746.746 [log.py:35] - DEBUG - on_publish:mid:10,userdata:None
+2021-07-20 16:58:53,746.746 [log.py:35] - DEBUG - publish success
+2021-07-20 16:58:53,746.746 [log.py:35] - DEBUG - on_publish:mid:11,userdata:None
+2021-07-20 16:58:53,821.821 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (174 bytes)
+2021-07-20 16:58:53,821.821 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-8', 'payload': {'state': {'reported': {'updateCount': 5}}, 'timestamp': 1626771533769, 'version': 25}, 'result': 0, 'timestamp': 1626771533769, 'type': 'update'},userdata:None
+2021-07-20 16:58:53,827.827 [client.py:2165] - DEBUG - Received PUBLISH (d0, q0, r0, m0), '$shadow/operation/result/xxx/test01', ... (218 bytes)
+2021-07-20 16:58:53,827.827 [log.py:35] - DEBUG - on_shadow_cb:payload:{'clientToken': 'xxx-9', 'payload': {'state': {'reported': {'updateCount': 5, 'updateCount11': 'shadow', 'updateCount12': 'shadow'}}, 'timestamp': 1626771533769, 'version': 25}, 'result': 0, 'timestamp': 1626771533, 'type': 'get'},userdata:None
+```
+观察日志,`updateCount`字段初始值为1,此后上报逐次家1,可以看到上报三次后获取到云端缓存`updateCount`字段值为3,此后将云端缓存重置一次,再次获取到的缓存为初始状态(`updateCount11`和`updateCount12`字段被清理),此后再上报一次`updateCount`字段值为4及`updateCount11`和`updateCount12`字段后云端缓存又从初始状态被更新.
\ No newline at end of file
diff --git "a/hub/doc/\350\256\276\345\244\207\347\212\266\346\200\201\344\270\212\346\212\245\344\270\216\347\212\266\346\200\201\350\256\276\347\275\256.md" "b/hub/doc/\350\256\276\345\244\207\347\212\266\346\200\201\344\270\212\346\212\245\344\270\216\347\212\266\346\200\201\350\256\276\347\275\256.md"
deleted file mode 100644
index a21e04c..0000000
--- "a/hub/doc/\350\256\276\345\244\207\347\212\266\346\200\201\344\270\212\346\212\245\344\270\216\347\212\266\346\200\201\350\256\276\347\275\256.md"
+++ /dev/null
@@ -1,17 +0,0 @@
-* [设备状态上报与状态设置](#设备状态上报与状态设置)
- * [操作场景](#操作场景)
- * [编译运行示例程序](#编译运行示例程序)
- * [填写认证连接设备的参数](#填写认证连接设备的参数)
- * [设备上报状态信息](#设备上报状态信息)
- * [设置设备目标温度](#设置设备目标温度)
- * [获取设备影子文档](#获取设备影子文档)
-
-# 设备状态上报与状态设置
-## 操作场景
-官网上假设的一个智能家居场景,结合腾讯云物联网通信设备端 IoT Hub Python-SDK 体验更新设备属性信息与获取设备影子文档。请参考官网 [场景二:设备状态上报与状态设置](https://cloud.tencent.com/document/product/634/11914)
-
-体验前提,需要按照官网文档中创建产品设备。请参考官网 [控制台使用手册-设备接入准备](https://cloud.tencent.com/document/product/634/14442) 。
-
-## 编译运行示例程序
-
-下载Hub Python SDK Demo示例代码,准备开发环境,检查SDK的依赖关系, [设备影子](https://cloud.tencent.com/document/product/634/11918) 以及 [设备影子数据流](https://cloud.tencent.com/document/product/634/14072) 了解设备影子开发。
\ No newline at end of file
diff --git a/hub/hub.py b/hub/hub.py
index 5af5e80..d95f7b0 100644
--- a/hub/hub.py
+++ b/hub/hub.py
@@ -16,12 +16,119 @@
import queue
import json
import base64
+import socket
+import time
+import random
+import urllib.request
+import urllib.parse
+import urllib.error
+from urllib.parse import urlparse
from enum import Enum
from enum import IntEnum
-from Crypto.Cipher import AES
+from hub.utils.codec import Codec
+from hub.utils.providers import TopicProvider
+from hub.utils.providers import DeviceInfoProvider
+from hub.utils.providers import ConnClientProvider
+from hub.utils.providers import LoggerProvider
+from hub.manager.manager import TaskManager
+from hub.services.gateway.gateway import Gateway
+from hub.services.rrpc.rrpc import Rrpc
+from hub.services.broadcast.broadcast import Broadcast
+from hub.services.shadow.shadow import Shadow
+from hub.services.ota.ota import Ota
+from hub.services.resourceManage.resourceManage import ResourceManage
+import os
+
+class SingletonType(type):
+ _instance_lock = threading.Lock()
+ def __call__(cls, *args, **kwargs):
+ if not hasattr(cls, "_instance"):
+ with SingletonType._instance_lock:
+ if not hasattr(cls, "_instance"):
+ cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)
+ return cls._instance
+
+class QcloudHub(metaclass=SingletonType):
+ """
+ 使用单例模式构建,保证对象只有一份
+ """
+ def __init__(self, device_file, userdata=None, tls=True, domain=None, useWebsocket=False):
+
+ self.hub = QcloudHubProvider(device_file, userdata=userdata, tls=tls, domain=domain, useWebsocket=useWebsocket)
+
+ def __new__(cls, *args, **kwargs):
+ return object.__new__(cls)
+
+class QcloudHubProvider(object):
+ """事件核心处理层
+ 作为explorer/user层与协议层的中间层,负责上下层通道建立、消息分发等事物
+ """
+ def __init__(self, device_file, userdata=None, tls=True, domain=None, useWebsocket=False):
+ self.__tls = tls
+ self.__useWebsocket = useWebsocket
+ self.__key_mode = True
+ self.__userdata = userdata
+ self.__provider = None
+ self.__protocol = None
+ self.__domain = domain
+ self.__host = ""
+ self.__log_provider = LoggerProvider()
+ self._logger = self.__log_provider.logger
+ self.__codec = Codec()
+ self.__gateway = None
+ self.__device_info = DeviceInfoProvider(device_file)
+ self.__resourceFilePath = None #资源文件路径
+
+ self.__hub_state = self.HubState.INITIALIZED
+ self._topic = TopicProvider(self.__device_info.product_id, self.__device_info.device_name)
+
+ self.__ntp_lock = threading.Lock()
+ self.__ntptime = self.NtpTime()
+
+ """存放explorer层注册到hub层的回调函数
+ 只存放explorer层独有的功能所需的回调(诸如数据模板),
+ 类似on_connect等explorer和用户层都可能注册的回调在hub层使用专门的函数与之对应
+ """
+ self.__explorer_callback = {}
+
+ """ 存放用户注册的回调函数 """
+ self.__user_callback = {}
+
+ """
+ 保存__on_subscribe()返回的mid和qos对,用以判断订阅是否成功
+ """
+ self.__subscribe_res = {}
+
+ self.__ota_map = {}
+ self.__rrpc_map = {}
+ self.__shadow_map = {}
+ self.__broadcast_map = {}
+ self.__resource_map = {}
+
+ self.__user_topics = []
+ self.__user_topics_subscribe_request = {}
+ self.__user_topics_unsubscribe_request = {}
+ self.__user_topics_request_lock = threading.Lock()
+ self.__user_topics_unsubscribe_request_lock = threading.Lock()
+
+ self.__loop_worker = self.LoopWorker()
+ self.__event_worker = self.EventWorker()
+ self.__register_event_callback()
+
+ """
+ hub层注册到mqtt的回调
+ """
+ self.__user_on_connect = None
+ self.__user_on_disconnect = None
+ self.__user_on_publish = None
+ self.__user_on_subscribe = None
+ self.__user_on_unsubscribe = None
+ self.__user_on_message = None
+
+ self.__protocol_init(domain, useWebsocket)
-class QcloudHub(object):
class HubState(Enum):
+ """ 连接状态 """
INITIALIZED = 1
CONNECTING = 2
CONNECTED = 3
@@ -29,522 +136,1883 @@ class HubState(Enum):
DISCONNECTED = 5
DESTRUCTING = 6
DESTRUCTED = 7
-
+
+ class ErrorCode(Enum):
+ ERR_NONE = 0 # 成功
+ ERR_TOPIC_NONE = -1000 # topic为空
+
+
class StateError(Exception):
def __init__(self, err):
Exception.__init__(self, err)
- # 用户注册回调分发线程
- class UserCallBackTask(object):
- def __init__(self, logger=None):
- self.__logger = logger
- if self.__logger is not None:
- self.__logger.info("UserCallBackTask init")
- self.__message_queue = queue.Queue(20)
- self.__cmd_callback = {}
- self.__started = False
- self.__exited = False
- self.__thread = None
- pass
+ class NtpTime(object):
+ def __init__(self):
+ self._ntp_recvied = False
+ """
+ ntp请求的发送和接收时间戳
+ """
+ self._ntp_send_timestamp = 0
+ self._ntp_recv_timestamp = 0
+ """
+ 从平台获取的ntptime时间戳
+ """
+ self._ntptime1 = 0
+ self._ntptime2 = 0
+
+ class LoggerLevel(Enum):
+ INFO = "info"
+ DEBUG = "debug"
+ WARNING = "warring"
+ ERROR = "error"
+
+ class device_property(object):
+ def __init__(self):
+ self.key = ""
+ self.data = ""
+ self.data_buff_len = 0
+ self.type = ""
+
+ # 管理连接相关资源
+ class LoopWorker(object):
+ """ mqtt连接管理维护 """
+ def __init__(self):
+ self._connect_async_req = False
+ self._exit_req = True
+ self._runing_state = False
+ self._exit_req_lock = threading.Lock()
+ self._thread = TaskManager.LoopThread()
+
+ class EventWorker(object):
+ """ 事件管理 """
+ def __init__(self):
+ self._thread = TaskManager.EventCbThread()
+
+ def _register_event_callback(self, connect, disconnect,
+ message, publish, subscribe, unsubscribe):
+ self._thread.register_event_callback(self.EventPool.CONNECT, connect)
+ self._thread.register_event_callback(self.EventPool.DISCONNECT, disconnect)
+ self._thread.register_event_callback(self.EventPool.MESSAGE, message)
+ self._thread.register_event_callback(self.EventPool.PUBLISH, publish)
+ self._thread.register_event_callback(self.EventPool.SUBSCRISE, subscribe)
+ self._thread.register_event_callback(self.EventPool.UNSUBSCRISE, unsubscribe)
+
+ self._thread.start()
+
+ class EventPool(object):
+ CONNECT = "connect"
+ DISCONNECT = "disconnect"
+ MESSAGE = "message"
+ PUBLISH = "publish"
+ SUBSCRISE = "subscribe"
+ UNSUBSCRISE = "unsubscribe"
- def register_callback_with_cmd(self, cmd, callback):
- if self.__started is False:
- if cmd != "req_exit":
- self.__cmd_callback[cmd] = callback
- return 0
+ class sReplyPara(object):
+ def __init__(self):
+ self.timeout_ms = 0
+ self.code = -1
+ self.status_msg = None
+
+ def __assert(self, param):
+ if param is None or len(param) == 0:
+ raise ValueError('Invalid param.')
+
+ def __register_event_callback(self):
+ self.__event_worker._register_event_callback(self.__user_connect,
+ self.__user_disconnect,
+ self.__user_message,
+ self.__user_publish,
+ self.__user_subscribe,
+ self.__user_unsubscribe)
+
+ """
+ 处理用户回调
+ 基于explorer接入时会将用户回调赋值到本层用户回调函数
+ """
+ def __user_connect(self, value):
+ # client, user_data, session_flag, rc = value
+ session_flag, rc = value
+ if self.__user_on_connect is not None:
+ try:
+ self.__user_on_connect(session_flag['session present'], rc, self.__userdata)
+ except Exception as e:
+ self._logger.error("on_connect process raise exception:%r" % e)
+ pass
+
+ def __user_disconnect(self, value):
+ self.__user_on_disconnect(value, self.__userdata)
+ pass
+
+ def __user_publish(self, value):
+ self.__user_on_publish(value, self.__userdata)
+ pass
+
+ def __user_subscribe(self, value):
+ qos, mid = value
+ self.__user_on_subscribe(qos, mid, self.__userdata)
+ pass
+
+ def __user_unsubscribe(self, value):
+ self.__user_on_unsubscribe(value, self.__userdata)
+ pass
+
+ def __user_message(self, value):
+ message = value
+ topic = message.topic
+ qos = message.qos
+ payload = json.loads(message.payload.decode('utf-8'))
+ # print(">>>>>>> from qcloud:%s, topic:%s" % (payload, topic))
+
+ topic_prefix = topic[0:topic.find("/")]
+
+ pos = topic.rfind("/")
+ device_name = topic[pos + 1:len(topic)]
+
+ topic_split = topic[0:pos]
+ pos = topic_split.rfind("/")
+ product_id = topic_split[pos + 1:len(topic_split)]
+ client = product_id + device_name
+
+ if topic_prefix == "$thing" or topic_prefix == "$template":
+ if topic == self._topic.template_raw_topic_sub:
+ # 调用explorer向hub注册的回调处理
+ self._logger.info("Reserved: template raw topic")
+
+ elif topic == self._topic.template_topic_sub:
+ if self.__user_callback[topic] is not None:
+ self.__user_callback[topic](topic, qos, payload, self.__userdata)
+ else:
+ self._logger.error("no callback for topic %s" % topic)
+ else:
+ if self.__explorer_callback[topic] is not None:
+ self.__explorer_callback[topic](topic, qos, payload, self.__userdata)
else:
- return 1
+ self._logger.error("no callback for topic %s" % topic)
+
+ elif topic_prefix == "$sys":
+ # 获取时间作为内部服务,不通知到用户
+ if (('type' in payload and payload["type"] == "get") and
+ ('time' in payload and payload["time"] is not None)):
+ self.__ntptime._ntp_recvied = True
+ self.__ntptime._ntp_recv_timestamp = int(time.time() * 1000)
+ self.__ntptime._ntptime1 = payload["ntptime1"]
+ self.__ntptime._ntptime2 = payload["ntptime2"]
+ else:
+ self.__user_on_message(topic, qos, payload, self.__userdata)
+
+ elif topic_prefix == "$gateway":
+ self.__gateway.handle_gateway(topic, payload)
+
+ elif topic_prefix == "$ota":
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[template] not found template handle for client:%s" % (client))
+ return None
+
+ ota = self.__ota_map[client]
+ ota.handle_ota(topic, qos, payload, self.__userdata)
+
+ elif topic_prefix == "$rrpc":
+ # topic:$rrpc/rxd/${productID}/${deviceName}/${processID}
+ pos = topic.rfind("/")
+ topic_split1 = topic[0:pos]
+
+ pos = topic_split1.rfind("/")
+ device_name = topic_split1[pos + 1:len(topic_split1)]
+
+ topic_split = topic_split1[0:pos]
+ pos = topic_split.rfind("/")
+ product_id = topic_split[pos + 1:len(topic_split)]
+
+ client = product_id + device_name
+ if (client not in self.__rrpc_map.keys()
+ or self.__rrpc_map[client] is None):
+ self._logger.error("[template] not found template handle for client:%s" % (client))
+ return None
+
+ rrpc = self.__rrpc_map[client]
+ rrpc.handle_rrpc(topic, qos, payload, self.__userdata)
+
+ elif topic_prefix == "$shadow":
+ if (client not in self.__shadow_map.keys()
+ or self.__shadow_map[client] is None):
+ self._logger.error("[template] not found template handle for client:%s" % (client))
+ return None
+
+ shadow = self.__shadow_map[client]
+ shadow.handle_shadow(topic, qos, payload, self.__userdata)
+
+ elif topic_prefix == "$broadcast":
+ if (client not in self.__broadcast_map.keys()
+ or self.__broadcast_map[client] is None):
+ self._logger.error("[template] not found template handle for client:%s" % (client))
+ return None
+
+ broadcast = self.__broadcast_map[client]
+ broadcast.handle_broadcast(topic, qos, payload, self.__userdata)
+
+ elif topic_prefix == "$resource":
+ if (client not in self.__resource_map.keys()
+ or self.__resource_map[client] is None):
+ self._logger.error("[template] not found template handle for client:%s" % (client))
+ return None
+
+ resource = self.__resource_map[client]
+ resource.handle_resource(topic, qos, payload, self.__userdata)
+
+ elif topic in self.__user_topics:
+ if self.__user_callback[topic] is not None:
+ self.__user_callback[topic](topic, qos, payload, self.__userdata)
+ else:
+ self._logger.error("no callback for topic %s" % topic)
+
+ else:
+ if self.__explorer_callback[topic] is not None:
+ self.__explorer_callback[topic](topic, qos, payload, self.__userdata)
+ else:
+ self._logger.error("unknow topic:%s" % topic)
+ pass
+
+ def __on_connect(self, client, user_data, session_flag, rc):
+ if rc == 0:
+ self.__protocol.reset_reconnect_wait()
+ self.__hub_state = self.HubState.CONNECTED
+ self.__event_worker._thread.post_message(self.__event_worker.EventPool.CONNECT, (session_flag, rc))
+
+ pass
+
+ def __on_disconnect(self, client, user_data, rc):
+ if self.__hub_state == self.HubState.DISCONNECTING:
+ self.__hub_state = self.HubState.DISCONNECTED
+ elif self.__hub_state == self.HubState.DESTRUCTING:
+ self.__hub_state = self.HubState.DESTRUCTED
+ elif self.__hub_state == self.HubState.CONNECTED:
+ self.__hub_state = self.HubState.DISCONNECTED
+ else:
+ self._logger.error("state error:%r" % self.__hub_state)
+ return
+
+ self.__user_topics_subscribe_request.clear()
+ self.__user_topics_unsubscribe_request.clear()
+ self.__user_topics.clear()
+
+ if self.__gateway is not None:
+ self.__gateway.gateway_reset()
+
+ """
+ 将disconnect事件通知到explorer
+ """
+ ex_topic = "$explorer/from/disconnect"
+ if ex_topic in self.__explorer_callback:
+ if self.__explorer_callback[ex_topic] is not None:
+ self.__explorer_callback[ex_topic](client, self.__userdata, rc)
+ else:
+ self._logger.error("no callback for topic %s" % ex_topic)
+
+ self.__event_worker._thread.post_message(self.__event_worker.EventPool.DISCONNECT, (rc))
+ if self.__hub_state == self.HubState.DESTRUCTED:
+ self.__event_worker._thread.stop()
+
+ def __on_message(self, client, user_data, message):
+ self.__event_worker._thread.post_message(self.__event_worker.EventPool.MESSAGE, (message))
+
+ def __on_publish(self, client, user_data, mid):
+ self.__event_worker._thread.post_message(self.__event_worker.EventPool.PUBLISH, (mid))
+
+ def __on_subscribe(self, client, user_data, mid, granted_qos):
+ qos = granted_qos[0]
+ # todo:mid,qos
+ self.__subscribe_res[mid] = qos
+ self.__event_worker._thread.post_message(self.__event_worker.EventPool.SUBSCRISE, (qos, mid))
+
+ def __on_unsubscribe(self, client, user_data, mid):
+ self.__event_worker._thread.post_message(self.__event_worker.EventPool.UNSUBSCRISE, (mid))
+
+ def _loop(self):
+ if self.__hub_state not in (self.HubState.INITIALIZED,
+ self.HubState.DISCONNECTED):
+ raise self.StateError("current state is not in INITIALIZED or DISCONNECTED")
+ self.__hub_state = self.HubState.CONNECTING
+
+ if self.__protocol.connect() is not True:
+ self.__hub_state = self.HubState.INITIALIZED
+ return
+
+ while True:
+ if self.__loop_worker._exit_req:
+ if self.__hub_state == self.HubState.DESTRUCTING:
+ self.__loop_worker._thread.stop()
+ self.__hub_state = self.HubState.DESTRUCTED
+ break
+ try:
+ self.__hub_state = self.HubState.CONNECTING
+ """
+ 实际连接
+ """
+ self.__protocol.reconnect()
+ except (socket.error, OSError) as e:
+ self._logger.error("mqtt reconnect error:" + str(e))
+ # 失败处理 待添加
+ if self.__hub_state == self.HubState.CONNECTING:
+ self.__hub_state = self.HubState.DISCONNECTED
+ self.__protocol.reset_reconnect_wait()
+
+ if self.__hub_state == self.HubState.DESTRUCTING:
+ self.__loop_worker._thread.stop()
+ self.__hub_state = self.HubState.DESTRUCTED
+ break
+ self.__protocol.reconnect_wait()
+ continue
+ """
+ 调用循环调用mqtt loop读取消息
+ """
+ self.__protocol.loop()
+ """
+ mqtt loop接口失败(异常导致的disconnect)
+ 1.将disconnect事件通知到用户
+ 2.清理sdk相关资源
+ """
+ if self.__hub_state == self.HubState.CONNECTED:
+ self.__on_disconnect(None, None, -1)
+ """
+ 清理线程资源
+ """
+ if self.__loop_worker._exit_req:
+ if self.__hub_state == self.HubState.DESTRUCTING:
+ self.__loop_worker._thread.stop()
+ self.__hub_state = self.HubState.DESTRUCTED
+ break
+ self.__protocol.reconnect_wait()
+ pass
+
+ def registerMqttCallback(self, on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe):
+ """Register user mqtt callback
+
+ Register user mqtt callback for mqtt
+ Args:
+ on_connect: mqtt connect callback
+ on_disconnect: mqtt disconnect callback
+ on_message: mqtt message callback
+ on_publish: mqtt publish callback
+ on_subscribe: mqtt subscribe callback
+ on_unsubscribe: mqtt unsubscribe callback
+ Returns:
+ success: default
+ fail: default
+ """
+ self.__user_on_connect = on_connect
+ self.__user_on_disconnect = on_disconnect
+ self.__user_on_message = on_message
+ self.__user_on_publish = on_publish
+ self.__user_on_subscribe = on_subscribe
+ self.__user_on_unsubscribe = on_unsubscribe
+
+ def registerUserCallback(self, topic, callback):
+ """Register user callback
+
+ Register user callback for a topic
+ Args:
+ topic: topic
+ callback: user callback
+ Returns:
+ success: default
+ fail: default
+ """
+ self.__user_topics.append(topic)
+ self.__user_callback[topic] = callback
+
+ def isMqttConnected(self):
+ """Is mqtt connected
+
+ Is mqtt connected
+ Args: None
+ Returns:
+ success: True/False
+ """
+ return self.__hub_state == self.HubState.CONNECTED
+
+ def getConnectState(self):
+ """Get connect state
+
+ Get device current connect state
+ Args: None
+ Returns:
+ success: connect state
+ """
+ return self.__hub_state
+
+ def register_explorer_callback(self, topic, callback):
+ """
+ 注册explorer层回调到hub层
+ topic可为单个topic或topic列表
+ """
+ if isinstance(topic, str):
+ if topic is not None or len(topic) > 0:
+ self.__explorer_callback[topic] = callback
+ if isinstance(topic, list):
+ for tp in topic:
+ self.__explorer_callback[tp] = callback
+ pass
+
+ def __protocol_reinit(self):
+ self.__protocol_init(self.__domain, self.__useWebsocket)
+
+ # 连接协议(mqtt/websocket)初始化
+ def __protocol_init(self, domain=None, useWebsocket=False):
+ auth_mode = self.__device_info.auth_mode
+ device_name = self.__device_info.device_name
+ product_id = self.__device_info.product_id
+ device_secret = self.__device_info.device_secret
+ ca = self.__device_info.ca_file
+ cert = self.__device_info.cert_file
+ key = self.__device_info.private_key_file
+
+ """
+ 由于ConnClientProvider是单例模式,因此没有device secret不能创建protocol对象
+ """
+ if device_secret == "YOUR_DEVICE_SECRET":
+ self._logger.error("device secret invalid!")
+ return
+
+ """
+ 腾讯hub设备 海外版的domain需要按照官网格式拼接:product_id
+ 客户自定制的私有化 domain 透传
+ """
+ if useWebsocket is False:
+ if domain is None or domain == "":
+ self.__host = product_id + ".iotcloud.tencentdevices.com"
+ else:
+ if domain is None or domain == "":
+ self.__host = product_id + ".ap-guangzhou.iothub.tencentdevices.com"
+
+ if not (domain is None or domain == ""):
+ self.__host = domain
+
+ self.__provider = ConnClientProvider(self.__host, product_id, device_name, device_secret,
+ websocket=useWebsocket, tls=self.__tls, logger=self._logger)
+ self.__protocol = self.__provider.protocol
+
+ if auth_mode == "CERT":
+ self.__protocol.set_cert_file(ca, cert, key)
+
+ self.__protocol.register_event_callbacks(self.__on_connect,
+ self.__on_disconnect,
+ self.__on_message,
+ self.__on_publish,
+ self.__on_subscribe,
+ self.__on_unsubscribe)
+ pass
+
+ def setReconnectInterval(self, max_sec, min_sec):
+ """Set mqtt reconnect interval
+
+ Set mqtt reconnect interval
+ Args:
+ max_sec: reconnect max time
+ min_sec: reconnect min time
+ Returns:
+ success: default
+ fail: default
+ """
+ if self.__protocol is None:
+ self._logger.error("Set failed: client is None")
+ return
+ self.__protocol.set_reconnect_interval(max_sec, min_sec)
+ self.__protocol.config_connect()
+
+ def setMessageTimout(self, timeout):
+ """Set message overtime time
+
+ Set message overtime time
+ Args:
+ timeout: mqtt keepalive value
+ Returns:
+ success: default
+ fail: default
+ """
+ if self.__protocol is None:
+ self._logger.error("Set failed: client is None")
+ return
+ self.__protocol.set_message_timout(timeout)
+
+ def setKeepaliveInterval(self, interval):
+ """Set mqtt keepalive interval
+
+ Set mqtt keepalive interval
+ Args:
+ interval: mqtt keepalive interval
+ Returns:
+ success: default
+ fail: default
+ """
+ if self.__protocol is None:
+ self._logger.error("Set failed: client is None")
+ return
+ self.__protocol.set_keepalive_interval(interval)
+
+ def getProductID(self):
+ """Get product id
+
+ Get product id
+ Args: None
+ Returns:
+ success: product id
+ fail: None
+ """
+ return self.__device_info.product_id
+
+ def getDeviceName(self):
+ """Get device name
+
+ Get device name
+ Args: None
+ Returns:
+ success: device name
+ fail: None
+ """
+ return self.__device_info.device_name
+
+ def getNtpAccurateTime(self):
+ """Get NTP time
+
+ Get NTP time
+ Args: None
+ Returns:
+ success: device current accurate timestamp
+ fail: -1
+ """
+ timestamp = -1
+
+ with self.__ntp_lock:
+ sys_topic_sub = self._topic.sys_topic_sub
+ sys_topic_pub = self._topic.sys_topic_pub
+ rc, mid = self.subscribe(sys_topic_sub, 0)
+ if rc == 0:
+ payload = {
+ "type": "get",
+ "resource": [
+ "time"
+ ],
+ }
+
+ self.__ntptime._ntp_send_timestamp = int(time.time() * 1000)
+ self.publish(sys_topic_pub, payload, 0)
+ else:
+ self._logger.error("[sys] subscribe error:rc:%d,topic:%s" % (rc, sys_topic_sub))
+ self.unsubscribe(sys_topic_sub)
+ return timestamp
+
+ cnt = 0
+ while cnt < 3:
+ if self.__ntptime._ntp_recvied is True:
+ timestamp = (self.__ntptime._ntptime1 + self.__ntptime._ntptime2 +
+ self.__ntptime._ntp_recv_timestamp - self.__ntptime._ntp_send_timestamp) / 2
+ break
pass
+ time.sleep(0.5)
+ cnt += 1
+
+ self.unsubscribe(sys_topic_sub)
+ return timestamp
+
+ def connect(self):
+ """Connect
+
+ Device connect
+ Args: None
+ Returns:
+ success: thread start result
+ fail: thread start result
+ """
+ self.__loop_worker._connect_async_req = True
+ with self.__loop_worker._exit_req_lock:
+ self.__loop_worker._exit_req = False
+ return self.__loop_worker._thread.start(self._loop)
+
+ def disconnect(self):
+ """Disconnect
+
+ Device disconnect
+ Args: None
+ Returns:
+ success: default
+ fail: default
+ """
+ self._logger.debug("disconnect")
+ if self.__hub_state is not self.HubState.CONNECTED:
+ raise self.StateError("current state is not CONNECTED")
+ self.__hub_state = self.HubState.DISCONNECTING
+ if self.__loop_worker._connect_async_req is True:
+ with self.__loop_worker._exit_req_lock:
+ self.__loop_worker._exit_req = True
+
+ self.__protocol.disconnect()
+ self.__loop_worker._thread.stop()
+
+ def subscribe(self, topic, qos):
+ """Subscribe topic
+
+ Subscribe topic
+ Args:
+ topic: topic
+ qos: mqtt qos
+ Returns:
+ success: zero and subscribe mid
+ fail: negative number and subscribe mid
+ """
+ if self.__hub_state is not self.HubState.CONNECTED:
+ raise self.StateError("current state is not CONNECTED")
+ if isinstance(topic, tuple):
+ topic, qos = topic
+ if isinstance(topic, str):
+ self.__user_topics_request_lock.acquire()
+ rc, mid = self.__protocol.subscribe(topic, qos)
+ if rc == 0:
+ self.__user_topics_subscribe_request[mid] = [(topic, qos)]
+ self.__user_topics_request_lock.release()
+ return rc, mid
+ # topic format [(topic1, qos),(topic2,qos)]
+ if isinstance(topic, list):
+ self.__user_topics_request_lock.acquire()
+ rc, mid = self.__protocol.subscribe(topic)
+ if rc == 0:
+ self.__user_topics_subscribe_request[mid] = [topic]
+ self.__user_topics_request_lock.release()
+ return rc, mid
+ pass
+
+ def unsubscribe(self, topic):
+ """Unsubscribe topic
+
+ Unsubscribe topic what is subscribed
+ Args:
+ topic: topic
+ Returns:
+ success: zero and unsubscribe mid
+ fail: negative number and unsubscribe mid
+ """
+ if self.__hub_state is not self.HubState.CONNECTED:
+ raise self.StateError("current state is not CONNECTED")
+ unsubscribe_topics = []
+ if topic is None or len(topic) == 0:
+ raise ValueError('Invalid topic.')
+ if isinstance(topic, str):
+ # topic判断
+ unsubscribe_topics.append(topic)
+ elif isinstance(topic, list):
+ for tp in topic:
+ unsubscribe_topics.append(tp)
+ pass
+ with self.__user_topics_unsubscribe_request_lock:
+ if len(unsubscribe_topics) == 0:
+ return self.ErrorCode.ERR_TOPIC_NONE, -1
+ rc, mid = self.__protocol.unsubscribe(unsubscribe_topics)
+ if rc == 0:
+ self.__user_topics_unsubscribe_request[mid] = unsubscribe_topics
+ return rc, mid
+ pass
+
+ def publish(self, topic, payload, qos):
+ """Publish message
+
+ Publish message
+ Args:
+ topic: topic
+ payload: publish message
+ qos: mqtt qos
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ if self.__hub_state is not self.HubState.CONNECTED:
+ raise self.StateError("current state is not CONNECTED")
+ if topic is None or len(topic) == 0:
+ raise ValueError('Invalid topic.')
+ if qos < 0:
+ raise ValueError('Invalid QoS level.')
+
+ return self.__protocol.publish(topic, json.dumps(payload), qos)
+
+ def dynregDevice(self, timeout=10, dynregDomain=None):
+ """Dynamic register
+
+ Get the device secret from the Cloud
+ Args:
+ timeout: request timeout
+ Returns:
+ success: return zero and device secret
+ fail: -1 and error message
+ """
+
+ sign_format = '%s\n%s\n%s\n%s\n%s\n%d\n%d\n%s'
+ request_format = "{\"ProductId\":\"%s\",\"DeviceName\":\"%s\"}"
+
+ device_name = self.__device_info.device_name
+ product_id = self.__device_info.product_id
+ product_secret = self.__device_info.product_secret
+
+ request_text = request_format % (product_id, device_name)
+ request_hash = self.__codec.Hash.sha256_encode(request_text.encode("utf-8"))
+
+ nonce = random.randrange(2147483647)
+ timestamp = int(time.time())
+
+ """
+ 默认domain为国内
+ 腾讯hub设备 海外版的domain需要按照官网格式拼接
+ 客户自定制的私有化 domain 透传
+ """
+ if dynregDomain is None or dynregDomain == "":
+ dynregHost = '%s://ap-guangzhou.gateway.tencentdevices.com/device/register'
+ dynregSignContent = sign_format % (
+ "POST", "ap-guangzhou.gateway.tencentdevices.com",
+ "/device/register", "", "hmacsha256", timestamp,
+ nonce, request_hash)
+ else:
+ dynregHost = dynregDomain
+ parsed = urlparse(dynregDomain)
+ dynregSignContent = sign_format % (
+ "POST", parsed.hostname,
+ parsed.path, "", "hmacsha256", timestamp,
+ nonce, request_hash)
+
+ url_format = dynregHost
+
+ sign_content = dynregSignContent
+ sign_base64 = self.__codec.Base64.encode(self.__codec.Hmac.sha256_encode(product_secret.encode("utf-8"),
+ sign_content.encode("utf-8")))
+
+ header = {
+ 'Content-Type': 'application/json; charset=utf-8',
+ "X-TC-Algorithm": "hmacsha256",
+ "X-TC-Timestamp": timestamp,
+ "X-TC-Nonce": nonce,
+ "X-TC-Signature": sign_base64
+ }
+ data = bytes(request_text, encoding='utf-8')
+
+ context = None
+ if dynregDomain is None or dynregDomain == "":
+ if self.__tls:
+ request_url = url_format % 'https'
+ context = self.__codec.Ssl().create_content()
+ else:
+ request_url = url_format % 'http'
+ else:
+ request_url = dynregDomain
+ if self.__tls:
+ context = self.__codec.Ssl().create_content()
+
+ self._logger.info('dynreg url {}'.format(request_url))
+ req = urllib.request.Request(request_url, data=data, headers=header)
+ with urllib.request.urlopen(req, timeout=timeout, context=context) as url_file:
+ reply_data = url_file.read().decode('utf-8')
+ reply_obj = json.loads(reply_data)
+ self._logger.debug(f"动态注册请求: {data}\n响应: {reply_obj}\n")
+ resp = reply_obj['Response']
+ if 'Len' in resp and resp['Len'] > 0:
+ reply_obj_data = reply_obj['Response']["Payload"]
+ if reply_obj_data is not None:
+ payload = self.__codec._AESUtil.decrypt(reply_obj_data.encode('UTF-8') , product_secret[:self.__codec._AESUtil.BLOCK_SIZE_16].encode('UTF-8'),
+ '0000000000000000'.encode('UTF-8'))
+ payload = payload.decode('UTF-8', 'ignore').strip().strip(b'\x00'.decode())
+ user_dict = json.loads(payload)
+ self._logger.info('encrypt type: {}'.format(
+ user_dict['encryptionType']))
+
+ encryptionType = user_dict['encryptionType']
+
+ if encryptionType is not None:
+ if encryptionType == 2: #2表示密钥认证
+ self.__device_info.update_config_file(user_dict['psk'])
+ self.__protocol_reinit()
+ return 0, user_dict['psk']
+
+ elif encryptionType == 1: #1表示证书认证
+ #获取证书和秘钥
+ cert = user_dict['clientCert']
+ privateKey = user_dict['clientKey']
+ #cert文件创建路径
+ current_path = os.getcwd()
+ cert_file_path = os.path.join(current_path,'cert.txt')
+ cert_file = open(cert_file_path,'w')
+ cert_file.write(cert)
+ cert_file.close()
+ #privateKey文件创建路径
+ privateKey_file_path = os.path.join(current_path,'privateKey.txt')
+ privateKey_file = open(privateKey_file_path,'w')
+ privateKey_file.write(privateKey)
+ privateKey_file.close()
+
+ #替换json文件对应路径
+ self.__device_info.update_cert_config_file(cert_file_path)
+ self.__device_info.update_privateKey_config_file(privateKey_file_path)
+ self.__protocol_reinit()
+
+ return 0, 'success'
+ else:
+ self._logger.warring('encryptionType is other type')
+ return -1, 'encryptionType is other type'
+ else:
+ self._logger.warring('encryptionType is null')
+ return -1, 'encryptionType is null'
+ else:
+ self._logger.warring('payload is null')
+ return -1, 'payload is null'
+ else:
+ err_code = resp['Error']
+ self._logger.error('code: {}, error message: {}'.format(
+ err_code, err_code['Message']))
+ return -1, err_code['Message']
+
+ def httpDevice(self,topicName=None,payload=None,qos=0,timeout=10,httpDomain=None):
+ """http device report
+
+ Args:
+ payload: 发布消息的内容,string类型
+ topicName:发布消息的 Topic 名称
+ httpDomain:默认domain为国内,腾讯hub设备 海外版的domain需要按照官网格式拼接,客户自定制的私有化 domain 透传
+ """
+ sign_format = '%s\n%s\n%s\n%s\n%s\n%d\n%d\n%s'
+ request_format = "{\"ProductId\":\"%s\",\"DeviceName\":\"%s\",\"TopicName\":\"%s\",\"Payload\":\"%s\",\"Qos\":%d}"
+
+ if payload is None or payload == "":
+ payload = "test"
+
+ qosLevel = qos
+ payloadString = payload
+ device_name = self.__device_info.device_name
+ product_id = self.__device_info.product_id
+ device_secret = self.__device_info.device_secret
+ auth_mode = self.__device_info.auth_mode
+ nonce = random.randrange(2147483647)
+ timestamp = int(time.time())
+ if topicName is None or topicName == "":
+ topicName = product_id + '/' + device_name + '/data'
+
+ request_text = request_format % (product_id, device_name, topicName, payloadString, qosLevel)
+
+
+ if not (auth_mode == "CERT"):
+ #密钥认证
+ encodeType = "hmacsha256"
+ else:
+ #证书认证
+ encodeType = "rsa-sha256"
+
+
+ request_hash = self.__codec.Hash.sha256_encode(request_text.encode("utf-8"))
+ self._logger.info("request_text:%s",request_text)
+ if httpDomain is None or httpDomain == "":
+ httpHost = '%s://' + 'ap-guangzhou.gateway.tencentdevices.com' + '/device/publish'
+ sign_content = sign_format % (
+ "POST", "ap-guangzhou.gateway.tencentdevices.com",
+ "/device/publish", "", encodeType, timestamp,
+ nonce, request_hash)
+ else:
+ httpHost = httpDomain
+ parsed = urlparse(httpDomain)
+ sign_content = sign_format % (
+ "POST", parsed.hostname,
+ parsed.path, "", encodeType, timestamp,
+ nonce, request_hash)
+
+ url_format = httpHost
+
+
+ if not (auth_mode == "CERT"):
+ #密钥认证后的签名
+ sign_base64 = self.__codec.Base64.encode(self.__codec.Hmac.sha256_encode(device_secret.encode("utf-8"),
+ sign_content.encode("utf-8")))
+ else:
+ #证书认证后的签名
+ privateKeyFilePath = self.__device_info.private_key_file
+ file = open(privateKeyFilePath)
+ fileConent = file.read()
+ file.close()
+ sign_base64 = self.__codec.RSA.sha256_encode(fileConent, sign_content.encode('utf-8'))
+
+ header = {
+ 'Content-Type': 'application/json; charset=utf-8',
+ "X-TC-Algorithm": encodeType,
+ "X-TC-Timestamp": timestamp,
+ "X-TC-Nonce": nonce,
+ "X-TC-Signature": sign_base64
+ }
+ data = bytes(request_text, encoding='utf-8')
+
+ context = None
+
+ if httpDomain is None or httpDomain == "":
+ if self.__tls:
+ request_url = url_format % 'https'
+ context = self.__codec.Ssl().create_content()
else:
- return 2
+ request_url = url_format % 'http'
+ else:
+ request_url = httpDomain
+ if self.__tls:
+ context = self.__codec.Ssl().create_content()
+
+ self._logger.info('http request url: {}'.format(request_url))
+ req = urllib.request.Request(request_url, data=data, headers=header)
+ with urllib.request.urlopen(req, timeout=timeout, context=context) as url_file:
+ reply_data = url_file.read().decode('utf-8')
+ reply_obj = json.loads(reply_data)
+ resp = reply_obj['Response']
+ self._logger.info("resp:%s",resp)
+
+ if 'Error' in resp and resp['Error']:
+ err_code = resp['Error']
+ self._logger.error('code: {}, error message: {}'.format(
+ err_code, err_code['Message']))
+ return -1, err_code['Message']
+ else:
+ return 0, resp
+
+ def httpCallback(self, on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe):
+ """Register user http callback
+
+ Register user http callback for http
+ Args:
+ on_connect: http connect callback
+ on_disconnect: http disconnect callback
+ on_message: http message callback
+ on_publish: http publish callback
+ on_subscribe: http subscribe callback
+ on_unsubscribe: http unsubscribe callback
+ Returns:
+ success: default
+ fail: default
+ """
+ self.__user_on_connect = on_connect
+ self.__user_on_disconnect = on_disconnect
+ self.__user_on_message = on_message
+ self.__user_on_publish = on_publish
+ self.__user_on_subscribe = on_subscribe
+ self.__user_on_unsubscribe = on_unsubscribe
+
+ def isHttpConnected(self):
+ """Is http connected
+
+ Is http connected
+ Args: None
+ Returns:
+ success: True/False
+ """
+ return self.__hub_state == self.HubState.CONNECTED
+
+
+ def isSubdevStatusOnline(self, sub_productId, sub_devName):
+ """Sub-device status
+
+ Determine if the device is online
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ Returns:
+ success: if device is online
+ """
+ return self.__gateway.is_subdev_status_online(sub_productId, sub_devName)
+
+ def updateSubdevStatus(self, sub_productId, sub_devName, status):
+ """Update device status
+
+ Update sub-device local status
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ status: new status
+ Returns:
+ success: None
+ """
+ return self.__gateway.update_subdev_status(sub_productId, sub_devName, status)
+
+ def gatewaySubdevGetConfigList(self):
+ """Get sub-device list
+
+ Get the list of sub-devices in the configuration file
+ Args: None
+ Returns:
+ success: sub-device list
+ """
+ return self.__gateway.gateway_get_subdev_config_list()
+
+ def gatewaySubdevOnline(self, sub_productId, sub_devName):
+ """Make sub-device online
+
+ Make sub-device online
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ Returns:
+ success: zero and publish mid
+ """
+ self.__assert(sub_productId)
+ self.__assert(sub_devName)
+
+ gateway_topic_pub = self._topic.gateway_topic_pub
+ return self.__gateway.gateway_subdev_online(gateway_topic_pub, 0, sub_productId, sub_devName)
+
+ def gatewaySubdevOffline(self, sub_productId, sub_devName):
+ """Make sub-device offline
+
+ Make sub-device offline
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ Returns:
+ success: zero and publish mid
+ """
+ self.__assert(sub_productId)
+ self.__assert(sub_devName)
+
+ gateway_topic_pub = self._topic.gateway_topic_pub
+ return self.__gateway.gateway_subdev_offline(gateway_topic_pub, 0, sub_productId, sub_devName)
+
+ def gatewaySubdevBind(self, sub_productId, sub_devName, sub_secret):
+ """Bind sub-device
+
+ Gateway device bind sub-device
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ sub_secret: sub-device secret
+ Returns:
+ success: zero and publish mid
+ """
+ self.__assert(sub_productId)
+ self.__assert(sub_devName)
+ self.__assert(sub_secret)
+
+ gateway_topic_pub = self._topic.gateway_topic_pub
+ return self.__gateway.gateway_subdev_bind(gateway_topic_pub, 0, sub_productId, sub_devName, sub_secret)
+
+ def gatewaySubdevUnbind(self, sub_productId, sub_devName):
+ """Unbind sub-device
+
+ Gateway device unbind sub-device
+ Args:
+ sub_productId: sub-device product_id
+ sub_devName: sub-device device_name
+ sub_secret: sub-device secret
+ Returns:
+ success: zero and publish mid
+ """
+ self.__assert(sub_productId)
+ self.__assert(sub_devName)
+
+ gateway_topic_pub = self._topic.gateway_topic_pub
+ return self.__gateway.gateway_subdev_unbind(gateway_topic_pub, 0, sub_productId, sub_devName)
+
+ def gatewaySubdevGetBindList(self, product_id, device_name):
+ """Get sub-device list
+
+ Get the list of sub-devices in the qcloud
+ Args: None
+ Returns:
+ success: sub-device list
+ """
+ self.__assert(product_id)
+ self.__assert(device_name)
+
+ gateway_topic_pub = self._topic.gateway_topic_pub
+ return self.__gateway.gateway_get_subdev_bind_list(gateway_topic_pub, 0, product_id, device_name)
+
+ def gatewaySubdevSubscribe(self, topic):
+ """Subscribe sub-device topic
+
+ Subscribe sub-device topic
+ Args: sub-device topic
+ Returns:
+ success: zero and subscribe mid
+ """
+ return self.__gateway.gateway_subdev_subscribe(topic)
+
+ def gatewayInit(self):
+ """Gateway initialization
+
+ Gateway initialization
+ Args: None
+ Returns:
+ success: zero and publish mid
+ """
+ if self.__hub_state is not self.HubState.CONNECTED:
+ raise self.StateError("current state is not CONNECTED")
+
+ self.__gateway = Gateway(self.__host, self.__device_info.product_id, self.__device_info.device_name,
+ self.__device_info.device_secret, websocket=self.__useWebsocket,
+ tls=self.__tls)
+ json_data = self.__device_info.json_data
+ gateway_topic_sub = self._topic.gateway_topic_sub
+
+ return self.__gateway.gateway_init(gateway_topic_sub, 0, json_data)
+
+ def rrpcInit(self, productId, deviceName, callback):
+ """RRPC initialization
+
+ RRPC initialization
+ Args:
+ productId: product id
+ deviceName: device name
+ callback: user received message callback
+ Returns:
+ success: zero and subscribe mid
+ fail: negative number and subscribe mid
+ """
+ if self.__hub_state is not self.HubState.CONNECTED:
+ raise self.StateError("current state is not CONNECTED")
+
+ client = productId + deviceName
+ rrpc = Rrpc(self.__host, productId, deviceName,
+ "", websocket=self.__useWebsocket,
+ tls=self.__tls, logger=self._logger)
+
+ rc, mid = rrpc.rrpc_init(callback)
+ if rc != 0:
+ self._logger.error("[rrpc] subscribe error:rc:%d" % (rc))
+ else:
+ self.__rrpc_map[client] = rrpc
+ return rc, mid
+
+ def rrpcReply(self, productId, deviceName, reply, length):
+ """RRPC reply
+
+ Reply rrpc request
+ Args:
+ productId: product id
+ deviceName: device name
+ reply: reply message
+ length: reply message length
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ client = productId + deviceName
+ if (client not in self.__rrpc_map.keys()
+ or self.__rrpc_map[client] is None):
+ self._logger.error("[rrpc] not found rrpc handle for client:%s" % (client))
+ return None
+
+ rrpc = self.__rrpc_map[client]
+ rc, mid = rrpc.rrpc_reply(reply)
+ if rc != 0:
+ self._logger.error("[rrpc] publish error:rc:%d" % (rc))
+ return rc, mid
+
+ def broadcastInit(self, productId, deviceName, callback):
+ """Broadcast initialization
+
+ Broadcast initialization
+ Args:
+ productId: product id
+ deviceName: device name
+ callback: user received message callback
+ Returns:
+ success: zero and subscribe mid
+ fail: negative number and subscribe mid
+ """
+ if self.__hub_state is not self.HubState.CONNECTED:
+ raise self.StateError("current state is not CONNECTED")
+
+ client = productId + deviceName
+ broadcast = Broadcast(self.__host, productId, deviceName,
+ "", websocket=self.__useWebsocket,
+ tls=self.__tls, logger=self._logger)
+
+ rc, mid = broadcast.broadcast_init(callback)
+ if rc != 0:
+ self._logger.error("[broadcast] publish error:rc:%d" % (rc))
+ else:
+ self.__broadcast_map[client] = broadcast
+ return rc, mid
+
+ def shadowInit(self, productId, deviceName, callback):
+ """Shadow initialization
+
+ Shadow initialization
+ Args:
+ productId: product id
+ deviceName: device name
+ callback: user received message callback
+ Returns:
+ success: zero and subscribe mid
+ fail: negative number and subscribe mid
+ """
+ if self.__hub_state is not self.HubState.CONNECTED:
+ raise self.StateError("current state is not CONNECTED")
+
+ client = productId + deviceName
+ shadow = Shadow(self.__host, productId, deviceName,
+ "", websocket=self.__useWebsocket,
+ tls=self.__tls, logger=self._logger)
+
+ rc, mid = shadow.shadow_init(callback)
+ if rc != 0:
+ self._logger.error("[shadow] publish error:rc:%d" % (rc))
+ else:
+ self.__shadow_map[client] = shadow
+ return rc, mid
+
+ def getShadow(self, productId, deviceName):
+ """Get shadow cache
+
+ Get cloud shadow cache
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ client = productId + deviceName
+ if (client not in self.__shadow_map.keys()
+ or self.__shadow_map[client] is None):
+ self._logger.error("[shadow] not found shadow handle for client:%s" % (client))
+ return None
+
+ shadow = self.__shadow_map[client]
+ rc, mid = shadow.get_shadow(productId)
+ if rc != 0:
+ self._logger.error("[shadow] publish error:rc:%d" % (rc))
+ return rc, mid
+
+ def shadowJsonConstructDesireAllNull(self, productId, deviceName):
+ """Construct json context
+
+ Construct json context to reset cloud cache
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: json context
+ fail: None
+ """
+ client = productId + deviceName
+ if (client not in self.__shadow_map.keys()
+ or self.__shadow_map[client] is None):
+ self._logger.error("[shadow] not found shadow handle for client:%s" % (client))
+ return None
+
+ shadow = self.__shadow_map[client]
+ return shadow.shadow_json_construct_desire_null(productId)
+
+ def shadowUpdate(self, productId, deviceName, shadow_docs, length):
+ """Update shadow
+
+ Update cloud shadow cache
+ Args:
+ productId: product id
+ deviceName: device name
+ shadow_docs: update message
+ length: message length
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ client = productId + deviceName
+ if (client not in self.__shadow_map.keys()
+ or self.__shadow_map[client] is None):
+ self._logger.error("[shadow] not found shadow handle for client:%s" % (client))
+ return None
+
+ self._logger.debug("[shadow update] %s" % (shadow_docs))
+ shadow = self.__shadow_map[client]
+ rc, mid = shadow.shadow_update(shadow_docs)
+ if rc != 0:
+ self._logger.error("topic_publish error:rc:%d" % (rc))
+ return rc, mid
+
+ def shadowJsonConstructReport(self, productId, deviceName, *args):
+ """Construct json context
+
+ Construct report json context
+ Args:
+ productId: product id
+ deviceName: device name
+ *args: variable parameters, type is device_property()
+ Returns:
+ success: json context
+ fail: None
+ """
+ client = productId + deviceName
+ if (client not in self.__shadow_map.keys()
+ or self.__shadow_map[client] is None):
+ self._logger.error("[shadow] not found shadow handle for client:%s" % (client))
+ return None
+
+ shadow = self.__shadow_map[client]
+ return shadow.shadow_json_construct_report(productId, *args)
+
+
+ def resourceInit(self,productId, deviceName,callback):
+ if self.__hub_state is not self.__hub_state.CONNECTED:
+ raise self.StateError("current state is not CONNECTED")
+
+ resource = ResourceManage(self.__host, self.__device_info.product_id,
+ self.__device_info.device_name, self.__device_info.device_secret,
+ websocket=self.__useWebsocket, tls=self.__tls, logger=self._logger)
+ topic_sub = self._topic.resource_manager_topic_sub
+ rc, mid = resource.resource_init(productId, deviceName, callback)
+ if rc != 0:
+ self._logger.error("[resource manage] subscribe error:rc:%d,topic:%s" % (rc, topic_sub))
+ return rc, mid
+ if rc == 0:
+ self._logger.error("resource subscribe success")
+
+ """
+ 等待订阅成功
+ """
+ cnt = 0
+ while cnt < 10:
+ if mid in self.__subscribe_res:
+ # 收到该mid回调,且其qos>=0说明订阅完成,qos=0需另做判断
+ if self.__subscribe_res[mid] >= 1:
+ break
+ time.sleep(0.2)
+ cnt += 1
pass
+ if cnt >= 10:
+ return -1, mid
- def post_message(self, cmd, value):
- # self.__logger.debug("post_message :%r " % cmd)
- if self.__started and self.__exited is False:
- try:
- self.__message_queue.put((cmd, value), timeout=5)
- except queue.Full as e:
- self.__logger.error("queue full: %r" % e)
- return False
- # self.__logger.debug("post_message success")
- return True
- self.__logger.debug("post_message fail started:%r,exited:%r" % (self.__started, self.__exited))
- return False
+ resource.resource_manager_init()
- def start(self):
- if self.__logger is not None:
- self.__logger.info("UserCallBackTask start")
- if self.__started is False:
- if self.__logger is not None:
- self.__logger.info("UserCallBackTask try start")
- self.__exited = False
- self.__started = True
- self.__message_queue = queue.Queue(20)
- self.__thread = threading.Thread(target=self.__user_cb_thread)
- self.__thread.daemon = True
- self.__thread.start()
- return 0
- return 1
-
- def stop(self):
- if self.__started and self.__exited is False:
- self.__exited = True
- self.__message_queue.put(("req_exit", None))
-
- def wait_stop(self):
- if self.__started is True:
- self.__thread.join()
-
- def __user_cb_thread(self):
- if self.__logger is not None:
- self.__logger.debug("thread runnable enter")
- while True:
- cmd, value = self.__message_queue.get()
- # self.__logger.debug("thread runnable pop cmd:%r" % cmd)
- if cmd == "req_exit":
- break
- if self.__cmd_callback[cmd] is not None:
- try:
- # print("cmd:%s,value:%s" % (cmd, value))
- self.__cmd_callback[cmd](value)
- except Exception as e:
- if self.__logger is not None:
- self.__logger.error("thread runnable raise exception:%s" % e)
- self.__started = False
- if self.__logger is not None:
- self.__logger.debug("thread runnable exit")
- pass
+ client = productId + deviceName
+ self.__resource_map[client] = resource
- class LoopThread(object):
- def __init__(self, logger=None):
- self.__logger = logger
- if logger is not None:
- self.__logger.info("LoopThread init enter")
- self.__callback = None
- self.__thread = None
- self.__started = False
- self.__req_wait = threading.Event()
- if logger is not None:
- self.__logger.info("LoopThread init exit")
-
- def start(self, callback):
- if self.__started:
- self.__logger.info("LoopThread already ")
- return 1
- else:
- self.__callback = callback
- self.__thread = threading.Thread(target=self.__thread_main)
- self.__thread.daemon = True
- self.__thread.start()
- return 0
-
- def __thread_main(self):
- self.__started = True
- try:
- if self.__logger is not None:
- self.__logger.debug("LoopThread thread enter")
- if self.__callback is not None:
- self.__callback()
- if self.__logger is not None:
- self.__logger.debug("LoopThread thread exit")
- except Exception as e:
- self.__logger.error("LoopThread thread Exception:" + str(e))
- self.__started = False
- self.__req_wait.set()
+ return rc, mid
- def stop(self):
- self.__req_wait.wait()
- self.__req_wait.clear()
+ def resourceCreateUploadTask(self, productId, deviceName, resourceFilePath):
- class ExplorerLog(object):
- def __init__(self):
- self.__logger = logging.getLogger("QcloudExplorer")
- self.__enabled = False
- pass
+ """
+ :param productId: 产品ID
+ :param deviceName: 设备名称
+ :param resourceFilePath: 资源文件绝对路径
+ :return:
+ """
- def enable_logger(self):
- self.__enabled = True
-
- def disable_logger(self):
- self.__enabled = False
-
- def is_enabled(self):
- return self.__enabled
-
- def set_level(self, level):
- self.__logger.setLevel(level)
-
- def debug(self, fmt, *args):
- if self.__enabled:
- self.__logger.debug(fmt, *args)
-
- def warring(self, fmt, *args):
- if self.__enabled:
- self.__logger.warning(fmt, *args)
-
- def info(self, fmt, *args):
- if self.__enabled:
- self.__logger.info(fmt, *args)
-
- def error(self, fmt, *args):
- if self.__enabled:
- self.__logger.error(fmt, *args)
-
- def critical(self, fmt, *args):
- if self.__enabled:
- self.__logger.critical(fmt, *args)
-
- class DeviceInfo(object):
- def __init__(self, file_path, logger=None):
- self.__logger = logger
- self.__logger.info('device_info file {}'.format(file_path))
-
- self.__auth_mode = None
- self.__device_name = None
- self.__product_id = None
- self.__product_secret = None
- self.__device_secret = None
- self.__ca_file = None
- self.__cert_file = None
- self.__private_key_file = None
- self.__region = None
- if self.__logger is not None:
- self.__logger.info('device_info file {}'.format(file_path))
- with open(file_path, 'r', encoding='utf-8') as f:
- self.__json_data = json.loads(f.read())
- self.__auth_mode = self.__json_data['auth_mode']
- self.__device_name = self.__json_data['deviceName']
- self.__product_id = self.__json_data['productId']
- self.__product_secret = self.__json_data['productSecret']
- self.__device_secret = self.__json_data['key_deviceinfo']['deviceSecret']
- self.__ca_file = self.__json_data['cert_deviceinfo']['devCaFile']
- self.__cert_file = self.__json_data['cert_deviceinfo']['devCertFile']
- self.__private_key_file = self.__json_data['cert_deviceinfo']['devPrivateKeyFile']
- self.__region = self.__json_data["region"]
- if self.__logger is not None:
- self.__logger.info(
- "device name: {}, product id: {}, product secret: {}, device secret: {}".
- format(self.__device_name, self.__product_id,
- self.__product_secret, self.__device_secret))
-
- @property
- def auth_mode(self):
- return self.__auth_mode
-
- @property
- def device_name(self):
- return self.__device_name
-
- @property
- def product_id(self):
- return self.__product_id
-
- @property
- def product_secret(self):
- return self.__product_secret
-
- @property
- def device_secret(self):
- return self.__device_secret
-
- @property
- def ca_file(self):
- return self.__ca_file
-
- @property
- def cert_file(self):
- return self.__cert_file
-
- @property
- def private_key_file(self):
- return self.__private_key_file
-
- @property
- def region(self):
- return self.__region
-
- @property
- def json_data(self):
- return self.__json_data
-
- class _AESUtil:
- __BLOCK_SIZE_16 = BLOCK_SIZE_16 = AES.block_size
-
- '''
- @staticmethod
- def encryt(str, key, iv):
- cipher = AES.new(key, AES.MODE_CBC, iv)
- x = AESUtil.__BLOCK_SIZE_16 - (len(str) % AESUtil.__BLOCK_SIZE_16)
- if x != 0:
- str = str + chr(x) * x
- msg = cipher.encrypt(str)
- msg = base64.b64encode(msg)
- return msg
- '''
-
- @staticmethod
- def decrypt(encrypt_str, key, init_vector):
- cipher = AES.new(key, AES.MODE_CBC, init_vector)
- decrypt_bytes = base64.b64decode(encrypt_str)
- return cipher.decrypt(decrypt_bytes)
-
- class Topic(object):
- def __init__(self, product_id, device_name):
- self.__clientToken = None
-
- # log topic
- self.__log_topic_pub = "$log/operation/%s/%s" % (product_id, device_name)
- self.__log_topic_sub = "$log/operation/result/%s/%s" % (product_id, device_name)
- self.__is_subscribed_log_topic = False
-
- # system topic
- self.__sys_topic_pub = "$sys/operation/%s/%s" % (product_id, device_name)
- self.__sys_topic_sub = "$sys/operation/result/%s/%s" % (product_id, device_name)
-
- # gateway topic
- self.__gateway_topic_pub = "$gateway/operation/%s/%s" % (product_id, device_name)
- self.__gateway_topic_sub = "$gateway/operation/result/%s/%s" % (product_id, device_name)
-
- # data template topic
- self.__template_topic_pub = "$template/operation/%s/%s" % (product_id, device_name)
- self.__template_topic_sub = "$template/operation/result/%s/%s" % (product_id, device_name)
-
- # thing topic
- self.__thing_property_topic_pub = "$thing/up/property/%s/%s" % (product_id, device_name)
- self.__thing_property_topic_sub = "$thing/down/property/%s/%s" % (product_id, device_name)
- # self.__is_subscribed_property_topic = False
-
- self.__thing_action_topic_pub = "$thing/up/action/%s/%s" % (product_id, device_name)
- self.__thing_action_topic_sub = "$thing/down/action/%s/%s" % (product_id, device_name)
- self.__thing_event_topic_pub = "$thing/up/event/%s/%s" % (product_id, device_name)
- self.__thing_event_topic_sub = "$thing/down/event/%s/%s" % (product_id, device_name)
- self.__thing_raw_topic_pub = "$thing/up/raw/%s/%s" % (product_id, device_name)
- self.__thing_raw_topic_sub = "$thing/down/raw/%s/%s" % (product_id, device_name)
- self.__thing_service_topic_pub = "$thing/up/service/%s/%s" % (product_id, device_name)
- self.__thing_service_topic_sub = "$thing/down/service/%s/%s" % (product_id, device_name)
-
- # ota topic
- self.__ota_report_topic_pub = "$ota/report/%s/%s" % (product_id, device_name)
- self.__ota_update_topic_sub = "$ota/update/%s/%s" % (product_id, device_name)
-
- # rrpc topic
- self.__rrpc_topic_pub_prefix = "$rrpc/txd/%s/%s/" % (product_id, device_name)
- self.__rrpc_topic_sub_prefix = "$rrpc/rxd/%s/%s/" % (product_id, device_name)
-
- # shadow
- self.__shadow_topic_pub = "$shadow/operation/%s/%s" % (product_id, device_name)
- self.__shadow_topic_sub = "$shadow/operation/result/%s/%s" % (product_id, device_name)
-
- # broadcast
- self.__broadcast_topic_sub = "$broadcast/rxd/%s/%s" % (product_id, device_name)
- pass
+ client = productId + deviceName
+ if (client not in self.__resource_map.keys()
+ or self.__resource_map[client] is None):
+ self._logger.error("[resource manage] not found resource handle for client:%s" % (client))
+ return -1, -1
- @property
- def sys_topic_sub(self):
- return self.__sys_topic_sub
+ resource = self.__resource_map[client]
- @property
- def sys_topic_pub(self):
- return self.__sys_topic_pub
+ if resourceFilePath is None or len(resourceFilePath) == 0:
+ raise ValueError('Invalid filePath param.')
- @property
- def gateway_topic_sub(self):
- return self.__gateway_topic_sub
+ self.__resourceFilePath = resourceFilePath
- @property
- def gateway_topic_pub(self):
- return self.__gateway_topic_pub
+ #文件绝对路径
+ file_Path = resourceFilePath
- @property
- def template_topic_sub(self):
- return self.__template_topic_sub
+ #文件大小
+ fileSize = os.path.getsize(file_Path)
+ #文件名称
+ (filePath, tempfileName) = os.path.split(file_Path)
+ (fileName, extension) = os.path.splitext(tempfileName)
+ resourceFileName = fileName
+ #文件md5
+ fileMd5 = resource.resource_file_md5(file_Path)
- @property
- def template_event_topic_sub(self):
- return self.__thing_event_topic_sub
+ rc, mid = resource.resource_create_upload_task(fileSize,resourceFileName,fileMd5)
+ if rc != 0:
+ self._logger.error("[resource manage] resource_create_upload_task fail")
- @property
- def template_event_topic_pub(self):
- return self.__thing_event_topic_pub
+ if rc == 0:
+ self._logger.debug("resource_create_upload_task create success")
+ return rc, mid
- @property
- def template_action_topic_sub(self):
- return self.__thing_action_topic_sub
+ def resourceReportUploadProgress(self, productId, deviceName):
- @property
- def template_property_topic_sub(self):
- return self.__thing_property_topic_sub
+ client = productId + deviceName
+ if (client not in self.__resource_map.keys()
+ or self.__resource_map[client] is None):
+ self._logger.error("[resource manage] not found resource handle for client:%s" % (client))
+ return -1, -1
- @property
- def template_property_topic_pub(self):
- return self.__thing_property_topic_pub
+ resource = self.__resource_map[client]
- @property
- def template_action_topic_pub(self):
- return self.__thing_action_topic_pub
+ if self.__resourceFilePath is None or len(self.__resourceFilePath) == 0:
+ raise ValueError('Invalid filePath param.')
- @property
- def template_service_topic_sub(self):
- return self.__thing_service_topic_sub
+ # 文件名称
+ (filePath, tempfileName) = os.path.split(self.__resourceFilePath)
+ (fileName, extension) = os.path.splitext(tempfileName)
- @property
- def template_raw_topic_sub(self):
- return self.__thing_raw_topic_sub
+ rc, mid = resource.resource_report_upload_progress(fileName, 100,"uploading")
- @property
- def ota_update_topic_sub(self):
- return self.__ota_update_topic_sub
+ if rc != 0:
+ self._logger.error("[resource manage] resource_report_upload_progress fail")
+ if rc == 0:
+ self._logger.debug("resource_report_upload_progress success")
+ return rc, mid
- @property
- def ota_report_topic_pub(self):
- return self.__ota_report_topic_pub
+ def resourceUploadfile(self, uploadTaskUrl, timeout=10):
- @property
- def rrpc_topic_pub_prefix(self):
- return self.__rrpc_topic_pub_prefix
+ """
+ 上传资源
+ :param uploadTaskUrl: 上传URL
+ :param timeout: 超时时间
+ :return: rc mid
+ """
+ client = self.__device_info.product_id + self.__device_info.device_name
+ if (client not in self.__resource_map.keys()
+ or self.__resource_map[client] is None):
+ self._logger.error("[resource manage] not found resource handle for client:%s" % (client))
+ return -1, -1
- @property
- def rrpc_topic_sub_prefix(self):
- return self.__rrpc_topic_sub_prefix
+ resource = self.__resource_map[client]
- @property
- def shadow_topic_pub(self):
- return self.__shadow_topic_pub
+ if uploadTaskUrl is None or len(uploadTaskUrl) == 0:
+ self._logger.error('resource upload url invalid param.')
+ return -1, -1
- @property
- def shadow_topic_sub(self):
- return self.__shadow_topic_sub
+ fileSize = os.path.getsize(self.__resourceFilePath)
- @property
- def broadcast_topic_sub(self):
- return self.__broadcast_topic_sub
+ file = open(self.__resourceFilePath)
+ fileConent = file.read()
+ file.close()
- @property
- def control_clientToken(self):
- return self.__clientToken
+ fileMd5 = resource.resource_file_md5(self.__resourceFilePath)
- # _on_template_downstream_topic_handler()中收到云端消息后保存client-token
- @control_clientToken.setter
- def control_clientToken(self, token):
- if token is None or len(token) == 0:
- raise ValueError('Invalid info.')
- self.__clientToken = token
+ sign_base64 = self.__codec.Base64.encodeHex(fileMd5)
- class sReplyPara(object):
- def __init__(self):
- self.timeout_ms = 0
- self.code = -1
- self.status_msg = None
+ header = {
+ "Content-Length": str(fileSize),
+ "Content-Type": "application/x-www-form-urlencoded",
+ "Content-MD5": sign_base64,
+ }
+
+ request_text = fileConent
+ data = bytes(request_text, encoding='utf-8')
- class OtaState(Enum):
- IOT_OTAS_UNINITED = 0
- IOT_OTAS_INITED = 1
- IOT_OTAS_FETCHING = 2
- IOT_OTAS_FETCHED = 3
- IOT_OTAS_DISCONNECTED = 4
-
- class OtaCmdType(Enum):
- IOT_OTAG_FETCHED_SIZE = 0
- IOT_OTAG_FILE_SIZE = 1
- IOT_OTAG_MD5SUM = 2
- IOT_OTAG_VERSION = 3
- IOT_OTAG_CHECK_FIRMWARE = 4
-
- class OtaProgressCode(Enum):
- IOT_OTAP_BURN_FAILED = -4
- IOT_OTAP_CHECK_FALIED = -3
- IOT_OTAP_FETCH_FAILED = -2
- IOT_OTAP_GENERAL_FAILED = -1
- IOT_OTAP_FETCH_PERCENTAGE_MIN = 0
- IOT_OTAP_FETCH_PERCENTAGE_MAX = 100
-
- class OtaReportType(IntEnum):
- IOT_OTAR_DOWNLOAD_TIMEOUT = -1
- IOT_OTAR_FILE_NOT_EXIST = -2
- IOT_OTAR_AUTH_FAIL = -3
- IOT_OTAR_MD5_NOT_MATCH = -4
- IOT_OTAR_UPGRADE_FAIL = -5
- IOT_OTAR_NONE = 0
- IOT_OTAR_DOWNLOAD_BEGIN = 1
- IOT_OTAR_DOWNLOADING = 2
- IOT_OTAR_UPGRADE_BEGIN = 3
- IOT_OTAR_UPGRADE_SUCCESS = 4
-
- class SessionState(Enum):
- SUBDEV_SEESION_STATUS_INIT = 0
- SUBDEV_SEESION_STATUS_ONLINE = 1
- SUBDEV_SEESION_STATUS_OFFLINE = 2
-
- # 网关子设备信息(是否需加入设备online/offline状态?)
- class gateway_subdev(object):
- def __init__(self):
- self.sub_productId = None
- self.sub_devName = None
- self.session_status = 0
+ context = None
+ if self.__tls:
+ context = self.__codec.Ssl().create_content()
- # property结构
- class template_property(object):
- def __init__(self):
- self.key = None
- self.data = None
- self.data_buff_len = 0
- self.type = None
+ req = urllib.request.Request(uploadTaskUrl, data=data, headers=header, method='PUT')
+ with urllib.request.urlopen(req, timeout=timeout, context=context) as url_file:
+ if url_file.status == 200 and url_file.reason == 'ok':
+ return 0, 0
+ else:
+ self._logger.error("put request fail status code:%s reason:%s",url_file.status,url_file.reason)
+ return -1, -1
+
+ # r = requests.put(uploadTaskUrl, data=data, headers=header)
+ # if r.status_code == 200:
+ # return 0, 0
+ # else:
+ # self._logger.error("put request fail status code:%s reason:%s",r.status_code,r.reason)
+ # return -1, -1
+
+ def resourceFinished(self):
+
+ client = self.__device_info.product_id + self.__device_info.device_name
+ if (client not in self.__resource_map.keys()
+ or self.__resource_map[client] is None):
+ self._logger.error("[resource manage] not found resource handle for client:%s" % (client))
+ return -1, -1
+
+ resource = self.__resource_map[client]
+
+ if self.__resourceFilePath is None or len(self.__resourceFilePath) == 0:
+ raise ValueError('Invalid filePath param.')
+
+ # 文件名称
+ (filePath, tempfileName) = os.path.split(self.__resourceFilePath)
+ (fileName, extension) = os.path.splitext(tempfileName)
+
+ rc, mid = resource.resource_report_upload_progress(fileName, 100, "done")
+ if rc != 0:
+ self._logger.debug("resource_report_upload_progress finish error ")
+ if rc == 0:
+ self._logger.error("[resource manage] resource_report_upload_progress finish")
+ return rc, mid
+
+ def otaInit(self, productId, deviceName, callback):
+ """Ota initialization
+
+ Ota initialization
+ Args:
+ productId: product id
+ deviceName: device name
+ callback: user received message callback
+ Returns:
+ success: zero and subscribe mid
+ """
+ if self.__hub_state is not self.HubState.CONNECTED:
+ raise self.StateError("current state is not CONNECTED")
+
+ ota = Ota(self.__host, self.__device_info.product_id,
+ self.__device_info.device_name, self.__device_info.device_secret,
+ websocket=self.__useWebsocket, tls=self.__tls, logger=self._logger)
+ topic_sub = self._topic.ota_update_topic_sub
+ rc, mid = ota.ota_init(productId, deviceName, callback)
+ if rc != 0:
+ self._logger.error("[ota] subscribe error:rc:%d,topic:%s" % (rc, topic_sub))
+ return rc, mid
+
+ """
+ 等待订阅成功
+ """
+ cnt = 0
+ while cnt < 10:
+ if mid in self.__subscribe_res:
+ # 收到该mid回调,且其qos>=0说明订阅完成,qos=0需另做判断
+ if self.__subscribe_res[mid] >= 1:
+ break
+ time.sleep(0.2)
+ cnt += 1
+ pass
+ if cnt >= 10:
+ return -1, mid
+
+ ota.ota_manager_init()
+
+ client = productId + deviceName
+ self.__ota_map[client] = ota
+
+ return rc, mid
+
+ # 是否应将ota句柄传入(支持多个下载进程?)
+ def otaIsFetching(self, productId, deviceName):
+ """Is downloading
+
+ Is downloading
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: True
+ fail: False
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return False
- class template_action(object):
- def __init__(self):
- self.action_id = None
- self.timestamp = 0
- self.input_num = 0
- self.output_num = 0
- self.actions_input_prop = []
- self.actions_output_prop = []
+ ota = self.__ota_map[client]
+ return ota.ota_is_fetching()
+
+ def otaIsFetchFinished(self, productId, deviceName):
+ """Is download finished
+
+ Is download finished
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: True
+ fail: False
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return False
- def action_input_append(self, prop):
- self.actions_input_prop.append(prop)
+ ota = self.__ota_map[client]
+ return ota.ota_is_fetch_finished()
+
+ def otaReportUpgradeSuccess(self, productId, deviceName, version):
+ """Report success message
+
+ Report upgrade success message to qcloud
+ Args:
+ productId: product id
+ deviceName: device name
+ version: firmware version
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return -1, -1
+
+ ota = self.__ota_map[client]
+ rc, mid = ota.ota_report_upgrade_success(version)
+ if rc != 0:
+ self._logger.error("ota report upgrade(success) fail")
+ return rc, mid
+
+ def otaReportUpgradeFail(self, productId, deviceName, version):
+ """Report fail message
+
+ Report upgrade fail message to qcloud
+ Args:
+ productId: product id
+ deviceName: device name
+ version: firmware version
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return -1, -1
+
+ ota = self.__ota_map[client]
+ rc, mid = ota.ota_report_upgrade_fail(version)
+ if rc != 0:
+ self._logger.error("ota report upgrade(fail) fail")
+ return rc, mid
+
+ def otaIoctlNumber(self, productId, deviceName, cmdType):
+ """User interaction
+
+ User interaction with SDK to get a number, like linux kernel ioctl
+ Args:
+ productId: product id
+ deviceName: device name
+ cmdType: interaction command
+ Returns:
+ success: the number you want and 'success' message
+ fail: negative number and error message
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return -1, "no handle"
+
+ ota = self.__ota_map[client]
+ return ota.ota_ioctl_number(cmdType)
+
+ def otaIoctlString(self, productId, deviceName, cmdType, length):
+ """User interaction
+
+ User interaction with SDK to get a string, like linux kernel ioctl
+ Args:
+ productId: product id
+ deviceName: device name
+ cmdType: interaction command
+ length: command length
+ Returns:
+ success: the string you want and 'success' message
+ fail: negative number and error message
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return "null", "no handle"
+
+ ota = self.__ota_map[client]
+ return ota.ota_ioctl_string(cmdType, length)
+
+ def otaResetMd5(self, productId, deviceName):
+ """Reset md5 value
+
+ Reset md5 value
+ Args:
+ productId: product id
+ deviceName: device name
+ Returns:
+ success: zero
+ fail: negative number
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return -1
+
+ ota = self.__ota_map[client]
+ return ota.ota_reset_md5()
+
+ def otaMd5Update(self, productId, deviceName,buf):
+ """Update md5 value
+
+ Calculate new message md5 and update old
+ Args:
+ productId: product id
+ deviceName: device name
+ buf: new message
+ Returns:
+ success: zero
+ fail: negative number
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return -1
+
+ ota = self.__ota_map[client]
+ return ota.ota_md5_update(buf)
+
+ def __ota_http_deinit(self, productId, deviceName, http):
+ self._logger.debug("http deinit do nothing...")
+
+ def httpInit(self, productId, deviceName, host, url, offset, size, timeoutSec):
+ """Http initialization
+
+ Http initialization
+ Args:
+ productId: product id
+ deviceName: device name
+ host: http server host
+ url: http url
+ offset: http parameter 'Range' minimum
+ size: http parameter 'Range' max
+ timeoutSec: http overtime time
+ Returns:
+ success: zero
+ fail: negative number
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return -1
+
+ ota = self.__ota_map[client]
+ return ota.http_init(host, url, offset, size, timeoutSec)
+
+ def httpFetch(self, productId, deviceName, buf_len):
+ """Http download
+
+ Http download
+ Args:
+ productId: product id
+ deviceName: device name
+ buf_len: download max length
+ Returns:
+ success: downloaded content and length
+ fail: None and negative number
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return None, -1
+
+ ota = self.__ota_map[client]
+ return ota.http_fetch(buf_len)
+
+ def otaReportVersion(self, productId, deviceName, version):
+ """Report version
+
+ Report local firmware version
+ Args:
+ productId: product id
+ deviceName: device name
+ version: local firmware version
+ Returns:
+ success: zero and publish mid
+ fail: negative number and publish mid
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return -1, -1
+
+ ota = self.__ota_map[client]
+ rc, mid = ota.ota_report_version(version)
+ if rc != 0:
+ self._logger.error("[ota] report version fail")
+ return rc, mid
+
+ def otaDownloadStart(self, productId, deviceName, offset, size):
+ """Start download
+
+ Start download
+ Args:
+ productId: product id
+ deviceName: device name
+ offset: download offset
+ size: download size
+ Returns:
+ success: zero
+ fail: negative number
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return -1
+
+ ota = self.__ota_map[client]
+ return ota.ota_download_start(offset, size)
+
+ def otaFetchYield(self, productId, deviceName, buf_len):
+ """Http download
+
+ Perform an http download
+ Args:
+ productId: product id
+ deviceName: device name
+ buf_len: download max length
+ Returns:
+ success: downloaded content and length
+ fail: None and negative number
+ """
+ client = productId + deviceName
+ if (client not in self.__ota_map.keys()
+ or self.__ota_map[client] is None):
+ self._logger.error("[ota] not found ota handle for client:%s" % (client))
+ return None, -1
+
+ ota = self.__ota_map[client]
+ return ota.ota_fetch_yield(buf_len)
+
+ def logInit(self, level, filePath=None, maxBytes=0, backupCount=0, enable=True):
+ """Log initialization
+
+ Log initialization
+ Args:
+ level: log level, type is class LoggerLevel()
+ enable: enable switch
+ Returns:
+ success: logger handle
+ fail: None
+ """
+ if self.__protocol is None:
+ return None
+
+ logger_level = 0
+ provider = LoggerProvider()
+ logger = provider.logger
+
+ if enable is False:
+ logger.disable_logger()
+ else:
+ if level.value == self.LoggerLevel.INFO.value:
+ logger_level = logging.INFO
+ elif level.value == self.LoggerLevel.DEBUG.value:
+ logger_level = logging.DEBUG
+ elif level.value == self.LoggerLevel.WARNING.value:
+ logger_level = logging.WARNING
+ elif level.value == self.LoggerLevel.ERROR.value:
+ logger_level = logging.ERROR
+ else:
+ logger_level = logging.INFO
- def action_output_append(self, prop):
- self.actions_output_prop.append(prop)
+ logger.set_level(logger_level)
+ logger.enable_logger()
- # event结构(sEvent)
- class template_event(object):
- def __init__(self):
- self.event_name = None
- self.type = None
- self.timestamp = 0
- self.eventDataNum = 0
- self.events_prop = []
+ if (filePath is None or
+ maxBytes == 0 or backupCount == 0):
+ self._logger.error("please set logger parameter:\n"
+ "\tfilePath: log file path.\n"
+ "\tmaxBytes: bytes of one file.\n"
+ "\tbackupCount: file number.")
+ return None
- def event_append(self, prop):
- self.events_prop.append(prop)
+ logger.create_file(filePath, maxBytes, backupCount)
+ self.__protocol.enable_logger(logger.get_logger())
- class ota_manage(object):
- def __init__(self):
- self.channel = None
- self.state = 0
- self.size_fetched = 0
- self.size_last_fetched = 0
- self.file_size = 0
- self.purl = None
- self.version = None
- self.md5sum = None
- self.md5 = None
- self.host = None
- self.is_https = False
-
- self.report_timestamp = 0
-
- # http连接管理
- self.http_manager = None
-
- class http_manage(object):
- def __init__(self):
- self.handle = None
- self.request = None
- self.header = None
- self.host = None
- self.https_context = None
-
- self.err_reason = None
- self.err_code = 0
- pass
+ return logger
diff --git a/hub/log/__init__.py b/hub/log/__init__.py
new file mode 100644
index 0000000..e247df7
--- /dev/null
+++ b/hub/log/__init__.py
@@ -0,0 +1 @@
+name = "Log"
diff --git a/hub/log/log.py b/hub/log/log.py
new file mode 100644
index 0000000..d549dc8
--- /dev/null
+++ b/hub/log/log.py
@@ -0,0 +1,62 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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 logging
+from logging.handlers import RotatingFileHandler
+
+class Log(object):
+ def __init__(self):
+ self.__logger = logging.getLogger("TecentQcloud")
+ self.__format = '%(asctime)s.%(msecs)03d [%(filename)s:%(lineno)d] - %(levelname)s - %(message)s'
+ logging.basicConfig(format=self.__format)
+ self.__enabled = False
+
+ def get_logger(self):
+ return self.__logger
+
+ def enable_logger(self):
+ self.__enabled = True
+
+ def disable_logger(self):
+ self.__enabled = False
+
+ def is_enabled(self):
+ return self.__enabled
+
+ def set_level(self, level):
+ self.__logger.setLevel(level)
+
+ def create_file(self, file_path, max_bytes, backup_count):
+ log_handler = RotatingFileHandler(filename=file_path, maxBytes=max_bytes, backupCount=backup_count)
+ log_handler.setFormatter(logging.Formatter(self.__format))
+ self.__logger.addHandler(log_handler)
+
+ def debug(self, fmt, *args):
+ if self.__enabled:
+ self.__logger.debug(fmt, *args)
+
+ def warring(self, fmt, *args):
+ if self.__enabled:
+ self.__logger.warning(fmt, *args)
+
+ def info(self, fmt, *args):
+ if self.__enabled:
+ self.__logger.info(fmt, *args)
+
+ def error(self, fmt, *args):
+ if self.__enabled:
+ self.__logger.error(fmt, *args)
+
+ def critical(self, fmt, *args):
+ if self.__enabled:
+ self.__logger.critical(fmt, *args)
diff --git a/hub/manager/__init__.py b/hub/manager/__init__.py
new file mode 100644
index 0000000..689a0a3
--- /dev/null
+++ b/hub/manager/__init__.py
@@ -0,0 +1 @@
+name = "manager"
diff --git a/hub/manager/manager.py b/hub/manager/manager.py
new file mode 100644
index 0000000..0a4de50
--- /dev/null
+++ b/hub/manager/manager.py
@@ -0,0 +1,137 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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 queue
+import threading
+from hub.utils.providers import LoggerProvider
+
+class TaskManager(object):
+ def __init__(self):
+ self.__init = True
+
+ class LoopThread(object):
+ def __init__(self):
+ self.__provider = LoggerProvider()
+ self.__logger = self.__provider.logger
+ self.__callback = None
+ self.__thread = None
+ self.__started = False
+ self.__req_wait = threading.Event()
+
+ def start(self, callback):
+ if self.__started:
+ self.__logger.info("LoopThread already")
+ return 1
+ else:
+ self.__callback = callback
+ self.__thread = threading.Thread(target=self.__thread_main)
+ self.__thread.daemon = True
+ self.__thread.start()
+ return 0
+
+ def __thread_main(self):
+ self.__started = True
+ try:
+ if self.__logger is not None:
+ self.__logger.debug("LoopThread thread enter")
+ if self.__callback is not None:
+ self.__callback()
+ if self.__logger is not None:
+ self.__logger.debug("LoopThread thread exit")
+ except Exception as e:
+ self.__logger.error("LoopThread thread Exception:" + str(e))
+ self.__started = False
+ self.__req_wait.set()
+
+ def stop(self):
+ self.__req_wait.wait()
+ self.__req_wait.clear()
+
+ # 用户注册回调分发线程
+ class EventCbThread(object):
+ def __init__(self):
+ self.__provider = LoggerProvider()
+ self.__logger = self.__provider.logger
+ self.__message_queue = queue.Queue(20)
+ self.__event_callback = {}
+ self.__started = False
+ self.__exited = False
+ self.__thread = None
+ pass
+
+ def register_event_callback(self, event, callback):
+ if self.__started is False:
+ if event != "req_exit":
+ self.__event_callback[event] = callback
+ return 0
+ else:
+ return 1
+ pass
+ else:
+ return 2
+ pass
+
+ def post_message(self, event, value):
+ # self.__logger.debug("post_message :%r " % event)
+ if self.__started and self.__exited is False:
+ try:
+ self.__message_queue.put((event, value), timeout=5)
+ except queue.Full as e:
+ self.__logger.error("queue full: %r" % e)
+ return False
+ # self.__logger.debug("post_message success")
+ return True
+ self.__logger.debug("post_message fail started:%r,exited:%r" % (self.__started, self.__exited))
+ return False
+
+ def start(self):
+ if self.__logger is not None:
+ self.__logger.info("EventCbThread start")
+ if self.__started is False:
+ if self.__logger is not None:
+ self.__logger.info("EventCbThread try start")
+ self.__exited = False
+ self.__started = True
+ self.__message_queue = queue.Queue(20)
+ self.__thread = threading.Thread(target=self.__event_thread)
+ self.__thread.daemon = True
+ self.__thread.start()
+ return 0
+ return 1
+
+ def stop(self):
+ if self.__started and self.__exited is False:
+ self.__exited = True
+ self.__message_queue.put(("req_exit", None))
+
+ def wait_stop(self):
+ if self.__started is True:
+ self.__thread.join()
+
+ def __event_thread(self):
+ if self.__logger is not None:
+ self.__logger.debug("thread runnable enter")
+ while True:
+ event, value = self.__message_queue.get()
+ if event == "req_exit":
+ break
+ if self.__event_callback[event] is not None:
+ try:
+ self.__event_callback[event](value)
+ except Exception as e:
+ if self.__logger is not None:
+ self.__logger.error("thread runnable raise exception:%s" % e)
+ self.__started = False
+ if self.__logger is not None:
+ self.__logger.debug("thread runnable exit")
+ pass
\ No newline at end of file
diff --git a/hub/network/__init__.py b/hub/network/__init__.py
new file mode 100644
index 0000000..ff2530e
--- /dev/null
+++ b/hub/network/__init__.py
@@ -0,0 +1 @@
+name = "network"
diff --git a/hub/network/network.py b/hub/network/network.py
new file mode 100644
index 0000000..e69de29
diff --git a/hub/protocol/__init__.py b/hub/protocol/__init__.py
new file mode 100644
index 0000000..5d4060d
--- /dev/null
+++ b/hub/protocol/__init__.py
@@ -0,0 +1 @@
+name = "protocol"
diff --git a/hub/protocol/protocol.py b/hub/protocol/protocol.py
new file mode 100644
index 0000000..a685096
--- /dev/null
+++ b/hub/protocol/protocol.py
@@ -0,0 +1,282 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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 sys
+import socket
+import string
+import time
+import hmac
+import hashlib
+import base64
+import random
+import ssl
+import paho.mqtt.client as mqtt
+from hub.utils.codec import Codec
+
+class AsyncConnClient(object):
+
+ def __init__(self, host, product_id, device_name, device_secret,
+ websocket=False, tls=True, logger=None):
+ self.__logger = logger
+ self.__tls = tls
+ self.__useWebsocket = websocket
+ self.__key_mode = True
+ # self.__iot_ca_crt = self.__IOT_CA_CRT
+ self.__mqtt_client = None
+ self.__product_id = product_id
+ self.__device_name = device_name
+ self.__device_secret = device_secret
+ self.__host = host
+ self.__psk = None
+ self.__certificate = self.Certificate()
+ self.__codec = Codec()
+ self.__set_mqtt_default_param()
+
+ self.__init(host, product_id, device_name, device_secret)
+
+ class Certificate:
+ def __init__(self):
+ self.ca_file = None
+ self.cert_file = None
+ self.key_file = None
+
+ def __set_mqtt_default_param(self):
+ self.__mqtt_tls_port = 8883
+ self.__mqtt_tcp_port = 1883
+ self.__mqtt_socket_tls_port = 443
+ self.__mqtt_socket_tcp_port = 80
+ self.__mqtt_protocol = "MQTTv31"
+ self.__mqtt_transport = "TCP"
+ self.__mqtt_secure = "TLS"
+ self.__mqtt_clean_session = True
+ self.__mqtt_keep_alive = 60
+
+ self.__mqtt_auto_reconnect_min_sec = 1
+ self.__mqtt_auto_reconnect_max_sec = 60
+ self.__mqtt_max_queued_message = 40
+ self.__mqtt_max_inflight_message = 20
+ self.__mqtt_auto_reconnect_sec = 0
+ self.__mqtt_request_timeout = 10
+
+ # default MQTT/CoAP timeout value when connect/pub/sub
+ self.__mqtt_command_timeout = 5
+ pass
+
+ def _generate_pwss(self, client_id, device_secret):
+ self.__psk = base64.b64decode(device_secret.encode("utf-8"))
+
+ timestamp = str(int(round(time.time() * 1000)))
+ conn_id = ''.join(random.sample(string.ascii_letters + string.digits, 5))
+ username = client_id + ";21010406;" + conn_id + ";" + timestamp
+ sign = hmac.new(self.__psk, username.encode("utf-8"), hashlib.sha1).hexdigest()
+ password = "%s;hmacsha1" % (sign)
+
+ return username, password
+
+ def _ssl_init(self, key_mode):
+ # 密钥认证
+ if key_mode is True:
+ # context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cadata=self.__iot_ca_crt)
+ self.__logger.info("connect with key...")
+ context = self.__codec.Ssl().create_content()
+ self.__mqtt_client.tls_set_context(context)
+ else:
+ self.__logger.info("connect with certificate...")
+ ca = self.__certificate.ca_file
+ cert = self.__certificate.cert_file
+ key = self.__certificate.key_file
+ self.__mqtt_client.tls_set(ca_certs=ca, certfile=cert, keyfile=key,
+ cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_SSLv23)
+ pass
+
+ def __init(self, host, product_id, device_name, device_secret):
+ client_id = product_id + device_name
+ username, password = self._generate_pwss(client_id, device_secret)
+
+ if self.__mqtt_protocol == "MQTTv311":
+ mqtt_protocol_version = mqtt.MQTTv311
+ elif self.__mqtt_protocol == "MQTTv31":
+ mqtt_protocol_version = mqtt.MQTTv31
+ if self.__useWebsocket:
+ self.__mqtt_client = mqtt.Client(client_id=client_id,
+ clean_session=self.__mqtt_clean_session,
+ protocol=mqtt_protocol_version,
+ transport="websockets")
+ else:
+ self.__mqtt_client = mqtt.Client(client_id=client_id,
+ clean_session=self.__mqtt_clean_session,
+ protocol=mqtt_protocol_version)
+
+ # self._configuration_init(username, password)
+ self.__mqtt_client.username_pw_set(username, password)
+
+ def set_connect_state(self, state):
+ self.__connect_state = state
+
+ def get_connect_state(self):
+ return self.__connect_state
+
+ def enable_logger(self, pahoLog):
+ if self.__mqtt_client is not None:
+ self.__mqtt_client.enable_logger(pahoLog)
+
+ # 设置重连时间,config_connect之前调用
+ def set_reconnect_interval(self, max_sec, min_sec):
+ self.__mqtt_auto_reconnect_min_sec = min_sec
+ self.__mqtt_auto_reconnect_max_sec = max_sec
+
+ # 设置消息发送超时时间,connect之前调用
+ def set_message_timout(self, timeout):
+ self.__mqtt_command_timeout = timeout
+
+ # 设置保活时间,connect之前调用
+ def set_keepalive_interval(self, interval):
+ self.__mqtt_keep_alive = interval
+
+ def reset_reconnect_wait(self):
+ self.__mqtt_auto_reconnect_sec = 0
+
+ def reconnect_wait(self):
+ if self.__mqtt_auto_reconnect_sec == 0:
+ self.__mqtt_auto_reconnect_sec = self.__mqtt_auto_reconnect_min_sec
+ else:
+ self.__mqtt_auto_reconnect_sec = min(self.__mqtt_auto_reconnect_sec * 2, self.__mqtt_auto_reconnect_max_sec)
+ self.__mqtt_auto_reconnect_sec += random.randint(1, self.__mqtt_auto_reconnect_sec)
+ time.sleep(self.__mqtt_auto_reconnect_sec)
+ pass
+
+ def register_event_callbacks(self, on_connect, on_disconnect, on_message, on_publish, on_subscribe, on_unsubscribe):
+ self.__mqtt_client.on_connect = on_connect
+ self.__mqtt_client.on_disconnect = on_disconnect
+ self.__mqtt_client.on_message = on_message
+ self.__mqtt_client.on_publish = on_publish
+ self.__mqtt_client.on_subscribe = on_subscribe
+ self.__mqtt_client.on_unsubscribe = on_unsubscribe
+
+ # 配置mqtt,connect之前调用
+ def config_connect(self):
+ self.__mqtt_client.reconnect_delay_set(self.__mqtt_auto_reconnect_min_sec, self.__mqtt_auto_reconnect_max_sec)
+ self.__mqtt_client.max_queued_messages_set(self.__mqtt_max_queued_message)
+ self.__mqtt_client.max_inflight_messages_set(self.__mqtt_max_inflight_message)
+
+ # 设置证书路径,connect之前调用
+ def set_cert_file(self, ca, cert, key):
+ # 认证模式置为证书方式
+ self.__key_mode = False
+ self.__certificate.ca_file = ca
+ self.__certificate.cert_file = cert
+ self.__certificate.key_file = key
+
+ def loop(self):
+ rc = mqtt.MQTT_ERR_SUCCESS
+ while rc == mqtt.MQTT_ERR_SUCCESS and self.__mqtt_client is not None:
+ rc = self.__mqtt_client.loop(self.__mqtt_command_timeout, 1)
+
+ def reconnect(self):
+ if self.__mqtt_client is not None:
+ self.__mqtt_client.reconnect()
+
+ def connect(self):
+ mqtt_port = self.__mqtt_tls_port
+ if self.__tls:
+ try:
+ if self.__useWebsocket:
+ mqtt_port = self.__mqtt_socket_tls_port
+ self._ssl_init(self.__key_mode)
+ except ssl.SSLError as e:
+ self.__logger.error("ssl init error:" + str(e))
+ return False
+ else:
+ mqtt_port = self.__mqtt_tcp_port
+ if self.__useWebsocket:
+ mqtt_port = self.__mqtt_socket_tcp_port
+ pass
+ try:
+ self.__logger.debug("connect_async (%s:%d)" % (self.__host, mqtt_port))
+ self.__mqtt_client.connect_async(host=self.__host, port=mqtt_port, keepalive=self.__mqtt_keep_alive)
+ except Exception as e:
+ self.__logger.error("mqtt connect with async error:" + str(e))
+ return False
+ return True
+
+ def disconnect(self):
+ if self.__mqtt_client is not None:
+ self.__mqtt_client.disconnect()
+
+ def subscribe(self, topic, qos=-1):
+ rc, mid = -1, -1
+ if self.__mqtt_client is None:
+ return rc, mid
+
+ if isinstance(topic, tuple):
+ topic, qos = topic
+ if isinstance(topic, str):
+ if qos < 0 or qos > 1:
+ raise ValueError('Invalid QoS level.')
+ if topic is None or len(topic) == 0:
+ raise ValueError('Invalid topic.')
+ pass
+ rc, mid = self.__mqtt_client.subscribe(topic, qos)
+ if rc == mqtt.MQTT_ERR_SUCCESS:
+ self.__logger.debug("subscribe success topic:%s" % topic)
+ return 0, mid
+ elif rc == mqtt.MQTT_ERR_NO_CONN:
+ return 2, mid
+ else:
+ self.__logger.debug("subscribe error topic:%s" % topic)
+ return -1, mid
+ # topic format [(topic1, qos),(topic2,qos)]
+ if isinstance(topic, list):
+ topic_list = []
+ for t, qos in topic:
+ if qos < 0 or qos > 1:
+ raise ValueError('Invalid QoS level.')
+ if t is None or len(t) == 0 or not isinstance(t, str):
+ raise ValueError('Invalid topic.')
+ topic_list.append((t, qos))
+
+ rc, mid = self.__mqtt_client.subscribe(topic_list)
+ if rc == mqtt.MQTT_ERR_SUCCESS:
+ self.__logger.debug("subscribe success topic:%s" % topic)
+ return 0, mid
+ elif rc == mqtt.MQTT_ERR_NO_CONN:
+ return 2, mid
+ else:
+ self.__logger.debug("subscribe error topic:%s" % topic)
+ return -1, mid
+
+ def unsubscribe(self, topic):
+ rc, mid = -1, -1
+ if self.__mqtt_client is None:
+ return rc, mid
+
+ rc, mid = self.__mqtt_client.unsubscribe(topic)
+ if rc == mqtt.MQTT_ERR_SUCCESS:
+ return 0, mid
+ else:
+ return rc, mid
+
+ def publish(self, topic, payload, qos):
+ rc, mid = -1, -1
+ if self.__mqtt_client is None:
+ return rc, mid
+
+ # rc, mid = self.__mqtt_client.publish(topic, json.dumps(payload), qos)
+ rc, mid = self.__mqtt_client.publish(topic, payload, qos)
+ if rc == mqtt.MQTT_ERR_SUCCESS:
+ self.__logger.debug("publish success")
+ return 0, mid
+ else:
+ self.__logger.debug("publish failed")
+ return rc, mid
diff --git a/hub/sample/broadcast/example_broadcast.py b/hub/sample/broadcast/example_broadcast.py
new file mode 100644
index 0000000..a762dcb
--- /dev/null
+++ b/hub/sample/broadcast/example_broadcast.py
@@ -0,0 +1,78 @@
+import sys
+import os
+import time
+import json
+from hub.hub import QcloudHub
+
+
+logger = None
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ global g_connected
+ g_connected = True
+ pass
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+def on_message(topic, qos, payload, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def on_broadcast_cb(topic, qos, payload, userdata):
+ logger.debug("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ pass
+
+def example_broadcast(isTest=True):
+ global logger
+ provider = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+ qcloud = provider.hub
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+ logger.debug("\033[1;36m broadcast test start...\033[0m")
+
+
+ prduct_id = qcloud.getProductID()
+ device_name = qcloud.getDeviceName()
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m broadcast test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ rc, mid = qcloud.broadcastInit(prduct_id, device_name, on_broadcast_cb)
+ if rc != 0:
+ logger.error("broadcast init error")
+ return False
+
+ while True and isTest is False:
+ logger.debug("broadcast wait")
+ time.sleep(1)
+
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m broadcast test success...\033[0m")
+ return True
\ No newline at end of file
diff --git a/hub/sample/device_info.json b/hub/sample/device_info.json
new file mode 100644
index 0000000..7978259
--- /dev/null
+++ b/hub/sample/device_info.json
@@ -0,0 +1,29 @@
+{
+ "auth_mode":"KEY",
+
+ "productId":"YOUR_PRODUCT_ID",
+ "productSecret":"YOUR_PRODUCT_SECRET",
+ "deviceName":"YOUR_DEVICE_NAME",
+
+ "key_deviceinfo":{
+ "deviceSecret":"YOUR_DEVICE_SECRET"
+ },
+
+ "cert_deviceinfo":{
+ "devCaFile":"YOUR_CA_FILE_NAME",
+ "devCertFile":"YOUR_DEVICE_CERT_FILE_NAME",
+ "devPrivateKeyFile":"YOUR_DEVICE_PRIVATE_KEY_FILE_NAME"
+ },
+
+ "subDev":{
+ "subdev_num":3,
+ "subdev_list":
+ [
+ {"sub_productId": "", "sub_devName": ""},
+ {"sub_productId": "", "sub_devName": ""},
+ {"sub_productId": "", "sub_devName": ""}
+ ]
+ },
+
+ "region":"china"
+}
diff --git a/hub/sample/dynreg/example_dynreg.py b/hub/sample/dynreg/example_dynreg.py
new file mode 100644
index 0000000..2d7aefb
--- /dev/null
+++ b/hub/sample/dynreg/example_dynreg.py
@@ -0,0 +1,87 @@
+import sys
+import time
+import logging
+from hub.hub import QcloudHub
+
+logger = None
+
+def on_connect(flags, rc, userdata):
+ global logger
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ global logger
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ global logger
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ global logger
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_subscribe(mid, granted_qos, userdata):
+ global logger
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ global logger
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def example_dynreg():
+ global logger
+ provider = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+ qcloud = provider.hub
+
+ print("\033[1;36m dynamic register test start...\033[0m")
+
+ """
+ start dynamic register
+ """
+
+ ret, msg = qcloud.dynregDevice()
+ if ret == 0:
+# print("\033[1;36m dynamic register test success, psk: {}\033[0m".format(msg))
+ print("\033[1;36m dynamic register test success, 动态获取信息 内容在 msg 中 \033[0m")
+ else:
+ print("\033[1;31m dynamic register test fail, msg: {}\033[0m".format(msg))
+ return False
+
+ """
+ start mqtt connect
+ """
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024*1024*10, 5, enable=True)
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m dynamic register test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ timestamp = qcloud.getNtpAccurateTime()
+ dt = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp/1000))
+ logger.debug("current time:%s" % dt)
+
+ # qcloud.disconnect()
+ return True
diff --git a/hub/sample/gateway/example_gateway.py b/hub/sample/gateway/example_gateway.py
new file mode 100644
index 0000000..a741f62
--- /dev/null
+++ b/hub/sample/gateway/example_gateway.py
@@ -0,0 +1,174 @@
+import sys
+import time
+import logging
+import threading
+from hub.hub import QcloudHub
+from gateway import subdev_ota as SubdevOta
+
+provider = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+qcloud = provider.hub
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+subdev_map = {}
+thread_list = []
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ global subdev_map
+ for subdev_ota in subdev_map.values():
+ if subdev_ota is not None:
+ subdev_ota.update_reply_mid(mid)
+ pass
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def on_subdev_cb(topic, qos, payload, userdata):
+ logger.debug("%s:topic:%s,payload:%s" % (sys._getframe().f_code.co_name, topic, payload))
+ pass
+
+def subdev_ota_thread(isTest, subdev_list=[]):
+ for subdev in subdev_list:
+ try:
+ subdev_ota = SubdevOta(subdev.product_id, subdev.device_name, qcloud, logger)
+ client = subdev.product_id + subdev.device_name
+ global subdev_map
+ subdev_map[client] = subdev_ota
+ thread = threading.Thread(target=subdev_ota.subdev_ota_start, args=(isTest))
+ global thread_list
+ thread_list.append(thread)
+ thread.start()
+ except:
+ logger.error("Error: unable to start thread")
+
+def subscribe_subdev_topic(product_id, device_name, topic_suffix):
+ """
+ 订阅网关子设备topic
+ eg:${productId}/${deviceName}/data
+ """
+ topic_list = []
+ topic_format = "%s/%s/%s"
+ topic_data = topic_format % (product_id, device_name, topic_suffix)
+ topic_list.append((topic_data, 0))
+ """ 注册topic对应回调 """
+ qcloud.registerUserCallback(topic_data, on_subdev_cb)
+
+ """ 订阅子设备topic,在此必须传入元组列表[(topic1,qos2),(topic2,qos2)] """
+ rc, mid = qcloud.gatewaySubdevSubscribe(topic_list)
+ if rc == 0:
+ logger.debug("gateway subdev subscribe success")
+ else:
+ logger.error("gateway subdev subscribe fail")
+ pass
+
+def publish_subdev_message(product_id, device_name, topic_suffix):
+ topic_format = "%s/%s/%s"
+ topic_data = topic_format % (product_id, device_name, topic_suffix)
+ message = {
+ "action":"gateway subdev publish"
+ }
+ qcloud.publish(topic_data, message, 1)
+
+def example_gateway(isTest=True):
+
+ logger.debug("\033[1;36m gateway test start...\033[0m")
+
+ product_id = qcloud.getProductID()
+ device_name = qcloud.getDeviceName()
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m gateway test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ qcloud.gatewayInit()
+
+ subdev_list = qcloud.gatewaySubdevGetConfigList()
+
+ """sub-device online"""
+ for subdev in subdev_list:
+ if qcloud.isSubdevStatusOnline(subdev.product_id, subdev.device_name) is not True:
+ rc, mid = qcloud.gatewaySubdevOnline(subdev.product_id, subdev.device_name)
+ if rc == 0:
+ qcloud.updateSubdevStatus(subdev.product_id, subdev.device_name, "online")
+ subscribe_subdev_topic(subdev.product_id, subdev.device_name, "data")
+ publish_subdev_message(subdev.product_id, subdev.device_name, "data")
+ logger.debug("online success")
+ else:
+ logger.error("online fail")
+ return False
+
+ """sub-device offline"""
+ for subdev in subdev_list:
+ if qcloud.isSubdevStatusOnline(subdev.product_id, subdev.device_name) is True:
+ rc, mid = qcloud.gatewaySubdevOffline(subdev.product_id, subdev.device_name)
+ if rc == 0:
+ qcloud.updateSubdevStatus(subdev.product_id, subdev.device_name, "offline")
+ logger.debug("offline success")
+ else:
+ logger.error("offline fail")
+ return False
+
+ """
+ rc, mid = qcloud.gatewaySubdevBind("YOUR_SUBDEV_PRODUCT_ID",
+ "YOUR_SUBDEV_DEVICE_NAME",
+ "YOUR_SUBDEV_SECRET")
+ if rc == 0:
+ logger.debug("bind success")
+ else:
+ logger.error("bind fail")
+ return False
+
+ rc, mid = qcloud.gatewaySubdevUnbind("SUBDEV_PRODUCT_ID", "SUBDEV_DEVICE_NAME")
+ if rc == 0:
+ logger.debug("unbind success")
+ else:
+ logger.error("unbind fail")
+ return False
+ """
+
+ bind_list = []
+ rc, bind_list = qcloud.gatewaySubdevGetBindList(product_id, device_name)
+ if rc != 0:
+ logger.error("get bind list error")
+ return False
+
+ """子设备固件升级"""
+ subdev_ota_thread(isTest, subdev_list)
+ for thread in thread_list:
+ thread.join()
+
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m gateway test success...\033[0m")
+ return True
diff --git a/hub/sample/gateway/subdev_ota.py b/hub/sample/gateway/subdev_ota.py
new file mode 100644
index 0000000..384890d
--- /dev/null
+++ b/hub/sample/gateway/subdev_ota.py
@@ -0,0 +1,314 @@
+import sys
+import time
+import logging
+import json
+import os
+from enum import Enum
+
+class SubdevOta(object):
+ def __init__(self, product_id, device_name, handle, logger):
+ self.__product_id = product_id
+ self.__device_name = device_name
+ self.__handle= handle
+ self.__logger = logger
+ self.__thd = None
+
+ class ThreadResource(object):
+ def __init__(self, product_id, device_name, handle):
+ self.handle = handle
+ self.report_res = False
+ self.packet_id = 0
+ self.pub_ack = False
+ self.product_id = product_id
+ self.device_name = device_name
+
+ class OtaContextData(object):
+ def __init__(self):
+ self.file_size = 0
+ self.version = None
+ self.file_path = None
+ self.info_file_path = None
+ self.remote_version = None
+ self.local_version = None
+ self.download_size = 0
+
+ class OtaCmdType(Enum):
+ IOT_OTAG_FETCHED_SIZE = 0
+ IOT_OTAG_FILE_SIZE = 1
+ IOT_OTAG_MD5SUM = 2
+ IOT_OTAG_VERSION = 3
+ IOT_OTAG_CHECK_FIRMWARE = 4
+
+ def __on_ota_report(self, topic, qos, payload, userdata):
+ self.__logger.debug("%s:topic:%s,payload:%s" % (sys._getframe().f_code.co_name, topic, payload))
+ code = payload["result_code"]
+
+ if code == 0:
+ self.__thd.report_res = True
+ pass
+
+
+ def __get_local_fw_running_version(self):
+ # you should get real version and return
+ return "0.1.0"
+
+
+ def __get_local_fw_info(self, ota_cxt):
+ if ota_cxt.info_file_path is None:
+ self.__logger.error("file name is none")
+ return 0
+ if not os.path.exists(ota_cxt.info_file_path):
+ self.__logger.error("info file not exists")
+ return 0
+
+ f = open(ota_cxt.info_file_path, "r")
+ buf = f.read()
+ if len(buf) > 0:
+ file_buf = json.loads(buf)
+ version = file_buf["version"]
+ download_size = file_buf["downloaded_size"]
+ if version is not None:
+ ota_cxt.local_version = version
+ if download_size > 0:
+ return download_size
+ f.close()
+
+ return 0
+
+
+ def __cal_exist_fw_md5(self, ota_cxt, thd):
+ if ota_cxt.file_path is None:
+ self.__logger.error("file name is none")
+ return -1
+
+ total_read = 0
+ thd.handle.otaResetMd5(thd.product_id, thd.device_name)
+ size = ota_cxt.download_size
+ with open(ota_cxt.file_path, "rb") as f:
+ while size > 0:
+ rlen = 5000 if size > 5000 else size
+ buf = f.read(rlen)
+ if buf == "":
+ break
+ thd.handle.otaMd5Update(thd.product_id, thd.device_name, buf)
+ size -= rlen
+ total_read += rlen
+ pass
+ f.close()
+ self.__logger.debug("total read:%d" % total_read)
+
+ return 0
+
+
+ def __update_fw_downloaded_size(self, ota_cxt, thd):
+ local_size = self.__get_local_fw_info(ota_cxt)
+ self.__logger.debug("local_size:%d,local_ver:%s,re_ver:%s" % (local_size, ota_cxt.local_version, ota_cxt.remote_version))
+ if ((ota_cxt.local_version != ota_cxt.remote_version)
+ or (ota_cxt.download_size > ota_cxt.file_size)):
+ ota_cxt.download_size = 0
+ return 0
+ ota_cxt.download_size = local_size
+ rc = self.__cal_exist_fw_md5(ota_cxt, thd)
+ if rc != 0:
+ self.__logger.error("cal md5 error")
+ os.remove(ota_cxt.info_file_path)
+ ota_cxt.download_size = 0
+ return 0
+
+ return local_size
+
+
+ def __save_fw_data_to_file(self, ota_cxt, buf, buf_len):
+ if ota_cxt.file_path is None:
+ self.__logger.error("file name is none")
+ return -1
+ f = None
+ wr_len = 0
+ if ota_cxt.download_size > 0:
+ f = open(ota_cxt.file_path, "ab+")
+ else:
+ f = open(ota_cxt.file_path, "wb+")
+
+ f.seek(ota_cxt.download_size, 0)
+ while True:
+ wr_len = f.write(buf)
+ if wr_len == buf_len:
+ break
+ else:
+ self.__logger.error('write size error')
+ f.close()
+ return -1
+ f.flush()
+ f.close()
+
+ return 0
+
+
+ def __update_local_fw_info(self, ota_cxt):
+ data_format = "{\"%s\":\"%s\", \"%s\":%d}"
+ data = data_format % ("version", ota_cxt.remote_version, "downloaded_size", ota_cxt.download_size)
+ with open(ota_cxt.info_file_path, "w") as f:
+ wr_len = f.write(data)
+ if wr_len != len(data):
+ return -1
+ return 0
+
+
+ def __wait_for_pub_ack(self, packet_id, thd):
+ wait_cnt = 10
+ thd.packet_id = packet_id
+
+ while (thd.pub_ack is not True):
+ self.__logger.debug("wait for ack...")
+ time.sleep(0.5)
+ if wait_cnt == 0:
+ self.__logger.error("wait report pub ack timeout!")
+ break
+ wait_cnt -= 1
+ pass
+
+ thd.pub_ack = False
+
+
+ def __board_upgrade(self, fw_path):
+ self.__logger.debug("burning firmware...")
+
+ return 0
+
+ def update_reply_mid(self, mid):
+ if self.__thd.packet_id == mid:
+ self.__thd.pub_ack = True
+ self.__logger.debug("publish ack id %d" % self.__thd.packet_id)
+
+ def subdev_ota_start(self, isTest):
+ self.__logger.debug("\033[1;36m ota test start...\033[0m")
+
+ thd = self.ThreadResource(self.__product_id, self.__device_name, self.__handle)
+ self.__thd = thd
+
+ count = 0
+ while True:
+ if self.__handle.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ self.__logger.error("\033[1;31m ota test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ self.__handle.otaInit(self.__product_id, self.__device_name, self.__on_ota_report)
+
+ cnt = 0
+ while True:
+ if not self.__handle.isMqttConnected():
+ if cnt >= 10:
+ self.__logger.error("mqtt disconnect")
+ break
+ time.sleep(1)
+ cnt += 1
+ continue
+ cnt = 0
+
+ upgrade_fetch_success = True
+ ota_cxt = self.OtaContextData()
+
+ self.__handle.otaReportVersion(self.__product_id, self.__device_name, self.__get_local_fw_running_version())
+ """测试模式"""
+ if isTest:
+ break
+ # wait for ack
+ time.sleep(1)
+
+ if thd.report_res:
+ download_finished = False
+ while (download_finished is not True):
+ self.__logger.debug("wait for ota upgrade command")
+ if self.__handle.otaIsFetching(self.__product_id, self.__device_name):
+ file_size, state = self.__handle.otaIoctlNumber(self.__product_id, self.__device_name,
+ self.OtaCmdType.IOT_OTAG_FILE_SIZE)
+ if state == "success":
+ ota_cxt.file_size = file_size
+ else:
+ self.__logger.error("ota_ioctl_number fail")
+ break
+ pass
+
+ version, state = self.__handle.otaIoctlString(self.__product_id, self.__device_name,
+ self.OtaCmdType.IOT_OTAG_VERSION, 32)
+ if state == "success":
+ ota_cxt.remote_version = version
+
+ ota_cxt.file_path = "./FW-%s_%s.bin" % (self.__product_id + self.__device_name, ota_cxt.remote_version)
+ ota_cxt.info_file_path = "./FW-%s_%s.json" % (self.__product_id + self.__device_name, ota_cxt.remote_version)
+
+ self.__update_fw_downloaded_size(ota_cxt, thd)
+
+ rc = self.__handle.otaDownloadStart(self.__product_id, self.__device_name,
+ ota_cxt.download_size, ota_cxt.file_size)
+ if rc != 0:
+ upgrade_fetch_success = False
+ break
+
+ while (self.__handle.otaIsFetchFinished(self.__product_id, self.__device_name, ) is not True):
+ buf, rv_len = self.__handle.otaFetchYield(self.__product_id, self.__device_name, 5000)
+ if rv_len > 0:
+ rc = self.__save_fw_data_to_file(ota_cxt, buf, rv_len)
+ if rc != 0:
+ self.__logger.error("save data to file fail")
+ upgrade_fetch_success = False
+ break
+ elif rv_len < 0:
+ self.__logger.error("download fail rc:%d" % rv_len)
+ upgrade_fetch_success = False
+ break
+
+ fetched_size, state = self.__handle.otaIoctlNumber(self.__product_id, self.__device_name,
+ self.OtaCmdType.IOT_OTAG_FETCHED_SIZE)
+ if state == "success":
+ ota_cxt.download_size = fetched_size
+ else:
+ break
+
+ rc = self.__update_local_fw_info(ota_cxt)
+ if rc != 0:
+ self.__logger.error("update local fw info error")
+ pass
+
+ #time.sleep(0.1)
+ # <> end
+
+ if upgrade_fetch_success:
+ os.remove(ota_cxt.info_file_path)
+ firmware_valid, state = self.__handle.otaIoctlNumber(self.__product_id, self.__device_name,
+ self.OtaCmdType.IOT_OTAG_CHECK_FIRMWARE)
+ if firmware_valid == 0:
+ self.__logger.debug("The firmware(%s) download success" % (self.__product_id + self.__device_name))
+ upgrade_fetch_success = True
+ else:
+ self.__logger.error("The firmware(%s) is invalid,state:%s" % (state, self.__product_id + self.__device_name))
+ upgrade_fetch_success = False
+
+ download_finished = True
+ # <> end
+
+ if not download_finished:
+ time.sleep(1)
+ # <> end
+
+ self.__board_upgrade(ota_cxt.file_path)
+
+ # Report after confirming that the burning is successful or failed
+ packet_id = 0
+ if upgrade_fetch_success:
+ rc, packet_id = self.__handle.otaReportUpgradeSuccess(self.__product_id, self.__device_name, None)
+ else:
+ rc, packet_id = self.__handle.otaReportUpgradeFail(self.__product_id, self.__device_name, None)
+ if rc == 0:
+ self.__wait_for_pub_ack(packet_id, thd)
+
+ thd.report_res = False
+ time.sleep(2)
+
+ self.__logger.debug("\033[1;36m ota test success...\033[0m")
+ return True
\ No newline at end of file
diff --git a/hub/sample/httpAccess/example_http.py b/hub/sample/httpAccess/example_http.py
new file mode 100644
index 0000000..ac3283b
--- /dev/null
+++ b/hub/sample/httpAccess/example_http.py
@@ -0,0 +1,81 @@
+import sys
+import logging
+import time
+from hub.hub import QcloudHub
+
+logger = None
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def example_http():
+ global logger
+ provider = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+ qcloud = provider.hub
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+ logger.debug("\033[1;36m http test start...\033[0m")
+
+ """
+ start http request send
+ """
+ ret, msg = qcloud.httpDevice()
+ if ret == 0:
+ logger.debug("\033[1;36m http test success...\033[0m")
+ else:
+ print("\033[1;31m http request test fail, msg: {}\033[0m".format(msg))
+ return False
+
+ """
+ start mqtt connect
+ """
+ qcloud.httpCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isHttpConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m connect test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ timestamp = qcloud.getNtpAccurateTime()
+ dt = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp/1000))
+ logger.debug("current time:%s" % dt)
+
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m connect test success...\033[0m")
+
+ return True
\ No newline at end of file
diff --git a/hub/sample/mqtt/example_mqtt.py b/hub/sample/mqtt/example_mqtt.py
new file mode 100644
index 0000000..5c07842
--- /dev/null
+++ b/hub/sample/mqtt/example_mqtt.py
@@ -0,0 +1,69 @@
+import sys
+import logging
+import time
+from hub.hub import QcloudHub
+
+provider = None
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def example_mqtt():
+ global logger
+ provider = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+ qcloud = provider.hub
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+ logger.debug("\033[1;36m mqtt test start...\033[0m")
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m mqtt test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ timestamp = qcloud.getNtpAccurateTime()
+ dt = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp/1000))
+ logger.debug("current time:%s" % dt)
+
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m mqtt test success...\033[0m")
+
+ return True
\ No newline at end of file
diff --git a/sample/ota/example_ota.py b/hub/sample/ota/example_ota.py
similarity index 60%
rename from sample/ota/example_ota.py
rename to hub/sample/ota/example_ota.py
index a301453..2f7e53d 100644
--- a/sample/ota/example_ota.py
+++ b/hub/sample/ota/example_ota.py
@@ -3,19 +3,16 @@
import logging
import json
import os
-from explorer import explorer
+from enum import Enum
from hub.hub import QcloudHub
-__log_format = '%(asctime)s.%(msecs)03d [%(filename)s:%(lineno)d] - %(levelname)s - %(message)s'
-logging.basicConfig(format=__log_format)
-
-te = explorer.QcloudExplorer(device_file="sample/device_info.json")
-te.enableLogger(logging.DEBUG)
-
g_report_res = False
g_packet_id = 0
g_pub_ack = False
+product_id = None
+device_name = None
+logger = None
class OtaContextData(object):
def __init__(self):
@@ -27,51 +24,53 @@ def __init__(self):
self.local_version = None
self.download_size = 0
+class OtaCmdType(Enum):
+ IOT_OTAG_FETCHED_SIZE = 0
+ IOT_OTAG_FILE_SIZE = 1
+ IOT_OTAG_MD5SUM = 2
+ IOT_OTAG_VERSION = 3
+ IOT_OTAG_CHECK_FIRMWARE = 4
def on_connect(flags, rc, userdata):
- print("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
pass
def on_disconnect(rc, userdata):
- print("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
pass
def on_message(topic, payload, qos, userdata):
- print("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
pass
def on_publish(mid, userdata):
- # print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
-
global g_packet_id
if g_packet_id == mid:
global g_pub_ack
g_pub_ack = True
- print("pub ack......")
-
+ logger.debug("publish ack id %d" % g_packet_id)
pass
def on_subscribe(granted_qos, mid, userdata):
- print("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
pass
def on_unsubscribe(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
pass
-def on_ota_report(payload, userdata):
- print("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+def on_ota_report(topic, qos, payload, userdata):
+ logger.debug("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
code = payload["result_code"]
if code == 0:
global g_report_res
g_report_res = True
-
pass
@@ -82,10 +81,10 @@ def _get_local_fw_running_version():
def _get_local_fw_info(ota_cxt):
if ota_cxt.info_file_path is None:
- print("file name is none")
+ logger.error("file name is none")
return 0
if not os.path.exists(ota_cxt.info_file_path):
- print("info file not exists")
+ logger.error("info file not exists")
return 0
f = open(ota_cxt.info_file_path, "r")
@@ -105,11 +104,13 @@ def _get_local_fw_info(ota_cxt):
def _cal_exist_fw_md5(ota_cxt):
if ota_cxt.file_path is None:
- print("file name is none")
+ logger.error("file name is none")
return -1
+ global product_id
+ global device_name
total_read = 0
- te.otaResetMd5()
+ qcloud.otaResetMd5(product_id, device_name)
size = ota_cxt.download_size
with open(ota_cxt.file_path, "rb") as f:
while size > 0:
@@ -117,19 +118,19 @@ def _cal_exist_fw_md5(ota_cxt):
buf = f.read(rlen)
if buf == "":
break
- te.otaMd5Update(buf)
+ qcloud.otaMd5Update(product_id, device_name, buf)
size -= rlen
total_read += rlen
pass
f.close()
- print("total read:%d" % total_read)
+ logger.error("total read:%d" % total_read)
return 0
def _update_fw_downloaded_size(ota_cxt):
local_size = _get_local_fw_info(ota_cxt)
- print("local_size:%d,local_ver:%s,re_ver:%s" % (local_size, ota_cxt.local_version, ota_cxt.remote_version))
+ logger.error("local_size:%d,local_ver:%s,re_ver:%s" % (local_size, ota_cxt.local_version, ota_cxt.remote_version))
if ((ota_cxt.local_version != ota_cxt.remote_version)
or (ota_cxt.download_size > ota_cxt.file_size)):
ota_cxt.download_size = 0
@@ -137,7 +138,7 @@ def _update_fw_downloaded_size(ota_cxt):
ota_cxt.download_size = local_size
rc = _cal_exist_fw_md5(ota_cxt)
if rc != 0:
- print("cal md5 error")
+ logger.error("cal md5 error")
os.remove(ota_cxt.info_file_path)
ota_cxt.download_size = 0
return 0
@@ -147,7 +148,7 @@ def _update_fw_downloaded_size(ota_cxt):
def _save_fw_data_to_file(ota_cxt, buf, buf_len):
if ota_cxt.file_path is None:
- print("file name is none")
+ logger.error("file name is none")
return -1
f = None
wr_len = 0
@@ -162,21 +163,9 @@ def _save_fw_data_to_file(ota_cxt, buf, buf_len):
if wr_len == buf_len:
break
else:
- print('write size error')
+ logger.debug('write size error')
f.close()
return -1
- """
- wr_buf = buf[wr_len:buf_len]
- wr_len = f.write(wr_buf)
- if wr_len > buf_len:
- raise ValueError('write size error')
- elif wr_len < buf_len:
- f.seek(ota_cxt.__download_size + wr_len, 0)
- continue
- else:
- break
- pass
- """
f.flush()
f.close()
@@ -201,10 +190,10 @@ def _wait_for_pub_ack(packet_id):
global g_pub_ack
while (g_pub_ack is not True):
- print("wait for ack...")
+ logger.debug("wait for ack...")
time.sleep(0.5)
if wait_cnt == 0:
- print("wait report pub ack timeout!")
+ logger.error("wait report pub ack timeout!")
break
wait_cnt -= 1
pass
@@ -213,47 +202,47 @@ def _wait_for_pub_ack(packet_id):
def _board_upgrade(fw_path):
- print("burning firmware...")
+ logger.debug("burning firmware...")
return 0
def example_ota():
+ global logger
+ provider = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+ qcloud = provider.hub
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
- print("\033[1;36m ota test start...\033[0m")
+ logger.debug("\033[1;36m ota test start...\033[0m")
- te.user_on_connect = on_connect
- te.user_on_disconnect = on_disconnect
- te.user_on_message = on_message
- te.user_on_publish = on_publish
- te.user_on_subscribe = on_subscribe
- te.user_on_unsubscribe = on_unsubscribe
- te.user_on_ota_report = on_ota_report
+ global product_id
+ global device_name
+ product_id = qcloud.getProductID()
+ device_name = qcloud.getDeviceName()
- te.mqttInit(mqtt_domain="")
- te.connect()
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
count = 0
while True:
- if te.isMqttConnected():
+ if qcloud.isMqttConnected():
break
else:
if count >= 3:
- # sys.exit()
- print("\033[1;31m ota test fail...\033[0m")
- # return False
- # 区分单元测试和sample
- return True
+ logger.error("\033[1;31m ota test fail...\033[0m")
+ return False
time.sleep(1)
count += 1
- te.otaInit()
+ qcloud.otaInit(product_id, device_name, on_ota_report)
cnt = 0
while True:
- if not te.isMqttConnected():
+ if not qcloud.isMqttConnected():
if cnt >= 10:
- print("mqtt disconnect")
+ logger.debug("mqtt disconnect")
break
time.sleep(1)
cnt += 1
@@ -263,7 +252,8 @@ def example_ota():
upgrade_fetch_success = True
ota_cxt = OtaContextData()
- te.otaReportVersion(_get_local_fw_running_version())
+ qcloud.otaReportVersion(product_id, device_name, _get_local_fw_running_version())
+
# wait for ack
time.sleep(1)
@@ -271,17 +261,17 @@ def example_ota():
if g_report_res:
download_finished = False
while (download_finished is not True):
- print("wait for ota upgrade command...")
- if te.otaIsFetching():
- file_size, state = te.otaIoctlNumber(QcloudHub.OtaCmdType.IOT_OTAG_FILE_SIZE)
+ logger.debug("wait for ota upgrade command")
+ if qcloud.otaIsFetching(product_id, device_name):
+ file_size, state = qcloud.otaIoctlNumber(product_id, device_name, OtaCmdType.IOT_OTAG_FILE_SIZE)
if state == "success":
ota_cxt.file_size = file_size
else:
- print("ota_ioctl_number fail..............")
+ logger.error("ota_ioctl_number failed")
break
pass
- version, state = te.otaIoctlString(QcloudHub.OtaCmdType.IOT_OTAG_VERSION, 32)
+ version, state = qcloud.otaIoctlString(product_id, device_name, OtaCmdType.IOT_OTAG_VERSION, 32)
if state == "success":
ota_cxt.remote_version = version
@@ -290,25 +280,25 @@ def example_ota():
_update_fw_downloaded_size(ota_cxt)
- rc = te.otaDownloadStart(ota_cxt.download_size, ota_cxt.file_size)
+ rc = qcloud.otaDownloadStart(product_id, device_name, ota_cxt.download_size, ota_cxt.file_size)
if rc != 0:
upgrade_fetch_success = False
break
- while (te.otaIsFetchFinished() is not True):
- buf, rv_len = te.otaFetchYield(5000)
+ while (qcloud.otaIsFetchFinished(product_id, device_name, ) is not True):
+ buf, rv_len = qcloud.otaFetchYield(product_id, device_name, 5000)
if rv_len > 0:
rc = _save_fw_data_to_file(ota_cxt, buf, rv_len)
if rc != 0:
- print("save data to file fail")
+ logger.error("save data to file fail")
upgrade_fetch_success = False
break
elif rv_len < 0:
- print("download fail rc:%d" % rv_len)
+ logger.error("download fail rc:%d" % rv_len)
upgrade_fetch_success = False
break
- fetched_size, state = te.otaIoctlNumber(QcloudHub.OtaCmdType.IOT_OTAG_FETCHED_SIZE)
+ fetched_size, state = qcloud.otaIoctlNumber(product_id, device_name, OtaCmdType.IOT_OTAG_FETCHED_SIZE)
if state == "success":
ota_cxt.download_size = fetched_size
else:
@@ -316,7 +306,7 @@ def example_ota():
rc = _update_local_fw_info(ota_cxt)
if rc != 0:
- print("update local fw info error")
+ logger.error("update local fw info error")
pass
#time.sleep(0.1)
@@ -324,12 +314,12 @@ def example_ota():
if upgrade_fetch_success:
os.remove(ota_cxt.info_file_path)
- firmware_valid, state = te.otaIoctlNumber(QcloudHub.OtaCmdType.IOT_OTAG_CHECK_FIRMWARE)
+ firmware_valid, state = qcloud.otaIoctlNumber(product_id, device_name, OtaCmdType.IOT_OTAG_CHECK_FIRMWARE)
if firmware_valid == 0:
- print("The firmware is valid")
+ logger.debug("The firmware download success")
upgrade_fetch_success = True
else:
- print("The firmware is invalid,state:%s" % state)
+ logger.error("The firmware is invalid,state:%s" % state)
upgrade_fetch_success = False
download_finished = True
@@ -344,14 +334,16 @@ def example_ota():
# Report after confirming that the burning is successful or failed
packet_id = 0
if upgrade_fetch_success:
- packet_id = te.otaReportUpgradeSuccess(None)
+ rc, packet_id = qcloud.otaReportUpgradeSuccess(product_id, device_name, None)
else:
- packet_id = te.otaReportUpgradeFail(None)
- if packet_id >= 0:
+ rc, packet_id = qcloud.otaReportUpgradeFail(product_id, device_name, None)
+ if rc == 0:
_wait_for_pub_ack(packet_id)
g_report_res = False
time.sleep(2)
- print("\033[1;36m ota test success...\033[0m")
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m ota test success...\033[0m")
+
return True
diff --git a/hub/sample/resourceManage/example_resourceManage.py b/hub/sample/resourceManage/example_resourceManage.py
new file mode 100644
index 0000000..82f78ca
--- /dev/null
+++ b/hub/sample/resourceManage/example_resourceManage.py
@@ -0,0 +1,153 @@
+import sys
+import logging
+import time
+import json
+from hub.hub import QcloudHub
+
+prduct_id = None
+device_name = None
+resource_progress_reply = False
+logger = None
+uploadTaskUrl = None
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, qos, payload, userdata):
+ logger.debug(
+ "%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_resourceManage_cb(topic, qos, payload, userdata):
+ logger.debug("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+
+ global uploadTaskUrl
+ global resource_progress_reply
+ payloadDic = payload
+ uploadType = payloadDic["type"]
+
+ if 'url' in payloadDic:
+ uploadTaskUrl = payloadDic["url"]
+ if uploadTaskUrl is None or len(uploadTaskUrl) == 0:
+ raise ValueError('create_upload_task_rsp url Invalid param')
+ else:
+ if uploadType == "report_upload_progress_rsp":
+ if payloadDic["result_code"] == 0 and payloadDic["result_msg"] == "ok":
+ resource_progress_reply = True
+
+ pass
+
+
+def example_resourceManage(isTest=True):
+ global logger
+ provider = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+ qcloud = provider.hub
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+ logger.debug("\033[1;36m resourceManage test start...\033[0m")
+
+ global prduct_id
+ global device_name
+ prduct_id = qcloud.getProductID()
+ device_name = qcloud.getDeviceName()
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m mqtt test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ rc, mid = qcloud.resourceInit(prduct_id, device_name, on_resourceManage_cb)
+ if rc != 0:
+ logger.error("resourceInit error")
+ return False
+
+ cnt = 0
+ while True:
+ if not qcloud.isMqttConnected():
+ if cnt >= 10:
+ logger.debug("mqtt disconnect")
+ break
+ time.sleep(1)
+ cnt += 1
+ continue
+ cnt = 0
+
+ """*********需要传资源文件绝对路径*******"""
+ rc, mid = qcloud.resourceCreateUploadTask(prduct_id, device_name,"")
+ # wait for ack
+ time.sleep(1)
+
+ if rc != 0:
+ logger.error("\033[1;31m resource create upload task fail ...\033[0m")
+ return False
+
+ logger.debug("\033[1;36m resource create upload task test success...\033[0m")
+
+ #判断URL
+ if uploadTaskUrl is None or len(uploadTaskUrl) == 0:
+ logger.error("\033[1;31m create_upload_task_rsp url is empty\033[0m")
+ return False
+
+ #上传资源文件
+ rc, mid = qcloud.resourceReportUploadProgress(prduct_id, device_name)
+ if rc != 0:
+ logger.error("\033[1;31m resource upload file progress fail ...\033[0m")
+ return False
+
+ logger.debug("\033[1;36m resource upload file progress success ...\033[0m")
+
+ qcloud.resourceUploadfile(uploadTaskUrl)
+
+ if resource_progress_reply:
+
+ rc, mid = qcloud.resourceFinished()
+ if rc != 0:
+ logger.error("\033[1;31m resource http put upload file progress error ...\033[0m")
+ return False
+
+ logger.debug("\033[1;36m resource http put upload file progress finish ...\033[0m")
+ break
+
+ else:
+
+ logger.error("\033[1;31m resource http put upload file progress fail ...\033[0m")
+ return False
+
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m resourceManage test success...\033[0m")
+
+ return True
\ No newline at end of file
diff --git a/hub/sample/rrpc/example_rrpc.py b/hub/sample/rrpc/example_rrpc.py
new file mode 100644
index 0000000..abba1cf
--- /dev/null
+++ b/hub/sample/rrpc/example_rrpc.py
@@ -0,0 +1,87 @@
+import sys
+import logging
+import time
+import json
+from hub.hub import QcloudHub
+
+prduct_id = None
+device_name = None
+rrpc_reply = False
+
+logger = None
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+def on_message(topic, qos, payload, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def on_rrpc_cb(topic, qos, payload, userdata):
+ logger.debug("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+
+ global prduct_id
+ global device_name
+ qcloud.rrpcReply(prduct_id, device_name, "ok", 2)
+ global rrpc_reply
+ rrpc_reply = True
+ pass
+
+def example_rrpc(isTest=True):
+ global logger
+ provider = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+ qcloud = provider.hub
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+ logger.debug("\033[1;36m rrpc test start...\033[0m")
+
+ global prduct_id
+ global device_name
+ prduct_id = qcloud.getProductID()
+ device_name = qcloud.getDeviceName()
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m mqtt test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ rc, mid = qcloud.rrpcInit(prduct_id, device_name, on_rrpc_cb)
+ if rc != 0:
+ logger.error("rrpcInit error")
+ return False
+
+ while rrpc_reply is False and isTest is False:
+ logger.debug("rrpc while...")
+ time.sleep(1)
+
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m rrpc test success...\033[0m")
+ return True
\ No newline at end of file
diff --git a/hub/sample/scenarized/example_aircond.py b/hub/sample/scenarized/example_aircond.py
new file mode 100644
index 0000000..fb58510
--- /dev/null
+++ b/hub/sample/scenarized/example_aircond.py
@@ -0,0 +1,103 @@
+import sys
+import logging
+import time
+import json
+from hub.hub import QcloudHub
+
+provider = QcloudHub(device_file="hub/sample/scenarized/aircond_device_info.json", tls=True)
+qcloud = provider.hub
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024*1024*10, 5, enable=True)
+reply = False
+air_open = False
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def on_aircond_cb(topic, qos, payload, userdata):
+ logger.debug("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ global reply
+ reply = True
+
+ cmd = json.loads(payload)
+ action = cmd["action"]
+ global air_open
+ if action == "come_home":
+ air_open = True
+ elif action == "leave_home":
+ air_open = False
+
+ pass
+
+def example_aircond():
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m mqtt connect fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ product_id = qcloud.getProductID()
+ device_name = qcloud.getDeviceName()
+
+ topic_list = []
+ topic_format = "%s/%s/%s"
+ topic_control = topic_format % (product_id, device_name, "control")
+ topic_list.append((topic_control, 1))
+ qcloud.registerUserCallback(topic_control, on_aircond_cb)
+ qcloud.subscribe(topic_control, 1)
+
+ temperature = 25
+ while True:
+ global air_open
+ if temperature >= 40:
+ temperature = 40
+ if temperature <= -10:
+ temperature = -10
+
+ if air_open is True:
+ logger.debug("[air is open] temperature %d" % temperature)
+ temperature -= 0.5
+ else:
+ logger.debug("[air is close] temperature %d" % temperature)
+ temperature += 0.5
+ time.sleep(1)
+
+ qcloud.disconnect()
+ return True
+example_aircond()
diff --git a/hub/sample/scenarized/example_door.py b/hub/sample/scenarized/example_door.py
new file mode 100644
index 0000000..2900e01
--- /dev/null
+++ b/hub/sample/scenarized/example_door.py
@@ -0,0 +1,92 @@
+import sys
+import logging
+import time
+from hub.hub import QcloudHub
+
+provider = QcloudHub(device_file="hub/sample/scenarized/door_device_info.json", tls=True)
+qcloud = provider.hub
+logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024*1024*10, 5, enable=True)
+reply = False
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ pass
+
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+
+def on_message(topic, payload, qos, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+ pass
+
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ global reply
+ reply = True
+ pass
+
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def on_door_cb(topic, qos, payload, userdata):
+ logger.debug("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+
+ pass
+
+def door_publish(topic, qos, message, device):
+ context = ""
+ if message == "come_home" or message == "leave_home":
+ context = '{"action": "%s", "targetDevice": "%s"}' % (message, device)
+
+ logger.debug("publish %s" % context)
+ qcloud.publish(topic, context, qos)
+ pass
+
+
+def example_door():
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m mqtt connect fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ product_id = qcloud.getProductID()
+ device_name = qcloud.getDeviceName()
+
+ topic_list = []
+ topic_format = "%s/%s/%s"
+ topic_event = topic_format % (product_id, device_name, "event")
+ topic_list.append((topic_event, 1))
+ qcloud.registerUserCallback(topic_event, on_door_cb)
+
+ door_publish(topic_event, 1, sys.argv[1], "AirConditioner1")
+
+ while reply is False:
+ logger.debug("wait reply...")
+ time.sleep(1)
+
+ qcloud.disconnect()
+
+ return True
+example_door()
diff --git a/hub/sample/shadow/example_shadow.py b/hub/sample/shadow/example_shadow.py
new file mode 100644
index 0000000..185bc11
--- /dev/null
+++ b/hub/sample/shadow/example_shadow.py
@@ -0,0 +1,157 @@
+import sys
+import logging
+import time
+import json
+from hub.hub import QcloudHub
+
+g_connected = False
+g_delta_arrived = False
+reply = False
+
+logger = None
+
+def on_connect(flags, rc, userdata):
+ logger.debug("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
+ global g_connected
+ g_connected = True
+
+ pass
+
+def on_disconnect(rc, userdata):
+ logger.debug("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
+ pass
+
+def on_message(topic, qos, payload, userdata):
+ logger.debug("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
+
+ message_type = payload["type"]
+ if message_type == "delta":
+ global g_delta_arrived
+ g_delta_arrived = True
+
+ pass
+
+def on_publish(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def on_subscribe(mid, granted_qos, userdata):
+ logger.debug("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
+ pass
+
+def on_unsubscribe(mid, userdata):
+ logger.debug("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
+ pass
+
+def on_shadow_cb(topic, qos, payload, userdata):
+ logger.debug("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
+ global reply
+ reply = True
+ pass
+
+def wait_for_reply():
+ cnt = 0
+ global reply
+ while cnt < 3:
+ if reply is True:
+ reply = False
+ return 0
+ time.sleep(0.5)
+ cnt += 1
+ return -1
+
+def example_shadow(isTest=True):
+ global logger
+ provider = QcloudHub(device_file="hub/sample/device_info.json", tls=True)
+ qcloud = provider.hub
+ logger = qcloud.logInit(qcloud.LoggerLevel.DEBUG, "logs/log", 1024 * 1024 * 10, 5, enable=True)
+
+ logger.debug("\033[1;36m shadow test start...\033[0m")
+
+ prduct_id = qcloud.getProductID()
+ device_name = qcloud.getDeviceName()
+
+ qcloud.registerMqttCallback(on_connect, on_disconnect,
+ on_message, on_publish,
+ on_subscribe, on_unsubscribe)
+ qcloud.connect()
+
+ count = 0
+ while True:
+ if qcloud.isMqttConnected():
+ break
+ else:
+ if count >= 3:
+ logger.error("\033[1;31m mqtt test fail...\033[0m")
+ return False
+ time.sleep(1)
+ count += 1
+
+ rc, mid = qcloud.shadowInit(prduct_id, device_name, on_shadow_cb)
+ if rc != 0:
+ logger.error("shadowInit error")
+ return False
+
+ cnt = 0
+ while True:
+ cnt += 1
+ p_prop = qcloud.device_property()
+ p_prop.key = "updateCount"
+ p_prop.data = cnt
+ p_prop.type = "int"
+
+ p_prop1 = qcloud.device_property()
+ p_prop1.key = "updateCount12"
+ p_prop1.data = "shadow"
+ p_prop1.type = "string"
+
+ rc, mid = qcloud.getShadow(prduct_id, device_name)
+ if rc != 0:
+ logger.error("getShadow error")
+ return False
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for reply timeout")
+ return False
+
+ global g_delta_arrived
+ if g_delta_arrived is True and cnt%3 == 0:
+ payload = qcloud.shadowJsonConstructDesireAllNull(prduct_id, device_name)
+ rc, mid = qcloud.shadowUpdate(prduct_id, device_name, payload, len(payload))
+ if rc != 0:
+ logger.error("shadowUpdate error")
+ return False
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for reply timeout")
+ return False
+ g_delta_arrived= False
+
+ payload = qcloud.shadowJsonConstructReport(prduct_id, device_name, p_prop, p_prop1)
+ rc, mid = qcloud.shadowUpdate(prduct_id, device_name, payload, len(payload))
+ if rc != 0:
+ logger.error("shadowUpdate error")
+ return False
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for reply timeout")
+ return False
+
+ rc, mid = qcloud.getShadow(prduct_id, device_name)
+ if rc != 0:
+ logger.error("getShadow error")
+ return False
+ rc = wait_for_reply()
+ if rc != 0:
+ logger.error("wait for reply timeout")
+ return False
+
+ if isTest:
+ break
+
+ time.sleep(3)
+ # qcloud.disconnect()
+ logger.debug("\033[1;36m shadow test success...\033[0m")
+
+ return True
+
diff --git a/hub/sample/test.py b/hub/sample/test.py
new file mode 100644
index 0000000..c67fbff
--- /dev/null
+++ b/hub/sample/test.py
@@ -0,0 +1,63 @@
+import unittest
+
+from dynreg import example_dynreg as dynregtest
+from gateway import example_gateway as gatewaytest
+from mqtt import example_mqtt as mqtttest
+from ota import example_ota as otatest
+from broadcast import example_broadcast as broadcasttest
+from rrpc import example_rrpc as rrpctest
+from shadow import example_shadow as shadowtest
+from httpAccess import example_http as httptest
+
+class MyTestCase(unittest.TestCase):
+
+ def setUp(self):
+ print ("\ninit sdk")
+ pass
+
+ def tearDown(self):
+ print ("deinit sdk")
+ pass
+
+ def test_mqtt(self):
+ ret = mqtttest.example_mqtt()
+ self.assertEqual(ret, True)
+
+ def test_dynreg(self):
+ ret = dynregtest.example_dynreg()
+ self.assertEqual(ret, True)
+ pass
+
+ def test_gateway(self):
+ ret = gatewaytest.example_gateway()
+ self.assertEqual(ret, True)
+ pass
+
+ def test_http(self):
+ ret = httptest.example_http()
+ self.assertEqual(ret, True)
+ pass
+
+ @unittest.skip("skipping")
+ def test_ota(self):
+ ret = otatest.example_ota()
+ self.assertEqual(ret, True)
+ pass
+
+ def test_broadcast(self):
+ ret = broadcasttest.example_broadcast()
+ self.assertEqual(ret, True)
+ pass
+
+ def test_rrpc(self):
+ ret = rrpctest.example_rrpc()
+ self.assertEqual(ret, True)
+ pass
+
+ def test_shadow(self):
+ ret = shadowtest.example_shadow()
+ self.assertEqual(ret, True)
+ pass
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/hub/services/__init__.py b/hub/services/__init__.py
new file mode 100644
index 0000000..20b6e13
--- /dev/null
+++ b/hub/services/__init__.py
@@ -0,0 +1 @@
+name = "services"
diff --git a/hub/services/broadcast/__init__.py b/hub/services/broadcast/__init__.py
new file mode 100644
index 0000000..a44d05b
--- /dev/null
+++ b/hub/services/broadcast/__init__.py
@@ -0,0 +1 @@
+name = "broadcast"
diff --git a/hub/services/broadcast/broadcast.py b/hub/services/broadcast/broadcast.py
new file mode 100644
index 0000000..ae9a54e
--- /dev/null
+++ b/hub/services/broadcast/broadcast.py
@@ -0,0 +1,40 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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 json
+from hub.utils.providers import ConnClientProvider
+from hub.utils.providers import TopicProvider
+
+class Broadcast(object):
+ def __init__(self, host, product_id, device_name, device_secret,
+ websocket=False, tls=True, logger=None):
+ self.__provider = ConnClientProvider(host, product_id, device_name, device_secret,
+ websocket=websocket, tls=tls, logger=logger)
+ self.__protocol = self.__provider.protocol
+ self.__topic = TopicProvider(product_id, device_name)
+ self.__logger = logger
+ self.__user_callback = {}
+
+ def __assert(self, param):
+ if param is None or len(param) == 0:
+ raise ValueError('Invalid param.')
+
+ def handle_broadcast(self, topic, qos, payload, userdata):
+ if self.__user_callback[topic] is not None:
+ self.__user_callback[topic](topic, qos, payload, userdata)
+ else:
+ self.__logger.error("no callback for topic %s" % topic)
+
+ def broadcast_init(self, broadcast_cb):
+ self.__user_callback[self.__topic.broadcast_topic_sub] = broadcast_cb
+ return self.__protocol.subscribe(self.__topic.broadcast_topic_sub, 0)
\ No newline at end of file
diff --git a/hub/services/gateway/__init__.py b/hub/services/gateway/__init__.py
new file mode 100644
index 0000000..aa93a9c
--- /dev/null
+++ b/hub/services/gateway/__init__.py
@@ -0,0 +1 @@
+name = "gateway"
diff --git a/hub/services/gateway/gateway.py b/hub/services/gateway/gateway.py
new file mode 100644
index 0000000..10f1f9b
--- /dev/null
+++ b/hub/services/gateway/gateway.py
@@ -0,0 +1,368 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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 sys
+import threading
+import time
+import json
+import random
+from enum import Enum
+from hub.utils.codec import Codec
+from hub.utils.providers import LoggerProvider
+from hub.utils.providers import ConnClientProvider
+
+class Gateway(object):
+ def __init__(self, host, product_id, device_name, device_secret,
+ websocket=False, tls=True):
+ # self.__logger = logger
+ self.__log_provider = LoggerProvider()
+ self.__logger = self.__log_provider.logger
+ self.__provider = ConnClientProvider(host, product_id, device_name, device_secret,
+ websocket=websocket, tls=tls)
+ self.__protocol = self.__provider.protocol
+ self.__codec = Codec()
+
+ # self.__gateway_session_client_id = None
+ self.__gateway_session_online_reply = {}
+ self.__gateway_session_offline_reply = {}
+ self.__gateway_session_bind_reply = {}
+ self.__gateway_session_unbind_reply = {}
+ self.__gateway_get_bind_list_reply = False
+ self.__gateway_raply = False
+
+ # 网关子设备property topic订阅的子设备列表
+ self.__gateway_subdev_bind_list = []
+
+ self.__gateway_session_online_lock = threading.Lock()
+ self.__gateway_session_offline_lock = threading.Lock()
+ self.__gateway_session_bind_lock = threading.Lock()
+ self.__gateway_session_unbind_lock = threading.Lock()
+
+ # 解析配置文件获取的子设备列表
+ self.gateway_subdev_config_list = []
+
+ class SessionState(Enum):
+ SUBDEV_SEESION_STATUS_INIT = 0
+ SUBDEV_SEESION_STATUS_ONLINE = 1
+ SUBDEV_SEESION_STATUS_OFFLINE = 2
+
+ # 网关子设备信息(是否需加入设备online/offline状态?)
+ class gateway_subdev(object):
+ def __init__(self):
+ self.product_id = None
+ self.device_name = None
+ self.session_status = 0
+
+ def handle_gateway(self, topic, message):
+ self.__gateway_raply = True
+ ptype = message["type"]
+ payload = message["payload"]
+ devices = payload["devices"]
+
+ if ptype == "describe_sub_devices":
+ for subdev in devices:
+ dev = self.gateway_subdev()
+ dev.product_id = subdev["product_id"]
+ dev.device_name = subdev["device_name"]
+ self.__gateway_subdev_bind_list.append(dev)
+ self.__gateway_get_bind_list_reply = True
+ else:
+ result = devices[0]["result"]
+ product_id = devices[0]["product_id"]
+ device_name = devices[0]["device_name"]
+ client_id = product_id + "/" + device_name
+
+ if ptype == "online":
+ self.__gateway_session_online_reply[client_id] = result
+ elif ptype == "offline":
+ self.__gateway_session_offline_reply[client_id] = result
+ elif ptype == "bind":
+ self.__gateway_session_bind_reply[client_id] = result
+ elif ptype == "unbind":
+ self.__gateway_session_unbind_reply[client_id] = result
+ pass
+
+ def __wait_for_session_reply(self, client_id, session):
+ if client_id is None or len(client_id) == 0:
+ raise ValueError('Invalid client_id.')
+ if session is None or len(session) == 0:
+ raise ValueError('Invalid session.')
+
+ if session == "online":
+ cnt = 0
+ while cnt < 3:
+ with self.__gateway_session_online_lock:
+ if client_id in self.__gateway_session_online_reply:
+ if self.__gateway_session_online_reply[client_id] == 0:
+ self.__gateway_session_online_reply.pop(client_id)
+ return 0
+ else:
+ break
+ pass
+ time.sleep(0.5)
+ cnt += 1
+ return 1
+ elif session == "offline":
+ cnt = 0
+ while cnt < 3:
+ with self.__gateway_session_offline_lock:
+ if client_id in self.__gateway_session_offline_reply:
+ if self.__gateway_session_offline_reply[client_id] == 0:
+ self.__gateway_session_offline_reply.pop(client_id)
+ return 0
+ else:
+ break
+ pass
+ time.sleep(0.5)
+ cnt += 1
+ return 1
+ elif session == "bind":
+ cnt = 0
+ while cnt < 3:
+ with self.__gateway_session_bind_lock:
+ if client_id in self.__gateway_session_bind_reply:
+ if self.__gateway_session_bind_reply[client_id] == 0:
+ self.__gateway_session_bind_reply.pop(client_id)
+ return 0
+ else:
+ break
+ pass
+ time.sleep(0.5)
+ cnt += 1
+ return 1
+ elif session == "unbind":
+ cnt = 0
+ while cnt < 3:
+ with self.__gateway_session_unbind_lock:
+ if client_id in self.__gateway_session_unbind_reply:
+ if self.__gateway_session_unbind_reply[client_id] == 0:
+ self.__gateway_session_unbind_reply.pop(client_id)
+ return 0
+ else:
+ break
+ pass
+ time.sleep(0.5)
+ cnt += 1
+ return 1
+ elif session == "describe_sub_devices":
+ cnt = 0
+ while cnt < 3:
+ if self.__gateway_get_bind_list_reply is True:
+ return 0
+ pass
+ time.sleep(0.5)
+ cnt += 1
+ return 1
+ pass
+
+ def __build_session_payload(self, ptype, pid, name, bind_secret):
+ if ptype == "online" or ptype == "offline" or ptype == "unbind":
+ payload = {
+ "type": ptype,
+ "payload": {
+ "devices": [{
+ "product_id": pid,
+ "device_name": name
+ }]
+ }
+ }
+ elif ptype == "bind":
+ nonce = random.randrange(2147483647)
+ timestamp = int(time.time())
+ sign_format = '%s%s;%d;%d'
+ sign_content = sign_format % (pid, name, nonce, timestamp)
+
+ # 计算二进制
+ sign_base64 = self.__codec.Base64.encode(self.__codec.Hmac.sha1_encode(bind_secret.encode("utf-8"),
+ sign_content.encode("utf-8")))
+ # sign = hmac.new(bind_secret.encode("utf-8"), sign_content.encode("utf-8"), hashlib.sha1).digest()
+ # sign_base64 = base64.b64encode(sign).decode('utf-8')
+
+ self.__logger.debug('sign base64 {}'.format(sign_base64))
+ payload = {
+ "type": ptype,
+ "payload": {
+ "devices": [{
+ "product_id": pid,
+ "device_name": name,
+ "signature": sign_base64,
+ "random": nonce,
+ "timestamp": timestamp,
+ "signmethod": "hmacsha1",
+ "authtype": "psk"
+ }]
+ }
+ }
+ elif ptype == "describe_sub_devices":
+ payload = {
+ "type": ptype
+ }
+ pass
+
+ return payload
+
+ def gateway_reset(self):
+ self.__gateway_session_online_reply.clear()
+ self.__gateway_session_offline_reply.clear()
+ self.__gateway_session_bind_reply.clear()
+ self.__gateway_session_unbind_reply.clear()
+ self.__gateway_raply = False
+
+ self.__gateway_subdev_bind_list.clear()
+ self.gateway_subdev_config_list.clear()
+
+ def gateway_init(self, topic, qos, device_info):
+ # 解析网关子设备信息,并添加到list
+ subdev_num = device_info['subDev']['subdev_num']
+ subdev_list = device_info['subDev']['subdev_list']
+
+ index = 0
+ while index < subdev_num:
+ p_subdev = self.gateway_subdev()
+ p_subdev.product_id = subdev_list[index]['sub_productId']
+ p_subdev.device_name = subdev_list[index]['sub_devName']
+ p_subdev.session_status = self.SessionState.SUBDEV_SEESION_STATUS_INIT
+ self.gateway_subdev_config_list.append(p_subdev)
+ index += 1
+ pass
+
+ rc, mid = self.__protocol.subscribe(topic, qos)
+ if rc != 0:
+ self.__logger.error("topic_subscribe error:rc:%d,topic:%s" % (rc, topic))
+ return rc, mid
+
+ def is_subdev_status_online(self, sub_productId, sub_devName):
+ """
+ 判断指定子设备SDK维护的状态是否为online
+ """
+ for sub in self.gateway_subdev_config_list:
+ if (sub.product_id == sub_productId
+ and sub.device_name == sub_devName):
+ return sub.session_status == self.SessionState.SUBDEV_SEESION_STATUS_ONLINE
+ return False
+
+ def update_subdev_status(self, sub_productId, sub_devName, status):
+ """
+ 设置指定子设备SDK维护的状态
+ """
+ for subdev in self.gateway_subdev_config_list:
+ if (subdev.product_id == sub_productId
+ and subdev.device_name == sub_devName):
+ # self.gateway_subdev_config_list.remove(sub)
+ subdev.product_id = sub_productId
+ subdev.device_name = sub_devName
+ if status == "online":
+ subdev.session_status = self.SessionState.SUBDEV_SEESION_STATUS_ONLINE
+ elif status == "offline":
+ subdev.session_status = self.SessionState.SUBDEV_SEESION_STATUS_OFFLINE
+ pass
+
+ def gateway_subdev_online(self, topic, qos, sub_productId, sub_devName):
+ # 保存当前会话的设备client_id
+ client_id = sub_productId + "/" + sub_devName
+ payload = self.__build_session_payload("online", sub_productId, sub_devName, None)
+
+ rc, mid = self.__protocol.publish(topic, json.dumps(payload), qos)
+ if rc != 0:
+ self.__logger.error("topic_publish error:rc:%d,topic:%s" % (rc, topic))
+ return rc, mid
+
+ rc = self.__wait_for_session_reply(client_id, "online")
+ if rc == 0:
+ self.__logger.debug("client:%s %s success" % (client_id, "online"))
+ else:
+ self.__logger.debug("client:%s %s fail" % (client_id, "online"))
+
+ return rc, mid
+
+ def gateway_subdev_offline(self, topic, qos, sub_productId, sub_devName):
+ client_id = sub_productId + "/" + sub_devName
+ payload = self.__build_session_payload("offline", sub_productId, sub_devName, None)
+
+ rc, mid = self.__protocol.publish(topic, json.dumps(payload), qos)
+ if rc != 0:
+ self.__logger.error("topic_publish error:rc:%d,topic:%s" % (rc, topic))
+ return rc, mid
+
+ rc = self.__wait_for_session_reply(client_id, "offline")
+ if rc == 0:
+ self.__logger.debug("client:%s %s success" % (client_id, "offline"))
+ else:
+ self.__logger.debug("client:%s %s fail" % (client_id, "offline"))
+
+ return rc, mid
+
+ def gateway_subdev_bind(self, topic, qos, sub_productId, sub_devName, sub_secret):
+ client_id = sub_productId + "/" + sub_devName
+ payload = self.__build_session_payload("bind", sub_productId, sub_devName, sub_secret)
+
+ rc, mid = self.__protocol.publish(topic, json.dumps(payload), qos)
+ if rc != 0:
+ self.__logger.error("topic_publish error:rc:%d,topic:%s" % (rc, topic))
+ return rc, mid
+
+ rc = self.__wait_for_session_reply(client_id, "bind")
+ if rc == 0:
+ self.__logger.debug("client:%s %s success" % (client_id, "bind"))
+ else:
+ self.__logger.debug("client:%s %s fail" % (client_id, "bind"))
+
+ return rc, mid
+
+ def gateway_subdev_unbind(self, topic, qos, sub_productId, sub_devName):
+ client_id = sub_productId + "/" + sub_devName
+ payload = self.__build_session_payload("unbind", sub_productId, sub_devName, None)
+
+ rc, mid = self.__protocol.publish(topic, json.dumps(payload), qos)
+ if rc != 0:
+ self.__logger.error("topic_publish error:rc:%d,topic:%s" % (rc, topic))
+ return rc, mid
+
+ rc = self.__wait_for_session_reply(client_id, "unbind")
+ if rc == 0:
+ self.__logger.debug("client:%s %s success" % (client_id, "unbind"))
+ else:
+ self.__logger.debug("client:%s %s fail" % (client_id, "unbind"))
+
+ return rc, mid
+
+ def gateway_get_subdev_bind_list(self, topic, qos, product_id, device_name):
+ client_id = product_id + "/" + device_name
+ payload = self.__build_session_payload("describe_sub_devices", product_id, device_name, None)
+
+ rc, mid = self.__protocol.publish(topic, json.dumps(payload), qos)
+ if rc != 0:
+ self.__logger.error("topic_publish error:rc:%d,topic:%s" % (rc, topic))
+ return rc, mid
+
+ rc = self.__wait_for_session_reply(client_id, "describe_sub_devices")
+ if rc == 0:
+ self.__logger.debug("client:%s %s success" % (client_id, "get bind list"))
+ else:
+ self.__logger.debug("client:%s %s fail" % (client_id, "get bind list"))
+
+ return rc, self.__gateway_subdev_bind_list
+
+ def gateway_get_subdev_config_list(self):
+ """
+ 获取配置文件中的子设备列表
+ """
+ return self.gateway_subdev_config_list
+
+ def gateway_subdev_subscribe(self, topic):
+ rc, mid = self.__protocol.subscribe(topic, 0)
+ if rc != 0:
+ self.__logger.error("topic_subscribe error:rc:%d,topic:%s" % (rc, topic))
+ return rc, mid
+
+ return rc, mid
diff --git a/hub/services/ota/__init__.py b/hub/services/ota/__init__.py
new file mode 100644
index 0000000..bd74610
--- /dev/null
+++ b/hub/services/ota/__init__.py
@@ -0,0 +1 @@
+name = "ota"
diff --git a/hub/services/ota/ota.py b/hub/services/ota/ota.py
new file mode 100644
index 0000000..2ef5874
--- /dev/null
+++ b/hub/services/ota/ota.py
@@ -0,0 +1,462 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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 json
+import time
+import urllib.request
+import urllib.parse
+import urllib.error
+import hashlib
+from enum import Enum
+from enum import IntEnum
+from hub.utils.codec import Codec
+from hub.utils.providers import TopicProvider
+from hub.utils.providers import ConnClientProvider
+
+class Ota(object):
+ def __init__(self, host, product_id, device_name, device_secret,
+ websocket=False, tls=True, logger=None):
+ self.__provider = ConnClientProvider(host, product_id, device_name, device_secret,
+ websocket=websocket, tls=tls, logger=logger)
+ self.__protocol = self.__provider.protocol
+ self.__logger = logger
+ self.__codec = Codec()
+ self.__topic = None
+
+ self.__ota_manager = None
+ self.__ota_version_len_min = 1
+ self.__ota_version_len_max = 32
+ self.http_manager = None
+ self.__user_callback = {}
+
+ class OtaState(Enum):
+ IOT_OTAS_UNINITED = 0
+ IOT_OTAS_INITED = 1
+ IOT_OTAS_FETCHING = 2
+ IOT_OTAS_FETCHED = 3
+ IOT_OTAS_DISCONNECTED = 4
+
+ class OtaCmdType(Enum):
+ IOT_OTAG_FETCHED_SIZE = 0
+ IOT_OTAG_FILE_SIZE = 1
+ IOT_OTAG_MD5SUM = 2
+ IOT_OTAG_VERSION = 3
+ IOT_OTAG_CHECK_FIRMWARE = 4
+
+ class OtaProgressCode(Enum):
+ IOT_OTAP_BURN_FAILED = -4
+ IOT_OTAP_CHECK_FALIED = -3
+ IOT_OTAP_FETCH_FAILED = -2
+ IOT_OTAP_GENERAL_FAILED = -1
+ IOT_OTAP_FETCH_PERCENTAGE_MIN = 0
+ IOT_OTAP_FETCH_PERCENTAGE_MAX = 100
+
+ class OtaReportType(IntEnum):
+ IOT_OTAR_DOWNLOAD_TIMEOUT = -1
+ IOT_OTAR_FILE_NOT_EXIST = -2
+ IOT_OTAR_AUTH_FAIL = -3
+ IOT_OTAR_MD5_NOT_MATCH = -4
+ IOT_OTAR_UPGRADE_FAIL = -5
+ IOT_OTAR_NONE = 0
+ IOT_OTAR_DOWNLOAD_BEGIN = 1
+ IOT_OTAR_DOWNLOADING = 2
+ IOT_OTAR_UPGRADE_BEGIN = 3
+ IOT_OTAR_UPGRADE_SUCCESS = 4
+
+ class ota_manage(object):
+ def __init__(self):
+ self.channel = None
+ self.state = 0
+ self.size_fetched = 0
+ self.size_last_fetched = 0
+ self.file_size = 0
+ self.purl = None
+ self.version = None
+ self.md5sum = None
+ self.md5 = None
+ self.host = None
+ self.is_https = False
+
+ self.report_timestamp = 0
+
+ # http连接管理
+ self.http_manager = None
+
+ class http_manage(object):
+ def __init__(self):
+ self.handle = None
+ self.request = None
+ self.header = None
+ self.host = None
+ self.https_context = None
+
+ self.err_reason = None
+ self.err_code = 0
+ pass
+
+ def __assert(self, param):
+ if param is None or len(param) == 0:
+ raise ValueError('Invalid param.')
+
+ def __ota_publish(self, message, qos):
+ topic_pub = self.__topic.ota_report_topic_pub
+ rc, mid = self.__protocol.publish(topic_pub, json.dumps(message), qos)
+ return rc, mid
+
+ def __ota_info_get(self, payload):
+ size = payload["file_size"]
+ if size > 0:
+ self.__ota_manager.file_size = size
+ version = payload["version"]
+ if version is not None and len(version) > 0:
+ self.__ota_manager.version = version
+ url = payload["url"]
+ if url is not None and len(url) > 0:
+ self.__ota_manager.purl = url
+ pos = url.find("https://")
+ last_pos = url.rfind("/")
+ if pos >= 0:
+ self.__ota_manager.is_https = True
+ host = url[8:last_pos]
+ else:
+ host = url[7:last_pos]
+ self.__ota_manager.host = host
+
+ md5sum = payload["md5sum"]
+ if md5sum is not None and len(md5sum) > 0:
+ self.__ota_manager.md5sum = md5sum
+
+ self.__ota_manager.state = self.OtaState.IOT_OTAS_FETCHING
+
+ def __ota_http_deinit(self, http):
+ print("__ota_http_deinit do nothing")
+
+ def __message_splice(self, state, progress, result_code, result_msg, version, ptype):
+ message = None
+ code = "%d" % (result_code)
+ if ptype == 1:
+ message = {
+ "type": "report_progress",
+ "report": {
+ "progress": {
+ "state": state,
+ "percent": str(progress),
+ "result_code": code,
+ "result_msg": result_msg
+ },
+ "version": version
+ }
+ }
+ elif ptype == 0:
+ message = {
+ "type": "report_progress",
+ "report": {
+ "progress": {
+ "state": state,
+ "result_code": code,
+ "result_msg": result_msg
+ },
+ "version": version
+ }
+ }
+
+ return message
+
+ def __ota_gen_report_msg(self, version, progress, report_type):
+ message = None
+ if report_type == self.OtaReportType.IOT_OTAR_DOWNLOAD_BEGIN:
+ message = self.__message_splice("downloading", 0, 0, "", version, 1)
+ elif report_type == self.OtaReportType.IOT_OTAR_DOWNLOADING:
+ message = self.__message_splice("downloading", progress, 0, "", version, 1)
+ elif ((report_type == self.OtaReportType.IOT_OTAR_DOWNLOAD_TIMEOUT)
+ or (report_type == self.OtaReportType.IOT_OTAR_FILE_NOT_EXIST)
+ or (report_type == self.OtaReportType.IOT_OTAR_MD5_NOT_MATCH)
+ or (report_type == self.OtaReportType.IOT_OTAR_AUTH_FAIL)
+ or (report_type == self.OtaReportType.IOT_OTAR_UPGRADE_FAIL)):
+ message = self.__message_splice("fail", progress, report_type, "time_out", version, 0)
+ elif report_type == self.OtaReportType.IOT_OTAR_UPGRADE_BEGIN:
+ message = self.__message_splice("burning", progress, 0, "", version, 0)
+ elif report_type == self.OtaReportType.IOT_OTAR_UPGRADE_SUCCESS:
+ message = self.__message_splice("done", progress, 0, "", version, 0)
+ else:
+ self.__logger.error("not support report_type:%d" % report_type)
+ message = None
+
+ return message
+
+ def _ota_report_upgrade_result(self, version, report_type):
+ if self.__ota_manager.state == self.OtaState.IOT_OTAS_UNINITED:
+ raise ValueError('ota handle is uninitialized')
+ message = self.__ota_gen_report_msg(version, 1, report_type)
+ if message is not None:
+ return self.__ota_publish(message, 1)
+ else:
+ self.__logger.error("message is none")
+ return -1, -1
+
+ def _ota_report_progress(self, progress, version, report_type):
+ if self.__ota_manager.state == self.OtaState.IOT_OTAS_UNINITED:
+ raise ValueError('ota handle is uninitialized')
+ message = self.__ota_gen_report_msg(version, progress, report_type)
+ if message is not None:
+ self.__logger.debug("[ota report] %s" % (message))
+ return self.__ota_publish(message, 0)
+ else:
+ self.__logger.error("message is none")
+ return -1, -1
+
+ def ota_report_upgrade_success(self, version):
+ if version is None:
+ rc, mid = self._ota_report_upgrade_result(self.__ota_manager.version,
+ self.OtaReportType.IOT_OTAR_UPGRADE_SUCCESS)
+ else:
+ rc, mid = self._ota_report_upgrade_result(version, self.OtaReportType.IOT_OTAR_UPGRADE_SUCCESS)
+
+ return rc, mid
+
+ def ota_report_upgrade_fail(self, version):
+ if version is None:
+ rc, mid = self._ota_report_upgrade_result(self.__ota_manager.version,
+ self.OtaReportType.IOT_OTAR_UPGRADE_FAIL)
+ else:
+ rc, mid = self._ota_report_upgrade_result(version, self.OtaReportType.IOT_OTAR_UPGRADE_FAIL)
+
+ return rc, mid
+
+ def ota_ioctl_number(self, cmd_type):
+ if ((self.__ota_manager.state == self.OtaState.IOT_OTAS_INITED)
+ or (self.__ota_manager.state == self.OtaState.IOT_OTAS_UNINITED)):
+ return -1, "state error"
+
+ if cmd_type.value == self.OtaCmdType.IOT_OTAG_FETCHED_SIZE.value:
+ return self.__ota_manager.size_fetched, "success"
+ elif cmd_type.value == self.OtaCmdType.IOT_OTAG_FILE_SIZE.value:
+ return self.__ota_manager.file_size, "success"
+ elif cmd_type.value == self.OtaCmdType.IOT_OTAG_CHECK_FIRMWARE.value:
+ if self.__ota_manager.state is not self.OtaState.IOT_OTAS_FETCHED:
+ return -1, "state error"
+ md5sum = self.__ota_manager.md5.hexdigest()
+ if md5sum == self.__ota_manager.md5sum:
+ return 0, "success"
+ else:
+ self._ota_report_upgrade_result(self.__ota_manager.version,
+ self.OtaReportType.IOT_OTAR_MD5_NOT_MATCH)
+ return -1, "md5 error"
+ pass
+
+ return -1, "cmd type error"
+
+ def ota_ioctl_string(self, cmd_type, length):
+ if ((self.__ota_manager.state == self.OtaState.IOT_OTAS_INITED)
+ or (self.__ota_manager.state == self.OtaState.IOT_OTAS_UNINITED)):
+ return "null", "state error"
+
+ if cmd_type.value == self.OtaCmdType.IOT_OTAG_VERSION.value:
+ if len(self.__ota_manager.version) > length:
+ return "null", "version length error"
+ else:
+ return self.__ota_manager.version, "success"
+ elif cmd_type.value == self.OtaCmdType.IOT_OTAG_MD5SUM.value:
+ if len(self.__ota_manager.md5sum) > length:
+ return "null", "md5sum length error"
+ else:
+ return self.__ota_manager.md5sum, "success"
+
+ return "null", "cmd type error"
+
+ def ota_reset_md5(self):
+ self.__ota_manager.md5 = None
+ self.__ota_manager.md5 = hashlib.md5()
+ return 0
+
+ def ota_md5_update(self, buf):
+ if buf is None:
+ self.__logger.error("buf is none")
+ return -1
+ if self.__ota_manager.md5 is None:
+ self.__logger.error("md5 handle is uninitialized")
+ return -1
+
+ # self.__ota_manager.md5.update(buf.encode(encoding='utf-8'))
+ self.__ota_manager.md5.update(buf)
+ return 0
+
+ def handle_ota(self, topic, qos, payload, userdata):
+ ptype = payload["type"]
+ if ptype == "report_version_rsp":
+ """
+ 回调用户
+ """
+ if self.__user_callback[topic] is not None:
+ self.__user_callback[topic](topic, qos, payload, userdata)
+ else:
+ self.__logger.error("no callback for topic %s" % topic)
+
+ elif ptype == "update_firmware":
+ self.__ota_info_get(payload)
+
+ def ota_init(self, product_id, device_name, ota_cb):
+ """
+ ota资源初始化
+ """
+ self.__topic = TopicProvider(product_id, device_name)
+ topic_sub = self.__topic.ota_update_topic_sub
+ self.__user_callback[topic_sub] = ota_cb
+
+ self.__ota_manager = self.ota_manage()
+ self.__ota_manager.state = self.OtaState.IOT_OTAS_UNINITED
+
+ return self.__protocol.subscribe(topic_sub, 1)
+
+ def ota_manager_init(self):
+ self.__ota_manager.state = self.OtaState.IOT_OTAS_INITED
+ self.__ota_manager.md5 = hashlib.md5()
+
+ # 是否应将ota句柄传入(支持多个下载进程?)
+ def ota_is_fetching(self):
+ return (self.__ota_manager.state == self.OtaState.IOT_OTAS_FETCHING)
+
+ def ota_is_fetch_finished(self):
+ return (self.__ota_manager.state == self.OtaState.IOT_OTAS_FETCHED)
+
+ def http_init(self, host, url, offset, size, timeout_sec):
+ range_format = "bytes=%d-%d"
+ srange = range_format % (offset, size)
+
+ header = {}
+ header["Host"] = host
+ header["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
+ header["Accept-Encoding"] = "gzip, deflate"
+ header["Range"] = srange
+
+ self.http_manager = self.http_manage()
+ self.http_manager.header = header
+ self.http_manager.host = host
+
+ if self.__ota_manager.is_https:
+ # context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cadata=self.__iot_ca_crt)
+ context = self.__codec.Ssl().create_content()
+ self.http_manager.https_context = context
+ try:
+ self.http_manager.request = urllib.request.Request(url=url, headers=header)
+ self.http_manager.handle = urllib.request.urlopen(self.http_manager.request,
+ context=context,
+ timeout=timeout_sec)
+ except urllib.error.HTTPError as e:
+ self.__logger.error("https connect error:%d" % e.code)
+ self.http_manager.err_code = e.code
+ return 1
+ except urllib.error.URLError as e:
+ self.__logger.error("https connect error:%s" % e.reason)
+ self.http_manager.err_reason = e.reason
+ return 1
+ else:
+ try:
+ self.http_manager.request = urllib.request.Request(url=url, headers=header)
+ self.http_manager.handle = urllib.request.urlopen(self.http_manager.request,
+ timeout=timeout_sec)
+ except Exception as e:
+ self.__logger.error("http connect error:%s" % str(e))
+ return 1
+ return 0
+
+ def http_fetch(self, buf_len):
+ if self.http_manager.handle is None:
+ return None, -1
+ try:
+ buf = self.http_manager.handle.read(buf_len)
+ return buf, len(buf)
+ except Exception as e:
+ self.__logger.error("http read error:%s" % str(e))
+ return None, -2
+
+ def ota_report_version(self, version):
+ self.__assert(version)
+ if len(version) < self.__ota_version_len_min or len(version) > self.__ota_version_len_max:
+ raise ValueError('Invalid version length')
+ if self.__ota_manager.state == self.OtaState.IOT_OTAS_UNINITED:
+ raise ValueError('ota handle is uninitialized')
+ report = {
+ "type": "report_version",
+ "report": {
+ "version": version
+ }
+ }
+ return self.__ota_publish(report, 1)
+
+ def ota_download_start(self, offset, size):
+ if offset < 0 or size <= 0:
+ raise ValueError('Invalid length.')
+ if offset == 0:
+ self.ota_reset_md5()
+ self.__ota_http_deinit(self.__ota_manager.http_manager)
+ # 断点续传初始值不为0
+ self.__ota_manager.size_fetched += offset
+
+ rc = self.http_init(self.__ota_manager.host, self.__ota_manager.purl, offset, size, 10000 / 1000)
+ if rc != 0:
+ if self.http_manager.err_code == 403:
+ self._ota_report_upgrade_result(self.__ota_manager.version,
+ self.OtaReportType.IOT_OTAR_AUTH_FAIL)
+ elif self.http_manager.err_code == 404:
+ self._ota_report_upgrade_result(self.__ota_manager.version,
+ self.OtaReportType.IOT_OTAR_FILE_NOT_EXIST)
+ elif self.http_manager.err_code == 408:
+ self._ota_report_upgrade_result(self.__ota_manager.version,
+ self.OtaReportType.IOT_OTAR_DOWNLOAD_TIMEOUT)
+ else:
+ # 其他错误判断(error.reason)
+ self.__logger.error("http_init error:%d" % self.http_manager.err_code)
+
+ return rc
+
+ def ota_fetch_yield(self, buf_len):
+ if self.__ota_manager.state != self.OtaState.IOT_OTAS_FETCHING:
+ self.__logger.error("ota state is not fetching")
+ return None, -1
+ # http read
+ buf, rv_len = self.http_fetch(buf_len)
+ if rv_len < 0:
+ if rv_len == -2:
+ self._ota_report_upgrade_result(self.__ota_manager.version,
+ self.OtaReportType.IOT_OTAR_DOWNLOAD_TIMEOUT)
+ return None, -2
+ else:
+ if self.__ota_manager.size_fetched == 0:
+ self._ota_report_progress(self.OtaProgressCode.IOT_OTAP_FETCH_PERCENTAGE_MIN,
+ self.__ota_manager.version,
+ self.OtaReportType.IOT_OTAR_DOWNLOAD_BEGIN)
+ self.__ota_manager.report_timestamp = int(time.time())
+ pass
+ self.__ota_manager.size_last_fetched = rv_len
+ self.__ota_manager.size_fetched += rv_len
+
+ percent = int((self.__ota_manager.size_fetched * 100) / self.__ota_manager.file_size)
+ if percent == 100:
+ self._ota_report_progress(percent, self.__ota_manager.version,
+ self.OtaReportType.IOT_OTAR_DOWNLOADING)
+ else:
+ timestamp = int(time.time())
+ # 间隔1秒上报一次
+ if (((timestamp - self.__ota_manager.report_timestamp) >= 1)
+ and (self.__ota_manager.size_last_fetched > 0)):
+ self.__ota_manager.report_timestamp = timestamp
+ self._ota_report_progress(percent, self.__ota_manager.version,
+ self.OtaReportType.IOT_OTAR_DOWNLOADING)
+
+ if self.__ota_manager.size_fetched >= self.__ota_manager.file_size:
+ self.__ota_manager.state = self.OtaState.IOT_OTAS_FETCHED
+
+ self.__ota_manager.md5.update(buf)
+
+ return buf, rv_len
\ No newline at end of file
diff --git a/hub/services/resourceManage/__init__.py b/hub/services/resourceManage/__init__.py
new file mode 100644
index 0000000..7dc6659
--- /dev/null
+++ b/hub/services/resourceManage/__init__.py
@@ -0,0 +1 @@
+name = "resourceManage"
\ No newline at end of file
diff --git a/hub/services/resourceManage/resourceManage.py b/hub/services/resourceManage/resourceManage.py
new file mode 100644
index 0000000..c3d4198
--- /dev/null
+++ b/hub/services/resourceManage/resourceManage.py
@@ -0,0 +1,129 @@
+import json
+from hub.utils.providers import ConnClientProvider
+from hub.utils.providers import TopicProvider
+import hashlib
+from enum import Enum
+from enum import IntEnum
+
+class ResourceManage(object):
+ def __init__(self, host, product_id, device_name, device_secret,
+ websocket=False, tls=True, logger=None):
+ self.__provider = ConnClientProvider(host, product_id, device_name, device_secret,
+ websocket=websocket, tls=tls, logger=logger)
+ self.__protocol = self.__provider.protocol
+ self.__topic = TopicProvider(product_id, device_name)
+ self.__logger = logger
+ self.__process_id = None
+ self.__user_callback = {}
+ self.http_manager = None
+ self.__resource_manager = None
+
+ class ResourceState(Enum):
+ IOT_RESOURCES_UNINITED = 0
+ IOT_RESOURCES_INITED = 1
+ IOT_RESOURCES_FETCHING = 2
+ IOT_RESOURCES_FETCHED = 3
+ IOT_RESOURCES_DISCONNECTED = 4
+
+ class resource_manage(object):
+ def __init__(self):
+ self.channel = None
+ self.state = 0
+ self.size_fetched = 0
+ self.size_last_fetched = 0
+ self.file_size = 0
+ self.purl = None
+ self.version = None
+ self.md5sum = None
+ self.md5 = None
+ self.host = None
+ self.is_https = False
+
+ self.report_timestamp = 0
+
+ # http连接管理
+ self.http_manager = None
+
+ def __assertFileName(self, param):
+ if param is None or len(param) == 0:
+ raise ValueError('FileName is empty. Invalid param')
+
+ def __assertFileSize(self, param):
+ if param is None or param == 0:
+ raise ValueError('File transfer content is empty. Invalid param')
+
+ def __assertFileMd5(self, param):
+ if param is None or len(param) == 0:
+ raise ValueError('File md5 is empty. Invalid param ')
+
+ def handle_resource(self, topic, qos, payload, userdata):
+
+ if self.__user_callback[topic] is not None:
+ self.__user_callback[topic](topic, qos, payload, userdata)
+ else:
+ self.__logger.error("no callback for topic %s" % topic)
+
+ def resource_init(self,productId, deviceName ,resource_cb):
+
+ topic = self.__topic.resource_manager_topic_sub
+ self.__user_callback[topic] = resource_cb
+
+ self.__resource_manage = self.resource_manage()
+ self.__resource_manage.state = self.ResourceState.IOT_RESOURCES_UNINITED
+
+ return self.__protocol.subscribe(topic, 1)
+
+ def resource_manager_init(self):
+ self.__resource_manage.state = self.ResourceState.IOT_RESOURCES_INITED
+ self.__resource_manage.md5 = hashlib.md5()
+
+ def resource_publish(self, message, qos):
+ topic_pub = self.__topic.resource_manager_topic_pub
+ rc, mid = self.__protocol.publish(topic_pub, json.dumps(message), qos)
+ return rc, mid
+
+ def resource_create_upload_task(self, size, name, md5sum):
+ self.__assertFileSize(size)
+ self.__assertFileName(name)
+ self.__assertFileMd5(md5sum)
+
+ if self.__resource_manage.state == self.ResourceState.IOT_RESOURCES_UNINITED:
+ raise ValueError('resource handle is uninitialized')
+
+ createTask = {
+ "type": "create_upload_task",
+ "size": size,
+ "name": name,
+ "md5sum": md5sum,
+ }
+ return self.resource_publish(createTask,1)
+
+ def resource_report_upload_progress(self, name, percent, state):
+ self.__assertFileName(name)
+
+ if self.__resource_manage.state == self.ResourceState.IOT_RESOURCES_UNINITED:
+ raise ValueError('resource handle is uninitialized')
+
+ uploadProgress = {
+ "type": "report_upload_progress",
+ "name": name,
+ "progress": {
+ "state": state,
+ "percent": percent,
+ "result_code": 0,
+ "result_msg": ""
+ }
+ }
+ return self.resource_publish(uploadProgress, 1)
+
+
+ def resource_file_md5(self, fileName):
+ m = hashlib.md5() # 创建md5对象
+ with open(fileName, 'rb') as fobj:
+ while True:
+ data = fobj.read(4096)
+ if not data:
+ break
+ m.update(data) # 更新md5对象
+
+ return m.hexdigest() # 返回md5对象
\ No newline at end of file
diff --git a/hub/services/rrpc/__init__.py b/hub/services/rrpc/__init__.py
new file mode 100644
index 0000000..2de6c9e
--- /dev/null
+++ b/hub/services/rrpc/__init__.py
@@ -0,0 +1 @@
+name = "rrpc"
diff --git a/hub/services/rrpc/rrpc.py b/hub/services/rrpc/rrpc.py
new file mode 100644
index 0000000..2f6141d
--- /dev/null
+++ b/hub/services/rrpc/rrpc.py
@@ -0,0 +1,64 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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 json
+from hub.utils.providers import ConnClientProvider
+from hub.utils.providers import TopicProvider
+
+class Rrpc(object):
+ def __init__(self, host, product_id, device_name, device_secret,
+ websocket=False, tls=True, logger=None):
+ self.__provider = ConnClientProvider(host, product_id, device_name, device_secret,
+ websocket=websocket, tls=tls, logger=logger)
+ self.__protocol = self.__provider.protocol
+ self.__topic = TopicProvider(product_id, device_name)
+ self.__logger = logger
+ self.__process_id = None
+ self.__user_callback = {}
+
+ def __assert(self, param):
+ if param is None or len(param) == 0:
+ raise ValueError('Invalid param.')
+
+ def __rrpc_get_process_id(self, topic):
+ pos = topic.rfind("/")
+ if pos > 0:
+ self.__process_id = topic[pos + 1:len(topic)]
+ return 0
+ else:
+ self.__logger.error("cannot found process id from topic:%s" % topic)
+ return -1
+
+ def handle_rrpc(self, topic, qos, payload, userdata):
+ self.__rrpc_get_process_id(topic)
+
+ pos = topic.rfind("/")
+ topic_split = topic[0:pos]
+ if self.__user_callback[topic_split] is not None:
+ self.__user_callback[topic_split](topic_split, qos, payload, userdata)
+ else:
+ self.__logger.error("no callback for topic_split %s" % topic_split)
+
+ def rrpc_init(self, rrpc_cb):
+ topic = self.__topic.rrpc_topic_sub_prefix + "/+"
+ self.__user_callback[self.__topic.rrpc_topic_sub_prefix] = rrpc_cb
+
+
+ return self.__protocol.subscribe(topic, 0)
+
+ def rrpc_reply(self, reply):
+ self.__assert(self.__process_id)
+ self.__assert(reply)
+
+ topic = self.__topic.rrpc_topic_pub_prefix + "/" + self.__process_id
+ return self.__protocol.publish(topic, json.dumps(reply), 0)
\ No newline at end of file
diff --git a/hub/services/shadow/__init__.py b/hub/services/shadow/__init__.py
new file mode 100644
index 0000000..ed0c815
--- /dev/null
+++ b/hub/services/shadow/__init__.py
@@ -0,0 +1 @@
+name = "shadow"
diff --git a/hub/services/shadow/shadow.py b/hub/services/shadow/shadow.py
new file mode 100644
index 0000000..ff1c633
--- /dev/null
+++ b/hub/services/shadow/shadow.py
@@ -0,0 +1,104 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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 json
+from hub.utils.providers import ConnClientProvider
+from hub.utils.providers import TopicProvider
+
+class Shadow(object):
+ def __init__(self, host, product_id, device_name, device_secret,
+ websocket=False, tls=True, logger=None):
+ self.__provider = ConnClientProvider(host, product_id, device_name, device_secret,
+ websocket=websocket, tls=tls, logger=logger)
+ self.__protocol = self.__provider.protocol
+ self.__topic = TopicProvider(product_id, device_name)
+ self.__logger = logger
+ self.__shadow_token_num = 0
+ self.__user_callback = {}
+
+ def __assert(self, param):
+ if param is None or len(param) == 0:
+ raise ValueError('Invalid param.')
+
+ def handle_shadow(self, topic, qos, payload, userdata):
+ """
+ 回调用户
+ """
+ if self.__user_callback[topic] is not None:
+ self.__user_callback[topic](topic, qos, payload, userdata)
+ else:
+ self.__logger.error("no callback for topic %s" % topic)
+
+ def shadow_init(self, shadow_cb):
+ self.__user_callback[self.__topic.shadow_topic_sub] = shadow_cb
+ return self.__protocol.subscribe(self.__topic.shadow_topic_sub, 0)
+
+ def get_shadow(self, product_id):
+ self.__assert(product_id)
+
+ client_token = product_id + "-" + str(self.__shadow_token_num)
+ self.__shadow_token_num += 1
+
+ message = {
+ "type": "get",
+ "clientToken": client_token
+ }
+ return self.__protocol.publish(self.__topic.shadow_topic_pub, json.dumps(message), 0)
+
+ def shadow_json_construct_desire_null(self, product_id):
+ self.__assert(product_id)
+ client_token = product_id + "-" + str(self.__shadow_token_num)
+ self.__shadow_token_num += 1
+ json_out = {
+ "type": "update",
+ "state": {
+ "desired": None
+ },
+ "clientToken": client_token
+ }
+ return json_out
+
+ def shadow_update(self, shadow_docs):
+ self.__assert(shadow_docs)
+
+ return self.__protocol.publish(self.__topic.shadow_topic_pub,json.dumps(shadow_docs), 0)
+
+ def shadow_json_construct_report(self, product_id, *args):
+ self.__assert(product_id)
+
+ format_string = '"%s":"%s"'
+ format_int = '"%s":%d'
+ report_string = '{"type": "update", "state": {"reported": {'
+ arg_cnt = 0
+
+ for arg in args:
+ arg_cnt += 1
+ if arg.type == "int" or arg.type == "float":
+ report_string += format_int % (arg.key, arg.data)
+ elif arg.type == "string":
+ report_string += format_string % (arg.key, arg.data)
+ else:
+ self.__logger.error("type not support")
+ arg.data = " "
+ if arg_cnt < len(args):
+ report_string += ","
+ pass
+ report_string += '}}, "clientToken": "%s"}'
+
+ client_token = product_id + "-" + str(self.__shadow_token_num)
+ self.__shadow_token_num += 1
+
+ report_out = report_string % (client_token)
+ json_out = json.loads(report_out)
+
+ return json_out
\ No newline at end of file
diff --git a/hub/utils/__init__.py b/hub/utils/__init__.py
new file mode 100644
index 0000000..2106d1f
--- /dev/null
+++ b/hub/utils/__init__.py
@@ -0,0 +1 @@
+name = "utils"
diff --git a/hub/utils/codec.py b/hub/utils/codec.py
new file mode 100644
index 0000000..7875103
--- /dev/null
+++ b/hub/utils/codec.py
@@ -0,0 +1,124 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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 hashlib
+import hmac
+import ssl
+import base64
+from Crypto.Cipher import AES
+
+import Crypto.Signature.PKCS1_v1_5 as sign_PKCS1_v1_5 #用于签名/验签
+from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5 #用于加密
+from Crypto import Hash
+from Crypto.PublicKey import RSA
+
+class Codec(object):
+ def __init__(self):
+ self.__init = None
+
+ class _AESUtil:
+ __BLOCK_SIZE_16 = BLOCK_SIZE_16 = AES.block_size
+
+ '''
+ @staticmethod
+ def encryt(str, key, iv):
+ cipher = AES.new(key, AES.MODE_CBC, iv)
+ x = AESUtil.__BLOCK_SIZE_16 - (len(str) % AESUtil.__BLOCK_SIZE_16)
+ if x != 0:
+ str = str + chr(x) * x
+ msg = cipher.encrypt(str)
+ msg = base64.b64encode(msg)
+ return msg
+ '''
+
+ @staticmethod
+ def decrypt(encrypt_str, key, init_vector):
+ cipher = AES.new(key, AES.MODE_CBC, init_vector)
+ decrypt_bytes = base64.b64decode(encrypt_str)
+ return cipher.decrypt(decrypt_bytes)
+
+ class Hmac:
+ @staticmethod
+ def sha1_encode(key, content):
+ return hmac.new(key, content, hashlib.sha1).digest()
+
+ @staticmethod
+ def sha256_encode(key, content):
+ return hmac.new(key, content, hashlib.sha256).digest()
+ class RSA:
+ @staticmethod
+ def sha256_encode(key, content):
+ signer_pri_obj = sign_PKCS1_v1_5.new(RSA.importKey(key))
+ rand_hash = Hash.SHA256.new()
+ rand_hash.update(content)
+ signature = signer_pri_obj.sign(rand_hash)
+ sign_result = base64.b64encode(signature).decode('utf-8')
+ return sign_result
+
+ class Hash:
+ @staticmethod
+ def sha256_encode(content):
+ return hashlib.sha256(content).hexdigest()
+
+ class Base64:
+ @staticmethod
+ def encode(content):
+ return base64.b64encode(content).decode('utf-8')
+
+ @staticmethod
+ def encodeHex(contentHex):
+
+ byte_array = bytes.fromhex(contentHex)
+ byte_array.hex()
+ base64_byte_array = base64.b64encode(byte_array)
+ result = base64_byte_array.decode('utf-8')
+ return result
+
+
+ class Ssl():
+ __IOT_CA_CRT = "\
+-----BEGIN CERTIFICATE-----\n\
+MIIDxTCCAq2gAwIBAgIJALM1winYO2xzMA0GCSqGSIb3DQEBCwUAMHkxCzAJBgNV\n\
+BAYTAkNOMRIwEAYDVQQIDAlHdWFuZ0RvbmcxETAPBgNVBAcMCFNoZW5aaGVuMRAw\n\
+DgYDVQQKDAdUZW5jZW50MRcwFQYDVQQLDA5UZW5jZW50IElvdGh1YjEYMBYGA1UE\n\
+AwwPd3d3LnRlbmNlbnQuY29tMB4XDTE3MTEyNzA0MjA1OVoXDTMyMTEyMzA0MjA1\n\
+OVoweTELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUd1YW5nRG9uZzERMA8GA1UEBwwI\n\
+U2hlblpoZW4xEDAOBgNVBAoMB1RlbmNlbnQxFzAVBgNVBAsMDlRlbmNlbnQgSW90\n\
+aHViMRgwFgYDVQQDDA93d3cudGVuY2VudC5jb20wggEiMA0GCSqGSIb3DQEBAQUA\n\
+A4IBDwAwggEKAoIBAQDVxwDZRVkU5WexneBEkdaKs4ehgQbzpbufrWo5Lb5gJ3i0\n\
+eukbOB81yAaavb23oiNta4gmMTq2F6/hAFsRv4J2bdTs5SxwEYbiYU1teGHuUQHO\n\
+iQsZCdNTJgcikga9JYKWcBjFEnAxKycNsmqsq4AJ0CEyZbo//IYX3czEQtYWHjp7\n\
+FJOlPPd1idKtFMVNG6LGXEwS/TPElE+grYOxwB7Anx3iC5ZpE5lo5tTioFTHzqbT\n\
+qTN7rbFZRytAPk/JXMTLgO55fldm4JZTP3GQsPzwIh4wNNKhi4yWG1o2u3hAnZDv\n\
+UVFV7al2zFdOfuu0KMzuLzrWrK16SPadRDd9eT17AgMBAAGjUDBOMB0GA1UdDgQW\n\
+BBQrr48jv4FxdKs3r0BkmJO7zH4ALzAfBgNVHSMEGDAWgBQrr48jv4FxdKs3r0Bk\n\
+mJO7zH4ALzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDRSjXnBc3T\n\
+d9VmtTCuALXrQELY8KtM+cXYYNgtodHsxmrRMpJofsPGiqPfb82klvswpXxPK8Xx\n\
+SuUUo74Fo+AEyJxMrRKlbJvlEtnpSilKmG6rO9+bFq3nbeOAfat4lPl0DIscWUx3\n\
+ajXtvMCcSwTlF8rPgXbOaSXZidRYNqSyUjC2Q4m93Cv+KlyB+FgOke8x4aKAkf5p\n\
+XR8i1BN1OiMTIRYhGSfeZbVRq5kTdvtahiWFZu9DGO+hxDZObYGIxGHWPftrhBKz\n\
+RT16Amn780rQLWojr70q7o7QP5tO0wDPfCdFSc6CQFq/ngOzYag0kJ2F+O5U6+kS\n\
+QVrcRBDxzx/G\n\
+-----END CERTIFICATE-----"
+
+ def __init__(self):
+ self.__iot_ca_crt = self.__IOT_CA_CRT
+
+ def create_content(self, cadata=None):
+ if cadata is None:
+ cadata = self.__iot_ca_crt
+ return ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cadata=cadata)
+
+
+
+
\ No newline at end of file
diff --git a/hub/utils/providers.py b/hub/utils/providers.py
new file mode 100644
index 0000000..8562986
--- /dev/null
+++ b/hub/utils/providers.py
@@ -0,0 +1,311 @@
+#
+# Tencent is pleased to support the open source community by making IoT Hub available.
+# Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+
+# Licensed under the MIT License (the "License"); you may not use this file except in
+# compliance with the License. You may obtain a copy of the License at
+# http://opensource.org/licenses/MIT
+
+# 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 string
+import json
+import threading
+from hub.log.log import Log
+from hub.protocol.protocol import AsyncConnClient
+
+class SingletonType(type):
+ _instance_lock = threading.Lock()
+ def __call__(cls, *args, **kwargs):
+ if not hasattr(cls, "_instance"):
+ with SingletonType._instance_lock:
+ if not hasattr(cls, "_instance"):
+ cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)
+ return cls._instance
+
+class TopicProvider(object):
+ def __init__(self, product_id, device_name):
+ self.__clientToken = None
+
+ # log topic
+ self.__log_topic_pub = "$log/operation/%s/%s" % (product_id, device_name)
+ self.__log_topic_sub = "$log/operation/result/%s/%s" % (product_id, device_name)
+ self.__is_subscribed_log_topic = False
+
+ # system topic
+ self.__sys_topic_pub = "$sys/operation/%s/%s" % (product_id, device_name)
+ self.__sys_topic_sub = "$sys/operation/result/%s/%s" % (product_id, device_name)
+
+ # gateway topic
+ self.__gateway_topic_pub = "$gateway/operation/%s/%s" % (product_id, device_name)
+ self.__gateway_topic_sub = "$gateway/operation/result/%s/%s" % (product_id, device_name)
+
+ # data template topic
+ self.__template_topic_pub = "$template/operation/%s/%s" % (product_id, device_name)
+ self.__template_topic_sub = "$template/operation/result/%s/%s" % (product_id, device_name)
+
+ # thing topic
+ self.__thing_property_topic_pub = "$thing/up/property/%s/%s" % (product_id, device_name)
+ self.__thing_property_topic_sub = "$thing/down/property/%s/%s" % (product_id, device_name)
+
+ self.__thing_action_topic_pub = "$thing/up/action/%s/%s" % (product_id, device_name)
+ self.__thing_action_topic_sub = "$thing/down/action/%s/%s" % (product_id, device_name)
+ self.__thing_event_topic_pub = "$thing/up/event/%s/%s" % (product_id, device_name)
+ self.__thing_event_topic_sub = "$thing/down/event/%s/%s" % (product_id, device_name)
+ self.__thing_raw_topic_pub = "$thing/up/raw/%s/%s" % (product_id, device_name)
+ self.__thing_raw_topic_sub = "$thing/down/raw/%s/%s" % (product_id, device_name)
+ self.__thing_service_topic_pub = "$thing/up/service/%s/%s" % (product_id, device_name)
+ self.__thing_service_topic_sub = "$thing/down/service/%s/%s" % (product_id, device_name)
+
+ # ota topic
+ self.__ota_report_topic_pub = "$ota/report/%s/%s" % (product_id, device_name)
+ self.__ota_update_topic_sub = "$ota/update/%s/%s" % (product_id, device_name)
+
+ # rrpc topic
+ self.__rrpc_topic_pub_prefix = "$rrpc/txd/%s/%s" % (product_id, device_name)
+ self.__rrpc_topic_sub_prefix = "$rrpc/rxd/%s/%s" % (product_id, device_name)
+
+ # shadow
+ self.__shadow_topic_pub = "$shadow/operation/%s/%s" % (product_id, device_name)
+ self.__shadow_topic_sub = "$shadow/operation/result/%s/%s" % (product_id, device_name)
+
+ # broadcast
+ self.__broadcast_topic_sub = "$broadcast/rxd/%s/%s" % (product_id, device_name)
+
+ # resource manager
+ self.__resource_manager_topic_pub = '$resource/up/service/%s/%s' % (product_id, device_name)
+ self.__resource_manager_topic_sub = '$resource/down/service/%s/%s' % (product_id, device_name)
+
+ pass
+
+ @property
+ def sys_topic_sub(self):
+ return self.__sys_topic_sub
+
+ @property
+ def sys_topic_pub(self):
+ return self.__sys_topic_pub
+
+ @property
+ def gateway_topic_sub(self):
+ return self.__gateway_topic_sub
+
+ @property
+ def gateway_topic_pub(self):
+ return self.__gateway_topic_pub
+
+ @property
+ def template_topic_sub(self):
+ return self.__template_topic_sub
+
+ @property
+ def template_event_topic_sub(self):
+ return self.__thing_event_topic_sub
+
+ @property
+ def template_event_topic_pub(self):
+ return self.__thing_event_topic_pub
+
+ @property
+ def template_action_topic_sub(self):
+ return self.__thing_action_topic_sub
+
+ @property
+ def template_property_topic_sub(self):
+ return self.__thing_property_topic_sub
+
+ @property
+ def template_property_topic_pub(self):
+ return self.__thing_property_topic_pub
+
+ @property
+ def template_action_topic_pub(self):
+ return self.__thing_action_topic_pub
+
+ @property
+ def template_service_topic_sub(self):
+ return self.__thing_service_topic_sub
+
+ @property
+ def template_raw_topic_sub(self):
+ return self.__thing_raw_topic_sub
+
+ @property
+ def ota_update_topic_sub(self):
+ return self.__ota_update_topic_sub
+
+ @property
+ def ota_report_topic_pub(self):
+ return self.__ota_report_topic_pub
+
+ @property
+ def rrpc_topic_pub_prefix(self):
+ return self.__rrpc_topic_pub_prefix
+
+ @property
+ def rrpc_topic_sub_prefix(self):
+ return self.__rrpc_topic_sub_prefix
+
+ @property
+ def shadow_topic_pub(self):
+ return self.__shadow_topic_pub
+
+ @property
+ def shadow_topic_sub(self):
+ return self.__shadow_topic_sub
+
+ @property
+ def broadcast_topic_sub(self):
+ return self.__broadcast_topic_sub
+
+ @property
+ def control_clientToken(self):
+ return self.__clientToken
+
+ @property
+ def resource_manager_topic_pub(self):
+ return self.__resource_manager_topic_pub
+
+ @property
+ def resource_manager_topic_sub(self):
+ return self.__resource_manager_topic_sub
+
+
+ # _on_template_downstream_topic_handler()中收到云端消息后保存client-token
+ @control_clientToken.setter
+ def control_clientToken(self, token):
+ if token is None or len(token) == 0:
+ raise ValueError('Invalid info.')
+ self.__clientToken = token
+
+class DeviceInfoProvider(object):
+ def __init__(self, file_path):
+ self.__file_path = file_path
+ # self.__logger = logger
+ self.__log_provider = LoggerProvider()
+ self.__logger = self.__log_provider.logger
+ self.__logger.info('device_info file {}'.format(file_path))
+
+ self.__auth_mode = None
+ self.__device_name = None
+ self.__product_id = None
+ self.__product_secret = None
+ self.__device_secret = None
+ self.__ca_file = None
+ self.__cert_file = None
+ self.__private_key_file = None
+ self.__region = None
+ if self.__logger is not None:
+ self.__logger.info('device_info file {}'.format(file_path))
+ with open(file_path, 'r', encoding='utf-8') as f:
+ self.__json_data = json.loads(f.read())
+ self.__auth_mode = self.__json_data['auth_mode']
+ self.__device_name = self.__json_data['deviceName']
+ self.__product_id = self.__json_data['productId']
+ self.__product_secret = self.__json_data['productSecret']
+ self.__device_secret = self.__json_data['key_deviceinfo']['deviceSecret']
+ self.__ca_file = self.__json_data['cert_deviceinfo']['devCaFile']
+ self.__cert_file = self.__json_data['cert_deviceinfo']['devCertFile']
+ self.__private_key_file = self.__json_data['cert_deviceinfo']['devPrivateKeyFile']
+ self.__region = self.__json_data["region"]
+ if self.__logger is not None:
+ self.__logger.info(
+ "device name: {}, product id: {}, product secret: {}, device secret: {}".
+ format(self.__device_name, self.__product_id,
+ self.__product_secret, self.__device_secret))
+
+ def update_config_file(self, psk):
+ with open(self.__file_path, '+r', encoding='utf-8') as f:
+ t = f.read()
+ t = t.replace(self.__device_secret, psk)
+ f.seek(0, 0)
+ f.write(t)
+ f.truncate()
+
+ self.__device_secret = psk
+ pass
+
+ def update_cert_config_file(self, cert):
+ with open(self.__file_path, '+r', encoding='utf-8') as f:
+ t = f.read()
+ t = t.replace(self.__cert_file, cert)
+ f.seek(0, 0)
+ f.write(t)
+ f.truncate()
+
+ self.__cert_file = cert
+ pass
+
+ def update_privateKey_config_file(self, privateKey):
+ with open(self.__file_path, '+r', encoding='utf-8') as f:
+ t = f.read()
+ t = t.replace(self.__private_key_file, privateKey)
+ f.seek(0, 0)
+ f.write(t)
+ f.truncate()
+
+ self.__private_key_file = privateKey
+ pass
+
+ @property
+ def auth_mode(self):
+ return self.__auth_mode
+
+ @property
+ def device_name(self):
+ return self.__device_name
+
+ @property
+ def product_id(self):
+ return self.__product_id
+
+ @property
+ def product_secret(self):
+ return self.__product_secret
+
+ @property
+ def device_secret(self):
+ return self.__device_secret
+
+ @property
+ def ca_file(self):
+ return self.__ca_file
+
+ @property
+ def cert_file(self):
+ return self.__cert_file
+
+ @property
+ def private_key_file(self):
+ return self.__private_key_file
+
+ @property
+ def region(self):
+ return self.__region
+
+ @property
+ def json_data(self):
+ return self.__json_data
+
+class ConnClientProvider(metaclass=SingletonType):
+ """
+ 使用单例模式构建,保证对象只有一份
+ """
+ def __init__(self, host, product_id, device_name, device_secret, websocket=False, tls=True, logger=None):
+ self.protocol = AsyncConnClient(host, product_id, device_name, device_secret, websocket, tls, logger)
+
+ def __new__(cls, *args, **kwargs):
+ return object.__new__(cls)
+
+class LoggerProvider(metaclass=SingletonType):
+ """
+ 使用单例模式构建,保证对象只有一份
+ """
+ def __init__(self):
+ self.logger = Log()
+
+ def __new__(cls, *args, **kwargs):
+ return object.__new__(cls)
\ No newline at end of file
diff --git a/logs/logtext b/logs/logtext
new file mode 100644
index 0000000..a3a840f
--- /dev/null
+++ b/logs/logtext
@@ -0,0 +1 @@
+log存储
diff --git a/requirements.txt b/requirements.txt
index 3f703db..7986370 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
paho-mqtt==1.5.1
-pycrypto==2.6.1
\ No newline at end of file
+pycryptodome==3.19.1
diff --git a/sample/dynreg/example_dynreg.py b/sample/dynreg/example_dynreg.py
deleted file mode 100644
index fdd70d3..0000000
--- a/sample/dynreg/example_dynreg.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import sys
-import logging
-from explorer import explorer
-sys.path.append('../../')
-
-
-# log setting
-def example_dynreg():
- __log_format = '%(asctime)s.%(msecs)03d [%(filename)s:%(lineno)d] - %(levelname)s - %(message)s'
- logging.basicConfig(format=__log_format)
-
- print("\033[1;36m dynreg test start...\033[0m")
-
- dyn_explorer = explorer.QcloudExplorer('sample/device_info.json')
- dyn_explorer.enableLogger(logging.DEBUG)
- ret, msg = dyn_explorer.dynregDevice()
-
- if ret == 0:
- print('dynamic register success, psk: {}'.format(msg))
- print("\033[1;36m dynamic register test success...\033[0m")
- return True
- else:
- print('dynamic register fail, msg: {}'.format(msg))
- print("\033[1;31m dynamic register test fail...\033[0m")
- # return False
- # 区分单元测试和sample
- return True
diff --git a/sample/gateway/Z53CXC198M_config.json b/sample/gateway/Z53CXC198M_config.json
deleted file mode 100644
index 05a25ce..0000000
--- a/sample/gateway/Z53CXC198M_config.json
+++ /dev/null
@@ -1,184 +0,0 @@
-{
- "version": "1.0",
- "profile": {
- "ProductId": "Z53CXC198M",
- "CategoryId": "3"
- },
- "properties": [
- {
- "id": "power_switch",
- "name": "电灯开关",
- "desc": "控制电灯开灭",
- "required": true,
- "mode": "rw",
- "define": {
- "type": "bool",
- "mapping": {
- "0": "关",
- "1": "开"
- }
- }
- },
- {
- "id": "color",
- "name": "颜色",
- "desc": "灯光颜色",
- "mode": "rw",
- "define": {
- "type": "enum",
- "mapping": {
- "0": "Red",
- "1": "Green",
- "2": "Blue"
- }
- }
- },
- {
- "id": "brightness",
- "name": "亮度",
- "desc": "灯光亮度",
- "mode": "rw",
- "define": {
- "type": "int",
- "unit": "%",
- "step": "1",
- "min": "0",
- "max": "100",
- "start": "1"
- }
- },
- {
- "id": "name",
- "name": "灯位置名称",
- "desc": "灯位置名称:书房、客厅等",
- "mode": "rw",
- "required": false,
- "define": {
- "type": "string",
- "min": "0",
- "max": "64"
- }
- }
- ],
- "events": [
- {
- "id": "status_report",
- "name": "DeviceStatus",
- "desc": "Report the device status",
- "type": "info",
- "required": false,
- "params": [
- {
- "id": "status",
- "name": "running_state",
- "desc": "Report current device running state",
- "define": {
- "type": "bool",
- "mapping": {
- "0": "normal",
- "1": "fault"
- }
- }
- },
- {
- "id": "message",
- "name": "Message",
- "desc": "Some extra message",
- "define": {
- "type": "string",
- "min": "0",
- "max": "64"
- }
- }
- ]
- },
- {
- "id": "low_voltage",
- "name": "LowVoltage",
- "desc": "Alert for device voltage is low",
- "type": "alert",
- "required": false,
- "params": [
- {
- "id": "voltage",
- "name": "Voltage",
- "desc": "Current voltage",
- "define": {
- "type": "float",
- "unit": "V",
- "step": "1",
- "min": "0.0",
- "max": "24.0",
- "start": "1"
- }
- }
- ]
- },
- {
- "id": "hardware_fault",
- "name": "Hardware_fault",
- "desc": "Report hardware fault",
- "type": "fault",
- "required": false,
- "params": [
- {
- "id": "name",
- "name": "Name",
- "desc": "Name like: memory,tf card, censors ...",
- "define": {
- "type": "string",
- "min": "0",
- "max": "64"
- }
- },
- {
- "id": "error_code",
- "name": "Error_Code",
- "desc": "Error code for fault",
- "define": {
- "type": "int",
- "unit": "",
- "step": "1",
- "min": "0",
- "max": "2000",
- "start": "1"
- }
- }
- ]
- }
- ],
- "actions": [
- {
- "id": "c_sw",
- "name": "color_switch",
- "desc": "",
- "input": [
- {
- "id": "sw",
- "name": "sw",
- "define": {
- "type": "bool",
- "mapping": {
- "0": "不切换",
- "1": "切换"
- }
- }
- }
- ],
- "output": [
- {
- "id": "err_code",
- "name": "ret",
- "define": {
- "type": "bool",
- "mapping": {
- "0": "成功",
- "1": "失败"
- }
- }
- }
- ],
- "required": false
- }
- ]
-}
diff --git a/sample/gateway/ZPHBLEB4J5_config.json b/sample/gateway/ZPHBLEB4J5_config.json
deleted file mode 100644
index 72a02f5..0000000
--- a/sample/gateway/ZPHBLEB4J5_config.json
+++ /dev/null
@@ -1,184 +0,0 @@
-{
- "version": "1.0",
- "profile": {
- "ProductId": "ZPHBLEB4J5",
- "CategoryId": "3"
- },
- "properties": [
- {
- "id": "power_switch",
- "name": "电灯开关",
- "desc": "控制电灯开灭",
- "required": true,
- "mode": "rw",
- "define": {
- "type": "bool",
- "mapping": {
- "0": "关",
- "1": "开"
- }
- }
- },
- {
- "id": "color",
- "name": "颜色",
- "desc": "灯光颜色",
- "mode": "rw",
- "define": {
- "type": "enum",
- "mapping": {
- "0": "Red",
- "1": "Green",
- "2": "Blue"
- }
- }
- },
- {
- "id": "brightness",
- "name": "亮度",
- "desc": "灯光亮度",
- "mode": "rw",
- "define": {
- "type": "int",
- "unit": "%",
- "step": "1",
- "min": "0",
- "max": "100",
- "start": "1"
- }
- },
- {
- "id": "name",
- "name": "灯位置名称",
- "desc": "灯位置名称:书房、客厅等",
- "mode": "rw",
- "required": false,
- "define": {
- "type": "string",
- "min": "0",
- "max": "64"
- }
- }
- ],
- "events": [
- {
- "id": "status_report",
- "name": "DeviceStatus",
- "desc": "Report the device status",
- "type": "info",
- "required": false,
- "params": [
- {
- "id": "status",
- "name": "running_state",
- "desc": "Report current device running state",
- "define": {
- "type": "bool",
- "mapping": {
- "0": "normal",
- "1": "fault"
- }
- }
- },
- {
- "id": "message",
- "name": "Message",
- "desc": "Some extra message",
- "define": {
- "type": "string",
- "min": "0",
- "max": "64"
- }
- }
- ]
- },
- {
- "id": "low_voltage",
- "name": "LowVoltage",
- "desc": "Alert for device voltage is low",
- "type": "alert",
- "required": false,
- "params": [
- {
- "id": "voltage",
- "name": "Voltage",
- "desc": "Current voltage",
- "define": {
- "type": "float",
- "unit": "V",
- "step": "1",
- "min": "0.0",
- "max": "24.0",
- "start": "1"
- }
- }
- ]
- },
- {
- "id": "hardware_fault",
- "name": "Hardware_fault",
- "desc": "Report hardware fault",
- "type": "fault",
- "required": false,
- "params": [
- {
- "id": "name",
- "name": "Name",
- "desc": "Name like: memory,tf card, censors ...",
- "define": {
- "type": "string",
- "min": "0",
- "max": "64"
- }
- },
- {
- "id": "error_code",
- "name": "Error_Code",
- "desc": "Error code for fault",
- "define": {
- "type": "int",
- "unit": "",
- "step": "1",
- "min": "0",
- "max": "2000",
- "start": "1"
- }
- }
- ]
- }
- ],
- "actions": [
- {
- "id": "light_on",
- "name": "开灯",
- "desc": "",
- "input": [
- {
- "id": "is_on",
- "name": "on",
- "define": {
- "type": "bool",
- "mapping": {
- "0": "关",
- "1": "开"
- }
- }
- }
- ],
- "output": [
- {
- "id": "err_code",
- "name": "ret",
- "define": {
- "type": "bool",
- "mapping": {
- "0": "关",
- "1": "开"
- }
- }
- }
- ],
- "required": false
- }
- ]
-}
diff --git a/sample/gateway/example_gateway.py b/sample/gateway/example_gateway.py
deleted file mode 100644
index f670154..0000000
--- a/sample/gateway/example_gateway.py
+++ /dev/null
@@ -1,201 +0,0 @@
-import sys
-import time
-import logging
-from threading import Thread
-from explorer import explorer
-from hub.hub import QcloudHub
-
-from gateway import product_ZPHBLEB4J5 as product_ZPHBLEB4J5
-from gateway import product_Z53CXC198M as product_Z53CXC198M
-# import product_ZPHBLEB4J5
-# import product_Z53CXC198M
-
-g_property_params = None
-g_control_msg_arrived = False
-
-g_task_1_runing = False
-g_task_2_runing = False
-g_task_1 = None
-g_task_2 = None
-
-# 网关下所有产品名称列表
-g_product_list = ["ZPHBLEB4J5", "Z53CXC198M"]
-
-# 产品下所有设备列表
-g_Z53CXC198M_subdev_list = []
-g_ZPHBLEB4J5_subdev_list = []
-
-
-def on_connect(flags, rc, userdata):
- print("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
- pass
-
-
-def on_disconnect(rc, userdata):
- print("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
- pass
-
-
-def on_message(topic, payload, qos, userdata):
- print("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
- pass
-
-
-def on_publish(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-
-def on_subscribe(mid, granted_qos, userdata):
- print("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
- pass
-
-
-def on_unsubscribe(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-
-# 调用数据模板入口函数
-def task_1(product_id, subdev_list=[]):
-
- global te
- product_ZPHBLEB4J5.product_init(product_id, subdev_list, te)
-
- global g_task_1_runing
- g_task_1_runing = True
-
-
-def task_2(product_id, subdev_list=[]):
-
- global te
- product_Z53CXC198M.product_init(product_id, subdev_list, te)
-
- global g_task_2_runing
- g_task_2_runing = True
-
-
-def example_gateway():
- global g_task_1
- global g_task_2
-
- __log_format = '%(asctime)s.%(msecs)03d [%(filename)s:%(lineno)d] - %(levelname)s - %(message)s'
- logging.basicConfig(format=__log_format)
-
- te = explorer.QcloudExplorer(device_file="sample/device_info.json")
- te.enableLogger(logging.DEBUG)
-
- print("\033[1;36m gateway test start...\033[0m")
-
- te.user_on_connect = on_connect
- te.user_on_disconnect = on_disconnect
- te.user_on_message = on_message
- te.user_on_publish = on_publish
- te.user_on_subscribe = on_subscribe
- te.user_on_unsubscribe = on_unsubscribe
- te.mqttInit(mqtt_domain="")
- te.connect()
-
- count = 0
- while True:
- if te.isMqttConnected():
- break
- else:
- if count >= 3:
- # sys.exit()
- print("\033[1;31m gateway test fail...\033[0m")
- # return False
- # 区分单元测试和sample
- return True
- time.sleep(1)
- count += 1
-
-
- te.gatewayInit()
-
- # 获取到子设备信息后,在此维护设备状态,sdk中不处理设备状态
- subdev_list = te.gateway_subdev_list
-
- while True:
- try:
- msg = input()
- except KeyboardInterrupt:
- sys.exit()
- else:
- if msg == "1":
- for subdev in subdev_list:
- if subdev.session_status is not QcloudHub.SessionState.SUBDEV_SEESION_STATUS_ONLINE:
- rc = te.gatewaySubdevOnline(subdev.sub_productId, subdev.sub_devName)
- if rc == 0:
- subdev.session_status = QcloudHub.SessionState.SUBDEV_SEESION_STATUS_ONLINE
- print("online success")
- else:
- print("online fail")
-
- elif msg == "2":
- for subdev in subdev_list:
- if subdev.session_status == QcloudHub.SessionState.SUBDEV_SEESION_STATUS_ONLINE:
- rc = te.gatewaySubdevOffline(subdev.sub_productId, subdev.sub_devName)
- if rc == 0:
- subdev.session_status = QcloudHub.SessionState.SUBDEV_SEESION_STATUS_OFFLINE
- print("offline success")
- else:
- print("offline fail")
-
- elif msg == "3":
- rc = te.gatewaySubdevBind("YOUR_PRODUCT_ID", "YOUR_DEVICE_NAME", "YOUR_DEVICE_SECRET")
- if rc == 0:
- print("bind success")
- else:
- print("bind fail")
-
- elif msg == "4":
- rc = te.gatewaySubdevUnbind("YOUR_PRODUCT_ID", "YOUR_DEVICE_NAME", None)
- if rc == 0:
- print("unbind success")
- else:
- print("unbind fail")
-
- elif msg == "5":
- if not g_task_1_runing:
- product_id = g_product_list[0]
- for subdev in subdev_list:
- if subdev.sub_productId == product_id:
- g_ZPHBLEB4J5_subdev_list.append(subdev.sub_devName)
- else:
- continue
-
- # global g_task_1
- g_task_1 = Thread(target=task_1, args=(product_id, g_ZPHBLEB4J5_subdev_list,))
- g_task_1.start()
-
- if not g_task_2_runing:
- product_id = g_product_list[1]
- for subdev in subdev_list:
- if subdev.sub_productId == product_id:
- g_Z53CXC198M_subdev_list.append(subdev.sub_devName)
- else:
- continue
-
- # global g_task_2
- g_task_2 = Thread(target=task_2, args=(product_id, g_Z53CXC198M_subdev_list,))
- g_task_2.start()
-
- elif msg == "6":
- te.disconnect()
- # global g_task_1
- if g_task_1.is_alive():
- g_task_1.stop()
- g_task_1.join()
-
- # global g_task_2
- if g_task_2.is_alive():
- g_task_2.stop()
- g_task_2.join()
-
- else:
- sys.exit()
- print("\033[1;36m gateway test success...\033[0m")
- return True
-# if __name__ == '__main__':
-# example_gateway()
diff --git a/sample/gateway/product_Z53CXC198M.py b/sample/gateway/product_Z53CXC198M.py
deleted file mode 100644
index 0b68955..0000000
--- a/sample/gateway/product_Z53CXC198M.py
+++ /dev/null
@@ -1,156 +0,0 @@
-import sys
-import time
-import logging
-
-
-__log_format = '%(asctime)s.%(msecs)03d [%(filename)s:%(lineno)d] - %(levelname)s - %(message)s'
-logging.basicConfig(format=__log_format)
-
-g_te = None
-g_property_params = None
-g_control_msg_arrived = False
-
-
-def on_template_prop_changed(params, userdata):
- print("product_002:%s:params:%s,userdata:%s" % (sys._getframe().f_code.co_name, params, userdata))
-
- global g_te
- te = g_te
- # save changed propertys
- global g_property_params
- g_property_params = params
-
- global g_control_msg_arrived
- g_control_msg_arrived = True
-
- # deal down stream
-
- # 测试,实际应发送用户属性数据
- reply_param = te.sReplyPara()
- reply_param.code = 0
- reply_param.timeout_ms = 5 * 1000
- reply_param.status_msg = '\0'
-
- te.templateControlReply(reply_param)
-
- pass
-
-
-def on_template_event_post(payload, userdata):
- print("product_002:%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
- pass
-
-
-def on_template_action(payload, userdata):
- print("product_002:%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
-
- global g_te
- te = g_te
-
- clientToken = payload["clientToken"]
-
- """
- actionId = payload["actionId"]
- timestamp = payload["timestamp"]
- params = payload["params"]
- """
-
- reply_param = te.sReplyPara()
- reply_param.code = 0
- reply_param.timeout_ms = 5 * 1000
- reply_param.status_msg = "action execute success!"
- res = {
- "err_code": 0
- }
-
- te.templateActionReply(clientToken, res, reply_param)
- pass
-
-
-def product_init(product_id, subdev_list, te):
-
- print("product_002:=========%s==========" % product_id)
- global g_te
- g_te = te
-
- # 注册数据模板回调函数
- te.registerUserPropertyCallback(product_id, on_template_prop_changed)
- te.registerUserActionCallback(product_id, on_template_action)
- te.registerUserEventCallback(product_id, on_template_event_post)
-
- te.templateSetup("./Z53CXC198M_config.json")
-
- """
- # 保存自身property list
- prop_list = te.template_property_list
- event_list = te.template_events_list
- action_list = te.template_action_list
- """
-
- topic_property_format = "$thing/down/property/%s/%s"
- topic_action_format = "$thing/down/action/%s/%s"
- topic_event_format = "$thing/down/event/%s/%s"
-
- topic_property_list = []
- topic_action_list = []
- topic_event_list = []
- for subdev in subdev_list:
-
- topic_property = topic_property_format % (product_id, subdev)
- topic_property_list.append((topic_property, 0))
-
- topic_action = topic_action_format % (product_id, subdev)
- topic_action_list.append((topic_action, 0))
-
- topic_event = topic_event_format % (product_id, subdev)
- topic_event_list.append((topic_event, 0))
-
- # 订阅子设备topic,在此必须传入元组列表[(topic1,qos2),(topic2,qos2)]
- rc = te.gatewaySubdevSubscribe(product_id, topic_property_list, topic_action_list, topic_event_list)
- if rc == 0:
- print("gateway subdev subscribe success")
- else:
- print("gateway subdev subscribe fail")
-
- # sysinfo report
- sys_info = {
- "module_hardinfo": "ESP8266",
- "module_softinfo": "V1.0",
- "fw_ver": "3.1.4",
- "imei": "11-22-33-44",
- "lat": "22.546015",
- "lon": "113.941125",
- "device_label": {
- "append_info": "your self define info"
- }
- }
- rc = te.templateReportSysInfo(sys_info)
- if rc != 0:
- print("sysinfo report fail")
- return 1
-
- rc = te.templateGetStatus()
- if rc != 0:
- print("get status fail")
- return 1
-
- while te.isMqttConnected():
-
- # add user logic
- """
- if g_control_msg_arrived:
- params_in = te.template_json_construct_report_array(g_property_params)
- te.template_report(params_in)
- else:
- reports = {
- prop_list[0].key: prop_list[0].data,
- prop_list[1].key: prop_list[1].data,
- prop_list[2].key: prop_list[2].data,
- prop_list[3].key: prop_list[3].data
- }
-
- params_in = te.template_json_construct_report_array(reports)
- te.template_report(params_in)
- """
-
- time.sleep(1)
diff --git a/sample/gateway/product_ZPHBLEB4J5.py b/sample/gateway/product_ZPHBLEB4J5.py
deleted file mode 100644
index 62f27e4..0000000
--- a/sample/gateway/product_ZPHBLEB4J5.py
+++ /dev/null
@@ -1,156 +0,0 @@
-import sys
-import time
-import logging
-
-
-__log_format = '%(asctime)s.%(msecs)03d [%(filename)s:%(lineno)d] - %(levelname)s - %(message)s'
-logging.basicConfig(format=__log_format)
-
-g_te = None
-g_property_params = None
-g_control_msg_arrived = False
-
-
-def on_template_prop_changed(params, userdata):
- print("product_001:%s:params:%s,userdata:%s" % (sys._getframe().f_code.co_name, params, userdata))
-
- global g_te
- te = g_te
- # save changed propertys
- global g_property_params
- g_property_params = params
-
- global g_control_msg_arrived
- g_control_msg_arrived = True
-
- # deal down stream
-
- # 测试,实际应发送用户属性数据
- reply_param = te.sReplyPara()
- reply_param.code = 0
- reply_param.timeout_ms = 5 * 1000
- reply_param.status_msg = '\0'
-
- te.templateControlReply(reply_param)
-
- pass
-
-
-def on_template_event_post(payload, userdata):
- print("product_001:%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
- pass
-
-
-def on_template_action(payload, userdata):
- print("product_001:%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
-
- global g_te
- te = g_te
-
- clientToken = payload["clientToken"]
-
- """
- actionId = payload["actionId"]
- timestamp = payload["timestamp"]
- params = payload["params"]
- """
-
- reply_param = te.sReplyPara()
- reply_param.code = 0
- reply_param.timeout_ms = 5 * 1000
- reply_param.status_msg = "action execute success!"
- res = {
- "err_code": 0
- }
-
- te.templateActionReply(clientToken, res, reply_param)
- pass
-
-
-def product_init(product_id, subdev_list, te):
-
- print("product_001:=========%s==========" % product_id)
- global g_te
- g_te = te
-
- # 注册数据模板回调函数
- te.registerUserPropertyCallback(product_id, on_template_prop_changed)
- te.registerUserActionCallback(product_id, on_template_action)
- te.registerUserEventCallback(product_id, on_template_event_post)
-
- te.templateSetup("./ZPHBLEB4J5_config.json")
-
- """
- # 保存自身property list
- prop_list = te.template_property_list
- event_list = te.template_events_list
- action_list = te.template_action_list
- """
-
- topic_property_format = "$thing/down/property/%s/%s"
- topic_action_format = "$thing/down/action/%s/%s"
- topic_event_format = "$thing/down/event/%s/%s"
-
- topic_property_list = []
- topic_action_list = []
- topic_event_list = []
- for subdev in subdev_list:
-
- topic_property = topic_property_format % (product_id, subdev)
- topic_property_list.append((topic_property, 0))
-
- topic_action = topic_action_format % (product_id, subdev)
- topic_action_list.append((topic_action, 0))
-
- topic_event = topic_event_format % (product_id, subdev)
- topic_event_list.append((topic_event, 0))
-
- # 订阅子设备topic,在此必须传入元组列表[(topic1,qos2),(topic2,qos2)]
- rc = te.gatewaySubdevSubscribe(product_id, topic_property_list, topic_action_list, topic_event_list)
- if rc == 0:
- print("gateway subdev subscribe success")
- else:
- print("gateway subdev subscribe fail")
-
- # sysinfo report
- sys_info = {
- "module_hardinfo": "ESP8266",
- "module_softinfo": "V1.0",
- "fw_ver": "3.1.4",
- "imei": "11-22-33-44",
- "lat": "22.546015",
- "lon": "113.941125",
- "device_label": {
- "append_info": "your self define info"
- }
- }
- rc = te.templateReportSysInfo(sys_info)
- if rc != 0:
- print("sysinfo report fail")
- return 1
-
- rc = te.templateGetStatus()
- if rc != 0:
- print("get status fail")
- return 1
-
- while te.isMqttConnected():
-
- # add user logic
- """
- if g_control_msg_arrived:
- params_in = te.template_json_construct_report_array(g_property_params)
- te.template_report(params_in)
- else:
- reports = {
- prop_list[0].key: prop_list[0].data,
- prop_list[1].key: prop_list[1].data,
- prop_list[2].key: prop_list[2].data,
- prop_list[3].key: prop_list[3].data
- }
-
- params_in = te.template_json_construct_report_array(reports)
- te.template_report(params_in)
- """
-
- time.sleep(1)
diff --git a/sample/mqtt/example_mqtt.py b/sample/mqtt/example_mqtt.py
deleted file mode 100644
index e683974..0000000
--- a/sample/mqtt/example_mqtt.py
+++ /dev/null
@@ -1,106 +0,0 @@
-import sys
-from explorer import explorer
-import logging
-import time
-
-g_connected = False
-
-def on_connect(flags, rc, userdata):
- print("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
- global g_connected
- g_connected = True
-
- pass
-
-
-def on_disconnect(rc, userdata):
- print("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
- pass
-
-
-def on_message(topic, payload, qos, userdata):
- print("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
- pass
-
-
-def on_publish(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-
-def on_subscribe(mid, granted_qos, userdata):
- print("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
- pass
-
-
-def on_unsubscribe(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-
-def example_mqtt():
- __log_format = '%(asctime)s.%(msecs)03d [%(filename)s:%(lineno)d] - %(levelname)s - %(message)s'
- logging.basicConfig(format=__log_format)
-
- te = explorer.QcloudExplorer(device_file="sample/device_info.json", tls=True)
- te.enableLogger(logging.DEBUG)
-
- print("\033[1;36m mqtt test start...\033[0m")
-
- te.user_on_connect = on_connect
- te.user_on_disconnect = on_disconnect
- te.user_on_message = on_message
- te.user_on_publish = on_publish
- te.user_on_subscribe = on_subscribe
- te.user_on_unsubscribe = on_unsubscribe
-
-
- te.mqttInit(mqtt_domain="", useWebsocket=False)
- te.connect()
-
- count = 0
- while True:
- if te.isMqttConnected():
- break
- else:
- if count >= 3:
- print("\033[1;31m mqtt test fail...\033[0m")
- # return False
- # 区分单元测试和sample
- return True
- time.sleep(1)
- count += 1
-
- print("\033[1;36m mqtt test success...\033[0m")
- return True
- '''
- count = 0
- while True:
- if te.isMqttConnected():
- break
- else:
- if count >= 3:
- # sys.exit()
- return True
- time.sleep(1)
- count += 1
-
-
- te.shadowInit()
- te.broadcastInit()
-
- while True:
- try:
- msg = input()
- except KeyboardInterrupt:
- sys.exit()
- else:
- if msg == "1":
- te.disconnect()
- elif msg == "2":
- te.getShadow()
- else:
- sys.exit()
- '''
-# if __name__ == '__main__':
-# example_mqtt()
diff --git a/sample/shadow/example_shadow.py b/sample/shadow/example_shadow.py
deleted file mode 100644
index 9957880..0000000
--- a/sample/shadow/example_shadow.py
+++ /dev/null
@@ -1,113 +0,0 @@
-import sys
-from explorer import explorer
-import logging
-import time
-import json
-from hub.hub import QcloudHub
-
-g_connected = False
-g_delta_arrived = False
-
-def on_connect(flags, rc, userdata):
- print("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
- global g_connected
- g_connected = True
-
- pass
-
-
-def on_disconnect(rc, userdata):
- print("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
- pass
-
-
-def on_message(topic, payload, qos, userdata):
- print("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
-
- message_type = payload["type"]
- if message_type == "delta":
- global g_delta_arrived
- g_delta_arrived = True
-
- pass
-
-
-def on_publish(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-
-def on_subscribe(mid, granted_qos, userdata):
- print("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
- pass
-
-
-def on_unsubscribe(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-
-def example_shadow():
- __log_format = '%(asctime)s.%(msecs)03d [%(filename)s:%(lineno)d] - %(levelname)s - %(message)s'
- logging.basicConfig(format=__log_format)
-
- te = explorer.QcloudExplorer(device_file="sample/shadow/device_info.json", tls=True)
- te.enableLogger(logging.INFO)
-
- print("\033[1;36m shadow test start...\033[0m")
-
- te.user_on_connect = on_connect
- te.user_on_disconnect = on_disconnect
- te.user_on_message = on_message
- te.user_on_publish = on_publish
- te.user_on_subscribe = on_subscribe
- te.user_on_unsubscribe = on_unsubscribe
-
- te.mqttInit(mqtt_domain="", useWebsocket=False)
- te.connect()
-
- count = 0
- while True:
- if te.isMqttConnected():
- break
- else:
- if count >= 3:
- print("\033[1;31m mqtt test fail...\033[0m")
- # return False
- # 区分单元测试和sample
- return True
- time.sleep(1)
- count += 1
-
- te.shadowInit()
-
- p_prop = QcloudHub.template_property()
- p_prop.key = "updateCount"
- p_prop.data = 0
- p_prop.type = "int"
-
- p_prop1 = QcloudHub.template_property()
- p_prop1.key = "updateCount11"
- p_prop1.data = "shadow"
- p_prop1.type = "string"
-
- te.getShadow()
-
- global g_delta_arrived
- if g_delta_arrived is True:
- payload = te.shadowJsonConstructDesireAllNull()
- te.shadowUpdate(payload, len(payload))
- g_delta_arrived= False
-
- payload = te.shadowJsonConstructReport(p_prop, p_prop1)
- te.shadowUpdate(payload, len(payload))
-
- time.sleep(1)
- payload = te.shadowJsonConstructReport(p_prop)
- te.shadowUpdate(payload, len(payload))
-
- print("\033[1;36m shadow test success...\033[0m")
- return True
-
-if __name__ == '__main__':
- example_shadow()
diff --git a/sample/subscribe/example_subscribe.py b/sample/subscribe/example_subscribe.py
deleted file mode 100644
index 1c73d9a..0000000
--- a/sample/subscribe/example_subscribe.py
+++ /dev/null
@@ -1,93 +0,0 @@
-import sys
-from explorer import explorer
-import logging
-import time
-
-
-def on_connect(flags, rc, userdata):
- print("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
- pass
-
-
-def on_disconnect(rc, userdata):
- print("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
- pass
-
-
-def on_message(topic, payload, qos, userdata):
- print("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
- pass
-
-
-def on_publish(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-
-def on_subscribe(mid, granted_qos, userdata):
- print("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
- pass
-
-
-def on_unsubscribe(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-def on_subscribe_service_post(payload, userdata):
- print("payload:%s,userdata:%s" % (payload, userdata))
- pass
-
-def example_subscribe():
- __log_format = '%(asctime)s.%(msecs)03d [%(filename)s:%(lineno)d] - %(levelname)s - %(message)s'
- logging.basicConfig(format=__log_format)
-
- te = explorer.QcloudExplorer(device_file="sample/device_info.json", tls=True)
- te.enableLogger(logging.DEBUG)
-
- print("\033[1;36m mqtt test start...\033[0m")
-
- te.user_on_connect = on_connect
- te.user_on_disconnect = on_disconnect
- te.user_on_message = on_message
- te.user_on_publish = on_publish
- te.user_on_subscribe = on_subscribe
- te.user_on_unsubscribe = on_unsubscribe
- te.on_subscribe_service_post = on_subscribe_service_post
-
- te.mqttInit(mqtt_domain="", useWebsocket=False)
- te.connect()
-
- count = 0
- while True:
- if te.isMqttConnected():
- break
- else:
- if count >= 3:
- # sys.exit()
- print("\033[1;31m template test fail...\033[0m")
- # return False
- # dif unit test and sample
- return True
- time.sleep(1)
- count += 1
-
- te.subscribeInit()
- return True
-
- '''
- while True:
- try:
- msg = input()
- except KeyboardInterrupt:
- sys.exit()
- else:
- if msg == "1":
- te.disconnect()
- elif msg == "2":
- te.getShadow()
- else:
- sys.exit()
- '''
-
-# if __name__ == '__main__':
-# example_subscribe()
\ No newline at end of file
diff --git a/sample/template/example_template.py b/sample/template/example_template.py
deleted file mode 100644
index 282c7ea..0000000
--- a/sample/template/example_template.py
+++ /dev/null
@@ -1,238 +0,0 @@
-import sys
-import time
-import logging
-from explorer import explorer
-
-
-__log_format = '%(asctime)s.%(msecs)03d [%(filename)s:%(lineno)d] - %(levelname)s - %(message)s'
-logging.basicConfig(format=__log_format)
-
-te = explorer.QcloudExplorer(device_file="sample/device_info.json")
-te.enableLogger(logging.DEBUG)
-
-g_property_params = None
-g_control_msg_arrived = False
-
-
-def on_connect(flags, rc, userdata):
- print("%s:flags:%d,rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, flags, rc, userdata))
- pass
-
-
-def on_disconnect(rc, userdata):
- print("%s:rc:%d,userdata:%s" % (sys._getframe().f_code.co_name, rc, userdata))
- pass
-
-
-def on_message(topic, payload, qos, userdata):
- print("%s:topic:%s,payload:%s,qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, topic, payload, qos, userdata))
- pass
-
-
-def on_publish(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-
-def on_subscribe(mid, granted_qos, userdata):
- print("%s:mid:%d,granted_qos:%s,userdata:%s" % (sys._getframe().f_code.co_name, mid, granted_qos, userdata))
- pass
-
-
-def on_unsubscribe(mid, userdata):
- print("%s:mid:%d,userdata:%s" % (sys._getframe().f_code.co_name, mid, userdata))
- pass
-
-
-def on_template_prop_changed(params, userdata):
- print("%s:params:%s,userdata:%s" % (sys._getframe().f_code.co_name, params, userdata))
-
- # save changed propertys
- global g_property_params
- g_property_params = params
-
- global g_control_msg_arrived
- g_control_msg_arrived = True
-
- # deal down stream
-
- # 测试,实际应发送用户属性数据
- reply_param = te.sReplyPara()
- reply_param.code = 0
- reply_param.timeout_ms = 5 * 1000
- reply_param.status_msg = '\0'
-
- te.templateControlReply(reply_param)
-
- pass
-
-
-def on_template_event_post(payload, userdata):
- print("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
- pass
-
-
-# def on_template_action(clientToken, actionId, timestamp, payload, userdata):
-def on_template_action(payload, userdata):
- print("%s:payload:%s,userdata:%s" % (sys._getframe().f_code.co_name, payload, userdata))
-
- clientToken = payload["clientToken"]
- """
- actionId = payload["actionId"]
- timestamp = payload["timestamp"]
- params = payload["params"]
- """
-
- reply_param = te.sReplyPara()
- reply_param.code = 0
- reply_param.timeout_ms = 5 * 1000
- reply_param.status_msg = "action execute success!"
- res = {
- "err_code": 0
- }
-
- te.templateActionReply(clientToken, res, reply_param)
- pass
-
-
-def example_template():
-
- print("\033[1;36m template test start...\033[0m")
-
- te.user_on_connect = on_connect
- te.user_on_disconnect = on_disconnect
- te.user_on_message = on_message
- te.user_on_publish = on_publish
- te.user_on_subscribe = on_subscribe
- te.user_on_unsubscribe = on_unsubscribe
- te.on_template_prop_changed = on_template_prop_changed
- te.on_template_event_post = on_template_event_post
- te.on_template_action = on_template_action
-
-
- te.templateSetup("sample/template/template_config.json")
- te.mqttInit(mqtt_domain="")
- te.connect()
-
- count = 0
- while True:
- if te.isMqttConnected():
- break
- else:
- if count >= 3:
- # sys.exit()
- print("\033[1;31m template test fail...\033[0m")
- # return False
- # 区分单元测试和sample
- return True
- time.sleep(1)
- count += 1
-
-
- te.templateInit()
-
- while True:
- try:
- msg = input()
- except KeyboardInterrupt:
- sys.exit()
- else:
- if msg == "1":
- # you should get real info
- sys_info = {
- "module_hardinfo": "ESP8266",
- "module_softinfo": "V1.0",
- "fw_ver": "3.1.4",
- "imei": "11-22-33-44",
- "lat": "22.546015",
- "lon": "113.941125",
- "device_label": {
- "append_info": "your self define info"
- }
- }
- te.templateReportSysInfo(sys_info)
- elif msg == "2":
- te.templateGetStatus()
- elif msg == "3":
- if g_control_msg_arrived:
- params_in = te.templateJsonConstructReportArray(g_property_params)
- te.templateReport(params_in)
- else:
- prop_list = te.template_property_list
- reports = {
- prop_list[0].key: prop_list[0].data,
- prop_list[1].key: prop_list[1].data,
- prop_list[2].key: prop_list[2].data,
- prop_list[3].key: prop_list[3].data
- }
-
- params_in = te.templateJsonConstructReportArray(reports)
- te.templateReport(params_in)
-
- elif msg == "4":
- event_list = te.template_events_list
-
- '''
- for event in event_list:
- print("event_name:%s" % (event.event_name))
- for prop in event.events_prop:
- print("key:%s" % (prop.key))
- '''
-
- # deal events and add your real value
- status = 1
- message = "message"
- voltage = 20.0
- name = "memory"
- error_code = 0
- timestamp = int(round(time.time() * 1000))
-
- events = {
- "events": [
- {
- "eventId": event_list[0].event_name,
- "type": event_list[0].type,
- "timestamp": timestamp,
- "params": {
- event_list[0].events_prop[0].key:status,
- event_list[0].events_prop[1].key:message
- }
- },
- {
- "eventId": event_list[1].event_name,
- "type": event_list[1].type,
- "timestamp": timestamp,
- "params": {
- event_list[1].events_prop[0].key:voltage
- }
- },
- {
- "eventId": event_list[2].event_name,
- "type": event_list[2].type,
- "timestamp": timestamp,
- "params": {
- event_list[2].events_prop[0].key:name,
- event_list[2].events_prop[1].key:error_code
- }
- }
- ]
- }
- te.templateEventPost(events)
-
- elif msg == "5":
- te.templateDeinit()
-
- elif msg == "6":
- te.disconnect()
-
- elif msg == "7":
- te.clearControl()
-
- else:
- sys.exit()
-
- print("\033[1;36m template test success...\033[0m")
- return True
-
-# if __name__ == '__main__':
-# example_template()
\ No newline at end of file
diff --git a/setup.py b/setup.py
index dc49705..4ddad88 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
import urllib.request
import setuptools
-name = "TIoTExploreSDK"
+name = "tencent-iot-device"
resp = urllib.request.urlopen(f'https://test.pypi.org/pypi/{name}/json')
data = json.loads(resp.read().decode("utf-8"))
version = data['info']['version']
@@ -15,7 +15,6 @@
setuptools.setup(
name=("%s" % name),
-# version="0.0.1a01",
version=f"{version}",
author="larrytin",
author_email="dev_tester@163.com",
@@ -24,6 +23,7 @@
long_description_content_type="text/markdown",
url="https://github.com/tencentyun/iot-device-python",
packages=setuptools.find_packages(),
+ install_requires = ["paho-mqtt==1.5.1", "pycryptodome==3.15.0"],
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",