MQTT on GCP IoT Core: Connect Devices to Google Cloud

The most surprising thing about GCP IoT Core is that it’s not really about connecting devices to Google Cloud; it’s about connecting devices to your application, with Google Cloud providing the secure, scalable plumbing.

Let’s see it in action. Imagine a temperature sensor in a remote weather station. It wakes up, reads the ambient temperature, and publishes it to an MQTT topic.

# On the device (conceptual)
client.publish("devices/weather-station-01/telemetry", "{\"temperature\": 22.5}")

This message, via GCP IoT Core, can then be routed to Pub/Sub for processing, BigQuery for storage, or even Cloud Functions for immediate alerts.

The Core Problem: Secure, Scalable Device Communication

Before IoT Core, getting thousands or millions of devices to talk to a cloud backend securely and reliably was a massive undertaking. You’d need to manage certificates, handle network fluctuations, scale your message brokers, and secure every ingress point. IoT Core abstracts all of that. It provides a managed MQTT broker that handles device authentication, authorization, and message routing.

How It Works: The IoT Core Architecture

At its heart, IoT Core is a managed MQTT broker. Devices connect to a specific endpoint provided by Google Cloud, authenticate using either X.509 certificates or JWTs, and then publish or subscribe to MQTT topics.

  1. Device Registry: This is where you register your devices. Each device has a unique ID and can be associated with metadata. You define authentication credentials (certificates or JWT issuers) here.
  2. Device Authentication: When a device connects, IoT Core verifies its identity against the registry. For certificate-based auth, it checks the certificate against trusted root CAs you’ve configured. For JWTs, it validates the signature and expiration against a configured issuer.
  3. MQTT Broker: This is the central hub. Devices connect to mqtt.googleapis.com on port 8883 (TLS) or 443 (HTTP bridge). They can publish messages to specific topics and subscribe to others.
  4. Pub/Sub Integration: This is the magic glue. You configure "Telemetry" and "State" configurations within IoT Core.
    • Telemetry: Messages published by devices are automatically forwarded to a designated Pub/Sub topic. This is where your backend applications typically consume device data.
    • State: IoT Core can also push desired state changes (e.g., "set fan to low") to devices. These messages are sent via Pub/Sub to a specific "command" topic, and IoT Core delivers them to the subscribed device.

Connecting Your Device: The Practical Steps

Let’s say you’re using an ESP32 with the Paho MQTT client.

  1. Create a Google Cloud Project and Enable IoT Core API.

  2. Create a Device Registry:

    • Go to the IoT Core console.
    • Click "Create Registry."
    • Choose a Region (e.g., us-central1).
    • Select "MQTT" as the communication protocol.
    • Enable Pub/Sub for telemetry. Choose or create a Pub/Sub topic (e.g., projects/your-gcp-project/topics/device-telemetry).
    • Under "Security," choose "Create certificate authority" or "Use existing." For testing, "Create certificate authority" is easiest. You’ll download a public key certificate.
  3. Register a Device:

    • In your registry, click "Add Device."
    • Enter a unique Device ID (e.g., esp32-sensor-001).
    • Under "Authentication," select "Public key certificates." Upload the public key certificate you downloaded earlier.
    • Click "Create."
  4. Configure Your Device Code:

    • You’ll need the Google Cloud IoT Core MQTT endpoint: mqtt.googleapis.com.
    • The client ID is crucial: projects/YOUR_PROJECT_ID/locations/YOUR_REGION/registries/YOUR_REGISTRY_ID/devices/YOUR_DEVICE_ID.
    • You’ll need to connect using TLS. The certificates used for authentication are not the ones you use to verify the server’s identity; you’ll need Google’s root CA bundle (often roots.pem from the Mosquitto client distribution or similar).
    • Example using Paho MQTT (Python):
    import paho.mqtt.client as mqtt
    import ssl
    import json
    import time
    
    # --- Configuration ---
    PROJECT_ID = "your-gcp-project"
    REGION = "us-central1"
    REGISTRY_ID = "your-registry-id"
    DEVICE_ID = "esp32-sensor-001"
    PRIVATE_KEY_FILE = "rsa_private.pem" # Your device's private key
    CERTIFICATE_FILE = "rsa_public.pem"  # Your device's public key certificate
    ROOT_CA_FILE = "roots.pem"          # Google's root CA bundle
    
    MQTT_BROKER = "mqtt.googleapis.com"
    MQTT_PORT = 8883
    
    # --- Generate JWT (for token-based auth, often preferred over certs for embedded) ---
    # For simplicity, using certs here, but JWT is more common for production.
    # If using JWT, you'd generate a token with expiry and sign it with your private key.
    
    # --- MQTT Callbacks ---
    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            print("Connected successfully to MQTT Broker!")
            client.subscribe("devices/" + DEVICE_ID + "/config") # Example config topic
        else:
            print("Failed to connect, return code %d\n", rc)
    
    def on_disconnect(client, userdata, rc):
        print("Disconnected with result code: " + str(rc))
    
    def on_message(client, userdata, msg):
        print("Received message on topic: %s with Payload: %s" % (msg.topic, msg.payload))
        # Process configuration updates here
    
    # --- Setup Client ---
    client_id = f"projects/{PROJECT_ID}/locations/{REGION}/registries/{REGISTRY_ID}/devices/{DEVICE_ID}"
    
    client = mqtt.Client(client_id=client_id)
    
    # Set TLS/SSL context
    client.tls_set(
        ca_certs=ROOT_CA_FILE,
        certfile=CERTIFICATE_FILE,
        keyfile=PRIVATE_KEY_FILE,
        tls_version=ssl.PROTOCOL_TLSv1_2
    )
    client.tls_insecure_set(False) # Important for security
    
    # Assign callbacks
    client.on_connect = on_connect
    client.on_disconnect = on_disconnect
    client.on_message = on_message
    
    # Connect
    try:
        client.connect(MQTT_BROKER, MQTT_PORT, 60)
    except Exception as e:
        print(f"Error connecting: {e}")
        exit()
    
    # --- Publish Data ---
    def publish_telemetry(temperature):
        payload = json.dumps({
            "temperature": temperature,
            "timestamp": int(time.time())
        })
        topic = f"devices/{DEVICE_ID}/telemetry"
        print(f"Publishing to {topic}: {payload}")
        client.publish(topic, payload)
    
    # Loop indefinitely, publishing data every 30 seconds
    client.loop_start() # Start a background thread for network traffic
    try:
        while True:
            temp = 20 + (time.time() % 10) # Simulate temperature fluctuation
            publish_telemetry(round(temp, 1))
            time.sleep(30)
    except KeyboardInterrupt:
        print("Exiting...")
        client.loop_stop()
        client.disconnect()
    
    
    • You’ll need to generate your RSA private and public key pair (openssl req -x509 -newkey rsa:2048 -keyout rsa_private.pem -out rsa_public.pem -days 365 -nodes) and upload the rsa_public.pem to GCP IoT Core.
    • Download the Google root CA bundle (e.g., from pki.goog/roots.pem).

The Levers You Control

  • Device Registry Configuration: How devices authenticate, which Pub/Sub topics telemetry and state messages go to, and custom device metadata.
  • MQTT Topics: You define the structure of your topics (e.g., devices/{deviceId}/telemetry, devices/{deviceId}/events, devices/{deviceId}/commands). This is your application’s contract with the devices.
  • Pub/Sub Subscription: Your backend applications subscribe to the Pub/Sub topics you configured in IoT Core to receive device data.
  • Cloud Functions/Cloud Run/Dataflow: These services can process the Pub/Sub messages, trigger actions, store data, or perform analytics.

The most powerful aspect is the flexibility. You can route data from a single device to multiple Pub/Sub topics, allowing different backend services to consume the same data stream independently. Or, you can use a single Pub/Sub topic for telemetry from many devices and then filter/route based on the Pub/Sub message attributes (which IoT Core can be configured to populate with device ID, registry ID, etc.).

The one thing most people don’t immediately grasp is how tightly coupled the MQTT topic structure is to the Pub/Sub integration. When you configure IoT Core to send telemetry to a Pub/Sub topic, it doesn’t just dump the raw MQTT payload. It creates a Pub/Sub message where the data field contains the MQTT payload, and the attributes field contains metadata like deviceId, registryId, and eventId. This attribute-based metadata is key for routing and processing downstream.

The next logical step is to explore device state management and how to send commands to your devices.

Want structured learning?

Take the full Mqtt course →