/**The MIT License (MIT)
Copyright (c) 2018 by Giuliano Pisoni
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Thanks to Daniel Eichhorn (https://github.com/squix78) and LeRoy Miller (https://github.com/kd8bxp) for their inspiring code.
Base64 encoding code by Rene Nyfenegger:
http://www.adp-gmbh.ch/cpp/common/base64.html
*/
#include <M5Stack.h>
#include "fonts.h"
#include <WiFi.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <TimeLib.h>
#include "FS.h"
#include "SPIFFS.h"
int zoom = 0;
String _BSSID = "";
int status = WL_IDLE_STATUS;
String googleKey = "";
char yourSSID[] = "";
char yourPassword[] = "";
String jsonString = "{\n";
double latitude = 0.0;
double longitude = 0.0;
String coordinate; // = lat,lon
String coordinatePrev; // lat=0.0&lon=0.0
String myUTC;
int myTimezone;
int count;
String name[10], craft[10],risetime[5];
float duration[5];
String base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static String encodeBase64(char* bytes_to_encode, unsigned int in_len) {
String ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}
if (i)
{
for(j = i; j < 3; j++)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];
while((i++ < 3))
ret += '=';
}
return ret;
}
String timeZone(String location,String timeStamp){
Serial.println(F("TIMEZONE START"));
String _timeZone = "";
// Connect to HTTP server
WiFiClientSecure client;
client.setTimeout(10000);
if (!client.connect("maps.googleapis.com", 443)) {
Serial.println(F("Connection failed"));
_timeZone = "Fail";
return _timeZone;
}
Serial.println(F("Connected!"));
// Send HTTP request
client.println(("GET /maps/api/timezone/json?location=" + location + "×tamp=" + timeStamp + "&key=" + googleKey + " HTTP/1.0"));
client.println(F("Host: maps.googleapis.com"));
client.println(F("Connection: close"));
if (client.println() == 0) {
Serial.println(F("Failed to send request"));
_timeZone = "Fail";
return _timeZone;
}
// Check HTTP status
char status[32] = {0};
client.readBytesUntil('\r', status, sizeof(status));
if (strcmp(status, "HTTP/1.0 200 OK") != 0) {
Serial.print(F("Unexpected response: "));
Serial.println(status);
_timeZone = "Fail";
return _timeZone;
}
// Skip HTTP headers
char endOfHeaders[] = "\r\n\r\n";
if (!client.find(endOfHeaders)) {
Serial.println(F("Invalid response"));
_timeZone = "Fail";
return _timeZone;
}
// Allocate JsonBuffer
// Use arduinojson.org/assistant to compute the capacity.
const size_t capacity = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2) + 60;
DynamicJsonBuffer jsonBuffer(capacity);
// Parse JSON object
JsonObject& root = jsonBuffer.parseObject(client);
if (!root.success()) {
Serial.println(F("Parsing failed!"));
_timeZone = "Fail";
return _timeZone;
}
// Extract values
Serial.println(F("Response:"));
Serial.println(root["status"].as<char*>());
Serial.println(root["timeZoneID"].as<char*>());
Serial.println(root["timeZoneName"].as<char*>());
Serial.println(root["dstOffset"].as<char*>());
Serial.println(root["rawOffset"].as<char*>());
String dst = root["dstOffset"].as<char*>();
String raw = root["rawOffset"].as<char*>();
_timeZone = String(raw.toInt() + dst.toInt());
Serial.println(_timeZone);
// Disconnect
client.stop();
return _timeZone;
}
void geolocation(){
Serial.println(F("GEOLOCATION START"));
String MyCoord = "";
//*******************************
String multiAPString = "";
char bssid[6];
DynamicJsonBuffer jsonBuffer;
Serial.println("scan start");
int n = WiFi.scanNetworks();
if (n == 0)
Serial.println("no networks found");
else
{
Serial.print(n);
Serial.println(" networks found...");
}
if (n > 0) {
for (int i = 0; i < n; i++) {
if (i > 0) {
multiAPString += ";";
}
multiAPString += WiFi.BSSIDstr(i) + "," + WiFi.RSSI(i);
}
Serial.println(multiAPString);
char multiAPs[multiAPString.length() + 1];
multiAPString.toCharArray(multiAPs, multiAPString.length());
multiAPString = encodeBase64(multiAPs, multiAPString.length());
Serial.println(multiAPString);
//**********************************
// Connect to HTTP server
WiFi.begin(yourSSID, yourPassword);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(".");
WiFiClient client;
client.setTimeout(10000);
if (!client.connect("api.mylnikov.org", 80)) {
Serial.println(("Connection failed"));
MyCoord = "Fail";
return;
}
Serial.println(("Connected!"));
// Send HTTP request
client.println(("GET /geolocation/wifi?v=1.1&search=" + multiAPString + " HTTP/1.1"));
client.println(("Host: api.mylnikov.org"));
client.println(("Connection: close"));
if (client.println() == 0) {
Serial.println(("Failed to send request"));
MyCoord = "Fail";
return;
}
// Check HTTP status
char status[32] = {0};
client.readBytesUntil('\r', status, sizeof(status));
if (strcmp(status, "HTTP/1.1 200 OK") != 0) {
Serial.print(("Unexpected response: "));
Serial.println(status);
MyCoord = "Fail";
return;
}
// Skip HTTP headers
char endOfHeaders[] = "\r\n\r\n";
if (!client.find(endOfHeaders)) {
Serial.println(("Invalid response"));
MyCoord = "Fail";
return;
}
const size_t capacity = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2) + 60;
DynamicJsonBuffer jsonBuffer(capacity);
// Parse JSON object
JsonObject& root = jsonBuffer.parseObject(client);
if (!root.success()) {
Serial.println(("Parsing failed!"));
MyCoord = "Fail";
return;
}
// Extract values
Serial.println(("Response:"));
Serial.println(root["result"].as<char*>());
Serial.println(root["data"]["lat"].as<char*>());
Serial.println(root["data"]["lon"].as<char*>());
Serial.println(root["data"]["time"].as<char*>());
coordinate = String(root["data"]["lat"].as<char*>()) + "," + String(root["data"]["lon"].as<char*>());
coordinatePrev = "lat=" + String(root["data"]["lat"].as<char*>()) + "&lon=" + String(root["data"]["lon"].as<char*>());
myUTC = root["data"]["time"].as<char*>();
myTimezone = timeZone(coordinate,myUTC).toInt();
Serial.println("Current time : " + humanTime(myUTC.toInt()));
// Disconnect
client.stop();
}
}
String ISSCoord(){
Serial.println("ISS COORD START");
String _ISSCoord = "";
// Connect to HTTP server
WiFiClient client;
client.setTimeout(10000);
if (!client.connect("api.open-notify.org", 80)) {
Serial.println(F("Connection failed"));
_ISSCoord = "Fail";
return _ISSCoord;
}
Serial.println(F("Connected!"));
// Send HTTP request
client.println(F("GET /iss-now.json HTTP/1.0"));
client.println(F("Host: api.open-notify.org"));
client.println(F("Connection: close"));
if (client.println() == 0) {
Serial.println(F("Failed to send request"));
_ISSCoord = "Fail";
return _ISSCoord;
}
// Check HTTP status
char status[32] = {0};
client.readBytesUntil('\r', status, sizeof(status));
if (strcmp(status, "HTTP/1.1 200 OK") != 0) {
Serial.print(F("Unexpected response: "));
Serial.println(status);
_ISSCoord = "Fail";
return _ISSCoord;
}
// Skip HTTP headers
char endOfHeaders[] = "\r\n\r\n";
if (!client.find(endOfHeaders)) {
Serial.println(F("Invalid response"));
_ISSCoord = "Fail";
return _ISSCoord;
}
const size_t capacity = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2) + 60;
DynamicJsonBuffer jsonBuffer(capacity);
// Parse JSON object
JsonObject& root = jsonBuffer.parseObject(client);
if (!root.success()) {
Serial.println(F("Parsing failed!"));
_ISSCoord = "Fail";
return _ISSCoord;
}
// Extract values
Serial.println(F("Response:"));
Serial.println(root["message"].as<char*>());
Serial.println(root["timestamp"].as<char*>());
Serial.println(root["iss_position"]["latitude"].as<char*>());
Serial.println(root["iss_position"]["longitude"].as<char*>());
_ISSCoord = String(root["iss_position"]["latitude"].as<char*>()) + "," + String(root["iss_position"]["longitude"].as<char*>());
Serial.println(_ISSCoord);
// Disconnect
client.stop();
return _ISSCoord;
}
void ISSPrevision(){
Serial.println("ISS PREV START");
String ISSPrev = "";
// Connect to HTTP server
WiFiClient client;
client.setTimeout(10000);
if (!client.connect("api.open-notify.org", 80)) {
Serial.println(("Connection failed"));
}
Serial.println(("Connected!"));
// Send HTTP request
Serial.println(("GET /iss-pass.json?" + coordinatePrev + " HTTP/1.0"));
client.println(("GET /iss-pass.json?" + coordinatePrev + " HTTP/1.0"));
client.println(("Host: api.open-notify.org"));
client.println(("Connection: close"));
if (client.println() == 0) {
Serial.println(("Failed to send request"));
}
// Check HTTP status
char status[32] = {0};
client.readBytesUntil('\r', status, sizeof(status));
if (strcmp(status, "HTTP/1.1 200 OK") != 0) {
Serial.print(("Unexpected response: "));
Serial.println(status);
}
// Skip HTTP headers
char endOfHeaders[] = "\r\n\r\n";
if (!client.find(endOfHeaders)) {
Serial.println(("Invalid response"));
}
// Allocate JsonBuffer
// Use arduinojson.org/assistant to compute the capacity.
const size_t capacity = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2) + 60;
DynamicJsonBuffer jsonBuffer(capacity);
// Parse JSON object
JsonObject& root = jsonBuffer.parseObject(client);
if (!root.success()) {
Serial.println(("Parsing failed!"));
}
M5.Lcd.clearDisplay();
M5.Lcd.setBrightness(200);
M5.Lcd.setTextColor(0xFFFF);
M5.Lcd.fillScreen(0x0000);
M5.Lcd.setFreeFont(FF17);
M5.Lcd.setCursor(15, 15);
M5.Lcd.println("Next flyes over");
// Extract values
Serial.println(("Response:"));
Serial.println(root["message"].as<char*>());
Serial.println("EXTRACT START ");
count = root["request"]["passes"];
if (count > 5) {count = 5;}
for (int i=0;i<count; i++){
unsigned int riseUTC = root["response"][i]["risetime"];
risetime[i] = humanTime(riseUTC);
Serial.println(risetime[i]);
duration[i] = root["response"][i]["duration"];
duration[i] = duration[i] / 60;
Serial.println(duration[i]);
//M5.Lcd.drawString(risetime[i] + " for " + duration[i] + "mins" ,0 ,(i+1) * 24,GFXFF);
M5.Lcd.println(risetime[i] + " for " + duration[i] + " mins");
}
M5.Lcd.display();
// Disconnect
client.stop();
delay(5000);
updateMap(zoom);
}
String humanTime(unsigned int epoch){
Serial.println("HUMANTIME START: epoch " + String(epoch) + " + TimeShift " + String(myTimezone));
int localTime = epoch + myTimezone;
int h = hour(localTime);
int m = minute(localTime);
int d = day(localTime);
int mn = month(localTime);
int y = year(localTime);
char temp[100];
sprintf(temp, "%d/%d %d:%d ",mn,d,h,m);
return (String)temp;
}
void setup() {
Serial.begin(115200);
M5.begin();
/** add those lines if you are using ESP32 ad ILI9341 display
M5.Lcd.setRotation(7);
M5.Lcd.invertDisplay(false);
**/
M5.Lcd.setBrightness(200);
M5.Lcd.setCursor(15, 15);
M5.Lcd.setFreeFont(FF17);
M5.Lcd.println("Welcome to ISS tracker on ESP32");
M5.Lcd.println("by Giuliano Pisoni");
delay(1000);
SPIFFS.begin(true);
if(!SPIFFS.begin()){
Serial.println("SPIFFS Mount Failed");
return;
}
Serial.println();
for(uint8_t t = 4; t > 0; t--) {
Serial.printf("[SETUP] WAIT %d...\n", t);
Serial.flush();
delay(1000);
}
M5.Lcd.println("Searching your location ...");
geolocation();
M5.Lcd.println("Location found :");
M5.Lcd.println( coordinatePrev);
delay(1000);
WiFi.begin(yourSSID, yourPassword);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
M5.Lcd.println("Establishing connection to WiFi..");
}
M5.Lcd.println("Connecting to your network");
_BSSID = WiFi.BSSIDstr();
Serial.println(_BSSID);
M5.Lcd.println("UPDATING MAP ...");
updateMap(0);
}
void downloadFile(String url, String filename) {
Serial.println("DOWNLOADING " + url + " and saving as " + filename);
// wait for WiFi connection
if ((WiFi.status() == WL_CONNECTED)) {
HTTPClient http;
Serial.print("[HTTP] begin...\n");
// configure server and url
http.begin(url);
Serial.print("[HTTP] GET...\n");
// start connection and send HTTP header
int httpCode = http.GET();
if (httpCode > 0) {
//SPIFFS.remove(filename);
File f = SPIFFS.open(filename, "w+");
if (!f) {
Serial.println("file open failed");
return;
}
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK) {
// get lenght of document (is -1 when Server sends no Content-Length header)
int total = http.getSize();
int len = total;
//progressCallback(filename, 0, total);
// create buffer for read
uint8_t buff[2048] = { 0 };
Serial.println("START STREAM");
// get tcp stream
WiFiClient * stream = http.getStreamPtr();
// read all data from server
while (http.connected() && (len > 0 || len == -1)) {
// get available data size
size_t size = stream->available();
if (size) {
// read up to 128 byte
int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
// write it to Serial
f.write(buff, c);
if (len > 0) {
len -= c;
}
//progressCallback(filename, total - len, total);
}
delay(1);
}
Serial.println("STREAM DONE");
Serial.println();
Serial.print("[HTTP] connection closed or file end.\n");
}
f.close();
Serial.print("File Closed");
}
else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
}
void updateMap(int zoom){
Serial.println("UPDATE MAP START");
downloadFile("http://maps.googleapis.com/maps/api/staticmap?center=" + coordinate + "&zoom=" + String(zoom) + "&format=jpg-baseline&size=320x220&maptype=roadmap&markers=size:tiny%7C" + coordinate + "&markers=icon:https://images.srad.jp/topics/iss_64.png%7Cshadow:false%7C" + ISSCoord() + "&key=" + googleKey, "/staticmap.jpg");
M5.Lcd.clearDisplay();
M5.Lcd.setBrightness(200);
M5.Lcd.setTextColor(0xFFFF);
M5.Lcd.fillScreen(0x0000);
M5.Lcd.setFreeFont(FF18);
M5.Lcd.drawJpgFile(SPIFFS, "/staticmap.jpg");
M5.Lcd.drawString("ZOOM - FUNC ZOOM +", 10, 220,GFXFF);
M5.Lcd.display();
}
void loop() {
if (M5.BtnC.wasPressed()){
Serial.println("Button C pressed");
zoom += 1;
M5.Lcd.drawString("UPDATING MAP ...", 80, 100,GFXFF);
updateMap(zoom);
}
if (M5.BtnA.wasPressed()){
Serial.println("Button A pressed");
if (zoom >= 1) {
zoom -= 1;
M5.Lcd.drawString("UPDATING MAP ...", 80, 100,GFXFF);
updateMap(zoom);
}
}
if (M5.BtnB.wasPressed()){
Serial.println("Button B pressed");
M5.Lcd.drawString("UPDATING PASS TIME ...", 50, 100,GFXFF);
ISSPrevision();
}
M5.update();
}