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.
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:
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.