diff --git a/CMakeLists.txt b/CMakeLists.txt index 227d2632f485b8692952f3bb3f140c080bad21a1..37310bc340db176e5788eafbd34689ae763630d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ add_library(common OBJECT src/MessageParser.cpp src/ServicePool.cpp src/Helpers/CRCHelper.cpp - src/Helpers/TimeFormats.cpp + src/Time/UTCTimestamp.cpp src/Services/EventReportService.cpp src/Services/MemoryManagementService.cpp src/Services/ParameterService.cpp @@ -47,8 +47,7 @@ add_library(common OBJECT ) # Specify the .cpp files for the executables -file(GLOB x86_main_SRC "src/Platform/x86/*.cpp" "src/Platform/x86/Services/*.cpp" - "lib/logger/src/Platform/x86/*.cpp") +file(GLOB_RECURSE x86_main_SRC "src/Platform/x86/*.cpp" "lib/logger/src/Platform/x86/*.cpp") add_executable(ecss_services $<TARGET_OBJECTS:common> diff --git a/inc/Helpers/TimeFormats.hpp b/inc/Helpers/TimeFormats.hpp deleted file mode 100644 index 969c52da5ce3787801fb6a7cbf57f2591c98df25..0000000000000000000000000000000000000000 --- a/inc/Helpers/TimeFormats.hpp +++ /dev/null @@ -1,221 +0,0 @@ -#include <cstdint> -#include <Message.hpp> -#include "macros.hpp" -#include "etl/String.hpp" - -// SEE CCSDS 301.0-B-4 - -//////////////// CONSTANTS //////////////////// -inline constexpr uint8_t SECONDS_PER_MINUTE = 60; -inline constexpr uint16_t SECONDS_PER_HOUR = 3600; -inline constexpr uint32_t SECONDS_PER_DAY = 86400; -static constexpr uint8_t DAYSOFMONTH[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - -inline constexpr uint8_t ACUBESAT_CUC_SECONDS_COUNTER_BYTES = 2; // PER DDJF_TTC -inline constexpr uint8_t ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES = 2; // PER DDJF_TTC -// Unix Epoch includes leap seconds. -// Currently set at 01 Jan 2019 00h00m00s, TODO correct to set to 01 Jan. 2020 00h00m00s. -// May be broken, see Issue #121 on Gitlab. -inline constexpr uint32_t UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS = 1546300800; -// -inline constexpr uint16_t ACUBESAT_EPOCH_YEAR = 2019; -inline constexpr uint8_t ACUBESAT_EPOCH_MONTH = 1; -inline constexpr uint8_t ACUBESAT_EPOCH_DAY = 1; - -inline constexpr uint8_t MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP = 9; - -static_assert(ACUBESAT_EPOCH_YEAR >= 2019); -static_assert(ACUBESAT_EPOCH_MONTH < 11 && ACUBESAT_EPOCH_MONTH >= 0); -static_assert(ACUBESAT_EPOCH_DAY < DAYSOFMONTH[ACUBESAT_EPOCH_MONTH]); -////////////////////////////////////////////// - -//////// HELPER CONSTEXPR //////// -// Build CUC header at compile time for timestamps that have a short (1 byte) header -template <int seconds_counter_bytes, int fractional_counter_bytes> -inline constexpr uint8_t build_short_CUC_header() { - static_assert(seconds_counter_bytes <= 4, "Use build_long_CUC_header instead"); - static_assert(fractional_counter_bytes <= 3, "Use build_long_CUC_header instead"); - - uint8_t header = 0; - - // P-Field extension is 0, CUC header is not extended - header += 0; - - // AcubeSAT is using custom TAI epoch at 01 Jan 2020 - header = header << 3; - header += 0b010; - - // Number of bytes in the basic time unit - header = header << 2; - header += seconds_counter_bytes - 1; - - // Number of bytes in the fractional unit - header = header << 2; - header += fractional_counter_bytes; - - return header; -} - -// Build CUC header at compile time for timestamps that have a long (2 byte) header -template <int seconds_counter_bytes, int fractional_counter_bytes> -inline constexpr uint16_t build_long_CUC_header() { - // cppcheck-suppress redundantCondition - static_assert(seconds_counter_bytes > 4 || fractional_counter_bytes > 3, "Use build_short_CUC_header instead"); - static_assert(seconds_counter_bytes <= 7, - "Number of bytes for seconds over maximum number of octets allowed by CCSDS"); - static_assert(fractional_counter_bytes <= 6, - "Number of bytes for seconds over maximum number of octets allowed by CCSDS"); - - uint16_t header = 0; - - uint8_t first_octet_number_of_seconds_bytes = std::min(4, seconds_counter_bytes); - uint8_t second_octet_number_of_seconds_bytes = seconds_counter_bytes - first_octet_number_of_seconds_bytes; - - uint8_t first_octet_number_of_fractional_bytes = std::min(3, fractional_counter_bytes); - uint8_t second_octet_number_of_fractional_bytes = fractional_counter_bytes - first_octet_number_of_fractional_bytes; - - // P-Field extension is 1, CUC header is extended - header += 1; - - // AcubeSAT is using custom TAI epoch at 01 Jan 2020 - header = header << 3; - header += 0b010; - - // // Number of bytes in the basic time unit - header = header << 2; - header += first_octet_number_of_seconds_bytes - 1; - - // Number of bytes in the fractional unit - header = header << 2; - header += first_octet_number_of_fractional_bytes; - - // P-Field extension is 1, CUC header was extended - header = header << 1; - header += 1; - - // Number of bytes in the extended basic time unit - header = header << 2; - header += second_octet_number_of_seconds_bytes; - - // Number of bytes in the extended fractional unit - header = header << 3; - header += second_octet_number_of_fractional_bytes; - - // Last 3 LSb are reserved for custom mission use - header = header << 2; - header += 0; - - return header; -} - -template <typename T, int seconds_counter_bytes, int fractional_counter_bytes> -inline constexpr T build_CUC_header() { - static_assert((seconds_counter_bytes + fractional_counter_bytes) <= 8, - "Complete arbitrary precision not yet supported"); // TODO: see Issue #106 on Gitlab - // cppcheck-suppress syntaxError - // cppcheck-suppress redundantCondition - if constexpr (seconds_counter_bytes <= 4 && - fractional_counter_bytes <= 3) // if constexpr not supported yet in cppcheck - return build_short_CUC_header<seconds_counter_bytes, fractional_counter_bytes>(); - else - return build_long_CUC_header<seconds_counter_bytes, fractional_counter_bytes>(); -} - -inline constexpr uint8_t build_AcubeSAT_CDS_header() { - uint8_t header = 0; - - // bit 0 is at 0 - header += 0; - header << 1; - - // timecode identification - header += 0b100; - header << 3; - - // AcubeSAT is using custom TAI epoch at 01 Jan 2020 - header += 1; - header << 1; - - // AcubeSAT is using 16 bits day count segment - header += 0; - header << 1; - - // AcubeSAT is using picosecond resolution - header += 0b10; - // header << 2; - - return header; -} -//////////////////////////////////////////////// - -////////// CONVENIENCE FUNCTIONS //////////// -bool is_leap_year(uint16_t year); - -////////// Transitory timestamps //////////// -// CUSTOM EPOCH FOR ALL ACUBESAT TIMESTAMPS IS 01 JAN 2020, EXCEPT UTC (UNIX) -class AcubeSAT_CDS_timestamp { -public: - static constexpr uint8_t P_FIELD = build_AcubeSAT_CDS_header(); - uint16_t day; - uint16_t ms_of_day; - uint32_t submilliseconds; - - uint64_t to_CDS_timestamp(); - void from_CDS_timestamp(uint64_t); -}; - -// CUSTOM EPOCH FOR ALL ACUBESAT TIMESTAMPS IS 01 JAN 2020, EXCEPT UTC (UNIX) -/** - * A class that represents UTC time and date, from UNIX EPOCH - * - * @note - * This class represents UTC (Coordinated Universal Time) date - */ -class UTC_Timestamp { -public: - uint16_t year; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; - - /** - * Assign the instances with the Unix epoch 1/1/1970 00:00:00 - */ - UTC_Timestamp(); - - /** - * @param year the year as it used in Gregorian calendar - * @param month the month as it used in Gregorian calendar - * @param day the day as it used in Gregorian calendar - * @param hour UTC hour in 24 format - * @param minute UTC minutes - * @param second UTC seconds - */ - UTC_Timestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second); - - /** - * @param text_timestamp the timestamp to parse into a UTC date - */ - UTC_Timestamp(etl::string<32> text_timestamp); - - /** - * Compare two timestamps. - * - * @param Date the date that will be compared with the pointer `this` - * @return true if the condition is satisfied - */ - bool operator<(const UTC_Timestamp& Date); - bool operator>(const UTC_Timestamp& Date); - bool operator==(const UTC_Timestamp& Date); - bool operator<=(const UTC_Timestamp& Date); - bool operator>=(const UTC_Timestamp& Date); - - /** - * Pretty-print timestamp. - * - * @param Date the date that will be output - */ - friend std::ostream& operator<<(std::ostream& o, UTC_Timestamp const& Date); -}; diff --git a/inc/Helpers/TimeStamp.hpp b/inc/Helpers/TimeStamp.hpp deleted file mode 100644 index ef8190952f6ceefbac0a3a848093836c34a1b6bd..0000000000000000000000000000000000000000 --- a/inc/Helpers/TimeStamp.hpp +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef ECSS_SERVICES_TIME_HPP -#define ECSS_SERVICES_TIME_HPP - -#include <cstdint> -#include <algorithm> -#include "macros.hpp" -#include <etl/array.h> -#include "Helpers/TimeFormats.hpp" - -// SEE CCSDS 301.0-B-4 -// CUSTOM EPOCH FOR ALL ACUBESAT TIMESTAMPS IS 01 JAN 2020, EXCEPT UTC (UNIX) - -///////////// CLASS DECLARATION //////////////// -/** - * A class that represents an instant in time, with convenient conversion - * to and from usual time and date representations - * - * @note - * This class uses internally TAI time, and handles UTC leap seconds at conversion to and - * from UTC time system. - */ -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -class TimeStamp { -private: - static_assert(seconds_counter_bytes + fractional_counter_bytes <= 8, - "Currently, this class is not suitable for storage on internal counter larger than uint64_t"); - typedef typename std::conditional - < - seconds_counter_bytes < 4 && fractional_counter_bytes < 3, - uint8_t, - uint16_t - > - ::type CUC_header_t; - typedef typename std::conditional - < - (seconds_counter_bytes + fractional_counter_bytes) < 4, - uint32_t, - uint64_t - > - ::type tai_counter_t; - tai_counter_t tai_counter; - CUC_header_t CUC_header = build_CUC_header<CUC_header_t, seconds_counter_bytes, fractional_counter_bytes>(); - -public: - /** - * Initialize the instant at epoch - * - */ - TimeStamp() : tai_counter(0){}; - - /** - * Initialize the instant from a duration from epoch in TAI - * - * @param seconds an integer number of seconds from AcubeSAT custom epoch - */ - TimeStamp(int tai_seconds_from_AcubeSAT_epoch); - - /** - * Initialize the instant from the bytes of a CUC time stamp - * - * @param timestamp a complete CUC time stamp including header, of the maximum possible size, zero padded to the - * right - */ - TimeStamp(etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> timestamp); - - /** - * Initialize the instant from a UTC timestamp struct - * - * @param timestamp a UTC timestamp, from Unix Epoch - */ - TimeStamp(UTC_Timestamp timestamp); - - /** - * Initialize the instant from an AcubeSAT CDS timestamp - * - * @param timestamp a CDS timestamp, in the format from DDJF_TTC, from epoch 2020.01.01 - */ - TimeStamp(AcubeSAT_CDS_timestamp timestamp); - - /** - * Get the representation as seconds from epoch in TAI - * - * @return the seconds elapsed in TAI since 1 Jan 1958, cut to the integer part - */ - const int as_TAI_seconds(); - - /** - * Get the representation as CUC formatted bytes - * - * @return the instant, represented in the CCSDS CUC format - */ - const etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> as_CUC_timestamp(); - - /** - * Get the representation as a UTC timestamp - * - * @return the instant, represented in the structure that holds UTC timestamps - */ - const UTC_Timestamp as_UTC_timestamp(); - - /** - * Compare two instants. - * - * @param TimeStamp the date that will be compared with the pointer `this` - * @return true if the condition is satisfied - */ - bool operator<(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; - bool operator>(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; - bool operator==(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; - bool operator!=(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; - bool operator<=(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; - bool operator>=(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; -}; -//////////////////////////////////////////////// - -////// TEMPLATED METHODS INSTANTIATION ///////// -#include "TimeStamp.tpp" -//////////////////////////////////////////////// - -#endif diff --git a/inc/Helpers/TimeStamp.tpp b/inc/Helpers/TimeStamp.tpp deleted file mode 100644 index 1d70e753cd94a215fa6d9f368e05c70d23f65423..0000000000000000000000000000000000000000 --- a/inc/Helpers/TimeStamp.tpp +++ /dev/null @@ -1,240 +0,0 @@ -#define SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00001100 -#define SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2 0b01100000 -#define FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00000011 -#define FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2 0b00011100 - -#define SECONDS_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00001100 -#define FRACTIONAL_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00000011 - -#include <math.h> - -bool is_leap_year(uint16_t year); - -////////////: CONSTRUCTORS //////////// -//// FROM TAI SECONDS -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(int tai_seconds_from_AcubeSAT_epoch) { - static constexpr uint64_t max_seconds_counter_value = static_cast<uint64_t>(seconds_counter_bytes << 2) - 1; - // cppcheck-suppress useInitializationList - tai_counter = static_cast<tai_counter_t>(tai_seconds_from_AcubeSAT_epoch) << 8 * fractional_counter_bytes; - ASSERT_INTERNAL(tai_seconds_from_AcubeSAT_epoch >= max_seconds_counter_value, ErrorHandler::InternalErrorType::InvalidTimeStampInput); -} - -//// FROM CDS TIMESTAMP -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(AcubeSAT_CDS_timestamp timestamp) { - // tai_counter = 0; //TODO: see Issue #105 on Gitlab -} - -//// FROM CUC TIMESTAMP -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp( - etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> timestamp) { - // process header - int header_size = 1; - if (timestamp[0] & 0b10000000) { - header_size = 2; - }; - - int timestamp_fractional_bytes_count = 0; - int timestamp_seconds_bytes_count = 1; - - if (header_size == 2) { - timestamp_seconds_bytes_count += (timestamp[0] & SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 2; - timestamp_seconds_bytes_count += (timestamp[1] & SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2) >> 5; - timestamp_fractional_bytes_count = - ((timestamp[0] & FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 0) + - ((timestamp[1] & FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2) >> 2); - } else if (header_size == 1) { - timestamp_seconds_bytes_count += (timestamp[0] & SECONDS_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 2; - timestamp_fractional_bytes_count = - (timestamp[0] & FRACTIONAL_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 0; - } else { - ASSERT_INTERNAL(true, ErrorHandler::InternalErrorType::InvalidTimeStampInput); - } - - // check input validity (useless bytes set to 0) - int err = 0; - for (int i = header_size + timestamp_seconds_bytes_count + timestamp_fractional_bytes_count; i < 9; i++) { - if (timestamp[i] != 0) { - err += 1; - break; - } - } - ASSERT_INTERNAL(err == 0, ErrorHandler::InternalErrorType::InvalidTimeStampInput); - - // do checks wrt template precision parameters - ASSERT_INTERNAL(timestamp_seconds_bytes_count <= seconds_counter_bytes, - ErrorHandler::InternalErrorType::InvalidTimeStampInput); - ASSERT_INTERNAL(timestamp_fractional_bytes_count <= fractional_counter_bytes, - ErrorHandler::InternalErrorType::InvalidTimeStampInput); - - // put timestamp into internal counter - tai_counter = 0; - // add seconds until run out of bytes on input array - for (auto i = 0; i < timestamp_seconds_bytes_count; i++) { - tai_counter = tai_counter << 8; - tai_counter += timestamp[header_size + i]; - } - // add fractional until run out of bytes on input array - for (auto i = 0; i < timestamp_fractional_bytes_count; i++) { - tai_counter = tai_counter << 8; - tai_counter += timestamp[header_size + timestamp_seconds_bytes_count + i]; - } - // pad rightmost bytes to full length - tai_counter = tai_counter << 8 * (fractional_counter_bytes - timestamp_fractional_bytes_count); -} - -//// FROM UTC TIMESTAMP -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(UTC_Timestamp timestamp) { - int seconds = UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS; - for (int year = ACUBESAT_EPOCH_YEAR; year < timestamp.year; ++year) { - seconds += (is_leap_year(year) ? 366 : 365) * SECONDS_PER_DAY; - } - for (int month = ACUBESAT_EPOCH_MONTH; month < timestamp.month; ++month) { - seconds += DAYSOFMONTH[month - 1] * SECONDS_PER_DAY; - if ((month == 2U) && is_leap_year(timestamp.year)) { - seconds += SECONDS_PER_DAY; - } - } - seconds += (timestamp.day - ACUBESAT_EPOCH_DAY) * SECONDS_PER_DAY; - seconds += timestamp.hour * SECONDS_PER_HOUR; - seconds += timestamp.minute * SECONDS_PER_MINUTE; - seconds += timestamp.second; - tai_counter = static_cast<tai_counter_t>(seconds) << 8 * fractional_counter_bytes; -} - -////////////// GETTER /////////////// -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -const int TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::as_TAI_seconds() { - return tai_counter >> (8 * fractional_counter_bytes); -} - -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -const etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> -TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::as_CUC_timestamp() { - etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> return_array = {0}; - int index_first_non_header_byte; - - // cppcheck-suppress redundantCondition - static constexpr uint8_t header_size = seconds_counter_bytes < 4 && fractional_counter_bytes < 3 ? 1 : 2; // number of bytes in CUC header - - if (header_size == 1) { - return_array[0] = static_cast<uint8_t>(CUC_header); - index_first_non_header_byte = 1; - } - - else { // two-bytes CUC header - return_array[1] = static_cast<uint8_t>(CUC_header); - return_array[0] = static_cast<uint8_t>(CUC_header >> 8); - index_first_non_header_byte = 2; - } - - for (auto byte_being_filled_in_return = 0; - byte_being_filled_in_return < seconds_counter_bytes + fractional_counter_bytes; - byte_being_filled_in_return++) { - int index_of_interesting_byte_in_tai_counter = - 8 * (seconds_counter_bytes + fractional_counter_bytes - byte_being_filled_in_return - 1); - return_array[index_first_non_header_byte + byte_being_filled_in_return] = - tai_counter >> index_of_interesting_byte_in_tai_counter; - } - - return return_array; -} - -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -const UTC_Timestamp TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::as_UTC_timestamp() { - int seconds = as_TAI_seconds(); - - // elapsed seconds should be between dates, that are after 1/1/2019 and Unix epoch - ASSERT_INTERNAL(seconds >= UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS, ErrorHandler::InternalErrorType::InvalidDate); - - seconds -= UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS; // elapsed seconds from Unix epoch until AcubeSAT custom epoch 00:00:00 (UTC) - int year_utc = ACUBESAT_EPOCH_YEAR; - int month_utc = ACUBESAT_EPOCH_MONTH; - int day_utc = ACUBESAT_EPOCH_DAY; - int hour = 0; - int minute = 0; - int second = 0; - - // calculate years - while (seconds >= (is_leap_year(year_utc) ? 366 : 365) * SECONDS_PER_DAY) { - seconds -= (is_leap_year(year_utc) ? 366 : 365) * SECONDS_PER_DAY; - year_utc++; - } - - // calculate months - int current_month = 0; - while (seconds >= (DAYSOFMONTH[current_month] * SECONDS_PER_DAY)) { - month_utc++; - seconds -= (DAYSOFMONTH[current_month] * SECONDS_PER_DAY); - current_month++; - if ((current_month == 1U) && is_leap_year(year_utc)) { - if (seconds <= (28 * SECONDS_PER_DAY)) { - break; - } - month_utc++; - seconds -= 29 * SECONDS_PER_DAY; - current_month++; - } - } - - // calculate days - day_utc = seconds / SECONDS_PER_DAY; - seconds -= day_utc * SECONDS_PER_DAY; - day_utc++; // add 1 day because we start count from 1 January (and not 0 January!) - - // calculate hours - hour = seconds / SECONDS_PER_HOUR; - seconds -= hour * SECONDS_PER_HOUR; - - // calculate minutes - minute = seconds / SECONDS_PER_MINUTE; - seconds -= minute * SECONDS_PER_MINUTE; - - // calculate seconds - second = seconds; - - return UTC_Timestamp(year_utc, month_utc, day_utc, hour, minute, second); -} - -////////////// OPERATORS /////////// -/// COMPARISON -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator==( - const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { - return tai_counter == TimeStamp.tai_counter; -} - -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator!=( - const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { - return tai_counter != TimeStamp.tai_counter; -} - -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator<( - const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { - return tai_counter < TimeStamp.tai_counter; -} - -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator>( - const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { - return tai_counter > TimeStamp.tai_counter; -} - -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator<=( - const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { - return tai_counter <= TimeStamp.tai_counter; -} - -template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> -bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator>=( - const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { - return tai_counter >= TimeStamp.tai_counter; -} -/// ARITHMETIC -// TODO: see Issue #104 on Gitlab repository diff --git a/inc/Platform/x86/Helpers/UTCTimestamp.hpp b/inc/Platform/x86/Helpers/UTCTimestamp.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d6b5eed4059e48f8c713158be277191bdb4c923e --- /dev/null +++ b/inc/Platform/x86/Helpers/UTCTimestamp.hpp @@ -0,0 +1,5 @@ +#include <Time/UTCTimestamp.hpp> +#include <ostream> +#include <iomanip> + +std::ostream& operator<<(std::ostream& o, UTCTimestamp const& date); diff --git a/inc/Time/Time.hpp b/inc/Time/Time.hpp new file mode 100644 index 0000000000000000000000000000000000000000..55dbaa16ec64dfac8a811d7b05ea719296c1e7b4 --- /dev/null +++ b/inc/Time/Time.hpp @@ -0,0 +1,268 @@ +#pragma once +#include <cstdint> +#include <Message.hpp> +#include "macros.hpp" +#include "etl/String.hpp" + +/** + * @defgroup Time Time + * + * Group of classes, functions and variables to represent and operate on time + * + * The implementation of the ECSS services requires storing and handling timestamps quite often. However, in an embedded + * system with mission-critical tasks, it is wide to avoid the complexity of a full time management system, as the one + * that comes with Linux or contains [large timezone databases](https://en.wikipedia.org/wiki/Tz_database). + * + * This repository provides a set of classes and utilities that allow handling of on-board spacecraft time. Most formats + * are compatible to the [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) specification, and are + * customisable to support different precisions, lengths and durations. Additionally, a number of formats and headers + * are provided so that timestamps can be transmitted and received from the Ground Station. + * + * The base timestamp class representing an instant in time is @ref TimeStamp. This is used as the internal + * representation of all moments in this repository. Additional formats are provided for convenience and compatibility + * with other systems: + * - @ref UTCTimestamp + * + * You can convert between different types of timestamp, either by using constructors, or conversion functions + * defined in each timestamp class. + * + * Note that you should try sticking to the default @ref TimeStamp class unless needed, as it provides the best + * precision and performance for the least amount of memory, and is directly compatible with internal timestamps + * used, without loss of precision. + * + * @section Epoch Epoch + * + * Internal timestamp representations can use a single point in time referring to $t=0$. This point is referred to as + * the **Epoch** and is stored in @ref Time::Epoch. CSSDS suggests using _1 January 1958_ as the Epoch. UNIX uses _1 + * January 1970_. In this repository however, each implementor can choose a different epoch (e.g. the start of the + * mission). + * + * @section DevUtils Developer utilities + * Timestamp classes are designed to make the developer's life easier. + * + * You can easily compare timestamps without having to call any other functions: + * @code + * if (timestamp1 < timestamp2) + * @endcode + * + * If you are building on x86, you can directly print UTC timestamps: + * @code + * std::cout << utcTimestamp << std::endl; + * @endcode + * + * @section UTC UTC and Leap seconds + * All internal timestamps are represented in the GMT+00:00 timezone due to the high expense of timezone + * calculations. + * + * This implementation uses **TAI (International Atomic Time)** instead of UTC (Coordinated Universal Time). + * [TAI](https://en.wikipedia.org/wiki/International_Atomic_Time) is equivalent to UTC, with the exception of leap + * seconds, which are occasionally added to match terrestrial and astronomical time. As leap seconds are added after + * human intervention to UTC, it is impossible for a satellite to know the exact UTC time without ground station + * intervention. + */ + +/** + * An armada of utilities regarding timekeeping, timestamps and conversion between different internal and string + * time formats. This file implements [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf). + * + * @ingroup Time + * @author Baptiste Fournier + */ +namespace Time +{ +inline constexpr uint8_t SecondsPerMinute = 60; +inline constexpr uint16_t SecondsPerHour = 3600; +inline constexpr uint32_t SecondsPerDay = 86400; +static constexpr uint8_t DaysOfMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +/** + * Number of bytes used for the basic time units of the CUC header for this mission + */ +inline constexpr uint8_t CUCSecondsBytes = 2; + +/** + * Number of bytes used for the fractional time units of the CUC header for this mission + */ +inline constexpr uint8_t CUCFractionalBytes = 2; + +/** + * The system epoch (clock measurement starting time) + * All timestamps emitted by the ECSS services will show the elapsed time (seconds, days etc.) from this epoch. + */ +inline constexpr struct { + uint16_t year; + uint8_t month; + uint8_t day; +} Epoch{ + 2020, + 1, + 1, +}; + +/** + * Number of seconds elapsed between the UNIX epoch (1 January 1970) and the system epoch. + * + * The system epoch is defined by @ref Epoch. + * This constant is used for conversion between Unix and other timestamps. + * Leap seconds are not taken into account here. + * + * @warning This value MUST be updated after every change of the system @ref Epoch. You can use utilities such as + * https://www.unixtimestamp.com/ to obtain a correct result. + */ +inline constexpr uint32_t EpochSecondsFromUnix = 1577836800; + +/** + * The maximum theoretical size in bytes of a CUC timestamp, including headers (P-field and T-field) + */ +inline constexpr uint8_t CUCTimestampMaximumSize = 9; + +static_assert(Epoch.year >= 2019); +static_assert(Epoch.month < 11 && Epoch.month >= 0); +static_assert(Epoch.day < DaysOfMonth[Epoch.month]); + +/** + * Builds the short P-field of the CUC (CCSDS Unsegmented Time Code) format, as defined in CCSDS 301.0-B-4. + * + * The short P-field contains only one byte. It is used when many octets are used to represent the basic or fractional + * time units. + * + * @see CCSDS 301.0-B-4, Section 3.2.2 + * @tparam secondsBytes The number of octets used to represent the basic time units + * @tparam fractionalBytes The number of octets used to represent the fractional time units + * @return A single byte, representing the P-field contents + */ +template <int secondsBytes, int fractionalBytes> +inline constexpr uint8_t buildShortCUCHeader() { + static_assert(secondsBytes <= 4, "Use buildLongCUCHeader instead"); + static_assert(fractionalBytes <= 3, "Use buildLongCUCHeader instead"); + + uint8_t header = 0; + + // P-Field extension is 0, CUC header is not extended + header += 0; + + // We are using a custom TAI epoch ("agency-defined epoch") + header <<= 3U; + header += 0b010; + + // Number of bytes in the basic time unit + header <<= 2U; + header += secondsBytes - 1; + + // Number of bytes in the fractional unit + header <<= 2U; + header += fractionalBytes; + + return header; +} + +/** + * Builds the long P-field of the CUC (CCSDS Unsegmented Time Code) format, as defined in CCSDS 301.0-B-4. + * + * The long P-field contains two bytes. The 2nd byte is used to define the size of the additional octets added to the + * timestamp, which could not fit in a short P-field. + * + * @see CCSDS 301.0-B-4, Section 3.2.2 + * @tparam secondsBytes The number of octets used to represent the basic time units + * @tparam fractionalBytes The number of octets used to represent the fractional time units + * @return Two bytes, representing the P-field contents + */ +template <int secondsBytes, int fractionalBytes> +inline constexpr uint16_t buildLongCUCHeader() { + // cppcheck-suppress redundantCondition + static_assert(secondsBytes > 4 || fractionalBytes > 3, "Use buildShortCUCHeader instead"); + static_assert(secondsBytes <= 7, "Number of bytes for seconds over maximum number of octets allowed by CCSDS"); + static_assert(fractionalBytes <= 6, "Number of bytes for seconds over maximum number of octets allowed by CCSDS"); + + uint16_t header = 0; + + uint8_t octet1secondsBytes = std::min(4, secondsBytes); + uint8_t octet2secondsBytes = secondsBytes - octet1secondsBytes; + + uint8_t octet1fractionalBytes = std::min(3, fractionalBytes); + uint8_t octet2fractionalBytes = fractionalBytes - octet1fractionalBytes; + + // P-Field extension is 1, CUC header is extended + header += 1; + + // We are using custom a TAI epoch + header <<= 3U; + header += 0b010; + + // Number of bytes in the basic time unit + header <<= 2U; + header += octet1secondsBytes - 1; + + // Number of bytes in the fractional unit + header <<= 2U; + header += octet1fractionalBytes; + + // P-Field extension is 1, CUC header was extended + header <<= 1U; + header += 1; + + // Number of bytes in the extended basic time unit + header <<= 2U; + header += octet2secondsBytes; + + // Number of bytes in the extended fractional unit + header <<= 3U; + header += octet2fractionalBytes; + + // Last 3 LSB are reserved for custom mission use + header <<= 2U; + header += 0; + + return header; +} + +/** + * Builds the entire P-field of the CUC (CCSDS Unsegmented Time Code) format, as defined in CCSDS 301.0-B-4. + * + * The P-field contains the metadata of the timestamp, including information about its size and epoch. This function + * is implemented for arbitrary sizes (_octet count_) for the basic and fractional time units. + * + * The following options cannot be changed: + * - Time-code identification is set to an _agency-defined epoch_. This is represented by @ref Epoch. + * - Bits 6-7 of octet 2 (_reserved_) are set to `0` + * + * @note The P-field (header) does not contain the timestamp information, but only the description of the timestamp's + * structure. It may be entirely omitted if the structure is known beforehand. + * + * @see CCSDS 301.0-B-4, Section 3.2.2 + * @tparam T An arbitrary `uint` return type of the header + * @tparam secondsBytes The number of octets used to represent the basic time units + * @tparam fractionalBytes The number of octets used to represent the fractional time units + * @return One or two bytes representing the header + */ +template <typename T, int secondsBytes, int fractionalBytes> +inline constexpr T buildCUCHeader() { + // TODO: Gitlab issue #106 + static_assert((secondsBytes + fractionalBytes) <= 8, "Complete arbitrary precision not supported"); + // cppcheck-suppress syntaxError + // cppcheck-suppress redundantCondition + if constexpr (secondsBytes <= 4 && fractionalBytes <= 3) { + return buildShortCUCHeader<secondsBytes, fractionalBytes>(); + } else { + return buildLongCUCHeader<secondsBytes, fractionalBytes>(); + } +} + +/** + * Returns whether a year is a leap year according to the Gregorian calendar + */ +constexpr bool isLeapYear(uint16_t year) { + if ((year % 4) != 0) { + return false; + } + if ((year % 100) != 0) { + return true; + } + return (year % 400) == 0; +} + +typedef struct { + uint64_t elapsed100msTicks = 0; +} CustomCUC_t; + +} // namespace Time diff --git a/inc/Time/TimeStamp.hpp b/inc/Time/TimeStamp.hpp new file mode 100644 index 0000000000000000000000000000000000000000..499fb3c66ac9869be384e9fde471e4eb903d5c05 --- /dev/null +++ b/inc/Time/TimeStamp.hpp @@ -0,0 +1,168 @@ +#ifndef ECSS_SERVICES_TIME_HPP +#define ECSS_SERVICES_TIME_HPP + +#include <cstdint> +#include <algorithm> +#include "macros.hpp" +#include <etl/array.h> +#include "Time.hpp" +#include "UTCTimestamp.hpp" + +/** + * A class that represents an instant in time, with convenient conversion + * to and from usual time and date representations + * + * @note + * This class uses internally TAI time, and handles UTC leap seconds at conversion to and + * from UTC time system. + * + * @ingroup Time + * @author Baptiste Fournier + * @see [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) + */ +template <uint8_t secondsBytes, uint8_t fractionalBytes> +class TimeStamp { +private: + static_assert(secondsBytes + fractionalBytes <= 8, + "Currently, this class is not suitable for storage on internal counter larger than uint64_t"); + typedef typename std::conditional<(secondsBytes < 4 && fractionalBytes < 3), uint8_t, uint16_t>::type CUCHeader_t; + typedef typename std::conditional<(secondsBytes + fractionalBytes < 4), uint32_t, uint64_t>::type TAICounter_t; + + /** + * Integer counter of time units since the @ref Time::Epoch. This number essentially represents the timestamp. + * + * The unit represented by this variable depends on `secondsBytes` and `fractionalBytes`. The fractional + * part is included as the least significant bits of this variable, and the base part follows. + */ + TAICounter_t taiCounter; + + /** + * The constant header ("P-value") of the timestamp, if needed to be attached to any message + */ + static constexpr CUCHeader_t CUCHeader = Time::buildCUCHeader<CUCHeader_t, secondsBytes, fractionalBytes>(); + + /** + * The maximum value that can fit in @ref taiCounter, or the maximum number of seconds since epoch that can be + * represented in this base class + */ + static constexpr uint64_t maxSecondCounterValue = (1U << (8U * secondsBytes)) - 1; + + /** + * Returns whether the amount of `seconds` can be represented by this TimeStamp. + * If `seconds` is too large, the number of `secondsByte` may not be enough to represent this timestamp. + * + * @param seconds The amount of seconds from @ref Time::Epoch + */ + static constexpr bool areSecondsValid(TAICounter_t seconds); + +public: + /** + * Initialize the TimeStamp at @ref Time::Epoch + */ + TimeStamp() : taiCounter(0){}; + + /** + * Initialize the TimeStamp from a duration from epoch in TAI (leap seconds not accounted) + * + * @param taiSecondsFromEpoch An integer number of seconds from the custom @ref Time::Epoch + */ + explicit TimeStamp(uint64_t taiSecondsFromEpoch); + + /** + * Initialize the TimeStamp from a count of 100ms ticks from epoch in TAI (leap seconds not accounted) + * + * @param customCUCTimestamp An struct containing a 64 bit unsigned number of 100ms + * ticks from the custom @ref Time::Epoch + */ + explicit TimeStamp(Time::CustomCUC_t customCUCTimestamp); + + /** + * Initialize the TimeStamp from the bytes of a CUC time stamp + * + * @param timestamp A complete CUC timestamp including header, of the maximum possible size, zero padded to the + * right + */ + explicit TimeStamp(etl::array<uint8_t, Time::CUCTimestampMaximumSize> timestamp); + + /** + * Initialize the Timestamp from a UTC timestamp struct + * + * @param timestamp a UTC timestamp, from Unix Epoch + */ + explicit TimeStamp(const UTCTimestamp& timestamp); + + /** + * Get the representation as seconds from epoch in TAI + * + * @return The seconds elapsed in TAI since @ref Time::Epoch. This function is explicitly defined + */ + TAICounter_t asTAIseconds(); + + /** + * Get the representation as a struct containing 100ms ticks from epoch in TAI + * + * @return An struct containing a 64 bit unsigned number of 100ms + * ticks from the custom @ref Time::Epoch. This function is explicitly defined. + */ + Time::CustomCUC_t asCustomCUCTimestamp(); + + /** + * Get the representation as seconds from epoch in TAI, for a floating-point representation. + * For an integer result, see the overloaded @ref asTAIseconds function. + * + * @todo Implement integer seconds in this function + * @tparam T The return type of the seconds (float or double). + * @return The seconds elapsed in TAI since @ref Time::Epoch + */ + template <typename T> + T asTAIseconds(); + + /** + * Get the representation as CUC formatted bytes + * + * @return The TimeStamp, represented in the CCSDS CUC format + */ + etl::array<uint8_t, Time::CUCTimestampMaximumSize> toCUCtimestamp(); + + /** + * Get the representation as a UTC timestamp + * + * @return The TimeStamp, represented in the structure that holds UTC timestamps + */ + UTCTimestamp toUTCtimestamp(); + + /** + * Compare two timestamps. + * + * @param timestamp the date that will be compared with the pointer `this` + * @return true if the condition is satisfied + */ + bool operator<(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const { + return taiCounter < timestamp.taiCounter; + } + + bool operator>(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const { + return taiCounter > timestamp.taiCounter; + } + + bool operator==(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const { + return taiCounter == timestamp.taiCounter; + } + + bool operator!=(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const { + return taiCounter != timestamp.taiCounter; + } + + bool operator<=(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const { + return taiCounter <= timestamp.taiCounter; + } + + bool operator>=(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const { + return taiCounter >= timestamp.taiCounter; + } +}; + +#include "TimeStamp.tpp" +typedef TimeStamp<Time::CUCSecondsBytes, Time::CUCFractionalBytes> AcubeSATTimeStamp_t; + +#endif diff --git a/inc/Time/TimeStamp.tpp b/inc/Time/TimeStamp.tpp new file mode 100644 index 0000000000000000000000000000000000000000..34edf5a2c291153b1a38778ebc0462ec661d9fd6 --- /dev/null +++ b/inc/Time/TimeStamp.tpp @@ -0,0 +1,187 @@ +template <uint8_t secondsBytes, uint8_t fractionalBytes> +constexpr bool TimeStamp<secondsBytes, fractionalBytes>::areSecondsValid(TimeStamp::TAICounter_t seconds) { + return seconds < maxSecondCounterValue; +} + +template <uint8_t secondsBytes, uint8_t fractionalBytes> +TimeStamp<secondsBytes, fractionalBytes>::TimeStamp(uint64_t taiSecondsFromEpoch) { + ASSERT_INTERNAL(areSecondsValid((taiSecondsFromEpoch)), ErrorHandler::InternalErrorType::InvalidTimeStampInput); + + taiCounter = static_cast<TAICounter_t>(taiSecondsFromEpoch) << 8 * fractionalBytes; +} + +template <uint8_t secondsBytes, uint8_t fractionalBytes> +TimeStamp<secondsBytes, fractionalBytes>::TimeStamp(Time::CustomCUC_t customCUCTimestamp) { + ASSERT_INTERNAL(areSecondsValid((customCUCTimestamp.elapsed100msTicks / 10)), + ErrorHandler::InternalErrorType::InvalidTimeStampInput); + taiCounter = static_cast<TAICounter_t>(customCUCTimestamp.elapsed100msTicks / 10); + if (fractionalBytes > 0) { + TAICounter_t fractionalPart = static_cast<TAICounter_t>(customCUCTimestamp.elapsed100msTicks) - 10 * taiCounter; + taiCounter = taiCounter << 8; + taiCounter += fractionalPart * 256 / 10; + taiCounter = taiCounter << 8 * (fractionalBytes - 1); + } +} + +template <uint8_t secondsCounter, uint8_t fractionalBytes> +TimeStamp<secondsCounter, fractionalBytes>::TimeStamp(etl::array<uint8_t, Time::CUCTimestampMaximumSize> timestamp) { + // process header + uint8_t headerSize = 1; + if ((timestamp[0] & 0b10000000U) != 0) { + headerSize = 2; + } + + uint8_t inputSecondsBytes = ((timestamp[0] & 0b00001100U) >> 2U) + 1U; + uint8_t inputFractionalBytes = (timestamp[0] & 0b00000011U) >> 0U; + + if (headerSize == 2) { + inputSecondsBytes += (timestamp[1] & 0b01100000U) >> 5U; + inputFractionalBytes += (timestamp[1] & 0b00011100U) >> 2U; + } + + // check input validity (useless bytes set to 0) + for (int i = headerSize + inputSecondsBytes + inputFractionalBytes; i < Time::CUCTimestampMaximumSize; i++) { + if (timestamp[i] != 0) { + ErrorHandler::reportInternalError(ErrorHandler::InternalErrorType::InvalidTimeStampInput); + break; + } + } + + // do checks wrt template precision parameters + ASSERT_INTERNAL(inputSecondsBytes <= secondsCounter, ErrorHandler::InternalErrorType::InvalidTimeStampInput); + ASSERT_INTERNAL(inputFractionalBytes <= fractionalBytes, ErrorHandler::InternalErrorType::InvalidTimeStampInput); + + // put timestamp into internal counter + taiCounter = 0; + // add seconds until run out of bytes on input array + for (auto i = 0; i < inputSecondsBytes + inputFractionalBytes; i++) { + taiCounter = taiCounter << 8; + taiCounter += timestamp[headerSize + i]; + } + // pad rightmost bytes to full length + taiCounter = taiCounter << 8 * (fractionalBytes - inputFractionalBytes); +} + +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(const UTCTimestamp& timestamp) { + TAICounter_t seconds = 0; + for (int year = Time::Epoch.year; year < timestamp.year; ++year) { + seconds += (Time::isLeapYear(year) ? 366 : 365) * Time::SecondsPerDay; + } + for (int month = Time::Epoch.month; month < timestamp.month; ++month) { + seconds += Time::DaysOfMonth[month - 1] * Time::SecondsPerDay; + if ((month == 2U) && Time::isLeapYear(timestamp.year)) { + seconds += Time::SecondsPerDay; + } + } + seconds += (timestamp.day - Time::Epoch.day) * Time::SecondsPerDay; + seconds += timestamp.hour * Time::SecondsPerHour; + seconds += timestamp.minute * Time::SecondsPerMinute; + seconds += timestamp.second; + // TODO: Add check that `seconds` is within bounds (?) + taiCounter = static_cast<TAICounter_t>(seconds) << 8 * fractional_counter_bytes; +} + +template <uint8_t secondsBytes, uint8_t fractionalBytes> +typename TimeStamp<secondsBytes, fractionalBytes>::TAICounter_t +TimeStamp<secondsBytes, fractionalBytes>::asTAIseconds() { + return taiCounter >> (8 * fractionalBytes); +} + +template <uint8_t secondsBytes, uint8_t fractionalBytes> +Time::CustomCUC_t TimeStamp<secondsBytes, fractionalBytes>::asCustomCUCTimestamp() { + TAICounter_t temp = taiCounter; + Time::CustomCUC_t return_s = {0}; + if (fractionalBytes > 0) { + temp = temp >> 8 * (fractionalBytes - 1); + return_s.elapsed100msTicks += temp * 10 / 256; + } else { + return_s.elapsed100msTicks += temp * 10; + } + return return_s; +} + +template <uint8_t secondsBytes, uint8_t fractionalBytes> +template <typename T> +T TimeStamp<secondsBytes, fractionalBytes>::asTAIseconds() { + static_assert(std::is_floating_point_v<T>, "TimeStamp::asTAIseconds() only accepts numeric types."); + static_assert(std::numeric_limits<T>::max() >= maxSecondCounterValue); + + TAICounter_t decimalPart = taiCounter >> (8 * fractionalBytes); + + T fractionalPart = taiCounter - (decimalPart << (8 * fractionalBytes)); + T fractionalPartMax = (1U << (8U * fractionalBytes)) - 1U; + + return decimalPart + fractionalPart / fractionalPartMax; +} + +template <uint8_t secondsBytes, uint8_t fractionalBytes> +etl::array<uint8_t, Time::CUCTimestampMaximumSize> TimeStamp<secondsBytes, fractionalBytes>::toCUCtimestamp() { + etl::array<uint8_t, Time::CUCTimestampMaximumSize> returnArray = {0}; + + static constexpr uint8_t headerBytes = (secondsBytes < 4 && fractionalBytes < 3) ? 1 : 2; + + if (headerBytes == 1) { + returnArray[0] = static_cast<uint8_t>(CUCHeader); + } else { + returnArray[1] = static_cast<uint8_t>(CUCHeader); + returnArray[0] = static_cast<uint8_t>(CUCHeader >> 8); + } + + for (auto byte = 0; byte < secondsBytes + fractionalBytes; byte++) { + uint8_t taiCounterIndex = 8 * (secondsBytes + fractionalBytes - byte - 1); + returnArray[headerBytes + byte] = taiCounter >> taiCounterIndex; + } + + return returnArray; +} + +template <uint8_t secondsBytes, uint8_t fractionalBytes> +UTCTimestamp TimeStamp<secondsBytes, fractionalBytes>::toUTCtimestamp() { + using namespace Time; + + uint32_t totalSeconds = asTAIseconds(); + + uint16_t yearUTC = Epoch.year; + uint8_t monthUTC = Epoch.month; + uint8_t dayUTC = Epoch.day; + uint8_t hour = 0; + uint8_t minute = 0; + uint8_t second = 0; + + // calculate years + while (totalSeconds >= (isLeapYear(yearUTC) ? 366 : 365) * SecondsPerDay) { + totalSeconds -= (isLeapYear(yearUTC) ? 366 : 365) * SecondsPerDay; + yearUTC++; + } + + // calculate months + int currentMonth = 0; + while (totalSeconds >= (DaysOfMonth[currentMonth] * SecondsPerDay)) { + monthUTC++; + totalSeconds -= (DaysOfMonth[currentMonth] * SecondsPerDay); + currentMonth++; + if ((currentMonth == 1U) && isLeapYear(yearUTC)) { + if (totalSeconds <= (28 * SecondsPerDay)) { + break; + } + monthUTC++; + totalSeconds -= 29 * SecondsPerDay; + currentMonth++; + } + } + + dayUTC = totalSeconds / SecondsPerDay; + totalSeconds -= dayUTC * SecondsPerDay; + dayUTC++; // add 1 day because we start count from 1 January (and not 0 January!) + + hour = totalSeconds / SecondsPerHour; + totalSeconds -= hour * SecondsPerHour; + + minute = totalSeconds / SecondsPerMinute; + totalSeconds -= minute * SecondsPerMinute; + + second = totalSeconds; + + return {yearUTC, monthUTC, dayUTC, hour, minute, second}; +} diff --git a/inc/Time/UTCTimestamp.hpp b/inc/Time/UTCTimestamp.hpp new file mode 100644 index 0000000000000000000000000000000000000000..062402df0926efcf9d739cadd7182ca1710a8fe2 --- /dev/null +++ b/inc/Time/UTCTimestamp.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include <cstdint> +#include <etl/String.hpp> + +/** + * A class that represents a UTC time and date according to ISO 8601 + * + * This class contains a human-readable representation of a timestamp, accurate down to 1 second. It is not used + * for timestamp storage in the satellite due to its high performance and memory cost, but it can be used for + * debugging and logging purposes. + * + * @ingroup Time + * @note + * This class represents UTC (Coordinated Universal Time) date + */ +class UTCTimestamp { +public: + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + + /** + * Initialise a timestamp with the Unix epoch 1/1/1970 00:00:00 + */ + UTCTimestamp(); + + /** + * + * @todo See if this implements leap seconds + * @todo Implement leap seconds as ST[20] parameter + * @param year the year as it used in Gregorian calendar + * @param month the month as it used in Gregorian calendar (1-12 inclusive) + * @param day the day as it used in Gregorian calendar (1-31 inclusive) + * @param hour UTC hour in 24-hour format + * @param minute UTC minutes + * @param second UTC seconds + */ + UTCTimestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second); + + /** + * @param textTimestamp the timestamp to parse into a UTC date + * @todo Too expensive to implement (?). It is better to remove this and open it as another issue, or create + * a platform-specific converter that will be only used in x86. + */ + explicit UTCTimestamp(etl::string<32> textTimestamp); + + /** + * Compare two timestamps. + * @param Date the date that will be compared with the pointer `this` + */ + bool operator<(const UTCTimestamp& Date) const; + bool operator>(const UTCTimestamp& Date) const; ///< @copydoc UTCTimestamp::operator< + bool operator==(const UTCTimestamp& Date) const; ///< @copydoc UTCTimestamp::operator< + bool operator<=(const UTCTimestamp& Date) const; ///< @copydoc UTCTimestamp::operator< + bool operator>=(const UTCTimestamp& Date) const; ///< @copydoc UTCTimestamp::operator< +}; diff --git a/src/Platform/x86/Time/UTCTimestamp.cpp b/src/Platform/x86/Time/UTCTimestamp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c48aecd4f00d8e2a356c443bf933236d15c8052c --- /dev/null +++ b/src/Platform/x86/Time/UTCTimestamp.cpp @@ -0,0 +1,12 @@ +#include <Time/UTCTimestamp.hpp> +#include <ostream> +#include <iomanip> + +std::ostream& operator<<(std::ostream& o, UTCTimestamp const& date) { + // YYYY-MM-DDTHH:mm:ss.sssZ + o.fill('0'); + o << static_cast<int>(date.year) << "-" << std::setw(2) << static_cast<int>(date.month) << "-" << std::setw(2) + << static_cast<int>(date.day) << "T" << std::setw(2) << static_cast<int>(date.hour) << ":" << std::setw(2) + << static_cast<int>(date.minute) << ":" << std::setw(2) << static_cast<int>(date.second) << ":000Z" << std::endl; + return o; +} diff --git a/src/Platform/x86/main.cpp b/src/Platform/x86/main.cpp index 90f36f8b4f5869182d9cfc78c1546837186caa66..016647fda0cf1e2142123300802652a6b2899222 100644 --- a/src/Platform/x86/main.cpp +++ b/src/Platform/x86/main.cpp @@ -1,5 +1,7 @@ #include <iostream> #include <Logger.hpp> +#include <Time/UTCTimestamp.hpp> +#include <Platform/x86/Helpers/UTCTimestamp.hpp> #include "Helpers/CRCHelper.hpp" #include "Services/TestService.hpp" #include "Services/ParameterService.hpp" @@ -360,5 +362,7 @@ int main() { timeBasedSchedulingService.summaryReportActivitiesByID(receivedMsg); LOG_NOTICE << "ECSS Services test complete"; + + std::cout << UTCTimestamp() << std::endl; return 0; } diff --git a/src/Helpers/TimeFormats.cpp b/src/Time/UTCTimestamp.cpp similarity index 60% rename from src/Helpers/TimeFormats.cpp rename to src/Time/UTCTimestamp.cpp index a699de5ee859949d4586683ffa19fdea33ec8872..072d6b67ff32e1db02d1a835a6ae61c3d43d9a1f 100644 --- a/src/Helpers/TimeFormats.cpp +++ b/src/Time/UTCTimestamp.cpp @@ -1,29 +1,11 @@ -#include "Helpers/TimeFormats.hpp" +#include "Time/Time.hpp" +#include "Time/UTCTimestamp.hpp" #include <iomanip> -bool is_leap_year(uint16_t year) { - if ((year % 4) != 0) { - return false; - } - if ((year % 100) != 0) { - return true; - } - return (year % 400) == 0; -} +UTCTimestamp::UTCTimestamp() : year(1970), month(1), second(0), minute(0), hour(0), day(1) {} -UTC_Timestamp::UTC_Timestamp() { - // Unix epoch 1/1/1970 - year = 1970; - month = 1; - day = 1; - hour = 0; - minute = 0; - second = 0; -} - -UTC_Timestamp::UTC_Timestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { - // check if the parameters make sense - ASSERT_INTERNAL(2019 <= year, ErrorHandler::InternalErrorType::InvalidDate); +UTCTimestamp::UTCTimestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { + ASSERT_INTERNAL(1970 <= year, ErrorHandler::InternalErrorType::InvalidDate); ASSERT_INTERNAL((1 <= month) && (month <= 12), ErrorHandler::InternalErrorType::InvalidDate); ASSERT_INTERNAL((1 <= day) && (day <= 31), ErrorHandler::InternalErrorType::InvalidDate); ASSERT_INTERNAL(hour < 24, ErrorHandler::InternalErrorType::InvalidDate); @@ -39,8 +21,7 @@ UTC_Timestamp::UTC_Timestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t this->second = second; } -bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) { - // compare years +bool UTCTimestamp::operator<(const UTCTimestamp& Date) const { if (this->year < Date.year) { return true; } @@ -48,7 +29,6 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) { return false; } - // compare months if (this->month < Date.month) { return true; } @@ -56,7 +36,6 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) { return false; } - // compare days if (this->day < Date.day) { return true; } @@ -64,7 +43,6 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) { return false; } - // compare hours if (this->hour < Date.hour) { return true; } @@ -72,7 +50,6 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) { return false; } - // compare minutes if (this->minute < Date.minute) { return true; } @@ -80,7 +57,6 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) { return false; } - // compare seconds if (this->second < Date.second) { return true; } @@ -88,7 +64,7 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) { return false; } -bool UTC_Timestamp::operator>(const UTC_Timestamp& Date) { +bool UTCTimestamp::operator>(const UTCTimestamp& Date) const { if (this->year > Date.year) { return true; } @@ -126,7 +102,7 @@ bool UTC_Timestamp::operator>(const UTC_Timestamp& Date) { return false; } -bool UTC_Timestamp::operator==(const UTC_Timestamp& Date) { +bool UTCTimestamp::operator==(const UTCTimestamp& Date) const { if (this->year != Date.year) { return false; } @@ -148,19 +124,10 @@ bool UTC_Timestamp::operator==(const UTC_Timestamp& Date) { return true; } -bool UTC_Timestamp::operator<=(const UTC_Timestamp& Date) { +bool UTCTimestamp::operator<=(const UTCTimestamp& Date) const { return ((*this < Date) || (*this == Date)); } -bool UTC_Timestamp::operator>=(const UTC_Timestamp& Date) { +bool UTCTimestamp::operator>=(const UTCTimestamp& Date) const { return ((*this > Date) || (*this == Date)); } - -std::ostream& operator<<(std::ostream& o, UTC_Timestamp const& Date) { // NOLINT - // YYYY-MM-DDTHH:mm:ss.sssZ - o.fill('0'); - o << static_cast<int>(Date.year) << "-" << std::setw(2) << static_cast<int>(Date.month) << "-" << std::setw(2) - << static_cast<int>(Date.day) << "T" << std::setw(2) << static_cast<int>(Date.hour) << ":" << std::setw(2) - << static_cast<int>(Date.minute) << ":" << std::setw(2) << static_cast<int>(Date.second) << ":000Z" << std::endl; - return o; -} diff --git a/test/Helpers/TimeFormats.cpp b/test/Helpers/TimeFormats.cpp deleted file mode 100644 index c6562503f6f20fbf3e210623e0226e7705822734..0000000000000000000000000000000000000000 --- a/test/Helpers/TimeFormats.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "catch2/catch.hpp" -#include "Helpers/TimeFormats.hpp" -#include "../Services/ServiceTests.hpp" - -TEST_CASE("UTC timestamps") { - // invalid year - UTC_Timestamp Timestamp0{2018, 4, 10, 10, 15, 0}; - - // invalid month - UTC_Timestamp Timestamp1{2020, 60, 10, 10, 15, 0}; - - // invalid day - UTC_Timestamp Timestamp2{2020, 4, 35, 10, 15, 0}; - - // invalid hour - UTC_Timestamp Timestamp3{2020, 4, 10, 100, 15, 0}; - - // invalid minute - UTC_Timestamp Timestamp4{2020, 4, 10, 10, 200, 0}; - - // invalid second - UTC_Timestamp Timestamp5{2020, 4, 10, 10, 15, 100}; - - CHECK(ServiceTests::countErrors() == 6); - CHECK(ServiceTests::thrownError(ErrorHandler::InvalidDate)); -} diff --git a/test/Helpers/TimeStamp.cpp b/test/Helpers/TimeStamp.cpp deleted file mode 100644 index 4202cb166cd78afd21d9f10103979e4866db2d92..0000000000000000000000000000000000000000 --- a/test/Helpers/TimeStamp.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "catch2/catch.hpp" -#include "Helpers/TimeStamp.hpp" -#include "../Services/ServiceTests.hpp" -#include <typeinfo> -#include <iostream> - -TEST_CASE("TimeStamp class construction") { - // SECTION("Initialize with excessive precision, breaks at compile time"){ - // TimeStamp<5, 10> Epoch3; - // TimeStamp<4, 4> Epoch4; - // } - - SECTION("Test CUC headers generation") { - uint8_t cuc_header1 = build_CUC_header<uint8_t, 2, 2>(); - CHECK(cuc_header1 == 0b00100110); - - uint8_t cuc_header2 = build_CUC_header<uint8_t, 4, 1>(); - CHECK(cuc_header2 == 0b00101101); - - uint8_t cuc_header3 = build_CUC_header<uint8_t, 1, 1>(); - CHECK(cuc_header3 == 0b00100001); - - uint16_t cuc_header4 = build_CUC_header<uint16_t, 5, 1>(); - CHECK(cuc_header4 == 0b1010110110100000); - - uint16_t cuc_header5 = build_CUC_header<uint16_t, 1, 6>(); - CHECK(cuc_header5 == 0b1010001110001100); - - uint16_t cuc_header6 = build_CUC_header<uint16_t, 7, 1>(); - CHECK(cuc_header6 == 0b1010110111100000); - } - - SECTION("Check TAI idempotence") { - int input_time = 1000; - TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(input_time); - REQUIRE(Epoch.as_TAI_seconds() == input_time); // check initialization has intended effect - } - - SECTION("Check CUC idempotence") { - etl::array<uint8_t, 9> input_time1 = {0b00100110, 0, 1, 1, 3, 0, 0, 0, 0}; - TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch1(input_time1); - etl::array<uint8_t, 9> test_return1 = Epoch1.as_CUC_timestamp(); - - for (auto i = 0; i < 9; i++) { - bool test = input_time1[i] == test_return1[i]; - REQUIRE(test); - } - - etl::array<uint8_t, 9> input_time2 = {0b10101101, 0b10100000, 218, 103, 11, 0, 3, 23, 0}; - TimeStamp<5, 1> Epoch2(input_time2); - etl::array<uint8_t, 9> test_return2 = Epoch2.as_CUC_timestamp(); - - for (auto i = 0; i < 9; i++) { - bool test = input_time2[i] == test_return2[i]; - REQUIRE(test); - } - - etl::array<uint8_t, 9> input_time3 = {0b10100011, 0b10001100, 218, 103, 11, 0, 3, 23, 2}; - TimeStamp<1, 6> Epoch3(input_time3); - etl::array<uint8_t, 9> test_return3 = Epoch3.as_CUC_timestamp(); - - for (auto i = 0; i < 9; i++) { - bool test = input_time3[i] == test_return3[i]; - REQUIRE(test); - } - } - - SECTION("Check UTC idempotence") { - { - UTC_Timestamp timestamp1(2020, 4, 10, 10, 15, 0); // 10 Apr 2020, 10:15:00; - TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1); - UTC_Timestamp timestamp2 = Epoch.as_UTC_timestamp(); - bool cond = (timestamp2 == timestamp1); - REQUIRE(cond); - } - { - UTC_Timestamp timestamp1(2035, 1, 1, 0, 0, 1); // 1 Jan 2035 midnight passed; - TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1); - UTC_Timestamp timestamp2 = Epoch.as_UTC_timestamp(); - bool cond = (timestamp2 == timestamp1); - REQUIRE(cond); - } - } - - SECTION("UTC conversion to and from seconds timestamps") { - { - UTC_Timestamp timestamp1(2020, 12, 5, 0, 0, 0); // 5 Dec 2020, midnight; - TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1); - REQUIRE(Epoch.as_TAI_seconds() == 1607126400); - } - { - UTC_Timestamp timestamp1(2020, 2, 29, 0, 0, 0); // 5 Dec 2020, midnight; - TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1); - REQUIRE(Epoch.as_TAI_seconds() == 1582934400); - } - } - - // SECTION("Check different templates, should break at compile"){ - // TimeStamp<1, 2> Epoch1; - // TimeStamp<4, 4> Epoch2; - // REQUIRE(Epoch1==Epoch2); - // } - - SECTION("Check operators") { - TimeStamp<1, 2> Epoch1; - TimeStamp<1, 2> Epoch2; - TimeStamp<1, 2> Epoch3(10); - TimeStamp<1, 2> Epoch4(15); - TimeStamp<1, 2> Epoch5(12); - TimeStamp<1, 2> Epoch6(10); - TimeStamp<2, 2> Epoch7; - REQUIRE(Epoch1 == Epoch2); - REQUIRE(Epoch2 == Epoch1); - REQUIRE(Epoch3 == Epoch6); - REQUIRE(Epoch1 != Epoch3); - REQUIRE(Epoch3 != Epoch5); - REQUIRE(Epoch3 <= Epoch5); - REQUIRE(Epoch3 < Epoch5); - - // REQUIRE(Epoch1==Epoch7); //should fail at compile, different templates - } - - SECTION("Check runtime class size") { - int input_time = 1000; - TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(input_time); - REQUIRE(sizeof(Epoch) < 32); - } -} diff --git a/test/Time/TimeFormats.cpp b/test/Time/TimeFormats.cpp new file mode 100644 index 0000000000000000000000000000000000000000..883af362b940848bb07bfe4cae5a117fb5178098 --- /dev/null +++ b/test/Time/TimeFormats.cpp @@ -0,0 +1,27 @@ +#include "catch2/catch.hpp" +#include "Time/Time.hpp" +#include "Time/UTCTimestamp.hpp" +#include "../Services/ServiceTests.hpp" + +TEST_CASE("UTC timestamps") { + // invalid year + UTCTimestamp Timestamp0{1950, 4, 10, 10, 15, 0}; + + // invalid month + UTCTimestamp Timestamp1{2020, 60, 10, 10, 15, 0}; + + // invalid day + UTCTimestamp Timestamp2{2020, 4, 35, 10, 15, 0}; + + // invalid hour + UTCTimestamp Timestamp3{2020, 4, 10, 100, 15, 0}; + + // invalid minute + UTCTimestamp Timestamp4{2020, 4, 10, 10, 200, 0}; + + // invalid second + UTCTimestamp Timestamp5{2020, 4, 10, 10, 15, 100}; + + CHECK(ServiceTests::countErrors() == 6); + CHECK(ServiceTests::thrownError(ErrorHandler::InvalidDate)); +} diff --git a/test/Time/TimeStamp.cpp b/test/Time/TimeStamp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ac99522ba966f817f7a048a615ab4782975007bd --- /dev/null +++ b/test/Time/TimeStamp.cpp @@ -0,0 +1,190 @@ +#include "catch2/catch.hpp" +#include "Time/TimeStamp.hpp" + +using namespace Time; + +TEST_CASE("TimeStamp class construction") { + // SECTION("Initialize with excessive precision, breaks at compile time"){ + // TimeStamp<5, 10> Epoch3; + // TimeStamp<4, 4> Epoch4; + // } +} + +TEST_CASE("CUC headers generation") { + auto cuc_header1 = buildCUCHeader<uint8_t, 2, 2>(); + CHECK(cuc_header1 == 0b00100110); + + auto cuc_header2 = buildCUCHeader<uint8_t, 4, 1>(); + CHECK(cuc_header2 == 0b00101101); + + auto cuc_header3 = buildCUCHeader<uint8_t, 1, 1>(); + CHECK(cuc_header3 == 0b00100001); + + auto cuc_header4 = buildCUCHeader<uint16_t, 5, 1>(); + CHECK(cuc_header4 == 0b1010110110100000); + + auto cuc_header5 = buildCUCHeader<uint16_t, 1, 6>(); + CHECK(cuc_header5 == 0b1010001110001100); + + auto cuc_header6 = buildCUCHeader<uint16_t, 7, 1>(); + CHECK(cuc_header6 == 0b1010110111100000); +} + +TEST_CASE("TAI idempotence") { + int input_time = 1000; + TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(input_time); + + REQUIRE(time.asTAIseconds() == input_time); +} + +TEST_CASE("CUC idempotence") { + etl::array<uint8_t, 9> input1 = {0b00101010, 0, 1, 1, 3, 0, 0, 0, 0}; + TimeStamp<3, 2> time1(input1); + etl::array<uint8_t, 9> cuc1 = time1.toCUCtimestamp(); + + for (uint8_t i = 0; i < 9; i++) { + CHECK(input1[i] == cuc1[i]); + } + + etl::array<uint8_t, 9> input2 = {0b10101101, 0b10100000, 218, 103, 11, 0, 3, 23, 0}; + TimeStamp<5, 1> time2(input2); + etl::array<uint8_t, 9> cuc2 = time2.toCUCtimestamp(); + + for (auto i = 0; i < 9; i++) { + CHECK(input2[i] == cuc2[i]); + } + + etl::array<uint8_t, 9> input3 = {0b10100011, 0b10001100, 218, 103, 11, 0, 3, 23, 2}; + TimeStamp<1, 6> time3(input3); + etl::array<uint8_t, 9> cuc3 = time3.toCUCtimestamp(); + + for (auto i = 0; i < 9; i++) { + CHECK(input3[i] == cuc3[i]); + } +} + +TEST_CASE("Conversion between CUC formats") { + SECTION("Base unit conversion") { + TimeStamp<2, 2> time1(20123); + TimeStamp<5, 2> time2(time1.toCUCtimestamp()); + CHECK(time1.asTAIseconds() == time2.asTAIseconds()); + } + + SECTION("Floating unit conversion") { + etl::array<uint8_t, 9> timeInput = {0b00101010, 0, 1, 1, 3, 0, 0, 0, 0}; + TimeStamp<3, 2> time1(timeInput); + + TimeStamp<3, 5> time2(time1.toCUCtimestamp()); + CHECK(time1.asTAIseconds() == time2.asTAIseconds()); + } + + SECTION("All units conversion") { + etl::array<uint8_t, 9> timeInput = {0b00101010, 0, 1, 1, 3, 0, 0, 0, 0}; + TimeStamp<3, 2> time1(timeInput); + + TimeStamp<4, 4> time2(time1.toCUCtimestamp()); + CHECK(time1.asTAIseconds() == time2.asTAIseconds()); + } +} + +TEST_CASE("Use of custom Acubesat CUC format") { + SECTION("Check forward conversion") { + Time::CustomCUC_t customCUC1 = {1001}; + TimeStamp<3, 0> time1(customCUC1); + CHECK(time1.asTAIseconds() == 100); + CHECK(time1.asCustomCUCTimestamp().elapsed100msTicks == 1000); + TimeStamp<3, 2> time2(customCUC1); + CHECK(time2.asTAIseconds() == 100); + CHECK(time2.asCustomCUCTimestamp().elapsed100msTicks == 1000); + + // check rounding errors + Time::CustomCUC_t customCUC2 = {1004}; + TimeStamp<3, 0> time3(customCUC2); + CHECK(time3.asTAIseconds() == 100); + CHECK(time3.asCustomCUCTimestamp().elapsed100msTicks == 1000); + TimeStamp<3, 2> time4(customCUC2); + CHECK(time4.asTAIseconds() == 100); + CHECK(time4.asCustomCUCTimestamp().elapsed100msTicks == 1003); + + // check rounding errors + Time::CustomCUC_t customCUC3 = {1005}; + TimeStamp<3, 0> time5(customCUC3); + CHECK(time5.asTAIseconds() == 100); + CHECK(time5.asCustomCUCTimestamp().elapsed100msTicks == 1000); + TimeStamp<3, 2> time6(customCUC3); + CHECK(time6.asTAIseconds() == 100); + CHECK(time6.asCustomCUCTimestamp().elapsed100msTicks == 1005); + } + + SECTION("Check idempotence") { + Time::CustomCUC_t customCUC1 = {1000}; + TimeStamp<3, 3> time1(customCUC1); + Time::CustomCUC_t customCUC2 = time1.asCustomCUCTimestamp(); + CHECK(customCUC1.elapsed100msTicks == customCUC2.elapsed100msTicks); + } +} + +TEST_CASE("UTC idempotence") { + { + UTCTimestamp timestamp1(2020, 4, 10, 10, 15, 0); // 10 Apr 2020, 10:15:00; + TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1); + UTCTimestamp timestamp2 = time.toUTCtimestamp(); + bool cond = (timestamp2 == timestamp1); + REQUIRE(cond); + } + { + UTCTimestamp timestamp1(2035, 1, 1, 0, 0, 1); // 1 Jan 2035 midnight passed; + TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1); + UTCTimestamp timestamp2 = time.toUTCtimestamp(); + bool cond = (timestamp2 == timestamp1); + REQUIRE(cond); + } +} + +TEST_CASE("UTC conversion to and from seconds timestamps") { + { + UTCTimestamp timestamp1(2020, 12, 5, 0, 0, 0); + TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1); + REQUIRE(time.asTAIseconds() == 29289600); + } + { + UTCTimestamp timestamp1(2020, 2, 29, 0, 0, 0); + TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1); + REQUIRE(time.asTAIseconds() == 5097600); + } + { + UTCTimestamp timestamp1(2025, 3, 10, 0, 0, 0); + TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1); + REQUIRE(time.asTAIseconds() == 163728000); + } +} + +// SECTION("Check different templates, should break at compile"){ +// TimeStamp<1, 2> time1; +// TimeStamp<4, 4> time2; +// REQUIRE(time1==time2); +// } + +TEST_CASE("Time operators") { + TimeStamp<1, 2> time1; + TimeStamp<1, 2> time2; + TimeStamp<1, 2> time3(10); + TimeStamp<1, 2> time4(12); + TimeStamp<1, 2> time5(10); + TimeStamp<2, 2> time6; + REQUIRE(time1 == time2); + REQUIRE(time2 == time1); + REQUIRE(time3 == time5); + REQUIRE(time1 != time3); + REQUIRE(time3 != time4); + REQUIRE(time3 <= time4); + REQUIRE(time3 < time4); + + // REQUIRE(time1 == time6); //should fail at compile, different templates +} + +TEST_CASE("Time runtime class size") { + int input_time = 1000; + TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(input_time); + REQUIRE(sizeof(time) < 32); +}