Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/generate_msg_payload_rst.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def extract_struct_name(header_path):
def generate_payload_rst(header_path, output_dir):
"""Generate a single RST file for a message payload header."""
struct_name = extract_struct_name(header_path)
# Skip headers that don't define a Payload struct (e.g., definitions.h)
# Ignore headers that do not define a Payload struct
if not struct_name.endswith("Payload"):
return None, struct_name

Expand Down
1 change: 1 addition & 0 deletions docs/source/learn/making-modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ and subclasses of either:
making-modules/messaging/overview
making-modules/messaging/message-objects
making-modules/messaging/creating-message-types
making-modules/messaging/mission-parameters
making-modules/module-adapter
making-modules/module-python
making-modules/module-testing
Expand Down
115 changes: 115 additions & 0 deletions docs/source/learn/making-modules/messaging/mission-parameters.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
.. _messaging-mission-parameters:

Configuring Mission Parameters
==============================

Several message payloads and modules get their array bounds from a small set of
project-wide constants. These constants include sensor counts and effector
counts. They are in one header:

``mission/parameters.h``

One header only is in use for a build. That header gives every constant. The
build does not add values to it, and it does not give a default value for a
missing constant.

The Mission Parameters
----------------------

============================ ======= ========================================
Constant Default Meaning
============================ ======= ========================================
``MAX_KEY_POINTS`` 5000 Optical flow key points
``MAX_NUM_CSS_SENSORS`` 32 Coarse sun sensors in a constellation
``MAX_EFF_CNT`` 36 Generic effectors (thrusters and others)
``RW_EFF_CNT`` 36 Reaction wheels
``MAX_SICP_POINTS`` 5000 Point-cloud points
``SICP_POINT_DIM`` 3 Point-cloud point dimension
``MAX_SICP_ITERATIONS`` 250 Point-cloud registration iterations
``MAX_NUMBER_REGIONS`` 3 Regions of interest
============================ ======= ========================================

Each constant gives a bound for a message payload array. Thus all the constants
are part of the messaging ABI, and SWIG gives all of them to the Python layer.
The default values are large. Thus a build with no mission of its own operates
correctly.

Where the Values Come From
--------------------------

The array bounds belong to the mission, and a mission comes to the build as a
module root. A module root declares its own values with this call:

.. code-block:: cmake

xmera_provide_mission_parameters("${CMAKE_CURRENT_SOURCE_DIR}")

Put this call in the ``CMakeLists.txt`` of the module root. The argument is the
directory that *contains* a ``mission`` folder. When you add that root to
``XMERA_MODULE_ROOTS``, you also select the array bounds that its code uses.
The two are one selection. Thus they cannot become different, and there is no
second control to set.

The configure step shows the header that it resolved:

.. code-block:: text

-- Mission parameters: /path/to/myMission/mission/parameters.h

One source only is permitted. If two module roots declare parameters, the
configure step stops and gives the two directories. This check is necessary.
Before this check, a build compiled its Python message payloads against one set
of bounds, and its algorithm objects against a different set. The build gave no
error message. The build showed incorrect results only when a struct moved
across an FFI boundary.

If no module root declares a directory, the build uses
``src/defaults/mission/parameters.h``. That header gives the default values in
the table above.

Supplying Your Own Values
-------------------------

Make a ``mission/parameters.h`` file in your module root. Give a value for
**every** constant in the table. Some of the constants are not in your own
code, but a payload in a different part of the build uses them:

.. code-block:: c

#ifndef MISSION_PARAMETERS_H
#define MISSION_PARAMETERS_H

// clang-format off
#define MAX_NUM_CSS_SENSORS 12
#define MAX_EFF_CNT 12
#define RW_EFF_CNT 4

#define MAX_KEY_POINTS 5000
#define MAX_SICP_POINTS 5000
#define SICP_POINT_DIM 3
#define MAX_SICP_ITERATIONS 250
#define MAX_NUMBER_REGIONS 3
// clang-format on

#endif

Then declare the directory from the ``CMakeLists.txt`` of the module root, as
in the example above. The configure step does not find a missing constant. The
first payload or algorithm that uses that constant gives a compilation error.

Writing the Header
------------------

Write the values as plain integers, in the ``clang-format off`` guard in the
example above. SWIG also reads this header, to give the constants to the Python
layer. Its preprocessor reads the quote in a digit separator, for example
``5'000``, as the start of a character literal. SWIG then discards every
constant after that quote. The result is a missing attribute on
``xmera.architecture.messaging``, and not a build error.

The guard is necessary because the ``.clang-format`` file of this project sets
``IntegerLiteralSeparator``, which adds those separators for you.

``test_missionParameters.py`` prevents that class of failure. The test compares
the compiled array extent of each payload against the constant that SWIG gives
to Python.
4 changes: 4 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ foreach(d IN LISTS XMERA_MODULE_ROOTS)
add_subdirectory("${d}")
endforeach()

# This call comes after the module roots, because a module root can declare its own mission
# parameters.
xmera_resolve_mission_parameters()

xmera_generate_messaging_init()

install(DIRECTORY utilities DESTINATION xmera
Expand Down
4 changes: 4 additions & 0 deletions src/architecture/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ target_include_directories(xmera_core PRIVATE
"${CMAKE_SOURCE_DIR}"
)

# The link is PUBLIC, thus every target that uses the core also gets the mission include
# directory. Other libraries and test executables do not declare the directory again.
target_link_libraries(xmera_core PUBLIC Xmera::MissionParameters)

install(TARGETS xmera_core DESTINATION lib)

add_library(Xmera::Core ALIAS xmera_core)
Expand Down
73 changes: 73 additions & 0 deletions src/architecture/messaging/_UnitTest/test_missionParameters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""The mission parameters get to the build along two paths.

The two paths can give different values, and the build gives no error message.

The C++ compiler reads the ``#include`` directives and finds ``mission/parameters.h``. SWIG does
not read ``#include`` directives. SWIG reads only the files that a ``.i`` file ``%include``s.
Thus the constants that SWIG gives to Python come from a different preprocessor. Only convention
makes sure that the two preprocessors read the same header.

When the two paths give different values, no failure occurs. The compiler sets the layout of the
payload structs, and Python shows a different bound. The results are then incorrect, but only
after a struct with the incorrect layout moves across an FFI boundary. The known cause is a digit
separator in the header. SWIG discards every constant after the separator, and Python then has a
missing attribute.

Each payload field below is a Python list. The length of the list comes from the compiled struct,
but the constant comes from the SWIG preprocessor. Thus a comparison of the two values makes sure
that the two paths agree.
"""

import pytest

from xmera.architecture import messaging

# One (payload type, field, constant) triple for each constant that gives an array bound.
SIZED_FIELDS = [
("RWSpeedMsgPayload", "wheelSpeeds", "RW_EFF_CNT"),
("RWArrayConfigMsgPayload", "JsList", "RW_EFF_CNT"),
("RwMotorTorqueMsgPayload", "motorTorque", "RW_EFF_CNT"),
("CSSArraySensorMsgPayload", "CosValue", "MAX_NUM_CSS_SENSORS"),
("THRArrayOnTimeCmdMsgPayload", "OnTimeRequest", "MAX_EFF_CNT"),
("SunlineFilterMsgPayload", "postFitRes", "MAX_NUM_CSS_SENSORS"),
("RegionsIdentifiedMsgPayload", "timeTag", "MAX_NUMBER_REGIONS"),
]

CONSTANTS = [
"MAX_KEY_POINTS",
"MAX_NUM_CSS_SENSORS",
"MAX_EFF_CNT",
"RW_EFF_CNT",
"MAX_SICP_POINTS",
"SICP_POINT_DIM",
"MAX_SICP_ITERATIONS",
"MAX_NUMBER_REGIONS",
]


@pytest.mark.parametrize("name", CONSTANTS)
def test_constant_reaches_python(name):
"""SWIG gives every mission constant to Python.

If a constant is missing, the SWIG preprocessor discarded it. Usually the cause is text
before that constant in the header, and not a constant that no code uses.
"""
assert hasattr(messaging, name), (
f"{name} did not get to the Python layer. SWIG stopped at some line in "
f"mission/parameters.h and discarded the constants after that line."
)
assert isinstance(getattr(messaging, name), int)
assert getattr(messaging, name) > 0


@pytest.mark.parametrize("payload,field,constant", SIZED_FIELDS)
def test_payload_extent_matches_constant(payload, field, constant):
"""The compiled struct and the SWIG constant agree on the array bound."""
expected = getattr(messaging, constant)
actual = len(getattr(getattr(messaging, payload)(), field))
assert actual == expected, (
f"The build compiled {payload}.{field} with {actual} elements, but Python shows "
f"{constant} == {expected}. C++ and SWIG resolved different mission parameters. Make "
f"sure that the build selected the correct mission/parameters.h. Then examine the "
f"%include lines in the .i template."
)
4 changes: 3 additions & 1 deletion src/architecture/messaging/msgAutoSource/msgInterfacePy.i.in
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
%template(DoubleVector) std::vector<double, std::allocator<double>>;
%template(StringVector) std::vector<std::string, std::allocator<std::string>>;

%include <architecture/msgPayloadDef/definitions.h>
// SWIG does not read #include directives. Thus this file must %include the mission parameters
// for the constants to get to the Python layer.
%include <mission/parameters.h>
%include "fswAlgorithms/fswUtilities/fswDefinitions.h"
%include "simulation/dynamics/reactionWheels/reactionWheelSupport.h"
ARRAYINTASLIST(FSWdeviceAvailability)
Expand Down
2 changes: 1 addition & 1 deletion src/architecture/messaging/newMessaging.ih
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
%};
%{
#include "architecture/_GeneralModuleFiles/sys_model.h"
#include "architecture/msgPayloadDef/definitions.h"
#include <mission/parameters.h>
#include "fswAlgorithms/fswUtilities/fswDefinitions.h"
#include <vector>
%}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef ARRAY_EFFECTOR_LOCK_H
#define ARRAY_EFFECTOR_LOCK_H

#include "definitions.h"
#include <mission/parameters.h>

/*! @brief Structure used to define the output definition for vehicle effectors*/
typedef struct {
Expand Down
2 changes: 1 addition & 1 deletion src/architecture/msgPayloadDef/ArrayMotorForceMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef ARRAY_MOTOR_FORCE_H
#define ARRAY_MOTOR_FORCE_H

#include "definitions.h"
#include <mission/parameters.h>

/*! @brief Structure used to define the output definition for vehicle effectors*/
typedef struct {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef ARRAY_MOTOR_TORQUE_H
#define ARRAY_MOTOR_TORQUE_H

#include "definitions.h"
#include <mission/parameters.h>

/*! @brief Structure used to define the output definition for vehicle effectors*/
typedef struct {
Expand Down
2 changes: 1 addition & 1 deletion src/architecture/msgPayloadDef/CSSArraySensorMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef CSS_ARRAY_SENSOR_MESSAGE_H
#define CSS_ARRAY_SENSOR_MESSAGE_H

#include "definitions.h"
#include <mission/parameters.h>

/*! @brief Output structure for CSS array or constellation interface. Each element contains the raw measurement which
* should be a cosine value nominally */
Expand Down
3 changes: 2 additions & 1 deletion src/architecture/msgPayloadDef/CSSConfigMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
#define CSS_CONFIG_MESSAGE_H

#include "architecture/msgPayloadDef/CSSUnitConfigMsgPayload.h"
#include "definitions.h"

#include <mission/parameters.h>
#include <stdint.h>

/*! @brief Structure used to contain the configuration information for
Expand Down
2 changes: 1 addition & 1 deletion src/architecture/msgPayloadDef/MTBArrayConfigMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef MTB_ARRAY_CONFIG_MSG_H
#define MTB_ARRAY_CONFIG_MSG_H

#include "definitions.h"
#include <mission/parameters.h>

/*! @brief magnetic torque bar array configuration msg */
typedef struct {
Expand Down
2 changes: 1 addition & 1 deletion src/architecture/msgPayloadDef/MTBCmdMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef MTB_CMD_MSG_H
#define MTB_CMD_MSG_H

#include "definitions.h"
#include <mission/parameters.h>

/*! @brief Message for magnetic torque bar dipole commands. */
typedef struct {
Expand Down
2 changes: 1 addition & 1 deletion src/architecture/msgPayloadDef/PairedKeyPointsMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef KEYPOINTSMSG_H
#define KEYPOINTSMSG_H

#include <architecture/msgPayloadDef/definitions.h>
#include <mission/parameters.h>

//!@brief Optical Navigation measurement message containing the matched key points between two images
/*! This message is output by the optical flow module and contains the key points shared between the two image
Expand Down
3 changes: 2 additions & 1 deletion src/architecture/msgPayloadDef/PointCloudMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
#ifndef POINTCLOUDMSG_H
#define POINTCLOUDMSG_H

#include <architecture/msgPayloadDef/definitions.h>
#include <mission/parameters.h>

#include <array>

//!@brief N-D point cloud
Expand Down
2 changes: 1 addition & 1 deletion src/architecture/msgPayloadDef/RWArrayConfigMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef RW_CONFIG_MESSAGE_H
#define RW_CONFIG_MESSAGE_H

#include "definitions.h"
#include <mission/parameters.h>
#include <stdint.h>

/*! @brief RW array configuration FSW msg */
Expand Down
3 changes: 2 additions & 1 deletion src/architecture/msgPayloadDef/RWAvailabilityMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
#ifndef _RW_AVAILABILITY_FSW_MSG_H
#define _RW_AVAILABILITY_FSW_MSG_H

#include "definitions.h"
#include "fswAlgorithms/fswUtilities/fswDefinitions.h"

#include <mission/parameters.h>

/*! @brief Array with availability of RW */
typedef struct {
FSWdeviceAvailability wheelAvailability[RW_EFF_CNT]; //!< The current state of the wheel
Expand Down
3 changes: 2 additions & 1 deletion src/architecture/msgPayloadDef/RWConstellationMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
#define _RW_CONSTELLATION_MESSAGE_H

#include "RWConfigElementMsgPayload.h"
#include "definitions.h"

#include <mission/parameters.h>

/*! @brief Message used to define an array of RW FSW configurations */
typedef struct {
Expand Down
2 changes: 1 addition & 1 deletion src/architecture/msgPayloadDef/RWSpeedMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef RW_SPEED_MESSAGE_STRUCT_H
#define RW_SPEED_MESSAGE_STRUCT_H

#include "definitions.h"
#include <mission/parameters.h>

/*! @brief Structure used to define the output definition for reaction wheel speeds*/
typedef struct {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#ifndef REGIONS_IDENTIFIED_H
#define REGIONS_IDENTIFIED_H

#include "definitions.h"
#include <mission/parameters.h>

/*! @brief Regions of interest extracted by camera */
typedef struct {
Expand Down
2 changes: 1 addition & 1 deletion src/architecture/msgPayloadDef/RwMotorTorqueMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef RW_MOTOR_TORQUE_MESSAGE_H
#define RW_MOTOR_TORQUE_MESSAGE_H

#include "definitions.h"
#include <mission/parameters.h>

/*! @brief Structure used to define the message format of the motor torque */
typedef struct {
Expand Down
2 changes: 1 addition & 1 deletion src/architecture/msgPayloadDef/RwMotorVoltageMsgPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef SIM_RW_VOLTAGE_INPUT_H
#define SIM_RW_VOLTAGE_INPUT_H

#include "definitions.h"
#include <mission/parameters.h>

/*! @brief Structure used to define the message format of the motor voltage input */
typedef struct {
Expand Down
Loading
Loading