#include "SolarArrayDeploymentHandler.h" #include #include #include #include #include #include "devices/gpioIds.h" #include "fsfw/ipc/QueueFactory.h" #include "fsfw/objectmanager/ObjectManager.h" #include "fsfw_hal/common/gpio/GpioCookie.h" #include "mission/trace.h" static constexpr bool DEBUG_MODE = true; SolarArrayDeploymentHandler::SolarArrayDeploymentHandler(object_id_t setObjectId_, GpioIF& gpioInterface, PowerSwitchIF& mainLineSwitcher_, pcdu::Switches mainLineSwitch_, gpioId_t deplSA1, gpioId_t deplSA2, SdCardMountedIF& sdcMountedIF) : SystemObject(setObjectId_), gpioInterface(gpioInterface), deplSA1(deplSA1), deplSA2(deplSA2), mainLineSwitcher(mainLineSwitcher_), mainLineSwitch(mainLineSwitch_), sdcMan(sdcMountedIF), actionHelper(this, nullptr) { auto mqArgs = MqArgs(setObjectId_, static_cast(this)); commandQueue = QueueFactory::instance()->createMessageQueue( cmdQueueSize, MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs); } SolarArrayDeploymentHandler::~SolarArrayDeploymentHandler() = default; ReturnValue_t SolarArrayDeploymentHandler::performOperation(uint8_t operationCode) { using namespace std::filesystem; #if OBSW_THREAD_TRACING == 1 opCounter++; if (opCounter % 5 == 0) { sif::debug << "SA DEPL task running" << std::endl; } #endif if (opDivider.checkAndIncrement()) { auto activeSdc = sdcMan.getActiveSdCard(); if (activeSdc and activeSdc.value() == sd::SdCard::SLOT_0 and sdcMan.isSdCardUsable(activeSdc.value())) { if (exists(SD_0_DEPL_FILE)) { // perform autonomous deployment handling performAutonomousDepl(sd::SdCard::SLOT_0, dryRunStringInFile(SD_0_DEPL_FILE)); } } else if (activeSdc and activeSdc.value() == sd::SdCard::SLOT_1 and sdcMan.isSdCardUsable(activeSdc.value())) { if (exists(SD_1_DEPL_FILE)) { // perform autonomous deployment handling performAutonomousDepl(sd::SdCard::SLOT_1, dryRunStringInFile(SD_1_DEPL_FILE)); } } else { // TODO: This is FDIR domain. If both SD cards are not available for whatever reason, // there is not much we can do except somehow use the scratch buffer which is // not non-volatile. Implementation effort is considerable as well. } } readCommandQueue(); handleStateMachine(); return returnvalue::OK; } void SolarArrayDeploymentHandler::handleStateMachine() { if (stateMachine == MAIN_POWER_ON) { mainLineSwitcher.sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_ON); mainSwitchCountdown.setTimeout(mainLineSwitcher.getSwitchDelayMs()); stateMachine = WAIT_MAIN_POWER_ON; sif::info << "S/A Deployment: Deployment power line on" << std::endl; } if (stateMachine == MAIN_POWER_OFF) { // These should never fail allOff(); stateMachine = WAIT_MAIN_POWER_OFF; sif::info << "S/A Deployment: Deployment power line off" << std::endl; } if (stateMachine == WAIT_MAIN_POWER_ON) { if (checkMainPowerOn()) { if (DEBUG_MODE) { sif::debug << "SA DEPL FSM: WAIT_MAIN_POWER_ON done -> SWITCH_DEPL_GPIOS" << std::endl; } stateMachine = SWITCH_DEPL_GPIOS; } } if (stateMachine == WAIT_MAIN_POWER_OFF) { if (checkMainPowerOff()) { if (DEBUG_MODE) { sif::debug << "SA DEPL FSM: WAIT_MAIN_POWER_OFF done -> FSM DONE" << std::endl; } sif::info << "S/A Deployment: FSM done" << std::endl; finishFsm(returnvalue::OK); } } if (stateMachine == SWITCH_DEPL_GPIOS) { burnCountdown.setTimeout(fsmInfo.burnCountdownMs); // This should never fail channelAlternationCd.resetTimer(); if (not fsmInfo.dryRun) { if (fsmInfo.initChannel == 0) { sa2Off(); sa1On(); fsmInfo.alternationDummy = true; } else { sa1Off(); sa2On(); fsmInfo.alternationDummy = false; } } sif::info << "S/A Deployment: Burning" << std::endl; triggerEvent(BURN_PHASE_START, fsmInfo.burnCountdownMs, fsmInfo.dryRun); channelAlternationCd.resetTimer(); stateMachine = BURNING; } if (stateMachine == BURNING) { saGpioAlternation(); if (burnCountdown.hasTimedOut()) { if (DEBUG_MODE) { sif::debug << "SA DEPL FSM: BURNING done -> WAIT_MAIN_POWER_OFF" << std::endl; } allOff(); triggerEvent(BURN_PHASE_DONE, fsmInfo.burnCountdownMs, fsmInfo.dryRun); stateMachine = WAIT_MAIN_POWER_OFF; } } } ReturnValue_t SolarArrayDeploymentHandler::performAutonomousDepl(sd::SdCard sdCard, bool dryRun) { using namespace std::filesystem; using namespace std; auto initFile = [](const char* filename) { ofstream of(filename); of << "phase: init\n"; of << "secs_since_start: 0\n"; }; if (sdCard == sd::SdCard::SLOT_0) { if (not exists(SD_0_DEPLY_INFO)) { initFile(SD_0_DEPLY_INFO); } if (not autonomousDeplForFile(sd::SdCard::SLOT_0, SD_0_DEPLY_INFO, dryRun)) { initFile(SD_0_DEPLY_INFO); } } else if (sdCard == sd::SdCard::SLOT_1) { if (not exists(SD_1_DEPLY_INFO)) { initFile(SD_1_DEPLY_INFO); } if (not autonomousDeplForFile(sd::SdCard::SLOT_1, SD_1_DEPLY_INFO, dryRun)) { initFile(SD_1_DEPLY_INFO); } } return returnvalue::OK; } bool SolarArrayDeploymentHandler::autonomousDeplForFile(sd::SdCard sdCard, const char* filename, bool dryRun) { using namespace std; ifstream file(filename); string line; string word; unsigned int lineNum = 0; AutonomousDeplState deplState = AutonomousDeplState::INIT; bool stateSwitch = false; uint32_t secsSinceBoot = 0; while (std::getline(file, line)) { std::istringstream iss(line); if (lineNum == 0) { iss >> word; if (word.find("phase:") == string::npos) { return false; } iss >> word; if (word.find(PHASE_INIT_STR) != string::npos) { deplState = AutonomousDeplState::INIT; } else if (word.find(PHASE_FIRST_BURN_STR) != string::npos) { deplState = AutonomousDeplState::FIRST_BURN; } else if (word.find(PHASE_WAIT_STR) != string::npos) { deplState = AutonomousDeplState::WAIT; } else if (word.find(PHASE_SECOND_BURN_STR) != string::npos) { deplState = AutonomousDeplState::SECOND_BURN; } else if (word.find(PHASE_DONE) != string::npos) { deplState = AutonomousDeplState::DONE; } else { return false; } } else if (lineNum == 1) { iss >> word; if (iss.bad()) { return false; } if (word.find("secs_since_start:") == string::npos) { return false; } iss >> secsSinceBoot; if (iss.bad()) { return false; } if (not initUptime) { initUptime = secsSinceBoot; } auto switchCheck = [&](AutonomousDeplState expected) { if (deplState != expected) { deplState = expected; stateSwitch = true; } }; if ((secsSinceBoot > FIRST_BURN_START_TIME) and (secsSinceBoot < FIRST_BURN_END_TIME)) { switchCheck(AutonomousDeplState::FIRST_BURN); } else if ((secsSinceBoot > WAIT_START_TIME) and (secsSinceBoot < WAIT_END_TIME)) { switchCheck(AutonomousDeplState::WAIT); } else if ((secsSinceBoot > SECOND_BURN_START_TIME) and (secsSinceBoot < SECOND_BURN_END_TIME)) { switchCheck(AutonomousDeplState::SECOND_BURN); } else if (secsSinceBoot > SECOND_BURN_END_TIME) { switchCheck(AutonomousDeplState::DONE); } } lineNum++; } if (initUptime) { secsSinceBoot = initUptime.value(); } // Uptime has increased by X seconds so we need to update the uptime count inside the file secsSinceBoot += Clock::getUptime().tv_sec; if (stateSwitch or firstAutonomousCycle) { if (deplState == AutonomousDeplState::FIRST_BURN or deplState == AutonomousDeplState::SECOND_BURN) { startFsmOn(config::SA_DEPL_BURN_TIME_SECS, (config::SA_DEPL_BURN_TIME_SECS / 2) * 1000, 0, dryRun); } else if (deplState == AutonomousDeplState::WAIT or deplState == AutonomousDeplState::DONE or deplState == AutonomousDeplState::INIT) { startFsmOff(); } } if (deplState == AutonomousDeplState::DONE) { remove(filename); if (sdCard == sd::SdCard::SLOT_0) { remove(SD_0_DEPL_FILE); } else { remove(SD_1_DEPL_FILE); } triggerEvent(AUTONOMOUS_DEPLOYMENT_COMPLETED); } else { std::ofstream of(filename); of << "phase: "; if (deplState == AutonomousDeplState::INIT) { of << PHASE_INIT_STR << "\n"; } else if (deplState == AutonomousDeplState::FIRST_BURN) { of << PHASE_FIRST_BURN_STR << "\n"; } else if (deplState == AutonomousDeplState::WAIT) { of << PHASE_WAIT_STR << "\n"; } else if (deplState == AutonomousDeplState::SECOND_BURN) { of << PHASE_SECOND_BURN_STR << "\n"; } of << "secs_since_start: " << std::to_string(secsSinceBoot) << "\n"; } if (firstAutonomousCycle) { firstAutonomousCycle = false; } return true; } bool SolarArrayDeploymentHandler::checkMainPowerOn() { return checkMainPower(true); } bool SolarArrayDeploymentHandler::checkMainPowerOff() { return checkMainPower(false); } bool SolarArrayDeploymentHandler::checkMainPower(bool onOff) { if ((onOff and mainLineSwitcher.getSwitchState(mainLineSwitch) == PowerSwitchIF::SWITCH_ON) or (not onOff and mainLineSwitcher.getSwitchState(mainLineSwitch) == PowerSwitchIF::SWITCH_OFF)) { return true; } if (mainSwitchCountdown.hasTimedOut()) { if (onOff) { triggerEvent(MAIN_SWITCH_ON_TIMEOUT); } else { triggerEvent(MAIN_SWITCH_OFF_TIMEOUT); } if (retryCounter < 3) { if (onOff) { stateMachine = MAIN_POWER_ON; } else { stateMachine = MAIN_POWER_OFF; } retryCounter++; } else { finishFsm(MAIN_SWITCH_TIMEOUT_FAILURE); } } return false; } bool SolarArrayDeploymentHandler::startFsmOn(uint32_t burnCountdownSecs, uint32_t channelAlternationIntervalMs, uint8_t initChannel, bool dryRun) { if (stateMachine != StateMachine::IDLE) { return false; } channelAlternationCd.setTimeout(channelAlternationIntervalMs); if (burnCountdownSecs > config::SA_DEPL_MAX_BURN_TIME) { burnCountdownSecs = config::SA_DEPL_MAX_BURN_TIME; } fsmInfo.dryRun = dryRun; fsmInfo.burnCountdownMs = burnCountdownSecs * 1000; fsmInfo.initChannel = initChannel; stateMachine = StateMachine::MAIN_POWER_ON; retryCounter = 0; return true; } void SolarArrayDeploymentHandler::startFsmOff() { if (stateMachine != StateMachine::IDLE) { // off commands override the state machine. Cancel any active action commands. finishFsm(returnvalue::FAILED); } retryCounter = 0; stateMachine = StateMachine::MAIN_POWER_OFF; } void SolarArrayDeploymentHandler::finishFsm(ReturnValue_t resultForActionHelper) { retryCounter = 0; stateMachine = StateMachine::IDLE; fsmInfo.dryRun = false; fsmInfo.alternationDummy = false; if (actionActive) { bool success = false; if (resultForActionHelper == returnvalue::OK or resultForActionHelper == HasActionsIF::EXECUTION_FINISHED) { success = true; } actionHelper.finish(success, rememberCommanderId, activeCmd, resultForActionHelper); } } void SolarArrayDeploymentHandler::allOff() { deploymentTransistorsOff(); mainLineSwitcher.sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_OFF); mainSwitchCountdown.setTimeout(mainLineSwitcher.getSwitchDelayMs()); } bool SolarArrayDeploymentHandler::dryRunStringInFile(const char* filename) { std::ifstream ifile(filename); if (ifile.bad()) { return false; } std::string line; while (getline(ifile, line)) { if (line.find("dryrun") != std::string::npos) { return true; } } return false; } ReturnValue_t SolarArrayDeploymentHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, const uint8_t* data, size_t size) { ReturnValue_t result = returnvalue::OK; if (actionId == DEPLOY_SOLAR_ARRAYS_MANUALLY) { ManualDeploymentCommand cmd; if (size < cmd.getSerializedSize()) { return HasActionsIF::INVALID_PARAMETERS; } result = cmd.deSerialize(&data, &size, SerializeIF::Endianness::NETWORK); if (result != returnvalue::OK) { return result; } uint32_t burnCountdown = cmd.getBurnTimeSecs(); if (not startFsmOn(burnCountdown, cmd.getSwitchIntervalMs(), cmd.getInitChannel(), cmd.isDryRun())) { return HasActionsIF::IS_BUSY; } actionActive = true; rememberCommanderId = commandedBy; return result; } else if (actionId == SWITCH_OFF_DEPLOYMENT) { startFsmOff(); actionActive = true; rememberCommanderId = commandedBy; return result; } else { return HasActionsIF::INVALID_ACTION_ID; } return result; } ReturnValue_t SolarArrayDeploymentHandler::saGpioAlternation() { ReturnValue_t status = returnvalue::OK; ReturnValue_t result; if (channelAlternationCd.hasTimedOut() and not fsmInfo.dryRun) { if (fsmInfo.alternationDummy) { result = sa1Off(); if (result != returnvalue::OK) { status = result; } TaskFactory::delayTask(1); result = sa2On(); if (result != returnvalue::OK) { status = result; } } else { result = sa2Off(); if (result != returnvalue::OK) { status = result; } TaskFactory::delayTask(1); result = sa1On(); if (result != returnvalue::OK) { status = result; } } fsmInfo.alternationDummy = not fsmInfo.alternationDummy; channelAlternationCd.resetTimer(); } return status; } ReturnValue_t SolarArrayDeploymentHandler::deploymentTransistorsOff() { ReturnValue_t status = returnvalue::OK; ReturnValue_t result = sa1Off(); if (result != returnvalue::OK) { status = result; } result = sa2Off(); if (result != returnvalue::OK) { status = result; } return status; } ReturnValue_t SolarArrayDeploymentHandler::sa1On() { ReturnValue_t result = gpioInterface.pullHigh(deplSA1); if (result != returnvalue::OK) { sif::warning << "SolarArrayDeploymentHandler::handleStateMachine: Failed to pull solar" " array deployment switch 1 high" << std::endl; // If gpio switch high failed, state machine is reset to wait for a command re-initiating // the deployment sequence. triggerEvent(DEPL_SA1_GPIO_SWTICH_ON_FAILED); } return result; } ReturnValue_t SolarArrayDeploymentHandler::sa1Off() { ReturnValue_t result = gpioInterface.pullLow(deplSA1); if (result != returnvalue::OK) { sif::warning << "SolarArrayDeploymentHandler::handleStateMachine: Failed to pull solar" " array deployment switch 1 low" << std::endl; // If gpio switch high failed, state machine is reset to wait for a command re-initiating // the deployment sequence. triggerEvent(DEPL_SA1_GPIO_SWTICH_OFF_FAILED); } return result; } ReturnValue_t SolarArrayDeploymentHandler::sa2On() { ReturnValue_t result = gpioInterface.pullHigh(deplSA2); if (result != returnvalue::OK) { sif::warning << "SolarArrayDeploymentHandler::handleStateMachine: Failed to pull solar" " array deployment switch 2 high" << std::endl; // If gpio switch high failed, state machine is reset to wait for a command re-initiating // the deployment sequence. triggerEvent(DEPL_SA2_GPIO_SWTICH_ON_FAILED); } return result; } ReturnValue_t SolarArrayDeploymentHandler::sa2Off() { ReturnValue_t result = gpioInterface.pullLow(deplSA2); if (result != returnvalue::OK) { sif::warning << "SolarArrayDeploymentHandler::handleStateMachine: Failed to pull solar" " array deployment switch 2 low" << std::endl; // If gpio switch high failed, state machine is reset to wait for a command re-initiating // the deployment sequence. triggerEvent(DEPL_SA2_GPIO_SWTICH_OFF_FAILED); } return result; } void SolarArrayDeploymentHandler::readCommandQueue() { CommandMessage command; ReturnValue_t result = commandQueue->receiveMessage(&command); if (result != returnvalue::OK) { return; } result = actionHelper.handleActionMessage(&command); if (result == returnvalue::OK) { return; } } MessageQueueId_t SolarArrayDeploymentHandler::getCommandQueue() const { return commandQueue->getId(); } ReturnValue_t SolarArrayDeploymentHandler::initialize() { ReturnValue_t result = actionHelper.initialize(commandQueue); if (result != returnvalue::OK) { return ObjectManagerIF::CHILD_INIT_FAILED; } return SystemObject::initialize(); }