diff --git a/FprimeZephyrReference/Components/AntennaDeployer/AntennaDeployer.cpp b/FprimeZephyrReference/Components/AntennaDeployer/AntennaDeployer.cpp index 3e963563..0df4a3c9 100644 --- a/FprimeZephyrReference/Components/AntennaDeployer/AntennaDeployer.cpp +++ b/FprimeZephyrReference/Components/AntennaDeployer/AntennaDeployer.cpp @@ -113,8 +113,9 @@ void AntennaDeployer ::startNextAttempt() { this->log_ACTIVITY_HI_DeployAttempt(this->m_currentAttempt); - this->m_totalAttempts++; this->tlmWrite_DeployAttemptCount(this->m_totalAttempts); + this->m_totalAttempts++; + this->m_burnTicksThisAttempt = 0; if (this->isConnected_burnStart_OutputPort(0)) { diff --git a/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.cpp b/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.cpp index 55d093d5..2fda5aa1 100644 --- a/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.cpp +++ b/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.cpp @@ -5,13 +5,19 @@ #include "FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.hpp" +#include + namespace Components { // ---------------------------------------------------------------------- // Component construction and destruction // ---------------------------------------------------------------------- -PowerMonitor ::PowerMonitor(const char* const compName) : PowerMonitorComponentBase(compName) {} +PowerMonitor ::PowerMonitor(const char* const compName) + : PowerMonitorComponentBase(compName), + m_totalPower_mWh(0.0f), + m_totalGeneration_mWh(0.0f), + m_lastUpdateTime_s(0.0) {} PowerMonitor ::~PowerMonitor() {} @@ -23,12 +29,104 @@ void PowerMonitor ::run_handler(FwIndexType portNum, U32 context) { // System Power Monitor Requests this->sysVoltageGet_out(0); this->sysCurrentGet_out(0); - this->sysPowerGet_out(0); + F64 sysPowerW = this->sysPowerGet_out(0); // Solar Panel Power Monitor Requests this->solVoltageGet_out(0); this->solCurrentGet_out(0); - this->solPowerGet_out(0); + F64 solPowerW = this->solPowerGet_out(0); + + // Update total power consumption with combined system and solar power + F64 totalPowerW = sysPowerW + solPowerW; + this->updatePower(totalPowerW); + + // Update total solar power generation + this->updateGeneration(solPowerW); +} + +// ---------------------------------------------------------------------- +// Handler implementations for commands +// ---------------------------------------------------------------------- + +void PowerMonitor ::RESET_TOTAL_POWER_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { + this->m_totalPower_mWh = 0.0f; + this->m_lastUpdateTime_s = this->getCurrentTimeSeconds(); + this->log_ACTIVITY_LO_TotalPowerReset(); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); +} + +void PowerMonitor ::RESET_TOTAL_GENERATION_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { + this->m_totalGeneration_mWh = 0.0f; + this->log_ACTIVITY_LO_TotalGenerationReset(); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); +} + +// ---------------------------------------------------------------------- +// Helper method implementations +// ---------------------------------------------------------------------- + +F64 PowerMonitor ::getCurrentTimeSeconds() { + Fw::Time t = this->getTime(); + return static_cast(t.getSeconds()) + (static_cast(t.getUSeconds()) / 1.0e6); +} + +void PowerMonitor ::updatePower(F64 powerW) { + // Guard against invalid power values + if (powerW < 0.0 || powerW > 1000.0) { // Sanity check: power should be 0-1000W + return; + } + + F64 now_s = this->getCurrentTimeSeconds(); + + // Initialize time on first call + if (this->m_lastUpdateTime_s == 0.0) { + this->m_lastUpdateTime_s = now_s; + // Emit initial telemetry value + this->tlmWrite_TotalPowerConsumption(this->m_totalPower_mWh); + return; + } + + F64 dt_s = now_s - this->m_lastUpdateTime_s; + + // Only accumulate if time has passed and delta is reasonable (< 10 seconds to avoid time jumps) + if (dt_s > 0.0 && dt_s < 10.0) { + // Convert to mWh: Power (W) * time (hours) * 1000 + F32 energyAdded_mWh = static_cast(powerW * (dt_s / 3600.0) * 1000.0); + this->m_totalPower_mWh += energyAdded_mWh; + } + + this->m_lastUpdateTime_s = now_s; + + // Emit telemetry update + this->tlmWrite_TotalPowerConsumption(this->m_totalPower_mWh); +} + +void PowerMonitor ::updateGeneration(F64 powerW) { + // Guard against invalid power values + if (powerW < 0.0 || powerW > 1000.0) { // Sanity check: power should be 0-1000W + return; + } + + F64 now_s = this->getCurrentTimeSeconds(); + + // Initialize time on first call + if (this->m_lastUpdateTime_s == 0.0) { + // Emit initial telemetry value + this->tlmWrite_TotalPowerGenerated(this->m_totalGeneration_mWh); + return; + } + + F64 dt_s = now_s - this->m_lastUpdateTime_s; + + // Only accumulate if time has passed and delta is reasonable (< 10 seconds to avoid time jumps) + if (dt_s > 0.0 && dt_s < 10.0) { + // Convert to mWh: Power (W) * time (hours) * 1000 + F32 energyAdded_mWh = static_cast(powerW * (dt_s / 3600.0) * 1000.0); + this->m_totalGeneration_mWh += energyAdded_mWh; + } + + // Emit telemetry update + this->tlmWrite_TotalPowerGenerated(this->m_totalGeneration_mWh); } } // namespace Components diff --git a/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.fpp b/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.fpp index 58599345..5eb999b0 100644 --- a/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.fpp +++ b/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.fpp @@ -21,11 +21,51 @@ module Components { @ Port for sending powerGet calls to the Solar Panel Driver output port solPowerGet: Drv.PowerGet + @ Command to reset the accumulated power consumption + sync command RESET_TOTAL_POWER() + + @ Command to reset the accumulated power generation + sync command RESET_TOTAL_GENERATION() + + @ Telemetry channel for accumulated power consumption in mWh + telemetry TotalPowerConsumption: F32 + + @ Telemetry channel for accumulated solar power generation in mWh + telemetry TotalPowerGenerated: F32 + + @ Event logged when total power consumption is reset + event TotalPowerReset() \ + severity activity low \ + format "Total power consumption reset to 0 mWh" + + @ Event logged when total power generation is reset + event TotalGenerationReset() \ + severity activity low \ + format "Total power generation reset to 0 mWh" + ############################################################################### # Standard AC Ports: Required for Channels, Events, Commands, and Parameters # ############################################################################### @ Port for requesting the current time time get port timeCaller + @ Port for sending command registrations + command reg port cmdRegOut + + @ Port for receiving commands + command recv port cmdIn + + @ Port for sending command responses + command resp port cmdResponseOut + + @ Port for sending textual representation of events + text event port logTextOut + + @ Port for sending events to downlink + event port logOut + + @ Port for sending telemetry channels to downlink + telemetry port tlmOut + } } diff --git a/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.hpp b/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.hpp index ec4595b3..1ff57905 100644 --- a/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.hpp +++ b/FprimeZephyrReference/Components/PowerMonitor/PowerMonitor.hpp @@ -32,6 +32,46 @@ class PowerMonitor final : public PowerMonitorComponentBase { void run_handler(FwIndexType portNum, //!< The port number U32 context //!< The call order ) override; + + // ---------------------------------------------------------------------- + // Handler implementations for commands + // ---------------------------------------------------------------------- + + //! Handler implementation for RESET_TOTAL_POWER + void RESET_TOTAL_POWER_cmdHandler(FwOpcodeType opCode, //!< The opcode + U32 cmdSeq //!< The command sequence number + ) override; + + //! Handler implementation for RESET_TOTAL_GENERATION + void RESET_TOTAL_GENERATION_cmdHandler(FwOpcodeType opCode, //!< The opcode + U32 cmdSeq //!< The command sequence number + ) override; + + // ---------------------------------------------------------------------- + // Helper methods + // ---------------------------------------------------------------------- + + //! Get current time in seconds + F64 getCurrentTimeSeconds(); + + //! Update power consumption with new power reading + void updatePower(F64 powerW); + + //! Update solar power generation with new power reading + void updateGeneration(F64 powerW); + + // ---------------------------------------------------------------------- + // Member variables + // ---------------------------------------------------------------------- + + //! Accumulated power consumption in mWh + F32 m_totalPower_mWh; + + //! Accumulated solar power generation in mWh + F32 m_totalGeneration_mWh; + + //! Last update time in seconds + F64 m_lastUpdateTime_s; }; } // namespace Components diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi index 51738b44..9b21a34f 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi +++ b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi @@ -81,6 +81,8 @@ telemetry packets ReferenceDeploymentPackets { ReferenceDeployment.ina219SolManager.Voltage ReferenceDeployment.ina219SolManager.Current ReferenceDeployment.ina219SolManager.Power + ReferenceDeployment.powerMonitor.TotalPowerConsumption + ReferenceDeployment.powerMonitor.TotalPowerGenerated } } omit { diff --git a/FprimeZephyrReference/test/int/antenna_deployer_test.py b/FprimeZephyrReference/test/int/antenna_deployer_test.py index 3e5d89a1..f0a91e00 100644 --- a/FprimeZephyrReference/test/int/antenna_deployer_test.py +++ b/FprimeZephyrReference/test/int/antenna_deployer_test.py @@ -57,7 +57,7 @@ def test_deploy_without_distance_sensor(fprime_test_api: IntegrationTestAPI, sta proves_send_and_assert_command(fprime_test_api, f"{antenna_deployer}.DEPLOY") attempt_event: EventData = fprime_test_api.assert_event( - f"{antenna_deployer}.DeployAttempt", timeout=5 + f"{antenna_deployer}.DeployAttempt", args=[1], timeout=5 ) assert attempt_event.args[0].val == 1, ( "First deployment attempt should be attempt #1" @@ -98,7 +98,7 @@ def test_change_quiet_time_sec(fprime_test_api: IntegrationTestAPI, start_gds): # Verify deployment attempt starts after quiet time expires attempt_event: EventData = fprime_test_api.assert_event( - f"{antenna_deployer}.DeployAttempt", timeout=2 + f"{antenna_deployer}.DeployAttempt", args=[1], timeout=2 ) assert attempt_event.args[0].val == 1, ( "First deployment attempt should be attempt #1" @@ -140,7 +140,7 @@ def test_multiple_deploy_attempts(fprime_test_api: IntegrationTestAPI, start_gds # Verify first attempt attempt_event: EventData = fprime_test_api.assert_event( - f"{antenna_deployer}.DeployAttempt", timeout=5 + f"{antenna_deployer}.DeployAttempt", args=[1], timeout=5 ) assert attempt_event.args[0].val == 1, "First attempt should be #1" @@ -153,7 +153,7 @@ def test_multiple_deploy_attempts(fprime_test_api: IntegrationTestAPI, start_gds # Wait for retry delay and verify second attempt attempt_event = fprime_test_api.assert_event( - f"{antenna_deployer}.DeployAttempt", timeout=15 + f"{antenna_deployer}.DeployAttempt", args=[2], timeout=15 ) assert attempt_event.args[0].val == 2, "Second attempt should be #2" @@ -166,7 +166,7 @@ def test_multiple_deploy_attempts(fprime_test_api: IntegrationTestAPI, start_gds # Wait for retry delay and verify third attempt attempt_event = fprime_test_api.assert_event( - f"{antenna_deployer}.DeployAttempt", timeout=15 + f"{antenna_deployer}.DeployAttempt", args=[3], timeout=15 ) assert attempt_event.args[0].val == 3, "Third attempt should be #3" @@ -205,7 +205,7 @@ def test_burn_duration_sec(fprime_test_api: IntegrationTestAPI, start_gds): # Wait for deployment attempt to start attempt_event: EventData = fprime_test_api.assert_event( - f"{antenna_deployer}.DeployAttempt", timeout=5 + f"{antenna_deployer}.DeployAttempt", args=[1], timeout=5 ) assert attempt_event.args[0].val == 1, ( "First deployment attempt should be attempt #1" diff --git a/FprimeZephyrReference/test/int/power_monitor_test.py b/FprimeZephyrReference/test/int/power_monitor_test.py index 0ab97c45..89a171ff 100644 --- a/FprimeZephyrReference/test/int/power_monitor_test.py +++ b/FprimeZephyrReference/test/int/power_monitor_test.py @@ -14,6 +14,7 @@ ina219SysManager = "ReferenceDeployment.ina219SysManager" ina219SolManager = "ReferenceDeployment.ina219SolManager" +powerMonitor = "ReferenceDeployment.powerMonitor" @pytest.fixture(autouse=True) @@ -27,7 +28,7 @@ def send_packet(fprime_test_api: IntegrationTestAPI, start_gds): def test_01_power_manager_telemetry(fprime_test_api: IntegrationTestAPI, start_gds): - """Test that we can get Acceleration telemetry""" + """Test that we can get power telemetry from INA219 managers""" start: TimeType = TimeType().set_datetime(datetime.now()) sys_voltage: ChData = fprime_test_api.assert_telemetry( f"{ina219SysManager}.Voltage", start=start, timeout=65 @@ -48,6 +49,7 @@ def test_01_power_manager_telemetry(fprime_test_api: IntegrationTestAPI, start_g f"{ina219SolManager}.Power", start=start, timeout=65 ) + # TODO: Fix the power readings once INA219 power calculation is verified sys_voltage_reading: dict[float] = sys_voltage.get_val() sys_current_reading: dict[float] = sys_current.get_val() # sys_power_reading: dict[float] = sys_power.get_val() @@ -55,9 +57,23 @@ def test_01_power_manager_telemetry(fprime_test_api: IntegrationTestAPI, start_g sol_current_reading: dict[float] = sol_current.get_val() # sol_power_reading: dict[float] = sol_power.get_val() - assert sys_voltage_reading != 0, "Acceleration reading should be non-zero" - assert sys_current_reading != 0, "Acceleration reading should be non-zero" - # assert sys_power_reading != 0, "Acceleration reading should be non-zero" - assert sol_voltage_reading != 0, "Acceleration reading should be non-zero" - assert sol_current_reading != 0, "Acceleration reading should be non-zero" - # assert sol_power_reading != 0, "Acceleration reading should be non-zero" + assert sys_voltage_reading != 0, "System voltage reading should be non-zero" + assert sys_current_reading != 0, "System current reading should be non-zero" + # assert sys_power_reading != 0, "System power reading should be non-zero" + assert sol_voltage_reading != 0, "Solar voltage reading should be non-zero" + assert sol_current_reading != 0, "Solar current reading should be non-zero" + # assert sol_power_reading != 0, "Solar power reading should be non-zero" + + +def test_02_total_power_consumption_telemetry( + fprime_test_api: IntegrationTestAPI, start_gds +): + """Test that TotalPowerConsumption telemetry is being updated""" + total_power: ChData = fprime_test_api.assert_telemetry( + f"{powerMonitor}.TotalPowerConsumption", start="NOW", timeout=65 + ) + + total_power_reading: float = total_power.get_val() + + # Total power should be non-zero (accumulating over time) + assert total_power_reading != 0, "Total power consumption should be non-zero"