Baumann Works logo
Baumann Works Tools, code, and hardware
Baumann Works

Rotary Dial

Feb 2026 ·Active ·source

Rotary Dial

ESP32 firmware that reads a pulse-generating telephone rotary dial and publishes the dialled number to Home Assistant over MQTT.

The project is a standalone PlatformIO/Arduino firmware using WiFiManager for first-time Wi-Fi setup, LittleFS for local configuration storage, a small built-in web UI, and PubSubClient for MQTT.

How It Works

A classic rotary telephone dial closes and opens contacts as the dial is wound and released. This firmware expects two contacts:

ContactGPIOPurpose
Off-normal / active contactGPIO 4Goes active while a digit is being dialled
Pulse contactGPIO 5Generates the digit pulse train

Both pins use INPUT_PULLUP, so each contact should switch the GPIO to GND when active.

When the off-normal contact goes low, the firmware starts a digit. While that contact remains low, it counts rising edges on the pulse contact. When the off-normal contact returns high, the digit is complete:

PulsesDigit
1-91-9
10 or more0

Digits are appended to a string. After 3 seconds without another completed digit, the full number is published to MQTT. Leading zeros are preserved, so dialling 07 publishes "07".

Hardware

ItemDetails
MCUESP32 board supported by the configured PlatformIO environments
DialPulse-generating rotary telephone dial with off-normal and pulse contacts
Off-normal pinGPIO 4 (OFF_NORMAL_PIN in src/main.cpp)
Pulse pinGPIO 5 (PULSE_PIN in src/main.cpp)
WiringEach contact connects its GPIO to GND when active; firmware enables internal pull-ups

If your dial uses inverted contact logic, adjust the state checks in dialLoop() rather than only changing an interrupt trigger. The current implementation polls both pins and does not use GPIO interrupts.

Building and Flashing

Install PlatformIO, then run:

# Build the default ESP32 DevKit environment
pio run -e esp32dev

# Flash firmware
pio run -e esp32dev --target upload

# Upload the LittleFS image, including data/logo.png
pio run -e esp32dev --target uploadfs

# Serial monitor at 115200 baud
pio device monitor

The configured environments are:

EnvironmentBoard
esp32devESP32 Dev Module
esp32-s3-devkitc-1ESP32-S3-DevKitC-1

Use the matching environment name in the commands above. Close the serial monitor before uploading firmware because it holds the serial port.

First-Time Wi-Fi Setup

On first boot, WiFiManager opens an access point named Rotary-Dial. Connect with a phone or laptop and use the captive portal to enter Wi-Fi credentials.

The portal also exposes a Device hostname field. The default hostname is rotary-dial.

After the device joins Wi-Fi, it starts mDNS and the web UI is reachable at:

http://rotary-dial.local

The serial monitor also prints the assigned IP address.

Web UI

Browse to the device address to configure:

SettingNotes
HostnameSaved to LittleFS; changing it restarts the device
SecurityOptional HTTP basic auth, username admin
MQTTBroker host, port, username, password, topic prefix, and enable switch

Passwords are only replaced when a new non-empty password is submitted. Leaving an existing password field blank keeps the stored value.

The header logo is served from /logo.png, which comes from data/logo.png in the uploaded LittleFS image.

HTTP API

MethodPathDescription
GET/HTML configuration page
GET/logo.pngLogo image served from LittleFS
POST/configSave hostname, auth, and MQTT settings; redirects to /; restarts if hostname changed
POST/resetRestart the device immediately

When HTTP auth is enabled, the username is admin and the password is the value configured in the web UI.

MQTT

MQTT is disabled until it is enabled and configured in the web UI.

Default topic prefix: dial

DirectionTopicDescription
Published<prefix>/dialedComplete dialled number as a non-retained string event
Published<prefix>/statusonline on connect, offline as retained MQTT last will
Publishedhomeassistant/sensor/<object_id>/configRetained Home Assistant discovery payload

The firmware publishes Home Assistant MQTT discovery for a sensor named Dialed 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!"

Factory Reset

During boot, the firmware increments a reset counter in LittleFS and waits 3 seconds. Rapidly power-cycle or press EN before that window expires to accumulate reset counts.

Rapid boot countEffect
5-9Clears hostname, HTTP auth, and MQTT config
10+Clears hostname, HTTP auth, MQTT config, and Wi-Fi credentials

After the 3-second window, the counter is cleared if no reset threshold is reached.

Example serial output:

[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...

Tuning

The dial behavior is controlled by constants in src/main.cpp:

ConstantDefaultDescription
PULSE_DEBOUNCE_MS20 msMinimum time between counted pulse release edges
NUMBER_COMPLETE_MS3000 msIdle time after the last completed digit before publishing the number

The source also defines DIGIT_BOUNDARY_MS, but the current digit boundary is determined by the off-normal contact returning high.

← LabHelper M1730-Scale →