diff --git a/README.md b/README.md
index b6a18947..ff2c359d 100644
--- a/README.md
+++ b/README.md
@@ -11,13 +11,14 @@
4. [Useful and Common Host Commands](#host-commands)
5. [Setting up Prerequisites](#set-up-prereq)
6. [Remote Debugging](#remote-debugging)
-7. [Direct Debugging](#direct-debugging)
-8. [Transfering Files to the Q7S](#file-transfer)
-9. [Q7S OBC](#q7s)
-10. [Static Code Analysis](#static-code-analysis)
-11. [Eclipse](#eclipse)
-12. [Running the OBSW on a Raspberry Pi](#rpi)
-13. [FSFW](#fsfw)
+7. [TMTC testing](#tmtc-testing)
+8. [Direct Debugging](#direct-debugging)
+9. [Transfering Files to the Q7S](#file-transfer)
+10. [Q7S OBC](#q7s)
+11. [Static Code Analysis](#static-code-analysis)
+12. [Eclipse](#eclipse)
+13. [Running the OBSW on a Raspberry Pi](#rpi)
+14. [FSFW](#fsfw)
# General information
@@ -574,6 +575,40 @@ alias or shell script to do this quickly.
Note: When now setting up a debug session in the Xilinx SDK or Eclipse, the host must be set
to localhost instead of the IP address of the Q7S.
+# TMTC testing
+
+The OBSW supports sending PUS TM packets via TCP or the PDEC IP Core which transmits the data as
+CADU frames. To make the CADU frames receivabel by the
+[TMTC porgram](https://egit.irs.uni-stuttgart.de/eive/eive-tmtc), a python script is running as
+`systemd` service named `tmtc_bridge` on the flatsat PC which forwards TCP commands to the TCP
+server of the OBC and reads CADU frames from a serial interface.
+
+You can check whether the service is running the following command on the flatsat PC
+
+```sh
+systemctl status tmtc_bridge
+```
+
+The PUS packets transported with the CADU frames are extracted
+and forwared to the TMTC program's TCP client. The code of the TMTC bridge can be found
+[here](https://egit.irs.uni-stuttgart.de/eive/tmtc-bridge). To connect the TMTC program to the
+TMTC-bridge a port forwarding from a host must be set up with the following command:
+
+```sh
+ssh -L 1537:127.0.0.1:7100 eive@2001:7c0:2018:1099:babe:0:e1fe:f1a5 -t bash
+```
+
+You can print the output of the `systemd` service with
+
+```sh
+journalctl -u tmtc_bridge
+```
+
+This can be helpful to determine whether any TCs arrive or TMs are coming back.
+
+Note: The encoding of the TM packets and conversion of CADU frames takes some time.
+Thus the replies are received with a larger delay compared to a direct TCP connection.
+
# Direct Debugging
1. Assign static IP address to Q7S
diff --git a/bsp_q7s/boardconfig/busConf.h b/bsp_q7s/boardconfig/busConf.h
index 1149a54e..8905a125 100644
--- a/bsp_q7s/boardconfig/busConf.h
+++ b/bsp_q7s/boardconfig/busConf.h
@@ -52,6 +52,16 @@ namespace gpioNames {
static constexpr char EN_RW_4[] = "enable_rw_4";
static constexpr char SPI_MUX_SELECT[] = "spi_mux_select";
static constexpr char RAD_SENSOR_CHIP_SELECT[] = "rad_sensor_chip_select";
+ static constexpr char PAPB_BUSY_SIGNAL_VC0[] = "papb_busy_signal_vc0";
+ static constexpr char PAPB_EMPTY_SIGNAL_VC0[] = "papb_empty_signal_vc0";
+ static constexpr char PAPB_BUSY_SIGNAL_VC1[] = "papb_busy_signal_vc1";
+ static constexpr char PAPB_EMPTY_SIGNAL_VC1[] = "papb_empty_signal_vc1";
+ static constexpr char PAPB_BUSY_SIGNAL_VC2[] = "papb_busy_signal_vc2";
+ static constexpr char PAPB_EMPTY_SIGNAL_VC2[] = "papb_empty_signal_vc2";
+ static constexpr char PAPB_BUSY_SIGNAL_VC3[] = "papb_busy_signal_vc3";
+ static constexpr char PAPB_EMPTY_SIGNAL_VC3[] = "papb_empty_signal_vc3";
+ static constexpr char RS485_EN_TX_CLOCK[] = "tx_clock_enable_ltc2872";
+ static constexpr char RS485_EN_TX_DATA[] = "tx_data_enable_ltc2872";
}
}
diff --git a/bsp_q7s/core/InitMission.cpp b/bsp_q7s/core/InitMission.cpp
index deda0ed8..1715cada 100644
--- a/bsp_q7s/core/InitMission.cpp
+++ b/bsp_q7s/core/InitMission.cpp
@@ -98,6 +98,15 @@ void initmission::initTasks() {
}
#endif
+#if OBSW_USE_CCSDS_IP_CORE == 1
+ PeriodicTaskIF* ccsdsHandlerTask = factory->createPeriodicTask(
+ "UDP_POLLING", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc);
+ result = ccsdsHandlerTask->addComponent(objects::CCSDS_HANDLER);
+ if(result != HasReturnvaluesIF::RETURN_OK) {
+ initmission::printAddObjectError("CCSDS Handler", objects::CCSDS_HANDLER);
+ }
+#endif /* OBSW_USE_CCSDS_IP_CORE == 1 */
+
# if BOARD_TE0720 == 0
// FS task, task interval does not matter because it runs in permanent loop, priority low
// because it is a non-essential background task
@@ -147,6 +156,10 @@ void initmission::initTasks() {
tmtcPollingTask->startTask();
#endif
+#if OBSW_USE_CCSDS_IP_CORE == 1
+ ccsdsHandlerTask->startTask();
+#endif /* OBSW_USE_CCSDS_IP_CORE == 1 */
+
#if BOARD_TE0720 == 0
coreController->startTask();
#endif
diff --git a/bsp_q7s/core/ObjectFactory.cpp b/bsp_q7s/core/ObjectFactory.cpp
index b916977a..47b26a2a 100644
--- a/bsp_q7s/core/ObjectFactory.cpp
+++ b/bsp_q7s/core/ObjectFactory.cpp
@@ -1,8 +1,8 @@
#include
-
#include "ObjectFactory.h"
#include "OBSWConfig.h"
#include "devConf.h"
+#include "ccsdsConfig.h"
#include "busConf.h"
#include "tmtc/apid.h"
#include "devices/addresses.h"
@@ -25,8 +25,6 @@
#include "linux/devices/SusHandler.h"
#include "linux/csp/CspCookie.h"
#include "linux/csp/CspComIF.h"
-#include "linux/obc/CCSDSIPCoreBridge.h"
-
#include "mission/core/GenericFactory.h"
#include "mission/devices/PDU1Handler.h"
#include "mission/devices/PDU2Handler.h"
@@ -50,6 +48,8 @@
#include "mission/devices/devicedefinitions/RwDefinitions.h"
#include "mission/devices/devicedefinitions/StarTrackerDefinitions.h"
#include "mission/devices/GPSHyperionHandler.h"
+#include "mission/tmtc/CCSDSHandler.h"
+#include "mission/tmtc/VirtualChannel.h"
#include "mission/utility/TmFunnel.h"
#include "fsfw_hal/linux/uart/UartComIF.h"
@@ -75,6 +75,10 @@
#include "linux/boardtest/LibgpiodTest.h"
#endif
+#include
+#include
+#include
+
ResetArgs resetArgsGnss0;
ResetArgs resetArgsGnss1;
@@ -91,7 +95,12 @@ void Factory::setStaticFrameworkObjectIds() {
//DeviceHandlerBase::powerSwitcherId = objects::PCDU_HANDLER;
DeviceHandlerBase::powerSwitcherId = objects::NO_OBJECT;
+
+#if OBSW_TM_TO_PTME == 1
+ TmFunnel::downlinkDestination = objects::CCSDS_HANDLER;
+#else
TmFunnel::downlinkDestination = objects::TMTC_BRIDGE;
+#endif /* OBSW_TM_TO_PTME == 1 */
// No storage object for now.
TmFunnel::storageDestination = objects::NO_OBJECT;
@@ -164,6 +173,10 @@ void ObjectFactory::produce(void* args) {
#endif /* TE7020 != 0 */
+#if OBSW_USE_CCSDS_IP_CORE == 1
+ createCcsdsComponents(gpioComIF);
+#endif /* OBSW_USE_CCSDS_IP_CORE == 1 */
+
/* Test Task */
#if OBSW_ADD_TEST_CODE == 1
createTestComponents(gpioComIF);
@@ -863,6 +876,85 @@ void ObjectFactory::createReactionWheelComponents(LinuxLibgpioIF* gpioComIF) {
rw4SpiCookie->setCallbackArgs(rwHandler4);
}
+void ObjectFactory::createCcsdsComponents(LinuxLibgpioIF *gpioComIF) {
+ // GPIO definitions of signals connected to the virtual channel interfaces of the PTME IP Core
+ GpioCookie* gpioCookiePtmeIp = new GpioCookie;
+ GpiodRegularByLineName* gpio = nullptr;
+ std::stringstream consumer;
+ consumer << "0x" << std::hex << objects::PAPB_VC0;
+ gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_BUSY_SIGNAL_VC0, consumer.str());
+ gpioCookiePtmeIp->addGpio(gpioIds::VC0_PAPB_BUSY, gpio);
+ consumer.str("");
+ consumer << "0x" << std::hex << objects::PAPB_VC0;
+ gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC0, consumer.str());
+ gpioCookiePtmeIp->addGpio(gpioIds::VC0_PAPB_EMPTY, gpio);
+ consumer.str("");
+ consumer << "0x" << std::hex << objects::PAPB_VC1;
+ gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_BUSY_SIGNAL_VC1, consumer.str());
+ gpioCookiePtmeIp->addGpio(gpioIds::VC1_PAPB_BUSY, gpio);
+ consumer.str("");
+ consumer << "0x" << std::hex << objects::PAPB_VC1;
+ gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC1, consumer.str());
+ gpioCookiePtmeIp->addGpio(gpioIds::VC1_PAPB_EMPTY, gpio);
+ consumer.str("");
+ consumer << "0x" << std::hex << objects::PAPB_VC2;
+ gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_BUSY_SIGNAL_VC2, consumer.str());
+ gpioCookiePtmeIp->addGpio(gpioIds::VC2_PAPB_BUSY, gpio);
+ consumer.str("");
+ consumer << "0x" << std::hex << objects::PAPB_VC2;
+ gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC2, consumer.str());
+ gpioCookiePtmeIp->addGpio(gpioIds::VC2_PAPB_EMPTY, gpio);
+ consumer.str("");
+ consumer << "0x" << std::hex << objects::PAPB_VC3;
+ gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_BUSY_SIGNAL_VC3, consumer.str());
+ gpioCookiePtmeIp->addGpio(gpioIds::VC3_PAPB_BUSY, gpio);
+ consumer.str("");
+ consumer << "0x" << std::hex << objects::PAPB_VC3;
+ gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC3, consumer.str());
+ gpioCookiePtmeIp->addGpio(gpioIds::VC3_PAPB_EMPTY, gpio);
+
+ gpioComIF->addGpios(gpioCookiePtmeIp);
+
+ // Creating virtual channel interfaces
+ VcInterfaceIF* vc0 = new PapbVcInterface(objects::PAPB_VC0, gpioComIF, gpioIds::VC0_PAPB_BUSY,
+ gpioIds::VC0_PAPB_EMPTY, PtmeConfig::VC0_OFFSETT);
+ VcInterfaceIF* vc1 = new PapbVcInterface(objects::PAPB_VC1, gpioComIF, gpioIds::VC1_PAPB_BUSY,
+ gpioIds::VC1_PAPB_EMPTY, PtmeConfig::VC1_OFFSETT);
+ VcInterfaceIF* vc2 = new PapbVcInterface(objects::PAPB_VC2, gpioComIF, gpioIds::VC2_PAPB_BUSY,
+ gpioIds::VC2_PAPB_EMPTY, PtmeConfig::VC2_OFFSETT);
+ VcInterfaceIF* vc3 = new PapbVcInterface(objects::PAPB_VC3, gpioComIF, gpioIds::VC3_PAPB_BUSY,
+ gpioIds::VC3_PAPB_EMPTY, PtmeConfig::VC3_OFFSETT);
+
+ // Creating ptme object and adding virtual channel interfaces
+ Ptme* ptme = new Ptme(objects::PTME);
+ ptme->addVcInterface(ccsds::VC0, vc0);
+ ptme->addVcInterface(ccsds::VC1, vc1);
+ ptme->addVcInterface(ccsds::VC2, vc2);
+ ptme->addVcInterface(ccsds::VC3, vc3);
+
+ CCSDSHandler* ccsdsHandler = new CCSDSHandler(objects::CCSDS_HANDLER, objects::PTME);
+
+ VirtualChannel* vc = nullptr;
+ vc = new VirtualChannel(ccsds::VC0, config::VC0_QUEUE_SIZE);
+ ccsdsHandler->addVirtualChannel(ccsds::VC0, vc);
+ vc = new VirtualChannel(ccsds::VC1, config::VC1_QUEUE_SIZE);
+ ccsdsHandler->addVirtualChannel(ccsds::VC1, vc);
+ vc = new VirtualChannel(ccsds::VC2, config::VC2_QUEUE_SIZE);
+ ccsdsHandler->addVirtualChannel(ccsds::VC2, vc);
+ vc = new VirtualChannel(ccsds::VC3, config::VC3_QUEUE_SIZE);
+ ccsdsHandler->addVirtualChannel(ccsds::VC3, vc);
+
+ GpioCookie* gpioRS485Chip = new GpioCookie;
+ gpio = new GpiodRegularByLineName(q7s::gpioNames::RS485_EN_TX_CLOCK, "RS485 Transceiver",
+ gpio::Direction::OUT, gpio::HIGH);
+ gpioRS485Chip->addGpio(gpioIds::RS485_EN_TX_CLOCK, gpio);
+ gpio = new GpiodRegularByLineName(q7s::gpioNames::RS485_EN_TX_DATA, "RS485 Transceiver",
+ gpio::Direction::OUT, gpio::HIGH);
+ gpioRS485Chip->addGpio(gpioIds::RS485_EN_TX_DATA, gpio);
+
+ gpioComIF->addGpios(gpioRS485Chip);
+}
+
void ObjectFactory::createTestComponents(LinuxLibgpioIF* gpioComIF) {
#if BOARD_TE0720 == 0
@@ -901,10 +993,10 @@ void ObjectFactory::createTestComponents(LinuxLibgpioIF* gpioComIF) {
#if BOARD_TE0720 == 1 && OBSW_TEST_CCSDS_BRIDGE == 1
GpioCookie* gpioCookieCcsdsIp = new GpioCookie;
- GpiodRegular* papbBusyN = new GpiodRegular(std::string("gpiochip0"), 0, std::string("PAPBBusy_N"));
+ GpiodRegular* papbBusyN = new GpiodRegular(std::string("gpiochip0"), 0, std::string("PAPBBusy_VC0"));
gpioCookieCcsdsIp->addGpio(gpioIds::PAPB_BUSY_N, papbBusyN);
GpiodRegular* papbEmpty = new GpiodRegular(std::string("gpiochip0"), 1,
- std::string("Chip Select Sus Sensor"));
+ std::string("PAPBEmpty_VC0"));
gpioCookieCcsdsIp->addGpio(gpioIds::PAPB_EMPTY, papbEmpty);
gpioComIF->addGpios(gpioCookieCcsdsIp);
diff --git a/bsp_q7s/core/ObjectFactory.h b/bsp_q7s/core/ObjectFactory.h
index ad9533a5..0e723142 100644
--- a/bsp_q7s/core/ObjectFactory.h
+++ b/bsp_q7s/core/ObjectFactory.h
@@ -22,6 +22,7 @@ void createSolarArrayDeploymentComponents();
void createSyrlinksComponents();
void createRtdComponents(LinuxLibgpioIF* gpioComIF);
void createReactionWheelComponents(LinuxLibgpioIF* gpioComIF);
+void createCcsdsComponents(LinuxLibgpioIF *gpioComIF);
void createTestComponents(LinuxLibgpioIF* gpioComIF);
};
diff --git a/common/config/ccsdsConfig.h b/common/config/ccsdsConfig.h
new file mode 100644
index 00000000..8466a9fa
--- /dev/null
+++ b/common/config/ccsdsConfig.h
@@ -0,0 +1,15 @@
+#ifndef COMMON_CONFIG_CCSDSCONFIG_H_
+#define COMMON_CONFIG_CCSDSCONFIG_H_
+
+namespace ccsds {
+enum {
+ VC0,
+ VC1,
+ VC2,
+ VC3
+};
+}
+
+
+
+#endif /* COMMON_CONFIG_CCSDSCONFIG_H_ */
diff --git a/common/config/commonClassIds.h b/common/config/commonClassIds.h
index 5c8e263a..bddea49a 100644
--- a/common/config/commonClassIds.h
+++ b/common/config/commonClassIds.h
@@ -17,6 +17,7 @@ enum commonClassIds: uint8_t {
PLOC_SUPERVISOR_HANDLER, //PLSV
SUS_HANDLER, //SUSS
CCSDS_IP_CORE_BRIDGE, //IPCI
+ PTME, //PTME
PLOC_UPDATER, //PLUD
GOM_SPACE_HANDLER, //GOMS
PLOC_MEMORY_DUMPER, //PLMEMDUMP
diff --git a/common/config/commonConfig.h.in b/common/config/commonConfig.h.in
index 055f86aa..c8607fae 100644
--- a/common/config/commonConfig.h.in
+++ b/common/config/commonConfig.h.in
@@ -9,6 +9,13 @@
#define OBSW_ADD_TCPIP_BRIDGE 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
+// This will cause the OBSW to initialize the TMTC bridge responsible for exchanging data with the
+// CCSDS IP Cores.
+#define OBSW_USE_CCSDS_IP_CORE 1
+// Set to 1 if all telemetry should be sent to the PTME IP Core
+#define OBSW_TM_TO_PTME 1
+// Set to 1 if telecommands are received via the PDEC IP Core
+#define OBSW_TC_FROM_PDEC 0
#define OBSW_USE_TCP_BRIDGE 1
namespace common {
diff --git a/common/config/commonObjects.h b/common/config/commonObjects.h
index e44e0a1a..43cd396c 100644
--- a/common/config/commonObjects.h
+++ b/common/config/commonObjects.h
@@ -11,6 +11,12 @@ enum commonObjects: uint32_t {
TMTC_BRIDGE = 0x50000300,
TMTC_POLLING_TASK = 0x50000400,
FILE_SYSTEM_HANDLER = 0x50000500,
+ PTME = 0x50000600,
+ PAPB_VC0 = 0x50000700,
+ PAPB_VC1 = 0x50000701,
+ PAPB_VC2 = 0x50000702,
+ PAPB_VC3 = 0x50000703,
+ CCSDS_HANDLER = 0x50000800,
/* 0x43 ('C') for Controllers */
THERMAL_CONTROLLER = 0x43400001,
diff --git a/linux/obc/CCSDSIPCoreBridge.cpp b/linux/archive/tmtc/CCSDSIPCoreBridge.cpp
similarity index 98%
rename from linux/obc/CCSDSIPCoreBridge.cpp
rename to linux/archive/tmtc/CCSDSIPCoreBridge.cpp
index fe5a7007..982f7fc2 100644
--- a/linux/obc/CCSDSIPCoreBridge.cpp
+++ b/linux/archive/tmtc/CCSDSIPCoreBridge.cpp
@@ -1,7 +1,7 @@
#include
#include
-#include
+#include
CCSDSIPCoreBridge::CCSDSIPCoreBridge(object_id_t objectId, object_id_t tcDestination,
object_id_t tmStoreId, object_id_t tcStoreId, LinuxLibgpioIF* gpioComIF,
diff --git a/linux/obc/CCSDSIPCoreBridge.h b/linux/archive/tmtc/CCSDSIPCoreBridge.h
similarity index 100%
rename from linux/obc/CCSDSIPCoreBridge.h
rename to linux/archive/tmtc/CCSDSIPCoreBridge.h
diff --git a/linux/fsfwconfig/OBSWConfig.h.in b/linux/fsfwconfig/OBSWConfig.h.in
index 4feb45e2..924026af 100644
--- a/linux/fsfwconfig/OBSWConfig.h.in
+++ b/linux/fsfwconfig/OBSWConfig.h.in
@@ -86,6 +86,7 @@ debugging. */
#define OBSW_TEST_TE7020_HEATER 0
#define OBSW_TEST_GPIO_OPEN_BY_LABEL 0
#define OBSW_TEST_GPIO_OPEN_BY_LINE_NAME 0
+#define OBSW_LINK_IS_UP 1
#define OBSW_DEBUG_P60DOCK 0
#define OBSW_DEBUG_PDU1 0
@@ -123,6 +124,14 @@ namespace config {
/* Add mission configuration flags here */
static constexpr uint32_t OBSW_FILESYSTEM_HANDLER_QUEUE_SIZE = 50;
static constexpr uint32_t PLOC_UPDATER_QUEUE_SIZE = 50;
+static constexpr uint32_t CCSDS_HANDLER_QUEUE_SIZE = 50;
+static constexpr uint8_t NUMBER_OF_VIRTUAL_CHANNELS = 4;
+static constexpr uint8_t VC0_QUEUE_SIZE = 50;
+static constexpr uint8_t VC1_QUEUE_SIZE = 50;
+static constexpr uint8_t VC2_QUEUE_SIZE = 50;
+static constexpr uint8_t VC3_QUEUE_SIZE = 50;
+
+static constexpr uint8_t LIVE_TM = 0;
#ifdef __cplusplus
}
diff --git a/linux/fsfwconfig/devices/gpioIds.h b/linux/fsfwconfig/devices/gpioIds.h
index b450bf8b..5f09e5f6 100644
--- a/linux/fsfwconfig/devices/gpioIds.h
+++ b/linux/fsfwconfig/devices/gpioIds.h
@@ -89,7 +89,20 @@ enum gpioId_t {
EN_RW_CS,
- SPI_MUX
+ SPI_MUX,
+
+ VC0_PAPB_EMPTY,
+ VC0_PAPB_BUSY,
+ VC1_PAPB_EMPTY,
+ VC1_PAPB_BUSY,
+ VC2_PAPB_EMPTY,
+ VC2_PAPB_BUSY,
+ VC3_PAPB_EMPTY,
+ VC3_PAPB_BUSY,
+
+
+ RS485_EN_TX_DATA,
+ RS485_EN_TX_CLOCK
};
}
diff --git a/linux/obc/CMakeLists.txt b/linux/obc/CMakeLists.txt
index 79d9ba9b..315a0d33 100644
--- a/linux/obc/CMakeLists.txt
+++ b/linux/obc/CMakeLists.txt
@@ -1,5 +1,6 @@
target_sources(${TARGET_NAME} PUBLIC
- CCSDSIPCoreBridge.cpp
+ PapbVcInterface.cpp
+ Ptme.cpp
)
diff --git a/linux/obc/PapbVcInterface.cpp b/linux/obc/PapbVcInterface.cpp
new file mode 100644
index 00000000..5bb72066
--- /dev/null
+++ b/linux/obc/PapbVcInterface.cpp
@@ -0,0 +1,104 @@
+#include
+#include "fsfw/serviceinterface/ServiceInterface.h"
+
+PapbVcInterface::PapbVcInterface(object_id_t objectId, LinuxLibgpioIF* gpioComIF,
+ gpioId_t papbBusyId, gpioId_t papbEmptyId, uint32_t vcOffset) :
+ SystemObject(objectId), gpioComIF(gpioComIF), papbBusyId(
+ papbBusyId), papbEmptyId(papbEmptyId), vcOffset(vcOffset) {
+}
+
+PapbVcInterface::~PapbVcInterface() {
+}
+
+
+void PapbVcInterface::setRegisterAddress(uint32_t* ptmeBaseAddress) {
+ vcBaseReg = ptmeBaseAddress + vcOffset;
+}
+
+ReturnValue_t PapbVcInterface::write(const uint8_t * data, size_t size) {
+
+ if(pollPapbBusySignal() == RETURN_OK) {
+ startPacketTransfer();
+ }
+
+ for(size_t idx = 0; idx < size; idx++) {
+ if(pollPapbBusySignal() == RETURN_OK) {
+ *(vcBaseReg + DATA_REG_OFFSET) = static_cast(*(data + idx));
+ }
+ else {
+ sif::warning << "PapbVcInterface::write: Only written " << idx << " of "
+ << size << " data" << std::endl;
+ return RETURN_FAILED;
+ }
+ }
+
+ if(pollPapbBusySignal() == RETURN_OK) {
+ endPacketTransfer();
+ }
+ return RETURN_OK;
+}
+
+void PapbVcInterface::startPacketTransfer() {
+ *vcBaseReg = CONFIG_START;
+}
+
+void PapbVcInterface::endPacketTransfer() {
+ *vcBaseReg = CONFIG_END;
+}
+
+ReturnValue_t PapbVcInterface::pollPapbBusySignal() {
+ int papbBusyState = 0;
+ ReturnValue_t result = RETURN_OK;
+
+ /** Check if PAPB interface is ready to receive data */
+ result = gpioComIF->readGpio(papbBusyId, &papbBusyState);
+ if (result != RETURN_OK) {
+ sif::warning << "PapbVcInterface::pollPapbBusySignal: Failed to read papb busy signal"
+ << std::endl;
+ return RETURN_FAILED;
+ }
+ if (!papbBusyState) {
+ sif::warning << "PapbVcInterface::pollPapbBusySignal: PAPB busy" << std::endl;
+ return PAPB_BUSY;
+ }
+
+ return RETURN_OK;
+}
+
+void PapbVcInterface::isVcInterfaceBufferEmpty() {
+ ReturnValue_t result = RETURN_OK;
+ int papbEmptyState = 1;
+
+ result = gpioComIF->readGpio(papbEmptyId, &papbEmptyState);
+
+ if (result != RETURN_OK) {
+ sif::warning << "PapbVcInterface::isVcInterfaceBufferEmpty: Failed to read papb empty signal"
+ << std::endl;
+ return;
+ }
+
+ if (papbEmptyState == 1) {
+ sif::debug << "PapbVcInterface::isVcInterfaceBufferEmpty: Buffer is empty" << std::endl;
+ }
+ else {
+ sif::debug << "PapbVcInterface::isVcInterfaceBufferEmpty: Buffer is not empty" << std::endl;
+ }
+ return;
+}
+
+ReturnValue_t PapbVcInterface::sendTestFrame() {
+ /** Size of one complete transfer frame data field amounts to 1105 bytes */
+ uint8_t testPacket[1105];
+
+ /** Fill one test packet */
+ for(int idx = 0; idx < 1105; idx++) {
+ testPacket[idx] = static_cast(idx & 0xFF);
+ }
+
+ ReturnValue_t result = write(testPacket, 1105);
+ if(result != RETURN_OK) {
+ return result;
+ }
+
+ return RETURN_OK;
+}
diff --git a/linux/obc/PapbVcInterface.h b/linux/obc/PapbVcInterface.h
new file mode 100644
index 00000000..937f4280
--- /dev/null
+++ b/linux/obc/PapbVcInterface.h
@@ -0,0 +1,112 @@
+#ifndef LINUX_OBC_PAPBVCINTERFACE_H_
+#define LINUX_OBC_PAPBVCINTERFACE_H_
+
+#include "OBSWConfig.h"
+#include "linux/obc/VcInterfaceIF.h"
+#include
+#include
+#include "fsfw/returnvalues/HasReturnvaluesIF.h"
+#include "fsfw/objectmanager/ObjectManager.h"
+
+/**
+ * @brief This class handles the transmission of data to a virtual channel of the PTME IP Core
+ * via the PAPB interface.
+ *
+ * @author J. Meier
+ */
+class PapbVcInterface: public SystemObject,
+ public VcInterfaceIF,
+ public HasReturnvaluesIF {
+public:
+ /**
+ * @brief Constructor
+ *
+ * @param objectId
+ * @param papbBusyId The ID of the GPIO which is connected to the PAPBBusy_N signal of the
+ * VcInterface IP Core. A low logic level indicates the VcInterface is not
+ * ready to receive more data.
+ * @param papbEmptyId The ID of the GPIO which is connected to the PAPBEmpty signal of the
+ * VcInterface IP Core. The signal is high when there are no packets in the
+ * external buffer memory (BRAM).
+ */
+ PapbVcInterface(object_id_t objectId, LinuxLibgpioIF* gpioComIF, gpioId_t papbBusyId,
+ gpioId_t papbEmptyId, uint32_t vcOffset);
+ virtual ~PapbVcInterface();
+
+ ReturnValue_t write(const uint8_t* data, size_t size) override;
+
+ void setRegisterAddress(uint32_t* ptmeBaseAddress) override;
+
+private:
+
+ static const uint8_t INTERFACE_ID = CLASS_ID::CCSDS_IP_CORE_BRIDGE;
+
+ static const ReturnValue_t PAPB_BUSY = MAKE_RETURN_CODE(0xA0);
+
+ /**
+ * Configuration bits:
+ * bit[1:0]: Size of data (1,2,3 or 4 bytes). 1 Byte <=> b00
+ * bit[2]: Set this bit to 1 to abort a transfered packet
+ * bit[3]: Signals to VcInterface the start of a new telemetry packet
+ */
+ static const uint32_t CONFIG_START = 0x8;
+
+ /**
+ * Writing this word to the VcInterface base address signals to the virtual channel interface
+ * that a complete tm packet has been transferred.
+ */
+ static const uint32_t CONFIG_END = 0x0;
+
+ /**
+ * Writing to this offset within the memory space of a virtual channel will insert data for
+ * encoding to the external buffer memory of the PTME IP Core.
+ * The address offset is 0x400 (= 4 * 256)
+ */
+ static const int DATA_REG_OFFSET = 256;
+
+ LinuxLibgpioIF* gpioComIF = nullptr;
+
+ /** Pulled to low when virtual channel not ready to receive data */
+ gpioId_t papbBusyId = gpio::NO_GPIO;
+ /** High when external buffer memory of virtual channel is empty */
+ gpioId_t papbEmptyId = gpio::NO_GPIO;
+
+ uint32_t* vcBaseReg = nullptr;
+
+ uint32_t vcOffset = 0;
+
+ /**
+ * @brief This function sends the config byte to the virtual channel of the PTME IP Core
+ * to initiate a packet transfer.
+ */
+ void startPacketTransfer();
+
+ /**
+ * @brief This function sends the config byte to the virtual channel interface of the PTME
+ * IP Core to signal the end of a packet transfer.
+ */
+ void endPacketTransfer();
+
+ /**
+ * @brief This function reads the papb busy signal indicating whether the virtual channel
+ * interface is ready to receive more data or not. PAPB is ready when
+ * PAPB_Busy_N == '1'.
+ *
+ * @return RETURN_OK when ready to receive data else PAPB_BUSY.
+ */
+ ReturnValue_t pollPapbBusySignal();
+
+ /**
+ * @brief This function can be used for debugging to check whether there are packets in
+ * the packet buffer of the virtual channel or not.
+ */
+ void isVcInterfaceBufferEmpty();
+
+ /**
+ * @brief This function sends a complete telemetry transfer frame data field (1105 bytes)
+ * to the papb interface of the PTME IP Core. Can be used to test the implementation.
+ */
+ ReturnValue_t sendTestFrame();
+};
+
+#endif /* LINUX_OBC_PAPBVCINTERFACE_H_ */
diff --git a/linux/obc/Ptme.cpp b/linux/obc/Ptme.cpp
new file mode 100644
index 00000000..80f9945c
--- /dev/null
+++ b/linux/obc/Ptme.cpp
@@ -0,0 +1,73 @@
+#include
+#include
+
+#include
+#include "fsfw/serviceinterface/ServiceInterface.h"
+#include "PtmeConfig.h"
+
+Ptme::Ptme(object_id_t objectId) :
+ SystemObject(objectId) {
+}
+
+Ptme::~Ptme() {
+}
+
+ReturnValue_t Ptme::initialize() {
+
+ int fd = open(PtmeConfig::UIO_DEVICE_FILE, O_RDWR);
+ if (fd < 1) {
+ sif::warning << "Ptme::initialize: Invalid UIO device file" << std::endl;
+ return RETURN_FAILED;
+ }
+
+ /**
+ * Map uio device in virtual address space
+ * PROT_WRITE: Map uio device in writable only mode
+ */
+ ptmeBaseAddress = static_cast(mmap(NULL, MAP_SIZE, PROT_WRITE,
+ MAP_SHARED, fd, 0));
+
+ if (ptmeBaseAddress == MAP_FAILED) {
+ sif::error << "Ptme::initialize: Failed to map uio address" << std::endl;
+ return RETURN_FAILED;
+ }
+
+ VcInterfaceMapIter iter;
+ for (iter = vcInterfaceMap.begin(); iter != vcInterfaceMap.end(); iter++) {
+ iter->second->setRegisterAddress(ptmeBaseAddress);
+ }
+
+ return RETURN_OK;
+}
+
+ReturnValue_t Ptme::writeToVc(uint8_t vcId, const uint8_t * data, size_t size) {
+ ReturnValue_t result = RETURN_OK;
+ VcInterfaceMapIter vcInterfaceMapIter = vcInterfaceMap.find(vcId);
+ if (vcInterfaceMapIter == vcInterfaceMap.end()) {
+ sif::warning << "Ptme::writeToVc: No virtual channel interface found for the virtual "
+ "channel with id " << static_cast(vcId) << std::endl;
+ return UNKNOWN_VC_ID;
+ }
+ result = vcInterfaceMapIter->second->write(data, size);
+ return result;
+}
+
+void Ptme::addVcInterface(VcId_t vcId, VcInterfaceIF* vc) {
+
+ if (vcId > config::NUMBER_OF_VIRTUAL_CHANNELS) {
+ sif::warning << "Ptme::addVcInterface: Invalid virtual channel ID" << std::endl;
+ return;
+ }
+
+ if (vc == nullptr) {
+ sif::warning << "Ptme::addVcInterface: Invalid virtual channel interface" << std::endl;
+ return;
+ }
+
+ auto status = vcInterfaceMap.emplace(vcId, vc);
+ if (status.second == false) {
+ sif::warning << "Ptme::addVcInterface: Failed to add virtual channel interface to "
+ "virtual channel map" << std::endl;
+ return;
+ }
+}
diff --git a/linux/obc/Ptme.h b/linux/obc/Ptme.h
new file mode 100644
index 00000000..be687e17
--- /dev/null
+++ b/linux/obc/Ptme.h
@@ -0,0 +1,91 @@
+#ifndef LINUX_OBC_PTME_H_
+#define LINUX_OBC_PTME_H_
+
+#include "OBSWConfig.h"
+#include "linux/obc/PtmeIF.h"
+#include "linux/obc/VcInterfaceIF.h"
+#include
+#include
+#include "fsfw/returnvalues/HasReturnvaluesIF.h"
+
+#include
+#include
+
+/**
+ * @brief This class handles the interfacing to the telemetry (PTME) IP core responsible for the
+ * encoding of telemetry packets according to the CCSDS standards CCSDS 131.0-B-3 (TM Synchro-
+ * nization and channel coding) and CCSDS 132.0-B-2 (TM Space Data Link Protocoll).
+ * The IP cores are implemented on the programmable logic and are accessible through the
+ * linux UIO driver.
+ */
+class Ptme : public PtmeIF,
+ public SystemObject,
+ public HasReturnvaluesIF {
+public:
+
+ using VcId_t = uint8_t;
+
+ /**
+ * @brief Constructor
+ *
+ * @param objectId
+ */
+ Ptme(object_id_t objectId);
+ virtual ~Ptme();
+
+ ReturnValue_t initialize() override;
+ ReturnValue_t writeToVc(uint8_t vcId, const uint8_t* data, size_t size) override;
+
+ /**
+ * @brief This function adds the reference to a virtual channel interface to the vcInterface
+ * map.
+ */
+ void addVcInterface(VcId_t vcId, VcInterfaceIF* vc);
+
+private:
+
+ static const uint8_t INTERFACE_ID = CLASS_ID::PTME;
+
+ static const ReturnValue_t UNKNOWN_VC_ID = MAKE_RETURN_CODE(0xA0);
+
+#if BOARD_TE0720 == 1
+ /** Size of mapped address space */
+ static const int MAP_SIZE = 0x40000;
+#else
+ /** Size of mapped address space */
+ static const int MAP_SIZE = 0x40000;
+#endif /* BOARD_TE0720 == 1 */
+
+ /**
+ * Configuration bits:
+ * bit[1:0]: Size of data (1,2,3 or 4 bytes). 1 Byte <=> b00
+ * bit[2]: Set this bit to 1 to abort a transfered packet
+ * bit[3]: Signals to PTME the start of a new telemetry packet
+ */
+ static const uint32_t PTME_CONFIG_START = 0x8;
+
+ /**
+ * Writing this word to the ptme base address signals to the PTME that a complete tm packet has
+ * been transferred.
+ */
+ static const uint32_t PTME_CONFIG_END = 0x0;
+
+ /**
+ * Writing to this offset within the PTME memory space will insert data for encoding to the
+ * PTME IP core.
+ * The address offset is 0x400 (= 4 * 256)
+ */
+ static const int PTME_DATA_REG_OFFSET = 256;
+
+ /** The file descriptor of the UIO driver */
+ int fd;
+
+ uint32_t* ptmeBaseAddress = nullptr;
+
+ using VcInterfaceMap = std::unordered_map;
+ using VcInterfaceMapIter = VcInterfaceMap::iterator;
+
+ VcInterfaceMap vcInterfaceMap;
+};
+
+#endif /* LINUX_OBC_PTME_H_ */
diff --git a/linux/obc/PtmeConfig.h b/linux/obc/PtmeConfig.h
new file mode 100644
index 00000000..f4468570
--- /dev/null
+++ b/linux/obc/PtmeConfig.h
@@ -0,0 +1,26 @@
+#ifndef LINUX_OBC_PTMECONFIG_H_
+#define LINUX_OBC_PTMECONFIG_H_
+
+#include "fsfw/returnvalues/HasReturnvaluesIF.h"
+#include
+
+
+/**
+ * @brief Configuration parameters derived from FPGA design and device tree.
+ *
+ * @author J. Meier
+ */
+namespace PtmeConfig {
+ /**
+ * Offset of virtual channels mapped into address space
+ * 0x10000 = (0x4000 * 4)
+ */
+ static const uint32_t VC0_OFFSETT = 0;
+ static const uint32_t VC1_OFFSETT = 0x4000;
+ static const uint32_t VC2_OFFSETT = 0x8000;
+ static const uint32_t VC3_OFFSETT = 0xC000;
+
+ static const char UIO_DEVICE_FILE[] = "/dev/uio0";
+};
+
+#endif /* LINUX_OBC_PTMECONFIG_H_ */
diff --git a/linux/obc/PtmeIF.h b/linux/obc/PtmeIF.h
new file mode 100644
index 00000000..53d9498b
--- /dev/null
+++ b/linux/obc/PtmeIF.h
@@ -0,0 +1,28 @@
+#ifndef LINUX_OBC_PTMEIF_H_
+#define LINUX_OBC_PTMEIF_H_
+
+#include "fsfw/returnvalues/HasReturnvaluesIF.h"
+
+
+/**
+ * @brief Interface class for managing the PTME IP Core implemented in the programmable logic.
+ *
+ * @details PTME IP Core: https://www.esa.int/Enabling_Support/Space_Engineering_Technology/
+ * Microelectronics/PTME
+ * @author J. Meier
+ */
+class PtmeIF {
+public:
+ virtual ~PtmeIF(){};
+
+ /**
+ * @brief Implements to function to write to a specific virtual channel.
+ *
+ * @param vcId Virtual channel to write to
+ * @param data Pointer to buffer holding the data to write
+ * @param size Number of bytes to write
+ */
+ virtual ReturnValue_t writeToVc(uint8_t vcId, const uint8_t* data, size_t size) = 0;
+};
+
+#endif /* LINUX_OBC_PTMEIF_H_ */
diff --git a/linux/obc/VcInterfaceIF.h b/linux/obc/VcInterfaceIF.h
new file mode 100644
index 00000000..28b8d1f1
--- /dev/null
+++ b/linux/obc/VcInterfaceIF.h
@@ -0,0 +1,29 @@
+#ifndef LINUX_OBC_VCINTERFACEIF_H_
+#define LINUX_OBC_VCINTERFACEIF_H_
+
+#include
+#include "fsfw/returnvalues/HasReturnvaluesIF.h"
+
+/**
+ * @brief Interface class for managing different virtual channels of the PTME IP core implemented
+ * in the programmable logic.
+ *
+ * @author J. Meier
+ */
+class VcInterfaceIF {
+public:
+ virtual ~VcInterfaceIF(){};
+
+ /**
+ * @brief Implememts the functionality to write data in the virtual channel of the PTME IP
+ * Core.
+ *
+ * @param data Pointer to buffer holding the data to write
+ * @param size Number of bytes to write
+ */
+ virtual ReturnValue_t write(const uint8_t* data, size_t size) = 0;
+
+ virtual void setRegisterAddress(uint32_t* ptmeBaseAddress) = 0;
+};
+
+#endif /* LINUX_OBC_VCINTERFACEIF_H_ */
diff --git a/mission/CMakeLists.txt b/mission/CMakeLists.txt
index 46f562a5..876201a8 100644
--- a/mission/CMakeLists.txt
+++ b/mission/CMakeLists.txt
@@ -2,3 +2,4 @@ add_subdirectory(core)
add_subdirectory(devices)
add_subdirectory(utility)
add_subdirectory(memory)
+add_subdirectory(tmtc)
diff --git a/mission/tmtc/CCSDSHandler.cpp b/mission/tmtc/CCSDSHandler.cpp
new file mode 100644
index 00000000..95d557ee
--- /dev/null
+++ b/mission/tmtc/CCSDSHandler.cpp
@@ -0,0 +1,122 @@
+#include "fsfw/serviceinterface/ServiceInterface.h"
+#include "fsfw/serviceinterface/serviceInterfaceDefintions.h"
+#include "fsfw/objectmanager/ObjectManager.h"
+#include "fsfw/ipc/QueueFactory.h"
+
+#include "CCSDSHandler.h"
+
+CCSDSHandler::CCSDSHandler(object_id_t objectId, object_id_t ptmeId) :
+ SystemObject(objectId), ptmeId(ptmeId), parameterHelper(this) {
+ commandQueue = QueueFactory::instance()->createMessageQueue(QUEUE_SIZE);
+}
+
+CCSDSHandler::~CCSDSHandler() {
+}
+
+ReturnValue_t CCSDSHandler::performOperation(uint8_t operationCode) {
+ readCommandQueue();
+ handleTelemetry();
+ handleTelecommands();
+ return RETURN_OK;
+}
+
+void CCSDSHandler::handleTelemetry() {
+ VirtualChannelMapIter iter;
+ for (iter = virtualChannelMap.begin(); iter != virtualChannelMap.end(); iter++) {
+ iter->second->performOperation();
+ }
+}
+
+void CCSDSHandler::handleTelecommands() {
+
+}
+
+ReturnValue_t CCSDSHandler::initialize() {
+ ReturnValue_t result = RETURN_OK;
+ PtmeIF* ptme = ObjectManager::instance()->get(ptmeId);
+ if (ptme == nullptr) {
+ sif::warning << "Invalid PTME object" << std::endl;
+ return ObjectManagerIF::CHILD_INIT_FAILED;
+ }
+
+ result = parameterHelper.initialize();
+ if (result != HasReturnvaluesIF::RETURN_OK) {
+ return result;
+ }
+
+ VirtualChannelMapIter iter;
+ for (iter = virtualChannelMap.begin(); iter != virtualChannelMap.end(); iter++) {
+ result = iter->second->initialize();
+ if (result != RETURN_OK) {
+ return result;
+ }
+ iter->second->setPtmeObject(ptme);
+ }
+
+ return result;
+}
+
+void CCSDSHandler::readCommandQueue(void) {
+ CommandMessage commandMessage;
+ ReturnValue_t result = RETURN_FAILED;
+
+ result = commandQueue->receiveMessage(&commandMessage);
+ if (result == RETURN_OK) {
+ result = parameterHelper.handleParameterMessage(&commandMessage);
+ if (result == RETURN_OK) {
+ return;
+ }
+ CommandMessage reply;
+ reply.setReplyRejected(CommandMessage::UNKNOWN_COMMAND,
+ commandMessage.getCommand());
+ commandQueue->reply(&reply);
+ }
+}
+
+MessageQueueId_t CCSDSHandler::getCommandQueue() const {
+ return commandQueue->getId();
+}
+
+void CCSDSHandler::addVirtualChannel(VcId_t vcId, VirtualChannel* virtualChannel) {
+ if (vcId > config::NUMBER_OF_VIRTUAL_CHANNELS) {
+ sif::warning << "CCSDSHandler::addVirtualChannel: Invalid virtual channel ID" << std::endl;
+ return;
+ }
+
+ if (virtualChannel == nullptr) {
+ sif::warning << "CCSDSHandler::addVirtualChannel: Invalid virtual channel interface" << std::endl;
+ return;
+ }
+
+ auto status = virtualChannelMap.emplace(vcId, virtualChannel);
+ if (status.second == false) {
+ sif::warning << "CCSDSHandler::addVirtualChannel: Failed to add virtual channel to "
+ "virtual channel map" << std::endl;
+ return;
+ }
+}
+
+MessageQueueId_t CCSDSHandler::getReportReceptionQueue(uint8_t virtualChannel) {
+ if (virtualChannel < config::NUMBER_OF_VIRTUAL_CHANNELS) {
+ VirtualChannelMapIter iter = virtualChannelMap.find(virtualChannel);
+ if (iter != virtualChannelMap.end()) {
+ return iter->second->getReportReceptionQueue();
+ }
+ else {
+ sif::warning << "CCSDSHandler::getReportReceptionQueue: Virtual channel with ID "
+ << static_cast(virtualChannel) << " not in virtual channel map"
+ << std::endl;
+ return MessageQueueIF::NO_QUEUE;
+ }
+ } else {
+ sif::debug << "CCSDSHandler::getReportReceptionQueue: Invalid virtual channel requested";
+
+ }
+ return MessageQueueIF::NO_QUEUE;
+}
+
+ReturnValue_t CCSDSHandler::getParameter(uint8_t domainId, uint8_t uniqueIdentifier,
+ ParameterWrapper *parameterWrapper, const ParameterWrapper *newValues,
+ uint16_t startAtIndex) {
+ return RETURN_OK;
+}
diff --git a/mission/tmtc/CCSDSHandler.h b/mission/tmtc/CCSDSHandler.h
new file mode 100644
index 00000000..05693a38
--- /dev/null
+++ b/mission/tmtc/CCSDSHandler.h
@@ -0,0 +1,75 @@
+#ifndef CCSDSHANDLER_H_
+#define CCSDSHANDLER_H_
+
+#include "OBSWConfig.h"
+#include "fsfw/objectmanager/SystemObject.h"
+#include "fsfw/tasks/ExecutableObjectIF.h"
+#include "fsfw/returnvalues/HasReturnvaluesIF.h"
+#include "fsfw/parameters/ParameterHelper.h"
+#include "VirtualChannel.h"
+#include
+
+/**
+ * @brief This class handles the data exchange with the CCSDS IP cores implemented in the
+ * programmable logic of the Q7S.
+ *
+ * @author J. Meier
+ */
+class CCSDSHandler: public SystemObject,
+ public ExecutableObjectIF,
+ public AcceptsTelemetryIF,
+ public HasReturnvaluesIF,
+ public ReceivesParameterMessagesIF {
+public:
+
+ using VcId_t = uint8_t;
+
+ /**
+ * @brief Constructor
+ *
+ * @param objectId Object ID of the CCSDS handler
+ * @param ptmeId Object ID of the PTME object providing access to the PTME IP Core.
+ */
+ CCSDSHandler(object_id_t objectId, object_id_t ptmeId);
+
+ ~CCSDSHandler();
+
+ ReturnValue_t performOperation(uint8_t operationCode = 0) override;
+ ReturnValue_t initialize();
+ MessageQueueId_t getCommandQueue() const;
+
+ /**
+ * @brief Function to add a virtual channel
+ *
+ * @param virtualChannelId ID of the virtual channel to add
+ * @param virtualChannel Pointer to virtual channel object
+ */
+ void addVirtualChannel(VcId_t virtualChannelId, VirtualChannel* virtualChannel);
+
+ MessageQueueId_t getReportReceptionQueue(uint8_t virtualChannel = 0);
+ ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueIdentifier,
+ ParameterWrapper *parameterWrapper, const ParameterWrapper *newValues,
+ uint16_t startAtIndex);
+
+private:
+
+ static const uint32_t QUEUE_SIZE = config::CCSDS_HANDLER_QUEUE_SIZE;
+
+ using VirtualChannelMap = std::unordered_map;
+ using VirtualChannelMapIter = VirtualChannelMap::iterator;
+
+ VirtualChannelMap virtualChannelMap;
+
+ // Object ID of PTME object
+ object_id_t ptmeId;
+
+ MessageQueueIF* commandQueue = nullptr;
+
+ ParameterHelper parameterHelper;
+
+ void readCommandQueue(void);
+ void handleTelemetry();
+ void handleTelecommands();
+};
+
+#endif /* CCSDSHANDLER_H_ */
diff --git a/mission/tmtc/CMakeLists.txt b/mission/tmtc/CMakeLists.txt
new file mode 100644
index 00000000..7da87b6c
--- /dev/null
+++ b/mission/tmtc/CMakeLists.txt
@@ -0,0 +1,6 @@
+target_sources(${TARGET_NAME} PUBLIC
+ CCSDSHandler.cpp
+ VirtualChannel.cpp
+)
+
+
diff --git a/mission/tmtc/VirtualChannel.cpp b/mission/tmtc/VirtualChannel.cpp
new file mode 100644
index 00000000..3423692b
--- /dev/null
+++ b/mission/tmtc/VirtualChannel.cpp
@@ -0,0 +1,69 @@
+#include "CCSDSHandler.h"
+#include "VirtualChannel.h"
+#include "OBSWConfig.h"
+
+#include "fsfw/serviceinterface/ServiceInterfaceStream.h"
+#include "fsfw/objectmanager/ObjectManager.h"
+#include "fsfw/tmtcservices/TmTcMessage.h"
+#include "fsfw/ipc/QueueFactory.h"
+
+
+VirtualChannel::VirtualChannel(uint8_t vcId, uint32_t tmQueueDepth) :
+ vcId(vcId) {
+ tmQueue = QueueFactory::instance()->createMessageQueue(tmQueueDepth,
+ MessageQueueMessage::MAX_MESSAGE_SIZE);
+}
+
+ReturnValue_t VirtualChannel::initialize() {
+ tmStore = ObjectManager::instance()->get(objects::TM_STORE);
+ if(tmStore == nullptr) {
+ sif::error << "VirtualChannel::initialize: Failed to get tm store" << std::endl;
+ return RETURN_FAILED;
+ }
+ return RETURN_OK;
+}
+
+ReturnValue_t VirtualChannel::performOperation() {
+ ReturnValue_t result = RETURN_OK;
+ TmTcMessage message;
+
+ while(tmQueue->receiveMessage(&message) == RETURN_OK) {
+ store_address_t storeId = message.getStorageId();
+ const uint8_t* data = nullptr;
+ size_t size = 0;
+ result = tmStore->getData(storeId, &data, &size);
+ if (result != RETURN_OK) {
+ sif::warning << "VirtualChannel::performOperation: Failed to read data from IPC store"
+ << std::endl;
+ tmStore->deleteData(storeId);
+ return result;
+ }
+
+ if (linkIsUp) {
+ result = ptme->writeToVc(vcId, data, size);
+ }
+
+ tmStore->deleteData(storeId);
+
+ if (result != RETURN_OK) {
+ return result;
+ }
+ }
+ return result;
+}
+
+MessageQueueId_t VirtualChannel::getReportReceptionQueue(uint8_t virtualChannel) {
+ return tmQueue->getId();
+}
+
+void VirtualChannel::setPtmeObject(PtmeIF* ptme_) {
+ if (ptme_ == nullptr) {
+ sif::warning << "VirtualChannel::setPtmeObject: Invalid ptme object" << std::endl;
+ return;
+ }
+ ptme = ptme_;
+}
+
+void VirtualChannel::setLinkState(bool linkIsUp) {
+ linkIsUp = linkIsUp;
+}
diff --git a/mission/tmtc/VirtualChannel.h b/mission/tmtc/VirtualChannel.h
new file mode 100644
index 00000000..d7d95cb7
--- /dev/null
+++ b/mission/tmtc/VirtualChannel.h
@@ -0,0 +1,58 @@
+#ifndef VIRTUALCHANNEL_H_
+#define VIRTUALCHANNEL_H_
+
+#include "OBSWConfig.h"
+#include "fsfw/tmtcservices/AcceptsTelemetryIF.h"
+#include "fsfw/returnvalues/HasReturnvaluesIF.h"
+#include
+#include
+
+/**
+ * @brief This class represents a virtual channel. Sending a tm message to an object of this class
+ * will forward the tm packet to the respective virtual channel of the PTME IP Core.
+ *
+ * @author J. Meier
+ */
+class VirtualChannel: public AcceptsTelemetryIF, public HasReturnvaluesIF {
+ public:
+ /**
+ * @brief Constructor
+ *
+ * @param vcId The virtual channel id assigned to this object
+ * @param tmQueueDepth Queue depth of queue receiving telemetry from other objects
+ */
+ VirtualChannel(uint8_t vcId, uint32_t tmQueueDepth);
+
+ ReturnValue_t initialize();
+ MessageQueueId_t getReportReceptionQueue(uint8_t virtualChannel = 0) override;
+ ReturnValue_t performOperation();
+
+ /**
+ * @brief Sets the PTME object which handles access to the PTME IP Core.
+ *
+ * @param ptme Pointer to ptme object
+ */
+ void setPtmeObject(PtmeIF* ptme_);
+
+ /**
+ * @brief Can be used by the owner to set the link state. Packets will be discarded if link
+ * to ground station is down.
+ */
+ void setLinkState(bool linkIsUp);
+
+private:
+
+ PtmeIF* ptme = nullptr;
+ MessageQueueIF* tmQueue = nullptr;
+ uint8_t vcId;
+
+#if OBSW_LINK_IS_UP == 1
+ bool linkIsUp = true;
+#else
+ bool linkIsUp = false;
+#endif /* OBSW_LINK_IS_UP == 1 */
+
+ StorageManagerIF* tmStore = nullptr;
+};
+
+#endif /* VIRTUALCHANNEL_H_ */
diff --git a/mission/utility/TmFunnel.cpp b/mission/utility/TmFunnel.cpp
index c52fa6f9..4b9eebdf 100644
--- a/mission/utility/TmFunnel.cpp
+++ b/mission/utility/TmFunnel.cpp
@@ -1,3 +1,4 @@
+#include "OBSWConfig.h"
#include
#include
#include
@@ -45,7 +46,7 @@ ReturnValue_t TmFunnel::performOperation(uint8_t operationCode) {
ReturnValue_t TmFunnel::handlePacket(TmTcMessage* message) {
uint8_t* packetData = nullptr;
size_t size = 0;
- ReturnValue_t result = tmPool->modifyData(message->getStorageId(),
+ ReturnValue_t result = tmStore->modifyData(message->getStorageId(),
&packetData, &size);
if(result != HasReturnvaluesIF::RETURN_OK){
return result;
@@ -59,7 +60,7 @@ ReturnValue_t TmFunnel::handlePacket(TmTcMessage* message) {
result = tmQueue->sendToDefault(message);
if(result != HasReturnvaluesIF::RETURN_OK){
- tmPool->deleteData(message->getStorageId());
+ tmStore->deleteData(message->getStorageId());
sif::error << "TmFunnel::handlePacket: Error sending to downlink "
"handler" << std::endl;
return result;
@@ -68,7 +69,7 @@ ReturnValue_t TmFunnel::handlePacket(TmTcMessage* message) {
if(storageDestination != objects::NO_OBJECT) {
result = storageQueue->sendToDefault(message);
if(result != HasReturnvaluesIF::RETURN_OK){
- tmPool->deleteData(message->getStorageId());
+ tmStore->deleteData(message->getStorageId());
sif::error << "TmFunnel::handlePacket: Error sending to storage "
"handler" << std::endl;
return result;
@@ -79,8 +80,8 @@ ReturnValue_t TmFunnel::handlePacket(TmTcMessage* message) {
ReturnValue_t TmFunnel::initialize() {
- tmPool = ObjectManager::instance()->get(objects::TM_STORE);
- if(tmPool == nullptr) {
+ tmStore = ObjectManager::instance()->get(objects::TM_STORE);
+ if(tmStore == nullptr) {
sif::error << "TmFunnel::initialize: TM store not set."
<< std::endl;
sif::error << "Make sure the tm store is set up properly"
@@ -97,7 +98,13 @@ ReturnValue_t TmFunnel::initialize() {
"properly and implements AcceptsTelemetryIF" << std::endl;
return ObjectManagerIF::CHILD_INIT_FAILED;
}
+
+#if OBSW_TM_TO_PTME == 1
+ // Live TM will be sent via the virtual channel 0
+ tmQueue->setDefaultDestination(tmTarget->getReportReceptionQueue(config::LIVE_TM));
+#else
tmQueue->setDefaultDestination(tmTarget->getReportReceptionQueue());
+#endif /* OBSW_TM_TO_PTME == 1 */
// Storage destination is optional.
if(storageDestination == objects::NO_OBJECT) {
diff --git a/mission/utility/TmFunnel.h b/mission/utility/TmFunnel.h
index 2d4575a0..b85a09e0 100644
--- a/mission/utility/TmFunnel.h
+++ b/mission/utility/TmFunnel.h
@@ -41,7 +41,7 @@ private:
MessageQueueIF* tmQueue = nullptr;
MessageQueueIF* storageQueue = nullptr;
- StorageManagerIF* tmPool = nullptr;
+ StorageManagerIF* tmStore = nullptr;
uint32_t messageDepth = 0;
ReturnValue_t handlePacket(TmTcMessage* message);
diff --git a/scripts/q7s-port.sh b/scripts/q7s-port.sh
index e475e8b7..e350d4c2 100755
--- a/scripts/q7s-port.sh
+++ b/scripts/q7s-port.sh
@@ -2,10 +2,12 @@
echo "Setting up all Q7S ports"
echo "-L 1534:192.168.133.10:1534 for connection to TCF agent"
echo "-L 1535:192.168.133.10:22 for file transfers"
-echo "-L 1536:192.168.133.10:7301 for TMTC commanding"
+echo "-L 1536:192.168.133.10:7301 for TMTC commanding using the TCP/IP IF"
+echo "-L 1537:127.0.0.1:7100 for TMTC commanding using the CCSDS IF"
ssh -L 1534:192.168.133.10:1534 \
-L 1535:192.168.133.10:22 \
-L 1536:192.168.133.10:7301 \
+ -L 1537:127.0.0.1:7100 \
eive@2001:7c0:2018:1099:babe:0:e1fe:f1a5 \
-t 'CONSOLE_PREFIX="[Q7S Tunnel]" /bin/bash'
diff --git a/tmtc b/tmtc
index 56717935..7b49babb 160000
--- a/tmtc
+++ b/tmtc
@@ -1 +1 @@
-Subproject commit 56717935e6d90e8a501894f44cd250aec8d12d03
+Subproject commit 7b49babb1febb3959f0f817aa4b4ce4c32d6d21c