2022-05-02 08:11:47 -07:00
|
|
|
#define SATDUMP_DLL_EXPORT 1
|
2022-04-21 16:10:00 +02:00
|
|
|
#include "tle.h"
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include "common/utils.h"
|
|
|
|
|
#include "logger.h"
|
|
|
|
|
#include "core/config.h"
|
2022-09-17 01:04:44 +02:00
|
|
|
#include <filesystem>
|
2023-07-18 21:31:13 +02:00
|
|
|
#include "core/plugin.h"
|
2023-08-02 14:38:32 +02:00
|
|
|
#include <thread>
|
2023-10-25 12:11:17 -04:00
|
|
|
#include "nlohmann/json_utils.h"
|
2022-04-21 16:10:00 +02:00
|
|
|
|
|
|
|
|
namespace satdump
|
|
|
|
|
{
|
|
|
|
|
TLERegistry general_tle_registry;
|
|
|
|
|
|
2023-10-19 00:24:43 -04:00
|
|
|
int parseTLEStream(std::istream &inputStream, TLERegistry &new_registry)
|
|
|
|
|
{
|
|
|
|
|
std::string this_line;
|
|
|
|
|
std::deque<std::string> tle_lines;
|
|
|
|
|
int total_lines = 0;
|
|
|
|
|
while (std::getline(inputStream, this_line))
|
|
|
|
|
{
|
|
|
|
|
this_line.erase(0, this_line.find_first_not_of(" \t\n\r"));
|
|
|
|
|
this_line.erase(this_line.find_last_not_of(" \t\n\r") + 1);
|
2024-05-24 20:59:18 +02:00
|
|
|
if (this_line != "")
|
2023-10-19 00:24:43 -04:00
|
|
|
tle_lines.push_back(this_line);
|
|
|
|
|
|
|
|
|
|
if (tle_lines.size() == 3)
|
|
|
|
|
{
|
|
|
|
|
bool checksum_good = true;
|
|
|
|
|
for (int i = 1; i < 3; i++)
|
|
|
|
|
{
|
|
|
|
|
if (!isdigit(tle_lines[i].back()) || !isdigit(tle_lines[i].front()) || tle_lines[i].front() - '0' != i)
|
|
|
|
|
{
|
|
|
|
|
checksum_good = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int checksum = tle_lines[i].back() - '0';
|
|
|
|
|
int actualsum = 0;
|
2023-10-19 00:29:33 -04:00
|
|
|
for (int j = 0; j < (int)tle_lines[i].size() - 1; j++)
|
2023-10-19 00:24:43 -04:00
|
|
|
{
|
|
|
|
|
if (tle_lines[i][j] == '-')
|
|
|
|
|
actualsum++;
|
2023-10-19 09:49:53 -04:00
|
|
|
else if (isdigit(tle_lines[i][j]))
|
|
|
|
|
actualsum += tle_lines[i][j] - '0';
|
2023-10-19 00:24:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
checksum_good = checksum == actualsum % 10;
|
|
|
|
|
if (!checksum_good)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (!checksum_good)
|
|
|
|
|
{
|
|
|
|
|
tle_lines.pop_front();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int norad;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
std::string noradstr = tle_lines[2].substr(2, tle_lines[2].substr(2, tle_lines[2].size() - 1).find(' '));
|
|
|
|
|
norad = std::stoi(noradstr);
|
|
|
|
|
}
|
2024-05-24 20:59:18 +02:00
|
|
|
catch (std::exception &)
|
2023-10-19 00:24:43 -04:00
|
|
|
{
|
|
|
|
|
tle_lines.pop_front();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new_registry.push_back({norad, tle_lines[0], tle_lines[1], tle_lines[2]});
|
|
|
|
|
tle_lines.clear();
|
|
|
|
|
total_lines++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-24 20:59:18 +02:00
|
|
|
// Sort and remove duplicates
|
|
|
|
|
sort(new_registry.begin(), new_registry.end(), [](TLE &a, TLE &b)
|
|
|
|
|
{ return a.name < b.name; });
|
|
|
|
|
new_registry.erase(std::unique(new_registry.begin(), new_registry.end(), [](TLE &a, TLE &b)
|
|
|
|
|
{ return a.norad == b.norad; }),
|
|
|
|
|
new_registry.end());
|
2023-10-19 00:24:43 -04:00
|
|
|
|
|
|
|
|
return total_lines;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-21 16:10:00 +02:00
|
|
|
void updateTLEFile(std::string path)
|
|
|
|
|
{
|
|
|
|
|
std::vector<int> norads_to_fetch = config::main_cfg["tle_settings"]["tles_to_fetch"].get<std::vector<int>>();
|
2023-09-26 00:07:04 +02:00
|
|
|
std::vector<std::string> urls_to_fetch = config::main_cfg["tle_settings"]["urls_to_fetch"].get<std::vector<std::string>>();
|
2022-04-21 16:10:00 +02:00
|
|
|
|
2024-01-26 09:33:21 -05:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (!std::filesystem::exists(std::filesystem::path(path).parent_path()))
|
|
|
|
|
std::filesystem::create_directories(std::filesystem::path(path).parent_path());
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception &e)
|
|
|
|
|
{
|
|
|
|
|
logger->error("Cannot create directory for TLE file: %s", e.what());
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-05-23 01:11:11 +02:00
|
|
|
|
2022-12-11 17:49:59 +01:00
|
|
|
bool success = true;
|
2023-10-19 00:24:43 -04:00
|
|
|
TLERegistry new_registry;
|
2022-04-21 16:10:00 +02:00
|
|
|
|
|
|
|
|
for (int norad : norads_to_fetch)
|
|
|
|
|
{
|
|
|
|
|
std::string url_str = config::main_cfg["tle_settings"]["url_template"].get<std::string>();
|
|
|
|
|
while (url_str.find("%NORAD%") != std::string::npos)
|
|
|
|
|
url_str.replace(url_str.find("%NORAD%"), 7, std::to_string(norad));
|
2023-09-26 00:07:04 +02:00
|
|
|
urls_to_fetch.push_back(url_str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto &url_str : urls_to_fetch)
|
|
|
|
|
{
|
2022-04-21 16:10:00 +02:00
|
|
|
|
|
|
|
|
logger->info(url_str);
|
|
|
|
|
std::string result;
|
|
|
|
|
|
2023-08-01 19:59:26 +02:00
|
|
|
int http_res = 1, trials = 0;
|
|
|
|
|
while (http_res == 1 && trials < 10)
|
|
|
|
|
{
|
|
|
|
|
if ((http_res = perform_http_request(url_str, result)) != 1)
|
2023-08-02 14:37:27 +02:00
|
|
|
{
|
2023-10-19 00:24:43 -04:00
|
|
|
std::istringstream tle_stream(result);
|
|
|
|
|
success = parseTLEStream(tle_stream, new_registry) > 0;
|
2023-08-02 14:37:27 +02:00
|
|
|
}
|
2023-08-01 19:59:26 +02:00
|
|
|
else
|
|
|
|
|
success = false;
|
|
|
|
|
trials++;
|
2023-08-02 14:37:27 +02:00
|
|
|
if (!success)
|
2023-08-02 14:38:32 +02:00
|
|
|
{
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
2023-08-13 12:31:33 +02:00
|
|
|
logger->info("Failed getting TLEs. Retrying...");
|
2023-08-02 14:38:32 +02:00
|
|
|
}
|
2023-08-01 19:59:26 +02:00
|
|
|
}
|
2023-08-13 12:31:33 +02:00
|
|
|
|
|
|
|
|
if (!success)
|
2023-10-27 12:45:23 -04:00
|
|
|
{
|
2023-09-26 00:07:04 +02:00
|
|
|
logger->warn("Failed to get TLE for %s", url_str.c_str());
|
2023-10-27 13:36:49 -04:00
|
|
|
break;
|
2023-10-27 12:45:23 -04:00
|
|
|
}
|
2022-04-21 16:10:00 +02:00
|
|
|
}
|
2022-12-11 17:49:59 +01:00
|
|
|
|
2023-10-27 13:36:49 -04:00
|
|
|
if (success)
|
2022-12-11 17:49:59 +01:00
|
|
|
{
|
2023-10-19 00:24:43 -04:00
|
|
|
std::ofstream outfile(path, std::ios::trunc);
|
2024-05-24 20:59:18 +02:00
|
|
|
for (TLE &tle : new_registry)
|
|
|
|
|
outfile << tle.name << std::endl
|
|
|
|
|
<< tle.line1 << std::endl
|
|
|
|
|
<< tle.line2 << std::endl
|
|
|
|
|
<< std::endl;
|
2023-10-19 00:24:43 -04:00
|
|
|
outfile.close();
|
|
|
|
|
general_tle_registry = new_registry;
|
2023-10-25 12:11:17 -04:00
|
|
|
config::main_cfg["user"]["tles_last_updated"] = time(NULL);
|
|
|
|
|
config::saveUserConfig();
|
|
|
|
|
logger->info("%zu TLEs loaded!", new_registry.size());
|
2023-10-19 00:24:43 -04:00
|
|
|
eventBus->fire_event<TLEsUpdatedEvent>(TLEsUpdatedEvent());
|
2022-12-11 17:49:59 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
logger->error("Error updating TLEs. Not updated.");
|
2022-04-21 16:10:00 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-25 12:11:17 -04:00
|
|
|
void autoUpdateTLE(std::string path)
|
|
|
|
|
{
|
|
|
|
|
std::string update_setting = getValueOrDefault<std::string>(config::main_cfg["satdump_general"]["tle_update_interval"]["value"], "1 day");
|
|
|
|
|
time_t next_update = getValueOrDefault<time_t>(config::main_cfg["user"]["tles_last_updated"], 0);
|
|
|
|
|
bool honor_setting = true;
|
|
|
|
|
if (update_setting == "Never")
|
|
|
|
|
honor_setting = false;
|
|
|
|
|
else if (update_setting == "4 hours")
|
|
|
|
|
next_update += 14400;
|
|
|
|
|
else if (update_setting == "1 day")
|
|
|
|
|
next_update += 86400;
|
|
|
|
|
else if (update_setting == "3 days")
|
|
|
|
|
next_update += 259200;
|
|
|
|
|
else if (update_setting == "7 days")
|
|
|
|
|
next_update += 604800;
|
|
|
|
|
else if (update_setting == "14 days")
|
|
|
|
|
next_update += 1209600;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
logger->error("Invalid TLE Auto-update interval: %s", update_setting.c_str());
|
|
|
|
|
honor_setting = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((honor_setting && time(NULL) > next_update) || satdump::general_tle_registry.size() == 0)
|
|
|
|
|
updateTLEFile(path);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-21 16:10:00 +02:00
|
|
|
void loadTLEFileIntoRegistry(std::string path)
|
|
|
|
|
{
|
|
|
|
|
logger->info("Loading TLEs from " + path);
|
|
|
|
|
std::ifstream tle_file(path);
|
2023-10-19 00:24:43 -04:00
|
|
|
TLERegistry new_registry;
|
|
|
|
|
parseTLEStream(tle_file, new_registry);
|
|
|
|
|
tle_file.close();
|
|
|
|
|
logger->info("%zu TLEs loaded!", new_registry.size());
|
|
|
|
|
general_tle_registry = new_registry;
|
2023-07-18 21:31:13 +02:00
|
|
|
eventBus->fire_event<TLEsUpdatedEvent>(TLEsUpdatedEvent());
|
2022-04-21 16:10:00 +02:00
|
|
|
}
|
2022-10-12 16:45:35 +02:00
|
|
|
|
|
|
|
|
void fetchTLENow(int norad)
|
|
|
|
|
{
|
|
|
|
|
std::string url_str = config::main_cfg["tle_settings"]["url_template"].get<std::string>();
|
|
|
|
|
|
|
|
|
|
while (url_str.find("%NORAD%") != std::string::npos)
|
|
|
|
|
url_str.replace(url_str.find("%NORAD%"), 7, std::to_string(norad));
|
|
|
|
|
|
|
|
|
|
logger->info(url_str);
|
|
|
|
|
std::string result;
|
|
|
|
|
|
|
|
|
|
if (perform_http_request(url_str, result) != 1)
|
|
|
|
|
{
|
2023-10-19 00:24:43 -04:00
|
|
|
std::istringstream tle_stream(result);
|
|
|
|
|
int loaded_count = parseTLEStream(tle_stream, general_tle_registry);
|
|
|
|
|
logger->info("%d TLEs added!", loaded_count);
|
2022-10-12 16:45:35 +02:00
|
|
|
}
|
2023-07-18 21:31:13 +02:00
|
|
|
|
|
|
|
|
eventBus->fire_event<TLEsUpdatedEvent>(TLEsUpdatedEvent());
|
2022-10-12 16:45:35 +02:00
|
|
|
}
|
2024-05-24 20:59:18 +02:00
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
std::optional<TLE> TLERegistry::get_from_norad(int norad)
|
|
|
|
|
{
|
|
|
|
|
std::vector<TLE>::iterator it = std::find_if(begin(),
|
|
|
|
|
end(),
|
|
|
|
|
[&norad](const TLE &e)
|
|
|
|
|
{
|
|
|
|
|
return e.norad == norad;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (it != end())
|
|
|
|
|
return std::optional<TLE>(*it);
|
|
|
|
|
else
|
|
|
|
|
return std::optional<TLE>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<TLE> TLERegistry::get_from_norad_time(int norad, time_t timestamp)
|
|
|
|
|
{
|
|
|
|
|
time_t last_update = getValueOrDefault<time_t>(config::main_cfg["user"]["tles_last_updated"], 0);
|
|
|
|
|
std::string sc_login = getValueOrDefault<std::string>(config::main_cfg["satdump_general"]["tle_space_track_login"]["value"], "");
|
|
|
|
|
std::string sc_passw = getValueOrDefault<std::string>(config::main_cfg["satdump_general"]["tle_space_track_password"]["value"], "");
|
|
|
|
|
|
2024-05-24 23:28:01 -04:00
|
|
|
// Use local time if Space Track credentials are not entered, or time is close to TLE catalog time
|
|
|
|
|
if (sc_login == "" || sc_passw == "" || sc_login == "yourloginemail" || sc_passw == "yourpassword" ||
|
|
|
|
|
fabs((double)last_update - (double)timestamp) < 4 * 24 * 3600)
|
|
|
|
|
return get_from_norad(norad);
|
|
|
|
|
|
|
|
|
|
// Otherwise, request on Space-Track's archive
|
|
|
|
|
std::string timestamp_day, timestamp_daytime;
|
|
|
|
|
{
|
|
|
|
|
if (timestamp < 0)
|
|
|
|
|
timestamp = 0;
|
|
|
|
|
time_t tttime = timestamp;
|
|
|
|
|
std::tm *timeReadable = gmtime(&tttime);
|
|
|
|
|
timestamp_day = std::to_string(timeReadable->tm_year + 1900) + "-" +
|
|
|
|
|
(timeReadable->tm_mon + 1 > 9 ? std::to_string(timeReadable->tm_mon + 1) : "0" + std::to_string(timeReadable->tm_mon + 1)) + "-" +
|
|
|
|
|
(timeReadable->tm_mday > 9 ? std::to_string(timeReadable->tm_mday) : "0");
|
|
|
|
|
timestamp_daytime = (timeReadable->tm_hour > 9 ? std::to_string(timeReadable->tm_hour) : "0" + std::to_string(timeReadable->tm_hour)) + "%3A" +
|
|
|
|
|
(timeReadable->tm_min > 9 ? std::to_string(timeReadable->tm_min) : "0" + std::to_string(timeReadable->tm_min)) + "%3A" +
|
|
|
|
|
(timeReadable->tm_sec > 9 ? std::to_string(timeReadable->tm_sec) : "0" + std::to_string(timeReadable->tm_sec));
|
2024-05-24 20:59:18 +02:00
|
|
|
}
|
|
|
|
|
|
2024-05-24 23:28:01 -04:00
|
|
|
std::string post_request = "identity=" + sc_login +
|
|
|
|
|
"&password=" + sc_passw +
|
|
|
|
|
"&query=https://www.space-track.org/basicspacedata/query/class/gp_history/NORAD_CAT_ID/" + std::to_string(norad) +
|
|
|
|
|
"/EPOCH/%3C" + timestamp_day + "T" + timestamp_daytime + "/orderby/EPOCH%20desc/limit/1/emptyresult/show";
|
|
|
|
|
std::string url = "https://www.space-track.org/ajaxauth/login";
|
2024-05-24 20:59:18 +02:00
|
|
|
|
2024-05-24 23:28:01 -04:00
|
|
|
std::string result;
|
|
|
|
|
if (perform_http_request_post(url, result, post_request) != 1)
|
|
|
|
|
{
|
2024-06-24 23:16:11 -04:00
|
|
|
try
|
|
|
|
|
{
|
2024-06-27 12:02:29 +02:00
|
|
|
bool parsed = true;
|
|
|
|
|
nlohmann::json res;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
res = nlohmann::json::parse(result)[0];
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception &)
|
|
|
|
|
{
|
|
|
|
|
parsed = false;
|
|
|
|
|
}
|
|
|
|
|
if (!parsed || !res.contains("TLE_LINE0") || !res.contains("TLE_LINE1") || !res.contains("TLE_LINE2"))
|
|
|
|
|
{
|
|
|
|
|
logger->warn("Error pulling TLE from Space-Track! Returned data: %s", result.c_str());
|
|
|
|
|
return get_from_norad(norad);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TLE tle;
|
|
|
|
|
tle.norad = norad;
|
|
|
|
|
tle.name = res["TLE_LINE0"].get<std::string>().substr(2, res["TLE_LINE0"].get<std::string>().size());
|
|
|
|
|
tle.line1 = res["TLE_LINE1"].get<std::string>();
|
|
|
|
|
tle.line2 = res["TLE_LINE2"].get<std::string>();
|
|
|
|
|
return tle;
|
2024-06-24 23:16:11 -04:00
|
|
|
}
|
2024-06-27 12:02:29 +02:00
|
|
|
catch (std::exception &e)
|
2024-06-24 23:16:11 -04:00
|
|
|
{
|
2024-06-27 12:02:29 +02:00
|
|
|
logger->error("Could not get TLE from Space-Track : %s", e.what());
|
2024-06-24 23:16:11 -04:00
|
|
|
return get_from_norad(norad);
|
|
|
|
|
}
|
2024-05-24 20:59:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return std::optional<TLE>();
|
|
|
|
|
}
|
2023-10-19 00:29:33 -04:00
|
|
|
}
|