Skip to content

Latest commit

 

History

48 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ClickNovaShutter

CI

Note

ClickNova Shutter is a DIY Bluetooth camera remote for iOS, built on a LilyGO TTGO T-Display (ESP32). It pairs with an iPhone as a BLE HID Consumer Control device and triggers the native Camera app over the volume-up shutter shortcut — no companion app needed. It supports single-press shooting, a timelapse/astrophotography intervalometer with selectable cadence and shot count, automatic reconnection to the bonded phone, and deep sleep with wake-on-shutter for months of battery life on a single 18650 cell. The 1.14" color display shows pairing, reconnect, battery, and error states at a glance. Firmware is C++/Arduino on PlatformIO with NimBLE, and most of the decision logic lives in header-only modules covered by 120+ native unit tests. GitHub Actions runs the full test suite and a clean firmware build on every push, and tags produce validated factory images. This is a fully vibecoded side project — designed, implemented, tested, and stabilized in conversation with an AI coding agents (chatgpt5.6sol/kimi3), then verified on real hardware against an iPhone. It reliably takes exactly one photo per press, which is all it was ever meant to do.


IMG_0157 IMG_0156

ClickNovaShutter is greenfield firmware for turning an original ESP32-based LilyGO TTGO T-Display into a dedicated Bluetooth camera shutter for an iPhone. It is designed to pair securely, reconnect to one bonded phone, send one BLE HID Volume Increment action per shutter press, and show connection and battery status on the built-in display.

Release status: v1.1 is functionally accepted on the original T-Display and iPhone 16 Pro. It preserves v1.0 behavior, adds bounded automatic bonded reconnect recovery, and publishes one tag-gated Linux factory image.

Target hardware and pins

The only v1 target is the original 240x135 LilyGO TTGO T-Display with an ESP32 and integrated 1.14-inch ST7789 TFT. T-Display S3 and other similarly named boards are not compatible targets.

Function GPIO Notes
Shutter button / deep-sleep wake 35 Active-low; use plain input mode because GPIO35 has no internal pull-up
Utility button 0 Active-low
Battery voltage ADC 34 Reads the board's switched voltage divider
Battery-divider enable 14 Drive high only while sampling
TFT MOSI 19 Project-local TFT configuration
TFT clock 18 Project-local TFT configuration
TFT chip select 5 Project-local TFT configuration
TFT data/command 16 Project-local TFT configuration
TFT reset 23 Project-local TFT configuration
TFT backlight 4 PWM brightness control

Battery safety

Use only a protected 1S 3.7 V 18650 Li-ion cell connected through the board's two-pin JST battery socket. Confirm the board's + and - markings against the actual plug with a multimeter before connection: two-pin JST leads are not universally wired with the same polarity. Reversed polarity can permanently damage the board or battery and can create a fire hazard. Do not connect a multi-cell pack, an unprotected cell, or a damaged cell.

Intended behavior

  • Advertise as ClickNova Shutter and bond securely with one iPhone.
  • Act as an encrypted BLE HID Consumer Control device. A valid shutter press sends Volume Increment (0xE9), followed by a release, exactly once.
  • Ignore shutter presses until the encrypted HID connection is ready; presses while disconnected are never queued for later.
  • Recover a bonded connection that remains non-ready for 15 seconds, and refresh filtered reconnect advertising through a directed attempt every 15 seconds without deleting the bond or rebooting.
  • Optionally run a finite intervalometer at a selectable 2, 5, 10, 15, 30, or 60-second start-to-start cadence for 10, 25, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, or 1000 shutter actions.
  • Keep the screen at full brightness for 60 seconds of inactivity, dim it to 10% for a further 10 minutes, then enter deep sleep at 11 minutes total.
  • Re-sample the battery every 10 minutes. The ready view shows an orange warning bar with a Low battery - charge soon footer at 10% or below, and the device enters protective deep sleep at 5% or below—even while an intervalometer session is running—to protect the cell from deep discharge.
  • Wake from deep sleep with the shutter button. The wake-causing press only wakes the device; release it before taking a photo.
  • Use a short utility-button press to restore full brightness and refresh the battery estimate.
  • Start bond reset after a ten-second utility-button hold. After release, confirm with another short utility press within 10 seconds; timeout or a shutter press cancels the reset.

The shutter command works through iOS's volume-button behavior. The native Camera app must already be open in the foreground; the remote cannot launch Camera, select a mode, or confirm that iOS actually saved a photo.

Build, flash, and monitor

The reproducible PlatformIO configuration uses these pinned versions:

  • PlatformIO platform espressif32@7.0.1
  • Board lilygo-t-display
  • Arduino framework
  • NimBLE-Arduino@2.5.0
  • TFT_eSPI@2.5.43, using a project-local T-Display setup

Install Git, Python 3 with its venv module, and ordinary serial-port access for your user account. No global PlatformIO installation is required.

The following Linux commands create an isolated development environment and run both quality gates:

python3 -m venv .venv
.venv/bin/python -m pip install -r requirements-dev.txt
.venv/bin/pio test -e native
.venv/bin/pio run -e lilygo-t-display

Connect the T-Display with a USB data cable. The original board normally appears as a Silicon Labs CP210x bridge (10c4:ea60) and a serial port such as /dev/ttyUSB0. Find the current port after each reconnect because its number may change:

.venv/bin/pio device list
lsusb

Flash and monitor using the discovered port:

.venv/bin/pio run -e lilygo-t-display -t upload --upload-port /dev/ttyUSB0
.venv/bin/pio device monitor --port /dev/ttyUSB0 --baud 115200

Exit the monitor with Ctrl-C before another upload. A successful boot starts its serial log with firmware=1.2.0, reports the 240x135 display, and then reports either general pairing or stored-bond reconnect advertising.

Factory release image

Starting with v1.1, each accepted vX.Y release publishes one ClickNovaShutter-vX.Y-factory.bin for Linux. v1.0 is not backfilled. Verify the image's SHA-256 value against the digest in that release's notes before flashing.

The factory image starts at 0x0 and clears the ESP32 NVS bond store. It requires fresh pairing in iPhone Settings and must not be used as an in-place update image.

sha256sum ClickNovaShutter-vX.Y-factory.bin
python3 -m venv .clicknova-flash
.clicknova-flash/bin/python -m pip install esptool==4.11.0
.clicknova-flash/bin/python -m esptool --chip esp32 --port /dev/ttyUSB0 \
  --baud 460800 write_flash --flash_mode dio --flash_freq 40m \
  --flash_size 4MB 0x0 ClickNovaShutter-vX.Y-factory.bin

First pairing and normal use

  1. Build and flash the lilygo-t-display environment, then power or reset the board.
  2. On an iPhone, open Settings > Bluetooth and select ClickNova Shutter while the device shows Pairing Mode.
  3. Wait until the display reports Ready. Securing and Connecting are normal intermediate states.
  4. Open Apple's Camera app in the foreground.
  5. Press and release the shutter button once for one photo.

After a successful bond, rebooting the device should reconnect without a new pairing prompt. Shutter presses made before Ready are discarded and are never replayed after reconnection.

Controls

Control Action
Shutter press while Ready Send one camera shutter action
Shutter press while disconnected Consume the press without sending or queueing it
Utility release before 1 second Restore full brightness and refresh battery
Utility release from 1 to under 2 seconds No secondary action
Utility release from 2 to under 5 seconds while Ready Enter intervalometer setup
Utility release from 5 to under 10 seconds No secondary action
Utility hold for 10 seconds Show bond-reset prompt; release to open confirmation
Short Utility release during reset confirmation Delete bonds and reboot
Shutter during reset confirmation Cancel reset without taking a photo

To use the intervalometer while Ready, hold Utility for at least two but less than five seconds and release. Shutter cycles each value and Utility confirms it. Confirm the interval, then the action count; the first action is immediate. Either button stops a running session. A Bluetooth loss aborts it without replay or automatic resume.

The UI shows a thin red bar while Utility is held on the normal Ready view. It fills to the two-second entry threshold, stays full and bright while release will enter setup, and clears on release, connection loss, mode change, or at the five-second interval-entry release limit. It remains hidden while HID is unavailable. Setup shows the total plan duration prominently.

The landscape UI follows the real BLE lifecycle with distinct pairing, reconnecting, securing, connecting, ready, recovery, and error views. An accepted manual shutter notification produces a brief full-screen flash, then returns to the current underlying view. The ready screen displays an approximate battery percentage from a protected calibrated sample, or BATTERY --% when the reading is unavailable or implausible.

Sleep and wake

After 60 seconds without accepted activity, the backlight dims to 10%. After a further 10 minutes—11 minutes total—the firmware disconnects BLE, turns off the display and divider supply, and enters deep sleep. Press Shutter to wake it. The wake-causing press is consumed; release it, wait for Ready, then make a new complete press to take a photo. A running intervalometer remains awake.

Changing phones or clearing a stale bond

  1. Hold Utility continuously for 10 seconds until the reset prompt appears.
  2. Release Utility.
  3. Short-press and release Utility within 10 seconds.
  4. Wait for Bonds Cleared, reboot, and Pairing Mode.
  5. On the old iPhone, forget ClickNova Shutter if it is still listed.
  6. Pair the intended iPhone from Settings > Bluetooth.

Waiting 10 seconds or pressing Shutter during confirmation safely cancels the reset and retains the bond.

Serial diagnostics

The 115200-baud log is the primary troubleshooting record. Useful prefixes are:

  • [boot] for firmware version, reset reason, and wake reason.
  • [init] for pins, display dimensions, backlight PWM, and BLE advertising.
  • [event] for connection, security, HID subscription, and shutter reports.
  • [state] for BLE, power, UI, intervalometer, and error projections.
  • [battery] for raw/converted millivolts, sample count, validity, and time.
  • [reconnect] for phase, elapsed time, retry attempt, action, and result.
  • [reset] and [sleep] for confirmed reset and shutdown/wake sequences.
  • [error] for fail-closed initialization or recoverable runtime failures.

Troubleshooting

  • No serial port: use a data-capable USB cable, reconnect the board, and rerun pio device list. The port may return as /dev/ttyUSB0 even if it previously used another number.
  • Permission denied on /dev/ttyUSB*: add the user to the distribution's serial-access group (commonly dialout), sign out/in, and verify the port permissions. Do not run the complete toolchain as root.
  • Upload says the port is busy: exit every serial monitor with Ctrl-C, confirm the current port, and retry the explicit --upload-port command.
  • ClickNova is absent from Other Devices: confirm the board says Pairing Mode. Toggle Bluetooth off/on in the iPhone Settings app, remain on the Bluetooth page, and reset the board once. If an old entry exists, use the confirmed bond reset before forgetting that entry on iOS.
  • iPhone says connected but ClickNova is not Ready: wait for Securing and Connecting to finish. v1.1 automatically drops a non-ready link after 15 seconds and retries; allow up to 30 seconds before manually toggling Bluetooth in Settings. Do not press Shutter until Ready.
  • No photo: put Apple's native Camera in the foreground, confirm Ready, and use a new complete press/release. ClickNova cannot launch Camera or confirm that iOS saved the image.
  • BATTERY --%: the ADC result was unavailable or outside the plausible single-cell range. This is intentionally safer than displaying a fabricated percentage; verify battery polarity/source and inspect the [battery] log.
  • Wrong display or pin behavior: confirm the board is the original 240x135 ESP32 T-Display, not a T-Display S3 or another similarly named model.

Documentation

Where the original requirements or task inventory disagree with the implementation plan, follow the implementation plan.

Quality and current limitations

The 118-case native logic suite, four release-tool tests, local and hosted pinned firmware builds, fail-closed CI gates, and all recorded v1.0 and v1.1 hardware gates pass. The project does not claim measured shutter latency or microamp deep-sleep current without suitable instrumentation. Android, multiple-phone selection, OTA updates, camera launching, and other T-Display variants are outside v1.

About

ESP32 based iOS remote shutter

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages