File SimSatClient.cpp¶
File List > flight_segment > orion > Orion > Utils > SimSatClient.cpp
Go to the documentation of this file
#include "SimSatClient.hpp"
#include <curl/curl.h>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <string>
// stb_image declarations only — the implementation is already compiled
// inside libmtmd.a (llama.cpp's multimodal library).
#include "Vendor/stb_image.h"
// stb_image_resize2 implementation — not in llama.cpp, compiled here.
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "Vendor/stb_image_resize2.h"
namespace Orion {
// ---------------------------------------------------------------------------
// Env var helpers
// ---------------------------------------------------------------------------
const char* SimSatClient::getBaseUrl() {
const char* p = ::getenv("ORION_SIMSAT_URL");
return p ? p : "http://localhost:9005";
}
// ---------------------------------------------------------------------------
// libcurl callbacks
// ---------------------------------------------------------------------------
size_t SimSatClient::writeCallback(void* contents, size_t size, size_t nmemb, void* userp) {
size_t totalSize = size * nmemb;
auto* buf = static_cast<std::string*>(userp);
buf->append(static_cast<char*>(contents), totalSize);
return totalSize;
}
size_t SimSatClient::headerCallback(char* buffer, size_t size, size_t nitems, void* userp) {
size_t totalSize = size * nitems;
auto* metadataOut = static_cast<std::string*>(userp);
// Look for the mapbox_metadata header
const char* prefix = "mapbox_metadata: ";
size_t prefixLen = strlen(prefix);
if (totalSize > prefixLen && strncasecmp(buffer, prefix, prefixLen) == 0) {
// Extract the value (strip trailing \r\n)
const char* val = buffer + prefixLen;
size_t valLen = totalSize - prefixLen;
while (valLen > 0 && (val[valLen - 1] == '\r' || val[valLen - 1] == '\n')) {
valLen--;
}
metadataOut->assign(val, valLen);
}
return totalSize;
}
// ---------------------------------------------------------------------------
// Simple JSON helpers (avoids pulling in a full JSON library)
// The SimSat responses are simple enough to parse with string operations.
// ---------------------------------------------------------------------------
static bool parseJsonNumberArray(const std::string& json, const char* key, double* out, int count) {
std::string needle = std::string("\"") + key + "\"";
size_t pos = json.find(needle);
if (pos == std::string::npos) return false;
// Find the opening bracket
pos = json.find('[', pos);
if (pos == std::string::npos) return false;
pos++; // skip '['
for (int i = 0; i < count; i++) {
// Skip whitespace
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\n')) pos++;
if (pos >= json.size()) return false;
char* end = nullptr;
out[i] = strtod(json.c_str() + pos, &end);
if (end == json.c_str() + pos) return false; // no number parsed
pos = static_cast<size_t>(end - json.c_str());
// Skip comma
while (pos < json.size() && (json[pos] == ' ' || json[pos] == ',')) pos++;
}
return true;
}
static bool parseJsonBool(const std::string& json, const char* key, bool& out) {
std::string needle = std::string("\"") + key + "\"";
size_t pos = json.find(needle);
if (pos == std::string::npos) return false;
pos = json.find(':', pos);
if (pos == std::string::npos) return false;
pos++;
// Skip whitespace
while (pos < json.size() && json[pos] == ' ') pos++;
if (json.compare(pos, 4, "true") == 0) {
out = true;
return true;
} else if (json.compare(pos, 5, "false") == 0) {
out = false;
return true;
}
return false;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
bool SimSatClient::fetchPosition(double& lat, double& lon, double& alt) {
CURL* curl = curl_easy_init();
if (!curl) return false;
std::string url = std::string(getBaseUrl()) + "/data/current/position";
std::string responseBody;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBody);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
CURLcode res = curl_easy_perform(curl);
long httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_easy_cleanup(curl);
if (res != CURLE_OK || httpCode != 200) {
return false;
}
// Parse: {"lon-lat-alt": [lon, lat, alt], "timestamp": "..."}
double coords[3];
if (!parseJsonNumberArray(responseBody, "lon-lat-alt", coords, 3)) {
return false;
}
// SimSat returns [longitude, latitude, altitude]
lon = coords[0];
lat = coords[1];
alt = coords[2];
return true;
}
bool SimSatClient::fetchMapboxImage(uint8_t* rgbOut, uint32_t outWidth, uint32_t outHeight) {
CURL* curl = curl_easy_init();
if (!curl) return false;
std::string url = std::string(getBaseUrl()) + "/data/current/image/mapbox";
std::string responseBody;
std::string metadataHeader;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBody);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerCallback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &metadataHeader);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
CURLcode res = curl_easy_perform(curl);
long httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_easy_cleanup(curl);
if (res != CURLE_OK || httpCode != 200) {
return false;
}
// Check metadata header for image availability
if (!metadataHeader.empty()) {
bool imageAvailable = false;
bool targetVisible = false;
parseJsonBool(metadataHeader, "image_available", imageAvailable);
parseJsonBool(metadataHeader, "target_visible", targetVisible);
if (!imageAvailable || !targetVisible) {
return false;
}
}
// Decode PNG from response body
int imgW = 0, imgH = 0, imgChannels = 0;
unsigned char* decoded = stbi_load_from_memory(reinterpret_cast<const unsigned char*>(responseBody.data()),
static_cast<int>(responseBody.size()), &imgW, &imgH, &imgChannels,
3 // force RGB output
);
if (!decoded) {
return false;
}
// Resize to target dimensions if needed
if (static_cast<uint32_t>(imgW) == outWidth && static_cast<uint32_t>(imgH) == outHeight) {
// No resize needed — direct copy
memcpy(rgbOut, decoded, outWidth * outHeight * 3);
} else {
// Resize using stb_image_resize2
stbir_resize_uint8_linear(decoded, imgW, imgH, 0, rgbOut, static_cast<int>(outWidth),
static_cast<int>(outHeight), 0, STBIR_RGB);
}
stbi_image_free(decoded);
return true;
}
} // namespace Orion