Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
a810456ef1
|
|||
|
5ae2321f2d
|
|||
|
65babb5faa
|
@@ -26,33 +26,67 @@ If your dial's contact is normally closed instead, change the interrupt trigger
|
||||
## Building and flashing
|
||||
|
||||
```bash
|
||||
# Build (S3 DevKit)
|
||||
pio run -e esp32-s3-devkitc-1
|
||||
# Build
|
||||
pio run -e esp32dev
|
||||
|
||||
# Flash
|
||||
pio run -e esp32-s3-devkitc-1 --target upload
|
||||
# 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`).
|
||||
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 **5 times in quick succession** (before boot completes). All settings are cleared and the device restarts into the Wi-Fi portal.
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -67,7 +101,7 @@ Default prefix: `dial`
|
||||
|
||||
### Home Assistant
|
||||
|
||||
On MQTT connect the device publishes a discovery payload so HA automatically creates a **sensor** entity (`sensor.<hostname>_dialed_number`) that shows the last dialled number.
|
||||
On MQTT connect the device publishes a discovery payload so HA automatically creates a **sensor** entity that shows the last dialled number.
|
||||
|
||||
Example automation:
|
||||
|
||||
|
||||
+33
-6
@@ -23,7 +23,7 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define DIAL_PIN 4 // GPIO connected to the pulse contact (to GND)
|
||||
#define PULSE_DEBOUNCE_MS 15 // ignore edges faster than this
|
||||
#define PULSE_DEBOUNCE_MS 100 // ignore edges faster than this
|
||||
#define INTER_PULSE_MS 350 // silence longer than this = digit complete
|
||||
#define INTER_DIGIT_MS 2500 // silence longer than this = number complete, publish
|
||||
|
||||
@@ -58,6 +58,7 @@ static volatile int dialPulses = 0;
|
||||
static volatile unsigned long dialLastPulse = 0; // millis() of last counted pulse
|
||||
static String dialNumber = "";
|
||||
static unsigned long dialLastDigit = 0; // millis() of last completed digit
|
||||
static int dialRawCount = 0; // raw digit count since last publish (odd positions = wind-up spurious)
|
||||
|
||||
static void IRAM_ATTR onDialPulse() {
|
||||
unsigned long now = millis();
|
||||
@@ -135,11 +136,14 @@ static void saveConfig() {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void mqttPublishDialed(const String& number) {
|
||||
if (!mqttCfg.enabled || !mqttClient.connected()) return;
|
||||
if (!mqttCfg.enabled || !mqttClient.connected()) {
|
||||
Serial.printf("[DIAL] MQTT unavailable, number=%s not sent\n", number.c_str());
|
||||
return;
|
||||
}
|
||||
char topic[128];
|
||||
snprintf(topic, sizeof(topic), "%s/dialed", mqttCfg.prefix);
|
||||
mqttClient.publish(topic, number.c_str(), false); // not retained — it's an event
|
||||
Serial.printf("[DIAL] published %s -> %s\n", topic, number.c_str());
|
||||
bool ok = mqttClient.publish(topic, number.c_str(), false); // not retained — it's an event
|
||||
Serial.printf("[DIAL] publish %s -> %s ok=%d\n", topic, number.c_str(), ok);
|
||||
}
|
||||
|
||||
static void mqttCallback(char* topic, byte* payload, unsigned int len) {
|
||||
@@ -254,6 +258,7 @@ static void mqttLoop() {
|
||||
|
||||
static void dialLoop() {
|
||||
unsigned long now = millis();
|
||||
static int lastLoggedPulses = 0;
|
||||
|
||||
// Snapshot volatile state atomically
|
||||
noInterrupts();
|
||||
@@ -261,24 +266,46 @@ static void dialLoop() {
|
||||
unsigned long lastPulse = dialLastPulse;
|
||||
interrupts();
|
||||
|
||||
// Log each new pulse as it arrives (ISR can't use Serial, so we log here)
|
||||
if (pulses != lastLoggedPulses) {
|
||||
Serial.printf("[DIAL] pulse #%d\n", pulses);
|
||||
lastLoggedPulses = pulses;
|
||||
}
|
||||
|
||||
// Burst silence exceeded → digit complete
|
||||
if (pulses > 0 && (now - lastPulse) >= INTER_PULSE_MS) {
|
||||
noInterrupts();
|
||||
dialPulses = 0;
|
||||
interrupts();
|
||||
lastLoggedPulses = 0;
|
||||
|
||||
dialRawCount++;
|
||||
// 10 pulses = digit '0'; 1–9 pulses = digits '1'–'9'
|
||||
char digit = '0' + (pulses >= 10 ? 0 : pulses);
|
||||
bool spurious = (dialRawCount % 2 == 1) && (digit == '1');
|
||||
if (spurious) {
|
||||
Serial.printf("[DIAL] burst end pulses=%d -> digit='%c' (wind-up spurious, discarded)\n", pulses, digit);
|
||||
} else {
|
||||
dialNumber += digit;
|
||||
dialLastDigit = now;
|
||||
Serial.printf("[DIAL] digit=%c number_so_far=%s\n", digit, dialNumber.c_str());
|
||||
Serial.printf("[DIAL] burst end pulses=%d -> digit='%c' number_so_far=%s\n", pulses, digit, dialNumber.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Number complete when no further digits arrive within the timeout
|
||||
if (dialNumber.length() > 0 && (now - dialLastDigit) >= INTER_DIGIT_MS) {
|
||||
Serial.printf("[DIAL] complete number=%s\n", dialNumber.c_str());
|
||||
Serial.printf("[DIAL] number complete=%s\n", dialNumber.c_str());
|
||||
mqttPublishDialed(dialNumber);
|
||||
dialNumber = "";
|
||||
dialRawCount = 0;
|
||||
}
|
||||
|
||||
// If the sequence stalled with no accepted digit (e.g. user wound up then
|
||||
// stopped), reset the count so the next wind-up is treated as position 1.
|
||||
if (dialRawCount > 0 && dialNumber.length() == 0 && pulses == 0
|
||||
&& (now - lastPulse) >= INTER_DIGIT_MS) {
|
||||
Serial.println("[DIAL] idle reset");
|
||||
dialRawCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user