My electronics projects, from a first blinking LED to Pixel the DJ, a Wi-Fi desk robot that dances to my music, shows album art, turns its head with a servo and talks to me on Telegram.
Built on an ESP32 DevKit (Arduino framework) and a Raspberry Pi Pico (MicroPython), with parts from two starter kits.
⭐ Pixel the DJ (pixel_dj/)
A desk companion with a personality that reacts to the room, to me, and to whatever I'm listening to.
On the desk
- Animated eyes on an OLED that blink, look around and change mood (happy, tired, bored, grumpy)
- A head that turns (SG90 servo): it sways on every beat, does a double-take when I arrive, shakes "no" when grumpy and relaxes the motor when idle
- Music mode: when a song starts on my phone, the OLED shows the album art (downloaded, decoded and dithered to 1-bit), the LCD scrolls title and artist, and the LED strip pulses to the beat in the cover's main colour
- Comments on my music: "First song of the day!", "This one again?!", "Song #3 from Tame Impala!"
- Late night mode: music in a dark room gives dim purple light and sleepy dancing instead of sleeping
- Touch pad: tap, double-tap (saves the current song), or hold to pet it
- Reacts to presence (PIR), light (LDR) and temperature/humidity (DHT11), and gets bored if ignored
From anywhere, through a Telegram bot
| Command | What it does |
|---|---|
/np |
Current song with album art, artist, album and BPM |
/pet |
Pixel purrs and nuzzles on the desk |
/say <text> |
Shows a message on Pixel's LCD |
/room |
Temperature, humidity, lights on or off, whether someone is there, mood |
/today |
Today's song count and top artists |
/saved |
Songs I saved by double-tapping |
/party |
Rainbow LEDs, a little tune and wild head swaying |
/guard on|notify|off |
Desk alarm: 10-second countdown to disarm (secret IR hold), then siren + phone alert |
/remind 20m drink water, /reminders, /cancel |
Reminders on the desk (chime, LCD, flashing strip) and on the phone |
/focus 25 |
Focus timer: quiet mode, LED progress bar, break reminder. Also by holding a hand over the IR sensor. |
/alarm 07:00 |
Daily alarm with sunrise lights; wave to snooze, touch to stop |
/breaks 50, /water on |
Stand-up nudge after sitting too long (tracked with the PIR), hourly water reminder |
/weather, /location |
Weather (Open-Meteo); location shared from the phone with one tap |
/history |
24-hour chart of temperature, humidity, light and desk time (QuickChart) |
/game, /rlgl |
Reaction game (wave when the eyes flash) and Red Light, Green Light with the turning head |
/head <55-125>, /trim, /dance on|off |
Turn the head, fine-tune its centre, switch head dancing |
/status, /log |
Uptime, Wi-Fi signal, memory, last error, recent log lines (also at http://<Pixel IP>/) |
| Automatic | Morning weather briefing on first arrival, rain warnings, nightly recap at 23:00 |
The bot only answers its owner's chat ID.
Phone (YT Music) ──► Pano Scrobbler ──► Last.fm API ─┐
│ Wi-Fi
MusicBrainz + AcousticBrainz (tempo) ─────────────────┤
Telegram Bot API (commands, alerts) ◄────────────────►│
▼
┌─────────────────────────── ESP32 ───────────────────────────┐
│ Core 0: netTask Core 1: loop() (the pet) │
│ - polls Last.fm every 8 s - eyes, LCD, LED strip │
│ - downloads + dithers album art - servo head gestures │
│ - looks up the song's BPM - touch, PIR, LDR, DHT11 │
│ - Telegram long-lived TLS link - non-blocking buzzer │
│ └──── FreeRTOS queues + mutex ────┘ │
└──────────────────────────────────────────────────────────────┘
All slow network work runs on its own core, so the eyes and the dancing never freeze while a download is in progress.
- 5 V LCD on a 3.3 V board: the LCD's I2C backpack pulls its data lines up to 5 V. Two 3.3 kΩ resistors in series to GND on each line bring them down to ~2.9 V, so the LCD and OLED can share one I2C bus safely.
- Out of memory: the PNG decoder needs ~40 KB in one block. Allocating it at boot, before Wi-Fi and TLS fragment the heap, fixed a crash I traced with the ESP32 backtrace decoder.
- Only one TLS connection fits: the Telegram connection is closed briefly while album art and tempo are downloaded, then reopens.
- HTTPS failing with 80 KB free: the TLS library reported
BIGNUM - Memory allocation failed. Free memory was fragmented, so I removed 19 KB of static buffers (the album-art work area now reuses the download buffer). Weather and location use plain HTTP to save TLS memory. - Too close to the router: a −4 dBm signal overloaded the radio, so bigger transfers failed. The
/statuscommand now warns about it. - No USB cable, still debuggable: logs go to a RAM ring buffer, readable with
/logor a small status web page, and updates install over Wi-Fi (OTA). - A "memory leak" that wasn't: heap dropped ~300 bytes per Last.fm request, then levelled off after 2 minutes. Closed TCP sockets stay in TIME_WAIT for a while.
- Buzzer vs servo: both use the ESP32's PWM hardware. Putting them on channels with different hardware timers stops the buzzer's pitch changes from disturbing the servo's 50 Hz signal.
- Progressive JPEG covers: asking Last.fm for
.pnginstead gives an image the microcontroller can decode.
Listed in the order I built them.
| Sketch | What it does |
|---|---|
esp32_blink |
First program: blink the onboard LED |
step1_oled |
Find the OLED on the I2C bus and draw on it |
step2_dht11 |
Temperature and humidity on the OLED |
step3_pin_check, step3_rgb_test |
Diagnose the RGB LED's wiring pin by pin |
step3_temp_color |
RGB LED colour follows the temperature |
step4_buzzer, buzzer_test |
Temperature alarm with a passive buzzer |
step5_wifi |
Live sensor dashboard as a web page over Wi-Fi (http://esp32.local/) |
step6_motion |
Adds a PIR motion sensor to the dashboard |
| Sketch | What it does |
|---|---|
red_light_green_light |
Statue game: the PIR catches you moving on red. High score saved in flash. |
lcd_oled_test |
Two displays on one I2C bus (5 V LCD + 3.3 V OLED) |
now_playing |
What's playing on my phone, with dithered album art and an LED strip in the cover's colour |
now_playing_dual |
Same, on two screens, with a beat-synced equaliser from the song's BPM |
| Sketch | What it does |
|---|---|
desk_pet_sensor_check |
Shows every sensor's live reading, to check the wiring |
desk_pet |
Pixel, stage 1: eyes, moods, speech bubble, petting, sleep |
servo_test |
Learn how a servo works (pulse length → angle) before it became Pixel's head |
pixel_dj |
Pixel the DJ: everything above + music, Telegram, a moving head, daily helpers, desk health, weather and games. Split into tabs: 01_pet … 13_games. |
| Project | What it does |
|---|---|
pico/lcd_dimmer |
Turn a knob to fade an LED; the LCD shows the brightness and a bar graph. Includes my own small LCD1602 driver. |
| Part | Used for |
|---|---|
| ESP32 DevKit (ESP32-D0WD-V3) | Main board |
| 0.96" SSD1306 OLED (I2C) | Eyes, album art |
| LCD1602 with I2C backpack | Speech bubble, song title |
| WS2812 8-LED strip | Mood and beat lighting |
| SG90 micro servo | Pixel's head |
| HC-SR501 PIR, LDR, DHT11, touch pad (GPIO32) | Presence, light, climate, petting |
| IR obstacle module | Wave and hold gestures, reaction game |
| Passive buzzer | Sounds and melodies |
| Raspberry Pi Pico | MicroPython projects |
Pixel's pins: OLED + LCD on SDA 22 / SCL 23 · strip 5 · servo 18 · IR 19 · PIR 33 · buzzer 13 · LDR 34 · touch 32 · DHT11 4
- Install the ESP32 board package (Espressif, v3.x) in the Arduino IDE or
arduino-cli. - Install the libraries: Adafruit SSD1306, Adafruit GFX, Adafruit NeoPixel, DHT sensor library, LiquidCrystal I2C, ArduinoJson (v7), PNGdec, FluxGarage RoboEyes.
- For sketches that use Wi-Fi, copy
secrets.example.htosecrets.hin the same folder and fill in your Wi-Fi, Last.fm API key and Telegram bot token (from @BotFather).secrets.his git-ignored. - Build and upload, e.g.
arduino-cli compile --fqbn esp32:esp32:esp32:PartitionScheme=min_spiffs pixel_dj arduino-cli upload -p COM9 --fqbn esp32:esp32:esp32:PartitionScheme=min_spiffs pixel_dj
Pixel uses the Minimal SPIFFS partition scheme (1.9 MB app, with OTA). After the first USB upload it can be updated over Wi-Fi with espota (password in secrets.h).
For music features, your phone needs to scrobble to Last.fm (I use YT Music + the Pano Scrobbler app).
- Pixel's body turns toward you: a DC motor on a homemade turntable, with the Pico as a motor controller talking to the ESP32
- Wave gestures with two IR sensors, and a smart lamp with the relay
- Robot car