From d7d1be4ca95afa688caae680915dda63b189c9ef Mon Sep 17 00:00:00 2001 From: kongr45gpen <electrovesta@gmail.com> Date: Sun, 4 Sep 2022 18:29:51 +0000 Subject: [PATCH] Define lazy, function-based parameters --- inc/ErrorHandler.hpp | 12 ++ inc/Helpers/LazyParameter.hpp | 105 ++++++++++++++++++ inc/Helpers/Parameter.hpp | 26 +++-- inc/etl/String.hpp | 2 +- lib/etl | 2 +- test/Parameters/LazyParameterTests.cpp | 83 ++++++++++++++ .../ParameterTests.cpp} | 0 7 files changed, 216 insertions(+), 14 deletions(-) create mode 100644 inc/Helpers/LazyParameter.hpp create mode 100644 test/Parameters/LazyParameterTests.cpp rename test/{Services/Parameter.cpp => Parameters/ParameterTests.cpp} (100%) diff --git a/inc/ErrorHandler.hpp b/inc/ErrorHandler.hpp index 351a75ca..e9b341bf 100644 --- a/inc/ErrorHandler.hpp +++ b/inc/ErrorHandler.hpp @@ -344,6 +344,18 @@ public: * ID is not controlled by the Service (ST[14]) */ NotControlledApplication = 50, + /** + * Parameter is requested, but the provider of the parameter value does not exist yet + */ + ParameterValueMissing = 51, + /** + * Attempted to write to a read-only parameter + */ + ParameterReadOnly = 52, + /** + * Attempted to read from a write-only parameter + */ + ParameterWriteOnly = 53, }; /** diff --git a/inc/Helpers/LazyParameter.hpp b/inc/Helpers/LazyParameter.hpp new file mode 100644 index 00000000..d28c3baf --- /dev/null +++ b/inc/Helpers/LazyParameter.hpp @@ -0,0 +1,105 @@ +#ifndef ECSS_SERVICES_LAZYPARAMETER_HPP +#define ECSS_SERVICES_LAZYPARAMETER_HPP + +#include <etl/optional.h> +#include <functional> +#include "Parameter.hpp" + +/** + * A Lazy Parameter is a ParameterService parameter that does not keep a value in + * memory, but calls an external function to fetch a new value whenever needed. + * + * The LazyParameter allows its users to call expensive value-fetching operators + * ONLY when a value is requested. This prevents having to update a value in + * memory every so often. + * + * This "lazy" fetching is useful when it is expensive (in terms of time, power + * etc.) to get updated values, e.g. from peripherals or difficult calculations. + * + * @warning This class is NOT re-entrant. The developer will have to make sure + * that only one thread has access to it at a time, otherwise undefined behaviour + * will occur. + * + * @tparam DataType The data type of the parameter's value + */ +template <typename DataType> +class LazyParameter : public ParameterBase { +public: + /** + * The type of the function that returns the current value of this parameter. + */ + using Getter = std::function<DataType()>; + + /** + * LazyParameter constructor without a getter function. + * + * When a getter function is not present, an error may be shown when fetching a value + * for the parameter. A @p fallback value will be returned in this case, if needed. + * @param fallback + */ + explicit LazyParameter(const DataType& fallback = 0) : fallback(fallback) {} + + /** + * LazyParameter constructor with a pre-defined getter function + * @param getter + * @param fallback + */ + explicit LazyParameter(const Getter& getter, const DataType& fallback = 0) : getter(getter), fallback(fallback) {} + + /** + * Set a getter function for this parameter. The getter function is called + * whenever a value for this parameter is requested. + */ + void setGetter(const Getter& _getter) { + LazyParameter::getter = _getter; + } + + /** + * Remove the getter function of this parameter. + */ + void unsetGetter() { + getter.reset(); + } + + /** + * Get the current value of this parameter, if the getter is defined. + * + * @note This function may take some time to return a value, since it calls + * the "expensive" getter function. + */ + etl::optional<DataType> getValue() { + if (getter) { + return (*getter)(); + } else { + return {}; + } + } + + inline double getValueAsDouble() override { + if constexpr (std::is_arithmetic_v<DataType>) { + return static_cast<double>(getValue().value_or(fallback)); + } else { + return 0; + } + } + + inline void appendValueToMessage(Message& message) override { + if (getter) { + message.append<DataType>((*getter)()); + } else { + message.append<DataType>(fallback); + ErrorHandler::reportError(message, ErrorHandler::ParameterValueMissing); + } + }; + + inline void setValueFromMessage(Message& message) override { + [[maybe_unused]] auto skippedBytes = message.read<DataType>(); + ErrorHandler::reportError(message, ErrorHandler::ParameterReadOnly); + }; +private: + etl::optional<Getter> getter; + DataType fallback; +}; + + +#endif //ECSS_SERVICES_LAZYPARAMETER_HPP diff --git a/inc/Helpers/Parameter.hpp b/inc/Helpers/Parameter.hpp index 3ce19915..60241984 100644 --- a/inc/Helpers/Parameter.hpp +++ b/inc/Helpers/Parameter.hpp @@ -32,8 +32,22 @@ */ class ParameterBase { public: + /** + * Given an ECSS message that contains this parameter as its first input, this loads the value from that parameter + */ virtual void appendValueToMessage(Message& message) = 0; + + /** + * Appends the parameter as an ECSS value to an ECSS Message + */ virtual void setValueFromMessage(Message& message) = 0; + + /** + * Converts the value of a parameter to a double. + * + * Some precision may be lost in the process. If the value is not arithmetic, + * then usually 0 is returned. + */ virtual double getValueAsDouble() = 0; }; @@ -58,12 +72,6 @@ public: return currentValue; } - /** - * Converts the value of a parameter to a double. - * - * Some precision may be lost in the process. If the value is not arithmetic, - * then 0 is returned. - */ inline double getValueAsDouble() override { if constexpr (std::is_arithmetic_v<DataType>) { return static_cast<double>(currentValue); @@ -72,16 +80,10 @@ public: } } - /** - * Given an ECSS message that contains this parameter as its first input, this loads the value from that paremeter - */ inline void setValueFromMessage(Message& message) override { currentValue = message.read<DataType>(); }; - /** - * Appends the parameter as an ECSS value to an ECSS Message - */ inline void appendValueToMessage(Message& message) override { message.append<DataType>(currentValue); }; diff --git a/inc/etl/String.hpp b/inc/etl/String.hpp index 7691f783..c97573e9 100644 --- a/inc/etl/String.hpp +++ b/inc/etl/String.hpp @@ -2,7 +2,7 @@ #define ECSS_SERVICES_ETL_STRING_HPP #include <cstddef> -#include <etl/cstring.h> +#include <etl/string.h> /** * A fixed-size string diff --git a/lib/etl b/lib/etl index 5a7e1811..fce2b5ca 160000 --- a/lib/etl +++ b/lib/etl @@ -1 +1 @@ -Subproject commit 5a7e181100dae73659db133783ae964c7661437b +Subproject commit fce2b5ca8d2a139e0aefb450773b82e2cdfaa55f diff --git a/test/Parameters/LazyParameterTests.cpp b/test/Parameters/LazyParameterTests.cpp new file mode 100644 index 00000000..800c865c --- /dev/null +++ b/test/Parameters/LazyParameterTests.cpp @@ -0,0 +1,83 @@ +#include "../Services/ServiceTests.hpp" +#include "Helpers/LazyParameter.hpp" +#include "Message.hpp" +#include "catch2/catch_all.hpp" + +TEST_CASE("Lazy Parameter: Getter") { + LazyParameter<uint32_t> parameter; + CHECK(parameter.getValue().has_value() == false); + + parameter.setGetter([]() -> auto{ + static uint32_t value = 99100; + return value++; + }); + + CHECK(parameter.getValue().value() == 99100); + CHECK(parameter.getValue().value() == 99101); + CHECK(parameter.getValueAsDouble() == 99102); + + parameter.unsetGetter(); + + CHECK(parameter.getValue().has_value() == false); + CHECK(parameter.getValueAsDouble() == 0); +} + +TEST_CASE("Lazy Parameter: Messages") { + const int16_t fallback = -1; + const int16_t value = 42; + + LazyParameter<int16_t> parameter( + []() -> auto{ + return value; + }, + fallback); + + SECTION("Getter works") { + Message message(0, 0, Message::TC); + parameter.appendValueToMessage(message); + + CHECK(message.dataSize == 2); + CHECK(message.readSint16() == value); + CHECK(ServiceTests::hasNoErrors()); + } + + SECTION("Getter broken") { + parameter.unsetGetter(); + + Message message(0, 0, Message::TC); + parameter.appendValueToMessage(message); + + CHECK(message.dataSize == 2); + CHECK(message.readSint16() == fallback); + CHECK(ServiceTests::thrownError(ErrorHandler::ParameterValueMissing)); + } + + SECTION("Attempt to write") { + Message message(0, 0, Message::TC); + message.appendSint16(99); + message.appendSint16(100); + + parameter.setValueFromMessage(message); + CHECK(ServiceTests::thrownError(ErrorHandler::ParameterReadOnly)); + + // Make sure that the appropriate amount of bytes has been skipped + CHECK(message.readSint16() == 100); + } +} + +TEST_CASE("Lazy Parameter: Complex Types") { + LazyParameter<Time::CustomCUC_t> parameter({100}); + + SECTION("Default value") { + CHECK(parameter.getValue().has_value() == false); + } + + SECTION("Set value") { + parameter.setGetter([]() -> Time::CustomCUC_t { + return {200}; + }); + + CHECK(parameter.getValue().value() == Time::CustomCUC_t{200}); + CHECK(parameter.getValueAsDouble() == 0); + } +} diff --git a/test/Services/Parameter.cpp b/test/Parameters/ParameterTests.cpp similarity index 100% rename from test/Services/Parameter.cpp rename to test/Parameters/ParameterTests.cpp -- GitLab