/*
Works on CYD
====================================================================
Copyright (c) 2019 Thorsten Godau (dl9sec). All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
3. Neither the name of the author(s) nor the names of any contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
====================================================================*/
//
//https://github.com/Hopperpop/Sgp4-Library/tree/master/examples
//
// Call up the SPIFFS FLASH filing system this is part of the ESP Core
#define FS_NO_GLOBALS
#include <FS.h>
// So I used image magick with the following settings to get the proper color bit depth and compression:
// magick convert -depth 24 -compress none -background black -alpha remove image.bmp new-image.bmp
#ifdef ESP32
#include "SPIFFS.h" // For ESP32 only
#endif
#include <WiFi.h>
#include <HTTPClient.h>
#include "time.h"
#include <TimeLib.h>
#include <WiFiUdp.h>
#include "settings.h" // Important: see settings.h to configure your settings!!!
time_t getNtpTime();
#include <Sgp4.h>
#include <Ticker.h> //https://github.com/sstaub/Ticker
Sgp4 sat;
Ticker tkSecond; // this lib create some task at schedule time (second...)
//Ticker tickerObject(callbackFunction, 1000);
//tickerObject.start(); //start the ticker.
#include "Free_Fonts.h" // Include the header file attached to this sketch
#include <TFT_eSPI.h> // Hardware-specific library
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library
// Include the header files that contain the icons
#include "Info.h"
long loopCounter = 0;
int year_;
int mon_;
int day_;
int hr_;
int min_;
double sec_;
long count = 0; // Loop count
double dfreqRX = 145.800; // Nominal downlink frequency
double dfreqTX = 437.800; // Nominal uplink frequency
int iYear = 2021; // Set start year
int iMonth = 4; // Set start month
int iDay = 2; // Set start day
int iHour = 20; // Set start hour
int iMinute = 00; // Set start minute
int iSecond = 00; // Set start second
double dSatLAT = 0; // Satellite latitude
double dSatLON = 0; // Satellite longitude
double dSatAZ = 0; // Satellite azimuth
double dSatEL = 0; // Satellite elevation
double dSunLAT = 0; // Sun latitude
double dSunLON = 0; // Sun longitude
double dSunAZ = 0; // Sun azimuth
double dSunEL = 0; // Sun elevation
int ixQTH = 0; // Map pixel coordinate x of QTH
int iyQTH = 0; // Map pixel coordinate y of QTH
int ixSAT = 0; // Map pixel coordinate x of satellite
int iySAT = 0; // Map pixel coordinate y of satellite
int ixSUN = 0; // Map pixel coordinate x of sun
int iySUN = 0; // Map pixel coordinate y of sun
char acBuffer[20]; // Buffer for ASCII time
int aiSatFP[32][2]; // Array for storing the satellite footprint map coordinates
int aiSunFP[32][2]; // Array for storing the sunlight footprint map coordinates
int xpos = 0;
int xposInit = 0;
int ypos = 200; // Top left corner ot clock text, about half way down
int ysecs = ypos;
int xyfont = 2;
byte xcolon = 0, xsecs = 0;
uint32_t targetTime = 0; // for next 1 second timeout
unsigned long unixtime = 1617906546;
//int timezone = 12 ; //utc + 12
int framerate;
static uint8_t conv2d(const char* p); // Forward declaration needed for IDE 1.6.x
String MyTLEName = "xxxxxxxxxx";
String MyTLE1 = "xxxxxxxxxxxxxxxxxxxxxxx";
String MyTLE2 = "yyyyyyyyyyyyyyyyyyyyyyy";
//byte omm = 99, oss = 99;
unsigned int colour = 0;
unsigned long previousMillis = 0 ;
unsigned long interval = 1000;
// *************************************************************
char TLE[500]; //Variable to store satellite TLEs.
int numSats = 4;
int SAT = 0;
char satname[] = "ISS (ZARYA)";
char tle_line1[] = "1 25544U 98067A 21097.40647234 .00001690 00000-0 39002-4 0 9993"; //Line one from the TLE data
char tle_line2[] = "2 25544 51.6464 332.8116 0002933 191.9452 346.3978 15.48867738277621"; //Line two from the TLE data
char satnames[4][30] = {"ISS (ZARYA)",
"NEOSSAT",
"M3MSAT ",
"SCISAT"
};// Names of satellites.
char satURL[4][30] = {"/satcat/tle.php?CATNR=25544",
"/satcat/tle.php?CATNR=39089",
"/satcat/tle.php?CATNR=41605",
"/satcat/tle.php?CATNR=27858"
}; // URL of Celestrak TLEs for satellites (In same order as names).
char TLE1[4][70];
char TLE2[4][70];
char server[] = "https://celestrak.org"; //Web address to get TLE (CELESTRAK)
HTTPClient client;
String HourStr[10];
// Epochtime
// NTP server to request epoch time
const char* ntpServer = "pool.ntp.org";
// Variable to save current epoch time
unsigned long epochTime;
// Function that gets current epoch time
unsigned long getTime() {
time_t now;
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
//Serial.println("Failed to obtain time");
return (0);
}
time(&now);
return now;
}
void setup()
{
Serial.begin(115200);
if (!SPIFFS.begin()) {
Serial.println("SPIFFS initialisation failed!");
while (1) yield(); // Stay here twiddling thumbs waiting
}
Serial.println("\r\nSPIFFS initialised.");
delay(250);
tft.begin();
tft.setRotation(1); // landscape
tft.fillScreen(TFT_BLACK);
// Slapsh screen
drawBmp("/ISS_20years.bmp", 0, 0); // 320x106
delay(500);
tft.setFreeFont(TT1); // Select the orginal small TomThumb font
tft.drawString("ISS tracker", 20, 120, FONT2);
delay(2000);
tft.drawString("Connecting to Wifi...", 20, 140, FONT2);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print("_>");
tft.drawString("Connection...", 20, 160, FONT2);
}
Serial.println("Connected to the WiFi ");
tft.drawString("Connection success....", 20, 180, FONT2);
delay(2000);
#ifdef DEBUG
Serial.print("IP number assigned by DHCP is ");
Serial.println(WiFi.localIP());
#endif
//delay(100);
tft.fillRect(0, 107, 320, 240, TFT_BLACK);
delay(500);
tft.fillRect(0, 0, 320, 106, TFT_WHITE);
delay(500);
drawBmp("/Ntp.bmp", 20, 10); // 100x72
tft.drawString("Getting time from NTP server...", 10, 130, FONT2);
// printing epochTime
// (how many second from Jan 1st, 1970 )
// this is usefull for calculating the Sat track
configTime(0, 0, ntpServer);
epochTime = getTime();
for (int i = 0; i < 5; i++) {
tft.drawNumber(epochTime, 10, 170, FONT4); //ok
epochTime = getTime();
delay(1000);
}
tft.drawString("This is the Unixtime", 100, 210, FONT2);
delay(2000);
tft.fillRect(0, 107, 320, 240, TFT_BLACK);
delay(500);
drawBmp("/Celestrak.bmp", 0, 0);
delay(500);
tft.drawString("Connecting to Celestrak", 20, 140, FONT2);
delay(500);
tft.drawString("Looking for the lastest TLE", 20, 160, FONT2);
GetTLE();
//TLE1 and 2 received
tft.drawString("TLE1 and TLE2 uploaded...", 20, 180, FONT2);
delay(1000);
tft.fillRect(0, 0, 320, 120, TFT_WHITE);
delay(500);
tft.fillRect(0, 121, 320, 240, TFT_BLACK);
delay(500);
drawBmp("/Nasa.bmp", 0, 122); // 120x102
delay(500);
drawBmp("/Esa.bmp", 100, 100); // 132x60
delay(500);
drawBmp("/CsaAsc.bmp", 0, 5); // 110x106
delay(500);
drawBmp("/Jaxa.bmp", 120, 10); // 120x77
delay(500);
drawBmp("/Roscosmos2.bmp", 200, 163); // 120x77
delay(5000);
/*
tft.fillRect(0, 0, 320, 120, TFT_WHITE);
delay(500);
tft.fillRect(0, 121, 320, 240, TFT_BLACK);
delay(200);
drawBmp("/MissionAlpha.bmp", 20, 70);
delay(2000);
// drawBmp("/Crew2.bmp", 0, 0);
// delay(2000);
*/
drawIcon(info, 10, 10, infoWidth, infoHeight);
int i = 0;
sat.site(dMyLAT, dMyLON , dMyALT); //set site latitude[°], longitude[°] and altitude[m]
delay(2000);
sat.init(satname, tle_line1, tle_line2); //initialize satellite parameters
//Display TLE epoch time
double jdC = sat.satrec.jdsatepoch;
invjday(jdC , timeZone, true, year_, mon_, day_, hr_, min_, sec_);
Serial.println("Epoch: " + String(day_) + '/' + String(mon_) + '/' + String(year_) + ' ' + String(hr_) + ':' + String(min_) + ':' + String(sec_));
Serial.println();
tkSecond.attach(1, Second_Tick);
Serial.println("go");
//Serial.setDebugOutput(false); // Comment out for UNO
targetTime = millis() + 1000;
tft.fillScreen(TFT_BLACK);
// map is 320 x 160
drawBmp("/Map4.bmp", 0, 0);
} // EndSetup
// *************************
void loop()
{
loopCounter = loopCounter + 1;
if (loopCounter > 2160000) {
clearTracks();
}
epochTime = getTime();
// we use Ticker lib so we can't use delay function.
if ( millis() - previousMillis >= interval) {
#ifdef DEBUG
Serial.println("---------------------> millis > 1000 ------");
#endif
previousMillis = millis();
DrawWatch();
Serial.print("Epoch Time: ");
Serial.println(epochTime);
}
// DisplayInformation1();
sat.findsat(epochTime);
framerate += 1;
/*
// disply tle informations
tft.setFreeFont(TT1); // Select the orginal small TomThumb font
tft.drawString(satname, 40, 173, GFXFF);
tft.drawString(tle_line1, 40, 183, GFXFF);
tft.drawString(tle_line2, 40, 193, GFXFF);
*/
//epoch
//tft.drawNumber(epochTime, 0, 205, FONT2); //ok
tft.drawString("Where is the ISS ?", 0, 225, FONT2);
tft.drawString("Lat :", 150, 225, FONT2);
tft.drawNumber(sat.satLat, 190, 225, FONT2); //ok
tft.drawString("Long :", 225, 225, FONT2);
tft.drawNumber(sat.satLon, 270, 225, FONT2);
// x, y center on lat 0, Long 0
//
// screen is 320 x 240 pixels
// map is 320 x 160
// center of map is at x = 160, y = 80
// Longitude calculated is from -180 to 180 ( sat.satLon )
// Latitude calculated is from -90 to 90 ( sat.satLat )
//
// Drawing coef
float xx = ( ( sat.satLon + 180 ) * 320 ) / 360 ;
int xTft = xx; // ( ( sat.satLon + 180 ) * 320 ) / 360 ; //
// int xTft = sat.satLon + 180; //
int yTft = (90 - sat.satLat); // (220/180)); //ok
// draw SAT marker on screen (one dot drawing a line)
drawMarker( xTft, yTft);
// draw my location marker on screen (one dot drawing a line)
//drawMarker( (dMyLON + 180 * (320/360)) + 160, (dMyLAT + 90 * (220/180)) + 120 );
// mark my position with a circle
int xMyPos = 160 + dMyLON; //
int yMyPos = 90 - dMyLAT; //
//drawCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color),
tft.drawCircle(xMyPos, yMyPos, 2, TFT_GREEN);
tft.drawCircle(xMyPos, yMyPos, 6, TFT_GREEN);
/*
//test
tft.drawCircle(160, 90, 4, TFT_RED);
tft.drawCircle(160+19, 90+34, 4, TFT_RED); //magellan
tft.drawCircle(160-67, 90+54, 4, TFT_RED); //horn
*/
// Draw 2 lines for lat 0 longitude 0
tft.drawLine(160, 0, 160, 180, TFT_RED);
tft.drawLine(0, 90, 320, 90, TFT_RED);
} // end of loop
// *******************************************************************
// This is the function to draw the icon stored as an array in program memory (FLASH)
// To speed up rendering we use a 64 pixel buffer
#define BUFF_SIZE 64
// Draw array "icon" of defined width and height at coordinate x,y
// Maximum icon size is 255x255 pixels to avoid integer overflow
void drawIcon(const unsigned short* icon, int16_t x, int16_t y, uint16_t width, uint16_t height) {
uint16_t pix_buffer[BUFF_SIZE]; // Pixel buffer (16 bits per pixel)
// Set up a window the right size to stream pixels into
tft.setWindow(x, y, x + width - 1, y + height - 1);
// Work out the number whole buffers to send
uint16_t nb = ((uint16_t)height * width) / BUFF_SIZE;
// Fill and send "nb" buffers to TFT
for (int i = 0; i < nb; i++) {
for (int j = 0; j < BUFF_SIZE; j++) {
pix_buffer[j] = pgm_read_word(&icon[i * BUFF_SIZE + j]);
}
tft.pushColors(pix_buffer, BUFF_SIZE);
}
// Work out number of pixels not yet sent
uint16_t np = ((uint16_t)height * width) % BUFF_SIZE;
// Send any partial buffer left over
if (np) {
for (int i = 0; i < np; i++) pix_buffer[i] = pgm_read_word(&icon[nb * BUFF_SIZE + i]);
tft.pushColors(pix_buffer, np);
}
}
// *******************************************************************
void connectWifi() {
if (WiFi.status() == WL_CONNECTED) return;
//Manual Wifi
Serial.printf("Connecting to WiFi %s/%s", ssid);
WiFi.disconnect();
WiFi.mode(WIFI_STA);
WiFi.hostname(WIFI_HOSTNAME);
WiFi.begin(ssid, password);
int i = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
if (i > 80) i = 0;
drawProgress(i, "Connecting to WiFi '" + String(ssid) + "'");
i += 10;
Serial.print(".");
}
drawProgress(100, "Connected to WiFi '" + String(ssid) + "'");
Serial.println("connected.");
}
// *******************************************************************
void printDigits(int digits)
{
// utility for digital clock display: prints preceding colon and leading 0
Serial.print(":");
if (digits < 10)
Serial.print('0');
Serial.print(digits);
}
// *******************************************************************
// Draw a + mark centred on x,y
void drawMarker(int x, int y)
{
tft.drawLine(x - 1, y, x + 1, y, TFT_BLUE);
tft.drawLine(x, y - 1, x, y + 1, TFT_BLUE);
}
// *******************************************************************
// Progress bar helper
void drawProgress(uint8_t percentage, String text) {
"xxx";
}
// *******************************************************************
/*-------- NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime()
{
IPAddress ntpServerIP; // NTP server's ip address
while (Udp.parsePacket() > 0) ; // discard any previously received packets
Serial.println("Transmit NTP Request");
// get a random server from the pool
WiFi.hostByName(ntpServerName, ntpServerIP);
Serial.print(ntpServerName);
Serial.print(": ");
Serial.println(ntpServerIP);
sendNTPpacket(ntpServerIP);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Serial.println("Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
}
}
Serial.println("No NTP Response :-(");
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
// *******************************************************************
// Function to extract numbers from compile time string
static uint8_t conv2d(const char* p) {
uint8_t v = 0;
if ('0' <= *p && *p <= '9')
v = *p - '0';
return 10 * v + *++p - '0';
}
// *******************************************************************
void DrawWatch() {
// Update digital time
tft.setFreeFont(FF18); // Select the font
tft.setFreeFont(FF1); // Select the font
// Draw hours and minutes
xposInit = xpos;
if (hour() < 10) xpos += tft.drawChar('0', xpos, ypos, xyfont); // Add hours leading zero for 24 hr clock
xpos += tft.drawNumber(hour(), xpos, ypos, xyfont); // Draw hours
xcolon = xpos; // Save colon coord for later to flash on/off later
xpos += tft.drawChar(':', xpos, ypos, xyfont); // adding a char space for ':' in xpos
if (minute() < 10) xpos += tft.drawChar('0', xpos, ypos, xyfont); // Add minutes leading zero
xpos += tft.drawNumber(minute(), xpos, ypos, xyfont); // Draw minutes
xpos += tft.drawChar(':', xpos, ypos, xyfont); // adding a char space for ':' in xpos
xsecs = xpos; // Save seconds 'x' position for later display updates
if (second() < 10) xpos += tft.drawChar('0', xpos, ypos, xyfont); // Add leading zero
tft.drawNumber(second(), xpos, ypos, xyfont); // Draw seconds
xpos = xposInit;
}
// ******************************************************************
void DisplayInformation1()
{
//tft.setFreeFont(FF18); // Select the font
tft.setFreeFont(FF1); // Select the font
tft.drawString("Time", 210, 0, 2);
tft.drawNumber(hour(), 260, 0, 2);
tft.drawNumber(minute(), 280, 0, 2);
tft.drawNumber(second(), 300, 0, 2);
tft.drawString("millis()", 210, 15, 2);
tft.drawNumber(millis(), 260, 15, 2);
tft.drawNumber(previousMillis, 280, 30, 2);
}
// *******************************************************************
void Second_Tick()
{
unixtime += 1;
invjday(sat.satJd , timeZone, true, year_, mon_, day_, hr_, min_, sec_);
Serial.println(String(day_) + '/' + String(mon_) + '/' + String(year_) + ' ' + String(hr_) + ':' + String(min_) + ':' + String(sec_));
Serial.println("azimuth = " + String( sat.satAz) + " elevation = " + String(sat.satEl) + " distance = " + String(sat.satDist));
Serial.println("latitude = " + String( sat.satLat) + " longitude = " + String( sat.satLon) + " altitude = " + String( sat.satAlt));
switch (sat.satVis) {
case -2:
Serial.println("Visible : Under horizon");
//tft.setFreeFont(FF1); // Select the font
//tft.drawString("Visible : Under horizon", 150, 210, 2);
break;
case -1:
Serial.println("Visible : Daylight");
//tft.setFreeFont(FF1); // Select the font
//tft.drawString("Visible : Daylight", 150, 210, 2);
break;
default:
Serial.println("Visible : " + String(sat.satVis)); //0:eclipsed - 1000:visible
//tft.setFreeFont(FF1); // Select the font
//tft.drawString("Visible :", 150, 210, 2);
//tft.drawString(String(sat.satVis), 190, 210, 2);
break;
}
Serial.println("Framerate: " + String(framerate) + " calc/sec");
Serial.println();
framerate = 0;
}
//#################### MYSTUFF HERE #################################
void clearTracks() {
drawBmp("/Map4.bmp", 0, 0);
// draw my position
int xMyPos = 160 + dMyLON; //
int yMyPos = 90 - dMyLAT; //
tft.drawCircle(xMyPos, yMyPos, 2, TFT_GREEN);
tft.drawCircle(xMyPos, yMyPos, 6, TFT_GREEN);
// Draw 2 lines for lat 0 longitude 0
tft.drawLine(160, 0, 160, 180, TFT_RED);
tft.drawLine(0, 90, 320, 90, TFT_RED);
Serial.println("**** clearTracks ****");
loopCounter = 0;
}
//#################### END MYSTUFF #################################################
// *******************************************************************
// There follows a crude way of flagging that this example sketch needs fonts which
// have not been enbabled in the User_Setup.h file inside the TFT_HX8357 library.
//
// These lines produce errors during compile time if settings in User_Setup are not correct
//
// The error will be "does not name a type" but ignore this and read the text between ''
// it will indicate which font or feature needs to be enabled
//
// Either delete all the following lines if you do not want warnings, or change the lines
// to suit your sketch modifications.
#ifndef LOAD_GLCD
//ERROR_Please_enable_LOAD_GLCD_in_User_Setup
#endif
#ifndef LOAD_GFXFF
ERROR_Please_enable_LOAD_GFXFF_in_User_Setup!
#endif