4.4 KiB
Rotary Dial
ESP32 firmware that reads a pulse-generating telephone rotary dial and publishes the dialled number to Home Assistant over MQTT.
Built on the ESP32 Device Framework.
How it works
A classic rotary telephone dial generates a burst of pulses as it returns to rest after each digit — one pulse for 1, two for 2, …, nine for 9, ten for 0. The firmware counts those pulses on a single GPIO pin and assembles digits into a string. When the user stops dialling (2.5 s silence), the complete number is published to MQTT.
Leading zeros are preserved: dialling 07 sends "07", not "7".
Hardware
| Item | Details |
|---|---|
| MCU | ESP32 (any variant) |
| Dial | Pulse-generating rotary telephone dial |
| Pulse pin | GPIO 4 (DIAL_PIN in main.cpp) |
| Wiring | Pulse contact between GPIO 4 and GND. Pin uses INPUT_PULLUP. |
The pulse contact is normally open. As the dial returns it briefly shorts GPIO 4 to GND on each pulse — the rising edge (release) is what the interrupt counts.
If your dial's contact is normally closed instead, change the interrupt trigger in setup() from RISING to FALLING.
Building and flashing
# Build
pio run -e esp32dev
# Flash firmware
pio run -e esp32dev --target upload
# Upload filesystem image (logo, etc.) — only needed when data/ changes
pio run -e esp32dev --target uploadfs
# Serial monitor (115 200 baud)
pio device monitor
Note: close the serial monitor before uploading — it holds the serial port exclusively.
Board targets: esp32dev, esp32-s3-devkitc-1
First-time Wi-Fi setup
On first boot the device opens an access point named Rotary-Dial. Connect with any phone or laptop; a captive portal will appear.
- Enter your Wi-Fi credentials.
- Optionally set the Device hostname (default:
rotary-dial, max 63 characters). - Save. The device joins your network and restarts.
Reachable at http://rotary-dial.local or the IP shown in the serial monitor.
Factory reset
Power-cycle (or press EN) rapidly before the 3-second boot window expires. The reset counter accumulates across rapid reboots:
| Cycles | Effect |
|---|---|
| 5 – 9 | Clears hostname, auth, and MQTT config; restarts into normal Wi-Fi |
| 10+ | Clears all of the above plus stored Wi-Fi credentials; restarts into captive portal |
Serial output shows the accumulated count:
[BOOT] reset count 3
[BOOT] reset count 4
...
[BOOT] reset count 5, triggering factory reset
[BOOT] factory reset triggered (clearWifi=0)!
[BOOT] factory reset complete, restarting...
Web UI
Browse to the device address to configure hostname, authentication, and MQTT. The logo in the header is served from data/logo.png in the filesystem image.
Click Save to persist settings. Changing the hostname automatically restarts the device. Click Reset Device to restart immediately.
HTTP API
| Method | Path | Description |
|---|---|---|
GET |
/ |
HTML configuration page |
GET |
/logo.png |
Logo image served from LittleFS |
POST |
/config |
Save configuration; redirects to /. Restarts if hostname changed |
POST |
/reset |
Restart the device immediately |
MQTT
Topics
| Direction | Topic | Description |
|---|---|---|
| Published | <prefix>/dialed |
Complete dialled number as a string (not retained) |
| Published | <prefix>/status |
online on connect, offline as LWT (retained) |
Default prefix: dial
Home Assistant
On MQTT connect the device publishes a discovery payload so HA automatically creates a sensor entity that shows the last dialled number.
Example automation:
automation:
- alias: Act on dialled number
trigger:
platform: mqtt
topic: dial/dialed
action:
- choose:
- conditions:
- condition: template
value_template: "{{ trigger.payload == '112' }}"
sequence:
- service: notify.mobile_app
data:
message: "Emergency number dialled!"
Timing constants
Adjust in main.cpp if your dial runs faster or slower than standard:
| Constant | Default | Description |
|---|---|---|
PULSE_DEBOUNCE_MS |
15 ms | Minimum time between counted edges |
INTER_PULSE_MS |
350 ms | Silence after last pulse = digit complete |
INTER_DIGIT_MS |
2500 ms | Silence after last digit = number complete |