File EventAction.cpp¶
File List > Components > EventAction > EventAction.cpp
Go to the documentation of this file
#include "EventAction.hpp"
#include <dirent.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
namespace Orion {
static const char* getMediumStoragePath() {
const char* p = ::getenv("ORION_MEDIUM_STORAGE_DIR");
return p ? p : "./media/sd/medium/";
}
EventAction::EventAction(const char* compName)
: EventActionComponentBase(compName),
m_portsConnected(false),
m_inEclipse(false),
m_prevCommWindow(false),
m_prevMode(MissionMode::IDLE),
m_flushingMedium(false),
m_mediumFlushed(0) {}
EventAction::~EventAction() {}
// ---------------------------------------------------------------------------
// Schedule handler — evaluates mode transitions at 1 Hz
// ---------------------------------------------------------------------------
void EventAction::schedIn_handler(FwIndexType portNum, U32 context) {
// First tick: ports are now connected, broadcast the initial IDLE mode
if (!m_portsConnected) {
m_portsConnected = true;
MissionMode mode = stateToMode(this->missionMode_getState());
for (FwIndexType i = 0; i < 4; i++) {
this->modeChangeOut_out(i, mode);
}
this->tlmWrite_CurrentMode(mode);
}
// Query NavTelemetry for current position and comm window state
NavState nav = this->navStateIn_out(0);
bool inCommWindow = nav.get_inCommWindow();
// Paced MEDIUM flush: queue one file per tick to avoid overwhelming
// FileDownlink's 10-entry queue. Delete the file after successful queue.
if (m_flushingMedium) {
// Abort flush if we left DOWNLINK
if (m_prevMode.e != MissionMode::DOWNLINK) {
this->log_ACTIVITY_HI_MediumStorageFlushed(m_mediumFlushed);
m_flushingMedium = false;
} else {
const char* storageDir = getMediumStoragePath();
DIR* dir = ::opendir(storageDir);
bool found = false;
if (dir) {
struct dirent* entry;
while ((entry = ::readdir(dir)) != nullptr) {
if (::strncmp(entry->d_name, "orion_medium_", 13) != 0) {
continue;
}
char srcPath[256];
::snprintf(srcPath, sizeof(srcPath), "%s%s", storageDir, entry->d_name);
// Rename before queueing so we don't re-queue on next tick.
// FileDownlink reads from the renamed path asynchronously.
char sentPath[256];
::snprintf(sentPath, sizeof(sentPath), "%s.sent", srcPath);
::rename(srcPath, sentPath);
Svc::SendFileResponse resp =
this->sendFileOut_out(0, Fw::String(sentPath), Fw::String(entry->d_name), 0, 0);
if (resp.get_status() == Svc::SendFileStatus::STATUS_OK) {
m_mediumFlushed++;
} else {
// Queue full — rename back for next attempt
::rename(sentPath, srcPath);
}
found = true;
break; // One file per tick
}
::closedir(dir);
}
if (!found) {
// Directory empty or gone — flush complete
this->log_ACTIVITY_HI_MediumStorageFlushed(m_mediumFlushed);
m_flushingMedium = false;
}
}
}
// Detect comm window edges and send signals to the state machine
F64 distance = nav.get_gsDistanceKm();
if (inCommWindow && !m_prevCommWindow) {
this->log_ACTIVITY_HI_CommWindowOpened(distance);
this->missionMode_sendSignal_commWindowOpened();
} else if (!inCommWindow && m_prevCommWindow) {
this->log_ACTIVITY_HI_CommWindowClosed(distance);
this->missionMode_sendSignal_commWindowClosed();
}
m_prevCommWindow = inCommWindow;
}
// ---------------------------------------------------------------------------
// Command handlers
// ---------------------------------------------------------------------------
void EventAction::SET_ECLIPSE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq, bool inEclipse) {
m_inEclipse = inEclipse;
// Power doctrine: MEASURE during eclipse (on battery, can't charge anyway),
// IDLE during sunlit (charge batteries, conserve for next eclipse).
// State machine signal names are abstract: "sunUp" = activate MEASURE,
// "eclipse" = deactivate to IDLE.
if (inEclipse) {
this->missionMode_sendSignal_sunUp(); // eclipse → MEASURE
} else {
this->missionMode_sendSignal_eclipse(); // sun visible → IDLE (charge)
}
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
void EventAction::ENTER_SAFE_MODE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
if (m_prevMode.e == MissionMode::SAFE) {
this->log_WARNING_LO_GotoRejected(Fw::String("SAFE"), Fw::String("SAFE"));
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
this->missionMode_sendSignal_fault();
this->log_WARNING_HI_SafeModeEntered();
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
void EventAction::EXIT_SAFE_MODE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
if (m_prevMode.e != MissionMode::SAFE) {
this->log_WARNING_LO_GotoRejected(Fw::String("IDLE"), Fw::String(modeToStr(m_prevMode)));
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
this->missionMode_sendSignal_clearFault();
this->log_ACTIVITY_HI_SafeModeExited();
// Re-sync with current conditions so we don't miss an in-progress
// comm window or sun state that changed while we were in SAFE.
NavState nav = this->navStateIn_out(0);
bool inCommWindow = nav.get_inCommWindow();
m_prevCommWindow = inCommWindow;
if (inCommWindow) {
this->log_ACTIVITY_HI_CommWindowOpened(nav.get_gsDistanceKm());
this->missionMode_sendSignal_commWindowOpened();
} else if (m_inEclipse) {
this->missionMode_sendSignal_sunUp(); // eclipse → MEASURE
}
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
void EventAction::FLUSH_MEDIUM_STORAGE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
// Only allow during comm window
if (m_prevMode.e != MissionMode::DOWNLINK) {
this->log_WARNING_LO_MediumFlushRejected(Fw::String(modeToStr(m_prevMode)));
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
const char* storageDir = getMediumStoragePath();
// Validate path length (FileDownlink limit is 100 chars)
if (::strlen(storageDir) + 22 >= 100) {
this->log_WARNING_HI_MediumPathTooLong();
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
// Start the paced flush — schedIn will queue one file per tick
m_flushingMedium = true;
m_mediumFlushed = 0;
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
void EventAction::GOTO_IDLE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
// Only from MEASURE or DOWNLINK
if (m_prevMode.e != MissionMode::MEASURE && m_prevMode.e != MissionMode::DOWNLINK) {
this->log_WARNING_LO_GotoRejected(Fw::String("IDLE"), Fw::String(modeToStr(m_prevMode)));
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
this->missionMode_sendSignal_returnToIdle();
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
void EventAction::GOTO_MEASURE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
// Only from IDLE
if (m_prevMode.e != MissionMode::IDLE) {
this->log_WARNING_LO_GotoRejected(Fw::String("MEASURE"), Fw::String(modeToStr(m_prevMode)));
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
this->missionMode_sendSignal_sunUp();
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
void EventAction::GOTO_DOWNLINK_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
// Only from IDLE
if (m_prevMode.e != MissionMode::IDLE) {
this->log_WARNING_LO_GotoRejected(Fw::String("DOWNLINK"), Fw::String(modeToStr(m_prevMode)));
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
this->missionMode_sendSignal_commWindowOpened();
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
// ---------------------------------------------------------------------------
// State machine actions
// ---------------------------------------------------------------------------
void EventAction::Orion_MissionModeSm_action_broadcastMode(SmId smId, Orion_MissionModeSm::Signal signal) {
// Skip broadcast during state machine init — ports aren't connected yet
if (!m_portsConnected) {
return;
}
// NOTE: F-Prime runs entry actions BEFORE updating m_state, so
// getState() returns the old state here. Derive target from signal.
MissionMode mode = signalToTargetMode(signal);
// Broadcast to all 4 pipeline components
for (FwIndexType i = 0; i < 4; i++) {
this->modeChangeOut_out(i, mode);
}
this->tlmWrite_CurrentMode(mode);
}
void EventAction::Orion_MissionModeSm_action_logModeChange(SmId smId, Orion_MissionModeSm::Signal signal) {
// NOTE: F-Prime runs entry actions BEFORE updating m_state, so
// getState() returns the old state here. Derive target from signal.
MissionMode currentMode = signalToTargetMode(signal);
this->log_ACTIVITY_HI_ModeChanged(Fw::String(modeToStr(m_prevMode)), Fw::String(modeToStr(currentMode)));
m_prevMode = currentMode;
}
// ---------------------------------------------------------------------------
// State machine guard
// ---------------------------------------------------------------------------
bool EventAction::Orion_MissionModeSm_guard_sunIsUp(SmId smId, Orion_MissionModeSm::Signal signal) const {
// Guard name is abstract. Returns true → MEASURE, false → IDLE.
// MEASURE during eclipse (battery powered), IDLE during sun (charging).
return m_inEclipse;
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
MissionMode EventAction::stateToMode(Orion_MissionModeSm::State state) {
switch (state.e) {
case Orion_MissionModeSm::State::IDLE:
return MissionMode::IDLE;
case Orion_MissionModeSm::State::MEASURE:
return MissionMode::MEASURE;
case Orion_MissionModeSm::State::DOWNLINK:
return MissionMode::DOWNLINK;
case Orion_MissionModeSm::State::SAFE:
return MissionMode::SAFE;
default:
return MissionMode::IDLE;
}
}
MissionMode EventAction::signalToTargetMode(Orion_MissionModeSm::Signal signal) const {
switch (signal) {
case Orion_MissionModeSm::Signal::sunUp:
return MissionMode::MEASURE;
case Orion_MissionModeSm::Signal::commWindowOpened:
return MissionMode::DOWNLINK;
case Orion_MissionModeSm::Signal::fault:
return MissionMode::SAFE;
case Orion_MissionModeSm::Signal::commWindowClosed:
// POST_DOWNLINK choice: MEASURE during eclipse, IDLE during sun.
return m_inEclipse ? MissionMode::MEASURE : MissionMode::IDLE;
case Orion_MissionModeSm::Signal::eclipse:
case Orion_MissionModeSm::Signal::returnToIdle:
case Orion_MissionModeSm::Signal::clearFault:
case Orion_MissionModeSm::Signal::__FPRIME_INITIAL_TRANSITION:
default:
return MissionMode::IDLE;
}
}
const char* EventAction::modeToStr(MissionMode mode) {
switch (mode.e) {
case MissionMode::IDLE:
return "IDLE";
case MissionMode::MEASURE:
return "MEASURE";
case MissionMode::DOWNLINK:
return "DOWNLINK";
case MissionMode::SAFE:
return "SAFE";
default:
return "UNKNOWN";
}
}
} // namespace Orion