├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Components ├── CMakeLists.txt ├── MathReceiver │ ├── CMakeLists.txt │ ├── MathReceiver.cpp │ ├── MathReceiver.fpp │ ├── MathReceiver.hpp │ ├── docs │ │ └── sdd.md │ └── test │ │ └── ut │ │ ├── MathReceiverTestMain.cpp │ │ ├── MathReceiverTester.cpp │ │ └── MathReceiverTester.hpp └── MathSender │ ├── CMakeLists.txt │ ├── MathSender.cpp │ ├── MathSender.fpp │ ├── MathSender.hpp │ ├── docs │ └── sdd.md │ └── test │ └── ut │ ├── MathSenderTestMain.cpp │ ├── MathSenderTester.cpp │ └── MathSenderTester.hpp ├── LICENSE ├── MathDeployment ├── CMakeLists.txt ├── Main.cpp ├── README.md └── Top │ ├── CMakeLists.txt │ ├── MathDeploymentPackets.xml │ ├── MathDeploymentTopology.cpp │ ├── MathDeploymentTopology.hpp │ ├── MathDeploymentTopologyDefs.hpp │ ├── instances.fpp │ └── topology.fpp ├── Ports ├── CMakeLists.txt └── MathPorts.fpp ├── README.md ├── Types ├── CMakeLists.txt └── MathTypes.fpp ├── docs ├── _config.yml ├── _includes │ └── toc.md ├── img │ └── top.png └── math-component.md ├── project.cmake └── settings.ini /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | venv 4 | fprime-venv 5 | **/build-artifacts 6 | **/build-fprime-* 7 | **-template 8 | **/logs 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "fprime"] 2 | path = fprime 3 | url = https://github.com/nasa/fprime.git 4 | branch = devel 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #### 2 | # 3 | #### 4 | cmake_minimum_required(VERSION 3.13) 5 | project(fprime-tutorial-math-component C CXX) 6 | 7 | ### 8 | # F' Core Setup 9 | # This includes all of the F prime core components, and imports the make-system. 10 | ### 11 | include("${CMAKE_CURRENT_LIST_DIR}/fprime/cmake/FPrime.cmake") 12 | # NOTE: register custom targets between these two lines 13 | include("${FPRIME_FRAMEWORK_PATH}/cmake/FPrime-Code.cmake") 14 | 15 | 16 | # This includes project-wide objects 17 | include("${CMAKE_CURRENT_LIST_DIR}/project.cmake") 18 | -------------------------------------------------------------------------------- /Components/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Include project-wide components here 2 | 3 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/MathReceiver") 4 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/MathSender") 5 | -------------------------------------------------------------------------------- /Components/MathReceiver/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #### 2 | # F prime CMakeLists.txt: 3 | # 4 | # SOURCE_FILES: combined list of source and autocoding files 5 | # MOD_DEPS: (optional) module dependencies 6 | # UT_SOURCE_FILES: list of source files for unit tests 7 | # 8 | #### 9 | set(SOURCE_FILES 10 | "${CMAKE_CURRENT_LIST_DIR}/MathReceiver.fpp" 11 | "${CMAKE_CURRENT_LIST_DIR}/MathReceiver.cpp" 12 | ) 13 | 14 | # Uncomment and add any modules that this component depends on, else 15 | # they might not be available when cmake tries to build this component. 16 | 17 | # set(MOD_DEPS 18 | # Add your dependencies here 19 | # ) 20 | 21 | register_fprime_module() 22 | 23 | set(UT_SOURCE_FILES 24 | "${CMAKE_CURRENT_LIST_DIR}/MathReceiver.fpp" 25 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/MathReceiverTester.cpp" 26 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/MathReceiverTestMain.cpp" 27 | ) 28 | set(UT_AUTO_HELPERS ON) 29 | set(UT_MOD_DEPS STest) 30 | register_fprime_ut() -------------------------------------------------------------------------------- /Components/MathReceiver/MathReceiver.cpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title MathReceiver.cpp 3 | // \author asloan 4 | // \brief cpp file for MathReceiver component implementation class 5 | // ====================================================================== 6 | 7 | 8 | #include 9 | #include 10 | 11 | namespace MathModule { 12 | 13 | // ---------------------------------------------------------------------- 14 | // Construction, initialization, and destruction 15 | // ---------------------------------------------------------------------- 16 | 17 | MathReceiver :: 18 | MathReceiver( 19 | const char *const compName 20 | ) : MathReceiverComponentBase(compName), 21 | numMathOps(0) 22 | { 23 | 24 | } 25 | 26 | MathReceiver :: 27 | ~MathReceiver() 28 | { 29 | 30 | } 31 | 32 | // ---------------------------------------------------------------------- 33 | // Handler implementations for user-defined typed input ports 34 | // ---------------------------------------------------------------------- 35 | 36 | void MathReceiver :: 37 | mathOpIn_handler( 38 | const FwIndexType portNum, 39 | F32 val1, 40 | const MathModule::MathOp &op, 41 | F32 val2 42 | ) 43 | { 44 | // Get the initial result 45 | F32 res = 0.0; 46 | switch (op.e) { 47 | case MathOp::ADD: 48 | res = val1 + val2; 49 | break; 50 | case MathOp::SUB: 51 | res = val1 - val2; 52 | break; 53 | case MathOp::MUL: 54 | res = val1 * val2; 55 | break; 56 | case MathOp::DIV: 57 | if ( val2 == 0 ){ 58 | this->log_ACTIVITY_HI_DIVIDE_BY_ZERO(); 59 | break; 60 | } 61 | res = val1 / val2; 62 | break; 63 | default: 64 | FW_ASSERT(0, op.e); 65 | break; 66 | }//end switch 67 | 68 | // Get the factor value 69 | Fw::ParamValid valid; 70 | F32 factor = paramGet_FACTOR(valid); 71 | FW_ASSERT( 72 | valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT, 73 | valid.e 74 | ); 75 | 76 | // Multiply result by factor 77 | res *= factor; 78 | 79 | // Increment number of math ops 80 | numMathOps++; 81 | 82 | // Emit telemetry and events 83 | this->log_ACTIVITY_HI_OPERATION_PERFORMED(op); 84 | this->tlmWrite_OPERATION(op); 85 | this->tlmWrite_NUMBER_OF_OPS(numMathOps); 86 | 87 | // Emit result 88 | this->mathResultOut_out(0, res); 89 | }//end mathOpIn_handler 90 | 91 | 92 | void MathReceiver :: 93 | schedIn_handler( 94 | const FwIndexType portNum, 95 | U32 context 96 | ) 97 | { 98 | U32 numMsgs = this->m_queue.getMessagesAvailable(); 99 | for (U32 i = 0; i < numMsgs; ++i) { 100 | (void) this->doDispatch(); 101 | } 102 | } 103 | 104 | // ---------------------------------------------------------------------- 105 | // Command handler implementations 106 | // ---------------------------------------------------------------------- 107 | 108 | void MathReceiver :: 109 | CLEAR_EVENT_THROTTLE_cmdHandler( 110 | const FwOpcodeType opCode, 111 | const U32 cmdSeq 112 | ) 113 | { 114 | // clear throttle 115 | this->log_ACTIVITY_HI_FACTOR_UPDATED_ThrottleClear(); 116 | // send event that throttle is cleared 117 | this->log_ACTIVITY_HI_THROTTLE_CLEARED(); 118 | // reply with completion status 119 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); 120 | } 121 | 122 | // Parameter Checker 123 | 124 | // In: MathReceiver.cpp 125 | void MathReceiver :: 126 | parameterUpdated(FwPrmIdType id) 127 | { 128 | switch (id) { 129 | case PARAMID_FACTOR: { 130 | Fw::ParamValid valid; 131 | F32 val = this->paramGet_FACTOR(valid); 132 | FW_ASSERT( 133 | valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT, 134 | valid.e 135 | ); 136 | this->log_ACTIVITY_HI_FACTOR_UPDATED(val); 137 | break; 138 | } 139 | default: 140 | FW_ASSERT(0, id); 141 | break; 142 | } 143 | } 144 | 145 | } // end namespace MathModule 146 | -------------------------------------------------------------------------------- /Components/MathReceiver/MathReceiver.fpp: -------------------------------------------------------------------------------- 1 | # In: MathReceiver.fpp 2 | module MathModule { 3 | 4 | @ Component for receiving and performing a math operation 5 | queued component MathReceiver { 6 | 7 | # ---------------------------------------------------------------------- 8 | # General ports 9 | # ---------------------------------------------------------------------- 10 | 11 | @ Port for receiving the math operation 12 | async input port mathOpIn: OpRequest 13 | 14 | @ Port for returning the math result 15 | output port mathResultOut: MathResult 16 | 17 | @ The rate group scheduler input 18 | sync input port schedIn: Svc.Sched 19 | 20 | # ---------------------------------------------------------------------- 21 | # Special ports 22 | # ---------------------------------------------------------------------- 23 | 24 | @ Command receive 25 | command recv port cmdIn 26 | 27 | @ Command registration 28 | command reg port cmdRegOut 29 | 30 | @ Command response 31 | command resp port cmdResponseOut 32 | 33 | @ Event 34 | event port eventOut 35 | 36 | @ Parameter get 37 | param get port prmGetOut 38 | 39 | @ Parameter set 40 | param set port prmSetOut 41 | 42 | @ Telemetry 43 | telemetry port tlmOut 44 | 45 | @ Text event 46 | text event port textEventOut 47 | 48 | @ Time get 49 | time get port timeGetOut 50 | 51 | # ---------------------------------------------------------------------- 52 | # Parameters 53 | # ---------------------------------------------------------------------- 54 | 55 | @ The multiplier in the math operation 56 | param FACTOR: F32 default 1.0 id 0 \ 57 | set opcode 10 \ 58 | save opcode 11 59 | 60 | # ---------------------------------------------------------------------- 61 | # Events 62 | # ---------------------------------------------------------------------- 63 | 64 | @ Factor updated 65 | event FACTOR_UPDATED( 66 | val: F32 @< The factor value 67 | ) \ 68 | severity activity high \ 69 | id 0 \ 70 | format "Factor updated to {f}" \ 71 | throttle 3 72 | 73 | @ Math operation performed 74 | event OPERATION_PERFORMED( 75 | val: MathOp @< The operation 76 | ) \ 77 | severity activity high \ 78 | id 1 \ 79 | format "{} operation performed" 80 | 81 | @ Event throttle cleared 82 | event THROTTLE_CLEARED \ 83 | severity activity high \ 84 | id 2 \ 85 | format "Event throttle cleared" 86 | 87 | @ Commanded to divide by zero 88 | event DIVIDE_BY_ZERO \ 89 | severity activity high \ 90 | id 3 \ 91 | format "ERROR: Received zero as denominator. Opperands dropped." 92 | 93 | # ---------------------------------------------------------------------- 94 | # Commands 95 | # ---------------------------------------------------------------------- 96 | 97 | @ Clear the event throttle 98 | async command CLEAR_EVENT_THROTTLE \ 99 | opcode 0 100 | 101 | # ---------------------------------------------------------------------- 102 | # Telemetry 103 | # ---------------------------------------------------------------------- 104 | 105 | @ The operation 106 | telemetry OPERATION: MathOp id 0 107 | 108 | @ Multiplication factor 109 | telemetry FACTOR: F32 id 1 110 | 111 | @ Number of math operations 112 | telemetry NUMBER_OF_OPS: U32 113 | 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /Components/MathReceiver/MathReceiver.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title MathReceiver.hpp 3 | // \author asloan 4 | // \brief hpp file for MathReceiver component implementation class 5 | // ====================================================================== 6 | 7 | #ifndef MathReceiver_HPP 8 | #define MathReceiver_HPP 9 | 10 | #include "Components/MathReceiver/MathReceiverComponentAc.hpp" 11 | 12 | namespace MathModule { 13 | 14 | class MathReceiver : 15 | public MathReceiverComponentBase 16 | { 17 | 18 | public: 19 | 20 | // ---------------------------------------------------------------------- 21 | // Construction, initialization, and destruction 22 | // ---------------------------------------------------------------------- 23 | 24 | //! Construct object MathReceiver 25 | //! 26 | MathReceiver( 27 | const char *const compName /*!< The component name*/ 28 | ); 29 | 30 | //! Destroy object MathReceiver 31 | //! 32 | ~MathReceiver(); 33 | 34 | PRIVATE: 35 | 36 | // ---------------------------------------------------------------------- 37 | // Handler implementations for user-defined typed input ports 38 | // ---------------------------------------------------------------------- 39 | void parameterUpdated(FwPrmIdType id); 40 | //! Handler implementation for mathOpIn 41 | //! 42 | void mathOpIn_handler( 43 | const FwIndexType portNum, /*!< The port number*/ 44 | F32 val1, /*!< 45 | The first operand 46 | */ 47 | const MathModule::MathOp &op, /*!< 48 | The operation 49 | */ 50 | F32 val2 /*!< 51 | The second operand 52 | */ 53 | ); 54 | 55 | //! Handler implementation for schedIn 56 | //! 57 | void schedIn_handler( 58 | const FwIndexType portNum, /*!< The port number*/ 59 | U32 context /*!< 60 | The call order 61 | */ 62 | ); 63 | 64 | PRIVATE: 65 | 66 | // ---------------------------------------------------------------------- 67 | // Command handler implementations 68 | // ---------------------------------------------------------------------- 69 | 70 | //! Implementation for CLEAR_EVENT_THROTTLE command handler 71 | //! Clear the event throttle 72 | void CLEAR_EVENT_THROTTLE_cmdHandler( 73 | const FwOpcodeType opCode, /*!< The opcode*/ 74 | const U32 cmdSeq /*!< The command sequence number*/ 75 | ); 76 | 77 | 78 | PRIVATE: 79 | // ---------------------------------------------------------------------- 80 | // Member variables 81 | // ---------------------------------------------------------------------- 82 | U32 numMathOps; 83 | 84 | }; 85 | 86 | } // end namespace MathModule 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /Components/MathReceiver/docs/sdd.md: -------------------------------------------------------------------------------- 1 | # MathModule::MathReceiver 2 | 3 | Example Component for F Prime FSW framework. 4 | 5 | ## Usage Examples 6 | Add usage examples here 7 | 8 | ### Diagrams 9 | Add diagrams here 10 | 11 | ### Typical Usage 12 | And the typical usage of the component here 13 | 14 | ## Class Diagram 15 | Add a class diagram here 16 | 17 | ## Port Descriptions 18 | | Name | Description | 19 | |---|---| 20 | |---|---| 21 | 22 | ## Component States 23 | Add component states in the chart below 24 | | Name | Description | 25 | |---|---| 26 | |---|---| 27 | 28 | ## Sequence Diagrams 29 | Add sequence diagrams here 30 | 31 | ## Parameters 32 | | Name | Description | 33 | |---|---| 34 | |---|---| 35 | 36 | ## Commands 37 | | Name | Description | 38 | |---|---| 39 | |---|---| 40 | 41 | ## Events 42 | | Name | Description | 43 | |---|---| 44 | |---|---| 45 | 46 | ## Telemetry 47 | | Name | Description | 48 | |---|---| 49 | |---|---| 50 | 51 | ## Unit Tests 52 | Add unit test descriptions in the chart below 53 | | Name | Description | Output | Coverage | 54 | |---|---|---|---| 55 | |---|---|---|---| 56 | 57 | ## Requirements 58 | Add requirements in the chart below 59 | | Name | Description | Validation | 60 | |---|---|---| 61 | |---|---|---| 62 | 63 | ## Change Log 64 | | Date | Description | 65 | |---|---| 66 | |---| Initial Draft | -------------------------------------------------------------------------------- /Components/MathReceiver/test/ut/MathReceiverTestMain.cpp: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------- 2 | // TestMain.cpp 3 | // ---------------------------------------------------------------------- 4 | 5 | #include "MathReceiverTester.hpp" 6 | #include "STest/Random/Random.hpp" 7 | 8 | /* 9 | TEST(Nominal, ToDo) { 10 | MathModule::MathReceiverTester tester; 11 | tester.toDo(); 12 | } 13 | */ 14 | 15 | TEST(Nominal, AddCommand) { 16 | MathModule::MathReceiverTester tester; 17 | tester.testAdd(); 18 | } 19 | 20 | TEST(Nominal, SubCommand) { 21 | MathModule::MathReceiverTester tester; 22 | tester.testSub(); 23 | } 24 | 25 | TEST(Nominal, Throttle) { 26 | MathModule::MathReceiverTester tester; 27 | tester.testThrottle(); 28 | } 29 | 30 | int main(int argc, char **argv) { 31 | ::testing::InitGoogleTest(&argc, argv); 32 | STest::Random::seed(); 33 | return RUN_ALL_TESTS(); 34 | } 35 | -------------------------------------------------------------------------------- /Components/MathReceiver/test/ut/MathReceiverTester.cpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title MathReceiver.hpp 3 | // \author asloan 4 | // \brief cpp file for MathReceiver test harness implementation class 5 | // ====================================================================== 6 | 7 | #include "MathReceiverTester.hpp" 8 | #include "STest/Pick/Pick.hpp" 9 | 10 | namespace MathModule { 11 | #define CMD_SEQ 42 12 | // ---------------------------------------------------------------------- 13 | // Construction and destruction 14 | // ---------------------------------------------------------------------- 15 | 16 | MathReceiverTester :: 17 | MathReceiverTester() : 18 | MathReceiverGTestBase("Tester", MathReceiverTester::MAX_HISTORY_SIZE), 19 | component("MathReceiver") 20 | { 21 | this->initComponents(); 22 | this->connectPorts(); 23 | } 24 | 25 | MathReceiverTester :: 26 | ~MathReceiverTester() 27 | { 28 | 29 | } 30 | 31 | // ---------------------------------------------------------------------- 32 | // Tests 33 | // ---------------------------------------------------------------------- 34 | 35 | void MathReceiverTester :: 36 | toDo() 37 | { 38 | // TODO 39 | } 40 | 41 | F32 MathReceiverTester :: 42 | pickF32Value() 43 | { 44 | const F32 m = 10e6; 45 | return m * (1.0 - 2 * STest::Pick::inUnitInterval()); 46 | } 47 | 48 | void MathReceiverTester :: 49 | setFactor( 50 | F32 factor, 51 | ThrottleState throttleState 52 | ) 53 | { 54 | // clear history 55 | this->clearHistory(); 56 | // set the parameter 57 | this->paramSet_FACTOR(factor, Fw::ParamValid::VALID); 58 | const U32 instance = STest::Pick::any(); 59 | const U32 cmdSeq = STest::Pick::any(); 60 | this->paramSend_FACTOR(instance, cmdSeq); 61 | if (throttleState == ThrottleState::NOT_THROTTLED) { 62 | // verify the parameter update notification event was sent 63 | ASSERT_EVENTS_SIZE(1); 64 | ASSERT_EVENTS_FACTOR_UPDATED_SIZE(1); 65 | ASSERT_EVENTS_FACTOR_UPDATED(0, factor); 66 | } 67 | else { 68 | ASSERT_EVENTS_SIZE(0); 69 | } 70 | } 71 | 72 | F32 MathReceiverTester :: 73 | computeResult( 74 | F32 val1, 75 | MathOp op, 76 | F32 val2, 77 | F32 factor 78 | ) 79 | { 80 | F32 result = 0; 81 | switch (op.e) { 82 | case MathOp::ADD: 83 | result = val1 + val2; 84 | break; 85 | case MathOp::SUB: 86 | result = val1 - val2; 87 | break; 88 | case MathOp::MUL: 89 | result = val1 * val2; 90 | break; 91 | case MathOp::DIV: 92 | result = val1 / val2; 93 | break; 94 | default: 95 | FW_ASSERT(0, op.e); 96 | break; 97 | } 98 | result *= factor; 99 | return result; 100 | } 101 | 102 | void MathReceiverTester :: 103 | doMathOp( 104 | MathOp op, 105 | F32 factor 106 | ) 107 | { 108 | 109 | // pick values 110 | const F32 val1 = pickF32Value(); 111 | const F32 val2 = pickF32Value(); 112 | 113 | // clear history 114 | this->clearHistory(); 115 | 116 | // invoke operation port with add operation 117 | this->invoke_to_mathOpIn(0, val1, op, val2); 118 | // invoke scheduler port to dispatch message 119 | const U32 context = STest::Pick::any(); 120 | this->invoke_to_schedIn(0, context); 121 | 122 | // verify the result of the operation was returned 123 | 124 | // check that there was one port invocation 125 | ASSERT_FROM_PORT_HISTORY_SIZE(1); 126 | // check that the port we expected was invoked 127 | ASSERT_from_mathResultOut_SIZE(1); 128 | // check that the component performed the operation correctly 129 | const F32 result = computeResult(val1, op, val2, factor); 130 | ASSERT_from_mathResultOut(0, result); 131 | 132 | // verify events 133 | 134 | // check that there was one event 135 | // if you're dviding by zero, there may be two events ;) 136 | ASSERT_EVENTS_SIZE(1); 137 | // check that it was the op event 138 | ASSERT_EVENTS_OPERATION_PERFORMED_SIZE(1); 139 | // check that the event has the correct argument 140 | ASSERT_EVENTS_OPERATION_PERFORMED(0, op); 141 | 142 | // verify telemetry 143 | 144 | // check that one channel was written 145 | ASSERT_TLM_SIZE(2); 146 | // check that it was the op channel 147 | ASSERT_TLM_OPERATION_SIZE(1); 148 | // check for the correct value of the channel 149 | ASSERT_TLM_OPERATION(0, op); 150 | 151 | } 152 | 153 | void MathReceiverTester :: 154 | testAdd() 155 | { 156 | // Set the factor parameter by command 157 | const F32 factor = pickF32Value(); 158 | this->setFactor(factor, ThrottleState::NOT_THROTTLED); 159 | // Do the add operation 160 | this->doMathOp(MathOp::ADD, factor); 161 | } 162 | 163 | void MathReceiverTester :: 164 | testSub() 165 | { 166 | // Set the factor parameter by loading parameters 167 | const F32 factor = pickF32Value(); 168 | this->paramSet_FACTOR(factor, Fw::ParamValid::VALID); 169 | this->component.loadParameters(); 170 | // Do the operation 171 | this->doMathOp(MathOp::SUB, factor); 172 | } 173 | 174 | void MathReceiverTester :: 175 | testThrottle() 176 | { 177 | 178 | // send the number of commands required to throttle the event 179 | // Use the autocoded value so the unit test passes if the 180 | // throttle value is changed 181 | const F32 factor = pickF32Value(); 182 | for ( 183 | U16 cycle = 0; 184 | cycle < MathReceiverComponentBase::EVENTID_FACTOR_UPDATED_THROTTLE; 185 | cycle++ 186 | ) { 187 | this->setFactor(factor, ThrottleState::NOT_THROTTLED); 188 | } 189 | 190 | // Event should now be throttled 191 | this->setFactor(factor, ThrottleState::THROTTLED); 192 | 193 | // send the command to clear the throttle 194 | this->sendCmd_CLEAR_EVENT_THROTTLE(TEST_INSTANCE_ID, CMD_SEQ); 195 | // invoke scheduler port to dispatch message 196 | const U32 context = STest::Pick::any(); 197 | this->invoke_to_schedIn(0, context); 198 | // verify clear event was sent 199 | ASSERT_EVENTS_SIZE(1); 200 | ASSERT_EVENTS_THROTTLE_CLEARED_SIZE(1); 201 | 202 | // Throttling should be cleared 203 | this->setFactor(factor, ThrottleState::NOT_THROTTLED); 204 | 205 | } 206 | 207 | // ---------------------------------------------------------------------- 208 | // Handlers for typed from ports 209 | // ---------------------------------------------------------------------- 210 | 211 | void MathReceiverTester :: 212 | from_mathResultOut_handler( 213 | const FwIndexType portNum, 214 | F32 result 215 | ) 216 | { 217 | this->pushFromPortEntry_mathResultOut(result); 218 | } 219 | 220 | 221 | } // end namespace MathModule 222 | -------------------------------------------------------------------------------- /Components/MathReceiver/test/ut/MathReceiverTester.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title MathReceiver/test/ut/Tester.hpp 3 | // \author asloan 4 | // \brief hpp file for MathReceiver test harness implementation class 5 | // ====================================================================== 6 | 7 | #ifndef TESTER_HPP 8 | #define TESTER_HPP 9 | 10 | #include "MathReceiverGTestBase.hpp" 11 | #include "Components/MathReceiver/MathReceiver.hpp" 12 | 13 | namespace MathModule { 14 | 15 | class MathReceiverTester : 16 | public MathReceiverGTestBase 17 | { 18 | 19 | // ---------------------------------------------------------------------- 20 | // Construction and destruction 21 | // ---------------------------------------------------------------------- 22 | 23 | public: 24 | // Maximum size of histories storing events, telemetry, and port outputs 25 | static const U32 MAX_HISTORY_SIZE = 10; 26 | // Instance ID supplied to the component instance under test 27 | static const FwEnumStoreType TEST_INSTANCE_ID = 0; 28 | // Queue depth supplied to component instance under test 29 | static const FwSizeType TEST_INSTANCE_QUEUE_DEPTH = 10; 30 | 31 | //! Construct object MathReceiverTester 32 | //! 33 | MathReceiverTester(); 34 | 35 | //! Destroy object MathReceiverTester 36 | //! 37 | ~MathReceiverTester(); 38 | 39 | private: 40 | 41 | // ---------------------------------------------------------------------- 42 | // Types 43 | // ---------------------------------------------------------------------- 44 | 45 | enum class ThrottleState { 46 | THROTTLED, 47 | NOT_THROTTLED 48 | }; 49 | 50 | public: 51 | 52 | // ---------------------------------------------------------------------- 53 | // Tests 54 | // ---------------------------------------------------------------------- 55 | 56 | //! To do 57 | //! 58 | void toDo(); 59 | 60 | F32 pickF32Value(); 61 | void setFactor( 62 | F32 factor, 63 | ThrottleState throttleState 64 | ); 65 | 66 | F32 computeResult( 67 | F32 val1, 68 | MathOp op, 69 | F32 val2, 70 | F32 factor 71 | ); 72 | 73 | void doMathOp( 74 | MathOp op, 75 | F32 factor 76 | ); 77 | 78 | void testAdd(); 79 | 80 | void testSub(); 81 | 82 | void testThrottle(); 83 | 84 | private: 85 | 86 | // ---------------------------------------------------------------------- 87 | // Handlers for typed from ports 88 | // ---------------------------------------------------------------------- 89 | 90 | //! Handler for from_mathResultOut 91 | //! 92 | void from_mathResultOut_handler( 93 | const FwIndexType portNum, /*!< The port number*/ 94 | F32 result /*!< 95 | the result of the operation 96 | */ 97 | ); 98 | 99 | private: 100 | 101 | // ---------------------------------------------------------------------- 102 | // Helper methods 103 | // ---------------------------------------------------------------------- 104 | 105 | //! Connect ports 106 | //! 107 | void connectPorts(); 108 | 109 | //! Initialize components 110 | //! 111 | void initComponents(); 112 | 113 | private: 114 | 115 | // ---------------------------------------------------------------------- 116 | // Variables 117 | // ---------------------------------------------------------------------- 118 | 119 | //! The component under test 120 | //! 121 | MathReceiver component; 122 | 123 | 124 | 125 | }; 126 | 127 | } // end namespace MathModule 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /Components/MathSender/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #### 2 | # F prime CMakeLists.txt: 3 | # 4 | # SOURCE_FILES: combined list of source and autocoding files 5 | # MOD_DEPS: (optional) module dependencies 6 | # UT_SOURCE_FILES: list of source files for unit tests 7 | # 8 | #### 9 | set(SOURCE_FILES 10 | "${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp" 11 | "${CMAKE_CURRENT_LIST_DIR}/MathSender.cpp" 12 | ) 13 | 14 | # Uncomment and add any modules that this component depends on, else 15 | # they might not be available when cmake tries to build this component. 16 | 17 | # set(MOD_DEPS 18 | # Add your dependencies here 19 | # ) 20 | 21 | register_fprime_module() 22 | 23 | 24 | # Unit testing 25 | 26 | set(UT_SOURCE_FILES 27 | "${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp" 28 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/MathSenderTester.cpp" 29 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/MathSenderTestMain.cpp" 30 | ) 31 | set(UT_AUTO_HELPERS ON) 32 | set(UT_MOD_DEPS STest) 33 | register_fprime_ut() -------------------------------------------------------------------------------- /Components/MathSender/MathSender.cpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title MathSender.cpp 3 | // \author asloan 4 | // \brief cpp file for MathSender component implementation class 5 | // ====================================================================== 6 | 7 | 8 | #include 9 | #include 10 | 11 | namespace MathModule { 12 | 13 | // ---------------------------------------------------------------------- 14 | // Construction, initialization, and destruction 15 | // ---------------------------------------------------------------------- 16 | 17 | MathSender :: 18 | MathSender( 19 | const char *const compName 20 | ) : MathSenderComponentBase(compName) 21 | { 22 | 23 | } 24 | 25 | MathSender :: 26 | ~MathSender() 27 | { 28 | 29 | } 30 | 31 | // ---------------------------------------------------------------------- 32 | // Handler implementations for user-defined typed input ports 33 | // ---------------------------------------------------------------------- 34 | 35 | void MathSender :: 36 | mathResultIn_handler( 37 | const FwIndexType portNum, 38 | F32 result 39 | ) 40 | { 41 | this->tlmWrite_RESULT(result); 42 | this->log_ACTIVITY_HI_RESULT(result); 43 | } 44 | 45 | // ---------------------------------------------------------------------- 46 | // Command handler implementations 47 | // ---------------------------------------------------------------------- 48 | 49 | void MathSender :: 50 | DO_MATH_cmdHandler( 51 | const FwOpcodeType opCode, 52 | const U32 cmdSeq, 53 | F32 val1, 54 | MathModule::MathOp op, 55 | F32 val2 56 | ) 57 | { 58 | this->tlmWrite_VAL1(val1); 59 | this->tlmWrite_OP(op); 60 | this->tlmWrite_VAL2(val2); 61 | this->log_ACTIVITY_LO_COMMAND_RECV(val1, op, val2); 62 | this->mathOpOut_out(0, val1, op, val2); 63 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); 64 | } 65 | 66 | } // end namespace MathModule 67 | -------------------------------------------------------------------------------- /Components/MathSender/MathSender.fpp: -------------------------------------------------------------------------------- 1 | module MathModule { 2 | 3 | @ Component for sending a math operation 4 | active component MathSender { 5 | 6 | # ---------------------------------------------------------------------- 7 | # General ports 8 | # ---------------------------------------------------------------------- 9 | 10 | @ Port for sending the operation request 11 | output port mathOpOut: OpRequest 12 | 13 | @ Port for receiving the result 14 | async input port mathResultIn: MathResult 15 | 16 | # ---------------------------------------------------------------------- 17 | # Special ports 18 | # ---------------------------------------------------------------------- 19 | 20 | @ Command receive port 21 | command recv port cmdIn 22 | 23 | @ Command registration port 24 | command reg port cmdRegOut 25 | 26 | @ Command response port 27 | command resp port cmdResponseOut 28 | 29 | @ Event port 30 | event port eventOut 31 | 32 | @ Telemetry port 33 | telemetry port tlmOut 34 | 35 | @ Text event port 36 | text event port textEventOut 37 | 38 | @ Time get port 39 | time get port timeGetOut 40 | 41 | # ---------------------------------------------------------------------- 42 | # Commands 43 | # ---------------------------------------------------------------------- 44 | 45 | @ Do a math operation 46 | async command DO_MATH( 47 | val1: F32 @< The first operand 48 | op: MathOp @< The operation 49 | val2: F32 @< The second operand 50 | ) 51 | 52 | # ---------------------------------------------------------------------- 53 | # Events 54 | # ---------------------------------------------------------------------- 55 | 56 | @ Math command received 57 | event COMMAND_RECV( 58 | val1: F32 @< The first operand 59 | op: MathOp @< The operation 60 | val2: F32 @< The second operand 61 | ) \ 62 | severity activity low \ 63 | format "Math command received: {f} {} {f}" 64 | 65 | @ Received math result 66 | event RESULT( 67 | result: F32 @< The math result 68 | ) \ 69 | severity activity high \ 70 | format "Math result is {f}" 71 | 72 | # ---------------------------------------------------------------------- 73 | # Telemetry 74 | # ---------------------------------------------------------------------- 75 | 76 | @ The first value 77 | telemetry VAL1: F32 78 | 79 | @ The operation 80 | telemetry OP: MathOp 81 | 82 | @ The second value 83 | telemetry VAL2: F32 84 | 85 | @ The result 86 | telemetry RESULT: F32 87 | 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /Components/MathSender/MathSender.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title MathSender.hpp 3 | // \author asloan 4 | // \brief hpp file for MathSender component implementation class 5 | // ====================================================================== 6 | 7 | #ifndef MathSender_HPP 8 | #define MathSender_HPP 9 | 10 | #include "Components/MathSender/MathSenderComponentAc.hpp" 11 | 12 | namespace MathModule { 13 | 14 | class MathSender : 15 | public MathSenderComponentBase 16 | { 17 | 18 | public: 19 | 20 | // ---------------------------------------------------------------------- 21 | // Construction, initialization, and destruction 22 | // ---------------------------------------------------------------------- 23 | 24 | //! Construct object MathSender 25 | //! 26 | MathSender( 27 | const char *const compName /*!< The component name*/ 28 | ); 29 | 30 | //! Destroy object MathSender 31 | //! 32 | ~MathSender(); 33 | 34 | PRIVATE: 35 | 36 | // ---------------------------------------------------------------------- 37 | // Handler implementations for user-defined typed input ports 38 | // ---------------------------------------------------------------------- 39 | 40 | //! Handler implementation for mathResultIn 41 | //! 42 | void mathResultIn_handler( 43 | const FwIndexType portNum, /*!< The port number*/ 44 | F32 result /*!< 45 | the result of the operation 46 | */ 47 | ); 48 | 49 | PRIVATE: 50 | 51 | // ---------------------------------------------------------------------- 52 | // Command handler implementations 53 | // ---------------------------------------------------------------------- 54 | 55 | //! Implementation for DO_MATH command handler 56 | //! Do a math operation 57 | void DO_MATH_cmdHandler( 58 | const FwOpcodeType opCode, /*!< The opcode*/ 59 | const U32 cmdSeq, /*!< The command sequence number*/ 60 | F32 val1, /*!< 61 | The first operand 62 | */ 63 | MathModule::MathOp op, /*!< 64 | The operation 65 | */ 66 | F32 val2 /*!< 67 | The second operand 68 | */ 69 | ); 70 | 71 | 72 | }; 73 | 74 | } // end namespace MathModule 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /Components/MathSender/docs/sdd.md: -------------------------------------------------------------------------------- 1 | # MathModule::MathSender 2 | 3 | Example Component for F Prime FSW framework. 4 | 5 | ## Usage Examples 6 | Add usage examples here 7 | 8 | ### Diagrams 9 | Add diagrams here 10 | 11 | ### Typical Usage 12 | And the typical usage of the component here 13 | 14 | ## Class Diagram 15 | Add a class diagram here 16 | 17 | ## Port Descriptions 18 | | Name | Description | 19 | |---|---| 20 | |---|---| 21 | 22 | ## Component States 23 | Add component states in the chart below 24 | | Name | Description | 25 | |---|---| 26 | |---|---| 27 | 28 | ## Sequence Diagrams 29 | Add sequence diagrams here 30 | 31 | ## Parameters 32 | | Name | Description | 33 | |---|---| 34 | |---|---| 35 | 36 | ## Commands 37 | | Name | Description | 38 | |---|---| 39 | |---|---| 40 | 41 | ## Events 42 | | Name | Description | 43 | |---|---| 44 | |---|---| 45 | 46 | ## Telemetry 47 | | Name | Description | 48 | |---|---| 49 | |---|---| 50 | 51 | ## Unit Tests 52 | Add unit test descriptions in the chart below 53 | | Name | Description | Output | Coverage | 54 | |---|---|---|---| 55 | |---|---|---|---| 56 | 57 | ## Requirements 58 | Add requirements in the chart below 59 | | Name | Description | Validation | 60 | |---|---|---| 61 | |---|---|---| 62 | 63 | ## Change Log 64 | | Date | Description | 65 | |---|---| 66 | |---| Initial Draft | -------------------------------------------------------------------------------- /Components/MathSender/test/ut/MathSenderTestMain.cpp: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------- 2 | // TestMain.cpp 3 | // ---------------------------------------------------------------------- 4 | 5 | #include "MathSenderTester.hpp" 6 | #include "STest/Random/Random.hpp" 7 | 8 | /* 9 | TEST(Nominal, ToDo) { 10 | MathModule::MathSenderTester tester; 11 | tester.toDo(); 12 | } 13 | */ 14 | 15 | TEST(Nominal, AddCommand) { 16 | MathModule::MathSenderTester tester; 17 | tester.testAddCommand(); 18 | } 19 | 20 | TEST(Nominal, Result) { 21 | MathModule::MathSenderTester tester; ///@TODO 22 | tester.testResult(); 23 | } 24 | 25 | int main(int argc, char **argv) { 26 | ::testing::InitGoogleTest(&argc, argv); 27 | STest::Random::seed(); 28 | return RUN_ALL_TESTS(); 29 | } 30 | -------------------------------------------------------------------------------- /Components/MathSender/test/ut/MathSenderTester.cpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title MathSender.hpp 3 | // \author asloan 4 | // \brief cpp file for MathSender test harness implementation class 5 | // ====================================================================== 6 | 7 | #include "MathSenderTester.hpp" 8 | #include "STest/Pick/Pick.hpp" 9 | 10 | namespace MathModule { 11 | 12 | // ---------------------------------------------------------------------- 13 | // Construction and destruction 14 | // ---------------------------------------------------------------------- 15 | 16 | MathSenderTester :: 17 | MathSenderTester() : 18 | MathSenderGTestBase("Tester", MathSenderTester::MAX_HISTORY_SIZE), 19 | component("MathSender") 20 | { 21 | this->initComponents(); 22 | this->connectPorts(); 23 | } 24 | 25 | MathSenderTester :: 26 | ~MathSenderTester() 27 | { 28 | 29 | } 30 | 31 | // ---------------------------------------------------------------------- 32 | // Tests 33 | // ---------------------------------------------------------------------- 34 | 35 | void MathSenderTester :: 36 | toDo() 37 | { 38 | // TODO 39 | } 40 | 41 | void MathSenderTester :: 42 | testDoMath(MathOp op) 43 | { 44 | // Pick values 45 | const F32 val1 = 2.0; 46 | const F32 val2 = 3.0; 47 | // Send the command 48 | // pick a command sequence number 49 | const U32 cmdSeq = 10; 50 | // send DO_MATH command 51 | this->sendCmd_DO_MATH(0, cmdSeq, val1, op, val2); 52 | // retrieve the message from the message queue and dispatch the command to the handler 53 | this->component.doDispatch(); 54 | // Verify command receipt and response 55 | // verify command response was sent 56 | ASSERT_CMD_RESPONSE_SIZE(1); 57 | // verify the command response was correct as expected 58 | ASSERT_CMD_RESPONSE(0, MathSenderComponentBase::OPCODE_DO_MATH, cmdSeq, Fw::CmdResponse::OK); 59 | // Verify operation request on mathOpOut 60 | // verify that one output port was invoked overall 61 | ASSERT_FROM_PORT_HISTORY_SIZE(1); 62 | // verify that the math operation port was invoked once 63 | ASSERT_from_mathOpOut_SIZE(1); 64 | // verify the arguments of the operation port 65 | ASSERT_from_mathOpOut(0, val1, op, val2); 66 | // Verify telemetry 67 | // verify that 3 channels were written 68 | ASSERT_TLM_SIZE(3); 69 | // verify that the desired telemetry values were sent once 70 | ASSERT_TLM_VAL1_SIZE(1); 71 | ASSERT_TLM_VAL2_SIZE(1); 72 | ASSERT_TLM_OP_SIZE(1); 73 | // verify that the correct telemetry values were sent 74 | ASSERT_TLM_VAL1(0, val1); 75 | ASSERT_TLM_VAL2(0, val2); 76 | ASSERT_TLM_OP(0, op); 77 | // Verify event reports 78 | // verify that one event was sent 79 | ASSERT_EVENTS_SIZE(1); 80 | // verify the expected event was sent once 81 | ASSERT_EVENTS_COMMAND_RECV_SIZE(1); 82 | // verify the correct event arguments were sent 83 | ASSERT_EVENTS_COMMAND_RECV(0, val1, op, val2); 84 | } 85 | 86 | void MathSenderTester :: 87 | testAddCommand() 88 | { 89 | this->testDoMath(MathOp::ADD); 90 | } 91 | 92 | void MathSenderTester :: 93 | testResult() 94 | { 95 | // Generate an expected result 96 | const F32 result = 10.0; 97 | // reset all telemetry and port history 98 | this->clearHistory(); 99 | // call result port with result 100 | this->invoke_to_mathResultIn(0, result); 101 | // retrieve the message from the message queue and dispatch the command to the handler 102 | this->component.doDispatch(); 103 | // verify one telemetry value was written 104 | ASSERT_TLM_SIZE(1); 105 | // verify the desired telemetry channel was sent once 106 | ASSERT_TLM_RESULT_SIZE(1); 107 | // verify the values of the telemetry channel 108 | ASSERT_TLM_RESULT(0, result); 109 | // verify one event was sent 110 | ASSERT_EVENTS_SIZE(1); 111 | // verify the expected event was sent once 112 | ASSERT_EVENTS_RESULT_SIZE(1); 113 | // verify the expect value of the event 114 | ASSERT_EVENTS_RESULT(0, result); 115 | } 116 | 117 | // ---------------------------------------------------------------------- 118 | // Handlers for typed from ports 119 | // ---------------------------------------------------------------------- 120 | 121 | void MathSenderTester :: 122 | from_mathOpOut_handler( 123 | const FwIndexType portNum, 124 | F32 val1, 125 | const MathModule::MathOp &op, 126 | F32 val2 127 | ) 128 | { 129 | this->pushFromPortEntry_mathOpOut(val1, op, val2); 130 | } 131 | 132 | 133 | } // end namespace MathModule 134 | -------------------------------------------------------------------------------- /Components/MathSender/test/ut/MathSenderTester.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title MathSender/test/ut/Tester.hpp 3 | // \author asloan 4 | // \brief hpp file for MathSender test harness implementation class 5 | // ====================================================================== 6 | 7 | #ifndef TESTER_HPP 8 | #define TESTER_HPP 9 | 10 | #include "MathSenderGTestBase.hpp" 11 | #include "Components/MathSender/MathSender.hpp" 12 | 13 | namespace MathModule { 14 | 15 | class MathSenderTester : 16 | public MathSenderGTestBase 17 | { 18 | 19 | // ---------------------------------------------------------------------- 20 | // Construction and destruction 21 | // ---------------------------------------------------------------------- 22 | 23 | public: 24 | // Maximum size of histories storing events, telemetry, and port outputs 25 | static const U32 MAX_HISTORY_SIZE = 10; 26 | // Instance ID supplied to the component instance under test 27 | static const FwEnumStoreType TEST_INSTANCE_ID = 0; 28 | // Queue depth supplied to component instance under test 29 | static const FwSizeType TEST_INSTANCE_QUEUE_DEPTH = 10; 30 | 31 | //! Construct object MathSenderTester 32 | //! 33 | MathSenderTester(); 34 | 35 | //! Destroy object MathSenderTester 36 | //! 37 | ~MathSenderTester(); 38 | 39 | public: 40 | 41 | // ---------------------------------------------------------------------- 42 | // Tests 43 | // ---------------------------------------------------------------------- 44 | 45 | //! To do 46 | //! 47 | void toDo(); 48 | 49 | void testDoMath(MathOp op); 50 | 51 | void testAddCommand(); 52 | 53 | void testResult(); 54 | 55 | private: 56 | 57 | // ---------------------------------------------------------------------- 58 | // Handlers for typed from ports 59 | // ---------------------------------------------------------------------- 60 | 61 | //! Handler for from_mathOpOut 62 | //! 63 | void from_mathOpOut_handler( 64 | const FwIndexType portNum, /*!< The port number*/ 65 | F32 val1, /*!< 66 | The first operand 67 | */ 68 | const MathModule::MathOp &op, /*!< 69 | The operation 70 | */ 71 | F32 val2 /*!< 72 | The second operand 73 | */ 74 | ); 75 | 76 | private: 77 | 78 | // ---------------------------------------------------------------------- 79 | // Helper methods 80 | // ---------------------------------------------------------------------- 81 | 82 | //! Connect ports 83 | //! 84 | void connectPorts(); 85 | 86 | //! Initialize components 87 | //! 88 | void initComponents(); 89 | 90 | private: 91 | 92 | // ---------------------------------------------------------------------- 93 | // Variables 94 | // ---------------------------------------------------------------------- 95 | 96 | //! The component under test 97 | //! 98 | MathSender component; 99 | 100 | }; 101 | 102 | } // end namespace MathModule 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MathDeployment/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ##### 2 | # 'MathDeployment' Deployment: 3 | # 4 | # This registers the 'MathDeployment' deployment to the build system. 5 | # Custom components that have not been added at the project-level should be added to 6 | # the list below. 7 | # 8 | ##### 9 | 10 | ### 11 | # Topology and Components 12 | ### 13 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Top/") 14 | 15 | # Add custom components to this specific deployment here 16 | # add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/MyComponent/") 17 | 18 | 19 | set(SOURCE_FILES "${CMAKE_CURRENT_LIST_DIR}/Main.cpp") 20 | set(MOD_DEPS ${FPRIME_CURRENT_MODULE}/Top) 21 | 22 | register_fprime_deployment() 23 | 24 | -------------------------------------------------------------------------------- /MathDeployment/Main.cpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title Main.cpp 3 | // \brief main program for the F' application. Intended for CLI-based systems (Linux, macOS) 4 | // 5 | // ====================================================================== 6 | // Used to access topology functions 7 | #include 8 | // Used for signal handling shutdown 9 | #include 10 | // Used for command line argument processing 11 | #include 12 | // Used for printf functions 13 | #include 14 | // Os Console 15 | #include 16 | 17 | /** 18 | * \brief print command line help message 19 | * 20 | * This will print a command line help message including the available command line arguments. 21 | * 22 | * @param app: name of application 23 | */ 24 | void print_usage(const char* app) { 25 | (void)printf("Usage: ./%s [options]\n-a\thostname/IP address\n-p\tport_number\n", app); 26 | } 27 | 28 | /** 29 | * \brief shutdown topology cycling on signal 30 | * 31 | * The reference topology allows for a simulated cycling of the rate groups. This simulated cycling needs to be stopped 32 | * in order for the program to shutdown. This is done via handling signals such that it is performed via Ctrl-C 33 | * 34 | * @param signum 35 | */ 36 | static void signalHandler(int signum) { 37 | MathDeployment::stopSimulatedCycle(); 38 | } 39 | 40 | /** 41 | * \brief execute the program 42 | * 43 | * This F´ program is designed to run in standard environments (e.g. Linux/macOs running on a laptop). Thus it uses 44 | * command line inputs to specify how to connect. 45 | * 46 | * @param argc: argument count supplied to program 47 | * @param argv: argument values supplied to program 48 | * @return: 0 on success, something else on failure 49 | */ 50 | int main(int argc, char* argv[]) { 51 | U32 port_number = 0; 52 | I32 option = 0; 53 | char* hostname = nullptr; 54 | Os::Console::init(); 55 | // Loop while reading the getopt supplied options 56 | while ((option = getopt(argc, argv, "hp:a:")) != -1) { 57 | switch (option) { 58 | // Handle the -a argument for address/hostname 59 | case 'a': 60 | hostname = optarg; 61 | break; 62 | // Handle the -p port number argument 63 | case 'p': 64 | port_number = static_cast(atoi(optarg)); 65 | break; 66 | // Cascade intended: help output 67 | case 'h': 68 | // Cascade intended: help output 69 | case '?': 70 | // Default case: output help and exit 71 | default: 72 | print_usage(argv[0]); 73 | return (option == 'h') ? 0 : 1; 74 | } 75 | } 76 | // Object for communicating state to the reference topology 77 | MathDeployment::TopologyState inputs; 78 | inputs.hostname = hostname; 79 | inputs.port = port_number; 80 | 81 | // Setup program shutdown via Ctrl-C 82 | signal(SIGINT, signalHandler); 83 | signal(SIGTERM, signalHandler); 84 | (void)printf("Hit Ctrl-C to quit\n"); 85 | 86 | // Setup, cycle, and teardown topology 87 | MathDeployment::setupTopology(inputs); 88 | MathDeployment::startSimulatedCycle(Fw::TimeInterval(1, 0)); // Program loop cycling rate groups at 1Hz 89 | MathDeployment::teardownTopology(inputs); 90 | (void)printf("Exiting...\n"); 91 | return 0; 92 | } 93 | -------------------------------------------------------------------------------- /MathDeployment/README.md: -------------------------------------------------------------------------------- 1 | # MathDeployment Application 2 | 3 | This deployment was auto-generated by the F' utility tool. 4 | 5 | ## Building and Running the MathDeployment Application 6 | 7 | In order to build the MathDeployment application, or any other F´ application, we first need to generate a build directory. This can be done with the following commands: 8 | 9 | ``` 10 | cd MathDeployment 11 | fprime-util generate 12 | ``` 13 | 14 | The next step is to build the MathDeployment application's code. 15 | ``` 16 | fprime-util build 17 | ``` 18 | 19 | ## Running the application and F' GDS 20 | 21 | The following command will spin up the F' GDS as well as run the application binary and the components necessary for the GDS and application to communicate. 22 | 23 | ``` 24 | cd MathDeployment 25 | fprime-gds 26 | ``` 27 | 28 | To run the ground system without starting the MathDeployment app: 29 | ``` 30 | cd MathDeployment 31 | fprime-gds --no-app 32 | ``` 33 | 34 | The application binary may then be run independently from the created 'bin' directory. 35 | 36 | ``` 37 | cd MathDeployment/build-artifacts//bin/ 38 | ./MathDeployment -a 127.0.0.1 -p 50000 39 | ``` 40 | -------------------------------------------------------------------------------- /MathDeployment/Top/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #### 2 | # F prime CMakeLists.txt: 3 | # 4 | # SOURCE_FILES: combined list of source and autocoding files 5 | # MOD_DEPS: (optional) module dependencies 6 | #### 7 | 8 | set(SOURCE_FILES 9 | "${CMAKE_CURRENT_LIST_DIR}/instances.fpp" 10 | "${CMAKE_CURRENT_LIST_DIR}/topology.fpp" 11 | "${CMAKE_CURRENT_LIST_DIR}/MathDeploymentTopology.cpp" 12 | ) 13 | set(MOD_DEPS 14 | Fw/Logger 15 | Svc/PosixTime 16 | # Communication Implementations 17 | Drv/Udp 18 | Drv/TcpClient 19 | ) 20 | 21 | register_fprime_module() 22 | -------------------------------------------------------------------------------- /MathDeployment/Top/MathDeploymentPackets.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | MathDeployment/Top/MathDeploymentTopologyAppAi.xml 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /MathDeployment/Top/MathDeploymentTopology.cpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title MathDeploymentTopology.cpp 3 | // \brief cpp file containing the topology instantiation code 4 | // 5 | // ====================================================================== 6 | // Provides access to autocoded functions 7 | #include 8 | 9 | // Necessary project-specified types 10 | #include 11 | #include 12 | 13 | // Used for 1Hz synthetic cycling 14 | #include 15 | 16 | // Allows easy reference to objects in FPP/autocoder required namespaces 17 | using namespace MathDeployment; 18 | 19 | // The reference topology uses a malloc-based allocator for components that need to allocate memory during the 20 | // initialization phase. 21 | Fw::MallocAllocator mallocator; 22 | 23 | // FprimeFrameDetector is used to configure the FrameAccumulator to detect F Prime frames 24 | Svc::FrameDetectors::FprimeFrameDetector frameDetector; 25 | 26 | Svc::ComQueue::QueueConfigurationTable configurationTable; 27 | 28 | // The reference topology divides the incoming clock signal (1Hz) into sub-signals: 1Hz, 1/2Hz, and 1/4Hz 29 | Svc::RateGroupDriver::DividerSet rateGroupDivisors = {{{1, 0}, {2, 0}, {4, 0}}}; 30 | 31 | // Rate groups may supply a context token to each of the attached children whose purpose is set by the project. The 32 | // reference topology sets each token to zero as these contexts are unused in this project. 33 | U32 rateGroup1Context[Svc::ActiveRateGroup::CONNECTION_COUNT_MAX] = {}; 34 | U32 rateGroup2Context[Svc::ActiveRateGroup::CONNECTION_COUNT_MAX] = {}; 35 | U32 rateGroup3Context[Svc::ActiveRateGroup::CONNECTION_COUNT_MAX] = {}; 36 | 37 | // A number of constants are needed for construction of the topology. These are specified here. 38 | enum TopologyConstants { 39 | CMD_SEQ_BUFFER_SIZE = 5 * 1024, 40 | FILE_DOWNLINK_TIMEOUT = 1000, 41 | FILE_DOWNLINK_COOLDOWN = 1000, 42 | FILE_DOWNLINK_CYCLE_TIME = 1000, 43 | FILE_DOWNLINK_FILE_QUEUE_DEPTH = 10, 44 | HEALTH_WATCHDOG_CODE = 0x123, 45 | COMM_PRIORITY = 100, 46 | // bufferManager constants 47 | FRAMER_BUFFER_SIZE = FW_MAX(FW_COM_BUFFER_MAX_SIZE, FW_FILE_BUFFER_MAX_SIZE + sizeof(U32)) + HASH_DIGEST_LENGTH + Svc::FpFrameHeader::SIZE, 48 | FRAMER_BUFFER_COUNT = 30, 49 | DEFRAMER_BUFFER_SIZE = FW_MAX(FW_COM_BUFFER_MAX_SIZE, FW_FILE_BUFFER_MAX_SIZE + sizeof(U32)), 50 | DEFRAMER_BUFFER_COUNT = 30, 51 | COM_DRIVER_BUFFER_SIZE = 3000, 52 | COM_DRIVER_BUFFER_COUNT = 30, 53 | BUFFER_MANAGER_ID = 200 54 | }; 55 | 56 | // Ping entries are autocoded, however; this code is not properly exported. Thus, it is copied here. 57 | Svc::Health::PingEntry pingEntries[] = { 58 | {PingEntries::MathDeployment_tlmSend::WARN, PingEntries::MathDeployment_tlmSend::FATAL, "chanTlm"}, 59 | {PingEntries::MathDeployment_cmdDisp::WARN, PingEntries::MathDeployment_cmdDisp::FATAL, "cmdDisp"}, 60 | {PingEntries::MathDeployment_cmdSeq::WARN, PingEntries::MathDeployment_cmdSeq::FATAL, "cmdSeq"}, 61 | {PingEntries::MathDeployment_eventLogger::WARN, PingEntries::MathDeployment_eventLogger::FATAL, "eventLogger"}, 62 | {PingEntries::MathDeployment_fileDownlink::WARN, PingEntries::MathDeployment_fileDownlink::FATAL, "fileDownlink"}, 63 | {PingEntries::MathDeployment_fileManager::WARN, PingEntries::MathDeployment_fileManager::FATAL, "fileManager"}, 64 | {PingEntries::MathDeployment_fileUplink::WARN, PingEntries::MathDeployment_fileUplink::FATAL, "fileUplink"}, 65 | {PingEntries::MathDeployment_prmDb::WARN, PingEntries::MathDeployment_prmDb::FATAL, "prmDb"}, 66 | {PingEntries::MathDeployment_rateGroup1::WARN, PingEntries::MathDeployment_rateGroup1::FATAL, "rateGroup1"}, 67 | {PingEntries::MathDeployment_rateGroup2::WARN, PingEntries::MathDeployment_rateGroup2::FATAL, "rateGroup2"}, 68 | {PingEntries::MathDeployment_rateGroup3::WARN, PingEntries::MathDeployment_rateGroup3::FATAL, "rateGroup3"}, 69 | }; 70 | 71 | /** 72 | * \brief configure/setup components in project-specific way 73 | * 74 | * This is a *helper* function which configures/sets up each component requiring project specific input. This includes 75 | * allocating resources, passing-in arguments, etc. This function may be inlined into the topology setup function if 76 | * desired, but is extracted here for clarity. 77 | */ 78 | void configureTopology() { 79 | // Command sequencer needs to allocate memory to hold contents of command sequences 80 | cmdSeq.allocateBuffer(0, mallocator, CMD_SEQ_BUFFER_SIZE); 81 | 82 | // Rate group driver needs a divisor list 83 | rateGroupDriver.configure(rateGroupDivisors); 84 | 85 | // Rate groups require context arrays. 86 | rateGroup1.configure(rateGroup1Context, FW_NUM_ARRAY_ELEMENTS(rateGroup1Context)); 87 | rateGroup2.configure(rateGroup2Context, FW_NUM_ARRAY_ELEMENTS(rateGroup2Context)); 88 | rateGroup3.configure(rateGroup3Context, FW_NUM_ARRAY_ELEMENTS(rateGroup3Context)); 89 | 90 | // File downlink requires some project-derived properties. 91 | fileDownlink.configure(FILE_DOWNLINK_TIMEOUT, FILE_DOWNLINK_COOLDOWN, FILE_DOWNLINK_CYCLE_TIME, 92 | FILE_DOWNLINK_FILE_QUEUE_DEPTH); 93 | 94 | // Parameter database is configured with a database file name, and that file must be initially read. 95 | prmDb.configure("PrmDb.dat"); 96 | prmDb.readParamFile(); 97 | 98 | // Health is supplied a set of ping entires. 99 | health.setPingEntries(pingEntries, FW_NUM_ARRAY_ELEMENTS(pingEntries), HEALTH_WATCHDOG_CODE); 100 | 101 | // Buffer managers need a configured set of buckets and an allocator used to allocate memory for those buckets. 102 | Svc::BufferManager::BufferBins upBuffMgrBins; 103 | memset(&upBuffMgrBins, 0, sizeof(upBuffMgrBins)); 104 | upBuffMgrBins.bins[0].bufferSize = FRAMER_BUFFER_SIZE; 105 | upBuffMgrBins.bins[0].numBuffers = FRAMER_BUFFER_COUNT; 106 | upBuffMgrBins.bins[1].bufferSize = DEFRAMER_BUFFER_SIZE; 107 | upBuffMgrBins.bins[1].numBuffers = DEFRAMER_BUFFER_COUNT; 108 | upBuffMgrBins.bins[2].bufferSize = COM_DRIVER_BUFFER_SIZE; 109 | upBuffMgrBins.bins[2].numBuffers = COM_DRIVER_BUFFER_COUNT; 110 | bufferManager.setup(BUFFER_MANAGER_ID, 0, mallocator, upBuffMgrBins); 111 | 112 | // FprimeFrameDetector is used to configure the FrameAccumulator to detect F Prime frames 113 | frameAccumulator.configure(frameDetector, 1, mallocator, 2048); 114 | 115 | // Note: Uncomment when using Svc:TlmPacketizer 116 | //tlmSend.setPacketList(MathDeploymentPacketsPkts, MathDeploymentPacketsIgnore, 1); 117 | 118 | // Events (highest-priority) 119 | configurationTable.entries[0] = {.depth = 100, .priority = 0}; 120 | // Telemetry 121 | configurationTable.entries[1] = {.depth = 500, .priority = 2}; 122 | // File Downlink 123 | configurationTable.entries[2] = {.depth = 100, .priority = 1}; 124 | // Allocation identifier is 0 as the MallocAllocator discards it 125 | comQueue.configure(configurationTable, 0, mallocator); 126 | } 127 | 128 | // Public functions for use in main program are namespaced with deployment name MathDeployment 129 | namespace MathDeployment { 130 | void setupTopology(const TopologyState& state) { 131 | // Autocoded initialization. Function provided by autocoder. 132 | initComponents(state); 133 | // Autocoded id setup. Function provided by autocoder. 134 | setBaseIds(); 135 | // Autocoded connection wiring. Function provided by autocoder. 136 | connectComponents(); 137 | // Autocoded command registration. Function provided by autocoder. 138 | regCommands(); 139 | // Project-specific component configuration. Function provided above. May be inlined, if desired. 140 | configureTopology(); 141 | if (state.hostname != nullptr && state.port != 0) { 142 | comDriver.configure(state.hostname, state.port); 143 | } 144 | // Autocoded parameter loading. Function provided by autocoder. 145 | loadParameters(); 146 | // Autocoded task kick-off (active components). Function provided by autocoder. 147 | startTasks(state); 148 | // Initialize socket client communication if and only if there is a valid specification 149 | if (state.hostname != nullptr && state.port != 0) { 150 | Os::TaskString name("ReceiveTask"); 151 | // Uplink is configured for receive so a socket task is started 152 | comDriver.start(name, COMM_PRIORITY, Default::STACK_SIZE); 153 | } 154 | } 155 | 156 | // Variables used for cycle simulation 157 | Os::Mutex cycleLock; 158 | volatile bool cycleFlag = true; 159 | 160 | void startSimulatedCycle(Fw::TimeInterval interval) { 161 | linuxTimer.startTimer(interval.getSeconds()*1000+interval.getUSeconds()/1000); 162 | } 163 | 164 | void stopSimulatedCycle() { 165 | linuxTimer.quit(); 166 | } 167 | 168 | void teardownTopology(const TopologyState& state) { 169 | // Autocoded (active component) task clean-up. Functions provided by topology autocoder. 170 | stopTasks(state); 171 | freeThreads(state); 172 | 173 | // Other task clean-up. 174 | comDriver.stop(); 175 | (void)comDriver.join(); 176 | 177 | // Resource deallocation 178 | cmdSeq.deallocateBuffer(mallocator); 179 | bufferManager.cleanup(); 180 | } 181 | }; // namespace MathDeployment 182 | -------------------------------------------------------------------------------- /MathDeployment/Top/MathDeploymentTopology.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title MathDeploymentTopology.hpp 3 | // \brief header file containing the topology instantiation definitions 4 | // 5 | // ====================================================================== 6 | #ifndef MATHDEPLOYMENT_MATHDEPLOYMENTTOPOLOGY_HPP 7 | #define MATHDEPLOYMENT_MATHDEPLOYMENTTOPOLOGY_HPP 8 | // Included for access to MathDeployment::TopologyState and MathDeployment::ConfigObjects::pingEntries. These definitions are required by the 9 | // autocoder, but are also used in this hand-coded topology. 10 | #include 11 | 12 | // Remove unnecessary MathDeployment:: qualifications 13 | using namespace MathDeployment; 14 | namespace MathDeployment { 15 | /** 16 | * \brief initialize and run the F´ topology 17 | * 18 | * Initializes, configures, and runs the F´ topology. This is performed through a series of steps, some provided via 19 | * autocoded functions, and others provided via the functions implementation. These steps are: 20 | * 21 | * 1. Call the autocoded `initComponents()` function initializing each component via the `component.init` method 22 | * 2. Call the autocoded `setBaseIds()` function to set the base IDs (offset) for each component instance 23 | * 3. Call the autocoded `connectComponents()` function to wire-together the topology of components 24 | * 4. Configure components requiring custom configuration 25 | * 5. Call the autocoded `loadParameters()` function to cause each component to load initial parameter values 26 | * 6. Call the autocoded `startTasks()` function to start the active component tasks 27 | * 7. Start tasks not owned by active components 28 | * 29 | * Step 4 and step 7 are custom and supplied by the project. The ordering of steps 1, 2, 3, 5, and 6 are critical for 30 | * F´ topologies to function. Configuration (step 4) typically assumes a connect but not started topology and is thus 31 | * inserted between step 3 and 5. Step 7 may come before or after the active component initializations. Since these 32 | * custom tasks often start radio communication it is convenient to start them last. 33 | * 34 | * The state argument carries command line inputs used to setup the topology. For an explanation of the required type 35 | * MathDeployment::TopologyState see: MathDeploymentTopologyDefs.hpp. 36 | * 37 | * \param state: object shuttling CLI arguments (hostname, port) needed to construct the topology 38 | */ 39 | void setupTopology(const TopologyState& state); 40 | 41 | /** 42 | * \brief teardown the F´ topology 43 | * 44 | * Tears down the F´ topology in preparation for shutdown. This is done via a series of steps, some provided by 45 | * autocoded functions, and others provided via the function implementation. These steps are: 46 | * 47 | * 1. Call the autocoded `stopTasks()` function to stop the tasks started by `startTasks()` (active components) 48 | * 2. Call the autocoded `freeThreads()` function to join to the tasks started by `startTasks()` 49 | * 3. Stop the tasks not owned by active components 50 | * 4. Join to the tasks not owned by active components 51 | * 5. Deallocate other resources 52 | * 53 | * Step 1, 2, 3, and 4 must occur in-order as the tasks must be stopped before being joined. These tasks must be stopped 54 | * and joined before any active resources may be deallocated. 55 | * 56 | * For an explanation of the required type MathDeployment::TopologyState see: MathDeploymentTopologyDefs.hpp. 57 | * 58 | * \param state: state object provided to setupTopology 59 | */ 60 | void teardownTopology(const TopologyState& state); 61 | 62 | /** 63 | * \brief cycle the rate group driver at a crude rate 64 | * 65 | * The reference topology does not have a true 1Hz input clock for the rate group driver because it is designed to 66 | * operate across various computing endpoints (e.g. laptops) where a clear 1Hz source may not be easily and generically 67 | * achieved. This function mimics the cycling via a Task::delay(milliseconds) loop that manually invokes the ISR call 68 | * to the example block driver. 69 | * 70 | * This loop is stopped via a startSimulatedCycle call. 71 | * 72 | * Note: projects should replace this with a component that produces an output port call at the appropriate frequency. 73 | * 74 | * \param milliseconds: milliseconds to delay for each cycle. Default: 1000 or 1Hz. 75 | */ 76 | void startSimulatedCycle(Fw::TimeInterval interval); 77 | 78 | /** 79 | * \brief stop the simulated cycle started by startSimulatedCycle 80 | * 81 | * This stops the cycle started by startSimulatedCycle. 82 | */ 83 | void stopSimulatedCycle(); 84 | 85 | } // namespace MathDeployment 86 | #endif 87 | -------------------------------------------------------------------------------- /MathDeployment/Top/MathDeploymentTopologyDefs.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title MathDeploymentTopologyDefs.hpp 3 | // \brief required header file containing the required definitions for the topology autocoder 4 | // 5 | // ====================================================================== 6 | #ifndef MATHDEPLOYMENT_MATHDEPLOYMENTTOPOLOGYDEFS_HPP 7 | #define MATHDEPLOYMENT_MATHDEPLOYMENTTOPOLOGYDEFS_HPP 8 | 9 | #include "Fw/Types/MallocAllocator.hpp" 10 | #include "MathDeployment/Top/FppConstantsAc.hpp" 11 | #include "Svc/FramingProtocol/FprimeProtocol.hpp" 12 | #include "Svc/Health/Health.hpp" 13 | 14 | // Definitions are placed within a namespace named after the deployment 15 | namespace MathDeployment { 16 | 17 | /** 18 | * \brief required type definition to carry state 19 | * 20 | * The topology autocoder requires an object that carries state with the name `MathDeployment::TopologyState`. Only the type 21 | * definition is required by the autocoder and the contents of this object are otherwise opaque to the autocoder. The 22 | * contents are entirely up to the definition of the project. This reference application specifies hostname and port 23 | * fields, which are derived by command line inputs. 24 | */ 25 | struct TopologyState { 26 | const char* hostname; 27 | U32 port; 28 | }; 29 | 30 | /** 31 | * \brief required ping constants 32 | * 33 | * The topology autocoder requires a WARN and FATAL constant definition for each component that supports the health-ping 34 | * interface. These are expressed as enum constants placed in a namespace named for the component instance. These 35 | * are all placed in the PingEntries namespace. 36 | * 37 | * Each constant specifies how many missed pings are allowed before a WARNING_HI/FATAL event is triggered. In the 38 | * following example, the health component will emit a WARNING_HI event if the component instance cmdDisp does not 39 | * respond for 3 pings and will FATAL if responses are not received after a total of 5 pings. 40 | * 41 | * ```c++ 42 | * namespace PingEntries { 43 | * namespace cmdDisp { 44 | * enum { WARN = 3, FATAL = 5 }; 45 | * } 46 | * } 47 | * ``` 48 | */ 49 | namespace PingEntries { 50 | namespace MathDeployment_blockDrv { 51 | enum { WARN = 3, FATAL = 5 }; 52 | } 53 | namespace MathDeployment_tlmSend { 54 | enum { WARN = 3, FATAL = 5 }; 55 | } 56 | namespace MathDeployment_cmdDisp { 57 | enum { WARN = 3, FATAL = 5 }; 58 | } 59 | namespace MathDeployment_cmdSeq { 60 | enum { WARN = 3, FATAL = 5 }; 61 | } 62 | namespace MathDeployment_eventLogger { 63 | enum { WARN = 3, FATAL = 5 }; 64 | } 65 | namespace MathDeployment_fileDownlink { 66 | enum { WARN = 3, FATAL = 5 }; 67 | } 68 | namespace MathDeployment_fileManager { 69 | enum { WARN = 3, FATAL = 5 }; 70 | } 71 | namespace MathDeployment_fileUplink { 72 | enum { WARN = 3, FATAL = 5 }; 73 | } 74 | namespace MathDeployment_prmDb { 75 | enum { WARN = 3, FATAL = 5 }; 76 | } 77 | namespace MathDeployment_rateGroup1 { 78 | enum { WARN = 3, FATAL = 5 }; 79 | } 80 | namespace MathDeployment_rateGroup2 { 81 | enum { WARN = 3, FATAL = 5 }; 82 | } 83 | namespace MathDeployment_rateGroup3 { 84 | enum { WARN = 3, FATAL = 5 }; 85 | } 86 | } // namespace PingEntries 87 | } // namespace MathDeployment 88 | #endif 89 | -------------------------------------------------------------------------------- /MathDeployment/Top/instances.fpp: -------------------------------------------------------------------------------- 1 | module MathDeployment { 2 | 3 | # ---------------------------------------------------------------------- 4 | # Defaults 5 | # ---------------------------------------------------------------------- 6 | 7 | module Default { 8 | constant QUEUE_SIZE = 10 9 | constant STACK_SIZE = 64 * 1024 10 | } 11 | 12 | # ---------------------------------------------------------------------- 13 | # Active component instances 14 | # ---------------------------------------------------------------------- 15 | 16 | instance rateGroup1: Svc.ActiveRateGroup base id 0x0200 \ 17 | queue size Default.QUEUE_SIZE \ 18 | stack size Default.STACK_SIZE \ 19 | priority 120 20 | 21 | instance rateGroup2: Svc.ActiveRateGroup base id 0x0300 \ 22 | queue size Default.QUEUE_SIZE \ 23 | stack size Default.STACK_SIZE \ 24 | priority 119 25 | 26 | instance rateGroup3: Svc.ActiveRateGroup base id 0x0400 \ 27 | queue size Default.QUEUE_SIZE \ 28 | stack size Default.STACK_SIZE \ 29 | priority 118 30 | 31 | instance cmdDisp: Svc.CommandDispatcher base id 0x0500 \ 32 | queue size 20 \ 33 | stack size Default.STACK_SIZE \ 34 | priority 101 35 | 36 | instance cmdSeq: Svc.CmdSequencer base id 0x0600 \ 37 | queue size Default.QUEUE_SIZE \ 38 | stack size Default.STACK_SIZE \ 39 | priority 100 40 | 41 | instance comQueue: Svc.ComQueue base id 0x0700 \ 42 | queue size Default.QUEUE_SIZE \ 43 | stack size Default.STACK_SIZE \ 44 | priority 100 \ 45 | 46 | instance fileDownlink: Svc.FileDownlink base id 0x0800 \ 47 | queue size 30 \ 48 | stack size Default.STACK_SIZE \ 49 | priority 100 50 | 51 | instance fileManager: Svc.FileManager base id 0x0900 \ 52 | queue size 30 \ 53 | stack size Default.STACK_SIZE \ 54 | priority 100 55 | 56 | instance fileUplink: Svc.FileUplink base id 0x0A00 \ 57 | queue size 30 \ 58 | stack size Default.STACK_SIZE \ 59 | priority 100 60 | 61 | 62 | instance mathSender: MathModule.MathSender base id 0xE00 \ 63 | queue size Default.QUEUE_SIZE \ 64 | stack size Default.STACK_SIZE \ 65 | priority 100 66 | 67 | 68 | instance eventLogger: Svc.ActiveLogger base id 0x0B00 \ 69 | queue size Default.QUEUE_SIZE \ 70 | stack size Default.STACK_SIZE \ 71 | priority 98 72 | 73 | # comment in Svc.TlmChan or Svc.TlmPacketizer 74 | # depending on which form of telemetry downlink 75 | # you wish to use 76 | 77 | instance tlmSend: Svc.TlmChan base id 0x0C00 \ 78 | queue size Default.QUEUE_SIZE \ 79 | stack size Default.STACK_SIZE \ 80 | priority 97 81 | 82 | #instance tlmSend: Svc.TlmPacketizer base id 0x0C00 \ 83 | # queue size Default.QUEUE_SIZE \ 84 | # stack size Default.STACK_SIZE \ 85 | # priority 97 86 | 87 | instance prmDb: Svc.PrmDb base id 0x0D00 \ 88 | queue size Default.QUEUE_SIZE \ 89 | stack size Default.STACK_SIZE \ 90 | priority 96 91 | 92 | # ---------------------------------------------------------------------- 93 | # Queued component instances 94 | # ---------------------------------------------------------------------- 95 | 96 | instance $health: Svc.Health base id 0x2000 \ 97 | queue size 25 98 | 99 | 100 | instance mathReceiver: MathModule.MathReceiver base id 0x2700 \ 101 | queue size Default.QUEUE_SIZE 102 | 103 | 104 | # ---------------------------------------------------------------------- 105 | # Passive component instances 106 | # ---------------------------------------------------------------------- 107 | 108 | @ Communications driver. May be swapped with other comm drivers like UART 109 | instance comDriver: Drv.TcpClient base id 0x4000 110 | 111 | instance framer: Svc.FprimeFramer base id 0x4100 112 | 113 | instance fatalAdapter: Svc.AssertFatalAdapter base id 0x4200 114 | 115 | instance fatalHandler: Svc.FatalHandler base id 0x4300 116 | 117 | instance bufferManager: Svc.BufferManager base id 0x4400 118 | 119 | instance posixTime: Svc.PosixTime base id 0x4500 120 | 121 | instance rateGroupDriver: Svc.RateGroupDriver base id 0x4600 122 | 123 | instance textLogger: Svc.PassiveTextLogger base id 0x4800 124 | 125 | instance deframer: Svc.FprimeDeframer base id 0x4900 126 | 127 | instance systemResources: Svc.SystemResources base id 0x4A00 128 | 129 | instance comStub: Svc.ComStub base id 0x4B00 130 | 131 | instance frameAccumulator: Svc.FrameAccumulator base id 0x4C00 132 | 133 | instance fprimeRouter: Svc.FprimeRouter base id 0x4D00 134 | 135 | instance linuxTimer: Svc.LinuxTimer base id 0x4E00 136 | } 137 | -------------------------------------------------------------------------------- /MathDeployment/Top/topology.fpp: -------------------------------------------------------------------------------- 1 | module MathDeployment { 2 | 3 | # ---------------------------------------------------------------------- 4 | # Symbolic constants for port numbers 5 | # ---------------------------------------------------------------------- 6 | 7 | enum Ports_RateGroups { 8 | rateGroup1 9 | rateGroup2 10 | rateGroup3 11 | } 12 | 13 | topology MathDeployment { 14 | 15 | # ---------------------------------------------------------------------- 16 | # Instances used in the topology 17 | # ---------------------------------------------------------------------- 18 | 19 | instance $health 20 | instance tlmSend 21 | instance cmdDisp 22 | instance cmdSeq 23 | instance comDriver 24 | instance comQueue 25 | instance comStub 26 | instance deframer 27 | instance fprimeRouter 28 | instance frameAccumulator 29 | instance eventLogger 30 | instance fatalAdapter 31 | instance fatalHandler 32 | instance fileDownlink 33 | instance fileManager 34 | instance fileUplink 35 | instance bufferManager 36 | instance framer 37 | instance posixTime 38 | instance prmDb 39 | instance rateGroup1 40 | instance rateGroup2 41 | instance rateGroup3 42 | instance rateGroupDriver 43 | instance textLogger 44 | instance systemResources 45 | 46 | instance mathSender 47 | instance mathReceiver 48 | instance linuxTimer 49 | 50 | # ---------------------------------------------------------------------- 51 | # Pattern graph specifiers 52 | # ---------------------------------------------------------------------- 53 | 54 | command connections instance cmdDisp 55 | 56 | event connections instance eventLogger 57 | 58 | param connections instance prmDb 59 | 60 | telemetry connections instance tlmSend 61 | 62 | text event connections instance textLogger 63 | 64 | time connections instance posixTime 65 | 66 | health connections instance $health 67 | 68 | # ---------------------------------------------------------------------- 69 | # Direct graph specifiers 70 | # ---------------------------------------------------------------------- 71 | 72 | connections Downlink { 73 | # Inputs to ComQueue (events, telemetry, file) 74 | eventLogger.PktSend -> comQueue.comPacketQueueIn[0] 75 | tlmSend.PktSend -> comQueue.comPacketQueueIn[1] 76 | fileDownlink.bufferSendOut -> comQueue.bufferQueueIn[0] 77 | comQueue.bufferReturnOut[0] -> fileDownlink.bufferReturn 78 | # ComQueue <-> Framer 79 | comQueue.dataOut -> framer.dataIn 80 | framer.dataReturnOut -> comQueue.dataReturnIn 81 | framer.comStatusOut -> comQueue.comStatusIn 82 | # Buffer Management for Framer 83 | framer.bufferAllocate -> bufferManager.bufferGetCallee 84 | framer.bufferDeallocate -> bufferManager.bufferSendIn 85 | # Framer <-> ComStub 86 | framer.dataOut -> comStub.dataIn 87 | comStub.dataReturnOut -> framer.dataReturnIn 88 | comStub.comStatusOut -> framer.comStatusIn 89 | # ComStub <-> ComDriver 90 | comStub.drvSendOut -> comDriver.$send 91 | comDriver.sendReturnOut -> comStub.drvSendReturnIn 92 | comDriver.ready -> comStub.drvConnected 93 | } 94 | 95 | connections FaultProtection { 96 | eventLogger.FatalAnnounce -> fatalHandler.FatalReceive 97 | } 98 | 99 | connections RateGroups { 100 | # Block driver 101 | linuxTimer.CycleOut -> rateGroupDriver.CycleIn 102 | 103 | # Rate group 1 104 | rateGroupDriver.CycleOut[Ports_RateGroups.rateGroup1] -> rateGroup1.CycleIn 105 | rateGroup1.RateGroupMemberOut[0] -> tlmSend.Run 106 | rateGroup1.RateGroupMemberOut[1] -> fileDownlink.Run 107 | rateGroup1.RateGroupMemberOut[2] -> systemResources.run 108 | 109 | # Rate group 2 110 | rateGroupDriver.CycleOut[Ports_RateGroups.rateGroup2] -> rateGroup2.CycleIn 111 | rateGroup2.RateGroupMemberOut[0] -> cmdSeq.schedIn 112 | 113 | # Rate group 3 114 | rateGroupDriver.CycleOut[Ports_RateGroups.rateGroup3] -> rateGroup3.CycleIn 115 | rateGroup3.RateGroupMemberOut[0] -> $health.Run 116 | rateGroup3.RateGroupMemberOut[1] -> bufferManager.schedIn 117 | } 118 | 119 | connections Sequencer { 120 | cmdSeq.comCmdOut -> cmdDisp.seqCmdBuff 121 | cmdDisp.seqCmdStatus -> cmdSeq.cmdResponseIn 122 | } 123 | 124 | connections Uplink { 125 | # ComDriver buffer allocations 126 | comDriver.allocate -> bufferManager.bufferGetCallee 127 | comDriver.deallocate -> bufferManager.bufferSendIn 128 | # ComDriver <-> ComStub 129 | comDriver.$recv -> comStub.drvReceiveIn 130 | comStub.drvReceiveReturnOut -> comDriver.recvReturnIn 131 | # ComStub <-> FrameAccumulator 132 | comStub.dataOut -> frameAccumulator.dataIn 133 | frameAccumulator.dataReturnOut -> comStub.dataReturnIn 134 | # FrameAccumulator buffer allocations 135 | frameAccumulator.bufferDeallocate -> bufferManager.bufferSendIn 136 | frameAccumulator.bufferAllocate -> bufferManager.bufferGetCallee 137 | # FrameAccumulator <-> Deframer 138 | frameAccumulator.dataOut -> deframer.dataIn 139 | deframer.dataReturnOut -> frameAccumulator.dataReturnIn 140 | # Deframer <-> Router 141 | deframer.dataOut -> fprimeRouter.dataIn 142 | fprimeRouter.dataReturnOut -> deframer.dataReturnIn 143 | # Router buffer allocations 144 | fprimeRouter.bufferAllocate -> bufferManager.bufferGetCallee 145 | fprimeRouter.bufferDeallocate -> bufferManager.bufferSendIn 146 | # Router <-> CmdDispatcher/FileUplink 147 | fprimeRouter.commandOut -> cmdDisp.seqCmdBuff 148 | cmdDisp.seqCmdStatus -> fprimeRouter.cmdResponseIn 149 | fprimeRouter.fileOut -> fileUplink.bufferSendIn 150 | fileUplink.bufferSendOut -> fprimeRouter.fileBufferReturnIn 151 | } 152 | 153 | connections MathDeployment { 154 | # Add here connections to user-defined components 155 | rateGroup1.RateGroupMemberOut[3] -> mathReceiver.schedIn 156 | 157 | mathSender.mathOpOut -> mathReceiver.mathOpIn 158 | mathReceiver.mathResultOut -> mathSender.mathResultIn 159 | } 160 | 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /Ports/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCE_FILES 2 | "${CMAKE_CURRENT_LIST_DIR}/MathPorts.fpp" 3 | ) 4 | 5 | register_fprime_module() -------------------------------------------------------------------------------- /Ports/MathPorts.fpp: -------------------------------------------------------------------------------- 1 | module MathModule{ 2 | @ Port for requesting an operation on two numbers 3 | port OpRequest( 4 | val1: F32 @< The first operand 5 | op: MathOp @< The operation 6 | val2: F32 @< The second operand 7 | ) 8 | 9 | @ Port for returning the result of a math operation 10 | port MathResult( 11 | result: F32 @< the result of the operation 12 | ) 13 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # F Prime MathComponent Tutorial 2 | 3 | This repository contains the source code and documentation for the F Prime MathComponent tutorial. 4 | 5 | The Math Component tutorial shows how to construct an F Prime application with user-defined ports and data types. It covers more advanced uses of events, telemetry, commands, and parameters. It also covers unit testing of F Prime components. 6 | 7 | To run through the tutorial, please visit the F´ website and look for the tutorial section: https://fprime.jpl.nasa.gov 8 | 9 | ## Contributing 10 | If you would like to contribute to this tutorial, please open a pull request. 11 | 12 | ## Resources 13 | - [Discussions](https://github.com/nasa/fprime/discussions) 14 | - [Submit an Issue](https://github.com/nasa/fprime/issues) 15 | - [F´ Community](https://github.com/fprime-community) 16 | -------------------------------------------------------------------------------- /Types/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCE_FILES 2 | "${CMAKE_CURRENT_LIST_DIR}/MathTypes.fpp" 3 | ) 4 | 5 | register_fprime_module() -------------------------------------------------------------------------------- /Types/MathTypes.fpp: -------------------------------------------------------------------------------- 1 | module MathModule{ 2 | 3 | @ Math operations 4 | enum MathOp { 5 | ADD @< Addition 6 | SUB @< Subtraction 7 | MUL @< Multiplication 8 | DIV @< Division 9 | } 10 | } -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: "F´" 2 | description: "Flight Software & Embedded Systems Framework" 3 | remote_theme: "fprime-community/fprime-theme@main" 4 | includes_dir: _includes 5 | -------------------------------------------------------------------------------- /docs/_includes/toc.md: -------------------------------------------------------------------------------- 1 | 2 |

F´ Documentation

3 |
    4 | 5 |
6 |

MathComponent Tutorial

7 |
    8 |
  1. Project Setup
  2. 9 |
  3. Defining Types
  4. 10 |
  5. Constructing Ports
  6. 11 |
  7. Creating Components
  8. 12 |
  9. Developing Deployments
  10. 13 |
  11. Writing Unit Tests
  12. 14 |
  13. Adding Telemetry
  14. 15 |
  15. Error handling
  16. 16 |
17 | -------------------------------------------------------------------------------- /docs/img/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-tutorial-math-component/12e3d0895d5699f1b733c131a99e5828f735097f/docs/img/top.png -------------------------------------------------------------------------------- /docs/math-component.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: / 3 | --- 4 | 5 | # MathComponent Tutorial 6 | 7 | Welcome to the MathComponent tutorial! This tutorial shows how to develop, test, and deploy a simple topology 8 | consisting of two components: 9 | 10 | 1. `MathSender`: A component that receives commands and forwards work to 11 | `MathReceiver`. 12 | 13 | 2. `MathReceiver`: A component that carries out arithmetic operations and 14 | returns the results to `MathSender`. 15 | 16 | See the diagram below. 17 | 18 | ![A simple topology for arithmetic computation](img/top.png) 19 | 20 | ## What is covered 21 | This tutorial will cover the following concepts: 22 | 23 | 1. Defining types, ports, and components in F'. 24 | 25 | 2. Creating a deployment and running the F' GDS (Ground Data System). 26 | 27 | 3. Writing unit tests. 28 | 29 | 4. Handling errors, creating events, and adding telemetry channels. 30 | 31 | > [!TIP] 32 | > The source for this tutorial is located here: [https://github.com/fprime-community/fprime-tutorial-math-component](https://github.com/fprime-community/fprime-tutorial-math-component). If you are stuck at some point during the tutorial, you may refer to that reference as the "solution". 33 | 34 | 35 | --- 36 | ## Project Setup 37 | ### Bootstrapping F´ 38 | 39 | > [!NOTE] 40 | > If you have followed the [HelloWorld tutorial](https://fprime-community.github.io/fprime-tutorial-hello-world/) previously, this should feel very familiar... 41 | 42 | An F´ project ties to a specific version of tools to work with F´. In order to create 43 | this project and install the correct version of tools, you should perform a bootstrap of F´: 44 | 45 | 1. Ensure you meet the [F´ System Requirements](https://github.com/nasa/fprime?tab=readme-ov-file#system-requirements) 46 | 2. [Bootstrap your F´ project](https://nasa.github.io/fprime/INSTALL.html#creating-a-new-f-project) with the name `MathProject` 47 | 48 | Bootstrapping your F´ project created a folder called `MathProject` (or any name you chose) containing the standard F´ project structure as well as the virtual environment up containing the tools to work with F´. 49 | 50 | 51 | ### Building the New F´ Project 52 | 53 | The next step is to set up and build the newly created project. This will serve as a build environment for any newly 54 | created components, and will build the F´ framework supplied components. 55 | 56 | ```bash 57 | cd MathProject 58 | fprime-util generate 59 | fprime-util build -j4 60 | ``` 61 | 62 | > [!NOTE] 63 | > `fprime-util generate` sets up the build environment for a project/deployment. It only needs to be done once. At the end of this tutorial, a new deployment will be created and `fprime-util generate` will also be used then. 64 | 65 | ### Summary 66 | 67 | A new project has been created with the name `MathProject` and has been placed in a new folder called in `MathProject` in 68 | the current directory. It includes the initial build system setup, and F´ version. It is still empty in that the user 69 | will still need to create components and deployments. 70 | 71 | For the remainder of this Getting Started tutorial we should use the tools installed for our project and issue commands 72 | within this new project's folder. Change into the project directory and load the newly install tools with: 73 | 74 | ```bash 75 | cd MathProject 76 | . fprime-venv/bin/activate 77 | ``` 78 | 79 | --- 80 | 81 | ## Defining Types 82 | 83 | ### Background 84 | 85 | In F Prime, a **type definition** defines a kind of data that you can pass between components or use in commands and telemetry. 86 | 87 | For this tutorial, you need one type definition. The type will define an enumeration called `MathOp`, which represents a mathematical operation. 88 | 89 | ### In this section 90 | 91 | In this section, you will create a `Types` directory and add it to the project build. You will create an enumeration to represent several mathematical operations. 92 | 93 | ### Setup 94 | 95 | To start, create a directory where your type(s) will live: 96 | 97 | ```shell 98 | # In: MathProject 99 | mkdir Types 100 | cd Types 101 | ``` 102 | 103 | The user defines types in an fpp (F prime prime) file. Use the command below to create an empty fpp file to define the `MathOp` type: 104 | 105 | ```shell 106 | # In: Types 107 | touch MathTypes.fpp 108 | ``` 109 | Here you have created an empty fpp file named MathTypes in the Types directory. 110 | 111 | ### Implementing the Types 112 | 113 | Use your favorite text editor, visual studios, nano, vim, etc..., and add the following to `MathTypes.fpp`. 114 | 115 | ``` 116 | # In: MathTypes.fpp 117 | module MathModule { 118 | 119 | @ Math operations 120 | enum MathOp { 121 | ADD @< Addition 122 | SUB @< Subtraction 123 | MUL @< Multiplication 124 | DIV @< Division 125 | } 126 | } 127 | ``` 128 | > [!IMPORTANT] 129 | > Think of modules similar to a cpp namespace. Whenever you want to make use of the enumeration, `MathOp`, you will need to use the MathModule module. 130 | 131 | Above you have created an enumeration of the four math types that are used in this tutorial. 132 | 133 | 134 | ### Adding to the Build 135 | 136 | To specify how `MathTypes.fpp` should build with the project, you need to make two modifications to the MathProject: 137 | 138 | 1. Create and edit `CMakeLists.txt` in `Types` to include `MathTypes.fpp` into the build. 139 | 140 | To create CMakeLists.txt use: 141 | 142 | ```shell 143 | # In: Types 144 | touch CMakeLists.txt 145 | ``` 146 | 147 | > [!IMPORTANT] 148 | > Capitalization and spelling is important when creating files! 149 | 150 | Use a text editor to replace whatever is in CMakeLists.txt, most likely nothing, with the following. 151 | 152 | ```cmake 153 | set(SOURCE_FILES 154 | "${CMAKE_CURRENT_LIST_DIR}/MathTypes.fpp" 155 | ) 156 | 157 | register_fprime_module() 158 | ``` 159 | 160 | 2. Add the `Types` directory to the overall project build by adding to `project.cmake`. 161 | 162 | Edit `project.cmake`, located in the `MathProject` directory, and **add** the following line at the end of the file: 163 | 164 | ```cmake 165 | # In: MathProject/project.cmake 166 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Types/") 167 | ``` 168 | 169 | The `Types` directory should now build without any issues. Test the build with the following command before moving forward. 170 | 171 | ```shell 172 | # In: Types 173 | fprime-util build 174 | ``` 175 | 176 | > [!NOTE] 177 | > If you have not generated a build cache already, you may need to run `fprime-util generate` before you can build. 178 | 179 | The output should indicate that the model built without any errors. If not, try to identify and correct what is wrong, either by deciphering the error output, or by going over the steps again. If you get stuck, you can look at the [reference implementation](https://github.com/fprime-community/fprime-tutorial-math-component). 180 | 181 | > [!NOTE] 182 | > Advanced users may want to go inspect the generated code. Go to the directory `MathProject/build-fprime-automatic-native/Types`. The directory `build-fprime-automatic-native` is where all the generated code lives for the "automatic native" build of the project. Within that directory is a directory tree that mirrors the project structure. In particular, `build-fprime-automatic-native/Types` contains the generated code for `Types`. 183 | 184 | > [!NOTE] 185 | > The files MathOpEnumAc.hpp and MathOpEnumAc.cpp are the auto-generated C++ files corresponding to the MathOp enum. You may wish to study the file MathOpEnumAc.hpp. This file gives the interface to the C++ class MathModule::MathOp. All enum types have a similar auto-generated class interface. 186 | 187 | ### Summary 188 | At this point you have successfully created the `MathOp` type 189 | and added it to the project build. You can add more types here 190 | later if you feel so inclined. 191 | 192 | --- 193 | 194 | ## Constructing Ports 195 | 196 | ### Background 197 | 198 | A **port** is the endpoint of a connection between two components. 199 | A **port definition** is like a function signature; it defines the type of the data carried on a port. 200 | 201 | ### Requirements 202 | For this tutorial, you will need two port definitions: 203 | 204 | * `OpRequest` for sending an arithmetic operation request from 205 | `MathSender` to `MathReceiver`. 206 | 207 | * `MathResult` for sending the result of an arithmetic 208 | operation from `MathReceiver` to `MathSender`. 209 | 210 | ### In this section 211 | 212 | In this section, you will create a `Ports` directory where you will create two ports in `MathPorts.fpp`. 213 | 214 | ### Setup 215 | 216 | Start by making a directory where the ports will be defined. Create a directory called `Ports` in the `MathProject` directory: 217 | 218 | ```shell 219 | # In: MathProject 220 | mkdir Ports 221 | cd Ports 222 | ``` 223 | 224 | While in "Ports", create an empty fpp file called `MathPorts.fpp`, this is where the ports will be defined: 225 | 226 | ```shell 227 | # In: Ports 228 | touch MathPorts.fpp 229 | ``` 230 | 231 | ### Implementing the Ports 232 | 233 | Use your favorite text editor to add the following to `MathPorts.fpp`: 234 | 235 | ```fpp 236 | # In: MathPorts.fpp 237 | module MathModule { 238 | @ Port for requesting an operation on two numbers 239 | port OpRequest( 240 | val1: F32 @< The first operand 241 | op: MathOp @< The operation 242 | val2: F32 @< The second operand 243 | ) 244 | 245 | @ Port for returning the result of a math operation 246 | port MathResult( 247 | result: F32 @< the result of the operation 248 | ) 249 | } 250 | ``` 251 | > [!NOTE] 252 | > Notice how we define ports in MathModule, which is where we defined MathOp as well. 253 | 254 | Here, you have created two ports. The first port, called `OpRequest`, carries two 32-bit floats (`val1` and `val2`) and a math operations `op`. The second port only carries one 32-bit float (result). The first port is intended to send an operation and operands to the `MathReceiver`. 255 | The second port is designed to send the results of the operation back to `MathSender`. 256 | 257 | For more information about port definitions, see [_The FPP User's Guide_](https://nasa.github.io/fpp/fpp-users-guide.html). 258 | 259 | ### Adding to the Build 260 | 261 | Create a `CMakeLists.txt` file in `Ports`. 262 | 263 | ```shell 264 | # In: Ports 265 | touch CMakeLists.txt 266 | ``` 267 | 268 | Add the following to the `CMakeLists.txt`. 269 | 270 | ```cmake 271 | # In: Ports/CMakeLists.txt 272 | set(SOURCE_FILES 273 | "${CMAKE_CURRENT_LIST_DIR}/MathPorts.fpp" 274 | ) 275 | 276 | register_fprime_module() 277 | ``` 278 | 279 | Add the following to `project.cmake`. Remember that `project.cmake` is in MathProject, not Ports. 280 | 281 | ```cmake 282 | # In: MathProject/project.cmake 283 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Ports/") 284 | ``` 285 | 286 | Ports should build without any issues. Use the following to build: 287 | 288 | ```shell 289 | # In: Ports 290 | fprime-util build 291 | ``` 292 | 293 | Check in `MathProject/build-fprime-automatic-native/Ports` for port definitions. The names of the auto-generated C++ 294 | files end in `*PortAc.hpp` and `*PortAc.cpp`. 295 | Note however, the auto-generated C++ port files are used by the autocoded component implementations; you won't ever program directly against their interfaces. 296 | 297 | At this point, you have successfully implemented all the ports used for this tutorial and added them to the build. 298 | 299 | 300 | --- 301 | 302 | ## Creating Components Part 1: Creating the MathSender 303 | 304 | ### Background 305 | 306 | Components are the lifeblood of an F' deployment. In this tutorial the components are strictly virtual, however in many deployments components will represent unique pieces of hardware, such as sensors and microcontrollers! 307 | 308 | ### In this section 309 | 310 | In this section, you will begin creating an active component and write its F Prime Prime (fpp) implementation. You will generate cpp and hpp files using the fpp. Note, that you will implement component behavior in `MathSender.cpp` in the next section. Most commonly, the steps to create a new component are the following: 311 | 312 | 1. Construct the FPP model. 313 | 2. Add the model to the project. 314 | 3. Build the stub implementation. 315 | 4. Complete the implementation. 316 | 5. Write and run unit tests. 317 | 318 | 319 | ### Component Description 320 | The `MathSender` is an active component which receives parameters, sends parameters, logs events, and sends telemetry. 321 | 322 | With the component description in mind, use the following command to create the `MathSender` component: 323 | 324 | ### Creating the MathSender 325 | F´ projects conveniently come with a `Components/` folder to create components in. It is not required for components to live there, but this tutorial will make use of it. 326 | 327 | ```shell 328 | # In: MathProject/Components/ 329 | fprime-util new --component 330 | ``` 331 | This command will prompt you for some inputs. Answer the prompts as shown below: 332 | 333 | ``` 334 | [INFO] Cookiecutter source: using builtin 335 | Component name [MyComponent]: MathSender 336 | Component short description [Example Component for F Prime FSW framework.]: Active component used for sending operations and operands to the MathReceiver. 337 | Component namespace [Component]: MathModule 338 | Select component kind: 339 | 1 - active 340 | 2 - passive 341 | 3 - queued 342 | Choose from 1, 2, 3 [1]: 1 343 | Enable Commands?: 344 | 1 - yes 345 | 2 - no 346 | Choose from 1, 2 [1]: 1 347 | Enable Telemetry?: 348 | 1 - yes 349 | 2 - no 350 | Choose from 1, 2 [1]: 1 351 | Enable Events?: 352 | 1 - yes 353 | 2 - no 354 | Choose from 1, 2 [1]: 1 355 | Enable Parameters?: 356 | 1 - yes 357 | 2 - no 358 | Choose from 1, 2 [1]: 1 359 | [INFO] Found CMake file at 'MathProject/project.cmake' 360 | Add component Components/MathSender to MathProject/project.cmake at end of file (yes/no)? yes 361 | Generate implementation files (yes/no)? yes 362 | ``` 363 | 364 | 365 | Before doing anything to the files you have just generated, try building: 366 | 367 | ```shell 368 | cd MathSender 369 | fprime-util build 370 | ``` 371 | 372 | ### Editing the FPP Model 373 | Now that you have created the component, you can start working on implementing the component behavior. The first part of implementing component behavior is editing the fpp file. The fpp file will specify what goes into the auto-generated cpp and hpp files. Writing the fpp file will not implement component behavior on its own, but it will generate templates for most of what you will write in cpp and hpp files. 374 | 375 | In `Components/MathSender`, open `MathSender.fpp` and **entirely replace its contents with the following**: 376 | 377 | 378 | ```fpp 379 | # In: MathSender.fpp 380 | module MathModule { 381 | 382 | @ Component for sending a math operation 383 | active component MathSender { 384 | 385 | # ---------------------------------------------------------------------- 386 | # General ports 387 | # ---------------------------------------------------------------------- 388 | 389 | @ Port for sending the operation request 390 | output port mathOpOut: OpRequest 391 | 392 | @ Port for receiving the result 393 | async input port mathResultIn: MathResult 394 | 395 | # ---------------------------------------------------------------------- 396 | # Special ports 397 | # ---------------------------------------------------------------------- 398 | 399 | @ Command receive port 400 | command recv port cmdIn 401 | 402 | @ Command registration port 403 | command reg port cmdRegOut 404 | 405 | @ Command response port 406 | command resp port cmdResponseOut 407 | 408 | @ Event port 409 | event port eventOut 410 | 411 | @ Telemetry port 412 | telemetry port tlmOut 413 | 414 | @ Text event port 415 | text event port textEventOut 416 | 417 | @ Time get port 418 | time get port timeGetOut 419 | 420 | # ---------------------------------------------------------------------- 421 | # Commands 422 | # ---------------------------------------------------------------------- 423 | 424 | @ Do a math operation 425 | async command DO_MATH( 426 | val1: F32 @< The first operand 427 | op: MathOp @< The operation 428 | val2: F32 @< The second operand 429 | ) 430 | 431 | # ---------------------------------------------------------------------- 432 | # Events 433 | # ---------------------------------------------------------------------- 434 | 435 | @ Math command received 436 | event COMMAND_RECV( 437 | val1: F32 @< The first operand 438 | op: MathOp @< The operation 439 | val2: F32 @< The second operand 440 | ) \ 441 | severity activity low \ 442 | format "Math command received: {f} {} {f}" 443 | 444 | @ Received math result 445 | event RESULT( 446 | result: F32 @< The math result 447 | ) \ 448 | severity activity high \ 449 | format "Math result is {f}" 450 | 451 | # ---------------------------------------------------------------------- 452 | # Telemetry 453 | # ---------------------------------------------------------------------- 454 | 455 | @ The first value 456 | telemetry VAL1: F32 457 | 458 | @ The operation 459 | telemetry OP: MathOp 460 | 461 | @ The second value 462 | telemetry VAL2: F32 463 | 464 | @ The result 465 | telemetry RESULT: F32 466 | 467 | } 468 | 469 | } 470 | ``` 471 | 472 | ### About this Component 473 | 474 | The above code defines `MathSender` component. The component is **active**, which means it has its own thread. 475 | 476 | Inside the definition of the `MathSender` component are several specifiers. We have divided the specifiers into five groups. 477 | 478 | 1. **General ports:** These are user-defined ports for application-specific functions. 479 | There are two general ports: an output port `mathOpOut` of type `OpRequest` and an input port `mathResultIn` of type `MathResult`. 480 | Notice that these port specifiers use the ports that you defined. 481 | The input port is **asynchronous**. This means that invoking the port (i.e., sending data on the port) puts a message on a queue. The handler runs later, on the thread of this component. 482 | 483 | 2. **Special ports:** These are ports that have a special meaning in F Prime. 484 | The special ports are ports for registering commands with the dispatcher, receiving commands, sending command responses, emitting event reports, emitting telemetry, and getting the time. 485 | 486 | 3. **Commands:** These are commands sent from the ground or from a sequencer and dispatched to this component. 487 | There is one command `DO_MATH` for doing a math operation. 488 | The command is asynchronous. This means that when the command arrives, it goes on a queue and its handler is later run on the thread of this component 489 | 490 | 4. **Events:** These are event reports that this component can emit. 491 | There are two event reports, one for receiving a command and one for receiving a result. 492 | 493 | 5. **Telemetry:** These are **channels** that define telemetry points that the this component can emit. 494 | There are four telemetry channels: three for the arguments to the last command received and one for the last result received. 495 | 496 | > [!NOTE] 497 | > For more information on defining components, see the [_FPP User's Guide_](https://nasa.github.io/fpp/fpp-users-guide.html#Defining-Components). 498 | 499 | 500 | ### Generate the Implementation Files 501 | 502 | Now you have written the FPP code for the component, but the cpp and hpp files do not yet reflect the changes you have made to the fpp file. To get the cpp and hpp to reflect the specs you have defined in the fpp, you need to use the implement command as shown below: 503 | 504 | ```shell 505 | # In: MathSender 506 | fprime-util impl 507 | ``` 508 | 509 | Now, In `MathSender`, you will see two new files, `MathSender.template.cpp` and `MathSender.template.hpp`. The template files are the files you just generated using the FPP model. Whenever F' generates code, it creates new file with the `.template.` so as to not burn down any old code. In this case, you did not write anything in the original `MathSender.cpp` or `MathSender.hpp`, so you can use a move command to replace the old code with the new code: 510 | 511 | 512 | ```shell 513 | # In: MathSender 514 | mv MathSender.template.cpp MathSender.cpp 515 | mv MathSender.template.hpp MathSender.hpp 516 | ``` 517 | 518 | Build MathSender to make sure everything worked as expected. 519 | 520 | ```shell 521 | # In: MathSender 522 | fprime-util build 523 | ``` 524 | 525 | ### Wait... Shouldn't You Add this to the Build? 526 | 527 | If you've been paying attention to the tutorial thus far, you might be getting some warning bells that you have not added your new component to the build. Fear not, when using `fprime-util new --component` all of the `CMakeLists.txt` and `project.cmake` work was done for you! Take a look at both files to verify for yourself. 528 | 529 | ### Summary 530 | 531 | You are about two thirds of the way through finishing `MathSender`. In the next section you will implement `MathSender`'s component behavior. 532 | 533 | 534 | --- 535 | 536 | 537 | ## Creating Components Part 2: Implementing MathSender Behavior 538 | 539 | ### In this section 540 | 541 | In this section you will edit `MathSender.cpp` to implement the desired component behavior. 542 | 543 | As a reminder, below is the component behavior you are trying to implement in this section of the tutorial. 544 | 545 | ### Component Description 546 | The `MathSender` is going to be an active component which will receive parameters, send parameters, log events, and send telemetry. 547 | 548 | ### Editing the Do Math Command Handler 549 | 550 | The handler `DO_MATH_handler` is called when the `MathSender` component receives a `DO_MATH` command. This handler overrides the corresponding pure virtual function in the auto-generated base class. Fill in the handler so that it looks like this: 551 | 552 | ```cpp 553 | // In: MathSender.cpp 554 | void MathSender :: 555 | DO_MATH_cmdHandler( 556 | const FwOpcodeType opCode, 557 | const U32 cmdSeq, 558 | F32 val1, 559 | MathOp op, 560 | F32 val2 561 | ) 562 | { 563 | this->tlmWrite_VAL1(val1); 564 | this->tlmWrite_OP(op); 565 | this->tlmWrite_VAL2(val2); 566 | this->log_ACTIVITY_LO_COMMAND_RECV(val1, op, val2); 567 | this->mathOpOut_out(0, val1, op, val2); 568 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); 569 | } 570 | ``` 571 | ### Explanation 572 | The first two arguments to the handler function provide the command opcode and the command sequence number (a unique identifier generated by the command dispatcher). The remaining arguments are supplied when the command is sent, for example, from the F Prime ground data system (GDS). The implementation code does the following: 573 | 574 | 1. Emit telemetry and events. 575 | 576 | 2. Invoke the `mathOpOut` port to request that `MathReceiver` 577 | perform the operation. 578 | 579 | 3. Send a command response indicating success. 580 | The command response goes out on the special port 581 | `cmdResponseOut`. 582 | 583 | In F Prime, every execution of a command handler must end by sending a command response. 584 | The proper behavior of other framework components (e.g., command dispatcher, command sequencer) depends upon adherence to this rule. 585 | 586 | 587 | Check the build using: 588 | 589 | 590 | ```shell 591 | # In: MathSender 592 | fprime-util build 593 | ``` 594 | 595 | ### Editing the Result Handler 596 | The handler `mathResultIn_handler` is called when the `MathReceiver` component code returns a result by invoking the `mathResultIn` port. Again the handler overrides the corresponding pure virtual function in the auto-generated base class. Fill in the handler so that it looks like this: 597 | 598 | ```cpp 599 | // In: MathSender.cpp 600 | void MathSender :: 601 | mathResultIn_handler( 602 | const FwIndexType portNum, 603 | F32 result 604 | ) 605 | { 606 | this->tlmWrite_RESULT(result); 607 | this->log_ACTIVITY_HI_RESULT(result); 608 | } 609 | ``` 610 | 611 | ### Explanation 612 | 613 | The implementation code emits the result on the `RESULT` telemetry channel and as a `RESULT` event report. 614 | 615 | 616 | Check the build using: 617 | 618 | ```shell 619 | # In: MathSender 620 | fprime-util build 621 | ``` 622 | 623 | ### Summary 624 | Congratulations, you have completed `MathSender`! Well... there's always more to be done, such as error handling, adding more telemetry, 625 | creating more events, and generally messing around with what `MathSender` can do. But for the purposes of getting a deployment 626 | working, this component is done! 627 | 628 | --- 629 | 630 | 631 | ## Creating Components Part 3: Starting the MathReceiver 632 | 633 | ### In this Section 634 | 635 | In this section you will begin creating `MathReceiver` following the same steps as the last section. Note, that the `MathReceiver` is a little different than the `MathSender`. 636 | 637 | ### Component Description 638 | 639 | The `MathReceiver` is a queued component which receives parameters, send parameters, logs events, and sends telemetry. With this is mind, use the following command to create the `MathReceiver` component. 640 | 641 | ### Creating the MathReceiver 642 | 643 | ```shell 644 | # In: Components 645 | fprime-util new --component 646 | ``` 647 | This command will prompt you for some inputs. You should specify the follow so that the components matches the short description above. Don't forget that this is a QUEUED component: 648 | 649 | ``` 650 | [INFO] Cookiecutter source: using builtin 651 | Component name [MyComponent]: MathReceiver 652 | Component short description [Example Component for F Prime FSW framework.]: Your description 653 | Component namespace [Component]: MathModule 654 | Select component kind: 655 | 1 - active 656 | 2 - passive 657 | 3 - queued 658 | Choose from 1, 2, 3 [1]: 3 659 | Enable Commands?: 660 | 1 - yes 661 | 2 - no 662 | Choose from 1, 2 [1]: 1 663 | Enable Telemetry?: 664 | 1 - yes 665 | 2 - no 666 | Choose from 1, 2 [1]: 1 667 | Enable Events?: 668 | 1 - yes 669 | 2 - no 670 | Choose from 1, 2 [1]: 1 671 | Enable Parameters?: 672 | 1 - yes 673 | 2 - no 674 | Choose from 1, 2 [1]: 1 675 | [INFO] Found CMake file at 'MathProject/project.cmake' 676 | Add component Components/MathSender to MathProject/project.cmake at end of file (yes/no)? yes 677 | Generate implementation files (yes/no)? yes 678 | ``` 679 | 680 | Before doing anything to the files you have just generated, try building: 681 | 682 | ```shell 683 | # In: MathReceiver 684 | fprime-util build 685 | ``` 686 | 687 | ### Editing the F Prime Prime Model 688 | Now that you have created the component, you can implement the component behavior in the fpp model. Use a text editor to entirely replace the existing `MathReceiver.fpp` with the following: 689 | 690 | ```fpp 691 | # In: MathReceiver.fpp 692 | module MathModule { 693 | 694 | @ Component for receiving and performing a math operation 695 | queued component MathReceiver { 696 | 697 | # ---------------------------------------------------------------------- 698 | # General ports 699 | # ---------------------------------------------------------------------- 700 | 701 | @ Port for receiving the math operation 702 | async input port mathOpIn: OpRequest 703 | 704 | @ Port for returning the math result 705 | output port mathResultOut: MathResult 706 | 707 | @ The rate group scheduler input 708 | sync input port schedIn: Svc.Sched 709 | 710 | # ---------------------------------------------------------------------- 711 | # Special ports 712 | # ---------------------------------------------------------------------- 713 | 714 | @ Command receive 715 | command recv port cmdIn 716 | 717 | @ Command registration 718 | command reg port cmdRegOut 719 | 720 | @ Command response 721 | command resp port cmdResponseOut 722 | 723 | @ Event 724 | event port eventOut 725 | 726 | @ Parameter get 727 | param get port prmGetOut 728 | 729 | @ Parameter set 730 | param set port prmSetOut 731 | 732 | @ Telemetry 733 | telemetry port tlmOut 734 | 735 | @ Text event 736 | text event port textEventOut 737 | 738 | @ Time get 739 | time get port timeGetOut 740 | 741 | # ---------------------------------------------------------------------- 742 | # Parameters 743 | # ---------------------------------------------------------------------- 744 | 745 | @ The multiplier in the math operation 746 | param FACTOR: F32 default 1.0 id 0 \ 747 | set opcode 10 \ 748 | save opcode 11 749 | 750 | # ---------------------------------------------------------------------- 751 | # Events 752 | # ---------------------------------------------------------------------- 753 | 754 | @ Factor updated 755 | event FACTOR_UPDATED( 756 | val: F32 @< The factor value 757 | ) \ 758 | severity activity high \ 759 | id 0 \ 760 | format "Factor updated to {f}" \ 761 | throttle 3 762 | 763 | @ Math operation performed 764 | event OPERATION_PERFORMED( 765 | val: MathOp @< The operation 766 | ) \ 767 | severity activity high \ 768 | id 1 \ 769 | format "{} operation performed" 770 | 771 | @ Event throttle cleared 772 | event THROTTLE_CLEARED \ 773 | severity activity high \ 774 | id 2 \ 775 | format "Event throttle cleared" 776 | 777 | # ---------------------------------------------------------------------- 778 | # Commands 779 | # ---------------------------------------------------------------------- 780 | 781 | @ Clear the event throttle 782 | async command CLEAR_EVENT_THROTTLE \ 783 | opcode 0 784 | 785 | # ---------------------------------------------------------------------- 786 | # Telemetry 787 | # ---------------------------------------------------------------------- 788 | 789 | @ The operation 790 | telemetry OPERATION: MathOp id 0 791 | 792 | @ Multiplication factor 793 | telemetry FACTOR: F32 id 1 794 | 795 | } 796 | 797 | } 798 | ``` 799 | 800 | ### Explanation 801 | This code defines a component `MathReceiver`. 802 | The component is **queued**, which means it has a queue but no thread. 803 | Work occurs when the thread of another component invokes the `schedIn` port of this component. 804 | 805 | We have divided the specifiers of this component into six groups: 806 | 807 | 1. **General ports:** There are three ports: 808 | - an input port `mathOpIn` for receiving a math operation, 809 | `mathOpIn` is asynchronous. That means invocations of `mathOpIn` put messages on a queue. 810 | - an output port `mathResultOut` for sending a math result, and 811 | - an input port `schedIn` for receiving invocations from the scheduler. 812 | `schedIn` is synchronous. That means invocations of `schedIn` immediately call the handler function to do work. 813 | 814 | 2. **Special ports:** 815 | As before, there are special ports for commands, events, telemetry, and time. There are also special ports for getting and setting parameters. We will explain the function of these ports below. 816 | 817 | 3. **Parameters:** There is one **parameter**. 818 | A parameter is a constant that is configurable by command. In this case there is one parameter `FACTOR`. 819 | It has the default value 1.0 until its value is changed by command. 820 | When doing math, the `MathReceiver` component performs the requested operation and then multiplies by this factor. 821 | For example, if the arguments of the `mathOpIn` port are _v1_, `ADD`, and _v2_, and the factor is _f_, then the result sent on `mathResultOut` is _(v1 + v2) f_. 822 | 823 | 4. **Events:** There are three event reports: 824 | 825 | 1. `FACTOR_UPDATED`: Emitted when the `FACTOR` parameter 826 | is updated by command. 827 | This event is **throttled** to a limit of three. 828 | That means that after the event is emitted three times 829 | it will not be emitted any more, until the throttling 830 | is cleared by command (see below). 831 | 832 | 2. `OPERATION_PERFORMED`: Emitted when this component 833 | performs a math operation. 834 | 835 | 3. `THROTTLE_CLEARED`: Emitted when the event throttling 836 | is cleared. 837 | 838 | 5. **Commands:** There is one command for clearing the event throttle. 839 | 840 | 6. **Telemetry:** There two telemetry channels: one for reporting the last operation received and one for reporting the factor parameter. 841 | 842 | For the parameters, events, commands, and telemetry, we chose to put in all the opcodes and identifiers explicitly. 843 | These can also be left implicit, as in the `MathSender` component example. 844 | For more information, see the [_FPP User's Guide_](https://nasa.github.io/fpp/fpp-users-guide.html#Defining-Components). 845 | 846 | 847 | ### Generate the Implementation Files 848 | 849 | 850 | Generate cpp and hpp files based off your `MathReceiver` by using: 851 | 852 | ```shell 853 | # In: MathReceiver 854 | fprime-util impl 855 | ``` 856 | 857 | Replace the original cpp and hpp files with the ones you just created: 858 | 859 | ```shell 860 | # In: MathReceiver 861 | mv MathReceiver.template.cpp MathReceiver.cpp 862 | mv MathReceiver.template.hpp MathReceiver.hpp 863 | ``` 864 | 865 | Test the build: 866 | 867 | ```shell 868 | # In: MathReceiver 869 | fprime-util build 870 | ``` 871 | 872 | ### Summary 873 | You are two thirds of the way through finishing `MathReceiver`. 874 | So far, you have created a queued component stub, filled in the fpp 875 | file, and wrote component characteristics in `MathReceiver.fpp`. Next, 876 | you will write the behavior for `MathReceiver`. 877 | 878 | --- 879 | 880 | ## Creating Components Part 4: Implementing MathReceiver Behavior 881 | 882 | ### In this Section 883 | 884 | In this section you will complete the implementation of the `MathReciever` by filling in `MathReceiver.cpp` and `MathReceiver.hpp`. 885 | 886 | ### Editing the Math Op In Handler 887 | Fill in the mathOpIn handler: In MathReceiver.cpp, complete the implementation of `MathReceiver::mathOpIn_handler()` so that it looks like this: 888 | 889 | ```cpp 890 | // In: MathReceiver.cpp 891 | void MathReceiver :: 892 | mathOpIn_handler( 893 | const FwIndexType portNum, 894 | F32 val1, 895 | const MathOp& op, 896 | F32 val2 897 | ) 898 | { 899 | // Get the initial result 900 | F32 res = 0.0; 901 | switch (op.e) { 902 | case MathOp::ADD: 903 | res = val1 + val2; 904 | break; 905 | case MathOp::SUB: 906 | res = val1 - val2; 907 | break; 908 | case MathOp::MUL: 909 | res = val1 * val2; 910 | break; 911 | case MathOp::DIV: 912 | res = val1 / val2; 913 | break; 914 | default: 915 | FW_ASSERT(0, op.e); 916 | break; 917 | }//end switch 918 | 919 | // Get the factor value 920 | Fw::ParamValid valid; 921 | F32 factor = paramGet_FACTOR(valid); 922 | FW_ASSERT( 923 | valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT, 924 | valid.e 925 | ); 926 | 927 | // Multiply result by factor 928 | res *= factor; 929 | 930 | // Emit telemetry and events 931 | this->log_ACTIVITY_HI_OPERATION_PERFORMED(op); 932 | this->tlmWrite_OPERATION(op); 933 | 934 | // Emit result 935 | this->mathResultOut_out(0, res); 936 | 937 | }//end mathOpIn_handler 938 | ``` 939 | 940 | ### Explanation 941 | `MathOpIn_Handler` does the following: 942 | 943 | 1. Compute an initial result based on the input values and the requested operation. 944 | 945 | 2. Get the value of the factor parameter. Check that the value is a valid value from the parameter database or a default parameter value. 946 | 947 | 3. Multiply the initial result by the factor to generate the final result. 948 | 949 | 4. Emit telemetry and events. 950 | 951 | 5. Emit the result. 952 | 953 | Note that in step 1, `op` is an enum (a C++ class type), and `op.e` is the corresponding numeric value (an integer type). Note also that in the `default` case we deliberately fail an assertion. This is a standard pattern for exhaustive case checking. We should never hit the assertion. If we do, then a bug has occurred: we missed a case. 954 | 955 | 956 | ### Editing the Schedule Handler 957 | Fill in the schedIn handler in `MathReceiver.cpp`, complete the implementation of `schedIn_handler` so that it looks like this: 958 | 959 | ```cpp 960 | // In: MathReceiver.cpp 961 | void MathReceiver :: 962 | schedIn_handler( 963 | const FwIndexType portNum, 964 | U32 context 965 | ) 966 | { 967 | FwSizeType numMsgs = this->m_queue.getMessagesAvailable(); 968 | for (FwSizeType i = 0; i < numMsgs; ++i) { 969 | (void) this->doDispatch(); 970 | } 971 | } 972 | ``` 973 | ### Explanation 974 | This code dispatches all the messages on the queue. Note that for a queued component, we have to do this dispatch explicitly in the schedIn handler. For an active component, the framework auto-generates the dispatch code. 975 | 976 | ### Editing the Throttle Command Handler 977 | Fill in the `CLEAR_EVENT_THROTTLE` command handler: In `MathReceiver.cpp`, complete the implementation of `CLEAR_EVENT_THROTTLE_cmdHandler` so that it looks like this: 978 | 979 | ```cpp 980 | // In: MathReceiver.cpp 981 | void MathReceiver :: 982 | CLEAR_EVENT_THROTTLE_cmdHandler( 983 | const FwOpcodeType opCode, 984 | const U32 cmdSeq 985 | ) 986 | { 987 | // clear throttle 988 | this->log_ACTIVITY_HI_FACTOR_UPDATED_ThrottleClear(); 989 | // send event that throttle is cleared 990 | this->log_ACTIVITY_HI_THROTTLE_CLEARED(); 991 | // reply with completion status 992 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); 993 | } 994 | ``` 995 | ### Explanation 996 | The call to `log_ACTIVITY_HI_FACTOR_UPDATED_ThrottleClear` clears the throttling of the `FACTOR_UPDATED` event. The next two lines send a notification event and send a command response. 997 | 998 | ### Writing a Parameter Checker 999 | Add the following function to `MathReceiver.cpp`. You will need to add the corresponding function header to `MathReceiver.hpp`. Note: this function is completely new, there is no preexisting stub for this function. 1000 | 1001 | ```cpp 1002 | // In: MathReceiver.cpp 1003 | void MathReceiver :: 1004 | parameterUpdated(FwPrmIdType id) 1005 | { 1006 | switch (id) { 1007 | case PARAMID_FACTOR: { 1008 | Fw::ParamValid valid; 1009 | F32 val = this->paramGet_FACTOR(valid); 1010 | FW_ASSERT( 1011 | valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT, 1012 | valid.e 1013 | ); 1014 | this->log_ACTIVITY_HI_FACTOR_UPDATED(val); 1015 | break; 1016 | } 1017 | default: 1018 | FW_ASSERT(0, id); 1019 | break; 1020 | } 1021 | } 1022 | ``` 1023 | 1024 | ```cpp 1025 | // In: MathReceiver.hpp 1026 | // As a Private under: Handler implementations for user-defined typed input ports 1027 | void parameterUpdated(FwPrmIdType id); 1028 | ``` 1029 | 1030 | ### Explanation 1031 | This code implements an optional function that, if present, is called when a parameter is updated by command. The parameter identifier is passed in as the id argument of the function. Here the function does the following: 1032 | 1033 | 1. If the parameter identifier is PARAMID_FACTOR (the parameter identifier corresponding to the FACTOR parameter), then get the parameter value and emit an event report. 1034 | 1035 | 2. Otherwise fail an assertion. This code should never run, because there are no other parameters. 1036 | 1037 | ### Test the build 1038 | 1039 | ```shell 1040 | # In: MathReceiver 1041 | fprime-util build 1042 | ``` 1043 | 1044 | ### Summary 1045 | 1046 | Congratulations, you have finished `MathReceiver`! 1047 | 1048 | --- 1049 | 1050 | ## Developing Deployments 1051 | 1052 | ### Background 1053 | The deployment is the portion of F' that will actually run on the spacecraft. Think of the deployment like an executable. 1054 | 1055 | ### In this Section 1056 | 1057 | In this section, you will create a deployment and integrate the deployment with the other work you have completed. At the end of this section, you will run the F' ground data system and test your components by actually running them! 1058 | 1059 | 1060 | ### Create a Deployment 1061 | Use the following command to create the deployment: 1062 | 1063 | ```shell 1064 | # In: MathProject 1065 | fprime-util new --deployment 1066 | ``` 1067 | 1068 | This command will ask for some input. Respond with the following: 1069 | 1070 | ``` 1071 | Deployment name (MyDeployment): MathDeployment 1072 | ``` 1073 | 1074 | > For any other questions, select the default response. 1075 | 1076 | Test the build to make sure everything is okay: 1077 | 1078 | ```shell 1079 | # In MathProject/MathDeployment 1080 | fprime-util build 1081 | ``` 1082 | 1083 | 1084 | ### Add Component Instances to the Deployment 1085 | Create an instance for `MathSender` in `instances.fpp`. 1086 | 1087 | ```fpp 1088 | # In: MathDeployment/Top/instances.fpp 1089 | # Under: Active component instances 1090 | instance mathSender: MathModule.MathSender base id 0xE00 \ 1091 | queue size Default.QUEUE_SIZE \ 1092 | stack size Default.STACK_SIZE \ 1093 | priority 100 1094 | 1095 | # Under: Queued component instances 1096 | instance mathReceiver: MathModule.MathReceiver base id 0x2700 \ 1097 | queue size Default.QUEUE_SIZE 1098 | ``` 1099 | 1100 | ### Explanation 1101 | 1102 | This code defines an instance `mathSender` of component `MathSender`. It has base identifier 0xE00. FPP adds the base identifier to each the relative identifier defined in the component to compute the corresponding identifier for the instance. For example, component MathSender has a telemetry channel MathOp with identifier 1, so instance mathSender has a command MathOp with identifier 0xE01 1103 | 1104 | The `mathReceiver` was defined with base identifier 0x2700 and the default queue size. 1105 | 1106 | ### Update the Topology 1107 | 1108 | Add the instances you created to `topology.fpp`. 1109 | 1110 | ```fpp 1111 | # In: MathDeployment/Top/topology.fpp 1112 | # Under: Instances used in the topology 1113 | instance mathSender 1114 | instance mathReceiver 1115 | ``` 1116 | 1117 | > [!NOTE] 1118 | > This step highlights the importance of capitalization. The easiest way to differentiate between the component definition and instance is the capitalization. 1119 | 1120 | ### Explanation 1121 | 1122 | These lines add the mathSender and mathReceiver instances to the topology. 1123 | 1124 | ### Check the Build 1125 | Just to be safe, check the build after this step. 1126 | 1127 | ```shell 1128 | # In: MathProject/MathDeployment 1129 | fprime-util build 1130 | ``` 1131 | 1132 | ### Check for Unconnected Ports 1133 | Check to make sure all of the ports have been connected: 1134 | 1135 | ```shell 1136 | # In: MathDeployment/Top 1137 | fprime-util fpp-check -u unconnected.txt 1138 | cat unconnected.txt 1139 | ``` 1140 | 1141 | At this point in time, several `mathSender` and `mathReceiver` ports (such as `mathOpIn` or `schedIn`) are still unconnected. Hence, they should appear on this list. 1142 | 1143 | Go into `topology.fpp`, connect `mathReceiver.schedIn` to rate group one using the code below: 1144 | 1145 | ```fpp 1146 | # In: Top/topology.fpp 1147 | # Under: connections RateGroups for rateGroup1 1148 | rateGroup1.RateGroupMemberOut[3] -> mathReceiver.schedIn 1149 | ``` 1150 | 1151 | > [!NOTE] 1152 | > `[3]` is the next available index in rate group one. 1153 | 1154 | ### Explanation 1155 | This line adds the connection that drives the `schedIn` port of the `mathReceiver` component instance. 1156 | 1157 | Verify that you successfully took a port off the list of unconnected ports. 1158 | 1159 | Add the connections between the mathSender and mathReceiver 1160 | 1161 | ```fpp 1162 | # In: Top/topology.fpp 1163 | # Under: connections MathDeployment 1164 | mathSender.mathOpOut -> mathReceiver.mathOpIn 1165 | mathReceiver.mathResultOut -> mathSender.mathResultIn 1166 | ``` 1167 | 1168 | ### Test and Run 1169 | 1170 | **Re-run the check for unconnected ports**: Notice that no mathSender or mathReceiver ports are unconnected. 1171 | 1172 | Now it is time to build the entire project and run it! Navigate back to `MathDeployment` and build: 1173 | 1174 | ```shell 1175 | # In: MathProject/MathDeployment 1176 | fprime-util build 1177 | ``` 1178 | 1179 | Run the MathComponent deployment through the GDS: 1180 | 1181 | ```shell 1182 | # In: MathProject/MathDeployment 1183 | fprime-gds 1184 | ``` 1185 | > [!TIP] 1186 | > If you encounter an error on this step, make sure you are running in the MathDeployment directory. 1187 | 1188 | ### Send Some Commands 1189 | Under _Commanding_ there is a drop-down menu called "mnemonic". Click Mnemonic and find mathSender.DO_MATH. When you select DO_MATH, three new option should appear. In put 7 into val1, put 6 into val2, and put MUL into op. Press send command. Navigate to _Events_ (top left) and find the results of your command. You should see The Ultimate Answer to Life, the Universe, and Everything: 42. 1190 | 1191 | For a more detailed guide to the F´ GDS, see [GDS Introduction](https://fprime.jpl.nasa.gov/latest/documentation/user-manual/overview/gds-introduction/). 1192 | 1193 | ### Optional: Visualizing the topology 1194 | 1195 | The F Prime tool suite provides a visualizer webapp to look at topology connections in a graphical way. This is a great tool that you can use on any F Prime deployment to help gather further insights into how things are connected, and can help for development, review, and documentation purposes. Make sure you are in the `Top/` directory of your deployment before you run the command. 1196 | 1197 | ```shell 1198 | cd MathDeployment/Top 1199 | fprime-util visualize 1200 | ``` 1201 | 1202 | Each `connections {}` block in the FPP definition is represented in a different graph. Open the `RateGroups` or `MathDeployment` diagram using the dropdown menu and confirm that you can see the connections you just created in the sections above. 1203 | 1204 | ### Summary 1205 | 1206 | In this section of the tutorial, you created a deployment. While at it, you filled out the projects instance and topology. These steps are what turn a bunch hard worked code into flight-software. Further more, you ran the software! 1207 | 1208 | ### Congratulations 1209 | 1210 | You have completed your F' deployment! If you wish to stop here, you may. You can also rest assured knowing that the work you have done is reusable. In other words, you've written code in the same way that you will write code for actual spacecrafts. Except... actual spacecrafts will make extensive use of unit tests and error handling. Keep going in this tutorial to learn more about unit testing, error handling, and just to practice using F'. 1211 | 1212 | --- 1213 | 1214 | ## Writing Unit Tests Part 1: Creating the Implementation Stub 1215 | 1216 | ### Background 1217 | 1218 | **Unit tests** are an important part of FSW development. At the component level, unit tests typically invoke input ports, send commands, and check for expected values on output ports (including telemetry and event ports). 1219 | 1220 | 1221 | ### In this Section 1222 | 1223 | In this section of the tutorial, you will create a stub implementation of a unit test that will test `MathSender`. 1224 | 1225 | First, let's create our Unit Test build cache: 1226 | 1227 | ```shell 1228 | # In MathProject 1229 | fprime-util generate --ut 1230 | ``` 1231 | 1232 | ### Generate the Unit Test Stub 1233 | 1234 | Generate a stub implementation of the unit tests. 1235 | This stub contains all the boilerplate necessary to write and run unit tests against the `MathSender` component: 1236 | 1237 | ```shell 1238 | # In: MathSender 1239 | fprime-util impl --ut 1240 | ``` 1241 | 1242 | You have just created `MathSender/test/ut` folder with three new files `MathSenderTester.template.cpp`, `MathSenderTester.template.hpp` and `MathSenderTestMain.template.cpp`. 1243 | Since this is the start of the test implementation, we use the generated template files for our initial test implementation. Inside your `MathSender/test/ut` directory, rename the files removing the `.template` suffix: 1244 | 1245 | ```bash 1246 | # In MathSender/test/ut 1247 | mv MathSenderTester.template.hpp MathSenderTester.hpp 1248 | mv MathSenderTester.template.cpp MathSenderTester.cpp 1249 | mv MathSenderTestMain.template.cpp MathSenderTestMain.cpp 1250 | ``` 1251 | 1252 | ### Add the Tests to the Build 1253 | 1254 | Add the unit test sources to the build by uncommenting the following lines at the very end of the `CMakeLists.txt` file in your `MathProject/Components/MathSender` directory: 1255 | 1256 | ```cmake 1257 | # In: MathSender/CMakeLists.txt 1258 | 1259 | ### Unit Tests ### 1260 | set(UT_SOURCE_FILES 1261 | "${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp" 1262 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/MathSenderTestMain.cpp" 1263 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/MathSenderTester.cpp" 1264 | ) 1265 | set(UT_MOD_DEPS 1266 | STest 1267 | ) 1268 | set(UT_AUTO_HELPERS ON) 1269 | register_fprime_ut() 1270 | ``` 1271 | 1272 | Build the unit test in MathSender: 1273 | 1274 | ```shell 1275 | # In: MathSender 1276 | fprime-util build --ut 1277 | ``` 1278 | > [!WARNING] 1279 | > Don't forget to add ```--ut``` or else you are just going to build the component again. 1280 | 1281 | ### (Optional) Inspect the generated code 1282 | The unit test build generates some code to support unit testing. The code is located at `MathSender/build-fprime-automatic-native-ut/Components/MathSender`. 1283 | This directory contains two auto-generated classes: 1284 | 1285 | 1. `MathSenderGTestBase`: This is the direct base class of `Tester`. It provides a test interface implemented with Google Test macros. 1286 | 1287 | 2. `MathSenderTesterBase`: This is the direct base class of `MathSenderGTestBase`. 1288 | It provides basic features such as histories of port invocations. It is not specific to Google Test, so you can use this class without Google Test if desired. 1289 | 1290 | You can look at the header files for these generated classes to see what operations they provide. 1291 | In the next sections we will provide some example uses of these operations. 1292 | 1293 | 1294 | ### Summary 1295 | 1296 | In this section you created the stub implementation of a unit test. In the next section you will finish the unit test and run it. 1297 | 1298 | --- 1299 | 1300 | ## Writing Unit Tests Part 2: Completing the Stub & Running the Test 1301 | 1302 | ### In this Section 1303 | 1304 | In this section of the tutorial, you will fill in the stub implementation you created in the last section and run the unit test. 1305 | 1306 | ### Create a Helper Function 1307 | Write a generic helper function so you can reuse code while writing unit tests. 1308 | Start by writing a function signature in `MathSenderTester.hpp` in `MathSender/test/ut`: 1309 | 1310 | ```cpp 1311 | // In: MathSenderTester.hpp 1312 | void testDoMath(MathOp op); 1313 | ``` 1314 | 1315 | Fill out the corresponding function body in `MathSenderTester.cpp`: 1316 | 1317 | ```cpp 1318 | // In: MathSenderTester.cpp 1319 | 1320 | void MathSenderTester :: 1321 | testDoMath(MathOp op) 1322 | { 1323 | // Pick values 1324 | const F32 val1 = 2.0; 1325 | const F32 val2 = 3.0; 1326 | // Send the command 1327 | // pick a command sequence number 1328 | const U32 cmdSeq = 10; 1329 | // send DO_MATH command 1330 | this->sendCmd_DO_MATH(0, cmdSeq, val1, op, val2); 1331 | // retrieve the message from the message queue and dispatch the command to the handler 1332 | this->component.doDispatch(); 1333 | // Verify command receipt and response 1334 | // verify command response was sent 1335 | ASSERT_CMD_RESPONSE_SIZE(1); 1336 | // verify the command response was correct as expected 1337 | ASSERT_CMD_RESPONSE(0, MathSenderComponentBase::OPCODE_DO_MATH, cmdSeq, Fw::CmdResponse::OK); 1338 | // Verify operation request on mathOpOut 1339 | // verify that one output port was invoked overall 1340 | ASSERT_FROM_PORT_HISTORY_SIZE(1); 1341 | // verify that the math operation port was invoked once 1342 | ASSERT_from_mathOpOut_SIZE(1); 1343 | // verify the arguments of the operation port 1344 | ASSERT_from_mathOpOut(0, val1, op, val2); 1345 | // Verify telemetry 1346 | // verify that 3 channels were written 1347 | ASSERT_TLM_SIZE(3); 1348 | // verify that the desired telemetry values were sent once 1349 | ASSERT_TLM_VAL1_SIZE(1); 1350 | ASSERT_TLM_VAL2_SIZE(1); 1351 | ASSERT_TLM_OP_SIZE(1); 1352 | // verify that the correct telemetry values were sent 1353 | ASSERT_TLM_VAL1(0, val1); 1354 | ASSERT_TLM_VAL2(0, val2); 1355 | ASSERT_TLM_OP(0, op); 1356 | // Verify event reports 1357 | // verify that one event was sent 1358 | ASSERT_EVENTS_SIZE(1); 1359 | // verify the expected event was sent once 1360 | ASSERT_EVENTS_COMMAND_RECV_SIZE(1); 1361 | // verify the correct event arguments were sent 1362 | ASSERT_EVENTS_COMMAND_RECV(0, val1, op, val2); 1363 | } 1364 | ``` 1365 | 1366 | ### Explanation 1367 | This function is parameterized over different operations. 1368 | It is divided into five sections: sending the command, checking the command response, checking the output on `mathOpOut`, checking telemetry, and checking events. 1369 | The comments explain what is happening in each section. For further information about the F Prime unit test interface, see the F Prime User's Guide. 1370 | 1371 | Notice that after sending the command to the component, we call the function `doDispatch` on the component. We do this in order to simulate the behavior of the active component in a unit test environment. 1372 | In a flight configuration, the component has its own thread, and the thread blocks on the `doDispatch` call until another thread puts a message on the queue. 1373 | In a unit test context, there is only one thread, so the pattern is to place work on the queue and then call `doDispatch` on the same thread. 1374 | 1375 | There are a couple of pitfalls to watch out for with this pattern: 1376 | 1377 | 1. If you put work on the queue and forget to call `doDispatch`, the work won't get dispatched. 1378 | Likely this will cause a unit test failure. 1379 | 1380 | 2. If you call `doDispatch` without putting work on the queue, the unit test will block until you kill the process (e.g., with control-C). 1381 | 1382 | ### Write a Function to Test ADD 1383 | 1384 | You will now create a function to test the `ADD` command. Add a function signature to MathSenderTester.hpp: 1385 | 1386 | ```cpp 1387 | // In: MathSenderTester.hpp 1388 | void testAddCommand(); 1389 | ``` 1390 | 1391 | Write the corresponding tester function using the helper function you just wrote: 1392 | 1393 | ```cpp 1394 | // In: MathSenderTester.cpp 1395 | void MathSenderTester :: 1396 | testAddCommand() 1397 | { 1398 | this->testDoMath(MathOp::ADD); 1399 | } 1400 | ``` 1401 | 1402 | Write a Google test macro in MathSenderTestMain.cpp and make sure the test macro goes before main: 1403 | 1404 | ```cpp 1405 | // In: MathSenderTestMain.cpp 1406 | TEST(Nominal, AddCommand) { 1407 | MathModule::MathSenderTester tester; 1408 | tester.testAddCommand(); 1409 | } 1410 | ``` 1411 | 1412 | ### Explanation 1413 | The `TEST` macro is an instruction to Google Test to run a test. Without this step, your tests will never run. `Nominal` is the name of a test suite. We put this test in the `Nominal` suite because it addresses nominal (expected) behavior. `AddCommand` is the name of the test. 1414 | Inside the body of the macro, the first line declares a new object `tester` of type `MathSenderTester`. We typically declare a new object for each unit test, so that each test starts in a fresh state. The second line invokes the function `testAddCommand` that we wrote in the previous section. 1415 | 1416 | 1417 | ### Run Your Tests 1418 | Run the test you have written. Make sure to execute the following in ```MathSender```. 1419 | 1420 | ```shell 1421 | # In: MathSender 1422 | fprime-util check 1423 | ``` 1424 | 1425 | As an exercise, try the following: 1426 | 1427 | 1. Change the behavior of the component so that it does something incorrect. 1428 | For example, try adding one to a telemetry value before emitting it. 1429 | 1430 | 2. Rerun the test and observe what happens. 1431 | 1432 | 1433 | ### Check Your Test Coverage 1434 | 1435 | You can also check the coverage that your test covers. The following should also be executed in ```MathSender```. 1436 | 1437 | ```shell 1438 | # In: MathSender 1439 | fprime-util check --coverage 1440 | ``` 1441 | 1442 | In addition to printing out your test coverage overview in the console, the `--coverage` flag will generate a `coverage/` directory that has HTML files showing additional code coverage info. This all uses Gcov under the hood. 1443 | 1444 | 1445 | ### Add more command tests 1446 | Try to follow the pattern given in the previous section to add three more tests, one each for operations `SUB`, `MUL`, and `DIV`. Most of the work should be done in the helper that we already wrote. Each new test requires just a short test function and a short test macro. 1447 | 1448 | Run the tests to make sure everything compiles and the tests pass. 1449 | 1450 | ### Summary 1451 | 1452 | In this section you filled out your unit test implementation stub and ran your unit test. 1453 | 1454 | --- 1455 | 1456 | ## Writing Unit Tests Part 3: Testing the Results 1457 | 1458 | ### In this Section 1459 | In this section of the tutorial, you will add another test into `MathSender/test/ut` and run the new test. 1460 | 1461 | **Add a result test:** 1462 | Add a test for exercising the scenario in which the `MathReceiver` component sends a result back to `MathSender`. 1463 | 1464 | Add the following function signature in the "Tests" section of `MathSenderTester.hpp`: 1465 | 1466 | ```c++ 1467 | // In: MathSender/test/ut/MathSenderTester.hpp 1468 | //! Test receipt of a result 1469 | void testResult(); 1470 | ``` 1471 | 1472 | Add the corresponding function body in `MathSenderTester.cpp`: 1473 | 1474 | ```c++ 1475 | // In: MathSenderTester.cpp 1476 | void MathSenderTester :: 1477 | testResult() 1478 | { 1479 | // Generate an expected result 1480 | const F32 result = 10.0; 1481 | // reset all telemetry and port history 1482 | this->clearHistory(); 1483 | // call result port with result 1484 | this->invoke_to_mathResultIn(0, result); 1485 | // retrieve the message from the message queue and dispatch the command to the handler 1486 | this->component.doDispatch(); 1487 | // verify one telemetry value was written 1488 | ASSERT_TLM_SIZE(1); 1489 | // verify the desired telemetry channel was sent once 1490 | ASSERT_TLM_RESULT_SIZE(1); 1491 | // verify the values of the telemetry channel 1492 | ASSERT_TLM_RESULT(0, result); 1493 | // verify one event was sent 1494 | ASSERT_EVENTS_SIZE(1); 1495 | // verify the expected event was sent once 1496 | ASSERT_EVENTS_RESULT_SIZE(1); 1497 | // verify the expect value of the event 1498 | ASSERT_EVENTS_RESULT(0, result); 1499 | } 1500 | ``` 1501 | **Explanation:** 1502 | This code is similar to the helper function in the previous section. 1503 | The main difference is that it invokes a port directly (the `mathResultIn` port) instead of sending a command. 1504 | 1505 | ### Add the Tests to MathSenderTestMain and Run 1506 | Add the following test macro to `MathSenderTestMain.cpp`: 1507 | 1508 | ```c++ 1509 | // In: MathSenderTestMain.cpp 1510 | TEST(Nominal, Result) { 1511 | MathModule::MathSenderTester tester; 1512 | tester.testResult(); 1513 | } 1514 | ``` 1515 | 1516 | Run the tests: 1517 | 1518 | ```shell 1519 | # In: MathSender 1520 | fprime-util check 1521 | ``` 1522 | 1523 | Again you can try altering something in the component code to see what effect it has on the test output. 1524 | 1525 | ### Summary 1526 | 1527 | In this section, you created another helper function used to look test the received results as seen by the `MathSender`. You ran the test that you should wrote to ensure that it worked. 1528 | 1529 | --- 1530 | 1531 | ## Writing Unit Tests Part 4: Random testing 1532 | 1533 | ### Background 1534 | 1535 | Testing using only numbers that you hard code into your tests can easily leave edge cases untouched can allow you, the programmer, to miss bugs. 1536 | 1537 | F' provides a module called STest that provides helper classes and functions for writing unit tests. As an exercise, use the interface provided by STest/STest/Pick.hpp to pick random values to use in the tests instead of using hard-coded values such as 2.0, 3.0, and 10. 1538 | 1539 | ### In this Section 1540 | 1541 | In this section of the tutorial, you will create test that uses random numbers instead of hard coded numbers. 1542 | 1543 | To incorporate random numbers into the existing tests you have written for `MathSender`, you only need to make a couple small modifications. 1544 | 1545 | **First,** edit `MathSender/test/ut/MathSenderTester.cpp` by adding a `Pick.hpp` to the includes: 1546 | 1547 | ```cpp 1548 | // In: MathSenderTester.cpp 1549 | #include "MathSenderTester.hpp" 1550 | #include "STest/Pick/Pick.hpp" 1551 | ``` 1552 | 1553 | 1554 | **Second,** edit `MathSender/test/ut/MathSenderTester.cpp` to pick random values: 1555 | 1556 | ```cpp 1557 | // In: MathSenderTester.cpp 1558 | // Within: void MathSenderTester::testDoMath(MathOp op) 1559 | // Pick values 1560 | const F32 val1 = STest::Pick::any(); 1561 | const F32 val2 = STest::Pick::any() + STest::Pick::inUnitInterval(); 1562 | ``` 1563 | 1564 | 1565 | **Third,** modify `MathSenderTestMain.cpp` to include `Random.hpp`: 1566 | 1567 | ```cpp 1568 | // In: MathSenderTestMain.cpp 1569 | #include "MathSenderTester.hpp" 1570 | #include "STest/Random/Random.hpp" 1571 | ``` 1572 | 1573 | 1574 | **Fourth,** add the following line to the main function of `MathSenderTestMain.cpp`, just *before* the return statement: 1575 | 1576 | ```cpp 1577 | // In: MathSenderTestMain.cpp 1578 | // Within: int main(){ 1579 | STest::Random::seed(); 1580 | ``` 1581 | 1582 | **Fifth,** modify `MathSender/CMakeLists.txt` to include STest as a build dependency: 1583 | 1584 | ```cmake 1585 | # In: /MathSender/CMakeLists.txt 1586 | # Above: register_fprime_ut() 1587 | set(UT_MOD_DEPS STest) 1588 | ``` 1589 | **Sixth,** recompile and rerun the tests. 1590 | 1591 | ```shell 1592 | # In: MathSender 1593 | fprime-util check 1594 | ``` 1595 | 1596 | Go to MathProject/build-fprime-automatic-native-ut/Components/MathSender and inspect the file `seed-history`. This file is a log of random seed values. Each line represents the seed used in the corresponding run. 1597 | 1598 | **Fixing the Random Seed:** 1599 | Sometimes you may want to run a test with a particular seed value, e.g., for replay debugging. To do this, put the seed value into a file `seed` in the same directory as `seed-history`. If the file seed exists, then STest will use the seed it contains instead of generating a new seed. 1600 | 1601 | Try the following: 1602 | 1603 | 1. Copy the last value S of ```seed-history``` into ```seed```. 1604 | 1605 | 2. In Components/MathSender, re-run the unit tests a few times. 1606 | 1607 | 3. Inspect ```MathProject/build-fprime-automatic-native-ut/Components/MathSender/seed-history```. You should see that the value S was used in the runs you just did (corresponding to the last few entries in seed-history). 1608 | 1609 | ### Summary 1610 | 1611 | In this section you incorporated random testing into your existing tests. 1612 | 1613 | --- 1614 | 1615 | ## Writing Unit Tests Part 5: Creating the Implementation Stub 1616 | 1617 | ### In this Section 1618 | 1619 | In this section of the tutorial, you will be repeating the steps you used to create an implementation stub for `MathSender`. 1620 | 1621 | Generate a stub implementation of the unit tests. 1622 | 1623 | ```shell 1624 | # In: MathReceiver 1625 | fprime-util impl --ut 1626 | ``` 1627 | > [!NOTE] 1628 | > These commands may take a while to run. 1629 | 1630 | You have just generated three new files `MathReceiverTester.template.cpp`, `MathReceiverTester.template.hpp`, and `MathReceiverTestMain.template.cpp` in the `MathReceiver/test/ut/` directory. Remove the `template` suffix, so we can use them as our initial test implementation: 1631 | 1632 | ```bash 1633 | # In MathReceiver/test/ut 1634 | mv MathReceiverTester.template.hpp MathReceiverTester.hpp 1635 | mv MathReceiverTester.template.cpp MathReceiverTester.cpp 1636 | mv MathReceiverTestMain.template.cpp MathReceiverTestMain.cpp 1637 | ``` 1638 | 1639 | Add the unit test to the build. Do so by uncommenting the appropriate lines in the CMakeLists.txt. The UT section should now look like the following: 1640 | 1641 | ```cmake 1642 | # In: MathReceiver/CMakeLists.txt 1643 | ### Unit Tests ### 1644 | set(UT_SOURCE_FILES 1645 | "${CMAKE_CURRENT_LIST_DIR}/MathReceiver.fpp" 1646 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/MathReceiverTestMain.cpp" 1647 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/MathReceiverTester.cpp" 1648 | ) 1649 | set(UT_MOD_DEPS 1650 | STest 1651 | ) 1652 | set(UT_AUTO_HELPERS ON) 1653 | register_fprime_ut() 1654 | ``` 1655 | 1656 | Build the unit test in MathReceiver: 1657 | 1658 | ```shell 1659 | # In: MathReceiver 1660 | fprime-util build --ut 1661 | ``` 1662 | > [!WARNING] 1663 | > Don't forget to add ```--ut``` or else you are just going to build the component again. 1664 | 1665 | 1666 | ### Preparing for Random Testing 1667 | 1668 | Complete the following steps to prepare for random testing. 1669 | 1670 | 1671 | ```cpp 1672 | // In: MathReceiverTester.cpp 1673 | #include "MathReceiverTester.hpp" 1674 | #include "STest/Pick/Pick.hpp" 1675 | ``` 1676 | 1677 | ```cpp 1678 | // In: MathReceiverTestMain.cpp 1679 | #include "MathReceiverTester.hpp" 1680 | #include "STest/Random/Random.hpp" 1681 | ``` 1682 | 1683 | ```cpp 1684 | // In: MathReceiverTestMain.cpp 1685 | // Within: int main(){ 1686 | STest::Random::seed(); 1687 | ``` 1688 | 1689 | ```cmake 1690 | # In: /MathReceiver/CMakeLists.txt 1691 | # Above: register_fprime_ut() 1692 | set(UT_MOD_DEPS STest) 1693 | ``` 1694 | 1695 | ### Summary 1696 | 1697 | In this section you have setup implementation stubs to begin writing unit tests for `MathReceiver`. 1698 | 1699 | --- 1700 | 1701 | ## Writing Unit Tests Part 6: Writing Helper Functions 1702 | 1703 | ### In this Section 1704 | In this section of the tutorial, you will write helper functions to tests various function of `MathReceiver`. 1705 | 1706 | ### Add a ThrottleState enum class 1707 | Add the following code to the beginning of the 1708 | `MathReceiverTester` class in `MathReceiverTester.hpp`: 1709 | 1710 | ```c++ 1711 | // In: MathReceiverTester.hpp 1712 | private: 1713 | 1714 | // ---------------------------------------------------------------------- 1715 | // Types 1716 | // ---------------------------------------------------------------------- 1717 | 1718 | enum class ThrottleState { 1719 | THROTTLED, 1720 | NOT_THROTTLED 1721 | }; 1722 | ``` 1723 | 1724 | This code defines a C++ enum class for recording whether an 1725 | event is throttled. 1726 | 1727 | ### Add helper functions 1728 | Add each of the functions described below to the "Helper methods" section of `MathReceiverTester.cpp`. 1729 | For each function, you must add the corresponding function prototype to `MathReceiverTester.hpp`. 1730 | After adding each function, compile the unit tests to make sure that everything still compiles. Fix any errors that occur. 1731 | 1732 | Add a `pickF32Value` function. 1733 | 1734 | ```c++ 1735 | // In: MathReceiverTester.cpp 1736 | F32 MathReceiverTester :: 1737 | pickF32Value() 1738 | { 1739 | const F32 m = 10e6; 1740 | return m * (1.0 - 2 * STest::Pick::inUnitInterval()); 1741 | } 1742 | ``` 1743 | > [!WARNING] 1744 | > Remember to add a function signature in `MathReceiverTester.hpp`. 1745 | 1746 | This function picks a random `F32` value in the range 1747 | _[ -10^6, 10^6 ]_. 1748 | 1749 | 1750 | At this point, it is a good to check the build. Use the following to check the build: 1751 | 1752 | ```shell 1753 | # In: MathReceiver 1754 | fprime-util build --ut -j4 1755 | ``` 1756 | 1757 | ### Add a Set Factor function 1758 | 1759 | Copy and paste in the code below to create the `setFactor` function 1760 | 1761 | ```c++ 1762 | // In MathReceiverTester.cpp 1763 | void MathReceiverTester :: 1764 | setFactor( 1765 | F32 factor, 1766 | ThrottleState throttleState 1767 | ) 1768 | { 1769 | // clear history 1770 | this->clearHistory(); 1771 | // set the parameter 1772 | this->paramSet_FACTOR(factor, Fw::ParamValid::VALID); 1773 | const U32 instance = STest::Pick::any(); 1774 | const U32 cmdSeq = STest::Pick::any(); 1775 | this->paramSend_FACTOR(instance, cmdSeq); 1776 | if (throttleState == ThrottleState::NOT_THROTTLED) { 1777 | // verify the parameter update notification event was sent 1778 | ASSERT_EVENTS_SIZE(1); 1779 | ASSERT_EVENTS_FACTOR_UPDATED_SIZE(1); 1780 | ASSERT_EVENTS_FACTOR_UPDATED(0, factor); 1781 | } 1782 | else { 1783 | ASSERT_EVENTS_SIZE(0); 1784 | } 1785 | } 1786 | ``` 1787 | > [!NOTE] 1788 | > Make sure that set factor is below where you defined `ThrottleSate` and remember to add a function signature in `MathReceiverTester.hpp`. 1789 | 1790 | ### Explanation 1791 | 1792 | This function does the following: 1793 | 1794 | 1. Clear the test history. 1795 | 1796 | 2. Send a command to the component to set the `FACTOR` parameter to the value `factor`. 1797 | 1798 | 3. If `throttleState` is `NOT_THROTTLED`, then check that the event was emitted. 1799 | Otherwise check that the event was throttled (not emitted). 1800 | 1801 | **Build** to make sure everything is working. 1802 | 1803 | ### Create a Compute Result Function 1804 | 1805 | Add a function `computeResult` to `MathReceiverTester.cpp`. 1806 | 1807 | ```c++ 1808 | // In: MathReceiverTester.cpp 1809 | F32 MathReceiverTester :: 1810 | computeResult( 1811 | F32 val1, 1812 | MathOp op, 1813 | F32 val2, 1814 | F32 factor 1815 | ) 1816 | { 1817 | F32 result = 0; 1818 | switch (op.e) { 1819 | case MathOp::ADD: 1820 | result = val1 + val2; 1821 | break; 1822 | case MathOp::SUB: 1823 | result = val1 - val2; 1824 | break; 1825 | case MathOp::MUL: 1826 | result = val1 * val2; 1827 | break; 1828 | case MathOp::DIV: 1829 | result = val1 / val2; 1830 | break; 1831 | default: 1832 | FW_ASSERT(0, op.e); 1833 | break; 1834 | } 1835 | result *= factor; 1836 | return result; 1837 | } 1838 | ``` 1839 | > [!WARNING] 1840 | > Don't forget to add a function signature in `MathReceiverTester.hpp`. 1841 | 1842 | This function carries out the math computation of the math component. 1843 | By running this function and comparing, we can check the output of the component. 1844 | 1845 | Build to make sure everything is working. 1846 | 1847 | ### Create a Do Math Op Functions 1848 | 1849 | Add a `doMathOp` function to `MathReceiverTester.cpp`. 1850 | 1851 | ```c++ 1852 | // In: MathReceiverTester.cpp 1853 | void MathReceiverTester :: 1854 | doMathOp( 1855 | MathOp op, 1856 | F32 factor 1857 | ) 1858 | { 1859 | 1860 | // pick values 1861 | const F32 val1 = pickF32Value(); 1862 | const F32 val2 = pickF32Value(); 1863 | 1864 | // clear history 1865 | this->clearHistory(); 1866 | 1867 | // invoke operation port with add operation 1868 | this->invoke_to_mathOpIn(0, val1, op, val2); 1869 | // invoke scheduler port to dispatch message 1870 | const U32 context = STest::Pick::any(); 1871 | this->invoke_to_schedIn(0, context); 1872 | 1873 | // verify the result of the operation was returned 1874 | 1875 | // check that there was one port invocation 1876 | ASSERT_FROM_PORT_HISTORY_SIZE(1); 1877 | // check that the port we expected was invoked 1878 | ASSERT_from_mathResultOut_SIZE(1); 1879 | // check that the component performed the operation correctly 1880 | const F32 result = computeResult(val1, op, val2, factor); 1881 | ASSERT_from_mathResultOut(0, result); 1882 | 1883 | // verify events 1884 | 1885 | // check that there was one event 1886 | // if you're dviding by zero, there may be two events ;) 1887 | ASSERT_EVENTS_SIZE(1); 1888 | // check that it was the op event 1889 | ASSERT_EVENTS_OPERATION_PERFORMED_SIZE(1); 1890 | // check that the event has the correct argument 1891 | ASSERT_EVENTS_OPERATION_PERFORMED(0, op); 1892 | 1893 | // verify telemetry 1894 | 1895 | // check that one channel was written 1896 | ASSERT_TLM_SIZE(1); 1897 | // check that it was the op channel 1898 | ASSERT_TLM_OPERATION_SIZE(1); 1899 | // check for the correct value of the channel 1900 | ASSERT_TLM_OPERATION(0, op); 1901 | 1902 | } 1903 | ``` 1904 | > [!WARNING] 1905 | > Don't forget to add a function signature in `MathReceiverTester.hpp`. 1906 | 1907 | This function is similar to the `doMath` helper function that we wrote for the `MathSender` component. 1908 | Notice that the method for invoking a port is different. 1909 | Since the component is queued, we don't call `doDispatch` directly. Instead we invoke `schedIn`. 1910 | 1911 | **Build** before moving onto the next section. 1912 | 1913 | 1914 | --- 1915 | 1916 | ## Writing Unit Tests Part 7: Writing the Tests 1917 | 1918 | ### In this Section 1919 | In this section of the tutorial, you will write tests that make use of the helper functions you wrote in the last section of the tutorial. 1920 | 1921 | ### Preface 1922 | 1923 | For each of the tests described below, you must add the corresponding function prototype to `MathReceiverTester.hpp` and the corresponding test macro to `main.cpp`. If you can't remember how to do it, look back at the `MathSender` examples. After writing each test, run all the tests and make sure that they pass. 1924 | 1925 | ### Write an ADD test 1926 | Add the following function to the `Tests` section of `MathReceiverTester.cpp`: 1927 | 1928 | ```c++ 1929 | // In: MathReceiverTester.cpp 1930 | void MathReceiverTester :: 1931 | testAdd() 1932 | { 1933 | // Set the factor parameter by command 1934 | const F32 factor = pickF32Value(); 1935 | this->setFactor(factor, ThrottleState::NOT_THROTTLED); 1936 | // Do the add operation 1937 | this->doMathOp(MathOp::ADD, factor); 1938 | } 1939 | ``` 1940 | > [!WARNING] 1941 | > Don't forget to add a function signature in `MathReceiverTester.hpp`. 1942 | 1943 | `testAdd()` calls the `setFactor` helper function to set the factor parameter. Then it calls the `doMathOp` function to do a math operation. 1944 | 1945 | ### Write a SUB test 1946 | Add the following function to the `Tests` section of `MathReceiverTester.cpp`: 1947 | 1948 | ```c++ 1949 | // In: MathReceiverTester.cpp 1950 | void MathReceiverTester :: 1951 | testSub() 1952 | { 1953 | // Set the factor parameter by loading parameters 1954 | const F32 factor = pickF32Value(); 1955 | this->paramSet_FACTOR(factor, Fw::ParamValid::VALID); 1956 | this->component.loadParameters(); 1957 | // Do the operation 1958 | this->doMathOp(MathOp::SUB, factor); 1959 | } 1960 | ``` 1961 | 1962 | `testSub()` is similar to `testAdd`, but it shows another way to set a parameter. `testAdd` shows how to set a parameter by command. You can also set a parameter by initialization, as follows: 1963 | 1964 | 1. Call the `paramSet` function as shown. 1965 | This function sets the parameter value in the part of the test harness that mimics the behavior of the parameter database component. 1966 | 1967 | 2. Call the `loadParameters` function as shown. 1968 | In flight, the function `loadParameters` is typically called at the start of FSW to load the parameters from the database; here it loads the parameters from the test harness. There is no command to update a parameter, so `parameterUpdated` is not called, and no event is emitted. 1969 | 1970 | As before, after setting the parameter you call `doMathOp` to do the operation. 1971 | 1972 | **Write a MUL test:** 1973 | This test is the same as the ADD test, except that it uses MUL instead of add. 1974 | 1975 | **Write a DIV test:** 1976 | This test is the same as the SUB test, except that it uses DIV instead of SUB. 1977 | 1978 | **Write a throttle test:** 1979 | Add the following constant definition to the top of the `MathReceiverTester.cpp` file: 1980 | 1981 | ```C++ 1982 | // In: MathReceiverTester.cpp 1983 | #define CMD_SEQ 42 1984 | ``` 1985 | ### Write a Throttle Test 1986 | 1987 | Add the following function to the "Tests" section of `MathReceiverTester.cpp`: 1988 | 1989 | ```c++ 1990 | // In: MathReceiverTester.cpp 1991 | void MathReceiverTester :: 1992 | testThrottle() 1993 | { 1994 | 1995 | // send the number of commands required to throttle the event 1996 | // Use the autocoded value so the unit test passes if the 1997 | // throttle value is changed 1998 | const F32 factor = pickF32Value(); 1999 | for ( 2000 | U16 cycle = 0; 2001 | cycle < MathReceiverComponentBase::EVENTID_FACTOR_UPDATED_THROTTLE; 2002 | cycle++ 2003 | ) { 2004 | this->setFactor(factor, ThrottleState::NOT_THROTTLED); 2005 | } 2006 | 2007 | // Event should now be throttled 2008 | this->setFactor(factor, ThrottleState::THROTTLED); 2009 | 2010 | // send the command to clear the throttle 2011 | this->sendCmd_CLEAR_EVENT_THROTTLE(TEST_INSTANCE_ID, CMD_SEQ); 2012 | // invoke scheduler port to dispatch message 2013 | const U32 context = STest::Pick::any(); 2014 | this->invoke_to_schedIn(0, context); 2015 | // verify clear event was sent 2016 | ASSERT_EVENTS_SIZE(1); 2017 | ASSERT_EVENTS_THROTTLE_CLEARED_SIZE(1); 2018 | 2019 | // Throttling should be cleared 2020 | this->setFactor(factor, ThrottleState::NOT_THROTTLED); 2021 | 2022 | } 2023 | ``` 2024 | 2025 | ### Explanation 2026 | This test first loops over the throttle count, which is stored for us in the constant `EVENTID_FACTOR_UPDATED_THROTTLE` of the `MathReceiver` component base class. On each iteration, it calls `setFactor`. At the end of this loop, the `FACTOR_UPDATED` event should be throttled. 2027 | 2028 | Next the test calls `setFactor` with a second argument of `ThrottleState::THROTTLED`. This code checks that the event is throttled. 2029 | 2030 | Next the test sends the command `CLEAR_EVENT_THROTTLE`, checks for the corresponding notification event, and checks that the throttling is cleared. 2031 | 2032 | Add your tests to `MathReceiverTestMain.cpp` so that the tests run when `fprime-util check' is called. 2033 | 2034 | Here is how to include `testAdd` to `MathReceiverTestMain.cpp`. Follow this pattern to include any other unit tests you wrote: 2035 | 2036 | ```cpp 2037 | // In: MathReceiverTestMain.cpp 2038 | TEST(Nominal, AddCommand) { 2039 | MathModule::MathReceiverTester tester; 2040 | tester.testAdd(); 2041 | } 2042 | ``` 2043 | 2044 | See if your tests are working and trouble shoot any errors: 2045 | 2046 | ```shell 2047 | # In: MathReceiver 2048 | fprime-util check 2049 | ``` 2050 | 2051 | --- 2052 | 2053 | ## Adding Telemetry 2054 | 2055 | ### In this Section 2056 | 2057 | In this section of the tutorial, you will add a telemetry channel to report the number of math operations the `MathReceiver` has performed. 2058 | 2059 | Before reading these steps, do your best to look at the existing files in this tutorial and implement a telemetry channel on your own. 2060 | 2061 | 1. Add a telemetry channel to `MathReceiver.fpp`: 2062 | 2063 | ```fpp 2064 | # In: MathReceiver.fpp, under the Telemetry section 2065 | @ Number of math operations 2066 | telemetry NUMBER_OF_OPS: U32 2067 | ``` 2068 | **Explanation:** Here you defined a telemetry channel which you arbitrarily named `NUMBER_OF_OPS` which carries a 32 bit unsigned integer. 2069 | 2070 | 2. Add a member variable to `MathReceiver.hpp`: 2071 | 2072 | ```cpp 2073 | // In: MathReceiver.hpp 2074 | // Under: PRIVATE 2075 | U32 numMathOps; 2076 | ``` 2077 | 2078 | 3. Update the constructor so that it initializes `numMathOps` to zero: 2079 | 2080 | ```cpp 2081 | // In: MathReceiver.cpp 2082 | // Under: Construction, Initialization, and Destruction 2083 | MathReceiver :: 2084 | MathReceiver( 2085 | const char *const compName 2086 | ) : MathReceiverComponentBase(compName), 2087 | numMathOps(0) 2088 | { 2089 | 2090 | } 2091 | ``` 2092 | 2093 | 4. Increment numMathOps: 2094 | 2095 | ```cpp 2096 | // In: MathReceiver.cpp 2097 | // Within mathOpIn_handler 2098 | numMathOps++; 2099 | ``` 2100 | 2101 | 5. Emit telemetry: 2102 | ```cpp 2103 | // In: MathReceiver.cpp 2104 | // Within: mathOpIn_handler 2105 | // After: numMathOps++ 2106 | this->tlmWrite_NUMBER_OF_OPS(numMathOps); 2107 | ``` 2108 | > [!NOTE] 2109 | > This function will get autocoded by FPP since we defined the telemetry channel. 2110 | 2111 | 6. Add the channel to the pre-existing MathReceiver packet in `MathDeploymentPackets.xml`: 2112 | 2113 | ```xml 2114 | 2115 | 2116 | 2117 | 2118 | 2119 | 2120 | ``` 2121 | 2122 | 2123 | 7. Build and test: 2124 | 2125 | ```shell 2126 | # In: MathProject 2127 | fprime-util build -j4 2128 | fprime-gds 2129 | ``` 2130 | 2131 | Send a command and verify that the channel gets value 1. 2132 | 2133 | Write some unit tests to prove that this channel is working. 2134 | 2135 | ### Summary 2136 | 2137 | In this section you defined a telemetry channel and implemented a new variable, that will be sent through the channel. 2138 | 2139 | --- 2140 | 2141 | ## Error Handling 1: Critical Thinking 2142 | 2143 | ### Background 2144 | On a flight mission, even a short timeout, let alone a system crash, can be mission critical. It is imperative that programmer account for as many possible error or faults as possible so avoidable errors are prevented. 2145 | 2146 | 2147 | Think about what will happen if the floating-point math operation performed by `MathReceiver` causes an error. 2148 | For example, suppose that `mathOpIn` is invoked with `op = DIV` and `val2 = 0.0`. 2149 | What will happen? 2150 | As currently designed and implemented, the `MathReceiver` component will perform the requested operation. 2151 | On some systems the result will be `INF` (floating-point infinity). 2152 | In this case, the result will be sent back to `MathSender` and reported in the usual way. 2153 | On other systems, the hardware could issue a floating-point exception. 2154 | 2155 | Suppose you wanted to handle the case of division by zero explicitly. 2156 | How would you change the design? 2157 | Here are some questions to think about: 2158 | 2159 | 1. How would you check for division by zero? 2160 | Note that `val2 = 0.0` is not the only case in which a division 2161 | by zero error can occur. 2162 | It can also occur for very small values of `val2`. 2163 | 2164 | 2. Should the error be caught in `MathSender` or `MathReceiver`? 2165 | 2166 | 3. Suppose the design says that `MathSender` catches the error, and so never sends requests to `MathReceiver` to divide by zero. 2167 | What if anything should `MathReceiver` do if it receives a divide by zero request? 2168 | Carry out the operation normally? 2169 | Emit a warning? 2170 | Fail a FSW assertion? 2171 | 2172 | 4. If the error is caught by `MathReceiver`, does the interface between the components have to change? 2173 | If so, how? 2174 | What should `MathSender` do if `MathReceiver` reports an error instead of a valid result? 2175 | 2176 | Try to revise the MathSender and MathReceiver components to implement your ideas. 2177 | Challenge yourself to add unit tests covering the new behavior. 2178 | 2179 | The next section gives one idea of how to do some error handling for the divide by zero case. Before looking at it, try to solve the problem on your own and compare against the method shown in this tutorial. 2180 | 2181 | 2182 | --- 2183 | 2184 | ## Error Handling 2: One Solution 2185 | 2186 | ### Example Solution 2187 | 2188 | Below is a basic and incomplete solution to the divide by zero problem presented in the previous section. 2189 | 2190 | The solution works as follows: Use an if statement to catch the case that `val2` (the denominator) is zero. 2191 | In the case that `val2` is zero, do nothing with the operands and report the error through an event. 2192 | 2193 | Use an if statement in `MathReceiver.cpp` to catch when the denominator is zero: 2194 | 2195 | ```cpp 2196 | // In: MathReceiver.cpp 2197 | F32 res = 0.0; 2198 | switch (op.e) { 2199 | case MathOp::ADD: 2200 | res = val1 + val2; 2201 | break; 2202 | case MathOp::SUB: 2203 | res = val1 - val2; 2204 | break; 2205 | case MathOp::MUL: 2206 | res = val1 * val2; 2207 | break; 2208 | case MathOp::DIV: 2209 | if ( val2 == 0 ){ 2210 | 2211 | break; 2212 | } 2213 | res = val1 / val2; 2214 | break; 2215 | default: 2216 | FW_ASSERT(0, op.e); 2217 | break; 2218 | } 2219 | ``` 2220 | > [!NOTE] 2221 | > Technically speaking, this solution will prevent the error, but it would be good to output some error message before throwing away the operands and returning the default `res`. 2222 | 2223 | Create an event to notify that a divide by zero command was received by the `MathReceiver`: 2224 | 2225 | ```fpp 2226 | # In: MathRecevier.fpp 2227 | @ Commanded to divide by zero 2228 | event DIVIDE_BY_ZERO \ 2229 | severity activity high \ 2230 | id 3 \ 2231 | format "ERROR: Received zero as denominator. Opperands dropped." 2232 | ``` 2233 | > [!NOTE] 2234 | > Write your own error message between the quotes after `format`! 2235 | 2236 | Add your even into the case where `MathOp::DIV` and `val2` is 0: 2237 | 2238 | ```cpp 2239 | case MathOp::DIV: 2240 | //step 2 2241 | if ( val2 == 0 ){ 2242 | this->log_ACTIVITY_HI_DIVIDE_BY_ZERO(); 2243 | break; 2244 | } 2245 | ``` 2246 | 2247 | ### Summary 2248 | You just created a way to not only handle the case where `MathReceiver` is asked to divide by 0, you also created an event to report that an error has occurred. As a challenge, try to handle more of the cases and problems discussed in [Error handling 1](#error-handling-1-critical-thinking). 2249 | 2250 | ### Congratulations!!! 2251 | 2252 | You have **finished** the MathComponent Tutorial. 2253 | You have now experienced a significant part of F' and are ready to start building your own deployments. 2254 | 2255 | -------------------------------------------------------------------------------- /project.cmake: -------------------------------------------------------------------------------- 1 | # This CMake file is intended to register project-wide objects so they can be 2 | # reused easily between deployments, but also by other projects. 3 | 4 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Components/") 5 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Types/") 6 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Ports/") 7 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/MathDeployment/") 8 | -------------------------------------------------------------------------------- /settings.ini: -------------------------------------------------------------------------------- 1 | [fprime] 2 | project_root: . 3 | framework_path: ./fprime 4 | --------------------------------------------------------------------------------