From d0293013ec5cb0d02f47c69be7ec3d026fc11539 Mon Sep 17 00:00:00 2001 From: Raphael Coeffic Date: Tue, 30 Nov 2021 13:39:44 +0100 Subject: [PATCH] WiP: Add model loading --- companion/src/firmwares/CMakeLists.txt | 1 + .../src/firmwares/edgetx/edgetxinterface.cpp | 37 ++++- .../src/firmwares/edgetx/edgetxinterface.h | 11 ++ .../firmwares/edgetx/yaml_generalsettings.h | 20 ++- .../src/firmwares/edgetx/yaml_modeldata.cpp | 43 ++++++ .../src/firmwares/edgetx/yaml_modeldata.h | 140 ++++++++++++++++++ companion/src/firmwares/edgetx/yaml_ops.h | 20 +++ companion/src/storage/categorized.cpp | 53 ++++++- 8 files changed, 315 insertions(+), 10 deletions(-) create mode 100644 companion/src/firmwares/edgetx/yaml_modeldata.cpp create mode 100644 companion/src/firmwares/edgetx/yaml_modeldata.h diff --git a/companion/src/firmwares/CMakeLists.txt b/companion/src/firmwares/CMakeLists.txt index e419ad923..0b3d501da 100644 --- a/companion/src/firmwares/CMakeLists.txt +++ b/companion/src/firmwares/CMakeLists.txt @@ -34,6 +34,7 @@ set(firmwares_SRCS edgetx/yaml_generalsettings.cpp edgetx/yaml_calibdata.cpp edgetx/yaml_switchconfig.cpp + edgetx/yaml_modeldata.cpp datahelpers.cpp customisation_data.cpp ) diff --git a/companion/src/firmwares/edgetx/edgetxinterface.cpp b/companion/src/firmwares/edgetx/edgetxinterface.cpp index b38f0fbc0..ddab0fc0d 100644 --- a/companion/src/firmwares/edgetx/edgetxinterface.cpp +++ b/companion/src/firmwares/edgetx/edgetxinterface.cpp @@ -22,6 +22,7 @@ #include "yaml_ops.h" #include "yaml_generalsettings.h" +#include "yaml_modeldata.h" static YAML::Node loadYamlFromByteArray(const QByteArray& data) { @@ -30,11 +31,45 @@ static YAML::Node loadYamlFromByteArray(const QByteArray& data) return YAML::Load(data_istream); } +bool loadModelsListFromYaml(std::vector categories, + EtxModelfiles& modelFiles, + const QByteArray& data) +{ + try { + YAML::Node node = loadYamlFromByteArray(data); + if (!node.IsSequence()) return false; + + for (const auto& cat : node) { + + if (!cat.IsMap()) continue; + + for (const auto& cat_map : cat) { + categories.push_back(cat_map.first.Scalar().c_str()); + + const auto& models = cat_map.second; + if (!models.IsSequence()) continue; + + for (const auto& model : models) { + std::string filename; + model["filename"] >> filename; + modelFiles.push_back({ filename, categories.size()-1 }); + } + } + } + + } catch (const std::runtime_error& e) { + qDebug() << "YAML::ParserException: " << e.what(); + return false; + } + + return true; +} + bool loadModelFromYaml(ModelData& model, const QByteArray& data) { try { YAML::Node node = loadYamlFromByteArray(data); - //model << node; + node >> model; } catch (const std::runtime_error& e) { qDebug() << "YAML::ParserException: " << e.what(); return false; diff --git a/companion/src/firmwares/edgetx/edgetxinterface.h b/companion/src/firmwares/edgetx/edgetxinterface.h index bdfe01bf7..1a87cb181 100644 --- a/companion/src/firmwares/edgetx/edgetxinterface.h +++ b/companion/src/firmwares/edgetx/edgetxinterface.h @@ -23,6 +23,17 @@ #include "radiodata.h" #include +#include +#include +#include + +typedef std::pair EtxModelfileCategoryPair; +typedef std::list EtxModelfiles; + +bool loadModelsListFromYaml(std::vector categories, + EtxModelfiles& modelFiles, + const QByteArray& data); + bool loadModelFromYaml(ModelData& model, const QByteArray& data); bool loadRadioSettingsFromYaml(GeneralSettings& settings, const QByteArray& data); diff --git a/companion/src/firmwares/edgetx/yaml_generalsettings.h b/companion/src/firmwares/edgetx/yaml_generalsettings.h index ef3d8d144..47307816b 100644 --- a/companion/src/firmwares/edgetx/yaml_generalsettings.h +++ b/companion/src/firmwares/edgetx/yaml_generalsettings.h @@ -47,16 +47,24 @@ namespace YAML { static bool decode(const Node& node, GeneralSettings& rhs) { + if (!node.IsMap()) return false; + + // for (const auto& n : node) { + // qDebug() << QString::fromStdString(n.first.Scalar()); + // } + // TODO: check board string and fetch Board instance std::string board; node["board"] >> board; - // TODO: we should be able to fetch the board based on 'board' - auto fw = getCurrentFirmware(); - if (fw && fw->getId().toStdString() != ("edgetx-" + board)) { - throw std::runtime_error("Wrong board"); - } - + // auto fw = getCurrentFirmware(); + // if (fw) { + // qDebug() << "FW Id: " << fw->getId(); + // if (fw->getId() != ("edgetx-" + radioData.generalSettings.board)) { + // throw std::runtime_error("Wrong board"); + // } + // } + node["version"] >> rhs.version; // Radio calib diff --git a/companion/src/firmwares/edgetx/yaml_modeldata.cpp b/companion/src/firmwares/edgetx/yaml_modeldata.cpp new file mode 100644 index 000000000..968f7949a --- /dev/null +++ b/companion/src/firmwares/edgetx/yaml_modeldata.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) OpenTX + * + * Based on code named + * 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 "yaml_modeldata.h" +#include "modeldata.h" + +const YamlLookupTable timerModeLut = { + {TimerData::TIMERMODE_OFF, "OFF"}, + {TimerData::TIMERMODE_ON, "ON"}, + {TimerData::TIMERMODE_START, "START"}, + {TimerData::TIMERMODE_THR, "THR"}, + {TimerData::TIMERMODE_THR_REL, "THR_REL"}, + {TimerData::TIMERMODE_THR_START, "THR_START"}, +}; + +const YamlLookupTable trainerModeLut = { + { TRAINER_MODE_OFF, "OFF" }, + { TRAINER_MODE_MASTER_JACK, "MASTER_TRAINER_JACK" }, + { TRAINER_MODE_SLAVE_JACK, "SLAVE" }, + { TRAINER_MODE_MASTER_SBUS_EXTERNAL_MODULE, "MASTER_SBUS_EXT" }, + { TRAINER_MODE_MASTER_CPPM_EXTERNAL_MODULE, "MASTER_CPPM_EXT" }, + { TRAINER_MODE_MASTER_BATTERY_COMPARTMENT, "MASTER_BATT_COMP" }, + { TRAINER_MODE_MASTER_BLUETOOTH, "MASTER_BT" }, + { TRAINER_MODE_SLAVE_BLUETOOTH, "SLAVE_BT" }, + { TRAINER_MODE_MULTI, "MASTER_MULTI" }, +}; diff --git a/companion/src/firmwares/edgetx/yaml_modeldata.h b/companion/src/firmwares/edgetx/yaml_modeldata.h new file mode 100644 index 000000000..6573a02ef --- /dev/null +++ b/companion/src/firmwares/edgetx/yaml_modeldata.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) OpenTX + * + * Based on code named + * 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 "yaml_ops.h" +#include "modeldata.h" + +#include + +extern const YamlLookupTable timerModeLut; +extern const YamlLookupTable trainerModeLut; + +// extern const YamlLookupTable backlightModeLut; +// extern const YamlLookupTable bluetoothModeLut; +// extern const YamlLookupTable uartModeLut; +// extern const YamlLookupTable antennaModeLut; + +namespace YAML { + +// ENUM_CONVERTER(GeneralSettings::BeeperMode, beeperModeLut); + +template <> +struct convert { + // static YAML::Node encode(const GeneralSettings& rhs) { + // Node node; + // // TODO: convert struct into Node + // return node; + // } + + static bool decode(const Node& node, TimerData& rhs) + { + // TODO: node["swtch"] >> rhs.swtch; + node["mode"] >> timerModeLut >> rhs.mode; + node["name"] >> rhs.name; + node["minuteBeep"] >> rhs.minuteBeep; + node["countdownBeep"] >> rhs.countdownBeep; + node["start"] >> rhs.val; + node["persistent"] >> rhs.persistent; + node["countdownStart"] >> rhs.countdownStart; + node["value"] >> rhs.pvalue; + return true; + } +}; + +template <> +struct convert { + // static YAML::Node encode(const GeneralSettings& rhs) { + // Node node; + // // TODO: convert struct into Node + // return node; + // } + + static bool decode(const Node& node, ModelData& rhs) + { + if (!node.IsMap()) return false; + + if (node["header"]) { + const auto& header = node["header"]; + if (!header.IsMap()) return false; + + header["name"] >> rhs.name; + header["bitmap"] >> rhs.bitmap; + // TODO: modelId + } + + if (node["timers"]) { + const auto& timers = node["timers"]; + if (!timers.IsMap()) return false; + timers >> rhs.timers; + } + + node["noGlobalFunctions"] >> rhs.noGlobalFunctions; + node["thrTrim"] >> rhs.thrTrim; + node["trimInc"] >> rhs.trimInc; + node["displayTrims"] >> rhs.trimsDisplay; + node["disableThrottleWarning"] >> rhs.disableThrottleWarning; + //node[] >> rhs.beepANACenter; + node["extendedLimits"] >> rhs.extendedLimits; + node["extendedTrims"] >> rhs.extendedTrims; + node["throttleReversed"] >> rhs.throttleReversed; + + //flightModeData[] + //mixData[] + //limitData[] + //inputNames[] + //expoData[] + //curves[] + //logicalSw[] + //customFn[] + + //swashRingData + //thrTraceSrc + //switchWarningStates + //thrTrimSwitch + //potsWarningMode + //potsWarnEnabled[] + //potsWarnPosition[] + + node["displayChecklist"] >> rhs.displayChecklist; + + //gvarData[] + //mavlink ??? + //telemetryProtocol ??? + //frsky + //rssiSource + //rssiAlarms + + node["trainerMode"] >> trainerModeLut >> rhs.trainerMode; + + //moduleData[] + //scriptData[] + //sensorData[] + + node["toplcdTimer"] >> rhs.toplcdTimer; + + //customScreens + //topBarData + node["view"] >> rhs.view; + node["modelRegistrationID"] >> rhs.registrationId; + + return true; + } +}; +} // namespace YAML diff --git a/companion/src/firmwares/edgetx/yaml_ops.h b/companion/src/firmwares/edgetx/yaml_ops.h index d4d6c255d..af79fba0c 100644 --- a/companion/src/firmwares/edgetx/yaml_ops.h +++ b/companion/src/firmwares/edgetx/yaml_ops.h @@ -21,6 +21,7 @@ #pragma once #include +#include typedef std::pair YamlLookupTableElmt; typedef std::vector YamlLookupTable; @@ -91,6 +92,25 @@ void operator >> (const YAML::Node& node, char (&value)[N]) } } +template +void operator>>(const YAML::Node& node, T (&value)[N]) +{ + if (!node.IsMap()) return;// false; + + for (const auto& elmt : node) { + try { + int idx = std::stoi(elmt.first.Scalar()); + if (idx < 0 || idx >= N) return;// false; + + elmt.second >> value[idx]; + } catch (...) { + //return false; + return; + } + } + //return true; +} + #define ENUM_CONVERTER(enum_type, lut) \ template <> \ struct convert { \ diff --git a/companion/src/storage/categorized.cpp b/companion/src/storage/categorized.cpp index 984c01842..24f0c9979 100644 --- a/companion/src/storage/categorized.cpp +++ b/companion/src/storage/categorized.cpp @@ -175,7 +175,7 @@ bool CategorizedStorageFormat::writeBin(const RadioData & radioData) if (!getCurrentFirmware()->getCapability(HasModelCategories)) { // Use format with model number and file name. This is needed because // radios without category support can have unused model slots - modelsList.append(QString("%1 %2\n").arg(m).arg(model.filename)); + modelsList.append(QString("%1 %2\n").arg(m).arg(model.filename).toLocal8Bit()); } else { sortedModels[model.category].push_back(QString("%1\n").arg(model.filename)); } @@ -183,10 +183,12 @@ bool CategorizedStorageFormat::writeBin(const RadioData & radioData) if (getCurrentFirmware()->getCapability(HasModelCategories)) { for (size_t c=0; c + EtxModelfiles modelFiles; + + if (!loadModelsListFromYaml(radioData.categories, modelFiles, modelslistBuffer)) { + setError(tr("Can't load MODELS/models.yml")); + return false; + } + + radioData.models.reserve(modelFiles.size()); + + int modelIdx = 0; + for (const auto& mc : modelFiles) { + qDebug() << "Filename: " << mc.first.c_str() << " / Category: " << mc.second; + + QByteArray modelBuffer; + QString filename = "MODELS/" + QString::fromStdString(mc.first); + if (!loadFile(modelBuffer, filename)) { + setError(tr("Can't extract ") + filename); + return false; + } + + // Please note: + // ModelData() use memset to clear everything to 0 + // + auto& model = radioData.models[modelIdx]; + model.category = mc.second; + model.modelIndex = modelIdx; + strncpy(model.filename, mc.first.c_str(), sizeof(model.filename)-1); + + if (!loadModelFromYaml(radioData.models[modelIdx],modelBuffer)) { + setError(tr("Can't load ") + filename); + return false; + } + + modelIdx++; + } + + } else { + // fetch "MODELS/modelXX.yml" + } + return true; }