Introduction
Most ESP32 + AWS IoT Core examples/tutorials use the Arduino framework and rely on manually clicking through the AWS console. That approach works for quick demos, but it quickly becomes confusing and error-prone—especially if you need to reproduce the setup or scale to multiple devices.
I recently created a repository that makes it much easier to get ESP32 devices securely talking to AWS IoT Core with Infrastructure-as-Code, and modern firmware written in Rust using the Espressif ESP-IDF framework.
Prerequisites
- The tools needed to get this example working are described in the repository
- Basic understanding of the MQTT protocol
- Basic understanding of terraform
- Clone the repository
git clone https://github.com/RamMaths/aws-iot-esp32-example.git
What we are going to build
Instead of another “hello world” demo, the goal here is to create a minimal but extensible template for ESP32 devices communicating with AWS IoT Core.
Step 1: Subscribe to Topics
In MQTT, a topic works like a communication channel between devices and services.
- ESP32 devices subscribe to the topic:
esp32/1
- AWS IoT Core Test Client subscribes to the topic:
esp32/2
This setup ensures that:
- Commands sent from the test client on
esp32/1
will be received by the ESP32 devices. - Device responses published to
esp32/2
will be received by the test client.
Step 2: Publish a Command from the Test Client
Next, we use the AWS IoT Test Client to publish a message in JSON format to the topic esp32/1
.
{ "message": "ping" }
- The message field contains the command we want the ESP32 device to execute.
- In this example, we send "ping".
- The firmware listens for this value and matches it against predefined actions (e.g., respond with "pong").
Using JSON keeps the communication simple and extensible—you can later add more fields or new commands without changing the overall structure.
Step 3: Device Response
When an ESP32 device receives a command, the firmware matches it against predefined actions.
For the ping command, the device responds by publishing:
{ "message": "pong" }
to the topic esp32/2
.
Since the test client is subscribed to this topic, it immediately receives the response.
🔧 This example only implements a simple ping → pong workflow, but this project is designed to be extensible. For example, if a device subscribed to esp32/light
, you could add commands like:
{ "message": "on" } { "message": "off" }
Acquiring cloud resources
With the repository cloned and the terraform CLI installed, we can now provision the required AWS IoT Core resources.
1. Initialize Terraform
Navigate into the Terraform directory and initialize the project:
cd terraform terraform init
👉 Make sure your AWS credentials are configured (aws configure) before proceeding.
2. Configure Variables
Copy the example variables file:
cp terraform.tfvars.example terraform.tfvars
Open terraform.tfvars
and edit the things array. Each thing represents an IoT device in AWS IoT Core. For each device, you define:
-
name
: the device identifier in AWS IoT -
topic_prefix
: the MQTT topic namespace the device can publish/subscribe to
Example configuration for two ESP32 devices (S3 and C3):
# Terraform variables for AWS IoT ESP32 Example # Copy this file to terraform.tfvars and customize the values # List of IoT Things to create with their configurations things = [ { name = "esp32s3" topic_prefix = "esp32" }, { name = "esp32c3" topic_prefix = "esp32" } ] # AWS region where IoT resources will be created region = "us-east-1" # Tags to apply to all AWS resources tags = { Project = "ESP32-IoT-Example" Environment = "development" ManagedBy = "terraform" Owner = "your-name" }
3. Apply Terraform
Finally, deploy the resources:
terraform plan terraform apply
This will create:
- IoT Things for each device (esp32s3, esp32c3)
- Certificates and policies (automatically downloaded into certs/)
- Topic permissions based on the prefixes you specified
✅ At this point, your AWS environment is fully provisioned and ready for your ESP32 devices to connect securely.
Compiling the Firmware
With the cloud resources provisioned by Terraform, the next step is to configure, build, and flash the ESP32 firmware.
1. Configure the Project
Navigate into the firmware directory for your ESP32 variant:
After terraform creates the resources it will output something like this
cd firmware/example
(See the README setup guide for details on configuring Xtensa vs RISC-V targets.)
2. Create cfg.toml
Terraform outputs the connection details you need. Your config should look like this:
[example] # Wi-Fi Configuration wifi_ssid = "YOUR_WIFI_NETWORK" wifi_pass = "YOUR_WIFI_PASSWORD" mqtt_url = "mqtts://your-endpoint.iot.region.amazonaws.com" mqtt_client_id = "thing_name" mqtt_topic_pub = "topic_prefix" mqtt_topic_sub = "topic_prefix" cert_ca = "certs/AmazonRootCA1.pem" cert_crt = "certs/[certificate-id]-certificate.pem.crt" cert_key = "certs/[certificate-id]-private.pem.key"
- Replace
wifi_ssid
andwifi_pass
with your network credentials. - Set
mqtt_topic_pub
= "esp32/2" andmqtt_topic_sub
= "esp32/1". - Leave the rest as generated by Terraform.
Save this as cfg.toml
inside your firmware project.
3. Copy Certificates
Copy the certificates for your device into the firmware directory. The folder must be named certs
:
cp -r ../../terraform/certs/thing_name ./certs
4. Build and Flash
Now compile and flash the firmware to your ESP32:
cargo build --release cargo espflash ./path/to/the/binary -p /path/to/your/esp32/port cargo espmonitor /path/to/your/esp32/port
What to Expect in the Logs
Once the ESP32 connects to Wi-Fi and establishes an MQTT session with AWS IoT Core, you should see logs similar to the following:
I (1234) example: WiFi connected successfully I (1235) example: MQTT client created successfully I (1236) example: Subscribed to topic "esp32/1" I (1237) example: Starting main application loop
These messages confirm that:
- Your device joined the Wi-Fi network
- The MQTT client was initialized correctly
- The subscription to the expected topic succeeded
- The application is now running and ready to handle messages
Common Errors
It’s normal to briefly see messages like:
Failed to subscribe to topic "esp32/1": {}, retrying...
This happens while AWS IoT validates the certificates and sets up the secure connection. As long as the retries eventually succeed and you see the subscription message, your setup is working correctly.
🎉 Congratulations!
If you’ve followed along to this point, you’ve come a long way — from setting up cloud resources with Terraform, to configuring certificates, flashing firmware, and seeing your ESP32 securely connect to AWS IoT Core. That’s a huge achievement! 🚀
Thank you for reading and coding along. I hope you not only got things working but also learned something new about how ESP32, MQTT, and AWS IoT fit together.
🧑💻 What’s Next? Experiment!
This repository is meant as a starting point. Feel free to dive in and make it your own:
- Add more commands beyond the simple ping → pong demo
- Explore how the Wi-Fi connection is established in
startup.rs
- Take a closer look at our generalized MQTT client in
client.rs
- It runs a separate thread to keep listening for messages
- Uses
crossbeam_channel
to forward messages to the main thread safely
- Experiment with publishing structured JSON commands or even sensor data
🔧 Extending the Main Loop
Inside main.rs
, you’ll find the main loop where incoming MQTT messages are matched against commands. Right now it only handles "ping". Here’s where you can extend it:
match msg.message.as_str() { "ping" => { info!("Ping received, sending pong"); JsonMessage { message: format!("pong from: {}", app.config.mqtt_client_id), } } "light_on" => { info!("Turning light ON"); JsonMessage { message: "Light is now ON".to_string(), } } "light_off" => { info!("Turning light OFF"); JsonMessage { message: "Light is now OFF".to_string(), } } _ => { warn!("Unknown action: {}", msg.message); JsonMessage { message: format!("Unknown action: {}", msg.message), } } }
You can use this pattern to hook up any device action — toggling GPIO pins, controlling peripherals, or sending structured responses back to AWS IoT Core.
🙌 Thank You
Thanks again for reading. Now go ahead — experiment, break things, fix them again, and build something awesome. That’s where the real learning happens.
If you found this helpful:
- ⭐ Star the repository on GitHub to support the project
- 💬 Share your feedback, questions, or improvements in the repo’s Discussions/Issues
- 🚀 Show off what you’ve built with this template — I’d love to see your projects!
Top comments (0)