# 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](../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 ```bash # 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. 1. Enter your Wi-Fi credentials. 2. Optionally set the **Device hostname** (default: `rotary-dial`, max 63 characters). 3. 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 | `/dialed` | Complete dialled number as a string (not retained) | | Published | `/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: ```yaml 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 |