diff --git a/file_downloader.cpp b/file_downloader.cpp new file mode 100644 index 0000000..9cad14c --- /dev/null +++ b/file_downloader.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** + * + * Copyright (c) 2022 IMProject Development Team. All rights reserved. + * Authors: Igor Misic + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name IMProject nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#include "file_downloader.h" + +namespace file_downloader { + +FileDownloader::FileDownloader() = default; +FileDownloader::~FileDownloader() = default; + +void FileDownloader::StartDownload(QUrl url) +{ + QNetworkRequest request(url); + request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); + reply_ = net_access_manager_.get(request); + connect(reply_, &QNetworkReply::downloadProgress,this, &FileDownloader::SetDownloadProgress); + connect(reply_, &QNetworkReply::finished, this, &FileDownloader::FileDownloaded); +} + +void FileDownloader::SetDownloadProgress(qint64 bytes_received, qint64 bytes_total) +{ + emit DownloadProgress(bytes_received, bytes_total); +} + +void FileDownloader::FileDownloaded() { + emit Downloaded(); +} + +void FileDownloader::GetDownloadedData(QByteArray& downloaded_data) { + downloaded_data = reply_->readAll(); + reply_->deleteLater(); +} + +} // namespace file_downloader diff --git a/file_downloader.h b/file_downloader.h new file mode 100644 index 0000000..360255b --- /dev/null +++ b/file_downloader.h @@ -0,0 +1,69 @@ +/**************************************************************************** + * + * Copyright (c) 2022 IMProject Development Team. All rights reserved. + * Authors: Igor Misic + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name IMProject nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef FILE_DOWNLOADER_H_ +#define FILE_DOWNLOADER_H_ + +#include +#include +#include +#include +#include + +namespace file_downloader { + +class FileDownloader : public QObject +{ + Q_OBJECT + public: + explicit FileDownloader(); + virtual ~FileDownloader(); + virtual void StartDownload(QUrl url); + virtual void GetDownloadedData(QByteArray& downloaded_data); + + signals: + void Downloaded(); + void DownloadProgress(qint64& bytes_received, qint64& bytes_total); + + private slots: + void SetDownloadProgress(qint64 bytes_received, qint64 bytes_total); + void FileDownloaded(); + private: + QNetworkAccessManager net_access_manager_; + QNetworkReply *reply_; +}; + +} // namespace file_downloader + +#endif // FILE_DOWNLOADER_H_ diff --git a/flasher.cpp b/flasher.cpp index d9e2ba3..9189e9a 100755 --- a/flasher.cpp +++ b/flasher.cpp @@ -42,6 +42,9 @@ #include "crc32.h" #include "socket_client.h" +#include "file_downloader.h" + +#define USE_SIGNATURE 0 QT_BEGIN_NAMESPACE void Worker::DoWork() @@ -53,16 +56,21 @@ QT_END_NAMESPACE namespace flasher { namespace { +#if USE_SIGNATURE constexpr qint64 kSignatureSize {64}; -constexpr int kEraseTimeoutInMs {5000}; +#else +constexpr qint64 kSignatureSize {0}; +#endif +constexpr int kEraseTimeoutInMs {8000}; constexpr qint64 kPacketSize {256}; constexpr unsigned long kThreadSleepTimeInMs {100U}; -constexpr int kSerialTimeoutInMs {100}; -constexpr int kCollectBoardIdSerialTimeoutInMs {300}; -constexpr int kCollectBoardInfoSerialTimeoutInMs {300}; +constexpr int kSerialTimeoutInMs {3000}; +constexpr int kCollectBoardIdSerialTimeoutInMs {1000}; +constexpr int kCollectBoardInfoSerialTimeoutInMs {1000}; constexpr int kCrc32Size {4}; constexpr int kBoardIdSize {32}; constexpr int kTryToConnectTimeoutInMs {20000}; +constexpr int kTryToDownloadFirmwareTimeoutInMs {5000}; // Commands constexpr char kVerifyFlasherCmd[] = "IMFlasher_Verify"; @@ -130,6 +138,10 @@ void Flasher::Init() socket_client_ = std::make_shared(servers_array); } + file_downloader_ = new file_downloader::FileDownloader(); + connect(file_downloader_, &file_downloader::FileDownloader::Downloaded, this, &Flasher::FileDownloaded); + connect(file_downloader_, &file_downloader::FileDownloader::DownloadProgress, this, &Flasher::DownloadProgress); + Worker *worker = new Worker; worker->moveToThread(&worker_thread_); connect(&worker_thread_, &QThread::finished, worker, &QObject::deleteLater); @@ -139,6 +151,18 @@ void Flasher::Init() emit RunLoop(); } +void Flasher::FileDownloaded() +{ + file_downloader_->GetDownloadedData(file_content_); + is_firmware_downloaded_ = true; +} + +void Flasher::DownloadProgress(qint64& bytes_received, qint64& bytes_total) +{ + timer_.start(); + emit UpdateProgress(bytes_received, bytes_total); +} + void Flasher::LoopHandler() { switch (state_) { @@ -220,15 +244,8 @@ void Flasher::LoopHandler() if (!board_info_.empty() && socket_client_) { if (socket_client_->SendBoardInfo(board_info_, bl_version_, fw_version_)) { if (socket_client_->ReceiveProductInfo(board_info_, product_info_)) { - - foreach (const QJsonValue &value, product_info_) - { - QJsonObject obj = value.toObject(); - QString firmware_download = "version: "; - firmware_download.append(obj["fw_version"].toString()); - firmware_download.append(" url: "); - firmware_download.append(obj["url"].toString()); - emit ShowTextInBrowser(firmware_download); + if (!product_info_.empty()) { + emit SetFirmwareList(product_info_); } } } @@ -237,7 +254,7 @@ void Flasher::LoopHandler() SetState(FlasherStates::kIdle); break; - case FlasherStates::kSelectFirmware: + case FlasherStates::kBrowseFirmware: { QString file_path = QFileDialog::getOpenFileName(nullptr, tr("Firmware binary"), @@ -255,8 +272,50 @@ void Flasher::LoopHandler() break; } + case FlasherStates::kLoadFirmwareFile: + { + // Load local firmware file + if (firmware_file_.size() != 0) { + file_content_ = firmware_file_.readAll(); + firmware_file_.close(); + firmware_file_.remove(); + SetState(FlasherStates::kFlash); + + // Load firmware file from url + } else { + + DownloadFirmwareFromUrl(); + emit ShowStatusMsg("Downloading"); + timer_.start(); + SetState(FlasherStates::kDownloadFirmwareFile); + } + + break; + } + + case FlasherStates::kDownloadFirmwareFile: + { + if (is_firmware_downloaded_) { + is_firmware_downloaded_ = false; + if (file_content_.isEmpty()) { + emit ShowStatusMsg("Download error"); + SetState(FlasherStates::kIdle); + } else { + SetState(FlasherStates::kFlash); + } + } + + if (timer_.hasExpired(kTryToDownloadFirmwareTimeoutInMs)) { + emit ShowStatusMsg("Download timeout"); + SetState(FlasherStates::kIdle); + } + + break; + } + case FlasherStates::kFlash: { + emit ShowStatusMsg("Flashing"); FlashingInfo flashing_info = Flash(); ShowInfoMsg(flashing_info.title, flashing_info.description); emit ClearProgress(); @@ -356,15 +415,14 @@ void Flasher::LoopHandler() FlashingInfo Flasher::Flash() { FlashingInfo flashing_info; - QByteArray file_content = firmware_file_.readAll(); - firmware_file_.close(); - const qint64 firmware_size = firmware_file_.size() - kSignatureSize; + const qint64 firmware_size = file_content_.size() - kSignatureSize; const qint64 num_of_packets = (firmware_size / kPacketSize); - const char *data_signature = file_content.data(); - const char *data_firmware = file_content.data() + kSignatureSize; + const char *data_signature = file_content_.data(); + const char *data_firmware = file_content_.data() + kSignatureSize; +#if USE_SIGNATURE if (serial_port_.isOpen()) { flashing_info.success = SendMessage(kCheckSignatureCmd, sizeof(kCheckSignatureCmd), kSerialTimeoutInMs); @@ -387,6 +445,14 @@ FlashingInfo Flasher::Flash() } } +#else +if (serial_port_.isOpen()) { + flashing_info.success = true; +} else { + flashing_info.title = "Error"; + flashing_info.description = "Serial port is not opened"; +} +#endif if (flashing_info.success) { flashing_info.success = SendMessage(kVerifyFlasherCmd, sizeof(kVerifyFlasherCmd), kSerialTimeoutInMs); @@ -705,6 +771,11 @@ void Flasher::SetState(const FlasherStates& state) state_ = state; } +void Flasher::SetSelectedFirmwareVersion(const QString& selected_frimware_version) +{ + selected_frimware_version_ = selected_frimware_version; +} + void Flasher::TryToConnectConsole() { QElapsedTimer timer; @@ -748,6 +819,20 @@ void Flasher::TryToConnect() } } +void Flasher::DownloadFirmwareFromUrl() +{ + foreach (const QJsonValue &value, product_info_) + { + QJsonObject obj = value.toObject(); + if (obj["fw_version"].toString() == selected_frimware_version_) { + + QUrl firmware_url(obj["url"].toString()); + file_downloader_->StartDownload(firmware_url); + break; + } + } +} + bool Flasher::OpenConfigFile(QJsonDocument& json_document) { bool success = false; diff --git a/flasher.h b/flasher.h index 0b2884f..2508857 100755 --- a/flasher.h +++ b/flasher.h @@ -51,6 +51,12 @@ class SocketClient; } // namespace socket +namespace file_downloader { + +class FileDownloader; + +} // namespace file_downloader + QT_BEGIN_NAMESPACE class Worker : public QObject { @@ -72,8 +78,10 @@ enum class FlasherStates { kConnected, kDisconnected, kServerDataExchange, - kSelectFirmware, + kBrowseFirmware, kCheckBoardInfo, + kLoadFirmwareFile, + kDownloadFirmwareFile, kFlash, kEnterBootloader, kEnteringBootloader, @@ -104,6 +112,7 @@ class Flasher : public QObject bool SendEnterBootloaderCommand(); void SendFlashCommand(); void SetState(const FlasherStates& state); + void SetSelectedFirmwareVersion(const QString& selected_frimware_version); void TryToConnectConsole(); signals: @@ -118,9 +127,12 @@ class Flasher : public QObject void SetReadProtectionButtonText(const bool& isEnabled); void DisableAllButtons(); void EnableLoadButton(); + void SetFirmwareList(const QJsonArray& product_info); public slots: void LoopHandler(); + void FileDownloaded(); + void DownloadProgress(qint64& bytes_received, qint64& bytes_total); private: QString board_id_; @@ -128,14 +140,18 @@ class Flasher : public QObject QJsonObject bl_version_; QJsonObject fw_version_; QJsonArray product_info_; + QString selected_frimware_version_; QFile config_file_; QFile firmware_file_; bool is_bootloader_ {false}; bool is_bootloader_expected_ {false}; bool is_read_protection_enabled_ {false}; bool is_timer_started_ {false}; + bool is_firmware_downloaded_{false}; + QByteArray file_content_; communication::SerialPort serial_port_; std::shared_ptr socket_client_; + file_downloader::FileDownloader *file_downloader_; FlasherStates state_ {FlasherStates::kIdle}; QElapsedTimer timer_; QThread worker_thread_; @@ -150,6 +166,7 @@ class Flasher : public QObject bool SendMessage(const char *data, qint64 length, int timeout_ms); bool ReadMessageWithCrc(const char *in_data, qint64 length, int timeout_ms, QByteArray& out_data); void TryToConnect(); + void DownloadFirmwareFromUrl(); bool OpenConfigFile(QJsonDocument& json_document); void CreateDefaultConfigFile(); }; diff --git a/imflasher.pro b/imflasher.pro index b2378fd..770971c 100644 --- a/imflasher.pro +++ b/imflasher.pro @@ -14,6 +14,7 @@ DEFINES += GIT_HASH=\\\"$$GIT_HASH\\\" DEFINES += GIT_BRANCH=\\\"$$GIT_BRANCH\\\" SOURCES += \ crc32.cpp \ + file_downloader.cpp \ flasher.cpp \ main.cpp \ mainwindow.cpp \ @@ -22,6 +23,7 @@ SOURCES += \ HEADERS += \ crc32.h \ + file_downloader.h \ flasher.h \ flashing_info.h \ mainwindow.h \ diff --git a/mainwindow.cpp b/mainwindow.cpp index 2535da7..1699661 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -53,7 +53,12 @@ MainWindow::MainWindow(std::shared_ptr flasher, QWidget *paren ClearProgress(); connect(flasher_.get(), &flasher::Flasher::UpdateProgress, this, [&] (const qint64& sent_size, const qint64& firmware_size) { - const int progress_percentage = (100 * sent_size) / firmware_size; + + int progress_percentage = 0; + if(firmware_size != 0) { + progress_percentage = (100 * sent_size) / firmware_size; + } + ui_.progressBar->setValue(progress_percentage); qInfo() << sent_size << "/" << firmware_size << "B, " << progress_percentage << "%"; }); @@ -77,12 +82,12 @@ MainWindow::MainWindow(std::shared_ptr flasher, QWidget *paren if (is_bootloader) { ui_.enterBootloader->setText("Exit bootloader"); - ui_.selectFirmware->setEnabled(true); + ui_.browseFirmware->setEnabled(true); ui_.protectButton->setEnabled(true); } else { ui_.enterBootloader->setText("Enter bootloader"); - ui_.selectFirmware->setEnabled(false); + ui_.browseFirmware->setEnabled(false); ui_.protectButton->setEnabled(false); } }); @@ -99,6 +104,7 @@ MainWindow::MainWindow(std::shared_ptr flasher, QWidget *paren connect(flasher_.get(), &flasher::Flasher::DisableAllButtons, this, &MainWindow::DisableAllButtons); connect(flasher_.get(), &flasher::Flasher::EnableLoadButton, this, [&] { ui_.loadFirmware->setEnabled(true); }); + connect(flasher_.get(), &flasher::Flasher::SetFirmwareList, this, &MainWindow::SetFirmwareList); } MainWindow::~MainWindow() = default; @@ -112,11 +118,26 @@ void MainWindow::ClearProgress() void MainWindow::DisableAllButtons() { ui_.enterBootloader->setEnabled(false); - ui_.selectFirmware->setEnabled(false); + ui_.availableFirmware->setEnabled(false); + ui_.browseFirmware->setEnabled(false); ui_.loadFirmware->setEnabled(false); ui_.protectButton->setEnabled(false); } +void MainWindow::SetFirmwareList(const QJsonArray& product_info) +{ + ui_.availableFirmware->clear(); + foreach (const QJsonValue &value, product_info) + { + QJsonObject obj = value.toObject(); + ui_.availableFirmware->addItem(obj["fw_version"].toString()); + } + + ui_.availableFirmware->show(); + ui_.availableFirmware->setEnabled(true); + ui_.loadFirmware->setEnabled(true); +} + void MainWindow::ConnectActions() { connect(ui_.actionConnect, &QAction::triggered, this, [&] (void) @@ -148,6 +169,7 @@ void MainWindow::InitActions() ui_.actionConnect->setEnabled(true); ui_.actionDisconnect->setEnabled(false); ui_.actionQuit->setEnabled(true); + ui_.availableFirmware->hide(); } void MainWindow::ShowStatusMessage(const QString& message) @@ -155,16 +177,17 @@ void MainWindow::ShowStatusMessage(const QString& message) ui_.statusLabel->setText(message); } -void MainWindow::on_selectFirmware_clicked() +void MainWindow::on_browseFirmware_clicked() { - flasher_->SetState(flasher::FlasherStates::kSelectFirmware); + flasher_->SetState(flasher::FlasherStates::kBrowseFirmware); } void MainWindow::on_loadFirmware_clicked() { + flasher_->SetSelectedFirmwareVersion(ui_.availableFirmware->currentText()); ui_.loadFirmware->setEnabled(false); ui_.progressBar->show(); - flasher_->SetState(flasher::FlasherStates::kFlash); + flasher_->SetState(flasher::FlasherStates::kLoadFirmwareFile); } void MainWindow::on_enterBootloader_clicked() diff --git a/mainwindow.h b/mainwindow.h index 6b61439..5589771 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -36,6 +36,7 @@ #define MAINWINDOW_H_ #include +#include #include "ui_mainwindow.h" @@ -54,7 +55,7 @@ class MainWindow : public QMainWindow ~MainWindow(); private slots: - void on_selectFirmware_clicked(); + void on_browseFirmware_clicked(); void on_loadFirmware_clicked(); void on_enterBootloader_clicked(); void on_protectButton_clicked(); @@ -73,6 +74,7 @@ class MainWindow : public QMainWindow void DisableAllButtons(); void InitActions(); void ShowStatusMessage(const QString& message); + void SetFirmwareList(const QJsonArray& product_info); }; } // namespace gui diff --git a/mainwindow.ui b/mainwindow.ui index 70c4b1e..ec9c3cc 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -67,7 +67,7 @@ - + 0 @@ -81,7 +81,14 @@ - Firmware + Browse + + + + + + + Qt::LeftToRight @@ -127,7 +134,7 @@ 0 0 676 - 27 + 19