#include "PdecHandler.h" #include #include #include #include #include #include #include "OBSWConfig.h" #include "fsfw/ipc/QueueFactory.h" #include "fsfw/objectmanager/ObjectManager.h" #include "fsfw/serviceinterface/ServiceInterface.h" #include "fsfw/tmtcservices/TmTcMessage.h" #include "fsfw_hal/linux/uio/UioMapper.h" #include "pdec.h" using namespace pdec; // If this is ever shared, protect it with a mutex! uint32_t PdecHandler::CURRENT_FAR = 0; PdecHandler::PdecHandler(object_id_t objectId, object_id_t tcDestinationId, LinuxLibgpioIF* gpioComIF, gpioId_t pdecReset, UioNames names) : SystemObject(objectId), tcDestinationId(tcDestinationId), gpioComIF(gpioComIF), pdecReset(pdecReset), actionHelper(this, nullptr), uioNames(names) { auto mqArgs = MqArgs(objectId, static_cast(this)); commandQueue = QueueFactory::instance()->createMessageQueue( QUEUE_SIZE, MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs); } PdecHandler::~PdecHandler() {} ReturnValue_t PdecHandler::initialize() { tcStore = ObjectManager::instance()->get(objects::TC_STORE); if (tcStore == nullptr) { sif::error << "PdecHandler::initialize: Invalid TC store" << std::endl; return ObjectManagerIF::CHILD_INIT_FAILED; } tcDestination = ObjectManager::instance()->get(tcDestinationId); if (tcDestination == nullptr) { sif::error << "PdecHandler::initialize: Invalid tc destination specified" << std::endl; return ObjectManagerIF::CHILD_INIT_FAILED; } ReturnValue_t result = returnvalue::OK; UioMapper regMapper(uioNames.registers); result = regMapper.getMappedAdress(®isterBaseAddress, UioMapper::Permissions::READ_WRITE); if (result != returnvalue::OK) { return ObjectManagerIF::CHILD_INIT_FAILED; } UioMapper configMemMapper(uioNames.configMemory); result = configMemMapper.getMappedAdress(&memoryBaseAddress, UioMapper::Permissions::READ_WRITE); if (result != returnvalue::OK) { return ObjectManagerIF::CHILD_INIT_FAILED; } UioMapper ramMapper(uioNames.ramMemory); result = ramMapper.getMappedAdress(&ramBaseAddress, UioMapper::Permissions::READ_WRITE); if (result != returnvalue::OK) { return ObjectManagerIF::CHILD_INIT_FAILED; } if (OP_MODE == Modes::IRQ and uioNames.irq == nullptr) { sif::error << "Can not use IRQ mode if IRQ UIO name is invalid" << std::endl; return returnvalue::FAILED; } writePdecConfig(); result = releasePdec(); if (result != returnvalue::OK) { return ObjectManagerIF::CHILD_INIT_FAILED; } result = actionHelper.initialize(commandQueue); if (result != returnvalue::OK) { return result; } return returnvalue::OK; } ReturnValue_t PdecHandler::performOperation(uint8_t operationCode) { if (OP_MODE == Modes::POLLED) { return polledOperation(); } else if (OP_MODE == Modes::IRQ) { return irqOperation(); } } ReturnValue_t PdecHandler::polledOperation() { ReturnValue_t result = returnvalue::OK; readCommandQueue(); switch (state) { case State::INIT: resetFarStatFlag(); if (result != returnvalue::OK) { // Requires reconfiguration and reinitialization of PDEC triggerEvent(INVALID_FAR); state = State::WAIT_FOR_RECOVERY; return result; } state = State::RUNNING; break; case State::RUNNING: if (newTcReceived()) { handleNewTc(); } checkLocks(); break; case State::WAIT_FOR_RECOVERY: break; default: sif::error << "PdecHandler::performOperation: Invalid state" << std::endl; break; } return returnvalue::OK; } ReturnValue_t PdecHandler::irqOperation() { ReturnValue_t result = returnvalue::OK; int fd = open(uioNames.irq, O_RDWR); if (fd < 0) { sif::error << "PdecHandler::irqOperation: Opening UIO IRQ file" << uioNames.irq << " failed" << std::endl; return returnvalue::FAILED; } struct pollfd fds = {.fd = fd, .events = POLLIN, .revents = 0}; // Used to unmask IRQ uint32_t info = 1; ssize_t nb = 0; int ret = 0; while (true) { readCommandQueue(); switch (state) { case State::INIT: resetFarStatFlag(); if (result != returnvalue::OK) { // Requires reconfiguration and reinitialization of PDEC triggerEvent(INVALID_FAR); state = State::WAIT_FOR_RECOVERY; return result; } state = State::RUNNING; break; case State::RUNNING: { // TODO: Add poll() based IRQ handling nb = write(fd, &info, sizeof(info)); if (nb != static_cast(sizeof(info))) { sif::error << "PdecHandler::irqOperation: Unmasking IRQ failed" << std::endl; close(fd); } ret = poll(&fds, 1, IRQ_TIMEOUT_MS); if (ret == 0) { // No TCs for timeout period checkLocks(); lockCheckCd.resetTimer(); } else if (ret >= 1) { nb = read(fd, &info, sizeof(info)); if (nb == static_cast(sizeof(info))) { uint32_t pisr = *(registerBaseAddress + PDEC_PISR_OFFSET); if ((pisr & TC_NEW_MASK) == TC_NEW_MASK) { // handle TC handleNewTc(); } if ((pisr & TC_ABORT_MASK) == TC_ABORT_MASK) { tcAbortCounter += 1; } if ((pisr & NEW_FAR_MASK) == NEW_FAR_MASK) { // Read FAR here CURRENT_FAR = readFar(); } if (lockCheckCd.hasTimedOut()) { checkLocks(); lockCheckCd.resetTimer(); } // Clear interrupts with dummy read ret = *(registerBaseAddress + PDEC_PIR_OFFSET); } } else { sif::error << "PdecHandler::irqOperation: Poll error with errno " << errno << ": " << strerror(errno) << std::endl; triggerEvent(POLL_ERROR_PDEC, errno); } break; } case State::WAIT_FOR_RECOVERY: break; default: sif::error << "PdecHandler::performOperation: Invalid state" << std::endl; break; } } return returnvalue::OK; } void PdecHandler::readCommandQueue(void) { CommandMessage commandMessage; ReturnValue_t result = returnvalue::FAILED; result = commandQueue->receiveMessage(&commandMessage); if (result == returnvalue::OK) { result = actionHelper.handleActionMessage(&commandMessage); if (result == returnvalue::OK) { return; } CommandMessage reply; reply.setReplyRejected(CommandMessage::UNKNOWN_COMMAND, commandMessage.getCommand()); commandQueue->reply(&reply); return; } } MessageQueueId_t PdecHandler::getCommandQueue() const { return commandQueue->getId(); } void PdecHandler::writePdecConfig() { PdecConfig pdecConfig; *(memoryBaseAddress + FRAME_HEADER_OFFSET) = pdecConfig.getConfigWord(0); *(memoryBaseAddress + FRAME_HEADER_OFFSET + 1) = pdecConfig.getConfigWord(1); if (OP_MODE == Modes::IRQ) { // Configure interrupt mask register to enable interrupts *(registerBaseAddress + PDEC_IMR_OFFSET) = pdecConfig.getImrReg(); } // Configure all MAP IDs as invalid for (int idx = 0; idx <= MAX_MAP_ADDR; idx += 4) { *(memoryBaseAddress + MAP_ADDR_LUT_OFFSET + idx / 4) = NO_DESTINATION << 24 | NO_DESTINATION << 16 | NO_DESTINATION << 8 | NO_DESTINATION; } // All TCs with MAP ID 7 will be routed to the PM module (can then be read from memory) uint8_t routeToPm = calcMapAddrEntry(PM_BUFFER); *(memoryBaseAddress + MAP_ADDR_LUT_OFFSET + 1) = (NO_DESTINATION << 24) | (NO_DESTINATION << 16) | (NO_DESTINATION << 8) | routeToPm; // Write map id clock frequencies for (int idx = 0; idx <= MAX_MAP_ADDR; idx += 4) { *(memoryBaseAddress + MAP_CLK_FREQ_OFFSET + idx / 4) = MAP_CLK_FREQ << 24 | MAP_CLK_FREQ << 16 | MAP_CLK_FREQ << 8 | MAP_CLK_FREQ; } } ReturnValue_t PdecHandler::resetFarStatFlag() { uint32_t pdecFar = readFar(); if (pdecFar != FAR_RESET) { sif::warning << "PdecHandler::resetFarStatFlag: FAR register did not match expected value." << " Read value: 0x" << std::hex << static_cast(pdecFar) << std::endl; CURRENT_FAR = pdecFar; return returnvalue::FAILED; } #if OBSW_DEBUG_PDEC_HANDLER == 1 sif::debug << "PdecHandler::resetFarStatFlag: read FAR with value: 0x" << std::hex << pdecFar << std::endl; #endif /* OBSW_DEBUG_PDEC_HANDLER == 1 */ CURRENT_FAR = pdecFar; return returnvalue::OK; } ReturnValue_t PdecHandler::releasePdec() { ReturnValue_t result = returnvalue::OK; result = gpioComIF->pullHigh(pdecReset); if (result != returnvalue::OK) { sif::error << "PdecHandler::releasePdec: Failed to release PDEC reset signal" << std::endl; } return result; } bool PdecHandler::newTcReceived() { uint32_t pdecFar = readFar(); if (pdecFar >> STAT_POSITION != NEW_FAR_RECEIVED) { CURRENT_FAR = pdecFar; return false; } if (!checkFrameAna(pdecFar)) { CURRENT_FAR = pdecFar; return false; } return true; } void PdecHandler::checkLocks() { uint32_t clcw = getClcw(); if (not(clcw & NO_RF_MASK) && not carrierLock) { triggerEvent(CARRIER_LOCK); carrierLock = true; } else if ((clcw & NO_RF_MASK) && carrierLock) { carrierLock = false; triggerEvent(LOST_CARRIER_LOCK_PDEC); } if (not(clcw & NO_BITLOCK_MASK) && not bitLock) { triggerEvent(BIT_LOCK_PDEC); bitLock = true; } else if ((clcw & NO_BITLOCK_MASK) && bitLock) { bitLock = false; triggerEvent(LOST_BIT_LOCK_PDEC); } } bool PdecHandler::checkFrameAna(uint32_t pdecFar) { bool frameValid = false; FrameAna_t frameAna = static_cast((pdecFar & FRAME_ANA_MASK) >> FRAME_ANA_POSITION); switch (frameAna) { case (FrameAna_t::ABANDONED_CLTU): { triggerEvent(INVALID_TC_FRAME, ABANDONED_CLTU); sif::warning << "PdecHandler::checkFrameAna: Abondoned CLTU" << std::endl; break; } case (FrameAna_t::FRAME_DIRTY): { triggerEvent(INVALID_TC_FRAME, FRAME_DIRTY); sif::warning << "PdecHandler::checkFrameAna: Frame dirty" << std::endl; break; } case (FrameAna_t::FRAME_ILLEGAL): { sif::warning << "PdecHandler::checkFrameAna: Frame illegal for one reason" << std::endl; handleIReason(pdecFar, FRAME_ILLEGAL_ONE_REASON); break; } case (FrameAna_t::FRAME_ILLEGAL_MULTI_REASON): { sif::warning << "PdecHandler::checkFrameAna: Frame illegal for multiple reasons" << std::endl; handleIReason(pdecFar, FRAME_ILLEGAL_MULTIPLE_REASONS); break; } case (FrameAna_t::AD_DISCARDED_LOCKOUT): { triggerEvent(INVALID_TC_FRAME, AD_DISCARDED_LOCKOUT); sif::warning << "PdecHandler::checkFrameAna: AD frame discarded because of lockout" << std::endl; break; } case (FrameAna_t::AD_DISCARDED_WAIT): { triggerEvent(INVALID_TC_FRAME, AD_DISCARDED_LOCKOUT); sif::warning << "PdecHandler::checkFrameAna: AD frame discarded because of wait" << std::endl; break; } case (FrameAna_t::AD_DISCARDED_NS_VR): { triggerEvent(INVALID_TC_FRAME, AD_DISCARDED_NS_VS); sif::warning << "PdecHandler::checkFrameAna: AD frame discarded because N(S) or V(R)" << std::endl; break; } case (FrameAna_t::FRAME_ACCEPTED): { #if OBSW_DEBUG_PDEC_HANDLER == 1 sif::info << "PdecHandler::checkFrameAna: Accepted TC frame" << std::endl; #endif frameValid = true; break; } default: { sif::debug << "PdecHandler::checkFrameAna: Invalid frame analysis report" << std::endl; break; } } return frameValid; } void PdecHandler::handleIReason(uint32_t pdecFar, ReturnValue_t parameter1) { IReason_t ireason = static_cast((pdecFar & IREASON_MASK) >> IREASON_POSITION); switch (ireason) { case (IReason_t::NO_REPORT): { triggerEvent(INVALID_TC_FRAME, parameter1, NO_REPORT); sif::info << "PdecHandler::handleIReason: No illegal report" << std::endl; break; } case (IReason_t::ERROR_VERSION_NUMBER): { triggerEvent(INVALID_TC_FRAME, parameter1, ERROR_VERSION_NUMBER); sif::info << "PdecHandler::handleIReason: Error in version number and reserved A and B " << "fields" << std::endl; break; } case (IReason_t::ILLEGAL_COMBINATION): { triggerEvent(INVALID_TC_FRAME, parameter1, ILLEGAL_COMBINATION); sif::info << "PdecHandler::handleIReason: Illegal combination (AC) of bypass and control " << "command flags" << std::endl; break; } case (IReason_t::INVALID_SC_ID): { triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_SC_ID); sif::info << "PdecHandler::handleIReason: Invalid spacecraft identifier " << std::endl; break; } case (IReason_t::INVALID_VC_ID_MSB): { triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_VC_ID_MSB); sif::info << "PdecHandler::handleIReason: VC identifier bit 0 to 4 did not match " << std::endl; break; } case (IReason_t::INVALID_VC_ID_LSB): { triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_VC_ID_LSB); sif::info << "PdecHandler::handleIReason: VC identifier bit 5 did not match " << std::endl; break; } case (IReason_t::NS_NOT_ZERO): { triggerEvent(INVALID_TC_FRAME, parameter1, NS_NOT_ZERO); sif::info << "PdecHandler::handleIReason: N(S) of BC or BD frame not set to all zeros" << std::endl; break; } case (IReason_t::INCORRECT_BC_CC): { triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_BC_CC); sif::info << "PdecHandler::handleIReason: Invalid BC control command format" << std::endl; break; } default: { sif::info << "PdecHandler::handleIReason: Invalid reason id" << std::endl; break; } } } void PdecHandler::handleNewTc() { ReturnValue_t result = returnvalue::OK; uint32_t tcLength = 0; result = readTc(tcLength); if (result != returnvalue::OK) { return; } #if OBSW_DEBUG_PDEC_HANDLER == 1 unsigned int mapId = tcSegment[0] & MAP_ID_MASK; sif::info << "PdecHandler::handleNewTc: Received TC segment with map ID " << mapId << std::endl; printTC(tcLength); #endif /* OBSW_DEBUG_PDEC_HANDLER */ store_address_t storeId; result = tcStore->addData(&storeId, tcSegment + 1, tcLength - 1); if (result != returnvalue::OK) { sif::warning << "PdecHandler::handleNewTc: Failed to add received space packet to store" << std::endl; return; } TmTcMessage message(storeId); result = MessageQueueSenderIF::sendMessage(tcDestination->getRequestQueue(), &message); if (result != returnvalue::OK) { sif::warning << "PdecHandler::handleNewTc: Failed to send message to TC destination" << std::endl; tcStore->deleteData(storeId); return; } return; } ReturnValue_t PdecHandler::readTc(uint32_t& tcLength) { uint32_t tcOffset = (*(registerBaseAddress + PDEC_BPTR_OFFSET) - PHYSICAL_RAM_BASE_ADDRESS) / 4; #if OBSW_DEBUG_PDEC_HANDLER == 1 sif::debug << "PdecHandler::readTc: TC offset: 0x" << std::hex << tcOffset << std::endl; #endif /* OBSW_DEBUG_PDEC_HANDLER */ tcLength = *(registerBaseAddress + PDEC_SLEN_OFFSET); #if OBSW_DEBUG_PDEC_HANDLER == 1 sif::debug << "PdecHandler::readTc: TC segment length: " << std::dec << tcLength << std::endl; #endif /* OBSW_DEBUG_PDEC_HANDLER */ if (tcLength > MAX_TC_SEGMENT_SIZE) { sif::warning << "PdecHandler::handleNewTc: Read invalid TC length from PDEC register" << std::endl; return returnvalue::FAILED; } uint32_t idx = 0; uint32_t tcData = 0; for (idx = 0; idx <= tcLength; idx = idx + 4) { tcData = *(ramBaseAddress + tcOffset + idx / 4); if (idx == 0) { tcSegment[idx] = static_cast((tcData >> 16) & 0xFF); tcSegment[idx + 1] = static_cast((tcData >> 8) & 0xFF); tcSegment[idx + 2] = static_cast(tcData & 0xFF); } else if (tcLength - idx + 1 == 3) { tcSegment[idx - 1] = static_cast((tcData >> 24) & 0xFF); tcSegment[idx] = static_cast((tcData >> 16) & 0xFF); tcSegment[idx + 1] = static_cast((tcData >> 8) & 0xFF); } else if (tcLength - idx + 1 == 2) { tcSegment[idx - 1] = static_cast((tcData >> 24) & 0xFF); tcSegment[idx] = static_cast((tcData >> 16) & 0xFF); } else if (tcLength - idx + 1 == 1) { tcSegment[idx - 1] = static_cast((tcData >> 24) & 0xFF); } else { tcSegment[idx - 1] = static_cast((tcData >> 24) & 0xFF); tcSegment[idx] = static_cast((tcData >> 16) & 0xFF); tcSegment[idx + 1] = static_cast((tcData >> 8) & 0xFF); tcSegment[idx + 2] = static_cast(tcData & 0xFF); } } // Backend buffer is handled back to PDEC3 *(registerBaseAddress + PDEC_BFREE_OFFSET) = 0; return returnvalue::OK; } void PdecHandler::printTC(uint32_t tcLength) { std::stringstream tcSegmentStream; tcSegmentStream << "TC segment data: 0x"; for (uint32_t idx = 0; idx < tcLength; idx++) { tcSegmentStream << std::setfill('0') << std::setw(2) << std::hex << static_cast(tcSegment[idx]); } sif::info << tcSegmentStream.str() << std::endl; } uint8_t PdecHandler::calcMapAddrEntry(uint8_t moduleId) { uint8_t lutEntry = 0; uint8_t parity = getOddParity(moduleId | (1 << VALID_POSITION)); lutEntry = (parity << PARITY_POSITION) | (1 << VALID_POSITION) | moduleId; return lutEntry; } uint8_t PdecHandler::getOddParity(uint8_t number) { uint8_t parityBit = 0; uint8_t countBits = 0; for (unsigned int idx = 0; idx < sizeof(number) * 8; idx++) { countBits += (number >> idx) & 0x1; } parityBit = ~(countBits & 0x1) & 0x1; return parityBit; } uint32_t PdecHandler::getClcw() { return *(registerBaseAddress + PDEC_CLCW_OFFSET); } uint32_t PdecHandler::getPdecMon() { return *(registerBaseAddress + PDEC_MON_OFFSET); } void PdecHandler::printClcw() { uint32_t clcw = getClcw(); uint8_t type = static_cast((clcw >> 31) & 0x1); uint8_t versionNo = static_cast((clcw >> 29) & 0x3); uint8_t status = static_cast((clcw >> 26) & 0x7); uint8_t cop = static_cast((clcw >> 24) & 0x3); uint8_t vcId = static_cast((clcw >> 18) & 0x3F); uint8_t noRf = static_cast((clcw >> 15) & 0x1); uint8_t noBitLock = static_cast((clcw >> 14) & 0x1); uint8_t lockoutFlag = static_cast((clcw >> 13) & 0x1); uint8_t waitFlag = static_cast((clcw >> 12) & 0x1); uint8_t retransmitFlag = static_cast((clcw >> 11) & 0x1); uint8_t farmBcnt = static_cast((clcw >> 9) & 0x3); // Expected frame sequence number in te next AD frame uint8_t repValue = static_cast(clcw & 0xFF); sif::info << std::setw(30) << std::left << "CLCW type: " << std::hex << "0x" << static_cast(type) << std::endl; sif::info << std::setw(30) << std::left << "CLCW version no: " << std::hex << "0x" << static_cast(versionNo) << std::endl; sif::info << std::setw(30) << std::left << "CLCW status: " << std::hex << "0x" << static_cast(status) << std::endl; sif::info << std::setw(30) << std::left << "CLCW COP: " << std::hex << "0x" << static_cast(cop) << std::endl; sif::info << std::setw(30) << std::left << "CLCW virtual channel ID: " << std::hex << "0x" << static_cast(vcId) << std::endl; sif::info << std::setw(30) << std::left << "CLCW no RF: " << std::hex << "0x" << static_cast(noRf) << std::endl; sif::info << std::setw(30) << std::left << "CLCW no bit lock: " << std::hex << "0x" << static_cast(noBitLock) << std::endl; sif::info << std::setw(30) << std::left << "CLCW lockout flag: " << std::hex << "0x" << static_cast(lockoutFlag) << std::endl; sif::info << std::setw(30) << std::left << "CLCW wait flag: " << std::hex << "0x" << static_cast(waitFlag) << std::endl; sif::info << std::setw(30) << std::left << "CLCW retransmit flag: " << std::hex << "0x" << static_cast(retransmitFlag) << std::endl; sif::info << std::setw(30) << std::left << "CLCW FARM B count: " << std::hex << "0x" << static_cast(farmBcnt) << std::endl; sif::info << std::setw(30) << std::left << "CLCW rep value: " << std::hex << "0x" << static_cast(repValue) << std::endl; } void PdecHandler::printPdecMon() { uint32_t pdecMon = getPdecMon(); uint32_t tc0ChannelStatus = (pdecMon & TC0_STATUS_MASK) >> TC0_STATUS_POS; uint32_t tc1ChannelStatus = (pdecMon & TC1_STATUS_MASK) >> TC1_STATUS_POS; uint32_t tc2ChannelStatus = (pdecMon & TC2_STATUS_MASK) >> TC2_STATUS_POS; uint32_t tc3ChannelStatus = (pdecMon & TC3_STATUS_MASK) >> TC3_STATUS_POS; uint32_t tc4ChannelStatus = (pdecMon & TC4_STATUS_MASK) >> TC4_STATUS_POS; uint32_t tc5ChannelStatus = (pdecMon & TC5_STATUS_MASK) >> TC5_STATUS_POS; uint32_t lock = (pdecMon & LOCK_MASK) >> LOCK_POS; sif::info << std::setw(30) << std::left << "TC0 status: " << getMonStatusString(tc0ChannelStatus) << std::endl; sif::info << std::setw(30) << std::left << "TC1 status: " << getMonStatusString(tc1ChannelStatus) << std::endl; sif::info << std::setw(30) << std::left << "TC2 status: " << getMonStatusString(tc2ChannelStatus) << std::endl; sif::info << std::setw(30) << std::left << "TC3 status: " << getMonStatusString(tc3ChannelStatus) << std::endl; sif::info << std::setw(30) << std::left << "TC4 status: " << getMonStatusString(tc4ChannelStatus) << std::endl; sif::info << std::setw(30) << std::left << "TC5 status: " << getMonStatusString(tc5ChannelStatus) << std::endl; sif::info << std::setw(30) << std::left << "Start sequence lock: " << lock << std::endl; } uint32_t PdecHandler::readFar() { return *(registerBaseAddress + PDEC_FAR_OFFSET); } std::string PdecHandler::getMonStatusString(uint32_t status) { switch (status) { case TC_CHANNEL_INACTIVE: return std::string("inactive"); case TC_CHANNEL_ACTIVE: return std::string("active"); case TC_CHANNEL_TIMEDOUT: return std::string("timed out"); default: sif::warning << "PdecHandler::getMonStatusString: Invalid status" << std::endl; return std::string(); break; } } ReturnValue_t PdecHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, const uint8_t* data, size_t size) { switch (actionId) { case PRINT_CLCW: printClcw(); return EXECUTION_FINISHED; case PRINT_PDEC_MON: printPdecMon(); return EXECUTION_FINISHED; default: return COMMAND_NOT_IMPLEMENTED; } }