Custom Components: Creating a Message
Messages
In the Nominal Systems simulation architecture, a message is an object to store data about a specific simulation component. Most components have messages that store data and are useful for connecting component states. As seen in the previous tutorial, the thermal sensor was able to store a reference to both the power source message from the solar panel and the atmosphere message from the spacecraft. These were not owned by the sensor but by the objects they came from.
In this example, the thermal sensor should have a message that it owns with the following properties:
- Time: [s] The current time tag of the sensor’s state
- Temperature: [K] The current temperature that the thermal sensor is reading
Creating a Payload
All messages require a payload object to store the data. The payload is a struct
which can contain any number of parameters. This is required for the simulation architecture to serialize the data correctly and be used within the simulation. To create the payload, in a new folder, create a new file called ExampleThermalPayload.cs
. It should have the following as a base struct information:
namespace Editor;
public struct ExampleThermalPayload
{
public ExampleThermalPayload() { }
}
Once created, the payload can have the two parameters added, each initialized with a default value. These can be of any type, but this case should be of type double
.
public double Time = 0.0;
public double Temperature = 0.0;
The final payload class would look like the following:
Creating a Message
Once a payload has been created with all the correct data, a message class can also be added to the same folder. This one will be named ExampleThermalMessage.cs
. In this case, the message should inherit from the NominalSystems.Core
namespace class Message<T>
, where T
is the generic payload structure of the type that we used. Messages contain a payload that is of the type specified. This must be hard-coded within the class definition and cannot changed within the class.
namespace Editor;
using NominalSystems.Core;
public class ExampleThermalMessage : Message<ExampleThermalPayload>
{
public ExampleThermalMessage() { }
}
Note
Similar to simulation classes, both the payloads and the messages must have a default constructor present within the class for the serializer to work correctly.
Instead of adding the data to the message here, the two parameters must be added as properties, with getters and setters. This enables some error checks and ensures that the data is present within the payload data. A typical way to do this is by using a property structure and the get
and set
operations.
public double Time {
get => Payload.Time;
set => Payload.Time = value;
}
public double Temperature {
get => Payload.Temperature;
set => Payload.Temperature = value;
}
In this case, any data that is set on the message will be set directly on the payload and not stored on the message memory. The data retrieved from the message can then be pulled straight from the payload - where the payload is the message structure data that was created in the previous step. Using the set
values means additional error checks can be made on the data. For example, to ensure the temperature is greater than zero, a System.Math
function can be used.
set => Payload.Temperature = System.Math.Max(0, value);
Typically, a unit attribute is attached to a message property which will ensure that the correct units are displayed if the messages are printed to the screen in Nominal Editor, or shown correctly in Nominal Studio. This is done by adding in the Units
attribute and by specifying the units within the brackets. It can be done using [Units("-")]
. If there is no unit associated with a particular value, use the -
character. The final code for the message will look like the following:
Adding the Message
Now that the message is created, returning to the ExampleThermalSensor
class, a new parameter can be added to the top of the class. This will be of type ExampleThermalMessage
and will be named Out_ThermalMsg
. By default, since this is an output message, it should be constructed with a new reference.
public ExampleThermalMessage Out_ThermalMsg = new ExampleThermalMessage();
Note
It is common practice for output messages to be initialized with a default value and a new object every time they are created or in the constructor. By leaving the reference blank, the message will not be initialized. Input messages should not follow this convention and should be left empty after the constructor is called.
Once the message is added, returning to the OnUpdate
function, the temperature
value should be set to the message once it is calculated. This will be done by adjusting the property on the message itself. The same should be done with the timestamp.
Out_ThermalMsg.Time = time;
Out_ThermalMsg.Temperature = temperature;
Cleaning Up
That is all the code that is required for this particular component. Once done, the code should be cleaned up and comments should be added to explain what is happening. The final code with comments will look like this: