← All posts

I Built a Vision Water Reminder That Sleeps Until It’s Time to Drink

Prateek Mehra · jalLijiye · 5 min read

Jal lijiye meme
About this meme

The problem

Sitting at a desk all day, it’s easy to forget to drink water. So I built jalLijiye (Hindi for “please have some water”): a macOS menu bar app that reminds me, then uses the webcam to check that my water bottle actually shows up. No bottle, no peace.

v0

The first version was the obvious one. It watched me all day. The webcam ran at 5 FPS, a person model checked I was at my desk, a custom bottle detector looked for my bottle, and a small heuristic guessed whether the bottle was near my mouth. A state machine turned all that into “drank” or “didn’t drink”.

Original always-on detection pipeline
v0 | High level diagram
Original application demo

It worked, and it was a bad citizen. On my M1 MacBook Air, Activity Monitor showed the Python process at about 149% of a core on CPU, and about 70% on the Apple GPU path (MPS). That is a lot of resource taken for a reminder. A water reminder should not be the loudest thing on the machine.

Original resource usage

The idea: stay asleep until it matters

The fix was a design change, not a faster model. Most of the day the app has nothing to do, so it shouldn’t do anything.

The new flow is shown below. The app waits for the reminder interval before it opens the camera or loads the ML runtime. When a reminder is due, it shows a red “Drink water” prompt in the menu bar and runs only the bottle detector for up to 60 seconds. If the bottle shows up in at least 5 of the last 10 sampled frames (at 1FPS), the reminder has done its work and it goes back to sleep. If not, it stops, keeps the prompt visible and retries after a 10 minute snooze. It also restarts the check window after the Mac wakes, and it only runs on my work-from-home days.

I also dropped the person and face models. The bottle is the only thing the app looks for now.

Reminder-triggered bottle detection flow

Part 1: train a bottle detector that knows my bottle

A generic detector isn’t great at one specific checkered bottle on my desk. So I recorded short webcam clips, pulled one frame per second, and had MLX SAM 3 (Native Apple Silicon Implemetation of SAM 3) draft the labels. I reviewed every label preview by eye and fixed the misses. That gave a small dataset of 92 frames, split in time order (60 train, 16 validation, 16 test) so near-identical neighbouring frames wouldn’t leak across splits.

Annotated bottle training frames
Annotated frames | YOLOv8n

I trained YOLOv8n and YOLO26n on it. YOLOv8n found the bottle in all 14 positive test frames. The first YOLO26n run stopped early and found none. Retrained for the full 30 epochs it recovered to 11 of 14. YOLOv8n stayed in the app.

YOLO bottle detector comparison

Part 2: PyTorch on MPS vs Core ML

Next I exported the model to Core ML (FP16) and compared it with PyTorch on MPS, using frames replayed from a recorded clip. This was done to evaluate the consequence of the power efficiency and ANE (Appled Neural Engine) utilization by CoreML. This was an isolated inference test, not the full app.

So Core ML was a little faster and lighter on memory, but not a free win on CPU. I’m sharing that on purpose.

PyTorch MPS and Core ML comparison

Part 3: just check less often

The biggest saver wasn’t the model. It was the schedule. With the app idle between reminders and checking at 1 FPS, my Activity Monitor readings during checks were roughly 0.2 to 12% of one core, with GPU use near zero. Moving to 5 FPS pushed it to about 4 to 15%. I also tried an INT8 model and a 320x320 FP16 model. They didn’t show a clear improvement in what I measured, and I haven’t measured the 320 model’s accuracy yet.

Resource usage at different frame rates

What I’d tell you before you trust these numbers

The conclusion

The thing that made jalLijiye cheap (~6% CPU, ~1% GPU) wasn’t a clever model. It was deciding when not to run one. Going from an always-on person and bottle pipeline at well over 100% of a core to a reminder that sleeps, wakes, looks for one bottle and goes back to sleep brought it down to a low single-digit to low double-digit percent of a core while it’s checking, and idle the rest of the time.

The app is now live in my MacBook Air M1 24x7. I plan to include a minor dashboard where I can monitor my last week’s hydration activity.

If you’re building something that watches the camera, ask first whether it needs to watch all the time.

The code, training workflow and every measurement above are in the repo: github.com/prateek-mehra/jalLijiye.