From 851d6baa0d57b17bd9202bad7d19b7cadc19a00c Mon Sep 17 00:00:00 2001 From: Nemanja Stojoski Date: Sun, 14 Apr 2024 22:01:23 +0200 Subject: [PATCH 1/2] [GS] Add Imperial Units This patch adds support for imperial units on the Ground Station. Closes #375. --- ground_station/src/config.cpp | 6 +++ ground_station/src/config.hpp | 16 ++++++- ground_station/src/hmi/settings.cpp | 69 +++++++++++++++++++++++++++ ground_station/src/hmi/settings.hpp | 70 +++------------------------- ground_station/src/hmi/window.cpp | 72 ++++++++++++++++++----------- ground_station/src/hmi/window.hpp | 3 +- ground_station/src/systemParser.cpp | 24 +++++++++- ground_station/src/systemParser.hpp | 13 ++---- ground_station/src/utils.hpp | 5 ++ 9 files changed, 174 insertions(+), 104 deletions(-) create mode 100644 ground_station/src/hmi/settings.cpp diff --git a/ground_station/src/config.cpp b/ground_station/src/config.cpp index bfa1f964..f02d76b9 100644 --- a/ground_station/src/config.cpp +++ b/ground_station/src/config.cpp @@ -19,6 +19,7 @@ void Config::save() { systemParser.setLinkPhrase2(config.linkPhrase2); systemParser.setTelemetryMode(static_cast(config.receiverMode)); systemParser.setNeverStopLoggingFlag(config.neverStopLogging); + systemParser.setUnitSystem(config.unitSystem); systemParser.setTimeZone(config.timeZoneOffset); systemParser.setMagCalib(config.mag_calib); systemParser.saveFile("/config.json"); @@ -73,6 +74,11 @@ void Config::load() { } else { console.log.println(config.timeZoneOffset); } + if (!systemParser.getUnitSystem(config.unitSystem)) { + config.unitSystem = UnitSystem::kMetric; + } else { + console.log.println(unit_map[static_cast(config.unitSystem)]); + } config.neverStopLogging = stop; config.receiverMode = static_cast(mode); diff --git a/ground_station/src/config.hpp b/ground_station/src/config.hpp index 239e32ab..5899f67f 100644 --- a/ground_station/src/config.hpp +++ b/ground_station/src/config.hpp @@ -4,12 +4,23 @@ #pragma once -#include "systemParser.hpp" - #include +#include "hmi/settings.hpp" + enum ReceiverTelemetryMode_e : bool { SINGLE = false, DUAL = true }; +enum class UnitSystem : uint8_t { kMetric = 0, kImperial = 1 }; + +struct mag_calib_t { + int32_t mag_offset_x; + int32_t mag_offset_y; + int32_t mag_offset_z; + int32_t mag_scale_x; + int32_t mag_scale_y; + int32_t mag_scale_z; +}; + // Maximum number of characters for link & test phrases inline constexpr uint32_t kMaxPhraseLen = 16; @@ -21,6 +32,7 @@ struct systemConfig_t { char linkPhrase2[kMaxPhraseLen + 1]; char testingPhrase[kMaxPhraseLen + 1]; mag_calib_t mag_calib; + UnitSystem unitSystem; }; class Config { diff --git a/ground_station/src/hmi/settings.cpp b/ground_station/src/hmi/settings.cpp new file mode 100644 index 00000000..1cd02d87 --- /dev/null +++ b/ground_station/src/hmi/settings.cpp @@ -0,0 +1,69 @@ +/// Copyright (C) 2020, 2024 Control and Telemetry Systems GmbH +/// +/// SPDX-License-Identifier: GPL-3.0-or-later + +#include "hmi/settings.hpp" +#include "config.hpp" + +// NOLINTNEXTLINE(cppcoreguidelines-interfaces-global-init) +const device_settings_t settingsTable[][4] = { + { + {"Stop Logging", + "Down: Stop the log at touchdown", + "Never: Never stop logging after liftoff", + TOGGLE, + {.lookup = TABLE_LOGGING}, + &systemConfig.config.neverStopLogging}, + + { + "Version", + "Firmware Version: " FIRMWARE_VERSION, + "", + BUTTON, + {.fun_ptr = nullptr}, + nullptr, + }, + { + "Start Bootloader", + "Press A to start the bootloader", + "Make sure you are connected to a computer", + BUTTON, + {.fun_ptr = Utils::startBootloader}, + nullptr, + }, + }, + { + {"Mode", + "Single: Use both receiver to track one rocket", + "Dual: Use both receivers individually", + TOGGLE, + {.lookup = TABLE_MODE}, + &systemConfig.config.receiverMode}, + {"Link Phrase 1", + "Single Mode: Set phrase for both receivers", + "Dual Mode: Set phrase for the left receiver", + STRING, + {.stringLength = kMaxPhraseLen}, + systemConfig.config.linkPhrase1}, + {"Link Phrase 2", + "Single Mode: No functionality", + "Dual Mode: Set phrase for the right receiver", + STRING, + {.stringLength = kMaxPhraseLen}, + systemConfig.config.linkPhrase2}, + {"Testing Phrase", + "Set the phrase for the testing mode", + "", + STRING, + {.stringLength = kMaxPhraseLen}, + systemConfig.config.testingPhrase}, + }, + { + {"Time Zone", + "Time offset relative to UTC", + "", + NUMBER, + {.minmax = {.min = -12, .max = 12}}, + &systemConfig.config.timeZoneOffset}, + {"Units", "Metric: meters", "Imperial: feet", TOGGLE, {.lookup = TABLE_UNIT}, &systemConfig.config.unitSystem}, + }}; diff --git a/ground_station/src/hmi/settings.hpp b/ground_station/src/hmi/settings.hpp index 2b2ae91f..b974710a 100644 --- a/ground_station/src/hmi/settings.hpp +++ b/ground_station/src/hmi/settings.hpp @@ -52,7 +52,7 @@ const char* const mode_map[2] = { const char* const unit_map[2] = { "METRIC", - "RETARDED", + "IMPERIAL", }; const char* const logging_map[2] = { @@ -76,69 +76,11 @@ const lookup_table_entry_t lookup_tables[] = { }; enum { - kSettingPages = 2, + kSettingPages = 3, }; -const char* const settingPageName[kSettingPages] = {"General", "Telemetry"}; - -// NOLINTNEXTLINE(cppcoreguidelines-interfaces-global-init) -const device_settings_t settingsTable[][4] = { - { - {"Time Zone", - "Set the time offset", - "", - NUMBER, - {.minmax = {.min = -12, .max = 12}}, - &systemConfig.config.timeZoneOffset}, - {"Stop Logging", - "Down: Stop the log at touchdown", - "Never: Never stop logging after liftoff", - TOGGLE, - {.lookup = TABLE_LOGGING}, - &systemConfig.config.neverStopLogging}, - { - "Version", - "Firmware Version: " FIRMWARE_VERSION, - "", - BUTTON, - {.fun_ptr = nullptr}, - nullptr, - }, - { - "Start Bootloader", - "Press A to start the bootloader", - "Make sure you are connected to a computer", - BUTTON, - {.fun_ptr = Utils::startBootloader}, - nullptr, - }, - }, - { - {"Mode", - "Single: Use both receiver to track one rocket", - "Dual: Use both receivers individually", - TOGGLE, - {.lookup = TABLE_MODE}, - &systemConfig.config.receiverMode}, - {"Link Phrase 1", - "Single Mode: Set phrase for both receivers", - "Dual Mode: Set phrase for the left receiver", - STRING, - {.stringLength = kMaxPhraseLen}, - systemConfig.config.linkPhrase1}, - {"Link Phrase 2", - "Single Mode: No functionality", - "Dual Mode: Set phrase for the right receiver", - STRING, - {.stringLength = kMaxPhraseLen}, - systemConfig.config.linkPhrase2}, - {"Testing Phrase", - "Set the phrase for the testing mode", - "", - STRING, - {.stringLength = kMaxPhraseLen}, - systemConfig.config.testingPhrase}, - }, -}; +const char* const settingPageName[kSettingPages] = {"General", "Telemetry", "Location"}; + +extern const device_settings_t settingsTable[][4]; -const uint16_t settingsTableValueCount[kSettingPages] = {4, 4}; +const uint16_t settingsTableValueCount[kSettingPages] = {3, 4, 2}; diff --git a/ground_station/src/hmi/window.cpp b/ground_station/src/hmi/window.cpp index 08463b0e..4a299179 100644 --- a/ground_station/src/hmi/window.cpp +++ b/ground_station/src/hmi/window.cpp @@ -4,6 +4,8 @@ #include "window.hpp" #include "bmp.hpp" +#include "config.hpp" +#include "utils.hpp" #include #include @@ -362,12 +364,25 @@ void Window::updateLiveData(TelemetryData *data, int16_t index, uint16_t color) drawCentreString(stateName[data->state()], static_cast(xOffset + 100), 42); display.setCursor(static_cast(xOffset + 35), 70); - display.print(data->altitude()); - display.print(" m"); + const int32_t altitude_m = data->altitude(); + if (systemConfig.config.unitSystem == UnitSystem::kMetric) { + display.print(altitude_m); + display.print(" m"); + } else { + display.print(Utils::MetersToFeet(altitude_m)); + display.print(" ft"); + } display.setCursor(static_cast(xOffset + 35), 95); - display.print(data->velocity()); - display.print(" m/s"); + + const int16_t velocity_m_s = data->velocity(); + if (systemConfig.config.unitSystem == UnitSystem::kMetric) { + display.print(velocity_m_s); + display.print(" m/s"); + } else { + display.print(Utils::MetersToFeet(velocity_m_s)); + display.print(" ft/s"); + } display.setCursor(static_cast(xOffset + 35), 120); display.print(data->lat(), 4); @@ -515,8 +530,14 @@ void Window::updateRecovery(Navigation *navigation) { } display.setCursor(70, 170); - display.print(navigation->getDistance()); - display.print("m"); + const float distance_m = navigation->getDistance(); + if (systemConfig.config.unitSystem == UnitSystem::kMetric) { + display.print(distance_m); + display.print(" m"); + } else { + display.print(Utils::MetersToFeet(distance_m)); + display.print(" ft"); + } display.setFont(&FreeSans9pt7b); @@ -1026,7 +1047,7 @@ void Window::initSensorCalibrateDone() { display.refresh(); } -void Window::initSettings(int16_t submenu) { +void Window::initSettings(int16_t submenuIdx) { clearMainScreen(); display.drawLine(0, 49, 400, 49, BLACK); @@ -1036,21 +1057,17 @@ void Window::initSettings(int16_t submenu) { display.setTextColor(WHITE); display.fillRect(0, 19, 400, 30, BLACK); - drawCentreString(settingPageName[submenu], 200, 42); + drawCentreString(settingPageName[submenuIdx], 200, 42); display.setTextColor(BLACK); - for (int i = 0; i < settingsTableValueCount[submenu]; i++) { - addSettingEntry(i, &settingsTable[submenu][i]); + for (int i = 0; i < settingsTableValueCount[submenuIdx]; i++) { + addSettingEntry(i, &settingsTable[submenuIdx][i]); } - if (submenu == 0) { - display.fillTriangle(386, 33, 378, 25, 378, 41, WHITE); - } else { - display.fillTriangle(13, 33, 21, 25, 21, 41, WHITE); - } + drawSettingsTriangles(submenuIdx, WHITE); oldSettingsIndex = -1; - subMenuSettingIndex = submenu; + subMenuSettingIndex = submenuIdx; display.drawLine(0, 177, 400, 177, BLACK); display.refresh(); @@ -1112,21 +1129,13 @@ void Window::updateSettings(int16_t index) { highlightSetting(oldSettingsIndex, BLACK); } } else { - if (subMenuSettingIndex == 0) { - display.fillTriangle(386, 33, 378, 25, 378, 41, BLACK); - } else { - display.fillTriangle(13, 33, 21, 25, 21, 41, BLACK); - } + drawSettingsTriangles(subMenuSettingIndex, BLACK); } if (index >= 0) { highlightSetting(index, WHITE); } else { - if (subMenuSettingIndex == 0) { - display.fillTriangle(386, 33, 378, 25, 378, 41, WHITE); - } else { - display.fillTriangle(13, 33, 21, 25, 21, 41, WHITE); - } + drawSettingsTriangles(subMenuSettingIndex, WHITE); display.fillRect(0, 178, 400, 62, WHITE); } @@ -1134,6 +1143,17 @@ void Window::updateSettings(int16_t index) { display.refresh(); } +void Window::drawSettingsTriangles(int16_t submenuIdx, int16_t color) { + if (submenuIdx == 0) { // first page + display.fillTriangle(386, 33, 378, 25, 378, 41, color); + } else if (submenuIdx == kSettingPages - 1) { // last page + display.fillTriangle(13, 33, 21, 25, 21, 41, color); + } else { // other pages + display.fillTriangle(386, 33, 378, 25, 378, 41, color); + display.fillTriangle(13, 33, 21, 25, 21, 41, color); + } +} + void Window::highlightSetting(int16_t index, uint16_t color) { display.setFont(&FreeSans12pt7b); const auto yPos = static_cast(52 + 30 * index); diff --git a/ground_station/src/hmi/window.hpp b/ground_station/src/hmi/window.hpp index ee1d5890..dabd4256 100644 --- a/ground_station/src/hmi/window.hpp +++ b/ground_station/src/hmi/window.hpp @@ -66,8 +66,9 @@ class Window { void initSensorCalibrateDone(); void updateSensors(Navigation *navigation); - void initSettings(int16_t submenu); + void initSettings(int16_t submenuIdx); void updateSettings(int16_t index); + void drawSettingsTriangles(int16_t submenuIdx, int16_t color); void initBox(const char *text); diff --git a/ground_station/src/systemParser.cpp b/ground_station/src/systemParser.cpp index b610b3a0..8bd1406b 100644 --- a/ground_station/src/systemParser.cpp +++ b/ground_station/src/systemParser.cpp @@ -4,13 +4,15 @@ #include "systemParser.hpp" +#include + +#include + #include "USB.h" #include "config.hpp" #include "console.hpp" #include "utils.hpp" -#include - SystemParser::SystemParser() = default; /** @@ -94,6 +96,11 @@ bool SystemParser::setMagCalib(mag_calib_t calib) { return true; } +bool SystemParser::setUnitSystem(UnitSystem unit_system) { + doc["unit_system"] = unit_map[static_cast(unit_system)]; + return true; +} + bool SystemParser::getLinkPhrase1(char* phrase) { if (doc.containsKey("link_phrase_1") && phrase != nullptr) { strncpy(phrase, doc["link_phrase_1"].as(), kMaxPhraseLen + 1); @@ -156,6 +163,19 @@ bool SystemParser::getMagCalib(mag_calib_t& calib) { return false; } +bool SystemParser::getUnitSystem(UnitSystem& unit_system) { + if (doc.containsKey("unit_system")) { + if (doc["unit_system"].as() == + std::string_view{unit_map[static_cast(UnitSystem::kImperial)]}) { + unit_system = UnitSystem::kImperial; + } else { + unit_system = UnitSystem::kMetric; + } + return true; + } + return false; +} + // NOLINTEND(readability-convert-member-functions-to-static,readability-simplify-boolean-expr) /** diff --git a/ground_station/src/systemParser.hpp b/ground_station/src/systemParser.hpp index 4c500aa4..652f490f 100644 --- a/ground_station/src/systemParser.hpp +++ b/ground_station/src/systemParser.hpp @@ -6,16 +6,9 @@ #include -constexpr uint32_t MAX_SYSTEM_FILE_SIZE = 1 * 1024UL; +#include "config.hpp" -struct mag_calib_t { - int32_t mag_offset_x; - int32_t mag_offset_y; - int32_t mag_offset_z; - int32_t mag_scale_x; - int32_t mag_scale_y; - int32_t mag_scale_z; -}; +constexpr uint32_t MAX_SYSTEM_FILE_SIZE = 1 * 1024UL; class SystemParser { public: @@ -29,6 +22,7 @@ class SystemParser { bool setTimeZone(int16_t timezone); bool setTelemetryMode(bool mode); bool setMagCalib(mag_calib_t calib); + bool setUnitSystem(UnitSystem unit_system); bool getLinkPhrase1(char* phrase); bool getLinkPhrase2(char* phrase); @@ -37,6 +31,7 @@ class SystemParser { bool getTimeZone(int16_t& timezone); bool getTelemetryMode(bool& mode); bool getMagCalib(mag_calib_t& calib); + bool getUnitSystem(UnitSystem& unit_system); bool saveFile(const char* path = nullptr); diff --git a/ground_station/src/utils.hpp b/ground_station/src/utils.hpp index 5f6821e2..821775ed 100644 --- a/ground_station/src/utils.hpp +++ b/ground_station/src/utils.hpp @@ -31,6 +31,11 @@ class Utils { explicit operator bool() const { return mscReady; } + template + [[nodiscard]] constexpr static T MetersToFeet(T meters) { + return static_cast(meters * 3.28084); + } + private: const char *serial = "0"; volatile bool mscReady = false; From 8c7391be14056abd861041cfed95c8eb1863bf1a Mon Sep 17 00:00:00 2001 From: Nemanja Stojoski Date: Sat, 25 May 2024 20:07:47 +0200 Subject: [PATCH 2/2] Fix issue introduced by PR#379. --- .github/workflows/merge-criteria.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-criteria.yml b/.github/workflows/merge-criteria.yml index 04844aee..601166d0 100644 --- a/.github/workflows/merge-criteria.yml +++ b/.github/workflows/merge-criteria.yml @@ -46,7 +46,7 @@ jobs: contains_dotgithub=$(echo '${{ steps.changed-files.outputs.all_changed_files }}' | jq 'map(select(. == ".github")) | length') is_manual_trigger=$(echo '${{ github.event.inputs.commit_sha }}') - if [[ -z "$is_manual_trigger" || "${GITHUB_REF}" == "refs/heads/main" || $contains_dot -gt 0 || $contains_dotgithub -gt 0 ]]; then + if [[ -n "$is_manual_trigger" || "${GITHUB_REF}" == "refs/heads/main" || $contains_dot -gt 0 || $contains_dotgithub -gt 0 ]]; then filtered_array='["flight_computer","ground_station","telemetry"]' else filtered_array='${{ steps.changed-files.outputs.all_changed_files }}'