text: /****************************************************************************
   *
   * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
   *
   * QGroundControl is licensed according to the terms in the file
   * COPYING.md in the root of the source code directory.
   *
   ****************************************************************************/
  
  #include "FirmwareUpgradeController.h"
  #include "Bootloader.h"
  #include "QGCApplication.h"
  #include "QGCFileDownload.h"
  #include "QGCOptions.h"
  #include "QGCCorePlugin.h"
  #include "FirmwareUpgradeSettings.h"
  #include "SettingsManager.h"
  #include "QGCZlib.h"
  #include "JsonHelper.h"
  
  #include <QStandardPaths>
  #include <QRegularExpression>
  #include <QJsonDocument>
  #include <QJsonObject>
  #include <QJsonArray>
  #include <QNetworkProxy>
  
  #include "zlib.h"
  
  const char* FirmwareUpgradeController::_manifestFirmwareJsonKey =               "firmware";
  const char* FirmwareUpgradeController::_manifestBoardIdJsonKey =                "board_id";
  const char* FirmwareUpgradeController::_manifestMavTypeJsonKey =                "mav-type";
  const char* FirmwareUpgradeController::_manifestFormatJsonKey =                 "format";
  const char* FirmwareUpgradeController::_manifestUrlJsonKey =                    "url";
  const char* FirmwareUpgradeController::_manifestMavFirmwareVersionTypeJsonKey = "mav-firmware-version-type";
  const char* FirmwareUpgradeController::_manifestUSBIDJsonKey =                  "USBID";
  const char* FirmwareUpgradeController::_manifestMavFirmwareVersionJsonKey =     "mav-firmware-version";
  const char* FirmwareUpgradeController::_manifestBootloaderStrJsonKey =          "bootloader_str";
  const char* FirmwareUpgradeController::_manifestLatestKey =                     "latest";
  const char* FirmwareUpgradeController::_manifestPlatformKey =                   "platform";
  const char* FirmwareUpgradeController::_manifestBrandNameKey =                  "brand_name";
  
  struct FirmwareToUrlElement_t {
      FirmwareUpgradeController::AutoPilotStackType_t     stackType;
      FirmwareUpgradeController::FirmwareBuildType_t      firmwareType;
      FirmwareUpgradeController::FirmwareVehicleType_t    vehicleType;
      QString                                             url;
  };
  
  uint qHash(const FirmwareUpgradeController::FirmwareIdentifier& firmwareId)
  {
      return static_cast<uint>(( firmwareId.autopilotStackType |
                                 (firmwareId.firmwareType << 8) |
                                 (firmwareId.firmwareVehicleType << 16) ));
  }
  
  /// @Brief Constructs a new FirmwareUpgradeController Widget. This widget is used within the PX4VehicleConfig set of screens.
  FirmwareUpgradeController::FirmwareUpgradeController(void)
      : _singleFirmwareURL                (qgcApp()->toolbox()->corePlugin()->options()->firmwareUpgradeSingleURL())
      , _singleFirmwareMode               (!_singleFirmwareURL.isEmpty())
      , _downloadingFirmwareList          (false)
      , _downloadManager                  (nullptr)
      , _downloadNetworkReply             (nullptr)
      , _statusLog                        (nullptr)
      , _selectedFirmwareBuildType             (StableFirmware)
      , _image                            (nullptr)
      , _apmBoardDescriptionReplaceText   ("<APMBoardDescription>")
      , _apmChibiOSSetting                (qgcApp()->toolbox()->settingsManager()->firmwareUpgradeSettings()->apmChibiOS())
      , _apmVehicleTypeSetting            (qgcApp()->toolbox()->settingsManager()->firmwareUpgradeSettings()->apmVehicleType())
  {
      _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap["OFFICIAL"] =  StableFirmware;
      _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap["BETA"] =      BetaFirmware;
      _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap["DEV"] =       DeveloperFirmware;
  
      _manifestMavTypeToFirmwareVehicleTypeMap["Copter"] =        CopterFirmware;
      _manifestMavTypeToFirmwareVehicleTypeMap["HELICOPTER"] =    HeliFirmware;
      _manifestMavTypeToFirmwareVehicleTypeMap["FIXED_WING"] =    PlaneFirmware;
      _manifestMavTypeToFirmwareVehicleTypeMap["GROUND_ROVER"] =  RoverFirmware;
      _manifestMavTypeToFirmwareVehicleTypeMap["SUBMARINE"] =     SubFirmware;
  
      _threadController = new PX4FirmwareUpgradeThreadController(this);
      Q_CHECK_PTR(_threadController);
  
      connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBoard,             this, &FirmwareUpgradeController::_foundBoard);
      connect(_threadController, &PX4FirmwareUpgradeThreadController::noBoardFound,           this, &FirmwareUpgradeController::_noBoardFound);
      connect(_threadController, &PX4FirmwareUpgradeThreadController::boardGone,              this, &FirmwareUpgradeController::_boardGone);
      connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBoardInfo,         this, &FirmwareUpgradeController::_foundBoardInfo);
      connect(_threadController, &PX4FirmwareUpgradeThreadController::error,                  this, &FirmwareUpgradeController::_error);
      connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress,         this, &FirmwareUpgradeController::_updateProgress);
      connect(_threadController, &PX4FirmwareUpgradeThreadController::status,                 this, &FirmwareUpgradeController::_status);
      connect(_threadController, &PX4FirmwareUpgradeThreadController::eraseStarted,           this, &FirmwareUpgradeController::_eraseStarted);
      connect(_threadController, &PX4FirmwareUpgradeThreadController::eraseComplete,          this, &FirmwareUpgradeController::_eraseComplete);
      connect(_threadController, &PX4FirmwareUpgradeThreadController::flashComplete,          this, &FirmwareUpgradeController::_flashComplete);
      connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress,         this, &FirmwareUpgradeController::_updateProgress);
      
      connect(&_eraseTimer, &QTimer::timeout, this, &FirmwareUpgradeController::_eraseProgressTick);
  
  #if !defined(NO_ARDUPILOT_DIALECT)
      connect(_apmChibiOSSetting,     &Fact::rawValueChanged, this, &FirmwareUpgradeController::_buildAPMFirmwareNames);
      connect(_apmVehicleTypeSetting, &Fact::rawValueChanged, this, &FirmwareUpgradeController::_buildAPMFirmwareNames);
  #endif
  
      _initFirmwareHash();
      _determinePX4StableVersion();
  
  #if !defined(NO_ARDUPILOT_DIALECT)
      _downloadArduPilotManifest();
  #endif
  }
  
  FirmwareUpgradeController::~FirmwareUpgradeController()
  {
      qgcApp()->toolbox()->linkManager()->setConnectionsAllowed();
  }
  
  void FirmwareUpgradeController::startBoardSearch(void)
  {
      LinkManager* linkMgr = qgcApp()->toolbox()->linkManager();
  
      linkMgr->setConnectionsSuspended(tr("Connect not allowed during Firmware Upgrade."));
  
      // FIXME: Why did we get here with active vehicle?
      if (!qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()) {
          // We have to disconnect any inactive links
          linkMgr->disconnectAll();
      }
  
      _bootloaderFound = false;
      _startFlashWhenBootloaderFound = false;
      _threadController->startFindBoardLoop();
  }
  
  void FirmwareUpgradeController::flash(AutoPilotStackType_t stackType,
                                        FirmwareBuildType_t firmwareType,
                                        FirmwareVehicleType_t vehicleType)
  {
      qCDebug(FirmwareUpgradeLog) << "_flash stackType:firmwareType:vehicleType" << stackType << firmwareType << vehicleType;
      FirmwareIdentifier firmwareId = FirmwareIdentifier(stackType, firmwareType, vehicleType);
      if (_bootloaderFound) {
          _getFirmwareFile(firmwareId);
      } else {
          // We haven't found the bootloader yet. Need to wait until then to flash
          _startFlashWhenBootloaderFound = true;
          _startFlashWhenBootloaderFoundFirmwareIdentity = firmwareId;
          _firmwareFilename.clear();
      }
  }
  
  void FirmwareUpgradeController::flashFirmwareUrl(QString firmwareFlashUrl)
  {
      _firmwareFilename = firmwareFlashUrl;
      if (_bootloaderFound) {
          _downloadFirmware();
      } else {
          // We haven't found the bootloader yet. Need to wait until then to flash
          _startFlashWhenBootloaderFound = true;
      }
  }
  
  void FirmwareUpgradeController::flash(const FirmwareIdentifier& firmwareId)
  {
      flash(firmwareId.autopilotStackType, firmwareId.firmwareType, firmwareId.firmwareVehicleType);
  }
  
  void FirmwareUpgradeController::flashSingleFirmwareMode(FirmwareBuildType_t firmwareType)
  {
      flash(SingleFirmwareMode, firmwareType, DefaultVehicleFirmware);
  }
  
  void FirmwareUpgradeController::cancel(void)
  {
      _eraseTimer.stop();
      _threadController->cancel();
  }
  
  QStringList FirmwareUpgradeController::availableBoardsName(void)
  {
      QGCSerialPortInfo::BoardType_t boardType;
      QString boardName;
      QStringList names;
  
      auto ports = QGCSerialPortInfo::availablePorts();
      for(const auto info : ports) {
          if(info.canFlash()) {
              info.getBoardInfo(boardType, boardName);
              names.append(boardName);
          }
      }
  
      return names;
  }
  
  void FirmwareUpgradeController::_foundBoard(bool firstAttempt, const QSerialPortInfo& info, int boardType, QString boardName)
  {
      _boardInfo      = info;
      _boardType      = static_cast<QGCSerialPortInfo::BoardType_t>(boardType);
      _boardTypeName  = boardName;
  
      qDebug() << info.manufacturer() << info.description();
  
      _startFlashWhenBootloaderFound = false;
  
      if (_boardType == QGCSerialPortInfo::BoardTypeSiKRadio) {
          if (!firstAttempt) {
              // Radio always flashes latest firmware, so we can start right away without
              // any further user input.
              _startFlashWhenBootloaderFound = true;
              _startFlashWhenBootloaderFoundFirmwareIdentity = FirmwareIdentifier(ThreeDRRadio,
                                                                                  StableFirmware,
                                                                                  DefaultVehicleFirmware);
          }
      }
      
      qCDebug(FirmwareUpgradeLog) << _boardType << _boardTypeName;
      emit boardFound();
  }
  
  
  void FirmwareUpgradeController::_noBoardFound(void)
  {
      emit noBoardFound();
  }
  
  void FirmwareUpgradeController::_boardGone(void)
  {
      emit boardGone();
  }
  
  /// @brief Called when the bootloader is connected to by the findBootloader process. Moves the state machine
  ///         to the next step.
  void FirmwareUpgradeController::_foundBoardInfo(int bootloaderVersion, int boardID, int flashSize)
  {
      _bootloaderFound            = true;
      _bootloaderVersion          = static_cast<uint32_t>(bootloaderVersion);
      _bootloaderBoardID          = static_cast<uint32_t>(boardID);
      _bootloaderBoardFlashSize   = static_cast<uint32_t>(flashSize);
      
      _appendStatusLog(tr("Connected to bootloader:"));
      _appendStatusLog(tr("  Version: %1").arg(_bootloaderVersion));
      _appendStatusLog(tr("  Board ID: %1").arg(_bootloaderBoardID));
      _appendStatusLog(tr("  Flash size: %1").arg(_bootloaderBoardFlashSize));
      
      if (_startFlashWhenBootloaderFound) {
          flash(_startFlashWhenBootloaderFoundFirmwareIdentity);
      }
  
      if (_rgManifestFirmwareInfo.count()) {
          _buildAPMFirmwareNames();
      }
  
      emit bootloaderFound();
  }
  
  
  /// @brief intializes the firmware hashes with proper urls.
  /// This happens only once for a class instance first time when it is needed.
  void FirmwareUpgradeController::_initFirmwareHash()
  {
      // indirect check whether this function has been called before or not
      // may have to be modified if _rgPX4FMUV2Firmware disappears
      if (!_rgPX4FMUV2Firmware.isEmpty()) {
          return;
      }
  
      //////////////////////////////////// PX4FMU aerocore firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t  rgAeroCoreFirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://gumstix-aerocore.s3.amazonaws.com/PX4/stable/aerocore_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://gumstix-aerocore.s3.amazonaws.com/PX4/beta/aerocore_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://gumstix-aerocore.s3.amazonaws.com/PX4/master/aerocore_default.px4"},
      };
  
      //////////////////////////////////// AUAVX2_1 firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t rgAUAVX2_1FirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/auav-x21_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/auav-x21_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/auav-x21_default.px4"},
      };
      //////////////////////////////////// MindPXFMUV2 firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t rgMindPXFMUV2FirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/mindpx-v2_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/mindpx-v2_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/mindpx-v2_default.px4"},
      };
      //////////////////////////////////// TAPV1 firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t rgTAPV1FirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/tap-v1_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/tap-v1_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/tap-v1_default.px4"},
          { SingleFirmwareMode,StableFirmware,    DefaultVehicleFirmware, _singleFirmwareURL},
      };
      //////////////////////////////////// ASCV1 firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t rgASCV1FirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/asc-v1_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/asc-v1_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/asc-v1_default.px4"},
      };
  
      //////////////////////////////////// Crazyflie 2.0 firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t rgCrazyflie2FirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/crazyflie_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/crazyflie_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/crazyflie_default.px4"},
      };
  
      //////////////////////////////////// Omnibus F4 SD firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t rgOmnibusF4SDFirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/omnibus_f4sd_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/omnibus_f4sd_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/omnibus_f4sd_default.px4"},
      };
      
      //////////////////////////////////// FMUK66V3 firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t rgFMUK66V3FirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/nxp_fmuk66-v3_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/nxp_fmuk66-v3_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/nxp_fmuk66-v3_default.px4"},
      };
  
      //////////////////////////////////// Kakute F7 firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t rgKakuteF7FirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/holybro_kakutef7_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/holybro_kakutef7_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/holybro_kakutef7_default.px4"},
      };
      
      //////////////////////////////////// Durandal firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t rgDurandalV1FirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/holybro_durandal-v1_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/holybro_durandal-v1_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/holybro_durandal-v1_default.px4"},
      };
  
      //////////////////////////////////// ModalAI FC v1 firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t rgModalFCV1FirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/modalai_fc-v1_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/modalai_fc-v1_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/modalai_fc-v1_default.px4"},
      };
      //////////////////////////////////// UVify FC firmwares //////////////////////////////////////////////////
      FirmwareToUrlElement_t rgUVifyCoreFirmwareArray[] = {
          { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/uvify_core_default.px4"},
          { AutoPilotStackPX4, BetaFirmware,      DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/uvify_core_default.px4"},
          { AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/uvify_core_default.px4"},
      };
  
      /////////////////////////////// px4flow firmwares ///////////////////////////////////////
      FirmwareToUrlElement_t rgPX4FLowFirmwareArray[] = {
          { PX4FlowPX4, StableFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Flow/master/px4flow.px4" },
      #if !defined(NO_ARDUPILOT_DIALECT)
          { PX4FlowAPM, StableFirmware, DefaultVehicleFirmware, "http://firmware.ardupilot.org/Tools/PX4Flow/px4flow-klt-latest.px4" },
      #endif
      };
  
      /////////////////////////////// 3dr radio firmwares ///////////////////////////////////////
      FirmwareToUrlElement_t rg3DRRadioFirmwareArray[] = {
          { ThreeDRRadio, StableFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/SiK/stable/radio~hm_trp.ihx"}
      };
  
      // We build the maps for PX4 firmwares dynamically using the data below
  
  #if 0
      Example URLs for PX4 and ArduPilot
      { AutoPilotStackPX4, StableFirmware,    DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/px4fmu-v4_default.px4"},
      { AutoPilotStackAPM, StableFirmware,    CopterFirmware,         "http://firmware.ardupilot.org/Copter/stable/PX4/ArduCopter-v4.px4"},
      { AutoPilotStackAPM, DeveloperFirmware, CopterChibiosFirmware,  "http://firmware.ardupilot.org/Copter/latest/fmuv4/arducopter.apj"},
  #endif
  
      QString px4Url          ("http://px4-travis.s3.amazonaws.com/Firmware/%1/px4fmu-%2_default.px4");
  
      QMap<FirmwareBuildType_t, QString> px4MapFirmwareTypeToDir;
      px4MapFirmwareTypeToDir[StableFirmware] =       QStringLiteral("stable");
      px4MapFirmwareTypeToDir[BetaFirmware] =         QStringLiteral("beta");
      px4MapFirmwareTypeToDir[DeveloperFirmware] =    QStringLiteral("master");
  
      // PX4 Firmwares
      for (const FirmwareBuildType_t& firmwareType: px4MapFirmwareTypeToDir.keys()) {
          QString dir = px4MapFirmwareTypeToDir[firmwareType];
          _rgFMUV5Firmware.insert     (FirmwareIdentifier(AutoPilotStackPX4, firmwareType, DefaultVehicleFirmware), px4Url.arg(dir).arg("v5"));
          _rgFMUV4PROFirmware.insert  (FirmwareIdentifier(AutoPilotStackPX4, firmwareType, DefaultVehicleFirmware), px4Url.arg(dir).arg("v4pro"));
          _rgFMUV4Firmware.insert     (FirmwareIdentifier(AutoPilotStackPX4, firmwareType, DefaultVehicleFirmware), px4Url.arg(dir).arg("v4"));
          _rgFMUV3Firmware.insert     (FirmwareIdentifier(AutoPilotStackPX4, firmwareType, DefaultVehicleFirmware), px4Url.arg(dir).arg("v3"));
          _rgPX4FMUV2Firmware.insert  (FirmwareIdentifier(AutoPilotStackPX4, firmwareType, DefaultVehicleFirmware), px4Url.arg(dir).arg("v2"));
      }
  
      int size = sizeof(rgAeroCoreFirmwareArray)/sizeof(rgAeroCoreFirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgAeroCoreFirmwareArray[i];
          _rgAeroCoreFirmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rgAUAVX2_1FirmwareArray)/sizeof(rgAUAVX2_1FirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgAUAVX2_1FirmwareArray[i];
          _rgAUAVX2_1Firmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rgMindPXFMUV2FirmwareArray)/sizeof(rgMindPXFMUV2FirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgMindPXFMUV2FirmwareArray[i];
          _rgMindPXFMUV2Firmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rgTAPV1FirmwareArray)/sizeof(rgTAPV1FirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgTAPV1FirmwareArray[i];
          _rgTAPV1Firmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rgASCV1FirmwareArray)/sizeof(rgASCV1FirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgASCV1FirmwareArray[i];
          _rgASCV1Firmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rgCrazyflie2FirmwareArray)/sizeof(rgCrazyflie2FirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgCrazyflie2FirmwareArray[i];
          _rgCrazyflie2Firmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rgOmnibusF4SDFirmwareArray)/sizeof(rgOmnibusF4SDFirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgOmnibusF4SDFirmwareArray[i];
          _rgOmnibusF4SDFirmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
      
      size = sizeof(rgKakuteF7FirmwareArray)/sizeof(rgKakuteF7FirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgKakuteF7FirmwareArray[i];
          _rgKakuteF7Firmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rgDurandalV1FirmwareArray)/sizeof(rgDurandalV1FirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgDurandalV1FirmwareArray[i];
          _rgDurandalV1Firmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rgFMUK66V3FirmwareArray)/sizeof(rgFMUK66V3FirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgFMUK66V3FirmwareArray[i];
          _rgFMUK66V3Firmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rgModalFCV1FirmwareArray)/sizeof(rgModalFCV1FirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgModalFCV1FirmwareArray[i];
          _rgModalFCV1Firmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rgUVifyCoreFirmwareArray)/sizeof(rgUVifyCoreFirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgUVifyCoreFirmwareArray[i];
          _rgUVifyCoreFirmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rgPX4FLowFirmwareArray)/sizeof(rgPX4FLowFirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rgPX4FLowFirmwareArray[i];
          _rgPX4FLowFirmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rg3DRRadioFirmwareArray)/sizeof(rg3DRRadioFirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rg3DRRadioFirmwareArray[i];
          _rg3DRRadioFirmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  
      size = sizeof(rg3DRRadioFirmwareArray)/sizeof(rg3DRRadioFirmwareArray[0]);
      for (int i = 0; i < size; i++) {
          const FirmwareToUrlElement_t& element = rg3DRRadioFirmwareArray[i];
          _rg3DRRadioFirmware.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
      }
  }
  
  /// @brief Called when the findBootloader process is unable to sync to the bootloader. Moves the state
  ///         machine to the appropriate error state.
  void FirmwareUpgradeController::_bootloaderSyncFailed(void)
  {
      _errorCancel("Unable to sync with bootloader.");
  }
  
  QHash<FirmwareUpgradeController::FirmwareIdentifier, QString>* FirmwareUpgradeController::_firmwareHashForBoardId(int boardId)
  {
  
      _rgFirmwareDynamic.clear();
  
      switch (boardId) {
      case Bootloader::boardIDPX4Flow:
          _rgFirmwareDynamic = _rgPX4FLowFirmware;
          break;
      case Bootloader::boardIDPX4FMUV2:
          _rgFirmwareDynamic = _rgPX4FMUV2Firmware;
          break;
      case Bootloader::boardIDPX4FMUV3:
          _rgFirmwareDynamic = _rgFMUV3Firmware;
          break;
      case Bootloader::boardIDPX4FMUV4:
          _rgFirmwareDynamic = _rgFMUV4Firmware;
          break;
      case Bootloader::boardIDPX4FMUV4PRO:
          _rgFirmwareDynamic = _rgFMUV4PROFirmware;
          break;
      case Bootloader::boardIDPX4FMUV5:
          _rgFirmwareDynamic = _rgFMUV5Firmware;
          break;
      case Bootloader::boardIDAeroCore:
          _rgFirmwareDynamic = _rgAeroCoreFirmware;
          break;
      case Bootloader::boardIDAUAVX2_1:
          _rgFirmwareDynamic = _rgAUAVX2_1Firmware;
          break;
      case Bootloader::boardIDMINDPXFMUV2:
          _rgFirmwareDynamic = _rgMindPXFMUV2Firmware;
          break;
      case Bootloader::boardIDTAPV1:
          _rgFirmwareDynamic = _rgTAPV1Firmware;
          break;
      case Bootloader::boardIDASCV1:
          _rgFirmwareDynamic = _rgASCV1Firmware;
          break;
      case Bootloader::boardIDCrazyflie2:
          _rgFirmwareDynamic = _rgCrazyflie2Firmware;
          break;
      case Bootloader::boardIDOmnibusF4SD:
          _rgFirmwareDynamic = _rgOmnibusF4SDFirmware;
          break;
      case Bootloader::boardIDKakuteF7:
          _rgFirmwareDynamic = _rgKakuteF7Firmware;
          break;
      case Bootloader::boardIDDurandalV1:
          _rgFirmwareDynamic = _rgDurandalV1Firmware;
          break;
      case Bootloader::boardIDFMUK66V3:
          _rgFirmwareDynamic = _rgFMUK66V3Firmware;
          break;
      case Bootloader::boardIDModalFCV1:
          _rgFirmwareDynamic = _rgModalFCV1Firmware;
          break;
      case Bootloader::boardIDUVifyCore:
          _rgFirmwareDynamic = _rgUVifyCoreFirmware;
          break;
      case Bootloader::boardID3DRRadio:
          _rgFirmwareDynamic = _rg3DRRadioFirmware;
          break;
      default:
          // Unknown board id
          break;
      }
  
      return &_rgFirmwareDynamic;
  }
  
  void FirmwareUpgradeController::_getFirmwareFile(FirmwareIdentifier firmwareId)
  {
      QHash<FirmwareIdentifier, QString>* prgFirmware = _firmwareHashForBoardId(static_cast<int>(_bootloaderBoardID));
      if (firmwareId.firmwareType == CustomFirmware) {
          _firmwareFilename = QString();
          _errorCancel(tr("Custom firmware selected but no filename given."));
      } else {
          if (prgFirmware->contains(firmwareId)) {
              _firmwareFilename = prgFirmware->value(firmwareId);
          } else {
              _errorCancel(tr("Unable to find specified firmware for board type"));
              return;
          }
      }
      
      if (_firmwareFilename.isEmpty()) {
          _errorCancel(tr("No firmware file selected"));
      } else {
          _downloadFirmware();
      }
  }
  
  /// @brief Begins the process of downloading the selected firmware file.
  void FirmwareUpgradeController::_downloadFirmware(void)
  {
      Q_ASSERT(!_firmwareFilename.isEmpty());
      
      _appendStatusLog(tr("Downloading firmware..."));
      _appendStatusLog(tr(" From: %1").arg(_firmwareFilename));
      
      QGCFileDownload* downloader = new QGCFileDownload(this);
      connect(downloader, &QGCFileDownload::downloadComplete, this, &FirmwareUpgradeController::_firmwareDownloadComplete);
      connect(downloader, &QGCFileDownload::downloadProgress, this, &FirmwareUpgradeController::_firmwareDownloadProgress);
      downloader->download(_firmwareFilename);
  }
  
  /// @brief Updates the progress indicator while downloading
  void FirmwareUpgradeController::_firmwareDownloadProgress(qint64 curr, qint64 total)
  {
      // Take care of cases where 0 / 0 is emitted as error return value
      if (total > 0) {
          _progressBar->setProperty("value", static_cast<float>(curr) / static_cast<float>(total));
      }
  }
  
  /// @brief Called when the firmware download completes.
  void FirmwareUpgradeController::_firmwareDownloadComplete(QString /*remoteFile*/, QString localFile, QString errorMsg)
  {
      if (errorMsg.isEmpty()) {
      _appendStatusLog(tr("Download complete"));
      
      FirmwareImage* image = new FirmwareImage(this);
      
      connect(image, &FirmwareImage::statusMessage, this, &FirmwareUpgradeController::_status);
      connect(image, &FirmwareImage::errorMessage, this, &FirmwareUpgradeController::_error);
      
      if (!image->load(localFile, _bootloaderBoardID)) {
          _errorCancel(tr("Image load failed"));
          return;
      }
      
      // We can't proceed unless we have the bootloader
      if (!_bootloaderFound) {
          _errorCancel(tr("Bootloader not found"));
          return;
      }
      
      if (_bootloaderBoardFlashSize != 0 && image->imageSize() > _bootloaderBoardFlashSize) {
          _errorCancel(tr("Image size of %1 is too large for board flash size %2").arg(image->imageSize()).arg(_bootloaderBoardFlashSize));
          return;
      }
  
      _threadController->flash(image);
      } else {
          _errorCancel(errorMsg);
      }
  }
  
  /// @brief returns firmware type as a string
  QString FirmwareUpgradeController::firmwareTypeAsString(FirmwareBuildType_t type) const
  {
      switch (type) {
      case StableFirmware:
          return "stable";
      case DeveloperFirmware:
          return "developer";
      case BetaFirmware:
          return "beta";
      default:
          return "custom";
      }
  }
  
  /// @brief Signals completion of one of the specified bootloader commands. Moves the state machine to the
  ///         appropriate next step.
  void FirmwareUpgradeController::_flashComplete(void)
  {
      delete _image;
      _image = nullptr;
      
      _appendStatusLog(tr("Upgrade complete"), true);
      _appendStatusLog("------------------------------------------", false);
      emit flashComplete();
      qgcApp()->toolbox()->linkManager()->setConnectionsAllowed();
  }
  
  void FirmwareUpgradeController::_error(const QString& errorString)
  {
      delete _image;
      _image = nullptr;
      
      _errorCancel(QString("Error: %1").arg(errorString));
  }
  
  void FirmwareUpgradeController::_status(const QString& statusString)
  {
      _appendStatusLog(statusString);
  }
  
  /// @brief Updates the progress bar from long running bootloader commands
  void FirmwareUpgradeController::_updateProgress(int curr, int total)
  {
      // Take care of cases where 0 / 0 is emitted as error return value
      if (total > 0) {
          _progressBar->setProperty("value", static_cast<float>(curr) / static_cast<float>(total));
      }
  }
  
  /// @brief Moves the progress bar ahead on tick while erasing the board
  void FirmwareUpgradeController::_eraseProgressTick(void)
  {
      _eraseTickCount++;
      _progressBar->setProperty("value", static_cast<float>(_eraseTickCount*_eraseTickMsec) / static_cast<float>(_eraseTotalMsec));
  }
  
  /// Appends the specified text to the status log area in the ui
  void FirmwareUpgradeController::_appendStatusLog(const QString& text, bool critical)
  {
      Q_ASSERT(_statusLog);
      
      QVariant returnedValue;
      QVariant varText;
      
      if (critical) {
          varText = QString("<font color=\"yellow\">%1</font>").arg(text);
      } else {
          varText = text;
      }
      
      QMetaObject::invokeMethod(_statusLog,
                                "append",
                                Q_RETURN_ARG(QVariant, returnedValue),
                                Q_ARG(QVariant, varText));
  }
  
  void FirmwareUpgradeController::_errorCancel(const QString& msg)
  {
      _appendStatusLog(msg, false);
      _appendStatusLog(tr("Upgrade cancelled"), true);
      _appendStatusLog("------------------------------------------", false);
      emit error();
      cancel();
      qgcApp()->toolbox()->linkManager()->setConnectionsAllowed();
  }
  
  void FirmwareUpgradeController::_eraseStarted(void)
  {
      // We set up our own progress bar for erase since the erase command does not provide one
      _eraseTickCount = 0;
      _eraseTimer.start(_eraseTickMsec);
  }
  
  void FirmwareUpgradeController::_eraseComplete(void)
  {
      _eraseTimer.stop();
  }
  
  void FirmwareUpgradeController::setSelectedFirmwareBuildType(FirmwareBuildType_t firmwareType)
  {
      _selectedFirmwareBuildType = firmwareType;
      emit selectedFirmwareBuildTypeChanged(_selectedFirmwareBuildType);
      _buildAPMFirmwareNames();
  }
  
  void FirmwareUpgradeController::_buildAPMFirmwareNames(void)
  {
  #if !defined(NO_ARDUPILOT_DIALECT)
  
      bool                    chibios =           _apmChibiOSSetting->rawValue().toInt() == 0;
      FirmwareVehicleType_t   vehicleType =       static_cast<FirmwareVehicleType_t>(_apmVehicleTypeSetting->rawValue().toInt());
      QString                 boardDescription =  _boardInfo.description();
      quint16                 boardVID =          _boardInfo.vendorIdentifier();
      quint16                 boardPID =          _boardInfo.productIdentifier();
      uint32_t                rawBoardId =        _bootloaderBoardID == Bootloader::boardIDPX4FMUV3 ? Bootloader::boardIDPX4FMUV2 : _bootloaderBoardID;
  
      qCDebug(FirmwareUpgradeLog) << QStringLiteral("_buildAPMFirmwareNames description(%1) vid(%2/0x%3) pid(%4/0x%5)").arg(boardDescription).arg(boardVID).arg(boardVID, 1, 16).arg(boardPID).arg(boardPID, 1, 16);
  
      _apmFirmwareNames.clear();
      _apmFirmwareNamesBestIndex = -1;
      _apmFirmwareUrls.clear();
  
      QString apmDescriptionSuffix("-BL");
      QString boardDescriptionPrefix;
      bool    bootloaderMatch = boardDescription.endsWith(apmDescriptionSuffix);
  
      int currentIndex = 0;
      for (const ManifestFirmwareInfo_t& firmwareInfo: _rgManifestFirmwareInfo) {
          bool match = false;
          if (firmwareInfo.firmwareBuildType == _selectedFirmwareBuildType && firmwareInfo.chibios == chibios && firmwareInfo.vehicleType == vehicleType && firmwareInfo.boardId == rawBoardId) {
              if (firmwareInfo.fmuv2 && _bootloaderBoardID == Bootloader::boardIDPX4FMUV3) {
                  qCDebug(FirmwareUpgradeLog) << "Skipping fmuv2 manifest entry for fmuv3 board:" << firmwareInfo.friendlyName << boardDescription << firmwareInfo.rgBootloaderPortString << firmwareInfo.url << firmwareInfo.vehicleType;
              } else {
                  qCDebug(FirmwareUpgradeLog) << "Board id match:" << firmwareInfo.friendlyName << boardDescription << firmwareInfo.rgBootloaderPortString << firmwareInfo.url << firmwareInfo.vehicleType;
                  match = true;
                  if (bootloaderMatch && _apmFirmwareNamesBestIndex == -1 && firmwareInfo.rgBootloaderPortString.contains(boardDescription)) {
                      _apmFirmwareNamesBestIndex = currentIndex;
                      qCDebug(FirmwareUpgradeLog) << "Bootloader best match:" << firmwareInfo.friendlyName << boardDescription << firmwareInfo.rgBootloaderPortString << firmwareInfo.url << firmwareInfo.vehicleType;
                  }
              }
          }
  
          if (match) {
              _apmFirmwareNames.append(firmwareInfo.friendlyName);
              _apmFirmwareUrls.append(firmwareInfo.url);
              currentIndex++;
          }
      }
  
      if (_apmFirmwareNamesBestIndex == -1) {
          _apmFirmwareNamesBestIndex++;
          if (_apmFirmwareNames.count() > 1) {
              _apmFirmwareNames.prepend(tr("Choose board type"));
              _apmFirmwareUrls.prepend(QString());
          }
      }
  
      emit apmFirmwareNamesChanged();
  #endif
  }
  
  FirmwareUpgradeController::FirmwareVehicleType_t FirmwareUpgradeController::vehicleTypeFromFirmwareSelectionIndex(int index)
  {
      if (index < 0 || index >= _apmVehicleTypeFromCurrentVersionList.count()) {
          qWarning() << "Invalid index, index:count" << index << _apmVehicleTypeFromCurrentVersionList.count();
          return CopterFirmware;
      }
  
      return _apmVehicleTypeFromCurrentVersionList[index];
  }
  
  void FirmwareUpgradeController::_determinePX4StableVersion(void)
  {
      QGCFileDownload* downloader = new QGCFileDownload(this);
      connect(downloader, &QGCFileDownload::downloadComplete, this, &FirmwareUpgradeController::_px4ReleasesGithubDownloadComplete);
      downloader->download(QStringLiteral("https://api.github.com/repos/PX4/Firmware/releases"));
  }
  
  void FirmwareUpgradeController::_px4ReleasesGithubDownloadComplete(QString /*remoteFile*/, QString localFile, QString errorMsg)
  {
      if (errorMsg.isEmpty()) {
          QFile jsonFile(localFile);
          if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
              qCWarning(FirmwareUpgradeLog) << "Unable to open github px4 releases json file" << localFile << jsonFile.errorString();
              return;
          }
          QByteArray bytes = jsonFile.readAll();
          jsonFile.close();
  
          QJsonParseError jsonParseError;
          QJsonDocument doc = QJsonDocument::fromJson(bytes, &jsonParseError);
          if (jsonParseError.error != QJsonParseError::NoError) {
              qCWarning(FirmwareUpgradeLog) <<  "Unable to open px4 releases json document" << localFile << jsonParseError.errorString();
              return;
          }
  
          // Json should be an array of release objects
          if (!doc.isArray()) {
              qCWarning(FirmwareUpgradeLog) <<  "px4 releases json document is not an array" << localFile;
              return;
          }
          QJsonArray releases = doc.array();
  
          // The first release marked prerelease=false is stable
          // The first release marked prerelease=true is beta
          bool foundStable = false;
          bool foundBeta = false;
          for (int i=0; i<releases.count() && (!foundStable || !foundBeta); i++) {
              QJsonObject release = releases[i].toObject();
              if (!foundStable && !release["prerelease"].toBool()) {
                  _px4StableVersion = release["name"].toString();
                  emit px4StableVersionChanged(_px4StableVersion);
                  qCDebug(FirmwareUpgradeLog()) << "Found px4 stable version" << _px4StableVersion;
                  foundStable = true;
              } else if (!foundBeta && release["prerelease"].toBool()) {
                  _px4BetaVersion = release["name"].toString();
                  emit px4StableVersionChanged(_px4BetaVersion);
                  qCDebug(FirmwareUpgradeLog()) << "Found px4 beta version" << _px4BetaVersion;
                  foundBeta = true;
              }
          }
  
          if (!foundStable) {
              qCDebug(FirmwareUpgradeLog()) << "Unable to find px4 stable version" << localFile;
          }
          if (!foundBeta) {
              qCDebug(FirmwareUpgradeLog()) << "Unable to find px4 beta version" << localFile;
          }
      } else {
          qCWarning(FirmwareUpgradeLog) << "PX4 releases github download failed" << errorMsg;
      }
  }
  
  void FirmwareUpgradeController::_downloadArduPilotManifest(void)
  {
      _downloadingFirmwareList = true;
      emit downloadingFirmwareListChanged(true);
  
      QGCFileDownload* downloader = new QGCFileDownload(this);
      connect(downloader, &QGCFileDownload::downloadComplete, this, &FirmwareUpgradeController::_ardupilotManifestDownloadComplete);
      downloader->download(QStringLiteral("http://firmware.ardupilot.org/manifest.json.gz"));
  }
  
  void FirmwareUpgradeController::_ardupilotManifestDownloadComplete(QString remoteFile, QString localFile, QString errorMsg)
  {
      if (errorMsg.isEmpty()) {
          // Delete the QGCFileDownload object
          sender()->deleteLater();
  
          qCDebug(FirmwareUpgradeLog) << "_ardupilotManifestDownloadFinished" << remoteFile << localFile;
  
          QString jsonFileName(QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).absoluteFilePath("ArduPilot.Manifest.json"));
          if (!QGCZlib::inflateGzipFile(localFile, jsonFileName)) {
              qCWarning(FirmwareUpgradeLog) << "Inflate of compressed manifest failed" << localFile;
              return;
          }
  
          QString         errorString;
          QJsonDocument   doc;
          if (!JsonHelper::isJsonFile(jsonFileName, doc, errorString)) {
              qCWarning(FirmwareUpgradeLog) << "Json file read failed" << errorString;
              return;
          }
  
          QJsonObject json =          doc.object();
          QJsonArray  rgFirmware =    json[_manifestFirmwareJsonKey].toArray();
  
          for (int i=0; i<rgFirmware.count(); i++) {
              const QJsonObject& firmwareJson = rgFirmware[i].toObject();
  
              FirmwareVehicleType_t   firmwareVehicleType =   _manifestMavTypeToFirmwareVehicleType(firmwareJson[_manifestMavTypeJsonKey].toString());
              FirmwareBuildType_t     firmwareBuildType =     _manifestMavFirmwareVersionTypeToFirmwareBuildType(firmwareJson[_manifestMavFirmwareVersionTypeJsonKey].toString());
              QString                 format =                firmwareJson[_manifestFormatJsonKey].toString();
              QString                 platform =              firmwareJson[_manifestPlatformKey].toString();
  
              if (firmwareVehicleType != DefaultVehicleFirmware && firmwareBuildType != CustomFirmware && (format == QStringLiteral("apj") || format == QStringLiteral("px4"))) {
                  if (platform.contains("-heli") && firmwareVehicleType != HeliFirmware) {
                      continue;
                  }
  
                  _rgManifestFirmwareInfo.append(ManifestFirmwareInfo_t());
                  ManifestFirmwareInfo_t& firmwareInfo = _rgManifestFirmwareInfo.last();
  
                  firmwareInfo.boardId =              static_cast<uint32_t>(firmwareJson[_manifestBoardIdJsonKey].toInt());
                  firmwareInfo.firmwareBuildType =    firmwareBuildType;
                  firmwareInfo.vehicleType =          firmwareVehicleType;
                  firmwareInfo.url =                  firmwareJson[_manifestUrlJsonKey].toString();
                  firmwareInfo.version =              firmwareJson[_manifestMavFirmwareVersionJsonKey].toString();
                  firmwareInfo.chibios =              format == QStringLiteral("apj");                firmwareInfo.fmuv2 =                platform.contains(QStringLiteral("fmuv2"));
  
                  QJsonArray bootloaderArray = firmwareJson[_manifestBootloaderStrJsonKey].toArray();
                  for (int j=0; j<bootloaderArray.count(); j++) {
                      firmwareInfo.rgBootloaderPortString.append(bootloaderArray[j].toString());
                  }
  
                  QJsonArray usbidArray = firmwareJson[_manifestUSBIDJsonKey].toArray();
                  for (int j=0; j<usbidArray.count(); j++) {
                      QStringList vidpid = usbidArray[j].toString().split('/');
                      QString vid = vidpid[0];
                      QString pid = vidpid[1];
  
                      bool ok;
                      firmwareInfo.rgVID.append(vid.right(vid.count() - 2).toInt(&ok, 16));
                      firmwareInfo.rgPID.append(pid.right(pid.count() - 2).toInt(&ok, 16));
                  }
  
                  QString brandName = firmwareJson[_manifestBrandNameKey].toString();
                  firmwareInfo.friendlyName = QStringLiteral("%1 - %2").arg(brandName.isEmpty() ? platform : brandName).arg(firmwareInfo.version);
              }
          }
  
          if (_bootloaderFound) {
              _buildAPMFirmwareNames();
          }
  
          _downloadingFirmwareList = false;
          emit downloadingFirmwareListChanged(false);
      } else {
          qCWarning(FirmwareUpgradeLog) << "ArduPilot Manifest download failed" << errorMsg;
      }
  }
  
  FirmwareUpgradeController::FirmwareBuildType_t FirmwareUpgradeController::_manifestMavFirmwareVersionTypeToFirmwareBuildType(const QString& manifestMavFirmwareVersionType)
  {
      if (_manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap.contains(manifestMavFirmwareVersionType)) {
          return _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap[manifestMavFirmwareVersionType];
      } else {
          return CustomFirmware;
      }
  }
  
  FirmwareUpgradeController::FirmwareVehicleType_t FirmwareUpgradeController::_manifestMavTypeToFirmwareVehicleType(const QString& manifestMavType)
  {
      if (_manifestMavTypeToFirmwareVehicleTypeMap.contains(manifestMavType)) {
          return _manifestMavTypeToFirmwareVehicleTypeMap[manifestMavType];
      } else {
          return DefaultVehicleFirmware;
      }
  }
  