Skip to content

Instantly share code, notes, and snippets.

@savannahostrowski
Last active July 22, 2020 02:08
Show Gist options
  • Save savannahostrowski/1af38dd1b74b41ad9942ac26f4c4138c to your computer and use it in GitHub Desktop.
Save savannahostrowski/1af38dd1b74b41ad9942ac26f4c4138c to your computer and use it in GitHub Desktop.
/*
* This sketch does a couple things:
1. Grabs your IP address
2. Grabs your location from the IP address
3. Uses the location to get the current weather's description from the Open Weather API
4. Uses that description to query the Unsplash API for a suitable image related to the current weather in your area
* Savannah Ostrowski
*/
/************************** Configuration ***********************************/
// edit the config.h tab and enter your Adafruit IO credentials
// and any additional configuration needed for WiFi, cellular,
// or ethernet clients.
#include "config.h"
/************************ Code Starts Here *******************************/
#include <ESP8266WiFi.h> // WiFi Feather library
#include <ESP8266HTTPClient.h> // HTTP Feather library
//ArduinoJson can decode Unicode escape sequence, but this feature is disabled by default
//because it makes the code bigger. To enable it, you must define ARDUINOJSON_DECODE_UNICODE to 1
#define ARDUINOJSON_DECODE_UNICODE 1
// Provides the ability to parse and construct JSON objects
#include <ArduinoJson.h>
// SET UP IP STRUCT
// ---------------------------
typedef struct
{ //here we create a new data type definition, a box to hold other data types
String ip;
String cc; //for each name:value pair coming in from the service, we will create a slot
String cn; //in our structure to hold our data
String rc;
String rn;
String cy;
String ln;
String lt;
} GeoData; //then we give our new data structure a name so we can use it in our code
GeoData location; //we have created a GeoData type, but not an instance of that type, so we create the variable 'location' of type GeoData
// The setup function runs once when you press reset or power the board
void setup()
{
Serial.begin(115200);
delay(10);
// Wait for serial monitor to open then print build details
while (!Serial);
// Print blank line
Serial.println();
// Print that we are connection to the WiFi network
Serial.println("Connecting to " + String(WIFI_SSID));
// Sets the WiFi mode to Station; Station (STA) mode is used to get ESP module connected to a Wi-Fi network established by an access point
WiFi.mode(WIFI_STA);
// Configures the SSID and password for the network
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED)
{ // While we are waiting to connect to the network,
// Print a . every 500ms
delay(500);
Serial.print(".");
}
Serial.println("Getting location");
// Gets the location of the user based on IP address
getGeo();
Serial.println("You are in " + location.cy + ", " + location.rc);
Serial.println("Getting the current weather...");
// Gets the description of the weather from the Open Weather API
String weatherDescription = getCurrentWeatherDescription();
Serial.println("One might describe the weather as: " + weatherDescription);
Serial.println("Fetching a suitable image...");
// Fetches an image from the Unsplash API based on that weather description
String weatherImageUrl = getWeatherImageUrl(weatherDescription);
Serial.println("Your image is: " + weatherImageUrl);
}
// Nothing in the loop
void loop() {}
// A helper function to get the current weather's description from the Open Weather API
String getCurrentWeatherDescription()
{
// Instantiates a client object
HTTPClient theClient;
// Instantiates the description as empty string...to be set later
String description = "";
// Print to the serial monitor that we are starting the request
Serial.println("Making the weather HTTP request");
// Format the url for the API request to the open weather API
theClient.begin("http://api.openweathermap.org/data/2.5/weather?q=" + location.cy + "," + location.rc + "," + location.cc + "&appid=" + WEATHER_API_KEY);
// Make a GET request to the client
int httpCode = theClient.GET();
// If we have a status code
if (httpCode > 0)
{
// Status code is 200; request was successful
if (httpCode == 200)
{
//Print to serial monitor that we got data from the endpoint
Serial.println("Received HTTP payload.");
// Instantiate the object to store the JSON document/blob
StaticJsonDocument<1024> doc;
// Get the JSON blob back from the endpoint (it's serialized at this point)
String payload = theClient.getString();
Serial.println("Parsing...");
// Derialize the payload into the doc variable
deserializeJson(doc, payload);
// Instantiates an error variable which we will check in the next line
DeserializationError error = deserializeJson(doc, payload);
// If there was an error
if (error)
{
//Print that deserialization failed to the serial monitor
Serial.print("deserializeJson() failed with error code ");
// Derialization failed so we print the error
Serial.println(error.c_str());
return "error";
}
// Sets the description variable to the description of the weather with spaces replaced with %20
// for URL compatibility
description = replaceSpaces(doc["weather"][0]["description"].as<String>());
}
else
{
// Print that there's something wrong with the endpoint to the serial monitor
Serial.println("Something went wrong with connecting to the endpoint.");
}
}
// Return the weather description
return description;
}
// A helper function to get a related image URL for the weather description from the Unsplash API
String getWeatherImageUrl(String description)
{
// Instantiates the client object
HTTPClient theClient;
// Instantiates an empty url
String imageUrl = "";
//Defines/set the client using a fingerprint for HTTPS
theClient.begin("https://api.unsplash.com/photos/random/?client_id=" + String(UNSPLASH_API_KEY) + "&query=" + description, FINGERPRINT);
// Make a GET request to the client
int httpCode = theClient.GET();
// If we have a status code
if (httpCode > 0)
{
// Status code is 200; request was successful
if (httpCode == 200)
{
// Instantiate the object to store the JSON document/blob
StaticJsonDocument<20000> doc;
// Get the reponse payload data
String payload = theClient.getString();
// Derialize the payload into the doc variable
deserializeJson(doc, payload);
// Instantiates an error variable which we will check in the next linel
DeserializationError error = deserializeJson(doc, payload);
// If there was an error
if (error)
{
//Print that deserialization failed to the serial monitor
Serial.print("deserializeJson() failed with error code ");
// Derialization failed so we print the error
Serial.println(error.c_str());
return "error";
}
// Get URL from JSON at the random index
imageUrl = doc["urls"]["raw"].as<String>();
}
else
{
Serial.println("Something went wrong with connecting to the endpoint.");
// Returns the string "error"
return "error";
}
}
// Returns the image url
return imageUrl;
}
// A helper function to get the IP address from ipify
String getIP()
{
HTTPClient theClient; // Instantiates the client object
String ipAddress; // Instantiates an ipAddress variable
//Defines/set the client as the ipify website
theClient.begin("http://api.ipify.org/?format=json");
// Make a GET request to the client
int httpCode = theClient.GET();
// If we have a status code
if (httpCode > 0)
{
// Status code is 200; request was successful
if (httpCode == 200)
{
// Instantiate the object to store the JSON document/blob
StaticJsonDocument<100> doc;
// Get the reponse payload data
String payload = theClient.getString();
// Derialize the payload into the doc variable
deserializeJson(doc, payload);
// Grab the IP address from the doc and cast it to a string
ipAddress = doc["ip"].as<String>();
}
else
{
Serial.println("Something went wrong with connecting to the endpoint.");
// Returns the string "error"
return "error";
}
}
// Returns the IP address
return ipAddress;
}
// A helper function which sets all the fields in our GeoData struct based on our IP address endpoint call
void getGeo()
{
// Instantiates a client object
HTTPClient theClient;
// Print to the serial monitor that we are starting the request
Serial.println("Making HTTP request");
// Return IP as .json object
theClient.begin("http://api.ipstack.com/" + getIP() + "?access_key=" + IP_ADDRESS_API_KEY);
// Make a GET request to the client
int httpCode = theClient.GET();
// If we have a status code
if (httpCode > 0)
{
// Status code is 200; request was successful
if (httpCode == 200)
{
//Print to serial monitor that we got data from the endpoint
Serial.println("Received HTTP payload.");
// Instantiate the object to store the JSON document/blob
StaticJsonDocument<1024> doc;
// Get the JSON blob back from the endpoint (it's serialized at this point)
String payload = theClient.getString();
Serial.println("Parsing...");
// Derialize the payload into the doc variable
deserializeJson(doc, payload);
// Instantiates an error variable which we will check in the next line
DeserializationError error = deserializeJson(doc, payload);
// If there was an error
if (error)
{
//Print that deserialization failed to the serial monitor
Serial.print("deserializeJson() failed with error code ");
// Derialization failed so we print the error
Serial.println(error.c_str());
return;
}
// Using .dot syntax, we refer to the variable "location" which is of type GeoData, and place our data into the data structure.
// We cast the values as Strings because the 'slots' in GeoData are Strings
location.ip = doc["ip"].as<String>();
location.cc = doc["country_code"].as<String>();
location.cn = doc["country_name"].as<String>();
location.rc = doc["region_code"].as<String>();
location.rn = doc["region_name"].as<String>();
location.cy = doc["city"].as<String>();
location.lt = doc["latitude"].as<String>();
location.ln = doc["longitude"].as<String>();
}
else
{
// Print that there's something wrong with the endpoint to the serial monitor
Serial.println("Something went wrong with connecting to the endpoint.");
}
}
}
// A helper function to create a string which has all spaces replaced with %20 for URL compatibility
String replaceSpaces(String myString)
{
// Instantiates a string object that we will build up as we iterate
// In place modification of strings is ugly :)
String newString;
// We use a for loop to iterate over each character in the loop
for (int i = 0; i < myString.length(); i++)
{
// Assign the current char to a variable to improve readability
char currentChar = myString[i];
// Use a ternary operator to check if the current character is a space
// If it is, we append %20 to our new string
// If not, we append the current character to our new string
currentChar == ' ' ? newString += "%20" : newString += currentChar;
}
return newString;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment