Search code examples
httparduinoesp8266http-getstrava

How to access strava API?


I am trying to access strava's API on arduino esp8266. My http.GET() using the ESP8266HTTPClient.h header is returning -1 which means that it fails to connect at some point (HTTPC_ERROR_CONNECTION_REFUSED). I am able to access other sites but am just having trouble linking up to Strava. Here is Strava's api site

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>

const char* ssid = "**SSIDNAME**";
const char* password = "**PASSWORD**";

void setup() 
{
  Serial.begin(115200);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) 
  {
    delay(1000);
    Serial.println("Connecting...");
  }
}

void loop() 
{
  if (WiFi.status() == WL_CONNECTED) 
  {
    Serial.println("Connected!");
    HTTPClient http; //Object of class HTTPClient
    http.begin("https://www.strava.com/athletes/**ATHLETENUMBER**", "**MY CLIENT SECRET**");
    int httpCode = http.GET();

    if (httpCode > 0) 
    {
      const size_t bufferSize = JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(8) + 370;
      DynamicJsonBuffer jsonBuffer(bufferSize);
      JsonObject& root = jsonBuffer.parseObject(http.getString());

      const char* miles = root["all_run_totals"]; 

      Serial.print("Total miles:");
      Serial.println(miles);
    }
    http.end(); //Close connection
  }
  delay(60000);
}

Solution

  • This API is accessible only by TLS/SSL. The code above works for APIs hosted on an insecure http site, but Strava and most other APIs nowadays force https. To access https there is a required fingerprint you will need to provide in the sketch. This fingerprint changes from time to time (the root CA certificate changes less often, maybe every 10 years, and can also be used). The following code will not provide any fingerprint and instead access that secure API insecurely. The problem with accessing an API without verifying the fingerprint is that someone can fake the server, but I expect that to not be an issue in most use cases. This code will GET from Strava's API every 15 minutes, parse the response to JSON, and print out total miles run for an athlete. (Replace **********'ed items).

    #include <Arduino.h>
    #include <ESP8266WiFi.h>
    #include <ESP8266WiFiMulti.h>
    #include <ESP8266HTTPClient.h>
    #include <WiFiClientSecureBearSSL.h>
    #include <ArduinoJson.h>
    
    ESP8266WiFiMulti WiFiMulti;
    
    void setup() {
      Serial.begin(115200);
      delay(4000); //4 seconds
      WiFi.mode(WIFI_STA);
      WiFiMulti.addAP("*****ssid name******", "*******ssid password*******");
    }
    
    void loop() {  
      Serial.print(milesRun());
      Serial.println(" miles");
      delay(900000); //wait 15 minutes
    }
    
    int milesRun() { //connect to wifi, get miles run from strava api, disconnect from wifi
      if ((WiFiMulti.run() == WL_CONNECTED)) {    
        std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
        client->setInsecure();
        HTTPClient https;
        if (https.begin(*client, "https://www.strava.com/api/v3/athletes/*****athlete#*****/stats?access_token=*****access token here *****")) {  // HTTPS
          int httpCode = https.GET();
          if (httpCode > 0) {
            if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
              String payload = https.getString();
              const size_t capacity = 6*JSON_OBJECT_SIZE(5) + 3*JSON_OBJECT_SIZE(6) + JSON_OBJECT_SIZE(11) + 1220;
              DynamicJsonBuffer jsonBuffer(capacity);
              JsonObject& root = jsonBuffer.parseObject(https.getString());
              if (!root.success()) {
                return 0; //json parsing failed
              }
              JsonObject& all_run_totals = root["all_run_totals"];
              int meters = all_run_totals["distance"];
              return meters * 0.000621371; //to miles
            }
          } else {
            return 0; //error check httpCode
          }
          https.end();
        } else {
          return 0; //error unable to connect
        }
      }
    }