Exploded Onshape assembly of the wristband with labeled parts: capacitive touch pad, printed housing, vibration motor, MPU6050 IMU, printed base, RJ45 tether socket, and thermistor on the skin side
All work

Model-Based Gesture Detection for a Wine Tasting Wristband

/wristband

A wristband that talks you through a wine tasting, checks from your wrist motion that you actually swirled, sniffed, and sipped, and stamps every sensor reading with the moment each step happened.

Context
Final project, Application of Sensing Systems (EN.665.681), Johns Hopkins
Timeline
Summer 2026
Data
1,026 labeled windows from 122 gestures

Tools & skills

Embedded
Raspberry Pi Pico 2 W, dual-core firmware, Wi-Fi dashboard
Sensing
MPU6050 IMU, NTC thermistor, PPG pulse sensor, MAX4466 microphone, PN532 NFC
Models
Random forest, on-device rule-based detector, Whisper speech recognition
Mechanical
Onshape, 3D-printed wristband and hub housings

The problem

Food scientists can read how much someone likes what they taste from their body: skin temperature, skin conductance, heart rate, the voice. But these measurements only mean something inside a short window, two to three seconds, starting at the exact moment the sample enters the mouth. A few seconds late and you are measuring chewing instead.

In every study I reviewed, that moment came from an experimenter in a lab booth who handed over the sample and knew when it happened. Nobody drinks wine in a booth. Take the experimenter away and the method has nothing to line its window up against.

My approach: let the device play the experimenter. It tells you what to do next (swirl, sniff, sip), watches your wrist until it sees you do it, and only then moves on. The instant it confirms the gesture becomes the start of the measurement window. If it never sees the gesture, it waits and asks again instead of logging a wrong sample.

Where the mechanical design meets the electronics

I split the hardware by what each part has to do on the table. The wristband only carries what has to be on the wrist: the IMU, a thermistor pressed against the skin, a capacitive touch pad to start a session, and a vibration motor for prompts. It has no processor. All of its signals run back to the hub over one CAT6 cable, which gives me eight conductors in a single flexible tether.

The hub holds everything that works better sitting still: a Raspberry Pi Pico 2 W, the microphone, the pulse sensor, and the NFC reader. The lid has a circular pocket for the glass, with the NFC reader directly underneath. Setting the tagged glass down in the pocket is what identifies the wine. I designed it that way on purpose: reading the wine is a placement, not a hand motion, so it can never be confused with the gestures the classifier is watching for.

The pulse sensor is the one part left outside the case, so a taster can hold it against a fingertip without opening anything. Its cable runs in through the ventilation holes in the enclosure wall.

Exploded Onshape assembly of the hub with labeled parts: printed lid with glass pocket, PN532 NFC reader, thermistor RC filter board, RJ45 breakout, pulse sensor outside the case, Pico 2 W breakout, printed base, screw-terminal board, and MAX4466 microphone
The hub, exploded. The NFC reader sits directly under the glass pocket in the lid; a screw-terminal board lands every line from the tether.
Blue 3D-printed wristband housing strapped to a forearm with a velcro band, a capacitive touch module on top, and a tether cable leaving the end
As built: the wristband on a velcro strap, touch pad on top, tether out the end.
Wine glass seated in a circular pocket on the lid of a white 3D-printed hub, with the red NFC reader board visible beneath the glass
As built: a tagged glass seated in the hub's pocket, over the NFC reader.

The housing design fed straight back into the software. Midway through the project I remounted the IMU while fitting it into the enclosure, and the trained model stopped confirming gestures immediately. The features that separate a sip from a sniff depend on how the sensor sits on the wrist, so a change in the mount is a change in the data. I recollected and retrained in the final mounting orientation, and every result below comes from that orientation.

Sensor / actuatorLocationInterfaceRate
MPU6050 6-axis IMUWristbandI²C0 (GP8 / GP9)50 Hz
NTC thermistor, 10 kΩWristband, against the skin (filter board in hub)ADC0 (GP26)10 Hz
Capacitive touch padWristband, top faceDigital in (GP16)Event
Vibration motorWristbandPWM (GP15)Output
MAX4466 microphoneHubADC1 (GP27)16 kHz
PPG pulse sensorHub, outside the case (fingertip)ADC2 (GP28)100 Hz
PN532 NFC readerHub, under the glass pocketI²C1 (GP2 / GP3)Event

Four sample rates on one small chip

The Pico samples four channels at once, at rates that differ by more than a thousand times: audio at 16 kHz, pulse at 100 Hz, motion at 50 Hz, and skin temperature at 10 Hz. Audio sets the hardest limit. At 16 kHz a late sample is not just late, it is a hole in the waveform.

So I gave audio a core of its own. The second core does nothing but capture sound in a tight timing loop. The first core runs everything else as cooperative tasks: the IMU, pulse, and temperature readers, the USB link to the PC, and a small Wi-Fi dashboard. Under full load the slower channels held 87 to 98 percent of their target rates. Audio reached about half its nominal rate, so each recording is stamped with the rate actually measured rather than the one requested.

Timeline showing Core 1 running continuous 16 kHz audio capture while Core 0 interleaves USB sending, IMU, pulse, temperature, and event-driven dashboard tasks
Dual-core split. Core 1 captures audio without pausing; Core 0 interleaves every other task, so USB and Wi-Fi traffic never open a gap in the audio.

Cleaning the signals on the device

Every channel leaves the device already in physical units, so the PC never has to guess what a raw count meant.

Pulse to beats per minute. The pulse sensor is an optical sensor (a photoplethysmograph): its output is a wavy ADC signal whose peaks are heartbeats, riding on a slowly drifting baseline. Two moving averages pull them apart. A fast one (α = 0.3) follows the waveform, a slow one (α = 0.01) follows the drift, and their difference is the pulse alone. A beat counts when that difference crosses a threshold set at half the recent peak height, with a floor above the noise I measured with no finger on the sensor. Each heartbeat also has a smaller second bump about half a second later. I measured it at 460 to 550 ms, so the detector ignores any crossing within 500 ms of the last beat. The reported rate is the median of the last five beat-to-beat intervals, and an interval roughly double the median (a missed beat) is kept out of it.

Thermistor to degrees. The thermistor sits in a voltage divider. The firmware converts the ADC reading to resistance and then to temperature with the β-form of the Steinhart–Hart equation (β = 3950). The cable adds brief contact-resistance spikes, so a five-sample median filter removes them at the cost of about 0.2 s of lag.

Motion to features. The IMU stream is cut into one-second windows that overlap by half. A ten-second still period at the start of each session measures the gyroscope's zero offset and the direction of gravity. From each window the model gets 24 features: how often the gyroscope crosses zero (a swirl is periodic), peak acceleration (a sip has a lift), wrist pitch and roll (a sniff tilts less than a sip), and rotation measured around the gravity axis, which describes a swirl however the band happens to sit.

Grid of wrist-IMU traces for swirl, sniff, sip, and negative examples showing gyro Z, acceleration magnitude, and wrist pitch over time
Real wrist traces. The swirl is a clean oscillation; sniff and sip both start with a lift and differ mostly in how far the wrist tips; the confounders (NEG) look like neither.

Prompt, verify, advance

A session goes: touch the pad, sit still for a resting baseline, set the glass on the hub, then swirl, sniff, sip, and answer three spoken questions about the wine. Each motion step follows the same loop. The device buzzes and speaks the instruction, the classifier watches one-second windows, and the step only counts when two windows in a row agree. That confirmation is logged to the millisecond as the start time for every other channel.

Two different detectors can confirm a step: a random forest on the PC and a set of hand-tuned rules on the Pico. The PC also runs speech recognition (Whisper), a neural voice for the prompts, and a local language model for the conversation, with nothing sent to the cloud. The step instructions themselves are fixed text, not generated, because the part of the system that must never drift should not depend on the part that can.

Control loop diagram: prescribed step, prompt by haptic and speech, wearer, IMU at 50 Hz, classifier, verification by two agreeing windows, then advance state and log anchor t equals zero, with a wait path back to the wearer
The guidance loop. If verification never comes, the loop waits and re-prompts rather than advancing.

Results

I trained on 1,026 labeled windows from 122 gesture instances. The recording firmware paced itself with haptic cues, so each label boundary is the exact moment the device asked, not a person's reaction time. Besides the three gestures and rest, I recorded a set of deliberate confounders: reaching for the glass, setting it down, and talking with my hands. Cross-validation folds were split by gesture instance, so overlapping windows from the same gesture never end up on both sides.

15 / 15
sniffs confirmed
15 / 15
sips confirmed
11 / 15
swirls confirmed
1.5 s
median prompt-to-confirm
2.5 s
worst case
1.0 %
false positives on confounders

Prompted detection, measured on held-out gestures with the same two-window rule the device uses. Every confirmation falls inside the 2–3 s windows the physiology methods need.

Two row-normalized confusion matrices comparing the on-device rule-based classifier and the random forest across swirl, sniff, sip, rest, and negative classes
The random forest (right) reaches 0.66 accuracy and 0.69 macro F1, against 0.42 / 0.35 for the on-device rules (left). Sniff and sip reach 93 % recall; rest holds only 49 %.

That rest row shaped the whole design. The model is good at telling you which gesture a window looks most like, and poor at telling you nothing is happening. When I replayed a full recording through the model continuously, it claimed a gesture during 69 % of the quiet time, and no confidence threshold fixed it. Asking for one specific gesture and waiting for it turns an open question the features cannot answer into a yes-or-no question they answer well.

End to end, I ran two complete guided sessions. Every motion step was confirmed from the wrist alone, the two detectors agreed on all six confirmations, and each session produced a millisecond event log that ties every sample on every channel to a named step of the tasting.

Demo

A full session recorded from the live dashboard, with the spoken prompts audible. The dashboard shows the current step, live motion readings, pulse, skin temperature, and the wine read from the tag.

One complete tasting, about five minutes. Turn the sound on to hear the prompts.
Wine Tasting Wristband web dashboard showing the current instruction, skin temperature, pulse rate, and six IMU readings
The Wi-Fi dashboard served by the Pico during the baseline step.

What is still open

All gesture data comes from one wearer, so these numbers describe the pipeline, not how it would do across many people. The pulse and skin-temperature calibrations are single-point corrections against my own readings and need a proper calibration before the values mean anything absolute. Predicting whether someone likes a wine was out of scope; the device logs the data for that as a byproduct, and a real preference study would need many more tasters.