snippets = [
// ===== SENSOR READING =====
{
id: "sensor-dht22",
title: "DHT22 Temperature & Humidity Reading",
category: "Sensors",
platform: "Arduino/ESP32",
difficulty: "Beginner",
tags: ["temperature", "humidity", "dht22", "sensor"],
description: "Read temperature and humidity from DHT22 sensor with error handling",
code: `#include <DHT.h>
#define DHT_PIN 4
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);
void setup() {
Serial.begin(115200);
dht.begin();
}
void loop() {
float humidity = dht.readHumidity();
float tempC = dht.readTemperature();
float tempF = dht.readTemperature(true);
// Check for read errors
if (isnan(humidity) || isnan(tempC)) {
Serial.println("DHT read failed!");
delay(2000);
return;
}
// Calculate heat index
float heatIndex = dht.computeHeatIndex(tempC, humidity, false);
Serial.printf("Temp: %.1f°C (%.1f°F)\\n", tempC, tempF);
Serial.printf("Humidity: %.1f%%\\n", humidity);
Serial.printf("Heat Index: %.1f°C\\n", heatIndex);
delay(2000); // DHT22 needs 2s between reads
}`,
explanation: "DHT22 requires a 2-second minimum delay between readings. Always check for NaN values as the sensor can fail reads. The heat index calculation combines temperature and humidity for 'feels like' temperature."
},
{
id: "sensor-analog",
title: "Analog Sensor with Averaging",
category: "Sensors",
platform: "Arduino/ESP32",
difficulty: "Beginner",
tags: ["analog", "adc", "averaging", "noise"],
description: "Read analog sensor with multi-sample averaging to reduce noise",
code: `const int SENSOR_PIN = 34; // ADC1 pin on ESP32
const int NUM_SAMPLES = 10;
const float V_REF = 3.3;
const int ADC_MAX = 4095; // 12-bit ADC
float readAnalogAveraged(int pin, int samples) {
long sum = 0;
for (int i = 0; i < samples; i++) {
sum += analogRead(pin);
delay(1); // Small delay between samples
}
return (float)sum / samples;
}
float adcToVoltage(float adcValue) {
return (adcValue / ADC_MAX) * V_REF;
}
void setup() {
Serial.begin(115200);
analogReadResolution(12); // ESP32: 9-12 bits
analogSetAttenuation(ADC_11db); // 0-3.3V range
}
void loop() {
float avgReading = readAnalogAveraged(SENSOR_PIN, NUM_SAMPLES);
float voltage = adcToVoltage(avgReading);
Serial.printf("ADC: %.1f, Voltage: %.3fV\\n", avgReading, voltage);
delay(100);
}`,
explanation: "Multi-sample averaging reduces ADC noise significantly. For ESP32, use ADC1 pins (GPIO 32-39) when using Wi-Fi. Attenuation setting determines voltage range: 0dB (0-1.1V), 2.5dB (0-1.5V), 6dB (0-2.2V), 11dB (0-3.3V)."
},
{
id: "sensor-i2c-scan",
title: "I2C Device Scanner",
category: "Sensors",
platform: "Arduino/ESP32",
difficulty: "Beginner",
tags: ["i2c", "scanner", "debug", "address"],
description: "Scan I2C bus to find connected device addresses",
code: `#include <Wire.h>
void setup() {
Serial.begin(115200);
Wire.begin(); // Default pins: SDA=21, SCL=22 on ESP32
Serial.println("I2C Scanner Starting...");
scanI2C();
}
void scanI2C() {
byte deviceCount = 0;
Serial.println("Scanning...");
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
byte error = Wire.endTransmission();
if (error == 0) {
Serial.printf("Device found at 0x%02X", addr);
// Common device identification
switch(addr) {
case 0x3C: case 0x3D: Serial.print(" (OLED Display)"); break;
case 0x40: Serial.print(" (HDC1080/INA219)"); break;
case 0x48: Serial.print(" (ADS1115/TMP102)"); break;
case 0x50: Serial.print(" (EEPROM)"); break;
case 0x68: Serial.print(" (MPU6050/DS3231)"); break;
case 0x76: case 0x77: Serial.print(" (BME280/BMP280)"); break;
}
Serial.println();
deviceCount++;
}
}
Serial.printf("\\nFound %d device(s)\\n", deviceCount);
}
void loop() {
delay(5000);
scanI2C(); // Rescan periodically
}`,
explanation: "I2C addresses are 7-bit (1-127). Common ranges: 0x20-0x27 (PCF8574), 0x48-0x4F (ADC), 0x50-0x57 (EEPROM), 0x68-0x6F (RTC/IMU), 0x76-0x77 (pressure sensors)."
},
// ===== MQTT =====
{
id: "mqtt-publish",
title: "MQTT Publish with Reconnection",
category: "MQTT",
platform: "ESP32",
difficulty: "Intermediate",
tags: ["mqtt", "publish", "wifi", "reconnect"],
description: "Robust MQTT publishing with automatic Wi-Fi and broker reconnection",
code: `#include <Wi-Fi.h>
#include <PubSubClient.h>
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-password";
const char* MQTT_SERVER = "broker.hivemq.com";
const int MQTT_PORT = 1883;
const char* MQTT_CLIENT_ID = "esp32-sensor-01";
const char* MQTT_TOPIC = "iot/sensors/temperature";
Wi-FiClient wifiClient;
PubSubClient mqtt(wifiClient);
unsigned long lastPublish = 0;
const long PUBLISH_INTERVAL = 5000;
void connectWi-Fi() {
if (Wi-Fi.status() == WL_CONNECTED) return;
Serial.printf("Connecting to %s", WIFI_SSID);
Wi-Fi.begin(WIFI_SSID, WIFI_PASS);
int attempts = 0;
while (Wi-Fi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (Wi-Fi.status() == WL_CONNECTED) {
Serial.printf("\\nConnected! IP: %s\\n", Wi-Fi.localIP().toString().c_str());
} else {
Serial.println("\\nWi-Fi connection failed!");
}
}
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("Connecting to MQTT...");
if (mqtt.connect(MQTT_CLIENT_ID)) {
Serial.println("connected!");
} else {
Serial.printf("failed (rc=%d), retry in 5s\\n", mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
connectWi-Fi();
connectMQTT();
}
void loop() {
// Maintain connections
if (Wi-Fi.status() != WL_CONNECTED) connectWi-Fi();
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
// Publish periodically
if (millis() - lastPublish >= PUBLISH_INTERVAL) {
float temperature = 25.5; // Replace with sensor reading
char payload[50];
snprintf(payload, sizeof(payload), "{\\"temp\\":%.1f}", temperature);
if (mqtt.publish(MQTT_TOPIC, payload)) {
Serial.printf("Published: %s\\n", payload);
} else {
Serial.println("Publish failed!");
}
lastPublish = millis();
}
}`,
explanation: "PubSubClient requires mqtt.loop() in every iteration. Always check both Wi-Fi and MQTT connections. Use snprintf for safe string formatting. Client ID must be unique per broker connection."
},
{
id: "mqtt-subscribe",
title: "MQTT Subscribe with Callback",
category: "MQTT",
platform: "ESP32",
difficulty: "Intermediate",
tags: ["mqtt", "subscribe", "callback", "json"],
description: "Subscribe to MQTT topics and handle incoming messages with JSON parsing",
code: `#include <Wi-Fi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-password";
const char* MQTT_SERVER = "broker.hivemq.com";
const char* MQTT_TOPIC_CMD = "iot/device/+/command"; // + is wildcard
const char* MQTT_TOPIC_STATUS = "iot/device/esp32-01/status";
Wi-FiClient wifiClient;
PubSubClient mqtt(wifiClient);
// Callback for incoming messages
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.printf("Message on [%s]: ", topic);
// Convert payload to string
char message[length + 1];
memcpy(message, payload, length);
message[length] = '\\0';
Serial.println(message);
// Parse JSON
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, message);
if (error) {
Serial.printf("JSON parse failed: %s\\n", error.c_str());
return;
}
// Handle commands
const char* command = doc["cmd"];
if (strcmp(command, "led_on") == 0) {
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("LED turned ON");
} else if (strcmp(command, "led_off") == 0) {
digitalWrite(LED_BUILTIN, LOW);
Serial.println("LED turned OFF");
} else if (strcmp(command, "status") == 0) {
publishStatus();
}
}
void publishStatus() {
StaticJsonDocument<200> doc;
doc["device"] = "esp32-01";
doc["uptime"] = millis() / 1000;
doc["rssi"] = Wi-Fi.RSSI();
doc["heap"] = ESP.getFreeHeap();
char buffer[200];
serializeJson(doc, buffer);
mqtt.publish(MQTT_TOPIC_STATUS, buffer);
}
void connectMQTT() {
while (!mqtt.connected()) {
if (mqtt.connect("esp32-01")) {
mqtt.subscribe(MQTT_TOPIC_CMD);
Serial.println("Subscribed to command topic");
} else {
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
Wi-Fi.begin(WIFI_SSID, WIFI_PASS);
while (Wi-Fi.status() != WL_CONNECTED) delay(500);
mqtt.setServer(MQTT_SERVER, 1883);
mqtt.setCallback(mqttCallback);
connectMQTT();
}
void loop() {
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
}`,
explanation: "MQTT wildcards: + matches one level, # matches all remaining levels. Always set callback before connecting. ArduinoJson StaticJsonDocument is stack-allocated (faster, fixed size). Use DynamicJsonDocument for variable-size messages."
},
// ===== HTTP/REST =====
{
id: "http-post-json",
title: "HTTP POST with JSON",
category: "HTTP",
platform: "ESP32",
difficulty: "Intermediate",
tags: ["http", "post", "json", "rest", "api"],
description: "Send sensor data to REST API using HTTP POST with JSON body",
code: `#include <Wi-Fi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-password";
const char* API_ENDPOINT = "https://api.example.com/sensor-data";
const char* API_KEY = "your-api-key";
void sendSensorData(float temperature, float humidity) {
if (Wi-Fi.status() != WL_CONNECTED) {
Serial.println("Wi-Fi not connected!");
return;
}
HTTPClient http;
http.begin(API_ENDPOINT);
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", String("Bearer ") + API_KEY);
http.setTimeout(10000); // 10 second timeout
// Build JSON payload
StaticJsonDocument<200> doc;
doc["device_id"] = "esp32-sensor-01";
doc["temperature"] = temperature;
doc["humidity"] = humidity;
doc["timestamp"] = millis();
String payload;
serializeJson(doc, payload);
Serial.printf("Sending: %s\\n", payload.c_str());
int httpCode = http.POST(payload);
if (httpCode > 0) {
Serial.printf("HTTP Response: %d\\n", httpCode);
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_CREATED) {
String response = http.getString();
Serial.printf("Response: %s\\n", response.c_str());
}
} else {
Serial.printf("HTTP Error: %s\\n", http.errorToString(httpCode).c_str());
}
http.end();
}
void setup() {
Serial.begin(115200);
Wi-Fi.begin(WIFI_SSID, WIFI_PASS);
while (Wi-Fi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\\nWi-Fi connected!");
}
void loop() {
float temp = 23.5 + random(-10, 10) / 10.0;
float hum = 55.0 + random(-50, 50) / 10.0;
sendSensorData(temp, hum);
delay(30000); // Send every 30 seconds
}`,
explanation: "Always call http.end() to free resources. HTTP response codes: 200=OK, 201=Created, 400=Bad Request, 401=Unauthorized, 500=Server Error. Use https:// for secure connections (ESP32 supports TLS)."
},
// ===== DEEP SLEEP =====
{
id: "deep-sleep-timer",
title: "Deep Sleep with Timer Wakeup",
category: "Power",
platform: "ESP32",
difficulty: "Intermediate",
tags: ["sleep", "power", "battery", "timer", "wakeup"],
description: "Ultra-low power deep sleep with timed wakeup for battery-powered sensors",
code: `#include <Wi-Fi.h>
#include <esp_sleep.h>
#define uS_TO_S_FACTOR 1000000ULL // Microseconds to seconds
#define SLEEP_DURATION_SEC 300 // 5 minutes
RTC_DATA_ATTR int bootCount = 0; // Survives deep sleep
void printWakeupReason() {
esp_sleep_wakeup_cause_t reason = esp_sleep_get_wakeup_cause();
switch(reason) {
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("Wakeup: Timer");
break;
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println("Wakeup: External (RTC_IO)");
break;
case ESP_SLEEP_WAKEUP_EXT1:
Serial.println("Wakeup: External (RTC_CNTL)");
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
Serial.println("Wakeup: Touchpad");
break;
default:
Serial.printf("Wakeup: Other (%d)\\n", reason);
}
}
void doWork() {
// Quick Wi-Fi connect
Wi-Fi.begin("ssid", "password");
Wi-Fi.setSleep(false); // Disable modem sleep for faster connection
int timeout = 0;
while (Wi-Fi.status() != WL_CONNECTED && timeout < 20) {
delay(500);
timeout++;
}
if (Wi-Fi.status() == WL_CONNECTED) {
// Send data quickly
Serial.printf("Boot #%d - IP: %s\\n", bootCount, Wi-Fi.localIP().toString().c_str());
// ... send sensor data ...
}
Wi-Fi.disconnect(true);
Wi-Fi.mode(WIFI_OFF);
}
void goToSleep() {
Serial.printf("Sleeping for %d seconds...\\n", SLEEP_DURATION_SEC);
Serial.flush();
esp_sleep_enable_timer_wakeup(SLEEP_DURATION_SEC * uS_TO_S_FACTOR);
esp_deep_sleep_start();
// Code below this never runs
}
void setup() {
Serial.begin(115200);
delay(100);
bootCount++;
Serial.printf("\\n=== Boot #%d ===\\n", bootCount);
printWakeupReason();
doWork();
goToSleep();
}
void loop() {
// Never reached in deep sleep mode
}`,
explanation: "RTC_DATA_ATTR variables persist across deep sleep. ESP32 deep sleep current: ~10uA. Timer wakeup has ~150ms boot time. Use esp_wifi_start() with stored credentials for faster reconnection (<1s vs ~3s)."
},
{
id: "deep-sleep-gpio",
title: "Deep Sleep with GPIO Wakeup",
category: "Power",
platform: "ESP32",
difficulty: "Intermediate",
tags: ["sleep", "power", "gpio", "interrupt", "wakeup"],
description: "Wake from deep sleep on button press or sensor trigger",
code: `#include <esp_sleep.h>
#define WAKEUP_PIN GPIO_NUM_33 // RTC GPIO
#define LED_PIN 2
RTC_DATA_ATTR int wakeupCount = 0;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
pinMode(WAKEUP_PIN, INPUT_PULLUP);
wakeupCount++;
Serial.printf("Wakeup #%d\\n", wakeupCount);
// Blink to show we're awake
for (int i = 0; i < 3; i++) {
digitalWrite(LED_PIN, HIGH);
delay(100);
digitalWrite(LED_PIN, LOW);
delay(100);
}
// Check wakeup cause
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT0) {
Serial.println("Woken by button press!");
// Handle button press event
handleButtonWakeup();
}
// Configure wakeup on LOW signal (button pressed)
esp_sleep_enable_ext0_wakeup(WAKEUP_PIN, 0); // 0 = LOW level
// Optional: Also set timer as backup
esp_sleep_enable_timer_wakeup(3600 * 1000000ULL); // 1 hour
Serial.println("Going to sleep...");
Serial.flush();
// Disable peripherals for lowest power
esp_wifi_stop();
esp_bt_controller_disable();
esp_deep_sleep_start();
}
void handleButtonWakeup() {
// Debounce
delay(50);
if (digitalRead(WAKEUP_PIN) == LOW) {
Serial.println("Button confirmed pressed");
// Do something useful
}
}
void loop() {
// Never reached
}`,
explanation: "EXT0 wakeup uses one RTC GPIO (0,2,4,12-15,25-27,32-39). EXT1 can use multiple pins with bitmask. Wakeup level: 0=LOW, 1=HIGH. Internal pullups work during deep sleep on RTC GPIOs only."
},
// ===== BLE =====
{
id: "ble-beacon",
title: "BLE iBeacon Transmitter",
category: "BLE",
platform: "ESP32",
difficulty: "Intermediate",
tags: ["ble", "beacon", "ibeacon", "advertising"],
description: "Broadcast as an iBeacon for proximity detection and indoor positioning",
code: `#include <BLEDevice.h>
#include <BLEBeacon.h>
// iBeacon UUID - generate your own at uuidgenerator.net
#define BEACON_UUID "8ec76ea3-6668-48da-9866-75be8bc86f4d"
#define MAJOR 1
#define MINOR 1
#define TX_POWER -59 // Calibrated at 1 meter
BLEAdvertising *pAdvertising;
void setBeacon() {
BLEBeacon beacon;
beacon.setManufacturerId(0x4C00); // Apple
beacon.setProximityUUID(BLEUUID(BEACON_UUID));
beacon.setMajor(MAJOR);
beacon.setMinor(MINOR);
beacon.setSignalPower(TX_POWER);
BLEAdvertisementData advData;
advData.setFlags(0x04); // BR_EDR_NOT_SUPPORTED
std::string strBeacon = "";
strBeacon += (char)26; // Length
strBeacon += (char)0xFF; // Type: Manufacturer Specific
strBeacon += beacon.getData();
advData.addData(strBeacon);
pAdvertising->setAdvertisementData(advData);
// Scan response with device name
BLEAdvertisementData scanResponse;
scanResponse.setName("IoT-Beacon-01");
pAdvertising->setScanResponseData(scanResponse);
}
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE Beacon...");
BLEDevice::init("IoT-Beacon");
pAdvertising = BLEDevice::getAdvertising();
setBeacon();
pAdvertising->setAdvertisementType(ADV_TYPE_NONCONN_IND);
pAdvertising->start();
Serial.println("Beacon active!");
Serial.printf("UUID: %s\\n", BEACON_UUID);
Serial.printf("Major: %d, Minor: %d\\n", MAJOR, MINOR);
}
void loop() {
delay(1000);
}`,
explanation: "iBeacon uses UUID (128-bit), Major (16-bit), Minor (16-bit) for identification. TX_POWER is RSSI at 1 meter, used for distance estimation. Calibrate TX_POWER by measuring actual RSSI at 1m."
},
// ===== LoRa =====
{
id: "lora-transmit",
title: "LoRa Point-to-Point Transmit",
category: "LoRa",
platform: "ESP32",
difficulty: "Intermediate",
tags: ["lora", "wireless", "long-range", "transmit"],
description: "Send data over LoRa with configurable spreading factor and bandwidth",
code: `#include <SPI.h>
#include <LoRa.h>
// Pin definitions for common modules
// TTGO LoRa32: SS=18, RST=14, DIO0=26
// Heltec Wi-Fi LoRa: SS=18, RST=14, DIO0=26
#define LORA_SS 18
#define LORA_RST 14
#define LORA_DIO0 26
#define LORA_FREQ 915E6 // 915MHz for US, 868E6 for EU
struct SensorPacket {
uint8_t nodeId;
uint16_t packetNum;
float temperature;
float humidity;
uint16_t battery; // mV
} __attribute__((packed));
uint16_t packetCounter = 0;
void setup() {
Serial.begin(115200);
SPI.begin(5, 19, 27, LORA_SS); // SCK, MISO, MOSI, SS
LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0);
if (!LoRa.begin(LORA_FREQ)) {
Serial.println("LoRa init failed!");
while (1);
}
// Configure for long range
LoRa.setSpreadingFactor(10); // 7-12, higher = longer range
LoRa.setSignalBandwidth(125E3); // 125kHz standard
LoRa.setCodingRate4(5); // 4/5 to 4/8
LoRa.setTxPower(20); // 2-20 dBm
LoRa.enableCrc();
Serial.println("LoRa Transmitter Ready");
Serial.printf("Frequency: %.1f MHz, SF: %d\\n",
LORA_FREQ/1E6, LoRa.getSpreadingFactor());
}
void sendPacket() {
SensorPacket packet;
packet.nodeId = 1;
packet.packetNum = packetCounter++;
packet.temperature = 25.5;
packet.humidity = 60.0;
packet.battery = 3700;
LoRa.beginPacket();
LoRa.write((uint8_t*)&packet, sizeof(packet));
LoRa.endPacket();
Serial.printf("Sent packet #%d (%d bytes)\\n",
packet.packetNum, sizeof(packet));
}
void loop() {
sendPacket();
delay(30000); // Respect duty cycle limits
}`,
explanation: "LoRa settings trade-off: Higher SF = longer range but slower. SF7@125kHz = 5.5kbps, SF12@125kHz = 0.3kbps. EU868 has 1% duty cycle limit. Use packed structs for efficient binary payloads."
},
// ===== JSON HANDLING =====
{
id: "json-serialize",
title: "JSON Serialization Best Practices",
category: "Data",
platform: "Arduino/ESP32",
difficulty: "Beginner",
tags: ["json", "serialize", "data", "format"],
description: "Properly serialize sensor data to JSON with nested objects and arrays",
code: `#include <ArduinoJson.h>
void createSensorJson() {
// Calculate required size: https://arduinojson.org/v6/assistant/
StaticJsonDocument<512> doc;
// Basic fields
doc["device_id"] = "sensor-node-01";
doc["firmware"] = "1.2.3";
doc["timestamp"] = millis();
// Nested object for location
JsonObject location = doc.createNestedObject("location");
location["lat"] = 37.7749;
location["lon"] = -122.4194;
location["accuracy"] = 10.5;
// Nested object for readings
JsonObject readings = doc.createNestedObject("readings");
readings["temperature"] = 23.5;
readings["humidity"] = 65.2;
readings["pressure"] = 1013.25;
// Array of alerts
JsonArray alerts = doc.createNestedArray("alerts");
alerts.add("low_battery");
alerts.add("high_temp");
// Array of historical values
JsonArray history = doc.createNestedArray("temp_history");
float temps[] = {22.1, 22.5, 23.0, 23.5};
for (float t : temps) {
history.add(t);
}
// Serialize to string
String output;
serializeJson(doc, output);
Serial.println("Compact:");
Serial.println(output);
// Pretty print
Serial.println("\\nPretty:");
serializeJsonPretty(doc, Serial);
// Check memory usage
Serial.printf("\\n\\nMemory used: %d/%d bytes\\n",
doc.memoryUsage(), doc.capacity());
}
void parseSensorJson(const char* json) {
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, json);
if (error) {
Serial.printf("Parse error: %s\\n", error.c_str());
return;
}
// Safe access with defaults
const char* deviceId = doc["device_id"] | "unknown";
float temp = doc["readings"]["temperature"] | -999.0;
float lat = doc["location"]["lat"] | 0.0;
// Check if field exists
if (doc.containsKey("alerts")) {
JsonArray alerts = doc["alerts"];
Serial.printf("Alerts: %d\\n", alerts.size());
for (const char* alert : alerts) {
Serial.printf(" - %s\\n", alert);
}
}
Serial.printf("Device: %s, Temp: %.1f, Lat: %.4f\\n",
deviceId, temp, lat);
}
void setup() {
Serial.begin(115200);
delay(1000);
createSensorJson();
Serial.println("\\n--- Parsing ---");
const char* testJson = "{\\"device_id\\":\\"test\\",\\"readings\\":{\\"temperature\\":25.5}}";
parseSensorJson(testJson);
}
void loop() {}`,
explanation: "StaticJsonDocument is stack-allocated with fixed capacity. Use DynamicJsonDocument for heap allocation when size varies. The | operator provides default values for missing fields. Always check DeserializationError."
},
// ===== OTA UPDATE =====
{
id: "ota-basic",
title: "Over-the-Air (OTA) Updates",
category: "Updates",
platform: "ESP32",
difficulty: "Advanced",
tags: ["ota", "update", "firmware", "wireless"],
description: "Enable wireless firmware updates for deployed IoT devices",
code: `#include <Wi-Fi.h>
#include <ESPmDNS.h>
#include <Wi-FiUdp.h>
#include <ArduinoOTA.h>
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-password";
const char* OTA_HOSTNAME = "esp32-sensor-01";
const char* OTA_PASSWORD = "update123"; // Optional
void setupOTA() {
ArduinoOTA.setHostname(OTA_HOSTNAME);
ArduinoOTA.setPassword(OTA_PASSWORD);
ArduinoOTA.onStart([]() {
String type = (ArduinoOTA.getCommand() == U_FLASH)
? "firmware" : "filesystem";
Serial.printf("OTA Start: %s\\n", type.c_str());
});
ArduinoOTA.onEnd([]() {
Serial.println("\\nOTA Complete!");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\\r", (progress * 100) / total);
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("OTA Error[%u]: ", error);
switch (error) {
case OTA_AUTH_ERROR: Serial.println("Auth Failed"); break;
case OTA_BEGIN_ERROR: Serial.println("Begin Failed"); break;
case OTA_CONNECT_ERROR: Serial.println("Connect Failed"); break;
case OTA_RECEIVE_ERROR: Serial.println("Receive Failed"); break;
case OTA_END_ERROR: Serial.println("End Failed"); break;
}
});
ArduinoOTA.begin();
Serial.printf("OTA Ready! Hostname: %s.local\\n", OTA_HOSTNAME);
}
void setup() {
Serial.begin(115200);
Wi-Fi.mode(WIFI_STA);
Wi-Fi.begin(WIFI_SSID, WIFI_PASS);
while (Wi-Fi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.printf("\\nConnected: %s\\n", Wi-Fi.localIP().toString().c_str());
setupOTA();
}
void loop() {
ArduinoOTA.handle(); // Must be called regularly!
// Your normal code here
delay(10);
}`,
explanation: "Upload via Arduino IDE: Tools > Port > Network ports. Or use espota.py script. OTA requires ~50% free flash (app runs while updating). Use partitions with OTA support (e.g., min_spiffs). ArduinoOTA.handle() must be called frequently."
},
// ===== WATCHDOG =====
{
id: "watchdog-timer",
title: "Watchdog Timer for Reliability",
category: "Reliability",
platform: "ESP32",
difficulty: "Intermediate",
tags: ["watchdog", "reliability", "crash", "recovery"],
description: "Automatic reset if code hangs or crashes",
code: `#include <esp_task_wdt.h>
#define WDT_TIMEOUT_SEC 30 // Reset if no feed for 30s
void setup() {
Serial.begin(115200);
// Configure watchdog
esp_task_wdt_init(WDT_TIMEOUT_SEC, true); // true = panic on timeout
esp_task_wdt_add(NULL); // Add current task to watchdog
Serial.printf("Watchdog configured: %ds timeout\\n", WDT_TIMEOUT_SEC);
// Check if last reset was from watchdog
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_TASK_WDT || reason == ESP_RST_WDT) {
Serial.println("WARNING: Last reset was from watchdog!");
// Log this event, maybe reduce workload
}
}
void doSensorWork() {
// Simulate sensor reading
Serial.println("Reading sensors...");
delay(1000);
// Feed watchdog after successful operation
esp_task_wdt_reset();
}
void doNetworkWork() {
// Simulate network operation
Serial.println("Sending data...");
delay(2000);
// Feed watchdog
esp_task_wdt_reset();
}
void loop() {
static unsigned long lastRun = 0;
if (millis() - lastRun > 5000) {
lastRun = millis();
doSensorWork();
doNetworkWork();
// Uncomment to test watchdog (will reset after WDT_TIMEOUT_SEC)
// while(1) { delay(100); }
}
// Feed watchdog in main loop too
esp_task_wdt_reset();
delay(100);
}`,
explanation: "Watchdog resets ESP32 if not fed within timeout. Place esp_task_wdt_reset() after each major operation. Don't set timeout too short - network operations can take seconds. Check reset reason to detect and log watchdog events."
},
// ===== DATA STRUCTURES =====
{
id: "ring-buffer",
title: "Ring Buffer for Sensor Data",
category: "Data",
platform: "Arduino/ESP32",
difficulty: "Intermediate",
tags: ["buffer", "queue", "circular", "memory"],
description: "Efficient circular buffer for storing recent sensor readings",
code: `template<typename T, size_t SIZE>
class RingBuffer {
private:
T buffer[SIZE];
size_t head = 0;
size_t tail = 0;
size_t count = 0;
public:
bool push(const T& item) {
buffer[head] = item;
head = (head + 1) % SIZE;
if (count < SIZE) {
count++;
} else {
tail = (tail + 1) % SIZE; // Overwrite oldest
}
return true;
}
bool pop(T& item) {
if (count == 0) return false;
item = buffer[tail];
tail = (tail + 1) % SIZE;
count--;
return true;
}
bool peek(T& item) const {
if (count == 0) return false;
item = buffer[tail];
return true;
}
T& operator[](size_t index) {
return buffer[(tail + index) % SIZE];
}
size_t size() const { return count; }
size_t capacity() const { return SIZE; }
bool isEmpty() const { return count == 0; }
bool isFull() const { return count == SIZE; }
float average() const {
if (count == 0) return 0;
float sum = 0;
for (size_t i = 0; i < count; i++) {
sum += buffer[(tail + i) % SIZE];
}
return sum / count;
}
void clear() {
head = tail = count = 0;
}
};
// Usage example
RingBuffer<float, 60> temperatureHistory; // Last 60 readings
void setup() {
Serial.begin(115200);
}
void loop() {
// Simulate sensor reading
float temp = 20.0 + random(0, 100) / 10.0;
temperatureHistory.push(temp);
Serial.printf("Temp: %.1f, Buffer: %d/%d, Avg: %.1f\\n",
temp,
temperatureHistory.size(),
temperatureHistory.capacity(),
temperatureHistory.average());
delay(1000);
}`,
explanation: "Ring buffers are O(1) for push/pop and use fixed memory. Perfect for storing N most recent readings. When full, oldest data is automatically overwritten. Use for rolling averages, trend detection, and buffering before transmission."
}
]
// Extract unique values
categories = [...new Set(snippets.map(s => s.category))].sort()
platforms = [...new Set(snippets.map(s => s.platform))].sort()
difficulties = ["Beginner", "Intermediate", "Advanced"]
viewof filterCategory = Inputs.select(["All Categories", ...categories], {
label: "Category:",
value: "All Categories"
})
viewof filterPlatform = Inputs.select(["All Platforms", ...platforms], {
label: "Platform:",
value: "All Platforms"
})
viewof filterDifficulty = Inputs.select(["All Levels", ...difficulties], {
label: "Difficulty:",
value: "All Levels"
})
viewof searchTerm = Inputs.text({
label: "Search:",
placeholder: "mqtt, sensor, sleep, json..."
})