495. Use the Message Bridge
495.0.1 Task overview
For an overview of the Foretify Message Bridge, see the Concepts topic. To use the Foretify Message Bridge in your project, do the following:
-
Build a test bridge.
-
Inspect the files.
-
Create a custom bridge.
495.0.2 Building a test bridge
Foretify includes testing for the Message Bridge for all 3 protocols. If you build this bridge, you can inspect the generated files to get a sense of the overall process.
This example is for the ROS bridge, but similar steps apply to the others:
-
Install Foretify, python3 and the System (e.g. ROS Melodic).
-
Source the System setup file, for example:
Shell command: set up environment$ source /opt/ros/melodic/setup.bash -
Compile the test package.
Shell commands: compile the test package$ cd $FTX/tool/message_bridge/test/ros/test_ws $ catkin_make -
Build the bridge:
Shell commands: build the bridge$ cd $FTX/tool/message_bridge $ python3 build_bridge.py config/ros_test.jsonIf you are using a release version of Foretify, 'python3' is not required, just: 'build_bridge config/ros_test.json'.
-
Run the test:
Shell command: invoke Foretify to run the test$ foretify --load $FTX/message_bridge/test/ros/message_bridge_test.osc --run
The test performs a full end-to-end message round-trip even though there is no SUT present: 1. An event with a test message is emitted in the test scenario 2. The message is serialized and sent from Foretify to the DSP 3. The DSP deserializes the message and publishes it in the System 4. The DSP also subscribes to the same topic it publishes on, so it receives the same message 5. The DSP serializes and passes the message back to Foretify 6. Foretify-side code deserializes the message and emits an event with its contents 7. The scenario compares the received event's content to the original emitted message
495.0.3 Inspecting the source files
The important source files are:
-
The config file
-
The message definitions
495.0.3.1 The config file
The input config file schema is as follows. Optional fields are in [square brackets]:
{
"protocol": <string>
["vehicleKind"]: <string>
["codeDestinations"]: {
["foretifyBridge"]: <string>,
["dsp"]: <string>,
["dspSh"]: <string>,
["dspConfig"]: <string>,
["libName"]: <string>,
["copyChangingFilesOnly"]: <boolean> # DDS only
},
"messages": [
{
"direction": <string>
["type"]: <string>
"definition": <string>
"address": <string>
["header"]: <string>
["package"]: <string>
["messageName"]: <string> # DDS only
["struct"]: <string>
["event"]: <string>
["generatable"]: <boolean>
["initialPublishDelayMs"]: <int>
},
{
...
}
]
}
"protocol"- Is “ros”, "ros2" or "dds".
<vehicleKind>- Is “sut” (default) or “traffic” (for all other actors). This indicates which vehicle's simulation actor ID is sent to the DSP at launch time. The actor IDs can then be used as part of the topic addresses (see <address>).
<foretifyBridge>- Is the destination for the Foretify-side code that is generated. This defaults to "generated/foretify_
_bridge". <dsp>- Is the destination for the DSP-side code that is generated. This defaults to "generated/
_dsp". The last part of the path is also used as the name of the ROS package that is built. <dspSh>- Is the destination for the DSP launch shell script that is generated. This defaults to "
_dsp.sh". <dspConfig>- Is the destination for the DSP config OSC2 file that is generated. This defaults to "
_dsp_config". <libName>- Is the Foretify-side library name that is put into $FTX/lib. This defaults to "libmessage_bridge.so".
<copyChangingFilesOnly>- If true indicates that only the message_bridge_events.osc, message_bridge_handlers.osc, message_bridge.cpp, and the DSP's message_handler.h and message_handler.cpp are copied to their destinations.
<direction>- Is “up” or “down”. Up indicates from the SUT to Foretify, and down is from Foretify to the SUT.
<type>- Is “topic”.
<definition>-
Is the pathname of a message specification, for example:
JSON code: name: value pair"definition": "$FTX/ros/ws/my_node/msg/MyMessage.msg" <address>-
Is the System topic or service endpoint address. If the address depends on a field of the message, such as an actor id, enclose the variable in angle brackets. For example the following indicates a path dependent on the message field my_id:
JSON code: name: value pair"address": "/ftx/<my_id>/my_topic_name" <header>- Is the path to the message C++ header file. The default header is determined from the definition. For example, the default header for the <definition> above is "$FTX/ros/ws/devel/include/my_node/MyMessage.h".
<package>- Is the name of the package (the node). The default name is the package name of the message specification. For example, the default package for the <definition> above is "my_node".
<messageName>- Is the name of the message in the DDS .idl file to use. Unlike ROS, DDS .idl files can contain any number of struct definitions. This is the name of the message to send/receive.
<struct>- Is the name of the OSC2 message struct to generate. The default name is based on the message name. For example, the default struct for the <definition> above is "my_message_msg".
<event>- Is the name of the OSC2 message-related event to generate. The default name is based on the message name. For example, the default event for the <definition> above is "my_message".
<generatable>- Indicates whether the fields of the OSC2 struct that is created should be generatable (without the 'var' keyword). Default is false (with the 'var' keyword).
<initialPublishDelayMs>-
Specifies a millisecond delay before sending the first message. For example, if the DSP is called every 20ms, defining the following delay postpones publishing the first message until the second DSP call.
JSON code: name: value pair["initialPublishDelayMs"]: 40
Examples
There are example configs in $FTX/tool/message_bridge/config/. Here is the config file for the test bridge.
{
"protocol": "ros",
"vehicleKind": "sut",
"messages": [
{
"direction": "down",
"type": "topic",
"definition": "$FTX_TOOL/message_bridge/test/ros/test_ws/src/test_msgs/msg/TypesTest.msg",
"address": "/ftx/<string_f>/types_test",
"initialPublishDelayMs": 40
},
{
"direction": "up",
"type": "topic",
"definition": "$FTX_TOOL/message_bridge/test/ros/test_ws/src/test_msgs/msg/TypesTest.msg",
"address": "/ftx/<string_f>/types_test",
"event": "up_event"
}
]
}
495.0.3.2 The message files
ROS1 message files contain one or more lines in the following format for simple types:
type name
or the following for arrays (lists):
type[] name
Examples
Here are the message definitions for the ROS1 test bridge:
# TypesTest.msg
Header header
# supported types
string string_f
float32 float32_f
float64 float64_f
int32 int32_f
int64 int64_f
uint32 uint32_f
uint64 uint64_f
bool bool_f
time time_f
geometry_msgs/Point point_f
# array examples
string[] string_list
time[] time_list
geometry_msgs/Point[] point_list
# local type example
NestedType nested_f
# array of nested
NestedType[] nested_list
# std_msgs example
std_msgs/String std_string
# other package example
geometry_msgs/Vector3 vector3_f
# NestedType.msg
Header header
string some_field
495.0.4 Inspecting the generated files
The generated files for the test bridge are in $FTX_TOOL/message_bridge/generated/.
On the Foretify side, the Bridge generates these files:
- message_bridge_events.osc,
- message_bridge_handlers.osc,
- message_bridge_top.osc
- message_bridge.cpp
- Makefile
The .osc files contain message struct definitions and events that can be emitted in scenarios to send messages down, or acted on when messages arrive up. message_bridge_top.osc should be included in your scenario. message_bridge.cpp contains the serialization/deserialization code for the messages, which is built into a .so file with the Makefile. Inspect the files to understand the available functionality.
On the DSP side, the Bridge generates an entire skeleton C++ DSP that acts as a node in the pub-sub System, publishing and subscribing to the specified message topics (services will also be supported).
495.0.5 Creating a custom bridge
To create a custom bridge:
-
For ROS/ROS2, create a workspace.
For instructions on how to create a workspace, see http://wiki.ros.org/.
-
Create the messages and config files.
You can copy the test messages and config files into the proper location in your workspace. Then modify or expand them as necessary to implement the required functionality.
-
Build the bridge.
This is the same process as described in Building a test bridge, except that you compile the files in your own workspace and use the config file that you created.
-
Define the OSC2 scenarios that use the bridge.
This task is described below.
495.0.5.1 Defining the OSC2 scenarios
The DSP comes with a launch bash script dsp.sh and a dsp_config.osc. The latter must be included in any scenario that uses the bridge. Foretify launches the DSP script and passes to it the list of simulator actor IDs for the vehicles in the scenario. Only IDs of vehicles that correspond to "vehicleKind" are sent. These IDs allow publishing a message type to different topic addresses, based on the specified field in the message.
You must also include the generated message_bridge_top.osc in the scenario.
Let’s assume that the following messages and config file are defined:
# requestPosition.msg
Header header
string vehicle_id
uint request_type
# replyPosition.msg
string vehicle_id
geometry_msgs/Point curr_position
# config file
{
"protocol": "ros",
"vehicleKind": "traffic",
"messages": [
{
"direction": "down",
"type": "topic",
"definition": "$FTX/env/project_ws/src/msgs/msg/requestPosition.msg",
"address": "/ftx/<vehicle_id>/requestPosition
},
{
"direction": "up",
"type": "topic",
"definition": "$FTX/env/project_ws/src/msgs/msg/replyPosition.msg",
"address": "/ftx/<vehicle_id>/replyPosition",
"event": "replyPosition_event"
}
]
}
The example uses the messages defined above. The message_bridge_top.osc imports the message_bridge_events.osc and message_bridge_handlers.osc files.
import $FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc
import $FTX_TOOL/message_bridge/generated/foretify_ros_bridge/dsp_config.osc
import $FTX_TOOL/message_bridge/generated/foretify_ros_bridge/message_bridge_top.osc
scenario get_position:
car1: car
msg: request_position_msg with (
vehicle_id: "$(car1.sim_id)",
request_type: 1)
on @sut.reply_position => e:
log_info("$(car1.sim_id) is at this location:
$(e.msg.curr_position)")
do serial:
emit sut.request_position(msg: msg)
car1.drive(duration: 3s)