Compare commits
3 Commits
411500680b
...
a810456ef1
| 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
|
## Building and flashing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build (S3 DevKit)
|
# Build
|
||||||
pio run -e esp32-s3-devkitc-1
|
pio run -e esp32dev
|
||||||
|
|
||||||
# Flash
|
# Flash firmware
|
||||||
pio run -e esp32-s3-devkitc-1 --target upload
|
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)
|
# Serial monitor (115 200 baud)
|
||||||
pio device monitor
|
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
|
## 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.
|
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.
|
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.
|
3. Save. The device joins your network and restarts.
|
||||||
|
|
||||||
Reachable at `http://rotary-dial.local` or the IP shown in the serial monitor.
|
Reachable at `http://rotary-dial.local` or the IP shown in the serial monitor.
|
||||||
|
|
||||||
### Factory reset
|
### 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
|
## 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
|
## MQTT
|
||||||
|
|
||||||
@@ -67,7 +101,7 @@ Default prefix: `dial`
|
|||||||
|
|
||||||
### Home Assistant
|
### 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:
|
Example automation:
|
||||||
|
|
||||||
|
|||||||
+33
-6
@@ -23,7 +23,7 @@
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
#define DIAL_PIN 4 // GPIO connected to the pulse contact (to GND)
|
#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_PULSE_MS 350 // silence longer than this = digit complete
|
||||||
#define INTER_DIGIT_MS 2500 // silence longer than this = number complete, publish
|
#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 volatile unsigned long dialLastPulse = 0; // millis() of last counted pulse
|
||||||
static String dialNumber = "";
|
static String dialNumber = "";
|
||||||
static unsigned long dialLastDigit = 0; // millis() of last completed digit
|
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() {
|
static void IRAM_ATTR onDialPulse() {
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
@@ -135,11 +136,14 @@ static void saveConfig() {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
static void mqttPublishDialed(const String& number) {
|
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];
|
char topic[128];
|
||||||
snprintf(topic, sizeof(topic), "%s/dialed", mqttCfg.prefix);
|
snprintf(topic, sizeof(topic), "%s/dialed", mqttCfg.prefix);
|
||||||
mqttClient.publish(topic, number.c_str(), false); // not retained — it's an event
|
bool ok = mqttClient.publish(topic, number.c_str(), false); // not retained — it's an event
|
||||||
Serial.printf("[DIAL] published %s -> %s\n", topic, number.c_str());
|
Serial.printf("[DIAL] publish %s -> %s ok=%d\n", topic, number.c_str(), ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mqttCallback(char* topic, byte* payload, unsigned int len) {
|
static void mqttCallback(char* topic, byte* payload, unsigned int len) {
|
||||||
@@ -254,6 +258,7 @@ static void mqttLoop() {
|
|||||||
|
|
||||||
static void dialLoop() {
|
static void dialLoop() {
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
|
static int lastLoggedPulses = 0;
|
||||||
|
|
||||||
// Snapshot volatile state atomically
|
// Snapshot volatile state atomically
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
@@ -261,24 +266,46 @@ static void dialLoop() {
|
|||||||
unsigned long lastPulse = dialLastPulse;
|
unsigned long lastPulse = dialLastPulse;
|
||||||
interrupts();
|
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
|
// Burst silence exceeded → digit complete
|
||||||
if (pulses > 0 && (now - lastPulse) >= INTER_PULSE_MS) {
|
if (pulses > 0 && (now - lastPulse) >= INTER_PULSE_MS) {
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
dialPulses = 0;
|
dialPulses = 0;
|
||||||
interrupts();
|
interrupts();
|
||||||
|
lastLoggedPulses = 0;
|
||||||
|
|
||||||
|
dialRawCount++;
|
||||||
// 10 pulses = digit '0'; 1–9 pulses = digits '1'–'9'
|
// 10 pulses = digit '0'; 1–9 pulses = digits '1'–'9'
|
||||||
char digit = '0' + (pulses >= 10 ? 0 : pulses);
|
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;
|
dialNumber += digit;
|
||||||
dialLastDigit = now;
|
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
|
// Number complete when no further digits arrive within the timeout
|
||||||
if (dialNumber.length() > 0 && (now - dialLastDigit) >= INTER_DIGIT_MS) {
|
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);
|
mqttPublishDialed(dialNumber);
|
||||||
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