Custom Components: Creating a Sensor
Custom Thermal Sensor
The next class that will be created is a custom thermal sensor. This particular sensor will be able to detect the temperature of the atmosphere by reading the atmosphere message from the spacecraft. It will also add a temperature value that is dependent on the power of the solar panel if it is attached to the spacecraft. Finally, it will then apply some noise to the reading before creating an output value. This is an example sensor and a realistic sensor would not be as simple.
First, a new class needs to be constructed and inherited from the Sensor
class. This one will be named ExampleThermalSensor
and will have another script inside the same folder as the solar panel one.
using NominalSystems.Classes;
namespace Editor;
public class ExampleThermalSensor : Sensor
{
public ExampleThermalSensor() { }
protected override void OnBegin(double time) {
base.OnBegin(time);
}
protected override void OnUpdate(double time, double step) {
base.OnUpdate(time, step);
}
}
Creating a Reference Message
Nominal Systems has two types of messages; input and output messages. Although the types themselves are the same regardless if it is an input or output, the general definition is components that have output messages will own those messages and be responsible for managing the data. Those messages can then be attached to other component’s input messages in a one-to-many relationship. In this case, the atmosphere message contains information about the density and the temperature of the atmosphere at the current spacecraft’s location. All messages are found within the NominalSystems.Messages
namespace. To create an input message field, use the following lines:
using NominalSystems.Messages;
public class ExampleThermalSensor : Sensor
{
private AtmospherePropsMessage In_AtmoPropsMsg;
...
}
Fetching the Atmosphere Message
The spacecraft will already have an atmosphere message that is attached to the craft. This will be named, as expected, Out_AtmoPropsMsg
.
Note
It is a standard to label the input and output messages in the field parameters and name them such that the type of the message is within the name of the message.
In the OnBegin
event of the sensor, the spacecraft can be fetched using the GetSpacecraft
function. Provided that the sensor is attached to a spacecraft, this will return the spacecraft that the sensor is attached to as a parent. From here, the messages can be connected up so that the In_AtmoPropsMsg
is referencing the actual message value that the spacecraft is responsible for.
base.OnBegin(time);
var spacecraft = GetSpacecraft();
In_AtmoPropsMsg = spacecraft.Out_AtmoPropsMsg;
Note
The var
keyword will let the compiler detect what kind of parameter the variable is rather than having to define it. Although this may be more ambiguous to read, when getting data from functions that have a fixed data type, it can be cleaner and easier to type out. This example is equivalent to writing Spacecraft
instead.
Visual Studio will show the meta-data about the classes if clicked on. By pressing ‘control’ and clicking on the AtmosphereMessage
object, it will show the parameters available within the message. In this case, there is a LocalDensity
and a LocalTemperature
.
Solar Panel Message
The power of the solar panel can be found in the PowerSourceMessage
, which contains information about how much power a source is producing. As a solar panel is a sub-class of a power source, this can be useful for reading the power values without having to store a reference to a solar panel itself.
private PowerSourceMessage In_PowerSourceMsg;
Similar to the spacecraft, the power source message can be pulled from the solar panel if it exists. It is worth noting that a lot of classes will instead just expose the message parameter and require the user to connect the messages themselves manually within Unreal. However, for this tutorial, it will be connected via code. First, we need to attempt to fetch a solar panel object attached to the spacecraft. If the object exists, then the message can be connected in the same way that the power source message is connected. The != null
check will validate whether the object exists.
var solarPanel = spacecraft.GetChild<SolarPanel>();
if (solarPanel != null) {
In_PowerSourceMsg = solarPanel.Out_PowerSourceMsg;
}
Note
The GetChild
function will return any child that is attached to the object (recursively checking down the hierarchy) that is of the type specified. In the case of multiple objects of that type, the first one added to the spacecraft will be returned. The GetChildren
function will return all objects that exist of that type as an array.
One thing to check, if the solar panel does not exist, we will throw an exception that will contain the error message. This way, the component will stop running. If an exception is thrown inside one of the simulation functions, then the component will be disabled and will not run in the simulation. This ensures that the rest of the simulation does not crash due to an error in one component.
else {
throw new System.Exception("No Solar Panel attached to the Spacecraft.");
}
Calculating the Temperature
The temperature of the sensor calculation can be determined in the OnUpdate
event, which will be calculated by adding the temperature from the atmosphere message with the power from the solar panel (multiplied by some value).
double atmoTemp = In_AtmoPropsMsg.LocalTemperature;
double powerTemp = In_PowerSourceMsg.Power * 15.0;
double temperature = atmoTemp + powerTemp;
Note
For now, the temperature will be recorded on a local property. The next tutorial will show how to create a message that contains the data and will use an output message for this sensor to store the data.
Once the raw temperature value is created, some noise should be added to the temperature to scatter the reading a little bit. This can be done using a Normal Distribution. The NominalSystems.Maths
namespace contains some functions for doing this. Include the namespace at the top of the script.
using NominalSystems.Maths;
The NormalDistribution
class can take in a mean value and a standard deviation value for the sensor. We will expose these parameters to the sensor class values. These can be done using public doubles
and will be added to the top of the class.
public class ExampleThermalSensor : Sensor
{
public double NoiseMean = 0.0;
public double NoiseSigma = 1.0;
...
}
In the OnBegin
call, the normal distribution will be created and stored as a local variable. This ensures the distribution is created once, without having to be created multiple times for each update step.
private NormalDistribution Normal;
protected override void OnBegin(double time) {
base.OnBegin(time);
...
Normal = new NormalDistribution(NoiseMean, NoiseSigma);
}
Once the distribution has been set up with the parameters, the error can be applied to the temperature using the +=
operator to add it to the temperature reading in the OnUpdate
event.
...
double temperature = atmoTemp + powerTemp;
temperature += Normal.NextDouble();