Linux CommandExecutor #536

Merged
muellerr merged 9 commits from eive/fsfw:mueller/cmd-executor into development 2022-02-02 10:17:52 +01:00
23 changed files with 663 additions and 93 deletions

View File

@ -5,7 +5,7 @@ RUN apt-get --yes upgrade
#tzdata is a dependency, won't install otherwise
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get --yes install gcc g++ cmake make lcov git valgrind nano
RUN apt-get --yes install gcc g++ cmake make lcov git valgrind nano iputils-ping
RUN git clone https://github.com/catchorg/Catch2.git && \
cd Catch2 && \

View File

@ -3,7 +3,13 @@ cmake_minimum_required(VERSION 3.13)
# Can also be changed by upper CMakeLists.txt file
find_library(LIB_FSFW_NAME fsfw REQUIRED)
option(FSFW_HAL_ADD_LINUX "Add the Linux HAL to the sources. Required gpiod library" OFF)
option(FSFW_HAL_ADD_LINUX "Add the Linux HAL to the sources. Requires gpiod library" OFF)
# On by default for now because I did not have an issue including and compiling those files
# and libraries on a Desktop Linux system and the primary target of the FSFW is still embedded
# Linux. The only exception from this is the gpiod library which requires a dedicated installation,
# but CMake is able to determine whether this library is installed with find_library.
option(FSFW_HAL_LINUX_ADD_PERIPHERAL_DRIVERS "Add peripheral drivers for embedded Linux" ON)
option(FSFW_HAL_ADD_RASPBERRY_PI "Add Raspberry Pi specific code to the sources" OFF)
option(FSFW_HAL_ADD_STM32H7 "Add the STM32H7 HAL to the sources" OFF)
option(FSFW_HAL_WARNING_SHADOW_LOCAL_GCC "Enable -Wshadow=local warning in GCC" ON)

View File

@ -1,7 +1,7 @@
add_subdirectory(devicehandlers)
add_subdirectory(common)
if(FSFW_HAL_ADD_LINUX)
if(UNIX)
add_subdirectory(linux)
endif()

View File

@ -4,10 +4,13 @@ endif()
target_sources(${LIB_FSFW_NAME} PRIVATE
UnixFileGuard.cpp
CommandExecutor.cpp
utility.cpp
)
add_subdirectory(gpio)
add_subdirectory(spi)
add_subdirectory(i2c)
add_subdirectory(uart)
if(FSFW_HAL_LINUX_ADD_PERIPHERAL_DRIVERS)
add_subdirectory(gpio)
add_subdirectory(spi)
add_subdirectory(i2c)
add_subdirectory(uart)
endif()

View File

@ -0,0 +1,213 @@
#include "CommandExecutor.h"
#include "fsfw/serviceinterface.h"
#include "fsfw/container/SimpleRingBuffer.h"
#include "fsfw/container/DynamicFIFO.h"
#include <unistd.h>
#include <cstring>
CommandExecutor::CommandExecutor(const size_t maxSize):
readVec(maxSize) {
waiter.events = POLLIN;
}
ReturnValue_t CommandExecutor::load(std::string command, bool blocking, bool printOutput) {
if(state == States::PENDING) {
return COMMAND_PENDING;
}
currentCmd = command;
this->blocking = blocking;
this->printOutput = printOutput;
if(state == States::IDLE) {
state = States::COMMAND_LOADED;
}
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t CommandExecutor::execute() {
if(state == States::IDLE) {
return NO_COMMAND_LOADED_OR_PENDING;
}
else if(state == States::PENDING) {
return COMMAND_PENDING;
}
currentCmdFile = popen(currentCmd.c_str(), "r");
if(currentCmdFile == nullptr) {
lastError = errno;
return HasReturnvaluesIF::RETURN_FAILED;
}
if(blocking) {
ReturnValue_t result = executeBlocking();
state = States::IDLE;
return result;
}
else {
currentFd = fileno(currentCmdFile);
waiter.fd = currentFd;
}
state = States::PENDING;
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t CommandExecutor::close() {
if(state == States::PENDING) {
// Attempt to close process, irrespective of if it is running or not
if(currentCmdFile != nullptr) {
pclose(currentCmdFile);
}
}
return HasReturnvaluesIF::RETURN_OK;
}
void CommandExecutor::printLastError(std::string funcName) const {
if(lastError != 0) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << funcName << " pclose failed with code " << lastError << ": " <<
strerror(lastError) << std::endl;
#else
sif::printError("%s pclose failed with code %d: %s\n",
funcName, lastError, strerror(lastError));
#endif
}
}
void CommandExecutor::setRingBuffer(SimpleRingBuffer *ringBuffer,
DynamicFIFO<uint16_t>* sizesFifo) {
this->ringBuffer = ringBuffer;
this->sizesFifo = sizesFifo;
}
ReturnValue_t CommandExecutor::check(bool& replyReceived) {
if(blocking) {
return HasReturnvaluesIF::RETURN_OK;
}
switch(state) {
case(States::IDLE):
case(States::COMMAND_LOADED): {
return NO_COMMAND_LOADED_OR_PENDING;
}
case(States::PENDING): {
break;
}
}
int result = poll(&waiter, 1, 0);
switch(result) {
case(0): {
return HasReturnvaluesIF::RETURN_OK;
break;
}
case(1): {
if (waiter.revents & POLLIN) {
ssize_t readBytes = read(currentFd, readVec.data(), readVec.size());
if(readBytes == 0) {
// Should not happen
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "CommandExecutor::check: No bytes read "
"after poll event.." << std::endl;
#else
sif::printWarning("CommandExecutor::check: No bytes read after poll event..\n");
#endif
break;
}
else if(readBytes > 0) {
replyReceived = true;
if(printOutput) {
// It is assumed the command output is line terminated
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::info << currentCmd << " | " << readVec.data();
#else
sif::printInfo("%s | %s", currentCmd, readVec.data());
#endif
}
if(ringBuffer != nullptr) {
ringBuffer->writeData(reinterpret_cast<const uint8_t*>(
readVec.data()), readBytes);
}
if(sizesFifo != nullptr) {
if(not sizesFifo->full()) {
sizesFifo->insert(readBytes);
}
}
}
else {
// Should also not happen
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "CommandExecutor::check: Error " << errno << ": " <<
strerror(errno) << std::endl;
#else
sif::printWarning("CommandExecutor::check: Error %d: %s\n", errno, strerror(errno));
#endif
}
}
if(waiter.revents & POLLERR) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "CommandExecuter::check: Poll error" << std::endl;
#else
sif::printWarning("CommandExecuter::check: Poll error\n");
#endif
return COMMAND_ERROR;
}
if(waiter.revents & POLLHUP) {
result = pclose(currentCmdFile);
ReturnValue_t retval = EXECUTION_FINISHED;
if(result != 0) {
lastError = result;
retval = HasReturnvaluesIF::RETURN_FAILED;
}
state = States::IDLE;
currentCmdFile = nullptr;
currentFd = 0;
return retval;
}
break;
}
}
return HasReturnvaluesIF::RETURN_OK;
}
void CommandExecutor::reset() {
CommandExecutor::close();
currentCmdFile = nullptr;
currentFd = 0;
state = States::IDLE;
}
int CommandExecutor::getLastError() const {
// See: https://stackoverflow.com/questions/808541/any-benefit-in-using-wexitstatus-macro-in-c-over-division-by-256-on-exit-statu
return WEXITSTATUS(this->lastError);
}
CommandExecutor::States CommandExecutor::getCurrentState() const {
return state;
}
ReturnValue_t CommandExecutor::executeBlocking() {
while(fgets(readVec.data(), readVec.size(), currentCmdFile) != nullptr) {
std::string output(readVec.data());
if(printOutput) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::info << currentCmd << " | " << output;
#else
sif::printInfo("%s | %s", currentCmd, output);
#endif
}
if(ringBuffer != nullptr) {
ringBuffer->writeData(reinterpret_cast<const uint8_t*>(output.data()), output.size());
}
if(sizesFifo != nullptr) {
if(not sizesFifo->full()) {
sizesFifo->insert(output.size());
}
}
}
int result = pclose(currentCmdFile);
if(result != 0) {
lastError = result;
return HasReturnvaluesIF::RETURN_FAILED;
}
return HasReturnvaluesIF::RETURN_OK;
}

View File

@ -0,0 +1,135 @@
#ifndef FSFW_SRC_FSFW_OSAL_LINUX_COMMANDEXECUTOR_H_
#define FSFW_SRC_FSFW_OSAL_LINUX_COMMANDEXECUTOR_H_
#include "fsfw/returnvalues/HasReturnvaluesIF.h"
#include "fsfw/returnvalues/FwClassIds.h"
#include <poll.h>
#include <string>
#include <vector>
class SimpleRingBuffer;
template <typename T> class DynamicFIFO;
/**
* @brief Helper class to execute shell commands in blocking and non-blocking mode
* @details
* This class is able to execute processes by using the Linux popen call. It also has the
* capability of writing the read output of a process into a provided ring buffer.
*
* The executor works by first loading the command which should be executed and specifying
* whether it should be executed blocking or non-blocking. After that, execution can be started
* with the execute command. In blocking mode, the execute command will block until the command
* has finished
*/
class CommandExecutor {
public:
enum class States {
IDLE,
COMMAND_LOADED,
PENDING
};
static constexpr uint8_t CLASS_ID = CLASS_ID::LINUX_OSAL;
//! [EXPORT] : [COMMENT] Execution of the current command has finished
static constexpr ReturnValue_t EXECUTION_FINISHED =
HasReturnvaluesIF::makeReturnCode(CLASS_ID, 0);
//! [EXPORT] : [COMMENT] Command is pending. This will also be returned if the user tries
//! to load another command but a command is still pending
static constexpr ReturnValue_t COMMAND_PENDING =
HasReturnvaluesIF::makeReturnCode(CLASS_ID, 1);
//! [EXPORT] : [COMMENT] Some bytes have been read from the executing process
static constexpr ReturnValue_t BYTES_READ =
HasReturnvaluesIF::makeReturnCode(CLASS_ID, 2);
//! [EXPORT] : [COMMENT] Command execution failed
static constexpr ReturnValue_t COMMAND_ERROR =
HasReturnvaluesIF::makeReturnCode(CLASS_ID, 3);
//! [EXPORT] : [COMMENT]
static constexpr ReturnValue_t NO_COMMAND_LOADED_OR_PENDING =
HasReturnvaluesIF::makeReturnCode(CLASS_ID, 4);
static constexpr ReturnValue_t PCLOSE_CALL_ERROR =
HasReturnvaluesIF::makeReturnCode(CLASS_ID, 6);
/**
* Constructor. Is initialized with maximum size of internal buffer to read data from the
* executed process.
* @param maxSize
*/
CommandExecutor(const size_t maxSize);
/**
* Load a new command which should be executed
* @param command
* @param blocking
* @param printOutput
* @return
*/
ReturnValue_t load(std::string command, bool blocking, bool printOutput = true);
/**
* Execute the loaded command.
* @return
* - In blocking mode, it will return RETURN_FAILED if
* the result of the system call was not 0. The error value can be accessed using
* getLastError
* - In non-blocking mode, this call will start
* the execution and then return RETURN_OK
*/
ReturnValue_t execute();
/**
* Only used in non-blocking mode. Checks the currently running command.
* @param bytesRead Will be set to the number of bytes read, if bytes have been read
* @return
* - BYTES_READ if bytes have been read from the executing process. It is recommended to call
* check again after this
* - RETURN_OK execution is pending, but no bytes have been read from the executing process
* - RETURN_FAILED if execution has failed, error value can be accessed using getLastError
* - EXECUTION_FINISHED if the process was executed successfully
* - NO_COMMAND_LOADED_OR_PENDING self-explanatory
* - COMMAND_ERROR internal poll error
*/
ReturnValue_t check(bool& replyReceived);
/**
* Abort the current command. Should normally not be necessary, check can be used to find
* out whether command execution was successful
* @return RETURN_OK
*/
ReturnValue_t close();
States getCurrentState() const;
int getLastError() const;
void printLastError(std::string funcName) const;
/**
* Assign a ring buffer and a FIFO which will be filled by the executor with the output
* read from the started process
* @param ringBuffer
* @param sizesFifo
*/
void setRingBuffer(SimpleRingBuffer* ringBuffer, DynamicFIFO<uint16_t>* sizesFifo);
/**
* Reset the executor. This calls close internally and then reset the state machine so new
* commands can be loaded and executed
*/
void reset();
private:
std::string currentCmd;
bool blocking = true;
FILE* currentCmdFile = nullptr;
int currentFd = 0;
bool printOutput = true;
std::vector<char> readVec;
struct pollfd waiter {};
SimpleRingBuffer* ringBuffer = nullptr;
DynamicFIFO<uint16_t>* sizesFifo = nullptr;
States state = States::IDLE;
int lastError = 0;
ReturnValue_t executeBlocking();
};
#endif /* FSFW_SRC_FSFW_OSAL_LINUX_COMMANDEXECUTOR_H_ */

View File

@ -1,12 +1,16 @@
target_sources(${LIB_FSFW_NAME} PRIVATE
LinuxLibgpioIF.cpp
)
# This abstraction layer requires the gpiod library. You can install this library
# with "sudo apt-get install -y libgpiod-dev". If you are cross-compiling, you need
# to install the package before syncing the sysroot to your host computer.
find_library(LIB_GPIO gpiod REQUIRED)
find_library(LIB_GPIO gpiod)
if(${LIB_GPIO} MATCHES LIB_GPIO-NOTFOUND)
message(STATUS "gpiod library not found, not linking against it")
else()
target_sources(${LIB_FSFW_NAME} PRIVATE
LinuxLibgpioIF.cpp
)
target_link_libraries(${LIB_FSFW_NAME} PRIVATE
${LIB_GPIO}
)
endif()
target_link_libraries(${LIB_FSFW_NAME} PRIVATE
${LIB_GPIO}
)

View File

@ -1,8 +1,10 @@
#include "fsfw/FSFW.h"
#include "fsfw_hal/linux/i2c/I2cComIF.h"
#include "fsfw_hal/linux/utility.h"
#include "fsfw_hal/linux/UnixFileGuard.h"
#include "fsfw/serviceinterface/ServiceInterface.h"
#include "fsfw/serviceinterface.h"
#include <unistd.h>
#include <fcntl.h>
@ -24,12 +26,16 @@ ReturnValue_t I2cComIF::initializeInterface(CookieIF* cookie) {
std::string deviceFile;
if(cookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::initializeInterface: Invalid cookie!" << std::endl;
#endif
return NULLPOINTER;
}
I2cCookie* i2cCookie = dynamic_cast<I2cCookie*>(cookie);
if(i2cCookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::initializeInterface: Invalid I2C cookie!" << std::endl;
#endif
return NULLPOINTER;
}
@ -41,15 +47,19 @@ ReturnValue_t I2cComIF::initializeInterface(CookieIF* cookie) {
I2cInstance i2cInstance = {std::vector<uint8_t>(maxReplyLen), 0};
auto statusPair = i2cDeviceMap.emplace(i2cAddress, i2cInstance);
if (not statusPair.second) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::initializeInterface: Failed to insert device with address " <<
i2cAddress << "to I2C device " << "map" << std::endl;
#endif
return HasReturnvaluesIF::RETURN_FAILED;
}
return HasReturnvaluesIF::RETURN_OK;
}
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::initializeInterface: Device with address " << i2cAddress <<
"already in use" << std::endl;
#endif
return HasReturnvaluesIF::RETURN_FAILED;
}
@ -61,8 +71,10 @@ ReturnValue_t I2cComIF::sendMessage(CookieIF *cookie,
std::string deviceFile;
if(sendData == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::sendMessage: Send Data is nullptr"
<< std::endl;
#endif
return HasReturnvaluesIF::RETURN_FAILED;
}
@ -72,15 +84,19 @@ ReturnValue_t I2cComIF::sendMessage(CookieIF *cookie,
I2cCookie* i2cCookie = dynamic_cast<I2cCookie*>(cookie);
if(i2cCookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::sendMessage: Invalid I2C Cookie!" << std::endl;
#endif
return NULLPOINTER;
}
address_t i2cAddress = i2cCookie->getAddress();
i2cDeviceMapIter = i2cDeviceMap.find(i2cAddress);
if (i2cDeviceMapIter == i2cDeviceMap.end()) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::sendMessage: i2cAddress of Cookie not "
<< "registered in i2cDeviceMap" << std::endl;
#endif
return HasReturnvaluesIF::RETURN_FAILED;
}
@ -94,10 +110,12 @@ ReturnValue_t I2cComIF::sendMessage(CookieIF *cookie,
return result;
}
if (write(fd, sendData, sendLen) != (int)sendLen) {
if (write(fd, sendData, sendLen) != static_cast<int>(sendLen)) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::sendMessage: Failed to send data to I2C "
"device with error code " << errno << ". Error description: "
<< strerror(errno) << std::endl;
#endif
return HasReturnvaluesIF::RETURN_FAILED;
}
return HasReturnvaluesIF::RETURN_OK;
@ -119,7 +137,9 @@ ReturnValue_t I2cComIF::requestReceiveMessage(CookieIF *cookie,
I2cCookie* i2cCookie = dynamic_cast<I2cCookie*>(cookie);
if(i2cCookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::requestReceiveMessage: Invalid I2C Cookie!" << std::endl;
#endif
i2cDeviceMapIter->second.replyLen = 0;
return NULLPOINTER;
}
@ -127,8 +147,10 @@ ReturnValue_t I2cComIF::requestReceiveMessage(CookieIF *cookie,
address_t i2cAddress = i2cCookie->getAddress();
i2cDeviceMapIter = i2cDeviceMap.find(i2cAddress);
if (i2cDeviceMapIter == i2cDeviceMap.end()) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::requestReceiveMessage: i2cAddress of Cookie not "
<< "registered in i2cDeviceMap" << std::endl;
#endif
i2cDeviceMapIter->second.replyLen = 0;
return HasReturnvaluesIF::RETURN_FAILED;
}
@ -156,7 +178,9 @@ ReturnValue_t I2cComIF::requestReceiveMessage(CookieIF *cookie,
<< requestLen << " bytes" << std::endl;
#endif
i2cDeviceMapIter->second.replyLen = 0;
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::debug << "I2cComIF::requestReceiveMessage: Read " << readLen << " of " << requestLen << " bytes" << std::endl;
#endif
return HasReturnvaluesIF::RETURN_FAILED;
}
@ -168,15 +192,19 @@ ReturnValue_t I2cComIF::readReceivedMessage(CookieIF *cookie,
uint8_t **buffer, size_t* size) {
I2cCookie* i2cCookie = dynamic_cast<I2cCookie*>(cookie);
if(i2cCookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::readReceivedMessage: Invalid I2C Cookie!" << std::endl;
#endif
return NULLPOINTER;
}
address_t i2cAddress = i2cCookie->getAddress();
i2cDeviceMapIter = i2cDeviceMap.find(i2cAddress);
if (i2cDeviceMapIter == i2cDeviceMap.end()) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "I2cComIF::readReceivedMessage: i2cAddress of Cookie not "
<< "found in i2cDeviceMap" << std::endl;
#endif
return HasReturnvaluesIF::RETURN_FAILED;
}
*buffer = i2cDeviceMapIter->second.replyBuffer.data();

View File

@ -1,8 +1,8 @@
#include "UartComIF.h"
#include "OBSWConfig.h"
#include "fsfw/FSFW.h"
#include "fsfw_hal/linux/utility.h"
#include "fsfw/serviceinterface/ServiceInterface.h"
#include "fsfw/serviceinterface.h"
#include <cstring>
#include <fcntl.h>
@ -26,7 +26,9 @@ ReturnValue_t UartComIF::initializeInterface(CookieIF* cookie) {
UartCookie* uartCookie = dynamic_cast<UartCookie*>(cookie);
if (uartCookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "UartComIF::initializeInterface: Invalid UART Cookie!" << std::endl;
#endif
return NULLPOINTER;
}
@ -42,14 +44,18 @@ ReturnValue_t UartComIF::initializeInterface(CookieIF* cookie) {
UartElements uartElements = {fileDescriptor, std::vector<uint8_t>(maxReplyLen), 0};
auto status = uartDeviceMap.emplace(deviceFile, uartElements);
if (status.second == false) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::initializeInterface: Failed to insert device " <<
deviceFile << "to UART device map" << std::endl;
#endif
return RETURN_FAILED;
}
}
else {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::initializeInterface: UART device " << deviceFile <<
" already in use" << std::endl;
#endif
return RETURN_FAILED;
}
@ -70,15 +76,19 @@ int UartComIF::configureUartPort(UartCookie* uartCookie) {
int fd = open(deviceFile.c_str(), flags);
if (fd < 0) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::configureUartPort: Failed to open uart " << deviceFile <<
"with error code " << errno << strerror(errno) << std::endl;
#endif
return fd;
}
/* Read in existing settings */
if(tcgetattr(fd, &options) != 0) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::configureUartPort: Error " << errno << "from tcgetattr: "
<< strerror(errno) << std::endl;
#endif
return fd;
}
@ -99,8 +109,10 @@ int UartComIF::configureUartPort(UartCookie* uartCookie) {
/* Save option settings */
if (tcsetattr(fd, TCSANOW, &options) != 0) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::configureUartPort: Failed to set options with error " <<
errno << ": " << strerror(errno);
#endif
return fd;
}
return fd;
@ -152,7 +164,9 @@ void UartComIF::setDatasizeOptions(struct termios* options, UartCookie* uartCook
options->c_cflag |= CS8;
break;
default:
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::setDatasizeOptions: Invalid size specified" << std::endl;
#endif
break;
}
}
@ -259,7 +273,9 @@ void UartComIF::configureBaudrate(struct termios* options, UartCookie* uartCooki
cfsetospeed(options, B460800);
break;
default:
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::configureBaudrate: Baudrate not supported" << std::endl;
#endif
break;
}
}
@ -275,29 +291,37 @@ ReturnValue_t UartComIF::sendMessage(CookieIF *cookie,
}
if(sendData == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::sendMessage: Send data is nullptr" << std::endl;
#endif
return RETURN_FAILED;
}
UartCookie* uartCookie = dynamic_cast<UartCookie*>(cookie);
if(uartCookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::sendMessasge: Invalid UART Cookie!" << std::endl;
#endif
return NULLPOINTER;
}
deviceFile = uartCookie->getDeviceFile();
uartDeviceMapIter = uartDeviceMap.find(deviceFile);
if (uartDeviceMapIter == uartDeviceMap.end()) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::debug << "UartComIF::sendMessage: Device file " << deviceFile <<
"not in UART map" << std::endl;
#endif
return RETURN_FAILED;
}
fd = uartDeviceMapIter->second.fileDescriptor;
if (write(fd, sendData, sendLen) != (int)sendLen) {
if (write(fd, sendData, sendLen) != static_cast<int>(sendLen)) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "UartComIF::sendMessage: Failed to send data with error code " <<
errno << ": Error description: " << strerror(errno) << std::endl;
#endif
return RETURN_FAILED;
}
@ -314,7 +338,9 @@ ReturnValue_t UartComIF::requestReceiveMessage(CookieIF *cookie, size_t requestL
UartCookie* uartCookie = dynamic_cast<UartCookie*>(cookie);
if(uartCookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::debug << "UartComIF::requestReceiveMessage: Invalid Uart Cookie!" << std::endl;
#endif
return NULLPOINTER;
}
@ -327,8 +353,10 @@ ReturnValue_t UartComIF::requestReceiveMessage(CookieIF *cookie, size_t requestL
}
if (uartDeviceMapIter == uartDeviceMap.end()) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::debug << "UartComIF::requestReceiveMessage: Device file " << deviceFile
<< " not in uart map" << std::endl;
#endif
return RETURN_FAILED;
}
@ -425,8 +453,10 @@ ReturnValue_t UartComIF::handleNoncanonicalRead(UartCookie &uartCookie, UartDevi
}
else if (bytesRead != static_cast<int>(requestLen)) {
if(uartCookie.isReplySizeFixed()) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::requestReceiveMessage: Only read " << bytesRead <<
" of " << requestLen << " bytes" << std::endl;
#endif
return RETURN_FAILED;
}
}
@ -442,15 +472,19 @@ ReturnValue_t UartComIF::readReceivedMessage(CookieIF *cookie,
UartCookie* uartCookie = dynamic_cast<UartCookie*>(cookie);
if(uartCookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::debug << "UartComIF::readReceivedMessage: Invalid uart cookie!" << std::endl;
#endif
return NULLPOINTER;
}
deviceFile = uartCookie->getDeviceFile();
uartDeviceMapIter = uartDeviceMap.find(deviceFile);
if (uartDeviceMapIter == uartDeviceMap.end()) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::debug << "UartComIF::readReceivedMessage: Device file " << deviceFile <<
" not in uart map" << std::endl;
#endif
return RETURN_FAILED;
}
@ -468,7 +502,9 @@ ReturnValue_t UartComIF::flushUartRxBuffer(CookieIF *cookie) {
UartDeviceMapIter uartDeviceMapIter;
UartCookie* uartCookie = dynamic_cast<UartCookie*>(cookie);
if(uartCookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::flushUartRxBuffer: Invalid uart cookie!" << std::endl;
#endif
return NULLPOINTER;
}
deviceFile = uartCookie->getDeviceFile();
@ -486,7 +522,9 @@ ReturnValue_t UartComIF::flushUartTxBuffer(CookieIF *cookie) {
UartDeviceMapIter uartDeviceMapIter;
UartCookie* uartCookie = dynamic_cast<UartCookie*>(cookie);
if(uartCookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::flushUartTxBuffer: Invalid uart cookie!" << std::endl;
#endif
return NULLPOINTER;
}
deviceFile = uartCookie->getDeviceFile();
@ -504,7 +542,9 @@ ReturnValue_t UartComIF::flushUartTxAndRxBuf(CookieIF *cookie) {
UartDeviceMapIter uartDeviceMapIter;
UartCookie* uartCookie = dynamic_cast<UartCookie*>(cookie);
if(uartCookie == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "UartComIF::flushUartTxAndRxBuf: Invalid uart cookie!" << std::endl;
#endif
return NULLPOINTER;
}
deviceFile = uartCookie->getDeviceFile();

View File

@ -1,6 +1,6 @@
#include "fsfw_hal/linux/uart/UartCookie.h"
#include <fsfw/serviceinterface/ServiceInterface.h>
#include <fsfw/serviceinterface.h>
UartCookie::UartCookie(object_id_t handlerId, std::string deviceFile, UartModes uartMode,
uint32_t baudrate, size_t maxReplyLen):
@ -42,7 +42,9 @@ void UartCookie::setBitsPerWord(uint8_t bitsPerWord_) {
case 8:
break;
default:
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::debug << "UartCookie::setBitsPerWord: Invalid bits per word specified" << std::endl;
#endif
return;
}
bitsPerWord = bitsPerWord_;

View File

@ -9,36 +9,44 @@ import webbrowser
import shutil
import sys
import time
from shutil import which
from typing import List
UNITTEST_FOLDER_NAME = 'build-tests'
DOCS_FOLDER_NAME = 'build-docs'
UNITTEST_FOLDER_NAME = "build-tests"
DOCS_FOLDER_NAME = "build-docs"
def main():
parser = argparse.ArgumentParser(description="FSFW helper script")
choices = ('docs', 'tests')
choices = ("docs", "tests")
parser.add_argument(
'type', metavar='type', choices=choices,
help=f'Target type. Choices: {choices}'
"type", metavar="type", choices=choices, help=f"Target type. Choices: {choices}"
)
parser.add_argument(
'-a', '--all', action='store_true',
help='Create, build and open specified type'
"-a", "--all", action="store_true", help="Create, build and open specified type"
)
parser.add_argument(
'-c', '--create', action='store_true',
help='Create docs or test build configuration'
"-c",
"--create",
action="store_true",
help="Create docs or test build configuration",
)
parser.add_argument(
'-b', '--build', action='store_true',
help='Build the specified type'
"-b", "--build", action="store_true", help="Build the specified type"
)
parser.add_argument(
'-o', '--open', action='store_true',
help='Open test or documentation data in webbrowser'
"-o",
"--open",
action="store_true",
help="Open test or documentation data in webbrowser",
)
parser.add_argument(
"-v",
"--valgrind",
action="store_true",
help="Run valgrind on generated test binary",
)
args = parser.parse_args()
@ -46,26 +54,26 @@ def main():
args.build = True
args.create = True
args.open = True
elif not args.build and not args.create and not args.open:
elif not args.build and not args.create and not args.open and not args.valgrind:
print(
'Please select at least one operation to perform. '
'Use helper.py -h for more information'
"Please select at least one operation to perform. "
"Use helper.py -h for more information"
)
sys.exit(1)
# This script can be called from root and from script folder
if not os.path.isfile('README.md'):
os.chdir('..')
if not os.path.isfile("README.md"):
os.chdir("..")
build_dir_list = []
if not args.create:
build_dir_list = build_build_dir_list()
if args.type == 'tests':
if args.type == "tests":
handle_tests_type(args, build_dir_list)
elif args.type == 'docs':
elif args.type == "docs":
handle_docs_type(args, build_dir_list)
else:
print('Invalid or unknown type')
print("Invalid or unknown type")
sys.exit(1)
@ -76,7 +84,9 @@ def handle_docs_type(args, build_dir_list: list):
create_docs_build_cfg()
build_directory = DOCS_FOLDER_NAME
elif len(build_dir_list) == 0:
print('No valid CMake docs build directory found. Trying to set up docs build system')
print(
"No valid CMake docs build directory found. Trying to set up docs build system"
)
shutil.rmtree(DOCS_FOLDER_NAME)
create_docs_build_cfg()
build_directory = DOCS_FOLDER_NAME
@ -87,18 +97,18 @@ def handle_docs_type(args, build_dir_list: list):
build_directory = determine_build_dir(build_dir_list)
os.chdir(build_directory)
if args.build:
os.system('cmake --build . -j')
os.system("cmake --build . -j")
if args.open:
if not os.path.isfile('docs/sphinx/index.html'):
if not os.path.isfile("docs/sphinx/index.html"):
# try again..
os.system('cmake --build . -j')
if not os.path.isfile('docs/sphinx/index.html'):
os.system("cmake --build . -j")
if not os.path.isfile("docs/sphinx/index.html"):
print(
"No Sphinx documentation file detected. "
"Try to build it first with the -b argument"
)
sys.exit(1)
webbrowser.open('docs/sphinx/index.html')
webbrowser.open("docs/sphinx/index.html")
def handle_tests_type(args, build_dir_list: list):
@ -109,8 +119,8 @@ def handle_tests_type(args, build_dir_list: list):
build_directory = UNITTEST_FOLDER_NAME
elif len(build_dir_list) == 0:
print(
'No valid CMake tests build directory found. '
'Trying to set up test build system'
"No valid CMake tests build directory found. "
"Trying to set up test build system"
)
create_tests_build_cfg()
build_directory = UNITTEST_FOLDER_NAME
@ -123,24 +133,33 @@ def handle_tests_type(args, build_dir_list: list):
if args.build:
perform_lcov_operation(build_directory, False)
if args.open:
if not os.path.isdir('fsfw-tests_coverage'):
print("No Unittest folder detected. Try to build them first with the -b argument")
if not os.path.isdir("fsfw-tests_coverage"):
print(
"No Unittest folder detected. Try to build them first with the -b argument"
)
sys.exit(1)
webbrowser.open('fsfw-tests_coverage/index.html')
webbrowser.open("fsfw-tests_coverage/index.html")
if args.valgrind:
if which("valgrind") is None:
print("Please install valgrind first")
sys.exit(1)
os.chdir(UNITTEST_FOLDER_NAME)
os.system("valgrind --leak-check=full ./fsfw-tests")
os.chdir("..")
def create_tests_build_cfg():
os.mkdir(UNITTEST_FOLDER_NAME)
os.chdir(UNITTEST_FOLDER_NAME)
os.system('cmake -DFSFW_OSAL=host -DFSFW_BUILD_UNITTESTS=ON ..')
os.chdir('..')
os.system("cmake -DFSFW_OSAL=host -DFSFW_BUILD_UNITTESTS=ON ..")
os.chdir("..")
def create_docs_build_cfg():
os.mkdir(DOCS_FOLDER_NAME)
os.chdir(DOCS_FOLDER_NAME)
os.system('cmake -DFSFW_OSAL=host -DFSFW_BUILD_DOCS=ON ..')
os.chdir('..')
os.system("cmake -DFSFW_OSAL=host -DFSFW_BUILD_DOCS=ON ..")
os.chdir("..")
def build_build_dir_list() -> list:

View File

@ -18,6 +18,10 @@
// FSFW core defines
#ifndef FSFW_TCP_RECV_WIRETAPPING_ENABLED
#define FSFW_TCP_RECV_WIRETAPPING_ENABLED 0
#endif
#ifndef FSFW_CPP_OSTREAM_ENABLED
#define FSFW_CPP_OSTREAM_ENABLED 1
#endif /* FSFW_CPP_OSTREAM_ENABLED */

View File

@ -5,8 +5,7 @@
#include <cstddef>
/**
* @brief Circular buffer implementation, useful for buffering
* into data streams.
* @brief Circular buffer implementation, useful for buffering into data streams.
* @details
* Note that the deleteData() has to be called to increment the read pointer.
* This class allocated dynamically, so
@ -20,8 +19,8 @@ public:
* @param size
* @param overwriteOld If the ring buffer is overflowing at a write
* operation, the oldest data will be overwritten.
* @param maxExcessBytes These additional bytes will be allocated in addtion
* to the specified size to accomodate contiguous write operations
* @param maxExcessBytes These additional bytes will be allocated in addition
* to the specified size to accommodate continuous write operations
* with getFreeElement.
*
*/
@ -32,10 +31,10 @@ public:
* @param buffer
* @param size
* @param overwriteOld
* If the ring buffer is overflowing at a write operartion, the oldest data
* If the ring buffer is overflowing at a write operation, the oldest data
* will be overwritten.
* @param maxExcessBytes
* If the buffer can accomodate additional bytes for contigous write
* If the buffer can accommodate additional bytes for contiguous write
* operations with getFreeElement, this is the maximum allowed additional
* size
*/
@ -48,7 +47,7 @@ public:
* Write to circular buffer and increment write pointer by amount.
* @param data
* @param amount
* @return -@c RETURN_OK if write operation was successfull
* @return -@c RETURN_OK if write operation was successful
* -@c RETURN_FAILED if
*/
ReturnValue_t writeData(const uint8_t* data, size_t amount);
@ -108,7 +107,7 @@ public:
* Delete data by incrementing read pointer.
* @param amount
* @param deleteRemaining
* If the amount specified is larger than the remaing size to read and this
* If the amount specified is larger than the remaining size to read and this
* is set to true, the remaining amount will be deleted as well
* @param trueAmount [out]
* If deleteRemaining was set to true, the amount deleted will be assigned

View File

@ -7,7 +7,7 @@
/**
* @brief Base class to implement reconfiguration and failure handling for
* redundant devices by monitoring their modes health states.
* redundant devices by monitoring their modes and health states.
* @details
* Documentation: Dissertation Baetz p.156, 157.
*

View File

@ -85,9 +85,10 @@ public:
* Called by DHB in the GET_WRITE doGetWrite().
* Get send confirmation that the data in sendMessage() was sent successfully.
* @param cookie
* @return - @c RETURN_OK if data was sent successfull
* - Everything else triggers falure event with
* returnvalue as parameter 1
* @return
* - @c RETURN_OK if data was sent successfully but a reply is expected
* - NO_REPLY_EXPECTED if data was sent successfully and no reply is expected
* - Everything else to indicate failure
*/
virtual ReturnValue_t getSendSuccess(CookieIF *cookie) = 0;

View File

@ -334,8 +334,7 @@ protected:
* - @c RETURN_OK to send command after #rawPacket and #rawPacketLen
* have been set.
* - @c HasActionsIF::EXECUTION_COMPLETE to generate a finish reply immediately. This can
* be used if no reply is expected. Otherwise, the developer can call #actionHelper.finish
* to finish the command handling.
* be used if no reply is expected
* - Anything else triggers an event with the return code as a parameter as well as a
* step reply failed with the return code
*/

View File

@ -120,7 +120,8 @@ public:
static const ReturnValue_t WRONG_MODE_FOR_COMMAND = MAKE_RETURN_CODE(0xA5);
static const ReturnValue_t TIMEOUT = MAKE_RETURN_CODE(0xA6);
static const ReturnValue_t BUSY = MAKE_RETURN_CODE(0xA7);
static const ReturnValue_t NO_REPLY_EXPECTED = MAKE_RETURN_CODE(0xA8); //!< Used to indicate that this is a command-only command.
//!< Used to indicate that this is a command-only command.
static const ReturnValue_t NO_REPLY_EXPECTED = MAKE_RETURN_CODE(0xA8);
static const ReturnValue_t NON_OP_TEMPERATURE = MAKE_RETURN_CODE(0xA9);
static const ReturnValue_t COMMAND_NOT_IMPLEMENTED = MAKE_RETURN_CODE(0xAA);

View File

@ -1,20 +1,19 @@
target_sources(${LIB_FSFW_NAME}
PRIVATE
Clock.cpp
BinarySemaphore.cpp
CountingSemaphore.cpp
FixedTimeslotTask.cpp
InternalErrorCodes.cpp
MessageQueue.cpp
Mutex.cpp
MutexFactory.cpp
PeriodicPosixTask.cpp
PosixThread.cpp
QueueFactory.cpp
SemaphoreFactory.cpp
TaskFactory.cpp
tcpipHelpers.cpp
unixUtility.cpp
target_sources(${LIB_FSFW_NAME} PRIVATE
Clock.cpp
BinarySemaphore.cpp
CountingSemaphore.cpp
FixedTimeslotTask.cpp
InternalErrorCodes.cpp
MessageQueue.cpp
Mutex.cpp
MutexFactory.cpp
PeriodicPosixTask.cpp
PosixThread.cpp
QueueFactory.cpp
SemaphoreFactory.cpp
TaskFactory.cpp
tcpipHelpers.cpp
unixUtility.cpp
)
find_package(Threads REQUIRED)

View File

@ -285,10 +285,10 @@ ReturnValue_t MessageQueue::sendMessageFromMessageQueue(MessageQueueId_t sendTo,
utility::printUnixErrorGeneric(CLASS_NAME, "sendMessageFromMessageQueue", "EBADF");
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "mq_send to: " << sendTo << " sent from "
<< sentFrom << "failed" << std::endl;
sif::warning << "mq_send to " << sendTo << " sent from "
<< sentFrom << " failed" << std::endl;
#else
sif::printWarning("mq_send to: %d sent from %d failed\n", sendTo, sentFrom);
sif::printWarning("mq_send to %d sent from %d failed\n", sendTo, sentFrom);
#endif
return DESTINATION_INVALID;
}

View File

@ -62,7 +62,8 @@ protected:
struct ChildInfo {
MessageQueueId_t commandQueue;
Mode_t mode;
Submode_t submode;bool healthChanged;
Submode_t submode;
bool healthChanged;
};
Mode_t mode;

View File

@ -21,3 +21,4 @@ add_subdirectory(storagemanager)
add_subdirectory(globalfunctions)
add_subdirectory(timemanager)
add_subdirectory(tmtcpacket)
add_subdirectory(hal)

View File

@ -0,0 +1,3 @@
target_sources(${FSFW_TEST_TGT} PRIVATE
testCommandExecutor.cpp
)

View File

@ -0,0 +1,112 @@
#include <catch2/catch_test_macros.hpp>
#include "fsfw/serviceinterface.h"
#include "fsfw/container/SimpleRingBuffer.h"
#include "fsfw/container/DynamicFIFO.h"
#include "fsfw_hal/linux/CommandExecutor.h"
#include "fsfw/platform.h"
#include <unistd.h>
#include <fstream>
#ifdef PLATFORM_UNIX
static const char TEST_FILE_NAME[] = "/tmp/fsfw-unittest-test.txt";
TEST_CASE( "Command Executor" , "[cmd-exec]") {
// Check blocking mode first
CommandExecutor cmdExecutor(1024);
std::string cmd = "echo \"test\" >> " + std::string(TEST_FILE_NAME);
REQUIRE(cmdExecutor.getCurrentState() == CommandExecutor::States::IDLE);
ReturnValue_t result = cmdExecutor.load(cmd, true, true);
REQUIRE(cmdExecutor.getCurrentState() == CommandExecutor::States::COMMAND_LOADED);
REQUIRE(result == HasReturnvaluesIF::RETURN_OK);
REQUIRE(cmdExecutor.execute() == HasReturnvaluesIF::RETURN_OK);
// Check that file exists with contents
std::ifstream file(TEST_FILE_NAME);
std::string line;
std::getline(file, line);
CHECK(line == "test");
std::remove(TEST_FILE_NAME);
REQUIRE(cmdExecutor.getCurrentState() == CommandExecutor::States::IDLE);
// Now check non-blocking mode
SimpleRingBuffer outputBuffer(524, true);
DynamicFIFO<uint16_t> sizesFifo(12);
cmdExecutor.setRingBuffer(&outputBuffer, &sizesFifo);
result = cmdExecutor.load("echo \"Hello World\"", false, false);
REQUIRE(result == HasReturnvaluesIF::RETURN_OK);
cmdExecutor.execute();
bool bytesHaveBeenRead = false;
size_t limitIdx = 0;
while(result != CommandExecutor::EXECUTION_FINISHED) {
limitIdx++;
result = cmdExecutor.check(bytesHaveBeenRead);
REQUIRE(result != CommandExecutor::COMMAND_ERROR);
usleep(500);
REQUIRE(limitIdx < 5);
}
limitIdx = 0;
CHECK(bytesHaveBeenRead == true);
CHECK(result == CommandExecutor::EXECUTION_FINISHED);
uint16_t readBytes = 0;
sizesFifo.retrieve(&readBytes);
REQUIRE(readBytes == 12);
REQUIRE(outputBuffer.getAvailableReadData() == 12);
uint8_t readBuffer[32] = {};
REQUIRE