Getting Started with ROSMOD
This post describes the design of the ROSMOD meta-model, how to use the rosmod toolsuite, and how to deploy ROSMOD code onto systems. If you aren’t familiar with WebGME (on which ROSMOD is built), then you should start by reading Introduction to WebGME.
ROSMOD is an extension and formalization of ROS which extends the scheduling layer, formalizes a component model with proper execution semantics, and adds modeling of software and systems for a full tool-suite according to model-driven engineering.
When developing models using ROSMOD, the top-level entity is a ROSMOD
Project
, which is a self-contained collection of
- Software
- Systems
- Deployments
- Experiments
These collections act as folders to categorize and separate the many different model objects by their associated modeling concept. In this way, the software defined for a project can be kept completely separate from any of the systems on which the software may run. Similarly, the different ways the software may be instantiated and collocated at run-time is separated into specific deployments which are independent of the hardware model and to some degree the software model.
Project Creation
To create a new project, you can either drag and drop a Project
object from the Part Browser
into the canvas when viewing the
Projects
root node or you can right click on the Projects
root
node of the Tree Browser
and create a new child of type Project
.
Please note that you cannot drag a Documentation
object into the
Projects
canvas to create a documentation object and that if you
choose to create a Documentation
object inside the Projects
root
node it will not be displayed in the RootViz
visualizer which shows
all the Projects and will not be included in any of the generated
documentation.
Project Attributes
Each project has special Code Documentation
attributes: Authors
,
Brief Description
, and Detailed Description
, which are best edited
by clicking on the CodeEditor
visualizer. This visualizer fills the
canvas with a CodeMirror instance which
allows the user to easily edit multi-line strings with:
- automatic saving when changes are made
- undo/redo
- syntax highlighting
- code completion, activated with
ctrl+space
- code folding, using the gutter buttons or
ctrl+q
on the top of the code to be folded (e.g. start of anif
block)
while allowing the user to configure (using drop-down menus):
- the currently viewed/edited attribute
- the current color theme of the code editor
- the current keybindings associated with the code editor (supported keybindings are
sublime
,emacs
, andvim
)
The CodeEditor
visualizer is used in many places throughout the UI;
any object that has attributes which support editing using the
CodeEditor
will display the CodeEditor
as a selection in the
visualizers list in the Visualizer Panel
.
Project Plugins
While viewing a project, the user can run the following plugins:
- SoftwareGenerator: for generating and optionally compiling the **
software
defined for the project according to thehost architectures
defined in thesystem models
of the project. - GenerateDocumenation: aggregates all the
Documentation
objects in the project’s tree, converts them toReStructuredText
and compiles them intohtml
andpdf
. - TimingAnalysis: generates a
Colored Petri-Net
model for performing timing analysis on thedeployments
(software instanced on abstract hardware)
Saving/Exporting the Project
Currently there are two ways to save/export and Load a Project
or
sub-model (e.g. a System
model).
The first way is to save a selected (sub-)tree of the WebGME model, by
right-clicking on the sub-tree’s root (e.g. a System
model) and
selecting Export Library
. This exported library can be shared among
WebGME models and organizations. To load a library saved in this
way, simply right-click on the parent
node in the Tree Browser
for
which you want the library to be a child. Select Import/Update
Library
and choose the library file you like.
The second way is to go to the root
of the WebGME model (i.e. the
Projects
node), and run the plugin ExportImport
which will provide
you a dialog to configure what you wish to do.
Software
The software model
contains all the information required to generate
and compile the software for the project. Because ROSMOD is an
extension of ROS, software is organized by
Packages
, which are ROS
applications that contain executable code,
as well as message and service definitions. We have extended and
formalized these concepts (described in the documentation for a
Package
) and have included information related to required System
Libraries
or Source Libraries
. These libraries are defined in the
software model and include relevant attributes required for
compilation (e.g. link libraries
or include directories
).
In this tutorial, we will not be covering the definition or use of
these libraries, but examples of their use can be found in the
Blinking LEDs
, AGSE
and Traffic Light Controller
examples.
Package
A ROSMOD package contains the definitions for its associated
Messages
and Services
, which follow the ROS
definitions, as well as the definitions for ROSMOD Components
.
Messages
Messages contain a definition
attribute (editable using a
CodeMirror dialog). This definition
attribute conforms to the ROS Message Description
Specification. Messages allow components to
interact using Publishers
and Subscribers
, through a
non-blocking, one-to-many publish/subscribe interaction pattern.
Non-blocking means that when a component publishes a message, the
publish returns immediately, without waiting for any or all
subscribers to acknowledge that they have received the message.
Here is an example Message definition
:
int32 X=123
int32 Y=-123
string FOO="this is a constant"
string EXAMPLE="this is another constant"
The definition
is edited using the CodeEditor
visualizer, as
described in the beginning of this sample’s documentation. Since a
Message
has no other valid visualizers, when you double-click on a
message, it will automatically open into its definition to be
viewed/edited using the CodeEditor
visualizer.
Services
Services contain a definition
attribute (editable using a
CodeMirror dialog). This definition attribute
conforms to the ROS Service Description
Specification. Services allow components to
interact using Clients
and Servers
, through a blocking,
one-to-one client/server interaction pattern. Blocking means that
the component that issues the client call to the server must wait and
cannot execute other code until it receives the response from the
server.
Here is an example Service definition
:
#request constants
int8 FOO=1
int8 BAR=2
#request fields
int8 foobar
another_pkg/AnotherMessage msg
---
#response constants
uint32 SECRET=123456
#response fields
another_pkg/YetAnotherMessage val
CustomMessageDefinedInThisPackage value
uint32 an_integer
The definition
is edited using the CodeEditor
visualizer, as
described in the beginning of this sample’s documentation. Since a
Service
has no other valid visualizers, when you double-click on a
message, it will automatically open into its definition to be
viewed/edited using the CodeEditor
visualizer.
Components
Components are single threaded actors which communicate with other
components using the publish/subscribe and client/server interaction
patterns. These interactions trigger operations to fire in the
components, where the operation is a function implemented by the user
inside the operation
attribute of the relevant subscriber
or
server
. The component can also have timer operations which fire
either sporadically or periodically and similarly have an operation
attribute in which the user specifies the c++ code to be run when the
operation executes. These operations happen serially through the
operation queue and are not preemptable by other operations of that
component. Inside these operations, publisher
or client
objects
can be used to trigger operations on components which have associated
and connected servers
or subscribers
. These publisher
,
subscriber
, client
, server
, and timer
objects are added by the
user and defined inside the component.
Components contain forwards
, members
, definitions
,
initialization
, and destruction
attributes which provide an
interface for the user to add their own C++ code
to the component.
Each of those attributes is (as previously) editable using the
CodeEditor
visualizer.
-
Forwards
corresponds to code that comes before the class declaration in the generatedcomponent header file
, e.g#include <stdio.h>
or user-created structure or class definitions. -
Members
corresponds to private members and methods that the user wishes to add to the component in the generatedcomponent header file
. -
Definitions
corresponds to function definitions or other code that the user wishes to add to the generatedcomponent source file
. -
Initialization
corresponds to the code the user wishes to run when the component starts up to initialize members to specific values or begin the process of triggering other components in the system through publish or client interactions. -
Destruction
allows the user to specify destruction of any objects they have allocated on the heap that need to be manually destructed during component destruction.
Additionally, components contain User Configuration
and User
Artifacts
attributes, which are editable as JSON
code using the
CodeEditor
visualizer.
User Configuration
is a way of specifying the options passed to the component at run-time. The configuration object will be stored as a (possibly nested) dictionary within the component’s config, atconfig["User Configuration"]
. These data are used instead of command line arguments to allow the user to send complex/nested data structures as configuration to the component, allow multiple component (instance) within a process to be configured with different values for the same parameter, and to save developer time by automatically parsing these data into usable structures. For example, given the following config:
{
"logSensorData": true,
"logPeriod": 0.1,
"logFields":
{
"time": "float",
"data": "int"
},
"sensorOffsets":
[
0.1, 0.2, 0.3
]
}
The user could access the configuration
structure using the following c++
code anywhere within the
component:
bool logData = config["User Configuration"]["logSensorData"].asBool();
float period = config["User Configuration"]["logPeriod"].asFloat();
std::string firstField = config["User Configuration"]["logFields"]["time"].asString();
std::string secondField = config["User Configuration"]["logFields"]["data"].asString();
float firstOffset = config["User Configuration"]["sensorOffsets"][0].asFloat();
float secondOffset = config["User Configuration"]["sensorOffsets"][1].asFloat();
The documentation for the generated objects can be found at
jsoncpp,
which is the library used to parse the JSON
.
User Artifacts
is a way for the user to specify any files that may be produced by the component so that the experiment / plotting infrastructure can manage and version them. The attribute is specified as aJSON array
ofstrings
where each string is a filename of an output file.
Note that both the User Artifacts
and User Configuration
can be
overridden independently within any Component Instance
in a
Deployment
.
Inside the component, the user can define the interaction ports the
component supports (i.e. any publisher
, subscriber
, client
, and
server
objects), by dragging and dropping them into the component’s
canvas. The relevant Message
or Service
pointers for these
objects can be defined by either creating the port by dragging the
relevant Message
or Service
object from the Tree Browser
into
the canvas and selecting the appropriate port type from the pop-up
dialog or by dragging the Message
or Service
object onto the
relevant Pointer
of the already created port. Alternatively, the
Message
or Service
object can be dragged on to the port in the
canvas.
For Subscribers
, Servers
, and Timers
, you can edit the
Operation
which gets executed on behalf of the object by double
clicking on the object to open a CodeEditor
with the operation code.
Timers
Inside this aspect is where the user can specify the c++ code that
will execute upon the expiry of the relevant timer
, or when relevant
data is received for a subscriber
or server
. The attributes for
the ports and timers can be specified in this aspect as well. These
attributes include the period
of the timer
or the deadline
of
the subscriber operation, for instance.
Libraries
Also inside this aspect is where the user can select the Set Editor
visualizer, which allows the user to see or configure the set of
Libraries that the component requires for
compilation/execution. The user can drag a Source Library
or System
Library
from the Tree Browser
to into the Libraries
Set Editor to
add the library as a requirement for the component.
Constraints
The user can drag in constraints
from the Part Browser
and name
them accordingly to specify that the component must be deployed onto a
Host
which has a Capability
with a name that matches the
constraint’s name.
Example Receiver Component
This component acts as an example for an event-triggered component,
which only executes when it receives the appropriate Message
subscription operation or Service
request. The c++ code which
executes when these operations are triggered can be found in the
Operation
of the respective subscriber
and server
.
Systems
The Systems
folder provides a place to group the different System
definitions on which you might like to run the software defined in the
Software
model. When compiling the software using the
SoftwareGenerator
plugin from the project’s root aspect, the
different system models are used to determine for which computer
architectures
to compile the software.
System
In a System
model, you define Hosts
, Users
, Networks
, and
Links
:
Users
The Users
which have an associated SSH Key location and
Directory. The user’s Directory is where any processes
started during experiment deployment or compilation on behalf of the
user will be run and where any artifacts will be generated.
Networks
A Network defines the Subnet of IP addresses available as well as the Netmask which, together with the Subnet defines the range of available IP addresses.
Links
Links connect a hosts Interface
to a Network
and has an
associated IP address that the interface will have on that
network. The user can create links and assign the IP addresses by
clicking and dragging from the host’s interface to a network. The IP
address is displayed on the link and can be edited by clicking on the
Link and editing its IP property in the Property Panel
.
Hosts
The Hosts
which are computers with a specified OS,
Architecture, Device ID, and Device ID Command. The
Device ID allows the user to delineate between two different
devices (e.g. a BeagleBone Black and an NVIDIA Jetson TK1) which may
have the same Architecture (e.g. armv7l
), but may need to be
separated for binary/library incompatibility. Because these devices
may have different subsystems, we allow the user to specify the
Device ID Command which is run on the host and should return a
string containing the specified Device ID. Hosts can have any
number of Interface
children, which are displayed as ports
on
the Host
.
Hosts can have two types of children: Interfaces
and Capabilities
.
The attributes of Hosts
are described in the section regarding
System
models.
Interfaces
Interfaces are used to specify the network interfaces available on the
host. Each interface only has a name, e.g. eth0
, specifying the
network interface as it would be seen on the actual hardware, for
instance by running ifconfig
in linux.
Capabilities
Capabilities are the corresponding object to Constraints
, which are
contained by Components
. A Capability
corresponds to some
provision that the Host
has that the software may need, for instance
hasCamera
, or hasGPIO
. Components which have the correspondingly
named Constraint
can only be mapped during deployment/execution to
hosts which have Capabilities
of the same name. Note that
Capabilities
need not be unique within or between hosts.
Users
Hosts have a Set of Users
which specify the users which have valid
credentials on this host. The set of users is viewed and edited by
selecting the SetEditor
visualizer from within a Host. You
specify that a user can access a host by dragging the user object
(which exists in the parent system model) from the tree browser into
the Users
SetEditor of the relevant host.
Deployments
Deployments are an abstract grouping of Instances of Components
into processes (which are called, using ROS terminology, Nodes
), and
further grouping processes into Containers
, which are an abstract
definition of Hosts
. The actual mapping from Containers
to
Hosts
actually happens automatically in an Experiment
.
Deployment
A Deployment can have any number of Containers
, which contain Node
processes and will execute on separate hardware from each other. In
this way, a Container
is an abstract notion of a Host
.
Container
A container can contain any number of Nodes
(POSIX processes).
These nodes will all run in parallel on the Host
to which this
Container
is mapped during an Experiment
.
Node
A node can contain any number of Instances of Components
defined
in the Software
model. A node also has a Priority attribute
which specifies the linux scheduling priority of the node’s process,
respectively.
NOTE:: To create a
Component
Instance, you never drag from thePart Browser
, but must instead drag and drop aComponent
from this project’sSoftware
model into the canvas and selectCreate Instance Here
.
By creating Component Instances
in this way, the user can go into a
Component Instance and change attributes, e.g. a Timer
’s Period
or Priority, which will not require a code re-compilation.
Further, any changes made to the original component in the software
model will automatically propagate to any Component Instances
that
have been created from it. Any changes made to the instance will
override the original component’s properties, but will not affect
the original component or any other component instances. To make such
changes to timer or other port properties, simply double click on the
newly created instance and select the relevant port, after which
you may edit its attributes. Once an attribute has been over-ridden,
any changes made to the original port attribute or component attribute
will not reflect in the instance.
Similarly, the component instance’s User Configuration
attribute can
be over-ridden here to provide instance-specific configuration
parameters, which are more powerful, complex configuration data than
command line arguments (see the Component
documentation in the
Software
).
Finally, each Component Instance
corresponds to a separate thread of
the Node
’s process.
Experiments
Experiments are deployable instances of software mapped to hardware.
An experiment contains a pointer to a Deployment
and a pointer to a
System
. In this way, the same conceptual grouping of component
instances into processes and containers can be easily redeployed onto
a different system by just swapping the experiment’s System
pointer.
The experiment will automatically ensure that the system’s
capabilities satisfy the deployment’s software’s constraints.
Experiment
An experiment maps a Deployment
’s abstract grouping of component
instances into processes and finally into Containers
to Hosts
from
the selected System
.
RunExperiment Plugin
The mapping and experiment deployment is performed automatically by
the RunExperiment
plugin based on which Hosts
are reachable and
not busy (i.e. not running any other experiment or compilation
processes) from the selected system, and also ensures that the hosts
satisfy all of the constraints from all of the component instances in
a container’s nodes. If there are not enough hosts for the number of
containers or if the constraints of the software cannot be satisfied
by the available hosts, the RunExperiment
plugin informs the user,
else the plugin finishes the deployment and saves the mapping into the
model for reference and showing to the user.
StopExperiment Plugin
If an experiment is currently running (based on the existence of model
objects corresponding to the mapping of containers to hosts), the
StopExperiment
plugin will stop all the associated experiment
processes and copy back all the components’ generated logs. The logs
are saved onto the server file system and their contents are also
copied into attributes of an automatically created Results
object,
whose name will be the current time at which the experiment finished.
If the user opens the Results
object and selects the ResultsViz
visualizer, any tracing logs that were recovered will be automatically
plotted in the canvas.