Idea
For my final project, I want to build a simple bike computer that sends my rides to Strava. I started with postman to check if using Strava API is smooth, before implementing POST and GET requests from Arduino. After, I need the user input to authenticate to use Strava. Then, I will send some POST and GET requests to Strava to finish the authentications.
POSTMAN
It is a nice software that allows you to send and receive GET and POST requests, as I was able to explore Strava capabilities with a nice interface. Thus, I was able to use it to upload a GPX file which worked.

Authentication Steps
- GET Request - Get Authentication:
https://www.strava.com/oauth/authorize?client_id=........&redirect_uri=http://localhost&response_type=code&scope=activity:write
- POST Request - Get Access Token:
https://www.strava.com/oauth/token?client_id=........&client_secret=client_secret &code=code&grant_type=authorization_code
- POST Request - Refresh Token (Your token expires every 6 hours):
https://www.strava.com/oauth/token?client_id=........&client_secret=client_secret &refresh_token=...&grant_type=refresh_token
Sample Response (from browser URL):
http://localhost/?state=&code=code&scope=read,activity:write
Sample Response:
{
"token_type": "Bearer",
"expires_at": 1702254692,
"expires_in": 21315,
"refresh_token": "...",
"access_token": "...",
"athlete": {
"id": ID,
"username": "ma",
"resource_state": 2,
"firstname": "..",
"lastname": "..",
"bio": null,
"city": null,
"state": null,
"country": null,
"sex": "F",
"premium": false,
"summit": false,
"created_at": "2022-03-18T05:53:05Z",
"updated_at": "2022-06-21T01:24:31Z",
"badge_type_id": 0,
"weight": 0.0,
"profile_medium": "https://lh3.googleusercontent.com/a/ACg8ocKILra9HQW1bjoc9bkjeYSrvsfmTLAtLiLHeY6bb2cvHUU=s96-c",
"profile": "https://lh3.googleusercontent.com/a/ACg8ocKILra9HQW1bjoc9bkjeYSrvsfmTLAtLiLHeY6bb2cvHUU=s96-c",
"friend": null,
"follower": null
}
}
Sample Response:
{
"token_type": "Bearer",
"access_token": "....",
"expires_at": 1702253456,
"expires_in": 20793,
"refresh_token": "...."
}
API Endpoints
- Read Athlete Activities:
https://www.strava.com/api/v3/athlete/activities?access_token=....
- Upload GPX File:
https://www.strava.com/api/v3/uploads
- Check Upload Status:
https://www.strava.com/api/v3/uploads/11090510901
Sample Response:
[
{
"resource_state":3,
"athlete":{
"id":22761504,
"resource_state":1
},
"name":"Morning Ride",
"distance":30835.3,
"moving_time":4770,
"elapsed_time":4770,
"total_elevation_gain":220.1,
"type":"Ride",
"workout_type":null,
"id":2696844830,
"external_id":"garmin_push_4537861411",
"upload_id":2868331151,
"start_date":"2019-12-16T08:00:45Z",
"start_date_local":"2019-12-16T09:00:45Z",
"timezone":"America/New_York",
"utc_offset":-18000,
"start_latlng":[
40.73,
-74.03
],
"end_latlng":[
40.78,
-73.99
],
"location_city":"New York",
"location_state":"NY",
"location_country":"United States",
"start_latitude":40.73,
"start_longitude":-74.03,
"achievement_count":0,
"kudos_count":2,
"comment_count":0,
"athlete_count":2,
"photo_count":0,
"map":{
"id":"a2696844830",
"summary_polyline":"ao~gEe~gsTsEeCsAeEwFjBgFxCqGgT{P{N",
"resource_state":2
},
"trainer":false,
"commute":false,
"manual":false,
"private":false,
"visibility":"everyone",
"flagged":false,
"gear_id":null,
"from_accepted_tag":false,
"upload_id_str":"2868331151",
"average_speed":6.46,
"max_speed":11.2,
"average_cadence":74.2,
"average_watts":143.5,
"kilojoules":684.4,
"device_watts":false,
"has_heartrate":true,
"average_heartrate":151.2,
"max_heartrate":178.0,
"heartrate_opt_out":false,
"display_hide_heartrate_option":true,
"elev_high":69.0,
"elev_low":3.5,
"pr_count":0,
"total_photo_count":0,
"has_kudoed":false,
"description":null,
"calories":682.7,
"perceived_exertion":null,
"prefer_perceived_exertion":null
}
]
Sample Response:
{
"id": 11090510901,
"id_str": "11090510901",
"external_id": "example.gpx",
"error": null,
"status": "Your activity is still being processed.",
"activity_id": null
}
Sample Response:
{
"id": 11090510901,
"id_str": "11090510901",
"external_id": "example.gpx",
"error": null,
"status": "Your activity is ready.",
"activity_id": 10360215181
}
cURL Example
curl --location 'https://www.strava.com/api/v3/uploads' \
--header 'Authorization: Bearer 1314b7933c65c2cb4032214823c1705cf4a3b577' \
--header 'Cookie: _strava4_session=65d748b9tf1cufe6l6cusg7mf2mdkbtu' \
--form 'activity_type="walk"' \
--form 'name="Test Walk"' \
--form 'description="Test description"' \
--form 'trainer="0"' \
--form 'commute="0"' \
--form 'data_type="gpx"' \
--form 'file=@"/Users/masarah/Documents/Arduino/Final Project /example.gpx"'
Sample Response:
{
"id": 11090510901,
"id_str": "11090510901",
"external_id": "example.gpx",
"error": null,
"status": "Your activity is still being processed.",
"activity_id": null
}
Check Upload Status
Endpoint: https://www.strava.com/api/v3/uploads/11090510901
curl --location 'https://www.strava.com/api/v3/uploads/11090510901' \
--header 'Authorization: Bearer 1314b7933c65c2cb4032214823c1705cf4a3b577' \
--header 'Cookie: _strava4_session=65d748b9tf1cufe6l6cusg7mf2mdkbtu'
Sample Response:
{
"id": 11090510901,
"id_str": "11090510901",
"external_id": "example.gpx",
"error": null,
"status": "Your activity is ready.",
"activity_id": 10360215181
}
Moving to the Pico
Try scanning networks

// Simple WiFi network scanner application
// Released to the public domain in 2022 by Earle F. Philhower, III
#include
void setup() {
Serial.begin(115200);
}
const char *macToString(uint8_t mac[6]) {
static char s[20];
sprintf(s, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return s;
}
const char *encToString(uint8_t enc) {
switch (enc) {
case ENC_TYPE_NONE: return "NONE";
case ENC_TYPE_TKIP: return "WPA";
case ENC_TYPE_CCMP: return "WPA2";
case ENC_TYPE_AUTO: return "AUTO";
}
return "UNKN";
}
void loop() {
delay(5000);
Serial.printf("Beginning scan at %lu\n", millis());
auto cnt = WiFi.scanNetworks();
if (!cnt) {
Serial.printf("No networks found\n");
} else {
Serial.printf("Found %d networks\n\n", cnt);
Serial.printf("%32s %5s %17s %2s %4s\n", "SSID", "ENC", "BSSID ", "CH", "RSSI");
for (auto i = 0; i < cnt; i++) {
uint8_t bssid[6];
WiFi.BSSID(i, bssid);
Serial.printf("%32s %5s %17s %2d %4ld\n", WiFi.SSID(i), encToString(WiFi.encryptionType(i)), macToString(bssid), WiFi.channel(i), WiFi.RSSI(i));
}
}
Serial.printf("\n--- Sleeping ---\n\n\n");
delay(5000);
}
Control LED through the web server
/*Pi Pico W WiFi Station Demo
picow-wifi-station.ino
Use WiFi library to connect Pico W to WiFi in Station mode
DroneBot Workshop 2022
https://dronebotworkshop.com
*/
// Include the WiFi Library
#include
// Replace with your network credentials
const char* ssid = "MasarahHS";
const char* password = "...";
// Set web server port number to 80
WiFiServer server(80);
// Variable to store the HTTP request
String header;
// Variable to store onboard LED state
String picoLEDState = "off";
// Current time
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0;
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;
void setup() {
// Start Serial Monitor
Serial.begin(115200);
// Initialize the LED as an output
pinMode(LED_BUILTIN, OUTPUT);
// Set LED off
digitalWrite(LED_BUILTIN, LOW);
// Connect to Wi-Fi network with SSID and password
WiFi.begin(ssid, password);
// Display progress on Serial monitor
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.print("WiFi connected at IP Address ");
Serial.println(WiFi.localIP());
// Start Server
server.begin();
}
void loop() {
WiFiClient client = server.available(); // Listen for incoming clients
if (client) { // If a new client connects,
currentTime = millis();
previousTime = currentTime;
Serial.println("New Client."); // print a message out in the serial port
String currentLine = ""; // make a String to hold incoming data from the client
while (client.connected() && currentTime - previousTime <= timeoutTime) { // loop while the client's connected
currentTime = millis();
if (client.available()) { // if there's bytes to read from the client,
char c = client.read(); // read a byte, then
Serial.write(c); // print it out the serial monitor
header += c;
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();
// Switch the LED on and off
if (header.indexOf("GET /led/on") >= 0) {
Serial.println("LED on");
picoLEDState = "on";
digitalWrite(LED_BUILTIN, HIGH);
} else if (header.indexOf("GET /led/off") >= 0) {
Serial.println("LED off");
picoLEDState = "off";
digitalWrite(LED_BUILTIN, LOW);
}
// Display the HTML web page
client.println("");
client.println("");
client.println("");
// CSS to style the on/off buttons
client.println("");
// Web Page Heading
client.println("Pico W LED Control
");
// Display current state, and ON/OFF buttons for Onboard LED
client.println("Onboard LED is " + picoLEDState + "
");
// Set buttons
if (picoLEDState == "off") {
//picoLEDState is off, display the ON button
client.println("");
} else {
//picoLEDState is on, display the OFF button
client.println("");
}
client.println("");
// The HTTP response ends with another blank line
client.println();
// Break out of the while loop
break;
} else { // if you got a newline, then clear currentLine
currentLine = "";
}
} else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
}
}
// Clear the header variable
header = "";
// Close the connection
client.stop();
Serial.println("Client disconnected.");
Serial.println("");
}
}
Get user input
Part of my final project, I want to get the user input for Strava identification. Thus, here is my attempt to accept the client ID, the client secret and code, sending them through a GET request to get the access token. That Unfortunately did not work out. Using POSTMAN, I completely forgot that this has more layers of certification and authentications to it. Thus, the connection simply failed. Running out of time as usual, I couldn't find the certificate for Strava and make it work in arduino. In terms of syntax, the code work. For reference, check my final project files.


Link to code
Link to strava header
Assignment Description
Individual Assignments:
- design, build, and connect wired or wireless node(s) with network or bus addresses and a local interface
Group Assignment:
- send a message between two projects