From gcp-iot
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 firmware. Triggers when user mentions "ESP32 not connecting", "sensor to cloud", "ESP32 HTTPS", "WiFi issues", "firmware debugging", or needs help with ESP32-to-GCP communication.
How this skill is triggered — by the user, by Claude, or both
Slash command
/gcp-iot:esp32-gcp-integrationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
```
ESP32 Sensor ──WiFi──► Internet ──HTTPS──► Cloud Run API
│ │
└── Readings ──JSON POST──────────────────►│
◄── 200 OK ────────────────────────────────┘
#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;
}
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:
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);
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
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);
}
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));
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);
}
#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 ...
}
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
}
}
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()
}
{
"deviceId": "esp32-001",
"temperature": 25.5
}
{
"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
}
}
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);
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 ===");
}
npx claudepluginhub maxcogar/agent-armory --plugin gcp-iotProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.