Merge remote-tracking branch 'origin/develop' into mueller/acs-ss-init
Some checks failed
EIVE/eive-obsw/pipeline/pr-develop There was a failure building this commit
Some checks failed
EIVE/eive-obsw/pipeline/pr-develop There was a failure building this commit
This commit is contained in:
@ -1,171 +1,470 @@
|
||||
#include "SolarArrayDeploymentHandler.h"
|
||||
|
||||
#include <devices/gpioIds.h>
|
||||
#include <fsfw/ipc/QueueFactory.h>
|
||||
#include <fsfw/objectmanager/ObjectManager.h>
|
||||
#include <fsfw_hal/common/gpio/GpioCookie.h>
|
||||
#include <fsfw/tasks/TaskFactory.h>
|
||||
|
||||
SolarArrayDeploymentHandler::SolarArrayDeploymentHandler(
|
||||
object_id_t setObjectId_, object_id_t gpioDriverId_, CookieIF* gpioCookie_,
|
||||
object_id_t mainLineSwitcherObjectId_, pcdu::Switches mainLineSwitch_, gpioId_t deplSA1,
|
||||
gpioId_t deplSA2, uint32_t burnTimeMs)
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "devices/gpioIds.h"
|
||||
#include "fsfw/ipc/QueueFactory.h"
|
||||
#include "fsfw/objectmanager/ObjectManager.h"
|
||||
#include "fsfw_hal/common/gpio/GpioCookie.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_),
|
||||
gpioDriverId(gpioDriverId_),
|
||||
gpioCookie(gpioCookie_),
|
||||
mainLineSwitcherObjectId(mainLineSwitcherObjectId_),
|
||||
mainLineSwitch(mainLineSwitch_),
|
||||
gpioInterface(gpioInterface),
|
||||
deplSA1(deplSA1),
|
||||
deplSA2(deplSA2),
|
||||
burnTimeMs(burnTimeMs),
|
||||
mainLineSwitcher(mainLineSwitcher_),
|
||||
mainLineSwitch(mainLineSwitch_),
|
||||
sdcMan(sdcMountedIF),
|
||||
actionHelper(this, nullptr) {
|
||||
auto mqArgs = MqArgs(setObjectId_, static_cast<void*>(this));
|
||||
commandQueue = QueueFactory::instance()->createMessageQueue(
|
||||
cmdQueueSize, MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs);
|
||||
}
|
||||
|
||||
SolarArrayDeploymentHandler::~SolarArrayDeploymentHandler() {}
|
||||
SolarArrayDeploymentHandler::~SolarArrayDeploymentHandler() = default;
|
||||
|
||||
ReturnValue_t SolarArrayDeploymentHandler::performOperation(uint8_t operationCode) {
|
||||
if (operationCode == DeviceHandlerIF::PERFORM_OPERATION) {
|
||||
handleStateMachine();
|
||||
return returnvalue::OK;
|
||||
}
|
||||
return returnvalue::OK;
|
||||
}
|
||||
|
||||
ReturnValue_t SolarArrayDeploymentHandler::initialize() {
|
||||
ReturnValue_t result = SystemObject::initialize();
|
||||
if (result != returnvalue::OK) {
|
||||
return ObjectManagerIF::CHILD_INIT_FAILED;
|
||||
}
|
||||
|
||||
gpioInterface = ObjectManager::instance()->get<GpioIF>(gpioDriverId);
|
||||
if (gpioInterface == nullptr) {
|
||||
sif::error << "SolarArrayDeploymentHandler::initialize: Invalid Gpio interface." << std::endl;
|
||||
return ObjectManagerIF::CHILD_INIT_FAILED;
|
||||
}
|
||||
|
||||
result = gpioInterface->addGpios(dynamic_cast<GpioCookie*>(gpioCookie));
|
||||
if (result != returnvalue::OK) {
|
||||
sif::error << "SolarArrayDeploymentHandler::initialize: Failed to initialize Gpio interface"
|
||||
<< std::endl;
|
||||
return ObjectManagerIF::CHILD_INIT_FAILED;
|
||||
}
|
||||
|
||||
if (mainLineSwitcherObjectId != objects::NO_OBJECT) {
|
||||
mainLineSwitcher = ObjectManager::instance()->get<PowerSwitchIF>(mainLineSwitcherObjectId);
|
||||
if (mainLineSwitcher == nullptr) {
|
||||
sif::error
|
||||
<< "SolarArrayDeploymentHandler::initialize: Main line switcher failed to fetch object"
|
||||
<< "from object ID." << std::endl;
|
||||
return ObjectManagerIF::CHILD_INIT_FAILED;
|
||||
using namespace std::filesystem;
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
result = actionHelper.initialize(commandQueue);
|
||||
if (result != returnvalue::OK) {
|
||||
return ObjectManagerIF::CHILD_INIT_FAILED;
|
||||
}
|
||||
|
||||
readCommandQueue();
|
||||
handleStateMachine();
|
||||
return returnvalue::OK;
|
||||
}
|
||||
|
||||
void SolarArrayDeploymentHandler::handleStateMachine() {
|
||||
switch (stateMachine) {
|
||||
case WAIT_ON_DELOYMENT_COMMAND:
|
||||
readCommandQueue();
|
||||
break;
|
||||
case SWITCH_8V_ON:
|
||||
mainLineSwitcher->sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_ON);
|
||||
mainSwitchCountdown.setTimeout(mainLineSwitcher->getSwitchDelayMs());
|
||||
stateMachine = WAIT_ON_8V_SWITCH;
|
||||
break;
|
||||
case WAIT_ON_8V_SWITCH:
|
||||
performWaitOn8VActions();
|
||||
break;
|
||||
case SWITCH_DEPL_GPIOS:
|
||||
switchDeploymentTransistors();
|
||||
break;
|
||||
case WAIT_ON_DEPLOYMENT_FINISH:
|
||||
handleDeploymentFinish();
|
||||
break;
|
||||
case WAIT_FOR_MAIN_SWITCH_OFF:
|
||||
if (mainLineSwitcher->getSwitchState(mainLineSwitch) == PowerSwitchIF::SWITCH_OFF) {
|
||||
stateMachine = WAIT_ON_DELOYMENT_COMMAND;
|
||||
} else if (mainSwitchCountdown.hasTimedOut()) {
|
||||
triggerEvent(MAIN_SWITCH_OFF_TIMEOUT);
|
||||
sif::error << "SolarArrayDeploymentHandler::handleStateMachine: Failed to switch main"
|
||||
<< " switch off" << std::endl;
|
||||
stateMachine = WAIT_ON_DELOYMENT_COMMAND;
|
||||
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;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sif::debug << "SolarArrayDeploymentHandler::handleStateMachine: Invalid state" << std::endl;
|
||||
break;
|
||||
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) {
|
||||
sa2Off();
|
||||
sa1On();
|
||||
fsmInfo.alternationDummy = true;
|
||||
}
|
||||
sif::info << "S/A Deployment: Burning" << std::endl;
|
||||
triggerEvent(BURN_PHASE_START, fsmInfo.burnCountdownMs, fsmInfo.dryRun);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SolarArrayDeploymentHandler::performWaitOn8VActions() {
|
||||
if (mainLineSwitcher->getSwitchState(mainLineSwitch) == PowerSwitchIF::SWITCH_ON) {
|
||||
stateMachine = SWITCH_DEPL_GPIOS;
|
||||
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;
|
||||
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, 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 {
|
||||
if (mainSwitchCountdown.hasTimedOut()) {
|
||||
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);
|
||||
actionHelper.finish(false, rememberCommanderId, DEPLOY_SOLAR_ARRAYS,
|
||||
MAIN_SWITCH_TIMEOUT_FAILURE);
|
||||
stateMachine = WAIT_ON_DELOYMENT_COMMAND;
|
||||
} 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, bool dryRun) {
|
||||
if (stateMachine != StateMachine::IDLE) {
|
||||
return false;
|
||||
}
|
||||
if (burnCountdownSecs > config::SA_DEPL_MAX_BURN_TIME) {
|
||||
burnCountdownSecs = config::SA_DEPL_MAX_BURN_TIME;
|
||||
}
|
||||
fsmInfo.dryRun = dryRun;
|
||||
fsmInfo.burnCountdownMs = burnCountdownSecs * 1000;
|
||||
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::switchDeploymentTransistors() {
|
||||
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;
|
||||
result = gpioInterface->pullHigh(deplSA1);
|
||||
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.getBurnTime();
|
||||
if (not startFsmOn(burnCountdown, 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) {
|
||||
sif::debug << "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 reinitiating
|
||||
* the deployment sequence. */
|
||||
stateMachine = WAIT_ON_DELOYMENT_COMMAND;
|
||||
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);
|
||||
actionHelper.finish(false, rememberCommanderId, DEPLOY_SOLAR_ARRAYS, SWITCHING_DEPL_SA2_FAILED);
|
||||
mainLineSwitcher->sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_OFF);
|
||||
}
|
||||
result = gpioInterface->pullHigh(deplSA2);
|
||||
if (result != returnvalue::OK) {
|
||||
sif::debug << "SolarArrayDeploymentHandler::handleStateMachine: Failed to pull solar"
|
||||
" array deployment switch 2 high "
|
||||
<< std::endl;
|
||||
stateMachine = WAIT_ON_DELOYMENT_COMMAND;
|
||||
triggerEvent(DEPL_SA2_GPIO_SWTICH_ON_FAILED);
|
||||
actionHelper.finish(false, rememberCommanderId, DEPLOY_SOLAR_ARRAYS, SWITCHING_DEPL_SA2_FAILED);
|
||||
mainLineSwitcher->sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_OFF);
|
||||
}
|
||||
deploymentCountdown.setTimeout(burnTimeMs);
|
||||
stateMachine = WAIT_ON_DEPLOYMENT_FINISH;
|
||||
return result;
|
||||
}
|
||||
|
||||
void SolarArrayDeploymentHandler::handleDeploymentFinish() {
|
||||
ReturnValue_t result = returnvalue::OK;
|
||||
if (deploymentCountdown.hasTimedOut()) {
|
||||
actionHelper.finish(true, rememberCommanderId, DEPLOY_SOLAR_ARRAYS, returnvalue::OK);
|
||||
result = gpioInterface->pullLow(deplSA1);
|
||||
if (result != returnvalue::OK) {
|
||||
sif::debug << "SolarArrayDeploymentHandler::handleStateMachine: Failed to pull solar"
|
||||
" array deployment switch 1 low "
|
||||
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;
|
||||
}
|
||||
result = gpioInterface->pullLow(deplSA2);
|
||||
if (result != returnvalue::OK) {
|
||||
sif::debug << "SolarArrayDeploymentHandler::handleStateMachine: Failed to pull solar"
|
||||
" array deployment switch 2 low "
|
||||
<< std::endl;
|
||||
}
|
||||
mainLineSwitcher->sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_OFF);
|
||||
mainSwitchCountdown.setTimeout(mainLineSwitcher->getSwitchDelayMs());
|
||||
stateMachine = WAIT_FOR_MAIN_SWITCH_OFF;
|
||||
// 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() {
|
||||
@ -181,27 +480,14 @@ void SolarArrayDeploymentHandler::readCommandQueue() {
|
||||
}
|
||||
}
|
||||
|
||||
ReturnValue_t SolarArrayDeploymentHandler::executeAction(ActionId_t actionId,
|
||||
MessageQueueId_t commandedBy,
|
||||
const uint8_t* data, size_t size) {
|
||||
ReturnValue_t result;
|
||||
if (stateMachine != WAIT_ON_DELOYMENT_COMMAND) {
|
||||
sif::error << "SolarArrayDeploymentHandler::executeAction: Received command while not in"
|
||||
<< "waiting-on-command-state" << std::endl;
|
||||
return DEPLOYMENT_ALREADY_EXECUTING;
|
||||
}
|
||||
if (actionId != DEPLOY_SOLAR_ARRAYS) {
|
||||
sif::error << "SolarArrayDeploymentHandler::executeAction: Received invalid command"
|
||||
<< std::endl;
|
||||
result = COMMAND_NOT_SUPPORTED;
|
||||
} else {
|
||||
stateMachine = SWITCH_8V_ON;
|
||||
rememberCommanderId = commandedBy;
|
||||
result = returnvalue::OK;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -1,22 +1,46 @@
|
||||
#ifndef MISSION_DEVICES_SOLARARRAYDEPLOYMENT_H_
|
||||
#define MISSION_DEVICES_SOLARARRAYDEPLOYMENT_H_
|
||||
|
||||
#include <devices/powerSwitcherList.h>
|
||||
#include <fsfw/action/HasActionsIF.h>
|
||||
#include <fsfw/devicehandlers/CookieIF.h>
|
||||
#include <fsfw/devicehandlers/DeviceHandlerIF.h>
|
||||
#include <fsfw/objectmanager/SystemObject.h>
|
||||
#include <fsfw/power/PowerSwitchIF.h>
|
||||
#include <fsfw/returnvalues/returnvalue.h>
|
||||
#include <fsfw/tasks/ExecutableObjectIF.h>
|
||||
#include <fsfw/timemanager/Countdown.h>
|
||||
#include <fsfw_hal/common/gpio/GpioIF.h>
|
||||
#include <fsfw/globalfunctions/PeriodicOperationDivider.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "devices/powerSwitcherList.h"
|
||||
#include "eive/definitions.h"
|
||||
#include "events/subsystemIdRanges.h"
|
||||
#include "fsfw/action/HasActionsIF.h"
|
||||
#include "fsfw/devicehandlers/CookieIF.h"
|
||||
#include "fsfw/devicehandlers/DeviceHandlerIF.h"
|
||||
#include "fsfw/objectmanager/SystemObject.h"
|
||||
#include "fsfw/power/PowerSwitchIF.h"
|
||||
#include "fsfw/returnvalues/returnvalue.h"
|
||||
#include "fsfw/serialize/SerialLinkedListAdapter.h"
|
||||
#include "fsfw/tasks/ExecutableObjectIF.h"
|
||||
#include "fsfw/timemanager/Countdown.h"
|
||||
#include "fsfw_hal/common/gpio/GpioIF.h"
|
||||
#include "mission/memory/SdCardMountedIF.h"
|
||||
#include "returnvalues/classIds.h"
|
||||
|
||||
enum DeploymentChannels : uint8_t { SA_1 = 1, SA_2 = 2 };
|
||||
|
||||
class ManualDeploymentCommand : public SerialLinkedListAdapter<SerializeIF> {
|
||||
public:
|
||||
ManualDeploymentCommand() { setLinks(); }
|
||||
|
||||
void setLinks() {
|
||||
setStart(&burnTime);
|
||||
burnTime.setNext(&dryRun);
|
||||
}
|
||||
|
||||
uint32_t getBurnTime() const { return burnTime.entry; }
|
||||
|
||||
bool isDryRun() const { return dryRun.entry; }
|
||||
|
||||
private:
|
||||
SerializeElement<uint32_t> burnTime;
|
||||
SerializeElement<uint8_t> dryRun;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This class is used to control the solar array deployment.
|
||||
*
|
||||
@ -26,8 +50,29 @@ class SolarArrayDeploymentHandler : public ExecutableObjectIF,
|
||||
public SystemObject,
|
||||
public HasActionsIF {
|
||||
public:
|
||||
static const DeviceCommandId_t DEPLOY_SOLAR_ARRAYS = 0x5;
|
||||
//! Manual deployment of the solar arrays. Burn time and channels are supplied with TC parameters
|
||||
static constexpr DeviceCommandId_t DEPLOY_SOLAR_ARRAYS_MANUALLY = 0x05;
|
||||
static constexpr DeviceCommandId_t SWITCH_OFF_DEPLOYMENT = 0x06;
|
||||
|
||||
static constexpr uint32_t FIRST_BURN_START_TIME = config::SA_DEPL_INIT_BUFFER_SECS;
|
||||
static constexpr uint32_t FIRST_BURN_END_TIME =
|
||||
FIRST_BURN_START_TIME + config::SA_DEPL_BURN_TIME_SECS;
|
||||
static constexpr uint32_t WAIT_START_TIME = FIRST_BURN_END_TIME;
|
||||
static constexpr uint32_t WAIT_END_TIME = WAIT_START_TIME + config::SA_DEPL_WAIT_TIME_SECS;
|
||||
static constexpr uint32_t SECOND_BURN_START_TIME = WAIT_END_TIME;
|
||||
static constexpr uint32_t SECOND_BURN_END_TIME =
|
||||
SECOND_BURN_START_TIME + config::SA_DEPL_WAIT_TIME_SECS;
|
||||
|
||||
static constexpr char SD_0_DEPL_FILE[] = "/mnt/sd0/conf/deployment";
|
||||
static constexpr char SD_1_DEPL_FILE[] = "/mnt/sd1/conf/deployment";
|
||||
static constexpr char SD_0_DEPLY_INFO[] = "/mnt/sd0/conf/deployment_info.txt";
|
||||
static constexpr char SD_1_DEPLY_INFO[] = "/mnt/sd1/conf/deployment_info.txt";
|
||||
|
||||
static constexpr char PHASE_INIT_STR[] = "init";
|
||||
static constexpr char PHASE_FIRST_BURN_STR[] = "first_burn";
|
||||
static constexpr char PHASE_WAIT_STR[] = "wait";
|
||||
static constexpr char PHASE_SECOND_BURN_STR[] = "second_burn";
|
||||
static constexpr char PHASE_DONE[] = "done";
|
||||
/**
|
||||
* @brief constructor
|
||||
*
|
||||
@ -43,10 +88,9 @@ class SolarArrayDeploymentHandler : public ExecutableObjectIF,
|
||||
* @param deplSA2 gpioId of the GPIO controlling the deployment 2 transistor.
|
||||
* @param burnTimeMs Time duration the power will be applied to the burn wires.
|
||||
*/
|
||||
SolarArrayDeploymentHandler(object_id_t setObjectId, object_id_t gpioDriverId,
|
||||
CookieIF* gpioCookie, object_id_t mainLineSwitcherObjectId,
|
||||
pcdu::Switches mainLineSwitch, gpioId_t deplSA1, gpioId_t deplSA2,
|
||||
uint32_t burnTimeMs);
|
||||
SolarArrayDeploymentHandler(object_id_t setObjectId, GpioIF& gpio,
|
||||
PowerSwitchIF& mainLineSwitcher, pcdu::Switches mainLineSwitch,
|
||||
gpioId_t deplSA1, gpioId_t deplSA2, SdCardMountedIF& sdcMountedIF);
|
||||
|
||||
virtual ~SolarArrayDeploymentHandler();
|
||||
|
||||
@ -58,6 +102,26 @@ class SolarArrayDeploymentHandler : public ExecutableObjectIF,
|
||||
virtual ReturnValue_t initialize() override;
|
||||
|
||||
private:
|
||||
enum AutonomousDeplState { INIT, FIRST_BURN, WAIT, SECOND_BURN, DONE };
|
||||
|
||||
enum StateMachine {
|
||||
IDLE,
|
||||
MAIN_POWER_ON,
|
||||
MAIN_POWER_OFF,
|
||||
WAIT_MAIN_POWER_ON,
|
||||
WAIT_MAIN_POWER_OFF,
|
||||
SWITCH_DEPL_GPIOS,
|
||||
BURNING
|
||||
};
|
||||
|
||||
struct FsmInfo {
|
||||
// Not required anymore
|
||||
// DeploymentChannels channel;
|
||||
bool dryRun;
|
||||
bool alternationDummy = false;
|
||||
uint32_t burnCountdownMs = config::SA_DEPL_MAX_BURN_TIME;
|
||||
};
|
||||
|
||||
static const uint8_t INTERFACE_ID = CLASS_ID::SA_DEPL_HANDLER;
|
||||
static const ReturnValue_t COMMAND_NOT_SUPPORTED = MAKE_RETURN_CODE(0xA0);
|
||||
static const ReturnValue_t DEPLOYMENT_ALREADY_EXECUTING = MAKE_RETURN_CODE(0xA1);
|
||||
@ -66,23 +130,41 @@ class SolarArrayDeploymentHandler : public ExecutableObjectIF,
|
||||
static const ReturnValue_t SWITCHING_DEPL_SA2_FAILED = MAKE_RETURN_CODE(0xA4);
|
||||
|
||||
static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::SA_DEPL_HANDLER;
|
||||
static const Event MAIN_SWITCH_ON_TIMEOUT = MAKE_EVENT(0, severity::LOW);
|
||||
static const Event MAIN_SWITCH_OFF_TIMEOUT = MAKE_EVENT(1, severity::LOW);
|
||||
static const Event DEPLOYMENT_FAILED = MAKE_EVENT(2, severity::HIGH);
|
||||
static const Event DEPL_SA1_GPIO_SWTICH_ON_FAILED = MAKE_EVENT(3, severity::HIGH);
|
||||
static const Event DEPL_SA2_GPIO_SWTICH_ON_FAILED = MAKE_EVENT(4, severity::HIGH);
|
||||
|
||||
enum StateMachine {
|
||||
WAIT_ON_DELOYMENT_COMMAND,
|
||||
SWITCH_8V_ON,
|
||||
WAIT_ON_8V_SWITCH,
|
||||
SWITCH_DEPL_GPIOS,
|
||||
WAIT_ON_DEPLOYMENT_FINISH,
|
||||
WAIT_FOR_MAIN_SWITCH_OFF
|
||||
};
|
||||
//! [EXPORT] : [COMMENT] P1: Burn duration in milliseconds, P2: Dry run flag
|
||||
static constexpr Event BURN_PHASE_START = event::makeEvent(SUBSYSTEM_ID, 0, severity::INFO);
|
||||
//! [EXPORT] : [COMMENT] P1: Burn duration in milliseconds, P2: Dry run flag
|
||||
static constexpr Event BURN_PHASE_DONE = event::makeEvent(SUBSYSTEM_ID, 1, severity::INFO);
|
||||
static constexpr Event MAIN_SWITCH_ON_TIMEOUT = event::makeEvent(SUBSYSTEM_ID, 2, severity::LOW);
|
||||
static constexpr Event MAIN_SWITCH_OFF_TIMEOUT = event::makeEvent(SUBSYSTEM_ID, 3, severity::LOW);
|
||||
static constexpr Event DEPL_SA1_GPIO_SWTICH_ON_FAILED =
|
||||
event::makeEvent(SUBSYSTEM_ID, 4, severity::HIGH);
|
||||
static constexpr Event DEPL_SA2_GPIO_SWTICH_ON_FAILED =
|
||||
event::makeEvent(SUBSYSTEM_ID, 5, severity::HIGH);
|
||||
static constexpr Event DEPL_SA1_GPIO_SWTICH_OFF_FAILED =
|
||||
event::makeEvent(SUBSYSTEM_ID, 6, severity::HIGH);
|
||||
static constexpr Event DEPL_SA2_GPIO_SWTICH_OFF_FAILED =
|
||||
event::makeEvent(SUBSYSTEM_ID, 7, severity::HIGH);
|
||||
static constexpr Event AUTONOMOUS_DEPLOYMENT_COMPLETED =
|
||||
event::makeEvent(SUBSYSTEM_ID, 8, severity::INFO);
|
||||
|
||||
StateMachine stateMachine = WAIT_ON_DELOYMENT_COMMAND;
|
||||
FsmInfo fsmInfo;
|
||||
StateMachine stateMachine = IDLE;
|
||||
bool actionActive = false;
|
||||
bool firstAutonomousCycle = true;
|
||||
ActionId_t activeCmd = HasActionsIF::INVALID_ACTION_ID;
|
||||
std::optional<uint64_t> initUptime;
|
||||
PeriodicOperationDivider opDivider = PeriodicOperationDivider(5);
|
||||
uint8_t retryCounter = 3;
|
||||
|
||||
bool startFsmOn(uint32_t burnCountdownSecs, bool dryRun);
|
||||
void startFsmOff();
|
||||
|
||||
void finishFsm(ReturnValue_t resultForActionHelper);
|
||||
|
||||
ReturnValue_t performAutonomousDepl(sd::SdCard sdCard, bool dryRun);
|
||||
bool dryRunStringInFile(const char* filename);
|
||||
bool autonomousDeplForFile(sd::SdCard sdCard, const char* filename, bool dryRun);
|
||||
/**
|
||||
* This countdown is used to check if the PCDU sets the 8V line on in the intended time.
|
||||
*/
|
||||
@ -91,7 +173,10 @@ class SolarArrayDeploymentHandler : public ExecutableObjectIF,
|
||||
/**
|
||||
* This countdown is used to wait for the burn wire being successful cut.
|
||||
*/
|
||||
Countdown deploymentCountdown;
|
||||
Countdown burnCountdown;
|
||||
|
||||
Countdown channelAlternationCd =
|
||||
Countdown(config::SA_DEPL_CHANNEL_ALTERNATION_INTERVAL_SECS * 1000);
|
||||
|
||||
/**
|
||||
* The message queue id of the component commanding an action will be stored in this variable.
|
||||
@ -101,36 +186,25 @@ class SolarArrayDeploymentHandler : public ExecutableObjectIF,
|
||||
|
||||
/** Size of command queue */
|
||||
size_t cmdQueueSize = 20;
|
||||
|
||||
/** The object ID of the GPIO driver which switches the deployment transistors */
|
||||
object_id_t gpioDriverId;
|
||||
|
||||
CookieIF* gpioCookie;
|
||||
|
||||
/** Object id of the object responsible to switch the 8V power input. Typically the PCDU. */
|
||||
object_id_t mainLineSwitcherObjectId;
|
||||
|
||||
/** Switch number of the 8V power switch */
|
||||
uint8_t mainLineSwitch;
|
||||
|
||||
GpioIF& gpioInterface;
|
||||
gpioId_t deplSA1;
|
||||
gpioId_t deplSA2;
|
||||
|
||||
GpioIF* gpioInterface = nullptr;
|
||||
|
||||
/** Time duration switches are active to cut the burn wire */
|
||||
uint32_t burnTimeMs;
|
||||
|
||||
/** Queue to receive messages from other objects. */
|
||||
MessageQueueIF* commandQueue = nullptr;
|
||||
|
||||
/**
|
||||
* After initialization this pointer will hold the reference to the main line switcher object.
|
||||
*/
|
||||
PowerSwitchIF* mainLineSwitcher = nullptr;
|
||||
PowerSwitchIF& mainLineSwitcher;
|
||||
|
||||
/** Switch number of the 8V power switch */
|
||||
uint8_t mainLineSwitch;
|
||||
|
||||
SdCardMountedIF& sdcMan;
|
||||
|
||||
ActionHelper actionHelper;
|
||||
|
||||
/** Queue to receive messages from other objects. */
|
||||
MessageQueueIF* commandQueue = nullptr;
|
||||
|
||||
void readCommandQueue();
|
||||
|
||||
/**
|
||||
@ -142,18 +216,18 @@ class SolarArrayDeploymentHandler : public ExecutableObjectIF,
|
||||
* @brief This function polls the 8V switch state and changes the state machine when the
|
||||
* switch has been enabled.
|
||||
*/
|
||||
void performWaitOn8VActions();
|
||||
bool checkMainPowerOn();
|
||||
bool checkMainPowerOff();
|
||||
bool checkMainPower(bool onOff);
|
||||
|
||||
/**
|
||||
* @brief This functions handles the switching of the solar array deployment transistors.
|
||||
*/
|
||||
void switchDeploymentTransistors();
|
||||
void allOff();
|
||||
|
||||
/**
|
||||
* @brief This function performs actions to finish the deployment. Essentially switches
|
||||
* are turned of after the burn time has expired.
|
||||
*/
|
||||
void handleDeploymentFinish();
|
||||
ReturnValue_t deploymentTransistorsOff();
|
||||
ReturnValue_t saGpioAlternation();
|
||||
ReturnValue_t sa1On();
|
||||
ReturnValue_t sa1Off();
|
||||
ReturnValue_t sa2On();
|
||||
ReturnValue_t sa2Off();
|
||||
};
|
||||
|
||||
#endif /* MISSION_DEVICES_SOLARARRAYDEPLOYMENT_H_ */
|
||||
|
Reference in New Issue
Block a user