You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1223 lines
42 KiB

// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2020 Intel Corporation. All Rights Reserved.
#include <glad/glad.h>
#include "output-model.h"
#include <rs-config.h>
#include "ux-window.h"
#include "device-model.h"
#include "os.h"
#include <imgui_internal.h>
#include <librealsense2/hpp/rs_internal.hpp>
#include <fstream>
#include <iterator>
using namespace rs2;
using namespace rsutils::string;
void output_model::thread_loop()
{
while (!to_stop)
{
std::vector<rs2::device> dev_copy;
{
std::lock_guard<std::mutex> lock(devices_mutex);
dev_copy = devices;
}
if (enable_firmware_logs)
for (auto&& dev : dev_copy)
{
try
{
if (auto fwlogger = dev.as<rs2::firmware_logger>())
{
bool has_parser = false;
std::string hwlogger_xml = config_file::instance().get(configurations::viewer::hwlogger_xml);
std::ifstream f(hwlogger_xml.c_str());
if (f.good())
{
try
{
std::string str((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
fwlogger.init_parser(str);
has_parser = true;
}
catch (const std::exception& ex)
{
add_log( RS2_LOG_SEVERITY_WARN,
__FILE__,
__LINE__,
rsutils::string::from()
<< "Invalid Hardware Logger XML at '" << hwlogger_xml << "': " << ex.what()
<< "\nEither configure valid XML or remove it" );
}
}
auto message = fwlogger.create_message();
while (fwlogger.get_firmware_log(message))
{
auto parsed = fwlogger.create_parsed_message();
auto parsed_ok = false;
if (has_parser)
{
if (fwlogger.parse_log(message, parsed))
{
parsed_ok = true;
add_log( message.get_severity(),
parsed.file_name(),
parsed.line(),
rsutils::string::from()
<< "FW-LOG [" << parsed.thread_name() << "] " << parsed.message() );
}
}
if (!parsed_ok)
{
std::stringstream ss;
for (auto& elem : message.data())
ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(elem) << " ";
add_log(message.get_severity(), __FILE__, 0, ss.str());
}
if (!enable_firmware_logs && fwlogger.get_number_of_fw_logs() == 0)
break;
}
}
}
catch(const std::exception& ex)
{
add_log( RS2_LOG_SEVERITY_WARN,
__FILE__,
__LINE__,
rsutils::string::from() << "Failed to fetch firmware logs: " << ex.what() );
}
}
// FW define the logs polling intervals to be no less than 100msec to cope with limited resources.
// At the same time 100 msec should guarantee no log drops
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
output_model::~output_model()
{
to_stop = 1;
fw_logger.join();
}
output_model::output_model() : fw_logger([this](){ thread_loop(); }) , incoming_log_queue(100)
{
is_output_open = config_file::instance().get_or_default(
configurations::viewer::output_open, false);
search_line = config_file::instance().get_or_default(
configurations::viewer::search_term, std::string(""));
is_dashboard_open = config_file::instance().get_or_default(
configurations::viewer::dashboard_open, true );
if (search_line != "") search_open = true;
available_dashboards["Frame Drops per Second"] = [&](std::string name){
return std::make_shared<frame_drops_dashboard>(name, &number_of_drops, &total_frames);
};
auto front = available_dashboards.begin();
dashboards.push_back(front->second(front->first));
}
bool output_model::round_indicator(ux_window& win, std::string icon,
int count, ImVec4 color, std::string tooltip, bool& highlighted, std::string suffix)
{
std::stringstream ss;
ss << icon;
if (count > 0) ss << " " << count << suffix;
auto size = ImGui::CalcTextSize(ss.str().c_str());
if (count == 0 || (!is_output_open && !highlighted)) {
color = dark_sensor_bg;
ImGui::PushStyleColor(ImGuiCol_Text, header_color);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, header_color);
}
else
{
if (!highlighted)
{
color = saturate(color, 0.3f);
ImGui::PushStyleColor(ImGuiCol_Text, white);
}
else
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white);
}
auto pos = ImGui::GetCursorScreenPos();
ImGui::GetWindowDrawList()->AddRectFilled({ pos.x, pos.y + 3 },
{ pos.x + size.x + 15, pos.y + 27 }, ImColor(color), 12, 15);
auto res = ImGui::Button(ss.str().c_str(), ImVec2(size.x + 15, 28));
if (count > 0 && ImGui::IsItemHovered())
{
highlighted = true;
win.link_hovered();
ImGui::SetTooltip("%s", tooltip.c_str());
}
else highlighted = false;
ImGui::PopStyleColor(2);
return res;
}
void output_model::open(ux_window& win)
{
is_output_open = true;
config_file::instance().set(configurations::viewer::output_open, true);
default_log_h = static_cast<int>((win.height() - 100) / 2);
new_log = true;
}
void output_model::draw(ux_window& win, rect view_rect, device_models_list & device_models)
{
ImGui::PushStyleColor(ImGuiCol_FrameBg, scrollbar_bg);
auto x = view_rect.x;
auto y = view_rect.y;
auto w = view_rect.w;
auto h = view_rect.h;
auto flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings;
ImGui::PushStyleColor(ImGuiCol_WindowBg, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_Button, transparent);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, transparent);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, transparent);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white);
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::PushFont(win.get_font());
ImGui::SetNextWindowPos({ x, y });
ImGui::SetNextWindowSize({ w, h });
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(3, 3));
ImGui::Begin("Output", nullptr, flags);
ImGui::SetCursorPosX(w - 31);
if (!is_output_open)
{
if (ImGui::Button(u8"\uF139", ImVec2(28, 28)))
{
open(win);
}
if (ImGui::IsItemHovered())
{
win.link_hovered();
ImGui::SetTooltip("%s", "Open Debug Console Window");
}
if (default_log_h.value() != 36)
default_log_h = 36;
}
else
{
if (ImGui::Button(u8"\uF13A", ImVec2(28, 28)))
{
is_output_open = false;
config_file::instance().set(configurations::viewer::output_open, false);
default_log_h = 36;
search_open = false;
}
if (ImGui::IsItemHovered())
{
win.link_hovered();
ImGui::SetTooltip("%s", "Collapse Debug Console Window");
}
int h_val = (int)((win.height() - 100) / 2);
if (default_log_h.value() != h_val)
default_log_h = h_val;
}
ImGui::SameLine();
ImGui::SetCursorPosX(5);
if (errors_selected) errors_highlighted = true;
if (round_indicator(win, u8"\uF057", number_of_errors, redish, "Instances of logged errors", errors_highlighted))
{
errors_selected = !errors_selected;
open(win);
}
ImGui::SameLine();
if (warnings_selected) warnings_highlighted = true;
if (round_indicator(win, u8"\uF071", number_of_warnings, orange, "Instances of logged warnings", warnings_highlighted))
{
warnings_selected = !warnings_selected;
open(win);
}
ImGui::SameLine();
if (info_selected) info_highlighted = true;
if (round_indicator(win, u8"\uF05A", number_of_info, greenish, "Instances of logged info messages", info_highlighted))
{
info_selected = !info_selected;
open(win);
}
ImGui::SameLine();
if (!is_output_open || search_open)
{
ImGui::PushStyleColor(ImGuiCol_Text, header_color);
}
else
{
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
}
bool focus_search = false;
if (ImGui::Button(u8"\uF002", ImVec2(28, 28)))
{
focus_search = true;
search_open = true;
open(win);
}
if (ImGui::IsItemHovered())
{
win.link_hovered();
ImGui::SetTooltip("%s", "Search through logs");
}
ImGui::PopStyleColor(1);
ImGui::SameLine();
auto curr_x = ImGui::GetCursorPosX();
ImGui::SetCursorPosX(curr_x - 5);
int percent = total_frames ? (int)(100 * ((double)number_of_drops / (total_frames))) : 0;
std::stringstream ss;
ss << u8"\uF043";
if (percent) ss << " " << percent << "%";
auto size = ImGui::CalcTextSize(ss.str().c_str());
char buff[1024];
memcpy(buff, search_line.c_str(), search_line.size());
buff[search_line.size()] = 0;
auto actual_search_width = w - size.x - 100 - curr_x;
if (focus_search) search_width = (int)(actual_search_width);
if (search_open && search_width.value() != actual_search_width)
search_width = (int)(actual_search_width);
// if (is_output_open && search_width < 1)
// {
// search_open = true;
// }
if (search_open)
{
ImGui::PushFont(win.get_monofont());
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 4);
ImGui::PushItemWidth(static_cast<float>(search_width));
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, regular_blue);
if (ImGui::InputText("##SearchInLogs",buff, 1023))
{
search_line = buff;
config_file::instance().set(configurations::viewer::search_term, search_line);
}
if (focus_search) ImGui::SetKeyboardFocusHere();
ImGui::PopItemWidth();
ImGui::SameLine();
ImGui::PopStyleColor();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 4);
ImGui::PopFont();
}
ImGui::SetCursorPosX(w - size.x - 3 * 30);
if (enable_firmware_logs)
{
ImGui::PushStyleColor(ImGuiCol_Text, light_blue);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_blue);
}
else
{
if (is_output_open)
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
else
ImGui::PushStyleColor(ImGuiCol_Text, header_color);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white);
}
if (ImGui::Button(u8"\uF2DB", ImVec2(28, 28)))
{
enable_firmware_logs = !enable_firmware_logs;
}
ImGui::PopStyleColor(2);
if (ImGui::IsItemHovered())
{
win.link_hovered();
if (enable_firmware_logs) ImGui::SetTooltip("%s", "Disable Firmware Logs");
else ImGui::SetTooltip("%s", "Enable Firmware Logs");
}
ImGui::SameLine();
if (round_indicator(win, u8"\uF043", percent, regular_blue, "Frame drops", drops_highlighted, "%"))
{
open(win);
}
if (is_output_open)
{
ImGui::SetCursorPos(ImVec2(3, 35));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0,0));
ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, dark_sensor_bg);
const float log_area_width = w - get_dashboard_width() - 2;
ImGui::BeginChild("##LogArea",
ImVec2(log_area_width, h - 38 - ImGui::GetTextLineHeightWithSpacing() - 1), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
bool copy_all = false;
bool save_all = false;
std::vector<std::string> output_strings;
auto time_now = glfwGetTime();
int i = 0;
foreach_log([&](log_entry& log)
{
auto line = log.line;
if (log.line_number)
{
line = log.filename.substr(log.filename.find_last_of("/\\") + 1) + ":";
line += std::string( rsutils::string::from() << log.line_number) + " - ";
line += log.line;
}
bool ok = false;
if (info_selected || warnings_selected || errors_selected)
{
if (info_selected && log.severity <= RS2_LOG_SEVERITY_INFO) ok = true;
if (warnings_selected && log.severity == RS2_LOG_SEVERITY_WARN) ok = true;
if (errors_selected && log.severity >= RS2_LOG_SEVERITY_ERROR) ok = true;
}
else ok = true;
if (search_line != "" && to_lower(line).find(to_lower(search_line)) == std::string::npos) ok = false;
if (!ok) return;
std::stringstream ss; ss << log.timestamp << " [" << rs2_log_severity_to_string(log.severity) << "] ";
if (log.line_number) ss << log.filename << ":" << log.line_number;
ss << " - " << log.line;
std::string full = ss.str();
ImGui::PushFont(win.get_monofont());
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, regular_blue);
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::PushStyleColor(ImGuiCol_FrameBg, transparent);
ImVec4 color = redish;
if (log.severity >= RS2_LOG_SEVERITY_ERROR)
{
color = redish;
}
else if (log.severity >= RS2_LOG_SEVERITY_WARN)
{
color = orange;
}
else
{
color = greenish;
}
auto margin = ImGui::GetTextLineHeightWithSpacing() - ImGui::GetTextLineHeight();
auto size = ImGui::CalcTextSize(line.c_str());
auto t = single_wave(static_cast<float>(time_now - log.time_added + 0.3f)) * 0.2f;
if (log.selected) t = 0.2f;
auto pos = ImGui::GetCursorScreenPos();
ImGui::GetWindowDrawList()->AddRectFilled({ pos.x, pos.y },
{ pos.x + log_area_width, pos.y + size.y + 2 * margin },
ImColor(alpha(saturate(color, 0.3f + t), 0.7f + t)));
ImGui::GetWindowDrawList()->AddLine({ pos.x, pos.y + size.y + 2 * margin },
{ pos.x + log_area_width, pos.y + size.y + 2 * margin }, ImColor(alpha(color, 0.5f)));
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 4);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 4);
ImGui::Text("%s", log.timestamp.c_str()); ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 4);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 4);
std::string label = rsutils::string::from() << "##log_entry" << i++;
ImGui::InputTextEx(label.c_str(),
(char*)line.data(),
static_cast<int>(line.size() + 1),
ImVec2(-1, size.y + margin),
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_ReadOnly);
ImGui::PushStyleColor(ImGuiCol_PopupBg, almost_white_bg);
ImGui::PushStyleColor(ImGuiCol_Text, black);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, light_blue);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5,5));
label = rsutils::string::from() << "##log_entry" << i << "_context_menu";
if (ImGui::BeginPopupContextItem(label.c_str()))
{
log.selected = true;
ImGui::PushFont(win.get_font());
if (ImGui::Selectable("Copy Line")) {
glfwSetClipboardString(win, full.c_str());
}
if (ImGui::Selectable("Copy All")) {
copy_all = true;
}
if (ImGui::Selectable("Save As...")) {
save_all = true;
}
ImGui::PopFont();
ImGui::EndPopup();
}
else log.selected = false;
ImGui::PopStyleVar();
ImGui::PopStyleColor(4);
ImGui::PopStyleColor(3);
ImGui::PopFont();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 1);
output_strings.push_back(full);
});
std::stringstream ss;
for (auto&& s : output_strings) ss << s << "\n";
if (copy_all)
glfwSetClipboardString(win, ss.str().c_str());
if (save_all)
{
if (auto fn = file_dialog_open(file_dialog_mode::save_file, "Log File\0*.log\0", nullptr, nullptr))
{
std::ofstream out(fn);
if (out.good())
{
out << ss.str();
}
out.close();
}
}
ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::SetCursorPos(ImVec2(7, h - ImGui::GetTextLineHeightWithSpacing() - 2));
ImGui::Text("%s", u8"\uF120"); ImGui::SameLine();
ImGui::SetCursorPos(ImVec2(30, h - ImGui::GetTextLineHeightWithSpacing() - 4));
ImGui::PushFont(win.get_monofont());
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, regular_blue);
ImGui::PushItemWidth( w - get_dashboard_width() - 30 );
bool force_refresh = false;
if (ImGui::IsWindowFocused() && (ImGui::IsKeyPressed(GLFW_KEY_UP) || ImGui::IsKeyPressed(GLFW_KEY_DOWN)))
{
if (commands_histroy.size())
{
if (ImGui::IsKeyPressed(GLFW_KEY_UP)) history_offset = (history_offset + 1) % commands_histroy.size();
if (ImGui::IsKeyPressed(GLFW_KEY_DOWN)) history_offset = (history_offset - 1 + (int)commands_histroy.size()) % commands_histroy.size();
command_line = commands_histroy[history_offset];
force_refresh = true;
}
}
if (ImGui::IsWindowFocused() && ImGui::IsKeyPressed(GLFW_KEY_TAB))
{
if (!autocomplete.size() || !starts_with(to_lower(autocomplete.front()), to_lower(command_line)))
{
std::string commands_xml = config_file::instance().get(configurations::viewer::commands_xml);
std::ifstream f(commands_xml.c_str());
if (f.good())
{
std::string str((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
autocomplete.clear();
std::regex exp("Command Name=\"(\\w+)\"");
std::smatch res;
std::string::const_iterator searchStart(str.cbegin());
while (regex_search(searchStart, str.cend(), res, exp))
{
if (starts_with(to_lower(res[1]), to_lower(command_line)))
autocomplete.push_back(res[1]);
searchStart = res.suffix().first;
}
}
}
if (autocomplete.size())
{
auto temp = autocomplete.front();
autocomplete.pop_front();
autocomplete.push_back(temp);
if (starts_with(to_lower(temp), command_line))
command_line = to_lower(autocomplete.front());
else
command_line = autocomplete.front();
force_refresh = true;
}
}
memcpy(buff, command_line.c_str(), command_line.size());
buff[command_line.size()] = 0;
int flags = ImGuiInputTextFlags_EnterReturnsTrue;
if (force_refresh)
{
flags = ImGuiInputTextFlags_ReadOnly;
}
ImGui::PushStyleColor(ImGuiCol_FrameBg, scrollbar_bg);
if (ImGui::InputText("##TerminalCommand", buff, 1023, flags))
{
}
if (!command_focus && !new_log) command_line = buff;
ImGui::PopStyleColor();
if (command_focus || new_log) ImGui::SetKeyboardFocusHere();
ImGui::PopFont();
ImGui::PopStyleColor();
if (ImGui::IsWindowFocused() && (ImGui::IsKeyPressed(GLFW_KEY_ENTER) || ImGui::IsKeyPressed(GLFW_KEY_KP_ENTER)))
{
if (commands_histroy.size() > 100) commands_histroy.pop_back();
commands_histroy.push_front(command_line);
run_command(command_line, device_models);
command_line = "";
command_focus = true;
}
else command_focus = false;
if (ImGui::IsWindowFocused() && ImGui::IsKeyPressed(GLFW_KEY_ESCAPE))
{
command_line = "";
}
float child_height = 0;
for( auto && dash : dashboards )
{
child_height += dash->get_height();
}
float new_dashboard_button_height = 40.f;
child_height == 0 ? child_height = new_dashboard_button_height : child_height += 5;
auto dashboard_width = get_dashboard_width();
ImGui::SetCursorPos( ImVec2( w - dashboard_width, 35 ) );
ImGui::BeginChild( "##StatsArea", ImVec2( dashboard_width - 3.f, h - 38 ), true );
const ImVec2 collapse_dashboard_button_size = { 28, 28 };
const int max_dashboard_width = (int)( ( 0.3f * w ) );
const int min_dashboard_width = static_cast<int>(collapse_dashboard_button_size.x) + 2;
if( is_dashboard_open )
{
if( ImGui::Button( u8"\uf138", collapse_dashboard_button_size ) ) // close dashboard
{
is_dashboard_open = false;
config_file::instance().set( configurations::viewer::dashboard_open, is_dashboard_open );
default_dashboard_w = min_dashboard_width;
}
if( ImGui::IsItemHovered() )
{
ImGui::SetTooltip( "Collapse dashboard" );
}
// Animation of opening dashboard panel
if( default_dashboard_w.value() != max_dashboard_width )
default_dashboard_w = max_dashboard_width;
}
else
{
float cursor_pos_x = ImGui::GetCursorPosX();
ImGui::SetCursorPosX( 0 );
if( ImGui::Button( u8"\uf137", collapse_dashboard_button_size ) ) // open dashboard
{
is_dashboard_open = true;
config_file::instance().set( configurations::viewer::dashboard_open, is_dashboard_open );
default_dashboard_w = max_dashboard_width;
}
if( ImGui::IsItemHovered() )
{
ImGui::SetTooltip( "Open dashboard" );
}
ImGui::SetCursorPosX( cursor_pos_x );
// Animation of closing dashboard panel
if( default_dashboard_w.value() != min_dashboard_width )
default_dashboard_w = min_dashboard_width;
}
auto top = 0;
if( is_dashboard_open && dashboard_width == max_dashboard_width )
{
for( auto && dash : dashboards )
{
auto h = dash->get_height();
auto r = rect{ 0.f, (float)top, get_dashboard_width() - 8.f, (float)h };
dash->draw( win, r );
top += h;
}
}
dashboards.erase(std::remove_if(dashboards.begin(), dashboards.end(),
[](std::shared_ptr<stream_dashboard> p){
return p->closing();
}), dashboards.end());
bool can_add = false;
for (auto&& kvp : available_dashboards)
{
auto name = kvp.first;
auto it = std::find_if(dashboards.begin(), dashboards.end(),
[name](std::shared_ptr<stream_dashboard> p){
return p->get_name() == name;
});
if (it == dashboards.end()) can_add = true;
}
if (can_add)
{
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5);
const auto new_dashboard_name = "new_dashboard";
ImGui::SameLine();
if (ImGui::Button(u8"\uF0D0 Add Dashboard", ImVec2(-1, 25)))
{
ImGui::OpenPopup(new_dashboard_name);
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Add one of the available stream dashboards to view");
win.link_hovered();
}
ImGui::PushStyleColor(ImGuiCol_PopupBg, almost_white_bg);
ImGui::PushStyleColor(ImGuiCol_Text, black);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, light_blue);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5,5));
if (ImGui::BeginPopup(new_dashboard_name))
{
for (auto&& kvp : available_dashboards)
{
auto name = kvp.first;
auto it = std::find_if(dashboards.begin(), dashboards.end(),
[name](std::shared_ptr<stream_dashboard> p){
return p->get_name() == name;
});
if (it == dashboards.end())
{
name = name + "##New";
bool selected = false;
if (ImGui::Selectable(name.c_str(), &selected))
{
dashboards.push_back(kvp.second(kvp.first));
}
}
}
ImGui::EndPopup();
}
ImGui::PopStyleColor(4);
ImGui::PopStyleVar();
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
else foreach_log([&](log_entry& log) {});
ImGui::End();
ImGui::PopStyleColor(7);
ImGui::PopStyleVar();
ImGui::PopFont();
{
std::lock_guard<std::mutex> lock(devices_mutex);
this->devices.clear();
for (auto && dev_model : device_models)
this->devices.push_back(dev_model->dev);
}
}
void output_model::foreach_log(std::function<void(log_entry& line)> action)
{
std::lock_guard<std::recursive_mutex> lock(m);
// Process only the messages that are available upon invocation
log_entry le;
for (size_t len = 0; len < incoming_log_queue.size(); len++)
{
if (incoming_log_queue.try_dequeue(&le))
{
if (le.severity >= RS2_LOG_SEVERITY_ERROR) number_of_errors++;
else if (le.severity >= RS2_LOG_SEVERITY_WARN) number_of_warnings++;
else number_of_info++;
notification_logs.push_back(le);
}
}
// Limit the notification window
while (notification_logs.size() > 1000)
{
auto&& le = notification_logs.front();
if (le.severity >= RS2_LOG_SEVERITY_ERROR) number_of_errors--;
else if (le.severity >= RS2_LOG_SEVERITY_WARN) number_of_warnings--;
else number_of_info--;
notification_logs.pop_front();
}
for (auto&& l : notification_logs)
action(l);
if (new_log)
{
ImGui::SetScrollPosHere();
new_log = false;
}
}
// Callback function must not include mutex
void output_model::add_log(rs2_log_severity severity, std::string filename, int line_number, std::string line)
{
if (!line.size()) return;
time_t rawtime;
struct tm * timeinfo;
char buffer[80];
time (&rawtime);
timeinfo = localtime(&rawtime);
strftime(buffer,sizeof(buffer),"%H:%M:%S",timeinfo);
log_entry e;
e.line = line;
e.line_number = line_number;
e.filename = filename;
e.severity = severity;
e.timestamp = buffer;
e.time_added = glfwGetTime();
incoming_log_queue.enqueue(std::move(e));
new_log = true;
}
void output_model::run_command(std::string command, device_models_list & device_models)
{
try
{
if (to_lower(command) == "clear")
{
while (notification_logs.size() > 0)
{
auto&& le = notification_logs.front();
if (le.severity >= RS2_LOG_SEVERITY_ERROR) number_of_errors--;
else if (le.severity >= RS2_LOG_SEVERITY_WARN) number_of_warnings--;
else number_of_info--;
notification_logs.pop_front();
for (auto& d : dashboards)
d->clear(true);
}
return;
}
if( user_defined_command( command, device_models ) )
return;
std::regex e( "([0-9A-Fa-f]{2}\\s)+" );
if (std::regex_match(command, e))
{
add_log(RS2_LOG_SEVERITY_INFO, __FILE__, 0, rsutils::string::from() << "Trying to send " << command << "...");
std::vector<uint8_t> raw_data;
std::stringstream ss(command);
std::string word;
while (ss >> word)
{
std::stringstream converter;
int temp;
converter << std::hex << word;
converter >> temp;
raw_data.push_back(temp);
}
if (raw_data.empty())
throw std::runtime_error("Invalid input!");
bool found = false;
for (auto&& dev : devices)
{
if (auto dbg = dev.as<rs2::debug_protocol>())
{
found = true;
auto res = dbg.send_and_receive_raw_data(raw_data);
std::stringstream ss;
int i = 0;
for (auto& elem : res)
{
ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(elem) << " ";
i++;
if (i > 80)
{
ss << "\n";
i = 0;
}
}
add_log(RS2_LOG_SEVERITY_INFO, __FILE__, 0, ss.str());
return;
}
}
if (!found)
{
add_log(RS2_LOG_SEVERITY_WARN, __FILE__, __LINE__, "No device is available to receive the command");
return;
}
}
std::string commands_xml = config_file::instance().get(configurations::viewer::commands_xml);
std::ifstream f(commands_xml.c_str());
if (f.good())
{
std::string str((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
auto terminal_parser = rs2::terminal_parser(str);
auto buffer = terminal_parser.parse_command(to_lower(command));
std::stringstream ss; ss << command << " = ";
for (auto& elem : buffer)
ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(elem) << " ";
add_log(RS2_LOG_SEVERITY_INFO, __FILE__, 0, ss.str());
bool found = false;
for (auto&& dev : devices)
{
if (auto dbg = dev.as<rs2::debug_protocol>())
{
found = true;
auto res = dbg.send_and_receive_raw_data(buffer);
std::string response = rsutils::string::from() << "\n" << terminal_parser.parse_response(to_lower(command), res);
add_log(RS2_LOG_SEVERITY_INFO, __FILE__, 0, response);
}
}
if (!found)
add_log(RS2_LOG_SEVERITY_WARN, __FILE__, __LINE__, "No device is available to receive the command");
return;
}
add_log(RS2_LOG_SEVERITY_WARN, __FILE__, __LINE__, rsutils::string::from() << "Unrecognized command '" << command << "'");
}
catch(const std::exception& ex)
{
add_log( RS2_LOG_SEVERITY_ERROR, __FILE__, __LINE__, ex.what() );
}
}
bool output_model::user_defined_command( std::string command, device_models_list & device_models )
{
bool user_defined_command_detected = false;
bool user_defined_command_activated = false;
// If a known command is detected , it will treated as a user_defined_command and will not be
// passed to the FW commands check logic.
// Note: For now we find the first device that supports the command and activate the command only on it.
if( to_lower( command ) == "get-nest" )
{
user_defined_command_detected = true;
for( auto && dev : devices )
{
if( auto dbg = dev.as< rs2::debug_protocol >() )
{
// Verify minimal version for handling this command
std::vector< uint8_t > special_command
= { 'G', 'E', 'T', '-', 'N', 'E', 'S', 'T' };
auto res = dbg.send_and_receive_raw_data( special_command );
user_defined_command_activated = true;
}
}
}
// Log a warning if a known command was not activated
if( user_defined_command_detected && ! user_defined_command_activated )
{
add_log( RS2_LOG_SEVERITY_WARN,
__FILE__,
__LINE__,
rsutils::string::from() << "None of the connected devices supports '" << command << "'" );
}
return user_defined_command_detected;
}
void output_model::update_dashboards(rs2::frame f)
{
for (auto&& d : dashboards)
d->add_frame(f);
}
void stream_dashboard::draw_dashboard(ux_window& win, rect& r)
{
auto min_x = 0.f;
auto max_x = 1.f;
auto min_y = 0.f;
auto max_y = 1.f;
if (xy.size())
{
min_x = xy[0].first;
max_x = xy[0].first;
min_y = xy[0].second;
max_y = xy[0].second;
for (auto&& p : xy)
{
min_x = std::min(min_x, p.first);
min_y = std::min(min_y, p.second);
max_x = std::max(max_x, p.first);
max_y = std::max(max_y, p.second);
}
}
auto gap_y = max_y - min_y;
auto gap_x = max_x - min_x;
auto height_y = r.h - 2 * ImGui::GetTextLineHeight() - 10;
auto ticks_y = ceil(height_y / ImGui::GetTextLineHeight());
auto max_y_label_width = 0.f;
for (int i = 0; i <= ticks_y; i++)
{
auto y = max_y - i * (gap_y / ticks_y);
std::string y_label = rsutils::string::from() << std::fixed << std::setprecision(2) << y;
auto size = ImGui::CalcTextSize(y_label.c_str());
max_y_label_width = std::max(max_y_label_width,
size.x);
}
auto pos = ImGui::GetCursorScreenPos();
ImGui::PushStyleColor(ImGuiCol_Text, white);
ImGui::GetWindowDrawList()->AddRectFilled({ pos.x, pos.y },
{ pos.x + r.w - 1, pos.y + get_height() - 1 }, ImColor(header_color));
ImGui::GetWindowDrawList()->AddRect({ pos.x, pos.y },
{ pos.x + r.w, pos.y + get_height() }, ImColor(dark_sensor_bg));
auto size = ImGui::CalcTextSize(name.c_str());
float collapse_buton_h = 28.f + 3.f; // Dashboard button size plus some spacing
ImGui::SetCursorPos(ImVec2( r.w / 2 - size.x / 2, 5 + collapse_buton_h));
ImGui::Text("%s", name.c_str());
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, grey);
ImGui::SetCursorPosX(r.w - 25);
ImGui::SetCursorPosY( 3.f + collapse_buton_h );
std::string id = rsutils::string::from() << u8"\uF00D##Close_" << name;
if (ImGui::Button(id.c_str(),ImVec2(22,22)))
{
close();
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Remove Dashboard from View");
win.link_hovered();
}
ImGui::PopStyleColor();
ImGui::GetWindowDrawList()->AddRectFilled({ pos.x + max_y_label_width + 15, pos.y + ImGui::GetTextLineHeight() + 5 },
{ pos.x + r.w - 10, pos.y + r.h - ImGui::GetTextLineHeight() - 5 }, ImColor(almost_white_bg));
//ImGui::PushFont(win.get_monofont());
for (int i = 0; i <= ticks_y; i++)
{
auto y = max_y - i * (gap_y / ticks_y);
std::string y_label = rsutils::string::from() << std::fixed << std::setprecision(2) << y;
auto y_pixel = ImGui::GetTextLineHeight() + i * (height_y / ticks_y);
ImGui::SetCursorPos(ImVec2( 10, y_pixel + collapse_buton_h));
ImGui::Text("%s", y_label.c_str());
ImGui::GetWindowDrawList()->AddLine({ pos.x + max_y_label_width + 15.f, pos.y + y_pixel + 5.f },
{ pos.x + r.w - 10.f, pos.y + y_pixel + 5.f }, ImColor(light_grey));
}
auto graph_width = r.w - max_y_label_width - 25;
int ticks_x = 2;
bool has_room = true;
while (has_room)
{
float total = 0;
for (int i = 0; i <= ticks_x; i++)
{
auto x = min_x + i * (gap_x / ticks_x);
std::string x_label = rsutils::string::from() << std::fixed << std::setprecision(2) << x;
auto size = ImGui::CalcTextSize(x_label.c_str());
total += size.x;
}
if (total < graph_width) ticks_x++;
else has_room = false;
}
ticks_x -= 3;
for (int i = 0; i < ticks_x; i++)
{
auto x = min_x + i * (gap_x / ticks_x);
std::string x_label = rsutils::string::from() << std::fixed << std::setprecision(2) << x;
ImGui::SetCursorPos(ImVec2( 15 + max_y_label_width+ i * (graph_width / ticks_x), r.h - ImGui::GetTextLineHeight() + collapse_buton_h));
ImGui::Text("%s", x_label.c_str());
ImGui::GetWindowDrawList()->AddLine({ pos.x + 15 + max_y_label_width + i * (graph_width / ticks_x), pos.y + ImGui::GetTextLineHeight() + 5 },
{ pos.x + max_y_label_width + 15 + i * (graph_width / ticks_x), pos.y + ImGui::GetTextLineHeight() + 5 + height_y }, ImColor(light_grey));
}
std::sort(xy.begin(), xy.end(), [](const std::pair<float, float>& a, const std::pair<float, float>& b) { return a.first < b.first; });
for (int i = 0; i + 1 < xy.size(); i++)
{
auto x0 = xy[i].first;
auto y0 = xy[i].second;
auto x1 = xy[i+1].first;
auto y1 = xy[i+1].second;
x0 = (x0 - min_x) / (max_x - min_x);
x1 = (x1 - min_x) / (max_x - min_x);
y0 = (y0 - min_y) / (max_y - min_y);
y1 = (y1 - min_y) / (max_y - min_y);
ImGui::GetWindowDrawList()->AddLine({ pos.x + 15 + max_y_label_width + x0 * graph_width, pos.y + ImGui::GetTextLineHeight() + 5 + height_y * (1.f - y0) },
{ pos.x + 15 + max_y_label_width + x1 * graph_width, pos.y + ImGui::GetTextLineHeight() + 5 + height_y * (1.f - y1) }, ImColor(black));
}
//ImGui::PopFont();
ImGui::PopStyleColor();
xy.clear();
}
void frame_drops_dashboard::process_frame(rs2::frame f)
{
write_shared_data([&](){
double ts = glfwGetTime();
if (method == 1) ts = f.get_timestamp() / 1000.f;
auto it = stream_to_time.find(f.get_profile().unique_id());
if (it != stream_to_time.end())
{
auto last = stream_to_time[f.get_profile().unique_id()];
double fps = (double)f.get_profile().fps();
if( f.supports_frame_metadata( RS2_FRAME_METADATA_ACTUAL_FPS ) )
fps = f.get_frame_metadata( RS2_FRAME_METADATA_ACTUAL_FPS ) / 1000.;
if (1000. * (ts - last) > 1.5 * (1000. / fps)) {
drops++;
}
}
counter++;
if (ts - last_time > 1.f)
{
if (drops_history.size() > 100) drops_history.pop_front();
drops_history.push_back(drops);
*total = counter;
*frame_drop_count = drops;
drops = 0;
last_time = ts;
counter = 0;
}
stream_to_time[f.get_profile().unique_id()] = ts;
});
}
void frame_drops_dashboard::draw(ux_window& win, rect r)
{
auto hist = read_shared_data<std::deque<int>>([&](){ return drops_history; });
for (int i = 0; i < hist.size(); i++)
{
add_point((float)i, (float)hist[i]);
}
r.h -= ImGui::GetTextLineHeightWithSpacing() + 10;
if( config_file::instance().get( configurations::viewer::dashboard_open) )
draw_dashboard(win, r);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 40);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 3);
ImGui::Text("%s", "Measurement Metric:"); ImGui::SameLine();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 3);
ImGui::SetCursorPosX( 11.5f * win.get_font_size() );
std::vector<const char*> methods;
methods.push_back("Viewer Processing Rate");
methods.push_back("Camera Timestamp Rate");
ImGui::PushItemWidth(-1.f);
if (ImGui::Combo("##fps_method", &method, methods.data(), (int)(methods.size())))
{
clear(false);
}
ImGui::PopItemWidth();
}
int frame_drops_dashboard::get_height() const
{
return (int)(160 + ImGui::GetTextLineHeightWithSpacing());
}
void frame_drops_dashboard::clear(bool full)
{
write_shared_data([&](){
stream_to_time.clear();
last_time = 0;
*total = 0;
*frame_drop_count = 0;
if (full)
{
drops_history.clear();
for (int i = 0; i < 100; i++)
drops_history.push_back(0);
}
});
}