diff --git a/README.md b/README.md index 38df4443..44d699d1 100644 --- a/README.md +++ b/README.md @@ -277,7 +277,7 @@ Copying a file from Q7S to flatsat PC scp -P 22 root@192.168.133.10:/tmp/kernel-config /tmp ```` -From a windows machine files can be copied with putty tools +From a windows machine files can be copied with putty tools (note: use IPv4 address) ```` pscp -scp -P 22 eive@192.168.199.227:/example-file ```` @@ -579,4 +579,23 @@ to install the required GPIO libraries before cloning the system root folder. When using Eclipse, there are two special build variables in the project properties → C/C++ Build → Build Variables called `Q7S_SYSROOT` or `RPI_SYSROOT`. You can set the sysroot path in those variables to get any additional includes like `gpiod.h` in the -Eclipse indexer. \ No newline at end of file +Eclipse indexer. + +## Xilinx UARTLIE +Get info about ttyUL* devices +```` +cat /proc/tty/driver +```` + +## I2C +Getting information about I2C device +```` +ls /sys/class/i2c-dev/i2c-0/device/device/driver +```` +This shows the memory mapping of /dev/i2c-0 + +## Useful Q7S Linux Commands +Rebooting currently running image: +```` +xsc_boot_copy -r +```` \ No newline at end of file diff --git a/bsp_q7s/InitMission.cpp b/bsp_q7s/InitMission.cpp index 3fe0ba0c..e5d33fdc 100644 --- a/bsp_q7s/InitMission.cpp +++ b/bsp_q7s/InitMission.cpp @@ -135,6 +135,8 @@ void initmission::initTasks() { initmission::printAddObjectError("PUS_17", objects::PUS_SERVICE_17_TEST); } +#if TE0720 == 0 + //TODO: Add handling of missed deadlines /* Polling Sequence Table Default */ #if Q7S_ADD_SPI_TEST == 0 @@ -147,7 +149,6 @@ void initmission::initTasks() { } #endif -#if TE0720 == 0 FixedTimeslotTaskIF* gomSpacePstTask = factory-> createFixedTimeslotTask("GS_PST_TASK", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE*8, 3.0, missedDeadlineFunc); @@ -155,6 +156,15 @@ void initmission::initTasks() { if(result != HasReturnvaluesIF::RETURN_OK) { sif::error << "InitMission::initTasks: GomSpace PST initialization failed!" << std::endl; } + +#else + FixedTimeslotTaskIF * pollingSequenceTableTE0720 = factory->createFixedTimeslotTask( + "PST_TASK_TE0720", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE * 4, 3.0, + missedDeadlineFunc); + result = pst::pollingSequenceTE0720(pollingSequenceTableTE0720); + if (result != HasReturnvaluesIF::RETURN_OK) { + sif::error << "InitMission::initTasks: Creating TE0720 PST failed!" << std::endl; + } #endif #if OBSW_ADD_TEST_CODE == 1 @@ -184,12 +194,11 @@ void initmission::initTasks() { udpBridgeTask->startTask(); udpPollingTask->startTask(); -#if TE0720 == 0 +#if TE0720 == 0 && Q7S_ADD_SPI_TEST == 0 gomSpacePstTask->startTask(); -#endif - -#if Q7S_ADD_SPI_TEST == 0 pollingSequenceTableTaskDefault->startTask(); +#elif TE0720 == 1 && Q7S_ADD_SPI_TEST == 0 + pollingSequenceTableTE0720->startTask(); #endif pusVerification->startTask(); diff --git a/bsp_q7s/ObjectFactory.cpp b/bsp_q7s/ObjectFactory.cpp index 153f6786..9102f79e 100644 --- a/bsp_q7s/ObjectFactory.cpp +++ b/bsp_q7s/ObjectFactory.cpp @@ -25,9 +25,11 @@ #include #include #include +#include #include #include +#include #include #include @@ -406,17 +408,23 @@ void ObjectFactory::produce(){ #endif /* Q7S_ADD_RTS_DEVICES == 1 */ + I2cCookie* imtqI2cCookie = new I2cCookie(addresses::IMTQ, IMTQ::MAX_REPLY_SIZE, + std::string("/dev/i2c-0")); + IMTQHandler* imtqHandler = new IMTQHandler(objects::IMTQ_HANDLER, objects::I2C_COM_IF, imtqI2cCookie); + imtqHandler->setStartUpImmediately(); + + UartCookie* plocUartCookie = new UartCookie(std::string("/dev/ttyUL3"), 115200, + PLOC::MAX_REPLY_SIZE); + PlocHandler* plocHandler = new PlocHandler(objects::PLOC_HANDLER, objects::UART_COM_IF, + plocUartCookie); + plocHandler->setStartUpImmediately(); + #endif /* TE0720 == 0 */ new UdpTmTcBridge(objects::UDP_BRIDGE, objects::CCSDS_PACKET_DISTRIBUTOR, objects::TM_STORE, objects::TC_STORE); new UdpTcPollingTask(objects::UDP_POLLING_TASK, objects::UDP_BRIDGE); - I2cCookie* imtqI2cCookie = new I2cCookie(addresses::IMTQ, IMTQ::MAX_REPLY_SIZE, - std::string("/dev/i2c-0")); - IMTQHandler* imtqHandler = new IMTQHandler(objects::IMTQ_HANDLER, objects::I2C_COM_IF, imtqI2cCookie); - imtqHandler->setStartUpImmediately(); - #if TE0720 == 1 && TEST_LIBGPIOD == 1 /* Configure MIO0 as input */ GpiodRegular gpioConfigMio0(std::string("gpiochip0"), 0, @@ -426,11 +434,18 @@ void ObjectFactory::produce(){ new LibgpiodTest(objects::LIBGPIOD_TEST, objects::GPIO_IF, gpioCookie); #elif TE0720 == 1 /* Configuration for MIO0 on TE0720-03-1CFA */ - GpiodRegular gpioConfigForDummyHeater(std::string("gpiochip0"), 0, - std::string("Heater0"), gpio::OUT, 0); - heaterGpiosCookie->addGpio(gpioIds::HEATER_0, gpioConfigForDummyHeater); - new HeaterHandler(objects::HEATER_HANDLER, objects::GPIO_IF, heaterGpiosCookie, - objects::PCDU_HANDLER, pcduSwitches::TCS_BOARD_8V_HEATER_IN); + GpiodRegular* heaterGpio = new GpiodRegular(std::string("gpiochip0"), 0, std::string("MIO0"), gpio::IN, 0); + GpioCookie* gpioCookie = new GpioCookie; + gpioCookie->addGpio(gpioIds::HEATER_0, heaterGpio); + new HeaterHandler(objects::HEATER_HANDLER, objects::GPIO_IF, gpioCookie, objects::PCDU_HANDLER, + pcduSwitches::TCS_BOARD_8V_HEATER_IN); + + UartCookie* plocUartCookie = new UartCookie(std::string("/dev/ttyPS1"), 115200, + PLOC::MAX_REPLY_SIZE); + /* Testing PlocHandler on TE0720-03-1CFA */ + PlocHandler* plocHandler = new PlocHandler(objects::PLOC_HANDLER, objects::UART_COM_IF, + plocUartCookie); + plocHandler->setStartUpImmediately(); #endif #if Q7S_ADD_SPI_TEST == 1 diff --git a/bsp_q7s/devices/HeaterHandler.cpp b/bsp_q7s/devices/HeaterHandler.cpp index 21a4a3f4..6c5af7be 100644 --- a/bsp_q7s/devices/HeaterHandler.cpp +++ b/bsp_q7s/devices/HeaterHandler.cpp @@ -58,8 +58,9 @@ ReturnValue_t HeaterHandler::initialize() { if(mainLineSwitcherObjectId != objects::NO_OBJECT) { mainLineSwitcher = objectManager->get(mainLineSwitcherObjectId); if (mainLineSwitcher == nullptr) { - sif::error << "HeaterHandler::initialize: Main line switcher failed to fetch object" - << "from object ID." << std::endl; + sif::error + << "HeaterHandler::initialize: Failed to get main line switcher. Make sure " + << "main line switcher object is initialized." << std::endl; return ObjectManagerIF::CHILD_INIT_FAILED; } } diff --git a/fsfwconfig/events/subsystemIdRanges.h b/fsfwconfig/events/subsystemIdRanges.h index b2e9aaac..b33ee807 100644 --- a/fsfwconfig/events/subsystemIdRanges.h +++ b/fsfwconfig/events/subsystemIdRanges.h @@ -21,7 +21,8 @@ enum: uint8_t { MGM_RM3100, PCDU_HANDLER, HEATER_HANDLER, - SA_DEPL_HANDLER + SA_DEPL_HANDLER, + PLOC_HANDLER }; } diff --git a/fsfwconfig/objects/systemObjectList.h b/fsfwconfig/objects/systemObjectList.h index 110b5c17..72dcfc21 100644 --- a/fsfwconfig/objects/systemObjectList.h +++ b/fsfwconfig/objects/systemObjectList.h @@ -52,6 +52,7 @@ namespace objects { GYRO_2_L3G_HANDLER = 0x44000013, IMTQ_HANDLER = 0x44000014, + PLOC_HANDLER = 0x44000015, /* Custom device handler */ PCDU_HANDLER = 0x44001000, diff --git a/fsfwconfig/pollingsequence/pollingSequenceFactory.cpp b/fsfwconfig/pollingsequence/pollingSequenceFactory.cpp index f7422786..fe728b12 100644 --- a/fsfwconfig/pollingsequence/pollingSequenceFactory.cpp +++ b/fsfwconfig/pollingsequence/pollingSequenceFactory.cpp @@ -5,13 +5,13 @@ #include #include #include +#include ReturnValue_t pst::pollingSequenceInitDefault(FixedTimeslotTaskIF *thisSequence) { /* Length of a communication cycle */ uint32_t length = thisSequence->getPeriodMs(); - thisSequence->addSlot(objects::TMP1075_HANDLER_1, length * 0, DeviceHandlerIF::PERFORM_OPERATION); thisSequence->addSlot(objects::TMP1075_HANDLER_2, length * 0, @@ -40,6 +40,7 @@ ReturnValue_t pst::pollingSequenceInitDefault(FixedTimeslotTaskIF *thisSequence) #endif /* Q7S_ADD_RTD_DEVICES */ thisSequence->addSlot(objects::IMTQ_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::PLOC_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); thisSequence->addSlot(objects::TMP1075_HANDLER_1, length * 0.2, DeviceHandlerIF::SEND_WRITE); thisSequence->addSlot(objects::TMP1075_HANDLER_2, length * 0.2, DeviceHandlerIF::SEND_WRITE); @@ -64,6 +65,7 @@ ReturnValue_t pst::pollingSequenceInitDefault(FixedTimeslotTaskIF *thisSequence) #endif /* Q7S_ADD_RTD_DEVICES */ thisSequence->addSlot(objects::IMTQ_HANDLER, length * 0.2, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::PLOC_HANDLER, length * 0.2, DeviceHandlerIF::SEND_WRITE); thisSequence->addSlot(objects::TMP1075_HANDLER_1, length * 0.4, DeviceHandlerIF::GET_WRITE); thisSequence->addSlot(objects::TMP1075_HANDLER_2, length * 0.4, DeviceHandlerIF::GET_WRITE); @@ -88,6 +90,7 @@ ReturnValue_t pst::pollingSequenceInitDefault(FixedTimeslotTaskIF *thisSequence) #endif /* Q7S_ADD_RTD_DEVICES */ thisSequence->addSlot(objects::IMTQ_HANDLER, length * 0.4, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::PLOC_HANDLER, length * 0.4, DeviceHandlerIF::GET_WRITE); thisSequence->addSlot(objects::TMP1075_HANDLER_1, length * 0.6, DeviceHandlerIF::SEND_READ); thisSequence->addSlot(objects::TMP1075_HANDLER_2, length * 0.6, DeviceHandlerIF::SEND_READ); @@ -112,6 +115,7 @@ ReturnValue_t pst::pollingSequenceInitDefault(FixedTimeslotTaskIF *thisSequence) #endif /* Q7S_ADD_RTD_DEVICES */ thisSequence->addSlot(objects::IMTQ_HANDLER, length * 0.6, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::PLOC_HANDLER, length * 0.6, DeviceHandlerIF::SEND_READ); thisSequence->addSlot(objects::TMP1075_HANDLER_1, length * 0.8, DeviceHandlerIF::GET_READ); thisSequence->addSlot(objects::TMP1075_HANDLER_2, length * 0.8, DeviceHandlerIF::GET_READ); @@ -136,6 +140,7 @@ ReturnValue_t pst::pollingSequenceInitDefault(FixedTimeslotTaskIF *thisSequence) #endif /* Q7S_ADD_RTD_DEVICES */ thisSequence->addSlot(objects::IMTQ_HANDLER, length * 0.8, DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::PLOC_HANDLER, length * 0.8, DeviceHandlerIF::GET_READ); if (thisSequence->checkSequence() == HasReturnvaluesIF::RETURN_OK) { return HasReturnvaluesIF::RETURN_OK; @@ -290,3 +295,25 @@ ReturnValue_t pst::pollingSequenceAcsTest(FixedTimeslotTaskIF *thisSequence) { } return HasReturnvaluesIF::RETURN_OK; } + +ReturnValue_t pst::pollingSequenceTE0720(FixedTimeslotTaskIF *thisSequence) { + uint32_t length = thisSequence->getPeriodMs(); + + thisSequence->addSlot(objects::PLOC_HANDLER, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::PLOC_HANDLER, length * 0.2, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::PLOC_HANDLER, length * 0.4, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::PLOC_HANDLER, length * 0.6, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::PLOC_HANDLER, length * 0.8, + DeviceHandlerIF::GET_READ); + + if (thisSequence->checkSequence() != HasReturnvaluesIF::RETURN_OK) { + sif::error << "Initialization of TE0720 PST failed" << std::endl; + return HasReturnvaluesIF::RETURN_FAILED; + } + return HasReturnvaluesIF::RETURN_OK; +} + diff --git a/fsfwconfig/pollingsequence/pollingSequenceFactory.h b/fsfwconfig/pollingsequence/pollingSequenceFactory.h index 09dd7242..73cdc0bb 100644 --- a/fsfwconfig/pollingsequence/pollingSequenceFactory.h +++ b/fsfwconfig/pollingsequence/pollingSequenceFactory.h @@ -35,6 +35,11 @@ ReturnValue_t pollingSequenceInitDefault(FixedTimeslotTaskIF *thisSequence); ReturnValue_t gomspacePstInit(FixedTimeslotTaskIF *thisSequence); ReturnValue_t pollingSequenceAcsTest(FixedTimeslotTaskIF* thisSequence); + +/** + * @brief This polling sequence will be created when the software is compiled for the TE0720. + */ +ReturnValue_t pollingSequenceTE0720(FixedTimeslotTaskIF* thisSequence); } diff --git a/fsfwconfig/returnvalues/classIds.h b/fsfwconfig/returnvalues/classIds.h index 93349596..22bb0457 100644 --- a/fsfwconfig/returnvalues/classIds.h +++ b/fsfwconfig/returnvalues/classIds.h @@ -20,6 +20,7 @@ enum { SA_DEPL_HANDLER, SYRLINKS_HANDLER, IMTQ_HANDLER, + PLOC_HANDLER }; } diff --git a/linux/uart/UartComIF.cpp b/linux/uart/UartComIF.cpp index 79470a45..8d9a15c3 100644 --- a/linux/uart/UartComIF.cpp +++ b/linux/uart/UartComIF.cpp @@ -363,6 +363,9 @@ ReturnValue_t UartComIF::readReceivedMessage(CookieIF *cookie, *buffer = uartDeviceMapIter->second.replyBuffer.data(); *size = uartDeviceMapIter->second.replyLen; + /* Length is reset to 0 to prevent reading the same data twice */ + uartDeviceMapIter->second.replyLen = 0; + return RETURN_OK; } diff --git a/mission/devices/CMakeLists.txt b/mission/devices/CMakeLists.txt index 6e8518b0..7d34aac9 100644 --- a/mission/devices/CMakeLists.txt +++ b/mission/devices/CMakeLists.txt @@ -13,6 +13,7 @@ target_sources(${TARGET_NAME} PUBLIC SyrlinksHkHandler.cpp Max31865PT1000Handler.cpp IMTQHandler.cpp + PlocHandler.cpp ) diff --git a/mission/devices/IMTQHandler.cpp b/mission/devices/IMTQHandler.cpp index 0e92a6c1..2051849f 100644 --- a/mission/devices/IMTQHandler.cpp +++ b/mission/devices/IMTQHandler.cpp @@ -21,7 +21,7 @@ void IMTQHandler::doStartUp(){ if(mode == _MODE_START_UP){ //TODO: Set to MODE_ON again setMode(MODE_NORMAL); - communicationStep = CommunicationStep::SELT_TEST; + communicationStep = CommunicationStep::SELF_TEST; } } diff --git a/mission/devices/PlocHandler.cpp b/mission/devices/PlocHandler.cpp new file mode 100644 index 00000000..30f3859d --- /dev/null +++ b/mission/devices/PlocHandler.cpp @@ -0,0 +1,476 @@ +#include "PlocHandler.h" + +#include +#include +#include +#include + +PlocHandler::PlocHandler(object_id_t objectId, object_id_t comIF, CookieIF * comCookie) : + DeviceHandlerBase(objectId, comIF, comCookie) { + if (comCookie == NULL) { + sif::error << "PlocHandler: Invalid com cookie" << std::endl; + } +} + +PlocHandler::~PlocHandler() { +} + + +void PlocHandler::doStartUp(){ + if(mode == _MODE_START_UP){ + setMode(MODE_ON); + } +} + +void PlocHandler::doShutDown(){ + +} + +ReturnValue_t PlocHandler::buildNormalDeviceCommand( + DeviceCommandId_t * id) { + return RETURN_OK; +} + +ReturnValue_t PlocHandler::buildTransitionDeviceCommand( + DeviceCommandId_t * id){ + return HasReturnvaluesIF::RETURN_OK; +} + +ReturnValue_t PlocHandler::buildCommandFromCommand( + DeviceCommandId_t deviceCommand, const uint8_t * commandData, + size_t commandDataLen) { + switch(deviceCommand) { + case(PLOC::TC_MEM_WRITE): { + return prepareTcMemWriteCommand(commandData, commandDataLen); + } + case(PLOC::TC_MEM_READ): { + return prepareTcMemReadCommand(commandData, commandDataLen); + } + default: + sif::debug << "PlocHandler::buildCommandFromCommand: Command not implemented" << std::endl; + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + return HasReturnvaluesIF::RETURN_FAILED; +} + +void PlocHandler::fillCommandAndReplyMap() { + this->insertInCommandMap(PLOC::TC_MEM_WRITE); + this->insertInCommandMap(PLOC::TC_MEM_READ); + this->insertInReplyMap(PLOC::ACK_REPORT, 1, nullptr, PLOC::SIZE_ACK_REPORT); + this->insertInReplyMap(PLOC::EXE_REPORT, 3, nullptr, PLOC::SIZE_EXE_REPORT); + this->insertInReplyMap(PLOC::TM_MEMORY_READ_REPORT, 2, nullptr, PLOC::SIZE_TM_MEM_READ_REPORT); +} + +ReturnValue_t PlocHandler::scanForReply(const uint8_t *start, + size_t remainingSize, DeviceCommandId_t *foundId, size_t *foundLen) { + + ReturnValue_t result = RETURN_OK; + + uint16_t apid = (*(start) << 8 | *(start + 1)) & APID_MASK; + + switch(apid) { + case(PLOC::APID_ACK_SUCCESS): + *foundLen = PLOC::SIZE_ACK_REPORT; + *foundId = PLOC::ACK_REPORT; + break; + case(PLOC::APID_ACK_FAILURE): + *foundLen = PLOC::SIZE_ACK_REPORT; + *foundId = PLOC::ACK_REPORT; + break; + case(PLOC::APID_TM_MEMORY_READ_REPORT): + *foundLen = PLOC::SIZE_TM_MEM_READ_REPORT; + *foundId = PLOC::TM_MEMORY_READ_REPORT; + break; + case(PLOC::APID_EXE_SUCCESS): + *foundLen = PLOC::SIZE_EXE_REPORT; + *foundId = PLOC::EXE_REPORT; + break; + case(PLOC::APID_EXE_FAILURE): + *foundLen = PLOC::SIZE_EXE_REPORT; + *foundId = PLOC::EXE_REPORT; + break; + default: { + sif::debug << "PlocHandler::scanForReply: Reply has invalid apid" << std::endl; + *foundLen = remainingSize; + result = INVALID_APID; + break; + } + } + + return result; +} + +ReturnValue_t PlocHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t *packet) { + + ReturnValue_t result = RETURN_OK; + + switch (id) { + case PLOC::ACK_REPORT: { + result = handleAckReport(packet); + break; + } + case (PLOC::TM_MEMORY_READ_REPORT): { + result = handleMemoryReadReport(packet); + break; + } + case (PLOC::EXE_REPORT): { + result = handleExecutionReport(packet); + break; + } + default: { + sif::debug << "PlocHandler::interpretDeviceReply: Unknown device reply id" << std::endl; + return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY; + } + } + + return result; +} + +void PlocHandler::setNormalDatapoolEntriesInvalid(){ + +} + +uint32_t PlocHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo){ + return 500; +} + +ReturnValue_t PlocHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + + return HasReturnvaluesIF::RETURN_OK; +} + +void PlocHandler::setModeNormal() { + mode = MODE_NORMAL; +} + +ReturnValue_t PlocHandler::prepareTcMemWriteCommand(const uint8_t * commandData, + size_t commandDataLen) { + const uint32_t memoryAddress = *(commandData) << 24 | *(commandData + 1) << 16 + | *(commandData + 2) << 8 | *(commandData + 3); + const uint32_t memoryData = *(commandData + 4) << 24 | *(commandData + 5) << 16 + | *(commandData + 6) << 8 | *(commandData + 7); + PLOC::TcMemWrite tcMemWrite(memoryAddress, memoryData); + if (tcMemWrite.getFullSize() > PLOC::MAX_COMMAND_SIZE) { + sif::debug << "PlocHandler::prepareTcMemWriteCommand: Command too big" << std::endl; + return RETURN_FAILED; + } + memcpy(commandBuffer, tcMemWrite.getWholeData(), tcMemWrite.getFullSize()); + rawPacket = commandBuffer; + rawPacketLen = tcMemWrite.getFullSize(); + nextReplyId = PLOC::ACK_REPORT; + return RETURN_OK; +} + +ReturnValue_t PlocHandler::prepareTcMemReadCommand(const uint8_t * commandData, + size_t commandDataLen) { + const uint32_t memoryAddress = *(commandData) << 24 | *(commandData + 1) << 16 + | *(commandData + 2) << 8 | *(commandData + 3); + PLOC::TcMemRead tcMemRead(memoryAddress); + if (tcMemRead.getFullSize() > PLOC::MAX_COMMAND_SIZE) { + sif::debug << "PlocHandler::prepareTcMemReadCommand: Command too big" << std::endl; + return RETURN_FAILED; + } + memcpy(commandBuffer, tcMemRead.getWholeData(), tcMemRead.getFullSize()); + rawPacket = commandBuffer; + rawPacketLen = tcMemRead.getFullSize(); + nextReplyId = PLOC::ACK_REPORT; + return RETURN_OK; +} + +ReturnValue_t PlocHandler::verifyPacket(const uint8_t* start, size_t foundLen) { + + uint16_t receivedCrc = *(start + foundLen - 2) << 8 | *(start + foundLen - 1); + + uint16_t recalculatedCrc = CRC::crc16ccitt(start, foundLen - 2, 0); + + if (receivedCrc != recalculatedCrc) { + return CRC_FAILURE; + } + + return RETURN_OK; +} + +ReturnValue_t PlocHandler::handleAckReport(const uint8_t* data) { + + ReturnValue_t result = RETURN_OK; + + result = verifyPacket(data, PLOC::SIZE_ACK_REPORT); + if(result == CRC_FAILURE) { + sif::error << "PlocHandler::handleAckReport: CRC failure" << std::endl; + nextReplyId = PLOC::NONE; + replyRawReplyIfnotWiretapped(data, PLOC::SIZE_ACK_REPORT); + triggerEvent(CRC_FAILURE_EVENT); + sendFailureReport(PLOC::ACK_REPORT, CRC_FAILURE); + disableAllReplies(); + return IGNORE_REPLY_DATA; + } + + uint16_t apid = (*(data) << 8 | *(data + 1)) & APID_MASK; + + switch(apid) { + case PLOC::APID_ACK_FAILURE: { + //TODO: Interpretation of status field in acknowledgment report + sif::debug << "PlocHandler::handleAckReport: Received Ack failure report" << std::endl; + DeviceCommandId_t commandId = getPendingCommand(); + if (commandId != DeviceHandlerIF::NO_COMMAND_ID) { + triggerEvent(ACK_FAILURE, commandId); + } + sendFailureReport(PLOC::ACK_REPORT, RECEIVED_ACK_FAILURE); + disableAllReplies(); + nextReplyId = PLOC::NONE; + result = IGNORE_REPLY_DATA; + break; + } + case PLOC::APID_ACK_SUCCESS: { + setNextReplyId(); + break; + } + default: { + sif::debug << "PlocHandler::handleAckReport: Invalid APID in Ack report" << std::endl; + result = RETURN_FAILED; + break; + } + } + + return result; +} + +ReturnValue_t PlocHandler::handleExecutionReport(const uint8_t* data) { + + ReturnValue_t result = RETURN_OK; + + result = verifyPacket(data, PLOC::SIZE_EXE_REPORT); + if(result == CRC_FAILURE) { + sif::error << "PlocHandler::handleExecutionReport: CRC failure" << std::endl; + nextReplyId = PLOC::NONE; + return result; + } + + uint16_t apid = (*(data) << 8 | *(data + 1)) & APID_MASK; + + switch (apid) { + case (PLOC::APID_EXE_SUCCESS): { + break; + } + case (PLOC::APID_EXE_FAILURE): { + //TODO: Interpretation of status field in execution report + sif::error << "PlocHandler::handleExecutionReport: Received execution failure report" + << std::endl; + DeviceCommandId_t commandId = getPendingCommand(); + if (commandId != DeviceHandlerIF::NO_COMMAND_ID) { + triggerEvent(EXE_FAILURE, commandId); + } + else { + sif::debug << "PlocHandler::handleExecutionReport: Unknown command id" << std::endl; + } + sendFailureReport(PLOC::EXE_REPORT, RECEIVED_EXE_FAILURE); + disableExeReportReply(); + result = IGNORE_REPLY_DATA; + break; + } + default: { + sif::error << "PlocHandler::handleExecutionReport: Unknown APID" << std::endl; + result = RETURN_FAILED; + break; + } + } + + nextReplyId = PLOC::NONE; + + return result; +} + +ReturnValue_t PlocHandler::handleMemoryReadReport(const uint8_t* data) { + + ReturnValue_t result = RETURN_OK; + + result = verifyPacket(data, PLOC::SIZE_TM_MEM_READ_REPORT); + + if(result == CRC_FAILURE) { + sif::error << "PlocHandler::handleMemoryReadReport: Memory read report has invalid crc" + << std::endl; + } + /** Send data to commanding queue */ + handleDeviceTM(data + PLOC::DATA_FIELD_OFFSET, PLOC::SIZE_MEM_READ_REPORT_DATA, + PLOC::TM_MEMORY_READ_REPORT); + + nextReplyId = PLOC::EXE_REPORT; + + return result; +} + +ReturnValue_t PlocHandler::enableReplyInReplyMap(DeviceCommandMap::iterator command, + uint8_t expectedReplies, bool useAlternateId, + DeviceCommandId_t alternateReplyID) { + + ReturnValue_t result = RETURN_OK; + + uint8_t enabledReplies = 0; + + switch (command->first) { + case PLOC::TC_MEM_WRITE: + enabledReplies = 2; + break; + case PLOC::TC_MEM_READ: { + enabledReplies = 3; + result = DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, + PLOC::TM_MEMORY_READ_REPORT); + if (result != RETURN_OK) { + sif::debug << "PlocHandler::enableReplyInReplyMap: Reply with id " + << PLOC::TM_MEMORY_READ_REPORT << " not in replyMap" << std::endl; + } + break; + } + default: + sif::debug << "PlocHandler::enableReplyInReplyMap: Unknown command id" << std::endl; + break; + } + + /** + * Every command causes at least one acknowledgment and one execution report. Therefore both + * replies will be enabled here. + */ + result = DeviceHandlerBase::enableReplyInReplyMap(command, + enabledReplies, true, PLOC::ACK_REPORT); + if (result != RETURN_OK) { + sif::debug << "PlocHandler::enableReplyInReplyMap: Reply with id " << PLOC::ACK_REPORT + << " not in replyMap" << std::endl; + } + + result = DeviceHandlerBase::enableReplyInReplyMap(command, + enabledReplies, true, PLOC::EXE_REPORT); + if (result != RETURN_OK) { + sif::debug << "PlocHandler::enableReplyInReplyMap: Reply with id " << PLOC::EXE_REPORT + << " not in replyMap" << std::endl; + } + + return RETURN_OK; +} + +void PlocHandler::setNextReplyId() { + switch(getPendingCommand()) { + case PLOC::TC_MEM_READ: + nextReplyId = PLOC::TM_MEMORY_READ_REPORT; + break; + default: + /* If no telemetry is expected the next reply is always the execution report */ + nextReplyId = PLOC::EXE_REPORT; + break; + } +} +size_t PlocHandler::getNextReplyLength(DeviceCommandId_t commandId){ + + size_t replyLen = 0; + + if (nextReplyId == PLOC::NONE) { + return replyLen; + } + + DeviceReplyIter iter = deviceReplyMap.find(nextReplyId); + if (iter != deviceReplyMap.end()) { + if (iter->second.delayCycles == 0) { + /* Reply inactive */ + return replyLen; + } + replyLen = iter->second.replyLen; + } + else { + sif::debug << "PlocHandler::getNextReplyLength: No entry for reply with reply id " + << std::hex << nextReplyId << " in deviceReplyMap" << std::endl; + } + + return replyLen; +} + +void PlocHandler::handleDeviceTM(const uint8_t* data, size_t dataSize, DeviceCommandId_t replyId) { + + ReturnValue_t result = RETURN_OK; + + if (wiretappingMode == RAW) { + /* Data already sent in doGetRead() */ + return; + } + + DeviceReplyMap::iterator iter = deviceReplyMap.find(replyId); + if (iter == deviceReplyMap.end()) { + sif::debug << "PlocHandler::handleDeviceTM: Unknown reply id" << std::endl; + return; + } + MessageQueueId_t queueId = iter->second.command->second.sendReplyTo; + + if (queueId == NO_COMMANDER) { + return; + } + + result = actionHelper.reportData(queueId, replyId, data, dataSize); + if (result != RETURN_OK) { + sif::debug << "PlocHandler::handleDeviceTM: Failed to report data" << std::endl; + } +} + +void PlocHandler::disableAllReplies() { + + DeviceReplyMap::iterator iter; + + /* Disable ack reply */ + iter = deviceReplyMap.find(PLOC::ACK_REPORT); + DeviceReplyInfo *info = &(iter->second); + info->delayCycles = 0; + info->command = deviceCommandMap.end(); + + DeviceCommandId_t commandId = getPendingCommand(); + + /* If the command expects a telemetry packet the appropriate tm reply will be disabled here */ + switch (commandId) { + case PLOC::TC_MEM_WRITE: + break; + case PLOC::TC_MEM_READ: { + iter = deviceReplyMap.find(PLOC::TM_MEMORY_READ_REPORT); + info = &(iter->second); + info->delayCycles = 0; + info->command = deviceCommandMap.end(); + break; + } + default: { + sif::debug << "PlocHandler::disableAllReplies: Unknown command id" << commandId + << std::endl; + break; + } + } + + /* We must always disable the execution report reply here */ + disableExeReportReply(); +} + +void PlocHandler::sendFailureReport(DeviceCommandId_t replyId, ReturnValue_t status) { + + DeviceReplyIter iter = deviceReplyMap.find(replyId); + + if (iter == deviceReplyMap.end()) { + sif::debug << "PlocHandler::sendFailureReport: Reply not in reply map" << std::endl; + return; + } + + DeviceCommandInfo* info = &(iter->second.command->second); + + if (info == nullptr) { + sif::debug << "PlocHandler::sendFailureReport: Reply has no active command" << std::endl; + return; + } + + if (info->sendReplyTo != NO_COMMANDER) { + actionHelper.finish(false, info->sendReplyTo, iter->first, status); + } + info->isExecuting = false; +} + +void PlocHandler::disableExeReportReply() { + DeviceReplyIter iter = deviceReplyMap.find(PLOC::EXE_REPORT); + DeviceReplyInfo *info = &(iter->second); + info->delayCycles = 0; + info->command = deviceCommandMap.end(); + /* Expected replies is set to one here. The value will set to 0 in replyToReply() */ + info->command->second.expectedReplies = 0; +} diff --git a/mission/devices/PlocHandler.h b/mission/devices/PlocHandler.h new file mode 100644 index 00000000..b241c838 --- /dev/null +++ b/mission/devices/PlocHandler.h @@ -0,0 +1,173 @@ +#ifndef MISSION_DEVICES_PLOCHANDLER_H_ +#define MISSION_DEVICES_PLOCHANDLER_H_ + +#include +#include +#include + +/** + * @brief This is the device handler for the PLOC. + * + * @details The PLOC uses the space packet protocol for communication. On each command the PLOC + * answers at least with one acknowledgment and one execution report. + * + * @author J. Meier + */ +class PlocHandler: public DeviceHandlerBase { +public: + + PlocHandler(object_id_t objectId, object_id_t comIF, CookieIF * comCookie); + virtual ~PlocHandler(); + + /** + * @brief Sets mode to MODE_NORMAL. Can be used for debugging. + */ + void setModeNormal(); + +protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t * id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t * id) override; + void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t * commandData,size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t remainingSize, + DeviceCommandId_t *foundId, size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, + const uint8_t *packet) override; + void setNormalDatapoolEntriesInvalid() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + ReturnValue_t enableReplyInReplyMap(DeviceCommandMap::iterator command, + uint8_t expectedReplies = 1, bool useAlternateId = false, + DeviceCommandId_t alternateReplyID = 0) override; + size_t getNextReplyLength(DeviceCommandId_t deviceCommand) override; + +private: + + static const uint8_t INTERFACE_ID = CLASS_ID::PLOC_HANDLER; + + static const ReturnValue_t CRC_FAILURE = MAKE_RETURN_CODE(0xA0); //!> Space Packet received from PLOC has invalid CRC + static const ReturnValue_t RECEIVED_ACK_FAILURE = MAKE_RETURN_CODE(0xA1); //!> Received ACK failure reply from PLOC + static const ReturnValue_t RECEIVED_EXE_FAILURE = MAKE_RETURN_CODE(0xA2); //!> Received execution failure reply from PLOC + static const ReturnValue_t INVALID_APID = MAKE_RETURN_CODE(0xA3); //!> Received space packet with invalid APID from PLOC + + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PLOC_HANDLER; + + static const Event MEMORY_READ_RPT_CRC_FAILURE = MAKE_EVENT(1, severity::LOW); //!> PLOC crc failure in telemetry packet + static const Event ACK_FAILURE = MAKE_EVENT(2, severity::LOW); //!> PLOC receive acknowledgment failure report + static const Event EXE_FAILURE = MAKE_EVENT(3, severity::LOW); //!> PLOC receive execution failure report + static const Event CRC_FAILURE_EVENT = MAKE_EVENT(4, severity::LOW); //!> PLOC reply has invalid crc + + static const uint16_t APID_MASK = 0x7FF; + + uint8_t commandBuffer[PLOC::MAX_COMMAND_SIZE]; + + /** + * This variable is used to store the id of the next reply to receive. This is necessary + * because the PLOC sends as reply to each command at least one acknowledgment and execution + * report. + */ + DeviceCommandId_t nextReplyId = PLOC::NONE; + + /** + * @brief This function fills the commandBuffer to initiate the write memory command. + * + * @param commandData Pointer to action command data. + * @param commanDataLen Size of command data in bytes. + * + * @return RETURN_OK if successful, else RETURN_FAILURE. + */ + ReturnValue_t prepareTcMemWriteCommand(const uint8_t * commandData, size_t commandDataLen); + + /** + * @brief This function fills the commandBuffer to initiate the write reads command. + * + * @param commandData Pointer to action command data. + * @param commanDataLen Size of command data in bytes. + * + * @return RETURN_OK if successful, else RETURN_FAILURE. + */ + ReturnValue_t prepareTcMemReadCommand(const uint8_t * commandData, size_t commandDataLen); + + /** + * @brief This function checks the crc of the received PLOC reply. + * + * @param start Pointer to the first byte of the reply. + * @param foundLen Pointer to the length of the whole packet. + * + * @return RETURN_OK if CRC is ok, otherwise CRC_FAILURE. + */ + ReturnValue_t verifyPacket(const uint8_t* start, size_t foundLen); + + /** + * @brief This function handles the acknowledgment report. + * + * @param data Pointer to the data holding the acknowledgment report. + * + * @return RETURN_OK if successful, otherwise an error code. + */ + ReturnValue_t handleAckReport(const uint8_t* data); + + /** + * @brief This function handles the data of a execution report. + * + * @param data Pointer to the received data packet. + * + * @return RETURN_OK if successful, otherwise an error code. + */ + ReturnValue_t handleExecutionReport(const uint8_t* data); + + /** + * @brief This function handles the memory read report. + * + * @param data Pointer to the data buffer holding the memory read report. + * + * @return RETURN_OK if successful, otherwise an error code. + */ + ReturnValue_t handleMemoryReadReport(const uint8_t* data); + + /** + * @brief Depending on the current active command, this function sets the reply id of the + * next reply after a successful acknowledgment report has been received. This is + * required by the function getNextReplyLength() to identify the length of the next + * reply to read. + */ + void setNextReplyId(); + + /** + * @brief This function handles action message replies in case the telemetry has been + * requested by another object. + * + * @param data Pointer to the telemetry data. + * @param dataSize Size of telemetry in bytes. + * @param replyId Id of the reply. This will be added to the ActionMessage. + */ + void handleDeviceTM(const uint8_t* data, size_t dataSize, DeviceCommandId_t replyId); + + /** + * @brief In case an acknowledgment failure reply has been received this function disables + * all previously enabled commands and resets the exepected replies variable of an + * active command. + */ + void disableAllReplies(); + + /** + * @brief This function sends a failure report if the active action was commanded by an other + * object. + * + * @param replyId The id of the reply which signals a failure. + * @param status A status byte which gives information about the failure type. + */ + void sendFailureReport(DeviceCommandId_t replyId, ReturnValue_t status); + + /** + * @brief This function disables the execution report reply. Within this function also the + * the variable expectedReplies of an active command will be set to 0. + */ + void disableExeReportReply(); +}; + +#endif /* MISSION_DEVICES_PLOCHANDLER_H_ */ diff --git a/mission/devices/devicedefinitions/IMTQHandlerDefinitions.h b/mission/devices/devicedefinitions/IMTQHandlerDefinitions.h index 0cbbb04e..1cfdf933 100644 --- a/mission/devices/devicedefinitions/IMTQHandlerDefinitions.h +++ b/mission/devices/devicedefinitions/IMTQHandlerDefinitions.h @@ -32,6 +32,7 @@ namespace IMTQ { static const uint8_t MAX_REPLY_SIZE = SIZE_ENG_HK_DATA_REPLY; static const uint8_t MAX_COMMAND_SIZE = 9; + /** Define entries in IMTQ specific dataset */ static const uint8_t ENG_HK_SET_POOL_ENTRIES = 11; static const uint8_t CAL_MTM_POOL_ENTRIES = 4; diff --git a/mission/devices/devicedefinitions/PlocDefinitions.h b/mission/devices/devicedefinitions/PlocDefinitions.h new file mode 100644 index 00000000..b7378a8a --- /dev/null +++ b/mission/devices/devicedefinitions/PlocDefinitions.h @@ -0,0 +1,173 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_PLOCDEFINITIONS_H_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_PLOCDEFINITIONS_H_ + +#include +#include +#include + +namespace PLOC { + + static const DeviceCommandId_t NONE = 0x0; + static const DeviceCommandId_t TC_MEM_WRITE = 0x1; + static const DeviceCommandId_t TC_MEM_READ = 0x2; + static const DeviceCommandId_t ACK_REPORT = 0x3; + static const DeviceCommandId_t EXE_REPORT = 0x5; + static const DeviceCommandId_t TM_MEMORY_READ_REPORT = 0x6; + + static const uint16_t SIZE_ACK_REPORT = 14; + static const uint16_t SIZE_EXE_REPORT = 14; + static const uint16_t SIZE_TM_MEM_READ_REPORT = 18; + + /** + * SpacePacket apids of PLOC telecommands and telemetry. + */ + static const uint16_t APID_TC_MEM_WRITE = 0x714; + static const uint16_t APID_TC_MEM_READ = 0x715; + static const uint16_t APID_TM_MEMORY_READ_REPORT = 0x404; + static const uint16_t APID_ACK_SUCCESS = 0x400; + static const uint16_t APID_ACK_FAILURE = 0x401; + static const uint16_t APID_EXE_SUCCESS = 0x402; + static const uint16_t APID_EXE_FAILURE = 0x403; + + /** Offset from first byte in Space packet to first byte of data field */ + static const uint8_t DATA_FIELD_OFFSET = 6; + + /** + * The size of payload data which will be forwarded to the requesting object. e.g. PUS Service + * 8. + */ + static const uint8_t SIZE_MEM_READ_REPORT_DATA = 10; + + /** + * PLOC space packet length for fixed size packets. This is the size of the whole packet data + * field. For the length field in the space packet this size will be substracted by one. + */ + static const uint16_t LENGTH_TC_MEM_WRITE = 12; + static const uint16_t LENGTH_TC_MEM_READ = 8; + + static const size_t MAX_REPLY_SIZE = SIZE_TM_MEM_READ_REPORT; + static const size_t MAX_COMMAND_SIZE = 18; + + /** + * @brief This class helps to build the memory read command for the PLOC. + * + * @details The last two bytes of the packet data field contain a CRC calculated over the whole + * space packet. This is the CRC-16-CCITT as specified in + * ECSS-E-ST-70-41C – Telemetry and telecommand packet utilization. + */ + class TcMemRead : public SpacePacket { + public: + + /** + * @brief Constructor + * + * @param memAddr The memory address to read from. + */ + TcMemRead(const uint32_t memAddr) : + SpacePacket(LENGTH_TC_MEM_READ - 1, true, APID_TC_MEM_READ) { + fillPacketDataField(&memAddr); + } + + private: + + /** + * @brief This function builds the packet data field for the mem read command. + * + * @param memAddrPtr Pointer to the memory address to read from. + */ + void fillPacketDataField(const uint32_t* memAddrPtr) { + /* Add memAddr to packet data field */ + size_t serializedSize = 0; + uint8_t* memoryAddressPos = this->localData.fields.buffer; + SerializeAdapter::serialize(memAddrPtr, &memoryAddressPos, &serializedSize, + sizeof(*memAddrPtr), SerializeIF::Endianness::BIG); + + /* Add memLen to packet data field */ + this->localData.fields.buffer[OFFSET_MEM_LEN_FIELD] = 0; + this->localData.fields.buffer[OFFSET_MEM_LEN_FIELD + 1] = 1; + + /* Calculate crc */ + uint16_t crc = CRC::crc16ccitt(this->localData.byteStream, + sizeof(CCSDSPrimaryHeader) + LENGTH_TC_MEM_READ - CRC_SIZE, 0); + + /* Add crc to packet data field of space packet */ + serializedSize = 0; + uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET; + SerializeAdapter::serialize(&crc, &crcPos, &serializedSize, + sizeof(crc), SerializeIF::Endianness::BIG); + memcpy(this->localData.fields.buffer + CRC_OFFSET, &crc, sizeof(crc)); + } + + static const uint8_t OFFSET_MEM_LEN_FIELD = 4; + static const uint8_t CRC_OFFSET = 6; + + }; + + /** + * @brief This class helps to generate the space packet to write to a memory address within + * the PLOC. + * @details The last two bytes of the packet data field contain a CRC calculated over the whole + * space packet. This is the CRC-16-CCITT as specified in + * ECSS-E-ST-70-41C – Telemetry and telecommand packet utilization. + */ + class TcMemWrite : public SpacePacket { + public: + /** + * @brief Constructor + * + * @param memAddr The PLOC memory address where to write to. + * @param memoryData The data to write to the specified memory address. + */ + TcMemWrite(const uint32_t memAddr, const uint32_t memoryData) : + SpacePacket(LENGTH_TC_MEM_WRITE - 1, true, APID_TC_MEM_WRITE) { + fillPacketDataField(&memAddr, &memoryData); + } + + private: + + /** + * @brief This function builds the packet data field for the mem write command. + * + * @param memAddrPtr Pointer to the PLOC memory address where to write to. + * @param memoryDataPtr Pointer to the memoryData to write + */ + void fillPacketDataField(const uint32_t* memAddrPtr, const uint32_t* memoryDataPtr) { + + /* Add memAddr to packet data field */ + size_t serializedSize = 0; + uint8_t* memoryAddressPos = this->localData.fields.buffer; + SerializeAdapter::serialize(memAddrPtr, &memoryAddressPos, &serializedSize, + sizeof(*memAddrPtr), SerializeIF::Endianness::BIG); + + /* Add memLen to packet data field */ + this->localData.fields.buffer[OFFSET_MEM_LEN_FIELD] = 0; + this->localData.fields.buffer[OFFSET_MEM_LEN_FIELD + 1] = 1; + + /* Add memData to packet data field */ + serializedSize = 0; + uint8_t* memoryDataPos = this->localData.fields.buffer + OFFSET_MEM_DATA_FIELD; + SerializeAdapter::serialize(memoryDataPtr, &memoryDataPos, &serializedSize, + sizeof(*memoryDataPtr), SerializeIF::Endianness::BIG); + + /* Calculate crc */ + uint16_t crc = CRC::crc16ccitt(this->localData.byteStream, + sizeof(CCSDSPrimaryHeader) + LENGTH_TC_MEM_WRITE - CRC_SIZE, 0); + + serializedSize = 0; + uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET; + /* Add crc to packet data field of space packet */ + SerializeAdapter::serialize(&crc, &crcPos, &serializedSize, + sizeof(crc), SerializeIF::Endianness::BIG); + memcpy(this->localData.fields.buffer + CRC_OFFSET, &crc, sizeof(crc)); + } + + /** Offsets from base address of packet data field */ + static const uint8_t OFFSET_MEM_LEN_FIELD = 4; + static const uint8_t OFFSET_MEM_DATA_FIELD = 6; + static const uint8_t CRC_OFFSET = 10; + }; + +} + + +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_PLOCDEFINITIONS_H_ */ diff --git a/tmtc b/tmtc index 4d2b7a61..e23bc116 160000 --- a/tmtc +++ b/tmtc @@ -1 +1 @@ -Subproject commit 4d2b7a61509a41e67cf62ecaa2864bba3acd9ef4 +Subproject commit e23bc116088fc673aaec45768c7a07c20d75a2f6