diff --git a/bsp_q7s/core/ObjectFactory.cpp b/bsp_q7s/core/ObjectFactory.cpp index bcf5a50f..519dec8f 100644 --- a/bsp_q7s/core/ObjectFactory.cpp +++ b/bsp_q7s/core/ObjectFactory.cpp @@ -28,8 +28,13 @@ #include "linux/boardtest/UartTestClass.h" #include "linux/csp/CspComIF.h" #include "linux/csp/CspCookie.h" +#include "linux/devices/GPSHyperionLinuxController.h" +#include "linux/devices/devicedefinitions/StarTrackerDefinitions.h" +#include "linux/devices/startracker/StarTrackerHandler.h" +#include "linux/devices/startracker/StrHelper.h" #include "tmtc/apid.h" #include "tmtc/pusIds.h" + #if OBSW_TEST_LIBGPIOD == 1 #include "linux/boardtest/LibgpiodTest.h" #endif @@ -51,13 +56,6 @@ #include "fsfw_hal/linux/spi/SpiCookie.h" #include "fsfw_hal/linux/uart/UartComIF.h" #include "fsfw_hal/linux/uart/UartCookie.h" -#include "linux/boardtest/SpiTestClass.h" -#include "linux/csp/CspComIF.h" -#include "linux/csp/CspCookie.h" -#include "linux/devices/GPSHyperionLinuxController.h" -#include "linux/devices/devicedefinitions/StarTrackerDefinitions.h" -#include "linux/devices/startracker/StarTrackerHandler.h" -#include "linux/devices/startracker/StrHelper.h" #include "mission/core/GenericFactory.h" #include "mission/devices/ACUHandler.h" #include "mission/devices/BpxBatteryHandler.h" @@ -69,6 +67,7 @@ #include "mission/devices/PCDUHandler.h" #include "mission/devices/PDU1Handler.h" #include "mission/devices/PDU2Handler.h" +#include "mission/devices/PayloadPcduHandler.h" #include "mission/devices/PlocMPSoCHandler.h" #include "mission/devices/RadiationSensorHandler.h" #include "mission/devices/RwHandler.h" @@ -83,6 +82,7 @@ #include "mission/devices/devicedefinitions/RwDefinitions.h" #include "mission/devices/devicedefinitions/SusDefinitions.h" #include "mission/devices/devicedefinitions/SyrlinksDefinitions.h" +#include "mission/devices/devicedefinitions/payloadPcduDefinitions.h" #include "mission/tmtc/CCSDSHandler.h" #include "mission/tmtc/VirtualChannel.h" #include "mission/utility/TmFunnel.h" @@ -390,8 +390,7 @@ void ObjectFactory::createSunSensorComponents(LinuxLibgpioIF* gpioComIF, SpiComI spiCookie = new SpiCookie(addresses::SUS_6, gpioIds::CS_SUS_6, q7s::SPI_DEFAULT_DEV, SUS::MAX_CMD_SIZE, spi::DEFAULT_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); - SusHandler* susHandler6 = new SusHandler(objects::SUS_6, 6, objects::SPI_COM_IF, spiCookie, - gpioComIF, gpioIds::CS_SUS_6); + SusHandler* susHandler6 = new SusHandler(objects::SUS_6, 6, objects::SPI_COM_IF, spiCookie); spiCookie = new SpiCookie(addresses::SUS_7, gpioIds::CS_SUS_7, q7s::SPI_DEFAULT_DEV, SUS::MAX_CMD_SIZE, @@ -1181,15 +1180,6 @@ void ObjectFactory::createPlPcduComponents(LinuxLibgpioIF* gpioComIF, SpiComIF* gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_DRO, consumer, Direction::OUT, gpio::Levels::LOW); plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_DRO, gpio); - consumer = "PLPCDU_ENB_HPA"; - gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_HPA, consumer, Direction::OUT, - gpio::Levels::LOW); - plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_HPA, gpio); - consumer = "PLPCDU_ENB_MPA"; - gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_MPA, consumer, Direction::OUT, - gpio::Levels::LOW); - plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_MPA, gpio); - consumer = "PLPCDU_ENB_X8"; gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_X8, consumer, Direction::OUT, gpio::Levels::LOW); @@ -1198,6 +1188,14 @@ void ObjectFactory::createPlPcduComponents(LinuxLibgpioIF* gpioComIF, SpiComIF* gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_TX, consumer, Direction::OUT, gpio::Levels::LOW); plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_TX, gpio); + consumer = "PLPCDU_ENB_MPA"; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_MPA, consumer, Direction::OUT, + gpio::Levels::LOW); + plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_MPA, gpio); + consumer = "PLPCDU_ENB_HPA"; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_HPA, consumer, Direction::OUT, + gpio::Levels::LOW); + plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_HPA, gpio); // Chip select pin is active low consumer = "PLPCDU_ADC_CS"; @@ -1205,6 +1203,22 @@ void ObjectFactory::createPlPcduComponents(LinuxLibgpioIF* gpioComIF, SpiComIF* gpio::Levels::HIGH); plPcduGpios->addGpio(gpioIds::PLPCDU_ADC_CS, gpio); gpioComIF->addGpios(plPcduGpios); + SpiCookie* spiCookie = new SpiCookie(addresses::PLPCDU_ADC, gpioIds::PLPCDU_ADC_CS, + q7s::SPI_DEFAULT_DEV, plpcdu::MAX_ADC_REPLY_SIZE, + spi::DEFAULT_MAX_1227_MODE, spi::PL_PCDU_MAX_1227_SPEED); + // Create device handler components + auto plPcduHandler = + new PayloadPcduHandler(objects::PLPCDU_HANDLER, objects::SPI_COM_IF, spiCookie, gpioComIF, + SdCardManager::instance(), false); + spiCookie->setCallbackMode(PayloadPcduHandler::extConvAsTwoCallback, plPcduHandler); + static_cast(plPcduHandler); +#if OBSW_TEST_PL_PCDU == 1 + plPcduHandler->setStartUpImmediately(); +#if OBSW_DEBUG_PL_PCDU == 1 + plPcduHandler->setToGoToNormalModeImmediately(true); + plPcduHandler->enablePeriodicPrintout(true, 5); +#endif +#endif } void ObjectFactory::createTestComponents(LinuxLibgpioIF* gpioComIF) { diff --git a/common/config/devConf.h b/common/config/devConf.h index ae730e46..8a35a0fa 100644 --- a/common/config/devConf.h +++ b/common/config/devConf.h @@ -32,6 +32,8 @@ static const uint32_t SUS_MAX1227_SPI_FREQ = 976'000; static constexpr uint32_t DEFAULT_MAX_1227_SPEED = 976'000; static constexpr spi::SpiModes DEFAULT_MAX_1227_MODE = spi::SpiModes::MODE_3; +static constexpr uint32_t PL_PCDU_MAX_1227_SPEED = 976'000; + static constexpr uint32_t DEFAULT_ADIS16507_SPEED = 976'000; static constexpr spi::SpiModes DEFAULT_ADIS16507_MODE = spi::SpiModes::MODE_3; diff --git a/mission/devices/CMakeLists.txt b/mission/devices/CMakeLists.txt index ebd603d5..6a9035cf 100644 --- a/mission/devices/CMakeLists.txt +++ b/mission/devices/CMakeLists.txt @@ -17,5 +17,6 @@ target_sources(${LIB_EIVE_MISSION} PRIVATE RwHandler.cpp max1227.cpp SusHandler.cpp + PayloadPcduHandler.cpp SolarArrayDeploymentHandler.cpp ) diff --git a/mission/devices/PayloadPcduHandler.cpp b/mission/devices/PayloadPcduHandler.cpp new file mode 100644 index 00000000..1d5f4b89 --- /dev/null +++ b/mission/devices/PayloadPcduHandler.cpp @@ -0,0 +1,808 @@ +#include "PayloadPcduHandler.h" + +#include + +#ifdef FSFW_OSAL_LINUX +#include +#include +#include +#include +#include +#endif + +#include "devices/gpioIds.h" + +PayloadPcduHandler::PayloadPcduHandler(object_id_t objectId, object_id_t comIF, CookieIF* cookie, + GpioIF* gpioIF, SdCardMountedIF* sdcMan, + bool periodicPrintout) + : DeviceHandlerBase(objectId, comIF, cookie), + adcSet(this), + periodicPrintout(periodicPrintout), + gpioIF(gpioIF), + sdcMan(sdcMan) {} + +void PayloadPcduHandler::doStartUp() { + if ((state != States::PCDU_OFF) and (state != States::ON_TRANS_SSR)) { + // Config error + sif::error << "PayloadPcduHandler::doStartUp: Invalid state" << std::endl; + } + if (state == States::PCDU_OFF) { + // Switch on relays here + gpioIF->pullHigh(gpioIds::PLPCDU_ENB_VBAT0); + gpioIF->pullHigh(gpioIds::PLPCDU_ENB_VBAT1); + state = States::ON_TRANS_SSR; + transitionOk = true; + } + if (state == States::ON_TRANS_SSR) { + // If necessary, check whether a certain amount of time has elapsed + if (transitionOk) { + transitionOk = false; + state = States::ON_TRANS_ADC_CLOSE_ZERO; + // We are now in ON mode + startTransition(MODE_NORMAL, 0); + adcCountdown.setTimeout(50); + adcCountdown.resetTimer(); + adcState = AdcStates::BOOT_DELAY; + // The ADC can now be read. If the values are not close to zero, we should not allow + // transition + monMode = MonitoringMode::CLOSE_TO_ZERO; + } + } +} + +void PayloadPcduHandler::stateMachineToNormal() { + using namespace plpcdu; + if (adcState == AdcStates::BOOT_DELAY) { + if (adcCountdown.hasTimedOut()) { + adcState = AdcStates::SEND_SETUP; + adcCmdExecuted = false; + } + } + if (adcState == AdcStates::SEND_SETUP) { + if (adcCmdExecuted) { + adcState = AdcStates::NORMAL; + setMode(MODE_NORMAL, NORMAL_ADC_ONLY); + adcCountdown.setTimeout(100); + adcCountdown.resetTimer(); + adcCmdExecuted = false; + } + } + if (submode == plpcdu::NORMAL_ALL_ON) { + if (state == States::ON_TRANS_ADC_CLOSE_ZERO) { + if (not commandExecuted) { + float waitTime = DFT_SSR_TO_DRO_WAIT_TIME; + params.getValue(PARAM_KEY_MAP[SSR_TO_DRO_WAIT_TIME], waitTime); + countdown.setTimeout(std::round(waitTime * 1000)); + countdown.resetTimer(); + commandExecuted = true; + // TODO: For now, skip ADC check + transitionOk = true; + } + // ADC values are ok, 5 seconds have elapsed + if (transitionOk and countdown.hasTimedOut()) { + state = States::ON_TRANS_DRO; + // Now start monitoring for negative voltages instead + monMode = MonitoringMode::NEGATIVE; + commandExecuted = false; + transitionOk = false; + } + } + if (state == States::ON_TRANS_DRO) { + if (not commandExecuted) { + float waitTime = DFT_DRO_TO_X8_WAIT_TIME; + params.getValue(PARAM_KEY_MAP[DRO_TO_X8_WAIT_TIME], waitTime); + countdown.setTimeout(std::round(waitTime * 1000)); + countdown.resetTimer(); +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Enabling PL PCDU DRO module" << std::endl; +#endif + // Switch on DRO and start monitoring for negative voltages + gpioIF->pullHigh(gpioIds::PLPCDU_ENB_DRO); + adcCountdown.setTimeout(100); + adcCountdown.resetTimer(); + commandExecuted = true; + } + // ADC values are ok, 5 seconds have elapsed + if (transitionOk and countdown.hasTimedOut()) { + state = States::ON_TRANS_X8; + commandExecuted = false; + transitionOk = false; + } + } + if (state == States::ON_TRANS_X8) { + if (not commandExecuted) { + float waitTime = DFT_X8_TO_TX_WAIT_TIME; + params.getValue(PARAM_KEY_MAP[X8_TO_TX_WAIT_TIME], waitTime); + countdown.setTimeout(std::round(waitTime * 1000)); + countdown.resetTimer(); +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Enabling PL PCDU X8 module" << std::endl; +#endif + // Switch on X8 + gpioIF->pullHigh(gpioIds::PLPCDU_ENB_X8); + adcCountdown.setTimeout(100); + adcCountdown.resetTimer(); + commandExecuted = true; + } + // ADC values are ok, 5 seconds have elapsed + if (transitionOk and countdown.hasTimedOut()) { + state = States::ON_TRANS_TX; + commandExecuted = false; + transitionOk = false; + } + } + if (state == States::ON_TRANS_TX) { + if (not commandExecuted) { + float waitTime = DFT_TX_TO_MPA_WAIT_TIME; + params.getValue(PARAM_KEY_MAP[TX_TO_MPA_WAIT_TIME], waitTime); + countdown.setTimeout(std::round(waitTime * 1000)); + countdown.resetTimer(); +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Enabling PL PCDU TX module" << std::endl; +#endif + // Switch on TX + gpioIF->pullHigh(gpioIds::PLPCDU_ENB_TX); + // Wait for 100 ms before checking ADC values + adcCountdown.setTimeout(100); + adcCountdown.resetTimer(); + commandExecuted = true; + } + // ADC values are ok, 5 seconds have elapsed + if (transitionOk and countdown.hasTimedOut()) { + state = States::ON_TRANS_MPA; + commandExecuted = false; + transitionOk = false; + } + } + if (state == States::ON_TRANS_MPA) { + if (not commandExecuted) { + float waitTime = DFT_MPA_TO_HPA_WAIT_TIME; + params.getValue(PARAM_KEY_MAP[MPA_TO_HPA_WAIT_TIME], waitTime); + countdown.setTimeout(std::round(waitTime * 1000)); + countdown.resetTimer(); +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Enabling PL PCDU MPA module" << std::endl; +#endif + // Switch on MPA + gpioIF->pullHigh(gpioIds::PLPCDU_ENB_MPA); + // Wait for 100 ms before checking ADC values + adcCountdown.setTimeout(100); + adcCountdown.resetTimer(); + commandExecuted = true; + } + // ADC values are ok, 5 seconds have elapsed + if (transitionOk and countdown.hasTimedOut()) { + state = States::ON_TRANS_HPA; + commandExecuted = false; + transitionOk = false; + } + } + if (state == States::ON_TRANS_HPA) { + if (not commandExecuted) { + // Switch on HPA + gpioIF->pullHigh(gpioIds::PLPCDU_ENB_HPA); +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Enabling PL PCDU HPA module" << std::endl; +#endif + commandExecuted = true; + } + // ADC values are ok, 5 seconds have elapsed + if (transitionOk and countdown.hasTimedOut()) { + state = States::PCDU_ON; + setMode(MODE_NORMAL, plpcdu::NORMAL_ALL_ON); + countdown.resetTimer(); + commandExecuted = false; + transitionOk = false; + } + } + } +} + +void PayloadPcduHandler::doTransition(Mode_t modeFrom, Submode_t subModeFrom) { + if (mode == _MODE_TO_NORMAL) { + stateMachineToNormal(); + } +} + +void PayloadPcduHandler::doShutDown() { transitionBackToOff(false); } + +ReturnValue_t PayloadPcduHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + switch (adcState) { + case (AdcStates::SEND_SETUP): { + *id = plpcdu::SETUP_CMD; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (AdcStates::NORMAL): { + *id = plpcdu::READ_WITH_TEMP_EXT; + return buildCommandFromCommand(*id, nullptr, 0); + } + default: { + break; + } + } + return NOTHING_TO_SEND; +} + +ReturnValue_t PayloadPcduHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + if (adcState == AdcStates::SEND_SETUP) { + *id = plpcdu::SETUP_CMD; + return buildCommandFromCommand(*id, nullptr, 0); + } + if (mode == _MODE_TO_NORMAL) { + return buildNormalDeviceCommand(id); + } + return NOTHING_TO_SEND; +} + +void PayloadPcduHandler::fillCommandAndReplyMap() { + insertInCommandAndReplyMap(plpcdu::READ_CMD, 2, &adcSet); + insertInCommandAndReplyMap(plpcdu::READ_TEMP_EXT, 1, &adcSet); + insertInCommandAndReplyMap(plpcdu::READ_WITH_TEMP_EXT, 1, &adcSet); + insertInCommandAndReplyMap(plpcdu::SETUP_CMD, 1); +} + +ReturnValue_t PayloadPcduHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + switch (deviceCommand) { + case (plpcdu::SETUP_CMD): { + cmdBuf[0] = plpcdu::SETUP_BYTE; + rawPacket = cmdBuf.data(); + rawPacketLen = 1; + break; + } + case (plpcdu::READ_CMD): { + max1227::prepareExternallyClockedRead0ToN(cmdBuf.data(), plpcdu::CHANNEL_N, rawPacketLen); + rawPacket = cmdBuf.data(); + break; + } + case (plpcdu::READ_TEMP_EXT): { + max1227::prepareExternallyClockedTemperatureRead(cmdBuf.data(), rawPacketLen); + rawPacket = cmdBuf.data(); + break; + } + case (plpcdu::READ_WITH_TEMP_EXT): { + size_t sz = 0; + max1227::prepareExternallyClockedRead0ToN(cmdBuf.data(), plpcdu::CHANNEL_N, sz); + max1227::prepareExternallyClockedTemperatureRead(cmdBuf.data() + sz, sz); + rawPacketLen = sz; + rawPacket = cmdBuf.data(); + break; + } + default: { + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + } + return RETURN_OK; +} + +ReturnValue_t PayloadPcduHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + // SPI is full duplex + *foundId = getPendingCommand(); + *foundLen = remainingSize; + return HasReturnvaluesIF::RETURN_OK; +} + +ReturnValue_t PayloadPcduHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t* packet) { + using namespace plpcdu; + switch (id) { + case (SETUP_CMD): { + if (mode == _MODE_TO_NORMAL) { + adcCmdExecuted = true; + } + break; + } + case (READ_TEMP_EXT): { + uint8_t tempStartIdx = TEMP_REPLY_SIZE - 2; + adcSet.tempC.value = + max1227::getTemperature(packet[tempStartIdx] << 8 | packet[tempStartIdx + 1]); + break; + } + case (READ_CMD): { + PoolReadGuard pg(&adcSet); + if (pg.getReadResult() != HasReturnvaluesIF::RETURN_OK) { + return pg.getReadResult(); + } + handleExtConvRead(packet); + checkAdcValues(); + adcSet.setValidity(true, true); + handlePrintout(); + break; + } + case (READ_WITH_TEMP_EXT): { + PoolReadGuard pg(&adcSet); + if (pg.getReadResult() != HasReturnvaluesIF::RETURN_OK) { + return pg.getReadResult(); + } + handleExtConvRead(packet); + uint8_t tempStartIdx = ADC_REPLY_SIZE + TEMP_REPLY_SIZE - 2; + adcSet.tempC.value = + max1227::getTemperature(packet[tempStartIdx] << 8 | packet[tempStartIdx + 1]); + checkAdcValues(); + adcSet.setValidity(true, true); + handlePrintout(); + break; + } + default: { + break; + } + } + return HasReturnvaluesIF::RETURN_OK; +} + +uint32_t PayloadPcduHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { + // 20 minutes transition delay is allowed + return 20 * 60 * 60; +} + +ReturnValue_t PayloadPcduHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(plpcdu::PlPcduPoolIds::CHANNEL_VEC, &channelValues); + localDataPoolMap.emplace(plpcdu::PlPcduPoolIds::PROCESSED_VEC, &processedValues); + localDataPoolMap.emplace(plpcdu::PlPcduPoolIds::TEMP, &tempC); + poolManager.subscribeForPeriodicPacket(adcSet.getSid(), false, 0.1, true); + return HasReturnvaluesIF::RETURN_OK; +} + +void PayloadPcduHandler::setToGoToNormalModeImmediately(bool enable) { + this->goToNormalMode = enable; +} + +void PayloadPcduHandler::handleExtConvRead(const uint8_t* bufStart) { + for (uint8_t idx = 0; idx < 12; idx++) { + adcSet.channels[idx] = bufStart[idx * 2 + 1] << 8 | bufStart[idx * 2 + 2]; + } +} + +void PayloadPcduHandler::handlePrintout() { + using namespace plpcdu; + if (periodicPrintout) { + if (opDivider.checkAndIncrement()) { + sif::info << "PL PCDU ADC hex [" << std::setfill('0') << std::hex; + for (uint8_t idx = 0; idx < 12; idx++) { + sif::info << std::setw(3) << adcSet.channels[idx]; + if (idx < 11) { + sif::info << ","; + } + } + sif::info << "] | T[C] " << std::dec << adcSet.tempC.value << std::endl; + sif::info << "Neg V: " << adcSet.processed[U_NEG_V_FB] << std::endl; + sif::info << "I HPA [mA]: " << adcSet.processed[I_HPA] << std::endl; + sif::info << "U HPA [V]: " << adcSet.processed[U_HPA_DIV_6] << std::endl; + sif::info << "I MPA [mA]: " << adcSet.processed[I_MPA] << std::endl; + sif::info << "U MPA [V]: " << adcSet.processed[U_MPA_DIV_6] << std::endl; + sif::info << "I TX [mA]: " << adcSet.processed[I_TX] << std::endl; + sif::info << "U TX [V]: " << adcSet.processed[U_TX_DIV_6] << std::endl; + sif::info << "I X8 [mA]: " << adcSet.processed[I_X8] << std::endl; + sif::info << "U X8 [V]: " << adcSet.processed[U_X8_DIV_6] << std::endl; + sif::info << "I DRO [mA]: " << adcSet.processed[I_DRO] << std::endl; + sif::info << "U DRO [V]: " << adcSet.processed[U_DRO_DIV_6] << std::endl; + } + } +} + +void PayloadPcduHandler::enablePeriodicPrintout(bool enable, uint8_t divider) { + this->periodicPrintout = enable; + opDivider.setDivider(divider); +} + +void PayloadPcduHandler::transitionBackToOff(bool notifyFdir) { + States currentState = state; + gpioIF->pullLow(gpioIds::PLPCDU_ENB_HPA); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_MPA); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_TX); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_X8); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_DRO); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_TX); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_VBAT0); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_VBAT1); + state = States::PCDU_OFF; + adcState = AdcStates::OFF; + setMode(MODE_OFF); + if (notifyFdir) { + triggerEvent(TRANSITION_BACK_TO_OFF, static_cast(currentState)); + } +} + +void PayloadPcduHandler::checkAdcValues() { + using namespace plpcdu; + checkJsonFileInit(); + adcSet.processed[U_BAT_DIV_6] = + static_cast(adcSet.channels[0]) * VOLTAGE_DIV / MAX122X_BIT * MAX122X_VREF; + adcSet.processed[U_NEG_V_FB] = + V_POS - VOLTAGE_DIV_U_NEG * + (V_POS - static_cast(adcSet.channels[1]) / MAX122X_BIT * MAX122X_VREF); + adcSet.processed[I_HPA] = static_cast(adcSet.channels[2]) * SCALE_CURRENT_HPA * 1000.0; + adcSet.processed[U_HPA_DIV_6] = static_cast(adcSet.channels[3]) * SCALE_VOLTAGE; + adcSet.processed[I_MPA] = static_cast(adcSet.channels[4]) * SCALE_CURRENT_MPA * 1000.0; + adcSet.processed[U_MPA_DIV_6] = static_cast(adcSet.channels[5]) * SCALE_VOLTAGE; + adcSet.processed[I_TX] = static_cast(adcSet.channels[6]) * SCALE_CURRENT_TX * 1000.0; + adcSet.processed[U_TX_DIV_6] = static_cast(adcSet.channels[7]) * SCALE_VOLTAGE; + adcSet.processed[I_X8] = static_cast(adcSet.channels[8]) * SCALE_CURRENT_X8 * 1000.0; + adcSet.processed[U_X8_DIV_6] = static_cast(adcSet.channels[9]) * SCALE_VOLTAGE; + adcSet.processed[I_DRO] = static_cast(adcSet.channels[10]) * SCALE_CURRENT_DRO * 1000.0; + adcSet.processed[U_DRO_DIV_6] = static_cast(adcSet.channels[11]) * SCALE_VOLTAGE; + float lowerBound = 0.0; + float upperBound = 0.0; + bool adcTransition = false; + adcTransition = state == States::ON_TRANS_DRO and adcCountdown.isBusy(); + // Now check against voltage and current limits, depending on state + if (state >= States::ON_TRANS_DRO and not adcTransition) { + if (ssrToDroInjectionRequested) { + handleFailureInjection("SSR to DRO", NEG_V_OUT_OF_BOUNDS); + ssrToDroInjectionRequested = false; + return; + } + params.getValue(PARAM_KEY_MAP[NEG_V_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[NEG_V_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_NEG_V_FB], lowerBound, upperBound, + NEG_V_OUT_OF_BOUNDS)) { + return; + } + params.getValue(PARAM_KEY_MAP[DRO_U_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[DRO_U_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_DRO_DIV_6], lowerBound, upperBound, + U_DRO_OUT_OF_BOUNDS)) { + return; + } + params.getValue(PARAM_KEY_MAP[DRO_I_UPPER_BOUND], upperBound); + if (not checkCurrent(adcSet.processed[I_DRO], upperBound, I_DRO_OUT_OF_BOUNDS)) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::warning << "Detected out of bounds current for DRO: " << adcSet.processed[I_DRO] + << ", Raw: " << adcSet.channels[I_DRO] << std::endl; +#endif + return; + } + } + adcTransition = state == States::ON_TRANS_X8 and adcCountdown.isBusy(); + if (state >= States::ON_TRANS_X8 and not adcTransition) { + if (droToX8InjectionRequested) { + handleFailureInjection("X8 to TX", U_X8_OUT_OF_BOUNDS); + droToX8InjectionRequested = false; + return; + } + params.getValue(PARAM_KEY_MAP[X8_U_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[X8_U_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_X8_DIV_6], lowerBound, upperBound, + U_X8_OUT_OF_BOUNDS)) { + return; + } + params.getValue(PARAM_KEY_MAP[X8_I_UPPER_BOUND], upperBound); + if (not checkCurrent(adcSet.processed[I_X8], upperBound, I_X8_OUT_OF_BOUNDS)) { + return; + } + } + adcTransition = state == States::ON_TRANS_TX and adcCountdown.isBusy(); + if (state >= States::ON_TRANS_TX and not adcTransition) { + if (txToMpaInjectionRequested) { + handleFailureInjection("TX to MPA", U_TX_OUT_OF_BOUNDS); + txToMpaInjectionRequested = false; + return; + } + params.getValue(PARAM_KEY_MAP[TX_U_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[TX_U_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_TX_DIV_6], lowerBound, upperBound, + U_TX_OUT_OF_BOUNDS)) { + return; + } + params.getValue(PARAM_KEY_MAP[TX_I_UPPER_BOUND], upperBound); + if (not checkCurrent(adcSet.processed[I_TX], upperBound, I_TX_OUT_OF_BOUNDS)) { + return; + } + } + adcTransition = state == States::ON_TRANS_MPA and adcCountdown.isBusy(); + if (state >= States::ON_TRANS_MPA and not adcTransition) { + if (mpaToHpaInjectionRequested) { + handleFailureInjection("MPA to HPA", U_HPA_OUT_OF_BOUNDS); + mpaToHpaInjectionRequested = false; + return; + } + params.getValue(PARAM_KEY_MAP[MPA_U_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[MPA_U_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_MPA_DIV_6], lowerBound, upperBound, + U_MPA_OUT_OF_BOUNDS)) { + return; + } + params.getValue(PARAM_KEY_MAP[MPA_I_UPPER_BOUND], upperBound); + if (not checkCurrent(adcSet.processed[I_MPA], upperBound, I_MPA_OUT_OF_BOUNDS)) { + return; + } + } + adcTransition = state == States::ON_TRANS_HPA and adcCountdown.isBusy(); + if (state >= States::ON_TRANS_HPA and not adcTransition) { + if (allOnInjectRequested) { + handleFailureInjection("All On", U_HPA_OUT_OF_BOUNDS); + allOnInjectRequested = false; + return; + } + params.getValue(PARAM_KEY_MAP[HPA_U_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[HPA_U_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_HPA_DIV_6], lowerBound, upperBound, + U_HPA_OUT_OF_BOUNDS)) { + return; + } + params.getValue(PARAM_KEY_MAP[HPA_I_UPPER_BOUND], upperBound); + if (not checkCurrent(adcSet.processed[I_HPA], upperBound, I_HPA_OUT_OF_BOUNDS)) { + return; + } + } + transitionOk = true; +} + +void PayloadPcduHandler::checkJsonFileInit() { + if (not jsonFileInitComplete) { + sd::SdCard prefSd; + sdcMan->getPreferredSdCard(prefSd); + if (sdcMan->isSdCardMounted(prefSd)) { + params.initialize(sdcMan->getCurrentMountPrefix(prefSd)); + jsonFileInitComplete = true; + } + } +} + +bool PayloadPcduHandler::checkVoltage(float val, float lowerBound, float upperBound, Event event) { + bool tooLarge = false; + if (val < lowerBound or val > upperBound) { + if (val > upperBound) { + tooLarge = true; + } else { + tooLarge = false; + } + uint32_t p2 = 0; + serializeFloat(p2, val); + triggerEvent(event, tooLarge, p2); + transitionOk = false; + transitionBackToOff(true); + return false; + } + return true; +} + +bool PayloadPcduHandler::checkCurrent(float val, float upperBound, Event event) { + if (val > upperBound) { + uint32_t p2 = 0; + serializeFloat(p2, val); + triggerEvent(event, true, p2); + transitionOk = false; + transitionBackToOff(true); + return false; + } + return true; +} + +ReturnValue_t PayloadPcduHandler::isModeCombinationValid(Mode_t mode, Submode_t submode) { + if (mode == MODE_NORMAL and submode <= 1) { + return HasReturnvaluesIF::RETURN_OK; + } + return DeviceHandlerBase::isModeCombinationValid(mode, submode); +} + +ReturnValue_t PayloadPcduHandler::serializeFloat(uint32_t& param, float val) { + size_t dummy = 0; + return SerializeAdapter::serialize(&val, reinterpret_cast(¶m), &dummy, 4, + SerializeIF::Endianness::NETWORK); +} + +ReturnValue_t PayloadPcduHandler::getParameter(uint8_t domainId, uint8_t uniqueId, + ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, + uint16_t startAtIndex) { + using namespace plpcdu; + switch (uniqueId) { + case (PlPcduParamIds::NEG_V_LOWER_BOUND): + case (PlPcduParamIds::NEG_V_UPPER_BOUND): + case (PlPcduParamIds::DRO_U_LOWER_BOUND): + case (PlPcduParamIds::DRO_U_UPPER_BOUND): + case (PlPcduParamIds::DRO_I_UPPER_BOUND): + case (PlPcduParamIds::X8_U_LOWER_BOUND): + case (PlPcduParamIds::X8_U_UPPER_BOUND): + case (PlPcduParamIds::X8_I_UPPER_BOUND): + case (PlPcduParamIds::TX_U_LOWER_BOUND): + case (PlPcduParamIds::TX_U_UPPER_BOUND): + case (PlPcduParamIds::TX_I_UPPER_BOUND): + case (PlPcduParamIds::MPA_U_LOWER_BOUND): + case (PlPcduParamIds::MPA_U_UPPER_BOUND): + case (PlPcduParamIds::MPA_I_UPPER_BOUND): + case (PlPcduParamIds::HPA_U_LOWER_BOUND): + case (PlPcduParamIds::HPA_U_UPPER_BOUND): + case (PlPcduParamIds::HPA_I_UPPER_BOUND): + case (PlPcduParamIds::SSR_TO_DRO_WAIT_TIME): + case (PlPcduParamIds::DRO_TO_X8_WAIT_TIME): + case (PlPcduParamIds::X8_TO_TX_WAIT_TIME): + case (PlPcduParamIds::TX_TO_MPA_WAIT_TIME): + case (PlPcduParamIds::MPA_TO_HPA_WAIT_TIME): { + handleDoubleParamUpdate(PARAM_KEY_MAP[static_cast(uniqueId)], + parameterWrapper, newValues); + break; + } + case (PlPcduParamIds::INJECT_SSR_TO_DRO_FAILURE): { + ssrToDroInjectionRequested = true; + break; + } + case (PlPcduParamIds::INJECT_DRO_TO_X8_FAILURE): { + droToX8InjectionRequested = true; + break; + } + case (PlPcduParamIds::INJECT_X8_TO_TX_FAILURE): { + x8ToTxInjectionRequested = true; + break; + } + case (PlPcduParamIds::INJECT_TX_TO_MPA_FAILURE): { + txToMpaInjectionRequested = true; + break; + } + case (PlPcduParamIds::INJECT_MPA_TO_HPA_FAILURE): { + mpaToHpaInjectionRequested = true; + break; + } + case (PlPcduParamIds::INJECT_ALL_ON_FAILURE): { + allOnInjectRequested = true; + break; + } + default: { + return DeviceHandlerBase::getParameter(domainId, uniqueId, parameterWrapper, newValues, + startAtIndex); + } + } + return HasReturnvaluesIF::RETURN_OK; +} + +void PayloadPcduHandler::handleFailureInjection(std::string output, Event event) { + sif::info << "PayloadPcduHandler::checkAdcValues: " << output + << " failure injection. " + "Transitioning back to off" + << std::endl; + triggerEvent(event, 0, 0); + transitionOk = false; + transitionBackToOff(true); + droToX8InjectionRequested = false; +} + +ReturnValue_t PayloadPcduHandler::handleDoubleParamUpdate(std::string key, + ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues) { + double newValue = 0.0; + ReturnValue_t result = newValues->getElement(&newValue, 0, 0); + if (result != HasReturnvaluesIF::RETURN_OK) { + return result; + } + params.setValue(key, newValue); + // Do this so the dumping and loading with the framework works as well + doubleDummy = newValue; + parameterWrapper->set(doubleDummy); + return params.writeJsonFile(); +} + +#ifdef FSFW_OSAL_LINUX +ReturnValue_t PayloadPcduHandler::extConvAsTwoCallback(SpiComIF* comIf, SpiCookie* cookie, + const uint8_t* sendData, size_t sendLen, + void* args) { + auto handler = reinterpret_cast(args); + if (handler == nullptr) { + sif::error << "GyroADIS16507Handler::spiSendCallback: Passed handler pointer is invalid!" + << std::endl; + return HasReturnvaluesIF::RETURN_FAILED; + } + DeviceCommandId_t currentCommand = handler->getPendingCommand(); + switch (currentCommand) { + case (plpcdu::READ_WITH_TEMP_EXT): { + return transferAsTwo(comIf, cookie, sendData, sendLen, false); + } + case (plpcdu::READ_TEMP_EXT): { + return transferAsTwo(comIf, cookie, sendData, sendLen, true); + } + default: { + return comIf->performRegularSendOperation(cookie, sendData, sendLen); + } + } + return HasReturnvaluesIF::RETURN_OK; +} + +ReturnValue_t PayloadPcduHandler::transferAsTwo(SpiComIF* comIf, SpiCookie* cookie, + const uint8_t* sendData, size_t sendLen, + bool tempOnly) { + ReturnValue_t result = HasReturnvaluesIF::RETURN_OK; + int retval = 0; + // Prepare transfer + int fileDescriptor = 0; + std::string device = cookie->getSpiDevice(); + UnixFileGuard fileHelper(device, &fileDescriptor, O_RDWR, "SpiComIF::sendMessage"); + if (fileHelper.getOpenResult() != HasReturnvaluesIF::RETURN_OK) { + return SpiComIF::OPENING_FILE_FAILED; + } + spi::SpiModes spiMode = spi::SpiModes::MODE_0; + uint32_t spiSpeed = 0; + cookie->getSpiParameters(spiMode, spiSpeed, nullptr); + comIf->setSpiSpeedAndMode(fileDescriptor, spiMode, spiSpeed); + cookie->assignWriteBuffer(sendData); + size_t transferLen = plpcdu::TEMP_REPLY_SIZE; + if (not tempOnly) { + transferLen += plpcdu::ADC_REPLY_SIZE; + } + cookie->setTransferSize(transferLen); + + gpioId_t gpioId = cookie->getChipSelectPin(); + GpioIF* gpioIF = comIf->getGpioInterface(); + MutexIF::TimeoutType timeoutType = MutexIF::TimeoutType::WAITING; + uint32_t timeoutMs = 0; + MutexIF* mutex = comIf->getMutex(&timeoutType, &timeoutMs); + if (mutex == nullptr or gpioIF == nullptr) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::warning << "GyroADIS16507Handler::spiSendCallback: " + "Mutex or GPIO interface invalid" + << std::endl; + return HasReturnvaluesIF::RETURN_FAILED; +#endif + } + + if (gpioId != gpio::NO_GPIO) { + result = mutex->lockMutex(timeoutType, timeoutMs); + if (result != RETURN_OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "SpiComIF::sendMessage: Failed to lock mutex" << std::endl; +#endif + return result; + } + } + spi_ioc_transfer* transferStruct = cookie->getTransferStructHandle(); + uint64_t origTx = transferStruct->tx_buf; + uint64_t origRx = transferStruct->rx_buf; + if (tempOnly) { + transferLen = 1; + } else { + transferLen = plpcdu::ADC_REPLY_SIZE + 1; + } + transferStruct->len = transferLen; + // Pull SPI CS low. For now, no support for active high given + if (gpioId != gpio::NO_GPIO) { + gpioIF->pullLow(gpioId); + } + + // Execute transfer + // Initiate a full duplex SPI transfer. + retval = ioctl(fileDescriptor, SPI_IOC_MESSAGE(1), cookie->getTransferStructHandle()); + if (retval < 0) { + utility::handleIoctlError("SpiComIF::sendMessage: ioctl error."); + result = SpiComIF::FULL_DUPLEX_TRANSFER_FAILED; + } +#if FSFW_HAL_SPI_WIRETAPPING == 1 + comIf->performSpiWiretapping(cookie); +#endif /* FSFW_LINUX_SPI_WIRETAPPING == 1 */ + + if (gpioId != gpio::NO_GPIO) { + gpioIF->pullHigh(gpioId); + } + + transferStruct->tx_buf += transferLen; + transferStruct->rx_buf += transferLen; + transferStruct->len = plpcdu::TEMP_REPLY_SIZE - 1; + if (gpioId != gpio::NO_GPIO) { + gpioIF->pullLow(gpioId); + } + + // Execute transfer + // Initiate a full duplex SPI transfer. + retval = ioctl(fileDescriptor, SPI_IOC_MESSAGE(1), cookie->getTransferStructHandle()); + if (retval < 0) { + utility::handleIoctlError("SpiComIF::sendMessage: ioctl error."); + result = SpiComIF::FULL_DUPLEX_TRANSFER_FAILED; + } +#if FSFW_HAL_SPI_WIRETAPPING == 1 + comIf->performSpiWiretapping(cookie); +#endif /* FSFW_LINUX_SPI_WIRETAPPING == 1 */ + + if (gpioId != gpio::NO_GPIO) { + gpioIF->pullHigh(gpioId); + } + + transferStruct->tx_buf = origTx; + transferStruct->rx_buf = origRx; + if (gpioId != gpio::NO_GPIO) { + mutex->unlockMutex(); + } + return HasReturnvaluesIF::RETURN_OK; +} + +#endif diff --git a/mission/devices/PayloadPcduHandler.h b/mission/devices/PayloadPcduHandler.h new file mode 100644 index 00000000..b1c34359 --- /dev/null +++ b/mission/devices/PayloadPcduHandler.h @@ -0,0 +1,170 @@ +#ifndef LINUX_DEVICES_PLPCDUHANDLER_H_ +#define LINUX_DEVICES_PLPCDUHANDLER_H_ + +#include +#include +#include + +#include "events/subsystemIdRanges.h" +#include "fsfw/FSFW.h" +#include "fsfw_hal/common/gpio/GpioIF.h" +#include "mission/devices/devicedefinitions/payloadPcduDefinitions.h" +#include "mission/memory/SdCardMountedIF.h" + +#ifdef FSFW_OSAL_LINUX +class SpiComIF; +class SpiCookie; +#endif + +/** + * @brief Device handler for the EIVE Payload PCDU + * @details + * Documentation: + * https://egit.irs.uni-stuttgart.de/eive/eive_dokumente/src/branch/master/400_Raumsegment/412_PayloaPCDUDocumentation/release/EIVE-D-421-001_PLPCDU_Documentation.pdf + * + * Important components: + * - SSR - Solid State Relay: Decouples voltages from battery + * - DRO - Dielectric Resonsant Oscillator: Generates modulation signal + * - X8: Frequency X8 Multiplicator + * - TX: Transmitter/Sender module. Modulates data onto carrier signal + * - MPA - Medium Power Amplifier + * - HPA - High Power Amplifier + */ +class PayloadPcduHandler : public DeviceHandlerBase { + public: + static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PL_PCDU_HANDLER; + //! [EXPORT] : [COMMENT] Could not transition properly and went back to ALL OFF + static constexpr Event TRANSITION_BACK_TO_OFF = + event::makeEvent(SUBSYSTEM_ID, 0, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event NEG_V_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 1, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event U_DRO_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 2, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event I_DRO_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 3, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event U_X8_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 4, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event I_X8_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 5, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event U_TX_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 6, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event I_TX_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 7, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event U_MPA_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 8, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event I_MPA_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 9, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event U_HPA_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 10, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event I_HPA_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 11, severity::MEDIUM); + + PayloadPcduHandler(object_id_t objectId, object_id_t comIF, CookieIF* cookie, GpioIF* gpioIF, + SdCardMountedIF* sdcMan, bool periodicPrintout); + + void setToGoToNormalModeImmediately(bool enable); + void enablePeriodicPrintout(bool enable, uint8_t divider); + + static ReturnValue_t extConvAsTwoCallback(SpiComIF* comIf, SpiCookie* cookie, + const uint8_t* sendData, size_t sendLen, void* args); + +#ifdef FSFW_OSAL_LINUX + static ReturnValue_t transferAsTwo(SpiComIF* comIf, SpiCookie* cookie, const uint8_t* sendData, + size_t sendLen, bool tempOnly); +#endif + + private: + enum class States : uint8_t { + PCDU_OFF, + // Solid State Relay, enable battery voltages VBAT0 and VBAT1. This will also switch on + // the ADC + ON_TRANS_SSR, + ON_TRANS_ADC_CLOSE_ZERO, + // Enable Dielectric Resonant Oscillator and start monitoring voltages as + // soon as DRO voltage reaches 6V + ON_TRANS_DRO, + // Switch on X8 compoennt and monitor voltages for 5 seconds + ON_TRANS_X8, + // Switch on TX component and monitor voltages for 5 seconds + ON_TRANS_TX, + // Switch on MPA component and monitor voltages for 5 seconds + ON_TRANS_MPA, + // Switch on HPA component and monitor voltages for 5 seconds + ON_TRANS_HPA, + // All components of the experiment are on + PCDU_ON, + } state = States::PCDU_OFF; + + enum class AdcMode { EXT_CONV, INT_CONV } adcMode = AdcMode::INT_CONV; + + enum class MonitoringMode { NONE, CLOSE_TO_ZERO, NEGATIVE } monMode = MonitoringMode::NONE; + + enum class AdcStates { OFF, BOOT_DELAY, SEND_SETUP, NORMAL } adcState = AdcStates::OFF; + + bool goToNormalMode = false; + plpcdu::PlPcduAdcSet adcSet; + std::array cmdBuf = {}; + // This variable is tied to DRO +6 V voltage. Voltages, currents are monitored and the experiment + // is shut down immediately if there is a negative voltage. + bool transitionOk = false; + bool commandExecuted = false; + bool adcCmdExecuted = false; + bool periodicPrintout = false; + bool jsonFileInitComplete = false; + double doubleDummy = 0.0; + + bool ssrToDroInjectionRequested = false; + bool droToX8InjectionRequested = false; + bool x8ToTxInjectionRequested = false; + bool txToMpaInjectionRequested = false; + bool mpaToHpaInjectionRequested = false; + bool allOnInjectRequested = false; + + PeriodicOperationDivider opDivider = PeriodicOperationDivider(5); + uint8_t tempReadDivisor = 1; + Countdown countdown = Countdown(5000); + Countdown adcCountdown = Countdown(50); + GpioIF* gpioIF; + SdCardMountedIF* sdcMan; + plpcdu::PlPcduParameter params; + + PoolEntry channelValues = PoolEntry({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + PoolEntry processedValues = + PoolEntry({0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}); + PoolEntry tempC = PoolEntry({0.0}); + + void doTransition(Mode_t modeFrom, Submode_t subModeFrom) override; + void doStartUp() override; + void doShutDown() override; + // Main FDIR function which goes from any PL PCDU state back to all off + void transitionBackToOff(bool notifyFdir); + 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; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + ReturnValue_t isModeCombinationValid(Mode_t mode, Submode_t submode) override; + ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueId, ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, uint16_t startAtIndex) override; + + void handleExtConvRead(const uint8_t* bufStart); + void handlePrintout(); + void checkAdcValues(); + void handleOutOfBoundsPrintout(); + void checkJsonFileInit(); + void stateMachineToNormal(); + bool checkVoltage(float val, float lowerBound, float upperBound, Event event); + bool checkCurrent(float val, float upperBound, Event event); + void handleFailureInjection(std::string output, Event event); + ReturnValue_t serializeFloat(uint32_t& param, float val); + ReturnValue_t handleDoubleParamUpdate(std::string key, ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues); +}; + +#endif /* LINUX_DEVICES_PLPCDUHANDLER_H_ */ diff --git a/mission/devices/devicedefinitions/payloadPcduDefinitions.h b/mission/devices/devicedefinitions/payloadPcduDefinitions.h new file mode 100644 index 00000000..b6ce949e --- /dev/null +++ b/mission/devices/devicedefinitions/payloadPcduDefinitions.h @@ -0,0 +1,231 @@ +#ifndef LINUX_DEVICES_DEVICEDEFINITIONS_PAYLOADPCDUDEFINITIONS_H_ +#define LINUX_DEVICES_DEVICEDEFINITIONS_PAYLOADPCDUDEFINITIONS_H_ + +#include +#include + +#include +#include +#include + +#include "OBSWConfig.h" +#include "mission/devices/max1227.h" +#include "mission/memory/NVMParameterBase.h" + +namespace plpcdu { + +using namespace max1227; + +enum PlPcduAdcChannels : uint8_t { + U_BAT_DIV_6 = 0, + // According to schematic, will be 2.2158V for Vneg = +0V and 0.2446V for Vneg = -6V + // Full Forumula: V_neg = V_post - (R1 + R2) / R1 * (V_pos - V_out) with R1 being 27.4k + // and R2 being 49.9k. FB = Feedback + U_NEG_V_FB = 1, + I_HPA = 2, + U_HPA_DIV_6 = 3, + I_MPA = 4, + U_MPA_DIV_6 = 5, + I_TX = 6, + U_TX_DIV_6 = 7, + I_X8 = 8, + U_X8_DIV_6 = 9, + I_DRO = 10, + U_DRO_DIV_6 = 11, + NUM_CHANNELS = 12 +}; + +enum PlPcduParamIds : uint8_t { + NEG_V_LOWER_BOUND = 0, + NEG_V_UPPER_BOUND = 1, + DRO_U_LOWER_BOUND = 2, + DRO_U_UPPER_BOUND = 3, + DRO_I_UPPER_BOUND = 4, + X8_U_LOWER_BOUND = 5, + X8_U_UPPER_BOUND = 6, + X8_I_UPPER_BOUND = 7, + TX_U_LOWER_BOUND = 8, + TX_U_UPPER_BOUND = 9, + TX_I_UPPER_BOUND = 10, + MPA_U_LOWER_BOUND = 11, + MPA_U_UPPER_BOUND = 12, + MPA_I_UPPER_BOUND = 13, + HPA_U_LOWER_BOUND = 14, + HPA_U_UPPER_BOUND = 15, + HPA_I_UPPER_BOUND = 16, + + SSR_TO_DRO_WAIT_TIME = 17, + DRO_TO_X8_WAIT_TIME = 18, + X8_TO_TX_WAIT_TIME = 19, + TX_TO_MPA_WAIT_TIME = 20, + MPA_TO_HPA_WAIT_TIME = 21, + + INJECT_SSR_TO_DRO_FAILURE = 30, + INJECT_DRO_TO_X8_FAILURE = 31, + INJECT_X8_TO_TX_FAILURE = 32, + INJECT_TX_TO_MPA_FAILURE = 33, + INJECT_MPA_TO_HPA_FAILURE = 34, + INJECT_ALL_ON_FAILURE = 35 +}; + +static std::map PARAM_KEY_MAP = { + {NEG_V_LOWER_BOUND, "negVoltLowerBound"}, {NEG_V_UPPER_BOUND, "negVoltUpperBound"}, + {DRO_U_LOWER_BOUND, "droVoltLowerBound"}, {DRO_U_UPPER_BOUND, "droVoltUpperBound"}, + {DRO_I_UPPER_BOUND, "droCurrUpperBound"}, {X8_U_LOWER_BOUND, "x8VoltLowerBound"}, + {X8_U_UPPER_BOUND, "x8VoltUpperBound"}, {X8_I_UPPER_BOUND, "x8CurrUpperBound"}, + {TX_U_LOWER_BOUND, "txVoltLowerBound"}, {TX_U_UPPER_BOUND, "txVoltUpperBound"}, + {TX_I_UPPER_BOUND, "txCurrUpperBound"}, {MPA_U_LOWER_BOUND, "mpaVoltLowerBound"}, + {MPA_U_UPPER_BOUND, "mpaVoltUpperBound"}, {MPA_I_UPPER_BOUND, "mpaCurrUpperBound"}, + {HPA_U_LOWER_BOUND, "hpaVoltLowerBound"}, {HPA_U_UPPER_BOUND, "hpaVoltUpperBound"}, + {HPA_I_UPPER_BOUND, "hpaCurrUpperBound"}, {SSR_TO_DRO_WAIT_TIME, "ssrToDroWait"}, + {DRO_TO_X8_WAIT_TIME, "droToX8Wait"}, {X8_TO_TX_WAIT_TIME, "x8ToTxWait"}, + {TX_TO_MPA_WAIT_TIME, "txToMpaWait"}, {MPA_TO_HPA_WAIT_TIME, "mpaToHpaWait"}, +}; + +enum PlPcduPoolIds : uint32_t { CHANNEL_VEC = 0, PROCESSED_VEC = 1, TEMP = 2 }; + +static constexpr size_t MAX_ADC_REPLY_SIZE = 64; + +static constexpr DeviceCommandId_t READ_CMD = 0; +static constexpr DeviceCommandId_t SETUP_CMD = 1; +static constexpr DeviceCommandId_t READ_TEMP_EXT = 2; +static constexpr DeviceCommandId_t READ_WITH_TEMP_EXT = 3; + +static constexpr Submode_t NORMAL_ADC_ONLY = 0; +static constexpr Submode_t NORMAL_ALL_ON = 1; + +// 12 ADC values * 2 + trailing zero +static constexpr size_t ADC_REPLY_SIZE = 25; +// Conversion byte + 24 * zero +static constexpr size_t TEMP_REPLY_SIZE = 25; + +static constexpr uint8_t SETUP_BYTE = + max1227::buildSetupByte(ClkSel::EXT_CONV_EXT_TIMED, RefSel::INT_REF_NO_WAKEUP, DiffSel::NONE_0); + +static constexpr uint32_t ADC_SET_ID = READ_CMD; +static constexpr uint8_t CHANNELS_NUM = 12; +static constexpr uint8_t CHANNEL_N = CHANNELS_NUM - 1; +// Store temperature as well +static constexpr size_t DATASET_ENTRIES = CHANNELS_NUM + 1; + +static constexpr uint8_t VOLTAGE_DIV = 6; +// 12-bit ADC: 2 to the power of 12 minus 1 +static constexpr uint16_t MAX122X_BIT = 4095; +static constexpr float MAX122X_VREF = 2.5; +static constexpr float GAIN_INA169 = 100.0; +static constexpr float R_SHUNT_HPA = 0.008; +static constexpr float R_SHUNT_MPA = 0.015; +static constexpr float R_SHUNT_TX = 0.05; +static constexpr float R_SHUNT_X8 = 0.015; +static constexpr float R_SHUNT_DRO = 0.22; +static constexpr float V_POS = 3.3; +static constexpr float VOLTAGE_DIV_U_NEG = (49.9 + 27.4) / 27.4; +static constexpr float MAX122X_SCALE = MAX122X_VREF / MAX122X_BIT; +static constexpr float SCALE_VOLTAGE = MAX122X_SCALE * VOLTAGE_DIV; +static constexpr float SCALE_CURRENT_HPA = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_HPA); +static constexpr float SCALE_CURRENT_MPA = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_MPA); +static constexpr float SCALE_CURRENT_TX = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_TX); +static constexpr float SCALE_CURRENT_X8 = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_X8); +static constexpr float SCALE_CURRENT_DRO = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_DRO); + +// TODO: Make these configurable parameters using a JSON file +// Upper bound of currents in milliamperes [mA] +static constexpr double DFT_NEG_V_LOWER_BOUND = -6.5; +static constexpr double DFT_NEG_V_UPPER_BOUND = -2.7; + +static constexpr double DFT_DRO_U_LOWER_BOUND = 5.0; +static constexpr double DFT_DRO_U_UPPER_BOUND = 7.0; +// Max Current DRO + Max Current Neg V | 40 + 15 +static constexpr double DFT_DRO_I_UPPER_BOUND = 55.0; + +static constexpr double DFT_X8_U_LOWER_BOUND = 2.6; +static constexpr double DFT_X8_U_UPPER_BOUND = 4.0; +static constexpr double DFT_X8_I_UPPER_BOUND = 100.0; + +static constexpr double DFT_TX_U_LOWER_BOUND = 2.6; +static constexpr double DFT_TX_U_UPPER_BOUND = 4.0; +static constexpr double DFT_TX_I_UPPER_BOUND = 250.0; + +static constexpr double DFT_MPA_U_LOWER_BOUND = 2.6; +static constexpr double DFT_MPA_U_UPPER_BOUND = 4.0; +static constexpr double DFT_MPA_I_UPPER_BOUND = 650.0; + +static constexpr double DFT_HPA_U_LOWER_BOUND = 9.4; +static constexpr double DFT_HPA_U_UPPER_BOUND = 11.0; +static constexpr double DFT_HPA_I_UPPER_BOUND = 3000.0; + +// Wait time in floating point seconds +static constexpr double DFT_SSR_TO_DRO_WAIT_TIME = 5.0; +static constexpr double DFT_DRO_TO_X8_WAIT_TIME = 905.0; +static constexpr double DFT_X8_TO_TX_WAIT_TIME = 5.0; +static constexpr double DFT_TX_TO_MPA_WAIT_TIME = 5.0; +static constexpr double DFT_MPA_TO_HPA_WAIT_TIME = 5.0; + +/** + * The current of the processed values is calculated and stored as a milliamperes [mA]. + * The voltages are stored as Volt values. + */ +class PlPcduAdcSet : public StaticLocalDataSet { + public: + PlPcduAdcSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, ADC_SET_ID) {} + + PlPcduAdcSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, ADC_SET_ID)) {} + + lp_vec_t channels = lp_vec_t(sid.objectId, CHANNEL_VEC, this); + lp_vec_t processed = lp_vec_t(sid.objectId, PROCESSED_VEC, this); + lp_var_t tempC = lp_var_t(sid.objectId, TEMP, this); +}; + +class PlPcduParameter : public NVMParameterBase { + public: + PlPcduParameter() : NVMParameterBase(""), mountPrefix("") { + using namespace plpcdu; + // Initialize with default values + resetValues(); + } + + ReturnValue_t initialize(std::string mountPrefix) { + setFullName(mountPrefix + "/conf/plpcdu.json"); + ReturnValue_t result = readJsonFile(); + if (result != HasReturnvaluesIF::RETURN_OK) { + // File does not exist or reading JSON failed for various reason. Rewrite the JSON file +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Creating PL PCDU JSON file at " << getFullName() << std::endl; +#endif + resetValues(); + writeJsonFile(); + } + return HasReturnvaluesIF::RETURN_OK; + } + void resetValues() { + insertValue(PARAM_KEY_MAP[SSR_TO_DRO_WAIT_TIME], DFT_SSR_TO_DRO_WAIT_TIME); + insertValue(PARAM_KEY_MAP[DRO_TO_X8_WAIT_TIME], DFT_DRO_TO_X8_WAIT_TIME); + insertValue(PARAM_KEY_MAP[X8_TO_TX_WAIT_TIME], DFT_X8_TO_TX_WAIT_TIME); + insertValue(PARAM_KEY_MAP[TX_TO_MPA_WAIT_TIME], DFT_TX_TO_MPA_WAIT_TIME); + insertValue(PARAM_KEY_MAP[MPA_TO_HPA_WAIT_TIME], DFT_MPA_TO_HPA_WAIT_TIME); + insertValue(PARAM_KEY_MAP[NEG_V_LOWER_BOUND], DFT_NEG_V_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[NEG_V_UPPER_BOUND], DFT_NEG_V_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[DRO_U_LOWER_BOUND], DFT_DRO_U_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[DRO_U_UPPER_BOUND], DFT_DRO_U_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[DRO_I_UPPER_BOUND], DFT_DRO_I_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[X8_U_LOWER_BOUND], DFT_X8_U_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[X8_U_UPPER_BOUND], DFT_X8_U_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[X8_I_UPPER_BOUND], DFT_X8_I_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[TX_U_LOWER_BOUND], DFT_TX_U_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[TX_U_UPPER_BOUND], DFT_TX_U_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[TX_I_UPPER_BOUND], DFT_TX_I_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[MPA_U_LOWER_BOUND], DFT_MPA_U_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[MPA_U_UPPER_BOUND], DFT_MPA_U_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[MPA_I_UPPER_BOUND], DFT_MPA_I_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[HPA_U_LOWER_BOUND], DFT_HPA_U_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[HPA_U_UPPER_BOUND], DFT_HPA_U_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[HPA_I_UPPER_BOUND], DFT_HPA_I_UPPER_BOUND); + } + + private: + std::string mountPrefix; +}; + +} // namespace plpcdu + +#endif /* LINUX_DEVICES_DEVICEDEFINITIONS_PAYLOADPCDUDEFINITIONS_H_ */