diff --git a/.editorconfig b/.editorconfig index 020d488b739f7d155fe2ca0ee0a8ffb33f3bf607..d7e5233df74ea49a7080b12ed2038cfd60fae128 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,6 +9,6 @@ end_of_line = lf insert_final_newline = true # 4 space indentation -[*.{c,cpp,h,hpp}] +[*.{c,cpp,h,hpp}, CMakeLists.txt] indent_style = tab indent_size = 4 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6f35ada2ca5f210505520aab5288777ee3d538d9..43a618b00b7c767591edd5a8adefea26f455289a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,6 +9,8 @@ before_script: build: stage: build + variables: + GIT_SUBMODULE_STRATEGY: normal script: - cmake . -DCMAKE_CXX_FLAGS="-Werror" - make -j4 diff --git a/.gitmodules b/.gitmodules index d49fb0620ae4893df6f92e7f878d7202c37cf84b..d2fb166c6a97657b49df5c5ade5002c3b97e1092 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/Catch2"] path = lib/Catch2 url = https://github.com/catchorg/Catch2.git +[submodule "lib/etl"] + path = lib/etl + url = https://github.com/ETLCPP/etl.git diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 76ade6edd581314bf32ea0f3fd0e76c366ce9100..fea94c457eb70a79c73b917a8993a9fd83f68f71 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -3,5 +3,6 @@ <component name="VcsDirectoryMappings"> <mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$/lib/Catch2" vcs="Git" /> + <mapping directory="$PROJECT_DIR$/lib/etl" vcs="Git" /> </component> </project> \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index cd9638ba8f06f3a897d8957a8088c24d72e62599..d25e161adf5dc35089d8dde1b20b9d7af1bb3ec5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(ecss_services) set(CMAKE_CXX_STANDARD 17) # Specify the directories for #includes -include_directories("${PROJECT_SOURCE_DIR}/inc") +include_directories("${PROJECT_SOURCE_DIR}/inc" "${PROJECT_SOURCE_DIR}/lib/etl/include") add_custom_target(check COMMAND ./cppcheck.sh @@ -19,17 +19,23 @@ add_library(common OBJECT src/Message.cpp src/MessageParser.cpp src/Helpers/CRCHelper.cpp - src/Services/EventReportService.cpp + src/Helpers/TimeHelper.cpp + src/Services/EventReportService.cpp src/Services/MemoryManagementService.cpp src/Services/ParameterService.cpp src/Services/RequestVerificationService.cpp - src/Services/TestService.cpp inc/Services/EventActionService.hpp src/Services/EventActionService.cpp) + src/Services/TestService.cpp + src/Services/TimeManagementService.cpp + inc/Services/EventActionService.hpp + src/Services/EventActionService.cpp + ) # Specify the .cpp files for the executables add_executable(ecss_services src/main.cpp $<TARGET_OBJECTS:common> - src/Platform/x86/Service.cpp) + src/Platform/x86/Service.cpp + ) IF (EXISTS "${PROJECT_SOURCE_DIR}/lib/Catch2/CMakeLists.txt") # Gather all the .cpp files corresponding to tests diff --git a/ci/cppcheck.sh b/ci/cppcheck.sh index f23bf34fdc66defa8c3f49d1622ec4d328c5d1a8..d6bcded38887bd666573d5e012d057287e91b32e 100755 --- a/ci/cppcheck.sh +++ b/ci/cppcheck.sh @@ -10,4 +10,5 @@ echo -e "\033[0;34mRunning cppcheck...\033[0m" cd "$(dirname "$0")/.." -cppcheck --enable=all --suppress=unusedFunction --suppress=missingIncludeSystem --error-exitcode=1 -I inc src test +cppcheck --enable=all --inline-suppr --suppress=unusedFunction --suppress=missingIncludeSystem \ + --suppress=noExplicitConstructor --error-exitcode=1 -I inc src test diff --git a/inc/ErrorHandler.hpp b/inc/ErrorHandler.hpp index 21710efa6bbe98524d2b5b923d2b95d80ce89119..adffdc132bffd5aa7830bcbd414f415a643c6340 100644 --- a/inc/ErrorHandler.hpp +++ b/inc/ErrorHandler.hpp @@ -1,7 +1,8 @@ #ifndef PROJECT_ERRORHANDLER_HPP #define PROJECT_ERRORHANDLER_HPP -#include "Message.hpp" +// Forward declaration of the class, since its header file depends on the ErrorHandler +class Message; /** * A class that handles unexpected software errors, including internal errors or errors due to diff --git a/inc/Helpers/TimeHelper.hpp b/inc/Helpers/TimeHelper.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8661fff4c05df60a8c43b583bec6a62695f060a7 --- /dev/null +++ b/inc/Helpers/TimeHelper.hpp @@ -0,0 +1,41 @@ +#ifndef ECSS_SERVICES_TIMEHELPER_HPP +#define ECSS_SERVICES_TIMEHELPER_HPP + +#include <cstdint> +#include <Message.hpp> + +/** + * 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! + */ +class TimeHelper { +public: + /** + * Generate the CUC time format + * + * @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 + * @todo time security for critical time operations + */ + static uint64_t implementCUCTimeFormat(uint32_t seconds); +}; + +#endif //ECSS_SERVICES_TIMEHELPER_HPP diff --git a/inc/Message.hpp b/inc/Message.hpp index 78bef90e7ee869cecdfe85f278df60437fd28f0c..0d4aa67d8e47cd6ee6045a48015991f93d78bf3d 100644 --- a/inc/Message.hpp +++ b/inc/Message.hpp @@ -1,10 +1,16 @@ #ifndef ECSS_SERVICES_PACKET_H #define ECSS_SERVICES_PACKET_H +// Forward declaration of the Message class, needed for the ErrorHandler +class Message; + #include "ECSS_Definitions.hpp" #include <cstdint> #include <cassert> -#include <iostream> +#include <etl/String.hpp> +#include <etl/wstring.h> +#include "ErrorHandler.hpp" +#include "macros.hpp" /** * A telemetry (TM) or telecommand (TC) message (request/report), as specified in ECSS-E-ST-70-41C @@ -78,23 +84,16 @@ public: void appendWord(uint32_t value); /** - * Appends \p size bytes to the message + * Appends a number of bytes to the message * - * @param size The amount of byte to append - * @param value An array containing at least \p size bytes - * @todo See if more than uint8_t strings will be supported - */ - void appendString(uint8_t size, const char *value); - - /** - * Appends \p size bytes to the message + * Note that this doesn't append the number of bytes that the string contains. For this, you + * need to use a function like Message::appendOctetString(), or have specified the size of the + * string beforehand. Note that the standard does not support null-terminated strings. * - * @param size The amount of byte to append - * @param value An array containing at least \p size bytes - * @todo See if more than uint8_t strings will be supported - * @todo Is uint16_t size too much or not enough? It has to be defined + * @param string The string to insert */ - void appendString(uint16_t size, const uint8_t *value); + template<const size_t SIZE> + void appendString(const String<SIZE> & string); /** * Reads the next \p numBits bits from the the message in a big-endian format @@ -269,9 +268,14 @@ public: * * PTC = 7, PFC = 0 */ - void appendOctetString(uint16_t size, uint8_t *byteString) { - appendUint16(size); - appendString(size, byteString); + template<const size_t SIZE> + void appendOctetString(const String<SIZE> & string) { + // Make sure that the string is large enough to count + assertI(string.size() <= (std::numeric_limits<uint16_t>::max)(), + ErrorHandler::StringTooLarge); + + appendUint16(string.size()); + appendString(string); } /** @@ -426,5 +430,16 @@ public: void resetRead(); }; +template<const size_t SIZE> +inline void Message::appendString(const String<SIZE> & string) { + assertI(dataSize + string.size() < ECSS_MAX_MESSAGE_SIZE, ErrorHandler::MessageTooLarge); + // TODO: Do we need to keep this check? How does etl::string handle it? + assertI(string.size() < string.capacity(), ErrorHandler::StringTooLarge); + + memcpy(data + dataSize, string.data(), string.size()); + + dataSize += string.size(); +} + #endif //ECSS_SERVICES_PACKET_H diff --git a/inc/MessageParser.hpp b/inc/MessageParser.hpp index 3f97b502ae644f767fa9cfcb6541e392d25f83aa..9de268e461bfdb2f872a6b6a6b4fe1822708434e 100644 --- a/inc/MessageParser.hpp +++ b/inc/MessageParser.hpp @@ -7,17 +7,19 @@ * A generic class responsible for the execution of the incoming telemetry and telecommand * packets. * + * @todo Make the connection between the ST[01]-request verification service and the services + * that initiate it * @todo Implement the execute function in the upcoming services or generally in the upcoming * activities * */ + class MessageParser { public: /** - * It is responsible to call the suitable function that execute the proper service. The way that - * the services are selected is based on the serviceType(class member of Message) of the param - * message + * It is responsible to call the suitable function that executes the proper service. The way that + * the services are selected is based on the serviceType of the \p message * * @todo The implementation of the execute function should correspond to the numbers of the * services/activities that have been created diff --git a/inc/Services/EventReportService.hpp b/inc/Services/EventReportService.hpp index 4f516648a2a681a17eb14879d2c676dd7fb56164..5d17779bf78e0bfa7a59437688e3de938db81ed3 100644 --- a/inc/Services/EventReportService.hpp +++ b/inc/Services/EventReportService.hpp @@ -2,11 +2,12 @@ #define ECSS_SERVICES_EVENTREPORTSERVICE_HPP #include "Service.hpp" +#include <bitset> /** * Implementation of ST[05] event reporting service * @todo add enum event definition id (and maybe some appending?) * - * @todo changes enums event IDs + * @todo add more enums event IDs * * Note: enum IDs are these just for test purposes * @@ -15,15 +16,48 @@ #define ECSS_EVENTS_BITS 16 class EventReportService : public Service { +private: + static const uint16_t numberOfEvents = 7; + std::bitset<numberOfEvents> stateOfEvents; public: + // Variables that count the event reports per severity level + uint16_t lowSeverityReportCount; + uint16_t mediumSeverityReportCount; + uint16_t highSeverityReportCount; + + // Variables that count the event occurences per severity level + uint16_t lowSeverityEventCount; + uint16_t mediumSeverityEventCount; + uint16_t highSeverityEventCount; + + uint16_t disabledEventsCount; + + uint16_t lastLowSeverityReportID; + uint16_t lastMediumSeverityReportID; + uint16_t lastHighSeverityReportID; + EventReportService() { + stateOfEvents.set(); serviceType = 5; + lowSeverityReportCount = 0; + mediumSeverityReportCount = 0; + highSeverityReportCount = 0; + disabledEventsCount = 0; + lowSeverityEventCount = 0; + mediumSeverityEventCount = 0; + highSeverityEventCount = 0; + lastLowSeverityReportID = 65535; + lastMediumSeverityReportID = 65535; + lastHighSeverityReportID = 65535; } /** * Type of the information event + * + * Note: Numbers are kept in code explicitly, so that there is no uncertainty when something + * changes. */ - enum InformationEvent { + enum Event { /** * An unknown event occured */ @@ -33,96 +67,113 @@ public: */ WWDGReset = 1, /** - * An assertion has failed + * Assertion has failed */ AssertionFail = 2, /** * Microcontroller has started */ MCUStart = 3, - }; - - /** - * Type of the low severity anomaly event - */ - enum LowSeverityAnomalyEvent { /** * An unknown anomaly of low severity anomalyhas occurred */ - LowSeverityUnknownEvent = 1, - }; - - /** - * Type of the medium severity anomaly event - */ - enum MediumSeverityAnomalyEvent { + LowSeverityUnknownEvent = 4, /** * An unknown anomaly of medium severity has occurred */ - MediumSeverityUnknownEvent = 2, - }; - - /** - * Type of the high severity anomaly event - */ - enum HighSeverityAnomalyEvent { + MediumSeverityUnknownEvent = 5, /** * An unknown anomaly of high severity has occurred */ - HighSeverityUnknownEvent = 3, + HighSeverityUnknownEvent = 6, + /** + * When an execution of a notification/event fails to start + */ + FailedStartOfExecution = 7 }; /** * TM[5,1] informative event report * Send report to inform the respective recipients about an event * - * Note: The parameters are defined by the standard, but the event definition id is missing! + * Note: The parameters are defined by the standard * * @param eventID event definition ID * @param data the data of the report * @param length the length of the data */ - void informativeEventReport(InformationEvent eventID, const uint8_t *data, uint8_t length); + void informativeEventReport(Event eventID, String<64> data); /** * TM[5,2] low severiity anomaly report * Send report when there is an anomaly event of low severity to the respective recipients * - * Note: The parameters are defined by the standard, but the event definition id is missing! + * Note: The parameters are defined by the standard * * @param eventID event definition ID * @param data the data of the report * @param length the length of the data */ - void - lowSeverityAnomalyReport(LowSeverityAnomalyEvent eventID, const uint8_t *data, uint8_t length); + void lowSeverityAnomalyReport(Event eventID, String<64> data); /** * TM[5,3] medium severity anomaly report * Send report when there is an anomaly event of medium severity to the respective recipients * - * Note: The parameters are defined by the standard, but the event definition id is missing! + * Note: The parameters are defined by the standard * * @param eventID event definition ID * @param data the data of the report * @param length the length of the data */ - void mediumSeverityAnomalyReport(MediumSeverityAnomalyEvent eventID, const uint8_t *data, - uint8_t length); + void mediumSeverityAnomalyReport(Event eventID, String<64> data); /** * TM[5,4] high severity anomaly report - * Send report when there is an anomaly event of hgih severity to the respective recipients + * Send report when there is an anomaly event of high severity to the respective recipients * - * Note: The parameters are defined by the standard, but the event definition id is missing! + * Note: The parameters are defined by the standard * * @param eventID event definition ID * @param data the data of the report * @param length the length of the data */ - void highSeverityAnomalyReport(HighSeverityAnomalyEvent eventID, const uint8_t *data, - uint8_t length); + void highSeverityAnomalyReport(Event eventID, String<64> data); + + /** + * TC[5,5] request to enable report generation + * Telecommand to enable the report generation of event definitions + */ + void enableReportGeneration(Message message); + /** + * TC[5,6] request to disable report generation + * Telecommand to disable the report generation of event definitions + * @param message + */ + void disableReportGeneration(Message message); + + /** + * TC[5,7] request to report the disabled event definitions + * Note: No arguments, according to the standard. + * @param message + */ + void requestListOfDisabledEvents(Message message); + + /** + * TM[5,8] disabled event definitions report + * Telemetry package of a report of the disabled event definitions + * @param message + */ + void listOfDisabledEventsReport(); + + /** + * Getter for stateOfEvents bitset + * @return stateOfEvents, just in case the whole bitset is needed + */ + std::bitset<numberOfEvents> getStateOfEvents() { + return stateOfEvents; + } }; #endif //ECSS_SERVICES_EVENTREPORTSERVICE_HPP diff --git a/inc/Services/TimeManagementService.hpp b/inc/Services/TimeManagementService.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d006a5d712b27ffc0218ef31dee469d5bb7d411a --- /dev/null +++ b/inc/Services/TimeManagementService.hpp @@ -0,0 +1,37 @@ +#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) + * + * @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) + */ +class TimeManagementService : public Service { +public: + TimeManagementService() { + serviceType = 9; + } + + /** + * TM[9,2] CUC time report + * + * @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(); +}; + + +#endif //ECSS_SERVICES_TIMEMANAGEMENTSERVICE_HPP diff --git a/inc/etl/String.hpp b/inc/etl/String.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8fbd401cc4eb033cce095feb13d4fa577aa7d013 --- /dev/null +++ b/inc/etl/String.hpp @@ -0,0 +1,68 @@ +#ifndef ECSS_SERVICES_ETL_STRING_HPP +#define ECSS_SERVICES_ETL_STRING_HPP + + +#include <cstddef> +#include <etl/cstring.h> + +/** + * A fixed-size string + * + * This class implements a string (i.e. an array of bytes) that has a maximum size (\p MAX_SIZE) + * known at compile time. As such, we can predict how much space the string will take, and prevent + * memory issues. + * + * Even though the maximum size (capacity) of the script should be specified beforehand, the size + * of the actual content is variable, and can be accessed quickly using the `String::size()` + * function. + * + * This class is a child of etl::string that provides some extra functionality that might be + * useful to us. For the full documentation, look at https://www.etlcpp.com/string.html + * + * Strings defined using the String class <b>do NOT have a null-terminating byte</b> + * + * @tparam MAX_SIZE The maximum size of the strings + */ +template <const size_t MAX_SIZE> +class String : public etl::string<MAX_SIZE> { +public: + /** + * String constructor from a uint8_t array, with a length equal to \p MAX_SIZE + * + * The array does NOT need to be null-terminated. + * + * @param data The array of uint8_t data + */ + String(const uint8_t * data) // NOLINTNEXTLINE(google-explicit-constructor) + : etl::string<MAX_SIZE>(reinterpret_cast<const char*>(data), MAX_SIZE) { + } + + /** + * String constructor from a uint8_t array + * + * The array does NOT need to be null-terminated. + * + * @param data The array of uint8_t data + * @param count The number of bytes to include + */ + String(const uint8_t * data, size_t count) + : etl::string<MAX_SIZE>(reinterpret_cast<const char*>(data), count) { + } + + /** + * Declaration of the constructor from const char*s that calls the parent constructor + * + * This is required for some reason, so that C++ recognises and converts string literals + * automatically + * + * @param text The pointer to a null terminated string + * + */ + String(const char* text) // NOLINTNEXTLINE(google-explicit-constructor) + : etl::string<MAX_SIZE>(text) + { + } +}; + + +#endif //ECSS_SERVICES_ETL_STRING_HPP diff --git a/inc/etl_profile.h b/inc/etl_profile.h new file mode 100644 index 0000000000000000000000000000000000000000..1b6ad5c151a4b1b7357796ca01fc75a2a1fc2a87 --- /dev/null +++ b/inc/etl_profile.h @@ -0,0 +1,15 @@ +/** + * Configuration for the ETL standard library replacement + */ + +#ifndef ECSS_SERVICES_ETL_PROFILE_H +#define ECSS_SERVICES_ETL_PROFILE_H + +#define ETL_THROW_EXCEPTIONS +#define ETL_VERBOSE_ERRORS +#define ETL_CHECK_PUSH_POP + +// Only GCC is used as a compiler +#include "etl/profiles/gcc_linux_x86.h" + +#endif //ECSS_SERVICES_ETL_PROFILE_H diff --git a/lib/etl b/lib/etl new file mode 160000 index 0000000000000000000000000000000000000000..f2f23395cde34704686233094d9619ad8d06453a --- /dev/null +++ b/lib/etl @@ -0,0 +1 @@ +Subproject commit f2f23395cde34704686233094d9619ad8d06453a diff --git a/src/Helpers/TimeHelper.cpp b/src/Helpers/TimeHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..462c7e8b35ea06bfe440f84b7e3e568e2a7891d2 --- /dev/null +++ b/src/Helpers/TimeHelper.cpp @@ -0,0 +1,43 @@ +#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); + + /** + * 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 + */ + uint32_t totalSeconds = seconds; + + /** + * 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); + + + return timeFormat; +} diff --git a/src/Message.cpp b/src/Message.cpp index b51954ec0ec3f08a29582f3bcf8bf8d7972d38fe..2d32f654ccd26b746380be4e1da50474a961e4c6 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -66,24 +66,6 @@ void Message::appendWord(uint32_t value) { dataSize += 4; } -void Message::appendString(uint8_t size, const char *value) { - assertI(dataSize + size < ECSS_MAX_MESSAGE_SIZE, ErrorHandler::MessageTooLarge); - assertI(size < ECSS_MAX_STRING_SIZE, ErrorHandler::StringTooLarge); - - memcpy(data + dataSize, value, size); - - dataSize += size; -} - -void Message::appendString(uint16_t size, const uint8_t *value) { - assertI(dataSize + size <= ECSS_MAX_MESSAGE_SIZE, ErrorHandler::MessageTooLarge); - assertI(size < ECSS_MAX_STRING_SIZE, ErrorHandler::StringTooLarge); - - memcpy(data + dataSize, value, size); - - dataSize += size; -} - uint16_t Message::readBits(uint8_t numBits) { assertR(numBits <= 16, ErrorHandler::TooManyBitsRead); // TODO: Add assert diff --git a/src/Services/EventReportService.cpp b/src/Services/EventReportService.cpp index 0a89f5c9765aa49e8932c0df9c1a4f1ed732a4ac..f0326119bfd473332673265aa1e6635d7f0477d3 100644 --- a/src/Services/EventReportService.cpp +++ b/src/Services/EventReportService.cpp @@ -1,46 +1,126 @@ +#include <Services/EventReportService.hpp> #include "Services/EventReportService.hpp" #include "Message.hpp" - -void EventReportService::informativeEventReport(InformationEvent eventID, const uint8_t *data, - uint8_t length) { +/** + * @todo: Add message type in TCs + * @todo: this code is error prone, depending on parameters given, add fail safes (probably?) + */ +void EventReportService::informativeEventReport(Event eventID, String<64> data) { // TM[5,1] - Message report = createTM(1); - report.appendEnum16(eventID); - report.appendString(length, data); + if (stateOfEvents[static_cast<uint16_t> (eventID)] == 1) { + Message report = createTM(1); + report.appendEnum16(eventID); + report.appendString(data); - storeMessage(report); + storeMessage(report); + } } void -EventReportService::lowSeverityAnomalyReport(LowSeverityAnomalyEvent eventID, const uint8_t *data, - uint8_t length) { +EventReportService::lowSeverityAnomalyReport(Event eventID, String<64> data) { + lowSeverityEventCount++; // TM[5,2] - Message report = createTM(2); - report.appendEnum16(eventID); - report.appendString(length, data); + if (stateOfEvents[static_cast<uint16_t> (eventID)] == 1) { + lowSeverityReportCount++; + Message report = createTM(2); + report.appendEnum16(eventID); + report.appendString(data); + lastLowSeverityReportID = static_cast<uint16_t >(eventID); - storeMessage(report); + storeMessage(report); + } } -void EventReportService::mediumSeverityAnomalyReport(MediumSeverityAnomalyEvent eventID, - const uint8_t *data, - uint8_t length) { +void EventReportService::mediumSeverityAnomalyReport(Event eventID, String<64> data) { + mediumSeverityEventCount++; // TM[5,3] - Message report = createTM(3); - report.appendEnum16(eventID); - report.appendString(length, data); + if (stateOfEvents[static_cast<uint16_t> (eventID)] == 1) { + mediumSeverityReportCount++; + Message report = createTM(3); + report.appendEnum16(eventID); + report.appendString(data); + lastMediumSeverityReportID = static_cast<uint16_t >(eventID); - storeMessage(report); + storeMessage(report); + } } void -EventReportService::highSeverityAnomalyReport(HighSeverityAnomalyEvent eventID, const uint8_t *data, - uint8_t length) { +EventReportService::highSeverityAnomalyReport(Event eventID, String<64> data) { + highSeverityEventCount++; // TM[5,4] - Message report = createTM(4); - report.appendEnum16(eventID); - report.appendString(length, data); + if (stateOfEvents[static_cast<uint16_t> (eventID)] == 1) { + highSeverityReportCount++; + Message report = createTM(4); + report.appendEnum16(eventID); + report.appendString(data); + lastHighSeverityReportID = static_cast<uint16_t >(eventID); + + storeMessage(report); + } +} + +void EventReportService::enableReportGeneration(Message message) { + // TC[5,5] + if (message.serviceType == 5 && message.packetType == Message::TC && message.messageType == 5) { + /** + * @todo: Report an error if length > numberOfEvents + */ + uint16_t length = message.readUint16(); + Event eventID[length]; + for (uint16_t i = 0; i < length; i++) { + eventID[i] = static_cast<Event >(message.readEnum16()); + } + if (length <= numberOfEvents) { + for (uint16_t i = 0; i < length; i++) { + stateOfEvents[static_cast<uint16_t> (eventID[i])] = 1; + } + } + disabledEventsCount = stateOfEvents.size() - stateOfEvents.count(); + } +} + +void EventReportService::disableReportGeneration(Message message) { + // TC[5,6] + if (message.serviceType == 5 && message.packetType == Message::TC && message.messageType + == 6) { + /** + * @todo: Report an error if length > numberOfEvents + */ + uint16_t length = message.readUint16(); + Event eventID[length]; + for (uint16_t i = 0; i < length; i++) { + eventID[i] = static_cast<Event >(message.readEnum16()); + } + if (length <= numberOfEvents) { + for (uint16_t i = 0; i < length; i++) { + stateOfEvents[static_cast<uint16_t> (eventID[i])] = 0; + } + } + disabledEventsCount = stateOfEvents.size() - stateOfEvents.count(); + } +} + +void EventReportService::requestListOfDisabledEvents(Message message) { + // TC[5,7] + // I think this is all that is needed here. + if (message.serviceType == 5 && message.packetType == Message::TC && message.messageType == 7) { + listOfDisabledEventsReport(); + } +} + +void EventReportService::listOfDisabledEventsReport() { + // TM[5,8] + Message report = createTM(8); + + uint16_t numberOfDisabledEvents = stateOfEvents.size() - stateOfEvents.count(); + report.appendHalfword(numberOfDisabledEvents); + for (uint16_t i = 0; i < stateOfEvents.size(); i++) { + if (stateOfEvents[i] == 0) { + report.appendEnum16(i); + } + } storeMessage(report); } diff --git a/src/Services/MemoryManagementService.cpp b/src/Services/MemoryManagementService.cpp index 05bd9bdfea50fb1de15ef10b3f8daf919d701c50..f904291c44b171161064a131300c79847b785346 100644 --- a/src/Services/MemoryManagementService.cpp +++ b/src/Services/MemoryManagementService.cpp @@ -1,6 +1,7 @@ #include "Services/MemoryManagementService.hpp" #include <iostream> #include <cerrno> +#include <etl/String.hpp> // Define the constructors for the classes MemoryManagementService::MemoryManagementService() : rawDataMemorySubservice(*this) { @@ -106,7 +107,8 @@ void MemoryManagementService::RawDataMemoryManagement::dumpRawData(Message &requ // This part is repeated N-times (N = iteration count) report.appendUint64(startAddress); // Start address - report.appendOctetString(readLength, readData); // Save the read data + report.appendOctetString(String<1024>(readData, readLength)); // Save the + // read data report.appendBits(16, CRCHelper::calculateCRC(readData, readLength)); } else { ErrorHandler::reportError(request, ErrorHandler::AddressOutOfRange); diff --git a/src/Services/TimeManagementService.cpp b/src/Services/TimeManagementService.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2b3feb68aa059b7131863d84cd2d6e79d8328fa1 --- /dev/null +++ b/src/Services/TimeManagementService.cpp @@ -0,0 +1,20 @@ +#include "Services/TimeManagementService.hpp" + +void TimeManagementService::cucTimeReport() { + // TM[9,2] CUC time report + + Message timeReport = createTM(2); + + /** + * 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 + + timeReport.appendByte(timeFormat); // append the P-field + timeReport.appendWord(timeFormat >> 8); // append the T-field + + storeMessage(timeReport); +} diff --git a/src/main.cpp b/src/main.cpp index 0883b6023edfb55e97d1300e4372a3a68039fa0e..bcc262edea6839be3fdee14bebf64daf2f0a1859 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,21 +1,24 @@ #include <iostream> #include "Helpers/CRCHelper.hpp" +#include "Helpers/TimeHelper.hpp" #include "Services/TestService.hpp" #include "Services/ParameterService.hpp" #include "Services/RequestVerificationService.hpp" #include "Services/MemoryManagementService.hpp" #include "Services/EventReportService.hpp" +#include "Services/TimeManagementService.hpp" #include "Message.hpp" #include "MessageParser.hpp" #include "Services/MemoryManagementService.hpp" #include "Helpers/CRCHelper.hpp" #include "ErrorHandler.hpp" +#include "etl/String.hpp" int main() { Message packet = Message(0, 0, Message::TC, 1); - packet.appendString(5, "hello"); + packet.appendString<5>("hello"); packet.appendBits(15, 0x28a8); packet.appendBits(1, 1); packet.appendFloat(5.7); @@ -88,10 +91,10 @@ int main() { rcvPack.appendEnum8(MemoryManagementService::MemoryID::EXTERNAL); // Memory ID rcvPack.appendUint16(2); // Iteration count rcvPack.appendUint64(reinterpret_cast<uint64_t >(pStr)); // Start address - rcvPack.appendOctetString(2, data); + rcvPack.appendOctetString(String<2>(data, 2)); rcvPack.appendBits(16, CRCHelper::calculateCRC(data, 2)); // Append the CRC value rcvPack.appendUint64(reinterpret_cast<uint64_t >(pStr + 1)); // Start address - rcvPack.appendOctetString(1, data); + rcvPack.appendOctetString(String<1>(data, 1)); rcvPack.appendBits(16, CRCHelper::calculateCRC(data, 1)); // Append the CRC value memMangService.rawDataMemorySubservice.loadRawData(rcvPack); @@ -125,17 +128,17 @@ int main() { receivedMessage = Message(1, 10, Message::TC, 3); reqVerifService.failRoutingVerification(receivedMessage); - // ST[05] test [works] - const unsigned char eventReportData[12] = "Hello World"; + // ST[05] (5,1 to 5,4) test [works] + const char eventReportData[12] = "Hello World"; EventReportService eventReportService; eventReportService.informativeEventReport(EventReportService::InformativeUnknownEvent, - eventReportData, 11); + eventReportData); eventReportService.lowSeverityAnomalyReport(EventReportService::LowSeverityUnknownEvent, - eventReportData, 11); + eventReportData); eventReportService.mediumSeverityAnomalyReport(EventReportService::MediumSeverityUnknownEvent, - eventReportData, 11); + eventReportData); eventReportService.highSeverityAnomalyReport(EventReportService::HighSeverityUnknownEvent, - eventReportData, 11); + eventReportData); // MessageParser class test std::cout << "\n"; @@ -169,5 +172,33 @@ 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; + timeReport.cucTimeReport(); + + // ST[05] (5,5 to 5,8) test [works] + EventReportService::Event eventIDs[] = {EventReportService::HighSeverityUnknownEvent, + EventReportService::MediumSeverityUnknownEvent}; + EventReportService::Event eventIDs2[] = {EventReportService::HighSeverityUnknownEvent}; + Message eventMessage(5, 6, Message::TC, 1); + eventMessage.appendUint16(2); + eventMessage.appendEnum16(eventIDs[0]); + eventMessage.appendEnum16(eventIDs[1]); + + Message eventMessage2(5, 5, Message::TC, 1); + eventMessage2.appendUint16(1); + eventMessage2.appendEnum16(eventIDs2[0]); + + Message eventMessage3(5, 7, Message::TC, 1); + eventReportService.disableReportGeneration(eventMessage); + eventReportService.listOfDisabledEventsReport(); + eventReportService.enableReportGeneration(eventMessage2); + eventReportService.requestListOfDisabledEvents(eventMessage3); + return 0; } diff --git a/test/Helpers/CRCHelper.cpp b/test/Helpers/CRCHelper.cpp index 73909a920026b63e62ba0364f0db51777ea665c9..eba63019bf29f99c108dae56a5fa1db2b26e8a97 100644 --- a/test/Helpers/CRCHelper.cpp +++ b/test/Helpers/CRCHelper.cpp @@ -1,7 +1,7 @@ #include "catch2/catch.hpp" #include "Helpers/CRCHelper.hpp" -TEST_CASE("CRC calculation - Basic string tests") { +TEST_CASE("CRC calculation - Basic String tests") { CHECK(CRCHelper::calculateCRC((uint8_t*) "Raccoon Squad!", 14) == 0x08FC); CHECK(CRCHelper::calculateCRC((uint8_t*) "ASAT", 4) == 0xBFFA); CHECK(CRCHelper::calculateCRC((uint8_t*) "All your space are belong to us", 31) == 0x545F); diff --git a/test/Helpers/TimeHelper.cpp b/test/Helpers/TimeHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0b23a2a05d7bb06a032bbfee1c51f2baa6d2f23d --- /dev/null +++ b/test/Helpers/TimeHelper.cpp @@ -0,0 +1,10 @@ +#include "catch2/catch.hpp" +#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); +} diff --git a/test/Message.cpp b/test/Message.cpp index dce148d340fff2b12dc09fabebc86608d3200493..f0b78e3c5af4a8e330282c3c6cbd4ef571c0fb37 100644 --- a/test/Message.cpp +++ b/test/Message.cpp @@ -157,7 +157,7 @@ TEST_CASE("Requirement 7.3.6 (Real)", "[message][ecss]") { TEST_CASE("Requirement 7.3.8 (Octet-string)", "[message][ecss]") { Message message(0, 0, Message::TC, 0); - message.appendString(4, "test"); + message.appendString<4>("test"); REQUIRE(message.dataSize == 4); diff --git a/test/Services/EventReportService.cpp b/test/Services/EventReportService.cpp index 32f4f30b18ee8d9e38c9c5a059fcfd07ad27c9f1..0bf8133a30947c79942c360fc9f4db86c98c0143 100644 --- a/test/Services/EventReportService.cpp +++ b/test/Services/EventReportService.cpp @@ -9,10 +9,10 @@ */ TEST_CASE("Informative Event Report TM[5,1]", "[service][st05]") { EventReportService eventReportService; - const unsigned char eventReportData[] = "HelloWorld"; + const char eventReportData[] = "HelloWorld"; char checkString[255]; eventReportService.informativeEventReport(EventReportService::InformativeUnknownEvent, - eventReportData, 10); + eventReportData); REQUIRE(ServiceTests::hasOneMessage()); Message report = ServiceTests::get(0); @@ -24,15 +24,15 @@ TEST_CASE("Informative Event Report TM[5,1]", "[service][st05]") { // Check for the value that is stored in <<data>> array(data-member of object response) CHECK(report.readEnum16() == 0); report.readString(checkString, 10); - CHECK(strcmp(checkString, reinterpret_cast<const char *>(eventReportData)) == 0); + CHECK(strcmp(checkString, eventReportData) == 0); } TEST_CASE("Low Severity Anomaly Report TM[5,2]", "[service][st05]") { EventReportService eventReportService; - const unsigned char eventReportData[] = "HelloWorld"; + const char eventReportData[] = "HelloWorld"; char checkString[255]; eventReportService.lowSeverityAnomalyReport(EventReportService::LowSeverityUnknownEvent, - eventReportData, 10); + eventReportData); REQUIRE(ServiceTests::hasOneMessage()); Message report = ServiceTests::get(0); @@ -42,17 +42,17 @@ TEST_CASE("Low Severity Anomaly Report TM[5,2]", "[service][st05]") { CHECK(report.packetType == Message::TM); // packet type(TM = 0, TC = 1) REQUIRE(report.dataSize == 12); // Check for the value that is stored in <<data>> array(data-member of object response) - CHECK(report.readEnum16() == 1); + CHECK(report.readEnum16() == 4); report.readString(checkString, 10); - CHECK(strcmp(checkString, reinterpret_cast<const char *>(eventReportData)) == 0); + CHECK(strcmp(checkString, eventReportData) == 0); } TEST_CASE("Medium Severity Anomaly Report TM[5,3]", "[service][st05]") { EventReportService eventReportService; - const unsigned char eventReportData[] = "HelloWorld"; + const char eventReportData[] = "HelloWorld"; char checkString[255]; eventReportService.mediumSeverityAnomalyReport - (EventReportService::MediumSeverityUnknownEvent, eventReportData, 10); + (EventReportService::MediumSeverityUnknownEvent, eventReportData); REQUIRE(ServiceTests::hasOneMessage()); Message report = ServiceTests::get(0); @@ -62,17 +62,17 @@ TEST_CASE("Medium Severity Anomaly Report TM[5,3]", "[service][st05]") { CHECK(report.packetType == Message::TM); // packet type(TM = 0, TC = 1) REQUIRE(report.dataSize == 12); // Check for the value that is stored in <<data>> array(data-member of object response) - CHECK(report.readEnum16() == 2); + CHECK(report.readEnum16() == 5); report.readString(checkString, 10); - CHECK(strcmp(checkString, reinterpret_cast<const char *>(eventReportData)) == 0); + CHECK(strcmp(checkString, eventReportData) == 0); } TEST_CASE("High Severity Anomaly Report TM[5,4]", "[service][st05]") { EventReportService eventReportService; - const unsigned char eventReportData[] = "HelloWorld"; + const char eventReportData[] = "HelloWorld"; char checkString[255]; eventReportService.highSeverityAnomalyReport(EventReportService::HighSeverityUnknownEvent, - eventReportData, 10); + eventReportData); REQUIRE(ServiceTests::hasOneMessage()); Message report = ServiceTests::get(0); @@ -82,7 +82,104 @@ TEST_CASE("High Severity Anomaly Report TM[5,4]", "[service][st05]") { CHECK(report.packetType == Message::TM); // packet type(TM = 0, TC = 1) REQUIRE(report.dataSize == 12); // Check for the value that is stored in <<data>> array(data-member of object response) - CHECK(report.readEnum16() == 3); + CHECK(report.readEnum16() == 6); report.readString(checkString, 10); - CHECK(strcmp(checkString, reinterpret_cast<const char *>(eventReportData)) == 0); + CHECK(strcmp(checkString, eventReportData) == 0); +} + +TEST_CASE("Enable Report Generation TC[5,5]", "[service][st05]") { + EventReportService eventReportService; + eventReportService.getStateOfEvents().reset(); + EventReportService::Event eventID[] = {EventReportService::AssertionFail, + EventReportService::LowSeverityUnknownEvent}; + Message message(5, 5, Message::TC, 1); + message.appendUint16(2); + message.appendEnum16(eventID[0]); + message.appendEnum16(eventID[1]); + eventReportService.enableReportGeneration(message); + CHECK(eventReportService.getStateOfEvents()[2] == 1); + CHECK(eventReportService.getStateOfEvents()[4] == 1); +} + +TEST_CASE("Disable Report Generation TC[5,6]", "[service][st05]") { + EventReportService eventReportService; + EventReportService::Event eventID[] = {EventReportService::InformativeUnknownEvent, + EventReportService::MediumSeverityUnknownEvent}; + Message message(5, 6, Message::TC, 1); + message.appendUint16(2); + message.appendEnum16(eventID[0]); + message.appendEnum16(eventID[1]); + eventReportService.disableReportGeneration(message); + CHECK(eventReportService.getStateOfEvents()[0] == 0); + CHECK(eventReportService.getStateOfEvents()[5] == 0); + + const String<64> eventReportData = "HelloWorld"; + eventReportService.highSeverityAnomalyReport(EventReportService::InformativeUnknownEvent, + eventReportData); + CHECK(ServiceTests::hasOneMessage() == false); +} + +TEST_CASE("Request list of disabled events TC[5,7]", "[service][st05]") { + EventReportService eventReportService; + Message message(5, 7, Message::TC, 1); + eventReportService.requestListOfDisabledEvents(message); + REQUIRE(ServiceTests::hasOneMessage()); + + Message report = ServiceTests::get(0); + // Check if there is message of type 8 created + CHECK(report.messageType == 8); +} + +TEST_CASE("List of Disabled Events Report TM[5,8]", "[service][st05]") { + EventReportService eventReportService; + EventReportService::Event eventID[] = {EventReportService::MCUStart, + EventReportService::HighSeverityUnknownEvent}; + Message message(5, 6, Message::TC, 1); + message.appendUint16(2); + message.appendEnum16(eventID[0]); + message.appendEnum16(eventID[1]); + // Disable 3rd and 6th + eventReportService.disableReportGeneration(message); + eventReportService.listOfDisabledEventsReport(); + REQUIRE(ServiceTests::hasOneMessage()); + + Message report = ServiceTests::get(0); + // Check for the data-members of the report Message created + CHECK(report.serviceType == 5); + CHECK(report.messageType == 8); + CHECK(report.packetType == Message::TM); // packet type(TM = 0, TC = 1) + REQUIRE(report.dataSize == 6); + // Check for the information stored in report + CHECK(report.readHalfword() == 2); + CHECK(report.readEnum16() == 3); + CHECK(report.readEnum16() == 6); +} + +TEST_CASE("List of observables 6.5.6", "[service][st05]") { + EventReportService eventReportService; + EventReportService::Event eventID[] = {EventReportService::HighSeverityUnknownEvent}; + Message message(5, 6, Message::TC, 1); + message.appendUint16(1); + message.appendEnum16(eventID[0]); + eventReportService.disableReportGeneration(message); + + const String<64> eventReportData = "HelloWorld"; + + eventReportService.highSeverityAnomalyReport(EventReportService::HighSeverityUnknownEvent, + eventReportData); + eventReportService.mediumSeverityAnomalyReport(EventReportService::MediumSeverityUnknownEvent, + eventReportData); + CHECK(eventReportService.lowSeverityReportCount == 0); + CHECK(eventReportService.mediumSeverityReportCount == 1); + CHECK(eventReportService.highSeverityReportCount == 0); + + CHECK(eventReportService.lowSeverityEventCount == 0); + CHECK(eventReportService.mediumSeverityEventCount == 1); + CHECK(eventReportService.highSeverityEventCount == 1); + + CHECK(eventReportService.disabledEventsCount == 1); + + CHECK(eventReportService.lastLowSeverityReportID == 65535); + CHECK(eventReportService.lastMediumSeverityReportID == 5); + CHECK(eventReportService.lastHighSeverityReportID == 65535); } diff --git a/test/Services/MemoryManagementService.cpp b/test/Services/MemoryManagementService.cpp index 06bbe264f0ce68c5585dcc158e5f8e8fcec12cf4..64b0181f904a8d53845d43019c6dc9f579b41b9b 100644 --- a/test/Services/MemoryManagementService.cpp +++ b/test/Services/MemoryManagementService.cpp @@ -18,10 +18,10 @@ TEST_CASE("TM[6,2]", "[service][st06]") { receivedPacket.appendEnum8(MemoryManagementService::MemoryID::EXTERNAL); // Memory ID receivedPacket.appendUint16(2); // Iteration count receivedPacket.appendUint64(reinterpret_cast<uint64_t >(pStr)); // Start address - receivedPacket.appendOctetString(2, data); + receivedPacket.appendOctetString(String<2>(data)); receivedPacket.appendBits(16, CRCHelper::calculateCRC(data, 2)); // Append CRC receivedPacket.appendUint64(reinterpret_cast<uint64_t >(pStr + 2)); // Start address - receivedPacket.appendOctetString(1, data); // Append CRC + receivedPacket.appendOctetString(String<1>(data)); // Append CRC receivedPacket.appendBits(16, CRCHelper::calculateCRC(data, 1)); memMangService.rawDataMemorySubservice.loadRawData(receivedPacket); diff --git a/test/Services/TimeManagementService.cpp b/test/Services/TimeManagementService.cpp new file mode 100644 index 0000000000000000000000000000000000000000..714c99e144480c5037a286a5dcdada334f06d145 --- /dev/null +++ b/test/Services/TimeManagementService.cpp @@ -0,0 +1,16 @@ +#include <catch2/catch.hpp> +#include <Services/TimeManagementService.hpp> +#include "ServiceTests.hpp" + +TEST_CASE("TM[9,2]", "[service][st09]") { + TimeManagementService timeFormat; + + uint32_t seconds; + seconds = time(nullptr); + + timeFormat.cucTimeReport(); + Message response = ServiceTests::get(0); + CHECK(response.readByte() == 50); + CHECK(response.readWord() == seconds); + +}