satdump/src-cli/autotrack/autotrack_web.cpp
2025-06-11 17:38:30 +02:00

197 lines
10 KiB
C++

#include "../webserver.h"
#include "autotrack.h"
#include "image/jpeg_utils.h"
#include "logger.h"
#include "utils/format.h"
#include "utils/time.h"
void AutoTrackApp::setup_webserver()
{
// If requested, boot up webserver
if (d_settings.contains("http_server"))
{
std::string http_addr = d_settings["http_server"].get<std::string>();
webserver::handle_callback = [this]()
{
nlohmann::json p;
live_pipeline_mtx.lock();
if (live_pipeline)
{
p["live_pipeline"] = live_pipeline->getModulesStats();
}
live_pipeline_mtx.unlock();
p["object_tracker"] = object_tracker.getStatus();
p["frequency"] = source_ptr->get_frequency();
vfos_mtx.lock();
if (vfo_list.size() > 0)
{
for (auto &vfo : vfo_list)
{
auto &pos = p["vfos"][vfo.id];
pos["frequency"] = vfo.freq;
if (vfo.selected_pipeline.id != "")
{
pos["live_pipeline"] = vfo.live_pipeline->getModulesStats();
}
if (vfo.file_sink)
{
pos["recording"]["written_size"] = vfo.file_sink->get_written();
pos["recording"]["written_raw_size"] = vfo.file_sink->get_written_raw();
}
}
}
vfos_mtx.unlock();
if (is_recording)
{
p["recording"]["written_size"] = file_sink->get_written();
p["recording"]["written_raw_size"] = file_sink->get_written_raw();
}
return p.dump(4);
};
webserver::add_polarplot_handler = true;
webserver::handle_callback_polarplot = [this]() -> std::vector<uint8_t>
{
auto img = object_tracker.getPolarPlotImg();
std::vector<uint8_t> vec = image::save_jpeg_mem(img);
return vec;
};
if (d_parameters.contains("fft_enable") && d_parameters["fft_enable"])
webserver::handle_callback_fft = [this]() -> std::vector<uint8_t>
{
if (!web_fft_is_enabled)
{
splitter->set_enabled("fft", true);
web_fft_is_enabled = true;
logger->trace("Enabling FFT");
}
web_last_fft_access = time(nullptr);
auto img = fft_plot->drawImg(512, 512);
std::vector<uint8_t> vec = image::save_jpeg_mem(img);
return vec;
};
webserver::handle_callback_schedule = [this]() -> std::vector<uint8_t>
{
auto img = auto_scheduler.getScheduleImage(512, satdump::getTime());
std::vector<uint8_t> vec = image::save_jpeg_mem(img);
return vec;
};
webserver::handle_callback_html = [this](std::string uri) -> std::string
{
if (uri == "/status")
{
live_pipeline_mtx.lock();
auto status = object_tracker.getStatus();
std::string rot_engaged;
std::string rot_tracking;
std::string aos_in;
std::string los_in;
std::string fft;
time_t cache_buster = time(nullptr);
if (status["rotator_engaged"].get<bool>() == true)
rot_engaged = "<span class=\"fakeinput true\">engaged</span>";
else
rot_engaged = "<span class=\"fakeinput false\">not engaged</span>";
if (status["rotator_tracking"].get<bool>() == true)
rot_tracking = "<span class=\"fakeinput true\">tracking</span>";
else
rot_tracking = "<span class=\"fakeinput false\">not tracking</span>";
if (status["next_event_is_aos"].get<bool>() == true)
{
aos_in = " (in <span class=\"fakeinput\">" + std::to_string((int)(status["next_event_in"].get<double>())) + "</span> seconds)";
los_in = "";
}
else
{
los_in = " (in <span class=\"fakeinput\">" + std::to_string((int)(status["next_event_in"].get<double>())) + "</span> seconds)";
aos_in = "";
}
if (d_parameters.contains("fft_enable") && d_parameters["fft_enable"])
fft = (std::string) "<h2>FFT</h2><img src=\"fft.jpeg?r=" + std::to_string(cache_buster) + "\" class=\"resp-img\" height=\"600\" width=\"600\" />";
std::string schedule = (std::string) "<h2>Schedule</h2><img src=\"schedule.jpeg?r=" + std::to_string(cache_buster) + "\" class=\"resp-img\" height=\"80\" width=\"600\" />";
std::string page =
(std::string) "<h2>Device</h2><p>Hardware: <span class=\"fakeinput\">" + selected_src.name + "</span></p>" + "<p>Started: <span class=\"fakeinput\">" +
std::string(is_started ? "YES" : "NO") + "</span></p>" + "<p>Sample rate: <span class=\"fakeinput\">" + std::to_string(source_ptr->get_samplerate() / 1e6) + "</span> Msps</p>" +
"<p>Frequency: <span class=\"fakeinput\">" + std::to_string(source_ptr->get_frequency() / 1e6) + "</span> MHz</p>" + "<h2>Object Tracker</h2>" +
"<div class=\"image-div\"><img src=\"polarplot.jpeg?r=" + std::to_string(cache_buster) + "\" width=256 height=256/></div>" + "<p>Next AOS time: <span class=\"fakeinput\">" +
satdump::timestamp_to_string(status["next_aos_time"].get<double>(), auto_scheduler.getAutoTrackCfg().use_localtime) + "</span>" + aos_in + "</p>" +
"<p>Next LOS time: <span class=\"fakeinput\">" + satdump::timestamp_to_string(status["next_los_time"].get<double>(), auto_scheduler.getAutoTrackCfg().use_localtime) + "</span>" +
los_in + "</p>" + "<p>Current object: <span class=\"fakeinput\">" + status["object_name"].get<std::string>() + "</span></p>" + "<p>Current position:<br />" +
"Azimuth <span class=\"fakeinput\">" + satdump::svformat("%.2f", (status["sat_current_pos"]["az"].get<double>())) + "</span> °<br />Elevation <span class=\"fakeinput\">" +
satdump::svformat("%.2f", (status["sat_current_pos"]["el"].get<double>())) + "</span> °<br /> Range <span class=\"fakeinput\">" +
satdump::svformat("%.2f", (status["sat_current_range"].get<double>())) + "</span> km</p>" + "<h2>Rotator Control</h2>" + "<p>Status:" + rot_engaged +
", <span class=\"fakeinput false\">" + rot_tracking + "</span></p>" + "<p>Azimuth: requested <span class=\"fakeinput\">" +
satdump::svformat("%.2f", (status["rot_current_req_pos"]["az"].get<double>())) + "</span> °, actual <span class=\"fakeinput\">" +
satdump::svformat("%.2f", (status["rot_current_pos"]["az"].get<double>())) + "</span> °</p>" + "<p>Elevation: requested <span class=\"fakeinput\">" +
satdump::svformat("%.2f", (status["rot_current_req_pos"]["el"].get<double>())) + "</span> °, actual <span class=\"fakeinput\">" +
satdump::svformat("%.2f", (status["rot_current_pos"]["el"].get<double>())) + "</span> °</p>" + schedule + fft;
live_pipeline_mtx.unlock();
return page;
}
else if (uri == "/")
{
std::string page = (std::string) "<!DOCTYPE html><html lang=\"EN\"><head>" + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" +
"<meta charset=\"utf-8\"><title>SatDump Status Page</title>" + "<script type=\"text/javascript\">" + "function xhr() {\n" + "var http;\n" +
"if (window.XMLHttpRequest) {\n" + "http = new XMLHttpRequest();\n" + "} else {\n" + "http = new ActiveXObject(\"Microsoft.XMLHTTP\");\n" + "}" +
"var url = \"/status\";\n" + "http.open(\"GET\", url, true);\n" + "http.onreadystatechange = function() { \n" +
"if (http.readyState == 4 && http.status == 200) {\n" + "document.getElementById('main-content').innerHTML = http.responseText;\n" + "}\n" + "}\n" +
"http.setRequestHeader(\"If-Modified-Since\", \"Sat, 1 Jan 2000 00:00:00 GMT\");\n" + "http.send(null);\n" + "}\n" + "window.onload = function() {\n" +
"xhr();\n"
"setInterval(\"xhr()\", 1000)\n" +
"}\n" + "</script>" +
"<!--[if lt IE 7 ]><style>body{width:600px;}</style><![endif]-->"
"<style>body{background-color:#111;font-family:sans-serif;color:#ddd;" +
"max-width:600px;margin-left:auto;margin-right:auto}h1{text-align:center}" + "h2{padding:5px;border-radius:5px;background-color:#3e3e43}" +
".fakeinput{padding:2px;border-radius:1px;background-color:#232526}" + ".resp-img{max-width:100%;height:auto}img{vertical-align:middle}" +
".true{color:#0f0}.false{color:red}.image-div{background-color:black;width:256px;height:256px;}</style></head>" +
"<div id=\"main-content\"><h2>Loading...</h2><p>If you see this, your browser does not support JavaScript. <a href=\"/status\">Click here</a> to view the status "
"(you will need to refresh it manually) :)</p></div>" +
"</body></html>";
return page;
}
else
{
return "Error 404";
}
};
logger->info("Start webserver on %s", http_addr.c_str());
webserver::start(http_addr);
}
}
void AutoTrackApp::stop_webserver()
{
if (d_settings.contains("http_server"))
webserver::stop();
}
void AutoTrackApp::web_heartbeat()
{
if (web_fft_is_enabled)
{
if (time(0) - web_last_fft_access > 10)
{
splitter->set_enabled("fft", false);
web_fft_is_enabled = false;
logger->trace("Shutting down FFT");
}
}
}