Join Milos as he tackles a familiar problem: it’s the holiday season, there’s lots of delicious food and candy everywhere, and it’s too cold to go out much. To help with that, Milos created a Smart Cookie Jar that keeps you true to your word. The jar locks itself with all those treats inside and won’t open until you reach your running goal!
In Milos' own words:
“It’s the holiday season… But if you want to get to that candy, we’ll make something that will make you run first.”
Watch the Project
What's the Idea
Milos first built this project more than six years ago, back before he owned a 3D printer. He’s rebuilt it now to make it smaller and more refined, taking advantage of 3D printing and modern microcontrollers.
The original mechanism was hand-cut from 4 mm plywood; the small push rods used sheet metal salvaged from a gutter, there was plenty of room for improvement.
This time he decided to use an established fitness app (Strava) to get activity data instead of a crude step counter app. The design brief was simple: the jar must look like an ordinary jar from the outside, no obvious electronics, just a jar full of candy.


Enacting the Mechanism
Milos began with the mechanism because it is the most critical part of the system. Instead of linear pistons in the original, the new lid uses small gears with swinging arms that extend to lock the lid in place. The whole mechanism is designed to be 3D printed and to use a very small servo (the common SG90).
The mechanism is compact and straightforward to assemble. All electronics are contained inside the lid: an ESP32-S3 microcontroller (for Wi-Fi), a battery, and a battery charger module. Optionally, you can add a power switch.
A quote from the video where Milos describes the mechanism:
“In the centre we have the servo horn… and around that we have the three pistons that move linearly. So they’re connected together using this small pushrod… For the new version, I want to go with gears.”
MR20 - Cookie Jar V2 - Video V1
The new lid packs everything tightly; from the outside (when unlocked) there’s no visual clue that anything special is inside — just candy.

Setting up the Software
The jar uses the Strava API to determine whether the running goal has been achieved. Milos chose Strava because he uses it personally and it offers a straightforward developer API.
As Milos says:
“To create a small web server on the ESP32, which will just grab the JSON packages from the Strava app so we can actually see my latest activity here.”
Strava flow (summary)
-
Create a Strava API application (Settings → My API Application) and note
client_idandclient_secret. -
Build and open the authorise URL to obtain the temporary
code. -
Exchange the
codeforaccess_tokenandrefresh_token(the example cURL is shown below). -
Copy the tokens into the Arduino sketch and flash the ESP32-S3. The code refreshes tokens automatically as needed.
-
Open the device web page and choose Show last activity to verify the device returns your latest Strava activity with HTTP 200.
The video also explains the UI / user-flow: set a distance goal (for example, 6 km) and the jar unlocks when recent activity meets the goal.
“It will just give you the name of your last activity, the distance you ran… ”
Key code excerpts (from CookieJarV2.ino)
Below are short, focused excerpts from the project code to explain the important behaviours. These are included to show how the software actually operates.
Configuration constants (Wi-Fi, Strava tokens and servo settings) - set these before flashing:
// Wi-Fi credentials const char *WIFI_SSID = "YOUR-SSID"; const char *WIFI_PASSWORD = "YOUR-PASSWORD"; // Strava tokens and keys (example placeholders) const char *STRAVA_CLIENT_ID = "YOUR_STRAVA_CLIENT_ID"; const char *STRAVA_CLIENT_SECRET = "YOUR_STRAVA_CLIENT_SECRET"; const char *STRAVA_ACCESS_TOKEN = "YOUR_STRAVA_ACCESS_TOKEN"; const char *STRAVA_REFRESH_TOKEN = "YOUR_STRAVA_REFRESH_TOKEN"; // Servo configuration const int PIN_SERVO = 3; // ESP32 GPIO (confirm for your board) const int SERVO_LOCK_ANGLE = 0; const int SERVO_UNLOCK_ANGLE = 110; const int SERVO_PULSE_DELAY_MS = 800;
Servo motion helper - the code attaches the servo, moves to the angle, waits, and detaches. Detaching stops the constant PWM “holding” signal, saving battery and stopping buzzing:
void moveServoTo(int angle) {
jarServo.attach(PIN_SERVO);
delay(100);
jarServo.write(angle);
delay(SERVO_PULSE_DELAY_MS);
jarServo.detach();
}
This corresponds with Milos’s on-camera note about saving power and avoiding servo buzzing:
“One important thing to point here is that I do attach and detach, because if you just move a servo to a position you will continue sending it the signal… You just do the detach which does stop sending the signal out, in order to save a bit of power, because this is a battery-powered project after all.”
Strava fetch (sketch of approach) - the code ensures Wi-Fi, calls the Strava API, parses activities and records the last activity details. The project polls Strava periodically (every five minutes by default):
bool fetchStravaLatestActivity() {
ensureWiFi();
if (WiFi.status() != WL_CONNECTED) {
lastError = "WiFi disconnected";
return false;
}
// HTTP call to Strava /athlete/activities or other endpoint
// Parse JSON, extract last activity id, name, distance (m -> km), calories etc.
// Update lastActivity struct and hasLastActivity flag
...
return true; // or false on error
}
The sketch includes a regular poll loop:


-
DAB
-
Cancel
-
Vote Up
0
Vote Down
-
-
Sign in to reply
-
More
-
Cancel
-
milosrasic98
in reply to DAB
-
Cancel
-
Vote Up
0
Vote Down
-
-
Sign in to reply
-
More
-
Cancel
Comment-
milosrasic98
in reply to DAB
-
Cancel
-
Vote Up
0
Vote Down
-
-
Sign in to reply
-
More
-
Cancel
Children