Recording Messages
Great, now that we have a functioning simulation where Basilisk modules are set up with their messages connected, how do we get access to the simulation data? This is done by creating recorder modules that will store a time history of the generated messages.
The figure above illustrates a sample Basilisk simulation. The single test module cModule1
has its output message connected to its input message. This creates a feedback loop that will cause the output message to change. See the module code to understand the underlying simple math. To record the message state at various time steps, recorder modules will be created that perform this task.
Note
The method of recording a message is the same, regardless if it is a C or C++ module, or the message has a C or C++ interface.
The simulation code is included below. As the recorded message data will be plotted in this script, the matplotlib
library is included at the top, as well as the helper package unitTestSupport
from Basilisk.utilities
.
1
2import sys
3
4import matplotlib.pyplot as plt
5from Basilisk.moduleTemplates import cModuleTemplate
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 = cModuleTemplate.cModuleTemplate()
27 mod1.ModelTag = "cModule1"
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 the single BSK module instance is created and added to the task list, new code is provided to set up the message recorders. The general syntax is as follows. Assume you want to record module.someOutMsg
. Note that this message can be either an output or input message. The corresponding recorder module is created using:
someMsgRec = module.someOutMsg.recorder()
scSim.AddModelToTask("taskName", someMsgRec)
The first line in the code block above creates the BSK message recorder object which is setup to record someOutMsg
. As with any Basilisk module, it next needs to be added to a task to be executed each update cycle. As is, the recorder is set up to record the message at the same frequency as the task list update frequency. If you want to reduce the number of data points being recorded, you can provide an option argument:
someMsgRec = module.someOutMsg.recorder(minUpdateTime)
Here minUpdateTime
is the minimum time interval that must pass before this recorder object will record the message.
In the full script above, the recorder module msgRec
is set up to record the message at the dynamicsTask
update rate. In contrast, the module msgRec20
is setup to record the message only after 20s have passed. Note that the minUpdateTime
argument must be provided again in nano-seconds.
That is all that is required to set up message recording. Next the code initializes the simulation and executes it.
Pulling the Recorded Message Data
After the simulation completes, the recorded data is stored inside the msgRec
and msgRec20
recorders. To access the variables of the message, simply use msgRec.variable
where variable
is the message structure variable you seek to access. To access the array of time values where the message was recorded use msgRec.times()
. A second time array is available that stores the times where the messages are written with msgRec.timesWritten()
. Why two time arrays? Consider an output message that is only updated every 3s, but the message is being read and recorded every second. The .timesWritten()
values will repeat until a new output message is created.
Module: cModuleTemplate output message only contains the array dataVector
. In this simulation it is recorded at the rate of 1Hz in msgRec
, and every 20s in the recorder msgRec20
. The simulation creates the following plot:
Clearing the Message Recorder Data Log
Note that the message recorder will continuously add message data to its internal data vectors. If you start and stop the simulation, pull the data, resume the simulation and so on, this message data recording process is cumulative. If you stop the simulation and want to clear the message recorder data log so that only new data is recorded, you can clear the message recorder module data buffer using the .clear()
method. For example, assume a message recorder scRec
has been setup and needs to be cleared, this is done with:
scRec.clear()
Reading the Current Value of a Message
If you have a message msg
and want to pull a current copy of the message data or payload, you can use
this method on both C and C++ wrapped message objects:
msgCopy = msg.read()