diff --git a/CHANGELOG.md b/CHANGELOG.md index 3907c5b6..8fa7f467 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/). versions since the last tag - Additional information is set to the last part of the git describe output for `FSFW_VERSION` now. - Version still need to be hand-updated if the FSFW is not included as a submodule for now. +- IPC Message Queue Handling: Allow passing an optional `MqArgs` argument into the MessageQueue + creation call. It allows passing context information and an arbitrary user argument into + the message queue. Also streamlined and simplified `MessageQueue` implementation for all OSALs + PR: https://egit.irs.uni-stuttgart.de/fsfw/fsfw/pulls/583 +- Clock: + - `timeval` to `TimeOfDay_t` + - Added Mutex for gmtime calls: (compare http://www.opengate.at/blog/2020/01/timeless/) + - Moved the statics used by Clock in ClockCommon.cpp to this file + - Better check for leap seconds + - Added Unittests for Clock (only getter) ## Removed @@ -51,6 +61,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Dedicated Version class and constant `fsfw::FSFW_VERSION` containing version information inside `fsfw/version.h` PR: https://egit.irs.uni-stuttgart.de/fsfw/fsfw/pulls/559 +- Added ETL dependency and improved library dependency management + PR: https://egit.irs.uni-stuttgart.de/fsfw/fsfw/pulls/592 ## Fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index ead7f531..337a56f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.13) +project(${LIB_FSFW_NAME}) set(FSFW_VERSION_IF_GIT_FAILS 4) set(FSFW_SUBVERSION_IF_GIT_FAILS 0) @@ -9,6 +10,22 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake-modules") set(MSG_PREFIX "fsfw |") +set(FSFW_ETL_LIB_MAJOR_VERSION 20 CACHE STRING + "ETL library major version requirement" +) +set(FSFW_ETL_LIB_VERSION ${FSFW_ETL_LIB_MAJOR_VERSION}.27.3 CACHE STRING + "ETL library exact version requirement" +) + +set(FSFW_CATCH2_LIB_MAJOR_VERSION 3 CACHE STRING + "Catch2 library major version requirement" +) +set(FSFW_CATCH2_LIB_VERSION v${FSFW_CATCH2_LIB_MAJOR_VERSION}.0.0-preview4 CACHE STRING + "Catch2 library exact version requirement" +) + +set(FSFW_ETL_LIB_NAME etl) + option(FSFW_GENERATE_SECTIONS "Generate function and data sections. Required to remove unused code" ON ) @@ -44,7 +61,6 @@ set(LIB_FSFW_NAME fsfw) set(FSFW_TEST_TGT fsfw-tests) set(FSFW_DUMMY_TGT fsfw-dummy) -project(${LIB_FSFW_NAME}) add_library(${LIB_FSFW_NAME}) set(FSFW_GIT_VER_HANDLING_OK FALSE) @@ -82,7 +98,7 @@ endif() if(FSFW_BUILD_UNITTESTS) message(STATUS "${MSG_PREFIX} Building the FSFW unittests in addition to the static library") # Check whether the user has already installed Catch2 first - find_package(Catch2 3 QUIET) + find_package(Catch2 ${FSFW_CATCH2_LIB_MAJOR_VERSION} CONFIG QUIET) # Not installed, so use FetchContent to download and provide Catch2 if(NOT Catch2_FOUND) message(STATUS "${MSG_PREFIX} Catch2 installation not found. Downloading Catch2 library with FetchContent") @@ -91,7 +107,7 @@ if(FSFW_BUILD_UNITTESTS) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.0.0-preview4 + GIT_TAG ${FSFW_CATCH2_LIB_VERSION} ) FetchContent_MakeAvailable(Catch2) @@ -116,6 +132,27 @@ if(FSFW_BUILD_UNITTESTS) endif() endif() +message(STATUS "Finding and/or providing etl library with version ${FSFW_ETL_LIB_MAJOR_VERSION}") + +# Check whether the user has already installed ETL first +# find_package(${FSFW_ETL_LIB_NAME} ${FSFW_ETL_LIB_MAJOR_VERSION} CONFIG QUIET) +# Not installed, so use FetchContent to download and provide etl +# if(NOT ${FSFW_ETL_LIB_NAME}_FOUND) +message(STATUS + "No etl installation was found with find_package. Installing and providing " + "etl with FetchContent" +) +include(FetchContent) + +FetchContent_Declare( + ${FSFW_ETL_LIB_NAME} + GIT_REPOSITORY https://github.com/ETLCPP/etl + GIT_TAG ${FSFW_ETL_LIB_VERSION} +) + +FetchContent_MakeAvailable(${FSFW_ETL_LIB_NAME}) +# endif() + set(FSFW_CORE_INC_PATH "inc") set_property(CACHE FSFW_OSAL PROPERTY STRINGS host linux rtems freertos) @@ -377,6 +414,7 @@ target_compile_options(${LIB_FSFW_NAME} PRIVATE ) target_link_libraries(${LIB_FSFW_NAME} PRIVATE + etl ${FSFW_ADDITIONAL_LINK_LIBS} ) diff --git a/README.md b/README.md index dff87b2e..99c842af 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,15 @@ with Airbus Defence and Space GmbH. ## Quick facts -The framework is designed for systems, which communicate with external devices, perform control loops, receive telecommands and send telemetry, and need to maintain a high level of availability. Therefore, a mode and health system provides control over the states of the software and the controlled devices. In addition, a simple mechanism of event based fault detection, isolation and recovery is implemented as well. +The framework is designed for systems, which communicate with external devices, perform control loops, +receive telecommands and send telemetry, and need to maintain a high level of availability. Therefore, +a mode and health system provides control over the states of the software and the controlled devices. +In addition, a simple mechanism of event based fault detection, isolation and recovery is implemented as well. -The FSFW provides abstraction layers for operating systems to provide a uniform operating system abstraction layer (OSAL). Some components of this OSAL are required internally by the FSFW but is also very useful for developers to implement the same application logic on different operating systems with a uniform interface. +The FSFW provides abstraction layers for operating systems to provide a uniform operating system +abstraction layer (OSAL). Some components of this OSAL are required internally by the FSFW but is +also very useful for developers to implement the same application logic on different operating +systems with a uniform interface. Currently, the FSFW provides the following OSALs: @@ -45,6 +51,28 @@ A template configuration folder was provided and can be copied into the project a starting point. The [configuration section](docs/README-config.md#top) provides more specific information about the possible options. +## Prerequisites + +The Embedded Template Library (etl) is a dependency of the FSFW which is automatically +installed and provided by the build system unless the correction version was installed. +The current recommended version can be found inside the fsfw `CMakeLists.txt` file or by using +`ccmake` and looking up the `FSFW_ETL_LIB_MAJOR_VERSION` variable. + +You can install the ETL library like this. On Linux, it might be necessary to add `sudo` before +the install call: + +```cpp +git clone https://github.com/ETLCPP/etl +cd etl +git checkout +mkdir build && cd build +cmake .. +cmake --install . +``` + +It is recommended to install `20.27.2` or newer for the package version handling of +ETL to work. + ## Adding the library The following steps show how to add and use FSFW components. It is still recommended to @@ -83,6 +111,19 @@ The FSFW also has unittests which use the [Catch2 library](https://github.com/ca These are built by setting the CMake option `FSFW_BUILD_UNITTESTS` to `ON` or `TRUE` from your project `CMakeLists.txt` file or from the command line. +You can install the Catch2 library, which prevents the build system to avoid re-downloading +the dependency if the unit tests are completely rebuilt. The current recommended version +can be found inside the fsfw `CMakeLists.txt` file or by using `ccmake` and looking up +the `FSFW_CATCH2_LIB_VERSION` variable. + +```sh +git clone https://github.com/catchorg/Catch2.git +cd Catch2 +git checkout +cmake -Bbuild -H. -DBUILD_TESTING=OFF +sudo cmake --build build/ --target install +``` + The fsfw-tests binary will be built as part of the static library and dropped alongside it. If the unittests are built, the library and the tests will be built with coverage information by default. This can be disabled by setting the `FSFW_TESTS_COV_GEN` option to `OFF` or `FALSE`. diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 34547211..01724b3a 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -19,6 +19,29 @@ A template configuration folder was provided and can be copied into the project a starting point. The [configuration section](docs/README-config.md#top) provides more specific information about the possible options. +Prerequisites +------------------- + +The Embedded Template Library (etl) is a dependency of the FSFW which is automatically +installed and provided by the build system unless the correction version was installed. +The current recommended version can be found inside the fsfw ``CMakeLists.txt`` file or by using +``ccmake`` and looking up the ``FSFW_ETL_LIB_MAJOR_VERSION`` variable. + +You can install the ETL library like this. On Linux, it might be necessary to add ``sudo`` before +the install call: + +.. code-block:: console + + git clone https://github.com/ETLCPP/etl + cd etl + git checkout + mkdir build && cd build + cmake .. + cmake --install . + +It is recommended to install ``20.27.2`` or newer for the package version handling of +ETL to work. + Adding the library ------------------- @@ -60,6 +83,20 @@ The FSFW also has unittests which use the `Catch2 library`_. These are built by setting the CMake option ``FSFW_BUILD_UNITTESTS`` to ``ON`` or `TRUE` from your project `CMakeLists.txt` file or from the command line. +You can install the Catch2 library, which prevents the build system to avoid re-downloading +the dependency if the unit tests are completely rebuilt. The current recommended version +can be found inside the fsfw ``CMakeLists.txt`` file or by using ``ccmake`` and looking up +the ``FSFW_CATCH2_LIB_VERSION`` variable. + +.. code-block:: console + + git clone https://github.com/catchorg/Catch2.git + cd Catch2 + git checkout + cmake -Bbuild -H. -DBUILD_TESTING=OFF + sudo cmake --build build/ --target install + + The fsfw-tests binary will be built as part of the static library and dropped alongside it. If the unittests are built, the library and the tests will be built with coverage information by default. This can be disabled by setting the `FSFW_TESTS_COV_GEN` option to `OFF` or `FALSE`. diff --git a/hal/src/fsfw_hal/devicehandlers/MgmLIS3MDLHandler.cpp b/hal/src/fsfw_hal/devicehandlers/MgmLIS3MDLHandler.cpp index a887934d..a13ae791 100644 --- a/hal/src/fsfw_hal/devicehandlers/MgmLIS3MDLHandler.cpp +++ b/hal/src/fsfw_hal/devicehandlers/MgmLIS3MDLHandler.cpp @@ -375,7 +375,7 @@ float MgmLIS3MDLHandler::getSensitivityFactor(MGMLIS3MDL::Sensitivies sens) { ReturnValue_t MgmLIS3MDLHandler::enableTemperatureSensor(const uint8_t *commandData, size_t commandDataLen) { - if(commandData == nullptr) { + if (commandData == nullptr) { return INVALID_COMMAND_PARAMETER; } triggerEvent(CHANGE_OF_SETUP_PARAMETER); diff --git a/src/fsfw/globalfunctions/DleParser.cpp b/src/fsfw/globalfunctions/DleParser.cpp index b68dbde1..71da7e6a 100644 --- a/src/fsfw/globalfunctions/DleParser.cpp +++ b/src/fsfw/globalfunctions/DleParser.cpp @@ -187,7 +187,7 @@ void DleParser::defaultErrorHandler(ErrorTypes err, ErrorInfo ctx) { case (ErrorTypes::ENCODED_BUF_TOO_SMALL): case (ErrorTypes::DECODING_BUF_TOO_SMALL): { char opt[64]; - snprintf(opt, sizeof(opt), ": Too small for packet with length %d", ctx.len); + snprintf(opt, sizeof(opt), ": Too small for packet with length %zu", ctx.len); if (err == ErrorTypes::ENCODED_BUF_TOO_SMALL) { errorPrinter("Encoded buf too small", opt); } else { diff --git a/src/fsfw/osal/freertos/Clock.cpp b/src/fsfw/osal/freertos/Clock.cpp index 05083968..cabf7d81 100644 --- a/src/fsfw/osal/freertos/Clock.cpp +++ b/src/fsfw/osal/freertos/Clock.cpp @@ -11,9 +11,6 @@ // TODO sanitize input? // TODO much of this code can be reused for tick-only systems -uint16_t Clock::leapSeconds = 0; -MutexIF* Clock::timeMutex = nullptr; - uint32_t Clock::getTicksPerSecond(void) { return 1000; } ReturnValue_t Clock::setClock(const TimeOfDay_t* time) { diff --git a/src/fsfw/osal/host/Clock.cpp b/src/fsfw/osal/host/Clock.cpp index d0acdfdf..19e120b3 100644 --- a/src/fsfw/osal/host/Clock.cpp +++ b/src/fsfw/osal/host/Clock.cpp @@ -2,6 +2,7 @@ #include +#include "fsfw/ipc/MutexGuard.h" #include "fsfw/platform.h" #include "fsfw/serviceinterface/ServiceInterface.h" @@ -11,9 +12,6 @@ #include #endif -uint16_t Clock::leapSeconds = 0; -MutexIF* Clock::timeMutex = NULL; - using SystemClock = std::chrono::system_clock; uint32_t Clock::getTicksPerSecond(void) { @@ -127,6 +125,13 @@ ReturnValue_t Clock::getDateAndTime(TimeOfDay_t* time) { auto seconds = std::chrono::time_point_cast(now); auto fraction = now - seconds; time_t tt = SystemClock::to_time_t(now); + ReturnValue_t result = checkOrCreateClockMutex(); + if (result != HasReturnvaluesIF::RETURN_OK) { + return result; + } + MutexGuard helper(timeMutex); + // gmtime writes its output in a global buffer which is not Thread Safe + // Therefore we have to use a Mutex here struct tm* timeInfo; timeInfo = gmtime(&tt); time->year = timeInfo->tm_year + 1900; diff --git a/src/fsfw/osal/linux/Clock.cpp b/src/fsfw/osal/linux/Clock.cpp index 50ee98d7..534e7e22 100644 --- a/src/fsfw/osal/linux/Clock.cpp +++ b/src/fsfw/osal/linux/Clock.cpp @@ -6,16 +6,11 @@ #include #include -#include #include +#include "fsfw/ipc/MutexGuard.h" #include "fsfw/serviceinterface/ServiceInterface.h" -uint16_t Clock::leapSeconds = 0; -MutexIF* Clock::timeMutex = NULL; - -void handleClockError(const char* func); - uint32_t Clock::getTicksPerSecond(void) { uint32_t ticks = sysconf(_SC_CLK_TCK); return ticks; @@ -30,7 +25,7 @@ ReturnValue_t Clock::setClock(const TimeOfDay_t* time) { int status = clock_settime(CLOCK_REALTIME, &timeUnix); if (status != 0) { - handleClockError("setClock"); + // TODO errno return HasReturnvaluesIF::RETURN_FAILED; } return HasReturnvaluesIF::RETURN_OK; @@ -42,7 +37,7 @@ ReturnValue_t Clock::setClock(const timeval* time) { timeUnix.tv_nsec = (__syscall_slong_t)time->tv_usec * 1000; int status = clock_settime(CLOCK_REALTIME, &timeUnix); if (status != 0) { - handleClockError("setClock"); + // TODO errno return HasReturnvaluesIF::RETURN_FAILED; } return HasReturnvaluesIF::RETURN_OK; @@ -52,7 +47,6 @@ ReturnValue_t Clock::getClock_timeval(timeval* time) { timespec timeUnix; int status = clock_gettime(CLOCK_REALTIME, &timeUnix); if (status != 0) { - handleClockError("getClock_timeval"); return HasReturnvaluesIF::RETURN_FAILED; } time->tv_sec = timeUnix.tv_sec; @@ -121,7 +115,13 @@ ReturnValue_t Clock::getDateAndTime(TimeOfDay_t* time) { // TODO errno return HasReturnvaluesIF::RETURN_FAILED; } - + ReturnValue_t result = checkOrCreateClockMutex(); + if (result != HasReturnvaluesIF::RETURN_OK) { + return result; + } + MutexGuard helper(timeMutex); + // gmtime writes its output in a global buffer which is not Thread Safe + // Therefore we have to use a Mutex here struct tm* timeInfo; timeInfo = gmtime(&timeUnix.tv_sec); time->year = timeInfo->tm_year + 1900; @@ -155,14 +155,3 @@ ReturnValue_t Clock::convertTimevalToJD2000(timeval time, double* JD2000) { *JD2000 = (time.tv_sec - 946728000. + time.tv_usec / 1000000.) / 24. / 3600.; return HasReturnvaluesIF::RETURN_OK; } - -void handleClockError(const char* func) { -#if FSFW_VERBOSE_LEVEL >= 1 -#if FSFW_CPP_OSTREAM_ENABLED == 1 - sif::warning << "Clock::" << func << ": Failed with code " << errno << ": " << strerror(errno) - << std::endl; -#else - sif::printWarning("Clock::%s: Failed with code %d: %s\n", func, errno, strerror(errno)); -#endif -#endif -} diff --git a/src/fsfw/osal/rtems/Clock.cpp b/src/fsfw/osal/rtems/Clock.cpp index 06b0c1d8..831c67d4 100644 --- a/src/fsfw/osal/rtems/Clock.cpp +++ b/src/fsfw/osal/rtems/Clock.cpp @@ -6,9 +6,6 @@ #include "fsfw/ipc/MutexGuard.h" #include "fsfw/osal/rtems/RtemsBasic.h" -uint16_t Clock::leapSeconds = 0; -MutexIF* Clock::timeMutex = nullptr; - uint32_t Clock::getTicksPerSecond(void) { rtems_interval ticks_per_second = rtems_clock_get_ticks_per_second(); return static_cast(ticks_per_second); diff --git a/src/fsfw/timemanager/CCSDSTime.cpp b/src/fsfw/timemanager/CCSDSTime.cpp index c5132cbb..9ebd1d79 100644 --- a/src/fsfw/timemanager/CCSDSTime.cpp +++ b/src/fsfw/timemanager/CCSDSTime.cpp @@ -91,7 +91,7 @@ ReturnValue_t CCSDSTime::convertFromCDS(Clock::TimeOfDay_t* to, const uint8_t* f if (result != HasReturnvaluesIF::RETURN_OK) { return result; } - return convertTimevalToTimeOfDay(to, &time); + return Clock::convertTimevalToTimeOfDay(&time, to); } ReturnValue_t CCSDSTime::convertFromCCS(Clock::TimeOfDay_t* to, const uint8_t* from, @@ -489,11 +489,6 @@ ReturnValue_t CCSDSTime::checkTimeOfDay(const Clock::TimeOfDay_t* time) { return RETURN_OK; } -ReturnValue_t CCSDSTime::convertTimevalToTimeOfDay(Clock::TimeOfDay_t* to, timeval* from) { - // This is rather tricky. Implement only if needed. Also, if so, move to OSAL. - return UNSUPPORTED_TIME_FORMAT; -} - ReturnValue_t CCSDSTime::convertFromCDS(timeval* to, const uint8_t* from, size_t* foundLength, size_t maxLength) { uint8_t pField = *from; @@ -583,7 +578,7 @@ ReturnValue_t CCSDSTime::convertFromCDS(Clock::TimeOfDay_t* to, const CCSDSTime: if (result != HasReturnvaluesIF::RETURN_OK) { return result; } - return CCSDSTime::convertTimevalToTimeOfDay(to, &tempTimeval); + return Clock::convertTimevalToTimeOfDay(&tempTimeval, to); } ReturnValue_t CCSDSTime::convertFromCUC(timeval* to, uint8_t pField, const uint8_t* from, diff --git a/src/fsfw/timemanager/CCSDSTime.h b/src/fsfw/timemanager/CCSDSTime.h index 9de41e09..19c980d0 100644 --- a/src/fsfw/timemanager/CCSDSTime.h +++ b/src/fsfw/timemanager/CCSDSTime.h @@ -223,7 +223,6 @@ class CCSDSTime : public HasReturnvaluesIF { uint8_t *day); static bool isLeapYear(uint32_t year); - static ReturnValue_t convertTimevalToTimeOfDay(Clock::TimeOfDay_t *to, timeval *from); }; #endif /* FSFW_TIMEMANAGER_CCSDSTIME_H_ */ diff --git a/src/fsfw/timemanager/Clock.h b/src/fsfw/timemanager/Clock.h index e9afff2e..75c898e5 100644 --- a/src/fsfw/timemanager/Clock.h +++ b/src/fsfw/timemanager/Clock.h @@ -173,6 +173,7 @@ class Clock { static MutexIF *timeMutex; static uint16_t leapSeconds; + static bool leapSecondsSet; }; #endif /* FSFW_TIMEMANAGER_CLOCK_H_ */ diff --git a/src/fsfw/timemanager/ClockCommon.cpp b/src/fsfw/timemanager/ClockCommon.cpp index 18407362..ca8b12a4 100644 --- a/src/fsfw/timemanager/ClockCommon.cpp +++ b/src/fsfw/timemanager/ClockCommon.cpp @@ -3,6 +3,10 @@ #include "fsfw/ipc/MutexGuard.h" #include "fsfw/timemanager/Clock.h" +uint16_t Clock::leapSeconds = 0; +MutexIF* Clock::timeMutex = nullptr; +bool Clock::leapSecondsSet = false; + ReturnValue_t Clock::convertUTCToTT(timeval utc, timeval* tt) { uint16_t leapSeconds; ReturnValue_t result = getLeapSeconds(&leapSeconds); @@ -29,12 +33,16 @@ ReturnValue_t Clock::setLeapSeconds(const uint16_t leapSeconds_) { MutexGuard helper(timeMutex); leapSeconds = leapSeconds_; + leapSecondsSet = true; return HasReturnvaluesIF::RETURN_OK; } ReturnValue_t Clock::getLeapSeconds(uint16_t* leapSeconds_) { - if (timeMutex == nullptr) { + if (not leapSecondsSet) { + return HasReturnvaluesIF::RETURN_FAILED; + } + if (checkOrCreateClockMutex() != HasReturnvaluesIF::RETURN_OK) { return HasReturnvaluesIF::RETURN_FAILED; } MutexGuard helper(timeMutex); @@ -46,6 +54,16 @@ ReturnValue_t Clock::getLeapSeconds(uint16_t* leapSeconds_) { ReturnValue_t Clock::convertTimevalToTimeOfDay(const timeval* from, TimeOfDay_t* to) { struct tm* timeInfo; + // According to https://en.cppreference.com/w/c/chrono/gmtime, the implementation of gmtime_s + // in the Windows CRT is incompatible with the C standard but this should not be an issue for + // this implementation + ReturnValue_t result = checkOrCreateClockMutex(); + if (result != HasReturnvaluesIF::RETURN_OK) { + return result; + } + MutexGuard helper(timeMutex); + // gmtime writes its output in a global buffer which is not Thread Safe + // Therefore we have to use a Mutex here timeInfo = gmtime(&from->tv_sec); to->year = timeInfo->tm_year + 1900; to->month = timeInfo->tm_mon + 1; diff --git a/tests/src/fsfw_tests/unit/globalfunctions/CMakeLists.txt b/tests/src/fsfw_tests/unit/globalfunctions/CMakeLists.txt index 79f847bf..348b99fc 100644 --- a/tests/src/fsfw_tests/unit/globalfunctions/CMakeLists.txt +++ b/tests/src/fsfw_tests/unit/globalfunctions/CMakeLists.txt @@ -3,4 +3,5 @@ target_sources(${FSFW_TEST_TGT} PRIVATE testOpDivider.cpp testBitutil.cpp testCRC.cpp + testTimevalOperations.cpp ) diff --git a/tests/src/fsfw_tests/unit/globalfunctions/testTimevalOperations.cpp b/tests/src/fsfw_tests/unit/globalfunctions/testTimevalOperations.cpp new file mode 100644 index 00000000..347d2204 --- /dev/null +++ b/tests/src/fsfw_tests/unit/globalfunctions/testTimevalOperations.cpp @@ -0,0 +1,124 @@ +#include + +#include +#include + +#include "fsfw_tests/unit/CatchDefinitions.h" + +TEST_CASE("TimevalTest", "[timevalOperations]") { + SECTION("Comparison") { + timeval t1; + t1.tv_sec = 1648227422; + t1.tv_usec = 123456; + timeval t2; + t2.tv_sec = 1648227422; + t2.tv_usec = 123456; + REQUIRE(t1 == t2); + REQUIRE(t2 == t1); + REQUIRE_FALSE(t1 != t2); + REQUIRE_FALSE(t2 != t1); + REQUIRE(t1 <= t2); + REQUIRE(t2 <= t1); + REQUIRE(t1 >= t2); + REQUIRE(t2 >= t1); + REQUIRE_FALSE(t1 < t2); + REQUIRE_FALSE(t2 < t1); + REQUIRE_FALSE(t1 > t2); + REQUIRE_FALSE(t2 > t1); + + timeval t3; + t3.tv_sec = 1648227422; + t3.tv_usec = 123457; + REQUIRE_FALSE(t1 == t3); + REQUIRE(t1 != t3); + REQUIRE(t1 <= t3); + REQUIRE_FALSE(t3 <= t1); + REQUIRE_FALSE(t1 >= t3); + REQUIRE(t3 >= t1); + REQUIRE(t1 < t3); + REQUIRE_FALSE(t3 < t1); + REQUIRE_FALSE(t1 > t3); + REQUIRE(t3 > t1); + + timeval t4; + t4.tv_sec = 1648227423; + t4.tv_usec = 123456; + REQUIRE_FALSE(t1 == t4); + REQUIRE(t1 != t4); + REQUIRE(t1 <= t4); + REQUIRE_FALSE(t4 <= t1); + REQUIRE_FALSE(t1 >= t4); + REQUIRE(t4 >= t1); + REQUIRE(t1 < t4); + REQUIRE_FALSE(t4 < t1); + REQUIRE_FALSE(t1 > t4); + REQUIRE(t4 > t1); + } + SECTION("Operators") { + timeval t1; + t1.tv_sec = 1648227422; + t1.tv_usec = 123456; + timeval t2; + t2.tv_sec = 1648227422; + t2.tv_usec = 123456; + timeval t3 = t1 - t2; + REQUIRE(t3.tv_sec == 0); + REQUIRE(t3.tv_usec == 0); + timeval t4 = t1 - t3; + REQUIRE(t4.tv_sec == 1648227422); + REQUIRE(t4.tv_usec == 123456); + timeval t5 = t3 - t1; + REQUIRE(t5.tv_sec == -1648227422); + REQUIRE(t5.tv_usec == -123456); + + timeval t6; + t6.tv_sec = 1648227400; + t6.tv_usec = 999999; + + timeval t7 = t6 + t1; + REQUIRE(t7.tv_sec == (1648227422ull + 1648227400ull + 1ull)); + REQUIRE(t7.tv_usec == 123455); + + timeval t8 = t1 - t6; + REQUIRE(t8.tv_sec == 1648227422 - 1648227400 - 1); + REQUIRE(t8.tv_usec == 123457); + + double scalar = 2; + timeval t9 = t1 * scalar; + REQUIRE(t9.tv_sec == 3296454844); + REQUIRE(t9.tv_usec == 246912); + timeval t10 = scalar * t1; + REQUIRE(t10.tv_sec == 3296454844); + REQUIRE(t10.tv_usec == 246912); + timeval t11 = t6 * scalar; + REQUIRE(t11.tv_sec == (3296454800 + 1)); + REQUIRE(t11.tv_usec == 999998); + + timeval t12 = t1 / scalar; + REQUIRE(t12.tv_sec == 824113711); + REQUIRE(t12.tv_usec == 61728); + + timeval t13 = t6 / scalar; + REQUIRE(t13.tv_sec == 824113700); + // Rounding issue + REQUIRE(t13.tv_usec == 499999); + + double scalar2 = t9 / t1; + REQUIRE(scalar2 == Catch::Approx(2.0)); + double scalar3 = t1 / t6; + REQUIRE(scalar3 == Catch::Approx(1.000000013)); + double scalar4 = t3 / t1; + REQUIRE(scalar4 == Catch::Approx(0)); + double scalar5 = t12 / t1; + REQUIRE(scalar5 == Catch::Approx(0.5)); + } + + SECTION("timevalOperations::toTimeval") { + double seconds = 1648227422.123456; + timeval t1 = timevalOperations::toTimeval(seconds); + REQUIRE(t1.tv_sec == 1648227422); + // Allow 1 usec rounding tolerance + REQUIRE(t1.tv_usec >= 123455); + REQUIRE(t1.tv_usec <= 123457); + } +} \ No newline at end of file diff --git a/tests/src/fsfw_tests/unit/osal/CMakeLists.txt b/tests/src/fsfw_tests/unit/osal/CMakeLists.txt index 293be2e8..030d363b 100644 --- a/tests/src/fsfw_tests/unit/osal/CMakeLists.txt +++ b/tests/src/fsfw_tests/unit/osal/CMakeLists.txt @@ -1,4 +1,5 @@ target_sources(${FSFW_TEST_TGT} PRIVATE TestMessageQueue.cpp TestSemaphore.cpp + TestClock.cpp ) diff --git a/tests/src/fsfw_tests/unit/osal/TestClock.cpp b/tests/src/fsfw_tests/unit/osal/TestClock.cpp new file mode 100644 index 00000000..38ec3915 --- /dev/null +++ b/tests/src/fsfw_tests/unit/osal/TestClock.cpp @@ -0,0 +1,86 @@ +#include +#include + +#include +#include +#include + +#include "fsfw_tests/unit/CatchDefinitions.h" + +TEST_CASE("OSAL::Clock Test", "[OSAL::Clock Test]") { + SECTION("Test getClock") { + timeval time; + ReturnValue_t result = Clock::getClock_timeval(&time); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + Clock::TimeOfDay_t timeOfDay; + result = Clock::getDateAndTime(&timeOfDay); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + timeval timeOfDayAsTimeval; + result = Clock::convertTimeOfDayToTimeval(&timeOfDay, &timeOfDayAsTimeval); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + // We require timeOfDayAsTimeval to be larger than time as it + // was request a few ns later + double difference = timevalOperations::toDouble(timeOfDayAsTimeval - time); + CHECK(difference >= 0.0); + CHECK(difference <= 0.005); + + // Conversion in the other direction + Clock::TimeOfDay_t timevalAsTimeOfDay; + result = Clock::convertTimevalToTimeOfDay(&time, &timevalAsTimeOfDay); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + CHECK(timevalAsTimeOfDay.year <= timeOfDay.year); + // TODO We should write TimeOfDay operators! + } + SECTION("Leap seconds") { + uint16_t leapSeconds = 0; + ReturnValue_t result = Clock::getLeapSeconds(&leapSeconds); + REQUIRE(result == HasReturnvaluesIF::RETURN_FAILED); + REQUIRE(leapSeconds == 0); + result = Clock::setLeapSeconds(18); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + result = Clock::getLeapSeconds(&leapSeconds); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + REQUIRE(leapSeconds == 18); + } + SECTION("usec Test") { + timeval timeAsTimeval; + ReturnValue_t result = Clock::getClock_timeval(&timeAsTimeval); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + uint64_t timeAsUsec = 0; + result = Clock::getClock_usecs(&timeAsUsec); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + double timeAsUsecDouble = static_cast(timeAsUsec) / 1000000.0; + timeval timeAsUsecTimeval = timevalOperations::toTimeval(timeAsUsecDouble); + double difference = timevalOperations::toDouble(timeAsUsecTimeval - timeAsTimeval); + // We accept 5 ms difference + CHECK(difference >= 0.0); + CHECK(difference <= 0.005); + uint64_t timevalAsUint64 = static_cast(timeAsTimeval.tv_sec) * 1000000ull + + static_cast(timeAsTimeval.tv_usec); + CHECK((timeAsUsec - timevalAsUint64) >= 0); + CHECK((timeAsUsec - timevalAsUint64) <= (5 * 1000)); + } + SECTION("Test j2000") { + double j2000; + timeval time; + time.tv_sec = 1648208539; + time.tv_usec = 0; + ReturnValue_t result = Clock::convertTimevalToJD2000(time, &j2000); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + double correctJ2000 = 2459663.98772 - 2451545.0; + CHECK(j2000 == Catch::Approx(correctJ2000).margin(1.2 * 1e-8)); + } + SECTION("Convert to TT") { + timeval utcTime; + utcTime.tv_sec = 1648208539; + utcTime.tv_usec = 999000; + timeval tt; + ReturnValue_t result = Clock::setLeapSeconds(27); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + result = Clock::convertUTCToTT(utcTime, &tt); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + CHECK(tt.tv_usec == 183000); + // The plus 1 is a own forced overflow of usecs + CHECK(tt.tv_sec == (1648208539 + 27 + 10 + 32 + 1)); + } +} \ No newline at end of file diff --git a/tests/src/fsfw_tests/unit/timemanager/TestCCSDSTime.cpp b/tests/src/fsfw_tests/unit/timemanager/TestCCSDSTime.cpp index 2e80487a..a43ff8ab 100644 --- a/tests/src/fsfw_tests/unit/timemanager/TestCCSDSTime.cpp +++ b/tests/src/fsfw_tests/unit/timemanager/TestCCSDSTime.cpp @@ -81,7 +81,8 @@ TEST_CASE("CCSDSTime Tests", "[TestCCSDSTime]") { std::string timeAscii = "2022-12-31T23:59:59.123Z"; Clock::TimeOfDay_t timeTo; const uint8_t* timeChar = reinterpret_cast(timeAscii.c_str()); - CCSDSTime::convertFromASCII(&timeTo, timeChar, timeAscii.length()); + auto result = CCSDSTime::convertFromASCII(&timeTo, timeChar, timeAscii.length()); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); REQUIRE(timeTo.year == 2022); REQUIRE(timeTo.month == 12); REQUIRE(timeTo.day == 31); @@ -89,6 +90,19 @@ TEST_CASE("CCSDSTime Tests", "[TestCCSDSTime]") { REQUIRE(timeTo.minute == 59); REQUIRE(timeTo.second == 59); REQUIRE(timeTo.usecond == Catch::Approx(123000)); + + std::string timeAscii2 = "2022-365T23:59:59.123Z"; + const uint8_t* timeChar2 = reinterpret_cast(timeAscii2.c_str()); + Clock::TimeOfDay_t timeTo2; + result = CCSDSTime::convertFromCcsds(&timeTo2, timeChar2, timeAscii2.length()); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + REQUIRE(timeTo2.year == 2022); + REQUIRE(timeTo2.month == 12); + REQUIRE(timeTo2.day == 31); + REQUIRE(timeTo2.hour == 23); + REQUIRE(timeTo2.minute == 59); + REQUIRE(timeTo2.second == 59); + REQUIRE(timeTo2.usecond == Catch::Approx(123000)); } SECTION("CDS Conversions") { @@ -119,6 +133,7 @@ TEST_CASE("CCSDSTime Tests", "[TestCCSDSTime]") { CHECK(cdsTime.msDay_h == 0xE0); CHECK(cdsTime.msDay_l == 0xC5); CHECK(cdsTime.msDay_ll == 0xC3); + CHECK(cdsTime.pField == CCSDSTime::P_FIELD_CDS_SHORT); // Conversion back to timeval timeval timeReturnAsTimeval; @@ -128,5 +143,56 @@ TEST_CASE("CCSDSTime Tests", "[TestCCSDSTime]") { timeval difference = timeAsTimeval - timeReturnAsTimeval; CHECK(difference.tv_usec == 456); CHECK(difference.tv_sec == 0); + + Clock::TimeOfDay_t timeReturnAsTimeOfDay; + result = CCSDSTime::convertFromCDS(&timeReturnAsTimeOfDay, &cdsTime); + CHECK(result == HasReturnvaluesIF::RETURN_OK); + CHECK(timeReturnAsTimeOfDay.year == 2020); + CHECK(timeReturnAsTimeOfDay.month == 2); + CHECK(timeReturnAsTimeOfDay.day == 29); + CHECK(timeReturnAsTimeOfDay.hour == 13); + CHECK(timeReturnAsTimeOfDay.minute == 24); + CHECK(timeReturnAsTimeOfDay.second == 45); + // micro seconds precision is lost + CHECK(timeReturnAsTimeOfDay.usecond == 123000); + + Clock::TimeOfDay_t timeReturnAsTodFromBuffer; + const uint8_t* buffer = reinterpret_cast(&cdsTime); + result = CCSDSTime::convertFromCDS(&timeReturnAsTodFromBuffer, buffer, sizeof(cdsTime)); + REQUIRE(result == HasReturnvaluesIF::RETURN_OK); + CHECK(timeReturnAsTodFromBuffer.year == time.year); + CHECK(timeReturnAsTodFromBuffer.month == time.month); + CHECK(timeReturnAsTodFromBuffer.day == time.day); + CHECK(timeReturnAsTodFromBuffer.hour == time.hour); + CHECK(timeReturnAsTodFromBuffer.minute == time.minute); + CHECK(timeReturnAsTodFromBuffer.second == time.second); + CHECK(timeReturnAsTodFromBuffer.usecond == 123000); + + Clock::TimeOfDay_t todFromCCSDS; + result = CCSDSTime::convertFromCcsds(&todFromCCSDS, buffer, sizeof(cdsTime)); + CHECK(result == HasReturnvaluesIF::RETURN_OK); + CHECK(todFromCCSDS.year == time.year); + CHECK(todFromCCSDS.month == time.month); + CHECK(todFromCCSDS.day == time.day); + CHECK(todFromCCSDS.hour == time.hour); + CHECK(todFromCCSDS.minute == time.minute); + CHECK(todFromCCSDS.second == time.second); + CHECK(todFromCCSDS.usecond == 123000); + } + SECTION("CCSDS Failures") { + Clock::TimeOfDay_t time; + time.year = 2020; + time.month = 12; + time.day = 32; + time.hour = 13; + time.minute = 24; + time.second = 45; + time.usecond = 123456; + CCSDSTime::Ccs_mseconds to; + auto result = CCSDSTime::convertToCcsds(&to, &time); + REQUIRE(result == CCSDSTime::INVALID_TIME_FORMAT); + CCSDSTime::Ccs_seconds to2; + result = CCSDSTime::convertToCcsds(&to2, &time); + REQUIRE(result == CCSDSTime::INVALID_TIME_FORMAT); } } \ No newline at end of file