Environment setup
2026-03-16Development 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
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
Board identification
2026-03-16Firmware
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
Project: blink
2026-03-16First MicroPython project. Blinks the onboard RGB NeoPixel LED red at 0.5s intervals.
Code
from machine import Pin import neopixel import time np = neopixel.NeoPixel(Pin(16), 1) while True: np[0] = (255, 0, 0) # Red np.write() time.sleep(0.5) np[0] = (0, 0, 0) # Off np.write() time.sleep(0.5)
Run
mpremote run ~/esp32/projects/blink/main.py
Project: wifi-test + Telegram
2026-03-16Connects 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.
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
- ~/esp32/lib/wifi.py — connect(ssid, password) → wlan
- ~/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!")
WLED installation
2026-03-16WLED 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.
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.