Overview
Tencent Cloud IoT Explorer (Tencent Cloud IoT platform = the cloud-side device management console) is Tencent Cloud's one-stop IoT development platform, paired with the WeChat mini program "Tencent Llianlian" for device provisioning (provisioning = "telling" the board the Wi-Fi account and password) and device control. This tutorial uses the official qcloud_demo example project to connect the Ai-WB2 to Tencent Cloud IoT Explorer: Bluetooth-assisted provisioning with the Tencent Llianlian mini program; after provisioning succeeds, the board automatically connects to Wi-Fi and the cloud platform; pressing the KEY button controls the onboard LED and reports the property (reporting = device→cloud data); you can also send commands (commands = cloud→device) from the mini program/console to toggle the light remotely.
In plain words: the Tencent Cloud IoT platform is like a "WeChat server" — the product ID, device name and device secret are the "account and password" issued by the platform; provisioning means using the WeChat mini program "Tencent Llianlian" to "whisper" your home Wi-Fi's account and password to the board (over the Bluetooth channel, no wiring); after that, when you press the button on the board, the light state "posts moments" in real time to the cloud, and the cloud-side device "stand-in" updates in sync; conversely, tapping the switch in the mini program sends a "private message" command down to the board, turning the light on.
This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version
release_bl_iot_sdk_1.6.40) exampleapplications/iot-solution/qcloud_demo; the code can be found directly in the local SDK.
This step “registers the account” on the cloud side: create the product and device, and get the triple (product ID/device name/device secret) the device uses to log into the platform.
- Open the Tencent Cloud official website, register and log in (new users need real-name verification).
- Search and enter the “IoT Explorer” console (on first entry you need to activate the service; the public instance is free).
- Create a project (e.g.
WB2_Demo) → create a product under the project: pick “Standard Category” for product category → Smart Home → Electrical & Lighting → Light; choose Wi-Fi for connection method, “Standard BLE Assisted” for provisioning method (this tutorial uses Bluetooth provisioning), and check the data template for “Device Data Protocol”. - “Device Debugging → Create Device”, enter a device name (e.g.
wb2_light); after creation you see the DeviceSecret — shown only once at creation, save it. - The ProductId and DeviceName can be seen in the product’s “Device List” — these two are always queryable, no need to save specially.
💡 Connecting to the platform needs the triple: product ID + device name + device secret (the secret is shown only once at device creation); the example code also reserves the ProductSecret for dynamic registration (optional feature, not used in this tutorial). These values go into the code in the next steps.
Hardware check: this example controls the light and button via GPIO, wiring as follows (preconfigured for the Ai-WB2-32S-Kit by default):
| Peripheral | Pin |
|---|---|
| LED (light) | GPIO14 |
| KEY (button; one press reports the switch state once) | GPIO8 |
| Re-provisioning button (hold ~5 seconds to clear Wi-Fi config) | GPIO4 |
Open a terminal and enter the official qcloud_demo example project directory:
cd ~/Ai-Thinker-WB2/applications/iot-solution/qcloud_demo
Note:
cdis the “change directory” command — entering the official example project; all subsequentmakecommands must run in this directory.
📌 This project consists of multiple source files; the Full Code section only shows
main.c— the rest can be found in the official project:
| File | Purpose |
|---|---|
sdk_app_qcloud/main.c |
Main program: Wi-Fi connection + deciding the provisioning/cloud flow, the main file this tutorial looks at |
sdk_app_qcloud/light_data_template_sample.c |
Data template core: property reporting/command control, LED and button logic |
sdk_app_qcloud/qcloud_wifi_config_sample.c |
Provisioning flow (with the provisioning-method switch macros WIFI_PROV_*) and Start_Qcloud_Demo() |
sdk_app_qcloud/ble_qiot_ble_device.c / ble_qiot_ble_service.c |
Bluetooth-assisted provisioning service (the Tencent Llianlian provisioning channel) |
components/qcloud_iot_c_sdk/ |
Tencent official IoT SDK (where the IOT_Template_* series lives), the triple is configured here |
Open components/qcloud_iot_c_sdk/platform/HAL_Device_freertos.c and replace the example triple with the values from the console:
/* product Id */
static char sg_product_id[MAX_SIZE_OF_PRODUCT_ID + 1] = "你的产品ID";
/* device name */
static char sg_device_name[MAX_SIZE_OF_DEVICE_NAME + 1] = "你的设备名称";
/* device secret of PSK device */
static char sg_device_secret[MAX_SIZE_OF_DEVICE_SECRET + 1] = "你的设备密钥";
⚠️ This file has two triple groups by provisioning method (the
WIFI_PROV_SOFT_AP_ENABLEgroup and theWIFI_PROV_BT_COMBO_CONFIG_ENABLEgroup); only the group matching your current provisioning method is compiled. This tutorial uses Bluetooth provisioning by default — change the Bluetooth group (the three variables under the#elif WIFI_PROV_BT_COMBO_CONFIG_ENABLEbranch). If you enable dynamic registration, also replacesg_product_secretunder theDEV_DYN_REG_ENABLEDbranch with your product secret.
Confirm the provisioning method: open the top of sdk_app_qcloud/qcloud_wifi_config_sample.c — Bluetooth-assisted provisioning is the default (WIFI_PROV_BT_COMBO_CONFIG_ENABLE 1, other WIFI_PROV_* are 0), keep the default:
#define WIFI_PROV_BT_COMBO_CONFIG_ENABLE 1 ///< wifi provisioning method: bt combo config, need Wechat Applets
This tutorial uses the official example project — no new code needed; the complete sdk_app_qcloud/main.c code has been moved to the end of this page:
📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (
applications/iot-solution/qcloud_demo/sdk_app_qcloud/main.c). The data template report/command logic is inlight_data_template_sample.c, the provisioning logic inqcloud_wifi_config_sample.c; the rest can be found in the official project.
Code highlights:
| Code | Purpose |
|---|---|
led_gpio_init(LED_PIN) / key_init(KEY_PIN) |
Initializes the LED and button pins; without it the light never turns on and the button does nothing |
easyflash_init() |
Initializes the power-off persistence library; provisioning info is stored here — without it the saved Wi-Fi can’t be read |
wifi_info_read() |
Reads the Wi-Fi account/password from the persistent area; auto-enters provisioning mode (LED blinking) if there’s none |
Q_Cloud_Config_Net_Start() |
Starts the Tencent Llianlian provisioning flow (Bluetooth channel) — provisioning is the first step of going online |
Start_Qcloud_Demo() |
After provisioning/networking succeeds, starts the cloud data template demo — the device officially connects to the cloud |
IOT_Template_Report(...) |
Reports properties (e.g. switch state) to the cloud; without it the cloud can’t see the device state |
Build in the project directory:
make -j8
Note:
makeis the “build” command, turning code into firmware (the program flashed into the board) the board can run;-j8builds with 8 parallel CPU cores, faster.
On success a firmware build_out/sdk_app_qcloud.bin is generated.
⚠️ If it reports
riscv64-unknown-elf-gcc: command not found, the toolchain permissions aren’t configured — runcd toolchain/riscv/Linux && . chmod755.shfirst, then rebuild.
Keep the board connected via USB, confirm the serial device, and flash:
make flash p=/dev/ttyUSB0 b=921600
Note:
make flashis the “flash” command, writing the compiled firmware into the board’s chip. Afterp=comes the serial device (often/dev/ttyUSB0on Linux,COM3and the like on Windows — use your actual one, check withls /dev/ttyUSB*),b=921600is the flash baud rate (serial transfer speed), keep the default.
⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode; wait for the progress bar to complete — that means the flash succeeded.
After flashing, the board automatically restarts; since it hasn’t been provisioned, it auto-enters provisioning mode (onboard LED blinking). Open the serial monitor (a serial debugging assistant app, baud rate 921600 — the baud rate is the “speaking speed” of the serial, both sides must match) to see the provisioning logs.
Step 1: Provision with the mini program. Search the “Tencent Llianlian” mini program in WeChat on your phone → enter “Add Device” → the mini program auto-scans nearby devices → when found, pick your Wi-Fi and enter the password as prompted to complete provisioning (provisioning = “telling” the board the Wi-Fi account and password).
Step 2: Wait to go online. After provisioning succeeds, the board automatically connects to the router and the cloud platform; the serial prints:
[APP] [EVT] GOT IP ...
Cloud Device Construct Success
Register data template propertys Success
Step 3: Button report. Press the KEY button on the board (GPIO8) — the LED toggles, and the serial prints data template reporte success; open the Tencent Llianlian mini program — the device shows online, and the light switch state follows.
Step 4: Cloud command. Tap the switch in the Tencent Llianlian mini program/console — the board’s LED turns on/off in sync, and the serial prints the received command message.
Seeing Cloud Device Construct Success and the device online in the Tencent Llianlian mini program with the switch state in sync means success; if you only see GOT IP without Cloud Device Construct Success, it hasn’t succeeded yet — check the FAQ at the end.
API Summary for This Tutorial
Start_Qcloud_Demo()
After provisioning/networking succeeds, starts the cloud data template demo: creates the bl_qcloud_main task to connect to Tencent Cloud (project-custom interface, source in sdk_app_qcloud/qcloud_wifi_config_sample.c).
Parameters:
- None
Return: none
Q_Cloud_Config_Net_Start()
Starts the Tencent Llianlian provisioning flow (Bluetooth-assisted by default); after provisioning succeeds, stores the Wi-Fi account/password in the persistent area (project-custom interface, source in sdk_app_qcloud/qcloud_wifi_config_sample.c).
Parameters:
- None
Return: none
IOT_Template_Construct(pParams, pMqttClient)
Constructs the data template client: parses the triple and creates the MQTT connection (Tencent official SDK interface, header components/qcloud_iot_c_sdk/include/exports/qcloud_iot_export_data_template.h).
Parameters:
pParams:TemplateInitParamsstruct pointer (region, timeouts, etc.; this example{ "china", NULL, NULL, NULL, 2000, 1000, 1, 1, { 0 } })pMqttClient: an existing MQTT client handle; passNULLif none (created internally by this function)
Return: client handle (void *) on success; NULL on failure
IOT_Template_Start_Yield_Thread(pClient)
Starts a dedicated send/receive thread that auto-loops IOT_Template_Yield to keep the heartbeat and message exchange alive; the main loop no longer needs manual Yield after this.
Parameters:
pClient: the client handle returned byIOT_Template_Construct
Return: QCLOUD_RET_SUCCESS (0) on success; negative error code on failure
IOT_Template_Register_Property(pClient, pProperty, callback)
Registers a data template property (e.g. the light switch light_switch) and binds the command callback — the callback fires when the cloud sends a command changing that property.
Parameters:
pClient: the client handlepProperty:DevicePropertystruct pointer (property key name, type, data pointer)callback: the command callback (this exampleOnControlMsgCallback)
Return: QCLOUD_RET_SUCCESS (0) on success; negative error code on failure
IOT_Template_JSON_ConstructReportArray(handle, jsonBuffer, sizeOfBuffer, count, pReportDataList)
Assembles the property list to be reported into a JSON packet (reporting = device→cloud data), for use by IOT_Template_Report.
Parameters:
handle: the client handlejsonBuffer: output buffer (this examplesg_data_report_buffer[2048])sizeOfBuffer: buffer sizecount: number of propertiespReportDataList:DeviceProperty *array holding the properties to report
Return: QCLOUD_RET_SUCCESS (0) on success; negative error code on failure
IOT_Template_Report(handle, pJsonDoc, sizeOfBuffer, callback, userContext, timeout_ms)
Reports the property JSON packet to the cloud; callback confirms the result when the cloud receives it.
Parameters:
handle: the client handlepJsonDoc: the property JSON packetsizeOfBuffer: packet lengthcallback: report reply callback (this exampleOnReportReplyCallback)userContext: pass-through data; passNULLif nonetimeout_ms: timeout in milliseconds (this exampleQCLOUD_IOT_MQTT_COMMAND_TIMEOUT)
Return: QCLOUD_RET_SUCCESS (0) on success; negative error code on failure
IOT_Template_ControlReply(handle, pJsonDoc, sizeOfBuffer, replyPara)
Replies to the cloud's command message — received commands must be acknowledged, otherwise the cloud thinks the device didn't get them and keeps the messages.
Parameters:
handle: the client handlepJsonDoc: reply packet buffersizeOfBuffer: buffer sizereplyPara:sReplyParastruct pointer (processing resultcode, timeout, etc.)
Return: QCLOUD_RET_SUCCESS (0) on success; negative error code on failure
IOT_Template_Yield(pClient, timeout_ms)
Lets the SDK handle cloud message exchange and heartbeat keep-alive; must be called repeatedly in the main loop (this example calls it every 200 ms); returns QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT while disconnected.
Parameters:
pClient: the client handletimeout_ms: timeout in milliseconds; this example200
Return: QCLOUD_RET_SUCCESS (0) on success; QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT while reconnecting
📌 The data template properties (
light_switch,color,brightness, etc.) must match the properties defined in the console product's "Data Template"; the example triggers reports via button interrupt + semaphore notifying the report task, while the main loop handles exchange and replies.
Full Code
Below is the complete sdk_app_qcloud/main.c source, identical to the official example (applications/iot-solution/qcloud_demo/sdk_app_qcloud/main.c) (the data template logic is in light_data_template_sample.c, the provisioning logic in qcloud_wifi_config_sample.c; the rest can be found in the official project):
📜 Click to expand the full sdk_app_qcloud/main.c code
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <aos/kernel.h>
#include <aos/yloop.h>
#include <lwip/tcpip.h>
#include <bl_sys.h>
#include <hal_wifi.h>
#include <blog.h>
#include <hal_sys.h>
#include <easyflash.h>
#include <wifi_mgmr_ext.h>
#include "FreeRTOS.h"
#include "task.h"
#include <hal_hwtimer.h>
#define KEY_PIN (8)
#define LED_PIN (14)
#define restore_key (4)
extern void key_init(uint8_t pin);
extern void led_gpio_init(uint8_t pin);
extern void restore_key_init(uint8_t pin);
static wifi_conf_t conf =
{
.country_code = "CN",
};
extern char sta_ssid[33];
extern char sta_passwd[65];
extern hw_timer_t* qcloud_demo_handle;
extern void Start_Qcloud_Demo(void);
static void wifi_sta_connect(char* ssid, char* password)
{
wifi_interface_t wifi_interface;
wifi_interface = wifi_mgmr_sta_enable();
wifi_mgmr_sta_connect(wifi_interface, ssid, password, NULL, NULL, 0, 0);
}
static bool wifi_info_read()
{
size_t len;
ef_get_env_blob("ssid", sta_ssid, sizeof(sta_ssid), &len);
if (strlen(sta_ssid) ==0) {
blog_error("[NV] get sta_ssid fail:%d ", len);
return false;
}
else {
blog_info("sta_ssid:%s", sta_ssid);
}
ef_get_env_blob("pwd", sta_passwd, sizeof(sta_passwd), &len);
if (strlen(sta_passwd) ==0) {
blog_error("[NV] get sta_passwd fail:%d ", len);
}
else {
blog_info("sta_passwd:%s", sta_passwd);
}
return true;
}
static void event_cb_wifi_event(input_event_t* event, void* private_data)
{
extern bool q_cloud_IsConnected;
static bool wifi_info_isSet = false;
switch (event->code) {
case CODE_WIFI_ON_INIT_DONE:
{
blog_info("[APP] [EVT] INIT DONE %lld", aos_now_ms());
wifi_mgmr_start_background(&conf);
}
break;
case CODE_WIFI_ON_MGMR_DONE:
{
blog_info("[APP] [EVT] MGMR DONE %lld", aos_now_ms());
if (wifi_info_read())
{
wifi_info_isSet = true;
wifi_sta_connect(sta_ssid, sta_passwd);
}
else
{
extern void Q_Cloud_Config_Net_Start(void);
Q_Cloud_Config_Net_Start();
}
}
break;
case CODE_WIFI_ON_MGMR_DENOISE:
{
blog_info("[APP] [EVT] Microwave Denoise is ON %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_SCAN_DONE:
{
blog_info("[APP] [EVT] SCAN Done %lld", aos_now_ms());
wifi_mgmr_cli_scanlist();
}
break;
case CODE_WIFI_ON_SCAN_DONE_ONJOIN:
{
blog_info("[APP] [EVT] SCAN On Join %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_DISCONNECT:
{
blog_info("[APP] [EVT] disconnect %lld, Reason: %s",
aos_now_ms(),
wifi_mgmr_status_code_str(event->value));
q_cloud_IsConnected = 0;
}
break;
case CODE_WIFI_ON_CONNECTING:
{
blog_info("[APP] [EVT] Connecting %lld", aos_now_ms());
}
break;
case CODE_WIFI_CMD_RECONNECT:
{
blog_info("[APP] [EVT] Reconnect %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTED:
{
blog_info("[APP] [EVT] connected %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_PRE_GOT_IP:
{
blog_info("[APP] [EVT] connected %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_GOT_IP:
{
blog_info("[APP] [EVT] GOT IP %lld", aos_now_ms());
blog_info("[SYS] Memory left is %d Bytes", xPortGetFreeHeapSize());
if (wifi_info_isSet)
{
qcloud_demo_handle = hal_hwtimer_create(100, Start_Qcloud_Demo, 1);
}
else
{
q_cloud_IsConnected = 1;
}
}
break;
case CODE_WIFI_ON_EMERGENCY_MAC:
{
blog_info("[APP] [EVT] EMERGENCY MAC %lld", aos_now_ms());
hal_reboot(); // one way of handling emergency is reboot. Maybe we should also consider solutions
}
break;
case CODE_WIFI_ON_PROV_CONNECT:
{
blog_info("[APP] [EVT] [PROV] [CONNECT] %lld", aos_now_ms());
blog_info("connecting to %s:%s...", sta_ssid, sta_passwd);
wifi_sta_connect(sta_ssid, sta_passwd);
}
break;
case CODE_WIFI_ON_PROV_DISCONNECT:
{
blog_info("[APP] [EVT] [PROV] [DISCONNECT] %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_AP_STA_ADD:
{
blog_info("[APP] [EVT] [AP] [ADD] %lld, sta idx is %lu", aos_now_ms(), (uint32_t)event->value);
}
break;
case CODE_WIFI_ON_AP_STA_DEL:
{
blog_info("[APP] [EVT] [AP] [DEL] %lld, sta idx is %lu", aos_now_ms(), (uint32_t)event->value);
}
break;
default:
{
blog_info("[APP] [EVT] Unknown code %u, %lld", event->code, aos_now_ms());
/*nothing*/
}
break;
}
}
/**
* @brief 直接串口打印系统任务管理器
*/
void show_task_state_task(void* param)
{
char* pcWriteBuffer = malloc(1024);
for (;;)
{
blog_info("=============================================");
blog_info("name \t\tstatus \tprio \tfree \tpid");
vTaskList((char*)pcWriteBuffer);
blog_info("%s", pcWriteBuffer);
blog_info("\n=============================================");
blog_info("=====================================");
blog_info("FreeHeapSize is %d bytes", xPortGetFreeHeapSize());
blog_info("MinimumEverFreeHeapSize is %d bytes", xPortGetMinimumEverFreeHeapSize());
blog_info("=====================================");
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
static void proc_main_entry(void* pvParameters)
{
easyflash_init();
aos_register_event_filter(EV_WIFI, event_cb_wifi_event, NULL);
hal_wifi_start_firmware_task();
/*Trigger to start Wi-Fi*/
aos_post_event(EV_WIFI, CODE_WIFI_ON_INIT_DONE, 0);
vTaskDelete(NULL);
}
int main(void)
{
led_gpio_init(LED_PIN);
key_init(KEY_PIN);
restore_key_init(restore_key);
bl_sys_init();
xTaskCreate(proc_main_entry, (char*)"main_entry", 1024, NULL, 15, NULL);
// xTaskCreate(show_task_state_task, (char *)"status_show", 512, NULL, 8, NULL);
tcpip_init(NULL, NULL);
blog_info("qcloud demo running");
return 0;
}FAQ & Troubleshooting
⚠️ Cloud Device Construct Success never appears (can't connect to the cloud platform)
Cause: the triple wasn't fully changed or the wrong group was changed (in HAL_Device_freertos.c the softAP group and Bluetooth group each have their own set; only the Bluetooth group is compiled by default), or the product category doesn't match the code's properties
Fix: check the Bluetooth group (WIFI_PROV_BT_COMBO_CONFIG_ENABLE branch) sg_product_id/sg_device_name/sg_device_secret against the console (mind the case); the product category should be "Smart Home → Electrical & Lighting → Light", and the data template must include properties like the switch
⚠️ The Tencent Llianlian mini program can't find the device / provisioning fails
Cause: the board isn't in provisioning mode (LED not blinking), the phone's Bluetooth/location permission is off, or the product's provisioning method isn't "Standard BLE Assisted"
Fix: confirm the LED blinks after flashing (blinking = in provisioning mode); grant the mini program Bluetooth and location permissions; check "Standard BLE Assisted" for the product's provisioning method in the console; move the phone close to the board and retry
⚠️ After provisioning, keeps printing Connecting, no GOT IP (can't connect to the router)
Cause: wrong Wi-Fi password during provisioning, the router is on 5GHz, or the signal is too weak
Fix: hold the GPIO4 button ~5 seconds to clear the saved Wi-Fi config and re-provision; confirm the router broadcasts 2.4GHz; move the board near the router; you can first run Connect Wi-Fi alone to verify internet access
⚠️ After pressing the button, the switch state doesn't change in the mini program
Cause: the button's GPIO8 isn't wired up, or the reported property doesn't match the product's data template
Fix: confirm the KEY button is on GPIO8 and the LED on GPIO14; check the property names in the report JSON match the definitions in "Product → Data Template"
⚠️ Serial device not found / can't open
Cause: USB-to-serial driver not installed, insufficient permission, or the cable only charges and can't transfer data
Fix: on Linux check with lsusb/dmesg; if permission denied run sudo usermod -aG dialout $USER and log back in, or sudo chmod 666 /dev/ttyUSB0; on Windows install the driver and check the COM port in Device Manager; try a data-capable cable
⚠️ Flashing keeps waiting / fails
Cause: didn't enter download mode, wrong baud rate, or wrong serial number
Fix: press and hold EN during flashing to enter download mode as prompted; confirm p=/dev/ttyUSB0 is your actual serial; try a different USB port or cable
Self-Check
The Tencent Llianlian mini program provisions successfully, the serial shows Cloud Device Construct Success, pressing KEY prints data template reporte success, the device is online in the mini program with the switch state and LED changing in sync (cloud commands also turn the LED on) — the Tencent Cloud IoT connection is verified.

