A Quick Project That’ll Save Me Hours Every Week
This week, I knocked together a quick little side project that doesn’t quite warrant a full-length Stock Pot video — but it’s exactly the kind of thing I like sharing here on The Saucepan. It’s simple, useful, and solves a very specific problem in a very satisfying way.
My Teleprompter Slows Me Down
For the talking-head sections of Stock Pot videos, I use a teleprompter that runs off a tablet. It scrolls the script at a preset speed, which is great… until I get slightly ahead or behind the scroll. Or worse, I want to redo a line — which means walking over to the teleprompter, scrolling with my finger, then walking back to my mark.
Not exactly a massive issue — but enough of a disruption that it breaks my flow and wastes time over the course of a full shoot.
What I needed was a remote. But I didn’t want a handheld one that I’d be constantly picking up and putting down.
So instead, I built this: a 3D-printed Bluetooth foot pedal with three momentary switches, powered by an ESP32 microcontroller and a leftover LiPo battery from the Everything Remote project.
It emulates a Bluetooth keyboard and connects directly to my tablet. The teleprompter app I use lets me map keyboard shortcuts to controls — so I now have:
Play / pause
Rewind / fast forward
Speed up / slow down
Each button supports short and long presses, which gives me six mappable functions — all without taking my hands off my lap or moving from my mark.
How It’s Built
Inside the 3D-printed PETG enclosure, it’s incredibly simple:
3 momentary foot switches
ESP32 microcontroller
A small LiPo battery
Wired to 3 GPIO pins with some hookup wire
The enclosure’s a bit rough around the edges — a half-day build, after all — but it works perfectly and feels great to use. Best of all, it cost almost nothing to make, and probably saved me more time during the very next shoot than it took to build.
Want to Build Your Own?
If you’d like to make something similar, you can find the files at the link below, and the code under this post! It’s a super easy project, and a great intro to ESP32, Bluetooth HID, and making little quality-of-life tools for your creative workflow.
Prints
Parts Required
4 x M3 x 10 Cap head screws
1 x Lolin ESP32 LITE
1 x Lipo battery
Code
#include <BleKeyboard.h> #include <Arduino.h> // ------------------- CONFIG ------------------- #define BUTTON_1 12 #define BUTTON_2 14 #define BUTTON_3 27 #define BATTERY_PIN 35 #define LED_PIN 2 // Built-in LED on Lolin D32 #define LONG_PRESS_TIME 1000 #define SLEEP_TIMEOUT 3600000 #define BATTERY_READ_INTERVAL 60000 BleKeyboard bleKeyboard("ESP32 Remote", "YourBrand", 100); unsigned long lastActivity = 0; unsigned long lastBatteryUpdate = 0; struct Button { uint8_t pin; bool lastState; unsigned long pressStart; bool longPressActive; }; Button buttons[3] = { {BUTTON_1, HIGH, 0, false}, {BUTTON_2, HIGH, 0, false}, {BUTTON_3, HIGH, 0, false} }; // ------------------- BATTERY ------------------- uint8_t readBatteryLevel() { int raw = analogRead(BATTERY_PIN); float voltage = (raw / 4095.0) * 3.3; uint8_t level; if (voltage >= 4.2) level = 100; else if (voltage >= 4.0) level = 80; else if (voltage >= 3.7) level = 60; else if (voltage >= 3.5) level = 40; else if (voltage >= 3.3) level = 20; else level = 10; return level; } void updateBatteryLevel() { if (bleKeyboard.isConnected()) { uint8_t level = readBatteryLevel(); bleKeyboard.setBatteryLevel(level); } } // ------------------- LED ------------------- void ledBlink(int times, int delayMs) { for (int i = 0; i < times; i++) { digitalWrite(LED_PIN, HIGH); delay(delayMs); digitalWrite(LED_PIN, LOW); delay(delayMs); } } void updateLEDStatus() { if (!bleKeyboard.isConnected()) { ledBlink(1, 200); } else { digitalWrite(LED_PIN, HIGH); } } // ------------------- SETUP ------------------- void setup() { Serial.begin(115200); analogReadResolution(12); if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1) { Serial.println("Woke from button press"); } bleKeyboard.begin(); for (int i = 0; i < 3; i++) { pinMode(buttons[i].pin, INPUT_PULLUP); } pinMode(BATTERY_PIN, INPUT); pinMode(LED_PIN, OUTPUT); lastActivity = millis(); lastBatteryUpdate = millis(); } // ------------------- PAIRING ------------------- void enterPairingMode() { Serial.println("Entering pairing mode..."); bleKeyboard.end(); delay(1000); bleKeyboard.begin(); } // ------------------- SLEEP ------------------- void goToSleep() { Serial.println("Going to deep sleep..."); esp_sleep_enable_ext1_wakeup( (1ULL << BUTTON_1) | (1ULL << BUTTON_2) | (1ULL << BUTTON_3), ESP_EXT1_WAKEUP_ANY_HIGH ); gpio_hold_en((gpio_num_t)BUTTON_1); gpio_hold_en((gpio_num_t)BUTTON_2); gpio_hold_en((gpio_num_t)BUTTON_3); digitalWrite(LED_PIN, LOW); esp_deep_sleep_start(); } // ------------------- LOOP ------------------- void loop() { unsigned long now = millis(); bool anyPressed = false; if (now - lastBatteryUpdate >= BATTERY_READ_INTERVAL) { updateBatteryLevel(); lastBatteryUpdate = now; } updateLEDStatus(); for (int i = 0; i < 3; i++) { bool state = digitalRead(buttons[i].pin) == LOW; // Button pressed if (state && !buttons[i].lastState) { buttons[i].pressStart = now; buttons[i].longPressActive = false; } // Long press activate if (state && !buttons[i].longPressActive && now - buttons[i].pressStart >= LONG_PRESS_TIME) { if (bleKeyboard.isConnected()) { if (i == 0) bleKeyboard.press(KEY_PAGE_DOWN); else if (i == 1) bleKeyboard.write(KEY_UP_ARROW); // Up arrow does not hold else if (i == 2) bleKeyboard.press(KEY_PAGE_UP); } buttons[i].longPressActive = true; lastActivity = now; } // Button released if (!state && buttons[i].lastState) { if (!buttons[i].longPressActive) { if (bleKeyboard.isConnected()) { if (i == 0) bleKeyboard.write(KEY_LEFT_ARROW); else if (i == 1) bleKeyboard.write(' '); else if (i == 2) bleKeyboard.write(KEY_RIGHT_ARROW); } } else { if (bleKeyboard.isConnected()) { if (i == 0) bleKeyboard.release(KEY_PAGE_DOWN); else if (i == 2) bleKeyboard.release(KEY_PAGE_UP); } } lastActivity = now; } buttons[i].lastState = state; if (state) anyPressed = true; } // Pairing mode if (digitalRead(BUTTON_1) == LOW && digitalRead(BUTTON_3) == LOW) { enterPairingMode(); } // Sleep timeout if (now - lastActivity >= SLEEP_TIMEOUT && !anyPressed) { goToSleep(); } delay(10); }