<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="https://community.element14.com/cfs-file/__key/system/syndication/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Earn Your Fitness Reward with a Smart Cookie Jar Using Strava and ESP32 -- Episode 694</title><link>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694</link><description /><dc:language>en-US</dc:language><generator>Telligent Community 12</generator><item><title>Earn Your Fitness Reward with a Smart Cookie Jar Using Strava and ESP32 -- Episode 694</title><link>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694</link><pubDate>Thu, 11 Dec 2025 14:57:33 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f08580bd-ff57-4539-b2f1-de7eb0a248b0</guid><dc:creator>e14sbhargav</dc:creator><comments>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694#comments</comments><description>Current Revision posted to Documents by e14sbhargav on 12/11/2025 2:57:33 PM&lt;br /&gt;
&lt;p data-start="708" data-end="1051"&gt;Join&amp;nbsp;Milos as he tackles a familiar problem: it&amp;rsquo;s the holiday season, there&amp;rsquo;s lots of delicious food and candy everywhere, and it&amp;rsquo;s too cold to go out much. To help with that,&amp;nbsp;Milos created a Smart Cookie Jar that keeps you true to your word. The jar locks itself with all those treats inside and won&amp;rsquo;t open until you reach your running goal!&lt;/p&gt;
&lt;p data-start="1053" data-end="1094"&gt;In Milos&amp;#39; own words:&lt;/p&gt;
&lt;blockquote data-start="1095" data-end="1250"&gt;
&lt;p data-start="1097" data-end="1250"&gt;&amp;ldquo;It&amp;rsquo;s the holiday season&amp;hellip; But if you want to get to that candy, we&amp;rsquo;ll make something that will make you run first.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6nmhpc0" data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;/strong&gt;Watch the Project&lt;/h2&gt;
&lt;p data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;a href="https://youtu.be/jsQTD_6HNTA"&gt;https://youtu.be/jsQTD_6HNTA&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj40" data-start="1278" data-end="1290"&gt;What&amp;#39;s the Idea&lt;/h2&gt;
&lt;p data-start="1291" data-end="1499"&gt;Milos first built this project more than six years ago, back before he owned a 3D printer. He&amp;rsquo;s rebuilt it now to make it smaller and more refined, taking advantage of 3D printing and modern microcontrollers.&lt;/p&gt;
&lt;p data-start="1501" data-end="1825"&gt;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.&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;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.&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-03-81/1205.Picture4.png" /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-03-81/1033.Picture5.png" /&gt;&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj41" data-start="2092" data-end="2109"&gt;Enacting the Mechanism&lt;/h2&gt;
&lt;p data-start="2110" data-end="2419"&gt;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).&lt;/p&gt;
&lt;p data-start="2421" data-end="2646"&gt;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.&lt;/p&gt;
&lt;p data-start="2648" data-end="2709"&gt;A quote from the video where Milos describes the mechanism:&lt;/p&gt;
&lt;blockquote data-start="2710" data-end="2953"&gt;
&lt;p data-start="2712" data-end="2953"&gt;&amp;ldquo;In the centre we have the servo horn&amp;hellip; and around that we have the three pistons that move linearly. So they&amp;rsquo;re connected together using this small pushrod&amp;hellip; For the new version, I want to go with gears.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="2955" data-end="3094"&gt;The new lid packs everything tightly; from the outside (when unlocked) there&amp;rsquo;s no visual clue that anything special is inside &amp;mdash; just candy.&lt;/p&gt;
&lt;p data-start="2955" data-end="3094"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-03-81/2604.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj52" data-start="3101" data-end="3117"&gt;Setting up the Software&lt;/h2&gt;
&lt;p data-start="3118" data-end="3298"&gt;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.&lt;/p&gt;
&lt;p data-start="3300" data-end="3317"&gt;As Milos says:&lt;/p&gt;
&lt;blockquote data-start="3318" data-end="3509"&gt;
&lt;p data-start="3320" data-end="3509"&gt;&amp;ldquo;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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="mcetoc_1jc6etsj53" data-start="3511" data-end="3536"&gt;Strava flow (summary)&lt;/h3&gt;
&lt;ol data-start="3537" data-end="4070"&gt;
&lt;li data-start="3537" data-end="3647"&gt;
&lt;p data-start="3540" data-end="3647"&gt;Create a Strava API application (Settings &amp;rarr; My API Application) and note &lt;code data-start="3613" data-end="3624"&gt;client_id&lt;/code&gt; and &lt;code data-start="3629" data-end="3644"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3648" data-end="3717"&gt;
&lt;p data-start="3651" data-end="3717"&gt;Build and open the authorise URL to obtain the temporary &lt;code data-start="3708" data-end="3714"&gt;code&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3718" data-end="3816"&gt;
&lt;p data-start="3721" data-end="3816"&gt;Exchange the &lt;code data-start="3734" data-end="3740"&gt;code&lt;/code&gt; for &lt;code data-start="3745" data-end="3759"&gt;access_token&lt;/code&gt; and &lt;code data-start="3764" data-end="3779"&gt;refresh_token&lt;/code&gt; (the example cURL is shown below).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3817" data-end="3936"&gt;
&lt;p data-start="3820" data-end="3936"&gt;Copy the tokens into the Arduino sketch and flash the ESP32-S3. The code refreshes tokens automatically as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3937" data-end="4070"&gt;
&lt;p data-start="3940" data-end="4070"&gt;Open the device web page and choose &lt;strong data-start="3976" data-end="3998"&gt;Show last activity&lt;/strong&gt; to verify the device returns your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-start="4072" data-end="4212"&gt;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.&lt;/p&gt;
&lt;blockquote data-start="4214" data-end="4332"&gt;
&lt;p data-start="4216" data-end="4332"&gt;&amp;ldquo;It will just give you the name of your last activity, the distance you ran&amp;hellip; &amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6nsfgb1" data-start="4339" data-end="4389"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-03-81/1108.jpg" /&gt;&lt;/h2&gt;
&lt;h2 id="mcetoc_1jc6etsj54" data-start="4339" data-end="4389"&gt;Key code excerpts (from &lt;code data-start="4369" data-end="4386"&gt;CookieJarV2.ino&lt;/code&gt;)&lt;/h2&gt;
&lt;p data-start="4390" data-end="4545"&gt;Below are short, focused excerpts from the project code to explain the important behaviours. These are included to show how the software actually operates.&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="4547" data-end="4616"&gt;Configuration constants (Wi-Fi, Strava tokens and servo settings)&lt;/strong&gt;&amp;nbsp;- set these before flashing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Wi-Fi credentials
const char *WIFI_SSID = &amp;quot;YOUR-SSID&amp;quot;;
const char *WIFI_PASSWORD = &amp;quot;YOUR-PASSWORD&amp;quot;;

// Strava tokens and keys (example placeholders)
const char *STRAVA_CLIENT_ID = &amp;quot;YOUR_STRAVA_CLIENT_ID&amp;quot;;
const char *STRAVA_CLIENT_SECRET = &amp;quot;YOUR_STRAVA_CLIENT_SECRET&amp;quot;;
const char *STRAVA_ACCESS_TOKEN = &amp;quot;YOUR_STRAVA_ACCESS_TOKEN&amp;quot;;
const char *STRAVA_REFRESH_TOKEN = &amp;quot;YOUR_STRAVA_REFRESH_TOKEN&amp;quot;;

// 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;
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="5252" data-end="5275"&gt;Servo motion helper&lt;/strong&gt;&amp;nbsp;- the code attaches the servo, moves to the angle, waits, and detaches. Detaching stops the constant PWM &amp;ldquo;holding&amp;rdquo; signal, saving battery and stopping buzzing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void moveServoTo(int angle) {
  jarServo.attach(PIN_SERVO);
  delay(100);
  jarServo.write(angle);
  delay(SERVO_PULSE_DELAY_MS);
  jarServo.detach();
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="5602" data-end="5695"&gt;This corresponds with Milos&amp;rsquo;s on-camera note about saving power and avoiding servo buzzing:&lt;/p&gt;
&lt;blockquote data-start="5696" data-end="6040"&gt;
&lt;p data-start="5698" data-end="6040"&gt;&amp;ldquo;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&amp;hellip; 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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;strong data-start="6042" data-end="6079"&gt;Strava fetch (sketch of approach)&lt;/strong&gt;&amp;nbsp;- 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):&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;bool fetchStravaLatestActivity() {
  ensureWiFi();
  if (WiFi.status() != WL_CONNECTED) {
    lastError = &amp;quot;WiFi disconnected&amp;quot;;
    return false;
  }

  // HTTP call to Strava /athlete/activities or other endpoint
  // Parse JSON, extract last activity id, name, distance (m -&amp;gt; km), calories etc.
  // Update lastActivity struct and hasLastActivity flag
  ...
  return true; // or false on error
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;The sketch includes a regular poll loop:&lt;/p&gt;
&lt;div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"&gt;
&lt;div class="sticky top-9"&gt;
&lt;div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;const unsigned long STRAVA_POLL_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 minutes

// inside loop():
unsigned long now = millis();
if (now - lastPollMs &amp;gt;= STRAVA_POLL_INTERVAL_MS || now &amp;lt; lastPollMs) {
  lastPollMs = now;
  ActivityStats stats;
  if (fetchStravaToday(stats)) {
    lastStats = stats;
  } else if (fetchStravaLatestActivity()) {
    lastStats.distanceKm = lastActivity.distanceKm;
    lastStats.calories = lastActivity.calories;
    lastStats.fresh = true;
  }
  evaluateGoalAndMaybeUnlock();
}
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;
&lt;p data-start="7229" data-end="7428"&gt;&lt;code data-start="7229" data-end="7259"&gt;evaluateGoalAndMaybeUnlock()&lt;/code&gt; is the function that takes the gathered activity stats and decides whether to call &lt;code data-start="7343" data-end="7376"&gt;moveServoTo(SERVO_UNLOCK_ANGLE)&lt;/code&gt; (unlock) or &lt;code data-start="7389" data-end="7420"&gt;moveServoTo(SERVO_LOCK_ANGLE)&lt;/code&gt; (lock).&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6f0gj05" data-start="7435" data-end="7490"&gt;Step-by-step: Strava auth/setup (short checklist)&lt;/h2&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="7491" data-end="7587"&gt;
&lt;p data-start="7493" data-end="7587"&gt;Create a Strava API app in your Strava account and note the &lt;code data-start="7553" data-end="7564"&gt;client_id&lt;/code&gt; and &lt;code data-start="7569" data-end="7584"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7588" data-end="7854"&gt;
&lt;p data-start="7590" data-end="7854"&gt;Build the authorisation URL (replace &lt;code data-start="7627" data-end="7638"&gt;CLIENT_ID&lt;/code&gt; and &lt;code data-start="7643" data-end="7657"&gt;REDIRECT_URI&lt;/code&gt;):&lt;br data-start="7659" data-end="7662" /&gt; &lt;code data-start="7664" data-end="7852"&gt;https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&amp;amp;response_type=code&amp;amp;redirect_uri=http://localhost/exchange_token&amp;amp;approval_prompt=auto&amp;amp;scope=read,activity:read,activity:read_all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7855" data-end="7959"&gt;
&lt;p data-start="7857" data-end="7959"&gt;Open the URL in a browser, approve the app, capture the returned &lt;code data-start="7922" data-end="7928"&gt;code&lt;/code&gt; (e.g. &lt;code data-start="7935" data-end="7955"&gt;...?code=YOUR_CODE&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7960" data-end="8227"&gt;
&lt;p data-start="7962" data-end="8023"&gt;Exchange the &lt;code data-start="7975" data-end="7981"&gt;code&lt;/code&gt; for tokens (on your PC) &amp;mdash; example cURL:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="batchfile"&gt;curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET \
  -d code=YOUR_CODE \
  -d grant_type=authorization_code
&lt;/pre&gt;&lt;/p&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="8228" data-end="8357"&gt;
&lt;p data-start="8230" data-end="8357"&gt;Set &lt;code data-start="8234" data-end="8252"&gt;STRAVA_CLIENT_ID&lt;/code&gt;, &lt;code data-start="8254" data-end="8276"&gt;STRAVA_CLIENT_SECRET&lt;/code&gt;, &lt;code data-start="8278" data-end="8299"&gt;STRAVA_ACCESS_TOKEN&lt;/code&gt; and &lt;code data-start="8304" data-end="8326"&gt;STRAVA_REFRESH_TOKEN&lt;/code&gt; in the &lt;code data-start="8334" data-end="8347"&gt;CookieJarV2&lt;/code&gt; sketch.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8358" data-end="8502"&gt;
&lt;p data-start="8360" data-end="8502"&gt;Flash the ESP32-S3 and test by opening the device&amp;rsquo;s web page &amp;rarr; &amp;ldquo;Show last activity&amp;rdquo;. You should see your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mcetoc_1jc6nt5li2" data-start="8509" data-end="8540"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-03-81/Picture14.jpg" /&gt;&lt;/h2&gt;
&lt;h2 id="mcetoc_1jc6f1e556" data-start="8509" data-end="8540"&gt;Final assembly and test&lt;/h2&gt;
&lt;p data-start="8541" data-end="8568"&gt;Once assembled and flashed:&lt;/p&gt;
&lt;ul data-start="8569" data-end="8825"&gt;
&lt;li data-start="8569" data-end="8718"&gt;
&lt;p data-start="8571" data-end="8718"&gt;Lock the jar, set your target distance via the device web UI, go for your run, and wait for the device to poll Strava and evaluate your activity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8719" data-end="8825"&gt;
&lt;p data-start="8721" data-end="8825"&gt;The sketch will automatically refresh tokens if required and will periodically check for new activities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="8827" data-end="9077"&gt;Milos demonstrates this flow in the video and finishes by going for a run to show the jar unlocking when the goal is met. He notes that the jar, when seen from the outside, looks like an ordinary jar of candy &amp;mdash; the mechanism is hidden inside the lid.&lt;/p&gt;
&lt;blockquote data-start="9079" data-end="9257"&gt;
&lt;p data-start="9081" data-end="9257"&gt;&amp;ldquo;If you look at it from outside, it just looks like a jar full of candy bars&amp;hellip; you won&amp;rsquo;t even know that there are some electronics inside.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6f1e557" data-start="9264" data-end="9281"&gt;Resources&lt;/h2&gt;
&lt;ul data-start="9282" data-end="9592"&gt;
&lt;li data-start="9282" data-end="9442"&gt;
&lt;p data-start="9284" data-end="9442"&gt;Cookie Jar v1 Writeup: &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="9327" data-end="9440" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9443" data-end="9521"&gt;
&lt;p data-start="9445" data-end="9521"&gt;Strava developer docs: &lt;a class="decorated-link" href="https://developers.strava.com/docs/getting-started/" data-start="9468" data-end="9519" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://developers.strava.com/docs/getting-started/&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9522" data-end="9592"&gt;
&lt;p data-start="9524" data-end="9592"&gt;Code and files:&amp;nbsp;&amp;nbsp;&lt;a href="https://community.element14.com/challenges-projects/element14-presents/m/files/150854"&gt;Episode 694 Supporting Files&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6u0l670"&gt;Bill of Materials&lt;/h2&gt;
&lt;table class="e14-product-bom-main"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Product Name&lt;/th&gt;
&lt;th&gt;Manufacturer&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;&lt;a id="e14-product-link-5e351" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=4200195,3769965,3879657&amp;nsku=74AK7863,52AJ5992,99AJ4470&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_BUY_KIT" class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('5e351'));" data-farnell="4200195,3769965,3879657" data-newark="74AK7863,52AJ5992,99AJ4470" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Kit&lt;/a&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XIAO ESP32S3&lt;/td&gt;
&lt;td&gt;SEEED STUDIO&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-e5db6" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=4200195&amp;nsku=74AK7863&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('e5db6'));" data-farnell="4200195" data-newark="74AK7863" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SG90 Servo&lt;/td&gt;
&lt;td&gt;DFROBOT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-0965c" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=3769965&amp;nsku=52AJ5992&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('0965c'));" data-farnell="3769965" data-newark="52AJ5992" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Battery Charger&lt;/td&gt;
&lt;td&gt;DFROBOT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-8d6fe" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=3879657&amp;nsku=99AJ4470&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('8d6fe'));" data-farnell="3879657" data-newark="99AJ4470" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="xs-hide"&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6u0l671"&gt;Additional Parts&lt;/h2&gt;
&lt;table class="e14-product-bom-additional"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Product Name&lt;/th&gt;
&lt;th&gt;Manufacturer&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glass Cookie Jar&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1S LION Battery&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D Printing Filament like PLA&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="mcetoc_1jc6u0l672"&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;

&lt;div style="font-size: 90%;"&gt;Tags: strava integration, 3d printed mechanism, wifi jar lock, diy smart jar, fitness reward system, esp32 project, esp32 servo control, fitness tracker diy, smart cookie jar, iot fitness project, strava API, friday_release, esp32 automation, locked biscuit barrel&lt;/div&gt;
</description></item><item><title>Earn Your Fitness Reward with a Smart Cookie Jar Using Strava and ESP32 -- Episode 694</title><link>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694/revision/9</link><pubDate>Thu, 11 Dec 2025 14:57:33 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f08580bd-ff57-4539-b2f1-de7eb0a248b0</guid><dc:creator>cstanton</dc:creator><comments>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694#comments</comments><description>Revision 9 posted to Documents by cstanton on 12/11/2025 2:57:33 PM&lt;br /&gt;
&lt;p data-start="708" data-end="1051"&gt;Join&amp;nbsp;Milos as he tackles a familiar problem: it&amp;rsquo;s the holiday season, there&amp;rsquo;s lots of delicious food and candy everywhere, and it&amp;rsquo;s too cold to go out much. To help with that,&amp;nbsp;Milos created a Smart Cookie Jar that keeps you true to your word. The jar locks itself with all those treats inside and won&amp;rsquo;t open until you reach your running goal!&lt;/p&gt;
&lt;p data-start="1053" data-end="1094"&gt;In Milos&amp;#39; own words:&lt;/p&gt;
&lt;blockquote data-start="1095" data-end="1250"&gt;
&lt;p data-start="1097" data-end="1250"&gt;&amp;ldquo;It&amp;rsquo;s the holiday season&amp;hellip; But if you want to get to that candy, we&amp;rsquo;ll make something that will make you run first.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6nmhpc0" data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;/strong&gt;Watch the Project&lt;/h2&gt;
&lt;p data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;a href="https://youtu.be/jsQTD_6HNTA"&gt;https://youtu.be/jsQTD_6HNTA&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj40" data-start="1278" data-end="1290"&gt;What&amp;#39;s the Idea&lt;/h2&gt;
&lt;p data-start="1291" data-end="1499"&gt;Milos first built this project more than six years ago, back before he owned a 3D printer. He&amp;rsquo;s rebuilt it now to make it smaller and more refined, taking advantage of 3D printing and modern microcontrollers.&lt;/p&gt;
&lt;p data-start="1501" data-end="1825"&gt;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.&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;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.&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1205.Picture4.png" /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1033.Picture5.png" /&gt;&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj41" data-start="2092" data-end="2109"&gt;Enacting the Mechanism&lt;/h2&gt;
&lt;p data-start="2110" data-end="2419"&gt;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).&lt;/p&gt;
&lt;p data-start="2421" data-end="2646"&gt;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.&lt;/p&gt;
&lt;p data-start="2648" data-end="2709"&gt;A quote from the video where Milos describes the mechanism:&lt;/p&gt;
&lt;blockquote data-start="2710" data-end="2953"&gt;
&lt;p data-start="2712" data-end="2953"&gt;&amp;ldquo;In the centre we have the servo horn&amp;hellip; and around that we have the three pistons that move linearly. So they&amp;rsquo;re connected together using this small pushrod&amp;hellip; For the new version, I want to go with gears.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="2955" data-end="3094"&gt;The new lid packs everything tightly; from the outside (when unlocked) there&amp;rsquo;s no visual clue that anything special is inside &amp;mdash; just candy.&lt;/p&gt;
&lt;p data-start="2955" data-end="3094"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/2604.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj52" data-start="3101" data-end="3117"&gt;Setting up the Software&lt;/h2&gt;
&lt;p data-start="3118" data-end="3298"&gt;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.&lt;/p&gt;
&lt;p data-start="3300" data-end="3317"&gt;As Milos says:&lt;/p&gt;
&lt;blockquote data-start="3318" data-end="3509"&gt;
&lt;p data-start="3320" data-end="3509"&gt;&amp;ldquo;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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="mcetoc_1jc6etsj53" data-start="3511" data-end="3536"&gt;Strava flow (summary)&lt;/h3&gt;
&lt;ol data-start="3537" data-end="4070"&gt;
&lt;li data-start="3537" data-end="3647"&gt;
&lt;p data-start="3540" data-end="3647"&gt;Create a Strava API application (Settings &amp;rarr; My API Application) and note &lt;code data-start="3613" data-end="3624"&gt;client_id&lt;/code&gt; and &lt;code data-start="3629" data-end="3644"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3648" data-end="3717"&gt;
&lt;p data-start="3651" data-end="3717"&gt;Build and open the authorise URL to obtain the temporary &lt;code data-start="3708" data-end="3714"&gt;code&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3718" data-end="3816"&gt;
&lt;p data-start="3721" data-end="3816"&gt;Exchange the &lt;code data-start="3734" data-end="3740"&gt;code&lt;/code&gt; for &lt;code data-start="3745" data-end="3759"&gt;access_token&lt;/code&gt; and &lt;code data-start="3764" data-end="3779"&gt;refresh_token&lt;/code&gt; (the example cURL is shown below).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3817" data-end="3936"&gt;
&lt;p data-start="3820" data-end="3936"&gt;Copy the tokens into the Arduino sketch and flash the ESP32-S3. The code refreshes tokens automatically as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3937" data-end="4070"&gt;
&lt;p data-start="3940" data-end="4070"&gt;Open the device web page and choose &lt;strong data-start="3976" data-end="3998"&gt;Show last activity&lt;/strong&gt; to verify the device returns your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-start="4072" data-end="4212"&gt;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.&lt;/p&gt;
&lt;blockquote data-start="4214" data-end="4332"&gt;
&lt;p data-start="4216" data-end="4332"&gt;&amp;ldquo;It will just give you the name of your last activity, the distance you ran&amp;hellip; &amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6nsfgb1" data-start="4339" data-end="4389"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1108.jpg" /&gt;&lt;/h2&gt;
&lt;h2 id="mcetoc_1jc6etsj54" data-start="4339" data-end="4389"&gt;Key code excerpts (from &lt;code data-start="4369" data-end="4386"&gt;CookieJarV2.ino&lt;/code&gt;)&lt;/h2&gt;
&lt;p data-start="4390" data-end="4545"&gt;Below are short, focused excerpts from the project code to explain the important behaviours. These are included to show how the software actually operates.&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="4547" data-end="4616"&gt;Configuration constants (Wi-Fi, Strava tokens and servo settings)&lt;/strong&gt;&amp;nbsp;- set these before flashing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Wi-Fi credentials
const char *WIFI_SSID = &amp;quot;YOUR-SSID&amp;quot;;
const char *WIFI_PASSWORD = &amp;quot;YOUR-PASSWORD&amp;quot;;

// Strava tokens and keys (example placeholders)
const char *STRAVA_CLIENT_ID = &amp;quot;YOUR_STRAVA_CLIENT_ID&amp;quot;;
const char *STRAVA_CLIENT_SECRET = &amp;quot;YOUR_STRAVA_CLIENT_SECRET&amp;quot;;
const char *STRAVA_ACCESS_TOKEN = &amp;quot;YOUR_STRAVA_ACCESS_TOKEN&amp;quot;;
const char *STRAVA_REFRESH_TOKEN = &amp;quot;YOUR_STRAVA_REFRESH_TOKEN&amp;quot;;

// 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;
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="5252" data-end="5275"&gt;Servo motion helper&lt;/strong&gt;&amp;nbsp;- the code attaches the servo, moves to the angle, waits, and detaches. Detaching stops the constant PWM &amp;ldquo;holding&amp;rdquo; signal, saving battery and stopping buzzing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void moveServoTo(int angle) {
  jarServo.attach(PIN_SERVO);
  delay(100);
  jarServo.write(angle);
  delay(SERVO_PULSE_DELAY_MS);
  jarServo.detach();
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="5602" data-end="5695"&gt;This corresponds with Milos&amp;rsquo;s on-camera note about saving power and avoiding servo buzzing:&lt;/p&gt;
&lt;blockquote data-start="5696" data-end="6040"&gt;
&lt;p data-start="5698" data-end="6040"&gt;&amp;ldquo;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&amp;hellip; 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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;strong data-start="6042" data-end="6079"&gt;Strava fetch (sketch of approach)&lt;/strong&gt;&amp;nbsp;- 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):&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;bool fetchStravaLatestActivity() {
  ensureWiFi();
  if (WiFi.status() != WL_CONNECTED) {
    lastError = &amp;quot;WiFi disconnected&amp;quot;;
    return false;
  }

  // HTTP call to Strava /athlete/activities or other endpoint
  // Parse JSON, extract last activity id, name, distance (m -&amp;gt; km), calories etc.
  // Update lastActivity struct and hasLastActivity flag
  ...
  return true; // or false on error
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;The sketch includes a regular poll loop:&lt;/p&gt;
&lt;div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"&gt;
&lt;div class="sticky top-9"&gt;
&lt;div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;const unsigned long STRAVA_POLL_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 minutes

// inside loop():
unsigned long now = millis();
if (now - lastPollMs &amp;gt;= STRAVA_POLL_INTERVAL_MS || now &amp;lt; lastPollMs) {
  lastPollMs = now;
  ActivityStats stats;
  if (fetchStravaToday(stats)) {
    lastStats = stats;
  } else if (fetchStravaLatestActivity()) {
    lastStats.distanceKm = lastActivity.distanceKm;
    lastStats.calories = lastActivity.calories;
    lastStats.fresh = true;
  }
  evaluateGoalAndMaybeUnlock();
}
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;
&lt;p data-start="7229" data-end="7428"&gt;&lt;code data-start="7229" data-end="7259"&gt;evaluateGoalAndMaybeUnlock()&lt;/code&gt; is the function that takes the gathered activity stats and decides whether to call &lt;code data-start="7343" data-end="7376"&gt;moveServoTo(SERVO_UNLOCK_ANGLE)&lt;/code&gt; (unlock) or &lt;code data-start="7389" data-end="7420"&gt;moveServoTo(SERVO_LOCK_ANGLE)&lt;/code&gt; (lock).&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6f0gj05" data-start="7435" data-end="7490"&gt;Step-by-step: Strava auth/setup (short checklist)&lt;/h2&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="7491" data-end="7587"&gt;
&lt;p data-start="7493" data-end="7587"&gt;Create a Strava API app in your Strava account and note the &lt;code data-start="7553" data-end="7564"&gt;client_id&lt;/code&gt; and &lt;code data-start="7569" data-end="7584"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7588" data-end="7854"&gt;
&lt;p data-start="7590" data-end="7854"&gt;Build the authorisation URL (replace &lt;code data-start="7627" data-end="7638"&gt;CLIENT_ID&lt;/code&gt; and &lt;code data-start="7643" data-end="7657"&gt;REDIRECT_URI&lt;/code&gt;):&lt;br data-start="7659" data-end="7662" /&gt; &lt;code data-start="7664" data-end="7852"&gt;https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&amp;amp;response_type=code&amp;amp;redirect_uri=http://localhost/exchange_token&amp;amp;approval_prompt=auto&amp;amp;scope=read,activity:read,activity:read_all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7855" data-end="7959"&gt;
&lt;p data-start="7857" data-end="7959"&gt;Open the URL in a browser, approve the app, capture the returned &lt;code data-start="7922" data-end="7928"&gt;code&lt;/code&gt; (e.g. &lt;code data-start="7935" data-end="7955"&gt;...?code=YOUR_CODE&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7960" data-end="8227"&gt;
&lt;p data-start="7962" data-end="8023"&gt;Exchange the &lt;code data-start="7975" data-end="7981"&gt;code&lt;/code&gt; for tokens (on your PC) &amp;mdash; example cURL:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="batchfile"&gt;curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET \
  -d code=YOUR_CODE \
  -d grant_type=authorization_code
&lt;/pre&gt;&lt;/p&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="8228" data-end="8357"&gt;
&lt;p data-start="8230" data-end="8357"&gt;Set &lt;code data-start="8234" data-end="8252"&gt;STRAVA_CLIENT_ID&lt;/code&gt;, &lt;code data-start="8254" data-end="8276"&gt;STRAVA_CLIENT_SECRET&lt;/code&gt;, &lt;code data-start="8278" data-end="8299"&gt;STRAVA_ACCESS_TOKEN&lt;/code&gt; and &lt;code data-start="8304" data-end="8326"&gt;STRAVA_REFRESH_TOKEN&lt;/code&gt; in the &lt;code data-start="8334" data-end="8347"&gt;CookieJarV2&lt;/code&gt; sketch.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8358" data-end="8502"&gt;
&lt;p data-start="8360" data-end="8502"&gt;Flash the ESP32-S3 and test by opening the device&amp;rsquo;s web page &amp;rarr; &amp;ldquo;Show last activity&amp;rdquo;. You should see your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mcetoc_1jc6nt5li2" data-start="8509" data-end="8540"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/Picture14.jpg" /&gt;&lt;/h2&gt;
&lt;h2 id="mcetoc_1jc6f1e556" data-start="8509" data-end="8540"&gt;Final assembly and test&lt;/h2&gt;
&lt;p data-start="8541" data-end="8568"&gt;Once assembled and flashed:&lt;/p&gt;
&lt;ul data-start="8569" data-end="8825"&gt;
&lt;li data-start="8569" data-end="8718"&gt;
&lt;p data-start="8571" data-end="8718"&gt;Lock the jar, set your target distance via the device web UI, go for your run, and wait for the device to poll Strava and evaluate your activity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8719" data-end="8825"&gt;
&lt;p data-start="8721" data-end="8825"&gt;The sketch will automatically refresh tokens if required and will periodically check for new activities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="8827" data-end="9077"&gt;Milos demonstrates this flow in the video and finishes by going for a run to show the jar unlocking when the goal is met. He notes that the jar, when seen from the outside, looks like an ordinary jar of candy &amp;mdash; the mechanism is hidden inside the lid.&lt;/p&gt;
&lt;blockquote data-start="9079" data-end="9257"&gt;
&lt;p data-start="9081" data-end="9257"&gt;&amp;ldquo;If you look at it from outside, it just looks like a jar full of candy bars&amp;hellip; you won&amp;rsquo;t even know that there are some electronics inside.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6f1e557" data-start="9264" data-end="9281"&gt;Resources&lt;/h2&gt;
&lt;ul data-start="9282" data-end="9592"&gt;
&lt;li data-start="9282" data-end="9442"&gt;
&lt;p data-start="9284" data-end="9442"&gt;Cookie Jar v1 Writeup: &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="9327" data-end="9440" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9443" data-end="9521"&gt;
&lt;p data-start="9445" data-end="9521"&gt;Strava developer docs: &lt;a class="decorated-link" href="https://developers.strava.com/docs/getting-started/" data-start="9468" data-end="9519" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://developers.strava.com/docs/getting-started/&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9522" data-end="9592"&gt;
&lt;p data-start="9524" data-end="9592"&gt;Code and files:&amp;nbsp;&amp;nbsp;&lt;a href="https://community.element14.com/challenges-projects/element14-presents/m/files/150854"&gt;Episode 694 Supporting Files&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6u0l670"&gt;Bill of Materials&lt;/h2&gt;
&lt;table class="e14-product-bom-main"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Product Name&lt;/th&gt;
&lt;th&gt;Manufacturer&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;&lt;a id="e14-product-link-a48d6" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=4200195,3769965,3879657&amp;nsku=74AK7863,52AJ5992,99AJ4470&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_BUY_KIT" class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('a48d6'));" data-farnell="4200195,3769965,3879657" data-newark="74AK7863,52AJ5992,99AJ4470" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Kit&lt;/a&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XIAO ESP32S3&lt;/td&gt;
&lt;td&gt;SEEED STUDIO&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-20989" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=4200195&amp;nsku=74AK7863&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('20989'));" data-farnell="4200195" data-newark="74AK7863" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SG90 Servo&lt;/td&gt;
&lt;td&gt;DFROBOT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-424be" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=3769965&amp;nsku=52AJ5992&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('424be'));" data-farnell="3769965" data-newark="52AJ5992" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Battery Charger&lt;/td&gt;
&lt;td&gt;DFROBOT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-59ef9" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=3879657&amp;nsku=99AJ4470&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('59ef9'));" data-farnell="3879657" data-newark="99AJ4470" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="xs-hide"&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6u0l671"&gt;Additional Parts&lt;/h2&gt;
&lt;table class="e14-product-bom-additional"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Product Name&lt;/th&gt;
&lt;th&gt;Manufacturer&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glass Cookie Jar&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1S LION Battery&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D Printing Filament like PLA&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="mcetoc_1jc6u0l672"&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;

&lt;div style="font-size: 90%;"&gt;Tags: strava integration, 3d printed mechanism, wifi jar lock, diy smart jar, fitness reward system, esp32 project, esp32 servo control, fitness tracker diy, smart cookie jar, iot fitness project, strava API, friday_release, esp32 automation, locked biscuit barrel&lt;/div&gt;
</description></item><item><title>Earn Your Fitness Reward with a Smart Cookie Jar Using Strava and ESP32 -- Episode 694</title><link>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694/revision/8</link><pubDate>Thu, 11 Dec 2025 14:53:56 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f08580bd-ff57-4539-b2f1-de7eb0a248b0</guid><dc:creator>cstanton</dc:creator><comments>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694#comments</comments><description>Revision 8 posted to Documents by cstanton on 12/11/2025 2:53:56 PM&lt;br /&gt;
&lt;p data-start="708" data-end="1051"&gt;Join&amp;nbsp;Milos as he tackles a familiar problem: it&amp;rsquo;s the holiday season, there&amp;rsquo;s lots of delicious food and candy everywhere, and it&amp;rsquo;s too cold to go out much. To help with that,&amp;nbsp;Milos created a Smart Cookie Jar that keeps you true to your word. The jar locks itself with all those treats inside and won&amp;rsquo;t open until you reach your running goal!&lt;/p&gt;
&lt;p data-start="1053" data-end="1094"&gt;In Milos&amp;#39; own words:&lt;/p&gt;
&lt;blockquote data-start="1095" data-end="1250"&gt;
&lt;p data-start="1097" data-end="1250"&gt;&amp;ldquo;It&amp;rsquo;s the holiday season&amp;hellip; But if you want to get to that candy, we&amp;rsquo;ll make something that will make you run first.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6nmhpc0" data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;/strong&gt;Watch the Project&lt;/h2&gt;
&lt;p data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;a href="https://youtu.be/jsQTD_6HNTA"&gt;https://youtu.be/jsQTD_6HNTA&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj40" data-start="1278" data-end="1290"&gt;What&amp;#39;s the Idea&lt;/h2&gt;
&lt;p data-start="1291" data-end="1499"&gt;Milos first built this project more than six years ago, back before he owned a 3D printer. He&amp;rsquo;s rebuilt it now to make it smaller and more refined, taking advantage of 3D printing and modern microcontrollers.&lt;/p&gt;
&lt;p data-start="1501" data-end="1825"&gt;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.&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;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.&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1205.Picture4.png" /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1033.Picture5.png" /&gt;&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj41" data-start="2092" data-end="2109"&gt;Enacting the Mechanism&lt;/h2&gt;
&lt;p data-start="2110" data-end="2419"&gt;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).&lt;/p&gt;
&lt;p data-start="2421" data-end="2646"&gt;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.&lt;/p&gt;
&lt;p data-start="2648" data-end="2709"&gt;A quote from the video where Milos describes the mechanism:&lt;/p&gt;
&lt;blockquote data-start="2710" data-end="2953"&gt;
&lt;p data-start="2712" data-end="2953"&gt;&amp;ldquo;In the centre we have the servo horn&amp;hellip; and around that we have the three pistons that move linearly. So they&amp;rsquo;re connected together using this small pushrod&amp;hellip; For the new version, I want to go with gears.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="2955" data-end="3094"&gt;The new lid packs everything tightly; from the outside (when unlocked) there&amp;rsquo;s no visual clue that anything special is inside &amp;mdash; just candy.&lt;/p&gt;
&lt;p data-start="2955" data-end="3094"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/2604.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj52" data-start="3101" data-end="3117"&gt;Setting up the Software&lt;/h2&gt;
&lt;p data-start="3118" data-end="3298"&gt;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.&lt;/p&gt;
&lt;p data-start="3300" data-end="3317"&gt;As Milos says:&lt;/p&gt;
&lt;blockquote data-start="3318" data-end="3509"&gt;
&lt;p data-start="3320" data-end="3509"&gt;&amp;ldquo;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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="mcetoc_1jc6etsj53" data-start="3511" data-end="3536"&gt;Strava flow (summary)&lt;/h3&gt;
&lt;ol data-start="3537" data-end="4070"&gt;
&lt;li data-start="3537" data-end="3647"&gt;
&lt;p data-start="3540" data-end="3647"&gt;Create a Strava API application (Settings &amp;rarr; My API Application) and note &lt;code data-start="3613" data-end="3624"&gt;client_id&lt;/code&gt; and &lt;code data-start="3629" data-end="3644"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3648" data-end="3717"&gt;
&lt;p data-start="3651" data-end="3717"&gt;Build and open the authorise URL to obtain the temporary &lt;code data-start="3708" data-end="3714"&gt;code&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3718" data-end="3816"&gt;
&lt;p data-start="3721" data-end="3816"&gt;Exchange the &lt;code data-start="3734" data-end="3740"&gt;code&lt;/code&gt; for &lt;code data-start="3745" data-end="3759"&gt;access_token&lt;/code&gt; and &lt;code data-start="3764" data-end="3779"&gt;refresh_token&lt;/code&gt; (the example cURL is shown below).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3817" data-end="3936"&gt;
&lt;p data-start="3820" data-end="3936"&gt;Copy the tokens into the Arduino sketch and flash the ESP32-S3. The code refreshes tokens automatically as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3937" data-end="4070"&gt;
&lt;p data-start="3940" data-end="4070"&gt;Open the device web page and choose &lt;strong data-start="3976" data-end="3998"&gt;Show last activity&lt;/strong&gt; to verify the device returns your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-start="4072" data-end="4212"&gt;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.&lt;/p&gt;
&lt;blockquote data-start="4214" data-end="4332"&gt;
&lt;p data-start="4216" data-end="4332"&gt;&amp;ldquo;It will just give you the name of your last activity, the distance you ran&amp;hellip; &amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6nsfgb1" data-start="4339" data-end="4389"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1108.jpg" /&gt;&lt;/h2&gt;
&lt;h2 id="mcetoc_1jc6etsj54" data-start="4339" data-end="4389"&gt;Key code excerpts (from &lt;code data-start="4369" data-end="4386"&gt;CookieJarV2.ino&lt;/code&gt;)&lt;/h2&gt;
&lt;p data-start="4390" data-end="4545"&gt;Below are short, focused excerpts from the project code to explain the important behaviours. These are included to show how the software actually operates.&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="4547" data-end="4616"&gt;Configuration constants (Wi-Fi, Strava tokens and servo settings)&lt;/strong&gt;&amp;nbsp;- set these before flashing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Wi-Fi credentials
const char *WIFI_SSID = &amp;quot;YOUR-SSID&amp;quot;;
const char *WIFI_PASSWORD = &amp;quot;YOUR-PASSWORD&amp;quot;;

// Strava tokens and keys (example placeholders)
const char *STRAVA_CLIENT_ID = &amp;quot;YOUR_STRAVA_CLIENT_ID&amp;quot;;
const char *STRAVA_CLIENT_SECRET = &amp;quot;YOUR_STRAVA_CLIENT_SECRET&amp;quot;;
const char *STRAVA_ACCESS_TOKEN = &amp;quot;YOUR_STRAVA_ACCESS_TOKEN&amp;quot;;
const char *STRAVA_REFRESH_TOKEN = &amp;quot;YOUR_STRAVA_REFRESH_TOKEN&amp;quot;;

// 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;
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="5252" data-end="5275"&gt;Servo motion helper&lt;/strong&gt;&amp;nbsp;- the code attaches the servo, moves to the angle, waits, and detaches. Detaching stops the constant PWM &amp;ldquo;holding&amp;rdquo; signal, saving battery and stopping buzzing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void moveServoTo(int angle) {
  jarServo.attach(PIN_SERVO);
  delay(100);
  jarServo.write(angle);
  delay(SERVO_PULSE_DELAY_MS);
  jarServo.detach();
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="5602" data-end="5695"&gt;This corresponds with Milos&amp;rsquo;s on-camera note about saving power and avoiding servo buzzing:&lt;/p&gt;
&lt;blockquote data-start="5696" data-end="6040"&gt;
&lt;p data-start="5698" data-end="6040"&gt;&amp;ldquo;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&amp;hellip; 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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;strong data-start="6042" data-end="6079"&gt;Strava fetch (sketch of approach)&lt;/strong&gt;&amp;nbsp;- 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):&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;bool fetchStravaLatestActivity() {
  ensureWiFi();
  if (WiFi.status() != WL_CONNECTED) {
    lastError = &amp;quot;WiFi disconnected&amp;quot;;
    return false;
  }

  // HTTP call to Strava /athlete/activities or other endpoint
  // Parse JSON, extract last activity id, name, distance (m -&amp;gt; km), calories etc.
  // Update lastActivity struct and hasLastActivity flag
  ...
  return true; // or false on error
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;The sketch includes a regular poll loop:&lt;/p&gt;
&lt;div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"&gt;
&lt;div class="sticky top-9"&gt;
&lt;div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;const unsigned long STRAVA_POLL_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 minutes

// inside loop():
unsigned long now = millis();
if (now - lastPollMs &amp;gt;= STRAVA_POLL_INTERVAL_MS || now &amp;lt; lastPollMs) {
  lastPollMs = now;
  ActivityStats stats;
  if (fetchStravaToday(stats)) {
    lastStats = stats;
  } else if (fetchStravaLatestActivity()) {
    lastStats.distanceKm = lastActivity.distanceKm;
    lastStats.calories = lastActivity.calories;
    lastStats.fresh = true;
  }
  evaluateGoalAndMaybeUnlock();
}
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;
&lt;p data-start="7229" data-end="7428"&gt;&lt;code data-start="7229" data-end="7259"&gt;evaluateGoalAndMaybeUnlock()&lt;/code&gt; is the function that takes the gathered activity stats and decides whether to call &lt;code data-start="7343" data-end="7376"&gt;moveServoTo(SERVO_UNLOCK_ANGLE)&lt;/code&gt; (unlock) or &lt;code data-start="7389" data-end="7420"&gt;moveServoTo(SERVO_LOCK_ANGLE)&lt;/code&gt; (lock).&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6f0gj05" data-start="7435" data-end="7490"&gt;Step-by-step: Strava auth/setup (short checklist)&lt;/h2&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="7491" data-end="7587"&gt;
&lt;p data-start="7493" data-end="7587"&gt;Create a Strava API app in your Strava account and note the &lt;code data-start="7553" data-end="7564"&gt;client_id&lt;/code&gt; and &lt;code data-start="7569" data-end="7584"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7588" data-end="7854"&gt;
&lt;p data-start="7590" data-end="7854"&gt;Build the authorisation URL (replace &lt;code data-start="7627" data-end="7638"&gt;CLIENT_ID&lt;/code&gt; and &lt;code data-start="7643" data-end="7657"&gt;REDIRECT_URI&lt;/code&gt;):&lt;br data-start="7659" data-end="7662" /&gt; &lt;code data-start="7664" data-end="7852"&gt;https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&amp;amp;response_type=code&amp;amp;redirect_uri=http://localhost/exchange_token&amp;amp;approval_prompt=auto&amp;amp;scope=read,activity:read,activity:read_all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7855" data-end="7959"&gt;
&lt;p data-start="7857" data-end="7959"&gt;Open the URL in a browser, approve the app, capture the returned &lt;code data-start="7922" data-end="7928"&gt;code&lt;/code&gt; (e.g. &lt;code data-start="7935" data-end="7955"&gt;...?code=YOUR_CODE&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7960" data-end="8227"&gt;
&lt;p data-start="7962" data-end="8023"&gt;Exchange the &lt;code data-start="7975" data-end="7981"&gt;code&lt;/code&gt; for tokens (on your PC) &amp;mdash; example cURL:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="batchfile"&gt;curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET \
  -d code=YOUR_CODE \
  -d grant_type=authorization_code
&lt;/pre&gt;&lt;/p&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="8228" data-end="8357"&gt;
&lt;p data-start="8230" data-end="8357"&gt;Set &lt;code data-start="8234" data-end="8252"&gt;STRAVA_CLIENT_ID&lt;/code&gt;, &lt;code data-start="8254" data-end="8276"&gt;STRAVA_CLIENT_SECRET&lt;/code&gt;, &lt;code data-start="8278" data-end="8299"&gt;STRAVA_ACCESS_TOKEN&lt;/code&gt; and &lt;code data-start="8304" data-end="8326"&gt;STRAVA_REFRESH_TOKEN&lt;/code&gt; in the &lt;code data-start="8334" data-end="8347"&gt;CookieJarV2&lt;/code&gt; sketch.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8358" data-end="8502"&gt;
&lt;p data-start="8360" data-end="8502"&gt;Flash the ESP32-S3 and test by opening the device&amp;rsquo;s web page &amp;rarr; &amp;ldquo;Show last activity&amp;rdquo;. You should see your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mcetoc_1jc6nt5li2" data-start="8509" data-end="8540"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/Picture14.jpg" /&gt;&lt;/h2&gt;
&lt;h2 id="mcetoc_1jc6f1e556" data-start="8509" data-end="8540"&gt;Final assembly and test&lt;/h2&gt;
&lt;p data-start="8541" data-end="8568"&gt;Once assembled and flashed:&lt;/p&gt;
&lt;ul data-start="8569" data-end="8825"&gt;
&lt;li data-start="8569" data-end="8718"&gt;
&lt;p data-start="8571" data-end="8718"&gt;Lock the jar, set your target distance via the device web UI, go for your run, and wait for the device to poll Strava and evaluate your activity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8719" data-end="8825"&gt;
&lt;p data-start="8721" data-end="8825"&gt;The sketch will automatically refresh tokens if required and will periodically check for new activities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="8827" data-end="9077"&gt;Milos demonstrates this flow in the video and finishes by going for a run to show the jar unlocking when the goal is met. He notes that the jar, when seen from the outside, looks like an ordinary jar of candy &amp;mdash; the mechanism is hidden inside the lid.&lt;/p&gt;
&lt;blockquote data-start="9079" data-end="9257"&gt;
&lt;p data-start="9081" data-end="9257"&gt;&amp;ldquo;If you look at it from outside, it just looks like a jar full of candy bars&amp;hellip; you won&amp;rsquo;t even know that there are some electronics inside.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6f1e557" data-start="9264" data-end="9281"&gt;Resources&lt;/h2&gt;
&lt;ul data-start="9282" data-end="9592"&gt;
&lt;li data-start="9282" data-end="9442"&gt;
&lt;p data-start="9284" data-end="9442"&gt;Cookie Jar v1 Writeup: &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="9327" data-end="9440" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9443" data-end="9521"&gt;
&lt;p data-start="9445" data-end="9521"&gt;Strava developer docs: &lt;a class="decorated-link" href="https://developers.strava.com/docs/getting-started/" data-start="9468" data-end="9519" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://developers.strava.com/docs/getting-started/&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9522" data-end="9592"&gt;
&lt;p data-start="9524" data-end="9592"&gt;Code and files:&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6u0l670"&gt;Bill of Materials&lt;/h2&gt;
&lt;table class="e14-product-bom-main"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Product Name&lt;/th&gt;
&lt;th&gt;Manufacturer&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;&lt;a id="e14-product-link-0b6d7" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=4200195,3769965,3879657&amp;nsku=74AK7863,52AJ5992,99AJ4470&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_BUY_KIT" class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('0b6d7'));" data-farnell="4200195,3769965,3879657" data-newark="74AK7863,52AJ5992,99AJ4470" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Kit&lt;/a&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XIAO ESP32S3&lt;/td&gt;
&lt;td&gt;SEEED STUDIO&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-18aa3" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=4200195&amp;nsku=74AK7863&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('18aa3'));" data-farnell="4200195" data-newark="74AK7863" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SG90 Servo&lt;/td&gt;
&lt;td&gt;DFROBOT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-bbf1f" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=3769965&amp;nsku=52AJ5992&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('bbf1f'));" data-farnell="3769965" data-newark="52AJ5992" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Battery Charger&lt;/td&gt;
&lt;td&gt;DFROBOT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-84ba9" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=3879657&amp;nsku=99AJ4470&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('84ba9'));" data-farnell="3879657" data-newark="99AJ4470" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="xs-hide"&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6u0l671"&gt;Additional Parts&lt;/h2&gt;
&lt;table class="e14-product-bom-additional"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Product Name&lt;/th&gt;
&lt;th&gt;Manufacturer&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glass Cookie Jar&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1S LION Battery&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D Printing Filament like PLA&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="mcetoc_1jc6u0l672"&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;

&lt;div style="font-size: 90%;"&gt;Tags: strava integration, 3d printed mechanism, wifi jar lock, diy smart jar, fitness reward system, esp32 project, esp32 servo control, fitness tracker diy, smart cookie jar, iot fitness project, strava API, friday_release, esp32 automation, locked biscuit barrel&lt;/div&gt;
</description></item><item><title>Earn Your Fitness Reward with a Smart Cookie Jar Using Strava and ESP32 -- Episode 694</title><link>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694/revision/7</link><pubDate>Thu, 11 Dec 2025 14:46:21 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f08580bd-ff57-4539-b2f1-de7eb0a248b0</guid><dc:creator>cstanton</dc:creator><comments>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694#comments</comments><description>Revision 7 posted to Documents by cstanton on 12/11/2025 2:46:21 PM&lt;br /&gt;
&lt;p data-start="708" data-end="1051"&gt;In this video, Milos tackles a familiar problem: it&amp;rsquo;s the holiday season, there&amp;rsquo;s lots of delicious food and candy everywhere, and it&amp;rsquo;s too cold to go out much. To help with that, he created a Smart Cookie Jar that keeps you true to your word. The jar locks itself with all those treats inside and won&amp;rsquo;t open until you reach your running goal.&lt;/p&gt;
&lt;p data-start="1053" data-end="1094"&gt;In Milos&amp;#39; own words:&lt;/p&gt;
&lt;blockquote data-start="1095" data-end="1250"&gt;
&lt;p data-start="1097" data-end="1250"&gt;&amp;ldquo;It&amp;rsquo;s the holiday season&amp;hellip; But if you want to get to that candy, we&amp;rsquo;ll make something that will make you run first.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6nmhpc0" data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;/strong&gt;Watch the Project&lt;/h2&gt;
&lt;p data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;a href="https://youtu.be/jsQTD_6HNTA"&gt;https://youtu.be/jsQTD_6HNTA&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj40" data-start="1278" data-end="1290"&gt;What&amp;#39;s the Idea&lt;/h2&gt;
&lt;p data-start="1291" data-end="1499"&gt;Milos first built this project more than six years ago, back before he owned a 3D printer. He&amp;rsquo;s rebuilt it now to make it smaller and more refined, taking advantage of 3D printing and modern microcontrollers.&lt;/p&gt;
&lt;p data-start="1501" data-end="1825"&gt;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.&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;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.&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1205.Picture4.png" /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1033.Picture5.png" /&gt;&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj41" data-start="2092" data-end="2109"&gt;Enacting the Mechanism&lt;/h2&gt;
&lt;p data-start="2110" data-end="2419"&gt;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).&lt;/p&gt;
&lt;p data-start="2421" data-end="2646"&gt;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.&lt;/p&gt;
&lt;p data-start="2648" data-end="2709"&gt;A quote from the video where Milos describes the mechanism:&lt;/p&gt;
&lt;blockquote data-start="2710" data-end="2953"&gt;
&lt;p data-start="2712" data-end="2953"&gt;&amp;ldquo;In the centre we have the servo horn&amp;hellip; and around that we have the three pistons that move linearly. So they&amp;rsquo;re connected together using this small pushrod&amp;hellip; For the new version, I want to go with gears.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="2955" data-end="3094"&gt;The new lid packs everything tightly; from the outside (when unlocked) there&amp;rsquo;s no visual clue that anything special is inside &amp;mdash; just candy.&lt;/p&gt;
&lt;p data-start="2955" data-end="3094"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/2604.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj52" data-start="3101" data-end="3117"&gt;Setting up the Software&lt;/h2&gt;
&lt;p data-start="3118" data-end="3298"&gt;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.&lt;/p&gt;
&lt;p data-start="3300" data-end="3317"&gt;As Milos says:&lt;/p&gt;
&lt;blockquote data-start="3318" data-end="3509"&gt;
&lt;p data-start="3320" data-end="3509"&gt;&amp;ldquo;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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="mcetoc_1jc6etsj53" data-start="3511" data-end="3536"&gt;Strava flow (summary)&lt;/h3&gt;
&lt;ol data-start="3537" data-end="4070"&gt;
&lt;li data-start="3537" data-end="3647"&gt;
&lt;p data-start="3540" data-end="3647"&gt;Create a Strava API application (Settings &amp;rarr; My API Application) and note &lt;code data-start="3613" data-end="3624"&gt;client_id&lt;/code&gt; and &lt;code data-start="3629" data-end="3644"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3648" data-end="3717"&gt;
&lt;p data-start="3651" data-end="3717"&gt;Build and open the authorize URL to obtain the temporary &lt;code data-start="3708" data-end="3714"&gt;code&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3718" data-end="3816"&gt;
&lt;p data-start="3721" data-end="3816"&gt;Exchange the &lt;code data-start="3734" data-end="3740"&gt;code&lt;/code&gt; for &lt;code data-start="3745" data-end="3759"&gt;access_token&lt;/code&gt; and &lt;code data-start="3764" data-end="3779"&gt;refresh_token&lt;/code&gt; (the example cURL is shown below).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3817" data-end="3936"&gt;
&lt;p data-start="3820" data-end="3936"&gt;Copy the tokens into the Arduino sketch and flash the ESP32-S3. The code refreshes tokens automatically as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3937" data-end="4070"&gt;
&lt;p data-start="3940" data-end="4070"&gt;Open the device web page and choose &lt;strong data-start="3976" data-end="3998"&gt;Show last activity&lt;/strong&gt; to verify the device returns your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-start="4072" data-end="4212"&gt;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.&lt;/p&gt;
&lt;blockquote data-start="4214" data-end="4332"&gt;
&lt;p data-start="4216" data-end="4332"&gt;&amp;ldquo;It will just give you the name of your last activity, the distance you ran&amp;hellip; &amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6nsfgb1" data-start="4339" data-end="4389"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1108.jpg" /&gt;&lt;/h2&gt;
&lt;h2 id="mcetoc_1jc6etsj54" data-start="4339" data-end="4389"&gt;Key code excerpts (from &lt;code data-start="4369" data-end="4386"&gt;CookieJarV2.ino&lt;/code&gt;)&lt;/h2&gt;
&lt;p data-start="4390" data-end="4545"&gt;Below are short, focused excerpts from the project code to explain the important behaviours. These are included to show how the software actually operates.&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="4547" data-end="4616"&gt;Configuration constants (Wi-Fi, Strava tokens and servo settings)&lt;/strong&gt;&amp;nbsp;- set these before flashing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Wi-Fi credentials
const char *WIFI_SSID = &amp;quot;YOUR-SSID&amp;quot;;
const char *WIFI_PASSWORD = &amp;quot;YOUR-PASSWORD&amp;quot;;

// Strava tokens and keys (example placeholders)
const char *STRAVA_CLIENT_ID = &amp;quot;YOUR_STRAVA_CLIENT_ID&amp;quot;;
const char *STRAVA_CLIENT_SECRET = &amp;quot;YOUR_STRAVA_CLIENT_SECRET&amp;quot;;
const char *STRAVA_ACCESS_TOKEN = &amp;quot;YOUR_STRAVA_ACCESS_TOKEN&amp;quot;;
const char *STRAVA_REFRESH_TOKEN = &amp;quot;YOUR_STRAVA_REFRESH_TOKEN&amp;quot;;

// 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;
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="5252" data-end="5275"&gt;Servo motion helper&lt;/strong&gt;&amp;nbsp;- the code attaches the servo, moves to the angle, waits, and detaches. Detaching stops the constant PWM &amp;ldquo;holding&amp;rdquo; signal, saving battery and stopping buzzing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void moveServoTo(int angle) {
  jarServo.attach(PIN_SERVO);
  delay(100);
  jarServo.write(angle);
  delay(SERVO_PULSE_DELAY_MS);
  jarServo.detach();
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="5602" data-end="5695"&gt;This corresponds with Milos&amp;rsquo;s on-camera note about saving power and avoiding servo buzzing:&lt;/p&gt;
&lt;blockquote data-start="5696" data-end="6040"&gt;
&lt;p data-start="5698" data-end="6040"&gt;&amp;ldquo;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&amp;hellip; 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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;strong data-start="6042" data-end="6079"&gt;Strava fetch (sketch of approach)&lt;/strong&gt;&amp;nbsp;- 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):&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;bool fetchStravaLatestActivity() {
  ensureWiFi();
  if (WiFi.status() != WL_CONNECTED) {
    lastError = &amp;quot;WiFi disconnected&amp;quot;;
    return false;
  }

  // HTTP call to Strava /athlete/activities or other endpoint
  // Parse JSON, extract last activity id, name, distance (m -&amp;gt; km), calories etc.
  // Update lastActivity struct and hasLastActivity flag
  ...
  return true; // or false on error
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;The sketch includes a regular poll loop:&lt;/p&gt;
&lt;div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"&gt;
&lt;div class="sticky top-9"&gt;
&lt;div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;const unsigned long STRAVA_POLL_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 minutes

// inside loop():
unsigned long now = millis();
if (now - lastPollMs &amp;gt;= STRAVA_POLL_INTERVAL_MS || now &amp;lt; lastPollMs) {
  lastPollMs = now;
  ActivityStats stats;
  if (fetchStravaToday(stats)) {
    lastStats = stats;
  } else if (fetchStravaLatestActivity()) {
    lastStats.distanceKm = lastActivity.distanceKm;
    lastStats.calories = lastActivity.calories;
    lastStats.fresh = true;
  }
  evaluateGoalAndMaybeUnlock();
}
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;
&lt;p data-start="7229" data-end="7428"&gt;&lt;code data-start="7229" data-end="7259"&gt;evaluateGoalAndMaybeUnlock()&lt;/code&gt; is the function that takes the gathered activity stats and decides whether to call &lt;code data-start="7343" data-end="7376"&gt;moveServoTo(SERVO_UNLOCK_ANGLE)&lt;/code&gt; (unlock) or &lt;code data-start="7389" data-end="7420"&gt;moveServoTo(SERVO_LOCK_ANGLE)&lt;/code&gt; (lock).&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6f0gj05" data-start="7435" data-end="7490"&gt;Step-by-step: Strava auth/setup (short checklist)&lt;/h2&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="7491" data-end="7587"&gt;
&lt;p data-start="7493" data-end="7587"&gt;Create a Strava API app in your Strava account and note the &lt;code data-start="7553" data-end="7564"&gt;client_id&lt;/code&gt; and &lt;code data-start="7569" data-end="7584"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7588" data-end="7854"&gt;
&lt;p data-start="7590" data-end="7854"&gt;Build the authorisation URL (replace &lt;code data-start="7627" data-end="7638"&gt;CLIENT_ID&lt;/code&gt; and &lt;code data-start="7643" data-end="7657"&gt;REDIRECT_URI&lt;/code&gt;):&lt;br data-start="7659" data-end="7662" /&gt; &lt;code data-start="7664" data-end="7852"&gt;https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&amp;amp;response_type=code&amp;amp;redirect_uri=http://localhost/exchange_token&amp;amp;approval_prompt=auto&amp;amp;scope=read,activity:read,activity:read_all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7855" data-end="7959"&gt;
&lt;p data-start="7857" data-end="7959"&gt;Open the URL in a browser, approve the app, capture the returned &lt;code data-start="7922" data-end="7928"&gt;code&lt;/code&gt; (e.g. &lt;code data-start="7935" data-end="7955"&gt;...?code=YOUR_CODE&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7960" data-end="8227"&gt;
&lt;p data-start="7962" data-end="8023"&gt;Exchange the &lt;code data-start="7975" data-end="7981"&gt;code&lt;/code&gt; for tokens (on your PC) &amp;mdash; example cURL:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="batchfile"&gt;curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET \
  -d code=YOUR_CODE \
  -d grant_type=authorization_code
&lt;/pre&gt;&lt;/p&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="8228" data-end="8357"&gt;
&lt;p data-start="8230" data-end="8357"&gt;Set &lt;code data-start="8234" data-end="8252"&gt;STRAVA_CLIENT_ID&lt;/code&gt;, &lt;code data-start="8254" data-end="8276"&gt;STRAVA_CLIENT_SECRET&lt;/code&gt;, &lt;code data-start="8278" data-end="8299"&gt;STRAVA_ACCESS_TOKEN&lt;/code&gt; and &lt;code data-start="8304" data-end="8326"&gt;STRAVA_REFRESH_TOKEN&lt;/code&gt; in the &lt;code data-start="8334" data-end="8347"&gt;CookieJarV2&lt;/code&gt; sketch.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8358" data-end="8502"&gt;
&lt;p data-start="8360" data-end="8502"&gt;Flash the ESP32-S3 and test by opening the device&amp;rsquo;s web page &amp;rarr; &amp;ldquo;Show last activity&amp;rdquo;. You should see your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mcetoc_1jc6nt5li2" data-start="8509" data-end="8540"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/Picture14.jpg" /&gt;&lt;/h2&gt;
&lt;h2 id="mcetoc_1jc6f1e556" data-start="8509" data-end="8540"&gt;Final assembly and test&lt;/h2&gt;
&lt;p data-start="8541" data-end="8568"&gt;Once assembled and flashed:&lt;/p&gt;
&lt;ul data-start="8569" data-end="8825"&gt;
&lt;li data-start="8569" data-end="8718"&gt;
&lt;p data-start="8571" data-end="8718"&gt;Lock the jar, set your target distance via the device web UI, go for your run, and wait for the device to poll Strava and evaluate your activity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8719" data-end="8825"&gt;
&lt;p data-start="8721" data-end="8825"&gt;The sketch will automatically refresh tokens if required and will periodically check for new activities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="8827" data-end="9077"&gt;Milos demonstrates this flow in the video and finishes by going for a run to show the jar unlocking when the goal is met. He notes that the jar, when seen from the outside, looks like an ordinary jar of candy &amp;mdash; the mechanism is hidden inside the lid.&lt;/p&gt;
&lt;blockquote data-start="9079" data-end="9257"&gt;
&lt;p data-start="9081" data-end="9257"&gt;&amp;ldquo;If you look at it from outside, it just looks like a jar full of candy bars&amp;hellip; you won&amp;rsquo;t even know that there are some electronics inside.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6f1e557" data-start="9264" data-end="9281"&gt;Resources&lt;/h2&gt;
&lt;ul data-start="9282" data-end="9592"&gt;
&lt;li data-start="9282" data-end="9442"&gt;
&lt;p data-start="9284" data-end="9442"&gt;Cookie Jar v1 Writeup: &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="9327" data-end="9440" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9443" data-end="9521"&gt;
&lt;p data-start="9445" data-end="9521"&gt;Strava developer docs: &lt;a class="decorated-link" href="https://developers.strava.com/docs/getting-started/" data-start="9468" data-end="9519" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://developers.strava.com/docs/getting-started/&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9522" data-end="9592"&gt;
&lt;p data-start="9524" data-end="9592"&gt;Code and files:&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6u0l670"&gt;Bill of Materials&lt;/h2&gt;
&lt;table class="e14-product-bom-main"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Product Name&lt;/th&gt;
&lt;th&gt;Manufacturer&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;&lt;a id="e14-product-link-62008" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=4200195,3769965,3879657&amp;nsku=74AK7863,52AJ5992,99AJ4470&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_BUY_KIT" class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('62008'));" data-farnell="4200195,3769965,3879657" data-newark="74AK7863,52AJ5992,99AJ4470" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Kit&lt;/a&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XIAO ESP32S3&lt;/td&gt;
&lt;td&gt;SEEED STUDIO&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-05e79" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=4200195&amp;nsku=74AK7863&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('05e79'));" data-farnell="4200195" data-newark="74AK7863" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SG90 Servo&lt;/td&gt;
&lt;td&gt;DFROBOT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-5c7d6" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=3769965&amp;nsku=52AJ5992&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('5c7d6'));" data-farnell="3769965" data-newark="52AJ5992" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Battery Charger&lt;/td&gt;
&lt;td&gt;DFROBOT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a id="e14-product-link-b67d6" data-at-areainteracted="rte-content" data-at-type="click" data-at-link-type="button" href="https://referral.element14.com/OrderCodeView?fsku=3879657&amp;nsku=99AJ4470&amp;COM=e14c-noscript&amp;CMP=e14c-noscript&amp;osetc=e14-noscript-tracking-loss" data-at-label="PRODUCT_POPUP_OPEN"class="e14-embedded e14_shopping-cart-far e14-button" onclick="event.preventDefault();e14.func.displayProduct(e14.meta.user.country, this, 'embedded-link', e14.func.getProductLinkJSON('b67d6'));" data-farnell="3879657" data-newark="99AJ4470" data-comoverride="" data-cmpoverride="" data-cpc="" data-avnetemea="" data-avnetema="" data-avnetasia="" &gt;Buy Now&lt;/a&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="xs-hide"&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6u0l671"&gt;Additional Parts&lt;/h2&gt;
&lt;table class="e14-product-bom-additional"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Product Name&lt;/th&gt;
&lt;th&gt;Manufacturer&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glass Cookie Jar&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1S LION Battery&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D Printing Filament like PLA&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="mcetoc_1jc6u0l672"&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;

&lt;div style="font-size: 90%;"&gt;Tags: strava integration, 3d printed mechanism, wifi jar lock, diy smart jar, fitness reward system, esp32 project, esp32 servo control, fitness tracker diy, smart cookie jar, iot fitness project, strava API, friday_release, esp32 automation, locked biscuit barrel&lt;/div&gt;
</description></item><item><title>Earn Your Fitness Reward with a Smart Cookie Jar Using Strava and ESP32 -- Episode 694</title><link>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694/revision/6</link><pubDate>Thu, 11 Dec 2025 12:59:40 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f08580bd-ff57-4539-b2f1-de7eb0a248b0</guid><dc:creator>cstanton</dc:creator><comments>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694#comments</comments><description>Revision 6 posted to Documents by cstanton on 12/11/2025 12:59:40 PM&lt;br /&gt;
&lt;p data-start="708" data-end="1051"&gt;In this video, Milos tackles a familiar problem: it&amp;rsquo;s the holiday season, there&amp;rsquo;s lots of delicious food and candy everywhere, and it&amp;rsquo;s too cold to go out much. To help with that, he created a Smart Cookie Jar that keeps you true to your word. The jar locks itself with all those treats inside and won&amp;rsquo;t open until you reach your running goal.&lt;/p&gt;
&lt;p data-start="1053" data-end="1094"&gt;In Milos&amp;#39; own words:&lt;/p&gt;
&lt;blockquote data-start="1095" data-end="1250"&gt;
&lt;p data-start="1097" data-end="1250"&gt;&amp;ldquo;It&amp;rsquo;s the holiday season&amp;hellip; But if you want to get to that candy, we&amp;rsquo;ll make something that will make you run first.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6nmhpc0" data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;/strong&gt;Watch the Project&lt;/h2&gt;
&lt;p data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;a href="https://youtu.be/jsQTD_6HNTA"&gt;https://youtu.be/jsQTD_6HNTA&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj40" data-start="1278" data-end="1290"&gt;What&amp;#39;s the Idea&lt;/h2&gt;
&lt;p data-start="1291" data-end="1499"&gt;Milos first built this project more than six years ago, back before he owned a 3D printer. He&amp;rsquo;s rebuilt it now to make it smaller and more refined, taking advantage of 3D printing and modern microcontrollers.&lt;/p&gt;
&lt;p data-start="1501" data-end="1825"&gt;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.&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;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.&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1205.Picture4.png" /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1033.Picture5.png" /&gt;&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj41" data-start="2092" data-end="2109"&gt;Enacting the Mechanism&lt;/h2&gt;
&lt;p data-start="2110" data-end="2419"&gt;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).&lt;/p&gt;
&lt;p data-start="2421" data-end="2646"&gt;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.&lt;/p&gt;
&lt;p data-start="2648" data-end="2709"&gt;A quote from the video where Milos describes the mechanism:&lt;/p&gt;
&lt;blockquote data-start="2710" data-end="2953"&gt;
&lt;p data-start="2712" data-end="2953"&gt;&amp;ldquo;In the centre we have the servo horn&amp;hellip; and around that we have the three pistons that move linearly. So they&amp;rsquo;re connected together using this small pushrod&amp;hellip; For the new version, I want to go with gears.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="2955" data-end="3094"&gt;The new lid packs everything tightly; from the outside (when unlocked) there&amp;rsquo;s no visual clue that anything special is inside &amp;mdash; just candy.&lt;/p&gt;
&lt;p data-start="2955" data-end="3094"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/2604.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj52" data-start="3101" data-end="3117"&gt;Setting up the Software&lt;/h2&gt;
&lt;p data-start="3118" data-end="3298"&gt;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.&lt;/p&gt;
&lt;p data-start="3300" data-end="3317"&gt;As Milos says:&lt;/p&gt;
&lt;blockquote data-start="3318" data-end="3509"&gt;
&lt;p data-start="3320" data-end="3509"&gt;&amp;ldquo;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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="mcetoc_1jc6etsj53" data-start="3511" data-end="3536"&gt;Strava flow (summary)&lt;/h3&gt;
&lt;ol data-start="3537" data-end="4070"&gt;
&lt;li data-start="3537" data-end="3647"&gt;
&lt;p data-start="3540" data-end="3647"&gt;Create a Strava API application (Settings &amp;rarr; My API Application) and note &lt;code data-start="3613" data-end="3624"&gt;client_id&lt;/code&gt; and &lt;code data-start="3629" data-end="3644"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3648" data-end="3717"&gt;
&lt;p data-start="3651" data-end="3717"&gt;Build and open the authorize URL to obtain the temporary &lt;code data-start="3708" data-end="3714"&gt;code&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3718" data-end="3816"&gt;
&lt;p data-start="3721" data-end="3816"&gt;Exchange the &lt;code data-start="3734" data-end="3740"&gt;code&lt;/code&gt; for &lt;code data-start="3745" data-end="3759"&gt;access_token&lt;/code&gt; and &lt;code data-start="3764" data-end="3779"&gt;refresh_token&lt;/code&gt; (the example cURL is shown below).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3817" data-end="3936"&gt;
&lt;p data-start="3820" data-end="3936"&gt;Copy the tokens into the Arduino sketch and flash the ESP32-S3. The code refreshes tokens automatically as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3937" data-end="4070"&gt;
&lt;p data-start="3940" data-end="4070"&gt;Open the device web page and choose &lt;strong data-start="3976" data-end="3998"&gt;Show last activity&lt;/strong&gt; to verify the device returns your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-start="4072" data-end="4212"&gt;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.&lt;/p&gt;
&lt;blockquote data-start="4214" data-end="4332"&gt;
&lt;p data-start="4216" data-end="4332"&gt;&amp;ldquo;It will just give you the name of your last activity, the distance you ran&amp;hellip; &amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6nsfgb1" data-start="4339" data-end="4389"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/1108.jpg" /&gt;&lt;/h2&gt;
&lt;h2 id="mcetoc_1jc6etsj54" data-start="4339" data-end="4389"&gt;Key code excerpts (from &lt;code data-start="4369" data-end="4386"&gt;CookieJarV2.ino&lt;/code&gt;)&lt;/h2&gt;
&lt;p data-start="4390" data-end="4545"&gt;Below are short, focused excerpts from the project code to explain the important behaviours. These are included to show how the software actually operates.&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="4547" data-end="4616"&gt;Configuration constants (Wi-Fi, Strava tokens and servo settings)&lt;/strong&gt;&amp;nbsp;- set these before flashing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Wi-Fi credentials
const char *WIFI_SSID = &amp;quot;YOUR-SSID&amp;quot;;
const char *WIFI_PASSWORD = &amp;quot;YOUR-PASSWORD&amp;quot;;

// Strava tokens and keys (example placeholders)
const char *STRAVA_CLIENT_ID = &amp;quot;YOUR_STRAVA_CLIENT_ID&amp;quot;;
const char *STRAVA_CLIENT_SECRET = &amp;quot;YOUR_STRAVA_CLIENT_SECRET&amp;quot;;
const char *STRAVA_ACCESS_TOKEN = &amp;quot;YOUR_STRAVA_ACCESS_TOKEN&amp;quot;;
const char *STRAVA_REFRESH_TOKEN = &amp;quot;YOUR_STRAVA_REFRESH_TOKEN&amp;quot;;

// 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;
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="5252" data-end="5275"&gt;Servo motion helper&lt;/strong&gt;&amp;nbsp;- the code attaches the servo, moves to the angle, waits, and detaches. Detaching stops the constant PWM &amp;ldquo;holding&amp;rdquo; signal, saving battery and stopping buzzing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void moveServoTo(int angle) {
  jarServo.attach(PIN_SERVO);
  delay(100);
  jarServo.write(angle);
  delay(SERVO_PULSE_DELAY_MS);
  jarServo.detach();
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="5602" data-end="5695"&gt;This corresponds with Milos&amp;rsquo;s on-camera note about saving power and avoiding servo buzzing:&lt;/p&gt;
&lt;blockquote data-start="5696" data-end="6040"&gt;
&lt;p data-start="5698" data-end="6040"&gt;&amp;ldquo;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&amp;hellip; 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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;strong data-start="6042" data-end="6079"&gt;Strava fetch (sketch of approach)&lt;/strong&gt;&amp;nbsp;- 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):&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;bool fetchStravaLatestActivity() {
  ensureWiFi();
  if (WiFi.status() != WL_CONNECTED) {
    lastError = &amp;quot;WiFi disconnected&amp;quot;;
    return false;
  }

  // HTTP call to Strava /athlete/activities or other endpoint
  // Parse JSON, extract last activity id, name, distance (m -&amp;gt; km), calories etc.
  // Update lastActivity struct and hasLastActivity flag
  ...
  return true; // or false on error
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;The sketch includes a regular poll loop:&lt;/p&gt;
&lt;div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"&gt;
&lt;div class="sticky top-9"&gt;
&lt;div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;const unsigned long STRAVA_POLL_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 minutes

// inside loop():
unsigned long now = millis();
if (now - lastPollMs &amp;gt;= STRAVA_POLL_INTERVAL_MS || now &amp;lt; lastPollMs) {
  lastPollMs = now;
  ActivityStats stats;
  if (fetchStravaToday(stats)) {
    lastStats = stats;
  } else if (fetchStravaLatestActivity()) {
    lastStats.distanceKm = lastActivity.distanceKm;
    lastStats.calories = lastActivity.calories;
    lastStats.fresh = true;
  }
  evaluateGoalAndMaybeUnlock();
}
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;
&lt;p data-start="7229" data-end="7428"&gt;&lt;code data-start="7229" data-end="7259"&gt;evaluateGoalAndMaybeUnlock()&lt;/code&gt; is the function that takes the gathered activity stats and decides whether to call &lt;code data-start="7343" data-end="7376"&gt;moveServoTo(SERVO_UNLOCK_ANGLE)&lt;/code&gt; (unlock) or &lt;code data-start="7389" data-end="7420"&gt;moveServoTo(SERVO_LOCK_ANGLE)&lt;/code&gt; (lock).&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6f0gj05" data-start="7435" data-end="7490"&gt;Step-by-step: Strava auth/setup (short checklist)&lt;/h2&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="7491" data-end="7587"&gt;
&lt;p data-start="7493" data-end="7587"&gt;Create a Strava API app in your Strava account and note the &lt;code data-start="7553" data-end="7564"&gt;client_id&lt;/code&gt; and &lt;code data-start="7569" data-end="7584"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7588" data-end="7854"&gt;
&lt;p data-start="7590" data-end="7854"&gt;Build the authorisation URL (replace &lt;code data-start="7627" data-end="7638"&gt;CLIENT_ID&lt;/code&gt; and &lt;code data-start="7643" data-end="7657"&gt;REDIRECT_URI&lt;/code&gt;):&lt;br data-start="7659" data-end="7662" /&gt; &lt;code data-start="7664" data-end="7852"&gt;https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&amp;amp;response_type=code&amp;amp;redirect_uri=http://localhost/exchange_token&amp;amp;approval_prompt=auto&amp;amp;scope=read,activity:read,activity:read_all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7855" data-end="7959"&gt;
&lt;p data-start="7857" data-end="7959"&gt;Open the URL in a browser, approve the app, capture the returned &lt;code data-start="7922" data-end="7928"&gt;code&lt;/code&gt; (e.g. &lt;code data-start="7935" data-end="7955"&gt;...?code=YOUR_CODE&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7960" data-end="8227"&gt;
&lt;p data-start="7962" data-end="8023"&gt;Exchange the &lt;code data-start="7975" data-end="7981"&gt;code&lt;/code&gt; for tokens (on your PC) &amp;mdash; example cURL:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="batchfile"&gt;curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET \
  -d code=YOUR_CODE \
  -d grant_type=authorization_code
&lt;/pre&gt;&lt;/p&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="8228" data-end="8357"&gt;
&lt;p data-start="8230" data-end="8357"&gt;Set &lt;code data-start="8234" data-end="8252"&gt;STRAVA_CLIENT_ID&lt;/code&gt;, &lt;code data-start="8254" data-end="8276"&gt;STRAVA_CLIENT_SECRET&lt;/code&gt;, &lt;code data-start="8278" data-end="8299"&gt;STRAVA_ACCESS_TOKEN&lt;/code&gt; and &lt;code data-start="8304" data-end="8326"&gt;STRAVA_REFRESH_TOKEN&lt;/code&gt; in the &lt;code data-start="8334" data-end="8347"&gt;CookieJarV2&lt;/code&gt; sketch.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8358" data-end="8502"&gt;
&lt;p data-start="8360" data-end="8502"&gt;Flash the ESP32-S3 and test by opening the device&amp;rsquo;s web page &amp;rarr; &amp;ldquo;Show last activity&amp;rdquo;. You should see your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mcetoc_1jc6nt5li2" data-start="8509" data-end="8540"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="/resized-image/__size/1280x720/__key/communityserver-wikis-components-files/00-00-00-04-35/Picture14.jpg" /&gt;&lt;/h2&gt;
&lt;h2 id="mcetoc_1jc6f1e556" data-start="8509" data-end="8540"&gt;Final assembly and test&lt;/h2&gt;
&lt;p data-start="8541" data-end="8568"&gt;Once assembled and flashed:&lt;/p&gt;
&lt;ul data-start="8569" data-end="8825"&gt;
&lt;li data-start="8569" data-end="8718"&gt;
&lt;p data-start="8571" data-end="8718"&gt;Lock the jar, set your target distance via the device web UI, go for your run, and wait for the device to poll Strava and evaluate your activity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8719" data-end="8825"&gt;
&lt;p data-start="8721" data-end="8825"&gt;The sketch will automatically refresh tokens if required and will periodically check for new activities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="8827" data-end="9077"&gt;Milos demonstrates this flow in the video and finishes by going for a run to show the jar unlocking when the goal is met. He notes that the jar, when seen from the outside, looks like an ordinary jar of candy &amp;mdash; the mechanism is hidden inside the lid.&lt;/p&gt;
&lt;blockquote data-start="9079" data-end="9257"&gt;
&lt;p data-start="9081" data-end="9257"&gt;&amp;ldquo;If you look at it from outside, it just looks like a jar full of candy bars&amp;hellip; you won&amp;rsquo;t even know that there are some electronics inside.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6f1e557" data-start="9264" data-end="9281"&gt;Resources&lt;/h2&gt;
&lt;ul data-start="9282" data-end="9592"&gt;
&lt;li data-start="9282" data-end="9442"&gt;
&lt;p data-start="9284" data-end="9442"&gt;Original project write-up (older version): &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="9327" data-end="9440" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9443" data-end="9521"&gt;
&lt;p data-start="9445" data-end="9521"&gt;Strava developer docs: &lt;a class="decorated-link" href="https://developers.strava.com/docs/getting-started/" data-start="9468" data-end="9519" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://developers.strava.com/docs/getting-started/&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9522" data-end="9592"&gt;
&lt;p data-start="9524" data-end="9592"&gt;Code and files:&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;

&lt;div style="font-size: 90%;"&gt;Tags: strava integration, 3d printed mechanism, wifi jar lock, diy smart jar, fitness reward system, esp32 project, esp32 servo control, fitness tracker diy, smart cookie jar, iot fitness project, strava API, friday_release, esp32 automation, locked biscuit barrel&lt;/div&gt;
</description></item><item><title>Earn Your Fitness Reward with a Smart Cookie Jar Using Strava and ESP32 -- Episode 694</title><link>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694/revision/5</link><pubDate>Thu, 11 Dec 2025 12:55:12 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f08580bd-ff57-4539-b2f1-de7eb0a248b0</guid><dc:creator>cstanton</dc:creator><comments>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694#comments</comments><description>Revision 5 posted to Documents by cstanton on 12/11/2025 12:55:12 PM&lt;br /&gt;
&lt;p data-start="708" data-end="1051"&gt;In this video, Milos tackles a familiar problem: it&amp;rsquo;s the holiday season, there&amp;rsquo;s lots of delicious food and candy everywhere, and it&amp;rsquo;s too cold to go out much. To help with that, he created a Smart Cookie Jar that keeps you true to your word. The jar locks itself with all those treats inside and won&amp;rsquo;t open until you reach your running goal.&lt;/p&gt;
&lt;p data-start="1053" data-end="1094"&gt;In Milos&amp;#39; own words:&lt;/p&gt;
&lt;blockquote data-start="1095" data-end="1250"&gt;
&lt;p data-start="1097" data-end="1250"&gt;&amp;ldquo;It&amp;rsquo;s the holiday season&amp;hellip; But if you want to get to that candy, we&amp;rsquo;ll make something that will make you run first.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;/strong&gt;Watch the Project&lt;/h2&gt;
&lt;p data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;a href="https://youtu.be/jsQTD_6HNTA"&gt;https://youtu.be/jsQTD_6HNTA&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj40" data-start="1278" data-end="1290"&gt;What&amp;#39;s the Idea&lt;/h2&gt;
&lt;p data-start="1291" data-end="1499"&gt;Milos first built this project more than six years ago, back before he owned a 3D printer. He&amp;rsquo;s rebuilt it now to make it smaller and more refined, taking advantage of 3D printing and modern microcontrollers.&lt;/p&gt;
&lt;p data-start="1501" data-end="1825"&gt;The original mechanism was hand-cut from 4 mm plywood; the small pushrods used sheet metal salvaged from a gutter, there was plenty of room for improvement.&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;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.&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj41" data-start="2092" data-end="2109"&gt;Enacting the Mechanism&lt;/h2&gt;
&lt;p data-start="2110" data-end="2419"&gt;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).&lt;/p&gt;
&lt;p data-start="2421" data-end="2646"&gt;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.&lt;/p&gt;
&lt;p data-start="2648" data-end="2709"&gt;A quote from the video where Milos describes the mechanism:&lt;/p&gt;
&lt;blockquote data-start="2710" data-end="2953"&gt;
&lt;p data-start="2712" data-end="2953"&gt;&amp;ldquo;In the centre we have the servo horn&amp;hellip; and around that we have the three pistons that move linearly. So they&amp;rsquo;re connected together using this small pushrod&amp;hellip; For the new version, I want to go with gears.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="2955" data-end="3094"&gt;The new lid packs everything tightly; from the outside (when unlocked) there&amp;rsquo;s no visual clue that anything special is inside &amp;mdash; just candy.&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6etsj52" data-start="3101" data-end="3117"&gt;Setting up the Software&lt;/h2&gt;
&lt;p data-start="3118" data-end="3298"&gt;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.&lt;/p&gt;
&lt;p data-start="3300" data-end="3317"&gt;As Milos says:&lt;/p&gt;
&lt;blockquote data-start="3318" data-end="3509"&gt;
&lt;p data-start="3320" data-end="3509"&gt;&amp;ldquo;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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="mcetoc_1jc6etsj53" data-start="3511" data-end="3536"&gt;Strava flow (summary)&lt;/h3&gt;
&lt;ol data-start="3537" data-end="4070"&gt;
&lt;li data-start="3537" data-end="3647"&gt;
&lt;p data-start="3540" data-end="3647"&gt;Create a Strava API application (Settings &amp;rarr; My API Application) and note &lt;code data-start="3613" data-end="3624"&gt;client_id&lt;/code&gt; and &lt;code data-start="3629" data-end="3644"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3648" data-end="3717"&gt;
&lt;p data-start="3651" data-end="3717"&gt;Build and open the authorize URL to obtain the temporary &lt;code data-start="3708" data-end="3714"&gt;code&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3718" data-end="3816"&gt;
&lt;p data-start="3721" data-end="3816"&gt;Exchange the &lt;code data-start="3734" data-end="3740"&gt;code&lt;/code&gt; for &lt;code data-start="3745" data-end="3759"&gt;access_token&lt;/code&gt; and &lt;code data-start="3764" data-end="3779"&gt;refresh_token&lt;/code&gt; (the example cURL is shown below).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3817" data-end="3936"&gt;
&lt;p data-start="3820" data-end="3936"&gt;Copy the tokens into the Arduino sketch and flash the ESP32-S3. The code refreshes tokens automatically as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3937" data-end="4070"&gt;
&lt;p data-start="3940" data-end="4070"&gt;Open the device web page and choose &lt;strong data-start="3976" data-end="3998"&gt;Show last activity&lt;/strong&gt; to verify the device returns your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-start="4072" data-end="4212"&gt;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.&lt;/p&gt;
&lt;blockquote data-start="4214" data-end="4332"&gt;
&lt;p data-start="4216" data-end="4332"&gt;&amp;ldquo;It will just give you the name of your last activity, the distance you ran&amp;hellip; &amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6etsj54" data-start="4339" data-end="4389"&gt;Key code excerpts (from &lt;code data-start="4369" data-end="4386"&gt;CookieJarV2.ino&lt;/code&gt;)&lt;/h2&gt;
&lt;p data-start="4390" data-end="4545"&gt;Below are short, focused excerpts from the project code to explain the important behaviours. These are included to show how the software actually operates.&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="4547" data-end="4616"&gt;Configuration constants (Wi-Fi, Strava tokens and servo settings)&lt;/strong&gt;&amp;nbsp;- set these before flashing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Wi-Fi credentials
const char *WIFI_SSID = &amp;quot;YOUR-SSID&amp;quot;;
const char *WIFI_PASSWORD = &amp;quot;YOUR-PASSWORD&amp;quot;;

// Strava tokens and keys (example placeholders)
const char *STRAVA_CLIENT_ID = &amp;quot;YOUR_STRAVA_CLIENT_ID&amp;quot;;
const char *STRAVA_CLIENT_SECRET = &amp;quot;YOUR_STRAVA_CLIENT_SECRET&amp;quot;;
const char *STRAVA_ACCESS_TOKEN = &amp;quot;YOUR_STRAVA_ACCESS_TOKEN&amp;quot;;
const char *STRAVA_REFRESH_TOKEN = &amp;quot;YOUR_STRAVA_REFRESH_TOKEN&amp;quot;;

// 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;
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="5252" data-end="5275"&gt;Servo motion helper&lt;/strong&gt;&amp;nbsp;- the code attaches the servo, moves to the angle, waits, and detaches. Detaching stops the constant PWM &amp;ldquo;holding&amp;rdquo; signal, saving battery and stopping buzzing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void moveServoTo(int angle) {
  jarServo.attach(PIN_SERVO);
  delay(100);
  jarServo.write(angle);
  delay(SERVO_PULSE_DELAY_MS);
  jarServo.detach();
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="5602" data-end="5695"&gt;This corresponds with Milos&amp;rsquo;s on-camera note about saving power and avoiding servo buzzing:&lt;/p&gt;
&lt;blockquote data-start="5696" data-end="6040"&gt;
&lt;p data-start="5698" data-end="6040"&gt;&amp;ldquo;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&amp;hellip; 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.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;strong data-start="6042" data-end="6079"&gt;Strava fetch (sketch of approach)&lt;/strong&gt;&amp;nbsp;- 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):&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;bool fetchStravaLatestActivity() {
  ensureWiFi();
  if (WiFi.status() != WL_CONNECTED) {
    lastError = &amp;quot;WiFi disconnected&amp;quot;;
    return false;
  }

  // HTTP call to Strava /athlete/activities or other endpoint
  // Parse JSON, extract last activity id, name, distance (m -&amp;gt; km), calories etc.
  // Update lastActivity struct and hasLastActivity flag
  ...
  return true; // or false on error
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;The sketch includes a regular poll loop:&lt;/p&gt;
&lt;div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"&gt;
&lt;div class="sticky top-9"&gt;
&lt;div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;const unsigned long STRAVA_POLL_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 minutes

// inside loop():
unsigned long now = millis();
if (now - lastPollMs &amp;gt;= STRAVA_POLL_INTERVAL_MS || now &amp;lt; lastPollMs) {
  lastPollMs = now;
  ActivityStats stats;
  if (fetchStravaToday(stats)) {
    lastStats = stats;
  } else if (fetchStravaLatestActivity()) {
    lastStats.distanceKm = lastActivity.distanceKm;
    lastStats.calories = lastActivity.calories;
    lastStats.fresh = true;
  }
  evaluateGoalAndMaybeUnlock();
}
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;
&lt;p data-start="7229" data-end="7428"&gt;&lt;code data-start="7229" data-end="7259"&gt;evaluateGoalAndMaybeUnlock()&lt;/code&gt; is the function that takes the gathered activity stats and decides whether to call &lt;code data-start="7343" data-end="7376"&gt;moveServoTo(SERVO_UNLOCK_ANGLE)&lt;/code&gt; (unlock) or &lt;code data-start="7389" data-end="7420"&gt;moveServoTo(SERVO_LOCK_ANGLE)&lt;/code&gt; (lock).&lt;/p&gt;
&lt;h2 id="mcetoc_1jc6f0gj05" data-start="7435" data-end="7490"&gt;Step-by-step: Strava auth/setup (short checklist)&lt;/h2&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="7491" data-end="7587"&gt;
&lt;p data-start="7493" data-end="7587"&gt;Create a Strava API app in your Strava account and note the &lt;code data-start="7553" data-end="7564"&gt;client_id&lt;/code&gt; and &lt;code data-start="7569" data-end="7584"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7588" data-end="7854"&gt;
&lt;p data-start="7590" data-end="7854"&gt;Build the authorisation URL (replace &lt;code data-start="7627" data-end="7638"&gt;CLIENT_ID&lt;/code&gt; and &lt;code data-start="7643" data-end="7657"&gt;REDIRECT_URI&lt;/code&gt;):&lt;br data-start="7659" data-end="7662" /&gt; &lt;code data-start="7664" data-end="7852"&gt;https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&amp;amp;response_type=code&amp;amp;redirect_uri=http://localhost/exchange_token&amp;amp;approval_prompt=auto&amp;amp;scope=read,activity:read,activity:read_all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7855" data-end="7959"&gt;
&lt;p data-start="7857" data-end="7959"&gt;Open the URL in a browser, approve the app, capture the returned &lt;code data-start="7922" data-end="7928"&gt;code&lt;/code&gt; (e.g. &lt;code data-start="7935" data-end="7955"&gt;...?code=YOUR_CODE&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7960" data-end="8227"&gt;
&lt;p data-start="7962" data-end="8023"&gt;Exchange the &lt;code data-start="7975" data-end="7981"&gt;code&lt;/code&gt; for tokens (on your PC) &amp;mdash; example cURL:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="batchfile"&gt;curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET \
  -d code=YOUR_CODE \
  -d grant_type=authorization_code
&lt;/pre&gt;&lt;/p&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="8228" data-end="8357"&gt;
&lt;p data-start="8230" data-end="8357"&gt;Set &lt;code data-start="8234" data-end="8252"&gt;STRAVA_CLIENT_ID&lt;/code&gt;, &lt;code data-start="8254" data-end="8276"&gt;STRAVA_CLIENT_SECRET&lt;/code&gt;, &lt;code data-start="8278" data-end="8299"&gt;STRAVA_ACCESS_TOKEN&lt;/code&gt; and &lt;code data-start="8304" data-end="8326"&gt;STRAVA_REFRESH_TOKEN&lt;/code&gt; in the &lt;code data-start="8334" data-end="8347"&gt;CookieJarV2&lt;/code&gt; sketch.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8358" data-end="8502"&gt;
&lt;p data-start="8360" data-end="8502"&gt;Flash the ESP32-S3 and test by opening the device&amp;rsquo;s web page &amp;rarr; &amp;ldquo;Show last activity&amp;rdquo;. You should see your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mcetoc_1jc6f1e556" data-start="8509" data-end="8540"&gt;Final assembly and test&lt;/h2&gt;
&lt;p data-start="8541" data-end="8568"&gt;Once assembled and flashed:&lt;/p&gt;
&lt;ul data-start="8569" data-end="8825"&gt;
&lt;li data-start="8569" data-end="8718"&gt;
&lt;p data-start="8571" data-end="8718"&gt;Lock the jar, set your target distance via the device web UI, go for your run, and wait for the device to poll Strava and evaluate your activity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8719" data-end="8825"&gt;
&lt;p data-start="8721" data-end="8825"&gt;The sketch will automatically refresh tokens if required and will periodically check for new activities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="8827" data-end="9077"&gt;Milos demonstrates this flow in the video and finishes by going for a run to show the jar unlocking when the goal is met. He notes that the jar, when seen from the outside, looks like an ordinary jar of candy &amp;mdash; the mechanism is hidden inside the lid.&lt;/p&gt;
&lt;blockquote data-start="9079" data-end="9257"&gt;
&lt;p data-start="9081" data-end="9257"&gt;&amp;ldquo;If you look at it from outside, it just looks like a jar full of candy bars&amp;hellip; you won&amp;rsquo;t even know that there are some electronics inside.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mcetoc_1jc6f1e557" data-start="9264" data-end="9281"&gt;Resources&lt;/h2&gt;
&lt;ul data-start="9282" data-end="9592"&gt;
&lt;li data-start="9282" data-end="9442"&gt;
&lt;p data-start="9284" data-end="9442"&gt;Original project write-up (older version): &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="9327" data-end="9440" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9443" data-end="9521"&gt;
&lt;p data-start="9445" data-end="9521"&gt;Strava developer docs: &lt;a class="decorated-link" href="https://developers.strava.com/docs/getting-started/" data-start="9468" data-end="9519" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://developers.strava.com/docs/getting-started/&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9522" data-end="9592"&gt;
&lt;p data-start="9524" data-end="9592"&gt;Code and files:&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;

&lt;div style="font-size: 90%;"&gt;Tags: strava integration, 3d printed mechanism, wifi jar lock, diy smart jar, fitness reward system, esp32 project, esp32 servo control, fitness tracker diy, smart cookie jar, iot fitness project, strava API, friday_release, esp32 automation, locked biscuit barrel&lt;/div&gt;
</description></item><item><title>Earn Your Fitness Reward with a Smart Cookie Jar Using Strava and ESP32 -- Episode 694</title><link>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694/revision/4</link><pubDate>Thu, 11 Dec 2025 11:39:15 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f08580bd-ff57-4539-b2f1-de7eb0a248b0</guid><dc:creator>cstanton</dc:creator><comments>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694#comments</comments><description>Revision 4 posted to Documents by cstanton on 12/11/2025 11:39:15 AM&lt;br /&gt;
&lt;p data-start="708" data-end="1051"&gt;In this video, Milos tackles a familiar problem: it&amp;rsquo;s the holiday season, there&amp;rsquo;s lots of delicious food and candy everywhere, and it&amp;rsquo;s too cold to go out much. To help with that, he created a Smart Cookie Jar that keeps you true to your word. The jar locks itself with all those treats inside and won&amp;rsquo;t open until you reach your running goal.&lt;/p&gt;
&lt;p data-start="1053" data-end="1094"&gt;A line from the video sums the idea up:&lt;/p&gt;
&lt;blockquote data-start="1095" data-end="1250"&gt;
&lt;p data-start="1097" data-end="1250"&gt;&amp;ldquo;It&amp;rsquo;s the holiday season&amp;hellip; But if you want to get to that candy, we&amp;rsquo;ll make something that will make you run first.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt; &lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;&lt;a href="https://youtu.be/jsQTD_6HNTA"&gt;https://youtu.be/jsQTD_6HNTA&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr data-start="1273" data-end="1276" /&gt;
&lt;h2 id="mcetoc_1jc6etsj40" data-start="1278" data-end="1290"&gt;Idea&lt;/h2&gt;
&lt;p data-start="1291" data-end="1499"&gt;Milos first built this project more than six years ago, back before he owned a 3D printer. He&amp;rsquo;s rebuilt it now to make it smaller and more refined, taking advantage of 3D printing and modern microcontrollers.&lt;/p&gt;
&lt;p data-start="1501" data-end="1825"&gt;The original mechanism was hand-cut from 4 mm plywood; the small pushrods used sheet metal salvaged from a gutter, there was plenty of room for improvement.&lt;br data-start="1709" data-end="1712" /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;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 &amp;mdash; no obvious electronics, just a jar full of candy.&lt;/p&gt;
&lt;hr data-start="2087" data-end="2090" /&gt;
&lt;h2 id="mcetoc_1jc6etsj41" data-start="2092" data-end="2109"&gt;Mechanism&lt;/h2&gt;
&lt;p data-start="2110" data-end="2419"&gt;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).&lt;/p&gt;
&lt;p data-start="2421" data-end="2646"&gt;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.&lt;/p&gt;
&lt;p data-start="2648" data-end="2709"&gt;A quote from the video where Milos describes the mechanism:&lt;/p&gt;
&lt;blockquote data-start="2710" data-end="2953"&gt;
&lt;p data-start="2712" data-end="2953"&gt;&amp;ldquo;In the centre we have the servo horn&amp;hellip; and around that we have the three pistons that move linearly. So they&amp;rsquo;re connected together using this small pushrod&amp;hellip; For the new version, I want to go with gears.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="2955" data-end="3094"&gt;The new lid packs everything tightly; from the outside (when unlocked) there&amp;rsquo;s no visual clue that anything special is inside &amp;mdash; just candy.&lt;/p&gt;
&lt;hr data-start="3096" data-end="3099" /&gt;
&lt;h2 id="mcetoc_1jc6etsj52" data-start="3101" data-end="3117"&gt;Software&lt;/h2&gt;
&lt;p data-start="3118" data-end="3298"&gt;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.&lt;/p&gt;
&lt;p data-start="3300" data-end="3317"&gt;From the video:&lt;/p&gt;
&lt;blockquote data-start="3318" data-end="3509"&gt;
&lt;p data-start="3320" data-end="3509"&gt;&amp;ldquo;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.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="mcetoc_1jc6etsj53" data-start="3511" data-end="3536"&gt;Strava flow (summary)&lt;/h3&gt;
&lt;ol data-start="3537" data-end="4070"&gt;
&lt;li data-start="3537" data-end="3647"&gt;
&lt;p data-start="3540" data-end="3647"&gt;Create a Strava API application (Settings &amp;rarr; My API Application) and note &lt;code data-start="3613" data-end="3624"&gt;client_id&lt;/code&gt; and &lt;code data-start="3629" data-end="3644"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3648" data-end="3717"&gt;
&lt;p data-start="3651" data-end="3717"&gt;Build and open the authorize URL to obtain the temporary &lt;code data-start="3708" data-end="3714"&gt;code&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3718" data-end="3816"&gt;
&lt;p data-start="3721" data-end="3816"&gt;Exchange the &lt;code data-start="3734" data-end="3740"&gt;code&lt;/code&gt; for &lt;code data-start="3745" data-end="3759"&gt;access_token&lt;/code&gt; and &lt;code data-start="3764" data-end="3779"&gt;refresh_token&lt;/code&gt; (the example cURL is shown below).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3817" data-end="3936"&gt;
&lt;p data-start="3820" data-end="3936"&gt;Copy the tokens into the Arduino sketch and flash the ESP32-S3. The code refreshes tokens automatically as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3937" data-end="4070"&gt;
&lt;p data-start="3940" data-end="4070"&gt;Open the device web page and choose &lt;strong data-start="3976" data-end="3998"&gt;Show last activity&lt;/strong&gt; to verify the device returns your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-start="4072" data-end="4212"&gt;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.&lt;/p&gt;
&lt;blockquote data-start="4214" data-end="4332"&gt;
&lt;p data-start="4216" data-end="4332"&gt;&amp;ldquo;It will just give you the name of your last activity, the distance you ran&amp;hellip; &amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-start="4334" data-end="4337" /&gt;
&lt;h2 id="mcetoc_1jc6etsj54" data-start="4339" data-end="4389"&gt;Key code excerpts (from &lt;code data-start="4369" data-end="4386"&gt;CookieJarV2.ino&lt;/code&gt;)&lt;/h2&gt;
&lt;p data-start="4390" data-end="4545"&gt;Below are short, focused excerpts from the project code to explain the important behaviours. These are included to show how the software actually operates.&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="4547" data-end="4616"&gt;Configuration constants (Wi-Fi, Strava tokens and servo settings)&lt;/strong&gt; &amp;mdash; set these before flashing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Wi-Fi credentials
const char *WIFI_SSID = &amp;quot;YOUR-SSID&amp;quot;;
const char *WIFI_PASSWORD = &amp;quot;YOUR-PASSWORD&amp;quot;;

// Strava tokens and keys (example placeholders)
const char *STRAVA_CLIENT_ID = &amp;quot;YOUR_STRAVA_CLIENT_ID&amp;quot;;
const char *STRAVA_CLIENT_SECRET = &amp;quot;YOUR_STRAVA_CLIENT_SECRET&amp;quot;;
const char *STRAVA_ACCESS_TOKEN = &amp;quot;YOUR_STRAVA_ACCESS_TOKEN&amp;quot;;
const char *STRAVA_REFRESH_TOKEN = &amp;quot;YOUR_STRAVA_REFRESH_TOKEN&amp;quot;;

// 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;
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="5252" data-end="5275"&gt;Servo motion helper&lt;/strong&gt; &amp;mdash; the code attaches the servo, moves to the angle, waits, and detaches. Detaching stops the constant PWM &amp;ldquo;holding&amp;rdquo; signal, saving battery and stopping buzzing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void moveServoTo(int angle) {
  jarServo.attach(PIN_SERVO);
  delay(100);
  jarServo.write(angle);
  delay(SERVO_PULSE_DELAY_MS);
  jarServo.detach();
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="5602" data-end="5695"&gt;This corresponds with Milos&amp;rsquo;s on-camera note about saving power and avoiding servo buzzing:&lt;/p&gt;
&lt;blockquote data-start="5696" data-end="6040"&gt;
&lt;p data-start="5698" data-end="6040"&gt;&amp;ldquo;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&amp;hellip; 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.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;strong data-start="6042" data-end="6079"&gt;Strava fetch (sketch of approach)&lt;/strong&gt; &amp;mdash; 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):&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;bool fetchStravaLatestActivity() {
  ensureWiFi();
  if (WiFi.status() != WL_CONNECTED) {
    lastError = &amp;quot;WiFi disconnected&amp;quot;;
    return false;
  }

  // HTTP call to Strava /athlete/activities or other endpoint
  // Parse JSON, extract last activity id, name, distance (m -&amp;gt; km), calories etc.
  // Update lastActivity struct and hasLastActivity flag
  ...
  return true; // or false on error
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;The sketch includes a regular poll loop:&lt;/p&gt;
&lt;div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"&gt;
&lt;div class="sticky top-9"&gt;
&lt;div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;const unsigned long STRAVA_POLL_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 minutes

// inside loop():
unsigned long now = millis();
if (now - lastPollMs &amp;gt;= STRAVA_POLL_INTERVAL_MS || now &amp;lt; lastPollMs) {
  lastPollMs = now;
  ActivityStats stats;
  if (fetchStravaToday(stats)) {
    lastStats = stats;
  } else if (fetchStravaLatestActivity()) {
    lastStats.distanceKm = lastActivity.distanceKm;
    lastStats.calories = lastActivity.calories;
    lastStats.fresh = true;
  }
  evaluateGoalAndMaybeUnlock();
}
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;
&lt;p data-start="7229" data-end="7428"&gt;&lt;code data-start="7229" data-end="7259"&gt;evaluateGoalAndMaybeUnlock()&lt;/code&gt; is the function that takes the gathered activity stats and decides whether to call &lt;code data-start="7343" data-end="7376"&gt;moveServoTo(SERVO_UNLOCK_ANGLE)&lt;/code&gt; (unlock) or &lt;code data-start="7389" data-end="7420"&gt;moveServoTo(SERVO_LOCK_ANGLE)&lt;/code&gt; (lock).&lt;/p&gt;
&lt;hr data-start="7430" data-end="7433" /&gt;
&lt;h2 id="mcetoc_1jc6f0gj05" data-start="7435" data-end="7490"&gt;Step-by-step: Strava auth/setup (short checklist)&lt;/h2&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="7491" data-end="7587"&gt;
&lt;p data-start="7493" data-end="7587"&gt;Create a Strava API app in your Strava account and note the &lt;code data-start="7553" data-end="7564"&gt;client_id&lt;/code&gt; and &lt;code data-start="7569" data-end="7584"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7588" data-end="7854"&gt;
&lt;p data-start="7590" data-end="7854"&gt;Build the authorisation URL (replace &lt;code data-start="7627" data-end="7638"&gt;CLIENT_ID&lt;/code&gt; and &lt;code data-start="7643" data-end="7657"&gt;REDIRECT_URI&lt;/code&gt;):&lt;br data-start="7659" data-end="7662" /&gt; &lt;code data-start="7664" data-end="7852"&gt;https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&amp;amp;response_type=code&amp;amp;redirect_uri=http://localhost/exchange_token&amp;amp;approval_prompt=auto&amp;amp;scope=read,activity:read,activity:read_all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7855" data-end="7959"&gt;
&lt;p data-start="7857" data-end="7959"&gt;Open the URL in a browser, approve the app, capture the returned &lt;code data-start="7922" data-end="7928"&gt;code&lt;/code&gt; (e.g. &lt;code data-start="7935" data-end="7955"&gt;...?code=YOUR_CODE&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7960" data-end="8227"&gt;
&lt;p data-start="7962" data-end="8023"&gt;Exchange the &lt;code data-start="7975" data-end="7981"&gt;code&lt;/code&gt; for tokens (on your PC) &amp;mdash; example cURL:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="batchfile"&gt;curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET \
  -d code=YOUR_CODE \
  -d grant_type=authorization_code
&lt;/pre&gt;&lt;/p&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="8228" data-end="8357"&gt;
&lt;p data-start="8230" data-end="8357"&gt;Set &lt;code data-start="8234" data-end="8252"&gt;STRAVA_CLIENT_ID&lt;/code&gt;, &lt;code data-start="8254" data-end="8276"&gt;STRAVA_CLIENT_SECRET&lt;/code&gt;, &lt;code data-start="8278" data-end="8299"&gt;STRAVA_ACCESS_TOKEN&lt;/code&gt; and &lt;code data-start="8304" data-end="8326"&gt;STRAVA_REFRESH_TOKEN&lt;/code&gt; in the &lt;code data-start="8334" data-end="8347"&gt;CookieJarV2&lt;/code&gt; sketch.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8358" data-end="8502"&gt;
&lt;p data-start="8360" data-end="8502"&gt;Flash the ESP32-S3 and test by opening the device&amp;rsquo;s web page &amp;rarr; &amp;ldquo;Show last activity&amp;rdquo;. You should see your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-start="8504" data-end="8507" /&gt;
&lt;h2 id="mcetoc_1jc6f1e556" data-start="8509" data-end="8540"&gt;Final assembly and test&lt;/h2&gt;
&lt;p data-start="8541" data-end="8568"&gt;Once assembled and flashed:&lt;/p&gt;
&lt;ul data-start="8569" data-end="8825"&gt;
&lt;li data-start="8569" data-end="8718"&gt;
&lt;p data-start="8571" data-end="8718"&gt;Lock the jar, set your target distance via the device web UI, go for your run, and wait for the device to poll Strava and evaluate your activity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8719" data-end="8825"&gt;
&lt;p data-start="8721" data-end="8825"&gt;The sketch will automatically refresh tokens if required and will periodically check for new activities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="8827" data-end="9077"&gt;Milos demonstrates this flow in the video and finishes by going for a run to show the jar unlocking when the goal is met. He notes that the jar, when seen from the outside, looks like an ordinary jar of candy &amp;mdash; the mechanism is hidden inside the lid.&lt;/p&gt;
&lt;blockquote data-start="9079" data-end="9257"&gt;
&lt;p data-start="9081" data-end="9257"&gt;&amp;ldquo;If you look at it from outside, it just looks like a jar full of candy bars&amp;hellip; you won&amp;rsquo;t even know that there are some electronics inside.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-start="9259" data-end="9262" /&gt;
&lt;h2 id="mcetoc_1jc6f1e557" data-start="9264" data-end="9281"&gt;Resources&lt;/h2&gt;
&lt;ul data-start="9282" data-end="9592"&gt;
&lt;li data-start="9282" data-end="9442"&gt;
&lt;p data-start="9284" data-end="9442"&gt;Original project write-up (older version): &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="9327" data-end="9440" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9443" data-end="9521"&gt;
&lt;p data-start="9445" data-end="9521"&gt;Strava developer docs: &lt;a class="decorated-link" href="https://developers.strava.com/docs/getting-started/" data-start="9468" data-end="9519" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://developers.strava.com/docs/getting-started/&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9522" data-end="9592"&gt;
&lt;p data-start="9524" data-end="9592"&gt;Code and files:&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;
</description></item><item><title>Earn Your Fitness Reward with a Smart Cookie Jar Using Strava and ESP32 -- Episode 694</title><link>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694/revision/3</link><pubDate>Thu, 11 Dec 2025 10:43:24 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f08580bd-ff57-4539-b2f1-de7eb0a248b0</guid><dc:creator>cstanton</dc:creator><comments>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694#comments</comments><description>Revision 3 posted to Documents by cstanton on 12/11/2025 10:43:24 AM&lt;br /&gt;
&lt;p data-start="708" data-end="1051"&gt;In this video, Milos tackles a familiar problem: it&amp;rsquo;s the holiday season, there&amp;rsquo;s lots of delicious food and candy everywhere, and it&amp;rsquo;s too cold to go out much. To help with that, he created a Smart Cookie Jar that keeps you true to your word. The jar locks itself with all those treats inside and won&amp;rsquo;t open until you reach your running goal.&lt;/p&gt;
&lt;p data-start="1053" data-end="1094"&gt;A line from the video sums the idea up:&lt;/p&gt;
&lt;blockquote data-start="1095" data-end="1250"&gt;
&lt;p data-start="1097" data-end="1250"&gt;&amp;ldquo;It&amp;rsquo;s the holiday season&amp;hellip; But if you want to get to that candy, we&amp;rsquo;ll make something that will make you run first.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;MAIN VIDEO LINK&lt;/strong&gt;&lt;/p&gt;
&lt;hr data-start="1273" data-end="1276" /&gt;
&lt;h2 id="mcetoc_1jc6etsj40" data-start="1278" data-end="1290"&gt;Idea&lt;/h2&gt;
&lt;p data-start="1291" data-end="1499"&gt;Milos first built this project more than six years ago, back before he owned a 3D printer. He&amp;rsquo;s rebuilt it now to make it smaller and more refined, taking advantage of 3D printing and modern microcontrollers.&lt;/p&gt;
&lt;p data-start="1501" data-end="1825"&gt;The original mechanism was hand-cut from 4 mm plywood; the small pushrods used sheet metal salvaged from a gutter, there was plenty of room for improvement.&lt;br data-start="1709" data-end="1712" /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;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 &amp;mdash; no obvious electronics, just a jar full of candy.&lt;/p&gt;
&lt;hr data-start="2087" data-end="2090" /&gt;
&lt;h2 id="mcetoc_1jc6etsj41" data-start="2092" data-end="2109"&gt;Mechanism&lt;/h2&gt;
&lt;p data-start="2110" data-end="2419"&gt;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).&lt;/p&gt;
&lt;p data-start="2421" data-end="2646"&gt;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.&lt;/p&gt;
&lt;p data-start="2648" data-end="2709"&gt;A quote from the video where Milos describes the mechanism:&lt;/p&gt;
&lt;blockquote data-start="2710" data-end="2953"&gt;
&lt;p data-start="2712" data-end="2953"&gt;&amp;ldquo;In the centre we have the servo horn&amp;hellip; and around that we have the three pistons that move linearly. So they&amp;rsquo;re connected together using this small pushrod&amp;hellip; For the new version, I want to go with gears.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="2955" data-end="3094"&gt;The new lid packs everything tightly; from the outside (when unlocked) there&amp;rsquo;s no visual clue that anything special is inside &amp;mdash; just candy.&lt;/p&gt;
&lt;hr data-start="3096" data-end="3099" /&gt;
&lt;h2 id="mcetoc_1jc6etsj52" data-start="3101" data-end="3117"&gt;Software&lt;/h2&gt;
&lt;p data-start="3118" data-end="3298"&gt;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.&lt;/p&gt;
&lt;p data-start="3300" data-end="3317"&gt;From the video:&lt;/p&gt;
&lt;blockquote data-start="3318" data-end="3509"&gt;
&lt;p data-start="3320" data-end="3509"&gt;&amp;ldquo;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.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="mcetoc_1jc6etsj53" data-start="3511" data-end="3536"&gt;Strava flow (summary)&lt;/h3&gt;
&lt;ol data-start="3537" data-end="4070"&gt;
&lt;li data-start="3537" data-end="3647"&gt;
&lt;p data-start="3540" data-end="3647"&gt;Create a Strava API application (Settings &amp;rarr; My API Application) and note &lt;code data-start="3613" data-end="3624"&gt;client_id&lt;/code&gt; and &lt;code data-start="3629" data-end="3644"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3648" data-end="3717"&gt;
&lt;p data-start="3651" data-end="3717"&gt;Build and open the authorize URL to obtain the temporary &lt;code data-start="3708" data-end="3714"&gt;code&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3718" data-end="3816"&gt;
&lt;p data-start="3721" data-end="3816"&gt;Exchange the &lt;code data-start="3734" data-end="3740"&gt;code&lt;/code&gt; for &lt;code data-start="3745" data-end="3759"&gt;access_token&lt;/code&gt; and &lt;code data-start="3764" data-end="3779"&gt;refresh_token&lt;/code&gt; (the example cURL is shown below).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3817" data-end="3936"&gt;
&lt;p data-start="3820" data-end="3936"&gt;Copy the tokens into the Arduino sketch and flash the ESP32-S3. The code refreshes tokens automatically as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3937" data-end="4070"&gt;
&lt;p data-start="3940" data-end="4070"&gt;Open the device web page and choose &lt;strong data-start="3976" data-end="3998"&gt;Show last activity&lt;/strong&gt; to verify the device returns your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-start="4072" data-end="4212"&gt;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.&lt;/p&gt;
&lt;blockquote data-start="4214" data-end="4332"&gt;
&lt;p data-start="4216" data-end="4332"&gt;&amp;ldquo;It will just give you the name of your last activity, the distance you ran&amp;hellip; &amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-start="4334" data-end="4337" /&gt;
&lt;h2 id="mcetoc_1jc6etsj54" data-start="4339" data-end="4389"&gt;Key code excerpts (from &lt;code data-start="4369" data-end="4386"&gt;CookieJarV2.ino&lt;/code&gt;)&lt;/h2&gt;
&lt;p data-start="4390" data-end="4545"&gt;Below are short, focused excerpts from the project code to explain the important behaviours. These are included to show how the software actually operates.&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="4547" data-end="4616"&gt;Configuration constants (Wi-Fi, Strava tokens and servo settings)&lt;/strong&gt; &amp;mdash; set these before flashing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Wi-Fi credentials
const char *WIFI_SSID = &amp;quot;YOUR-SSID&amp;quot;;
const char *WIFI_PASSWORD = &amp;quot;YOUR-PASSWORD&amp;quot;;

// Strava tokens and keys (example placeholders)
const char *STRAVA_CLIENT_ID = &amp;quot;YOUR_STRAVA_CLIENT_ID&amp;quot;;
const char *STRAVA_CLIENT_SECRET = &amp;quot;YOUR_STRAVA_CLIENT_SECRET&amp;quot;;
const char *STRAVA_ACCESS_TOKEN = &amp;quot;YOUR_STRAVA_ACCESS_TOKEN&amp;quot;;
const char *STRAVA_REFRESH_TOKEN = &amp;quot;YOUR_STRAVA_REFRESH_TOKEN&amp;quot;;

// 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;
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="5252" data-end="5275"&gt;Servo motion helper&lt;/strong&gt; &amp;mdash; the code attaches the servo, moves to the angle, waits, and detaches. Detaching stops the constant PWM &amp;ldquo;holding&amp;rdquo; signal, saving battery and stopping buzzing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void moveServoTo(int angle) {
  jarServo.attach(PIN_SERVO);
  delay(100);
  jarServo.write(angle);
  delay(SERVO_PULSE_DELAY_MS);
  jarServo.detach();
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="5602" data-end="5695"&gt;This corresponds with Milos&amp;rsquo;s on-camera note about saving power and avoiding servo buzzing:&lt;/p&gt;
&lt;blockquote data-start="5696" data-end="6040"&gt;
&lt;p data-start="5698" data-end="6040"&gt;&amp;ldquo;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&amp;hellip; 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.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;strong data-start="6042" data-end="6079"&gt;Strava fetch (sketch of approach)&lt;/strong&gt; &amp;mdash; 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):&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;bool fetchStravaLatestActivity() {
  ensureWiFi();
  if (WiFi.status() != WL_CONNECTED) {
    lastError = &amp;quot;WiFi disconnected&amp;quot;;
    return false;
  }

  // HTTP call to Strava /athlete/activities or other endpoint
  // Parse JSON, extract last activity id, name, distance (m -&amp;gt; km), calories etc.
  // Update lastActivity struct and hasLastActivity flag
  ...
  return true; // or false on error
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;The sketch includes a regular poll loop:&lt;/p&gt;
&lt;div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"&gt;
&lt;div class="sticky top-9"&gt;
&lt;div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;const unsigned long STRAVA_POLL_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 minutes

// inside loop():
unsigned long now = millis();
if (now - lastPollMs &amp;gt;= STRAVA_POLL_INTERVAL_MS || now &amp;lt; lastPollMs) {
  lastPollMs = now;
  ActivityStats stats;
  if (fetchStravaToday(stats)) {
    lastStats = stats;
  } else if (fetchStravaLatestActivity()) {
    lastStats.distanceKm = lastActivity.distanceKm;
    lastStats.calories = lastActivity.calories;
    lastStats.fresh = true;
  }
  evaluateGoalAndMaybeUnlock();
}
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;
&lt;p data-start="7229" data-end="7428"&gt;&lt;code data-start="7229" data-end="7259"&gt;evaluateGoalAndMaybeUnlock()&lt;/code&gt; is the function that takes the gathered activity stats and decides whether to call &lt;code data-start="7343" data-end="7376"&gt;moveServoTo(SERVO_UNLOCK_ANGLE)&lt;/code&gt; (unlock) or &lt;code data-start="7389" data-end="7420"&gt;moveServoTo(SERVO_LOCK_ANGLE)&lt;/code&gt; (lock).&lt;/p&gt;
&lt;hr data-start="7430" data-end="7433" /&gt;
&lt;h2 id="mcetoc_1jc6f0gj05" data-start="7435" data-end="7490"&gt;Step-by-step: Strava auth/setup (short checklist)&lt;/h2&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="7491" data-end="7587"&gt;
&lt;p data-start="7493" data-end="7587"&gt;Create a Strava API app in your Strava account and note the &lt;code data-start="7553" data-end="7564"&gt;client_id&lt;/code&gt; and &lt;code data-start="7569" data-end="7584"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7588" data-end="7854"&gt;
&lt;p data-start="7590" data-end="7854"&gt;Build the authorisation URL (replace &lt;code data-start="7627" data-end="7638"&gt;CLIENT_ID&lt;/code&gt; and &lt;code data-start="7643" data-end="7657"&gt;REDIRECT_URI&lt;/code&gt;):&lt;br data-start="7659" data-end="7662" /&gt; &lt;code data-start="7664" data-end="7852"&gt;https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&amp;amp;response_type=code&amp;amp;redirect_uri=http://localhost/exchange_token&amp;amp;approval_prompt=auto&amp;amp;scope=read,activity:read,activity:read_all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7855" data-end="7959"&gt;
&lt;p data-start="7857" data-end="7959"&gt;Open the URL in a browser, approve the app, capture the returned &lt;code data-start="7922" data-end="7928"&gt;code&lt;/code&gt; (e.g. &lt;code data-start="7935" data-end="7955"&gt;...?code=YOUR_CODE&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7960" data-end="8227"&gt;
&lt;p data-start="7962" data-end="8023"&gt;Exchange the &lt;code data-start="7975" data-end="7981"&gt;code&lt;/code&gt; for tokens (on your PC) &amp;mdash; example cURL:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="batchfile"&gt;curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET \
  -d code=YOUR_CODE \
  -d grant_type=authorization_code
&lt;/pre&gt;&lt;/p&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="8228" data-end="8357"&gt;
&lt;p data-start="8230" data-end="8357"&gt;Set &lt;code data-start="8234" data-end="8252"&gt;STRAVA_CLIENT_ID&lt;/code&gt;, &lt;code data-start="8254" data-end="8276"&gt;STRAVA_CLIENT_SECRET&lt;/code&gt;, &lt;code data-start="8278" data-end="8299"&gt;STRAVA_ACCESS_TOKEN&lt;/code&gt; and &lt;code data-start="8304" data-end="8326"&gt;STRAVA_REFRESH_TOKEN&lt;/code&gt; in the &lt;code data-start="8334" data-end="8347"&gt;CookieJarV2&lt;/code&gt; sketch.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8358" data-end="8502"&gt;
&lt;p data-start="8360" data-end="8502"&gt;Flash the ESP32-S3 and test by opening the device&amp;rsquo;s web page &amp;rarr; &amp;ldquo;Show last activity&amp;rdquo;. You should see your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-start="8504" data-end="8507" /&gt;
&lt;h2 id="mcetoc_1jc6f1e556" data-start="8509" data-end="8540"&gt;Final assembly and test&lt;/h2&gt;
&lt;p data-start="8541" data-end="8568"&gt;Once assembled and flashed:&lt;/p&gt;
&lt;ul data-start="8569" data-end="8825"&gt;
&lt;li data-start="8569" data-end="8718"&gt;
&lt;p data-start="8571" data-end="8718"&gt;Lock the jar, set your target distance via the device web UI, go for your run, and wait for the device to poll Strava and evaluate your activity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8719" data-end="8825"&gt;
&lt;p data-start="8721" data-end="8825"&gt;The sketch will automatically refresh tokens if required and will periodically check for new activities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="8827" data-end="9077"&gt;Milos demonstrates this flow in the video and finishes by going for a run to show the jar unlocking when the goal is met. He notes that the jar, when seen from the outside, looks like an ordinary jar of candy &amp;mdash; the mechanism is hidden inside the lid.&lt;/p&gt;
&lt;blockquote data-start="9079" data-end="9257"&gt;
&lt;p data-start="9081" data-end="9257"&gt;&amp;ldquo;If you look at it from outside, it just looks like a jar full of candy bars&amp;hellip; you won&amp;rsquo;t even know that there are some electronics inside.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-start="9259" data-end="9262" /&gt;
&lt;h2 id="mcetoc_1jc6f1e557" data-start="9264" data-end="9281"&gt;Resources&lt;/h2&gt;
&lt;ul data-start="9282" data-end="9592"&gt;
&lt;li data-start="9282" data-end="9442"&gt;
&lt;p data-start="9284" data-end="9442"&gt;Original project write-up (older version): &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="9327" data-end="9440" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9443" data-end="9521"&gt;
&lt;p data-start="9445" data-end="9521"&gt;Strava developer docs: &lt;a class="decorated-link" href="https://developers.strava.com/docs/getting-started/" data-start="9468" data-end="9519" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://developers.strava.com/docs/getting-started/&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9522" data-end="9592"&gt;
&lt;p data-start="9524" data-end="9592"&gt;Code and files:&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;
</description></item><item><title>Earn Your Fitness Reward with a Smart Cookie Jar Using Strava and ESP32 -- Episode 694</title><link>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694/revision/2</link><pubDate>Thu, 11 Dec 2025 10:25:25 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f08580bd-ff57-4539-b2f1-de7eb0a248b0</guid><dc:creator>cstanton</dc:creator><comments>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694#comments</comments><description>Revision 2 posted to Documents by cstanton on 12/11/2025 10:25:25 AM&lt;br /&gt;
&lt;p data-start="708" data-end="1051"&gt;In this video, Milos tackles a familiar problem: it&amp;rsquo;s the holiday season, there&amp;rsquo;s lots of delicious food and candy everywhere, and it&amp;rsquo;s too cold to go out much. To help with that, he created a Smart Cookie Jar that keeps you true to your word. The jar locks itself with all those treats inside and won&amp;rsquo;t open until you reach your running goal.&lt;/p&gt;
&lt;p data-start="1053" data-end="1094"&gt;A line from the video sums the idea up:&lt;/p&gt;
&lt;blockquote data-start="1095" data-end="1250"&gt;
&lt;p data-start="1097" data-end="1250"&gt;&amp;ldquo;It&amp;rsquo;s the holiday season&amp;hellip; But if you want to get to that candy, we&amp;rsquo;ll make something that will make you run first.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;MAIN VIDEO LINK&lt;/strong&gt;&lt;/p&gt;
&lt;hr data-start="1273" data-end="1276" /&gt;
&lt;h2 id="mcetoc_1jc6etsj40" data-start="1278" data-end="1290"&gt;Idea&lt;/h2&gt;
&lt;p data-start="1291" data-end="1499"&gt;Milos first built this project more than six years ago, back before he owned a 3D printer. He&amp;rsquo;s rebuilt it now to make it smaller and more refined, taking advantage of 3D printing and modern microcontrollers.&lt;/p&gt;
&lt;p data-start="1501" data-end="1825"&gt;The original mechanism was hand-cut from 4 mm plywood; the small pushrods used sheet metal salvaged from a gutter &amp;mdash; there was plenty of room for improvement. You can read his original, detailed write-up here:&lt;br data-start="1709" data-end="1712" /&gt; &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="1712" data-end="1825" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;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 &amp;mdash; no obvious electronics, just a jar full of candy.&lt;/p&gt;
&lt;hr data-start="2087" data-end="2090" /&gt;
&lt;h2 id="mcetoc_1jc6etsj41" data-start="2092" data-end="2109"&gt;Mechanism&lt;/h2&gt;
&lt;p data-start="2110" data-end="2419"&gt;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).&lt;/p&gt;
&lt;p data-start="2421" data-end="2646"&gt;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.&lt;/p&gt;
&lt;p data-start="2648" data-end="2709"&gt;A quote from the video where Milos describes the mechanism:&lt;/p&gt;
&lt;blockquote data-start="2710" data-end="2953"&gt;
&lt;p data-start="2712" data-end="2953"&gt;&amp;ldquo;In the centre we have the servo horn&amp;hellip; and around that we have the three pistons that move linearly. So they&amp;rsquo;re connected together using this small pushrod&amp;hellip; For the new version, I want to go with gears.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="2955" data-end="3094"&gt;The new lid packs everything tightly; from the outside (when unlocked) there&amp;rsquo;s no visual clue that anything special is inside &amp;mdash; just candy.&lt;/p&gt;
&lt;hr data-start="3096" data-end="3099" /&gt;
&lt;h2 id="mcetoc_1jc6etsj52" data-start="3101" data-end="3117"&gt;Software&lt;/h2&gt;
&lt;p data-start="3118" data-end="3298"&gt;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.&lt;/p&gt;
&lt;p data-start="3300" data-end="3317"&gt;From the video:&lt;/p&gt;
&lt;blockquote data-start="3318" data-end="3509"&gt;
&lt;p data-start="3320" data-end="3509"&gt;&amp;ldquo;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.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="mcetoc_1jc6etsj53" data-start="3511" data-end="3536"&gt;Strava flow (summary)&lt;/h3&gt;
&lt;ol data-start="3537" data-end="4070"&gt;
&lt;li data-start="3537" data-end="3647"&gt;
&lt;p data-start="3540" data-end="3647"&gt;Create a Strava API application (Settings &amp;rarr; My API Application) and note &lt;code data-start="3613" data-end="3624"&gt;client_id&lt;/code&gt; and &lt;code data-start="3629" data-end="3644"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3648" data-end="3717"&gt;
&lt;p data-start="3651" data-end="3717"&gt;Build and open the authorize URL to obtain the temporary &lt;code data-start="3708" data-end="3714"&gt;code&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3718" data-end="3816"&gt;
&lt;p data-start="3721" data-end="3816"&gt;Exchange the &lt;code data-start="3734" data-end="3740"&gt;code&lt;/code&gt; for &lt;code data-start="3745" data-end="3759"&gt;access_token&lt;/code&gt; and &lt;code data-start="3764" data-end="3779"&gt;refresh_token&lt;/code&gt; (the example cURL is shown below).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3817" data-end="3936"&gt;
&lt;p data-start="3820" data-end="3936"&gt;Copy the tokens into the Arduino sketch and flash the ESP32-S3. The code refreshes tokens automatically as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3937" data-end="4070"&gt;
&lt;p data-start="3940" data-end="4070"&gt;Open the device web page and choose &lt;strong data-start="3976" data-end="3998"&gt;Show last activity&lt;/strong&gt; to verify the device returns your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-start="4072" data-end="4212"&gt;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.&lt;/p&gt;
&lt;blockquote data-start="4214" data-end="4332"&gt;
&lt;p data-start="4216" data-end="4332"&gt;&amp;ldquo;It will just give you the name of your last activity, the distance you ran&amp;hellip; &amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-start="4334" data-end="4337" /&gt;
&lt;h2 id="mcetoc_1jc6etsj54" data-start="4339" data-end="4389"&gt;Key code excerpts (from &lt;code data-start="4369" data-end="4386"&gt;CookieJarV2.ino&lt;/code&gt;)&lt;/h2&gt;
&lt;p data-start="4390" data-end="4545"&gt;Below are short, focused excerpts from the project code to explain the important behaviours. These are included to show how the software actually operates.&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="4547" data-end="4616"&gt;Configuration constants (Wi-Fi, Strava tokens and servo settings)&lt;/strong&gt; &amp;mdash; set these before flashing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Wi-Fi credentials
const char *WIFI_SSID = &amp;quot;YOUR-SSID&amp;quot;;
const char *WIFI_PASSWORD = &amp;quot;YOUR-PASSWORD&amp;quot;;

// Strava tokens and keys (example placeholders)
const char *STRAVA_CLIENT_ID = &amp;quot;YOUR_STRAVA_CLIENT_ID&amp;quot;;
const char *STRAVA_CLIENT_SECRET = &amp;quot;YOUR_STRAVA_CLIENT_SECRET&amp;quot;;
const char *STRAVA_ACCESS_TOKEN = &amp;quot;YOUR_STRAVA_ACCESS_TOKEN&amp;quot;;
const char *STRAVA_REFRESH_TOKEN = &amp;quot;YOUR_STRAVA_REFRESH_TOKEN&amp;quot;;

// 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;
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="5252" data-end="5275"&gt;Servo motion helper&lt;/strong&gt; &amp;mdash; the code attaches the servo, moves to the angle, waits, and detaches. Detaching stops the constant PWM &amp;ldquo;holding&amp;rdquo; signal, saving battery and stopping buzzing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void moveServoTo(int angle) {
  jarServo.attach(PIN_SERVO);
  delay(100);
  jarServo.write(angle);
  delay(SERVO_PULSE_DELAY_MS);
  jarServo.detach();
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="5602" data-end="5695"&gt;This corresponds with Milos&amp;rsquo;s on-camera note about saving power and avoiding servo buzzing:&lt;/p&gt;
&lt;blockquote data-start="5696" data-end="6040"&gt;
&lt;p data-start="5698" data-end="6040"&gt;&amp;ldquo;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&amp;hellip; 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.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;strong data-start="6042" data-end="6079"&gt;Strava fetch (sketch of approach)&lt;/strong&gt; &amp;mdash; 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):&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;bool fetchStravaLatestActivity() {
  ensureWiFi();
  if (WiFi.status() != WL_CONNECTED) {
    lastError = &amp;quot;WiFi disconnected&amp;quot;;
    return false;
  }

  // HTTP call to Strava /athlete/activities or other endpoint
  // Parse JSON, extract last activity id, name, distance (m -&amp;gt; km), calories etc.
  // Update lastActivity struct and hasLastActivity flag
  ...
  return true; // or false on error
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;The sketch includes a regular poll loop:&lt;/p&gt;
&lt;div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"&gt;
&lt;div class="sticky top-9"&gt;
&lt;div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;const unsigned long STRAVA_POLL_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 minutes

// inside loop():
unsigned long now = millis();
if (now - lastPollMs &amp;gt;= STRAVA_POLL_INTERVAL_MS || now &amp;lt; lastPollMs) {
  lastPollMs = now;
  ActivityStats stats;
  if (fetchStravaToday(stats)) {
    lastStats = stats;
  } else if (fetchStravaLatestActivity()) {
    lastStats.distanceKm = lastActivity.distanceKm;
    lastStats.calories = lastActivity.calories;
    lastStats.fresh = true;
  }
  evaluateGoalAndMaybeUnlock();
}
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;
&lt;p data-start="7229" data-end="7428"&gt;&lt;code data-start="7229" data-end="7259"&gt;evaluateGoalAndMaybeUnlock()&lt;/code&gt; is the function that takes the gathered activity stats and decides whether to call &lt;code data-start="7343" data-end="7376"&gt;moveServoTo(SERVO_UNLOCK_ANGLE)&lt;/code&gt; (unlock) or &lt;code data-start="7389" data-end="7420"&gt;moveServoTo(SERVO_LOCK_ANGLE)&lt;/code&gt; (lock).&lt;/p&gt;
&lt;hr data-start="7430" data-end="7433" /&gt;
&lt;h2 id="mcetoc_1jc6f0gj05" data-start="7435" data-end="7490"&gt;Step-by-step: Strava auth/setup (short checklist)&lt;/h2&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="7491" data-end="7587"&gt;
&lt;p data-start="7493" data-end="7587"&gt;Create a Strava API app in your Strava account and note the &lt;code data-start="7553" data-end="7564"&gt;client_id&lt;/code&gt; and &lt;code data-start="7569" data-end="7584"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7588" data-end="7854"&gt;
&lt;p data-start="7590" data-end="7854"&gt;Build the authorisation URL (replace &lt;code data-start="7627" data-end="7638"&gt;CLIENT_ID&lt;/code&gt; and &lt;code data-start="7643" data-end="7657"&gt;REDIRECT_URI&lt;/code&gt;):&lt;br data-start="7659" data-end="7662" /&gt; &lt;code data-start="7664" data-end="7852"&gt;https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&amp;amp;response_type=code&amp;amp;redirect_uri=http://localhost/exchange_token&amp;amp;approval_prompt=auto&amp;amp;scope=read,activity:read,activity:read_all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7855" data-end="7959"&gt;
&lt;p data-start="7857" data-end="7959"&gt;Open the URL in a browser, approve the app, capture the returned &lt;code data-start="7922" data-end="7928"&gt;code&lt;/code&gt; (e.g. &lt;code data-start="7935" data-end="7955"&gt;...?code=YOUR_CODE&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7960" data-end="8227"&gt;
&lt;p data-start="7962" data-end="8023"&gt;Exchange the &lt;code data-start="7975" data-end="7981"&gt;code&lt;/code&gt; for tokens (on your PC) &amp;mdash; example cURL:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="batchfile"&gt;curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET \
  -d code=YOUR_CODE \
  -d grant_type=authorization_code
&lt;/pre&gt;&lt;/p&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="8228" data-end="8357"&gt;
&lt;p data-start="8230" data-end="8357"&gt;Set &lt;code data-start="8234" data-end="8252"&gt;STRAVA_CLIENT_ID&lt;/code&gt;, &lt;code data-start="8254" data-end="8276"&gt;STRAVA_CLIENT_SECRET&lt;/code&gt;, &lt;code data-start="8278" data-end="8299"&gt;STRAVA_ACCESS_TOKEN&lt;/code&gt; and &lt;code data-start="8304" data-end="8326"&gt;STRAVA_REFRESH_TOKEN&lt;/code&gt; in the &lt;code data-start="8334" data-end="8347"&gt;CookieJarV2&lt;/code&gt; sketch.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8358" data-end="8502"&gt;
&lt;p data-start="8360" data-end="8502"&gt;Flash the ESP32-S3 and test by opening the device&amp;rsquo;s web page &amp;rarr; &amp;ldquo;Show last activity&amp;rdquo;. You should see your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-start="8504" data-end="8507" /&gt;
&lt;h2 id="mcetoc_1jc6f1e556" data-start="8509" data-end="8540"&gt;Final assembly and test&lt;/h2&gt;
&lt;p data-start="8541" data-end="8568"&gt;Once assembled and flashed:&lt;/p&gt;
&lt;ul data-start="8569" data-end="8825"&gt;
&lt;li data-start="8569" data-end="8718"&gt;
&lt;p data-start="8571" data-end="8718"&gt;Lock the jar, set your target distance via the device web UI, go for your run, and wait for the device to poll Strava and evaluate your activity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8719" data-end="8825"&gt;
&lt;p data-start="8721" data-end="8825"&gt;The sketch will automatically refresh tokens if required and will periodically check for new activities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="8827" data-end="9077"&gt;Milos demonstrates this flow in the video and finishes by going for a run to show the jar unlocking when the goal is met. He notes that the jar, when seen from the outside, looks like an ordinary jar of candy &amp;mdash; the mechanism is hidden inside the lid.&lt;/p&gt;
&lt;blockquote data-start="9079" data-end="9257"&gt;
&lt;p data-start="9081" data-end="9257"&gt;&amp;ldquo;If you look at it from outside, it just looks like a jar full of candy bars&amp;hellip; you won&amp;rsquo;t even know that there are some electronics inside.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-start="9259" data-end="9262" /&gt;
&lt;h2 id="mcetoc_1jc6f1e557" data-start="9264" data-end="9281"&gt;Resources&lt;/h2&gt;
&lt;ul data-start="9282" data-end="9592"&gt;
&lt;li data-start="9282" data-end="9442"&gt;
&lt;p data-start="9284" data-end="9442"&gt;Original project write-up (older version): &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="9327" data-end="9440" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9443" data-end="9521"&gt;
&lt;p data-start="9445" data-end="9521"&gt;Strava developer docs: &lt;a class="decorated-link" href="https://developers.strava.com/docs/getting-started/" data-start="9468" data-end="9519" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://developers.strava.com/docs/getting-started/&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9522" data-end="9592"&gt;
&lt;p data-start="9524" data-end="9592"&gt;Code and files:&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;
</description></item><item><title>Earn Your Fitness Reward with a Smart Cookie Jar Using Strava and ESP32 -- Episode 694</title><link>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694/revision/1</link><pubDate>Thu, 11 Dec 2025 10:24:37 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f08580bd-ff57-4539-b2f1-de7eb0a248b0</guid><dc:creator>cstanton</dc:creator><comments>https://community.element14.com/challenges-projects/element14-presents/project-videos/w/documents/72001/earn-your-fitness-reward-with-a-smart-cookie-jar-using-strava-and-esp32----episode-694#comments</comments><description>Revision 1 posted to Documents by cstanton on 12/11/2025 10:24:37 AM&lt;br /&gt;
&lt;p data-start="708" data-end="1051"&gt;In this video, Milos tackles a familiar problem: it&amp;rsquo;s the holiday season, there&amp;rsquo;s lots of delicious food and candy everywhere, and it&amp;rsquo;s too cold to go out much. To help with that, he created a Smart Cookie Jar that keeps you true to your word. The jar locks itself with all those treats inside and won&amp;rsquo;t open until you reach your running goal.&lt;/p&gt;
&lt;p data-start="1053" data-end="1094"&gt;A line from the video sums the idea up:&lt;/p&gt;
&lt;blockquote data-start="1095" data-end="1250"&gt;
&lt;p data-start="1097" data-end="1250"&gt;&amp;ldquo;It&amp;rsquo;s the holiday season&amp;hellip; But if you want to get to that candy, we&amp;rsquo;ll make something that will make you run first.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="1252" data-end="1271"&gt;&lt;strong data-start="1252" data-end="1271"&gt;MAIN VIDEO LINK&lt;/strong&gt;&lt;/p&gt;
&lt;hr data-start="1273" data-end="1276" /&gt;
&lt;h2 id="mcetoc_1jc6etsj40" data-start="1278" data-end="1290"&gt;2. Idea&lt;/h2&gt;
&lt;p data-start="1291" data-end="1499"&gt;Milos first built this project more than six years ago, back before he owned a 3D printer. He&amp;rsquo;s rebuilt it now to make it smaller and more refined, taking advantage of 3D printing and modern microcontrollers.&lt;/p&gt;
&lt;p data-start="1501" data-end="1825"&gt;The original mechanism was hand-cut from 4 mm plywood; the small pushrods used sheet metal salvaged from a gutter &amp;mdash; there was plenty of room for improvement. You can read his original, detailed write-up here:&lt;br data-start="1709" data-end="1712" /&gt; &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="1712" data-end="1825" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-start="1827" data-end="2085"&gt;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 &amp;mdash; no obvious electronics, just a jar full of candy.&lt;/p&gt;
&lt;hr data-start="2087" data-end="2090" /&gt;
&lt;h2 id="mcetoc_1jc6etsj41" data-start="2092" data-end="2109"&gt;3. Mechanism&lt;/h2&gt;
&lt;p data-start="2110" data-end="2419"&gt;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).&lt;/p&gt;
&lt;p data-start="2421" data-end="2646"&gt;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.&lt;/p&gt;
&lt;p data-start="2648" data-end="2709"&gt;A quote from the video where Milos describes the mechanism:&lt;/p&gt;
&lt;blockquote data-start="2710" data-end="2953"&gt;
&lt;p data-start="2712" data-end="2953"&gt;&amp;ldquo;In the centre we have the servo horn&amp;hellip; and around that we have the three pistons that move linearly. So they&amp;rsquo;re connected together using this small pushrod&amp;hellip; For the new version, I want to go with gears.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="2955" data-end="3094"&gt;The new lid packs everything tightly; from the outside (when unlocked) there&amp;rsquo;s no visual clue that anything special is inside &amp;mdash; just candy.&lt;/p&gt;
&lt;hr data-start="3096" data-end="3099" /&gt;
&lt;h2 id="mcetoc_1jc6etsj52" data-start="3101" data-end="3117"&gt;4. Software&lt;/h2&gt;
&lt;p data-start="3118" data-end="3298"&gt;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.&lt;/p&gt;
&lt;p data-start="3300" data-end="3317"&gt;From the video:&lt;/p&gt;
&lt;blockquote data-start="3318" data-end="3509"&gt;
&lt;p data-start="3320" data-end="3509"&gt;&amp;ldquo;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.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="mcetoc_1jc6etsj53" data-start="3511" data-end="3536"&gt;Strava flow (summary)&lt;/h3&gt;
&lt;ol data-start="3537" data-end="4070"&gt;
&lt;li data-start="3537" data-end="3647"&gt;
&lt;p data-start="3540" data-end="3647"&gt;Create a Strava API application (Settings &amp;rarr; My API Application) and note &lt;code data-start="3613" data-end="3624"&gt;client_id&lt;/code&gt; and &lt;code data-start="3629" data-end="3644"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3648" data-end="3717"&gt;
&lt;p data-start="3651" data-end="3717"&gt;Build and open the authorize URL to obtain the temporary &lt;code data-start="3708" data-end="3714"&gt;code&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3718" data-end="3816"&gt;
&lt;p data-start="3721" data-end="3816"&gt;Exchange the &lt;code data-start="3734" data-end="3740"&gt;code&lt;/code&gt; for &lt;code data-start="3745" data-end="3759"&gt;access_token&lt;/code&gt; and &lt;code data-start="3764" data-end="3779"&gt;refresh_token&lt;/code&gt; (the example cURL is shown below).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3817" data-end="3936"&gt;
&lt;p data-start="3820" data-end="3936"&gt;Copy the tokens into the Arduino sketch and flash the ESP32-S3. The code refreshes tokens automatically as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="3937" data-end="4070"&gt;
&lt;p data-start="3940" data-end="4070"&gt;Open the device web page and choose &lt;strong data-start="3976" data-end="3998"&gt;Show last activity&lt;/strong&gt; to verify the device returns your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-start="4072" data-end="4212"&gt;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.&lt;/p&gt;
&lt;blockquote data-start="4214" data-end="4332"&gt;
&lt;p data-start="4216" data-end="4332"&gt;&amp;ldquo;It will just give you the name of your last activity, the distance you ran&amp;hellip; &amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-start="4334" data-end="4337" /&gt;
&lt;h2 id="mcetoc_1jc6etsj54" data-start="4339" data-end="4389"&gt;5. Key code excerpts (from &lt;code data-start="4369" data-end="4386"&gt;CookieJarV2.ino&lt;/code&gt;)&lt;/h2&gt;
&lt;p data-start="4390" data-end="4545"&gt;Below are short, focused excerpts from the project code to explain the important behaviours. These are included to show how the software actually operates.&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="4547" data-end="4616"&gt;Configuration constants (Wi-Fi, Strava tokens and servo settings)&lt;/strong&gt; &amp;mdash; set these before flashing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Wi-Fi credentials
const char *WIFI_SSID = &amp;quot;YOUR-SSID&amp;quot;;
const char *WIFI_PASSWORD = &amp;quot;YOUR-PASSWORD&amp;quot;;

// Strava tokens and keys (example placeholders)
const char *STRAVA_CLIENT_ID = &amp;quot;YOUR_STRAVA_CLIENT_ID&amp;quot;;
const char *STRAVA_CLIENT_SECRET = &amp;quot;YOUR_STRAVA_CLIENT_SECRET&amp;quot;;
const char *STRAVA_ACCESS_TOKEN = &amp;quot;YOUR_STRAVA_ACCESS_TOKEN&amp;quot;;
const char *STRAVA_REFRESH_TOKEN = &amp;quot;YOUR_STRAVA_REFRESH_TOKEN&amp;quot;;

// 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;
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;strong data-start="5252" data-end="5275"&gt;Servo motion helper&lt;/strong&gt; &amp;mdash; the code attaches the servo, moves to the angle, waits, and detaches. Detaching stops the constant PWM &amp;ldquo;holding&amp;rdquo; signal, saving battery and stopping buzzing:&lt;/p&gt;
&lt;p data-start="4547" data-end="4645"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void moveServoTo(int angle) {
  jarServo.attach(PIN_SERVO);
  delay(100);
  jarServo.write(angle);
  delay(SERVO_PULSE_DELAY_MS);
  jarServo.detach();
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="5602" data-end="5695"&gt;This corresponds with Milos&amp;rsquo;s on-camera note about saving power and avoiding servo buzzing:&lt;/p&gt;
&lt;blockquote data-start="5696" data-end="6040"&gt;
&lt;p data-start="5698" data-end="6040"&gt;&amp;ldquo;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&amp;hellip; 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.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;strong data-start="6042" data-end="6079"&gt;Strava fetch (sketch of approach)&lt;/strong&gt; &amp;mdash; 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):&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;bool fetchStravaLatestActivity() {
  ensureWiFi();
  if (WiFi.status() != WL_CONNECTED) {
    lastError = &amp;quot;WiFi disconnected&amp;quot;;
    return false;
  }

  // HTTP call to Strava /athlete/activities or other endpoint
  // Parse JSON, extract last activity id, name, distance (m -&amp;gt; km), calories etc.
  // Update lastActivity struct and hasLastActivity flag
  ...
  return true; // or false on error
}
&lt;/pre&gt;&lt;/p&gt;
&lt;p data-start="6042" data-end="6255"&gt;The sketch includes a regular poll loop:&lt;/p&gt;
&lt;div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"&gt;
&lt;div class="sticky top-9"&gt;
&lt;div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;const unsigned long STRAVA_POLL_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 minutes

// inside loop():
unsigned long now = millis();
if (now - lastPollMs &amp;gt;= STRAVA_POLL_INTERVAL_MS || now &amp;lt; lastPollMs) {
  lastPollMs = now;
  ActivityStats stats;
  if (fetchStravaToday(stats)) {
    lastStats = stats;
  } else if (fetchStravaLatestActivity()) {
    lastStats.distanceKm = lastActivity.distanceKm;
    lastStats.calories = lastActivity.calories;
    lastStats.fresh = true;
  }
  evaluateGoalAndMaybeUnlock();
}
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;&lt;/div&gt;
&lt;div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"&gt;
&lt;p data-start="7229" data-end="7428"&gt;&lt;code data-start="7229" data-end="7259"&gt;evaluateGoalAndMaybeUnlock()&lt;/code&gt; is the function that takes the gathered activity stats and decides whether to call &lt;code data-start="7343" data-end="7376"&gt;moveServoTo(SERVO_UNLOCK_ANGLE)&lt;/code&gt; (unlock) or &lt;code data-start="7389" data-end="7420"&gt;moveServoTo(SERVO_LOCK_ANGLE)&lt;/code&gt; (lock).&lt;/p&gt;
&lt;hr data-start="7430" data-end="7433" /&gt;
&lt;h2 id="mcetoc_1jc6f0gj05" data-start="7435" data-end="7490"&gt;6. Step-by-step: Strava auth/setup (short checklist)&lt;/h2&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="7491" data-end="7587"&gt;
&lt;p data-start="7493" data-end="7587"&gt;Create a Strava API app in your Strava account and note the &lt;code data-start="7553" data-end="7564"&gt;client_id&lt;/code&gt; and &lt;code data-start="7569" data-end="7584"&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7588" data-end="7854"&gt;
&lt;p data-start="7590" data-end="7854"&gt;Build the authorisation URL (replace &lt;code data-start="7627" data-end="7638"&gt;CLIENT_ID&lt;/code&gt; and &lt;code data-start="7643" data-end="7657"&gt;REDIRECT_URI&lt;/code&gt;):&lt;br data-start="7659" data-end="7662" /&gt; &lt;code data-start="7664" data-end="7852"&gt;https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&amp;amp;response_type=code&amp;amp;redirect_uri=http://localhost/exchange_token&amp;amp;approval_prompt=auto&amp;amp;scope=read,activity:read,activity:read_all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7855" data-end="7959"&gt;
&lt;p data-start="7857" data-end="7959"&gt;Open the URL in a browser, approve the app, capture the returned &lt;code data-start="7922" data-end="7928"&gt;code&lt;/code&gt; (e.g. &lt;code data-start="7935" data-end="7955"&gt;...?code=YOUR_CODE&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="7960" data-end="8227"&gt;
&lt;p data-start="7962" data-end="8023"&gt;Exchange the &lt;code data-start="7975" data-end="7981"&gt;code&lt;/code&gt; for tokens (on your PC) &amp;mdash; example cURL:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="batchfile"&gt;curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET \
  -d code=YOUR_CODE \
  -d grant_type=authorization_code
&lt;/pre&gt;&lt;/p&gt;
&lt;ul data-start="7491" data-end="8502"&gt;
&lt;li data-start="8228" data-end="8357"&gt;
&lt;p data-start="8230" data-end="8357"&gt;Set &lt;code data-start="8234" data-end="8252"&gt;STRAVA_CLIENT_ID&lt;/code&gt;, &lt;code data-start="8254" data-end="8276"&gt;STRAVA_CLIENT_SECRET&lt;/code&gt;, &lt;code data-start="8278" data-end="8299"&gt;STRAVA_ACCESS_TOKEN&lt;/code&gt; and &lt;code data-start="8304" data-end="8326"&gt;STRAVA_REFRESH_TOKEN&lt;/code&gt; in the &lt;code data-start="8334" data-end="8347"&gt;CookieJarV2&lt;/code&gt; sketch.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8358" data-end="8502"&gt;
&lt;p data-start="8360" data-end="8502"&gt;Flash the ESP32-S3 and test by opening the device&amp;rsquo;s web page &amp;rarr; &amp;ldquo;Show last activity&amp;rdquo;. You should see your latest Strava activity with HTTP 200.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-start="8504" data-end="8507" /&gt;
&lt;h2 id="mcetoc_1jc6f1e556" data-start="8509" data-end="8540"&gt;7. Final assembly and test&lt;/h2&gt;
&lt;p data-start="8541" data-end="8568"&gt;Once assembled and flashed:&lt;/p&gt;
&lt;ul data-start="8569" data-end="8825"&gt;
&lt;li data-start="8569" data-end="8718"&gt;
&lt;p data-start="8571" data-end="8718"&gt;Lock the jar, set your target distance via the device web UI, go for your run, and wait for the device to poll Strava and evaluate your activity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="8719" data-end="8825"&gt;
&lt;p data-start="8721" data-end="8825"&gt;The sketch will automatically refresh tokens if required and will periodically check for new activities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="8827" data-end="9077"&gt;Milos demonstrates this flow in the video and finishes by going for a run to show the jar unlocking when the goal is met. He notes that the jar, when seen from the outside, looks like an ordinary jar of candy &amp;mdash; the mechanism is hidden inside the lid.&lt;/p&gt;
&lt;blockquote data-start="9079" data-end="9257"&gt;
&lt;p data-start="9081" data-end="9257"&gt;&amp;ldquo;If you look at it from outside, it just looks like a jar full of candy bars&amp;hellip; you won&amp;rsquo;t even know that there are some electronics inside.&amp;rdquo; &lt;span class="" data-state="closed"&gt;&lt;span class="relative inline-flex items-center"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="not-prose mt-0! mb-0! flex-auto truncate"&gt;MR20 - Cookie Jar V2 - Video V1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-start="9259" data-end="9262" /&gt;
&lt;h2 id="mcetoc_1jc6f1e557" data-start="9264" data-end="9281"&gt;8. Resources&lt;/h2&gt;
&lt;ul data-start="9282" data-end="9592"&gt;
&lt;li data-start="9282" data-end="9442"&gt;
&lt;p data-start="9284" data-end="9442"&gt;Original project write-up (older version): &lt;a class="decorated-link" href="/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar" target="_new" data-start="9327" data-end="9440" data-e14adj="t"&gt;https://community.element14.com/challenges-projects/project14/theholidayspecial19/b/blog/posts/project-cookie-jar&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9443" data-end="9521"&gt;
&lt;p data-start="9445" data-end="9521"&gt;Strava developer docs: &lt;a class="decorated-link" href="https://developers.strava.com/docs/getting-started/" data-start="9468" data-end="9519" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://developers.strava.com/docs/getting-started/&lt;span class="ms-0.5 inline-block align-middle leading-none"&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-start="9522" data-end="9592"&gt;
&lt;p data-start="9524" data-end="9592"&gt;Code and files:&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;
</description></item></channel></rss>