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


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);
}
Previous
Previous

Outsourcing Herb Growing to My Smart Home

Next
Next

My TV Remote Stopped Working… So I Made My Own.