rforssen.net / projects / esp32

ESP32 Notes & Projects

MicroPython + WLED on ESP32 — homelab integrations and experiments
01

Environment setup

Development is done on the k3s control-plane RPi (raspberrypi, 192.168.100.1) via VS Code Remote SSH. The ESP32 is connected via USB to that same node.

Execution model

The ESP32 runs its own firmware (MicroPython or WLED). Your Raspberry Pi is only a development host used to: - flash firmware - upload files - open a REPL (interactive console) Once deployed, the ESP32 runs standalone — no dependency on the Pi.

Directory layout

~/esp32/
├── projects/
│   ├── blink/
│   └── wifi-test/
├── lib/              # reusable MicroPython modules
├── firmware/         # .bin firmware files
├── scripts/          # host-side helpers
└── venv/             # esptool + mpremote

Tooling

Python env
venv
Flash tool
esptool 5.2
REPL / deploy
mpremote
Serial port
/dev/ttyUSB0
Group
dialout

ESP32 filesystem

The ESP32 has a small internal filesystem (typically ~1–2MB). Files like main.py are stored there and executed on boot.

What is mpremote?

mpremote is a command-line tool to interact with a MicroPython device over USB. It allows you to:

  • List files on the ESP32 filesystem (eg., mpremote ls)
  • Copy files to the ESP32 filesystem (eg., mpremote cp main.py :main.py)
  • Remove files on the ESP32 filesystem (eg., mpremote rm)
  • Run scripts directly (eg. mpremote run main.py)
  • Access the REPL (interactive Python shell) (eg., mpremote connect /dev/ttyUSB0)
  • Reset the board (eg., mpremote reset)

tmux session

Added esp32 session to ~/shbin/tmux-init.sh. Activates the venv automatically on attach.

create_session esp32 "cd $HOME/esp32 && source $HOME/esp32/venv/bin/activate"

Deploy script

# scripts/deploy.sh
        mpremote connect $PORT cp ~/esp32/projects/$PROJECT/main.py :main.py
        mpremote connect $PORT reset

Troubleshooting USB

  • Check device appears: ls /dev/ttyUSB*
  • Permission denied → add user to dialout
  • Board not detected → press BOOT button during flash
02

Board identification

Chip
ESP32 (plain)
LED type
NeoPixel RGB
LED pin
GPIO 16
WiFi
2.4 GHz only

Firmware

Build
ESP32_GENERIC
Version
v1.24.1
Date
20241129

Flash commands

esptool --chip esp32 --port /dev/ttyUSB0 erase-flash

esptool --chip esp32 --port /dev/ttyUSB0 --baud 460800 \
  write-flash -z 0x1000 ESP32_GENERIC-20241129-v1.24.1.bin
04

Project: wifi-test + Telegram

working wifi https telegram

Connects to WiFi and sends a Telegram notification via the /notify/telegram endpoint on api.rforssen.net. The ESP32 never holds a Telegram token — it calls the internal API instead.

SSID
otraFruta (2.4GHz)
IP assigned
192.168.86.26
Network
192.168.86.x
API endpoint
POST /notify/telegram

Gotchas discovered

The API expects field text, not message. The urequests.post() data parameter must be bytes, not a plain string — encode with .encode("utf-8").

main.py

import network, time, urequests, json

SSID     = "otraFruta"
PASSWORD = "***"
API      = "https://api.rforssen.net/notify/telegram"

def connect_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, PASSWORD)
    while not wlan.isconnected():
        time.sleep(0.5)
    print("IP:", wlan.ifconfig()[0])

def send_telegram(text):
    payload = json.dumps({"text": text}).encode("utf-8")
    headers = {"Content-Type": "application/json"}
    r = urequests.post(API, data=payload, headers=headers)
    r.close()

connect_wifi()
send_telegram("Hello from ESP32!")

Reusable library modules

  • PY ~/esp32/lib/wifi.py — connect(ssid, password) → wlan
  • PY ~/esp32/lib/telegram.py — send(text) via api.rforssen.net

Usage in any project

import wifi
import telegram

wifi.connect("otraFruta", "your-password")
telegram.send("Hello from ESP32!")
05

WLED installation

working wled ws2812b wifi

WLED is a ready-made firmware for ESP32 that provides a full web UI, app control, and 100+ effects for addressable LED strips (WS2812B/NeoPixel). No coding required — the ESP32 becomes a dedicated LED controller.

WLED version
0.15.0
LED type
WS2812B
LED pin
GPIO 16
IP
192.168.86.26
Web UI
http://192.168.86.26
Network
otraFruta (2.4GHz)

Download firmware

cd ~/esp32/firmware
wget https://github.com/wled/WLED/releases/download/v0.15.0/WLED_0.15.0_ESP32.bin

Flash

WLED firmware is flashed at 0x10000 on top of an existing bootloader. The MicroPython bootloader at 0x1000 is compatible — no erase needed.

esptool --chip esp32 --port /dev/ttyUSB0 --baud 460800 \
  write-flash 0x10000 WLED_0.15.0_ESP32.bin

First boot

On first boot WLED creates a WiFi AP called WLED-AP (password: wled1234). Connect to it, enter your home WiFi credentials, save. The device reboots and joins your network.

LED configuration

In the WLED web UI go to Config → LED Preferences and set the data pin to 16. Set the LED type to WS2812B and configure the number of LEDs in your strip.

OTA updates

Once running, future firmware updates can be done via OTA directly from the WLED web UI — no USB cable needed.


JSON API

WLED exposes a full JSON API at /json/state. Always include Content-Type: application/json or the body is ignored.

# Get current state
curl http://192.168.86.26/json/state

# Get device info (firmware, LED count, WiFi, uptime...)
curl http://192.168.86.26/json/info

# List all 187 effects
curl http://192.168.86.26/json/effects

# List all 71 palettes
curl http://192.168.86.26/json/palettes

# Find effects by keyword
curl http://192.168.86.26/json/effects | python3 -c \
  "import json,sys; e=json.load(sys.stdin); [print(i,v) for i,v in enumerate(e) if 'comet' in v.lower()]"

# Get palette name by index
curl http://192.168.86.26/json/palettes | python3 -c \
  "import json,sys; p=json.load(sys.stdin); print(p[11])"

Controlling state

# Solid color (red)
curl -X POST http://192.168.86.26/json/state \
  -H "Content-Type: application/json" \
  -d '{"on":true,"bri":255,"seg":[{"fx":0,"col":[[255,0,0]]}]}'

# Set effect + palette
curl -X POST http://192.168.86.26/json/state \
  -H "Content-Type: application/json" \
  -d '{"seg":[{"fx":41,"pal":11}]}'

# Turn off
curl -X POST http://192.168.86.26/json/state \
  -H "Content-Type: application/json" \
  -d '{"on":false}'

Default effect

Stream effect (fx:41) with Rainbow palette (pal:11), warm orange base color, brightness 50%.

curl -X POST http://192.168.86.26/json/state \
  -H "Content-Type: application/json" \
  -d '{"on":true,"bri":127,"seg":[{"fx":41,"pal":11,"col":[[255,160,0],[0,0,0],[0,0,0]]}]}'

Notes

The 187 built-in effects are compiled C++ — not scriptable. For custom effects the options are: compile custom WLED firmware, or flash MicroPython back and drive LEDs directly on-device. External frame-by-frame control via JSON API is possible but not practical for smooth animations.

WLED has no native Google Assistant support. The standard integration path is via Home Assistant, which auto-discovers WLED devices and bridges to Google Home.