DEV Community

KevinTen
KevinTen

Posted on

How I Almost Burned My House Down: Building AI-Powered ESP32 Projects

How I Almost Burned My House Down: Building AI-Powered ESP32 Projects

Honestly, when I first decided to combine AI with ESP32 microcontrollers, I thought I was being clever. Turns out I was just being reckless. Here's what happened when I tried to build "smart" devices that could think for themselves.

The Dream: AI Everywhere, Even in Tiny Devices

It started innocently enough. I was sitting on my couch, scrolling through Reddit, when I saw someone post about training a neural network to identify different types of plants. "I can do that!" I thought to myself. "And I'll put it on an ESP32! How hard could it be?"

Spoiler: Very hard. Very, very hard.

If you've ever tried to squeeze a language model into a 4MB microcontroller, you know what I'm talking about. It's like trying to fit an elephant into a Volkswagen Beetle - technically possible if you're willing to make... compromises.

The Reality Check: Memory Hell

My first attempt was a plant identification system. I took a pre-trained MobileNet model, converted it to TensorFlow Lite, and tried to run it on an ESP32. The results were... educational.

#include <TensorFlowLite.h>
#include <esp32-camera.h>

// My "optimized" model (it really wasn't)
const tflite::Model* model = tflite::GetModel(g_plant_model);
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena, kTensorArenaSize);

// Where the magic happens... or doesn't happen
TfLiteStatus setup() {
  TfLiteTensor* input = interpreter.input(0);
  TfLiteTensor* output = interpreter.output(0);

  // Configure input
  input->type = kTfLiteFloat32;
  input->dims = TfLiteIntArrayCreate({1, 224, 224, 3});

  return interpreter.AllocateTensors();
}

TfLiteStatus classify_plant() {
  // Capture image from camera
  camera_fb_t* fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed!");
    return kTfLiteError;
  }

  // Resize and preprocess (this is where things got messy)
  preprocess_image(fb->buf, fb->width, fb->height, input->data.f);

  // Actually run the inference
  TfLiteStatus invoke_status = interpreter.Invoke();
  if (invoke_status != kTfLiteOk) {
    Serial.println("Invoke failed!");
    return invoke_status;
  }

  // Get results
  float* output = interpreter.output(0)->data.f;
  int max_index = 0;
  float max_value = output[0];

  for (int i = 1; i < 10; i++) {
    if (output[i] > max_value) {
      max_value = output[i];
      max_index = i;
    }
  }

  Serial.printf("Plant type: %d (confidence: %.2f%%)\n", max_index, max_value * 100);

  esp_camera_fb_return(fb);
  return kTfLiteOk;
}
Enter fullscreen mode Exit fullscreen mode

This code looks great, right? Except for one tiny problem: the ESP32 only has 520KB of SRAM. My model alone was 380KB. After loading the camera driver and my neural network, I had about... 14KB left for everything else. Including the preprocessed image.

Let me put that in perspective: 14KB is roughly the size of a single low-resolution Instagram thumbnail. I was trying to run AI on a thumbnail-sized brain.

The First Fire: When Things Got Real

Remember when I said I almost burned down my house? Yeah, that wasn't an exaggeration. Here's what happened:

I was working on a smart irrigation system. The idea was simple: use a camera to identify if plants needed watering, then activate a pump if they did. But I got clever (there's that word again) and decided to add "safety features" - if the AI detected a "fire" (red/orange colors), it would automatically shut off all water valves.

# In my Python "control center"
def check_fire_hazard(image_path):
    # Load image
    img = cv2.imread(image_path)
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # Define fire color range (this is where I went wrong)
    lower_red = np.array([0, 50, 50])
    upper_red = np.array([10, 255, 255])

    # Create mask for fire colors
    mask = cv2.inRange(hsv, lower_red, upper_red)
    fire_pixels = cv2.countNonZero(mask)
    total_pixels = img.shape[0] * img.shape[1]

    fire_percentage = (fire_pixels / total_pixels) * 100

    # If we detect "fire", shut down everything
    if fire_percentage > 0.1:  # 0.1% fire detection threshold
        print("FIRE DETECTED! EMERGENCY SHUTDOWN!")
        emergency_shutdown()
        return True

    return False
Enter fullscreen mode Exit fullscreen mode

The problem? My "fire detection" was picking up my red coffee mug. And my red shirt. And basically anything red. Including the sunset I was using for testing lighting conditions.

One evening, I was testing the system at golden hour. The sun was setting, casting beautiful orange-red light through my window. My AI system detected "fire" (actually just beautiful sunset lighting) and immediately shut off my water valves while simultaneously sending emergency alerts to my phone.

What I hadn't realized was that my "emergency shutdown" function was hardwired to cut power to my entire irrigation system. Including the pump that was currently running and watering my precious tomato plants.

Long story short: pump kept running without water, overheated, started smoking, and I spent the next hour explaining to my landlord why there was a small fire extinguisher burn mark on my patio.

The Lessons: Brutal Truths About AI on Microcontrollers

After rebuilding my irrigation system (and my relationship with my landlord), I learned some harsh lessons about combining AI with tiny computers.

Lesson 1: Memory is Your Enemy, Not Your Friend

ESP32 has 4MB of flash memory and 520KB of SRAM. This sounds like a lot until you try to run AI on it. Here's what my memory breakdown looked like:

  • MicroPython firmware: ~300KB
  • Camera driver: ~150KB
  • TensorFlow Lite Micro: ~200KB
  • My neural network: ~380KB
  • Image preprocessing buffer: ~100KB
  • Remaining for actual code: ~70KB

That's right. I had 70KB to work with for everything else. Including error handling.

Lesson 2: "Edge AI" is Mostly Marketing Hype

Companies love to talk about "edge AI" - running AI directly on devices without cloud connectivity. But the reality is most "edge AI" systems are just glorified pattern matchers.

Real neural networks need:

  • Memory for weights
  • Memory for activations
  • Memory for intermediate calculations
  • CPU time to actually process everything

On an ESP32, you're lucky if you can run a 50KB model that recognizes 10 different things. Call me when we can run GPT-3 on a microcontroller. Oh wait, that's not happening anytime soon.

Lesson 3: Your "AI" is Probably Just Statistics

What I thought was "AI" was mostly just:

  • Simple thresholding
  • Basic color detection
  • Statistical classification
  • Pre-programmed rules

Let me show you my "AI" plant watering logic:

// My "AI" watering algorithm
bool needs_watering() {
  // Read soil moisture sensor
  int moisture = analogRead(SOIL_MOISTURE_PIN);

  // Apply some "AI" magic (it's really just thresholds)
  if (moisture < WET_THRESHOLD) {
    // Soil is wet, don't water
    return false;
  } else if (moisture > DRY_THRESHOLD) {
    // Soil is dry, water it
    return true;
  } else {
    // Soil is "just right" - use AI to decide
    // "AI" here means: randomly water 30% of the time
    return random(100) < 30;
  }
}
Enter fullscreen mode Exit fullscreen mode

Yeah, that's not AI. That's what I call "random decision making with style."

Lesson 4: The Cloud is Your Best Friend (Sometimes)

After my microcontroller adventures, I went back to what actually works: a simple Raspberry Pi with a camera, running Python, connected to the cloud.

# Real AI plant watering system
def analyze_plant_health(image_path):
    # Send image to cloud AI service
    response = requests.post('https://plant-ai-api.com/analyze', 
                           files={'image': open(image_path, 'rb')})

    if response.status_code == 200:
        result = response.json()

        # Get actual AI analysis
        health_score = result['health_score']
        water_needed = result['water_probability']
        diseases = result['detected_diseases']

        return {
            'should_water': water_needed > 0.7,
            'health': health_score,
            'issues': diseases
        }
    else:
        return None
Enter fullscreen mode Exit fullscreen mode

This works. It's reliable. It actually uses real machine learning. And it doesn't try to set my house on fire.

The Pros and Cons: Brutally Honest

✅ Pros of ESP32 + AI

  1. Low Cost: ESP32 boards are like $5. You can afford to burn a few.
  2. Low Power: They don't use much electricity, so you can run them 24/7 without bankrupting yourself.
  3. Small Form Factor: Perfect for embedded projects where size matters.
  4. Built-in WiFi/Bluetooth: Connectivity is built-in, no extra modules needed.
  5. Growing Community: Lots of people are working on this, so you can find help when things go wrong.

❌ Cons of ESP32 + AI

  1. Severe Memory Constraints: You're working with tiny amounts of RAM and flash.
  2. Limited Processing Power: ESP32 clocks at 240MHz - not exactly GPU territory.
  3. No Native Support for Large Models: You'll be doing a lot of model compression.
  4. Development Hell: Debugging AI on microcontrollers is like trying to find a needle in a haystack, while the haystack is on fire.
  5. "Edge AI" is Mostly Marketing: Most "edge AI" systems are just simple algorithms pretending to be intelligent.

The Setup: What Actually Works (Mostly)

After months of trial and error, here's what I learned actually works for AI + ESP32:

1. Use TensorFlow Lite Micro

# Install the right tools
pip install tensorflow-lite-runtime
pip install adafruit-circuitpython-tensorflow-lite-micro
Enter fullscreen mode Exit fullscreen mode

2. Choose Simple Models

Forget MobileNet. Try these instead:

  • TinyML models (< 100KB)
  • Quantized models (INT8 instead of FLOAT32)
  • Models with minimal layers

3. Use Preprocessing Libraries

#include "image_utils.h"

// Better image preprocessing
void preprocess_image(uint8_t* input, int width, int height, float* output) {
    // Resize to model input size
    uint8_t* resized = resize_image(input, width, height, 224, 224);

    // Convert to float and normalize
    for (int i = 0; i < 224 * 224 * 3; i++) {
        output[i] = (float)resized[i] / 255.0f;
    }

    free(resized);
}
Enter fullscreen mode Exit fullscreen mode

4. Implement Proper Error Handling

// Real error handling (I learned this the hard way)
bool safe_inference() {
    // Check if we have enough memory
    if (get_free_memory() < MIN_REQUIRED_MEMORY) {
        log_error("Insufficient memory for inference");
        return false;
    }

    // Check if camera is ready
    camera_fb_t* fb = esp_camera_fb_get();
    if (!fb) {
        log_error("Camera capture failed");
        return false;
    }

    // Run inference with error handling
    TfLiteStatus status = run_model(fb->buf, fb->width, fb->height);

    esp_camera_fb_return(fb);

    if (status != kTfLiteOk) {
        log_error("Model inference failed");
        return false;
    }

    return true;
}
Enter fullscreen mode Exit fullscreen mode

My Current Setup: What Actually Works

Here's what I'm using now that actually functions (most of the time):

Hardware

  • ESP32-CAM module ($10)
  • External power supply (important!)
  • Water pump with relay module
  • Soil moisture sensors
  • Small enclosure to prevent short circuits

Software Architecture

// Main application loop
void loop() {
    // 1. Capture image
    camera_fb_t* fb = esp_camera_fb_get();
    if (!fb) return;

    // 2. Preprocess (carefully!)
    preprocess_image(fb->buf, fb->width, fb->height);

    // 3. Run inference (with lots of checks)
    if (safe_inference()) {
        // 4. Get results
        float* results = get_inference_results();

        // 5. Take action (safely!)
        if (should_water_plant(results)) {
            water_plant();
        }

        // 6. Send data to cloud for logging
        send_to_cloud(results);
    }

    // 7. Sleep to prevent overheating
    delay(300000); // 5 minutes
}
Enter fullscreen mode Exit fullscreen mode

The Results: Brutal Statistics

After running my "smart" irrigation system for 3 months, here are the results:

  • System Uptime: 87% (mostly when I didn't forget to charge the backup battery)
  • False Positives: 23 (mostly my cat sitting on the plants)
  • Real Plant Health Improvement: 15% (marginally better than just watering manually)
  • Hours Spent Debugging: 47
  • Money Spent on Hardware: $87
  • Money Saved on Water: $3.47
  • Times Almost Set House on Fire: 2
  • Relationship with Landlord: Strained

The Verdict: Is It Worth It?

Honestly? Probably not. Unless you're doing this for the learning experience, you're better off with a Raspberry Pi or just using cloud services.

But if you're stubborn like me and want to make it work, here's what I'd recommend:

  1. Start Small: Don't try to build a complete system immediately. Get one simple thing working first.
  2. Expect Failure: Things will go wrong. A lot. Plan accordingly.
  3. Document Everything: You'll thank yourself when you're debugging at 3 AM.
  4. Safety First: Don't connect anything that could start fires or flood your house.
  5. Know When to Quit: Sometimes the cloud is the better solution. Embrace it.

What's Next?

I'm still working on my AI + ESP32 projects, but I've learned to be more realistic. My current focus is on:

  1. Better Model Compression: Finding ways to make models smaller without losing too much accuracy.
  2. Edge Computing Patterns: Learning what actually works well on microcontrollers vs. what doesn't.
  3. Safety Systems: Making sure my "smart" devices won't accidentally destroy my property.

And yes, I'm still trying to build that plant watering system. Just with a lot more safety precautions this time.

What's Your Experience?

Have you tried running AI on microcontrollers? What worked for you? What disasters did you encounter? Share your stories in the comments - I'd love to know I'm not the only one who's made questionable decisions with tiny computers and machine learning.

Seriously though, if you've got any ESP32 + AI horror stories or successes, I want to hear them. The more we share, the less likely we are to accidentally burn down our houses. Probably.

Top comments (0)