Skip to content
Snippets Groups Projects
Commit 21283376 authored by athatheo's avatar athatheo
Browse files

Merge branch 'issue-140-lazy-parameters' into 'master'

Define lazy, function-based parameters

Closes #140

See merge request acubesat/obc/ecss-services!116
parents b991d46c d7d1be4c
No related branches found
No related tags found
No related merge requests found
...@@ -344,6 +344,18 @@ public: ...@@ -344,6 +344,18 @@ public:
* ID is not controlled by the Service (ST[14]) * ID is not controlled by the Service (ST[14])
*/ */
NotControlledApplication = 50, 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,
}; };
/** /**
......
#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
...@@ -32,8 +32,22 @@ ...@@ -32,8 +32,22 @@
*/ */
class ParameterBase { class ParameterBase {
public: 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; virtual void appendValueToMessage(Message& message) = 0;
/**
* Appends the parameter as an ECSS value to an ECSS Message
*/
virtual void setValueFromMessage(Message& message) = 0; 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; virtual double getValueAsDouble() = 0;
}; };
...@@ -58,12 +72,6 @@ public: ...@@ -58,12 +72,6 @@ public:
return currentValue; 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 { inline double getValueAsDouble() override {
if constexpr (std::is_arithmetic_v<DataType>) { if constexpr (std::is_arithmetic_v<DataType>) {
return static_cast<double>(currentValue); return static_cast<double>(currentValue);
...@@ -72,16 +80,10 @@ public: ...@@ -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 { inline void setValueFromMessage(Message& message) override {
currentValue = message.read<DataType>(); currentValue = message.read<DataType>();
}; };
/**
* Appends the parameter as an ECSS value to an ECSS Message
*/
inline void appendValueToMessage(Message& message) override { inline void appendValueToMessage(Message& message) override {
message.append<DataType>(currentValue); message.append<DataType>(currentValue);
}; };
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
#define ECSS_SERVICES_ETL_STRING_HPP #define ECSS_SERVICES_ETL_STRING_HPP
#include <cstddef> #include <cstddef>
#include <etl/cstring.h> #include <etl/string.h>
/** /**
* A fixed-size string * A fixed-size string
......
Subproject commit 5a7e181100dae73659db133783ae964c7661437b Subproject commit fce2b5ca8d2a139e0aefb450773b82e2cdfaa55f
#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);
}
}
File moved
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment