No description
Find a file
2026-05-07 16:43:10 +02:00
docs/adr Remove PRD, drop its references from ADR 2026-05-07 03:50:39 +02:00
.gitignore Initial commit: cuckoo clock bellows controller 2026-05-07 03:46:19 +02:00
LEESMIJ.md LEESMIJ: 'pulse' -> 'pulserend' in LED status table 2026-05-07 16:43:10 +02:00
nette.yaml Add Log Level select for runtime log verbosity control 2026-05-07 04:02:25 +02:00
README.md Add README for installer / integrator 2026-05-07 03:52:32 +02:00

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)

  1. Plug the ESP32-S3 board into your computer with USB-C.
  2. Edit nette.yaml and set your wifi network at the top of the file:
    wifi:
      ssid: "YOUR-WIFI-SSID"
      password: "YOUR-WIFI-PASSWORD"
    
    (Optional: also change the api.encryption.key and the ota.password if you plan to use this on a network you do not control.)
  3. Open a terminal in this directory and run:
    esphome run nette.yaml
    
    The first build downloads a few hundred MB of toolchain. Subsequent builds are fast.
  4. When prompted, choose the USB serial port (something like /dev/ttyACM0 on Linux, /dev/cu.usbserial-... on macOS, or COMx on Windows).
  5. 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

  1. Open the device's web page (browse to its IP address) or its Home Assistant page.
  2. Press Test Bellow A. The servo should fire once: down fast, hold briefly, back up slowly. The LED flashes red while it runs.
  3. Listen.
    • No whistle, just a thump? Increase Bellow A Stroke Speed.
    • Whistle is short or weak? Increase Bellow A Target Angle so the bellow goes deeper. Or increase Bellow A Hold slightly 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 Speed until the return is silent.
  4. Repeat for Bellow B with 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 the light: block in nette.yaml and 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 to 192.168.4.1, and you can re-enter wifi credentials.

No cuckoo calls happen on their own

  • Check that Cuckoo Auto is on (the switch in the web UI / Home Assistant).
  • Check that Cuckoo Min Interval and Cuckoo Max Interval are 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_led script in nette.yaml. Replace effect: idle_rainbow with a fixed color (set red / green / blue percentages and remove the effect: 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.