├── .clang-format ├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── SystemReference ├── CMakeLists.txt ├── Com │ ├── CMakeLists.txt │ └── XBee │ │ ├── CMakeLists.txt │ │ ├── XBee.cpp │ │ ├── XBee.fpp │ │ ├── XBee.hpp │ │ ├── docs │ │ └── sdd.md │ │ └── test │ │ └── ut │ │ ├── TestMain.cpp │ │ ├── Tester.cpp │ │ └── Tester.hpp ├── Gnc │ ├── CMakeLists.txt │ └── Imu │ │ ├── CMakeLists.txt │ │ ├── Imu.cpp │ │ ├── Imu.fpp │ │ ├── Imu.hpp │ │ ├── docs │ │ ├── img │ │ │ └── imu.png │ │ └── sdd.md │ │ └── test │ │ └── ut │ │ ├── TestMain.cpp │ │ ├── Tester.cpp │ │ └── Tester.hpp ├── Payload │ ├── CMakeLists.txt │ └── Camera │ │ ├── CMakeLists.txt │ │ ├── Camera.cpp │ │ ├── Camera.fpp │ │ ├── Camera.hpp │ │ ├── docs │ │ ├── IntegrationGuide.md │ │ ├── img │ │ │ └── camera.png │ │ └── sdd.md │ │ └── test │ │ └── ut │ │ ├── TEST_1.mov │ │ ├── TestMain.cpp │ │ ├── Tester.cpp │ │ └── Tester.hpp ├── Top │ ├── .gitignore │ ├── CMakeLists.txt │ ├── Main.cpp │ ├── SystemReferenceTelemetryPackets.fppi │ ├── SystemReferenceTopologyDefs.cpp │ ├── SystemReferenceTopologyDefs.hpp │ ├── instances.fpp │ └── topology.fpp └── settings.ini ├── bin ├── docker-run ├── docker-run.bat ├── docker-setup └── docker-setup.bat ├── docs ├── course │ ├── appendix-1.md │ ├── appendix-2.md │ ├── appendix-3.md │ ├── component-implementation.md │ ├── hardware.md │ ├── introduction.md │ ├── pdfs │ │ ├── appendix-1.pdf │ │ ├── appendix-2.pdf │ │ ├── appendix-3.pdf │ │ ├── component-implementation.pdf │ │ ├── hardware.pdf │ │ ├── introduction.pdf │ │ ├── prerequisites.pdf │ │ ├── requirements-and-design.pdf │ │ ├── teams.pdf │ │ └── topology-integration.pdf │ ├── prerequisites.md │ ├── requirements-and-design.md │ ├── teams.md │ ├── topology-integration.md │ └── unit-testing.md ├── img │ ├── rancher-config.png │ ├── rancher-running.png │ ├── wiring-diagram-m1.png │ └── wiring-diagram.png └── integration │ ├── camera │ ├── building-system-ref-with-libcamera.md │ ├── compiling-libcamera.md │ └── example.md │ ├── gnc-sensor-integration.md │ └── xbee-radio-integration.md ├── ground-tools ├── README.md ├── process_raw_image.py └── requirements.txt ├── libcamera-aarch32.txt └── libcamera-aarch64.txt /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Chromium 3 | IndentWidth: 4 4 | ColumnLimit: 120 5 | AccessModifierOffset: -2 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Ensures consistent behavior when mapping in and out of docker environments 2 | # across systems including Windows 3 | * lf 4 | bin/docker-run.bat crlf 5 | bin/docker-setup.bat crlf 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | venv 4 | **/logs 5 | **/build-artifacts 6 | **/build-fprime-* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "fprime"] 2 | path = fprime 3 | url = https://github.com/nasa/fprime.git 4 | branch = devel 5 | 6 | [submodule "libcamera"] 7 | path = libcamera 8 | url = https://github.com/raspberrypi/libcamera.git 9 | branch = main -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # F´ System Reference: A Reference Project Using an Open-Source Flight Software Framework 2 | 3 | ## Table of Contents 4 | 1. [About The Project](#about-the-project) 5 | 2. [Getting Started](#getting-started) 6 | 3. [Device Wiring](#Device-Wiring) 7 | 8 | # About The Project 9 | The purpose of this project is to provide an example embedded software application that uses the F´ framework. 10 | The System Reference consists of several subsystems: 11 | - Payload 12 | - Guidance Navigation and Control (GNC) 13 | - Communication 14 | 15 | ## Built With 16 | - [F Prime](https://github.com/nasa/fprime) 17 | - [ARM GNU Toolchain](https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads) 18 | - [libcamera](https://www.raspberrypi.com/documentation/computers/camera_software.html) 19 | 20 | # Getting Started 21 | 22 | ## Requirements 23 | The system reference depends on several items, before the user attempts to clone the project they should ensure that they have the listed requirements below before proceeding. 24 | 25 | Requirements: 26 | 1. Linux OS 27 | 2. libcamera 28 | - For libcamera dependencies, please see the list [here](https://github.com/raspberrypi/libcamera#dependencies). Ensure that the required dependencies are installed before building libcamera (steps for building libcamera are in the [Setup libcamera](#build-libcamera)) section below. 29 | - In addition, will need pkg-config installed 30 | 3. F Prime Environment 31 | - Python virtual environment with fprime-fpp, fprime-tools, and fprime-gds installed 32 | 4. Cross-Compilation tools for ARM 33 | - For steps on how to install all the dependencies required for cross-compiling for different architectures, see the [F´ Cross-Compilation Setup Tutorial](https://github.com/nasa/fprime/blob/devel/docs/Tutorials/CrossCompilationSetup/CrossCompilationSetupTutorial.md). 34 | 5. Raspberry Pi with Bullseye OS 35 | - In order to run the System Reference with libcamera, Bullseye OS is required. In addition, the firmware version may need to be upgraded in the event the system is configured to use the legacy camera stack. 36 | 37 | 38 | ## Clone the F´ System Reference 39 | ``` 40 | git clone https://github.com/fprime-community/fprime-system-reference.git 41 | ``` 42 | 43 | ## Setup libcamera 44 | libcamera is a library that provides a C++ API to applications that enables them to configure Raspberry Pi cameras and request image frames. The steps below cover how to build libcamera (for both native linux and ARM Linux), as well as how to build the System Reference with libcamera included. 45 | 46 | ### Build libcamera 47 | In order to run the camera subsystem, libcamera first has to be built. See the [Compiling libcamera for Native Linux](./docs/integration/camera/compiling-libcamera.md#compiling-libcamera-for-native-linux) guide for steps on how to build libcamera for native Linux. Otherwise, if you are looking to run the System Reference on ARM Linux, see the [Compiling libcamera for ARM Linux](./docs/integration/camera/compiling-libcamera.md#cross-compiling-libcamera-for-arm-linux) guide in order to cross-compile the library. 48 | 49 | ## Building the System Reference 50 | 51 | ### Building the System Reference with libcamera 52 | Once the libcamera library is built, you can build the System Reference with libcamera included. This is required in order to run the camera subsystem, without doing so, the System Reference will not detect a camera and configure, process, or save images. For steps on how to build the System Reference with libcamera, see the guide [here](./docs/integration/camera/building-system-ref-with-libcamera.md). 53 | 54 | ### Building the System Reference (no libcamera) 55 | To build the System Reference without libcamera, in a terminal run the below commands. 56 | 57 | Note: Without libcamera, the System Reference will not detect a camera and configure, process, or save images. 58 | 59 | For Native Linux: 60 | ```bash 61 | fprime-util generate 62 | fprime-util build 63 | ``` 64 | 65 | If compiling for ARM Linux, see the steps in the [Cross Compilation Tutorial](https://github.com/nasa/fprime/blob/devel/docs/Tutorials/CrossCompilationSetup/CrossCompilationTutorial.md). 66 | 67 | 68 | ## Upload to the Raspberry Pi 69 | To run the ground system: 70 | ``` 71 | fprime-gds -n --dictionary raspberrypi/dict/SystemReferenceTopologyDictionary.json 72 | ``` 73 | 74 | To copy the binary to the Raspberry Pi 75 | ``` 76 | scp build-artifacts/raspberrypi/bin/SystemReference pi@:~ 77 | ``` 78 | #### Running System Reference on the Raspberry Pi 79 | After logging into the Raspberry Pi via SSH, you can run the Ref deployment as follows: 80 | ``` 81 | ./SystemReference -a -p 50000 82 | ``` 83 | 84 | ## Examples 85 | - [Camera Component Tutorial](./docs//integration/camera/example.md) - Tutorial on how to capture frames, downlink the image data to the ground, and how to process it as PNG or JPEG. 86 | 87 | ### RPI Wiring 88 | ![wiring diagram](./docs/img/wiring-diagram.png) 89 | -------------------------------------------------------------------------------- /SystemReference/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #### 2 | # 'SystemReference' Deployment: 3 | # 4 | # This sets up the build for the 'SystemRef' Application, including the custom reference 5 | # components. In addition, it imports FPrime.cmake, which includes the core F Prime 6 | # components. 7 | # 8 | # This file has several sections. 9 | # 10 | # 1. Header Section: define basic properties of the build 11 | # 2. F prime core: includes all F prime core components, and build-system properties 12 | # 3. Local subdirectories: contains all deployment specific directory additions 13 | #### 14 | 15 | ## 16 | # Section 1: Basic Project Setup 17 | # 18 | # This contains the basic project information. Specifically, a cmake version and 19 | # project definition. 20 | ## 21 | cmake_minimum_required(VERSION 3.13) 22 | cmake_policy(SET CMP0048 NEW) 23 | project(SystemReference VERSION 1.0.0 LANGUAGES C CXX) 24 | 25 | ## 26 | # Section 2: F prime Core 27 | # 28 | # This includes all of the F prime core components, and imports the make-system. F prime core 29 | # components will be placed in the F-Prime binary subdirectory to keep them from 30 | # colliding with deployment specific items. 31 | ## 32 | include("${FPRIME_FRAMEWORK_PATH}/cmake/FPrime.cmake") 33 | # NOTE: register custom targets between these two lines 34 | include("${FPRIME_FRAMEWORK_PATH}/cmake/FPrime-Code.cmake") 35 | ## 36 | # Section 3: Components and Topology 37 | # 38 | # This section includes deployment specific directories. This allows use of non- 39 | # core components in the topology, which is also added here. 40 | ## 41 | 42 | # needed for libcamera 43 | set(CMAKE_CXX_STANDARD 17) 44 | 45 | # Add Topology subdirectory 46 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Top/") 47 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Gnc/") 48 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Payload/") 49 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Com/") 50 | set(SOURCE_FILES "${CMAKE_CURRENT_LIST_DIR}/Top/Main.cpp") 51 | set(MOD_DEPS ${PROJECT_NAME}/Top) 52 | 53 | register_fprime_deployment() 54 | -------------------------------------------------------------------------------- /SystemReference/Com/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Com subdirectories 2 | 3 | # Components 4 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/XBee/") 5 | -------------------------------------------------------------------------------- /SystemReference/Com/XBee/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #### 2 | # F prime CMakeLists.txt: 3 | # 4 | # SOURCE_FILES: combined list of source and autocoding diles 5 | # MOD_DEPS: (optional) module dependencies 6 | # 7 | # Note: using PROJECT_NAME as EXECUTABLE_NAME 8 | #### 9 | set(SOURCE_FILES 10 | "${CMAKE_CURRENT_LIST_DIR}/XBee.fpp" 11 | "${CMAKE_CURRENT_LIST_DIR}/XBee.cpp" 12 | ) 13 | register_fprime_module() 14 | 15 | set(UT_SOURCE_FILES 16 | "${CMAKE_CURRENT_LIST_DIR}/XBee.fpp" 17 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp" 18 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/TestMain.cpp" 19 | ) 20 | set(UT_MOD_DEPS 21 | STest 22 | ) 23 | register_fprime_ut() 24 | -------------------------------------------------------------------------------- /SystemReference/Com/XBee/XBee.fpp: -------------------------------------------------------------------------------- 1 | module Com { 2 | constant retryCount = 10 3 | array EnergyDensityType = [16] U8 4 | active component XBee { 5 | # ---------------------------------------------------------------------- 6 | # Framer, deframer, and queue ports 7 | # ---------------------------------------------------------------------- 8 | 9 | @ Data coming in from the framing component 10 | sync input port comDataIn: Drv.ByteStreamSend 11 | 12 | @ Status of the last radio transmission 13 | output port comStatus: Fw.SuccessCondition 14 | 15 | @ Com data passing back out 16 | output port comDataOut: Drv.ByteStreamRecv 17 | 18 | # ---------------------------------------------------------------------- 19 | # Byte stream model 20 | # ---------------------------------------------------------------------- 21 | 22 | @ Ready signal when driver is connected 23 | sync input port drvConnected: Drv.ByteStreamReady 24 | 25 | @ Data received from driver 26 | sync input port drvDataIn: Drv.ByteStreamRecv 27 | 28 | @ Data going to the underlying driver 29 | output port drvDataOut: Drv.ByteStreamSend 30 | 31 | # ---------------------------------------------------------------------- 32 | # Implementation ports 33 | # ---------------------------------------------------------------------- 34 | 35 | @ Allows for deallocation of XBee command communications 36 | output port deallocate: Fw.BufferSend 37 | 38 | @ Allows for allocation of buffers 39 | output port allocate: Fw.BufferGet 40 | 41 | # ---------------------------------------------------------------------- 42 | # Special ports 43 | # ---------------------------------------------------------------------- 44 | 45 | @ Port carrying 1HZ tick for timeout tracking 46 | sync input port run: Svc.Sched 47 | 48 | @ Time get port 49 | time get port timeCaller 50 | 51 | @ Command registration port 52 | command reg port cmdRegOut 53 | 54 | @ Command received port 55 | command recv port cmdIn 56 | 57 | @ Command response port 58 | command resp port cmdResponseOut 59 | 60 | @ Text event port 61 | text event port logTextOut 62 | 63 | @ Event port 64 | event port logOut 65 | 66 | @ Telemetry port 67 | telemetry port tlmOut 68 | 69 | # ---------------------------------------------------------------------- 70 | # Commands 71 | # ---------------------------------------------------------------------- 72 | 73 | @ Report the radio serial number 74 | async command ReportNodeIdentifier 75 | 76 | @ Perform energy density scan activity 77 | async command EnergyDensityScan 78 | 79 | # ---------------------------------------------------------------------- 80 | # Events 81 | # ---------------------------------------------------------------------- 82 | 83 | @ Produces a node-identifier 84 | event RadioNodeIdentifier( 85 | identifier: string size 20 @< Radio identifier 86 | ) \ 87 | severity activity high \ 88 | id 0 \ 89 | format "Radio identification: {}" 90 | 91 | # ---------------------------------------------------------------------- 92 | # Telemetry 93 | # ---------------------------------------------------------------------- 94 | @ Energy density graph 95 | telemetry EnergyDensity: EnergyDensityType id 0 update always 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /SystemReference/Com/XBee/XBee.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title XBee.hpp 3 | // \author mstarch 4 | // \brief hpp file for XBee component implementation class 5 | // ====================================================================== 6 | 7 | #ifndef XBee_HPP 8 | #define XBee_HPP 9 | 10 | #include "SystemReference/Com/XBee/XBeeComponentAc.hpp" 11 | #include "Utils/Types/CircularBuffer.hpp" 12 | #include "Os/Mutex.hpp" 13 | 14 | namespace Com { 15 | 16 | class XBee final : public XBeeComponentBase { 17 | public: 18 | static const FwSizeType retryLimit = 10; // Arbitrary limit to the number of retries 19 | static const FwSizeType xbeeQuiescentTime_ms = 1000; // Milliseconds to quiesce the radio prior to sending +++ 20 | static const U32 MAX_COMMAND_DATA = 48; // Max size of data from command mode NI command: 20 bytes, ED: 3x16: 48 21 | static const U32 TIMEOUT_TICKS_1HZ = 10; // Number of 1Hz ticks to timeout of command mode 22 | static const U32 QUIET_TICKS_1HZ = xbeeQuiescentTime_ms/1000 + 1; // Number of 1Hz ticks to quiet radio 23 | 24 | static_assert(QUIET_TICKS_1HZ < TIMEOUT_TICKS_1HZ, "Quiet ticks may not be less than timeout ticks"); 25 | 26 | /** 27 | * \brief state machine states for talking with the radio 28 | * 29 | * Represents the state transitions in the flow to talking to the XBee radio using command mode. Notably, the radio 30 | * will send data only when in PASSTHROUGH state, and waits without change when in ERROR_TIMEOUT. 31 | */ 32 | enum ComState { 33 | ERROR_TIMEOUT = -1, //!< Error state, waiting for the 10S command mode timeout 34 | QUIET_RADIO = 0, //!< State to quiet the radio so command mode will be respected 35 | AWAIT_COMMAND_MODE = 1, //!< Waiting for command mode, sends command when response processed 36 | AWAIT_COMMAND_RESPONSE = 2, //!< Waiting for command response, sends exit when response processed 37 | AWAIT_PASSTHROUGH = 3, //!< Waiting for exit response, sends "ready" for more data when response processed 38 | PASSTHROUGH = 4, //!< Passing through data to remote radio 39 | }; 40 | 41 | /** 42 | * \brief status of a processed command in radio command mode 43 | */ 44 | enum ProcessResponse { 45 | PROCESSED_GOOD, //!< Expected response found and processed 46 | PROCESSED_ERROR, //!< Unexpected response 47 | MORE_NEEDED, //!< More data is needed before processing can finish 48 | }; 49 | 50 | /** 51 | * \brief Format for configured radio commands 52 | */ 53 | struct RadioCommand { 54 | const CHAR* command; //!< AT text of command 55 | U32 length; //!< Length of text including \r and other characters 56 | U32 response_length; //!< Maximum length of expected response 57 | }; 58 | 59 | /** 60 | * \brief Command text to enter command mode 61 | */ 62 | static constexpr RadioCommand ENTER_COMMAND_MODE = { 63 | .command = "+++", 64 | .length = 3, 65 | .response_length = 2 66 | }; 67 | 68 | /** 69 | * \brief Report the radio identifier string 70 | */ 71 | static constexpr RadioCommand NODE_IDENTIFIER = { 72 | .command = "ATNI\r", 73 | .length = 5, 74 | .response_length = 20 75 | }; 76 | 77 | /** 78 | * \brief Report the energy density of each channel 79 | */ 80 | static constexpr RadioCommand ENERGY_DENSITY = { 81 | .command = "ATED\r", 82 | .length = 5, 83 | .response_length = 3 * 16 84 | }; 85 | 86 | /** 87 | * \brief Exit command mode 88 | */ 89 | static constexpr RadioCommand EXIT_COMMAND_MODE = { 90 | .command = "ATCN\r", 91 | .length = 5, 92 | .response_length = 2 93 | }; 94 | 95 | // ---------------------------------------------------------------------- 96 | // Construction, initialization, and destruction 97 | // ---------------------------------------------------------------------- 98 | 99 | //! Construct object XBee 100 | //! 101 | XBee(const char* const compName /*!< The component name*/ 102 | ); 103 | 104 | //! Destroy object XBee 105 | //! 106 | ~XBee(); 107 | 108 | //! Initialize object XBee 109 | //! 110 | void init(const FwSizeType queueDepth, /*!< The queue depth*/ 111 | const FwIndexType instance = 0 /*!< The instance number*/ 112 | ); 113 | 114 | PRIVATE: 115 | // ---------------------------------------------------------------------- 116 | // Handler implementations for user-defined typed input ports 117 | // ---------------------------------------------------------------------- 118 | 119 | //! Handler implementation for comDataIn 120 | //! 121 | Drv::SendStatus comDataIn_handler(const FwIndexType portNum, /*!< The port number*/ 122 | Fw::Buffer& sendBuffer); 123 | 124 | //! Handler implementation for drvConnected 125 | //! 126 | void drvConnected_handler(const FwIndexType portNum); 127 | 128 | //! Handler implementation for drvDataIn 129 | //! 130 | void drvDataIn_handler(const FwIndexType portNum, 131 | /*!< The port number*/ Fw::Buffer& recvBuffer, 132 | const Drv::RecvStatus& recvStatus); 133 | 134 | //! Handler implementation for Run port 135 | //! 136 | void run_handler(const FwIndexType portNum, /*!< The port number*/ 137 | U32 context); 138 | 139 | PRIVATE: 140 | // ---------------------------------------------------------------------- 141 | // Command handler implementations 142 | // ---------------------------------------------------------------------- 143 | 144 | //! Implementation for ReportXBeeSerialNumber command handler 145 | //! 146 | void ReportNodeIdentifier_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ 147 | const U32 cmdSeq /*!< The command sequence number*/ 148 | ); 149 | 150 | //! Implementation for ChannelScan command handler 151 | //! 152 | void EnergyDensityScan_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ 153 | const U32 cmdSeq /*!< The command sequence number*/ 154 | ); 155 | 156 | PRIVATE: 157 | // ---------------------------------------------------------------------- 158 | // Helper functions 159 | // ---------------------------------------------------------------------- 160 | /** 161 | * \brief Process response entry point dispatching internal response handlers 162 | * 163 | * All responses are processed using this function. This function delegates to the specific response handlers. It 164 | * will process any response that currently ends with '\r', otherwise it will report MORE_NEEDED. 165 | * 166 | * \return GOOD, ERROR, or MORE_NEEDED 167 | */ 168 | ProcessResponse process_response(); 169 | /** 170 | * \brief Processes node-identifier text 171 | * \return GOOD 172 | */ 173 | ProcessResponse process_node_identifier(); 174 | /** 175 | * \brief Processes OK/ERROR responses 176 | * \return GOOD/ERROR 177 | */ 178 | ProcessResponse process_ok_or_error(); 179 | /** 180 | * \brief Processes energy density responses 181 | * \return GOOD/ERROR 182 | */ 183 | ProcessResponse process_energy_density(); 184 | /** 185 | * \brief Stages a command for dispatch once command-mode has been reached. Sets state to QUIET_RADIO 186 | * \param command: radio command to stage 187 | * \param opCode: opcode of the command to stage 188 | * \param cmdSeq: sequence number of the command being staged 189 | */ 190 | void stage_command(const RadioCommand& command, const FwOpcodeType opCode, const U32 cmdSeq); 191 | /** 192 | * \brief Initiates command mode and sets state to AWAIT_COMMAND 193 | */ 194 | void initiate_command(); 195 | /** 196 | * \brief Deinitiates command mode, resets variables, and returns state to PASSTHROUGH 197 | * \param response: response to echo for commands 198 | * \return: true, for patallel state machine execution 199 | */ 200 | bool deinitiate_command(const Fw::CmdResponse& response); 201 | /** 202 | * \brief send a command string out radio 203 | * \param command: command to send out radio 204 | * \return: true on successful transmission, false otherwise 205 | */ 206 | bool send_radio_command(const RadioCommand& command); 207 | /** 208 | * \brief processes one iteration of the state machine as triggered via a response UART message 209 | */ 210 | void state_machine(); 211 | /** 212 | * \brief report the radio is ready for data to any attached com queue 213 | */ 214 | void report_ready(); 215 | /** 216 | * \brief converts ASCII hex-digits into their numerical equivalent 217 | * \param input: input ASCII hex character (0-F) 218 | * \return: numerical representation of the hex character 219 | */ 220 | inline U8 convert_char(U8 input) { 221 | return (input >= 'A') ? (input - 'A' + 0x0Au) : (input - '0'); 222 | } 223 | 224 | // ---------------------------------------------------------------------- 225 | // Private member variables 226 | // ---------------------------------------------------------------------- 227 | 228 | Types::CircularBuffer m_circular; //!< Used to capture data from UART port when handling cmd-response with XBee 229 | Os::Mutex m_lock; //!< Lock to protect state changes and UART port 230 | 231 | ComState m_state; //!< State of the component 232 | const RadioCommand* m_currentCommand; //!< Current command that is being dispatched to XBee 233 | 234 | FwOpcodeType m_opCode; //!< Opcode store for deferred command completion 235 | U32 m_cmdSeq; //!< Sequence store for deferred command completion 236 | 237 | U32 m_timeoutCount; //!< Counts the number of ticks while waiting for command-mode timeout 238 | bool m_reinit; //!< Does a new ready signal need to be sent when commanding is done 239 | U8 m_data[MAX_COMMAND_DATA + 1]; 240 | }; 241 | } // end namespace Com 242 | 243 | #endif 244 | -------------------------------------------------------------------------------- /SystemReference/Com/XBee/docs/sdd.md: -------------------------------------------------------------------------------- 1 | \page SvcComStubComponent Svc::ComStub Component 2 | # Svc::ComStub (Passive Component) 3 | 4 | ## 1. Introduction 5 | 6 | `Svc::ComStub` is an example F´ component implementing the minimal communication interface required to work within F´. 7 | Projects and users may choose to replace this with a complete communication implementation (i.e. a component managing 8 | a specific radio) once ready. As long as any communication implementation has at-least this same interface it can 9 | drop in and work with the standard F´ uplink and downlink chains. 10 | 11 | ## 2. Assumptions 12 | 13 | Using `Svc::ComStub` directly assumes that the driver layer (e.g. `Drv::TcpClient`) provides all capability needed to 14 | establish communications. In other words, the project communications strategy is `Tcp` then this stub will be 15 | sufficient. If this assumption does not hold, then the project or user should consider implementing a communication 16 | component with this interface, but matches the specific communication needs (e.g. initializing a radio). 17 | 18 | ## 3. Requirements 19 | 20 | 21 | | Requirement | Description | Rationale | Verification Method | 22 | |-----------------|---------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------|---------------------| 23 | | SVC-COMSTUB-001 | `Svc::ComStub` shall accept `Fw::Buffer` for transmission and pass them to a `Drv::ByteStreamSend` port | The comm interface must send `Fw::Buffer`s through a driver | Unit Test | 24 | | SVC-COMSTUB-002 | `Svc::ComStub` shall emit a `Svc::ComStatus::READY` signal on `Drv::ByteStreamSend` success | Successful sends must notify any attached `Svc::ComQueue` | Unit Test | 25 | | SVC-COMSTUB-003 | `Svc::ComStub` shall emit a `Svc::ComStatus::FAIL` signal on `Drv::ByteStreamSend` failure | Failed sends must notify any attached `Svc::ComQueue` | Unit Test | 26 | | SVC-COMSTUB-004 | `Svc::ComStub` shall retry sending to `Drv::ByteStreamSend` on `Drv::ByteStreamSend` retry | Sends indicating `RETRY` should be retried. | Unit Test | 27 | | SVC-COMSTUB-005 | `Svc::ComStub` shall pass-through `Fw::Buffer` from a `Drv::ByteStreamRead` on `Drv::ByteStreamSend` success | A Comm interface must receive `Fw::Buffer`s from a driver | Unit Test | 28 | 29 | ## 4. Design 30 | The diagram below shows the `Svc::ComStub` port interface. Any communications interface implementing or extending this 31 | port interface can be used alongside the other F´ communication components (`Svc::Framer`, `Svc::Deframer`, 32 | `Svc::ComQueue`). This interface is described below. 33 | 34 | **Svc::ComStub Uplink and Downlink Interface** 35 | ```mermaid 36 | graph LR 37 | subgraph Svc::Framer 38 | framedOut 39 | end 40 | subgraph Svc::Deframer 41 | framedIn 42 | end 43 | subgraph Svc::ComStub 44 | comDataIn 45 | comStatus 46 | comDataOut 47 | end 48 | subgraph Svc::ComQueue 49 | status 50 | end 51 | framedOut -->comDataIn 52 | comDataOut -->framedIn 53 | comStatus -->status 54 | ``` 55 | 56 | `Svc::ComStub`'s specific implementation delegates to a `Drv::ByteStreamDriverModel` as a way to transmit data and 57 | receive data. Other communication implementations may follow-suite. 58 | 59 | ```mermaid 60 | flowchart LR 61 | subgraph Drv::ByteStreamDriverModel 62 | send 63 | recv 64 | ready 65 | end 66 | subgraph Svc::ComStub 67 | drvDataOut 68 | drvDataIn 69 | drvConnected 70 | end 71 | drvDataOut ---->send 72 | recv ----->drvDataIn 73 | ready ----->drvConnected 74 | ``` 75 | 76 | 77 | 78 | ### 4.1. Ports 79 | 80 | `ComQueue` has the following ports. The first three ports are required for the interface, the second three are optional 81 | if the communication implementation attaches to an F´ driver for the device interface. i.e. a communication component 82 | may directly interact with device hardware, or it may pass through send and receive buffers to a UART for actual 83 | transmission. 84 | 85 | | Required | Kind | Name | Port Type | Usage | 86 | |----------|--------------|----------------|-----------------------|-----------------------------------------------------------------------------------| 87 | | Yes | `sync input` | `comDataIn` | `Drv.ByteStreamSend` | Port receiving `Fw::Buffer`s for transmission out `drvDataOut` | 88 | | Yes | `output` | `comStatus` | `Svc.ComStatus` | Port indicating ready or failed to possibly attached `Svc::ComQueue` | 89 | | Yes | `output` | `comDataOut` | `Drv.ByteStreamRecv` | Port providing received `Fw::Buffers` to a potential `Svc::Deframer` | 90 | | No | `sync input` | `drvConnected` | `Drv.ByteStreamReady` | Port called when the underlying driver has connected | 91 | | No | `sync input` | `drvDataIn` | `Drv.ByteStreamRecv` | Port receiving `Fw::Buffers` from underlying communications bus driver | 92 | | No | `output` | `drvDataOut` | `Drv.ByteStreamSend` | Port providing received `Fw::Buffers` to the underlying communications bus driver | 93 | 94 | 95 | ### 4.2. State, Configuration, and Runtime Setup 96 | 97 | `Svc::ComStub` has no state, no requires external configuration. However, other communication implementations are not 98 | precluded from having state nor requiring configuration. The component uses the standard constructor setup for F´ 99 | passive components. 100 | 101 | ### 4.3. Port Handlers 102 | 103 | #### 4.3.1 comDataIn 104 | 105 | The `comDataIn` port handler receives an `Fw::Buffer` from the F´ system for transmission to the ground. Typically, it 106 | is connected to the output of the `Svc::Framer` component. In this `Svc::ComStub` implementation, it passes this 107 | `Fw::Buffer` directly to the `drvDataOut` port. It will retry when that port responds with a `RETRY` request. Otherwise, 108 | the `comStatus` port will be invoked to indicate success or failure. Retries attempts are limited before the port 109 | asserts. 110 | 111 | #### 4.3.1 drvConnected 112 | 113 | This port receives the connected signal from the driver and responds with exactly one `READY` invocation to the 114 | `comStatus` port. This starts downlink. This occurs each time the driver reconnects. 115 | 116 | #### 4.3.1 drvDataIn 117 | 118 | The `drvDataIn` handler receives data read from the driver and supplies it out the `comDataOut` port. It usually is 119 | connected to the `Svc::Deframer` component 120 | 121 | ## 5. Change Log 122 | 123 | | Date | Description | 124 | |------------|---------------| 125 | | 2022-07-22 | Initial Draft | 126 | -------------------------------------------------------------------------------- /SystemReference/Com/XBee/test/ut/TestMain.cpp: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------- 2 | // TestMain.cpp 3 | // ---------------------------------------------------------------------- 4 | 5 | #include "Tester.hpp" 6 | 7 | TEST(Nominal, Initial) { 8 | Com::Tester tester; 9 | tester.test_initial(); 10 | } 11 | 12 | TEST(Nominal, BasicIo) { 13 | Com::Tester tester; 14 | tester.test_basic_io(); 15 | } 16 | 17 | 18 | TEST(Nominal, Fail) { 19 | Com::Tester tester; 20 | tester.test_fail(); 21 | } 22 | 23 | TEST(OffNominal, Retry) { 24 | Com::Tester tester; 25 | tester.test_retry(); 26 | } 27 | 28 | int main(int argc, char **argv) { 29 | ::testing::InitGoogleTest(&argc, argv); 30 | return RUN_ALL_TESTS(); 31 | } 32 | -------------------------------------------------------------------------------- /SystemReference/Com/XBee/test/ut/Tester.cpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title XBee.hpp 3 | // \author mstarch 4 | // \brief cpp file for XBee test harness implementation class 5 | // ====================================================================== 6 | 7 | #include "Tester.hpp" 8 | #include 9 | 10 | 11 | #define INSTANCE 0 12 | #define MAX_HISTORY_SIZE 100 13 | #define RETRIES 3 14 | 15 | U8 storage[RETRIES][10240]; 16 | 17 | 18 | namespace Com { 19 | 20 | // ---------------------------------------------------------------------- 21 | // Construction and destruction 22 | // ---------------------------------------------------------------------- 23 | 24 | Tester ::Tester() : XBeeGTestBase("Tester", MAX_HISTORY_SIZE), component("XBee"), m_send_mode(Drv::SendStatus::SEND_OK), m_retries(0) { 25 | this->initComponents(); 26 | this->connectPorts(); 27 | } 28 | 29 | Tester ::~Tester() {} 30 | 31 | // ---------------------------------------------------------------------- 32 | // Helpers 33 | // ---------------------------------------------------------------------- 34 | void Tester ::fill_buffer(Fw::Buffer& buffer_to_fill) { 35 | U8 size = STest::Pick::lowerUpper(1, sizeof(buffer_to_fill.getSize())); 36 | for (U32 i = 0; i < size; i++) { 37 | buffer_to_fill.getData()[i] = STest::Pick::any(); 38 | } 39 | buffer_to_fill.setSize(size); 40 | } 41 | 42 | // ---------------------------------------------------------------------- 43 | // Tests 44 | // ---------------------------------------------------------------------- 45 | void Tester ::test_initial() { 46 | Svc::ComSendStatus com_send_status = Svc::ComSendStatus::READY; 47 | invoke_to_drvConnected(0); 48 | ASSERT_from_comStatus_SIZE(1); 49 | ASSERT_from_comStatus(0, com_send_status); 50 | } 51 | 52 | void Tester ::test_basic_io() { 53 | Fw::Buffer buffer(storage[0], sizeof(storage[0])); 54 | Svc::ComSendStatus com_send_status = Svc::ComSendStatus::READY; 55 | this->fill_buffer(buffer); 56 | 57 | // Downlink 58 | ASSERT_EQ(invoke_to_comDataIn(0, buffer), Drv::SendStatus::SEND_OK); 59 | ASSERT_from_drvDataOut_SIZE(1); 60 | ASSERT_from_drvDataOut(0, buffer); 61 | ASSERT_from_comStatus(0, com_send_status); 62 | 63 | // Uplink 64 | Drv::RecvStatus status = Drv::RecvStatus::RECV_OK; 65 | invoke_to_drvDataIn(0, buffer, status); 66 | ASSERT_from_comDataOut_SIZE(1); 67 | ASSERT_from_comDataOut(0, buffer, status); 68 | } 69 | 70 | void Tester ::test_fail() { 71 | Fw::Buffer buffer(storage[0], sizeof(storage[0])); 72 | this->fill_buffer(buffer); 73 | Svc::ComSendStatus com_send_status = Svc::ComSendStatus::FAIL; 74 | m_send_mode = Drv::SendStatus::SEND_ERROR; 75 | 76 | // Downlink 77 | ASSERT_EQ(invoke_to_comDataIn(0, buffer), Drv::SendStatus::SEND_OK); 78 | ASSERT_from_drvDataOut_SIZE(1); 79 | ASSERT_from_drvDataOut(0, buffer); 80 | ASSERT_from_drvDataOut_SIZE(1); 81 | ASSERT_from_comStatus(0, com_send_status); 82 | 83 | // Uplink 84 | Drv::RecvStatus status = Drv::RecvStatus::RECV_ERROR; 85 | invoke_to_drvDataIn(0, buffer, status); 86 | ASSERT_from_comDataOut_SIZE(1); 87 | ASSERT_from_comDataOut(0, buffer, status); 88 | } 89 | 90 | void Tester ::test_retry() { 91 | Fw::Buffer buffers[RETRIES]; 92 | Svc::ComSendStatus com_send_status = Svc::ComSendStatus::READY; 93 | m_send_mode = Drv::SendStatus::SEND_RETRY; 94 | 95 | for (U32 i = 0; i < RETRIES; i++) { 96 | buffers[i].setData(storage[i]); 97 | buffers[i].setSize(sizeof(storage[i])); 98 | buffers[i].setContext(i); 99 | this->fill_buffer(buffers[i]); 100 | invoke_to_comDataIn(0, buffers[i]); 101 | ASSERT_from_drvDataOut_SIZE((i + 1) * RETRIES); 102 | m_retries = 0; 103 | } 104 | ASSERT_from_drvDataOut_SIZE(RETRIES * RETRIES); 105 | ASSERT_from_comStatus_SIZE(3); 106 | for (U32 i = 0; i < RETRIES; i++) { 107 | for (U32 j = 0; j < RETRIES; j++) { 108 | ASSERT_from_drvDataOut((i * RETRIES) + j, buffers[i]); 109 | } 110 | ASSERT_from_comStatus(i, com_send_status); 111 | } 112 | } 113 | 114 | // ---------------------------------------------------------------------- 115 | // Handlers for typed from ports 116 | // ---------------------------------------------------------------------- 117 | 118 | void Tester ::from_comDataOut_handler(const NATIVE_INT_TYPE portNum, 119 | Fw::Buffer& recvBuffer, 120 | const Drv::RecvStatus& recvStatus) { 121 | this->pushFromPortEntry_comDataOut(recvBuffer, recvStatus); 122 | } 123 | 124 | void Tester ::from_comStatus_handler(const NATIVE_INT_TYPE portNum, Svc::ComSendStatus& ComStatus) { 125 | this->pushFromPortEntry_comStatus(ComStatus); 126 | } 127 | 128 | Drv::SendStatus Tester ::from_drvDataOut_handler(const NATIVE_INT_TYPE portNum, Fw::Buffer& sendBuffer) { 129 | this->pushFromPortEntry_drvDataOut(sendBuffer); 130 | m_retries = (m_send_mode == Drv::SendStatus::SEND_RETRY) ? (m_retries + 1) : m_retries; 131 | if (m_retries < RETRIES) { 132 | return m_send_mode; 133 | } 134 | return Drv::SendStatus::SEND_OK; 135 | } 136 | 137 | // ---------------------------------------------------------------------- 138 | // Helper methods 139 | // ---------------------------------------------------------------------- 140 | 141 | void Tester ::connectPorts() { 142 | // comDataIn 143 | this->connect_to_comDataIn(0, this->component.get_comDataIn_InputPort(0)); 144 | 145 | // drvConnected 146 | this->connect_to_drvConnected(0, this->component.get_drvConnected_InputPort(0)); 147 | 148 | // drvDataIn 149 | this->connect_to_drvDataIn(0, this->component.get_drvDataIn_InputPort(0)); 150 | 151 | // comDataOut 152 | this->component.set_comDataOut_OutputPort(0, this->get_from_comDataOut(0)); 153 | 154 | // comStatus 155 | this->component.set_comStatus_OutputPort(0, this->get_from_comStatus(0)); 156 | 157 | // drvDataOut 158 | this->component.set_drvDataOut_OutputPort(0, this->get_from_drvDataOut(0)); 159 | } 160 | 161 | void Tester ::initComponents() { 162 | this->init(); 163 | this->component.init(INSTANCE); 164 | } 165 | 166 | } // end namespace Com 167 | -------------------------------------------------------------------------------- /SystemReference/Com/XBee/test/ut/Tester.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title XBee/test/ut/Tester.hpp 3 | // \author mstarch 4 | // \brief hpp file for XBee test harness implementation class 5 | // ====================================================================== 6 | 7 | #ifndef TESTER_HPP 8 | #define TESTER_HPP 9 | 10 | #include "GTestBase.hpp" 11 | #include "Com/XBee/XBee.hpp" 12 | 13 | namespace Com { 14 | 15 | class Tester : public XBeeGTestBase { 16 | // ---------------------------------------------------------------------- 17 | // Construction and destruction 18 | // ---------------------------------------------------------------------- 19 | 20 | public: 21 | //! Construct object Tester 22 | //! 23 | Tester(); 24 | 25 | //! Destroy object Tester 26 | //! 27 | ~Tester(); 28 | 29 | public: 30 | //! Buffer to fill with data 31 | //! 32 | void fill_buffer(Fw::Buffer& buffer_to_fill); 33 | // ---------------------------------------------------------------------- 34 | // Tests 35 | // ---------------------------------------------------------------------- 36 | 37 | //! Test initial READY setup 38 | //! 39 | void test_initial(); 40 | 41 | //! Tests the basic input and output of the component 42 | //! 43 | void test_basic_io(); 44 | 45 | //! Tests the basic failure case for the component 46 | //! 47 | void test_fail(); 48 | 49 | //! Tests the basic failure retry component 50 | //! 51 | void test_retry(); 52 | 53 | private: 54 | // ---------------------------------------------------------------------- 55 | // Handlers for typed from ports 56 | // ---------------------------------------------------------------------- 57 | 58 | //! Handler for from_comDataOut 59 | //! 60 | void from_comDataOut_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ 61 | Fw::Buffer& recvBuffer, 62 | const Drv::RecvStatus& recvStatus); 63 | 64 | //! Handler for from_comStatus 65 | //! 66 | void from_comStatus_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ 67 | Svc::ComSendStatus& ComStatus /*!< 68 | Status of communication state 69 | */ 70 | ); 71 | 72 | //! Handler for from_drvDataOut 73 | //! 74 | Drv::SendStatus from_drvDataOut_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ 75 | Fw::Buffer& sendBuffer); 76 | 77 | private: 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 | // Variables 93 | // ---------------------------------------------------------------------- 94 | 95 | //! The component under test 96 | //! 97 | XBee component; 98 | Drv::SendStatus m_send_mode; //! Send mode 99 | U32 m_retries; 100 | }; 101 | 102 | } // end namespace Com 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /SystemReference/Gnc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Module subdirectories 2 | 3 | # Components 4 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Imu/") 5 | -------------------------------------------------------------------------------- /SystemReference/Gnc/Imu/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #### 2 | # F prime CMakeLists.txt: 3 | # 4 | # SOURCE_FILES: combined list of source and autocoding diles 5 | # MOD_DEPS: (optional) module dependencies 6 | # 7 | # Note: using PROJECT_NAME as EXECUTABLE_NAME 8 | #### 9 | set(SOURCE_FILES 10 | "${CMAKE_CURRENT_LIST_DIR}/Imu.fpp" 11 | "${CMAKE_CURRENT_LIST_DIR}/Imu.cpp" 12 | ) 13 | register_fprime_module() 14 | 15 | # Register the unit test build 16 | set(UT_SOURCE_FILES 17 | "${CMAKE_CURRENT_LIST_DIR}/Imu.fpp" 18 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/TestMain.cpp" 19 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp" 20 | ) 21 | set(UT_MOD_DEPS STest) 22 | register_fprime_ut() -------------------------------------------------------------------------------- /SystemReference/Gnc/Imu/Imu.cpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title Imu.cpp 3 | // \author vbai 4 | // \brief cpp file for Imu component implementation class 5 | // ====================================================================== 6 | 7 | #include 8 | #include "Fw/Types/BasicTypes.hpp" 9 | 10 | namespace Gnc { 11 | 12 | // ---------------------------------------------------------------------- 13 | // Construction, initialization, and destruction 14 | // ---------------------------------------------------------------------- 15 | 16 | Imu ::Imu(const char* const compName) : ImuComponentBase(compName), m_power(PowerState::OFF) {} 17 | 18 | void Imu ::init(const FwIndexType instance) { 19 | ImuComponentBase::init(instance); 20 | } 21 | 22 | void Imu ::setup(I2cDevAddr::T devAddress) { 23 | m_i2cDevAddress = devAddress; 24 | } 25 | 26 | Imu ::~Imu() {} 27 | 28 | // ---------------------------------------------------------------------- 29 | // Handler implementations for user-defined typed input ports 30 | // ---------------------------------------------------------------------- 31 | 32 | void Imu ::Run_handler(const FwIndexType portNum, U32 context) { 33 | if (m_power == PowerState::ON) { 34 | updateAccel(); 35 | updateGyro(); 36 | } 37 | } 38 | 39 | void Imu ::PowerSwitch_cmdHandler(const FwOpcodeType opCode, const U32 cmdSeq, PowerState powerState) { 40 | power(powerState); 41 | this->log_ACTIVITY_HI_PowerStatus(powerState); 42 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); 43 | } 44 | 45 | // ---------------------------------------------------------------------- 46 | // Helper Functions 47 | // ---------------------------------------------------------------------- 48 | 49 | Drv::I2cStatus Imu ::setupReadRegister(U8 reg) { 50 | Fw::Buffer buffer(®, sizeof reg); 51 | return this->write_out(0, m_i2cDevAddress, buffer); 52 | } 53 | 54 | Drv::I2cStatus Imu ::readRegisterBlock(U8 startRegister, Fw::Buffer& buffer) { 55 | Drv::I2cStatus status; 56 | status = this->setupReadRegister(startRegister); 57 | if (status == Drv::I2cStatus::I2C_OK) { 58 | status = this->read_out(0, m_i2cDevAddress, buffer); 59 | } 60 | return status; 61 | } 62 | 63 | Gnc::Vector Imu ::deserializeVector(Fw::Buffer& buffer, F32 scaleFactor) { 64 | Gnc::Vector vector; 65 | I16 value; 66 | FW_ASSERT(buffer.getSize() >= 6, buffer.getSize()); 67 | FW_ASSERT(buffer.getData() != nullptr); 68 | // Data is big-endian as is fprime internal storage so we can use the built-in buffer deserialization 69 | auto deserializeHelper = buffer.getDeserializer(); 70 | FW_ASSERT(deserializeHelper.deserialize(value) == Fw::FW_SERIALIZE_OK); 71 | vector[0] = static_cast(value) / scaleFactor; 72 | 73 | FW_ASSERT(deserializeHelper.deserialize(value) == Fw::FW_SERIALIZE_OK); 74 | vector[1] = static_cast(value) / scaleFactor; 75 | 76 | FW_ASSERT(deserializeHelper.deserialize(value) == Fw::FW_SERIALIZE_OK); 77 | vector[2] = static_cast(value) / scaleFactor; 78 | return vector; 79 | } 80 | 81 | void Imu ::config() { 82 | U8 data[IMU_REG_SIZE_BYTES * 2]; 83 | Fw::Buffer buffer(data, sizeof data); 84 | 85 | // Set gyro range to +-250 deg/s 86 | data[0] = GYRO_CONFIG_ADDR; 87 | data[1] = 0; 88 | 89 | Drv::I2cStatus status = write_out(0, m_i2cDevAddress, buffer); 90 | if (status != Drv::I2cStatus::I2C_OK) { 91 | this->log_WARNING_HI_SetUpConfigError(status); 92 | } 93 | 94 | // Set accel range to +- 2g 95 | data[0] = ACCEL_CONFIG_ADDR; 96 | data[1] = 0; 97 | status =this-> write_out(0, m_i2cDevAddress, buffer); 98 | 99 | if (status != Drv::I2cStatus::I2C_OK) { 100 | this->log_WARNING_HI_SetUpConfigError(status); 101 | } 102 | } 103 | 104 | void Imu ::power(PowerState powerState) { 105 | U8 data[IMU_REG_SIZE_BYTES * 2]; 106 | Fw::Buffer buffer(data, sizeof data); 107 | 108 | // Check if already on/off 109 | if (powerState.e == m_power) { 110 | return; 111 | } 112 | 113 | data[0] = POWER_MGMT_ADDR; 114 | data[1] = (powerState.e == PowerState::ON) ? POWER_ON_VALUE : POWER_OFF_VALUE; 115 | 116 | Drv::I2cStatus status = this->write_out(0, m_i2cDevAddress, buffer); 117 | if (status != Drv::I2cStatus::I2C_OK) { 118 | this->log_WARNING_HI_PowerModeError(status); 119 | } else { 120 | m_power = powerState.e; 121 | // Must configure at power on 122 | if (m_power == PowerState::ON) { 123 | config(); 124 | } 125 | } 126 | } 127 | 128 | void Imu ::updateAccel() { 129 | U8 data[IMU_MAX_DATA_SIZE_BYTES]; 130 | Fw::Buffer buffer(data, sizeof data); 131 | 132 | // Read a block of registers from the IMU at the accelerometer's address 133 | Drv::I2cStatus status = this->readRegisterBlock(IMU_RAW_ACCEL_ADDR, buffer); 134 | 135 | // Check a successful read of 6 bytes before processing data 136 | if ((status == Drv::I2cStatus::I2C_OK) && (buffer.getSize() == 6) && (buffer.getData() != nullptr)) { 137 | Gnc::Vector vector = deserializeVector(buffer, accelScaleFactor); 138 | this->tlmWrite_accelerometer(vector); 139 | } else { 140 | this->log_WARNING_HI_TelemetryError(status); 141 | } 142 | } 143 | 144 | void Imu ::updateGyro() { 145 | U8 data[IMU_MAX_DATA_SIZE_BYTES]; 146 | Fw::Buffer buffer(data, sizeof data); 147 | 148 | // Read a block of registers from the IMU at the gyroscope's address 149 | Drv::I2cStatus status = this->readRegisterBlock(IMU_RAW_GYRO_ADDR, buffer); 150 | 151 | // Check a successful read of 6 bytes before processing data 152 | if ((status == Drv::I2cStatus::I2C_OK) && (buffer.getSize() == 6) && (buffer.getData() != nullptr)) { 153 | Gnc::Vector vector = deserializeVector(buffer, gyroScaleFactor); 154 | this->tlmWrite_gyroscope(vector); 155 | } else { 156 | this->log_WARNING_HI_TelemetryError(status); 157 | } 158 | } 159 | } // end namespace Gnc 160 | -------------------------------------------------------------------------------- /SystemReference/Gnc/Imu/Imu.fpp: -------------------------------------------------------------------------------- 1 | module Gnc { 2 | @ The power state enumeration 3 | enum PowerState {OFF, ON} 4 | 5 | @ 3-tuple type used for telemetry 6 | array Vector = [3] F32 7 | 8 | @ Component for receiving IMU data via poll method 9 | passive component Imu { 10 | # ---------------------------------------------------------------------- 11 | # General ports 12 | # ---------------------------------------------------------------------- 13 | 14 | @ Port to send telemetry to ground 15 | guarded input port Run: Svc.Sched 16 | 17 | @ Port that reads data from device 18 | output port read: Drv.I2c 19 | 20 | @ Port that writes data to device 21 | output port write: Drv.I2c 22 | 23 | # ---------------------------------------------------------------------- 24 | # Special ports 25 | # ---------------------------------------------------------------------- 26 | 27 | @ Command receive 28 | command recv port cmdIn 29 | 30 | @ Command registration 31 | command reg port cmdRegOut 32 | 33 | @ Command response 34 | command resp port cmdResponseOut 35 | 36 | @ Port for emitting events 37 | event port Log 38 | 39 | @ Port for emitting text events 40 | text event port LogText 41 | 42 | @ Port for getting the time 43 | time get port Time 44 | 45 | @ Telemetry port 46 | telemetry port Tlm 47 | 48 | # ---------------------------------------------------------------------- 49 | # Commands 50 | # ---------------------------------------------------------------------- 51 | 52 | @ Command to turn on the device 53 | guarded command PowerSwitch( 54 | powerState: PowerState 55 | ) \ 56 | opcode 0x01 57 | 58 | # ---------------------------------------------------------------------- 59 | # Events 60 | # ---------------------------------------------------------------------- 61 | 62 | @ Error occurred when requesting telemetry 63 | event TelemetryError( 64 | status: Drv.I2cStatus @< the status value returned 65 | ) \ 66 | severity warning high \ 67 | format "Telemetry request failed with status {}" 68 | 69 | @ Configuration failed 70 | event SetUpConfigError( 71 | writeStatus: Drv.I2cStatus @< the status of writing data to device 72 | ) \ 73 | severity warning high \ 74 | format "Setup Error: Write status failed with code {}" 75 | 76 | @ Device was not taken out of sleep mode 77 | event PowerModeError( 78 | writeStatus: Drv.I2cStatus @< the status of writing data to device 79 | ) \ 80 | severity warning high \ 81 | format "Setup Error: Power mode failed to set up with write code {}" 82 | 83 | @ Report power state 84 | event PowerStatus( 85 | powerStatus: PowerState @< power state of device 86 | ) \ 87 | severity activity high \ 88 | format "The device has been turned {}" 89 | 90 | # ---------------------------------------------------------------------- 91 | # Telemetry 92 | # --------------------------------------------------------------------- 93 | 94 | @ X, Y, Z acceleration from accelerometer 95 | telemetry accelerometer: Vector id 0 update always format "{} g" 96 | 97 | @ X, Y, Z degrees from gyroscope 98 | telemetry gyroscope: Vector id 1 update always format "{} deg/s" 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /SystemReference/Gnc/Imu/Imu.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title Imu.hpp 3 | // \author vbai 4 | // \brief hpp file for Imu component implementation class 5 | // ====================================================================== 6 | 7 | #ifndef Gnc_Imu_HPP 8 | #define Gnc_Imu_HPP 9 | 10 | #include "SystemReference/Gnc/Imu/ImuComponentAc.hpp" 11 | 12 | namespace Gnc { 13 | 14 | class Imu final : public ImuComponentBase { 15 | public: 16 | // ---------------------------------------------------------------------- 17 | // Constants and Types 18 | // ---------------------------------------------------------------------- 19 | 20 | // Values for the static consts were attained from the data sheet of the 21 | // MPU6050 22 | 23 | //! The I2C device addresses 24 | struct I2cDevAddr { 25 | enum T { 26 | //! The I2C device address with AD0 set to zero 27 | AD0_0 = 0x68, 28 | //! The I2C device address with AD0 set to one 29 | AD0_1 = 0x69 30 | }; 31 | }; 32 | static const U8 POWER_MGMT_ADDR = 0x6B; 33 | static const U16 IMU_MAX_DATA_SIZE_BYTES = 6; 34 | static const U16 IMU_REG_SIZE_BYTES = 1; 35 | static const U8 IMU_RAW_ACCEL_ADDR = 0x3B; 36 | static const U8 IMU_RAW_GYRO_ADDR = 0x43; 37 | static const U8 GYRO_CONFIG_ADDR = 0x1B; 38 | static const U8 ACCEL_CONFIG_ADDR = 0x1C; 39 | static const U8 POWER_ON_VALUE = 0; 40 | static const U8 POWER_OFF_VALUE = 0x40; 41 | static constexpr float accelScaleFactor = 16384.0f; 42 | static constexpr float gyroScaleFactor = 131.072f; 43 | 44 | // ---------------------------------------------------------------------- 45 | // Construction, initialization, and destruction 46 | // ---------------------------------------------------------------------- 47 | 48 | //! Construct object Imu 49 | //! 50 | Imu(const char* const compName /*!< The component name*/ 51 | ); 52 | 53 | //! Initialize object Imu 54 | //! 55 | void init(const FwIndexType instance = 0 /*!< The instance number*/ 56 | ); 57 | 58 | //! Destroy object Imu 59 | //! 60 | ~Imu(); 61 | 62 | void setup(I2cDevAddr::T devAddress); 63 | 64 | PRIVATE: 65 | // ---------------------------------------------------------------------- 66 | // Handler implementations for user-defined typed input ports 67 | // ---------------------------------------------------------------------- 68 | 69 | //! Handler implementation for Run 70 | //! 71 | void Run_handler(const FwIndexType portNum, /*!< The port number*/ 72 | U32 context /*! 8 | 9 | #include "Tester.hpp" 10 | #include 11 | 12 | #define INSTANCE 0 13 | #define MAX_HISTORY_SIZE 100 14 | #define ADDRESS_TEST Gnc::Imu::I2cDevAddr::AD0_0 15 | 16 | namespace Gnc { 17 | 18 | // ---------------------------------------------------------------------- 19 | // Construction and destruction 20 | // ---------------------------------------------------------------------- 21 | 22 | Tester ::Tester() : 23 | ImuGTestBase("Tester", MAX_HISTORY_SIZE), component("Imu"), 24 | addrBuf(0), 25 | accelSerBuf(this->accelBuf, sizeof this->accelBuf), 26 | gyroSerBuf(this->gyroBuf, sizeof this->gyroBuf) 27 | { 28 | memset(this->accelBuf, 0, sizeof this->accelBuf); 29 | memset(this->gyroBuf, 0, sizeof this->gyroBuf); 30 | this->initComponents(); 31 | this->connectPorts(); 32 | this->component.setup(ADDRESS_TEST); 33 | } 34 | 35 | Tester ::~Tester() {} 36 | 37 | // ---------------------------------------------------------------------- 38 | // Tests 39 | // ---------------------------------------------------------------------- 40 | 41 | void Tester ::testGetAccelTlm() { 42 | sendCmd_PowerSwitch(0, 0, PowerState::ON); 43 | for (U32 i = 0; i < 5; i++) { 44 | this->invoke_to_Run(0, 0); 45 | Gnc::Vector expectedVector; 46 | for (U32 j = 0; j < 3; ++j) { 47 | I16 intCoord = 0; 48 | const auto status = this->accelSerBuf.deserialize(intCoord); 49 | EXPECT_EQ(status, Fw::FW_SERIALIZE_OK); 50 | const F32 f32Coord = static_cast(intCoord) / Imu::accelScaleFactor; 51 | expectedVector[j] = f32Coord; 52 | } 53 | ASSERT_TLM_accelerometer(i, expectedVector); 54 | } 55 | } 56 | 57 | void Tester ::testGetGyroTlm() { 58 | sendCmd_PowerSwitch(0, 0, PowerState::ON); 59 | for (U32 i = 0; i < 5; i++) { 60 | this->invoke_to_Run(0, 0); 61 | Gnc::Vector expectedVector; 62 | for (U32 j = 0; j < 3; ++j) { 63 | I16 intCoord = 0; 64 | const auto status = this->gyroSerBuf.deserialize(intCoord); 65 | EXPECT_EQ(status, Fw::FW_SERIALIZE_OK); 66 | const F32 f32Coord = 67 | static_cast(intCoord) / Imu::gyroScaleFactor; 68 | expectedVector[j] = f32Coord; 69 | } 70 | ASSERT_TLM_gyroscope(i, expectedVector); 71 | } 72 | } 73 | 74 | void Tester::testTlmError() { 75 | sendCmd_PowerSwitch(0, 0, PowerState::ON); 76 | this->m_readStatus = Drv::I2cStatus::I2C_OTHER_ERR; 77 | this->m_writeStatus = Drv::I2cStatus::I2C_OTHER_ERR; 78 | this->invoke_to_Run(0, 0); 79 | ASSERT_EVENTS_TelemetryError_SIZE(2); 80 | ASSERT_EVENTS_TelemetryError(0, m_readStatus); 81 | ASSERT_EVENTS_TelemetryError(0, m_readStatus); 82 | } 83 | 84 | void Tester::testPowerError() { 85 | this->m_writeStatus = Drv::I2cStatus::I2C_WRITE_ERR; 86 | sendCmd_PowerSwitch(0, 0, PowerState::OFF); // Repeated action, no change 87 | ASSERT_EVENTS_PowerModeError_SIZE(0); 88 | sendCmd_PowerSwitch(0, 0, PowerState::ON); 89 | ASSERT_EVENTS_PowerModeError_SIZE(1); 90 | ASSERT_EVENTS_SetUpConfigError_SIZE(0); 91 | ASSERT_EVENTS_PowerModeError(0, m_writeStatus); 92 | } 93 | 94 | void Tester::testSetupError() { 95 | // Used to denote "OK" then "ERROR"; see handler 96 | this->m_writeStatus = Drv::I2cStatus::I2C_ADDRESS_ERR; 97 | sendCmd_PowerSwitch(0, 0, PowerState::ON); 98 | ASSERT_EVENTS_SetUpConfigError_SIZE(2); 99 | ASSERT_EVENTS_SetUpConfigError(0, m_writeStatus); 100 | } 101 | 102 | // ---------------------------------------------------------------------- 103 | // Handlers for typed from ports 104 | // ---------------------------------------------------------------------- 105 | 106 | Drv::I2cStatus Tester ::from_read_handler(const NATIVE_INT_TYPE portNum, U32 addr, Fw::Buffer& serBuffer) { 107 | this->pushFromPortEntry_read(addr, serBuffer); 108 | EXPECT_EQ(addr, ADDRESS_TEST); 109 | if (this->m_readStatus == Drv::I2cStatus::I2C_OK) { 110 | // Fill buffer with random data 111 | U8* const data = serBuffer.getData(); 112 | const U32 size = serBuffer.getSize(); 113 | const U32 imu_max_data_size = Gnc::Imu::IMU_MAX_DATA_SIZE_BYTES; 114 | EXPECT_LE(size, imu_max_data_size); 115 | for (U32 i = 0; i < size; ++i) { 116 | const U8 byte = STest::Pick::any(); 117 | data[i] = byte; 118 | } 119 | if (this->addrBuf == Imu::IMU_RAW_ACCEL_ADDR) { 120 | // Last address write was IMU_RAW_ACCEL_ADDR 121 | // Copy data into the accel buffer 122 | this->accelSerBuf.resetSer(); 123 | const auto status = this->accelSerBuf.pushBytes(&data[0], size); 124 | EXPECT_EQ(status, Fw::FW_SERIALIZE_OK); 125 | } 126 | else if (this->addrBuf == Imu::IMU_RAW_GYRO_ADDR) { 127 | // Last address write was IMU_RAW_GYRO_ADDR 128 | // Copy data into the gyro buffer 129 | this->gyroSerBuf.resetSer(); 130 | const auto status = this->gyroSerBuf.pushBytes(&data[0], size); 131 | EXPECT_EQ(status, Fw::FW_SERIALIZE_OK); 132 | } 133 | } 134 | return this->m_readStatus; 135 | } 136 | 137 | Drv::I2cStatus Tester ::from_write_handler(const NATIVE_INT_TYPE portNum, U32 addr, Fw::Buffer& serBuffer) { 138 | this->pushFromPortEntry_write(addr, serBuffer); 139 | EXPECT_EQ(addr, ADDRESS_TEST); 140 | const U32 size = serBuffer.getSize(); 141 | EXPECT_GT(size, 0); 142 | U8* const data = (U8*)serBuffer.getData(); 143 | Drv::I2cStatus status = Drv::I2cStatus::I2C_OK; 144 | if (size == 1) { 145 | // Address write: store the address in the address buffer 146 | this->addrBuf = data[0]; 147 | } 148 | if (this->m_writeStatus == Drv::I2cStatus::I2C_ADDRESS_ERR) { 149 | // If the write status indicates an address error, then return 150 | // OK this time and set up the write status for a write error next time 151 | this->m_writeStatus = Drv::I2cStatus::I2C_WRITE_ERR; 152 | } 153 | else { 154 | // Otherwise return the write status 155 | status = this->m_writeStatus; 156 | } 157 | return status; 158 | } 159 | 160 | // ---------------------------------------------------------------------- 161 | // Helper methods 162 | // ---------------------------------------------------------------------- 163 | 164 | void Tester ::connectPorts() { 165 | // Run 166 | this->connect_to_Run(0, this->component.get_Run_InputPort(0)); 167 | 168 | // cmdIn 169 | this->connect_to_cmdIn(0, this->component.get_cmdIn_InputPort(0)); 170 | 171 | // Log 172 | this->component.set_Log_OutputPort(0, this->get_from_Log(0)); 173 | 174 | // LogText 175 | this->component.set_LogText_OutputPort(0, this->get_from_LogText(0)); 176 | 177 | // Time 178 | this->component.set_Time_OutputPort(0, this->get_from_Time(0)); 179 | 180 | // Tlm 181 | this->component.set_Tlm_OutputPort(0, this->get_from_Tlm(0)); 182 | 183 | // cmdRegOut 184 | this->component.set_cmdRegOut_OutputPort(0, this->get_from_cmdRegOut(0)); 185 | 186 | // cmdResponseOut 187 | this->component.set_cmdResponseOut_OutputPort(0, this->get_from_cmdResponseOut(0)); 188 | 189 | // read 190 | this->component.set_read_OutputPort(0, this->get_from_read(0)); 191 | 192 | // write 193 | this->component.set_write_OutputPort(0, this->get_from_write(0)); 194 | } 195 | 196 | void Tester ::initComponents() { 197 | this->init(); 198 | this->component.init(INSTANCE); 199 | } 200 | 201 | } // end namespace Gnc 202 | -------------------------------------------------------------------------------- /SystemReference/Gnc/Imu/test/ut/Tester.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title Imu/test/ut/Tester.hpp 3 | // \author vbai 4 | // \brief hpp file for Imu test harness implementation class 5 | // ====================================================================== 6 | 7 | #ifndef TESTER_HPP 8 | #define TESTER_HPP 9 | 10 | #include "Fw/Types/SerialBuffer.hpp" 11 | #include "GTestBase.hpp" 12 | #include "STest/Pick/Pick.hpp" 13 | #include "STest/Random/Random.hpp" 14 | #include "SystemReference/Gnc/Imu/Imu.hpp" 15 | 16 | namespace Gnc { 17 | 18 | class Tester : public ImuGTestBase { 19 | public: 20 | // ---------------------------------------------------------------------- 21 | // Constants and Types 22 | // ---------------------------------------------------------------------- 23 | 24 | //! The size of the read buffer 25 | static constexpr U16 READ_BUF_SIZE_BYTES = Imu::IMU_MAX_DATA_SIZE_BYTES; 26 | 27 | public: 28 | // ---------------------------------------------------------------------- 29 | // Construction and destruction 30 | // ---------------------------------------------------------------------- 31 | 32 | //! Construct object Tester 33 | //! 34 | Tester(); 35 | 36 | //! Destroy object Tester 37 | //! 38 | ~Tester(); 39 | 40 | public: 41 | // ---------------------------------------------------------------------- 42 | // Tests 43 | // ---------------------------------------------------------------------- 44 | 45 | //! Test to get gyroscope telemetry 46 | //! 47 | void testGetGyroTlm(); 48 | 49 | //! Test to get accelerometer telemetry 50 | //! 51 | void testGetAccelTlm(); 52 | 53 | //! Test to check event of bad telemetry request 54 | //! 55 | void testTlmError(); 56 | 57 | //! Test to check power on error 58 | //! 59 | void testPowerError(); 60 | 61 | //! Test to check setup error 62 | //! 63 | void testSetupError(); 64 | 65 | private: 66 | // ---------------------------------------------------------------------- 67 | // Handlers for typed from ports 68 | // ---------------------------------------------------------------------- 69 | 70 | //! Handler for from_read 71 | //! 72 | Drv::I2cStatus from_read_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ 73 | U32 addr, /*!< I2C slave device address*/ 74 | Fw::Buffer& serBuffer /*!< Buffer with data to read/write to/from*/ 75 | ); 76 | 77 | //! Handler for from_write 78 | //! 79 | Drv::I2cStatus from_write_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ 80 | U32 addr, /*!< I2C slave device address*/ 81 | Fw::Buffer& serBuffer /*!< Buffer with data to read/write to/from*/ 82 | ); 83 | 84 | private: 85 | // ---------------------------------------------------------------------- 86 | // Helper methods 87 | // ---------------------------------------------------------------------- 88 | 89 | //! Connect ports 90 | //! 91 | void connectPorts(); 92 | 93 | //! Initialize components 94 | //! 95 | void initComponents(); 96 | 97 | private: 98 | // ---------------------------------------------------------------------- 99 | // Variables 100 | // ---------------------------------------------------------------------- 101 | 102 | //! The component under test 103 | //! 104 | Imu component; 105 | 106 | //! The read status from the driver 107 | Drv::I2cStatus m_readStatus; 108 | 109 | //! The write status from the driver 110 | Drv::I2cStatus m_writeStatus; 111 | 112 | //! Buffer for storing last address written 113 | U8 addrBuf; 114 | 115 | //! Buffer for storing accel data read by the component 116 | U8 accelBuf[READ_BUF_SIZE_BYTES]; 117 | 118 | //! Serial buffer wrapping accelBuf 119 | Fw::SerialBuffer accelSerBuf; 120 | 121 | //! Buffer for storing gyro data read by the component 122 | U8 gyroBuf[READ_BUF_SIZE_BYTES]; 123 | 124 | //! Serial buffer wrapping gyroBuf 125 | Fw::SerialBuffer gyroSerBuf; 126 | }; 127 | 128 | } // end namespace Gnc 129 | 130 | #endif 131 | -------------------------------------------------------------------------------- /SystemReference/Payload/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Components 2 | add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Camera/") 3 | -------------------------------------------------------------------------------- /SystemReference/Payload/Camera/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 | # Note: using PROJECT_NAME as EXECUTABLE_NAME 8 | #### 9 | set(SOURCE_FILES 10 | "${CMAKE_CURRENT_LIST_DIR}/Camera.fpp" 11 | "${CMAKE_CURRENT_LIST_DIR}/Camera.cpp" 12 | ) 13 | register_fprime_module() 14 | 15 | find_package(PkgConfig) 16 | 17 | pkg_check_modules(LIBCAMERA REQUIRED IMPORTED_TARGET GLOBAL libcamera) 18 | message(STATUS "libcamera library found:") 19 | message(STATUS " version: ${LIBCAMERA_VERSION}") 20 | message(STATUS " libraries: ${LIBCAMERA_LINK_LIBRARIES}") 21 | message(STATUS " include path: ${LIBCAMERA_INCLUDE_DIRS}") 22 | 23 | get_module_name(${CMAKE_CURRENT_LIST_DIR}) 24 | target_link_libraries(${MODULE_NAME} PUBLIC PkgConfig::LIBCAMERA) 25 | 26 | # Register the unit test build 27 | set(UT_SOURCE_FILES 28 | "${CMAKE_CURRENT_LIST_DIR}/Camera.fpp" 29 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/TestMain.cpp" 30 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp" 31 | ) 32 | set(UT_MOD_DEPS STest) 33 | register_fprime_ut() 34 | -------------------------------------------------------------------------------- /SystemReference/Payload/Camera/Camera.cpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title Camera.cpp 3 | // \author vbai 4 | // \brief cpp file for Camera component implementation class 5 | // ====================================================================== 6 | 7 | #include "Fw/Types/BasicTypes.hpp" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace Payload { 18 | 19 | // ---------------------------------------------------------------------- 20 | // Construction, initialization, and destruction 21 | // ---------------------------------------------------------------------- 22 | 23 | Camera ::Camera(const char *const compName): CameraComponentBase(compName), m_photoCount(0){} 24 | 25 | void Camera ::init(const FwIndexType queueDepth, const FwIndexType instance) { 26 | CameraComponentBase::init(queueDepth, instance); 27 | } 28 | // start camera manager and acquire camera 29 | // return true on success, false otherwise 30 | bool Camera::open(I32 deviceIndex) { 31 | int returnCode = camManager->start(); 32 | // check to see if the camera manager was started successfully 33 | if (returnCode == 0) { 34 | // if no cameras were detected, emit a CameraNotDetect event 35 | if(camManager->cameras().empty()) { 36 | this->log_WARNING_HI_CameraNotDetected(); 37 | return false; 38 | } 39 | // get the camera from the CameraManager and acquire it 40 | m_capture = camManager->cameras()[deviceIndex]; 41 | returnCode = m_capture->acquire(); 42 | 43 | // camera was acquired successfully 44 | if (returnCode == 0) { 45 | return true; 46 | } 47 | // camera was previously acquired, emit a CameraAlreadyOpen event 48 | else if (returnCode == EBUSY) { 49 | this->log_ACTIVITY_LO_CameraAlreadyOpen(); 50 | return true; 51 | } 52 | } 53 | // some other error occurred, emit a CameraOpenError event 54 | this->log_WARNING_HI_CameraOpenError(); 55 | return false; 56 | } 57 | 58 | Camera ::~Camera() {} 59 | 60 | void Camera ::parameterUpdated(FwPrmIdType id) { 61 | // Read the parameter value 62 | Fw::ParamValid isValid; 63 | ImgResolution resolution = this->paramGet_IMG_RESOLUTION(isValid); 64 | FW_ASSERT(isValid == Fw::ParamValid::VALID, isValid); 65 | 66 | if(PARAMID_IMG_RESOLUTION == id) { 67 | // update the camera configuration to use the updated IMG_RESOLUTION parameter value 68 | setCameraConfiguration(resolution); 69 | } 70 | } 71 | 72 | void Camera ::requestComplete(libcamera::Request *request) { 73 | if (request->status() == libcamera::Request::RequestCancelled) { 74 | return; 75 | } 76 | requestReceivedPtr = request; 77 | } 78 | 79 | void Camera ::allocateBuffers() { 80 | allocatorPtr = new libcamera::FrameBufferAllocator(m_capture); 81 | libcamera::Stream *stream = cameraConfig->at(0).stream(); 82 | // Set up buffer for image data 83 | allocatorPtr->allocate(stream); 84 | const std::vector> &buffers = allocatorPtr->buffers(stream); 85 | for (const std::unique_ptr &buffer : buffers) { 86 | size_t buffer_size = 0; 87 | for (unsigned i = 0; i < buffer->planes().size(); i++) { 88 | const libcamera::FrameBuffer::Plane &plane = buffer->planes()[i]; 89 | buffer_size += plane.length; 90 | if (i == buffer->planes().size() - 1 || plane.fd.get() != buffer->planes()[i + 1].fd.get()) { 91 | void *memory = mmap(NULL, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, plane.fd.get(), 0); 92 | mappedBuffers[buffer.get()].push_back(libcamera::Span(static_cast(memory), buffer_size)); 93 | buffer_size = 0; 94 | } 95 | } 96 | // multiple buffers may be allocated for the stream so we keep track of each 97 | availableFrameBuffers[stream].push(buffer.get()); 98 | } 99 | } 100 | 101 | void Camera::configureRequests() { 102 | libcamera::Stream *stream = cameraConfig->at(0).stream(); 103 | if (!availableFrameBuffers[stream].empty()) { 104 | // create an empty frame capture request for the application to fill with buffers 105 | std::unique_ptr request = m_capture->createRequest(); 106 | if(request) { 107 | frameRequest = std::move(request); 108 | libcamera::FrameBuffer *buffer = availableFrameBuffers[stream].front(); 109 | availableFrameBuffers[stream].pop(); 110 | frameRequest->addBuffer(stream, buffer); 111 | m_capture->requestCompleted.connect(this, &Camera::requestComplete); 112 | } 113 | } 114 | } 115 | 116 | // ---------------------------------------------------------------------- 117 | // Command handler implementations 118 | // ---------------------------------------------------------------------- 119 | 120 | void Camera ::CaptureImage_cmdHandler(const FwOpcodeType opCode, const U32 cmdSeq) { 121 | if(!m_capture) { 122 | this->log_WARNING_HI_CameraNotDetected(); 123 | this->log_WARNING_HI_CameraCaptureFail(); 124 | return; 125 | } 126 | // Read back the parameter value 127 | Fw::ParamValid isValid; 128 | ImgResolution currentResolution = this->paramGet_IMG_RESOLUTION(isValid); 129 | // if the IMG_RESOLUTION parameter is invalid or not set, use the default image resolution 130 | currentResolution = ((Fw::ParamValid::INVALID == isValid) || (Fw::ParamValid::UNINIT == isValid)) ? DEFAULT_IMG_RESOLUTION : currentResolution; 131 | 132 | // setup we need to do before we capture frames 133 | setCameraConfiguration(currentResolution); 134 | Fw::Buffer imgBuffer; 135 | allocateBuffers(); 136 | configureRequests(); 137 | 138 | // start the camera 139 | if(!cameraStarted) { 140 | m_capture->start(); 141 | cameraStarted = true; 142 | } 143 | // queue request for capture 144 | m_capture->queueRequest(frameRequest.get()); 145 | // wait a few seconds for the request to complete 146 | sleep(3); 147 | // check to see if the request was completed 148 | if (requestReceivedPtr && requestReceivedPtr->status() == libcamera::Request::RequestComplete) { 149 | const libcamera::Request::BufferMap &buffers = requestReceivedPtr->buffers(); 150 | for (auto bufferPair : buffers) { 151 | libcamera::FrameBuffer *buffer = bufferPair.second; 152 | const libcamera::FrameMetadata &metadata = buffer->metadata(); 153 | unsigned int bytesUsed = metadata.planes()[0].bytesused; 154 | 155 | // allocate memory to the framework buffer for storing frame data 156 | imgBuffer = allocate_out(0, bytesUsed); 157 | // check to see if the buffer is the correct size 158 | // if not, emit a InvalidBufferSizeError event 159 | if(imgBuffer.getSize() < bytesUsed) { 160 | this->log_WARNING_HI_InvalidBufferSizeError(imgBuffer.getSize(), bytesUsed); 161 | this->deallocate_out(0, imgBuffer); 162 | return; 163 | } 164 | 165 | // copy data to framework buffer and send it to the BufferLogger 166 | memcpy(imgBuffer.getData(), mappedBuffers.find(buffer)->second.back().data(), bytesUsed); 167 | this->save_out(0, imgBuffer); 168 | this->log_ACTIVITY_LO_CameraSave(); 169 | } 170 | } 171 | // no data, send blank frame event 172 | else { 173 | cleanup(); 174 | this->log_WARNING_HI_BlankFrame(); 175 | return; 176 | } 177 | // cleanup we need to do after capturing frames 178 | // ie: stop camera, deallocate memory, etc. 179 | cleanup(); 180 | m_photoCount++; 181 | this->tlmWrite_photosTaken(m_photoCount); 182 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); 183 | } 184 | 185 | // update camera configuration based on specified resolution 186 | bool Camera ::setCameraConfiguration(ImgResolution resolution) { 187 | if(!m_capture) { 188 | this->log_WARNING_HI_CameraNotDetected(); 189 | this->log_WARNING_HI_ImgConfigSetFail(resolution); 190 | return false; 191 | } 192 | 193 | // need to stop the camera before configuring it 194 | if(cameraStarted) { 195 | m_capture->stop(); 196 | cameraStarted = false; 197 | } 198 | 199 | // configure camera 200 | // specify configuration type as a "StillCapture" (high resolution & high-quality still images with low frame rate) 201 | cameraConfig = m_capture->generateConfiguration( { libcamera::StreamRole::StillCapture } ); 202 | if(!cameraConfig) { 203 | this->log_WARNING_HI_ImgConfigSetFail(resolution); 204 | return false; 205 | } 206 | 207 | // set stream height and width 208 | switch (resolution.e) { 209 | case ImgResolution::SIZE_640x480: 210 | cameraConfig->at(0).size.width = 640; 211 | cameraConfig->at(0).size.height = 480; 212 | break; 213 | case ImgResolution::SIZE_800x600: 214 | cameraConfig->at(0).size.width = 800; 215 | cameraConfig->at(0).size.height = 600; 216 | break; 217 | default: 218 | FW_ASSERT(0); 219 | } 220 | 221 | // set the pixel format 222 | cameraConfig->at(0).pixelFormat = libcamera::formats::RGB888; 223 | 224 | // check to see if the configuration was valid 225 | libcamera::CameraConfiguration::Status validationStatus = cameraConfig->validate(); 226 | if (validationStatus != libcamera::CameraConfiguration::Status::Invalid) { 227 | // apply the configuration and keep track of the return code 228 | int returnCode = m_capture->configure(cameraConfig.get()); 229 | 230 | // check to see if an error occurred and emit an ImgConfigSetFail event 231 | if (returnCode != 0) { 232 | this->log_WARNING_HI_ImgConfigSetFail(resolution); 233 | return false; 234 | } 235 | 236 | this->log_ACTIVITY_HI_SetImgConfig(resolution); 237 | return true; 238 | } 239 | else { 240 | // configuration was not valid, emit an ImgConfigSetFail event 241 | this->log_WARNING_HI_ImgConfigSetFail(resolution); 242 | return false; 243 | } 244 | } 245 | 246 | void Camera ::cleanup() { 247 | // remove requestCompleted signal 248 | m_capture->requestCompleted.disconnect(this, &Camera::requestComplete); 249 | // stop camera 250 | if(cameraStarted) { 251 | m_capture->stop(); 252 | cameraStarted = false; 253 | } 254 | // deallocate mapped buffers 255 | for (auto &iter : mappedBuffers) { 256 | for (auto &span : iter.second) { 257 | munmap(span.data(), span.size()); 258 | } 259 | } 260 | mappedBuffers.clear(); 261 | availableFrameBuffers.clear(); 262 | frameRequest = nullptr; 263 | // free buffers previously allocated for stream 264 | allocatorPtr->free(cameraConfig->at(0).stream()); 265 | delete allocatorPtr; 266 | allocatorPtr = nullptr; 267 | } 268 | 269 | } // end namespace Payload 270 | -------------------------------------------------------------------------------- /SystemReference/Payload/Camera/Camera.fpp: -------------------------------------------------------------------------------- 1 | module Payload { 2 | 3 | enum ImgResolution { SIZE_640x480 = 0 , SIZE_800x600 = 1 } 4 | 5 | @ Component to capture raw images 6 | active component Camera { 7 | # ---------------------------------------------------------------------- 8 | # General ports 9 | # ---------------------------------------------------------------------- 10 | 11 | @ Allocates memory to hold photo buffers 12 | output port allocate: Fw.BufferGet 13 | 14 | @ Deallocates memory that holds photo buffers 15 | output port deallocate: Fw.BufferSend 16 | 17 | @ Save photo to disk, send image to buffer logger 18 | output port $save: Fw.BufferSend 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 | @ Port for emitting events 34 | event port Log 35 | 36 | @ Port for emitting text events 37 | text event port LogText 38 | 39 | @ Port for getting the time 40 | time get port Time 41 | 42 | @ Telemetry port 43 | telemetry port Tlm 44 | 45 | @ Port to return the value of a parameter 46 | param get port prmGetOut 47 | 48 | @Port to set the value of a parameter 49 | param set port prmSetOut 50 | 51 | # ---------------------------------------------------------------------- 52 | # Commands 53 | # ---------------------------------------------------------------------- 54 | 55 | @ Capture image and save the raw data 56 | async command CaptureImage() \ 57 | opcode 0x01 58 | 59 | # ---------------------------------------------------------------------- 60 | # Events 61 | # ---------------------------------------------------------------------- 62 | 63 | @ Event where no camera was detected 64 | event CameraNotDetected \ 65 | severity warning high \ 66 | format "No cameras were detected" \ 67 | 68 | @ Event where error occurred when setting up camera 69 | event CameraOpenError \ 70 | severity warning high \ 71 | format "Camera failed to open" \ 72 | 73 | @ Event where camera is already open 74 | event CameraAlreadyOpen \ 75 | severity activity low \ 76 | format "The Camera is already open" \ 77 | 78 | event CameraSave \ 79 | severity activity low \ 80 | format "Image was saved" 81 | 82 | @ Camera failed to capture image 83 | event CameraCaptureFail \ 84 | severity warning high \ 85 | format "Camera failed to capture image" 86 | 87 | @ Event image configuration has been set 88 | event SetImgConfig( 89 | resolution: ImgResolution @< Image size, 90 | ) \ 91 | severity activity high \ 92 | format "The image resolution has been set to {}" \ 93 | 94 | @ Failed to set size and color format 95 | event ImgConfigSetFail( 96 | resolution: ImgResolution @< Image size 97 | ) \ 98 | severity warning high \ 99 | format "Image resolution of {} failed to set" \ 100 | 101 | @ Blank frame Error 102 | event BlankFrame \ 103 | severity warning high \ 104 | format "Error: Blank frame was grabbed" \ 105 | 106 | @ Invalid buffer size error 107 | event InvalidBufferSizeError( 108 | imgBufferSize: U32 @< size of imgBuffer to hold image data 109 | imgSize: U32 @< size of image 110 | ) \ 111 | severity warning high \ 112 | format "imgBuffer of size {} is less than imgSize of size {}" 113 | 114 | # ---------------------------------------------------------------------- 115 | # Telemetry 116 | # ---------------------------------------------------------------------- 117 | 118 | @ Total number of files captured 119 | telemetry photosTaken: U32 id 0 update on change 120 | 121 | 122 | # ---------------------------------------------------------------------- 123 | # Parameters 124 | # ---------------------------------------------------------------------- 125 | 126 | @ Image resolution that the camera should be configured for 127 | param IMG_RESOLUTION: ImgResolution 128 | 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /SystemReference/Payload/Camera/Camera.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title Camera.hpp 3 | // \author vbai 4 | // \brief hpp file for Camera component implementation class 5 | // ====================================================================== 6 | 7 | #ifndef Camera_HPP 8 | #define Camera_HPP 9 | 10 | #include "SystemReference/Payload/Camera/CameraComponentAc.hpp" 11 | #include 12 | #include 13 | 14 | namespace Payload { 15 | 16 | class Camera final : 17 | public CameraComponentBase 18 | { 19 | 20 | public: 21 | 22 | // ---------------------------------------------------------------------- 23 | // Construction, initialization, and destruction 24 | // ---------------------------------------------------------------------- 25 | 26 | //! Construct object Camera 27 | //! 28 | Camera( 29 | const char *const compName /*!< The component name*/ 30 | ); 31 | 32 | //! Initialize object Camera 33 | //! 34 | void init( 35 | const FwIndexType queueDepth, /*!< The queue depth*/ 36 | const FwIndexType instance = 0 /*!< The instance number*/ 37 | ); 38 | 39 | //! Startup camera device 40 | //! 41 | bool open(I32 deviceIndex = 0); 42 | 43 | // allocate buffers 44 | 45 | void allocateBuffers(); 46 | // create or re-use request 47 | void configureRequests(); 48 | 49 | // update camera configuration based on specified resolution 50 | bool setCameraConfiguration(ImgResolution resolution); 51 | 52 | // parameter updates 53 | void parameterUpdated(FwPrmIdType id); 54 | 55 | // stop camera, deallocate memory, etc. 56 | void cleanup(); 57 | 58 | //! Destroy object Camera 59 | //! 60 | ~Camera(); 61 | 62 | PRIVATE: 63 | 64 | void requestComplete(libcamera::Request *request); 65 | 66 | // ---------------------------------------------------------------------- 67 | // Command handler implementations 68 | // ---------------------------------------------------------------------- 69 | 70 | //! Implementation for CaptureImage command handler 71 | //! Capture image and save the raw data 72 | void CaptureImage_cmdHandler( 73 | const FwOpcodeType opCode, /*!< The opcode*/ 74 | const U32 cmdSeq /*!< The command sequence number*/ 75 | ); 76 | 77 | const ImgResolution DEFAULT_IMG_RESOLUTION = ImgResolution::SIZE_640x480; 78 | U32 m_photoCount; 79 | std::unique_ptr camManager = std::make_unique(); 80 | std::shared_ptr m_capture; 81 | std::unique_ptr cameraConfig; 82 | libcamera::Request *requestReceivedPtr; 83 | libcamera::FrameBufferAllocator *allocatorPtr = nullptr; 84 | std::unique_ptr frameRequest; 85 | std::map>> mappedBuffers; 86 | std::map> availableFrameBuffers; 87 | bool cameraStarted = false; 88 | }; 89 | 90 | } // end namespace Payload 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /SystemReference/Payload/Camera/docs/IntegrationGuide.md: -------------------------------------------------------------------------------- 1 | # How To Integrate a New Payload Device 2 | 3 | F´ projects will typically have a type of payload to fulfill the objective of the spacecraft. 4 | These projects may follow this guide to get a basic understanding of integrating similar sensors into F´. 5 | This guide will outline the process of how to integrate a payload. 6 | 7 | ### Assumptions and Definitions for Guide 8 | 9 | This guide makes the following assumptions about the user following the guide and the project setup that will deploy the 10 | component. 11 | 12 | 1. User is familiar with standard F´ concepts 13 | 2. User understands the [application-manager-drive](https://nasa.github.io/fprime/UsersGuide/best/app-man-drv.html) pattern 14 | 3. User is familiar with the F´ development process 15 | 4. User understands how to integrate components into an F´ topology 16 | 5. User has the device's data sheet on hand 17 | 6. User is using a third party library to interact with the sensors of the payload 18 | 19 | ## Example Hardware Description 20 | 21 | Throughout this guide, we will be using a Raspberry Pi Camera Module 2 and one of its component, the camera component, 22 | as an example for how to integrate a payload system. 23 | 24 | ## Step 1: Define Payload Requirements 25 | 26 | The first step is to define the type of requirements that the payload should fulfill. These requirements should 27 | capture: 28 | 29 | 1. Subsystem design 30 | 2. Ports and Interfaces 31 | 3. Events, telemetry, and commands 32 | 4. Component behavior 33 | 5. Assumptions about the interfaces 34 | 6. Overall system integration 35 | 36 | Using the Raspberry Pi camera as an example, requirements can be derived by the following procedure. 37 | We begin by designing the overall system of how images collected by the camera should be handled. 38 | As the collected photos by the camera are of raw image type another component will be needed to handle the processing of the image. 39 | When the images are done being processed they will then get send to the buffer logger where they will eventually will 40 | be saved to disk. 41 | 42 | We next define the requirements needed for the components. As the system mentioned earlier has two steps of how images 43 | will be handled we will need two components, a camera component and an image processor component. We will only be using 44 | the camera component to demonstrate how requirements should be defined. In accordance to the Application Manager Driver 45 | Pattern the camera component would fall under application layer. This is because, the application layer fulfills the 46 | function of a camera, but it knows nothing of the hardware interface to operate the camera not how to translate the images 47 | into a format that is usable. 48 | 49 | The next step is to determine the type of assumptions that are being made in regard to the interface. In the case of the 50 | Raspberry Pi Camera Module 2, information about the interface is provided by the data sheet given by the manufacturer. 51 | Therefore, the requirements, design and implementation will be based on the implicit assumption that the provided 52 | information on the data sheet is accurate in order to interact with the camera. From these considerations the example 53 | requirements derived are shown below: 54 | 55 | 56 | | Requirement | Description | Verification Method | 57 | |--------------------|--------------------------------------------------------------------------------------------------|---------------------| 58 | | PAYLOAD-CAMERA-001 | The `Payload::Camera` component shall capture images on command | Inspection | 59 | | PAYLOAD-CAMERA-002 | The `Payload::Camera` component shall save raw image files to disk | Inspection | 60 | | PAYLOAD-CAMERA-003 | The `Payload::Camera` component shall be able to send raw images to another component to process | Inspection | 61 | | PAYLOAD-CAMERA-004 | The `Payload::Camera` component shall allow users to be able to set image resolution | Unit-Test | 62 | | PAYLOAD-CAMERA-005 | The `Payload::Camera` component shall allows users to be able to set image color format | Unit-Test | 63 | | PAYLOAD-CAMERA-006 | The `Payload::Camera` component shall allows users to set exposure time | Unit-Test | 64 | 65 | 66 | ## Step 2: Component Design 67 | 68 | The second step to integrating a payload is designing the needed component models. This section will go through each design stage 69 | and each decision behind the design. Projects should implement design decisions based on their component requirements. 70 | 71 | The final FPP models can be found here for the 72 | [camera](https://github.com/fprime-community/fprime-system-reference/blob/devel/SystemReference/Payload/Camera/Camera.fpp) 73 | and [image processor](https://github.com/fprime-community/fprime-system-reference/blob/devel/SystemReference/Payload/ImageProcessor/ImageProcessor.fpp) component. 74 | 75 | ### 2.1 Base Component Design 76 | 77 | First, a project should choose the basic component properties. Namely, the component must be `active`, `passive` or 78 | `queued`. Properties of the component will be dependent on the complexity of the required behavior of the hardware. 79 | 80 | For the camera component example, since the device will be capturing images and will also be sending those image to a 81 | buffer logger based on a timed command the component will need to be active. 82 | 83 | ### 2.2 Port Design 84 | 85 | Ports are typically dependent on the outlined requirements, as well as the underlying driver or libraries needed to 86 | interact with the sensor. 87 | 88 | The example component does not use a driver, but it does use the buffer logger to send out image data and allocate 89 | memory. An example port chart can be seen below: 90 | 91 | | Kind | Name | Port Type | Usage | 92 | |-----------------|-------------------|-----------------|-------------------------------------------------------------------| 93 | | `output` | `process` | `Fw.BufferSend` | Port that outputs image data to get utilized by another component | 94 | | `output` | `allocate` | `Fw.BufferGet` | Port that allocates memory to hold image buffers | 95 | | `output` | `$save` | `Fw.BufferSend` | Port that saves image to disk | 96 | **Note:** standard event, telemetry, and command ports are not listed above. 97 | 98 | ### 2.3 Event Design 99 | 100 | Component implementors are free to define whatever events are needed for their project. Typically, Payload components will 101 | emit an error event when the underlying hardware calls fail, or that a command was successfully received. 102 | 103 | In this example, the Camera component defines multiple events: 104 | 105 | | Event | Severity | Description | 106 | |------------------------|-------------|------------------------------------------| 107 | | CameraOpenError | WARNING_HI | Error occurred when requesting telemetry | 108 | | CameraSave | ACTIVITY_LO | Take action command was set to save | 109 | | CameraProcess | ACTIVITY_LO | Take action command was set to process | 110 | | ExposureTimeSet | ACTIVITY_HI | Exposure time was set | 111 | | SetImgConfig | ACTIVITY_HI | Image configuration was set | 112 | | InvalidFormatCmd | WARNING_HI | Invalid format was given | 113 | | InvalidSizeCmd | WARNING_HI | Invalid size was given | 114 | | InvalidExposureTimeCmd | WARNING_HI | Invalid exposure time was give | 115 | | BlankFrame | WARNING_HI | Camera captured a blank image | 116 | | SaveError | WARNING_HI | Image failed to save | 117 | | InvalidTakeCmd | WARNING_HI | Invalid take command was given | 118 | 119 | 120 | ### 2.4 Telemetry Design 121 | 122 | Component implementors are free to define whatever telemetry/channels are needed for their project. Typically, payload 123 | components emit telemetry representing the state of the device. 124 | 125 | In this example, the camera component defined two types of telemetry that keep track of the cameras action: 126 | 127 | | Telemetry Channel | Type | Description | 128 | |-------------------|------|-----------------------------------| 129 | | photosTaken | U32 | Total number of photos captured | 130 | | commandNum | U32 | Total number of commands executed | 131 | 132 | ### 2.5 Commands Design 133 | 134 | Component implementors are free to define whatever commands are needed for their project. Some example of commands to consider 135 | would include the type of actions the sensor would need to take. 136 | 137 | In this example, the camera had three actions to take and therefore three commands were defined: 138 | 139 | | Commands | Type | Fields | Description | 140 | |--------------|---------|--------------------|-------------------------------------------------------------------| 141 | | ExposureTime | `async` | $time | Sets the exposure time of an image | 142 | | TakeAction | `async` | cameraAction | Sets the action of whether the image should be processed or saved | 143 | | ConfigImg | `async` | resolution, format | Sets the resolution and color format of an image | 144 | 145 | ## Implementation and Testing 146 | 147 | Projects will need to implement the port handlers and implementation for their payload components on their own. 148 | Specific implementations will diverge based on hardware and design choices. 149 | 150 | In order to help in this process, the example component implementation is available for the 151 | [camera](https://github.com/fprime-community/fprime-system-reference/blob/devel/SystemReference/Payload/Camera/Camera.cpp) 152 | and [image processor](https://github.com/fprime-community/fprime-system-reference/blob/devel/SystemReference/Payload/ImageProcessor/ImageProcessor.cpp). 153 | 154 | ## Topology Integration 155 | 156 | Once the design and implementation is done, the components can be added to a projects' topology. 157 | Project may attach additional support components as needed. 158 | 159 | ## Conclusion 160 | 161 | In this guide, we have covered the design of a payload components and seen how to integrate it into the 162 | topology. At this point, projects should be able to integrate payload components as needed. 163 | -------------------------------------------------------------------------------- /SystemReference/Payload/Camera/docs/img/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/SystemReference/Payload/Camera/docs/img/camera.png -------------------------------------------------------------------------------- /SystemReference/Payload/Camera/docs/sdd.md: -------------------------------------------------------------------------------- 1 | \page PayloadCameraComponent Payload::Camera Component 2 | # Payload::Camera (Active Component) 3 | 4 | ## 1. Introduction 5 | 'Payload::Camera' is an F' active component that captures images using the Raspberry Pi Camera Module 2. 6 | The component uses the library opencv to interact with the camera in order to capture images, set image resolution, and 7 | color format. 8 | More information can be found from the [data sheet](https://www.raspberrypi.com/documentation/accessories/camera.html#introducing-the-raspberry-pi-cameras), 9 | provided by the manufacturer. 10 | 11 | ## 2. Assumptions 12 | 1. 13 | 14 | ## 3. Requirements 15 | | Requirement | Description | Verification Method | 16 | |--------------------|------------------------------------------------------------------------------------------------------------------------|---------------------| 17 | | PAYLOAD-CAMERA-001 | The `Payload::Camera` component shall capture images on command | Inspection | 18 | | PAYLOAD-CAMERA-002 | The `Payload::Camera` component shall send raw images to a buffer logger when given a save command | Unit-Test | 19 | | PAYLOAD-CAMERA-003 | The `Payload::Camera` component shall send raw images to another component for processing when given a process command | Unit-Test | 20 | | PAYLOAD-CAMERA-004 | The `Payload::Camera` component shall allow users to be able to set image resolution | Unit-Test | 21 | | PAYLOAD-CAMERA-005 | The `Payload::Camera` component shall allows users to be able to set image color format | Unit-Test | 22 | | PAYLOAD-CAMERA-006 | The `Payload::Camera` component shall allows users to set exposure time | Unit-Test | 23 | 24 | ## 4. Design 25 | The diagram below shows the `Camera` component. 26 | 27 | 28 | ### 4.1. Component Design 29 | 30 | ![camera design](img/camera.png) 31 | 32 | ### 4.2. Ports 33 | `Camera` has the following ports: 34 | 35 | | Kind | Name | Port Type | Usage | 36 | |-----------------|------------------|-----------------|-------------------------------------------------------------------| 37 | | `output` | `process` | `Fw.BufferSend` | Port that outputs image data to get utilized by another component | 38 | | `output` | `allocate` | `Fw.BufferGet` | Port that allocates memory to hold image buffers | 39 | | `output` | `save` | `Fw.BufferSend` | Port that saves image to disk | 40 | **Note:** standard event, telemetry, and command ports are not listed above. 41 | 42 | 43 | ### 4.3. State 44 | `ImageProcessor` maintains the following state: 45 | 1. m_cmdCount: `U32` type that keeps track of the number of commands executed 46 | 2. m_photoCount: `U32` type that keeps track of the number of photos captured 47 | 3. m_validCommand: An instance of `bool` that indicates weather a valid command was received or not 48 | 4. m_capture: An instance of `cv::VideoCapture` that stores the captured image frame 49 | 5. m_imageFrame: An instance of `cv::Mat` that stores the image matrix 50 | 51 | ### 4.4. Port Handlers 52 | 53 | #### 4.4.1 TakeAction 54 | The `takeAction` port handler does the following: 55 | 1. Reads the image frame 56 | 2. Determines whether the image gets saved to disk or sent to the buffer logger 57 | 58 | #### 4.4.2 ExposureTime 59 | The `ExposureTime` port handler does the following: 60 | 1. Set the exposure time of the camera 61 | 62 | #### 4.4.3 ConfigImg 63 | The `ConfigImg` port handler does the following: 64 | 1. Sets the resolution of the image 65 | 2. Sets the color format of the image 66 | 67 | ### 4.5. Helper Functions 68 | 69 | #### 4.5.1 open 70 | Activates the camera and ensures that it is open. 71 | 72 | ## 4. Sequence Diagram 73 | -------------------------------------------------------------------------------- /SystemReference/Payload/Camera/test/ut/TEST_1.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/SystemReference/Payload/Camera/test/ut/TEST_1.mov -------------------------------------------------------------------------------- /SystemReference/Payload/Camera/test/ut/TestMain.cpp: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------- 2 | // TestMain.cpp 3 | // ---------------------------------------------------------------------- 4 | 5 | #include "Tester.hpp" 6 | 7 | TEST(Nominal, cameraSave) { 8 | Payload::Tester tester; 9 | tester.testCameraActionSave(); 10 | } 11 | 12 | TEST(Nominal, cameraProcess) { 13 | Payload::Tester tester; 14 | tester.testCameraActionProcess(); 15 | } 16 | 17 | TEST(Nominal, blankFrame) { 18 | Payload::Tester tester; 19 | tester.testBlankFrame(); 20 | } 21 | 22 | TEST(Nominal, badBuffer) { 23 | Payload::Tester tester; 24 | tester.testBadBufferRawImg(); 25 | } 26 | 27 | int main(int argc, char **argv) { 28 | ::testing::InitGoogleTest(&argc, argv); 29 | return RUN_ALL_TESTS(); 30 | } 31 | -------------------------------------------------------------------------------- /SystemReference/Payload/Camera/test/ut/Tester.cpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title Camera.hpp 3 | // \author vbai 4 | // \brief cpp file for Camera test harness implementation class 5 | // ====================================================================== 6 | 7 | #include "Tester.hpp" 8 | 9 | #define INSTANCE 0 10 | #define MAX_HISTORY_SIZE 10 11 | #define QUEUE_DEPTH 10 12 | 13 | namespace Payload { 14 | 15 | // ---------------------------------------------------------------------- 16 | // Construction and destruction 17 | // ---------------------------------------------------------------------- 18 | 19 | Tester ::Tester() 20 | : CameraGTestBase("Tester", MAX_HISTORY_SIZE), component("Camera") { 21 | component.m_capture.open("/Users/vbai/fprime-stuff/fprime-system-reference/SystemReference/Payload/Camera/test/ut/TEST_1.mov"); 22 | this->initComponents(); 23 | this->connectPorts(); 24 | } 25 | 26 | Tester ::~Tester() {} 27 | 28 | // ---------------------------------------------------------------------- 29 | // Tests 30 | // ---------------------------------------------------------------------- 31 | 32 | void Tester::testCameraActionSave() { 33 | CameraAction cameraAction = CameraAction::SAVE; 34 | this->sendCmd_TakeAction(0, 0, cameraAction); 35 | this->component.doDispatch(); 36 | ASSERT_EVENTS_SIZE(1); 37 | ASSERT_EVENTS_CameraSave_SIZE(1); 38 | ASSERT_TLM_SIZE(1); 39 | ASSERT_TLM_photosTaken_SIZE(1); 40 | ASSERT_CMD_RESPONSE_SIZE(1); 41 | ASSERT_CMD_RESPONSE(0, Camera::OPCODE_TAKEACTION, 0, Fw::CmdResponse::OK); 42 | this->clearHistory(); 43 | } 44 | 45 | void Tester::testCameraActionProcess() { 46 | CameraAction cameraAction = CameraAction::PROCESS; 47 | this->sendCmd_TakeAction(0, 0, cameraAction); 48 | this->component.doDispatch(); 49 | ASSERT_EVENTS_SIZE(1); 50 | ASSERT_EVENTS_CameraProcess_SIZE(1); 51 | ASSERT_TLM_SIZE(1); 52 | ASSERT_TLM_photosTaken_SIZE(1); 53 | ASSERT_CMD_RESPONSE_SIZE(1); 54 | ASSERT_CMD_RESPONSE(0, Camera::OPCODE_TAKEACTION, 0, Fw::CmdResponse::OK); 55 | this->clearHistory(); 56 | } 57 | 58 | 59 | void Tester::testBlankFrame() { 60 | cv::Mat frame; 61 | do{ 62 | this->component.m_capture.read(frame); 63 | }while(!frame.empty()); 64 | 65 | CameraAction cameraAction = CameraAction::PROCESS; 66 | this->sendCmd_TakeAction(0, 0, cameraAction); 67 | this->component.doDispatch(); 68 | ASSERT_EVENTS_SIZE(1); 69 | ASSERT_EVENTS_BlankFrame_SIZE(1); 70 | } 71 | 72 | void Tester::testBadBufferRawImg() { 73 | m_bufferSize = 0; 74 | cv::Mat frame; 75 | this->component.m_capture.read(frame); 76 | U32 imgSize = frame.rows * frame.cols * frame.elemSize(); 77 | Fw::Buffer buffer(new U8[m_bufferSize], m_bufferSize); 78 | CameraAction cameraAction = CameraAction::PROCESS; 79 | this->sendCmd_TakeAction(0, 0, cameraAction); 80 | this->component.doDispatch(); 81 | ASSERT_EVENTS_SIZE(1); 82 | ASSERT_EVENTS_InvalidBufferSizeError(0, m_bufferSize, imgSize); 83 | ASSERT_EVENTS_InvalidBufferSizeError_SIZE(1); 84 | } 85 | 86 | 87 | // ---------------------------------------------------------------------- 88 | // Handlers for typed from ports 89 | // ---------------------------------------------------------------------- 90 | 91 | Fw::Buffer Tester ::from_allocate_handler(const NATIVE_INT_TYPE portNum, 92 | U32 size) { 93 | size = m_bufferSize; 94 | this->pushFromPortEntry_allocate(size); 95 | Fw::Buffer buffer(new U8[size], size); 96 | return buffer; 97 | } 98 | 99 | void Tester ::from_deallocate_handler(const NATIVE_INT_TYPE portNum, 100 | Fw::Buffer &fwBuffer) { 101 | delete[] fwBuffer.getData(); 102 | this->pushFromPortEntry_deallocate(fwBuffer); 103 | } 104 | 105 | void Tester ::from_process_handler(const NATIVE_INT_TYPE portNum, 106 | Payload::RawImageData &ImageData) { 107 | this->pushFromPortEntry_process(ImageData); 108 | } 109 | 110 | void Tester ::from_save_handler(const NATIVE_INT_TYPE portNum, 111 | Fw::Buffer &fwBuffer) { 112 | this->pushFromPortEntry_save(fwBuffer); 113 | } 114 | 115 | // ---------------------------------------------------------------------- 116 | // Helper methods 117 | // ---------------------------------------------------------------------- 118 | 119 | void Tester ::connectPorts() { 120 | 121 | // cmdIn 122 | this->connect_to_cmdIn(0, this->component.get_cmdIn_InputPort(0)); 123 | 124 | // Log 125 | this->component.set_Log_OutputPort(0, this->get_from_Log(0)); 126 | 127 | // LogText 128 | this->component.set_LogText_OutputPort(0, this->get_from_LogText(0)); 129 | 130 | // Time 131 | this->component.set_Time_OutputPort(0, this->get_from_Time(0)); 132 | 133 | // Tlm 134 | this->component.set_Tlm_OutputPort(0, this->get_from_Tlm(0)); 135 | 136 | // allocate 137 | this->component.set_allocate_OutputPort(0, this->get_from_allocate(0)); 138 | 139 | // cmdRegOut 140 | this->component.set_cmdRegOut_OutputPort(0, this->get_from_cmdRegOut(0)); 141 | 142 | // cmdResponseOut 143 | this->component.set_cmdResponseOut_OutputPort( 144 | 0, this->get_from_cmdResponseOut(0)); 145 | 146 | // deallocate 147 | this->component.set_deallocate_OutputPort(0, this->get_from_deallocate(0)); 148 | 149 | // process 150 | this->component.set_process_OutputPort(0, this->get_from_process(0)); 151 | 152 | // save 153 | this->component.set_save_OutputPort(0, this->get_from_save(0)); 154 | } 155 | 156 | void Tester ::initComponents() { 157 | this->init(); 158 | this->component.init(QUEUE_DEPTH, INSTANCE); 159 | } 160 | 161 | } // end namespace Payload 162 | -------------------------------------------------------------------------------- /SystemReference/Payload/Camera/test/ut/Tester.hpp: -------------------------------------------------------------------------------- 1 | // ====================================================================== 2 | // \title Camera/test/ut/Tester.hpp 3 | // \author pel 4 | // \brief hpp file for Camera test harness implementation class 5 | // ====================================================================== 6 | 7 | #ifndef TESTER_HPP 8 | #define TESTER_HPP 9 | 10 | #include "GTestBase.hpp" 11 | #include "SystemReference/Payload/Camera/Camera.hpp" 12 | #include 13 | #include 14 | 15 | 16 | namespace Payload { 17 | 18 | class Tester : 19 | public CameraGTestBase 20 | { 21 | 22 | // ---------------------------------------------------------------------- 23 | // Construction and destruction 24 | // ---------------------------------------------------------------------- 25 | 26 | public: 27 | 28 | //! Construct object Tester 29 | //! 30 | Tester(); 31 | 32 | //! Destroy object Tester 33 | //! 34 | ~Tester(); 35 | 36 | public: 37 | 38 | // ---------------------------------------------------------------------- 39 | // Tests 40 | // ---------------------------------------------------------------------- 41 | 42 | //! Test for camera save command 43 | //! 44 | void testCameraActionSave(); 45 | 46 | //! Test for camera process command 47 | //! 48 | void testCameraActionProcess(); 49 | 50 | //! Test for blank frame event 51 | //! 52 | void testBlankFrame(); 53 | 54 | //! Test for bad buffer size event for raw image 55 | //! 56 | void testBadBufferRawImg(); 57 | 58 | private: 59 | 60 | // ---------------------------------------------------------------------- 61 | // Handlers for typed from ports 62 | // ---------------------------------------------------------------------- 63 | 64 | //! Handler for from_allocate 65 | //! 66 | Fw::Buffer from_allocate_handler( 67 | const NATIVE_INT_TYPE portNum, /*!< The port number*/ 68 | U32 size 69 | ); 70 | 71 | //! Handler for from_deallocate 72 | //! 73 | void from_deallocate_handler( 74 | const NATIVE_INT_TYPE portNum, /*!< The port number*/ 75 | Fw::Buffer &fwBuffer 76 | ); 77 | 78 | //! Handler for from_process 79 | //! 80 | void from_process_handler( 81 | const NATIVE_INT_TYPE portNum, /*!< The port number*/ 82 | Payload::RawImageData &ImageData /*!< Port that carries raw image data*/ 83 | ); 84 | 85 | //! Handler for from_save 86 | //! 87 | void from_save_handler( 88 | const NATIVE_INT_TYPE portNum, /*!< The port number*/ 89 | Fw::Buffer &fwBuffer 90 | ); 91 | 92 | private: 93 | 94 | // ---------------------------------------------------------------------- 95 | // Helper methods 96 | // ---------------------------------------------------------------------- 97 | 98 | //! Connect ports 99 | //! 100 | void connectPorts(); 101 | 102 | //! Initialize components 103 | //! 104 | void initComponents(); 105 | 106 | 107 | private: 108 | 109 | // ---------------------------------------------------------------------- 110 | // Variables 111 | // ---------------------------------------------------------------------- 112 | 113 | //! The component under test 114 | //! 115 | Camera component; 116 | 117 | U32 m_bufferSize; 118 | 119 | }; 120 | 121 | } // end namespace Payload 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /SystemReference/Top/.gitignore: -------------------------------------------------------------------------------- 1 | unconnected 2 | visual 3 | -------------------------------------------------------------------------------- /SystemReference/Top/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #### 2 | # F prime CMakeLists.txt: 3 | # 4 | # SOURCE_FILES: combined list of source and autocoding diles 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}/SystemReferenceTopologyDefs.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 | -------------------------------------------------------------------------------- /SystemReference/Top/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | void print_usage(const char* app) { 9 | (void) printf("Usage: ./%s [options]\n-p\tport_number\n-a\thostname/IP address\n",app); 10 | } 11 | 12 | #include 13 | #include 14 | 15 | SystemReference::TopologyState state; 16 | // Enable the console logging provided by Os::Log 17 | Os::Console logger; 18 | 19 | void stopCycle() { 20 | SystemReference::linuxTimer.quit(); 21 | } 22 | 23 | static void signalHandler(int signum) { 24 | stopCycle(); 25 | } 26 | 27 | int main(int argc, char* argv[]) { 28 | U32 port_number = 0; // Invalid port number forced 29 | I32 option; 30 | char *hostname; 31 | option = 0; 32 | hostname = nullptr; 33 | 34 | while ((option = getopt(argc, argv, "hp:a:")) != -1){ 35 | switch(option) { 36 | case 'h': 37 | print_usage(argv[0]); 38 | return 0; 39 | break; 40 | case 'p': 41 | port_number = static_cast(atoi(optarg)); 42 | break; 43 | case 'a': 44 | hostname = optarg; 45 | break; 46 | case '?': 47 | return 1; 48 | default: 49 | print_usage(argv[0]); 50 | return 1; 51 | } 52 | } 53 | 54 | (void) printf("Hit Ctrl-C to quit\n"); 55 | 56 | state = SystemReference::TopologyState(hostname, port_number); 57 | SystemReference::setup(state); 58 | 59 | // register signal handlers to exit program 60 | signal(SIGINT,signalHandler); 61 | signal(SIGTERM,signalHandler); 62 | 63 | SystemReference::linuxTimer.startTimer(1000); 64 | SystemReference::teardown(state); 65 | 66 | (void) printf("Exiting...\n"); 67 | 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /SystemReference/Top/SystemReferenceTelemetryPackets.fppi: -------------------------------------------------------------------------------- 1 | telemetry packets SystemReferencePackets { 2 | 3 | packet CDH id 1 group 1 { 4 | SystemReference.cmdDisp.CommandsDispatched 5 | SystemReference.rateGroup1Comp.RgMaxTime 6 | SystemReference.rateGroup2Comp.RgMaxTime 7 | SystemReference.rateGroup3Comp.RgMaxTime 8 | SystemReference.cmdSeq.CS_LoadCommands 9 | SystemReference.cmdSeq.CS_CancelCommands 10 | SystemReference.cmdSeq.CS_CommandsExecuted 11 | SystemReference.cmdSeq.CS_SequencesCompleted 12 | SystemReference.fileUplink.FilesReceived 13 | SystemReference.fileUplink.PacketsReceived 14 | SystemReference.comBufferManager.TotalBuffs 15 | SystemReference.comBufferManager.CurrBuffs 16 | SystemReference.comBufferManager.HiBuffs 17 | SystemReference.comQueue.comQueueDepth 18 | SystemReference.comQueue.buffQueueDepth 19 | SystemReference.fileDownlink.FilesSent 20 | SystemReference.fileDownlink.PacketsSent 21 | SystemReference.fileManager.CommandsExecuted 22 | SystemReference.tlmSend.SendLevel 23 | } 24 | 25 | packet CDHErrors id 2 group 1 { 26 | SystemReference.rateGroup1Comp.RgCycleSlips 27 | SystemReference.rateGroup2Comp.RgCycleSlips 28 | SystemReference.rateGroup3Comp.RgCycleSlips 29 | SystemReference.cmdSeq.CS_Errors 30 | SystemReference.fileUplink.Warnings 31 | SystemReference.fileDownlink.Warnings 32 | SystemReference.$health.PingLateWarnings 33 | SystemReference.fileManager.Errors 34 | SystemReference.comBufferManager.NoBuffs 35 | SystemReference.comBufferManager.EmptyBuffs 36 | SystemReference.fileManager.Errors 37 | } 38 | 39 | packet SystemResources1 id 5 group 2 { 40 | SystemReference.systemResources.MEMORY_TOTAL 41 | SystemReference.systemResources.MEMORY_USED 42 | SystemReference.systemResources.NON_VOLATILE_TOTAL 43 | SystemReference.systemResources.NON_VOLATILE_FREE 44 | } 45 | 46 | packet SystemResources2 id 6 group 2 { 47 | SystemReference.systemResources.CPU 48 | SystemReference.systemResources.CPU_00 49 | SystemReference.systemResources.CPU_01 50 | SystemReference.systemResources.CPU_02 51 | SystemReference.systemResources.CPU_03 52 | SystemReference.systemResources.CPU_04 53 | SystemReference.systemResources.CPU_05 54 | SystemReference.systemResources.CPU_06 55 | SystemReference.systemResources.CPU_07 56 | SystemReference.systemResources.CPU_08 57 | SystemReference.systemResources.CPU_09 58 | SystemReference.systemResources.CPU_10 59 | SystemReference.systemResources.CPU_11 60 | SystemReference.systemResources.CPU_12 61 | SystemReference.systemResources.CPU_13 62 | SystemReference.systemResources.CPU_14 63 | SystemReference.systemResources.CPU_15 64 | } 65 | 66 | packet Imu id 7 group 2 { 67 | SystemReference.imu.accelerometer 68 | SystemReference.imu.gyroscope 69 | } 70 | 71 | packet Camera id 8 group 2 { 72 | SystemReference.camera.photosTaken 73 | SystemReference.saveImageBufferLogger.BufferLogger_NumLoggedBuffers 74 | } 75 | 76 | } omit { 77 | SystemReference.cmdDisp.CommandErrors 78 | } 79 | 80 | -------------------------------------------------------------------------------- /SystemReference/Top/SystemReferenceTopologyDefs.cpp: -------------------------------------------------------------------------------- 1 | #include "SystemReference/Top/SystemReferenceTopologyDefs.hpp" 2 | 3 | namespace SystemReference { 4 | 5 | namespace Allocation { 6 | 7 | Fw::MallocAllocator mallocator; 8 | 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /SystemReference/Top/SystemReferenceTopologyDefs.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SystemReferenceTopologyDefs_HPP 2 | #define SystemReferenceTopologyDefs_HPP 3 | 4 | #include "Fw/Types/MallocAllocator.hpp" 5 | #include "SystemReference/Top/FppConstantsAc.hpp" 6 | #include "Svc/FramingProtocol/FprimeProtocol.hpp" 7 | #include "Fw/Logger/Logger.hpp" 8 | #include 9 | 10 | namespace SystemReference { 11 | 12 | namespace Allocation { 13 | 14 | // Malloc allocator for topology construction 15 | extern Fw::MallocAllocator mallocator; 16 | 17 | } 18 | 19 | // State for topology construction 20 | struct TopologyState { 21 | TopologyState() : 22 | hostName(""), 23 | portNumber(0) 24 | { 25 | 26 | } 27 | TopologyState( 28 | const char *hostName, 29 | U32 portNumber 30 | ) : 31 | hostName(hostName), 32 | portNumber(portNumber) 33 | { 34 | 35 | } 36 | const char* hostName; 37 | U32 portNumber; 38 | }; 39 | 40 | // Health ping entries 41 | namespace PingEntries { 42 | namespace SystemReference_chanTlm { enum { WARN = 3, FATAL = 5 }; } 43 | namespace SystemReference_cmdDisp { enum { WARN = 3, FATAL = 5 }; } 44 | namespace SystemReference_cmdSeq { enum { WARN = 3, FATAL = 5 }; } 45 | namespace SystemReference_eventLogger { enum { WARN = 3, FATAL = 5 }; } 46 | namespace SystemReference_fileDownlink { enum { WARN = 3, FATAL = 5 }; } 47 | namespace SystemReference_fileManager { enum { WARN = 3, FATAL = 5 }; } 48 | namespace SystemReference_fileUplink { enum { WARN = 3, FATAL = 5 }; } 49 | namespace SystemReference_prmDb { enum { WARN = 3, FATAL = 5 }; } 50 | namespace SystemReference_rateGroup1Comp { enum { WARN = 3, FATAL = 5 }; } 51 | namespace SystemReference_rateGroup2Comp { enum { WARN = 3, FATAL = 5 }; } 52 | namespace SystemReference_rateGroup3Comp { enum { WARN = 3, FATAL = 5 }; } 53 | namespace SystemReference_saveImageBufferLogger { enum {WARN = 3, FATAL = 5}; } 54 | namespace SystemReference_imageProcessor { enum {WARN = 3, FATAL = 5}; } 55 | namespace SystemReference_processedImageBufferLogger { enum {WARN = 3, FATAL = 5}; } 56 | namespace SystemReference_tlmSend { enum {WARN = 3, FATAL = 5}; } 57 | } 58 | 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /SystemReference/Top/topology.fpp: -------------------------------------------------------------------------------- 1 | module SystemReference { 2 | 3 | # ---------------------------------------------------------------------- 4 | # Symbolic constants for port numbers 5 | # ---------------------------------------------------------------------- 6 | 7 | enum Ports_RateGroups { 8 | rateGroup1 9 | rateGroup2 10 | rateGroup3 11 | } 12 | 13 | enum Ports_StaticMemory { 14 | downlink 15 | uplink 16 | } 17 | 18 | topology SystemReference { 19 | 20 | # ---------------------------------------------------------------------- 21 | # Instances used in the topology 22 | # ---------------------------------------------------------------------- 23 | 24 | instance $health 25 | 26 | instance cmdDisp 27 | instance cmdSeq 28 | instance comDriver 29 | instance comQueue 30 | instance radio 31 | instance framer 32 | instance eventLogger 33 | instance fatalAdapter 34 | instance fatalHandler 35 | instance fileDownlink 36 | instance fileManager 37 | instance fileUplink 38 | instance frameAccumulator 39 | instance comBufferManager 40 | instance posixTime 41 | instance prmDb 42 | instance rateGroup1Comp 43 | instance rateGroup2Comp 44 | instance rateGroup3Comp 45 | instance rateGroupDriverComp 46 | instance fprimeRouter 47 | instance textLogger 48 | instance tlmSend 49 | instance deframer 50 | instance systemResources 51 | instance imu 52 | instance imuI2cBus 53 | instance camera 54 | instance saveImageBufferLogger 55 | instance linuxTimer 56 | 57 | # ---------------------------------------------------------------------- 58 | # Pattern graph specifiers 59 | # ---------------------------------------------------------------------- 60 | 61 | command connections instance cmdDisp 62 | 63 | event connections instance eventLogger 64 | 65 | param connections instance prmDb 66 | 67 | telemetry connections instance tlmSend 68 | 69 | text event connections instance textLogger 70 | 71 | time connections instance posixTime 72 | 73 | health connections instance $health 74 | 75 | # ---------------------------------------------------------------------- 76 | # Telemetry packets 77 | # ---------------------------------------------------------------------- 78 | 79 | include "SystemReferenceTelemetryPackets.fppi" 80 | 81 | 82 | # ---------------------------------------------------------------------- 83 | # Direct graph specifiers 84 | # ---------------------------------------------------------------------- 85 | 86 | connections Downlink { 87 | # Inputs to ComQueue (events, telemetry, file) 88 | eventLogger.PktSend -> comQueue.comPacketQueueIn[0] 89 | tlmSend.PktSend -> comQueue.comPacketQueueIn[1] 90 | fileDownlink.bufferSendOut -> comQueue.bufferQueueIn[0] 91 | comQueue.bufferReturnOut[0] -> fileDownlink.bufferReturn 92 | # ComQueue <-> Framer 93 | comQueue.dataOut -> framer.dataIn 94 | framer.dataReturnOut -> comQueue.dataReturnIn 95 | framer.comStatusOut -> comQueue.comStatusIn 96 | # Buffer Management for Framer 97 | framer.bufferAllocate -> comBufferManager.bufferGetCallee 98 | framer.bufferDeallocate -> comBufferManager.bufferSendIn 99 | # Framer <-> ComStub 100 | framer.dataOut -> radio.dataIn 101 | radio.dataReturnOut -> framer.dataReturnIn 102 | radio.comStatusOut -> framer.comStatusIn 103 | # ComStub <-> ComDriver 104 | radio.drvSendOut -> comDriver.$send 105 | comDriver.sendReturnOut -> radio.drvSendReturnIn 106 | comDriver.ready -> radio.drvConnected 107 | } 108 | 109 | connections FaultProtection { 110 | eventLogger.FatalAnnounce -> fatalHandler.FatalReceive 111 | } 112 | 113 | connections RateGroups { 114 | 115 | # Linux Timer to drive rate groups 116 | linuxTimer.CycleOut -> rateGroupDriverComp.CycleIn 117 | 118 | # Rate group 1 119 | rateGroupDriverComp.CycleOut[Ports_RateGroups.rateGroup1] -> rateGroup1Comp.CycleIn 120 | rateGroup1Comp.RateGroupMemberOut[0] -> tlmSend.Run 121 | rateGroup1Comp.RateGroupMemberOut[1] -> fileDownlink.Run 122 | rateGroup1Comp.RateGroupMemberOut[2] -> systemResources.run 123 | rateGroup1Comp.RateGroupMemberOut[3] -> imu.Run 124 | 125 | # XBee Radio Integration 126 | # rateGroup1Comp.RateGroupMemberOut[4] -> radio.run 127 | 128 | # Rate group 2 129 | rateGroupDriverComp.CycleOut[Ports_RateGroups.rateGroup2] -> rateGroup2Comp.CycleIn 130 | rateGroup2Comp.RateGroupMemberOut[0] -> cmdSeq.schedIn 131 | 132 | # Rate group 3 133 | rateGroupDriverComp.CycleOut[Ports_RateGroups.rateGroup3] -> rateGroup3Comp.CycleIn 134 | rateGroup3Comp.RateGroupMemberOut[0] -> $health.Run 135 | rateGroup3Comp.RateGroupMemberOut[1] -> comBufferManager.schedIn 136 | } 137 | 138 | connections Sequencer { 139 | cmdSeq.comCmdOut -> cmdDisp.seqCmdBuff 140 | cmdDisp.seqCmdStatus -> cmdSeq.cmdResponseIn 141 | } 142 | 143 | connections Uplink { 144 | # ComDriver buffer allocations 145 | comDriver.allocate -> comBufferManager.bufferGetCallee 146 | comDriver.deallocate -> comBufferManager.bufferSendIn 147 | # ComDriver <-> ComStub 148 | comDriver.$recv -> radio.drvReceiveIn 149 | radio.drvReceiveReturnOut -> comDriver.recvReturnIn 150 | # ComStub <-> FrameAccumulator 151 | radio.dataOut -> frameAccumulator.dataIn 152 | frameAccumulator.dataReturnOut -> radio.dataReturnIn 153 | # FrameAccumulator buffer allocations 154 | frameAccumulator.bufferDeallocate -> comBufferManager.bufferSendIn 155 | frameAccumulator.bufferAllocate -> comBufferManager.bufferGetCallee 156 | # FrameAccumulator <-> Deframer 157 | frameAccumulator.dataOut -> deframer.dataIn 158 | deframer.dataReturnOut -> frameAccumulator.dataReturnIn 159 | # Deframer <-> Router 160 | deframer.dataOut -> fprimeRouter.dataIn 161 | fprimeRouter.dataReturnOut -> deframer.dataReturnIn 162 | # Router buffer allocations 163 | fprimeRouter.bufferAllocate -> comBufferManager.bufferGetCallee 164 | fprimeRouter.bufferDeallocate -> comBufferManager.bufferSendIn 165 | # Router <-> CmdDispatcher/FileUplink 166 | fprimeRouter.commandOut -> cmdDisp.seqCmdBuff 167 | cmdDisp.seqCmdStatus -> fprimeRouter.cmdResponseIn 168 | fprimeRouter.fileOut -> fileUplink.bufferSendIn 169 | fileUplink.bufferSendOut -> fprimeRouter.fileBufferReturnIn 170 | } 171 | 172 | # XBee Radio Integration 173 | # connections Radio { 174 | # radio.allocate -> comBufferManager.bufferGetCallee 175 | # radio.deallocate -> comBufferManager.bufferSendIn 176 | # } 177 | 178 | connections I2c { 179 | imu.read -> imuI2cBus.read 180 | imu.write -> imuI2cBus.write 181 | } 182 | 183 | connections Camera { 184 | camera.allocate -> comBufferManager.bufferGetCallee 185 | camera.deallocate -> comBufferManager.bufferSendIn 186 | camera.$save -> saveImageBufferLogger.bufferSendIn 187 | saveImageBufferLogger.bufferSendOut -> comBufferManager.bufferSendIn 188 | } 189 | 190 | } 191 | 192 | } 193 | 194 | -------------------------------------------------------------------------------- /SystemReference/settings.ini: -------------------------------------------------------------------------------- 1 | ; Ref requires no specific settings thus the [fprime] configuration block is empty 2 | ; For more information: https://nasa.github.io/fprime/UsersGuide/user/settings.html 3 | [fprime] 4 | project_root: ../ 5 | framework_path: ../fprime 6 | #config_directory: ./config 7 | -------------------------------------------------------------------------------- /bin/docker-run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #### 3 | # docker-run: 4 | # 5 | # This command runs up the docker image needed for building the system reference. 6 | #### 7 | DIRNAME="`dirname $0`" 8 | DIRNAME="`cd "${DIRNAME}"; pwd;`" 9 | 10 | if ! which docker > /dev/null 11 | then 12 | echo "[ERROR] 'docker' command not found. Please ensure 'docker' or 'Rancher Desktop' installed." 1 >&2 13 | exit 1 14 | fi 15 | if [ "`docker images -q nasafprime/fprime-raspi:devel 2> /dev/null`" = "" ] 16 | then 17 | echo "[INFO] Pulling Docker Image: nasafprime/fprime-raspi:devel" 18 | "${DIRNAME}/docker-setup" 19 | fi 20 | echo "[INFO] Running Docker Image: nasafprime/fprime-raspi:devel with args '$@'" 21 | docker run --net host -itv "${DIRNAME}/..:/project" nasafprime/fprime-raspi:devel "$@" 22 | -------------------------------------------------------------------------------- /bin/docker-run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | Rem docker-run: run the docker image 3 | set DIRNAME=%~dp0 4 | 5 | Rem checks that "docker" exists 6 | WHERE docker >nul 2>&1 7 | IF %ERRORLEVEL% NEQ 0 ( 8 | echo [ERROR] 'docker' command not found. Please ensure 'docker' or 'Rancher Desktop' installed. 9 | exit /B 1 10 | ) 11 | 12 | Rem checks that the image has been pulled, or pull it 13 | set docker_image=(docker images -q nasafprime/fprime-raspi:devel 2>nul ) 14 | IF "%docker_image%" == "" ( 15 | echo [INFO] Pulling Docker Image: nasafprime/fprime-raspi:devel 16 | call "%DIRNAME%/docker-setup" 17 | ) 18 | 19 | Rem run the image forwarding arguments 20 | echo [INFO] Running Docker Image: nasafprime/fprime-raspi:devel with args '%*' 21 | docker run --net host -itv "%DIRNAME%..":/project nasafprime/fprime-raspi:devel %* 22 | -------------------------------------------------------------------------------- /bin/docker-setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #### 3 | # docker-setup: 4 | # 5 | # This command sets up the docker image needed for building the system reference. 6 | #### 7 | 8 | if ! which docker > /dev/null 9 | then 10 | echo "[ERROR] 'docker' command not found. Please ensure 'docker' or 'Rancher Desktop' installed." 1 >&2 11 | exit 1 12 | fi 13 | docker pull nasafprime/fprime-raspi:devel 14 | -------------------------------------------------------------------------------- /bin/docker-setup.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | WHERE docker >nul 2>&1 3 | IF %ERRORLEVEL% NEQ 0 ( 4 | echo "[ERROR] 'docker' command not found. Please ensure 'docker' or 'Rancher Desktop' installed." 5 | exit /B 1 6 | ) 7 | docker pull nasafprime/fprime-raspi:devel 8 | -------------------------------------------------------------------------------- /docs/course/appendix-1.md: -------------------------------------------------------------------------------- 1 | # IMU Exercise Appendix I: Code Snippets 2 | 3 | This appendix contains some helper functions to help build the IMU component as part of our lab exercise. Each is 4 | expected to be defined as a member function within the IMU component. 5 | 6 | ## `setupRegisterRead`: Sets up the IMU to know where to read the next value from 7 | 8 | This function performs the write to the IMU to tell it where the next read should pull from. 9 | 10 | Key Features: 11 | - Wrap buffer around single byte 12 | - Write to I2C bus 13 | 14 | ``` C++ 15 | /** 16 | * \brief sets up the IMU to know what register the next read should be from 17 | * 18 | * The MPU-6050 requires a write call with a register's address before a read will function correctly. This helper 19 | * sets up that read address by writing it to the IMU via the I2C write port. 20 | * 21 | * \param reg: IMU internal address to the first register to be read 22 | * \return: I2C from the write call 23 | */ 24 | Drv::I2cStatus Imu ::setupReadRegister(U8 reg) { 25 | Fw::Buffer buffer(®, sizeof reg); 26 | return this->write_out(0, m_i2cDevAddress, buffer); 27 | } 28 | ``` 29 | 30 | ## `readRegisterBlock`: Reads a register block from IMU 31 | 32 | This function sets-up and performs a read of a block of registers from the IMU. 33 | 34 | Key Features: 35 | - Check IMU status (from `setupReadRegister` helper) 36 | - Read data from I2C bus 37 | 38 | ```c++ 39 | /** 40 | * \brief reads a block of registers from the IMU 41 | * 42 | * This function starts by writing the startRegister to the IMU by passing it to `setupReadRegister`. It then calls 43 | * the read port of the I2C bus to read data from the IMU. It will read `buffer.getSize()` bytes from the I2C device 44 | * and as such the caller must set this up. 45 | * 46 | * \param startRegister: register address to start reading from 47 | * \param buffer: buffer to read into. Determines size of read. 48 | * \return: I2C status of transactions 49 | */ 50 | Drv::I2cStatus Imu ::readRegisterBlock(U8 startRegister, Fw::Buffer& buffer) { 51 | Drv::I2cStatus status; 52 | status = this->setupReadRegister(startRegister); 53 | if (status == Drv::I2cStatus::I2C_OK) { 54 | status = this->read_out(0, m_i2cDevAddress, buffer); 55 | } 56 | return status; 57 | } 58 | ``` 59 | 60 | ## `deserializeVector`: Unpacks the data read from the IMU 61 | 62 | This function takes the data from the IMU read and unpacks it into a 3-element buffer. 63 | 64 | Key Features: 65 | - Work with a modeled array type 66 | - Use deserialization interface of `Fw::Buffer` 67 | 68 | ```c++ 69 | /** 70 | * \brief unpacks a buffer into a vector with scaled elements 71 | * 72 | * This will unpack data from buffer into a Gnc::Vector type by unpacking 3x I16 values (in big endian format) and 73 | * scales each by dividing by the scaleFactor provided. 74 | * 75 | * \param buffer: buffer wrapping data, must contain at least 6 byes (3x I16) 76 | * \param scaleFactor: scale factor to divide each element by 77 | * \return initalized vector 78 | */ 79 | Gnc::Vector Imu ::deserializeVector(Fw::Buffer& buffer, F32 scaleFactor) { 80 | Gnc::Vector vector; 81 | I16 value; 82 | FW_ASSERT(buffer.getSize() >= 6, buffer.getSize()); 83 | FW_ASSERT(buffer.getData() != nullptr); 84 | // Data is big-endian as is fprime internal storage so we can use the built-in buffer deserialization 85 | Fw::SerializeBufferBase& deserializeHelper = buffer.getSerializeRepr(); 86 | deserializeHelper.setBuffLen(buffer.getSize()); // Inform the helper what size we have available 87 | FW_ASSERT(deserializeHelper.deserialize(value) == Fw::FW_SERIALIZE_OK); 88 | vector[0] = static_cast(value) / scaleFactor; 89 | 90 | FW_ASSERT(deserializeHelper.deserialize(value) == Fw::FW_SERIALIZE_OK); 91 | vector[1] = static_cast(value) / scaleFactor; 92 | 93 | FW_ASSERT(deserializeHelper.deserialize(value) == Fw::FW_SERIALIZE_OK); 94 | vector[2] = static_cast(value) / scaleFactor; 95 | return vector; 96 | } 97 | ``` 98 | 99 | ## `config`: Configures the IMU 100 | 101 | This function calls the IMU twice with write commands. These commands configure the accelerometer and gyroscope. 102 | 103 | Key Features: 104 | - Wrap `Fw::Buffer` around a C++ array 105 | - Send events 106 | 107 | ```c++ 108 | //! Configure the accelerometer and gyroscope 109 | //! 110 | void Imu ::config() { 111 | U8 data[IMU_REG_SIZE_BYTES * 2]; 112 | Fw::Buffer buffer(data, sizeof data); 113 | 114 | // Set gyro range to +-250 deg/s 115 | data[0] = GYRO_CONFIG_ADDR; 116 | data[1] = 0; 117 | 118 | Drv::I2cStatus status = write_out(0, m_i2cDevAddress, buffer); 119 | if (status != Drv::I2cStatus::I2C_OK) { 120 | this->log_WARNING_HI_SetUpConfigError(status); 121 | } 122 | 123 | // Set accel range to +- 2g 124 | data[0] = ACCEL_CONFIG_ADDR; 125 | data[1] = 0; 126 | status =this-> write_out(0, m_i2cDevAddress, buffer); 127 | 128 | if (status != Drv::I2cStatus::I2C_OK) { 129 | this->log_WARNING_HI_SetUpConfigError(status); 130 | } 131 | } 132 | ``` 133 | 134 | ## `power`: Switches IMU power state 135 | 136 | This function turns on or off the IMU power state (sleep mode vs powered mode). 137 | 138 | Key Features: 139 | - Wrap `Fw::Buffer` around a C++ array 140 | - Send events 141 | - Work with modeled enum types 142 | 143 | ```c++ 144 | //! Turn power on/off of device 145 | //! 146 | void Imu ::power(PowerState powerState) { 147 | U8 data[IMU_REG_SIZE_BYTES * 2]; 148 | Fw::Buffer buffer(data, sizeof data); 149 | 150 | // Check if already on/off 151 | if (powerState.e == m_power) { 152 | return; 153 | } 154 | 155 | data[0] = POWER_MGMT_ADDR; 156 | data[1] = (powerState.e == PowerState::ON) ? POWER_ON_VALUE : POWER_OFF_VALUE; 157 | 158 | Drv::I2cStatus status = this->write_out(0, m_i2cDevAddress, buffer); 159 | if (status != Drv::I2cStatus::I2C_OK) { 160 | this->log_WARNING_HI_PowerModeError(status); 161 | } else { 162 | m_power = powerState.e; 163 | // Must configure at power on 164 | if (m_power == PowerState::ON) { 165 | config(); 166 | } 167 | } 168 | } 169 | ``` 170 | 171 | ## `updateAccel`: reads accelerometer data 172 | 173 | This function will read accelerometer data from the IMU, sends the data as telemetry, and updates the local copy. 174 | **Note:** the `updateGyro` function is nearly identical and is this not provided. 175 | 176 | Key Features: 177 | - Wrap `Fw::Buffer` around a C++ array 178 | - Send telemetry 179 | - Work with modeled serializable types 180 | - Send events 181 | 182 | ```c++ 183 | //! Read, telemeter, and update local compy of accelerometer data 184 | //! 185 | void Imu ::updateAccel() { 186 | U8 data[IMU_MAX_DATA_SIZE_BYTES]; 187 | Fw::Buffer buffer(data, sizeof data); 188 | 189 | // Read a block of registers from the IMU at the accelerometer's address 190 | Drv::I2cStatus status = this->readRegisterBlock(IMU_RAW_ACCEL_ADDR, buffer); 191 | 192 | // Check a successful read of 6 bytes before processing data 193 | if ((status == Drv::I2cStatus::I2C_OK) && (buffer.getSize() == 6) && (buffer.getData() != nullptr)) { 194 | Gnc::Vector vector = deserializeVector(buffer, accelScaleFactor); 195 | this->tlmWrite_accelerometer(vector); 196 | } else { 197 | this->log_WARNING_HI_TelemetryError(status); 198 | } 199 | } 200 | ``` 201 | -------------------------------------------------------------------------------- /docs/course/appendix-2.md: -------------------------------------------------------------------------------- 1 | # IMU Exercise Appendix II: Installing Rancher Desktop and the F´ ARM Container 2 | 3 | Some users may with to run cross-compilers within docker to minimize the impact of those tools on their systems. 4 | Macintosh users will be required to use docker as the ARM/Linux cross-compilers are not available natively for macOS and 5 | simple virtualization of a Linux box is no longer practical since the introduction of M1 hardware. 6 | 7 | ## Rancher Desktop Setup 8 | 9 | Rancher Desktop is an alternative to Docker Desktop that allows users to run docker containers directly on their desktop 10 | computer. It does not require a license for use like Docker Desktop does and also supports both intel and M1 based 11 | Macintosh computers. 12 | 13 | **Note:** non-Macintosh users are advised to follow the instructions for their operating system. 14 | 15 | To install [Rancher Desktop](https://rancherdesktop.io/), follow the instructions for your operating system. When 16 | presented with a "Welcome to Rancher Desktop" dialog, choose the following settings: 17 | 1. Disable Kubernetes 18 | 2. Select `dockerd` 19 | 3. Configure PATH Automatic 20 | 21 | ![Rancher Config](../img/rancher-config.png) 22 | 23 | Ensure that Rancher Desktop is running and that the VM it uses has been started. You can confirm this by ensuring no 24 | pop-ups nor progress bars are visible in Rancher Desktop's main window as shown below. 25 | 26 | ![Rancher Main Window](../img/rancher-running.png) 27 | 28 | Once this is done, users can install the container by running the following command in their host terminal. It should 29 | complete without errors. 30 | 31 | ```bash 32 | docker pull nasafprime/fprime-arm:devel 33 | ``` 34 | 35 | ## Running The Container 36 | 37 | In order to run the commands provided by the docker container (i.e. the cross-compilers), users must start the container 38 | and attach to a terminal inside. This should be done **after** the prerequisite 39 | [step 2](./prerequisites.md#step-2:-cloning-the-f´-system-reference) so that we can ensure our cloned code is shared 40 | with the container. 41 | 42 | The command to run a terminal in the docker container is: 43 | ```bash 44 | docker run --net host -itv ~/fprime-system-reference:/project nasafprime/fprime-arm:devel 45 | ``` 46 | 47 | > Anytime Macintosh users run cross-compilation commands, they **must** do so in a terminal inside the docker container. 48 | > `~/fprime-system-reference` should be replaced with `/project` in commands run in the container or a user may run 49 | > `ln -s /project ~/fprime-system-reference` each time the container is started. 50 | -------------------------------------------------------------------------------- /docs/course/appendix-3.md: -------------------------------------------------------------------------------- 1 | # IMU Exercise Appendix III: Common Errors 2 | 3 | This section will capture some common errors as seen in the IMU exercise. 4 | 5 | ## Command Not Found (E.g.`fprime-util: command not found`) 6 | 7 | This error is caused when a tool has not properly been installed. Make sure you've completed the following: 8 | 9 | 1. Set up your computer following the [prerequisites](./prerequisites.md) 10 | 2. Are running in the appropriate shell (i.e. Ubuntu shell on Windows) 11 | 3. Activated your virtual environment `. ~/class-venv/bin/activate` 12 | 13 | > Activating your virtual environment must be done in each new terminal window and tab you open. 14 | 15 | > Macintosh users should be aware that cross-compilation steps will not work on macOS, and they must team up with a 16 | > Linux user or run the [docker container](./appendix-2.md). 17 | 18 | ## `fpp-...` Exited With Non-Zero Exit Code 19 | 20 | This can occur for a variety of reasons. Typically, this is caused by a malformed FPP model file. Please ensure that 21 | your FPP model has the correct syntax. 22 | 23 | > This error has been seen on initial setup of Windows computers. If this happens, make sure that you've run 24 | > [step 2](./prerequisites.md#step-2:-cloning-the-f´-system-reference) from within an Ubuntu shell. 25 | 26 | 27 | ## CMake Error at CMakeLists.txt Line 32 28 | 29 | The full error is presented below. This happens when a user has not correctly run 30 | `git submodule update --init --recursive` during [step 2](./prerequisites.md#step-2:-cloning-the-f´-system-reference) of 31 | the system setup. Rerunning that command should fix the problem. 32 | 33 | **Full Error Message** 34 | ```text 35 | CMake Error at CMakeLists.txt:32 (include): 36 | -- Configuring incomplete, errors occurred! 37 | include could not find requested file: 38 | /Users/mstarch/code/fprime-infra/fprime-system-reference/fprime/cmake/FPrime.cmake 39 | 40 | 41 | CMake Error at CMakeLists.txt:34 (include): 42 | include could not find requested file: 43 | 44 | /Users/mstarch/code/fprime-infra/fprime-system-reference/fprime/cmake/FPrime-Code.cmake 45 | 46 | 47 | CMake Error at CMakeLists.txt:43 (add_fprime_subdirectory): 48 | Unknown CMake command "add_fprime_subdirectory". 49 | ``` 50 | 51 | ## macOS Python SSL Certificate Issue 52 | 53 | When installing Python on macOS, an SSL certificate issue may arise. This is caused when macOS certifificates are not 54 | made available to Python. The fix is covered in our 55 | [installation guide](https://github.com/nasa/fprime/blob/devel/docs/INSTALL.md#ssl-error-with-python-36-on-macos) 56 | as this is a common issue with Python on macOS. 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/course/component-implementation.md: -------------------------------------------------------------------------------- 1 | # IMU Exercise: Component Implementation 2 | 3 | Now it is time to write code! F´ implements components using C++ and as we saw in the design section, this 4 | implementation typically involves filling-in template files allowing us to focus on the specific challenges at-hand. 5 | 6 | ## Setup 7 | 8 | Before proceeding with implementation, we should add our `Imu.cpp` file created in the design step to the 9 | `CMakeLists.txt` file in the `Imu` directory. The new `CMakeList.txt` file should look like this: 10 | 11 | ```cmake 12 | set(SOURCE_FILES 13 | "${CMAKE_CURRENT_LIST_DIR}/Imu.fpp" 14 | "${CMAKE_CURRENT_LIST_DIR}/Imu.cpp" 15 | ) 16 | register_fprime_module() 17 | ``` 18 | 19 | We should also ensure that the `#include ` import exists in `Imu.cpp`. If you find 20 | something like `#include ` please change it now. 21 | 22 | ## Component Implementation 23 | 24 | F´ generates a series of handler functions for us to respond to each type of call. We need to fill in our specific logic 25 | for each of these empty handlers. Additionally, F´ creates a series of helper functions that may be called to send 26 | telemetry, emit events, and call output ports. 27 | 28 | To build our implementation we run `fprime-util build` while in the `Imu` folder. Run this now to ensure that the cmake 29 | system is configured correctly for the component. Feel free to re-run the `build` command to check for compiler errors 30 | or other problems as you build. 31 | 32 | Let's take a look at one specific handler. This is the power on command handler: 33 | 34 | ```C++ 35 | void Imu ::PowerSwitch_cmdHandler(const FwOpcodeType opCode, const U32 cmdSeq, PowerState powerState) { 36 | power(powerState); 37 | this->log_ACTIVITY_HI_PowerStatus(powerState); 38 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); 39 | } 40 | ``` 41 | 42 | The critical functions of this handler are: 43 | 1. The `power` helper is called to send the I2C traffic to power on or off the IMU 44 | 2. An event announces the change in power state 45 | 3. A response is sent to the command dispatcher to inform it the command is complete 46 | 47 | **Note:** some helper functions for working with the specifics of the Imu are available in [Appendix I](./appendix-1.md) 48 | and are useful for developers who are less familiar with C++ and working with hardware. Feel-free to use these helpers 49 | in your code, or write your own! 50 | 51 | A full implementation of the component is available [here](../../SystemReference/Gnc/Imu/Imu.cpp) and the associated 52 | header is available [here](../../SystemReference/Gnc/Imu/Imu.hpp). 53 | 54 | ## Additional Resources 55 | - [F´ User Guide](https://nasa.github.io/fprime/UsersGuide/guide.html) 56 | 57 | ## Next Steps 58 | - [Topology Integration and Testing](./topology-integration.md) 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /docs/course/hardware.md: -------------------------------------------------------------------------------- 1 | # IMU Exercise: Hardware Setup 2 | 3 | In order to complete the lab exercise we need to wire-up hardware. In this exercise may use the Raspberry PI 3 or the 4 | Odroid M1 depending on the hardware supplied to the class. The IMU will be the MPU-6050 IMU. The IMU uses I2C to 5 | communicate and should be connected to the computers I2C pins as shown below 6 | 7 | > In-class participants will be provided hardware and should verify the hardware is setup properly. 8 | 9 | ## Wiring Specification 10 | 11 | The IMU should be connected using the IMU wiring harness. The black connector connects to the IMU PCB and the jumper 12 | wires connect to the PI. The blue wire is the I2C data line and should be connected to the SDA1 pin of the PI (pin 3). 13 | The yellow line is the I2C clock to be connected to SCL1 on the PI (pin 5). Red is power (3.3V) and black is ground 14 | (pin 1 and pin 9) 15 | 16 | These connections can be seen on the following wiring diagram. **Note:** in this lab exercise we neither use the radio, 17 | nor the camera. These interfaces can safely be ignored for the lab exercise. 18 | 19 | ### Odroid M1 20 | 21 | The Odroid M1 will use the I2C-0 bus and is shown in the wiring diagram below. 22 | 23 | ![Wiring Diagram Odroid M1](../img/wiring-diagram-m1.png) 24 | 25 | ### RPI Wiring 26 | 27 | The RPI will use the I2C-1 bus and is shown in the wiring diagram below. 28 | 29 | ![Wiring Diagram RPI](../img/wiring-diagram.png) 30 | 31 | ## Additional Resources 32 | - [MPU-6050 IMU](https://learn.adafruit.com/mpu6050-6-dof-accelerometer-and-gyro) 33 | 34 | ## Next Steps 35 | - [Requirements and Design](./requirements-and-design.md) -------------------------------------------------------------------------------- /docs/course/introduction.md: -------------------------------------------------------------------------------- 1 | # IMU Exercise: Introduction 2 | 3 | This document will describe the use of the F´ system reference as a reference for the flight software workshop. We will 4 | explore flight software through the lens of F´ and this exercise. In the exercise, we will integrate the 5 | [MPU-6050 IMU](https://learn.adafruit.com/mpu6050-6-dof-accelerometer-and-gyro) into our F´ project with the goal of 6 | generating telemetry. 7 | 8 | > Users are expected to complete the [prerequisites](./prerequisites.md) section before attending class. 9 | 10 | The class will consist of the following sections. 11 | 12 | - [Prerequisites](./prerequisites.md) 13 | - [Hardware Setup](./hardware.md) 14 | - [Requirements and Design](./requirements-and-design.md) 15 | - [Component Implementation](./component-implementation.md) 16 | - [Unit Testing](./unit-testing.md) 17 | - [Topology Integration and Testing](./topology-integration.md) 18 | - [Appendix I: Code Snippets](./appendix-1.md) 19 | - [Appendix II: Installing Rancher Desktop and the F´ ARM Container](./appendix-2.md) 20 | - [Appendix III: Common Errors](./appendix-3.md) 21 | - [In-Person Class Information](./teams.md) 22 | 23 | > In-class participants may find hardware access information in the [in-person class information](./teams.md) document. 24 | 25 | 26 | ## Next Steps 27 | - [Prerequisites](./prerequisites.md) 28 | -------------------------------------------------------------------------------- /docs/course/pdfs/appendix-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/course/pdfs/appendix-1.pdf -------------------------------------------------------------------------------- /docs/course/pdfs/appendix-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/course/pdfs/appendix-2.pdf -------------------------------------------------------------------------------- /docs/course/pdfs/appendix-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/course/pdfs/appendix-3.pdf -------------------------------------------------------------------------------- /docs/course/pdfs/component-implementation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/course/pdfs/component-implementation.pdf -------------------------------------------------------------------------------- /docs/course/pdfs/hardware.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/course/pdfs/hardware.pdf -------------------------------------------------------------------------------- /docs/course/pdfs/introduction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/course/pdfs/introduction.pdf -------------------------------------------------------------------------------- /docs/course/pdfs/prerequisites.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/course/pdfs/prerequisites.pdf -------------------------------------------------------------------------------- /docs/course/pdfs/requirements-and-design.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/course/pdfs/requirements-and-design.pdf -------------------------------------------------------------------------------- /docs/course/pdfs/teams.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/course/pdfs/teams.pdf -------------------------------------------------------------------------------- /docs/course/pdfs/topology-integration.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/course/pdfs/topology-integration.pdf -------------------------------------------------------------------------------- /docs/course/prerequisites.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | In order to run code on the reference hardware, we need the ability to cross-compile for the given target architecture. 4 | ARM/Linux cross-compilers require a Linux computer in order to build the code. In this guide we will walk through how to 5 | set up Linux, cross-compilers, and F´ across the variety of computers students may be using. 6 | 7 | > Please come to the class having worked through and succeeded at each of the steps below. 8 | 9 | ### Step 0: Basic Requirements 10 | 11 | These are the basic requirements for students' computers to work through this section and attend the class. 12 | 13 | 1. Computer running Windows 10, Mac OS X, or Ubuntu 14 | 2. Administrator access 15 | 3. 5GB of free disk space, 8 GB of RAM 16 | 4. Knowledge of the command line for your operating system (Bash, PowerShell, etc). 17 | 18 | The following steps elaborate on the F´ [installation guide](https://nasa.github.io/fprime/INSTALL.html) with specifics 19 | for the F´ system reference used by this class. We include a breakdown of setup for each common OS type. Users may 20 | consult the troubleshooting section of the installation guide if problems arise. 21 | 22 | 23 | ## Step 1: Setting Up An Ubuntu Virtual Machine and Necessary Packages 24 | 25 | In this section we will set up Linux to run on each type of laptop. This is typically done through the use of virtual 26 | machines and/or emulation. Please follow the instructions for the operating system you run on your laptop. Our goal will 27 | be to get up and running with Ubuntu 20.04 on each type of laptop. *This does not require dual booting*. 28 | 29 | **Note:** you will need one of the operating systems found here. If you run something else, try setting up an Ubuntu 30 | 20.04 virtual machine in something like VirtualBox. 31 | 32 | ### Microsoft Windows 10 33 | 34 | Windows 10 ships with a technology known as WSL. WSL allows users to run Linux virtual machines transparently within 35 | the Windows 10 operating system. In order to install WSL please run the following commands *in an Administrator 36 | PowerShell console*. 37 | 38 | **PowerShell: Install WSL with Default Ubuntu** 39 | ``` 40 | wsl --install 41 | ``` 42 | 43 | To start Ubuntu under WSL, search for Ubuntu in the start menu and select the "Ubuntu 20.04 on Windows" APP. All class 44 | commands should be run these Ubuntu 20.04 terminals. 45 | 46 | **Note:** full instructions and troubleshooting help are available in the 47 | [Microsoft documentation](https://learn.microsoft.com/en-us/windows/wsl/install). 48 | 49 | Once WSL is running, you can install the base F´ required packages from the Ubuntu shell using: 50 | 51 | ```bash 52 | sudo apt update 53 | 54 | sudo apt install build-essential git g++ gdb cmake python3 python3-venv python3-pip 55 | ``` 56 | 57 | The cross-compilers are installed using a similar command: 58 | 59 | ```bash 60 | sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu 61 | ``` 62 | 63 | Finally, Windows users must open up a firewall port and forward that port to WSL to ensure the hardware can call back 64 | into F´ ground data system running in WSL. First we'll need to note the IP address of the WSL machine. This is done with 65 | the following command *in an administrator PowerShell*. 66 | 67 | ``` 68 | wsl hostname -I 69 | ``` 70 | > Record the output of this command for the next step. For this guide, we will use the value `127.0.0.1`. 71 | 72 | Next, we will add a firewall rule and forward it to the WSL instance. This is done with the following commands in 73 | *an administrator PowerShell*. Remember to change `127.0.0.1` to the address you noted above. 74 | 75 | > Warning: these commands work with the Windows firewall. Security and anti-virus tools can run extra firewalls. Users 76 | > must allow the port `50000` or disable these extra firewalls. 77 | 78 | **PowerShell: Add and Forward External Firewall Rule** 79 | ```bash 80 | New-NetFirewallRule -DisplayName "fprime" -Direction inbound -Profile Any -Action Allow -LocalPort 50000 -Protocol TCP 81 | 82 | netsh interface portproxy add v4tov4 listenport=50000 listenaddress=0.0.0.0 connectport=50000 connectaddress=127.0.0.1 83 | ``` 84 | > Remember to change `127.0.0.1` to your recorded ip address as discovered with `wsl hostname -I` 85 | > Users are advised to remove this rule after the class has been completed. 86 | 87 | ### Ubuntu 20.04 / Linux 88 | 89 | Ubuntu users just need to ensure that the basic packages and cross-compilers are installed on their system. The basic 90 | packages that F´ requires are installed with: 91 | 92 | ```bash 93 | sudo apt install build-essential git g++ gdb cmake python3 python3-venv python3-pip 94 | ``` 95 | 96 | The cross-compilers are installed using a similar command: 97 | 98 | ```bash 99 | sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu 100 | ``` 101 | 102 | ### Mac OS X 103 | 104 | MacOS like Linux is a Unix system and thus may be used directly for most of this class. However, Mac users must install 105 | the following utilities *and ensure they are available on the command line path*. 106 | 107 | 1. [Python 3](https://www.python.org/downloads/release/python-3913/) 108 | 2. [CMake](https://cmake.org/download/) 109 | 3. GCC/Clang typically installed with xcode-select 110 | 111 | **Installing GCC/Clang on macOS** 112 | ```bash 113 | xcode-select --install 114 | ``` 115 | 116 | Installing Python and running the above command to install gcc/clang should ensure that those tools are on the path. 117 | CMake requires one additional step to ensure it is on the path: 118 | 119 | ```bash 120 | sudo "/Applications/CMake.app/Contents/bin/cmake-gui" --install 121 | ``` 122 | 123 | In order to cross-compile for the ARM chips for this class, a Linux box is essential. Mac users may choose to pair with 124 | team members who have Linux/WSL setup, or may choose to follow the instructions in [Appendix II](./appendix-2.md) 125 | to install a docker container including the necessary tools. 126 | 127 | ## Step 2: Cloning The F´ System Reference 128 | 129 | This class activity will be taught within the F´ System Reference project. The F´ System Reference can be downloaded by 130 | and initialized with the following commands. 131 | 132 | ```bash 133 | cd ~ 134 | git clone https://github.com/fprime-community/fprime-system-reference 135 | cd fprime-system-reference 136 | git submodule update --init --recursive 137 | ``` 138 | 139 | > This class assumes we work within the home directory. Choosing differently means you **must** be prepared to adjust 140 | > all commands with your chosen path. 141 | 142 | > WSL users should clone within an Ubuntu shell. Using git on Windows is known to cause file line ending problems. 143 | 144 | ## Step 3: Installing F´ Tools 145 | 146 | Each version of F´, including the one shipped within the system reference is stamped with a known working set of F´ 147 | tools. These tools run in Python and are typically setup in a virtual environment to prevent issues with your OS. 148 | 149 | **Set Up a Virtual Environment** 150 | ```bash 151 | python3 -m venv ~/class-venv 152 | ``` 153 | 154 | **Activate The Virtual Environment** 155 | ```bash 156 | . ~/class-venv/bin/activate 157 | ``` 158 | 159 | **Update Python Packages** 160 | ```bash 161 | pip install -U setuptools setuptools_scm wheel pip 162 | ``` 163 | 164 | **Install F´ Tools** 165 | ```bash 166 | pip install -r ~/fprime-system-reference/fprime/requirements.txt 167 | ``` 168 | 169 | The F´ virtual environment is now setup with the correct version of the tools for the class. Should the version of F´ 170 | used by the class change, just rerun the installation command from within the activated environment. 171 | 172 | > Users **must** be within an activated virtual environment for the remainder of the class. When opening up a new shell 173 | > or terminal simply repeat the activation command above. There is no need to set up nor install again, simply activate. 174 | 175 | ## Step 4: Testing the Setup 176 | 177 | > **Reminder**: F´ tools need to be run from within an activated virtual environment. WSL users must also run from 178 | > within an Ubuntu shell. 179 | 180 | In this section we will build the F´ system reference for your local computer and run it against the F´ ground data 181 | system to ensure that all tools are set up correctly. If an error is encountered, please reference 182 | [Appendix III](./appendix-3.md) for help. 183 | 184 | First we generate a build cache for F´. This is a required first step, but is only needed to be run once for each target 185 | architecture. Notice we perform this work within the SystemReference folder as that is our F´ deployment. 186 | 187 | **Generate an F´ Build Cache** 188 | ```bash 189 | cd ~/fprime-system-reference/SystemReference 190 | fprime-util generate 191 | ``` 192 | 193 | Next the user should build F´. Building is repeated anytime a file is changed and the user wishes to rebuild the model 194 | or code. 195 | 196 | ```bash 197 | cd ~/fprime-system-reference/SystemReference 198 | fprime-util build 199 | ``` 200 | 201 | Finally, the user can run F´ and attach it to an F´ ground data system instance to see the working code. **Note:** 202 | success in this step is seeing the upper right corner of the GDS change to a green circle. 203 | 204 | ```bash 205 | cd ~/fprime-system-reference/SystemReference 206 | fprime-gds 207 | ``` 208 | 209 | You should now be prepared for the completing the remaining IMU exercise. 210 | -------------------------------------------------------------------------------- /docs/course/requirements-and-design.md: -------------------------------------------------------------------------------- 1 | # IMU Exercise: Requirements and Design 2 | 3 | When developing flight software and using F´ it is important to start the development of a component by laying out the 4 | requirements of that component and generating a design of that component. In this portion of the exercise we will walk 5 | through both of these steps. 6 | 7 | F´ formalizes the design of the component using the FPP modeling language. This allows us to take the design (called a 8 | model in FPP parlance) and generate code that performs much of the work within that component. This greatly simplifies 9 | the implementation later on. 10 | 11 | ## Preparation 12 | 13 | In order to prepare to specify a design we need to do some folder setup. First, make sure to have followed the class 14 | [prerequisites](./introduction.md#prerequisites) including starting the docker container and generating the F´ build 15 | cache. 16 | 17 | Next, move the existing IMU folder out of the way as we will be replacing it with our own design. We'll replace it with 18 | a basic folder with and initial `CMakeList.txt` file inside. 19 | 20 | **Inside Docker** 21 | ```bash 22 | cd ~/fprime-system-reference/SystemReference/Gnc 23 | mv Imu Imu.reference 24 | mkdir Imu 25 | ``` 26 | 27 | Finally, add the following `CMakeLists.txt` to that new folder. The new file must be named exactly `CMakeLists.txt` and 28 | exist in the new Imu folder. 29 | 30 | ```cmake 31 | set(SOURCE_FILES 32 | "${CMAKE_CURRENT_LIST_DIR}/Imu.fpp" 33 | ) 34 | register_fprime_module() 35 | ``` 36 | 37 | We are now setup to generate requirements for our component, codify them into a design, and generate a base 38 | implementation. **Note:** the above directory will not build until after we add the FPP file as noted in the design 39 | section. 40 | 41 | ## Requirements 42 | 43 | Good requirements capture both the behavior of a component and its interaction with other components within the system. 44 | Requirements should also capture necessary commands, events, telemetry channels, and parameters for the component. 45 | Additionally, failure and off-nominal behaviors should be included in the requirements. 46 | 47 | For this exercise, we can follow the GNC sensor integration guide "Step 1: Define Component Requirements" to determine 48 | the necessary requirements for the IMU. This section can be found 49 | [here](../integration/gnc-sensor-integration.md#step-1-define-component-requirements). 50 | 51 | In order to check the requirements we defined, let's compare with the requirements published in the IMU's SDD found 52 | [here](../../SystemReference/Gnc/Imu/docs/sdd.md). Note here, we have defined a command, several events, telemetry 53 | channels, and defined error conditions. 54 | 55 | ## Design 56 | 57 | In F´ the design of the component needs to be formally captured in a model. To do this, we write an FPP file. The design 58 | should fall out from the requirements previously captured. Generally, if there are aspects of the design that do not map 59 | to requirements, this is an indication that the requirements may be incomplete and should be revisited. 60 | 61 | For this exercise, we will follow the [design section](../integration/gnc-sensor-integration.md#step-2-component-design) 62 | of the GNC sensor integration guide. We need to make sure to capture the commands, events, telemetry, parameters, and 63 | ports of the IMU in our FPP model. 64 | 65 | Go ahead and construct an FPP file in your `Imu` directory called `Imu.fpp`. Below is a template you can use to get 66 | started. Notice we chose a module of `Gnc` which will result in the Imu being part of the `Gnc` namespace. 67 | 68 | ```fpp 69 | module Gnc { 70 | @ The power state enumeration 71 | enum PowerState {OFF, ON} 72 | 73 | @ Component for receiving IMU data via poll method 74 | passive component Imu { 75 | # ---------------------------------------------------------------------- 76 | # General ports 77 | # ---------------------------------------------------------------------- 78 | 79 | @ Port to send telemetry to ground 80 | guarded input port Run: Svc.Sched 81 | ... 82 | 83 | # ---------------------------------------------------------------------- 84 | # Special ports 85 | # ---------------------------------------------------------------------- 86 | 87 | @ Command receive 88 | command recv port cmdIn 89 | 90 | @ Command registration 91 | command reg port cmdRegOut 92 | 93 | @ Command response 94 | command resp port cmdResponseOut 95 | ... 96 | 97 | # ---------------------------------------------------------------------- 98 | # Commands 99 | # ---------------------------------------------------------------------- 100 | 101 | @ Command to turn on the device 102 | guarded command PowerSwitch( 103 | powerState: PowerState 104 | ) \ 105 | opcode 0x01 106 | 107 | 108 | # ---------------------------------------------------------------------- 109 | # Events 110 | # ---------------------------------------------------------------------- 111 | 112 | @ Event where error occurred when requesting telemetry 113 | event TelemetryError( 114 | status: Drv.I2cStatus @< the status value returned 115 | ) \ 116 | severity warning high \ 117 | format "Telemetry request failed with status {}" \ 118 | ... 119 | 120 | # ---------------------------------------------------------------------- 121 | # Telemetry 122 | # --------------------------------------------------------------------- 123 | 124 | @ X, Y, Z acceleration from accelerometer 125 | telemetry accelerometer: Vector id 0 update always format "{} g" 126 | ... 127 | } 128 | } 129 | ``` 130 | **Note:** your design may differ slightly from the example above. Fill in the `...` sections with other parts of your 131 | design. The system reference [IMU model](../../SystemReference/Gnc/Imu/Imu.fpp) can be used as a reference to compare 132 | our design against. 133 | 134 | ## Generating Implementation Templates 135 | 136 | The key advantage of using FPP as a design language is we can now generate the basic implementation of our component. 137 | Run the following commands, and you'll see what we mean: 138 | 139 | ```bash 140 | cd fprime-system-reference/SystemReference/Gnc/Imu 141 | fprime-util impl 142 | mv ImuComponentImpl.cpp-template Imu.cpp 143 | mv ImyComponentImpl.hpp-template Imu.hpp 144 | ``` 145 | 146 | These template files contain fill-in functions that allow us to implement the logic for the IMU without needing to focus 147 | on all the boilerplate code needed to make the `Imu` work with the rest of the system. 148 | 149 | ## Additional Resources 150 | - [FPP User Guide](https://fprime-community.github.io/fpp/fpp-users-guide.html) 151 | - [F´ User Guide](https://nasa.github.io/fprime/UsersGuide/guide.html) 152 | - [MPU-6050 Datasheet](http://www.invensense.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf) 153 | - [MPU-6050 Register Map](https://www.invensense.com/wp-content/uploads/2015/02/MPU-6000-Register-Map1.pdf) 154 | 155 | ## Next Steps 156 | - [Component implementation](./component-implementation.md) 157 | 158 | -------------------------------------------------------------------------------- /docs/course/teams.md: -------------------------------------------------------------------------------- 1 | # IMU Exercise: In-Person Class Information 2 | 3 | For students taking the in-person class, the information provided here is the necessary setup information. There are 14 4 | teams numbered from 1 through 14. Each team will have one hardware platform and may connect as described below. 5 | 6 | ## WiFi Setup 7 | 8 | Students should connect through our WiFi network when participating in the class. This allows access to our class 9 | hardware platforms. 10 | 11 | > Note: internet is not available on the class network 12 | 13 | | WiFi Network | Password | 14 | |--------------|-------------| 15 | | fprime-class | fpclass2020 | 16 | 17 | 18 | ## Hardware Connection Information 19 | 20 | Students should connect to the hardware through SSH. The SSH username and password are as follows. 21 | 22 | | Username | Password | 23 | |----------|----------| 24 | | odroid | odroid | 25 | 26 | The hostname to connect to is team-specific. Each team should look up their hardware by device number in the following 27 | table and use the provided hostname to ssh. 28 | 29 | ```bash 30 | ssh odroid@ 31 | 32 | # e.g. Team 1 33 | ssh odroid@odroid1.lan 34 | ``` 35 | 36 | ### Team Hostname Table 37 | 38 | | WiFi Number | Hostname | MAC Address | WiFi Adapter Name | 39 | |-------------|--------------|-------------------|-------------------| 40 | | 0 / 15 | odroid0.lan | 6c:5a:b0:81:51:99 | wlx6c5ab0815199 | 41 | | 1 | odroid1.lan | 6c:5a:b0:81:46:a9 | wlx6c5ab08146a9 | 42 | | 2 | odroid2.lan | 6c:5a:b0:81:3f:b2 | wlx6c5ab0813fb2 | 43 | | 3 | odroid3.lan | 6c:5a:b0:81:43:dd | wlx6c5ab08143dd | 44 | | 4 | odroid4.lan | 6c:5a:b0:81:51:a0 | wlx6c5ab08151a0 | 45 | | 5 | odroid5.lan | 6c:5a:b0:81:55:74 | wlx6c5ab0815574 | 46 | | 6 | odroid6.lan | 6c:5a:b0:81:4b:88 | wlx6c5ab0814b88 | 47 | | 7 | odroid7.lan | 6c:5a:b0:38:42:37 | wlx6c5ab0384237 | 48 | | 8 | odroid8.lan | 6c:5a:b0:81:51:d3 | wlx6c5ab08151d3 | 49 | | 9 | odroid9.lan | 6c:5a:b0:81:4b:28 | wlx6c5ab0814b28 | 50 | | 10 | odroid10.lan | 6c:5a:b0:81:51:86 | wlx6c5ab0815186 | 51 | | 11 | odroid11.lan | 6c:5a:b0:81:4c:0b | wlx6c5ab0814c0b | 52 | | 12 | odroid12.lan | 6c:5a:b0:81:51:e0 | wlx6c5ab08151e0 | 53 | | 13 | odroid13.lan | 6c:5a:b0:81:3f:b7 | wlx6c5ab0813fb7 | 54 | | 14 | odroid14.lan | 6c:5a:b0:81:57:a4 | wlx6c5ab08157a4 | 55 | -------------------------------------------------------------------------------- /docs/course/topology-integration.md: -------------------------------------------------------------------------------- 1 | # IMU Exercise: Topology Integration and Testing 2 | 3 | The next step in developing our component is to add our component into the system's topology, building it for the RPI, 4 | and running the final code. 5 | 6 | ## Topology Integration 7 | 8 | In order to see how a component is integrated into topology, we can look through two files in the `Top` folder. These 9 | files are `Top/topology.fpp` for the connections and `Top/instances.fpp` used for instantiating components. **Note:** 10 | these connections are already in-place as we are working in the system reference. You may delete and recreate them or 11 | survey what is present. 12 | 13 | In the topology file (`Top/topology.fpp`) we add two instances `imu` and `imuI2cBus`. The first is our `Imu` component 14 | and the second is the `imuI2cBus`, which is an instantiation of our `LinuxI2cDriver`. 15 | 16 | ```fpp 17 | module SystemReference { 18 | ... 19 | 20 | topology SystemReference { 21 | ... 22 | instance imu 23 | instance imuI2cBus 24 | ... 25 | 26 | connections I2c { 27 | imu.read -> imuI2cBus.read 28 | imu.write -> imuI2cBus.write 29 | } 30 | ... 31 | } 32 | } 33 | ``` 34 | 35 | These instantiations are found in `Top/instances.fpp` where we control the setup of the components. The relevant 36 | sections are below. First, we instantiate the IMU component, with a custom configuration phase implementation where we 37 | call setup for the component. Next, we instantiate the LinuxI2cDriver with a custom configuration phase to open the RPI 38 | device path for I2C. 39 | 40 | ```fpp 41 | module SystemReference { 42 | ... 43 | instance imu: Gnc.Imu base id 0x4C00 { 44 | phase Fpp.ToCpp.Phases.configComponents """ 45 | imu.setup(Gnc::Imu::I2C_DEV0_ADDR); 46 | """ 47 | } 48 | 49 | instance imuI2cBus: Drv.LinuxI2cDriver base id 0x4D00 { 50 | phase Fpp.ToCpp.Phases.configComponents """ 51 | if (!imuI2cBus.open("/dev/i2c-1")) { 52 | Fw::Logger::logMsg("[ERROR] Failed to open I2C device\\n"); 53 | } 54 | """ 55 | } 56 | } 57 | ``` 58 | 59 | ## Build and Test 60 | 61 | Next we can test the system. First we need to perform a complete cross compilation build. Then we upload the binary to 62 | our Raspberry PI and run it. 63 | 64 | ### Step 1: Cross-Compile 65 | 66 | In this step we will set up a build cache specifically for building for ARM hardware. This is done using generate and 67 | passing in the name of the desired toolchain file. Here we pass in `aarch64-linux`. Then we build passing in the same 68 | named toolchain. 69 | 70 | > Students working with the Raspberry PI 3 may use `arm-hf-linux` in place of `aarch64-linux` 71 | 72 | > Mac users must pair with a Linux user or run inside a docker shell for these steps and must then use `/project` 73 | > instead of `~/fprime-system-reference` in the following instruction. 74 | 75 | ```bash 76 | cd ~/fprime-system-reference/SystemReference 77 | fprime-util generate aarch64-linux 78 | fprime-util build aarch64-linux 79 | ``` 80 | 81 | 82 | ### Step 2: Upload the Built Binary 83 | 84 | The next step is to upload the binary file onto the Raspberry PI. Output products are placed in the 85 | `build-artifacts/` directory. In our case the binary is in `build-artifacts/aarch64-linux/bin`. We can upload 86 | it using ssh. 87 | 88 | ```bash 89 | scp build-artifacts/aarch64-linux/bin/SystemReference odroid@:SystemReference 90 | ``` 91 | 92 | **Note:** your team should have been provided the hostname and password for your hardware. 93 | 94 | ### Step 3: Running It! 95 | 96 | To run it we need to do two things: launch the GDS and launch the binary on the PI. First, let us launch the GDS. This 97 | is done with the `fprime-gds` command using the `-n` flag that prevents the GDS from also running the binary, and we 98 | pass in a dictionary, which was automatically built in the build step. 99 | 100 | > Macintosh users can launch the GDS outside of docker. Only the cross-compilation must use it. 101 | 102 | **Launching the GDS** 103 | ```bash 104 | cd ~/fprime-system-reference/SystemReference 105 | fprime-gds -n --dictionary build-artifacts/aarch64-linux/dict/SystemReferenceTopologyDictionary.json 106 | ``` 107 | 108 | Next we run the binary and tell it to connect back to the running GDS. This is done from within the PI. 109 | 110 | **Running the Binary on Hardware** 111 | ```bash 112 | ssh odroid@ 113 | ./SystemReference -a -p 50000 114 | ``` 115 | 116 | **Note:** you'll need to determine the IP address of the laptop on the network. 117 | **Note:** you may need to open ports on your laptop, if you are running a firewall. Make sure to open port `50000`. 118 | 119 | ## Additional Resources 120 | - [F´ User Guide](https://nasa.github.io/fprime/UsersGuide/guide.html) 121 | 122 | ## Next Steps 123 | - [Unit Testing](./unit-testing.md) 124 | -------------------------------------------------------------------------------- /docs/course/unit-testing.md: -------------------------------------------------------------------------------- 1 | # IMU Exercise: Unit Testing 2 | 3 | This section of the course will cover unit testing. The goal of this exercise is to set up the unit test harness, write 4 | unit tests, and try to achieve 100% code coverage. 5 | 6 | This exercise consists of several learning goals: 7 | 8 | 1. Generate unit test implementations using `fprime-util impl --ut` 9 | 2. Run unit tests using `fprime-util check` 10 | 3. Write unit tests to cover each of the [requirements](../../SystemReference/Gnc/Imu/docs/sdd.md#3-requirements) with 11 | verification of "Unit Test" 12 | 4. Generate coverage analysis for the unit tests developed 13 | 14 | ## Setup 15 | 16 | Given the varied state of the project that various students have it may be beneficial to reset to the instructor model. 17 | This will allow us to cover unit testing without accumulating issues from the implementation section. This can be 18 | accomplished with the following commands. 19 | 20 | ### Saving Your Current Work 21 | 22 | Before resetting to the instruction setup, you may wish to save your work. This can be done quickly with the following 23 | commands: 24 | 25 | ```bash 26 | cd SystemReference 27 | git checkout -b "my_class_branch" 28 | git add Gnc 29 | git commit -m "implementation and model of Imu component" 30 | ``` 31 | 32 | ### Resetting Codebase 33 | 34 | In order to reset the codebase, you must first ensure that all changes have been saved as above. If anything outside the 35 | Imu directory was edited, you may need to repeat the `git add` and `git commit` commands above pointing to those files. 36 | 37 | Once finished, we can switch to the latest instructor code with these commands: 38 | 39 | ```bash 40 | git fetch origin 41 | git checkout origin/main 42 | ``` 43 | 44 | ### Preparing for Unit Testing 45 | 46 | Like our modeling and implementation steps, we will need to move away the example unit tests in order to write 47 | our own. This is done with the following commands: 48 | 49 | ```bash 50 | cd SystemReference/Gnc/Imu 51 | mv test test.bak 52 | mkdir -p test/ut 53 | ``` 54 | 55 | We also need to pull the unittest `.cpp` source files out of our CMakeLists.txt. That file should now contain: 56 | 57 | ```cmake 58 | #### 59 | # F prime CMakeLists.txt: 60 | # 61 | # SOURCE_FILES: combined list of source and autocoding files 62 | # MOD_DEPS: (optional) module dependencies 63 | # 64 | # Note: using PROJECT_NAME as EXECUTABLE_NAME 65 | #### 66 | set(SOURCE_FILES 67 | "${CMAKE_CURRENT_LIST_DIR}/Imu.fpp" 68 | "${CMAKE_CURRENT_LIST_DIR}/Imu.cpp" 69 | ) 70 | register_fprime_module() 71 | 72 | # Register the unit test build 73 | set(UT_SOURCE_FILES 74 | "${CMAKE_CURRENT_LIST_DIR}/Imu.fpp" 75 | # "${CMAKE_CURRENT_LIST_DIR}/test/ut/TestMain.cpp" 76 | # "${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp" 77 | ) 78 | set(UT_MOD_DEPS STest) 79 | register_fprime_ut() 80 | ``` 81 | > Note: in this case we just commented-out those .cpp files as we will need to re-add them once we have generated them. 82 | 83 | ## Unit Test Implementation Stubs 84 | 85 | Like the implementation stubs you can implement the basic unittest harness with the `fprime-util impl` command. This is 86 | done by adding the `--ut` flag to the end. We must remember to prepare a build-cache using `fprime-util generate --ut`. 87 | This is done first. 88 | 89 | ```bash 90 | fprime-util generate --ut 91 | fprime-util impl --ut 92 | ``` 93 | 94 | The following three files have now been generated: `Tester.cpp`, `Tester.hpp`, and `TestMain.cpp`. These were placed in 95 | the component's base directory and should be moved into the `test/ut` directory. 96 | 97 | ```bash 98 | mv Test*.?pp test/ut 99 | ``` 100 | 101 | Now the `CMakeList.txt` may be restored to the reference and should contain: 102 | 103 | ```cmake 104 | #### 105 | # F prime CMakeLists.txt: 106 | # 107 | # SOURCE_FILES: combined list of source and autocoding diles 108 | # MOD_DEPS: (optional) module dependencies 109 | # 110 | # Note: using PROJECT_NAME as EXECUTABLE_NAME 111 | #### 112 | set(SOURCE_FILES 113 | "${CMAKE_CURRENT_LIST_DIR}/Imu.fpp" 114 | "${CMAKE_CURRENT_LIST_DIR}/Imu.cpp" 115 | ) 116 | register_fprime_module() 117 | 118 | # Register the unit test build 119 | set(UT_SOURCE_FILES 120 | "${CMAKE_CURRENT_LIST_DIR}/Imu.fpp" 121 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/TestMain.cpp" 122 | "${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp" 123 | ) 124 | set(UT_MOD_DEPS STest) 125 | register_fprime_ut() 126 | ``` 127 | 128 | > Learning goal #1 has now been accomplished! 129 | 130 | To check that everything is done properly, we can run `fprime-util check` to execute the unittest. There are no test 131 | cases defined, so it should pass. 132 | 133 | ```bash 134 | fprime-util check 135 | ``` 136 | > Learning goal #2 has now been accomplished! Repeat this command to run the tests as we implement. 137 | 138 | ## Developing Unit Tests 139 | 140 | Now it is time to write our first unit test. This is done in two steps: 141 | 142 | 1. Add a test case to `TestMain.cpp` 143 | 2. Add a test case implementation to `Tester.cpp` and `Tester.hpp` 144 | 145 | These steps may be repeated for each test that we need to define. 146 | 147 | ### Adding Tests to `TestMain.cpp` 148 | 149 | The first step is to open `test/ut/TestMain.cpp` and add a test case. This tells our unit test system (google test) to 150 | run the supplied code as a test. It takes a test suite name and a test case name. In the case below we are defining the 151 | "Nominal" test suite and the "getGyroscope" test case. Test suites are used to group tests and may be reused for future 152 | tests, however; test case names should be unique. 153 | 154 | > Note: the `testGetGyroTlm` member will be defined in the next step. 155 | 156 | ```c++ 157 | TEST(Nominal, getGyroscope) { 158 | Gnc::Tester tester; 159 | tester.testGetGyroTlm(); 160 | } 161 | ``` 162 | 163 | ### Adding Tests to `Tester.cpp` and `Tester.hpp` 164 | 165 | Next, we need to add test case implementations within the tester class. These are named with the function called in the 166 | case in `TestMain.cpp` and run the unit test. 167 | 168 | Below is an example test case implementation. It should be added to `test/ut/Tester.cpp`. Do not forget to add the 169 | member declaration to `test/ut/Tester.hpp` shown just below our example. 170 | 171 | **Test Case Implementation in `Tester.cpp`** 172 | ```c++ 173 | Tester() : 174 | ImuGTestBase("Tester", MAX_HISTORY_SIZE), 175 | component("Imu"), 176 | gyroSerBuf(this->gyroBuf, sizeof this->gyroBuf) ... 177 | 178 | void Tester ::testGetGyroTlm() { 179 | sendCmd_PowerSwitch(0, 0, PowerState::ON); 180 | for (U32 i = 0; i < 5; i++) { 181 | this->invoke_to_Run(0, 0); 182 | Gnc::Vector expectedVector; 183 | for (U32 j = 0; j < 3; ++j) { 184 | I16 intCoord = 0; 185 | const auto status = this->gyroSerBuf.deserialize(intCoord); 186 | EXPECT_EQ(status, Fw::FW_SERIALIZE_OK); 187 | const F32 f32Coord = 188 | static_cast(intCoord) / Imu::gyroScaleFactor; 189 | expectedVector[j] = f32Coord; 190 | } 191 | ASSERT_TLM_gyroscope(i, expectedVector); 192 | } 193 | } 194 | ... 195 | Drv::I2cStatus Tester ::from_read_handler(const NATIVE_INT_TYPE portNum, U32 addr, Fw::Buffer& serBuffer) { 196 | this->pushFromPortEntry_read(addr, serBuffer); 197 | // Fill buffer with random data 198 | U8* const data = serBuffer.getData(); 199 | const U32 size = serBuffer.getSize(); 200 | const U32 imu_max_data_size = Gnc::Imu::IMU_MAX_DATA_SIZE_BYTES; 201 | EXPECT_LE(size, imu_max_data_size); 202 | for (U32 i = 0; i < size; ++i) { 203 | const U8 byte = i; 204 | data[i] = byte; 205 | } 206 | // Copy data into the gyro buffer 207 | this->gyroSerBuf.resetSer(); 208 | const auto status = this->gyroSerBuf.pushBytes(&data[0], size); 209 | EXPECT_EQ(status, Fw::FW_SERIALIZE_OK); 210 | return Drv::I2cStatus::I2C_OK; 211 | } 212 | ``` 213 | > Note: we also had to update the `from_read_handler` to respond as if we are an I2C read port. 214 | 215 | **Test Case Definition in `Tester.hpp`** 216 | ```c++ 217 | #include "Fw/Types/SerialBuffer.hpp" 218 | ... 219 | class Tester : public ImuGTestBase { 220 | ... 221 | //! Test to get gyroscope telemetry 222 | //! 223 | void testGetGyroTlm(); 224 | 225 | //! Serial buffer wrapping gyroBuf 226 | Fw::SerialBuffer gyroSerBuf; 227 | 228 | //! Buffer for storing gyro data read by the component 229 | U8 gyroBuf[6]; 230 | ... 231 | }; 232 | ``` 233 | 234 | We should now be able to run the unittest! 235 | 236 | ```c++ 237 | fprime-util check 238 | ``` 239 | > Learning goal 3 has been partially accomplished. Look at 240 | > [classroom implementation](https://github.com/fprime-community/fprime-system-reference/tree/main/SystemReference/Gnc/Imu/test/ut) 241 | > for the implementation of further unit tests. 242 | 243 | ## Test Coverage 244 | 245 | Once we have implemented our unit tests, we can check our coverage by running the following command. 246 | 247 | ```c++ 248 | fprime-util check --coverage 249 | ``` 250 | 251 | This will generate a sub folder of the Imu directory called "coverage" containing an HTML file called "coverage.html". 252 | Open that file to inspect the coverage report. 253 | 254 | > Learning goal 4 has been accomplished! Nice work! 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /docs/img/rancher-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/img/rancher-config.png -------------------------------------------------------------------------------- /docs/img/rancher-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/img/rancher-running.png -------------------------------------------------------------------------------- /docs/img/wiring-diagram-m1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/img/wiring-diagram-m1.png -------------------------------------------------------------------------------- /docs/img/wiring-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fprime-community/fprime-system-reference/83268cee85cae3449b2bead565b0bb0b54540cd9/docs/img/wiring-diagram.png -------------------------------------------------------------------------------- /docs/integration/camera/building-system-ref-with-libcamera.md: -------------------------------------------------------------------------------- 1 | # Building the System Reference with libcamera 2 | 1. Open a terminal, source the project python virtual environment, and navigate to the `SystemReference` deployment 3 | 2. Set the PKG_CONFIG_PATH variable to the location where the libcamera pkgconfig directory is: 4 | ```bash 5 | export PKG_CONFIG_PATH=/lib/pkgconfig/ 6 | ``` 7 | 3. Next run `fprime-util generate ` and `fprime-util build ` to build the System Reference 8 | 9 | If building natively, run: 10 | ```bash 11 | fprime-util generate 12 | fprime-util build 13 | ``` 14 | 15 | Or if building for an ARM 32-bit or ARM 64-bit Linux platform, run: 16 | 17 | ```bash 18 | export ARM_TOOLS_PATH= 19 | 20 | #You can check to make sure the environment variable is set by running: 21 | echo $ARM_TOOLS_PATH 22 | 23 | # For Raspberry Pi/ARM 32-bit hardware 24 | # In: Deployment Folder 25 | fprime-util generate raspberrypi 26 | fprime-util build raspberrypi 27 | 28 | # For ARM 64-bit hardware 29 | # In: Deployment Folder 30 | fprime-util generate aarch64-linux 31 | fprime-util build aarch64-linux 32 | ``` -------------------------------------------------------------------------------- /docs/integration/camera/compiling-libcamera.md: -------------------------------------------------------------------------------- 1 | ## Compiling libcamera for Native Linux 2 | To compile libcamera for Native Linux, do the below: 3 | ```bash 4 | meson setup build -Dprefix=/libcamera/build/ 5 | cd build 6 | ninja 7 | ninja install 8 | ``` 9 | 10 | ## Cross Compiling libcamera for ARM Linux 11 | 12 | Before building the System Reference for ARM Linux, libcamera will first need to be cross-compiled for ARM 64-bit or ARM 32-bit Linux. 13 | 14 | 1. In a terminal, set the RPI_TOOLS variable to the path to the ARM cross compilers and add it to your PATH: 15 | ```bash 16 | export RPI_TOOLS= 17 | export PATH=$RPI_TOOLS:$PATH 18 | ``` 19 | 20 | 3. Navigate to the libcamera directory and cross-compile for ARM Linux by doing: 21 | 22 | For Raspberry Pi/ARM 32-bit: 23 | ```bash 24 | meson setup build -Dprefix=/libcamera/build/ -Dpipelines=rpi/vc4 -Dipas=rpi/vc4 --cross-file ../libcamera-aarch32.txt 25 | cd build 26 | ninja 27 | ninja install 28 | ``` 29 | 30 | For ARM 64-bit: 31 | ```bash 32 | meson setup build -Dprefix=/libcamera/build/ -Dpipelines=rpi/vc4 -Dipas=rpi/vc4 --cross-file ../libcamera-aarch64.txt 33 | cd build 34 | ninja 35 | ninja install 36 | ``` -------------------------------------------------------------------------------- /docs/integration/camera/example.md: -------------------------------------------------------------------------------- 1 | # Image Capture Tutorial 2 | 3 | ### Tutorial Steps: 4 | - [Required Setup](#required-setup) 5 | - [Capture Frame](#capture-frame) 6 | - [Downlink Image Data](#downlink-image-data) 7 | - [Process Image Data](#process-image-data) 8 | 9 | ### Required Setup 10 | Before getting started, ensure the below has been done: 11 | 1. libcamera has been built for the target system, see [here](../../../README.md#setup-libcamera) for steps. 12 | 2. The System Reference with libcamera has been built for the target system, see [here](building-system-ref-with-libcamera.md) for steps. 13 | 3. Ensure that the F´ GDS is running, the System Reference has been copied over to the Rapsberry Pi, and that the software is running. See steps [here](../../../README.md#upload-to-the-raspberry-pi) for more information. 14 | 15 | ## Capture Frame 16 | 1. Open a new log file by sending the `saveImageBufferLogger.BL_OpenFile` command with a unique file name as the argument. For example: `saveImageBufferLogger.BL_OpenFile, "Example"` 17 | 2. Configure the image resolution by setting the `IMG_RESOLUTION` parameter, which can be done by issuing `camera.IMG_RESOLUTION_PRM_SET` with desired resolution argument. Note: Keep track of the resolution used, since it will be referenced when processing the image data later on. For example: `camera.IMG_RESOLUTION_PRM_SET, SIZE_800x600` 18 | 3. Capture frame data by sending the `camera.CaptureImage` command 19 | 4. Close the log file by sending `saveImageBufferLogger.BL_CloseFile` 20 | 21 | ## Downlink Image Data 22 | Now that image data has been captured and saved to disk, we now want to downlink the data so that it can be processed on the ground. 23 | 1. Select the `fileDownlink.sendFile` and specify the source location of the file and the destination where the file should be stored on the ground. For example: `fileDownlink.SendFile, "/home/pi/images/saveImageExample.dat", "saveImageExample.dat"` 24 | 2. In F´ GDS, click the Downlink tab to check the downlink status. Once the file has been downlinked, take note of the destination location specified under the "Destination" column. 25 | 26 | ## Process Image Data 27 | Before getting started, ensure that the dependencies for running the ground tools have been installed, see [here](../../../ground-tools/README.md) for more details. 28 | 29 | From the project root, navigate to the ground tools directory: `cd ground-tools` 30 | 31 | Process the image data as PNG by running: 32 | ``` 33 | python process_raw_image.py -i <.dat filename on ground> -r -f PNG 34 | ``` 35 | 36 | Process the image data as JPEG by running: 37 | ``` 38 | python process_raw_image.py -i <.dat filename on ground> -r -f JPEG 39 | ``` 40 | 41 | Processed images will be saved in the same directory as the input file as the input file name followed by the .png or .jpg extension. 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/integration/gnc-sensor-integration.md: -------------------------------------------------------------------------------- 1 | # How To Integrate a New GNC Device 2 | 3 | F´ projects will typically have a set of GNC sensors in order to control the movement of the spacecraft. These projects may 4 | follow this guide to get a basic understanding of integrating similar sensors into F´. This guide will outline the process 5 | integrate Gnc sensors. 6 | 7 | ## Who Should Follow This Guide? 8 | 9 | Projects who wish to use the standard F driver models [here](https://github.com/nasa/fprime/tree/0ae2321bb552174ce607075b1283029d6d75a6d6/Drv) 10 | to communicate with GNC sensors. 11 | 12 | ### Assumptions and Definitions for Guide 13 | 14 | This guide makes the following assumptions about the user following the guide and the project setup that will deploy the 15 | component. 16 | 17 | 1. User is familiar with standard F´ concepts 18 | 2. User understands the [application-manager-drive](https://nasa.github.io/fprime/UsersGuide/best/app-man-drv.html) pattern 19 | 3. User is familiar with the F´ development process 20 | 4. User understands how to integrate components into an F´ topology 21 | 5. User has the device's data sheet on hand 22 | 23 | This guide also uses the following terminology: 24 | - GNC: Guidance, navigation and controls 25 | - IMU: Inertial measurement unit 26 | 27 | ## Example Hardware Description 28 | 29 | Throughout this guide, we will be using an MPU6050 IMU sensor and its I2C interface as an example for how to integrate IMU sensors. 30 | We are primarily interested in the accelerometer and gyroscope data that the sensor provides. 31 | 32 | ## Step 1: Define Component Requirements 33 | 34 | The first step is to define the type of requirements that the sensor should fulfill. These requirements should 35 | capture: 36 | 37 | 1. Ports and Interfaces 38 | 2. Events, telemetry, and commands 39 | 3. Component behavior 40 | 4. Assumptions about the interfaces 41 | 5. Overall system integration 42 | 43 | Using the MPU6050 IMU sensor as an example, requirements can be derived by the following process. 44 | We begin by determining that in accordance to the Application Manager Driver Pattern which layer the component would fall under. 45 | In the case of the MPU6050 IMU sensor it would be integrated into the system project at the manager level. 46 | This is because the IMU component is defined to only know its current orientation and acceleration, and the driver it should 47 | talk to in order to collect data. Besides, these aspects the IMU component does not know how the data will be 48 | used by the GNC application or even if the collected data will be used at all. The next step is to determine the type of 49 | assumptions that are being made in regard to the interface. In the case of the MPU6050 IMU sensor, information about the interface 50 | is provided by the data sheet given by the manufacturer. Therefore, the requirements, design and implementation will be 51 | based on the implicit assumption that the provided information on the data sheet is accurate in order to interact with 52 | the sensor. From these considerations the example requirements derived are shown below: 53 | 54 | | Requirement ID | Description | Verification Method | 55 | |-----------------|--------------------------------------------------------------------------------------------------|---------------------| 56 | | GNC-IMU-001 | The Gnc::Imu component shall produce telemetry of accelerometer data at 1Hz | Unit Test | 57 | | GNC-IMU-002 | The Gnc::Imu component shall produce telemetry of gyroscope data at 1Hz | Unit Test | 58 | | GNC-IMU-003 | The Gnc::Imu component shall be able to communicate with the MPU6050 over I2C | Inspection | 59 | | GNC-IMU-004 | The Gnc::IMu component shall produce the latest gyroscope and accelerometer data via a port call | Unit Test | 60 | | GNC-IMU-005 | The Gnc::Imu component shall support power on and power off commands | Unit Test | 61 | 62 | ## Step 2: Component Design 63 | 64 | The second step to integrating a GNC sensor is designing the component model. This section will go through each design stage 65 | and each decision behind the design. Projects should implement design decisions based on their component requirements. 66 | 67 | The final FPP model for the MPU6050 IMU component can be found [here](https://github.com/fprime-community/fprime-system-reference/blob/main/SystemReference/Gnc/Imu/Imu.fpp). 68 | 69 | ### 2.1 Base Component Design 70 | 71 | First, a project should choose the basic component properties. Namely, the component must be `active`, `passive` or 72 | `queued`. Properties of the component will be dependent on the complexity of the required behavior of the hardware. 73 | 74 | For the MPU6050 example, since the device will only be reading and writing accelerometer and gyroscope data, which 75 | consists of a few I2C bytes, a passive component is sufficient as the transactions will fit within the larger rate 76 | group driving the sensor. 77 | 78 | ### 2.2 Port Design 79 | 80 | Ports are typically dependent on the outlined requirements, as well as the underlying driver of the sensor. 81 | Furthermore, projects often use rate groups to drive Gnc sensor reading and take advantage of telemetry, events, 82 | parameters, and commands. 83 | 84 | The example component uses ['Drv::LinuxI2cDriver'](https://github.com/nasa/fprime/tree/master/Drv/LinuxI2cDriver) 85 | which uses the `Drv.I2c` for basic reads and writes. The component also defines a `Gnc::ImuDataPort` port for 86 | returning telemetry to other components. A rate group port is used for the 1Hz timing, and standard F´ports 87 | for events, channels, and telemetry ar needed. An example port chart can be seen below: 88 | 89 | | Kind | Name | Port Type | Usage | 90 | |-----------------|-------------------|-------------------|----------------------------------------------------| 91 | | `output` | `read` | `Drv.I2c` | Port that outputs the read data from sensor | 92 | | `output` | `write` | `Drv.I2c` | Port that outputs written data from sensor | 93 | | `guarded input` | `getGyroscope` | `Gnc.ImuDataPort` | Port that returns gyroscope data | 94 | | `guarded input` | `getAcceleration` | `Gnc.ImuDataPort` | Port that returns acceleration data | 95 | | `guarded input` | `run` | `Svc.Sched` | Port that updates accelerometer and gyroscope data | 96 | **Note:** standard event, telemetry, and command ports are not listed above. 97 | 98 | ### 2.3 Event Design 99 | 100 | Component implementors are free to define whatever events are needed for their project. Typically, GNC components will 101 | emit an error event when the underlying hardware calls fail. 102 | 103 | In this example, the IMU component will define one event: 104 | 105 | | Event | Severity | Description | 106 | |----------------|------------|------------------------------------------| 107 | | TelemetryError | WARNING_HI | Error occurred when requesting telemetry | 108 | 109 | ### 2.4 Telemetry Design 110 | 111 | Component implementors are free to define whatever telemetry/channels are needed for their project. Typically, Gnc 112 | components emit telemetry representing the state of the given GNC sensor. 113 | 114 | In this example, the IMU component will define two telemetry channels one for each of the sensor readings taken: 115 | 116 | | Telemetry Channel | Type | Description | 117 | |-------------------|--------|-----------------------------------------| 118 | | accelerometer | Vector | X, Y, Z acceleration from accelerometer | 119 | | gyroscope | Vector | X, Y, Z degrees from gyroscope | 120 | 121 | ### 2.5 Commands Design 122 | 123 | Component implementors are free to define whatever commands are needed for their project. Some example of commands to consider 124 | would include enable and disable of the sensor. 125 | 126 | In this example, no commands were defined. 127 | 128 | ## Implementation and Testing 129 | 130 | Projects will need to implement the port handlers and implementation for their GNC components on their own. 131 | Specific implementations will diverge based on hardware and design choices. 132 | 133 | In order to help in this process, the example component implementation is available for 134 | [reference](https://github.com/fprime-community/fprime-system-reference/blob/main/SystemReference/Gnc/Imu/Imu.cpp). 135 | 136 | 137 | ## Topology Integration 138 | 139 | Once the design and implementation is done, the component can be added to a projects' topology. 140 | Project may attach additional support components as needed. 141 | 142 | ## Conclusion 143 | 144 | In this guide, we have covered the design of new Gnc components and seen how to integrate it into the 145 | topology. At this point, projects should be able to integrate GNC components as needed. 146 | -------------------------------------------------------------------------------- /docs/integration/xbee-radio-integration.md: -------------------------------------------------------------------------------- 1 | # System Reference XBee Com Component Integration 2 | 3 | ## Table of Contents 4 | 5 | * [1. Initializing XBee Radios](#1-ensure-xbee-devices-are-working-together) 6 | * [2. Topology Changes for UART Driver](#2-change-the-topology-of-the-com-components-to-include-the-linux-uart-driver) 7 | * [3. Instance Changes for Com Component](#3-change-the-instances-of-the-com-components-to-use-xbee-radio-com) 8 | * [4. Rerun build](#4-rerun-build) 9 | * [5. Running F Prime with UART Devices](#5-running-f-prime-with-uart-devices) 10 | * [F Prime GDS](#f-prime-gds) 11 | * [F Prime Binary](#f-prime-binary) 12 | 13 | ## 1. Initializing XBee Radios 14 | 15 | We recommend you to test your XBee radios before implementing them into the F Prime ecosystem 16 | 17 | 1. Download XCTU from [Digi](https://www.digi.com/products/embedded-systems/digi-xbee/digi-xbee-tools/xctu) 18 | 2. Follow the steps to download the software and install it. 19 | 3. Plug in XBee into device and do a default search for your XBee devices 20 | 4. Open serial console and troubleshoot if they are not able to communicate with eachother 21 | 22 | [Link](https://www.digi.com/resources/documentation/digidocs/PDFs/90001458-13.pdf) to Digi documentation for XCTU. 23 | 24 | ## 2. Topology Changes for UART Driver 25 | 26 | Find the following code snippets and uncomment them in the `SystemReference/Top/topology.fpp` file. 27 | 28 | All segments you need to uncomment start with `# XBee Radio Integration` 29 | 30 | ``` 31 | # rateGroup1Comp.RateGroupMemberOut[4] -> radio.run 32 | ``` 33 | ``` 34 | # connections Radio { 35 | # radio.allocate -> comBufferManager.bufferGetCallee 36 | # radio.deallocate -> comBufferManager.bufferSendIn 37 | # } 38 | ``` 39 | 40 | 41 | ## 3. Instance Changes for Com Component 42 | 43 | Find the following code snippets and uncomment them in `SystemReference/Top/instances.fpp` file. 44 | 45 | All segments you need to uncomment start with `# XBee Radio Integration` 46 | 47 | ``` 48 | # instance comDriver: Drv.LinuxUartDriver base id 0x4000 { 49 | # phase Fpp.ToCpp.Phases.configConstants """ 50 | # enum { 51 | # PRIORITY = 100, 52 | # STACK_SIZE = Default::stackSize 53 | # }; 54 | # """ 55 | # 56 | # phase Fpp.ToCpp.Phases.startTasks """ 57 | # // Initialize socket server if and only if there is a valid specification 58 | # if (state.hostName != nullptr) { 59 | # Os::TaskString name("ReceiveTask"); 60 | # // Uplink is configured for receive so a socket task is started 61 | # if (comDriver.open(state.hostName, static_cast(state.portNumber), Drv::LinuxUartDriver::NO_FLOW, 62 | # Drv::LinuxUartDriver::PARITY_NONE, 1024)) { 63 | # comDriver.startReadThread( 64 | # ConfigConstants::comDriver::PRIORITY, 65 | # ConfigConstants::comDriver::STACK_SIZE 66 | # ); 67 | # } else { 68 | # printf("Failed to open UART port %s at speed %" PRIu32 "\n", state.hostName, state.portNumber); 69 | # } 70 | # } 71 | # """ 72 | # 73 | # phase Fpp.ToCpp.Phases.stopTasks """ 74 | # comDriver.quitReadThread(); 75 | # """ 76 | # 77 | # phase Fpp.ToCpp.Phases.freeThreads """ 78 | # comDriver.join(nullptr); 79 | # """ 80 | # } 81 | ``` 82 | 83 | ``` 84 | # instance radio: Com.XBee base id 0x1200 \ 85 | # queue size Default.queueSize \ 86 | # stack size Default.stackSize \ 87 | # priority 140 88 | ``` 89 | 90 | Comment out the following: 91 | 92 | All segments you need to comment out start with `# TCP/IP Integration` 93 | 94 | ``` 95 | instance radio: Svc.ComStub base id 0x1200 96 | 97 | # @ Communications driver. May be swapped with other comm drivers like UART 98 | # @ Note: Here we have TCP reliable uplink and UDP (low latency) downlink 99 | instance comDriver: Drv.ByteStreamDriverModel base id 0x4000 \ 100 | type "Drv::TcpClient" \ 101 | at "../../Drv/TcpClient/TcpClient.hpp" \ 102 | { 103 | phase Fpp.ToCpp.Phases.configConstants """ 104 | enum { 105 | PRIORITY = 100, 106 | STACK_SIZE = Default::stackSize 107 | }; 108 | """ 109 | 110 | phase Fpp.ToCpp.Phases.startTasks """ 111 | // Initialize socket server if and only if there is a valid specification 112 | if (state.hostName != nullptr && state.portNumber != 0) { 113 | Os::TaskString name("ReceiveTask"); 114 | // Uplink is configured for receive so a socket task is started 115 | comDriver.configure(state.hostName, state.portNumber); 116 | comDriver.startSocketTask( 117 | name, 118 | true, 119 | ConfigConstants::comDriver::PRIORITY, 120 | ConfigConstants::comDriver::STACK_SIZE 121 | ); 122 | } 123 | """ 124 | 125 | phase Fpp.ToCpp.Phases.freeThreads """ 126 | comDriver.stopSocketTask(); 127 | (void) comDriver.joinSocketTask(nullptr); 128 | """ 129 | 130 | } 131 | ``` 132 | 133 | ## 4. Rerun build 134 | 135 | After making these changes, you must rebuild the system reference 136 | 137 | ```bash 138 | #In: SystemReference 139 | fprime-util build 140 | ``` 141 | 142 | ## 5. Running F Prime with UART Devices 143 | 144 | In order to run F Prime with a UART device, you must start each part of F Prime seperately if testing on the same device 145 | 146 | ### F Prime GDS 147 | ```bash 148 | fprime-gds -n --dictionary --comm-adapter uart --uart-device --uart-baud 149 | ``` 150 | 151 | ### F Prime Binary 152 | ```bash 153 | ./SystemReference -a -p 154 | ``` 155 | -------------------------------------------------------------------------------- /ground-tools/README.md: -------------------------------------------------------------------------------- 1 | ## Setup 2 | 1. From the project root, navigate to the ground tools directory: `cd ground-tools` 3 | 2. In a python virtual environment, install the ground tool dependencies by doing: `pip install -r requirements.txt` 4 | 5 | 6 | ## Usage 7 | `process_raw_image.py`: Script that takes in a .dat file consisting of raw images data and processes it as either JPEG or PNG. Run the script with the `--help` option for more details. 8 | 9 | Example: `python process_raw_image.py -i capture_1.dat -f PNG -r 800x600` 10 | -------------------------------------------------------------------------------- /ground-tools/process_raw_image.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from io import BytesIO 3 | import argparse 4 | import sys 5 | import os 6 | 7 | def main(): 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument('-i', '--in_file', required=True, help="dat file containing raw image data") 10 | parser.add_argument('-f', '--format', required=False, default='JPEG', choices=['JPEG', 'PNG']) 11 | parser.add_argument('-r', '--resolution', required=False, default="640x480", choices=["800x600", "640x480"]) 12 | 13 | 14 | args = parser.parse_args() 15 | 16 | in_file = args.in_file 17 | file_format = args.format 18 | resolution = args.resolution 19 | 20 | if not os.path.isfile(in_file): 21 | print(f"File {in_file} does not exist, exiting.") 22 | sys.exit(0) 23 | 24 | if not in_file.endswith(".dat"): 25 | print("Incorrect input file format specified, exiting.") 26 | sys.exit(0) 27 | 28 | if '640x480' == resolution: 29 | resolution = (640, 480) 30 | else: 31 | resolution = (800, 600) 32 | 33 | out_file = in_file[:-4] 34 | if file_format == 'JPEG': 35 | out_file += '.jpg' 36 | else: 37 | out_file += '.png' 38 | 39 | f = open(in_file, "rb") 40 | data = f.read() 41 | f.close() 42 | 43 | processed_img = Image.frombytes(mode="RGB", size=resolution, data=data) 44 | processed_img.save(out_file) 45 | 46 | if __name__ == "__main__": 47 | main() -------------------------------------------------------------------------------- /ground-tools/requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow==10.0.0 -------------------------------------------------------------------------------- /libcamera-aarch32.txt: -------------------------------------------------------------------------------- 1 | [binaries] 2 | c = 'arm-linux-gnueabihf-gcc' 3 | cpp = 'arm-linux-gnueabihf-g++' 4 | ar = 'arm-linux-gnueabihf-ar' 5 | strip = 'arm-linux-gnueabihf-strip' 6 | 7 | [host_machine] 8 | system = 'linux' 9 | cpu_family = 'arm' 10 | cpu = 'arm' 11 | endian = 'little' -------------------------------------------------------------------------------- /libcamera-aarch64.txt: -------------------------------------------------------------------------------- 1 | [binaries] 2 | c = 'arm-none-linux-gnueabihf-gcc' 3 | cpp = 'arm-none-linux-gnueabihf-g++' 4 | ar = 'arm-none-linux-gnueabihf-ar' 5 | strip = 'arm-none-linux-gnueabihf-strip' 6 | 7 | [host_machine] 8 | system = 'linux' 9 | cpu_family = 'aarch64' 10 | cpu = 'aarch64' 11 | endian = 'little' --------------------------------------------------------------------------------