Overview
AWS IoT Core is the IoT platform in Amazon Web Services. Devices exchange messages with it over MQTT (the most mainstream lightweight messaging protocol in IoT), and authenticate with X.509 certificates (certificate = the "digital ID" the cloud platform issues to the device; combined with TLS encrypted transport, it's like putting messages in a tamper-proof encrypted envelope). This tutorial uses the official aws_iot_core example project to connect the Ai-WB2 to AWS IoT Core: after connecting, it loops publishing Light ON/OFF messages to a cloud topic and subscribes to cloud topics; the example also demonstrates the Shadow feature (the device's cloud-side "stand-in", always holding the device's latest state).
In plain words: AWS IoT is like the international version of a "WeChat server", but with a different login method — domestic platforms use "account and password" (the triple), AWS uses three keys: the device certificate (your ID card), the private key (a key only you have), and the root CA certificate (the "official stamp" that verifies identity). All three keys are embedded in the program so the board can get past AWS's "doorman" (the TLS encrypted connection) and log into the platform to send messages. This tutorial helps you issue these three keys in the AWS console and install them into the code.
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/aws_iot_core; the code can be found directly in the local SDK.
This step “issues the three keys” on the cloud side: create the Thing (AWS calls a “device” a Thing), generate and download certificates, then install the keys into the code (step ③).
- Open the AWS official website, register and log in (card verification required; new accounts get free tier).
- Search IoT Core in the console and enter the AWS IoT Core console (pick
us-east-1orus-east-2for the region — the region is where the servers live; use the same region for the endpoint later). - Left menu “Manage → Things”, click “Create things” → “Create a single thing” → enter a thing name (e.g.
Ai-WB2-M1, remember it — needed in the config later). - Generate certificates for the thing: choose “1-Click certificate”; after generation immediately download the three files (generated only once, keep them safe):
xxx.cert.pem— the device certificate (ID card)xxx.private.key— the private key (the key)AmazonRootCA1.pem— the root CA certificate (the stamp)
- Create a Policy (= the “pass” issued to the certificate, declaring what it’s allowed to do): “Secure → Policies → Create policy”, add the four actions
iot:Connect,iot:Subscribe,iot:Receive,iot:Publish, set Resources to*(enough for this tutorial’s testing), then “Secure → Certificates → select the certificate above → Attach policy”. - Note the Endpoint (= server address): “Settings → Device data endpoint”, shaped like
xxxxxxxx-ats.iot.us-east-2.amazonaws.com. - Open the console’s “MQTT test client”, subscribe to topic
$aws/things/<your thing name>/shadow/update, waiting for the board to connect and send messages (for verification later).
Open a terminal and enter the official aws_iot_core example project directory:
cd ~/Ai-Thinker-WB2/applications/iot-solution/aws_iot_core
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 |
|---|---|
aws_iot_core/main.c |
Main program: Wi-Fi connection + starting the AWS control task |
aws_iot_core/aws_control.c |
Main flow: init → connect → subscribe → loop publishing Light ON/OFF |
aws_iot_core/demo_publish.c |
Standalone publish example (publish only, no subscribe) |
aws_iot_core/demo_subscribe.c |
Standalone subscribe example (subscribe only, no publish) |
aws_iot_core/demo_shadow.c |
Shadow example: syncs the light state to the cloud “stand-in” |
aws_iot_core/aws_test_cert.h |
The certificate and endpoint configuration lives here (three keys + server address + topics) |
💡 The official repo also has a certificate-file example project
aws_iot_certunderapplications/iot-solution/(certificates as files read from the filesystem); this tutorial’saws_iot_corecompiles the certificates directly into the firmware, which is friendlier for beginners.
Open aws_iot_core/main.c and change the Wi-Fi account and password at the top (placeholder = the example values pre-written in the code; you need to replace them with your real ones):
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
Open aws_iot_core/aws_test_cert.h and do three things:
- Replace the content between
-----BEGIN CERTIFICATE-----and-----END CERTIFICATE-----inTEST_ROOT_CA_FILENAMEwith the root CA certificate downloaded in step ①; - Replace
TEST_CERTIFICATE_FILENAMEwith the device certificate andTEST_PRIVATE_KEY_FILENAMEwith the private key (the-----END RSA PRIVATE KEY-----section at the end); - Replace the endpoint with yours (shaped like
xxxxxxxx-ats.iot.us-east-2.amazonaws.com):
#define TEST_MQTT_HOST "your endpoint"
#define TEST_MQTT_PORT 8883
Then replace the client ID and thing name with your thing name, and the AiThinker in the topics too (topic = channel name; messages are sent/received by channel):
#define TEST_MQTT_CLIENT_ID "Ai-WB2-M1"
#define TEST_MY_THING_NAME "Ai-WB2-M1"
#define TEST_MYPUBTOPIC "$aws/things/AiThinker/shadow/update"
#define TEST_MYSUBTOPIC "$aws/things/AiThinker/shadow/update/accepted"
⚠️ Certificates are long text — when replacing, keep the surrounding double quotes and the
\r\nline-break escape characters intact; use an editor’s whole-value replace for the two certificate macros. The most common symptom of broken certificate content is TLS handshake failure when connecting.
This tutorial uses the official example project — no new code needed; the complete aws_iot_core/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/aws_iot_core/aws_iot_core/main.c). The main connect/send/receive flow is inaws_control.c, the Shadow indemo_shadow.c; the rest can be found in the official project.
Code highlights:
| Code | Purpose |
|---|---|
wifi_mgmr_sta_connect(if, ssid, pwd, ...) |
Makes the board connect to the router in STA mode (as a “client”); nothing works without the internet |
aws_iot_mqtt_init(&client, ¶ms) |
Initializes the client, filling the endpoint/port/three certificates into the config — any mistake here and nothing connects |
aws_iot_mqtt_connect(&client, ¶ms) |
Establishes the TLS encrypted connection with AWS using the certificates (TLS = the encryption envelope for communication, anti-eavesdropping) |
aws_iot_mqtt_subscribe(&client, topic, ...) |
Subscribes to a topic (topic = channel name); without subscribing you can’t receive cloud messages |
aws_iot_mqtt_publish(&client, topic, ...) |
Publishes a message to a topic; the cloud/device subscribed to that channel all receive it |
aws_iot_mqtt_yield(&client, 100) |
Loops send/receive processing and heartbeat keep-alive; without it you receive no messages and get kicked offline by the platform |
aws_iot_shadow_update(&client, thing, ...) |
Syncs the device’s latest state to the cloud Shadow (the device’s “stand-in”) |
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/aws_iot_core.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: it connects to Wi-Fi first, then to AWS. 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) and you should see:
[APP] [EVT] GOT IP ...
AWS IoT SDK Version 3.0.1-
Connecting...
...
Connect ok!!!, start Subscribing Topic=[$aws/things/AiThinker/shadow/update/accepted]...
Meanwhile go back to the AWS console’s “MQTT test client” (subscribed to $aws/things/<your thing name>/shadow/update) — you should receive a Light ON / LIGHT OFF alternating message every 1 second — the publish path is working.
Seeing Connect ok!!! means success; if you only see GOT IP without Connect ok, it hasn’t succeeded yet — check the FAQ at the end.
API Summary for This Tutorial
aws_iot_control(arg)
The AWS access main flow: init client → connect → subscribe TEST_MYSUBTOPIC → loop publishing Light ON/LIGHT OFF messages (project-custom interface, source in aws_iot_core/aws_control.c, called by main.c as a task after GOT IP).
Parameters:
arg: task parameter; this example passesNULL
Return: none (task function; disconnects and frees the client on exit)
aws_iot_demo_publish(arg)
Standalone publish example: publish only, no subscribe — loops sending Light ON/OFF to TEST_MYPUBTOPIC (project-custom interface, source in aws_iot_core/demo_publish.c).
Parameters:
arg: task parameter; passNULL
Return: none (task function)
aws_iot_demo_subscribe(arg)
Standalone subscribe example: subscribe only, no publish — prints the topic and content of received messages (project-custom interface, source in aws_iot_core/demo_subscribe.c).
Parameters:
arg: task parameter; passNULL
Return: none (task function)
aws_iot_demo_shadow(arg)
Shadow example: periodically syncs the lightstatus property (ON/OFF) to the cloud Shadow (Shadow = the device's cloud-side "stand-in", holding the device's latest state), and registers a delta callback to receive cloud-side modifications to the Shadow (project-custom interface, source in aws_iot_core/demo_shadow.c).
Parameters:
arg: task parameter; passNULL
Return: none (task function)
aws_iot_mqtt_init(&client, &mqttInitParams)
Initializes the MQTT client: configures the server address (pHostURL), port, the three certificates (pRootCALocation/pDeviceCertLocation/pDevicePrivateKeyLocation), timeouts and disconnect callbacks (AWS official Device SDK interface).
Parameters:
client:AWS_IoT_Clientstruct pointer, holds client statemqttInitParams:IoT_Client_Init_Paramsstruct pointer;iotClientInitParamsDefaultis the default, override members as needed
Return: SUCCESS (0) on success; error code on failure (IoT_Error_t enum, e.g. FAILURE)
aws_iot_mqtt_connect(&client, &connectParams)
Establishes the TLS encrypted connection with AWS IoT Core (TLS = encryption envelope, anti-eavesdropping); connection parameters include keep-alive interval, client ID, MQTT version, etc.
Parameters:
client: the client initialized byaws_iot_mqtt_initconnectParams:IoT_Client_Connect_Paramsstruct pointer, containingkeepAliveIntervalInSec,pClientID,MQTTVersion,isCleanSession, etc.
Return: SUCCESS (0) on success; error code on failure
aws_iot_mqtt_subscribe(&client, topicName, topicNameLen, qos, pApplicationHandler, pApplicationHandlerData)
Subscribes to a topic (topic = channel name); pApplicationHandler fires when the topic receives messages.
Parameters:
client: client struct pointertopicName: topic string (this exampleTEST_MYSUBTOPIC)topicNameLen: topic lengthqos: QoS level; this example usesQOS1(at-least-once)pApplicationHandler: the message callbackpApplicationHandlerData: data passed through to the callback; passNULLif none
Return: SUCCESS (0) on success; error code on failure
aws_iot_mqtt_publish(&client, topicName, topicNameLen, ¶ms)
Publishes a message to a topic (this example publishes Light ON/OFF text messages).
Parameters:
client: client struct pointertopicName: topic string (this exampleTEST_MYPUBTOPIC)topicNameLen: topic lengthparams:IoT_Publish_Message_Paramsstruct pointer, containingqos,isRetained,payload(message content),payloadLen
Return: SUCCESS (0) on success; error code on failure (publish ACK timeout returns MQTT_REQUEST_TIMEOUT_ERROR)
aws_iot_mqtt_yield(&client, timeout_ms)
Lets the SDK handle network send/receive and heartbeat keep-alive; must be called repeatedly in a loop, otherwise you receive no messages and get kicked offline by the platform. Returns NETWORK_ATTEMPTING_RECONNECT after a disconnect — skip business logic and wait for the reconnect then.
Parameters:
client: client struct pointertimeout_ms: receive-wait timeout in milliseconds; this example uses100
Return: IoT_Error_t values such as SUCCESS, NETWORK_ATTEMPTING_RECONNECT, NETWORK_RECONNECTED
aws_iot_shadow_update(&mqttClient, thingName, jsonDocument, callback, contextData, timeout_sec, isPersistent)
Syncs the device's latest state (JSON packet) to the cloud Shadow (Shadow = the cloud "stand-in"); the cloud/app reads the Shadow to get the device's latest state.
Parameters:
mqttClient: client struct pointerthingName: the thing name (TEST_MY_THING_NAME)jsonDocument: the state JSON packet (built byaws_iot_shadow_init_json_document/aws_iot_shadow_add_reported/aws_iot_finalize_json_document)callback: update result callback (SHADOW_ACK_ACCEPTEDmeans the cloud accepted it)contextData: pass-through data; passNULLif nonetimeout_sec: timeout in seconds; this example4isPersistent: whether to keep the subscription; this exampletrue
Return: SUCCESS (0) on success; error code on failure
📌 The AWS SDK headers (
aws_iot_mqtt_client_interface.hetc.) come from the AWS official Device SDK for Embedded C v3.0.1, fetched automatically during the build; this project compiles the certificates into the firmware as macro strings — a "demo" approach; for mass production, the recommended practice is theaws_iot_certproject with certificates on the filesystem.
Full Code
Below is the complete aws_iot_core/main.c source, identical to the official example (applications/iot-solution/aws_iot_core/aws_iot_core/main.c) (send/receive and Shadow logic are in aws_control.c, demo_shadow.c, etc.; the rest can be found in the official project):
📜 Click to expand the full aws_iot_core/main.c code
#include <FreeRTOS.h>
#include <task.h>
#include <stdio.h>
#include <string.h>
#include <aos/yloop.h>
#include <aos/kernel.h>
#include <lwip/tcpip.h>
#include <wifi_mgmr_ext.h>
#include <hal_wifi.h>
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
static wifi_conf_t conf =
{
.country_code = "CN",
};
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 void event_cb_wifi_event(input_event_t* event, void* private_data)
{
switch (event->code)
{
case CODE_WIFI_ON_INIT_DONE:
{
printf("[APP] [EVT] INIT DONE %lld\r\n", aos_now_ms());
wifi_mgmr_start_background(&conf);
}
break;
case CODE_WIFI_ON_MGMR_DONE:
{
printf("[APP] [EVT] MGMR DONE %lld\r\n", aos_now_ms());
wifi_sta_connect(ROUTER_SSID, ROUTER_PWD);
}
break;
case CODE_WIFI_ON_DISCONNECT:
{
printf("[APP] [EVT] disconnect %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTING:
{
printf("[APP] [EVT] Connecting %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_CMD_RECONNECT:
{
printf("[APP] [EVT] Reconnect %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTED:
{
printf("[APP] [EVT] connected %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_PRE_GOT_IP:
{
printf("[APP] [EVT] connected %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_GOT_IP:
{
printf("[APP] [EVT] GOT IP %lld\r\n", aos_now_ms());
printf("[SYS] Memory left is %d Bytes\r\n", xPortGetFreeHeapSize());
void aws_iot_control(void *arg);
xTaskCreate(aws_iot_control, (char*)"aws_iot_control", 4096, NULL, 10, NULL);
}
break;
case CODE_WIFI_ON_PROV_CONNECT:
{
printf("[APP] [EVT] [PROV] [CONNECT] %lld\r\n", aos_now_ms());
wifi_sta_connect(ROUTER_SSID, ROUTER_PWD);
}
break;
case CODE_WIFI_ON_PROV_DISCONNECT:
{
printf("[APP] [EVT] [PROV] [DISCONNECT] %lld\r\n", aos_now_ms());
}
break;
default:
{
printf("[APP] [EVT] Unknown code %u, %lld\r\n", event->code, aos_now_ms());
/*nothing*/
}
}
}
static void proc_main_entry(void* pvParameters)
{
aos_register_event_filter(EV_WIFI, event_cb_wifi_event, NULL);
hal_wifi_start_firmware_task();
aos_post_event(EV_WIFI, CODE_WIFI_ON_INIT_DONE, 0);
vTaskDelete(NULL);
}
void main()
{
xTaskCreate(proc_main_entry, (char*)"main_entry", 1024, NULL, 15, NULL);
tcpip_init(NULL, NULL);
}FAQ & Troubleshooting
⚠️ Connect ok!!! never appears; logs show TLS handshake failure / certificate verify failed
Cause: the three certificates weren't replaced cleanly or the format is broken (extra/missing \r\n, misplaced quotes), wrong endpoint, certificate-device mismatch, or the certificate expired (AWS device certificates must be regenerated after expiry)
Fix: re-replace the three macros TEST_ROOT_CA_FILENAME/TEST_CERTIFICATE_FILENAME/TEST_PRIVATE_KEY_FILENAME in aws_test_cert.h character by character, keeping the surrounding quotes and \r\n; confirm TEST_MQTT_HOST is the address in your console's "Settings → Device data endpoint"; for expired certificates, recreate and download them in the console
⚠️ Connection rejected; logs report permission/policy errors
Cause: the certificate has no attached Policy, or the policy doesn't allow the Connect/Subscribe/Publish/Receive actions
Fix: in the console "Secure → Certificates", select your certificate → "Actions → Attach policy"; confirm the policy includes the four actions iot:Connect, iot:Subscribe, iot:Receive, iot:Publish
⚠️ Connected, but the test client receives no messages
Cause: the thing name in the topics doesn't match — the AiThinker in TEST_MY_THING_NAME/TEST_MYPUBTOPIC/TEST_MYSUBTOPIC wasn't replaced with your thing name, or the test client subscribed to a different topic than the published one
Fix: replace every AiThinker in the topics in aws_test_cert.h with your thing name; the test client subscribes to $aws/things/<your thing name>/shadow/update, matching TEST_MYPUBTOPIC
⚠️ Keeps printing Connecting, no GOT IP (can't connect to the router)
Cause: ROUTER_SSID/ROUTER_PWD wrong in main.c, the router is on 5GHz, or the signal is too weak
Fix: check the Wi-Fi account and password; confirm the router broadcasts 2.4GHz; move the board near the router; you can first run Connect Wi-Fi alone to verify internet access
⚠️ 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 serial shows Connect ok!!!, and the AWS console's "MQTT test client" subscribed to $aws/things/<your thing name>/shadow/update keeps receiving Light ON / LIGHT OFF messages — the AWS IoT connection is verified.

