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.

664 lines
24 KiB

// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2017 Intel Corporation. All Rights Reserved.
#include "fw-update-helper.h"
#include "model-views.h"
#include "viewer.h"
#include "ux-window.h"
#include <rsutils/os/special-folder.h>
#include "os.h"
#include <rsutils/easylogging/easyloggingpp.h>
#include <map>
#include <vector>
#include <string>
#include <thread>
#include <condition_variable>
#ifdef INTERNAL_FW
#include "common/fw/D4XX_FW_Image.h"
#else
#define FW_D4XX_FW_IMAGE_VERSION ""
const char* fw_get_D4XX_FW_Image(int) { return NULL; }
#endif // INTERNAL_FW
constexpr const char* recommended_fw_url = "https://dev.intelrealsense.com/docs/firmware-updates";
namespace rs2
{
enum firmware_update_ui_state
{
RS2_FWU_STATE_INITIAL_PROMPT = 0,
RS2_FWU_STATE_IN_PROGRESS = 1,
RS2_FWU_STATE_COMPLETE = 2,
RS2_FWU_STATE_FAILED = 3,
};
bool is_recommended_fw_available(const std::string& product_line, const std::string& pid)
{
auto pl = parse_product_line(product_line);
auto fv = get_available_firmware_version(pl, pid);
return !(fv == "");
}
int parse_product_line(const std::string& product_line)
{
if (product_line == "D400") return RS2_PRODUCT_LINE_D400;
else return -1;
}
std::string get_available_firmware_version(int product_line, const std::string& pid)
{
if (product_line == RS2_PRODUCT_LINE_D400) return FW_D4XX_FW_IMAGE_VERSION;
else return "";
}
std::vector< uint8_t > get_default_fw_image( int product_line, const std::string & pid )
{
std::vector< uint8_t > image;
switch( product_line )
{
case RS2_PRODUCT_LINE_D400:
{
bool allow_rc_firmware = config_file::instance().get_or_default( configurations::update::allow_rc_firmware, false );
if( strlen( FW_D4XX_FW_IMAGE_VERSION ) && ! allow_rc_firmware )
{
int size = 0;
auto hex = fw_get_D4XX_FW_Image( size );
image = std::vector< uint8_t >( hex, hex + size );
}
}
break;
default:
break;
}
return image;
}
bool is_upgradeable(const std::string& curr, const std::string& available)
{
if (curr == "" || available == "") return false;
return rsutils::version( curr ) < rsutils::version( available );
}
bool firmware_update_manager::check_for(
std::function<bool()> action, std::function<void()> cleanup,
std::chrono::system_clock::duration delta)
{
using namespace std;
using namespace std::chrono;
auto start = system_clock::now();
auto now = system_clock::now();
do
{
try
{
if (action()) return true;
}
catch (const error& e)
{
fail(error_to_string(e));
cleanup();
return false;
}
catch (const std::exception& ex)
{
fail(ex.what());
cleanup();
return false;
}
catch (...)
{
fail("Unknown error during update.\nPlease reconnect the camera to exit recovery mode");
cleanup();
return false;
}
now = system_clock::now();
this_thread::sleep_for(milliseconds(100));
} while (now - start < delta);
return false;
}
void firmware_update_manager::process_mipi()
{
if (!_is_signed)
{
fail("Signed FW update for MIPI device - This FW file is not signed ");
return;
}
auto dev_updatable = _dev.as<updatable>();
if(!(dev_updatable && dev_updatable.check_firmware_compatibility(_fw)))
{
fail("Firmware Update failed - fw version must be newer than version 5.13.1.1");
return;
}
log("Burning Signed Firmware on MIPI device");
// Enter DFU mode
auto device_debug = _dev.as<rs2::debug_protocol>();
uint32_t dfu_opcode = 0x1e;
device_debug.build_command(dfu_opcode, 1);
_progress = 30;
// Write signed firmware to appropriate file descriptor
std::ofstream fw_path_in_device(_dev.get_info(RS2_CAMERA_INFO_DFU_DEVICE_PATH), std::ios::binary);
if (fw_path_in_device)
{
fw_path_in_device.write(reinterpret_cast<const char*>(_fw.data()), _fw.size());
}
else
{
fail("Firmware Update failed - wrong path or permissions missing");
return;
}
LOG_INFO("Firmware Update for MIPI device done.");
fw_path_in_device.close();
_progress = 100;
_done = true;
// need to find a way to update the fw version field in the viewer
}
void firmware_update_manager::process_flow(
std::function<void()> cleanup,
invoker invoke)
{
// if device is D457, and fw is signed - using mipi specific procedure
if (!strcmp(_dev.get_info(RS2_CAMERA_INFO_PRODUCT_ID), "ABCD") && _is_signed)
{
process_mipi();
return;
}
std::string serial = "";
if (_dev.supports(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID))
serial = _dev.get_info(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID);
else
serial = _dev.query_sensors().front().get_info(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID);
// Clear FW update related notification to avoid dismissing the notification on ~device_model()
// We want the notification alive during the whole process.
_model.related_notifications.erase(
std::remove_if( _model.related_notifications.begin(),
_model.related_notifications.end(),
[]( std::shared_ptr< notification_model > n ) {
return n->is< fw_update_notification_model >();
} ) , end(_model.related_notifications));
for (auto&& n : _model.related_notifications)
{
if (n->is< fw_update_notification_model >()
|| n->is< sw_recommended_update_alert_model >())
n->dismiss(false);
}
_progress = 5;
int next_progress = 10;
update_device dfu{};
if (auto upd = _dev.as<updatable>())
{
// checking firmware version compatibility with device
if (_is_signed)
{
if (!upd.check_firmware_compatibility(_fw))
{
std::stringstream ss;
ss << "The firmware version is not compatible with ";
ss << _dev.get_info(RS2_CAMERA_INFO_NAME) << std::endl;
fail(ss.str());
return;
}
}
log( "Trying to back-up camera flash memory" );
std::string log_backup_status;
try
{
auto flash = upd.create_flash_backup( [&]( const float progress )
{
_progress = ( ( ceil( progress * 5 ) / 5 ) * ( 30 - next_progress ) ) + next_progress;
} );
// Not all cameras supports this feature
if( !flash.empty() )
{
auto temp = rsutils::os::get_special_folder( rsutils::os::special_folder::app_data );
temp += serial + "." + get_timestamped_file_name() + ".bin";
{
std::ofstream file( temp.c_str(), std::ios::binary );
file.write( (const char*)flash.data(), flash.size() );
log_backup_status = "Backup completed and saved as '" + temp + "'";
}
}
else
{
log_backup_status = "Backup flash is not supported";
}
}
catch( const std::exception& e )
{
if( auto not_model_protected = get_protected_notification_model() )
{
log_backup_status = "WARNING: backup failed; continuing without it...";
not_model_protected->output.add_log( RS2_LOG_SEVERITY_WARN,
__FILE__,
__LINE__,
log_backup_status + ", Error: " + e.what() );
}
}
catch( ... )
{
if( auto not_model_protected = get_protected_notification_model() )
{
log_backup_status = "WARNING: backup failed; continuing without it...";
not_model_protected->add_log( log_backup_status + ", Unknown error occurred" );
}
}
if ( !log_backup_status.empty() )
log(log_backup_status);
next_progress = 40;
if (_is_signed)
{
log("Requesting to switch to recovery mode");
// in order to update device to DFU state, it will be disconnected then switches to DFU state
// if querying devices is called while device still switching to DFU state, an exception will be thrown
// to prevent that, a blocking is added to make sure device is updated before continue to next step of querying device
upd.enter_update_state();
if (!check_for([this, serial, &dfu]() {
auto devs = _ctx.query_devices();
for (uint32_t j = 0; j < devs.size(); j++)
{
try
{
auto d = devs[j];
if (d.is<update_device>())
{
if (d.supports(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID))
{
if (serial == d.get_info(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID))
{
log( "DFU device '" + serial + "' found" );
dfu = d;
return true;
}
}
}
}
catch (std::exception &e) {
if (auto not_model_protected = get_protected_notification_model())
{
not_model_protected->output.add_log(RS2_LOG_SEVERITY_WARN,
__FILE__,
__LINE__,
rsutils::string::from() << "Exception caught in FW Update process-flow: " << e.what() << "; Retrying...");
}
}
catch (...) {}
}
return false;
}, cleanup, std::chrono::seconds(60)))
{
fail("Recovery device did not connect in time!");
return;
}
}
}
else
{
dfu = _dev.as<update_device>();
}
if (dfu)
{
_progress = float(next_progress);
log("Recovery device connected, starting update..\n"
"Internal write is in progress\n"
"Please DO NOT DISCONNECT the camera");
dfu.update(_fw, [&](const float progress)
{
_progress = ((ceil(progress * 10) / 10 * (90 - next_progress)) + next_progress);
});
log( "Firmware Download completed, await DFU transition event" );
std::this_thread::sleep_for( std::chrono::seconds( 3 ) );
}
else
{
auto upd = _dev.as<updatable>();
upd.update_unsigned(_fw, [&](const float progress)
{
_progress = (ceil(progress * 10) / 10 * (90 - next_progress)) + next_progress;
});
log("Firmware Update completed, waiting for device to reconnect");
}
if (!check_for([this, serial]() {
auto devs = _ctx.query_devices();
for (uint32_t j = 0; j < devs.size(); j++)
{
try
{
auto d = devs[j];
if (d.query_sensors().size() && d.query_sensors().front().supports(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID))
{
auto s = d.query_sensors().front().get_info(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID);
if (s == serial)
{
log("Discovered connection of the original device");
return true;
}
}
}
catch (...) {}
}
return false;
}, cleanup, std::chrono::seconds(60)))
{
fail("Original device did not reconnect in time!");
return;
}
log( "Device reconnected successfully!\n"
"FW update process completed successfully" );
_progress = 100;
_done = true;
}
void fw_update_notification_model::draw_content(ux_window& win, int x, int y, float t, std::string& error_message)
{
using namespace std;
using namespace chrono;
ImGui::SetCursorScreenPos({ float(x + 9), float(y + 4) });
ImVec4 shadow{ 1.f, 1.f, 1.f, 0.1f };
ImGui::GetWindowDrawList()->AddRectFilled({ float(x), float(y) },
{ float(x + width), float(y + 25) }, ImColor(shadow));
if (update_state != RS2_FWU_STATE_COMPLETE)
{
ImGui::Text("Firmware Update Recommended!");
ImGui::SetCursorScreenPos({ float(x + 5), float(y + 27) });
draw_text(get_title().c_str(), x, y, height - 50);
ImGui::SetCursorScreenPos({ float(x + 9), float(y + height - 67) });
ImGui::PushStyleColor(ImGuiCol_Text, alpha(light_grey, 1.f - t));
if (update_state == RS2_FWU_STATE_INITIAL_PROMPT)
ImGui::Text("Firmware updates offer critical bug fixes and\nunlock new camera capabilities.");
else
ImGui::Text("Firmware update is underway...\nPlease do not disconnect the device");
ImGui::PopStyleColor();
}
else
{
//ImGui::PushStyleColor(ImGuiCol_Text, alpha(light_blue, 1.f - t));
ImGui::Text("Update Completed");
//ImGui::PopStyleColor();
ImGui::SetCursorScreenPos({ float(x + 10), float(y + 35) });
ImGui::PushFont(win.get_large_font());
std::string txt = rsutils::string::from() << textual_icons::throphy;
ImGui::Text("%s", txt.c_str());
ImGui::PopFont();
ImGui::SetCursorScreenPos({ float(x + 40), float(y + 35) });
ImGui::Text("Camera Firmware Updated Successfully");
}
ImGui::SetCursorScreenPos({ float(x + 5), float(y + height - 25) });
const auto bar_width = width - 115;
if (update_manager)
{
if (update_state == RS2_FWU_STATE_INITIAL_PROMPT)
{
auto sat = 1.f + sin(duration_cast<milliseconds>(system_clock::now() - created_time).count() / 700.f) * 0.1f;
ImGui::PushStyleColor(ImGuiCol_Button, saturate(sensor_header_light_blue, sat));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, saturate(sensor_header_light_blue, 1.5f));
std::string button_name = rsutils::string::from() << "Install" << "##fwupdate" << index;
if (ImGui::Button(button_name.c_str(), { float(bar_width), 20.f }) || update_manager->started())
{
// stopping stream before starting fw update
auto fw_update_manager = dynamic_cast<firmware_update_manager*>(update_manager.get());
if( ! fw_update_manager )
throw std::runtime_error( "Cannot convert firmware_update_manager" );
std::for_each(fw_update_manager->get_device_model().subdevices.begin(),
fw_update_manager->get_device_model().subdevices.end(),
[&](const std::shared_ptr<subdevice_model>& sm)
{
if (sm->streaming)
{
try
{
sm->stop(fw_update_manager->get_protected_notification_model());
}
catch (...)
{
// avoiding exception that can be sent by stop method
// this could happen if the sensor is not streaming and the stop method is called - for example
}
}
});
auto _this = shared_from_this();
auto invoke = [_this](std::function<void()> action) {
_this->invoke(action);
};
if (!update_manager->started())
update_manager->start(invoke);
update_state = RS2_FWU_STATE_IN_PROGRESS;
enable_dismiss = false;
_progress_bar.last_progress_time = system_clock::now();
}
ImGui::PopStyleColor(2);
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", "New firmware will be flashed to the device");
}
}
else if (update_state == RS2_FWU_STATE_IN_PROGRESS)
{
if (update_manager->done())
{
update_state = RS2_FWU_STATE_COMPLETE;
pinned = false;
_progress_bar.last_progress_time = last_interacted = system_clock::now();
}
if (!expanded)
{
if (update_manager->failed())
{
update_manager->check_error(error_message);
update_state = RS2_FWU_STATE_FAILED;
pinned = false;
dismiss(false);
}
draw_progress_bar(win, bar_width);
ImGui::SetCursorScreenPos({ float(x + width - 105), float(y + height - 25) });
string id = rsutils::string::from() << "Expand" << "##" << index;
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
if (ImGui::Button(id.c_str(), { 100, 20 }))
{
expanded = true;
}
ImGui::PopStyleColor();
}
}
}
else
{
std::string button_name = rsutils::string::from() << "Learn More..." << "##" << index;
if (ImGui::Button(button_name.c_str(), { float(bar_width), 20 }))
{
open_url(recommended_fw_url);
}
if (ImGui::IsItemHovered())
{
win.link_hovered();
ImGui::SetTooltip("%s", "Internet connection required");
}
}
}
void fw_update_notification_model::draw_expanded(ux_window& win, std::string& error_message)
{
if (update_manager->started() && update_state == RS2_FWU_STATE_INITIAL_PROMPT)
update_state = RS2_FWU_STATE_IN_PROGRESS;
auto flags = ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoCollapse;
ImGui::PushStyleColor(ImGuiCol_WindowBg, { 0, 0, 0, 0 });
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white);
ImGui::PushStyleColor(ImGuiCol_PopupBg, sensor_bg);
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(500, 100));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5, 5));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
std::string title = "Firmware Update";
if (update_manager->failed()) title += " Failed";
ImGui::OpenPopup(title.c_str());
if (ImGui::BeginPopupModal(title.c_str(), nullptr, flags))
{
ImGui::SetCursorPosX(200);
std::string progress_str = rsutils::string::from() << "Progress: " << update_manager->get_progress() << "%";
ImGui::Text("%s", progress_str.c_str());
ImGui::SetCursorPosX(5);
draw_progress_bar(win, 490);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, regular_blue);
auto s = update_manager->get_log();
ImGui::InputTextMultiline("##fw_update_log", const_cast<char*>(s.c_str()),
s.size() + 1, { 490,100 }, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_ReadOnly);
ImGui::PopStyleColor();
ImGui::SetCursorPosX(190);
if (visible || update_manager->done() || update_manager->failed())
{
if (ImGui::Button("OK", ImVec2(120, 0)))
{
if (update_manager->done() || update_manager->failed())
{
update_state = RS2_FWU_STATE_FAILED;
pinned = false;
dismiss(false);
}
expanded = false;
ImGui::CloseCurrentPopup();
}
}
else
{
ImGui::PushStyleColor(ImGuiCol_Button, transparent);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, transparent);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, transparent);
ImGui::PushStyleColor(ImGuiCol_Text, transparent);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, transparent);
ImGui::Button("OK", ImVec2(120, 0));
ImGui::PopStyleColor(5);
}
ImGui::EndPopup();
}
ImGui::PopStyleVar(3);
ImGui::PopStyleColor(4);
error_message = "";
}
int fw_update_notification_model::calc_height()
{
if (update_state != RS2_FWU_STATE_COMPLETE) return 150;
else return 65;
}
void fw_update_notification_model::set_color_scheme(float t) const
{
notification_model::set_color_scheme(t);
ImGui::PopStyleColor(1);
ImVec4 c;
if (update_state == RS2_FWU_STATE_COMPLETE)
{
c = alpha(saturate(light_blue, 0.7f), 1 - t);
ImGui::PushStyleColor(ImGuiCol_WindowBg, c);
}
else
{
c = alpha(sensor_bg, 1 - t);
ImGui::PushStyleColor(ImGuiCol_WindowBg, c);
}
}
fw_update_notification_model::fw_update_notification_model(std::string name,
std::shared_ptr<firmware_update_manager> manager, bool exp)
: process_notification_model(manager)
{
enable_expand = false;
expanded = exp;
if (expanded) visible = false;
message = name;
this->severity = RS2_LOG_SEVERITY_INFO;
this->category = RS2_NOTIFICATION_CATEGORY_FIRMWARE_UPDATE_RECOMMENDED;
pinned = true;
forced = true;
}
}