diff --git a/CMakeLists.txt b/CMakeLists.txt index b7dbea87..5f56a4fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,8 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_SCRIPT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") if(TGT_BSP MATCHES "arm/q7s") - option(Q7S_SIMPLE_MODE OFF "Simple mode with a minimal main function") + option(BUILD_WATCHDOG "Compile the OBSW watchdog insted" OFF) + option(BUILD_Q7S_SIMPLE_MODE OFF "Simple mode with a minimal main function") endif() option(ADD_ETL_LIB "Add ETL library" ON) @@ -32,8 +33,13 @@ endif() include(${CMAKE_SCRIPT_PATH}/PreProjectConfig.cmake) pre_project_config() +set(PROJECT_NAME_TO_SET eive-obsw-$ENV{USERNAME}) +if(BUILD_WATCHDOG) + set(PROJECT_NAME_TO_SET eive-watchdog) +endif() + # Project Name -project(eive_obsw ASM C CXX) +project(${PROJECT_NAME_TO_SET} ASM C CXX) ################################################################################ # Pre-Sources preparation @@ -60,6 +66,7 @@ set(MISSION_PATH mission) set(TEST_PATH test/testtasks) set(LINUX_PATH linux) set(COMMON_PATH common) +set(WATCHDOG_PATH watchdog) set(COMMON_CONFIG_PATH ${COMMON_PATH}/config) set(FSFW_HAL_LIB_PATH fsfw_hal) @@ -82,24 +89,27 @@ if(TGT_BSP) OR TGT_BSP MATCHES "arm/beagleboneblack" ) set(FSFW_CONFIG_PATH "linux/fsfwconfig") - if(NOT Q7S_SIMPLE_MODE) + if(NOT BUILD_Q7S_SIMPLE_MODE) set(ADD_LINUX_FILES TRUE) set(ADD_CSP_LIB TRUE) set(FSFW_HAL_ADD_LINUX ON) endif() endif() - if(${TGT_BSP} MATCHES "arm/raspberrypi") - add_definitions(-DRASPBERRY_PI) + if(${TGT_BSP} MATCHES "arm/raspberrypi") + # Used by configure file + set(RASPBERRY_PI ON) set(FSFW_HAL_ADD_RASPBERRY_PI ON) endif() - if(${TGT_BSP} MATCHES "arm/beagleboneblack") - add_definitions(-DBEAGLEBONEBLACK) + if(${TGT_BSP} MATCHES "arm/beagleboneblack") + # Used by configure file + set(BEAGLEBONEBLACK ON) endif() if(${TGT_BSP} MATCHES "arm/q7s") - add_definitions(-DXIPHOS_Q7S) + # Used by configure file + set(XIPHOS_Q7S ON) endif() else() # Required by FSFW library @@ -107,14 +117,17 @@ else() endif() # Configuration files -configure_file(${COMMON_CONFIG_PATH}/commonConfig.h.in commonConfig.h) -configure_file(${FSFW_CONFIG_PATH}/FSFWConfig.h.in FSFWConfig.h) -configure_file(${FSFW_CONFIG_PATH}/OBSWConfig.h.in OBSWConfig.h) -if(${TGT_BSP} MATCHES "arm/q7s") - configure_file(${BSP_PATH}/boardconfig/q7sConfig.h.in q7sConfig.h) -elseif(${TGT_BSP} MATCHES "arm/raspberrypi") - configure_file(${BSP_PATH}/boardconfig/rpiConfig.h.in rpiConfig.h) +if(NOT BUILD_WATCHDOG) + configure_file(${COMMON_CONFIG_PATH}/commonConfig.h.in commonConfig.h) + configure_file(${FSFW_CONFIG_PATH}/FSFWConfig.h.in FSFWConfig.h) + configure_file(${FSFW_CONFIG_PATH}/OBSWConfig.h.in OBSWConfig.h) + if(${TGT_BSP} MATCHES "arm/q7s") + configure_file(${BSP_PATH}/boardconfig/q7sConfig.h.in q7sConfig.h) + elseif(${TGT_BSP} MATCHES "arm/raspberrypi") + configure_file(${BSP_PATH}/boardconfig/rpiConfig.h.in rpiConfig.h) + endif() endif() +configure_file(${WATCHDOG_PATH}/watchdogConf.h.in watchdogConf.h) # Set common config path for FSFW set(FSFW_ADDITIONAL_INC_PATHS @@ -131,26 +144,28 @@ set(LWGPS_CONFIG_PATH "${COMMON_PATH}/config") # Add executable add_executable(${TARGET_NAME}) -if(ADD_CSP_LIB) - add_subdirectory(${CSP_LIB_PATH}) -endif() - if(ADD_ETL_LIB) add_subdirectory(${ETL_LIB_PATH}) endif() -if(ADD_LINUX_FILES) - add_subdirectory(${LINUX_PATH}) -endif() - if(ADD_JSON_LIB) add_subdirectory(${LIB_JSON_PATH}) endif() -add_subdirectory(${BSP_PATH}) -add_subdirectory(${COMMON_PATH}) -if(NOT Q7S_SIMPLE_MODE) + +if(NOT BUILD_WATCHDOG) + if(ADD_LINUX_FILES) + add_subdirectory(${LINUX_PATH}) + endif() + add_subdirectory(${BSP_PATH}) + add_subdirectory(${COMMON_PATH}) + if(ADD_CSP_LIB) + add_subdirectory(${CSP_LIB_PATH}) + endif() +endif() + +if((NOT BUILD_Q7S_SIMPLE_MODE) AND (NOT BUILD_WATCHDOG)) add_subdirectory(${LWGPS_LIB_PATH}) add_subdirectory(${FSFW_PATH}) add_subdirectory(${MISSION_PATH}) @@ -158,6 +173,9 @@ if(NOT Q7S_SIMPLE_MODE) add_subdirectory(${ARCSEC_LIB_PATH}) endif() +if(BUILD_WATCHDOG) + add_subdirectory(${WATCHDOG_PATH}) +endif() ################################################################################ # Post-Sources preparation @@ -165,35 +183,42 @@ endif() set_property(CACHE FSFW_OSAL PROPERTY STRINGS host linux) -if(NOT Q7S_SIMPLE_MODE) +if((NOT BUILD_Q7S_SIMPLE_MODE) AND (NOT BUILD_WATCHDOG)) # Add libraries for all sources. target_link_libraries(${TARGET_NAME} PRIVATE ${LIB_FSFW_NAME} ${LIB_OS_NAME} ${LIB_LWGPS_NAME} ${LIB_ARCSEC} - ${LIB_CXX_FS} ) endif() +if(NOT BUILD_WATCHDOG) + if(ADD_CSP_LIB) + target_link_libraries(${TARGET_NAME} PRIVATE + ${LIB_CSP_NAME} + ) + endif() +endif() + if(ADD_ETL_LIB) target_link_libraries(${TARGET_NAME} PRIVATE ${LIB_ETL_NAME} ) endif() -if(ADD_CSP_LIB) - target_link_libraries(${TARGET_NAME} PRIVATE - ${LIB_CSP_NAME} - ) -endif() - if(ADD_JSON_LIB) target_link_libraries(${TARGET_NAME} PRIVATE ${LIB_JSON_NAME} ) endif() + + +target_link_libraries(${TARGET_NAME} PRIVATE + ${LIB_CXX_FS} +) + # Add include paths for all sources. target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} @@ -249,10 +274,14 @@ if(NOT CMAKE_SIZE) endif() endif() -if(TGT_BSP) - set(TARGET_STRING "Target BSP: ${TGT_BSP}") +if(BUILD_WATCHDOG) + set(TARGET_STRING "OBSW Watchdog") else() - set(TARGET_STRING "Target BSP: Hosted") + if(TGT_BSP) + set(TARGET_STRING "Target BSP: ${TGT_BSP}") + else() + set(TARGET_STRING "Target BSP: Hosted") + endif() endif() string(CONCAT POST_BUILD_COMMENT diff --git a/README.md b/README.md index fc218749..fc4163e1 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,29 @@ When using Windows, run theses steps in MSYS2. # Useful and Common Commands (Host) +## Build generation + +Replace `Debug` with `Release` for release build. Add `-G "MinGW Makefiles` or `-G "Ninja"` +on Windows or when `ninja` should be used. You can build with `cmake --build . -j` after +build generation. You can finds scripts in `cmake/scripts` to perform the build commands +automatically. + +### Q7S OBSW + +```sh +mkdir build-Debug-Q7S && cd build-Debug-Q7S +cmake -DTGT_BSP=arm/q7s -DFSFW_OSAL=linux -DCMAKE_BUILD_TYPE=Debug .. +cmake --build . -j +``` + +### Q7S Watchdog + +```sh +mkdir build-Debug-Q7S && cd build-Debug-Q7S +cmake -DTGT_BSP=arm/q7s -DFSFW_OSAL=linux -DBUILD_WATCHDOG=ON -DCMAKE_BUILD_TYPE=Debug .. +cmake --build . -j +``` + ## Connect to EIVE flatsat ### DNS @@ -187,6 +210,18 @@ tmux new -s q7s-serial q7s_serial ``` +Other useful tmux commands: +- Enable scroll mode: You can press `ctrl + b` and then `[` (`AltGr + 8`) to enable scroll mode. + You can quit scroll mode with `q`. +- Kill a tmux session: run `ctrl + b` and then `k`. +- Detach from a tmux session: run `ctrl + b` and then `d` +- [Pipe last 3000 lines](https://unix.stackexchange.com/questions/26548/write-all-tmux-scrollback-to-a-file) + into file for copying or analysis: + 1. `ctrl + b` and `:` + 2. `capture-pane -S -3000` + `enter` + 3. `save-buffer /tmp/tmux-output.txt` + `enter` + + ### SSH console You can use the following command to connect to the Q7S with `ssh`: @@ -312,7 +347,7 @@ wget https://eive-cloud.irs.uni-stuttgart.de/index.php/s/rfoaistRd67yBbH/downloa or the following command for Linux (could be useful for CI/CD) ```sh -wget https://eive-cloud.irs.uni-stuttgart.de/index.php/s/2Fp2ag6NGnbtAsK/download/gcc-arm-linux-gnueabi.tar.gz +wget https://eive-cloud.irs.uni-stuttgart.de/index.php/s/MRaeA2XnMXpZ5Pp/download/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf.tar.xz ``` ## Installing CMake and MSYS2 on Windows @@ -581,10 +616,14 @@ pscp -scp -P 22 eive@192.168.199.227:/example-file More detailed information about the used q7s commands can be found in the Q7S user manual. -# Q7S +# Q7S OBC ## Launching an application at start-up +You can also do the steps performed here on a host computer inside the `q7s-rootfs` directory +of the [Q7S base repository](https://egit.irs.uni-stuttgart.de/eive/q7s-base). This might +be more convenient while also allowing to update all images at once with the finished `rootfs.xdi`. + Load the root partiton from the flash memory (there are to nor-flash memories and each flash holds two xdi images). Note: It is not possible to modify the currently loaded root partition, e.g. creating directories. To do this, the parition needs to be mounted. @@ -610,7 +649,7 @@ creating directories. To do this, the parition needs to be mounted. chmod +x application ``` -5. Create systemd service in /lib/systemd/system. The following shows an example service. +5. Create systemd service in `/etc/systemd/system`. The following shows an example service. ```sh cat > example.service @@ -628,11 +667,19 @@ creating directories. To do this, the parition needs to be mounted. [Install] WantedBy=multi-user.target ``` -6. Enable the service. This is normally done with systemctl enable. However, this is not possible - when the service is created for a mounted root partition. Therefore create a symlink as follows. + +6. Enable the service. This is normally done with `systemctl enable ` which would create + a symlink in the `multi-user.target.wants` directory. However, this is not possible + when the service is created for a mounted root partition. It is also not possible during run + time because symlinks can't be created in a read-only filesystem. Therefore, relative symlinks + are used like this: + ```sh - ln -s '/tmp/the-mounted-xdi-image/lib/systemd/system/example.service' '/tmp/the-mounted-xdi-image/etc/systemd/system/multi-user.target.wants/example.service' + cd etc/systemd/system/multi-user.target.wants/ + ln -s ../example.service example.service ``` + + You can check the symlinnks with `ls -l` 7. The modified root partition is written back when the partion is locked again. ```sh @@ -648,6 +695,47 @@ creating directories. To do this, the parition needs to be mounted. systemctl status example ``` +## Current user systemd services + +The following custom `systemd` services are currently running on the Q7S and can be found in +the `/etc/systemd/system` folder. +You can query that status of a service by running `systemctl status `. + +### `eive-watchdog` + +The watchdog will create a pipe at `/tmp/watchdog-pipe` which can be used both by the watchdog and +the EIVE OBSW. The watchdog will only read from this pipe while the OBSW will only write +to this pipe. The watchdog checks for basic ASCII commands as a first basic feature set. +The most important functionality is that the watchdog cant detect if a timeout +has happened. This can happen beause the OBSW is hanging (or at least the CoreController thread) or +there is simply now OBSW running on the system. It does to by checking whether the FIFO is +regulary written to, which means the EIVE OBSW is alive. + +If the EIVE OBSW is alive, a special file called `/tmp/obsw-running` will be created. +This file can be used by any other software component to query whether the EIVE OBSW is running. +The EIVE OBSW itself can be configured to check whether this file exists, which prevents two +EIVE OBSW instances running on the Q7S at once. + +If a timeout occurs, this special file will be deleted as well. +The watchdog and its configuration will be directly integrated into this repostory, which +makes adaptions easy. + +### `tcfagent` + +This starts the `/usr/bin/agent` program to allows remote debugging. Might not be part of +the mission code + +### `eive-early-config` + +This is a configuration script which runs early after `local-fs.target` and `sysinit.target` +Currently only pipes the output of `xsc_boot_copy` into the file `/tmp/curr_copy.txt` which can be +used by other software components to read the current chip and copy. + +### `eive-post-ntpd-config` + +This is a configuration scripts which runs after the Network Time Protocol has run. This script +currently sets the static IP address `192.168.133.10` and starts the `can` interface. + ## PCDU Connect to serial console of P60 Dock diff --git a/automation/Dockerfile-q7s b/automation/Dockerfile-q7s new file mode 100644 index 00000000..21b56439 --- /dev/null +++ b/automation/Dockerfile-q7s @@ -0,0 +1,19 @@ +FROM ubuntu:focal + +RUN apt-get update +RUN apt-get --yes upgrade +RUN apt-get --yes install cmake libgpiod-dev xz-utils nano curl + +# Q7S root filesystem, required for cross-compilation. +RUN mkdir -p /usr/rootfs; \ +curl https://buggy.irs.uni-stuttgart.de/eive/tools/cortexa9hf-neon-xiphos-linux-gnueabi.tar.gz \ + | tar -xz -C /usr/rootfs + +# Cross compiler +RUN mkdir -p /usr/tools; \ +curl https://buggy.irs.uni-stuttgart.de/eive/tools/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf.tar.gz \ + | tar -xz -C /usr/tools + + +ENV Q7S_SYSROOT="/usr/rootfs/cortexa9hf-neon-xiphos-linux-gnueabi" +ENV PATH=$PATH:"/usr/tools/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin" diff --git a/automation/Jenkinsfile b/automation/Jenkinsfile new file mode 100644 index 00000000..7b184280 --- /dev/null +++ b/automation/Jenkinsfile @@ -0,0 +1,49 @@ +pipeline { + agent any + stages { + stage('Build Container') { + when { + changeset "automation/Dockerfile-q7s" + branch 'develop' + } + steps { + sh 'docker build -t eive-fsw-build-q7s:gcc8 - < automation/Dockerfile-q7s' + + } + } + stage('Clean') { + when { + anyOf { + changelog 'cleanCI' + changeset '*.cmake' + changeset 'CMakeLists.txt' + } + } + steps { + sh 'rm -rf build-q7s-debug' + } + } + stage('Build Q7S') { + agent { + docker { + image 'eive-fsw-build-q7s:gcc8' + reuseNode true + } + } + steps { + dir('build-q7s-debug') { + sh 'cmake -DTGT_BSP="arm/q7s" -DCMAKE_BUILD_TYPE=Debug -DFSFW_OSAL=linux ..' + sh 'cmake --build . -j' + } + } + } + stage('Deploy') { + when { + tag 'v*.*.*' + } + steps { + sh 'echo Deploying' + } + } + } +} diff --git a/bsp_hosted/InitMission.cpp b/bsp_hosted/InitMission.cpp index 8ef40d06..ed6b8820 100644 --- a/bsp_hosted/InitMission.cpp +++ b/bsp_hosted/InitMission.cpp @@ -70,15 +70,15 @@ void initmission::initTasks() { } /* UDP bridge */ - PeriodicTaskIF* udpBridgeTask = factory->createPeriodicTask( - "UDP_UNIX_BRIDGE", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); - result = udpBridgeTask->addComponent(objects::TMTC_BRIDGE); + PeriodicTaskIF* tmtcBridgeTask = factory->createPeriodicTask( + "TMTC_UNIX_BRIDGE", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + result = tmtcBridgeTask->addComponent(objects::TMTC_BRIDGE); if(result != HasReturnvaluesIF::RETURN_OK) { sif::error << "Add component UDP Unix Bridge failed" << std::endl; } - PeriodicTaskIF* udpPollingTask = factory->createPeriodicTask( + PeriodicTaskIF* tmtcPollingTask = factory->createPeriodicTask( "UDP_POLLING", 80, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); - result = udpPollingTask->addComponent(objects::TMTC_POLLING_TASK); + result = tmtcPollingTask->addComponent(objects::TMTC_POLLING_TASK); if(result != HasReturnvaluesIF::RETURN_OK) { sif::error << "Add component UDP Polling failed" << std::endl; } @@ -142,8 +142,8 @@ void initmission::initTasks() { sif::info << "Starting tasks.." << std::endl; tmTcDistributor->startTask(); - udpBridgeTask->startTask(); - udpPollingTask->startTask(); + tmtcBridgeTask->startTask(); + tmtcPollingTask->startTask(); pusVerification->startTask(); pusEvents->startTask(); diff --git a/bsp_hosted/ObjectFactory.cpp b/bsp_hosted/ObjectFactory.cpp index 0933df5b..9ea97fd1 100644 --- a/bsp_hosted/ObjectFactory.cpp +++ b/bsp_hosted/ObjectFactory.cpp @@ -1,6 +1,5 @@ #include "ObjectFactory.h" - -#include +#include "OBSWConfig.h" #include #include #include @@ -11,8 +10,13 @@ #include #include -#include -#include +#if OBSW_USE_TMTC_TCP_BRIDGE == 0 +#include "fsfw/osal/common/UdpTcPollingTask.h" +#include "fsfw/osal/common/UdpTmTcBridge.h" +#else +#include "fsfw/osal/common/TcpTmTcBridge.h" +#include "fsfw/osal/common/TcpTmTcServer.h" +#endif #include @@ -40,7 +44,18 @@ void ObjectFactory::produce(void* args){ Factory::setStaticFrameworkObjectIds(); ObjectFactory::produceGenericObjects(); +#if OBSW_USE_TMTC_TCP_BRIDGE == 0 + sif::info << "Setting up UDP TMTC bridge with listener port " << + UdpTmTcBridge::DEFAULT_SERVER_PORT << std::endl; new UdpTmTcBridge(objects::TMTC_BRIDGE, objects::CCSDS_PACKET_DISTRIBUTOR); new UdpTcPollingTask(objects::TMTC_POLLING_TASK, objects::TMTC_BRIDGE); +#else + sif::info << "Setting up TCP TMTC bridge with listener port " << + TcpTmTcBridge::DEFAULT_SERVER_PORT << std::endl; + new TcpTmTcBridge(objects::TMTC_BRIDGE, objects::CCSDS_PACKET_DISTRIBUTOR); + new TcpTmTcServer(objects::TMTC_POLLING_TASK, objects::TMTC_BRIDGE); +#endif + + new TestTask(objects::TEST_TASK); } diff --git a/bsp_hosted/main.cpp b/bsp_hosted/main.cpp index 5b43da1e..153cc447 100644 --- a/bsp_hosted/main.cpp +++ b/bsp_hosted/main.cpp @@ -1,9 +1,10 @@ #include "InitMission.h" +#include "OBSWVersion.h" + +#include "fsfw/FSFWVersion.h" +#include "fsfw/tasks/TaskFactory.h" -#include -#include #include - #ifdef WIN32 static const char* COMPILE_PRINTOUT = "Windows"; #elif LINUX @@ -20,8 +21,9 @@ int main(void) { std::cout << "-- EIVE OBSW --" << std::endl; std::cout << "-- Compiled for " << COMPILE_PRINTOUT << " --" << std::endl; - std::cout << "-- Software version " << SW_NAME << " v" << SW_VERSION << "." - << SW_SUBVERSION << "." << SW_REVISION << " -- " << std::endl; + std::cout << "-- OBSW " << SW_NAME << " v" << SW_VERSION << "." << SW_SUBVERSION << + "." << SW_REVISION << ", FSFW v" << FSFW_VERSION << "." << FSFW_SUBVERSION << "." << + FSFW_REVISION << "--" << std::endl; std::cout << "-- " << __DATE__ << " " << __TIME__ << " --" << std::endl; initmission::initMission(); diff --git a/bsp_q7s/Dockerfile b/bsp_q7s/Dockerfile deleted file mode 100644 index 04184c4a..00000000 --- a/bsp_q7s/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -FROM ubuntu:latest -# FROM alpine:latest - -ENV TZ=Europe/Berlin -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN apt-get update && apt-get install -y curl cmake g++ - -# Q7S root filesystem, required for cross-compilation. Use IPv6 for curl -RUN mkdir -p /usr/rootfs; \ - curl -6 https://eive-cloud.irs.uni-stuttgart.de/index.php/s/dnfMy9kGpgynN6J/download/cortexa9hf-neon-xiphos-linux-gnueabi.tar.gz \ - | tar xvz -C /usr/rootfs -# Q7S C++ cross-compiler. Use IPv6 for curl -RUN mkdir -p /usr/tools; \ - curl -6 https://eive-cloud.irs.uni-stuttgart.de/index.php/s/RMsbHydJc6PSqcz/download/gcc-arm-linux-gnueabi.tar.gz \ - | tar xvz -C /usr/tools - -# RUN apk add cmake make g++ - -# Required for cmake build -ENV Q7S_SYSROOT="/usr/rootfs/cortexa9hf-neon-xiphos-linux-gnueabi" -ENV PATH=$PATH:"/usr/tools/gcc-arm-linux-gnueabi/bin" - -WORKDIR /usr/src/app -COPY . . - -RUN set -ex; \ - rm -rf build-q7s; \ - mkdir build-q7s; \ - cd build-q7s; \ - cmake -DCMAKE_BUILD_TYPE=Release -DOS_FSFW=linux -DTGT_BSP="arm/q7s" ..; - -ENTRYPOINT ["cmake", "--build", "build-q7s"] -CMD ["-j"] -# CMD ["bash"] diff --git a/bsp_q7s/boardconfig/q7sConfig.h.in b/bsp_q7s/boardconfig/q7sConfig.h.in index 2fd52eb7..61e924d8 100644 --- a/bsp_q7s/boardconfig/q7sConfig.h.in +++ b/bsp_q7s/boardconfig/q7sConfig.h.in @@ -5,9 +5,9 @@ #cmakedefine01 Q7S_SIMPLE_MODE -#define Q7S_SD_NONE 0 -#define Q7S_SD_COLD_REDUNDANT 1 -#define Q7S_SD_HOT_REDUNDANT 2 +#define Q7S_SD_NONE 0 +#define Q7S_SD_COLD_REDUNDANT 1 +#define Q7S_SD_HOT_REDUNDANT 2 // The OBSW will perform different actions to set up the SD cards depending on the flag set here // Set to Q7S_SD_NONE: Don't do anything // Set to Q7S_COLD_REDUNDANT: On startup, get the prefered SD card, turn it on and mount it, and @@ -15,7 +15,9 @@ // Set to Q7S_HOT_REDUNDANT: On startup, turn on both SD cards and mount them #define Q7S_SD_CARD_CONFIG Q7S_SD_COLD_REDUNDANT -#define Q7S_ADD_RTD_DEVICES 0 +// Probably better if this is disabled for mission code. Convenient for development +#define Q7S_CHECK_FOR_ALREADY_RUNNING_IMG 1 +#define Q7S_ADD_RTD_DEVICES 0 /* Only one of those 2 should be enabled! */ /* Add code for ACS board */ diff --git a/bsp_q7s/core/CoreController.cpp b/bsp_q7s/core/CoreController.cpp index 1285a56b..72171c65 100644 --- a/bsp_q7s/core/CoreController.cpp +++ b/bsp_q7s/core/CoreController.cpp @@ -1,6 +1,7 @@ #include "CoreController.h" #include "OBSWConfig.h" #include "OBSWVersion.h" +#include "watchdogConf.h" #include "fsfw/FSFWVersion.h" #include "fsfw/serviceinterface/ServiceInterface.h" @@ -13,10 +14,38 @@ #include "bsp_q7s/memory/scratchApi.h" #include "bsp_q7s/memory/SdCardManager.h" +#include +#include + #include +CoreController::Chip CoreController::currentChip = Chip::NO_CHIP; +CoreController::Copy CoreController::currentCopy = Copy::NO_COPY; + CoreController::CoreController(object_id_t objectId): - ExtendedControllerBase(objectId, objects::NO_OBJECT, 5) { + ExtendedControllerBase(objectId, objects::NO_OBJECT, 5), + opDivider(5) { + ReturnValue_t result = HasReturnvaluesIF::RETURN_OK; + try { + result = initWatchdogFifo(); + if(result != HasReturnvaluesIF::RETURN_OK) { + sif::warning << "CoreController::CoreController: Watchdog FIFO init failed" << + std::endl; + } + result = initSdCard(); + if(result != HasReturnvaluesIF::RETURN_OK) { + sif::warning << "CoreController::CoreController: SD card init failed" << std::endl; + } + result = initBootCopy(); + if(result != HasReturnvaluesIF::RETURN_OK) { + sif::warning << "CoreController::CoreController: Boot copy init" << std::endl; + } + + } + catch(const std::filesystem::filesystem_error& e) { + sif::error << "CoreController::CoreController: Failed with exception " << + e.what() << std::endl; + } } ReturnValue_t CoreController::handleCommandMessage(CommandMessage *message) { @@ -24,6 +53,7 @@ ReturnValue_t CoreController::handleCommandMessage(CommandMessage *message) { } void CoreController::performControlOperation() { + performWatchdogControlOperation(); } ReturnValue_t CoreController::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, @@ -37,16 +67,6 @@ LocalPoolDataSetBase* CoreController::getDataSetHandle(sid_t sid) { ReturnValue_t CoreController::initialize() { ReturnValue_t result = HasReturnvaluesIF::RETURN_OK; - try { - result = sdCardInit(); - if(result != HasReturnvaluesIF::RETURN_OK) { - sif::warning << "CoreController::initialize: SD card init failed" << std::endl; - } - } - catch(const std::filesystem::filesystem_error& e) { - sif::error << "CoreController::initialize: sdCardInit failed with exception " << e.what() - << std::endl; - } result = scratch::writeNumber(scratch::ALLOC_FAILURE_COUNT, 0); if(result != HasReturnvaluesIF::RETURN_OK) { @@ -62,7 +82,7 @@ ReturnValue_t CoreController::checkModeCommand(Mode_t mode, Submode_t submode, return HasReturnvaluesIF::RETURN_OK; } -ReturnValue_t CoreController::sdCardInit() { +ReturnValue_t CoreController::initSdCard() { #if Q7S_SD_CARD_CONFIG == Q7S_SD_NONE sif::info << "No SD card initialization will be performed" << std::endl; return HasReturnvaluesIF::RETURN_OK; @@ -136,51 +156,11 @@ ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_ const uint8_t *data, size_t size) { switch(actionId) { case(LIST_DIRECTORY_INTO_FILE): { - // TODO: Packet definition for clean deserialization - // 2 bytes for a and R flag, at least 5 bytes for minimum valid path /tmp with - // null termination, at least 7 bytes for minimum target file name /tmp/a with - // null termination. - if(size < 14) { - return HasActionsIF::INVALID_PARAMETERS; - } - // We could also make -l optional, but I can't think of a reason why to not use -l.. - - // This flag specifies to run ls with -a - bool aFlag = data[0]; - data += 1; - // This flag specifies to run ls with -R - bool RFlag = data[1]; - data += 1; - - size_t remainingSize = size - 2; - // One larger for null termination, which prevents undefined behaviour if the sent - // strings are not 0 terminated properly - std::vector repoAndTargetFileBuffer(remainingSize + 1, 0); - std::memcpy(repoAndTargetFileBuffer.data(), data, remainingSize); - const char* currentCharPtr = reinterpret_cast(repoAndTargetFileBuffer.data()); - // Full target file name - std::string repoName(currentCharPtr); - size_t repoLength = repoName.length(); - // The other string needs to be at least one letter plus NULL termination to be valid at all - // The first string also needs to be NULL terminated, but the termination is not included - // in the string length, so this is subtracted from the remaining size as well - if(repoLength > remainingSize - 3) { - return HasActionsIF::INVALID_PARAMETERS; - } - // The file length will not include the NULL termination, so we skip it - currentCharPtr += repoLength + 1; - std::string targetFileName(currentCharPtr); - std::ostringstream oss; - oss << "ls -l"; - if(aFlag) { - oss << "a"; - } - if(RFlag) { - oss << "R"; - } - oss << " " << repoName << " > " << targetFileName; - std::system(oss.str().c_str()); + return actionListDirectoryIntoFile(actionId, commandedBy, data, size); + } + case(REBOOT_OBC): { return HasReturnvaluesIF::RETURN_OK; + break; } default: { return HasActionsIF::INVALID_ACTION_ID; @@ -189,7 +169,7 @@ ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_ } ReturnValue_t CoreController::initializeAfterTaskCreation() { - ReturnValue_t result = versionFileInit(); + ReturnValue_t result = initVersionFile(); if(result != HasReturnvaluesIF::RETURN_OK) { sif::warning << "CoreController::initialize: Version initialization failed" << std::endl; } @@ -272,7 +252,7 @@ ReturnValue_t CoreController::incrementAllocationFailureCount() { return scratch::writeNumber(scratch::ALLOC_FAILURE_COUNT, count); } -ReturnValue_t CoreController::versionFileInit() { +ReturnValue_t CoreController::initVersionFile() { std::string unameFileName = "/tmp/uname_version.txt"; // TODO: No -v flag for now. If the kernel version is used, need to cut off first few letters @@ -356,14 +336,157 @@ ReturnValue_t CoreController::versionFileInit() { return HasReturnvaluesIF::RETURN_OK; } +ReturnValue_t CoreController::actionListDirectoryIntoFile(ActionId_t actionId, + MessageQueueId_t commandedBy, const uint8_t *data, size_t size) { + // TODO: Packet definition for clean deserialization + // 2 bytes for a and R flag, at least 5 bytes for minimum valid path /tmp with + // null termination, at least 7 bytes for minimum target file name /tmp/a with + // null termination. + if(size < 14) { + return HasActionsIF::INVALID_PARAMETERS; + } + // We could also make -l optional, but I can't think of a reason why to not use -l.. + + // This flag specifies to run ls with -a + bool aFlag = data[0]; + data += 1; + // This flag specifies to run ls with -R + bool RFlag = data[1]; + data += 1; + + size_t remainingSize = size - 2; + // One larger for null termination, which prevents undefined behaviour if the sent + // strings are not 0 terminated properly + std::vector repoAndTargetFileBuffer(remainingSize + 1, 0); + std::memcpy(repoAndTargetFileBuffer.data(), data, remainingSize); + const char* currentCharPtr = reinterpret_cast(repoAndTargetFileBuffer.data()); + // Full target file name + std::string repoName(currentCharPtr); + size_t repoLength = repoName.length(); + // The other string needs to be at least one letter plus NULL termination to be valid at all + // The first string also needs to be NULL terminated, but the termination is not included + // in the string length, so this is subtracted from the remaining size as well + if(repoLength > remainingSize - 3) { + return HasActionsIF::INVALID_PARAMETERS; + } + // The file length will not include the NULL termination, so we skip it + currentCharPtr += repoLength + 1; + std::string targetFileName(currentCharPtr); + std::ostringstream oss; + oss << "ls -l"; + if(aFlag) { + oss << "a"; + } + if(RFlag) { + oss << "R"; + } + oss << " " << repoName << " > " << targetFileName; + int result = std::system(oss.str().c_str()); + if(result != 0) { + utility::handleSystemError(result, "CoreController::actionListDirectoryIntoFile"); + actionHelper.finish(false, commandedBy, actionId); + } + return HasReturnvaluesIF::RETURN_OK; +} + +ReturnValue_t CoreController::initBootCopy() { + std::string fileName = "/tmp/curr_copy.txt"; + if(not std::filesystem::exists(fileName)) { + // Thils file is created by the systemd service eive-early-config so this should + // not happen normally + std::string cmd = "xsc_boot_copy > " + fileName; + int result = std::system(cmd.c_str()); + if(result != 0) { + utility::handleSystemError(result, "CoreController::initBootCopy"); + } + } + std::ifstream file(fileName); + std::string line; + std::getline(file, line); + std::istringstream iss(line); + int value = 0; + iss >> value; + currentChip = static_cast(value); + iss >> value; + currentCopy = static_cast(value); + return HasReturnvaluesIF::RETURN_OK; +} + +void CoreController::getCurrentBootCopy(Chip &chip, Copy ©) { + // Not really thread-safe but it does not need to be + chip = currentChip; + copy = currentCopy; +} + +ReturnValue_t CoreController::initWatchdogFifo() { + if(not std::filesystem::exists(watchdog::FIFO_NAME)) { + // Still return RETURN_OK for now + sif::info << "Watchdog FIFO " << watchdog::FIFO_NAME << " does not exist, can't initiate" << + " watchdog" << std::endl; + return HasReturnvaluesIF::RETURN_OK; + } + // Open FIFO write only and non-blocking to prevent SW from killing itself. + watchdogFifoFd = open(watchdog::FIFO_NAME.c_str(), O_WRONLY | O_NONBLOCK); + if(watchdogFifoFd < 0) { + if(errno == ENXIO) { + watchdogFifoFd = RETRY_FIFO_OPEN; + sif::info << "eive-watchdog not running. FIFO can not be opened" << std::endl; + } + else { + sif::error << "Opening pipe " << watchdog::FIFO_NAME << " write-only failed with " << + errno << ": " << strerror(errno) << std::endl; + return HasReturnvaluesIF::RETURN_FAILED; + } + } + return HasReturnvaluesIF::RETURN_OK; +} + void CoreController::initPrint() { #if OBSW_VERBOSE_LEVEL >= 1 #if OBSW_USE_TMTC_TCP_BRIDGE == 0 sif::info << "Created UDP server for TMTC commanding with listener port " << - UdpTmTcBridge::DEFAULT_UDP_SERVER_PORT << std::endl; + UdpTmTcBridge::DEFAULT_SERVER_PORT << std::endl; #else sif::info << "Created TCP server for TMTC commanding with listener port " << - TcpTmTcBridge::DEFAULT_TCP_SERVER_PORT << std::endl; + TcpTmTcBridge::DEFAULT_SERVER_PORT << std::endl; #endif + + if(watchdogFifoFd > 0) { + sif::info << "Opened watchdog FIFO successfully.." << std::endl; + } #endif } + +void CoreController::performWatchdogControlOperation() { + // Only perform each fifth iteration + if(watchdogFifoFd != 0 and opDivider.checkAndIncrement()) { + if(watchdogFifoFd == RETRY_FIFO_OPEN) { + // Open FIFO write only and non-blocking + watchdogFifoFd = open(watchdog::FIFO_NAME.c_str(), O_WRONLY | O_NONBLOCK); + if(watchdogFifoFd < 0) { + if(errno == ENXIO) { + watchdogFifoFd = RETRY_FIFO_OPEN; + // No printout for now, would be spam + return; + } + else { + sif::error << "Opening pipe " << watchdog::FIFO_NAME << + " write-only failed with " << errno << ": " << + strerror(errno) << std::endl; + return; + } + } + sif::info << "Opened " << watchdog::FIFO_NAME << " successfully" << std::endl; + } + else if(watchdogFifoFd > 0) { + // Write to OBSW watchdog FIFO here + const char writeChar = 'a'; + ssize_t writtenBytes = write(watchdogFifoFd, &writeChar, 1); + if(writtenBytes < 0) { + sif::error << "Errors writing to watchdog FIFO, code " << errno << ": " << + strerror(errno) << std::endl; + } + } + } + +} diff --git a/bsp_q7s/core/CoreController.h b/bsp_q7s/core/CoreController.h index 88415949..92b22dd4 100644 --- a/bsp_q7s/core/CoreController.h +++ b/bsp_q7s/core/CoreController.h @@ -1,19 +1,36 @@ #ifndef BSP_Q7S_CORE_CORECONTROLLER_H_ #define BSP_Q7S_CORE_CORECONTROLLER_H_ +#include #include "fsfw/controller/ExtendedControllerBase.h" #include "bsp_q7s/memory/SdCardManager.h" #include "events/subsystemIdRanges.h" + + class CoreController: public ExtendedControllerBase { public: + enum Chip: uint8_t { + CHIP_0, + CHIP_1, + NO_CHIP + }; + + enum Copy: uint8_t { + COPY_0, + COPY_1, + NO_COPY + }; + static constexpr ActionId_t LIST_DIRECTORY_INTO_FILE = 0; + static constexpr ActionId_t REBOOT_OBC = 1; static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::CORE; static constexpr Event ALLOC_FAILURE = event::makeEvent(SUBSYSTEM_ID, 0, severity::MEDIUM); + CoreController(object_id_t objectId); ReturnValue_t initialize() override; @@ -27,22 +44,39 @@ public: void performControlOperation() override; static ReturnValue_t incrementAllocationFailureCount(); + static void getCurrentBootCopy(Chip& chip, Copy& copy); + private: + static Chip currentChip; + static Copy currentCopy; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, LocalDataPoolManager& poolManager) override; LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, uint32_t *msToReachTheMode); - ReturnValue_t sdCardInit(); + ReturnValue_t initSdCard(); ReturnValue_t sdCardSetup(SdCardManager& sdcMan, SdCardManager::SdStatusPair& statusPair, sd::SdCard sdCard, sd::SdStatus status, std::string sdString); ReturnValue_t sdCardColdRedundantInit(SdCardManager* sdcMan, SdCardManager::SdStatusPair& statusPair); - ReturnValue_t versionFileInit(); + ReturnValue_t initVersionFile(); + ReturnValue_t initBootCopy(); + ReturnValue_t initWatchdogFifo(); + + ReturnValue_t actionListDirectoryIntoFile(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t *data, size_t size); + void initPrint(); + // Designated value for rechecking FIFO open + static constexpr int RETRY_FIFO_OPEN = -2; + int watchdogFifoFd = 0; + + PeriodicOperationDivider opDivider; + void performWatchdogControlOperation(); }; diff --git a/bsp_q7s/core/obsw.cpp b/bsp_q7s/core/obsw.cpp index 4a13e178..47b7b9d2 100644 --- a/bsp_q7s/core/obsw.cpp +++ b/bsp_q7s/core/obsw.cpp @@ -2,11 +2,15 @@ #include "OBSWVersion.h" #include "OBSWConfig.h" #include "InitMission.h" +#include "watchdogConf.h" #include "fsfw/tasks/TaskFactory.h" #include "fsfw/FSFWVersion.h" #include +#include + +static int OBSW_ALREADY_RUNNING = -2; int obsw::obsw() { std::cout << "-- EIVE OBSW --" << std::endl; @@ -20,6 +24,15 @@ int obsw::obsw() { FSFW_REVISION << "--" << std::endl; std::cout << "-- " << __DATE__ << " " << __TIME__ << " --" << std::endl; +#if Q7S_CHECK_FOR_ALREADY_RUNNING_IMG == 1 + // Check special file here. This file is created or deleted by the eive-watchdog application + // or systemd service! + if(std::filesystem::exists(watchdog::RUNNING_FILE_NAME)) { + sif::warning << "File " << watchdog::RUNNING_FILE_NAME << " exists so the software might " + "already be running. Aborting.." << std::endl; + return OBSW_ALREADY_RUNNING; + } +#endif initmission::initMission(); for(;;) { diff --git a/cmake/scripts/Q7S/simple/simple_make_debug_cfg.sh b/cmake/scripts/Q7S/simple/simple_make_debug_cfg.sh index 8a6c7b3f..50b87101 100755 --- a/cmake/scripts/Q7S/simple/simple_make_debug_cfg.sh +++ b/cmake/scripts/Q7S/simple/simple_make_debug_cfg.sh @@ -18,7 +18,7 @@ os_fsfw="linux" tgt_bsp="arm/q7s" build_dir="build-Simple-Q7S" build_generator="" -definitions="Q7S_SIMPLE_MODE=On" +definitions="BUILD_Q7S_SIMPLE_MODE=On" if [ "${OS}" = "Windows_NT" ]; then build_generator="MinGW Makefiles" python="py" diff --git a/cmake/scripts/Q7S/simple/simple_ninja_debug_cfg.sh b/cmake/scripts/Q7S/simple/simple_ninja_debug_cfg.sh index 965aae45..c97b1e54 100755 --- a/cmake/scripts/Q7S/simple/simple_ninja_debug_cfg.sh +++ b/cmake/scripts/Q7S/simple/simple_ninja_debug_cfg.sh @@ -18,7 +18,7 @@ os_fsfw="linux" tgt_bsp="arm/q7s" build_dir="build-Simple-Q7S" build_generator="Ninja" -definitions="Q7S_SIMPLE_MODE=On" +definitions="BUILD_Q7S_SIMPLE_MODE=On" if [ "${OS}" = "Windows_NT" ]; then python="py" # Could be other OS but this works for now. diff --git a/cmake/scripts/Q7S/watchdog/make_debug_cfg.sh b/cmake/scripts/Q7S/watchdog/make_debug_cfg.sh new file mode 100755 index 00000000..d5b58df9 --- /dev/null +++ b/cmake/scripts/Q7S/watchdog/make_debug_cfg.sh @@ -0,0 +1,36 @@ +#!/bin/sh +counter=0 +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f "cmake_build_config.py" ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "cmake_build_config.py not found in upper directories!" + exit 1 +fi + +os_fsfw="linux" +tgt_bsp="arm/q7s" +build_dir="build-Debug-Watchdog" +build_generator="" +definitions="BUILD_WATCHDOG=ON" +if [ "${OS}" = "Windows_NT" ]; then + build_generator="MinGW Makefiles" + python="py" +# Could be other OS but this works for now. +else + build_generator="Unix Makefiles" + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} cmake_build_config.py -o "${os_fsfw}" -g "${build_generator}" -b "debug" -t "${tgt_bsp}" \ + -d "${definitions}" -l"${build_dir}" +# set +x + diff --git a/cmake/scripts/Q7S/watchdog/make_release_cfg.sh b/cmake/scripts/Q7S/watchdog/make_release_cfg.sh new file mode 100755 index 00000000..726e82ed --- /dev/null +++ b/cmake/scripts/Q7S/watchdog/make_release_cfg.sh @@ -0,0 +1,36 @@ +#!/bin/sh +counter=0 +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f "cmake_build_config.py" ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "cmake_build_config.py not found in upper directories!" + exit 1 +fi + +os_fsfw="linux" +tgt_bsp="arm/q7s" +build_dir="build-Release-Watchdog" +build_generator="" +definitions="BUILD_WATCHDOG=ON" +if [ "${OS}" = "Windows_NT" ]; then + build_generator="MinGW Makefiles" + python="py" +# Could be other OS but this works for now. +else + build_generator="Unix Makefiles" + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} cmake_build_config.py -o "${os_fsfw}" -g "${build_generator}" -b "release" -t "${tgt_bsp}" \ + -d "${definitions} -l"${build_dir}" +# set +x + diff --git a/cmake/scripts/Q7S/watchdog/ninja_debug_cfg.sh b/cmake/scripts/Q7S/watchdog/ninja_debug_cfg.sh new file mode 100755 index 00000000..99a414e7 --- /dev/null +++ b/cmake/scripts/Q7S/watchdog/ninja_debug_cfg.sh @@ -0,0 +1,34 @@ +#!/bin/sh +counter=0 +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f "cmake_build_config.py" ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "cmake_build_config.py not found in upper directories!" + exit 1 +fi + +os_fsfw="linux" +tgt_bsp="arm/q7s" +build_dir="build-Debug-Watchdog" +build_generator="Ninja" +definitions="-DBUILD_WATCHDOG=ON" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} cmake_build_config.py -o "${os_fsfw}" -g "${build_generator}" -b "debug" -t "${tgt_bsp}" \ + -d "${definitions}" -l "${build_dir}" +# set +x + diff --git a/cmake/scripts/Q7S/watchdog/ninja_release_cfg.sh b/cmake/scripts/Q7S/watchdog/ninja_release_cfg.sh new file mode 100755 index 00000000..3e6beac3 --- /dev/null +++ b/cmake/scripts/Q7S/watchdog/ninja_release_cfg.sh @@ -0,0 +1,34 @@ +#!/bin/sh +counter=0 +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f "cmake_build_config.py" ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "cmake_build_config.py not found in upper directories!" + exit 1 +fi + +os_fsfw="linux" +tgt_bsp="arm/q7s" +build_dir="build-Release-Watchdog" +build_generator="Ninja" +definitions="BUILD_WATCHDOG=ON" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} cmake_build_config.py -o "${os_fsfw}" -g "${build_generator}" -b "release" -t "${tgt_bsp}" \ + -d "${definitions}" -l"${build_dir}" +# set +x + diff --git a/common/config/commonConfig.h.in b/common/config/commonConfig.h.in index 3d741bcf..2b0590c8 100644 --- a/common/config/commonConfig.h.in +++ b/common/config/commonConfig.h.in @@ -1,6 +1,10 @@ #ifndef COMMON_CONFIG_COMMONCONFIG_H_ #define COMMON_CONFIG_COMMONCONFIG_H_ -#define OBSW_ADD_LWGPS_TEST 0 +#define OBSW_ADD_LWGPS_TEST 0 + +// Use TCP instead of UDP for the TMTC bridge. This allows using the TMTC client locally +// because UDP packets are not allowed in the VPN +#define OBSW_USE_TMTC_TCP_BRIDGE 0 #endif /* COMMON_CONFIG_COMMONCONFIG_H_ */ diff --git a/fsfw b/fsfw index 1f6a5e63..54c028f9 160000 --- a/fsfw +++ b/fsfw @@ -1 +1 @@ -Subproject commit 1f6a5e635fcd6bd812e262cc65a15a8a054f7ecf +Subproject commit 54c028f913e81077855aa1ed727bac43e7efea82 diff --git a/linux/boardtest/SpiTestClass.cpp b/linux/boardtest/SpiTestClass.cpp index 71313f25..6f634168 100644 --- a/linux/boardtest/SpiTestClass.cpp +++ b/linux/boardtest/SpiTestClass.cpp @@ -1,4 +1,5 @@ #include "SpiTestClass.h" +#include "OBSWConfig.h" #include "devices/gpioIds.h" diff --git a/linux/fsfwconfig/OBSWConfig.h.in b/linux/fsfwconfig/OBSWConfig.h.in index 7a798717..778a1611 100644 --- a/linux/fsfwconfig/OBSWConfig.h.in +++ b/linux/fsfwconfig/OBSWConfig.h.in @@ -6,6 +6,10 @@ #ifndef FSFWCONFIG_OBSWCONFIG_H_ #define FSFWCONFIG_OBSWCONFIG_H_ +#cmakedefine RASPBERRY_Pi +#cmakedefine XIPHOS_Q7S +#cmakedefine BEAGLEBONEBLACK + #ifdef RASPBERRY_PI #include "rpiConfig.h" #elif defined(XIPHOS_Q7S) @@ -18,10 +22,6 @@ debugging. */ #define OBSW_VERBOSE_LEVEL 1 -// Use TCP instead of UDP for the TMTC bridge. This allows using the TMTC client locally -// because UDP packets are not allowed in the VPN -#define OBSW_USE_TMTC_TCP_BRIDGE 0 - #define OBSW_PRINT_MISSED_DEADLINES 1 #define OBSW_ADD_TEST_CODE 1 #define OBSW_ADD_TEST_PST 1 diff --git a/misc/eclipse/.cproject b/misc/eclipse/.cproject index 84814acc..fe0208b8 100644 --- a/misc/eclipse/.cproject +++ b/misc/eclipse/.cproject @@ -254,7 +254,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1044,6 +1370,9 @@ + + + @@ -1060,6 +1389,7 @@ + diff --git a/mission/devices/Max31865PT1000Handler.cpp b/mission/devices/Max31865PT1000Handler.cpp index 12fd5102..751d36eb 100644 --- a/mission/devices/Max31865PT1000Handler.cpp +++ b/mission/devices/Max31865PT1000Handler.cpp @@ -1,5 +1,7 @@ #include "Max31865PT1000Handler.h" +#include "fsfw/datapool/PoolReadGuard.h" + #include #include @@ -360,7 +362,6 @@ ReturnValue_t Max31865PT1000Handler::interpretDeviceReply( // do something with rtd value, will propably be stored in // dataset. float rtdValue = adcCode * RTD_RREF_PT1000 / INT16_MAX; - // calculate approximation float approxTemp = adcCode / 32.0 - 256.0; @@ -369,7 +370,7 @@ ReturnValue_t Max31865PT1000Handler::interpretDeviceReply( #if FSFW_CPP_OSTREAM_ENABLED == 1 sif::info << "Max31865PT1000Handler::interpretDeviceReply: Measured " << "resistance is " << rtdValue << " Ohms." << std::endl; - sif::info << "Approximated temperature is " << approxTemp << " °C" + sif::info << "Approximated temperature is " << approxTemp << " C" << std::endl; #else sif::printInfo("Max31865PT1000Handler::interpretDeviceReply: Measured resistance is %f" @@ -380,8 +381,8 @@ ReturnValue_t Max31865PT1000Handler::interpretDeviceReply( } #endif - ReturnValue_t result = sensorDataset.read(); - if(result != HasReturnvaluesIF::RETURN_OK) { + PoolReadGuard pg(&sensorDataset); + if(pg.getReadResult() != HasReturnvaluesIF::RETURN_OK) { // Configuration error #if FSFW_CPP_OSTREAM_ENABLED == 1 sif::debug << "Max31865PT1000Handler::interpretDeviceReply: Error reading dataset!" @@ -389,29 +390,17 @@ ReturnValue_t Max31865PT1000Handler::interpretDeviceReply( #else sif::printDebug("Max31865PT1000Handler::interpretDeviceReply: Error reading dataset!\n"); #endif - return result; + return pg.getReadResult(); } if(not sensorDataset.isValid()) { + sensorDataset.setValidity(true, false); + sensorDataset.rtdValue.setValid(true); sensorDataset.temperatureCelcius.setValid(true); } + sensorDataset.rtdValue = rtdValue; sensorDataset.temperatureCelcius = approxTemp; - - result = sensorDataset.commit(); - - if(result != HasReturnvaluesIF::RETURN_OK) { - // Configuration error -#if FSFW_CPP_OSTREAM_ENABLED == 1 - sif::debug << "Max31865PT1000Handler::interpretDeviceReply: " - "Error commiting dataset!" << std::endl; -#else - sif::printDebug("Max31865PT1000Handler::interpretDeviceReply: " - "Error commiting dataset!\n"); -#endif - return result; - } - break; } case(Max31865Definitions::REQUEST_FAULT_BYTE): { @@ -485,6 +474,7 @@ void Max31865PT1000Handler::doTransition(Mode_t modeFrom, ReturnValue_t Max31865PT1000Handler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(Max31865Definitions::PoolIds::RTD_VALUE, new PoolEntry({0})); localDataPoolMap.emplace(Max31865Definitions::PoolIds::TEMPERATURE_C, new PoolEntry({0}, 1, true)); localDataPoolMap.emplace(Max31865Definitions::PoolIds::FAULT_BYTE, diff --git a/mission/devices/devicedefinitions/Max31865Definitions.h b/mission/devices/devicedefinitions/Max31865Definitions.h index 26c702a3..9b2aa3a2 100644 --- a/mission/devices/devicedefinitions/Max31865Definitions.h +++ b/mission/devices/devicedefinitions/Max31865Definitions.h @@ -9,6 +9,7 @@ namespace Max31865Definitions { enum PoolIds: lp_id_t { + RTD_VALUE, TEMPERATURE_C, FAULT_BYTE }; @@ -46,6 +47,8 @@ public: StaticLocalDataSet(sid_t(objectId, MAX31865_SET_ID)) { } + lp_var_t rtdValue = lp_var_t(sid.objectId, + PoolIds::RTD_VALUE, this); lp_var_t temperatureCelcius = lp_var_t(sid.objectId, PoolIds::TEMPERATURE_C, this); lp_var_t errorByte = lp_var_t(sid.objectId, diff --git a/scripts/q7s-cp.py b/scripts/q7s-cp.py index 9de9d661..0c8c1a79 100755 --- a/scripts/q7s-cp.py +++ b/scripts/q7s-cp.py @@ -8,7 +8,13 @@ def main(): cmd = build_cmd(args) # Run the command print(f'Running command: {cmd}') - os.system(cmd) + result = os.system(cmd) + if result != 0: + print('') + print('Removing problematic SSH key and trying again..') + remove_ssh_key_cmd = 'ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[localhost]:1535"' + os.system(remove_ssh_key_cmd) + result = os.system(cmd) def handle_args(): diff --git a/watchdog/CMakeLists.txt b/watchdog/CMakeLists.txt new file mode 100644 index 00000000..0179053c --- /dev/null +++ b/watchdog/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(${TARGET_NAME} PRIVATE + main.cpp + Watchdog.cpp +) diff --git a/watchdog/Watchdog.cpp b/watchdog/Watchdog.cpp new file mode 100644 index 00000000..9ae60548 --- /dev/null +++ b/watchdog/Watchdog.cpp @@ -0,0 +1,258 @@ +#include "Watchdog.h" +#include "watchdogConf.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +WatchdogTask::WatchdogTask (): fd(0) { + int result = 0; + // Only create the FIFO if it does not exist yet + if(not std::filesystem::exists(watchdog::FIFO_NAME)) { + // Permission 666 or rw-rw-rw- + mode_t mode = DEFFILEMODE; + result = mkfifo(watchdog::FIFO_NAME.c_str(), mode); + if(result != 0) { + std::cerr << "eive-watchdog: Could not created named pipe at " << + watchdog::FIFO_NAME << ", error " << errno << ": " << strerror(errno) << + std::endl; + throw std::runtime_error("eive-watchdog: FIFO creation failed"); + } +#if WATCHDOG_VERBOSE_LEVEL >= 1 + std::cout << "eive-watchdog: Pipe at " << watchdog::FIFO_NAME << + " created successfully" << std::endl; +#endif + } +} + +WatchdogTask::~WatchdogTask() { + +} + +int WatchdogTask::performOperation() { + // Open FIFO read only and non-blocking + fd = open(watchdog::FIFO_NAME.c_str(), O_RDONLY | O_NONBLOCK); + if(fd < 0) { + std::cerr << "eive-watchdog: Opening pipe " << watchdog::FIFO_NAME << + "read-only failed with " << errno << ": " << strerror(errno) << std::endl; + return -1; + } + state = States::RUNNING; + + while(true) { + WatchdogTask::LoopResult loopResult = watchdogLoop(); + switch(loopResult) { + case(LoopResult::OK): { + performRunningOperation(); + break; + } + case(LoopResult::CANCEL_RQ): { + std::cout << "eive-watchdog: Received cancel request, closing watchdog.." << std::endl; + return 0; + } + case(LoopResult::SUSPEND_RQ): { + performSuspendOperation(); + break; + } + case(LoopResult::TIMEOUT): { + performNotRunningOperation(loopResult); + break; + } + case(LoopResult::HUNG_UP): { + performNotRunningOperation(loopResult); + break; + } + case(LoopResult::RESTART_RQ): { + if(state == States::SUSPENDED or state == States::FAULTY) { + performRunningOperation(); + } + break; + } + case(LoopResult::FAULT): { + using namespace std::chrono_literals; + // Configuration error + std::cerr << "Fault has occured in watchdog loop" << std::endl; + // Prevent spam + std::this_thread::sleep_for(2000ms); + + } + } + } + if (close(fd) < 0) { + std::cerr << "eive-watchdog: Closing named pipe at " << watchdog::FIFO_NAME << + "failed, error " << errno << ": " << strerror(errno) << std::endl; + } + std::cout << "eive-watchdog: Finished" << std::endl; + return 0; +} + +WatchdogTask::LoopResult WatchdogTask::watchdogLoop() { + using namespace std::chrono_literals; + struct pollfd waiter = {}; + waiter.fd = fd; + waiter.events = POLLIN; + + switch(state) { + case(States::SUSPENDED): { + // Sleep, then check whether a restart request was received + std::this_thread::sleep_for(1000ms); + break; + } + case(States::RUNNING): { + // Continue as usual + break; + } + case(States::NOT_STARTED): { + // This should not happen + std::cerr << "eive-watchdog: State is NOT_STARTED, configuration error" << std::endl; + break; + } + case(States::FAULTY): { + // TODO: Not sure what to do yet. Continue for now + break; + } + } + + // 10 seconds timeout, only poll one file descriptor + switch(poll(&waiter, 1, watchdog::TIMEOUT_MS)) { + case(0): { + return LoopResult::TIMEOUT; + } + case(1): { + return pollEvent(waiter); + } + default: { + std::cerr << "eive-watchdog: Unknown poll error at " << watchdog::FIFO_NAME << ", error " << + errno << ": " << strerror(errno) << std::endl; + break; + } + } + return LoopResult::OK; +} + +WatchdogTask::LoopResult WatchdogTask::pollEvent(struct pollfd& waiter) { + if (waiter.revents & POLLIN) { + ssize_t readLen = read(fd, buf.data(), buf.size()); + if (readLen < 0) { + std::cerr << "eive-watchdog: Read error on pipe " << watchdog::FIFO_NAME << + ", error " << errno << ": " << strerror(errno) << std::endl; + return LoopResult::OK; + } +#if WATCHDOG_VERBOSE_LEVEL == 2 + std::cout << "Read " << readLen << " byte(s) on the pipe " << FIFO_NAME + << std::endl; +#endif + else if(readLen >= 1) { + return parseCommandByte(readLen); + } + + } + else if(waiter.revents & POLLERR) { + std::cerr << "eive-watchdog: Poll error error on pipe " << watchdog::FIFO_NAME << + std::endl; + return LoopResult::FAULT; + } + else if (waiter.revents & POLLHUP) { + // Writer closed its end + return LoopResult::HUNG_UP; + } + return LoopResult::FAULT; +} + +WatchdogTask::LoopResult WatchdogTask::parseCommandByte(ssize_t readLen) { + for(ssize_t idx = 0; idx < readLen; idx++) { + char readChar = buf[idx]; + // Cancel request + if(readChar == watchdog::CANCEL_CHAR) { + return LoopResult::CANCEL_RQ; + } + // Begin request. Does not work if the operation was not suspended before + else if(readChar == watchdog::RESTART_CHAR) { + return LoopResult::RESTART_RQ; + } + // Suspend request + else if(readChar == watchdog::SUSPEND_CHAR) { + return LoopResult::SUSPEND_RQ; + } + // Everything else: All working as expected + } + return LoopResult::OK; +} + +int WatchdogTask::performRunningOperation() { + if(state != States::RUNNING) { + state = States::RUNNING; + } + + if(not obswRunning) { + if(printNotRunningLatch) { + // Reset latch so user can see timeouts + printNotRunningLatch = false; + } + + obswRunning = true; + std::cout << "eive-watchdog: Running OBSW detected.." << std::endl; +#if WATCHDOG_CREATE_FILE_IF_RUNNING == 1 + std::cout << "eive-watchdog: Creating " << watchdog::RUNNING_FILE_NAME << std::endl; + if (not std::filesystem::exists(watchdog::RUNNING_FILE_NAME)) { + std::ofstream obswRunningFile(watchdog::RUNNING_FILE_NAME); + if(not obswRunningFile.good()) { + std::cerr << "Creating file " << watchdog::RUNNING_FILE_NAME << " failed" + << std::endl; + } + } +#endif + } + return 0; +} + +int WatchdogTask::performNotRunningOperation(LoopResult type) { + // Latch prevents spam on console + if(not printNotRunningLatch) { + if(type == LoopResult::HUNG_UP) { + std::cout << "eive-watchdog: FIFO writer hung up!" << std::endl; + } + else { + std::cout << "eive-watchdog: The FIFO timed out!" << std::endl; + } + printNotRunningLatch = true; + } + + if(obswRunning) { +#if WATCHDOG_CREATE_FILE_IF_RUNNING == 1 + if (std::filesystem::exists(watchdog::RUNNING_FILE_NAME)) { + int result = std::remove(watchdog::RUNNING_FILE_NAME.c_str()); + if(result != 0) { + std::cerr << "Removing " << watchdog::RUNNING_FILE_NAME << " failed with code " << + errno << ": " << strerror(errno) << std::endl; + } + } +#endif + obswRunning = false; + } + if(type == LoopResult::HUNG_UP) { + using namespace std::chrono_literals; + // Prevent spam + std::this_thread::sleep_for(2000ms); + } + return 0; +} + +int WatchdogTask::performSuspendOperation() { + if(state == States::RUNNING or state == States::FAULTY) { + std::cout << "eive-watchdog: Suspending watchdog operations" << std::endl; + watchdogRunning = false; + state = States::SUSPENDED; + } + return 0; +} diff --git a/watchdog/Watchdog.h b/watchdog/Watchdog.h new file mode 100644 index 00000000..fb7ac65b --- /dev/null +++ b/watchdog/Watchdog.h @@ -0,0 +1,49 @@ +#ifndef WATCHDOG_WATCHDOG_H_ +#define WATCHDOG_WATCHDOG_H_ + +#include + + +class WatchdogTask { +public: + enum class States { + NOT_STARTED, + RUNNING, + SUSPENDED, + FAULTY + }; + + enum class LoopResult { + OK, + SUSPEND_RQ, + CANCEL_RQ, + RESTART_RQ, + TIMEOUT, + HUNG_UP, + FAULT + }; + + WatchdogTask(); + + virtual ~WatchdogTask(); + + int performOperation(); +private: + int fd = 0; + + bool obswRunning = false; + bool watchdogRunning = false; + bool printNotRunningLatch = false; + std::array buf; + States state = States::NOT_STARTED; + + LoopResult watchdogLoop(); + LoopResult pollEvent(struct pollfd& waiter); + LoopResult parseCommandByte(ssize_t readLen); + + int performRunningOperation(); + int performNotRunningOperation(LoopResult type); + int performSuspendOperation(); +}; + +#endif /* WATCHDOG_WATCHDOG_H_ */ diff --git a/watchdog/main.cpp b/watchdog/main.cpp new file mode 100644 index 00000000..ba75dc30 --- /dev/null +++ b/watchdog/main.cpp @@ -0,0 +1,24 @@ +#include "Watchdog.h" + +#include + +/** + * @brief This watchdog application uses a FIFO to check whether the OBSW is still running. + * It checks whether the OBSW writes to the the FIFO regularly. + */ +int main() { + std::cout << "eive-watchdog: Starting OBSW watchdog.." << std::endl; + try { + WatchdogTask watchdogTask; + int result = watchdogTask.performOperation(); + if(result != 0) { + return result; + } + } + catch(const std::runtime_error& e) { + std::cerr << "eive-watchdog: Run time exception " << e.what() << std::endl; + return -1; + } + return 0; +} + diff --git a/watchdog/watchdogConf.h.in b/watchdog/watchdogConf.h.in new file mode 100644 index 00000000..02d84e70 --- /dev/null +++ b/watchdog/watchdogConf.h.in @@ -0,0 +1,24 @@ +#include +#include + +#define WATCHDOG_VERBOSE_LEVEL 1 +/** + * This flag instructs the watchdog to create a special file in /tmp if the OBSW is running + * or to delete it if it is not running + */ +#define WATCHDOG_CREATE_FILE_IF_RUNNING 1 + +namespace watchdog { + +static constexpr int TIMEOUT_MS = 5 * 1000; +const std::string FIFO_NAME = "/tmp/watchdog-pipe"; +const std::string RUNNING_FILE_NAME = "/tmp/obsw-running"; + +// Suspend watchdog operations temporarily +static constexpr char SUSPEND_CHAR = 's'; +// Resume watchdog operations +static constexpr char RESTART_CHAR = 'b'; +// Causes the watchdog to close down +static constexpr char CANCEL_CHAR = 'c'; + +}