An automatic espresso shot timer that starts when your machine does.
Vibration-triggered Rust firmware for round Waveshare RP2040 and ESP32-S3
boards. No buttons, wires, or plumbing modifications.
What it does · How it works · Housing · Build & flash
No button press, plumbing modification, or connection to the espresso machine is required. Place the timer on the machine, pull a shot, and its QMI8658 motion sensor detects the pump vibration automatically.
- Automatic shot timing from machine vibration, with no button press required.
- Circular progress display and history of the three previous shots.
- Configurable vibration sensitivity, display, battery and sleep settings.
- Motion gesture to switch between the timer and diagnostic display.
- Shared Rust firmware for RP2040 and ESP32-S3 boards.
- Parametric, 3D-printable desktop and magnetic housings for the RP2040 board.
| Board | Firmware | Display | Motion sensor | Status |
|---|---|---|---|---|
| Waveshare RP2040-LCD-1.28 | firmware-rp2040 |
GC9A01, 240×240 | QMI8658 | Hardware-tested |
| Waveshare ESP32-S3-Touch-LCD-1.28 | firmware-esp32s3 |
GC9A01, 240×240 | QMI8658, CST816S touch | Hardware-tested |
Touch switches modes on the ESP32-S3 board; Wi-Fi, Bluetooth, and PSRAM are not used yet.
Two printable, screw-free housings are available for the RP2040-LCD-1.28 board and a MakerFocus 2000 mAh battery: a desktop stand and a magnetic version for mounting directly to the machine.
View on GitHub or MakerWorld.
Dimensions, assembly, and parametric CAD
crates/shottimer-core: timing, motion/orientation, battery estimation, settings.crates/shottimer-app: shared firmware loop, UI, display and IMU drivers.crates/firmware-rp2040: RP2040 board backend.crates/firmware-esp32s3: ESP32-S3 board backend.
Both boards run the same application through embedded-hal drivers and a small
Platform interface that separates shared behavior from board-specific hardware.
Dependencies flow from firmware → app → core; firmware can also use core directly.
Shared crates must not depend on MCU HALs.
flowchart LR
RP[RP2040 firmware] --> APP[shottimer-app]
ESP[ESP32-S3 firmware] --> APP
RP --> CORE[shottimer-core]
ESP --> CORE
APP --> CORE
After the three-second RGB display test, keep the display still and face-up during boot calibration. The default configuration then starts in Timer mode. In touch mode (the ESP32-S3 default) calibration is skipped.
- Detects vibration from the largest per-axis standard deviation across ten acceleration samples taken at 10 ms intervals.
- Starts a shot after two seconds of continued vibration and counts in calibrated 975 ms increments, resetting at 99 displayed seconds.
- Stops after a quiet timer bucket and corrects the result to the last detected vibration, excluding the stop-confirmation delay. Shots shorter than five seconds are discarded.
- Retains valid results for 60 seconds. Three seconds of continued vibration
replaces a retained result; the new timer first appears at
3. - Shows three previous valid shot times, newest first, excluding the current result. History survives resets, mode switches, and sleep, but clears on reboot.
The progress arc fills in two brown shades over two 25-second laps, remaining full after 50 seconds while the number continues.
Hold the LCD face-down for 0.5 seconds to toggle Timer and Debug modes. No prior
face-up state is needed. Holding it down triggers only once; move it to any
non-down orientation long enough to pass the release debounce before repeating.
Orientation filtering adds some response time; switching resets
the active timer. In touch mode, swipe up or down instead; "up" follows the
display rotation, and horizontal swipes are reserved. Set
debug_mode_enabled = false to disable Debug mode and both gestures.
Debug mode shows IMU address/errors, acceleration mean and standard deviation, filtered orientation, vibration threshold and rolling peak, timer/idle state, and battery voltage, estimated charge, ADC value, and trend.
Both boards log acceleration and orientation twice per second: RP2040 through USB CDC, ESP32-S3 through its CH343 UART bridge at 115200 baud. Logging and its interval are configurable; disabling logs leaves the serial interface available.
Each board has a config set in configs/: esp32s3.toml and
rp2040.toml. See config.md for the settings, vibration tuning,
sleep, battery and display options, and hardware assignments. Changes require
rebuilding and flashing the firmware.
Install rustup and use its Cargo/Rust binaries (put
~/.cargo/bin before any system Rust in PATH). Rustup installs the pinned
toolchain, components and RP2040 target from rust-toolchain.toml. Host checks
require no board:
cargo fmt --all -- --check
cargo test --locked
cargo clippy --locked --all-targets -- -D warningsrustup target add thumbv6m-none-eabi
cargo install elf2uf2-rs
cargo rp2040Hold BOOT while connecting USB, then flash:
cargo run --release -p firmware-rp2040 --target thumbv6m-none-eabiELF: target/thumbv6m-none-eabi/release/shottimer-rp2040.
The target runner converts it to UF2 and copies it to the BOOTSEL drive.
Install the Espressif Rust toolchain:
cargo install espup espflash
espup install --targets esp32s3
. ~/export-esp.sh
cargo +esp esp32s3Source the export script in each shell; if a custom export path was used, source
that instead. The esp toolchain and -Zbuild-std=core are required for Xtensa.
Connect the board over USB and flash via its CH343 serial bridge:
cargo +esp run --release -p firmware-esp32s3 \
--target xtensa-esp32s3-none-elf -Zbuild-std=coreELF: target/xtensa-esp32s3-none-elf/release/shottimer-esp32s3.
The runner uses espflash with ESP32-S3, 16 MB flash, and serial monitoring.
If automatic download fails, hold BOOT, press RESET, and retry.
Do not flash an RP2040 UF2 onto ESP32-S3.
Shot detection and the circular timer concept were inspired by
lspr98/profitec-go-waterlevel-shottimer.
This is an independent Rust implementation and contains none of that project's
source code. The referenced repository did not declare a software license when
this acknowledgement was written.
This project is available under either the Apache License 2.0 or MIT License.
Built for espresso machines that know when the shot begins.
View on GitHub

