521 lines
17 KiB
C++
521 lines
17 KiB
C++
#include "SolarArrayDeploymentHandler.h"
|
|
|
|
#include <fsfw/filesystem/HasFileSystemIF.h>
|
|
#include <fsfw/tasks/TaskFactory.h>
|
|
|
|
#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"
|
|
#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<void*>(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
|
|
trace::threadTrace(opCounter, "SA DEPL");
|
|
#endif
|
|
if (opDivider.checkAndIncrement()) {
|
|
auto activeSdc = sdcMan.getActiveSdCard();
|
|
std::error_code e;
|
|
if (activeSdc and activeSdc.value() == sd::SdCard::SLOT_0 and
|
|
sdcMan.isSdCardUsable(activeSdc.value())) {
|
|
if (exists(SD_0_DEPL_FILE, e)) {
|
|
// 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, e)) {
|
|
// 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";
|
|
};
|
|
std::error_code e;
|
|
if (sdCard == sd::SdCard::SLOT_0) {
|
|
if (not exists(SD_0_DEPLY_INFO, e)) {
|
|
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, e)) {
|
|
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* infoFile,
|
|
bool dryRun) {
|
|
using namespace std;
|
|
std::error_code e;
|
|
ifstream file(infoFile);
|
|
if (file.bad()) {
|
|
return false;
|
|
}
|
|
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) {
|
|
std::filesystem::remove(infoFile, e);
|
|
if (sdCard == sd::SdCard::SLOT_0) {
|
|
std::filesystem::remove(SD_0_DEPL_FILE, e);
|
|
} else {
|
|
std::filesystem::remove(SD_1_DEPL_FILE, e);
|
|
}
|
|
triggerEvent(AUTONOMOUS_DEPLOYMENT_COMPLETED);
|
|
} else {
|
|
std::ofstream of(infoFile);
|
|
if (of.bad()) {
|
|
return false;
|
|
}
|
|
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();
|
|
}
|