Skip to content

Add a ROS 2 Interface#

This page gives the procedure to add a message, a service or a topic to the ROS 2 side of ACRES. One interface has parts in five locations: the definition, the game, ACRES Core, the bridge and the tests.

Parts of an Interface#

A service request goes through these parts. A new interface needs a change in each part that it uses.

Part Location Function
Definition ROS/acres_interfaces/msg, ROS/acres_interfaces/srv The .msg or .srv file.
Episode-log schema Acres/Source/Acres/AcresEpisodeSchemas.h The definition text that the simulator writes into an episode log. A tool generates this file.
Operation of the game Acres/Source/Acres/AcresSimControl.cpp The operation of the simulator control channel that does the work.
Operation of ACRES Core ROS/acres_core_sim/src/CoreSimServer.cpp The same operation for the Core server.
Bridge ROS/acres_sim/src/sim_control.cpp, ROS/acres_sim/src/vehicle_bridge.cpp The ROS 2 service or topic. It changes the ROS 2 message into JSON and back.
Tests ROS/acres_sim/test, ROS/acres_core_sim/test A substitute for the game and the integration tests.

The bridge contains no simulation logic. It only changes the format of the data. Put the logic into the game and into ACRES Core, behind one operation of the control channel.

Use the service /acres/field_query as a model. The steps below show its code in each part.

WARNING

Do all ROS 2 commands in a shell that has the DDS loopback fence (source ROS/Env/setup_env.sh).

Before You Start#

Use an interface of a standard package when one exists. The bridge uses simulation_interfaces for the simulation state, the steps, the resets and the entities. Add a type to acres_interfaces only for data that the standard packages do not have.

Add the Definition#

  1. Write the .msg or .srv file in ROS/acres_interfaces/msg or ROS/acres_interfaces/srv.

    # Ground truth at one point (acres/field_query).
    string frame_id                   # "world" (grid ENU m, default) or "utm" (UTM 16N easting, northing)
    float64 x
    float64 y
    ---
    simulation_interfaces/Result result
    uint16 field                      # field id (0 outside fields)
    float64 ground_z                  # ground height, world m
    
  2. Obey these rules in the file.

    Rule Reason
    Start with a comment that describes the type. The page Messages shows this comment.
    Put a comment after each field. The same page shows it as the description of the field.
    Give the unit in the field name: _m, _mps, _deg, _rad, _s, _n, _kg, _m2. The generator of the page reads the unit from the name.
    Make simulation_interfaces/Result result the first field of a service response. All services of the bridge report errors in this field.
    Use the frame world for poses, and accept utm when a client has UTM coordinates. The control channel uses these two frames.
  3. Add the file to the list in ROS/acres_interfaces/CMakeLists.txt.

    rosidl_generate_interfaces(${PROJECT_NAME}
      ...
      "srv/FieldQuery.srv"
      DEPENDENCIES builtin_interfaces std_msgs geometry_msgs simulation_interfaces
    )
    
  4. Build the workspace and show the new type.

    source ROS/Env/setup_env.sh
    cd ROS
    colcon build
    cd ..
    source ROS/Env/setup_env.sh
    ros2 interface show acres_interfaces/srv/FieldQuery
    

    Expected Result

    The command prints the definition, with the fields of simulation_interfaces/Result below the field result.

Add a Message to the Episode Log#

Do this section only when the simulator writes the new message into the Episode Log. The game and ACRES Core write the log without ROS 2. They thus contain the definition text and an encoder for each message.

  1. Add the name of the message to the list TOP in Tools/EpisodeLog/gen_schemas.py.

  2. Generate the schema header again.

    python Tools/EpisodeLog/gen_schemas.py
    

    Expected Result

    The tool writes Acres/Source/Acres/AcresEpisodeSchemas.h. Do not edit this file.

  3. Add the message to the episode writer in Acres/Source/Acres/AcresEpisodeLog.h and AcresEpisodeLog.cpp.

    • A structure for the message, in the sequence of the fields of the .msg file.
    • An Encode function that writes the fields in the CDR format.
    • A channel name in FEpisodeWriter (for example /sim/shifts) and a Write function.

    ACRES Core uses the same writer through Core/Source/AcresCoreEpisodeLog.cpp.

  4. Run the tests of the episode log.

    Tools/EpisodeLog/build.sh
    

    Expected Result

    schemas up to date
    ...
    19 passed, 0 failed
    ROS 2 read 3029 messages: {'/sim/episode': 1, '/sim/agents': 1, '/sim/agent_states': 3000, '/sim/conditions': 25, '/sim/farm_stamps': 1, '/sim/farm_events': 1}
    ROS 2 check: PASS
    

    The first line is the result of gen_schemas.py --check. The last two lines come only in a shell with the ROS 2 environment.

    CAUTION

    A change of the fields of a logged message changes the file format. Old episode logs then need the old definition. Add fields at the end of a message when this is possible.

Add the Operation to the Simulators#

A service of the bridge calls one operation of the simulator control channel. The operation has a name, JSON fields and a result code.

  1. In the game, add the operation to FAcresSimControl::HandleLine in AcresSimControl.cpp and declare the function in AcresSimControl.h.

    else if (Op == TEXT("field_query"))
        Result = OpFieldQuery(*Req, Out, Error);
    

    The function fills the reply fields in Out and returns a result code of simulation_interfaces/Result: 1 = OK, 2 = not found, 3 = incorrect state, 4 = failed. Put the cause into Error.

  2. In the Core server, add the same operation to FCoreSimServer::HandleOp in ROS/acres_core_sim/src/CoreSimServer.cpp.

    if (Op == "field_query")
    {
        Result = OpFieldQuery(Req, Out, Error);
        return Out;
    }
    

    When ACRES Core cannot do the operation, return the code 0 and a text, as the operation screenshot does.

  3. Use the same field names and units in the two simulators. A client must not see a difference.

All operations of the game run on the game thread. Do not block in an operation.

Add the Interface to the Bridge#

  1. For a service, add a server to SimControl::create_interfaces in ROS/acres_sim/src/sim_control.cpp. Include the header of the type in sim_control.hpp.

    add(n.create_service<ai::FieldQuery>(
        "/acres/field_query",
        [this, timeout](const std::shared_ptr<ai::FieldQuery::Request> req,
                        std::shared_ptr<ai::FieldQuery::Response> res)
        {
            const std::string frame = req->frame_id.empty() ? "world" : req->frame_id;
            std::string error;
            const auto reply = request(
                Json{{"op", "field_query"}, {"x", req->x}, {"y", req->y}, {"frame", frame}}, timeout, &error);
            res->result = result_from_reply(reply, error);
            if (!reply) return;
            res->field = static_cast<uint16_t>(get_int(*reply, "field"));
            res->ground_z = get_number(*reply, "ground_z");
        },
        qos, control_group_));
    
    Function Use
    request() Sends one request and waits for the reply. It returns no value when the bridge has no connection to the simulator or gets no answer.
    result_from_reply() Makes the Result field from the reply, or the code 4 with the cause when there is no reply.
    get_int(), get_number(), get_truthy(), text() Read a JSON field. A field that is absent gives zero or an empty text.
    control_group_ The callback group for all services that do not step the simulation.
  2. For a topic of a vehicle, add the publisher or the subscription to VehicleBridge::setup in ROS/acres_sim/src/vehicle_bridge.cpp.

    • Use vehicle_qos() and a relative topic name, so that the namespace of the vehicle applies.
    • For a command, use the function subscribe<T>() of the class. In lockstep, the bridge then sends the command before the next step.
    • Put the conversion into a function without side effects in conversions.cpp, and add a unit test in test/test_conversions.cpp.
    • Use frame names with frames_.prefix, so that a session with more than one vehicle has different frames.
  3. Build the workspace again.

    cd ROS
    colcon build
    cd ..
    

Add Tests#

The tests of acres_sim do not start the game. The file ROS/acres_sim/test/fake_game.py is a substitute that uses the same three sockets.

  1. Add the operation to FakeGame.handle in fake_game.py. Return constant values.

    if op == "field_query":
        return {"field": 12, "surface": "soil", "ground_z": 188.25, "theta": 0.27}
    
  2. Add the absolute name of the new service or topic to the list ROOT_NAMES in ROS/acres_sim/test/harness.py. Each test bridge runs in a namespace of its own. The list tells the test which absolute names to move into this namespace.

  3. Add assertions to test_control_services_and_action in ROS/acres_sim/test/test_integration.py.

    fq = FieldQuery.Request()
    fq.frame_id, fq.x, fq.y = "utm", 500100.0, 4480200.0
    q = ros.call(FieldQuery, p + "/acres/field_query", fq)
    assert (q.field, q.surface) == (12, "soil")
    assert game.requests[-1]["frame"] == "utm"
    

    Examine the two directions: the fields of the response, and the JSON request that the substitute received.

  4. Add the same call to test_services_and_action in ROS/acres_core_sim/test/test_integration.py. This test uses ACRES Core as the simulator.

  5. Run the tests.

    cd ROS
    colcon test --packages-select acres_sim acres_core_sim
    colcon test-result --verbose
    cd ..
    

    Expected Result

    The summary shows 0 errors and 0 failures.

  6. For a change of the Polaris topics, compare a bag of the simulator with a bag of the real vehicle.

    python ROS/Tools/check_topic_parity.py --sim /data/sim_bag --vehicle /data/bags/dbw_direct_test_01
    python ROS/Tools/check_msg_defs.py /data/sim_bag --msgs ROS/vendor/ds_dbw_msgs --limit 200
    

    Expected Result

    The first tool shows no DIFF line for the topic. The second tool shows MATCH.

    Use check_msg_defs.py also when you change the release of the package ds_dbw_msgs in ROS/vendor.

Update the Documentation#

  1. Build the site. The build generates the page Messages from the .msg and .srv files.

  2. Add the new name to the correct reference page.

    Interface Page
    A topic Topics
    A service or an action Services and Actions
    An operation of the control channel Simulator Control Channel
    A channel of the episode log Episode Log
    A node, a parameter or a launch argument Nodes and Launch Files
  3. Run the coverage check. It fails when a .msg or .srv name of acres_interfaces is not on the pages Topics or Services and Actions.

    python Documentation/Tools/check_docs.py --coverage
    

    Expected Result

    The tool prints coverage: done and no error line.

Write Documentation gives the other checks of the site.

Checklist#

Item Check
The .msg or .srv file has a comment for the type and for each field. ros2 interface show
The schema header is up to date. python Tools/EpisodeLog/gen_schemas.py --check
The game and the Core server answer the operation with the same fields. The two integration tests.
The substitute for the game answers the operation. ROS/acres_sim/test/fake_game.py
The new absolute name is in ROOT_NAMES. ROS/acres_sim/test/harness.py
The reference pages contain the new name. python Documentation/Tools/check_docs.py --coverage