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 @@
-
+
@@ -1031,6 +1031,332 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -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';
+
+}