From 5199cb53e6ee512010559d65425172e9f71a145d Mon Sep 17 00:00:00 2001 From: Bodmer Date: Sat, 3 Nov 2018 19:00:15 +0000 Subject: [PATCH] Add new example This example runs on the ESP32 or ESP8266 and fetches International Space Station pass times for a given geographical location. --- examples/Space_Station/ISS_API_Class.cpp | 207 +++++++++++++++++++++++ examples/Space_Station/ISS_API_Class.h | 71 ++++++++ examples/Space_Station/Space_Station.ino | 173 +++++++++++++++++++ 3 files changed, 451 insertions(+) create mode 100644 examples/Space_Station/ISS_API_Class.cpp create mode 100644 examples/Space_Station/ISS_API_Class.h create mode 100644 examples/Space_Station/Space_Station.ino diff --git a/examples/Space_Station/ISS_API_Class.cpp b/examples/Space_Station/ISS_API_Class.cpp new file mode 100644 index 0000000..2167ac9 --- /dev/null +++ b/examples/Space_Station/ISS_API_Class.cpp @@ -0,0 +1,207 @@ +// Space Station class functions which supports the main sketch + +#include "ISS_API_Class.h" // Include the header with the function prototypes etc + +/*************************************************************************************** +** Connect to website and get the International Space Station pass times +***************************************************************************************/ +void SpaceStation::getPasses(String latitude, String longitude, ISS_pass* passData) +{ + this->passData = passData; // Make a copy of the pointer for this class + + JsonStreamingParser json; // Create an instance of the parser + json.setListener(this); // Pass pointer to "this" SpaceStation class to the listener + // so it can call the support functions in this class + + // Use WiFiClient class to create TCP connections + WiFiClient client; + + // URL and port of the server + const char* host = "api.open-notify.org"; + const int httpPort = 80; + + // Connect as a client to the server + if (!client.connect(host, httpPort)) { + Serial.println("connection failed"); + return; + } + + // Built up the GET request + String url = "http://api.open-notify.org/iss-pass.json?lat=" + latitude + "&lon=" + longitude + "&n=" + PASSES; + + // Send GET request + Serial.println("\nSending GET request to api.open-notify.org...\n"); + client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"); + + // Local variables for time-out etc + uint32_t timeout = millis(); + char c = 0; + uint16_t ccount = 0; + + Serial.println("=====> Header start <====="); + + // Read the header that precedes the JSON, ends with \r\n + while ( client.available() > 0 || client.connected()) + { + String line = client.readStringUntil('\n'); + if (line == "\r") { + Serial.println("=====> Header end <====="); + break; + } + + Serial.println(line); // Print the header to serial monitor for viewing + + // Check for timeout + if ((millis() - timeout) > 5000UL) + { + Serial.println ("HTTP header timeout"); + client.stop(); + return; + } + } + + // The JSON message should now be in the buffer for reading + + // Read the JSON character by character and pass it to the JSON decoder + // The decoder will call the SpaceStation class during decoding, so we can + // save the decoded values + + // Use OR since data may still be in the buffer when the client has disconnected! + while ( client.available() > 0 || client.connected()) + { + while(client.available() > 0) + { + c = client.read(); // Read a received character + + #ifdef SHOW_JSON // Optionally show the message with a simple formatter + Serial.print(c); + #endif + + json.parse(c); // Pass to the parser, parser will call listener support functions as needed + + // Check for timeout + if ((millis() - timeout) > 8000UL) + { + Serial.println ("JSON parse client timeout"); + json.reset(); + client.stop(); + return; + } + yield(); + } + } + + json.reset(); + + client.stop(); + +} + +/*************************************************************************************** +** JSON Decoder library calls this when a key has been read +***************************************************************************************/ +void SpaceStation::key(String key) { + currentKey = key; + + //Serial.print("key: "); + //Serial.println(key); +} + +/*************************************************************************************** +** JSON Decoder library calls this when a value has been read +***************************************************************************************/ +void SpaceStation::value(String val) { + + // Test only: + //Serial.print("\nvaluePath :"); Serial.println(valuePath); + //Serial.print("currentParent :"); Serial.println(currentParent); + //Serial.print("currentKey :"); Serial.println(currentKey); + //Serial.print("arrayIndex :"); Serial.println(arrayIndex); + //Serial.print("Value :"); Serial.println(val); + + if (currentParent == "") + { + if (currentKey == "message") passData->message = val; + return; + } + + else + if (currentParent == "request") + { + if (currentKey == "datetime") passData->datetime = (uint32_t)val.toInt(); + } + + else + if (valuePath == "/response") + { + if (currentKey == "duration") passData->passDuration[arrayIndex] = (uint16_t)val.toInt(); + else + if (currentKey == "risetime") passData->passRiseTime[arrayIndex] = (uint32_t)val.toInt(); + } +} + +/*************************************************************************************** +** JSON Decoder library calls this when a start of document decoded +***************************************************************************************/ +void SpaceStation::startDocument() { + currentParent = currentKey = ""; + arrayIndex = 0; + ended = false; + + //Serial.println("\nstart document"); +} + +/*************************************************************************************** +** JSON Decoder library calls this when a end of document decoded +***************************************************************************************/ +void SpaceStation::endDocument() { + ended = true; + + //Serial.println("end document. "); +} + +/*************************************************************************************** +** JSON Decoder library calls this when a start of object decoded +***************************************************************************************/ +void SpaceStation::startObject() { + currentParent = currentKey; + + //Serial.println("start object. "); +} + +/*************************************************************************************** +** JSON Decoder library calls this when a end of object decoded +***************************************************************************************/ +void SpaceStation::endObject() { + currentParent = ""; + arrayIndex++; + + //Serial.println("end object. "); +} + +/*************************************************************************************** +** JSON Decoder library calls this when an array of values has started +***************************************************************************************/ +void SpaceStation::startArray() { + arrayIndex = 0; + valuePath = currentParent + "/" + currentKey; + + //Serial.println("start array. "); +} + +/*************************************************************************************** +** JSON Decoder library calls this when an array of values has ended +***************************************************************************************/ +void SpaceStation::endArray() { + valuePath = ""; + + //Serial.println("end array. "); +} + +/*************************************************************************************** +** JSON Decoder library calls this when a character is whitespace (not used here) +***************************************************************************************/ +void SpaceStation::whitespace(char c) { + + //Serial.println("whitespace"); +} diff --git a/examples/Space_Station/ISS_API_Class.h b/examples/Space_Station/ISS_API_Class.h new file mode 100644 index 0000000..1cb1c45 --- /dev/null +++ b/examples/Space_Station/ISS_API_Class.h @@ -0,0 +1,71 @@ +// Header for Space Station class which supports the main sketch +#pragma once + +#define SHOW_JSON // Print the JSON to the serial monitor +#define PASSES 7 // Number of ISS passes to fetch + +// Choose the WiFi library to load +#ifdef ESP8266 + #include // Built in library for ESP8266 +#else // ESP32 + #include // Built in library for ESP32 +#endif + +#include // Built in library + +#include "JsonStreamingParser.h" +#include "JsonListener.h" + +// Structure to hold the parsed values +typedef struct ISS_pass { + String message; // Message, usually "sucess" + uint32_t datetime = 0; // Time of request, UTC unix time in seconds since Jan 01 1970 + uint16_t passDuration[PASSES] = { 0 }; // Pass duration in seconds + uint32_t passRiseTime[PASSES] = { 0 }; // Time of pass, UTC unix time +} ISS_pass; + +class SpaceStation: public JsonListener { + + public: + + void getPasses(String latitude, String longitude, ISS_pass* passData); + + // Start of Listener support functions + + void key(String key); + + void value(String value); + + void startDocument(); + + void endDocument(); + + void startObject(); + + void endObject(); + + void startArray(); + + void endArray(); + + void whitespace(char c); + + // End of Listener support functions + + private: + + bool ended = true; // Flag to indicate document has ended + + String currentParent; // Current object e.g. "request" + + String currentKey; // Name key of the name:value pair e.g "temperature" + + uint16_t arrayIndex; // Array index 0-N e.g. 4 for 5th pass, qualify with valuePath + + String valuePath; // object (i.e. sequential key) path (like a "file path") + // taken to the name:value pair in the form "/response" + // so values can be pulled from the correct array. + // Needed since different objects contain "data" arrays. + + ISS_pass *passData; // pointer provided by sketch +}; diff --git a/examples/Space_Station/Space_Station.ino b/examples/Space_Station/Space_Station.ino new file mode 100644 index 0000000..5fbf2ca --- /dev/null +++ b/examples/Space_Station/Space_Station.ino @@ -0,0 +1,173 @@ +// This is an example sketch for the JsonStreamingParser library: +// https://github.com/squix78/json-streaming-parser + +// It is compatible with the ESP8266 and ESP32, but could be adapted for other processors +// Created by Bodmer 3/11/18 + +// It connects to a website, requests a list of International Space Station passes that might +// be visible, decodes (parses) the returned JSON message and prints the results to the +// serial monitor window. + +// The sketch uses WiFi to connect to your router and sends a GET request to this +// website which responds with pass times and duration: +// http://open-notify.org/Open-Notify-API/ISS-Pass-Times/ +// This site is convenient for use in an example becuase you do not need to setup an +// account and the API requests per day is not limited. The website also outputs quite a +// simple JSON message with line feeds etc, so it prints nicely. + +// Click the following link to fetch an example JSON message and see it in a browser: +// http://api.open-notify.org/iss-pass.json?lat=27.9881&lon=86.9250 + +// An alternative ISS pass time API website is here is you wish to adapt the sketch: +// https://wheretheiss.at/w/developer + +// To support the example a C++ class is used, this could be in a library of it's own but +// it is attached in the ISS_API_Class tabs of this sketch. + +// Request every 5 minutes for demonstration only, ISS pass times are not updated often +const int UPDATE_INTERVAL_SECS = 5 * 60UL; // 5 minutes + +// >>>>>>>>>>>> Change to suit your WiFi router <<<<<<<<<<<< NOTE +#define WIFI_SSID "Your_SSID" +#define SSID_PASSWORD "Your_password" + +// >>>>>>>>>>>> Change to your location <<<<<<<<<<<< NOTE +// Set the latitude and longitude to at least 4 decimal places, website server bug means it +// will not accept 0.0000 but 0.0001 is OK, try 0.0000 to show error handling! +//const String latitude = "27.9881"; // 90.0000 to -90.0000 negative for Southern hemisphere +//const String longitude = "86.9250"; // 180.000 to -180.000 negative for West +const String latitude = "0.0000001"; // near Null Island +const String longitude = "0.0000001"; // + +#include // https://github.com/PaulStoffregen/Time/blob/master/TimeLib.h + +// Choose the WiFi library to load +#ifdef ESP8266 + #include +#else // ESP32 + #include +#endif + +#include + +#include "ISS_API_Class.h" // Local sketch functions + +SpaceStation api; // Create an instance of this sketches support class + +WiFiUDP udp; // A UDP instance to send and receive packets + +ISS_pass *pass_data; // Pointer to struct that will contain decoded values + +uint32_t nextUpdate = 0; // Time of next update request + +/*************************************************************************************** +** Setup +***************************************************************************************/ +void setup() { + Serial.begin(115200); + + WiFi.begin(WIFI_SSID, SSID_PASSWORD); + + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + Serial.print("."); + } + Serial.println(); + + unsigned int localPort = 2390; // local port to listen to for UDP packets + + udp.begin(localPort); + + nextUpdate = millis(); // Set so next update happens immediately +} + +/*************************************************************************************** +** Loop +***************************************************************************************/ +void loop() { + + // Check if it is time for an update + if (millis() >= nextUpdate) + { + // Set next update time + nextUpdate = millis() + 1000UL * UPDATE_INTERVAL_SECS; + + // Connect to server and fetch ISS pass time + getPassTimes(latitude, longitude); + } + +} + +/*************************************************************************************** +** Get the International Space Station pass times +***************************************************************************************/ +void getPassTimes(String latitude, String longitude) +{ + // Create a new struct to contain the decoded values + pass_data = new ISS_pass; + + uint32_t dt = millis(); // Time how long it takes out of interest only + + // Request passes from website API, provide location and pointer to struct + api.getPasses(latitude, longitude, pass_data); + + Serial.print("\nJSON response parsed in "); + Serial.print(millis()-dt); + Serial.println(" ms\n"); + + // Print pass details while struct still exists + printPasses(pass_data); + + // Delete the struct to recover the memory used + delete pass_data; +} + +/*************************************************************************************** +** Retrieve data from struct and print to serial monitor +***************************************************************************************/ +void printPasses(ISS_pass* pass_data) +{ + Serial.print("Message = "); Serial.println(pass_data->message); + Serial.print("Date = "); Serial.println(strDate(pass_data->datetime)); + Serial.println(); + + for (int i = 0; i < PASSES; i++ ) + { + Serial.print("Pass = "); Serial.println( i+1 ); + Serial.print("Duration = "); Serial.print(pass_data->passDuration[i]); Serial.println( " s" ); + Serial.print("Rise time = "); Serial.println(strDate(pass_data->passRiseTime[i])); + } +} + +/*************************************************************************************** +** Convert unix time to a local date + time string "Oct 16 17:18", ends with newline +***************************************************************************************/ +String strDate(time_t utc) +{ + setTime(utc); // Set system clock to utc time (not time zone compensated) + + String localDate = ""; + + localDate += monthShortStr(month(utc)); + localDate += " "; + localDate += day(utc); + localDate += " " + strTime(utc); + + return localDate; +} + +/*************************************************************************************** +** Convert unix time to a "local time" time string "12:34" +***************************************************************************************/ +String strTime(time_t unixTime) +{ + String localTime = ""; + + if (hour(unixTime) < 10) localTime += "0"; + localTime += hour(unixTime); + localTime += ":"; + if (minute(unixTime) < 10) localTime += "0"; + localTime += minute(unixTime); + + return localTime; +}