#define WIFI_SSID "<your SSID>"
#define PASSWORD "<your WiFi password>"
#include <TJpg_Decoder.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <TFT_eSPI.h>
#include "sprite.h"
#include <math.h>
#include <TimeLib.h>
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite icon = TFT_eSprite(&tft);
WiFiClientSecure client;
const String keys[] = {"longitude", "latitude", "altitude", "velocity", "visibilty", "timestamp"};
const uint8_t max_val_length = 20;
float latitude, longitude, altitude, velocity;
float prev_lat = 999.9;
float prev_lon = 999.9;
unsigned long epoch;
char buff[32];
char visibility[20];
char c;
char key_buffer[20]; // for reading key name
char val_buffer[max_val_length]; // for reading key value
int key_pointer;
int val_pointer;
String key;
boolean key_alert;
const uint16_t pixHor = 320;
const uint16_t pixVer = 240;
const int margin = 40; // y-coordinate upper border
uint8_t y_trail[pixHor];
int trak = 0;
int cur_x, cur_y, hulp;
#define BUFSIZE 30000 // should be enough for a 320x160 jpg file
uint8_t* jpgbuffer;
unsigned long jp = 0;
uint16_t* psram_buffer = NULL;
unsigned long buf_size = 51200; // for 320x160 pixels
const char* host = "api.wheretheiss.at";
const char* json_url = "/v1/satellites/25544";
const int httpsPort = 443;
uint32_t tm;
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap) {
if ( y >= tft.height() ) return 0;
unsigned long psp = (y - margin) * pixHor + x;
uint16_t bmp = 0;
for (int r = 0; r < h; r++) {
memcpy (psram_buffer + psp, bitmap + bmp, 2 * w); // Note: size is in bytes, hence 2*w !!
bmp = bmp + w;
psp = psp + pixHor;
}
return 1;
}
void setup() {
cur_x = -1;
cur_y = pixVer; // means: no value
tft.begin();
tft.setRotation(1); // landscape
tft.fillScreen(TFT_BLACK);
// create sprite *before* ps_malloc call!
icon.createSprite(32, 32);
icon.fillSprite(transparent);
icon.drawBitmap(0, 0, iss, 32, 32, TFT_RED);
icon.setPivot(16, 16);
for (int k = 0; k < pixHor; k++) {
y_trail[k] = pixVer; // value pixVer means: no value (yet)
}
jpgbuffer = (uint8_t *) malloc (BUFSIZE);
psram_buffer = (uint16_t*)ps_malloc(buf_size);
Serial.begin(115200);
// For TTGO T4 v1.3:
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, HIGH);
TJpgDec.setJpgScale(1);
TJpgDec.setSwapBytes(true);
TJpgDec.setCallback(tft_output);
WiFi.begin(WIFI_SSID, PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
web_image();
tm = millis();
}
void web_image() {
jp = 0;
bool loaded_ok = getFile("http://dellascolto.com/iss/getImage.php", jpgbuffer);
TJpgDec.drawJpg(0, margin, jpgbuffer, jp);
redraw_full();
tft.setCursor(6, 6);
tft.setTextColor(0x7BEF);
tft.setTextSize(3);
tft.print("ISS");
tft.setTextSize(1);
tft.setCursor(6, 206);
tft.print("Altitude: ");
tft.setCursor(166, 206);
tft.print("Velocity: ");
}
void loop() {
if (!client.connect(host, httpsPort)) {
Serial.println("no connection");
return;
}
client.print(String("GET ") + json_url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
delay(500);
int size = 0;
client.setNoDelay(false);
key_alert = false;
while (client.connected()) {
while ((size = client.available()) > 0) {
c = client.read();
if (c == '{' || c == ',') {
key_alert = true;
} else {
if (key_alert) {
if (c == '"') {
process_key();
process_val();
if (String(key_buffer) == "latitude") {
latitude = atof(val_buffer);
} else if (String(key_buffer) == "longitude") {
longitude = atof(val_buffer);
} else if (String(key_buffer) == "altitude") {
altitude = atof(val_buffer);
} else if (String(key_buffer) == "velocity") {
velocity = atof(val_buffer);
} else if (String(key_buffer) == "timestamp") {
epoch = atol(val_buffer);
} else if (String(key_buffer) == "visibility") {
memcpy(visibility, val_buffer, strlen(val_buffer));
}
}
if (c != ',') key_alert = false; // process_val() ends at a comma; in that case: stay alert
}
}
}
}
if (cur_x != -1) {
clear_icon(cur_x, cur_y);
}
new_pos(longitude, latitude);
// erase oldest part of previous trail
hulp = (cur_x + 30) % pixHor;
if (y_trail[hulp] != pixVer) { // value pixVer means: no (real)value (yet)
tft.drawPixel(hulp, y_trail[hulp], psram_buffer[(y_trail[hulp] - margin) * pixHor + hulp]);
}
if (millis() - tm > 270000) { // daylight curve shifts 1 pixel per 4.5 minute on a 320 pixel wide display
web_image();
tm = millis();
}
tft.setPivot(cur_x, cur_y);
icon.pushRotated(trak, transparent);
for (int k = 0; k < pixHor; k++) {
if (y_trail[k] != pixVer) tft.drawPixel(k, y_trail[k], TFT_RED);
}
update_text();
delay(3000);
}
float d2r (float degr) {
return (PI * degr/180.0);
}
uint16_t hoek(float from_lon, float from_lat, float to_lon, float to_lat) {
if (from_lon < 999.0 && from_lat < 999.0 && to_lon < 999.0 && to_lat < 999.0 && (to_lon != from_lon || to_lat != from_lat)) {
double dlon = d2r(to_lon) - d2r(from_lon);
double angle = atan2(cos(d2r(to_lat)) * sin(dlon), cos(d2r(from_lat)) * sin(d2r(to_lat)) - sin(d2r(from_lat)) * cos(d2r(to_lat)) * cos(dlon));
return (int)((180.0 * angle) / PI + 0.5);
} else {
return 0;
}
}
void new_pos(float iss_lon, float iss_lat) {
int new_x = map(round(iss_lon), -180, 180, 0, 319);
int new_y = map(round(iss_lat), 90, -90, margin, margin + 159);
trak = hoek(prev_lon, prev_lat, iss_lon, iss_lat);
prev_lon = iss_lon;
prev_lat = iss_lat;
if (new_x != cur_x || new_y != cur_y) {
y_trail[new_x] = new_y;
cur_x = new_x;
cur_y = new_y;
}
}
void clear_icon(int x, int y) {
uint16_t px, py;
long ps_index;
tft.startWrite();
px = max(x - 16, 0);
py = max(y - 16, margin);
ps_index = px + pixHor * (py - margin);
for (int r = 0; r < 32; r++) {
tft.pushImage(px, py + r, min(32, pixHor - px), 1, psram_buffer + ps_index);
ps_index += pixHor;
}
tft.endWrite();
}
void redraw_full() {
tft.startWrite(); // required on ESP32
tft.pushImage(0, margin, 320, 160, psram_buffer);
tft.endWrite(); // required on ESP32
}
bool getFile(String url, uint8_t* jpgbuffer) {
Serial.println("Downloading file from " + url);
// Check WiFi connection
if ((WiFi.status() == WL_CONNECTED)) {
HTTPClient http;
http.begin(url);
int httpCode = http.GET();
if (httpCode > 0) {
if (httpCode == HTTP_CODE_OK) {
int total = http.getSize();
int len = total;
uint8_t buff[128] = { 0 };
WiFiClient * stream = http.getStreamPtr();
while (http.connected() && (len > 0 || len == -1)) {
size_t size = stream->available();
if (size) {
int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
memcpy (jpgbuffer + jp, buff, c);
jp = jp + c;
if (len > 0) {
len -= c;
}
}
yield();
}
Serial.println();
Serial.print("[HTTP] connection closed or file end.\n");
}
}
else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
return 1;
}
void process_key() {
key_pointer = 0;
c = client.read();
while (c != '"' && key_pointer < 19) { // null termination takes one position
key_buffer[key_pointer] = c;
key_pointer++;
c = client.read();
}
yield();
key_buffer[key_pointer] = 0; // null termination
}
void process_val() {
// first we need to read the colon between key and value
c = client.read();
val_pointer = 0;
c = client.read();
char read_until;
if (c == '"') {
c = client.read();
read_until = '"';
} else {
read_until = ',';
}
while (c != read_until && val_pointer < max_val_length -1) { // null termination takes one position
val_buffer[val_pointer] = c;
val_pointer++;
c = client.read();
}
yield();
val_buffer[val_pointer] = 0; // null termination
}
void update_text() {
char LA, LO;
if (latitude < 0) {
latitude = fabs(latitude);
LA = 'S';
} else {
LA = 'N';
}
if (longitude < 0) {
longitude = fabs(longitude);
LO = 'W';
} else {
LO = 'E';
}
sprintf(buff, "%02d.%02d.%02d %02d:%02d:%02d", day(epoch), month(epoch), year(epoch), hour(epoch), minute(epoch), second(epoch));
tft.setTextSize(2);
tft.setCursor(100, 12);
tft.fillRect(100, 12, 210, 20, TFT_BLACK);
tft.print(longitude, 2); tft.print(" "); tft.print(LO); tft.print(" "); tft.print(latitude, 2); tft.print(" "); tft.print(LA);
tft.setTextSize(1);
tft.setCursor(70, 206);
tft.fillRect(70, 206, 50, 10, TFT_BLACK);
tft.print(altitude, 0); tft.print(" km");
tft.setCursor(230, 206);
tft.fillRect(230, 206, 50, 10, TFT_BLACK);
tft.print(velocity, 0); tft.print(" km/h");
tft.setCursor(80, 222);
tft.fillRect(80, 222, 145, 8, TFT_BLACK);
tft.print(buff); tft.print(" GMT");
}