320 lines
9.2 KiB
C++
320 lines
9.2 KiB
C++
/*
|
|
* Copyright (C) EdgeTX
|
|
*
|
|
* Based on code named
|
|
* opentx - https://github.com/opentx/opentx
|
|
* th9x - http://code.google.com/p/th9x
|
|
* er9x - http://code.google.com/p/er9x
|
|
* gruvin9x - http://code.google.com/p/gruvin9x
|
|
*
|
|
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "labeled.h"
|
|
#include "firmwares/opentx/opentxinterface.h"
|
|
#include "firmwares/edgetx/edgetxinterface.h"
|
|
|
|
#include <regex>
|
|
|
|
StorageType LabelsStorageFormat::probeFormat()
|
|
{
|
|
if (QFile(filename + "/RADIO/radio.yml").exists()) // converted
|
|
return getStorageType("radio.yml");
|
|
else
|
|
return getStorageType(filename);
|
|
}
|
|
|
|
bool LabelsStorageFormat::load(RadioData & radioData)
|
|
{
|
|
StorageType st = getStorageType(filename);
|
|
if (st == STORAGE_TYPE_UNKNOWN)
|
|
st = probeFormat();
|
|
|
|
if (getStorageType(filename) == STORAGE_TYPE_UNKNOWN && probeFormat() == STORAGE_TYPE_ETX) {
|
|
if (!QFile(filename + "/" + "RADIO/radio.yml").exists())
|
|
qDebug() << tr("Cannot find %1/RADIO/radio.yml").arg(filename);
|
|
else
|
|
qDebug() << tr("Found %1/RADIO/radio.yml").arg(filename);
|
|
|
|
if (!QFile(filename + "/" + "MODELS/models.yml").exists())
|
|
qDebug() << tr("Cannot find %1/MODELS/models.yml").arg(filename);
|
|
else
|
|
qDebug() << tr("Found %1/MODELS/models.yml").arg(filename);
|
|
}
|
|
else {
|
|
if (!QFile(filename).exists())
|
|
qDebug() << tr("Cannot find %1").arg(filename);
|
|
else
|
|
qDebug() << tr("Found %1").arg(filename);
|
|
}
|
|
|
|
if (!loadRadioSettings(radioData.generalSettings))
|
|
return false;
|
|
|
|
board = (Board::Type)radioData.generalSettings.variant;
|
|
|
|
// get models
|
|
EtxModelfiles modelFiles;
|
|
|
|
// Labels - Read the labels from labels.yml file
|
|
// - Always scan for /MODELS/modelXX.yml
|
|
// - Update possible labels with all found in models too
|
|
|
|
QByteArray labelslistBuffer;
|
|
int sortOrder = 0;
|
|
if (loadFile(labelslistBuffer, "MODELS/labels.yml")) {
|
|
try {
|
|
if (!loadLabelsListFromYaml(radioData.labels, sortOrder, modelFiles, labelslistBuffer)) {
|
|
setError(tr("Can't load MODELS/labels.yml"));
|
|
}
|
|
} catch(const std::runtime_error& e) {
|
|
setError(tr("Can't load MODELS/labels.yml") + ":\n" + QString(e.what()));
|
|
}
|
|
}
|
|
|
|
// Save Sort Order
|
|
radioData.sortOrder = sortOrder;
|
|
|
|
// Scan for all models
|
|
std::list<std::string> filelist;
|
|
if (!getFileList(filelist))
|
|
return false;
|
|
|
|
const std::regex yml_regex("MODELS/(model([0-9]+)\\.yml)", std::regex_constants::icase);
|
|
|
|
for(const auto& f : filelist) {
|
|
std::smatch match;
|
|
if (std::regex_match(f, match, yml_regex)) {
|
|
if (match.size() == 3) {
|
|
std::ssub_match modelFile = match[1];
|
|
std::ssub_match modelIdx = match[2];
|
|
modelFiles.push_back({ modelFile.str(), "", std::stoi(modelIdx.str()) });
|
|
}
|
|
}
|
|
}
|
|
|
|
int modelIdx = 0;
|
|
bool hasLabels = getCurrentFirmware()->getCapability(HasModelLabels);
|
|
|
|
if (hasLabels)
|
|
radioData.models.resize(modelFiles.size());
|
|
|
|
for (const auto& mc : modelFiles) {
|
|
qDebug() << "Filename: " << mc.filename.c_str();
|
|
|
|
if (!hasLabels) {
|
|
if (mc.modelIdx >= 0 && mc.modelIdx < (int)radioData.models.size()) {
|
|
modelIdx = mc.modelIdx;
|
|
if (!radioData.models[modelIdx].isEmpty()) {
|
|
qDebug() << QString("Warning: file %1 skipped as slot %2 already used").arg(mc.filename.c_str()).arg(mc.modelIdx + 1);
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
qDebug() << QString("Warning: file %1 skipped as slot %2 not available").arg(mc.filename.c_str()).arg(mc.modelIdx + 1);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
QByteArray modelBuffer;
|
|
QString filename = "MODELS/" + QString::fromStdString(mc.filename);
|
|
if (!loadFile(modelBuffer, filename)) {
|
|
setError(tr("Cannot extract ") + filename);
|
|
return false;
|
|
}
|
|
|
|
// Please note:
|
|
// ModelData() use memset to clear everything to 0
|
|
//
|
|
auto& model = radioData.models[modelIdx];
|
|
|
|
try {
|
|
if (!loadModelFromYaml(model,modelBuffer)) {
|
|
setError(tr("Cannot load ") + filename);
|
|
return false;
|
|
}
|
|
} catch(const std::runtime_error& e) {
|
|
setError(tr("Cannot load ") + filename + ":\n" + QString(e.what()));
|
|
return false;
|
|
}
|
|
|
|
if (!loadChecklist(model))
|
|
return false;
|
|
|
|
model.modelIndex = modelIdx;
|
|
strncpy(model.filename, mc.filename.c_str(), sizeof(model.filename)-1);
|
|
|
|
if (hasLabels && !strncmp(radioData.generalSettings.currModelFilename, model.filename, sizeof(model.filename)))
|
|
radioData.generalSettings.currModelIndex = modelIdx;
|
|
|
|
model.used = true;
|
|
modelIdx++;
|
|
}
|
|
|
|
// Add the labels in the models
|
|
if (hasLabels) {
|
|
radioData.addLabelsFromModels();
|
|
|
|
// If no labels, add a Favorites
|
|
if(!radioData.labels.size()) {
|
|
RadioData::LabelData ld = { tr("Favorites"), false };
|
|
radioData.labels.append(ld);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LabelsStorageFormat::write(RadioData & radioData)
|
|
{
|
|
// TODO
|
|
// move all unique radio settings to a separate file eg RADIO/hardware.yml.
|
|
// These settings/values should never be transferred to another radio.
|
|
// Also some are at a point in time eg calibration and
|
|
// therefore not restored to the radio from say an etx file
|
|
|
|
// may not exist on a new sd card or path
|
|
if (QFile(filename + "/RADIO/radio.yml").exists()) {
|
|
GeneralSettings gsCur;
|
|
|
|
if (loadRadioSettings(gsCur)) {
|
|
GeneralSettings & gsNew = radioData.generalSettings;
|
|
gsNew.txVoltageCalibration = gsCur.txVoltageCalibration;
|
|
|
|
for (int i = 0; i < CPN_MAX_INPUTS; i++) {
|
|
gsNew.inputConfig[i].calib = gsCur.inputConfig[i].calib;
|
|
}
|
|
|
|
qDebug() << "Hardware specific settings preserved";
|
|
} else {
|
|
setError("Error reading current settings from radio");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
QByteArray radioSettingsBuffer;
|
|
if (!writeRadioSettingsToYaml(radioData.generalSettings, radioSettingsBuffer))
|
|
return false;
|
|
|
|
if (!writeFile(radioSettingsBuffer, "RADIO/radio.yml"))
|
|
return false;
|
|
|
|
bool hasLabels = getCurrentFirmware()->getCapability(HasModelLabels);
|
|
|
|
// fetch "MODELS/modelXX.yml"
|
|
std::list<std::string> filelist;
|
|
if (!getFileList(filelist)) {
|
|
setError(tr("Cannot list files"));
|
|
return false;
|
|
}
|
|
|
|
// Delete all old modelxx.yml from radio MODELS folder before writing new modelxx.yml files
|
|
const std::regex yml_regex("MODELS/(model([0-9s]+)\\.yml)", std::regex_constants::icase);
|
|
for (const auto& f : filelist) {
|
|
std::smatch match;
|
|
if (std::regex_match(f, match, yml_regex)) {
|
|
if (match.size() == 3) {
|
|
if (!deleteFile(QString(f.c_str()))) {
|
|
setError(tr("Error deleting files"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
EtxModelfiles modelFiles;
|
|
for (const auto& model : radioData.models) {
|
|
|
|
if (model.isEmpty())
|
|
continue;
|
|
|
|
QString modelFilename;
|
|
if (hasLabels) {
|
|
std::string ymlFilename = patchFilenameToYaml(model.filename);
|
|
modelFilename = QString("MODELS/%1").arg(QString::fromStdString(ymlFilename));
|
|
modelFiles.push_back({ymlFilename, std::string(model.name)});
|
|
} else {
|
|
modelFilename = QString("MODELS/model%1.yml").arg(model.modelIndex, 2, 10, QLatin1Char('0'));
|
|
}
|
|
|
|
QByteArray modelData;
|
|
writeModelToYaml(model, modelData);
|
|
if (!writeFile(modelData, modelFilename))
|
|
return false;
|
|
|
|
if (!writeChecklist(model))
|
|
return false;
|
|
}
|
|
|
|
if (hasLabels) {
|
|
QByteArray labelsListBuffer;
|
|
if (!writeLabelsListToYaml(radioData, labelsListBuffer) || !writeFile(labelsListBuffer, "MODELS/labels.yml"))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LabelsStorageFormat::loadChecklist(ModelData & model)
|
|
{
|
|
const QString fname("MODELS/" + model.getChecklistFilename());
|
|
//qDebug() << "Searching for checklist file:" << fname;
|
|
|
|
if (!loadFile(model.checklistData, fname, true)) {
|
|
setError(tr("Cannot load ") + fname);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LabelsStorageFormat::writeChecklist(const ModelData & model)
|
|
{
|
|
const QString fname("MODELS/" + model.getChecklistFilename());
|
|
|
|
// not every model has a checklist
|
|
if (!model.checklistData.isEmpty()) {
|
|
//qDebug() << "Writing checklist file:" << fname;
|
|
if (!writeFile(model.checklistData, fname)) {
|
|
setError(tr("Cannot write ") + fname);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LabelsStorageFormat::loadRadioSettings(GeneralSettings & generalSettings)
|
|
{
|
|
QByteArray radioSettingsBuffer;
|
|
|
|
const QString file("RADIO/radio.yml");
|
|
const QString filePath(QDir::toNativeSeparators(filename + "/" + file));
|
|
|
|
if (!loadFile(radioSettingsBuffer, file)) {
|
|
setError(tr("Cannot extract %1").arg(filePath));
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
if (!loadRadioSettingsFromYaml(generalSettings, radioSettingsBuffer)) {
|
|
setError(tr("Cannot load %1").arg(filePath));
|
|
return false;
|
|
}
|
|
} catch(const std::runtime_error& e) {
|
|
setError(tr("Cannot load %1").arg(filePath) + ":\n" + QString(e.what()));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|