ESP32 to GCP Integration Guide

This skill provides ESP32 to Google Cloud integration patterns and troubleshooting. Use when working with ESP32 sensors connecting to Cloud Run, debugging WiFi or HTTPS issues, or implementing sensor

SKILL.md

GitHub

ESP32 to GCP Integration Guide

Architecture Overview

ESP32 Sensor ──WiFi──► Internet ──HTTPS──► Cloud Run API
     │                                          │
     └── Readings ──JSON POST──────────────────►│
     ◄── 200 OK ────────────────────────────────┘

Complete ESP32 Sketch Template

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// Configuration
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-password";
const char* API_ENDPOINT = "https://your-service.run.app/api/telemetry";
const char* DEVICE_ID = "esp32-001";
const char* API_KEY = "your-api-key";

// Timing
const unsigned long SEND_INTERVAL = 30000;  // 30 seconds
unsigned long lastSend = 0;

WiFiClientSecure client;

void setup() {
  Serial.begin(115200);
  delay(1000);

  connectWiFi();

  // For testing only - use proper CA cert in production
  client.setInsecure();
}

void loop() {
  // Reconnect WiFi if needed
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
  }

  // Send telemetry at interval
  if (millis() - lastSend >= SEND_INTERVAL) {
    sendTelemetry();
    lastSend = millis();
  }

  delay(100);
}

void connectWiFi() {
  Serial.print("Connecting to WiFi");
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 30) {
    delay(500);
    Serial.print(".");
    attempts++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println(" Connected!");
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println(" Failed!");
    delay(5000);
    ESP.restart();
  }
}

void sendTelemetry() {
  if (WiFi.status() != WL_CONNECTED) return;

  HTTPClient http;
  http.begin(client, API_ENDPOINT);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("X-API-Key", API_KEY);
  http.addHeader("X-Device-ID", DEVICE_ID);

  // Build JSON payload
  StaticJsonDocument<256> doc;
  doc["deviceId"] = DEVICE_ID;
  doc["timestamp"] = millis();

  JsonObject readings = doc.createNestedObject("readings");
  readings["temperature"] = readTemperature();
  readings["humidity"] = readHumidity();

  JsonObject meta = doc.createNestedObject("meta");
  meta["rssi"] = WiFi.RSSI();
  meta["uptime"] = millis();

  String payload;
  serializeJson(doc, payload);

  Serial.print("Sending: ");
  Serial.println(payload);

  int httpCode = http.POST(payload);

  if (httpCode > 0) {
    Serial.printf("Response: %d\n", httpCode);
    if (httpCode == 200) {
      Serial.println(http.getString());
    }
  } else {
    Serial.printf("Error: %s\n", http.errorToString(httpCode).c_str());
  }

  http.end();
}

float readTemperature() {
  // Replace with actual sensor reading
  return 25.0 + random(-50, 50) / 100.0;
}

float readHumidity() {
  // Replace with actual sensor reading
  return 60.0 + random(-100, 100) / 100.0;
}

Common Issues & Solutions

1. WiFi Won't Connect

Debug Code:

void debugWiFi() {
  Serial.printf("WiFi Status: %d\n", WiFi.status());
  // 0 = WL_IDLE_STATUS
  // 1 = WL_NO_SSID_AVAIL - SSID not found
  // 4 = WL_CONNECT_FAILED - Wrong password
  // 6 = WL_DISCONNECTED
  // 3 = WL_CONNECTED - Success

  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("IP: %s\n", WiFi.localIP().toString().c_str());
    Serial.printf("RSSI: %d dBm\n", WiFi.RSSI());
  }
}

Fixes:

  • Verify SSID/password are correct
  • ESP32 only supports 2.4GHz (not 5GHz)
  • Check router isn't blocking by MAC address
  • Try static IP if DHCP fails:
IPAddress local_IP(192, 168, 1, 100);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
WiFi.config(local_IP, gateway, subnet);

2. HTTPS Connection Fails

Symptoms: Connection refused, SSL errors

With Root CA (Production):

// Google Trust Services root CA for *.run.app
const char* rootCA = R"(
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
... (full cert) ...
-----END CERTIFICATE-----
)";

client.setCACert(rootCA);

Testing Without Cert (Development only):

client.setInsecure();  // Skip certificate verification

3. HTTP Error Codes

int httpCode = http.POST(payload);

switch(httpCode) {
  case 200: case 201:
    Serial.println("Success!");
    break;
  case 400:
    Serial.println("Bad Request - check JSON format");
    break;
  case 401:
    Serial.println("Unauthorized - check API key");
    break;
  case 403:
    Serial.println("Forbidden - check permissions");
    break;
  case 404:
    Serial.println("Not Found - check endpoint URL");
    break;
  case 500:
    Serial.println("Server Error - check Cloud Run logs");
    break;
  case -1:
    Serial.println("Connection failed");
    break;
  case -11:
    Serial.println("Connection timed out");
    break;
  default:
    Serial.printf("HTTP Error: %d\n", httpCode);
}

4. Memory Issues

Debug Memory:

void checkMemory() {
  Serial.printf("Free heap: %d\n", ESP.getFreeHeap());
  Serial.printf("Min free heap: %d\n", ESP.getMinFreeHeap());
  Serial.printf("Max alloc: %d\n", ESP.getMaxAllocHeap());
}

Prevent Memory Leaks:

// ALWAYS end HTTP connection
http.end();

// ALWAYS close client
client.stop();

// Use char arrays instead of String for less fragmentation
char buffer[256];
serializeJson(doc, buffer, sizeof(buffer));

5. DNS Resolution Fails

IPAddress ip;
if (WiFi.hostByName("your-service.run.app", ip)) {
  Serial.print("Resolved: ");
  Serial.println(ip);
} else {
  Serial.println("DNS failed!");

  // Try custom DNS
  WiFi.disconnect();
  IPAddress dns1(8, 8, 8, 8);  // Google DNS
  IPAddress dns2(8, 8, 4, 4);
  WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, dns1, dns2);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
}

Reliability Patterns

Watchdog Timer

#include <esp_task_wdt.h>

void setup() {
  // 30 second watchdog
  esp_task_wdt_init(30, true);
  esp_task_wdt_add(NULL);
}

void loop() {
  esp_task_wdt_reset();  // Feed the watchdog
  // ... your code ...
}

Exponential Backoff

int retryDelay = 1000;  // Start at 1 second
const int maxDelay = 60000;  // Max 1 minute

void sendWithRetry() {
  int httpCode = http.POST(payload);

  if (httpCode == 200) {
    retryDelay = 1000;  // Reset on success
  } else {
    Serial.printf("Failed, retrying in %dms\n", retryDelay);
    delay(retryDelay);
    retryDelay = min(retryDelay * 2, maxDelay);  // Double delay
    sendWithRetry();  // Retry
  }
}

Deep Sleep for Battery

const int SLEEP_SECONDS = 300;  // 5 minutes

void loop() {
  sendTelemetry();

  Serial.println("Going to sleep...");
  esp_deep_sleep(SLEEP_SECONDS * 1000000ULL);
  // Never reaches here - wakes up in setup()
}

Telemetry Payload Schema

Minimal Payload

{
  "deviceId": "esp32-001",
  "temperature": 25.5
}

Full Payload

{
  "deviceId": "esp32-001",
  "timestamp": 1705337600000,
  "type": "telemetry",
  "readings": {
    "temperature": 25.5,
    "humidity": 60.2,
    "pressure": 1013.25,
    "light": 450
  },
  "meta": {
    "firmware": "1.2.0",
    "rssi": -65,
    "battery": 85,
    "uptime": 3600000,
    "heap": 180000
  }
}

Cloud Run Backend Handler

const express = require('express');
const app = express();
app.use(express.json());

// Validate device
const validateDevice = (req, res, next) => {
  const apiKey = req.headers['x-api-key'];
  const deviceId = req.headers['x-device-id'];

  if (!apiKey || !deviceId) {
    return res.status(401).json({ error: 'Missing credentials' });
  }

  // Validate against your device registry
  // ...

  req.deviceId = deviceId;
  next();
};

app.post('/api/telemetry', validateDevice, async (req, res) => {
  const telemetry = {
    ...req.body,
    deviceId: req.deviceId,
    receivedAt: Date.now()
  };

  console.log('Telemetry received:', telemetry);

  // Publish to Pub/Sub
  await pubsub.topic('telemetry').publish(
    Buffer.from(JSON.stringify(telemetry))
  );

  res.status(200).json({
    status: 'received',
    messageId: '...'
  });
});

app.get('/health', (req, res) => {
  res.status(200).json({ status: 'healthy' });
});

const PORT = process.env.PORT || 8080;
app.listen(PORT);

Diagnostic Sketch

void runDiagnostic() {
  Serial.println("=== ESP32 Diagnostic ===\n");

  // Chip info
  Serial.printf("Chip: %s Rev %d\n", ESP.getChipModel(), ESP.getChipRevision());
  Serial.printf("Cores: %d\n", ESP.getChipCores());
  Serial.printf("Flash: %d MB\n", ESP.getFlashChipSize() / 1024 / 1024);

  // Memory
  Serial.printf("Free Heap: %d bytes\n", ESP.getFreeHeap());

  // WiFi
  Serial.printf("WiFi: %s\n", WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected");
  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("  SSID: %s\n", WiFi.SSID().c_str());
    Serial.printf("  IP: %s\n", WiFi.localIP().toString().c_str());
    Serial.printf("  RSSI: %d dBm\n", WiFi.RSSI());
  }

  // DNS
  IPAddress ip;
  Serial.printf("DNS: %s\n",
    WiFi.hostByName("google.com", ip) ? "OK" : "FAILED");

  // HTTPS
  Serial.print("HTTPS: ");
  WiFiClientSecure testClient;
  testClient.setInsecure();
  if (testClient.connect("www.google.com", 443)) {
    Serial.println("OK");
    testClient.stop();
  } else {
    Serial.println("FAILED");
  }

  Serial.println("\n=== End Diagnostic ===");
}