// License: Apache 2.0. See LICENSE file in root directory. // Copyright(c) 2020 Intel Corporation. All Rights Reserved. #include #include "output-model.h" #include #include "ux-window.h" #include "device-model.h" #include "os.h" #include #include #include #include using namespace rs2; using namespace rsutils::string; void output_model::thread_loop() { while (!to_stop) { std::vector dev_copy; { std::lock_guard lock(devices_mutex); dev_copy = devices; } if (enable_firmware_logs) for (auto&& dev : dev_copy) { try { if (auto fwlogger = dev.as()) { 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(f)), std::istreambuf_iterator()); 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(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(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((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(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 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(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(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(f)), std::istreambuf_iterator()); 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(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 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 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 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 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 action) { std::lock_guard 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 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()) { 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(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(f)), std::istreambuf_iterator()); 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(elem) << " "; add_log(RS2_LOG_SEVERITY_INFO, __FILE__, 0, ss.str()); bool found = false; for (auto&& dev : devices) { if (auto dbg = dev.as()) { 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& a, const std::pair& 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>([&](){ 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 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); } }); }