| docs/adr | ||
| .gitignore | ||
| LEESMIJ.md | ||
| nette.yaml | ||
| README.md | ||
Nette - Cuckoo Clock Bellows Controller
A small electronic brain that drives the two bellows of a mechanical cuckoo clock. You wire two model-aircraft servos to its bellows, plug in power and wifi, and the device fires a "cu-koo" call at random intervals (1 to 2 minutes by default). Everything you might want to tune - stroke depth, stroke speed, how often the cuckoo calls - lives behind sliders in a web page or in Home Assistant. No code changes needed once it is flashed.
This document is written for the person installing the box into the physical sculpture / clock. It assumes you can use a soldering iron and follow command-line instructions, but not that you know ESPHome.
What you need
Hardware
- 1x ESP32-S3 DevKitC-1 development board (the one with a single addressable RGB LED on the top side; both genuine Espressif and clones work).
- 2x hobby servos. Standard 180-degree positional servos with 3 wires (signal / +5V / GND). The bellows do not need much torque, but they do need speed - SG90 / MG90S class is fine, MG996R is overkill but works.
- 1x USB-C cable (for flashing and as an option for power).
- 1x external 5 V power supply rated for at least 2 A. Do not power both servos from the dev board's USB rail under load. A wall adapter with a barrel connector + breakout, or a USB charger feeding through a buck converter, both work.
- A small piece of perfboard or a screw-terminal breakout to join the servo power lines to the external supply with a common ground back to the ESP32.
Software (on your computer, once)
- ESPHome - the build / flash
tool. On Linux:
pip install esphome. On macOS:brew install esphome. On Windows: use the Home Assistant ESPHome add-on, or WSL. - A USB-serial driver for the dev board's chip. Most distros ship it; on Windows, download "CP210x" or "CH340" depending on the chip on the board.
Wiring
ESP32-S3 DevKitC-1
+---------------------+
| |
5V ---|+5V (USB) |
GND ---|GND |
| |
| GPIO13 ----|---> Bellow A servo signal
| GPIO12 ----|---> Bellow B servo signal
| GPIO38 ----|--- on-board RGB LED (already wired)
+---------------------+
Servo wiring (each servo, 3 wires):
signal -> ESP32 GPIO13 (A) or GPIO12 (B)
+5V -> EXTERNAL 5 V supply (NOT the ESP32's 5V pin)
GND -> EXTERNAL 5 V supply ground AND the ESP32 GND
(the two grounds must be tied together)
The single most important wiring rule: the servos' +5V comes from the external supply, the ground is shared between the supply and the ESP32, and only the signal wire goes to the GPIO pin. If you skip the shared ground the servos will twitch unpredictably or refuse to move.
If the on-board RGB LED on your board is on GPIO48 instead of GPIO38
(some early Espressif boards), open nette.yaml and change the pin: under
the light: section. Most boards in circulation are GPIO38.
First-time flashing (over USB)
- Plug the ESP32-S3 board into your computer with USB-C.
- Edit
nette.yamland set your wifi network at the top of the file:
(Optional: also change thewifi: ssid: "YOUR-WIFI-SSID" password: "YOUR-WIFI-PASSWORD"api.encryption.keyand theota.passwordif you plan to use this on a network you do not control.) - Open a terminal in this directory and run:
The first build downloads a few hundred MB of toolchain. Subsequent builds are fast.esphome run nette.yaml - When prompted, choose the USB serial port (something like
/dev/ttyACM0on Linux,/dev/cu.usbserial-...on macOS, orCOMxon Windows). - After flashing, ESPHome will tail the device's log over USB. You should see it boot, connect to wifi, and announce its IP address.
After it joins your network, future flashes can go over the air instead of USB:
esphome run nette.yaml --device esp32s3-test.local
What the LED tells you
The on-board RGB LED is the device's status display.
| Color / pattern | What it means |
|---|---|
| Green - blink x3 | Just booted |
| Green - slow pulse | Booted, no wifi yet (or wifi dropped) |
| Slow rainbow cycle | Idle, wifi connected, waiting for the next call |
| Red - fast pulse | A single bellow is firing (test or part of call) |
| Cyan - pulse | A full cuckoo call is in progress (A then B) |
| Yellow - fast pulse | OTA firmware update in progress |
If you ever see a stuck color that does not match the table, power-cycle the board.
If the LED is too bright for the room, slide LED Brightness down. It goes
from 1% (very dim) to 100%.
Tuning the bellows so they actually whistle
A cuckoo whistle is finicky. The bellow has to be pressed fast enough to push a column of air through the pipe, but the depth and the dwell at the bottom also matter. Each of the two bellows has the same four sliders, all of which are saved across reboots:
| Slider | What it does | Reasonable starting point |
|---|---|---|
Bellow X Target Angle |
How deep the press goes (0 = no press, 180 = full press) | 180 |
Bellow X Stroke Speed |
How fast the press happens. Higher = faster = louder whistle | 80 |
Bellow X Return Speed |
How fast the bellow goes back to rest. Lower = silent return | 25 |
Bellow X Hold |
How long to hold at the bottom of the stroke before returning | 50 ms |
How to tune one bellow
- Open the device's web page (browse to its IP address) or its Home Assistant page.
- Press
Test Bellow A. The servo should fire once: down fast, hold briefly, back up slowly. The LED flashes red while it runs. - Listen.
- No whistle, just a thump? Increase
Bellow A Stroke Speed. - Whistle is short or weak? Increase
Bellow A Target Angleso the bellow goes deeper. Or increaseBellow A Holdslightly so the bellow stays compressed longer. - Servo buzzes at the end of the stroke? You are pressing the linkage
past its mechanical end. Reduce
Bellow A Target Angle. - Whistle on the return stroke? Reduce
Bellow A Return Speeduntil the return is silent.
- No whistle, just a thump? Increase
- Repeat for
Bellow Bwith the matching sliders.
The two bellows should be tuned to two different pitches (that is the whole point of a cuckoo), so the sliders for A and B normally end up at slightly different values. The clock's pipes themselves set the pitch; the controller just decides how hard each pipe gets blown.
Tuning the schedule
| Slider | What it does | Default |
|---|---|---|
Cuckoo Min Interval |
Shortest possible wait between calls | 1.0 min |
Cuckoo Max Interval |
Longest possible wait between calls | 2.0 min |
Cuckoo Inter-Note Gap |
Pause between the "cu" and the "koo" | 150 ms |
The actual gap between calls is picked at random within those bounds, so the clock feels alive instead of metronomic.
If you set the min above the max, the device will silently treat them as equal and use that. So if you want a strictly fixed interval, set both to the same value.
The Cuckoo Auto switch on the same page enables or disables the random
schedule. The device boots with it on; turn it off if you are setting up
or doing something delicate near the bellows.
Manual buttons
| Button | What it does |
|---|---|
Cuckoo Now |
Fire one full A+B call right now. |
Test Bellow A |
Fire bellow A once. Used for tuning. |
Test Bellow B |
Fire bellow B once. Used for tuning. |
Stop All |
Stop everything in progress and the random scheduler. Useful in a panic. |
Park Bellows |
Stop everything, then drive both servos back to their rest position. |
Updating the firmware later
After the first USB flash, you do not need to plug the board into your computer again. As long as it is on the same wifi as you, you can update it over the air:
esphome run nette.yaml --device esp32s3-test.local
If you ever want to change the device's name (esp32s3-test), edit the top
of nette.yaml and re-flash once over USB; from then on the new mDNS name
applies.
Troubleshooting
The servos do not move at all
- Confirm the external 5 V supply is on, and that its ground is tied to the ESP32's ground. With no shared ground, the signal pulse looks like noise to the servo.
- Power-cycle the ESP32. On boot it parks both servos at angle 0.
The servos chatter or move randomly
- Almost always a power problem: dropping voltage when the servo draws current. Use a beefier 5 V supply or shorter / thicker wires.
The on-board RGB LED is dark
- The pin might be GPIO48 on your board instead of GPIO38. Edit the
pin:field in thelight:block innette.yamland re-flash.
Wifi will not connect
- The board falls back to its own access point named
ESP32-S3 Fallback(password:fallback-password). Connect to it with your phone, browse to192.168.4.1, and you can re-enter wifi credentials.
No cuckoo calls happen on their own
- Check that
Cuckoo Autois on (the switch in the web UI / Home Assistant). - Check that
Cuckoo Min IntervalandCuckoo Max Intervalare sane (they are in minutes, not seconds; setting both to 30 means a call every half hour).
I want a different idle color, or no idle effect
- The slow rainbow lives in the
set_idle_ledscript innette.yaml. Replaceeffect: idle_rainbowwith a fixed color (setred/green/bluepercentages and remove theeffect:line) and re-flash.
Files in this repository
| File | Purpose |
|---|---|
nette.yaml |
The ESPHome configuration. Flash this. |
docs/adr/0001-cuckoo-bellows-controller.md |
The design decisions behind this firmware. |
.gitignore |
Keeps build artefacts and secrets out of git. |
License
Use it, change it, install it in any clock that needs a voice.