diff --git a/CMakeLists.txt b/CMakeLists.txt index e01e2dd8..a69012ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ endif() add_library(Sts1CobcSw_Dummy STATIC) add_library(Sts1CobcSw_Edu STATIC) +add_library(Sts1CobcSw_Outcome INTERFACE) add_library(Sts1CobcSw_Serial INTERFACE) add_library(Sts1CobcSw_Utility STATIC) add_program(HelloDummy) diff --git a/Sts1CobcSw/Edu/CMakeLists.txt b/Sts1CobcSw/Edu/CMakeLists.txt index d542bdbf..2ff1aff4 100644 --- a/Sts1CobcSw/Edu/CMakeLists.txt +++ b/Sts1CobcSw/Edu/CMakeLists.txt @@ -1,5 +1,5 @@ target_sources(Sts1CobcSw_Edu PRIVATE ProgramQueue.cpp ProgramStatusHistory.cpp) -target_link_libraries(Sts1CobcSw_Edu PUBLIC rodos::rodos Sts1CobcSw_Serial) +target_link_libraries(Sts1CobcSw_Edu PUBLIC rodos::rodos Sts1CobcSw_Serial Sts1CobcSw_Outcome) target_link_libraries(Sts1CobcSw_Edu PRIVATE Sts1CobcSw_Utility) if(CMAKE_SYSTEM_NAME STREQUAL Generic) diff --git a/Sts1CobcSw/Edu/Edu.cpp b/Sts1CobcSw/Edu/Edu.cpp index 548f10f6..83149d51 100644 --- a/Sts1CobcSw/Edu/Edu.cpp +++ b/Sts1CobcSw/Edu/Edu.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -59,16 +60,16 @@ auto cepDataBuffer = std::array{}; // TODO: Rework -> Send(EduBasicCommand command) -> void; auto SendCommand(Byte commandId) -> void; -[[nodiscard]] auto SendData(std::span data) -> ErrorCode; +[[nodiscard]] auto SendData(std::span data) -> Result; // TODO: Make this read and return a Type instead of having to provide a destination. Use // Deserialize<>() internally. -[[nodiscard]] auto UartReceive(std::span destination) -> ErrorCode; -[[nodiscard]] auto UartReceive(void * destination) -> ErrorCode; +[[nodiscard]] auto UartReceive(std::span destination) -> Result; +[[nodiscard]] auto UartReceive(void * destination) -> Result; auto FlushUartBuffer() -> void; -[[nodiscard]] auto CheckCrc32(std::span data) -> ErrorCode; -[[nodiscard]] auto GetStatusCommunication() -> Status; -[[nodiscard]] auto ReturnResultCommunication() -> ResultInfo; -[[nodiscard]] auto ReturnResultRetry() -> ResultInfo; +[[nodiscard]] auto CheckCrc32(std::span data) -> Result; +[[nodiscard]] auto GetStatusCommunication() -> Result; +[[nodiscard]] auto ReturnResultCommunication() -> Result; +[[nodiscard]] auto ReturnResultRetry() -> Result; void MockWriteToFile(std::span data); auto Print(std::span data, int nRows = 30) -> void; // NOLINT @@ -104,7 +105,7 @@ auto TurnOff() -> void // TODO: Implement this -auto StoreArchive([[maybe_unused]] StoreArchiveData const & data) -> std::int32_t +auto StoreArchive([[maybe_unused]] StoreArchiveData const & data) -> Result { return 0; } @@ -129,7 +130,7 @@ auto StoreArchive([[maybe_unused]] StoreArchiveData const & data) -> std::int32_ //! @param timeout The available execution time for the student program //! //! @returns A relevant error code -auto ExecuteProgram(ExecuteProgramData const & data) -> ErrorCode +auto ExecuteProgram(ExecuteProgramData const & data) -> Result { RODOS::PRINTF("ExecuteProgram(programId = %d, startTime = %" PRIi32 ", timeout = %d)\n", data.programId, @@ -137,11 +138,7 @@ auto ExecuteProgram(ExecuteProgramData const & data) -> ErrorCode data.timeout); // Check if data command was successful auto serialData = Serialize(data); - auto errorCode = SendData(serialData); - if(errorCode != ErrorCode::success) - { - return errorCode; - } + OUTCOME_TRY(SendData(serialData)); // eduTimeout != timeout argument for data! // timeout specifies the time the student program has to execute @@ -158,7 +155,7 @@ auto ExecuteProgram(ExecuteProgramData const & data) -> ErrorCode { case cmdAck: { - return ErrorCode::success; + return outcome_v2::success(); } case cmdNack: { @@ -181,9 +178,9 @@ auto ExecuteProgram(ExecuteProgramData const & data) -> ErrorCode //! <- [N/ACK] //! <- [N/ACK] //! @returns A relevant error code -auto StopProgram() -> ErrorCode +auto StopProgram() -> Result { - return ErrorCode::success; + return outcome_v2::success(); // std::array dataBuf = {stopProgram}; // auto errorCode = SendData(dataBuf); @@ -216,170 +213,134 @@ auto StopProgram() -> ErrorCode //! @returns A status containing (Status Type, [Program ID], [Queue ID], [Exit Code], Error //! Code). Values in square brackets are only valid if the relevant Status Type is //! returned. -auto GetStatus() -> Status +auto GetStatus() -> Result { RODOS::PRINTF("GetStatus()\n"); auto serialData = Serialize(getStatusId); - auto sendDataError = SendData(serialData); - if(sendDataError != ErrorCode::success) - { - RODOS::PRINTF(" Returned .statusType = %d, .errorCode = %d\n", - static_cast(StatusType::invalid), - static_cast(sendDataError)); - return Status{.statusType = StatusType::invalid, .errorCode = sendDataError}; - } + OUTCOME_TRY(SendData(serialData)); - Status status; std::size_t errorCount = 0; - do + + while(true) { - status = GetStatusCommunication(); - if(status.errorCode == ErrorCode::success) + auto getStatusCommunicationResult = GetStatusCommunication(); + if(getStatusCommunicationResult.has_value()) { + auto status = getStatusCommunicationResult.value(); SendCommand(cmdAck); - break; + RODOS::PRINTF( + " .statusType = %d\n .programId = %d\n .startTime = %d\n exitCode = %d\n", + status.statusType, + status.programId, + status.startTime, + status.exitCode); + return status; } + // Error in GetStatusCommunication() FlushUartBuffer(); SendCommand(cmdNack); - } while(errorCount++ < maxNNackRetries); - - RODOS::PRINTF( - " .statusType = %d\n .errorCode = %d\n .programId = %d\n .startTime = %" PRIi32 - "\n exitCode = %d\n", - static_cast(status.statusType), - static_cast(status.errorCode), - status.programId, - status.startTime, - status.exitCode); - return status; + errorCount++; + + if(errorCount >= maxNNackRetries) + { + RODOS::PRINTF(" .errorCode = %d\n", getStatusCommunicationResult.error()); + return getStatusCommunicationResult.error(); + } + } } //! @brief Communication function for GetStatus() to separate a single try from //! retry logic. //! @returns The received EDU status -auto GetStatusCommunication() -> Status +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +auto GetStatusCommunication() -> Result { // Get header data auto headerBuffer = Buffer{}; - auto headerReceiveError = UartReceive(headerBuffer); + OUTCOME_TRY(UartReceive(headerBuffer)); auto headerData = Deserialize(headerBuffer); - if(headerReceiveError != ErrorCode::success) - { - return Status{.statusType = StatusType::invalid, .errorCode = headerReceiveError}; - } - if(headerData.command != cmdData) { - return Status{.statusType = StatusType::invalid, .errorCode = ErrorCode::invalidCommand}; + return ErrorCode::invalidCommand; + // return Status{.statusType = StatusType::invalid, .errorCode = ErrorCode::invalidCommand}; } if(headerData.length == 0U) { - return Status{.statusType = StatusType::invalid, .errorCode = ErrorCode::invalidLength}; + return ErrorCode::invalidLength; } // Get the status type code auto statusType = 0_b; - auto statusErrorCode = UartReceive(&statusType); - - if(statusErrorCode != ErrorCode::success) - { - return Status{.statusType = StatusType::invalid, .errorCode = statusErrorCode}; - } + OUTCOME_TRY(UartReceive(&statusType)); if(statusType == noEventCode) { if(headerData.length != nNoEventBytes) { - return Status{.statusType = StatusType::invalid, .errorCode = ErrorCode::invalidLength}; + return ErrorCode::invalidLength; } std::array statusTypeArray = {statusType}; - auto crc32Error = CheckCrc32(std::span(statusTypeArray)); - if(crc32Error != ErrorCode::success) - { - return Status{.statusType = StatusType::invalid, .errorCode = crc32Error}; - } + OUTCOME_TRY(CheckCrc32(std::span(statusTypeArray))); - return Status{.statusType = StatusType::noEvent, - .programId = 0, - .startTime = 0, - .exitCode = 0, - .errorCode = ErrorCode::success}; + return Status{.statusType = StatusType::noEvent}; } if(statusType == programFinishedCode) { if(headerData.length != nProgramFinishedBytes) { - return Status{.statusType = StatusType::invalid, .errorCode = ErrorCode::invalidLength}; + return ErrorCode::invalidLength; } auto dataBuffer = Buffer{}; - auto programFinishedError = UartReceive(dataBuffer); - - if(programFinishedError != ErrorCode::success) - { - return Status{.statusType = StatusType::invalid, .errorCode = programFinishedError}; - } + OUTCOME_TRY(UartReceive(dataBuffer)); // Create another Buffer which includes the status type that was received beforehand because // it is needed to calculate the CRC32 checksum auto fullDataBuffer = std::array{}; fullDataBuffer[0] = statusType; std::copy(dataBuffer.begin(), dataBuffer.end(), fullDataBuffer.begin() + 1); - auto crc32Error = CheckCrc32(fullDataBuffer); - if(crc32Error != ErrorCode::success) - { - return Status{.statusType = StatusType::invalid, .errorCode = crc32Error}; - } + OUTCOME_TRY(CheckCrc32(fullDataBuffer)); auto programFinishedData = Deserialize(dataBuffer); return Status{.statusType = StatusType::programFinished, .programId = programFinishedData.programId, .startTime = programFinishedData.startTime, - .exitCode = programFinishedData.exitCode, - .errorCode = ErrorCode::success}; + .exitCode = programFinishedData.exitCode}; } if(statusType == resultsReadyCode) { if(headerData.length != nResultsReadyBytes) { - return Status{.statusType = StatusType::invalid, .errorCode = ErrorCode::invalidLength}; + return ErrorCode::invalidLength; } auto dataBuffer = Buffer{}; - auto resultsReadyError = UartReceive(dataBuffer); - if(resultsReadyError != ErrorCode::success) - { - return Status{.statusType = StatusType::invalid, .errorCode = resultsReadyError}; - } + OUTCOME_TRY(UartReceive(dataBuffer)); // Create another Buffer which includes the status type that was received beforehand because // it is needed to calculate the CRC32 checksum auto fullDataBuffer = std::array{}; fullDataBuffer[0] = statusType; std::copy(dataBuffer.begin(), dataBuffer.end(), fullDataBuffer.begin() + 1); - auto crc32Error = CheckCrc32(fullDataBuffer); - if(crc32Error != ErrorCode::success) - { - return Status{.statusType = StatusType::invalid, .errorCode = crc32Error}; - } + OUTCOME_TRY(CheckCrc32(fullDataBuffer)); + auto resultsReadyData = Deserialize(dataBuffer); return Status{.statusType = StatusType::resultsReady, .programId = resultsReadyData.programId, - .startTime = resultsReadyData.startTime, - .errorCode = ErrorCode::success}; + .startTime = resultsReadyData.startTime}; } - return Status{.statusType = StatusType::invalid, .errorCode = ErrorCode::invalidStatusType}; + return ErrorCode::invalidStatusType; } -auto ReturnResult() -> ResultInfo +auto ReturnResult() -> Result { // DEBUG RODOS::PRINTF("ReturnResult()\n"); @@ -387,42 +348,47 @@ auto ReturnResult() -> ResultInfo // Send command auto serialCommand = Serialize(returnResultId); - auto commandError = SendData(serialCommand); - if(commandError != ErrorCode::success) - { - return ResultInfo{.errorCode = commandError, .resultSize = 0U}; - } + OUTCOME_TRY(SendData(serialCommand)); // DEBUG // RODOS::PRINTF("\nStart receiving result\n"); // END DEBUG std::size_t totalResultSize = 0U; - std::size_t packets = 0U; - ResultInfo resultInfo; - // TODO: Turn into for loop - while(packets < maxNPackets) + std::size_t nPackets = 0U; + // TODO: Turn into for loop + while(nPackets < maxNPackets) { // DEBUG // RODOS::PRINTF("\nPacket %d\n", static_cast(packets)); // END DEBUG - resultInfo = ReturnResultRetry(); + auto returnResultRetryResult = ReturnResultRetry(); + // TYPE Result // DEBUG - RODOS::PRINTF("ResultInfo{errorCode = %d, resultSize = %d}\n", - static_cast(resultInfo.errorCode), - static_cast(resultInfo.resultSize)); - // END DEBUG - if(resultInfo.errorCode != ErrorCode::success) + + // Break if an error is returned + if(returnResultRetryResult.has_error()) + { + auto errorCode = returnResultRetryResult.error(); + RODOS::PRINTF(" ReturnResultRetry() resulted in an error : %d", + static_cast(errorCode)); + return returnResultRetryResult.error(); + } + // or if EOF is reached + if(returnResultRetryResult.value().eofIsReached) { - break; + RODOS::PRINTF(" ReturnResultRetry() reached EOF\n"); + return ResultInfo{.eofIsReached = true, .resultSize = totalResultSize}; } + + // END DEBUG // RODOS::PRINTF("\nWriting to file...\n"); // TODO: Actually write to a file - totalResultSize += resultInfo.resultSize; - packets++; + totalResultSize += returnResultRetryResult.value().resultSize; + nPackets++; } - return ResultInfo{.errorCode = resultInfo.errorCode, .resultSize = totalResultSize}; + return ResultInfo{.eofIsReached = false, .resultSize = totalResultSize}; } @@ -430,23 +396,49 @@ auto ReturnResult() -> ResultInfo //! the actual ReturnResult function. The communication happens in ReturnResultCommunication. //! //! @returns An error code and the number of received bytes in ResultInfo -auto ReturnResultRetry() -> ResultInfo +auto ReturnResultRetry() -> Result { - ResultInfo resultInfo; std::size_t errorCount = 0U; - do + + // TODO: infinite loop could be avoided by setting + // errorCount <= maxNNackRetries as the termination condition + while(true) { - resultInfo = ReturnResultCommunication(); - if(resultInfo.errorCode == ErrorCode::success - or resultInfo.errorCode == ErrorCode::successEof) + auto returnResultCommunicationResult = ReturnResultCommunication(); + if(returnResultCommunicationResult.has_value()) { SendCommand(cmdAck); - return resultInfo; + // returns {eofIsReached, resultSize} + return returnResultCommunicationResult.value(); } + + // Error in ReturnResultCommunication() FlushUartBuffer(); SendCommand(cmdNack); - } while(errorCount++ < maxNNackRetries); - return resultInfo; + errorCount++; + if(errorCount == maxNNackRetries) + { + return returnResultCommunicationResult.error(); + } + } + + + // Result result = ErrorCode::noErrorCodeSet; + // std::size_t errorCount = 0U; + // // TODO: CHange this + // do + // { + // result = ReturnResultCommunication(); + // // Could have reached EOF or not + // if(result.has_value()) + // { + // SendCommand(cmdAck); + // return result; + // } + // FlushUartBuffer(); + // SendCommand(cmdNack); + // } while(errorCount++ < maxNNackRetries); + // return result.value(); } @@ -454,32 +446,28 @@ auto ReturnResultRetry() -> ResultInfo // directly and instead writes to a non-primary RAM bank as an intermediate step. // // Simple results -> 1 round should work with DMA to RAM -auto ReturnResultCommunication() -> ResultInfo +auto ReturnResultCommunication() -> Result { // Receive command // If no result is available, the command will be NACK, // otherwise DATA Byte command = 0_b; - auto commandError = UartReceive(&command); - if(commandError != ErrorCode::success) - { - return ResultInfo{.errorCode = commandError, .resultSize = 0U}; - } + OUTCOME_TRY(UartReceive(&command)); if(command == cmdNack) { // TODO: necessary to differentiate errors or just return success with resultSize 0? - return ResultInfo{.errorCode = ErrorCode::noResultAvailable, .resultSize = 0U}; + return ErrorCode::noResultAvailable; } if(command == cmdEof) { - return ResultInfo{.errorCode = ErrorCode::successEof, .resultSize = 0U}; + return ResultInfo{.eofIsReached = true, .resultSize = 0U}; } if(command != cmdData) { // DEBUG RODOS::PRINTF("\nNot DATA command\n"); // END DEBUG - return ResultInfo{.errorCode = ErrorCode::invalidCommand, .resultSize = 0U}; + return ErrorCode::invalidCommand; } // DEBUG @@ -487,16 +475,12 @@ auto ReturnResultCommunication() -> ResultInfo // END DEBUG auto dataLengthBuffer = Buffer{}; - auto lengthError = UartReceive(dataLengthBuffer); - if(lengthError != ErrorCode::success) - { - return ResultInfo{.errorCode = lengthError, .resultSize = 0U}; - } - + OUTCOME_TRY(UartReceive(dataLengthBuffer)); auto actualDataLength = Deserialize(dataLengthBuffer); + if(actualDataLength == 0U or actualDataLength > maxDataLength) { - return ResultInfo{.errorCode = ErrorCode::invalidLength, .resultSize = 0U}; + return ErrorCode::invalidLength; } // DEBUG @@ -504,31 +488,21 @@ auto ReturnResultCommunication() -> ResultInfo // END DEBUG // Get the actual data - auto dataError = UartReceive( - std::span(cepDataBuffer.begin(), cepDataBuffer.begin() + actualDataLength)); - - if(dataError != ErrorCode::success) - { - return ResultInfo{.errorCode = dataError, .resultSize = 0U}; - } + OUTCOME_TRY(UartReceive( + std::span(cepDataBuffer.begin(), cepDataBuffer.begin() + actualDataLength))); // DEBUG // RODOS::PRINTF("\nCheck CRC\n"); // END DEBUG - auto crc32Error = CheckCrc32( - std::span(cepDataBuffer.begin(), cepDataBuffer.begin() + actualDataLength)); - - if(crc32Error != ErrorCode::success) - { - return ResultInfo{.errorCode = crc32Error, .resultSize = 0U}; - } + OUTCOME_TRY(CheckCrc32( + std::span(cepDataBuffer.begin(), cepDataBuffer.begin() + actualDataLength))); // DEBUG RODOS::PRINTF("\nSuccess\n"); // END DEBUG - return {ErrorCode::success, actualDataLength}; + return ResultInfo{.eofIsReached = false, .resultSize = actualDataLength}; } @@ -547,33 +521,23 @@ auto ReturnResultCommunication() -> ResultInfo //! @param currentTime A unix timestamp //! //! @returns A relevant error code -auto UpdateTime(UpdateTimeData const & data) -> ErrorCode +auto UpdateTime(UpdateTimeData const & data) -> Result { RODOS::PRINTF("UpdateTime()\n"); auto serialData = Serialize(data); - auto errorCode = SendData(serialData); - if(errorCode != ErrorCode::success) - { - return errorCode; - } + OUTCOME_TRY(SendData(serialData)); // On success, wait for second N/ACK - // TODO: (Daniel) Change to UartReceive() // TODO: Refactor this common pattern into a function // TODO: Implement read functions that return a type and internally use Deserialize() auto answer = 0x00_b; - uart.suspendUntilDataReady(RODOS::NOW() + eduTimeout); + OUTCOME_TRY(UartReceive(&answer)); - auto nReadBytes = uart.read(&answer, 1); - if(nReadBytes == 0) - { - return ErrorCode::timeout; - } switch(answer) { case cmdAck: { - return ErrorCode::success; + return outcome_v2::success(); } case cmdNack: { @@ -601,7 +565,7 @@ void SendCommand(Byte commandId) //! @brief Send a data packet over UART to the EDU. //! //! @param data The data to be sent -auto SendData(std::span data) -> ErrorCode +auto SendData(std::span data) -> Result { std::size_t const nBytes = data.size(); if(nBytes >= maxDataLength) @@ -636,7 +600,7 @@ auto SendData(std::span data) -> ErrorCode { case cmdAck: { - return ErrorCode::success; + return outcome_v2::success(); } case cmdNack: { @@ -659,7 +623,7 @@ auto SendData(std::span data) -> ErrorCode //! //! @returns A relevant EDU error code // TODO: Use hal::ReadFrom() -auto UartReceive(std::span destination) -> ErrorCode +auto UartReceive(std::span destination) -> Result { if(size(destination) > maxDataLength) { @@ -679,7 +643,7 @@ auto UartReceive(std::span destination) -> ErrorCode } totalReceivedBytes += nReceivedBytes; } - return ErrorCode::success; + return outcome_v2::success(); } @@ -689,7 +653,7 @@ auto UartReceive(std::span destination) -> ErrorCode //! //! @returns A relevant EDU error code // TODO: Use hal::ReadFrom() -auto UartReceive(void * destination) -> ErrorCode +auto UartReceive(void * destination) -> Result { uart.suspendUntilDataReady(RODOS::NOW() + eduTimeout); auto nReceivedBytes = uart.read(destination, 1); @@ -697,7 +661,7 @@ auto UartReceive(void * destination) -> ErrorCode { return ErrorCode::timeout; } - return ErrorCode::success; + return outcome_v2::success(); } @@ -722,7 +686,7 @@ auto FlushUartBuffer() -> void } -auto CheckCrc32(std::span data) -> ErrorCode +auto CheckCrc32(std::span data) -> Result { auto const computedCrc32 = utility::Crc32(data); @@ -735,7 +699,7 @@ auto CheckCrc32(std::span data) -> ErrorCode auto crc32Buffer = Buffer{}; - auto receiveError = UartReceive(crc32Buffer); + OUTCOME_TRY(UartReceive(crc32Buffer)); // DEBUG // RODOS::PRINTF("Received CRC: "); @@ -743,15 +707,11 @@ auto CheckCrc32(std::span data) -> ErrorCode // RODOS::PRINTF("\n"); // END DEBUG - if(receiveError != ErrorCode::success) - { - return receiveError; - } if(computedCrc32 != Deserialize(crc32Buffer)) { return ErrorCode::wrongChecksum; } - return ErrorCode::success; + return outcome_v2::success(); } diff --git a/Sts1CobcSw/Edu/Edu.hpp b/Sts1CobcSw/Edu/Edu.hpp index fc1201aa..60776385 100644 --- a/Sts1CobcSw/Edu/Edu.hpp +++ b/Sts1CobcSw/Edu/Edu.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -12,16 +13,19 @@ namespace sts1cobcsw::edu { +template +using Result = outcome_v2::experimental::status_result; + auto Initialize() -> void; auto TurnOn() -> void; auto TurnOff() -> void; // TODO: Why does this return a std::int32_t? -[[nodiscard]] auto StoreArchive(StoreArchiveData const & data) -> std::int32_t; -[[nodiscard]] auto ExecuteProgram(ExecuteProgramData const & data) -> ErrorCode; -[[nodiscard]] auto StopProgram() -> ErrorCode; +[[nodiscard]] auto StoreArchive(StoreArchiveData const & data) -> Result; +[[nodiscard]] auto ExecuteProgram(ExecuteProgramData const & data) -> Result; +[[nodiscard]] auto StopProgram() -> Result; // TODD: Find better name (or maybe even mechanism) for GetStatus -[[nodiscard]] auto GetStatus() -> Status; -[[nodiscard]] auto ReturnResult() -> ResultInfo; -[[nodiscard]] auto UpdateTime(UpdateTimeData const & data) -> ErrorCode; +[[nodiscard]] auto GetStatus() -> Result; +[[nodiscard]] auto ReturnResult() -> Result; +[[nodiscard]] auto UpdateTime(UpdateTimeData const & data) -> Result; } diff --git a/Sts1CobcSw/Edu/EduMock.cpp b/Sts1CobcSw/Edu/EduMock.cpp index 74bee4fe..f6d6d608 100644 --- a/Sts1CobcSw/Edu/EduMock.cpp +++ b/Sts1CobcSw/Edu/EduMock.cpp @@ -10,14 +10,7 @@ using RODOS::PRINTF; using utility::PrintFormattedSystemUtc; -// TODO: Move this to the proper file -auto ResumeEduErrorCommunicationThread() -> void -{ - PRINTF("\nCall to ResumeEduErrorCommunicationThread()\n"); - PrintFormattedSystemUtc(); -} - - +// TODO: Move this to EduProgramQueueThreadMock.cpp or something auto ResumeEduProgramQueueThread() -> void { PRINTF("Call to ResumeEduProgramQueueThread()\n"); @@ -27,7 +20,6 @@ auto ResumeEduProgramQueueThread() -> void // TODO: This file is not used at all right now. Think about the mocking later. namespace edu { -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) auto Initialize() -> void { PrintFormattedSystemUtc(); @@ -35,7 +27,6 @@ auto Initialize() -> void } -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) auto TurnOn() -> void { PrintFormattedSystemUtc(); @@ -43,7 +34,6 @@ auto TurnOn() -> void } -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) auto TurnOff() -> void { PrintFormattedSystemUtc(); @@ -51,8 +41,7 @@ auto TurnOff() -> void } -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) -auto StoreArchive(StoreArchiveData const & data) -> std::int32_t +auto StoreArchive(StoreArchiveData const & data) -> Result { PrintFormattedSystemUtc(); PRINTF("Call to StoreArchive(programId = %d)\n", data.programId); @@ -60,80 +49,38 @@ auto StoreArchive(StoreArchiveData const & data) -> std::int32_t } -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) -auto ExecuteProgram(ExecuteProgramData const & data) -> ErrorCode +auto ExecuteProgram(ExecuteProgramData const & data) -> Result { PrintFormattedSystemUtc(); PRINTF("Call to ExecuteProgram(programId = %d, startTime = %" PRIi32 ", timeout = %d)\n", data.programId, data.startTime, data.timeout); - return ErrorCode::success; + return outcome_v2::success(); } -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) -auto StopProgram() -> ErrorCode +auto StopProgram() -> Result { PrintFormattedSystemUtc(); PRINTF("Call to StopProgram()\n"); - return ErrorCode::success; + return outcome_v2::success(); } -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) -auto GetStatus() -> Status +auto GetStatus() -> Result { PrintFormattedSystemUtc(); PRINTF("Call to GetStatus()\n"); - return {.statusType = StatusType::invalid, - .programId = 0, - .startTime = 0, - .exitCode = 0, - .errorCode = ErrorCode::success}; + return Status{.statusType = StatusType::invalid, .programId = 0, .startTime = 0, .exitCode = 0}; } -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) -auto UpdateTime(UpdateTimeData const & data) -> ErrorCode +auto UpdateTime(UpdateTimeData const & data) -> Result { PrintFormattedSystemUtc(); PRINTF("Call to UpdateTime(currentTime = %" PRIi32 ")\n", data.currentTime); - return ErrorCode::success; -} - - -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) -auto SendCommand(Byte commandId) -> void -{ - PrintFormattedSystemUtc(); - PRINTF("Call to SendCommand(commandId = 0x%02x)\n", static_cast(commandId)); -} - - -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) -auto SendData(std::span data) -> ErrorCode -{ - PrintFormattedSystemUtc(); - PRINTF("Call to SendData(size(data) = %d)\n", size(data)); - return ErrorCode::success; -} - - -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) -auto UartReceive([[maybe_unused]] std::span destination) -> ErrorCode -{ - PrintFormattedSystemUtc(); - PRINTF("Call to UartReceive(size(destination) = %d)\n", size(destination)); - return ErrorCode::success; -} - - -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) -void FlushUartBuffer() -{ - PrintFormattedSystemUtc(); - PRINTF("Call to FlushUartBuffer()\n"); + return outcome_v2::success(); } } } diff --git a/Sts1CobcSw/Edu/Enums.hpp b/Sts1CobcSw/Edu/Enums.hpp index a937fdc9..2d919a3e 100644 --- a/Sts1CobcSw/Edu/Enums.hpp +++ b/Sts1CobcSw/Edu/Enums.hpp @@ -6,10 +6,7 @@ namespace sts1cobcsw::edu { enum class ErrorCode { - noErrorCodeSet, - success, - successEof, - invalidResult, + invalidResult = 1, bufferTooSmall, uartNotInitialized, timeout, diff --git a/Sts1CobcSw/Edu/Structs.hpp b/Sts1CobcSw/Edu/Structs.hpp index fe4ff2a2..dfdc7d06 100644 --- a/Sts1CobcSw/Edu/Structs.hpp +++ b/Sts1CobcSw/Edu/Structs.hpp @@ -56,7 +56,6 @@ struct Status std::uint16_t programId = 0; std::int32_t startTime = 0; std::uint8_t exitCode = 0; - ErrorCode errorCode = ErrorCode::noErrorCodeSet; }; @@ -77,7 +76,7 @@ struct ProgramFinishedStatus struct ResultInfo { - ErrorCode errorCode = ErrorCode::noErrorCodeSet; + bool eofIsReached = false; std::size_t resultSize = 0; }; } diff --git a/Sts1CobcSw/EduListenerThread.cpp b/Sts1CobcSw/EduListenerThread.cpp index 04e74bdf..6957af34 100644 --- a/Sts1CobcSw/EduListenerThread.cpp +++ b/Sts1CobcSw/EduListenerThread.cpp @@ -46,18 +46,17 @@ class EduListenerThread : public RODOS::StaticThread<> eduIsAliveBufferForListener.get(eduIsAlive); // RODOS::PRINTF("[EduListenerThread] Read eduHasUpdate pin\n"); - // TODO: Check if edu is alive + // TODO: Check if EDU is alive if(eduIsAlive and eduHasUpdate) { // RODOS::PRINTF("[EduListenerThread] Edu is alive and has an update\n"); // Communicate with EDU - auto status = edu::GetStatus(); + auto getStatusResult = edu::GetStatus(); // RODOS::PRINTF("EduStatus : %d, EduErrorcode %d\n", status.statusType, // status.errorCode); - if(status.errorCode != edu::ErrorCode::success - and status.errorCode != edu::ErrorCode::successEof) + if(getStatusResult.has_error()) { // RODOS::PRINTF("[EduListenerThread] GetStatus() error code : %d.\n", // status.errorCode); @@ -66,73 +65,73 @@ class EduListenerThread : public RODOS::StaticThread<> // GetStatus().\n"); ResumeEduCommunicationErrorThread(); } - else + + if(getStatusResult.has_value()) { // RODOS::PRINTF("[EduListenerThread] Call to GetStatus() resulted in // success.\n"); - } - - switch(status.statusType) - { - case edu::StatusType::programFinished: + auto status = getStatusResult.value(); + switch(status.statusType) { - // Program has finished - // Find the correspongind queueEntry and update it, then resume edu queue - // thread - if(status.exitCode == 0) + case edu::StatusType::programFinished: { - edu::UpdateProgramStatusHistory( - status.programId, - status.startTime, - edu::ProgramStatus::programExecutionSucceeded); + // Program has finished + // Find the correspongind queueEntry and update it, then resume edu + // queue thread + if(status.exitCode == 0) + { + edu::UpdateProgramStatusHistory( + status.programId, + status.startTime, + edu::ProgramStatus::programExecutionSucceeded); + } + else + { + edu::UpdateProgramStatusHistory( + status.programId, + status.startTime, + edu::ProgramStatus::programExecutionFailed); + } + ResumeEduProgramQueueThread(); + break; } - else + case edu::StatusType::resultsReady: { + // Edu wants to send result file + // Send return result to Edu, Communicate, and interpret the results to + // update the S&H Entry from 3 or 4 to 5. + auto returnResultResult = edu::ReturnResult(); + if(returnResultResult.has_error()) + { + /* + RODOS::PRINTF( + "[EduListenerThread] Error Code From ReturnResult() : %d.\n", + errorCode); + RODOS::PRINTF( + "[EduListenerThread] Communication error after call to " + "ReturnResult().\n"); + */ + ResumeEduCommunicationErrorThread(); + } + else + { + // RODOS::PRINTF( + // "[EduListenerThread] Call to ReturnResults() resulted in " + // "success.\n"); + } + // break; + edu::UpdateProgramStatusHistory( status.programId, status.startTime, - edu::ProgramStatus::programExecutionFailed); + edu::ProgramStatus::resultFileTransfered); + break; } - ResumeEduProgramQueueThread(); - break; - } - case edu::StatusType::resultsReady: - { - // Edu wants to send result file - // Send return result to Edu, Communicate, and interpret the results to - // update the S&H Entry from 3 or 4 to 5. - auto resultsInfo = edu::ReturnResult(); - auto errorCode = resultsInfo.errorCode; - if(errorCode != edu::ErrorCode::success - and errorCode != edu::ErrorCode::successEof) - { - /* - RODOS::PRINTF( - "[EduListenerThread] Error Code From ReturnResult() : %d.\n", - errorCode); - RODOS::PRINTF( - "[EduListenerThread] Communication error after call to " - "ReturnResult().\n"); - */ - ResumeEduCommunicationErrorThread(); - } - else + case edu::StatusType::invalid: + case edu::StatusType::noEvent: { - // RODOS::PRINTF( - // "[EduListenerThread] Call to ReturnResults() resulted in " - // "success.\n"); + break; } - // break; - - edu::UpdateProgramStatusHistory(status.programId, - status.startTime, - edu::ProgramStatus::resultFileTransfered); - break; - } - case edu::StatusType::invalid: - case edu::StatusType::noEvent: - { - break; } } } diff --git a/Sts1CobcSw/EduProgramQueueThread.cpp b/Sts1CobcSw/EduProgramQueueThread.cpp index 22657c5f..659fad37 100644 --- a/Sts1CobcSw/EduProgramQueueThread.cpp +++ b/Sts1CobcSw/EduProgramQueueThread.cpp @@ -93,11 +93,12 @@ class EduProgramQueueThread : public RODOS::StaticThread RODOS::PRINTF("Resuming here after first wait.\n"); utility::PrintFormattedSystemUtc(); - auto errorCode = + auto updateTimeResult = edu::UpdateTime(edu::UpdateTimeData{.currentTime = utility::GetUnixUtc()}); - if(errorCode != edu::ErrorCode::success) + if(updateTimeResult.has_error()) { - RODOS::PRINTF("UpdateTime error code : %d\n", static_cast(errorCode)); + RODOS::PRINTF("UpdateTime error code : %d\n", + static_cast(updateTimeResult.error())); RODOS::PRINTF( "[EduProgramQueueThread] Communication error after call to UpdateTime().\n"); ResumeEduCommunicationErrorThread(); @@ -126,10 +127,10 @@ class EduProgramQueueThread : public RODOS::StaticThread auto executeProgramData = edu::ExecuteProgramData{ .programId = programId, .startTime = startTime, .timeout = timeout}; // Start Process - errorCode = edu::ExecuteProgram(executeProgramData); + auto executeProgramResult = edu::ExecuteProgram(executeProgramData); // errorCode = edu::ErrorCode::success; - if(errorCode != edu::ErrorCode::success) + if(executeProgramResult.has_error()) { RODOS::PRINTF( "[EduProgramQueueThread] Communication error after call to " diff --git a/Sts1CobcSw/Outcome/CMakeLists.txt b/Sts1CobcSw/Outcome/CMakeLists.txt new file mode 100644 index 00000000..c4b7ab93 --- /dev/null +++ b/Sts1CobcSw/Outcome/CMakeLists.txt @@ -0,0 +1,2 @@ +# TODO: When Outcome is installed via CMake link it here +target_link_libraries(Sts1CobcSw_Outcome INTERFACE rodos::rodos) diff --git a/Sts1CobcSw/Outcome/Outcome.hpp b/Sts1CobcSw/Outcome/Outcome.hpp new file mode 100644 index 00000000..ba469623 --- /dev/null +++ b/Sts1CobcSw/Outcome/Outcome.hpp @@ -0,0 +1,54 @@ +#pragma once + +#if defined(SYSTEM_ERROR2_NOT_POSIX) + #define SYSTEM_ERROR2_FATAL(msg) RODOS::hwResetAndReboot() +#endif + + +#include + +#include + + +struct RebootPolicy : outcome_v2::experimental::policy::base +{ + template + // NOLINTNEXTLINE(readability-identifier-naming) + static constexpr void wide_value_check(Impl && self) + { + //! Call RODOS::hwResetAndReboot() whenever .value() is called on an object that does not + //! contain a value + if(!base::_has_value(std::forward(self))) + { + RODOS::PRINTF( + "Error: The value is not present. Performing hardware reset and reboot.\n"); + RODOS::hwResetAndReboot(); + } + } + + template + // NOLINTNEXTLINE(readability-identifier-naming) + static constexpr void wide_error_check(Impl && self) + { + //! Call RODOS::hwResetAndReboot() whenever .error() is called on an object that does not + //! contain an error + if(!base::_has_error(std::forward(self))) + { + RODOS::PRINTF( + "Error: The error is not present. Performing hardware reset and reboot.\n"); + RODOS::hwResetAndReboot(); + } + } + + template + // NOLINTNEXTLINE(readability-identifier-naming) + static constexpr void wide_exception_check(Impl && self) + { + if(!base::_has_exception(std::forward(self))) + { + RODOS::PRINTF( + "Error: The exception is not present. Performing hardware reset and reboot.\n"); + RODOS::hwResetAndReboot(); + } + } +}; diff --git a/Tests/.clang-tidy b/Tests/.clang-tidy index 50ac4ada..1747a357 100644 --- a/Tests/.clang-tidy +++ b/Tests/.clang-tidy @@ -1,4 +1,3 @@ Checks: "-readability-identifier-length, \ -*magic-numbers*" InheritParentConfig: true - diff --git a/Tests/HardwareTests/EduCommandTests/EduCommands.test.cpp b/Tests/HardwareTests/EduCommandTests/EduCommands.test.cpp index 4cbe5116..ad222ef4 100644 --- a/Tests/HardwareTests/EduCommandTests/EduCommands.test.cpp +++ b/Tests/HardwareTests/EduCommandTests/EduCommands.test.cpp @@ -63,8 +63,16 @@ class EduCommandsTest : public RODOS::StaticThread<> { auto currentTime = utility::GetUnixUtc(); PRINTF("Sending UpdateTime(currentTime = %d)\n", static_cast(currentTime)); - auto errorCode = edu::UpdateTime({.currentTime = currentTime}); - PRINTF("Returned error code: %d\n", static_cast(errorCode)); + auto updateTimeResult = edu::UpdateTime({.currentTime = currentTime}); + if(updateTimeResult.has_error()) + { + PRINTF("Returned error code: %d\n", + static_cast(updateTimeResult.error())); + } + else + { + PRINTF("UpdateTime executed successfully\n"); + } break; } case 'e': @@ -92,30 +100,54 @@ class EduCommandsTest : public RODOS::StaticThread<> programId, startTime, timeout); - auto errorCode = edu::ExecuteProgram( + auto executeProgramResult = edu::ExecuteProgram( {.programId = programId, .startTime = startTime, .timeout = timeout}); - PRINTF("Returned error code: %d\n", static_cast(errorCode)); + // TODO: Fix naming + if(executeProgramResult.has_error()) + { + PRINTF("Returned error code: %d\n", + static_cast(executeProgramResult.error())); + } + else + { + PRINTF("Execute Program Returned no error"); + } break; } case 'g': { PRINTF("Sending GetStatus()\n"); - auto status = edu::GetStatus(); + auto getStatusResult = edu::GetStatus(); PRINTF("Returned status:\n"); - PRINTF(" type = %d\n", static_cast(status.statusType)); - PRINTF(" program ID = %d\n", static_cast(status.programId)); - PRINTF(" startTime = %d\n", static_cast(status.startTime)); - PRINTF(" exit code = %d\n", static_cast(status.exitCode)); - PRINTF(" error code = %d\n", static_cast(status.errorCode)); + if(getStatusResult.has_error()) + { + PRINTF(" error code = %d\n", static_cast(getStatusResult.error())); + } + else + { + auto status = getStatusResult.value(); + PRINTF(" type = %d\n", static_cast(status.statusType)); + PRINTF(" program ID = %d\n", static_cast(status.programId)); + PRINTF(" startTime = %d\n", static_cast(status.startTime)); + PRINTF(" exit code = %d\n", static_cast(status.exitCode)); + } break; } case 'r': { PRINTF("Sending ReturnResult()\n"); - auto resultInfo = edu::ReturnResult(); + auto returnResultResult = edu::ReturnResult(); PRINTF("Returned result info:\n"); - PRINTF(" error code = %d\n", static_cast(resultInfo.errorCode)); - PRINTF(" result size = %d\n", static_cast(resultInfo.resultSize)); + if(returnResultResult.has_error()) + { + PRINTF(" error code = %d\n", + static_cast(returnResultResult.error())); + } + else + { + PRINTF(" result size = %d\n", + static_cast(returnResultResult.value().resultSize)); + } break; } default: diff --git a/Tests/UnitTests/CMakeLists.txt b/Tests/UnitTests/CMakeLists.txt index 08dabf4c..072cd4a2 100644 --- a/Tests/UnitTests/CMakeLists.txt +++ b/Tests/UnitTests/CMakeLists.txt @@ -13,6 +13,9 @@ endif() add_program(Serial Serial.test.cpp) target_link_libraries(Sts1CobcSwTests_Serial PRIVATE Catch2::Catch2WithMain Sts1CobcSw_Serial) +add_program(Outcome Outcome.test.cpp) +target_link_libraries(Sts1CobcSwTests_Outcome PRIVATE Catch2::Catch2WithMain) + get_property( all_unit_test_targets DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/Tests/UnitTests/Outcome.test.cpp b/Tests/UnitTests/Outcome.test.cpp new file mode 100644 index 00000000..70ecc09a --- /dev/null +++ b/Tests/UnitTests/Outcome.test.cpp @@ -0,0 +1,175 @@ +// Test the outcome library + +#include +#include + +#include +#include +#include +#include +#include + + +// First define a policy +struct AbortPolicy : outcome_v2::experimental::policy::base +{ + template + // NOLINTNEXTLINE(readability-identifier-naming) + static constexpr void wide_value_check(Impl && self) + { + if(!base::_has_value(std::forward(self))) + { + std::abort(); + } + } + + template + // NOLINTNEXTLINE(readability-identifier-naming) + static constexpr void wide_error_check(Impl && self) + { + if(!base::_has_error(std::forward(self))) + { + std::abort(); + } + } + + template + // NOLINTNEXTLINE(readability-identifier-naming) + static constexpr void wide_exception_check(Impl && self) + { + if(!base::_has_exception(std::forward(self))) + { + std::abort(); + } + } +}; + + +enum class ConversionErrc +{ + success = 0, // 0 should not represent an error + emptyString = 1, // (for rationale, see tutorial on error codes) + illegalChar = 2, + tooLong = 3, +}; + + +template +using Result = outcome_v2::experimental::status_result; + + +auto Convert(std::string const & str) noexcept -> Result +{ + if(str.empty()) + { + return ConversionErrc::emptyString; + } + + // NOLINTNEXTLINE(readability-identifier-length) + if(!std::all_of(str.begin(), str.end(), [](unsigned char c) { return std::isdigit(c); })) + { + return ConversionErrc::illegalChar; + } + + if(str.length() > std::numeric_limits::digits10) + { + return ConversionErrc::tooLong; + } + + // NOLINTNEXTLINE(cert-err34-c) + return atoi(str.c_str()); +} + + +// NOLINTNEXTLINE(cert-err58-cpp) +TEST_CASE("Inspecting result") +{ + Result result = outcome_v2::success(); + REQUIRE(result.has_value()); + REQUIRE(result); // Boolean cast + + // Test each defined conversion error + result = Convert("abc"); + REQUIRE(result.has_error()); + REQUIRE(result.error() == ConversionErrc::illegalChar); + + result = Convert("314159265359"); + REQUIRE(result.has_error()); + REQUIRE(result.error() == ConversionErrc::tooLong); + + result = Convert(""); + REQUIRE(result.has_error()); + REQUIRE(result.error() == ConversionErrc::emptyString); + + + // Test success + result = Convert("278"); + REQUIRE(result.has_value()); + REQUIRE(result); // Boolean cast + REQUIRE(result.value() == 278); +} + + +// Dummy function to chain with Convert +auto WriteData(int * buffer, bool shouldSucceed) -> Result +{ + if(shouldSucceed) + { + *buffer = 1; + return outcome_v2::success(); + } + // Return some dummy error here, just to check that it is handled correctly + return ConversionErrc::emptyString; +} + + +// Dummy function to chain with Convert. Return type is a pair just to display how OUTCOME_TRY works +// with different return types +auto Add(int op1, std::string const & str) -> Result> +{ + // From https://ned14.github.io/outcome/tutorial/essential/result/inspecting/ + // Our control statement means: if Convert() returned failure, this same error information + // should be returned from Add(), even though Add() and Convert() have different result<> types. + // If Convert() returned success, we create variable op2 of type int with the value returned + // from Convert(). If control goes to subsequent line, it means Convert() succeeded and variable + // of type int is in scope. + OUTCOME_TRY(auto op2, Convert(str)); + return std::make_pair(op1 + op2, op2); +} + + +auto Write(bool shouldSucceed) -> Result +{ + // Same thing as in Add(), but this time the function that we are calling returns void in case + // of success, thus we do not need to provide two parameters to OUTCOME_TRY + int buffer = 0; + OUTCOME_TRY(WriteData(&buffer, shouldSucceed)); + return buffer; +} + + +TEST_CASE("TRY macro") +{ + // Test failure + auto result1 = Write(/*shouldSucceed=*/false); + REQUIRE(result1.has_error()); + REQUIRE(result1.error() == ConversionErrc::emptyString); + + // Test success + auto result2 = Write(/*shouldSucceed=*/true); + REQUIRE(result2.has_value()); + REQUIRE(result2); + REQUIRE(result2.value() == 1); + + // Test failure + auto result3 = Add(1, "3.14"); + REQUIRE(not result3.has_value()); + REQUIRE(result3.has_error()); + REQUIRE(result3.error() == ConversionErrc::illegalChar); + + // Test success + auto result4 = Add(1, "278"); + REQUIRE(result4.has_value()); + REQUIRE(result4); + REQUIRE(result4.value().first == 279); +}