diff --git a/inc/ErrorHandler.hpp b/inc/ErrorHandler.hpp index a55a0cae7bee5cf177294d65921684785632c844..e8622616c6245a924edacc4df2bc71a5a52c1254 100644 --- a/inc/ErrorHandler.hpp +++ b/inc/ErrorHandler.hpp @@ -52,7 +52,12 @@ public: UnacceptablePacket = 5, /** - * Asked a Message type that it doesn't exist + * A date that isn't valid according to the Gregorian calendar or cannot be parsed by the + * TimeHelper + */ + InvalidDate = 6, + /** + * Asked a Message type that doesn't exist */ UnknownMessageType = 6, }; diff --git a/inc/Helpers/TimeHelper.hpp b/inc/Helpers/TimeHelper.hpp index 8661fff4c05df60a8c43b583bec6a62695f060a7..711478568dcdd5ba64944852f6bbf0a6ae0ee233 100644 --- a/inc/Helpers/TimeHelper.hpp +++ b/inc/Helpers/TimeHelper.hpp @@ -4,38 +4,110 @@ #include <cstdint> #include <Message.hpp> +#define SECONDS_PER_MINUTE 60 +#define SECONDS_PER_HOUR 3600 +#define SECONDS_PER_DAY 86400 + +/** + * The time and date provided from Real Time Clock (Real Time Clock). + * + * @notes + * This struct is similar to the `struct tm` of <ctime> library but it is more embedded-friendly + * + * For the current implementation this struct takes dummy values, because RTC hasn't been + * implemented + */ +struct TimeAndDate { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; +}; + /** * This class formats the spacecraft time and cooperates closely with the ST[09] time management. - * The ECSS standard supports two time formats: the CUC and CSD that are described in - * CCSDS 301.0-B-4 standard - * The chosen time format is CUC. The reasons for this selection are the followings: - * 1)It is more flexible from the CSD. The designer is free to decide how much memory will use - * for the time unit and what that time unit will be(seconds, minutes, hours etc.). - * 2)It can use TAI(international atomic time) as reference time scale. So there is no need - * to worry about leap seconds(code UTC-based) * - * Note: The implementation of the time formats are in general RTC-dependent. First, we need to - * get the time data from the RTC, so we know what time is it and then format it! + * The ECSS standard supports two time formats: the CUC and CSD that are described in CCSDS + * 301.0-B-4 standard. The chosen time format is CDS and it is UTC-based (UTC: Coordinated + * Universal Time). It consists of two main fields: the time code preamble field (P-field) and + * the time specification field (T-field). The P-Field is the metadata for the T-Field. The + * T-Field is consisted of two segments: 1) the `DAY` and the 2) `ms of day` segments. + * The P-field won't be included in the code, because as the ECSS standards claims, it can be + * just implicitly declared. + * + * @note + * Since this code is UTC-based, the leap second correction must be made. The leap seconds that + * have been occurred between timestamps should be considered if a critical time-difference is + * needed + * */ class TimeHelper { public: + static constexpr uint8_t DaysOfMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + TimeHelper() = default; + + /** + * @param year The year that will be examined if it is a leap year (366 days) + * @return if the \p year is a leap year returns true and if it isn't returns false + */ + static bool IsLeapYear(uint16_t year); + + /** + * Convert UTC date to elapsed seconds since Unix epoch (1/1/1970 00:00:00). + * + * This is a reimplemented mktime() of <ctime> library in an embedded systems way + * + * @note + * This function can convert UTC dates after 1 January 2019 to elapsed seconds since Unix epoch + * + * @param TimeInfo the time information/data from the RTC (UTC format) + * @return the elapsed seconds between a given UTC date (after the Unix epoch) and Unix epoch + * @todo check if we need to change the epoch to the recommended one from the standard, 1 + * January 1958 + */ + static uint32_t utcToSeconds(struct TimeAndDate &TimeInfo); + + /** + * Convert elapsed seconds since Unix epoch to UTC date. + * + * This is a reimplemented gmtime() of <ctime> library in an embedded systems way + * + * @note + * This function can convert elapsed seconds since Unix epoch to UTC dates after 1 January 2019 + * + * @param seconds elapsed seconds since Unix epoch + * @return the UTC date based on the \p seconds + * @todo check if we need to change the epoch to the recommended one from the standard, 1 + * January 1958 + */ + static struct TimeAndDate secondsToUTC(uint32_t seconds); + /** - * Generate the CUC time format + * Generate the CDS time format (3.3 in CCSDS 301.0-B-4 standard). * - * @details The CUC time format consists of two main fields: the time code preamble field - * (P-field) and the time specification field(T-field). The P-Field is the metadata for the - * T-Field. The T-Field contains the value of the time unit and the designer decides what the - * time unit will be, so this is a subject for discussion. The recommended time unit from the - * standard is the second and it is probably the best solution for accuracy. - * @param seconds the seconds provided from the RTC. This function in general should have - * parameters corresponding with the RTC. For the time being we assume that the RTC has a - * 32-bit counter that counts seconds(the RTC in Nucleo F103RB!) - * @todo check if we need milliseconds(fractions of the time unit) - * @todo the time unit should be declared in the metadata. But how? - * @todo check if we need to define other epoch than the 1 January 1958 + * Converts a UTC date to CDS time format. + * + * @param TimeInfo is the data provided from RTC (UTC) + * @return TimeFormat the CDS time format. More specific, 48 bits are used for the T-field + * (16 for the `DAY` and 32 for the `ms of day`) * @todo time security for critical time operations + * @todo declare the implicit P-field + * @todo check if we need milliseconds + */ + static uint64_t generateCDStimeFormat(struct TimeAndDate &TimeInfo); + + /** + * Parse the CDS time format (3.3 in CCSDS 301.0-B-4 standard) + * + * @param data time information provided from the ground segment. The length of the data is a + * fixed size of 48 bits + * @return the UTC date */ - static uint64_t implementCUCTimeFormat(uint32_t seconds); + static struct TimeAndDate parseCDStimeFormat(const uint8_t *data); }; + #endif //ECSS_SERVICES_TIMEHELPER_HPP diff --git a/inc/Services/TimeManagementService.hpp b/inc/Services/TimeManagementService.hpp index d006a5d712b27ffc0218ef31dee469d5bb7d411a..533fcd5683f5dddd3258d00d8884c76a5da8b40c 100644 --- a/inc/Services/TimeManagementService.hpp +++ b/inc/Services/TimeManagementService.hpp @@ -1,20 +1,33 @@ #ifndef ECSS_SERVICES_TIMEMANAGEMENTSERVICE_HPP #define ECSS_SERVICES_TIMEMANAGEMENTSERVICE_HPP + #include <Service.hpp> -#include <ctime> #include "Helpers/TimeHelper.hpp" /** - * Implementation of the ST[09] time management. The current implementation sends - * a report with the spacecraft time that is formatted according to the CUC time code format - * (check TimeHelper for the format) + * Implementation of the ST[09] time management. + * + * @notes + * There is a noticeable difference between setting the time using GPS and setting the time + * using space packets from the ground segment. The GPS module sends the actual time of UTC (123519 + * is 12:35:19 UTC), while space packets, for time configuration, sends the elapsed time units + * (seconds, days depends on the time format) from a specific epoch (1 January 1958 00:00:00). Time + * updates using GPS have nothing to do with this service, but for consistency and simplicity we + * are trying to set the time with a common way independently of the time source. This is also + * the reason that we chose CDS time format (because it is UTC based, check class `TimeHelper`) + * + * About the GPS receiver, we assume that it outputs NMEA (message format) data * + * @todo check if we need to follow the standard for time-management or we should send the time-data + * like GPS + * @todo check if the final GPS receiver support NMEA protocol * @todo When the time comes for the application processes we should consider this: All reports * generated by the application process that is identified by APID 0 are time reports * @todo Declare the time accuracy that the standard claims in the spacecraft - * time reference section(6.9.3.d,e) + * time reference section (6.9.3.d,e) */ + class TimeManagementService : public Service { public: TimeManagementService() { @@ -22,15 +35,33 @@ public: } /** - * TM[9,2] CUC time report + * TM[9,3] CDS time report. + * + * This function sends reports with the spacecraft time that is formatted according to the CDS + * time code format (check class `TimeHelper` for the format) * + * @param TimeInfo the time information/data from the RTC (UTC format) * @todo check if we need spacecraft time reference status * @todo ECSS standard claims: <<The time reports generated by the time reporting subservice * are spacecraft time packets. A spacecraft time packet does not carry the message type, * consisting of the service type and message subtype.>> Check if we need to implement that * or should ignore the standard? */ - void cucTimeReport(); + + void cdsTimeReport(struct TimeAndDate &TimeInfo); + + /** + * TC[9,128] CDS time request. + * + * This function is a custom subservice (mission specific) with message type 128 (as defined + * from the standard for custom message types, 5.3.3.1.f) and it parses the data of the + * time-management telecommand packet. This data is formatted according to the CDS time code + * format (check class `TimeHelper` for the format) + * + * @param message the message that will be parsed for its time-data. The data of the \p message + * should be a fixed size of 48 bits + */ + struct TimeAndDate cdsTimeRequest(Message &message); }; diff --git a/src/Helpers/TimeHelper.cpp b/src/Helpers/TimeHelper.cpp index 462c7e8b35ea06bfe440f84b7e3e568e2a7891d2..2ce9562ba64ef73b854e365ae8dd3687944982fb 100644 --- a/src/Helpers/TimeHelper.cpp +++ b/src/Helpers/TimeHelper.cpp @@ -1,43 +1,136 @@ #include "Helpers/TimeHelper.hpp" -uint64_t TimeHelper::implementCUCTimeFormat(uint32_t seconds) { - // the total number of octets including the P-field (1 octet) and T-field(4 octets) is 5 - - // define the P-field - const uint8_t bit0 = 0; // P-field extension(‘zero’: no extension, ‘one’: field is extended) - const uint8_t bits1_3 = 1; // Time code identification ( 001 -> 1958 January 1 epoch ) - const uint8_t bits4_5 = 4 - 1; // Number of octets of the basic time unit minus one - const uint8_t bits6_7 = 0; // Number of octets of the fractional time unit - const uint8_t pField = (bits6_7 << 6 | bits4_5 << 4 | bits1_3 << 1 | bit0); - - // just a reminder to be careful with the assigned values - static_assert(bit0 < 2); - static_assert(bits1_3 < 16); - static_assert(bits4_5 < 4); - static_assert(bits6_7 < 4); +bool TimeHelper::IsLeapYear(uint16_t year) { + if (year % 4 != 0) { + return false; + } + if (year % 100 != 0) { + return true; + } + return (year % 400) == 0; +} + +uint32_t TimeHelper::utcToSeconds(struct TimeAndDate &TimeInfo) { + // the date, that \p TimeInfo represents, should be greater than or equal to 1/1/2019 and the + // date should be valid according to Gregorian calendar + assertI(TimeInfo.year >= 2019, ErrorHandler::InternalErrorType::InvalidDate); + assertI(1 <= TimeInfo.month && TimeInfo.month <= 12, + ErrorHandler::InternalErrorType::InvalidDate); + assertI(1 <= TimeInfo.day && TimeInfo.day <= 31, + ErrorHandler::InternalErrorType::InvalidDate); + assertI(0 <= TimeInfo.hour && TimeInfo.hour <= 24, + ErrorHandler::InternalErrorType::InvalidDate); + assertI(0 <= TimeInfo.minute && TimeInfo.minute <= 60, + ErrorHandler::InternalErrorType::InvalidDate); + assertI(0 <= TimeInfo.second && TimeInfo.second <= 60, + ErrorHandler::InternalErrorType::InvalidDate); + + uint32_t secs = 1546300800; // elapsed seconds from Unix epoch until 1/1/2019 00:00:00 (UTC) + for (uint16_t y = 2019; y < TimeInfo.year; ++y) { + secs += (IsLeapYear(y) ? 366 : 365) * SECONDS_PER_DAY; + } + for (uint16_t m = 1; m < TimeInfo.month; ++m) { + secs += DaysOfMonth[m - 1] * SECONDS_PER_DAY; + if (m == 2 && IsLeapYear(TimeInfo.year)) { + secs += SECONDS_PER_DAY; + } + } + secs += (TimeInfo.day - 1) * SECONDS_PER_DAY; + secs += TimeInfo.hour * SECONDS_PER_HOUR; + secs += TimeInfo.minute * SECONDS_PER_MINUTE; + secs += TimeInfo.second; + return secs; +} + +struct TimeAndDate TimeHelper::secondsToUTC(uint32_t seconds) { + // elapsed seconds should be between dates, that are after 1/1/2019 and Unix epoch + assertI(seconds >= 1546300800, ErrorHandler::InternalErrorType::InvalidDate); + + seconds -= 1546300800; // elapsed seconds from Unix epoch until 1/1/2019 00:00:00 (UTC) + struct TimeAndDate TimeInfo = {0}; + TimeInfo.year = 2019; + TimeInfo.month = 1; + TimeInfo.day = 0; + TimeInfo.hour = 0; + TimeInfo.minute = 0; + TimeInfo.second = 0; + + // calculate years + while (seconds >= (IsLeapYear(TimeInfo.year) ? 366 : 365) * SECONDS_PER_DAY) { + seconds -= (IsLeapYear(TimeInfo.year) ? 366 : 365) * SECONDS_PER_DAY; + TimeInfo.year++; + } + + // calculate months + uint8_t i = 0; + while (seconds >= (DaysOfMonth[i] * SECONDS_PER_DAY)) { + TimeInfo.month++; + seconds -= (DaysOfMonth[i] * SECONDS_PER_DAY); + i++; + if (i == 1 && IsLeapYear(TimeInfo.year)) { + if (seconds <= (28 * SECONDS_PER_DAY)) { + break; + } + TimeInfo.month++; + seconds -= 29 * SECONDS_PER_DAY; + i++; + } + } + + // calculate days + TimeInfo.day = seconds / SECONDS_PER_DAY; + seconds -= TimeInfo.day * SECONDS_PER_DAY; + TimeInfo.day++; // add 1 day because we start count from 1 January (and not 0 January!) + + // calculate hours + TimeInfo.hour = seconds / SECONDS_PER_HOUR; + seconds -= TimeInfo.hour * SECONDS_PER_HOUR; + + // calculate minutes + TimeInfo.minute = seconds / SECONDS_PER_MINUTE; + seconds -= TimeInfo.minute * SECONDS_PER_MINUTE; + + // calculate seconds + TimeInfo.second = seconds; + + return TimeInfo; +} + +uint64_t TimeHelper::generateCDStimeFormat(struct TimeAndDate &TimeInfo) { + /** + * Define the T-field. The total number of octets for the implementation of T-field is 6 (2 for + * the `DAY` and 4 for the `ms of day` + */ + + + uint32_t seconds = utcToSeconds(TimeInfo); /** - * Define the T-field, the seconds passed from the defined epoch 1 January 1958 - * We use 4 octets(32 bits) for the time unit(seconds) because 32 bits for the seconds are - * enough to count 136 years! But if we use 24 bits for the seconds then it will count 0,5 - * years and this isn't enough. Remember we can use only integers numbers of octets for the - * time unit(second) - * - * @todo the implementation of the total seconds depends on the structure of the RTC + * The `DAY` segment, 16 bits as defined from standard. Actually the days passed since Unix + * epoch */ - uint32_t totalSeconds = seconds; + auto elapsedDays = static_cast<uint16_t>(seconds / SECONDS_PER_DAY); /** - * Define time format including P-field and T-Field - * - * Notes: - * Only the 40 bits of the 64 will be used for the timeFormat(0-7 : P-field, 8-39: T-field) - * - * Shift operators have high priority. That's why we should do a type-casting first so we - * don't lose valuable bits - */ - uint64_t timeFormat = (static_cast<uint64_t>(totalSeconds) << 8 | pField); + * The `ms of day` segment, 32 bits as defined in standard. The `ms of the day` and DAY` + * should give the time passed since Unix epoch + */ + auto msOfDay = static_cast<uint32_t >((seconds % SECONDS_PER_DAY) * 1000); + uint64_t timeFormat = (static_cast<uint64_t>(elapsedDays) << 32 | msOfDay); return timeFormat; } + +struct TimeAndDate TimeHelper::parseCDStimeFormat(const uint8_t *data) { + uint16_t elapsedDays = (static_cast<uint16_t >(data[0])) << 8 | static_cast<uint16_t > + (data[1]); + uint32_t msOfDay = (static_cast<uint32_t >(data[2])) << 24 | + (static_cast<uint32_t >(data[3])) << 16 | + (static_cast<uint32_t >(data[4])) << 8 | + static_cast<uint32_t >(data[5]); + + uint32_t seconds = elapsedDays * SECONDS_PER_DAY + msOfDay / 1000; + + return secondsToUTC(seconds); +} diff --git a/src/Services/TimeManagementService.cpp b/src/Services/TimeManagementService.cpp index 2b3feb68aa059b7131863d84cd2d6e79d8328fa1..ad4d8c9208fb2fa558487c4fcf1868633f37a379 100644 --- a/src/Services/TimeManagementService.cpp +++ b/src/Services/TimeManagementService.cpp @@ -1,20 +1,26 @@ #include "Services/TimeManagementService.hpp" -void TimeManagementService::cucTimeReport() { - // TM[9,2] CUC time report +void TimeManagementService::cdsTimeReport(struct TimeAndDate &TimeInfo) { + // TM[9,3] CDS time report - Message timeReport = createTM(2); + Message timeReport = createTM(3); - /** - * For the time being we will use C++ functions to get a time value, but this will change - * when the RTC will be implemented - */ - uint32_t seconds; - seconds = time(nullptr); // seconds have passed since 00:00:00 GMT, Jan 1, 1970 - uint64_t timeFormat = TimeHelper::implementCUCTimeFormat(seconds); // store the return value + uint64_t timeFormat = TimeHelper::generateCDStimeFormat(TimeInfo); - timeReport.appendByte(timeFormat); // append the P-field - timeReport.appendWord(timeFormat >> 8); // append the T-field + timeReport.appendHalfword(static_cast<uint16_t >(timeFormat >> 32)); + timeReport.appendWord(static_cast<uint32_t >(timeFormat)); storeMessage(timeReport); } + +struct TimeAndDate TimeManagementService::cdsTimeRequest(Message &message) { + // TC[9,128] CDS time request + + // check if we have the correct size of the data. The size should be 6 (48 bits) + ErrorHandler::assertRequest(message.dataSize == 6, message, + ErrorHandler::AcceptanceErrorType::UnacceptableMessage); + + struct TimeAndDate timeInfo = TimeHelper::parseCDStimeFormat(message.data); + + return timeInfo; +} diff --git a/src/main.cpp b/src/main.cpp index 71cff47e6621395665b6f1b163b20b053238c2e0..bdf4914e8cd7903523843bc515b33309e0cccdf2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -197,14 +197,17 @@ int main() { errorMessage.appendBits(2, 7); errorMessage.appendByte(15); - - // TimeHelper test - uint64_t test = TimeHelper::implementCUCTimeFormat(1200); - std::cout << "\n" << test << "\n"; - // ST[09] test - TimeManagementService & timeReport = Services.timeManagement; - timeReport.cucTimeReport(); + TimeManagementService & timeReport = Services.timeManagement; + struct TimeAndDate timeInfo = {0}; + // 10/04/1998 10:15:00 + timeInfo.year = 1998; + timeInfo.month = 4; + timeInfo.day = 10; + timeInfo.hour = 10; + timeInfo.minute = 15; + timeInfo.second = 0; + timeReport.cdsTimeReport(timeInfo); // ST[05] (5,5 to 5,8) test [works] EventReportService::Event eventIDs[] = {EventReportService::HighSeverityUnknownEvent, diff --git a/test/Helpers/TimeHelper.cpp b/test/Helpers/TimeHelper.cpp index 0b23a2a05d7bb06a032bbfee1c51f2baa6d2f23d..c0c530d55a4e8704e0c833001769c0c28ac67ba3 100644 --- a/test/Helpers/TimeHelper.cpp +++ b/test/Helpers/TimeHelper.cpp @@ -2,9 +2,232 @@ #include "Helpers/TimeHelper.hpp" TEST_CASE("Time format implementation", "[CUC]") { - // very simple tests for the TimeHelper - CHECK(TimeHelper::implementCUCTimeFormat(60) == 0b11110000110010); - CHECK(TimeHelper::implementCUCTimeFormat(1000) == 0x3E832); - CHECK(TimeHelper::implementCUCTimeFormat(1200) == 0x4B032); + SECTION("Invalid date") { + struct TimeAndDate TimeInfo = {0}; + + // invalid year + TimeInfo.year = 2018; + TimeInfo.month = 4; + TimeInfo.day = 10; + TimeInfo.hour = 10; + TimeInfo.minute = 15; + TimeInfo.second = 0; + + TimeHelper time; + TimeHelper::utcToSeconds(TimeInfo); + + // invalid month + TimeInfo.year = 2018; + TimeInfo.month = 60; + TimeInfo.day = 10; + TimeInfo.hour = 10; + TimeInfo.minute = 15; + TimeInfo.second = 0; + + TimeHelper::utcToSeconds(TimeInfo); + + // invalid day + TimeInfo.year = 2018; + TimeInfo.month = 4; + TimeInfo.day = 35; + TimeInfo.hour = 10; + TimeInfo.minute = 15; + TimeInfo.second = 0; + + TimeHelper::utcToSeconds(TimeInfo); + + // invalid hour + TimeInfo.year = 2018; + TimeInfo.month = 4; + TimeInfo.day = 10; + TimeInfo.hour = 100; + TimeInfo.minute = 15; + TimeInfo.second = 0; + + TimeHelper::utcToSeconds(TimeInfo); + + // invalid minute + TimeInfo.year = 2018; + TimeInfo.month = 4; + TimeInfo.day = 10; + TimeInfo.hour = 10; + TimeInfo.minute = 200; + TimeInfo.second = 0; + + TimeHelper::utcToSeconds(TimeInfo); + + // invalid second + TimeInfo.year = 2018; + TimeInfo.month = 4; + TimeInfo.day = 10; + TimeInfo.hour = 10; + TimeInfo.minute = 15; + TimeInfo.second = 122; + + TimeHelper::utcToSeconds(TimeInfo); + } + + SECTION("Convert UTC date to elapsed seconds since Unix epoch") { + struct TimeAndDate TimeInfo = {0}; + // 10/04/2020 10:15:00 + TimeInfo.year = 2020; + TimeInfo.month = 4; + TimeInfo.day = 10; + TimeInfo.hour = 10; + TimeInfo.minute = 15; + TimeInfo.second = 0; + + TimeHelper time; + uint32_t currTime = TimeHelper::utcToSeconds(TimeInfo); + + uint16_t elapsedDays = currTime / 86400; + uint32_t msOfDay = currTime % 86400 * 1000; + uint64_t timeFormat = (static_cast<uint64_t>(elapsedDays) << 32 | msOfDay); + CHECK(TimeHelper::generateCDStimeFormat(TimeInfo) == timeFormat); + + // 1/1/2019 00:00:00 + TimeInfo.year = 2019; + TimeInfo.month = 1; + TimeInfo.day = 1; + TimeInfo.hour = 0; + TimeInfo.minute = 0; + TimeInfo.second = 0; + + currTime = TimeHelper::utcToSeconds(TimeInfo); + + elapsedDays = currTime / 86400; + msOfDay = currTime % 86400 * 1000; + timeFormat = (static_cast<uint64_t>(elapsedDays) << 32 | msOfDay); + CHECK(TimeHelper::generateCDStimeFormat(TimeInfo) == timeFormat); + + // 5/12/2020 00:00:00 + TimeInfo.year = 2020; + TimeInfo.month = 12; + TimeInfo.day = 5; + TimeInfo.hour = 0; + TimeInfo.minute = 0; + TimeInfo.second = 0; + + currTime = TimeHelper::utcToSeconds(TimeInfo); + CHECK(currTime == 1607126400); + + // 10/12/2020 00:00:00 + TimeInfo.year = 2020; + TimeInfo.month = 12; + TimeInfo.day = 10; + TimeInfo.hour = 0; + TimeInfo.minute = 0; + TimeInfo.second = 0; + + currTime = TimeHelper::utcToSeconds(TimeInfo); + CHECK(currTime == 1607558400); + + // 15/12/2020 00:00:00 + TimeInfo.year = 2020; + TimeInfo.month = 12; + TimeInfo.day = 15; + TimeInfo.hour = 0; + TimeInfo.minute = 0; + TimeInfo.second = 0; + + currTime = TimeHelper::utcToSeconds(TimeInfo); + CHECK(currTime == 1607990400); + + // 20/12/2020 00:00:00 + TimeInfo.year = 2020; + TimeInfo.month = 12; + TimeInfo.day = 20; + TimeInfo.hour = 0; + TimeInfo.minute = 0; + TimeInfo.second = 0; + + currTime = TimeHelper::utcToSeconds(TimeInfo); + CHECK(currTime == 1608422400); + } + + SECTION("Convert elapsed seconds since Unix epoch to UTC date"){ + uint32_t seconds = 1586513700; // elapsed seconds between 10/04/2020 10:15:00 and Unix epoch + + TimeHelper time; + struct TimeAndDate TimeInfo = TimeHelper::secondsToUTC(seconds); + CHECK(TimeInfo.year == 2020); + CHECK(TimeInfo.month == 4); + CHECK(TimeInfo.day == 10); + CHECK(TimeInfo.hour == 10); + CHECK(TimeInfo.minute == 15); + CHECK(TimeInfo.second == 0); + + seconds = 1546300800; // elapsed seconds between 1/1/2019 00:00:00 and Unix epoch + + TimeInfo = TimeHelper::secondsToUTC(seconds); + CHECK(TimeInfo.year == 2019); + CHECK(TimeInfo.month == 1); + CHECK(TimeInfo.day == 1); + CHECK(TimeInfo.hour == 0); + CHECK(TimeInfo.minute == 0); + CHECK(TimeInfo.second == 0); + + seconds = 1550966400; // elapsed seconds between 24/2/2019 00:00:00 and Unix epoch + + TimeInfo = TimeHelper::secondsToUTC(seconds); + CHECK(TimeInfo.year == 2019); + CHECK(TimeInfo.month == 2); + CHECK(TimeInfo.day == 24); + CHECK(TimeInfo.hour == 0); + CHECK(TimeInfo.minute == 0); + CHECK(TimeInfo.second == 0); + + seconds = 1551571200; // elapsed seconds between 3/3/2019 00:00:00 and Unix epoch + + TimeInfo = TimeHelper::secondsToUTC(seconds); + CHECK(TimeInfo.year == 2019); + CHECK(TimeInfo.month == 3); + CHECK(TimeInfo.day == 3); + CHECK(TimeInfo.hour == 0); + CHECK(TimeInfo.minute == 0); + CHECK(TimeInfo.second == 0); + + seconds = 1742907370; // elapsed seconds between 25/3/2025 12:56:10 and Unix epoch + + TimeInfo = TimeHelper::secondsToUTC(seconds); + CHECK(TimeInfo.year == 2025); + CHECK(TimeInfo.month == 3); + CHECK(TimeInfo.day == 25); + CHECK(TimeInfo.hour == 12); + CHECK(TimeInfo.minute == 56); + CHECK(TimeInfo.second == 10); + + seconds = 1583020800; // elapsed seconds between 1/3/2020 00:00:00 and Unix epoch + + TimeInfo = TimeHelper::secondsToUTC(seconds); + CHECK(TimeInfo.year == 2020); + CHECK(TimeInfo.month == 3); + CHECK(TimeInfo.day == 1); + CHECK(TimeInfo.hour == 0); + CHECK(TimeInfo.minute == 0); + CHECK(TimeInfo.second == 0); + + seconds = 1582934400; // elapsed seconds between 2/29/2020 00:00:00 and Unix epoch + + TimeInfo = TimeHelper::secondsToUTC(seconds); + CHECK(TimeInfo.year == 2020); + CHECK(TimeInfo.month == 2); + CHECK(TimeInfo.day == 29); + CHECK(TimeInfo.hour == 0); + CHECK(TimeInfo.minute == 0); + CHECK(TimeInfo.second == 0); + + seconds = 1577923200; // elapsed seconds between 2/1/2020 00:00:00 and Unix epoch + + TimeInfo = TimeHelper::secondsToUTC(seconds); + CHECK(TimeInfo.year == 2020); + CHECK(TimeInfo.month == 1); + CHECK(TimeInfo.day == 2); + CHECK(TimeInfo.hour == 0); + CHECK(TimeInfo.minute == 0); + CHECK(TimeInfo.second == 0); + + } + } diff --git a/test/Services/TimeManagementService.cpp b/test/Services/TimeManagementService.cpp index f9ca595348edeae12b818c716a36a29ec8d68ddb..9a4894f6ba09d939fe42dee17b68f0d19cd84461 100644 --- a/test/Services/TimeManagementService.cpp +++ b/test/Services/TimeManagementService.cpp @@ -2,15 +2,76 @@ #include <Services/TimeManagementService.hpp> #include "ServiceTests.hpp" -TimeManagementService & timeFormat = Services.timeManagement; +TimeManagementService & timeService = Services.timeManagement; -TEST_CASE("TM[9,2]", "[service][st09]") { - uint32_t seconds; - seconds = time(nullptr); +TEST_CASE("TM[9,3]", "[service][st09]") { + struct TimeAndDate TimeInfo = {0}; + // 10/04/2020 10:15:00 + TimeInfo.year = 2020; + TimeInfo.month = 4; + TimeInfo.day = 10; + TimeInfo.hour = 10; + TimeInfo.minute = 15; + TimeInfo.second = 0; - timeFormat.cucTimeReport(); + uint32_t currTime = TimeHelper::utcToSeconds(TimeInfo); + + uint16_t elapsedDays = currTime/86400; + uint32_t msOfDay = currTime % 86400 * 1000; + uint64_t timeFormat = (static_cast<uint64_t>(elapsedDays) << 32 | msOfDay); + + timeService.cdsTimeReport(TimeInfo); Message response = ServiceTests::get(0); - CHECK(response.readByte() == 50); - CHECK(response.readWord() == seconds); + CHECK(response.serviceType == 9); + CHECK(response.messageType == 3); + CHECK(response.packetType == Message::TM); + CHECK(response.readHalfword() == static_cast<uint16_t>(timeFormat >> 32)); + CHECK(response.readWord() == static_cast<uint32_t >(timeFormat)); + + Message message = Message(9, 128, Message::TC, 3); + message.appendHalfword(static_cast<uint16_t >(timeFormat >> 32)); + message.appendWord(static_cast<uint32_t >(timeFormat)); + + TimeInfo = timeService.cdsTimeRequest(message); + CHECK(TimeInfo.year == 2020); + CHECK(TimeInfo.month == 4); + CHECK(TimeInfo.day == 10); + CHECK(TimeInfo.hour == 10); + CHECK(TimeInfo.minute == 15); + CHECK(TimeInfo.second == 0); + + + // 1/1/2019 00:00:00 + TimeInfo.year = 2019; + TimeInfo.month = 1; + TimeInfo.day = 1; + TimeInfo.hour = 0; + TimeInfo.minute = 0; + TimeInfo.second = 0; + + currTime = TimeHelper::utcToSeconds(TimeInfo); + + elapsedDays = currTime/86400; + msOfDay = currTime % 86400 * 1000; + timeFormat = (static_cast<uint64_t>(elapsedDays) << 32 | msOfDay); + + timeService.cdsTimeReport(TimeInfo); + response = ServiceTests::get(1); + CHECK(response.serviceType == 9); + CHECK(response.messageType == 3); + CHECK(response.packetType == Message::TM); + CHECK(response.readHalfword() == static_cast<uint16_t>(timeFormat >> 32)); + CHECK(response.readWord() == static_cast<uint32_t >(timeFormat)); + + message = Message(9, 128, Message::TC, 3); + message.appendHalfword(static_cast<uint16_t >(timeFormat >> 32)); + message.appendWord(static_cast<uint32_t >(timeFormat)); + TimeInfo = timeService.cdsTimeRequest(message); + CHECK(TimeInfo.year == 2019); + CHECK(TimeInfo.month == 1); + CHECK(TimeInfo.day == 1); + CHECK(TimeInfo.hour == 0); + CHECK(TimeInfo.minute == 0); + CHECK(TimeInfo.second == 0); }