Recording Messages#

Once a simulation is functioning with connected messages between modules, you might want to access the resulting data. This is done by creating message recorder objects that store a time history of the message data.

../../_images/qs-bsk-4.svg

The figure above illustrates a sample simulation setup. The single test module mod1 has its output message connected to its input message, forming a feedback loop that causes the output to change over time. See the module code for details on the simple mathematical operations being performed. To track the state of the message at various time steps, message recorders are added to the simulation.

The corresponding simulation script is included below. Since the recorded data will be plotted, the matplotlib library is imported, along with the unitTestSupport helper module from Basilisk.utilities.

 1
 2import sys
 3
 4import matplotlib.pyplot as plt
 5from Basilisk.moduleTemplates import cppModuleTemplate
 6from Basilisk.utilities import SimulationBaseClass
 7from Basilisk.utilities import macros
 8from Basilisk.utilities import unitTestSupport
 9
10
11def run():
12    """
13    Illustration of recording messages
14    """
15
16    #  Create a sim module as an empty container
17    scSim = SimulationBaseClass.SimBaseClass()
18
19    #  create the simulation process
20    dynProcess = scSim.CreateNewProcess("dynamicsProcess")
21
22    # create the dynamics task and specify the integration update time
23    dynProcess.addTask(scSim.CreateNewTask("dynamicsTask", macros.sec2nano(1.)))
24
25    # create modules
26    mod1 = cppModuleTemplate.CppModuleTemplate()
27    mod1.modelTag = "module1"
28    scSim.AddModelToTask("dynamicsTask", mod1)
29    mod1.dataInMsg.subscribeTo(mod1.dataOutMsg)
30
31    # setup message recording
32    msgRec = mod1.dataOutMsg.recorder()
33    scSim.AddModelToTask("dynamicsTask", msgRec)
34    msgRec2 = mod1.dataOutMsg.recorder(macros.sec2nano(20.))
35    scSim.AddModelToTask("dynamicsTask", msgRec2)
36
37    #  initialize Simulation:
38    scSim.InitializeSimulation()
39
40    #   configure a simulation stop time and execute the simulation run
41    scSim.ConfigureStopTime(macros.sec2nano(60.0))
42    scSim.ExecuteSimulation()
43
44    # plot recorded data
45    plt.close("all")
46    plt.figure(1)
47    figureList = {}
48    for idx in range(3):
49        plt.plot(msgRec.times() * macros.NANO2SEC, msgRec.dataVector[:, idx],
50                 color=unitTestSupport.getLineColor(idx, 3),
51                 label='$x_{' + str(idx) + '}$')
52        plt.plot(msgRec2.times() * macros.NANO2SEC, msgRec2.dataVector[:, idx],
53                 '--',
54                 color=unitTestSupport.getLineColor(idx, 3),
55                 label=r'$\hat x_{' + str(idx) + '}$')
56    plt.legend(loc='lower right')
57    plt.xlabel('Time [sec]')
58    plt.ylabel('Module Data [units]')
59    if "pytest" not in sys.modules:
60        plt.show()
61    figureList["bsk-4"] = plt.figure(1)
62    plt.close("all")
63
64    return figureList
65
66
67if __name__ == "__main__":
68    run()

Adding a Message Recorder#

After creating a module instance and adding it to a task list, you can set up a message recorder. The syntax is as follows. Suppose you want to record module.someOutMsg. This message can be an output or input message. The recorder object is created using:

someMsgRec = module.someOutMsg.recorder()
scSim.AddModelToTask("taskName", someMsgRec)

The first line sets up the recorder to monitor someOutMsg. As with any simulation component, it must be added to a task to execute during simulation updates. By default, the recorder captures data at the same rate as the task’s update frequency. To reduce how often data points are recorded, provide a minUpdateTime argument:

someMsgRec = module.someOutMsg.recorder(minUpdateTime)

Here, minUpdateTime is the minimum interval (in nanoseconds) between recordings.

In the script example above, the recorder msgRec records at the task’s update rate. Meanwhile, msgRec20 records only once every 20 seconds, demonstrating how to manage data volume efficiently.

Pulling the Recorded Message Data#

Once the simulation completes, the data is stored in the respective recorder objects (msgRec, msgRec20, etc.). To access recorded variables, use msgRec.variable, where variable is a specific field in the message structure.

Use msgRec.times() to retrieve the array of time stamps when data was recorded. The method msgRec.timesWritten() returns time stamps of when the message was written. Why two time arrays? Imagine an output message updated every 3 seconds, while the recorder checks it every second. The timesWritten() array will contain repeated values until the message updates again.

In the referenced simulation, the output message contains only the array dataVector. It is recorded at a 1 Hz rate by msgRec and at 20-second intervals by msgRec20. The simulation produces the following plot:

_images/Scenarios/bsk-4.svg

Clearing the Message Recorder Data Log#

Recorders will continue appending data as long as the simulation runs. If you pause, read data, and then resume, the recording continues from where it left off. To clear the existing data and start fresh, use the .clear() method:

scRec.clear()

This ensures only new data is recorded after the method is called.

Reading the Current Value of a Message#

To access the most recent data in a message, use the read method:

msgCopy = msg.read()

This returns a snapshot of the current message content.