Project Overview & Difficulty Rating

Connecting a Raspberry Pi to AWS IoT Core is the backbone of many commercial and advanced hobbyist edge-computing deployments. While basic MQTT brokers are easy to spin up, AWS IoT Core requires strict mutual TLS (mTLS) authentication, which introduces specific certificate and policy failure modes that standard tutorials often gloss over.

Difficulty Rating: Intermediate (3.5/5)
Time to Complete: 45 minutes (hardware) + 30 minutes (AWS provisioning)
Target Board Variant: Raspberry Pi 5 (4GB or 8GB) running Raspberry Pi OS Bookworm 64-bit. The code and I2C bus mappings also apply directly to the Raspberry Pi 4 Model B.

Parts List & Specifications

ComponentExact Variant / ModelEstimated Cost (2026)
MicrocontrollerRaspberry Pi 5 (4GB RAM)$60.00
SensorAdafruit BME280 I2C/SPI Breakout (Product ID: 2652)$19.95
WiringPi Cobbler or 4x Female-to-Female Jumper Wires$5.00
Storage32GB SanDisk Extreme microSD (A2 rated)$12.00
PowerOfficial Raspberry Pi 27W USB-C Power Supply$12.00

Hardware Wiring & Pin Mapping

The BME280 communicates over I2C. On the Raspberry Pi 5 (and Pi 4), the primary hardware I2C bus is I2C1. You must enable the I2C interface via sudo raspi-config (Interface Options > I2C) before proceeding.

BME280 PinRaspberry Pi 5 GPIO / Physical PinWire Color (Standard)
VIN (VCC)3.3V Power (Physical Pin 1)Red
GNDGround (Physical Pin 6)Black
SCK (SCL)GPIO 3 / SCL.1 (Physical Pin 5)Yellow
SDI (SDA)GPIO 2 / SDA.1 (Physical Pin 3)Blue
Callout Tip: Never power the BME280 with 5V (Physical Pin 2). While some breakout boards have onboard voltage regulators, the raw BME280 chip is strictly 3.3V tolerant. Feeding it 5V will permanently brick the sensor's internal humidity membrane.

AWS IoT Core Setup & Certificate Provisioning

Before writing code, you must provision the AWS resources. We use the AWS IoT Core console for this build.

  1. Create a Thing: Navigate to AWS IoT Core > Manage > Things > Create things. Name it Pi5-BME280-01.
  2. Generate Certificates: Select 'Auto-generate a new certificate'. Download the Device Certificate (device.pem.crt), the Private Key (private.pem.key), and the Amazon Root CA 1 (AmazonRootCA1.pem). Do not skip the Root CA.
  3. Create an IoT Policy: Create a policy named Pi5-Telemetry-Policy. Attach the following JSON to allow connection, publishing, and subscribing. Replace YOUR_REGION and YOUR_ACCOUNT_ID with your actual AWS details.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect",
        "iot:Publish",
        "iot:Subscribe",
        "iot:Receive"
      ],
      "Resource": "*"
    }
  ]
}
  1. Attach Policy to Certificate: In the Certificates menu, select your new cert, click 'Attach policies', and select Pi5-Telemetry-Policy.
  2. Locate your Endpoint: Go to Settings > Device data endpoints. Copy the ATS endpoint (e.g., a1b2c3d4e5f6g7-ats.iot.us-east-1.amazonaws.com). Do not use the legacy endpoint.

Python Telemetry Code (AWS IoT SDK v2)

We use the official AWS IoT Device SDK v2 for Python (awsiotsdk) alongside smbus2 and bme280. Install dependencies via: pip install awsiotsdk smbus2 bme280.

Place your downloaded certificates in /home/pi/certs/. The code below targets the Raspberry Pi 5 I2C1 bus and includes robust error handling for AWS CRT exceptions.

import time
import json
import sys
from datetime import datetime
from awscrt import mqtt
from awsiot import mqtt_connection_builder
from bme280 import BME280
from smbus2 import SMBus

# --- AWS IoT Configuration ---
ENDPOINT = 'your-ats-endpoint.iot.us-east-1.amazonaws.com'
CLIENT_ID = 'Pi5-BME280-01'
PATH_TO_CERT = '/home/pi/certs/device.pem.crt'
PATH_TO_KEY = '/home/pi/certs/private.pem.key'
PATH_TO_ROOT = '/home/pi/certs/AmazonRootCA1.pem'
TOPIC = 'pi5/telemetry/bme280'

# --- Hardware I2C Pin Mapping Setup ---
# Using I2C bus 1 (SDA=GPIO2/Pin3, SCL=GPIO3/Pin5)
try:
    bus = SMBus(1)
    bme280 = BME280(i2c_dev=bus)
    # Warm up the sensor to avoid initial NaN readings
    bme280.get_temperature()
    time.sleep(1.0)
except Exception as e:
    print(f'Hardware I2C Init Failed on Bus 1: {e}')
    sys.exit(1)

# --- MQTT Callbacks ---
def on_connection_interrupted(connection, error, **kwargs):
    print(f'Connection interrupted. error: {error}')

def on_connection_resumed(connection, return_code, session_present, **kwargs):
    print(f'Connection resumed. return_code: {return_code}')

# --- Build MQTT Connection ---
print('Initializing AWS IoT MQTT Connection...')
mqtt_connection = mqtt_connection_builder.mtls_from_path(
    endpoint=ENDPOINT,
    cert_filepath=PATH_TO_CERT,
    pri_key_filepath=PATH_TO_KEY,
    ca_filepath=PATH_TO_ROOT,
    client_id=CLIENT_ID,
    clean_session=False,
    keep_alive_secs=60,
    on_connection_interrupted=on_connection_interrupted,
    on_connection_resumed=on_connection_resumed
)

try:
    connect_future = mqtt_connection.connect()
    connect_future.result()
    print('Successfully connected to AWS IoT Core')
except Exception as e:
    print(f'AWS CRT Connection Failed: {e}')
    sys.exit(1)

# --- Telemetry Loop ---
try:
    while True:
        temp_c = round(bme280.get_temperature(), 2)
        humidity = round(bme280.get_humidity(), 2)
        pressure = round(bme280.get_pressure(), 2)
        
        payload = {
            'timestamp': datetime.utcnow().isoformat(),
            'device': CLIENT_ID,
            'temp_c': temp_c,
            'humidity_pct': humidity,
            'pressure_hpa': pressure
        }
        
        print(f'Publishing: {json.dumps(payload)}')
        mqtt_connection.publish(
            topic=TOPIC,
            payload=json.dumps(payload),
            qos=mqtt.QoS.AT_LEAST_ONCE
        )
        
        time.sleep(10) # 10-second telemetry interval

except KeyboardInterrupt:
    print('Disconnecting...')
    disconnect_future = mqtt_connection.disconnect()
    disconnect_future.result()
    print('Disconnected cleanly.')

Debugging: First Three Things to Check When It Fails

When deploying aws iot raspberry pi integrations, the AWS Common Runtime (CRT) throws specific C-level errors that Python catches as generic exceptions. If your script crashes on connect_future.result(), check these three things in order:

1. The Exact Error: AWS_ERROR_MQTT_UNEXPECTED_HANGUP

This is the most common error. The TCP connection reaches AWS, but AWS immediately drops it without an MQTT CONNACK.

  • Cause A (Most Likely): Your IoT Policy is missing the iot:Connect action, or the Resource ARN is restricted to a different Client ID.
  • Cause B: You have another device (or a zombie process on the same Pi) already connected using the exact same CLIENT_ID. AWS IoT Core rejects duplicate client IDs by default.
  • Cause C: You are using the legacy ATS endpoint format instead of the Data-ATS endpoint found in your AWS IoT Settings.

2. The Exact Error: SSL: CERTIFICATE_VERIFY_FAILED

The TLS handshake fails before MQTT negotiation begins.

  • Cause A (Most Likely): Your Raspberry Pi's system clock is out of sync. TLS certificates require the local time to be within the validity window. Run timedatectl status and ensure NTP is active.
  • Cause B: You downloaded the wrong Root CA. AWS transitioned to Amazon Root CA 1 (RSA 2048). Ensure you are using AmazonRootCA1.pem, not the older Starfield/Verisign roots.
  • Cause C: The device certificate was manually revoked or deactivated in the AWS Console.

3. The Exact Error: ConnectionRefusedError: [Errno 111] Connection refused

The Pi cannot even establish a TCP socket on port 8883.

  • Cause A: Your local network firewall or ISP blocks outbound traffic on port 8883. Test with openssl s_client -connect YOUR_ENDPOINT:8883.
  • Cause B: You accidentally copied the HTTPS API endpoint instead of the MQTT Data endpoint from the AWS settings page.

Extending and Simplifying the Build

Depending on your production goals, you may need to scale this architecture up or strip it down.

How to Simplify

If the AWS IoT SDK v2 feels too heavy (it installs several compiled C-dependencies via awscrt), you can simplify the build by using the standard Eclipse Paho MQTT library (pip install paho-mqtt). Paho supports mTLS natively via client.tls_set(). You pass the exact same .pem files. This reduces the Python footprint and avoids AWS CRT compilation issues on older 32-bit Pi OS images.

How to Extend

To make this a production-grade edge node, extend the architecture by adding an AWS IoT Rule. Configure a rule to route messages where temp_c > 30.0 directly to an Amazon SNS topic for SMS alerts, while simultaneously dumping all raw telemetry into Amazon Timestream for long-term time-series analysis without writing any custom Lambda parsing code.

FAQ: AWS IoT Raspberry Pi Integration

How do I connect my Raspberry Pi to AWS IoT without the Python SDK?

You can use the mosquitto_pub command-line tool, which is pre-packaged in most Linux repos. This is excellent for quick bench testing without writing Python. The syntax is:
mosquitto_pub -h YOUR_ENDPOINT -p 8883 -i "Pi5-CLI" -t "test/topic" -m '{"temp":22}' --cafile AmazonRootCA1.pem --cert device.pem.crt --key private.pem.key -d
The -d flag enables debug mode, which prints the exact TLS handshake steps and MQTT CONNACK codes, making it the fastest way to verify your certificates and IoT policies before writing application code.

Why does my Raspberry Pi AWS IoT connection drop after exactly 5 minutes?

This is a classic NAT timeout issue. If your router or ISP drops idle TCP connections after 300 seconds (5 minutes), and your sensor only publishes every 10 minutes, the connection silently dies. The AWS IoT broker won't know the Pi is gone until it tries to send a PINGRESP. Fix this by setting the keep_alive_secs parameter in the SDK connection builder to 60 (as shown in the code above). This forces the Pi to send an MQTT PINGREQ every 60 seconds, keeping the NAT table alive.

Does the AWS IoT Core free tier cover continuous Raspberry Pi telemetry?

Yes, for most hobbyist and light-commercial deployments. As of 2026, the AWS IoT Core free tier includes 1 million messages per month for the first 12 months. If your Raspberry Pi publishes one 256-byte payload every 10 seconds, that equates to roughly 259,200 messages per month—well within the free tier. However, be aware that AWS charges per 5KB block. If you start publishing high-resolution base64-encoded images or massive JSON arrays that exceed 5KB, your message count will be multiplied, potentially exhausting the free tier early. Keep payloads lean and use binary formats like Protobuf if you need high-frequency data.

For deeper architectural guidance, refer to the official AWS IoT Core Device Connection Documentation and the Raspberry Pi Hardware Configuration Guides.