MetaImGUI 1.0.0
ImGui Application Template for C++20
Loading...
Searching...
No Matches
ISSTracker.cpp
Go to the documentation of this file.
1/*
2 MetaImGUI
3 Copyright (C) 2026 A P Nicholson
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>.
17*/
18
19#include "ISSTracker.h"
20
21#include "Logger.h"
22
23#include <nlohmann/json.hpp>
24
25#include <chrono>
26#include <stop_token>
27#include <thread>
28
29// HTTP requests using libcurl (cross-platform)
30#include <curl/curl.h>
31
32namespace MetaImGUI {
33
34ISSTracker::ISSTracker() : m_tracking(false) {}
35
39
40void ISSTracker::StartTracking(std::function<void(const ISSPosition&)> callback) {
41 const std::lock_guard<std::mutex> lock(m_threadMutex);
42
43 if (m_tracking) {
44 LOG_INFO("ISS Tracker: Already tracking, skipping");
45 return;
46 }
47
48 m_tracking = true;
49
50 // Store callback under lock protection
51 {
52 const std::lock_guard<std::mutex> callbackLock(m_callbackMutex);
53 m_callback = callback;
54 }
55
56 // C++20: std::jthread with stop_token for clean cancellation
57 // jthread creates its own stop_source internally
58 m_trackingThread = std::jthread([this](const std::stop_token& stopToken) { TrackingLoop(stopToken); });
59
60 LOG_INFO("ISS Tracker: Started tracking");
61}
62
64 const std::lock_guard<std::mutex> lock(m_threadMutex);
65
66 if (!m_tracking) {
67 return;
68 }
69
70 // Request stop using jthread's internal stop_source
71 m_trackingThread.request_stop();
72 m_tracking = false;
73
74 LOG_INFO("ISS Tracker: Stopped tracking");
75}
76
78 return m_tracking;
79}
80
82 const std::lock_guard<std::mutex> lock(m_dataMutex);
83 return m_currentPosition;
84}
85
86void ISSTracker::GetPositionHistory(std::vector<double>& latitudes, std::vector<double>& longitudes) const {
87 const std::lock_guard<std::mutex> lock(m_dataMutex);
88
89 latitudes.clear();
90 longitudes.clear();
91 latitudes.reserve(m_positionHistory.size());
92 longitudes.reserve(m_positionHistory.size());
93
94 for (const auto& pos : m_positionHistory) {
95 if (pos.valid) {
96 latitudes.push_back(pos.latitude);
97 longitudes.push_back(pos.longitude);
98 }
99 }
100}
101
103 return FetchPositionImpl();
104}
105
106void ISSTracker::TrackingLoop(const std::stop_token& stopToken) {
107 while (!stopToken.stop_requested()) {
108 try {
109 const ISSPosition position = FetchPositionImpl();
110
111 // Check if stop was requested after fetch (important for slow networks)
112 if (stopToken.stop_requested()) {
113 LOG_INFO("ISS Tracker: Stop requested, discarding fetched data");
114 break;
115 }
116
117 if (position.valid) {
118 // Update current position and add to history
119 {
120 const std::lock_guard<std::mutex> lock(m_dataMutex);
121 m_currentPosition = position;
122 AddToHistory(position);
123 }
124
125 // Invoke callback if set (copy under lock to avoid data race)
126 std::function<void(const ISSPosition&)> callback;
127 {
128 const std::lock_guard<std::mutex> lock(m_callbackMutex);
129 callback = m_callback;
130 }
131
132 if (callback) {
133 try {
134 callback(position);
135 } catch (const std::exception& e) {
136 LOG_ERROR("ISS Tracker: Callback threw exception: {}", e.what());
137 } catch (...) {
138 LOG_ERROR("ISS Tracker: Callback threw unknown exception");
139 }
140 }
141
142 LOG_INFO("ISS Tracker: Position updated - Lat: {}, Long: {}, Alt: {} km, Vel: {} km/h",
143 position.latitude, position.longitude, position.altitude, position.velocity);
144 }
145 } catch (const std::exception& e) {
146 LOG_ERROR("ISS Tracker: Error fetching position: {}", e.what());
147 } catch (...) {
148 LOG_ERROR("ISS Tracker: Unknown error fetching position");
149 }
150
151 // Wait 5 seconds before next update (if not stopped)
152 auto start = std::chrono::steady_clock::now();
153 while (!stopToken.stop_requested()) {
154 auto now = std::chrono::steady_clock::now();
155 auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - start).count();
156 if (elapsed >= 5) {
157 break;
158 }
159 std::this_thread::sleep_for(std::chrono::milliseconds(100));
160 }
161 }
162
163 LOG_INFO("ISS Tracker: Tracking loop exited");
164}
165
166ISSPosition ISSTracker::FetchPositionImpl() {
167 ISSPosition position;
168 position.valid = false;
169
170 try {
171 const std::string jsonResponse = FetchJSON(ISS_API_URL);
172 if (jsonResponse.empty()) {
173 LOG_ERROR("ISS Tracker: Empty response from server");
174 return position;
175 }
176
177 position = ParseJSON(jsonResponse);
178 } catch (const std::bad_alloc& e) {
179 LOG_ERROR("ISS Tracker: Memory allocation failed: {}", e.what());
180 } catch (const std::exception& e) {
181 LOG_ERROR("ISS Tracker: Fetch failed: {}", e.what());
182 } catch (...) {
183 LOG_ERROR("ISS Tracker: Unknown error during fetch");
184 }
185
186 return position;
187}
188
189namespace {
190// Callback for libcurl to write response data
191size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
192 static_cast<std::string*>(userp)->append(static_cast<char*>(contents), size * nmemb);
193 return size * nmemb;
194}
195} // namespace
196
197std::string ISSTracker::FetchJSON(const std::string& url) {
198 std::string result;
199
200 // RAII-wrap CURL handle to prevent leaks on exceptions
201 const std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> curl(curl_easy_init(), curl_easy_cleanup);
202 if (!curl) {
203 LOG_ERROR("ISS Tracker: Failed to initialize CURL");
204 return result;
205 }
206
207 curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
208 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback);
209 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &result);
210 curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, "MetaImGUI-ISSTracker/1.0");
211 curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);
212 curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, 30L); // Increased to 30 seconds for slow networks
213 curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, 1L);
214 curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYHOST, 2L);
215
216 const CURLcode res = curl_easy_perform(curl.get());
217
218 if (res != CURLE_OK) {
219 LOG_ERROR("ISS Tracker: Request failed: {}", curl_easy_strerror(res));
220 result.clear();
221 }
222
223 return result;
224}
225
226ISSPosition ISSTracker::ParseJSON(const std::string& jsonResponse) {
227 ISSPosition position;
228 position.valid = false;
229
230 try {
231 auto json = nlohmann::json::parse(jsonResponse);
232
233 // Parse required fields
234 if (json.contains("latitude") && json.contains("longitude")) {
235 position.latitude = json["latitude"].get<double>();
236 position.longitude = json["longitude"].get<double>();
237
238 // Parse optional fields
239 if (json.contains("altitude")) {
240 position.altitude = json["altitude"].get<double>();
241 }
242 if (json.contains("velocity")) {
243 position.velocity = json["velocity"].get<double>();
244 }
245 if (json.contains("timestamp")) {
246 position.timestamp = json["timestamp"].get<long>();
247 }
248
249 position.valid = true;
250 } else {
251 LOG_ERROR("ISS Tracker: Missing required fields in JSON response");
252 }
253 } catch (const nlohmann::json::parse_error& e) {
254 LOG_ERROR("ISS Tracker: JSON parse error: {}", e.what());
255 } catch (const nlohmann::json::type_error& e) {
256 LOG_ERROR("ISS Tracker: JSON type error: {}", e.what());
257 } catch (const std::exception& e) {
258 LOG_ERROR("ISS Tracker: Error parsing JSON: {}", e.what());
259 }
260
261 return position;
262}
263
264void ISSTracker::AddToHistory(const ISSPosition& position) {
265 // Note: Caller must hold m_dataMutex
266
267 if (!position.valid) {
268 return;
269 }
270
271 // Add to history
272 m_positionHistory.push_back(position);
273
274 // Keep only the last maxHistorySize positions
275 while (m_positionHistory.size() > m_maxHistorySize) {
276 m_positionHistory.pop_front();
277 }
278}
279
280} // namespace MetaImGUI
nlohmann::json json
#define LOG_INFO(...)
Definition Logger.h:218
#define LOG_ERROR(...)
Definition Logger.h:220
void GetPositionHistory(std::vector< double > &latitudes, std::vector< double > &longitudes) const
Get position history for orbit trail (thread-safe)
ISSPosition GetCurrentPosition() const
Get the current ISS position (thread-safe)
void StopTracking()
Stop tracking.
void StartTracking(std::function< void(const ISSPosition &)> callback=nullptr)
Start tracking ISS position asynchronously.
bool IsTracking() const
Check if tracking is active.
ISSPosition FetchPositionSync()
Manually fetch ISS position once (synchronous)