Cloud & Data

IoT Cloud Platforms

Compare AWS IoT Core, Azure IoT Hub, ThingSpeak, and Adafruit IO — learn to connect devices, manage fleets, and build scalable cloud IoT backends.

IoT Cloud Platforms

Cloud platforms handle the hard parts of IoT at scale: device authentication, message routing, data storage, fleet management, and analytics. This topic compares the major platforms and shows you how to connect devices to each.


Platform Comparison

PlatformProviderProtocolFree TierBest For
AWS IoT CoreAmazonMQTT, HTTP, WebSocket250K msgs/month (12 mo)Enterprise, scalable
Azure IoT HubMicrosoftMQTT, AMQP, HTTP8,000 msgs/day (F1)Microsoft stack
Google Cloud IoTGoogleMQTT, HTTPPay-per-useBigQuery integration
ThingSpeakMathWorksHTTP, MQTT3M msgs/yearLearning, MATLAB analytics
Adafruit IOAdafruitMQTT, HTTP30 msg/min, 10 feedsMaker projects
HiveMQ CloudHiveMQMQTT100 connectionsMQTT broker as service
LosantLosantMQTT, HTTP100 devicesMid-scale, good UI

ThingSpeak — Best for Learning

ThingSpeak is the fastest way to visualize IoT data. It requires almost no setup.

python
# MicroPython ESP32 → ThingSpeak (HTTP GET)
import urequests
import dht
from machine import Pin
import network, utime

# Your ThingSpeak credentials
WRITE_API_KEY = "YOUR_WRITE_API_KEY_HERE"
BASE_URL      = "https://api.thingspeak.com/update"

def connect_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(ssid, password)
    while not wlan.isconnected():
        utime.sleep_ms(500)
    print(f"WiFi: {wlan.ifconfig()[0]}")

def send_to_thingspeak(field1=None, field2=None, field3=None):
    """Send up to 3 fields to ThingSpeak channel."""
    params = f"api_key={WRITE_API_KEY}"
    if field1 is not None: params += f"&field1={field1}"
    if field2 is not None: params += f"&field2={field2}"
    if field3 is not None: params += f"&field3={field3}"

    url = f"{BASE_URL}?{params}"
    try:
        r = urequests.get(url, timeout=10)
        entry_id = r.text.strip()
        r.close()
        if entry_id == "0":
            print("ThingSpeak rejected data (rate limit?)")
            return False
        print(f"Sent to ThingSpeak, entry #{entry_id}")
        return True
    except Exception as e:
        print(f"ThingSpeak error: {e}")
        return False

connect_wifi("MySSID", "MyPassword")
sensor = dht.DHT22(Pin(4))

while True:
    sensor.measure()
    temp = sensor.temperature()
    hum  = sensor.humidity()

    send_to_thingspeak(field1=temp, field2=hum)
    utime.sleep(16)  # ThingSpeak free tier: min 15s between updates

AWS IoT Core — Enterprise Grade

AWS IoT Core is the most mature and scalable IoT platform. Every device gets a unique X.509 certificate.

Architecture

ESP32 Device
    │
    │ MQTT over TLS (port 8883)
    │ Client cert + private key
    ▼
AWS IoT Core (Broker)
    │
    ├── IoT Rules Engine
    │       │
    │       ├── DynamoDB (per-device last reading)
    │       ├── S3 (raw data archive)
    │       ├── Lambda (alerting, processing)
    │       └── SNS (email/SMS alerts)
    │
    └── AWS Timestream / InfluxDB (time-series storage)

Device Setup (Python)

python
# Raspberry Pi / PC → AWS IoT Core with paho-mqtt
import paho.mqtt.client as mqtt
import ssl
import json
import time

# Download these from AWS IoT Core → Manage → Things → Your Thing → Certificates
ENDPOINT    = "abc123-ats.iot.us-east-1.amazonaws.com"
CLIENT_CERT = "/path/to/device.cert.pem"
PRIVATE_KEY = "/path/to/device.private.key"
CA_CERT     = "/path/to/AmazonRootCA1.pem"  # Download from AWS
THING_NAME  = "temperature-sensor-01"

# AWS IoT MQTT topic convention
TELEMETRY_TOPIC = f"devices/{THING_NAME}/telemetry"
SHADOW_UPDATE   = f"$aws/things/{THING_NAME}/shadow/update"
SHADOW_GET      = f"$aws/things/{THING_NAME}/shadow/get"

def on_connect(client, userdata, flags, rc):
    print(f"AWS IoT: Connected (rc={rc})")
    # Subscribe to device shadow (cloud-to-device configuration)
    client.subscribe(f"$aws/things/{THING_NAME}/shadow/update/delta", qos=1)

def on_message(client, userdata, msg):
    """Handle shadow delta (desired state from cloud)."""
    topic = msg.topic
    data  = json.loads(msg.payload)
    print(f"Received shadow delta: {data}")

    # Apply desired state
    if "state" in data and "interval" in data["state"]:
        new_interval = data["state"]["interval"]
        print(f"Changing reporting interval to {new_interval}s")

def send_telemetry(client, temperature, humidity, battery_pct):
    payload = json.dumps({
        "device_id":   THING_NAME,
        "temperature": round(temperature, 2),
        "humidity":    round(humidity, 2),
        "battery":     battery_pct,
        "timestamp":   int(time.time())
    })
    client.publish(TELEMETRY_TOPIC, payload, qos=1)
    print(f"Published: {payload}")

client = mqtt.Client(client_id=THING_NAME)
client.tls_set(
    ca_certs   = CA_CERT,
    certfile   = CLIENT_CERT,
    keyfile    = PRIVATE_KEY,
    tls_version = ssl.PROTOCOL_TLS_CLIENT
)
client.on_connect = on_connect
client.on_message = on_message

client.connect(ENDPOINT, 8883, 60)
client.loop_start()

time.sleep(1)
for i in range(12):
    send_telemetry(client, 24.5 + i * 0.1, 61.0, 87)
    time.sleep(5)

client.loop_stop()
client.disconnect()

Device Shadow (AWS) / Device Twin (Azure)

Device shadows maintain a virtual copy of device state in the cloud — useful for configuration and offline devices.

python
# Update device shadow with current and desired state
def update_shadow(client, thing_name, reported_state, desired_state=None):
    """
    reported: what the device's actual current state is
    desired:  what the cloud wants the device to do
    """
    shadow = {"state": {"reported": reported_state}}
    if desired_state:
        shadow["state"]["desired"] = desired_state

    topic = f"$aws/things/{thing_name}/shadow/update"
    client.publish(topic, json.dumps(shadow), qos=1)

# Report current firmware version and interval
update_shadow(client, THING_NAME,
    reported_state={
        "firmware": "1.4.2",
        "interval": 30,
        "status":   "online"
    }
)

# Request shadow state on reconnect
client.publish(f"$aws/things/{THING_NAME}/shadow/get", "", qos=1)

Adafruit IO — Quick Maker Dashboards

python
# Python → Adafruit IO (MQTT)
import paho.mqtt.client as mqtt
import json
import time

AIO_USERNAME = "your_adafruit_username"
AIO_KEY      = "your_adafruit_io_key"

# Adafruit IO topics: {username}/feeds/{feed-name}
TEMP_FEED = f"{AIO_USERNAME}/feeds/temperature"
HUM_FEED  = f"{AIO_USERNAME}/feeds/humidity"

client = mqtt.Client()
client.username_pw_set(AIO_USERNAME, AIO_KEY)

def on_connect(c, ud, flags, rc):
    print("Adafruit IO connected")
    # Subscribe to receive dashboard input (sliders, buttons)
    client.subscribe(f"{AIO_USERNAME}/feeds/led-control", qos=1)

def on_message(c, ud, msg):
    value = msg.payload.decode()
    print(f"Dashboard input [{msg.topic}]: {value}")
    # value might be "ON", "OFF", or "50" (slider value)

client.on_connect = on_connect
client.on_message = on_message
client.connect("io.adafruit.com", 1883, 60)
client.loop_start()

for _ in range(10):
    # Publish to individual feeds
    client.publish(TEMP_FEED, "24.5", qos=1)
    client.publish(HUM_FEED,  "61.2", qos=1)
    time.sleep(2)  # Adafruit IO free: max 30 msgs/min

client.loop_stop()

Serverless Processing with AWS Lambda

python
# AWS Lambda function — triggered by IoT Rule when T > 35°C
import boto3
import json
import os

sns = boto3.client('sns')
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['READINGS_TABLE'])

def lambda_handler(event, context):
    """
    event contains the IoT message payload:
    {
        "device_id": "sensor-01",
        "temperature": 37.2,
        "humidity": 45.0,
        "timestamp": 1704067200
    }
    """
    device_id   = event['device_id']
    temperature = event['temperature']
    timestamp   = event['timestamp']

    # Store in DynamoDB
    table.put_item(Item={
        'device_id': device_id,
        'timestamp': timestamp,
        'temperature': str(temperature),
        'humidity': str(event.get('humidity', 0))
    })

    # Send alert if temperature is too high
    if temperature > 35:
        message = (
            f"🌡️ HIGH TEMPERATURE ALERT\n"
            f"Device: {device_id}\n"
            f"Temperature: {temperature}°C\n"
            f"Threshold: 35°C"
        )
        sns.publish(
            TopicArn=os.environ['ALERT_TOPIC_ARN'],
            Subject="IoT Temperature Alert",
            Message=message
        )
        print(f"Alert sent for {device_id}: {temperature}°C")

    return {"statusCode": 200}

Summary

  • ThingSpeak is the fastest to get started — no certificate setup, free dashboard
  • AWS IoT Core is the most scalable — X.509 certificates, rules engine, Lambda integration
  • Azure IoT Hub integrates naturally with the Microsoft ecosystem (Azure Functions, Power BI)
  • Device shadows maintain cloud-side state even when devices are offline — essential for configuration management
  • Serverless (Lambda/Azure Functions) processes IoT events without managing servers
  • Start with ThingSpeak or Adafruit IO for prototypes; migrate to AWS or Azure for production

Next, we'll look at time-series data collection and storage — the foundation for IoT analytics.