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