Ohmnilabs AMR API

This documentation introduces and explains some basic APIs of Ohmnilabs AMR. These APIs are exposed as ROS service calls and corresponds to the buttons on the Debug UI pages.

All the services should be called within the docker container, after sourcing the ROS bash script: source /opt/ros/melodic/setup.bash

Contents

  1. Mapping
    1.1. Start Mapping
    1.2. Finish Mapping
    1.3. Restart Mapping
    1.4. Cancel Mapping
    1.5. Delete Map
    1.6. Delete All Maps

  2. Navigation
    2.1. Load Map
    2.2. Relocalization
    2.3. Set Moving Speed
    2.4. Set Move_Base Goal
    2.5. Keyboard Teleop
    2.6. Set View Mode
    2.7. Toggle Global Costmap Visibility

  3. Path Following
    3.1. Path Recording
    3.2. Path Following

Mapping

Start Mapping

  • Description
    • This service is used to start a mapping session. The request of the service only has 1 field, a data string, which will be the name of the map.
    • If the data string is empty, map name will follow Map-<LocalDateTime> format
    • This service will take about 1-2 seconds to kick start all the background processes that handle the mapping, so please wait until the service returns a successful response before proceeding with other command.
    • The very first mapping session after booting up (or updating) might take a little bit longer (3-5s)
  • Service name: /tb_node/debug/start_mapping
  • Service type: tb_msgs/srv_string
    • Request:
      • string data
    • Response:
      • bool success - whether the service call is successful or not
      • string message - verbose response (e.g. error message if any)
  • Example - to start a mapping session for a map named test-map
    • Run rosservice call /tb_node/debug/start_mapping "data: 'test-map'"
    • You will get the response:
      • success: True
      • message: "Preparation done - Mapping can start now"
    • Afterward, you can use move_base or keyboard_twist_teleop to control the robot.

Finish Mapping

  • Description
    • This service has to be called at the end of a mapping session, so that the map can be properly processed and saved. The whole sequence might take about 2-3s to be completed.
    • Since the map names are not necessarily unique, we use GUID for each map.
    • After the service is called, and all the background processes has been completed, the service will returns a 128-bit GUID of the map. You might need to store these GUID somewhere for later usage (e.g. loading or deleting maps)
    • The map data is stored in 3 separated files, you can acess them (within the docker container) in these directories:
      • Compressed pgm file (image of static map): /app/maps/map-<guid>.pgmz
      • Map metadata file: /app/maps/map-<guid>.yaml
      • Recorded path during mapping: /app/paths/path-<guid>.json
      • Where <guid> is the 128-bit ID of the map, which is in the following format: xxxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    • After the service call has completed, the robot will automatically switch to navigation mode, with the map loaded and the robot localized, so you do not have to call load_map service for the map you have just recorded.
  • Service name: /tb_node/debug/done_mapping
  • Service type: std_srvs/Trigger
    • Request: None
    • Response:
      • bool success - whether the service all is successful or not
      • string message - verbose response
  • Example - to be called at the end of a mapping session
    • Run rosservice call /tb_node/debug/done_mapping
    • You will get a response:
      • success: True
      • message: "1b8a77ee-fc19-405c-b2bd-b7f3c441a268"

Restart Mapping

  • Description
    • This service is normally used when there are mistake during the mapping process, and the user wants to quickly restart (but keep the map name and subsequent ID)
    • This process is faster than having to call 2 successive services to cancel the current session and start a new one.
    • The whole process will take about 2-3s to clean up stale data and initialize a new map
  • Service name: /tb_node/debug/restart_mapping
  • Service type: std_srvs/Trigger
    • Request: None
    • Response:
      • bool success - whether the service all is successful or not
      • string message - verbose response
  • Example - to be called during a mapping session
    • Run rosservice call /tb_node/debug/restart_mapping
    • You will get a response:
      • success: True
      • message: "Clean up & restart done - Mapping can start now"

Cancel Mapping

  • Description
    • A service which is used to stop a mapping session.
    • The current map and its metadata will not be saved since it is cancelled
  • Service name: /tb_node/debug/cancel_mapping
  • Service type: std_srvs/Trigger
    • Request: None
    • Response:
      • bool success - whether the service all is successful or not
      • string message - verbose response
  • Example
    • Run rosservice call /tb_node/debug/cancel_mapping
    • You will get a response:
      • success: True
      • message: "Mapping has been cancelled"

Delete Map

  • Description
    • This service is used to delete a specific map. Refer to the next section if you want to delete all maps.
    • As mentioned earlier, you will need the GUID of the map to delete it.
  • Service name: /tb_node/debug/delete_map
  • Service type: tb_msgs/srv_string
    • Request: data: <map-guid>
    • Response:
      • bool success - whether the service all is successful or not
      • string message - verbose response
  • Example
    • Run rosservice call /tb_node/debug/delete_map "data: '1b8a77ee-fc19-405c-b2bd-b7f3c441a268'"
    • You will get the response:
      • success: True
      • message: "Map deleted"

Delete All Maps

  • There is no service to handle this. However, you can easily achieve this by simply deleting every files within /app/maps and /app/paths directories.
  • Note: This is irreversable - please take measures to backup if needed


Navigation

Load Map

  • Description
    • After booting up (or after updating), there is no map loaded by default! So at that time, the robot is in an unknown state. You will have to start a new mapping session, or load an old map for the stack to start working properly.
    • This is a service used to loaded a previously recorded map.
    • The request of the service call take the GUID of the map instead of the name, so please make sure to have the GUID handy.
    • The process will take about a second to complete.
    • NOTE: After loading a map, the robot most likely will lose its localization (i.e. it will not know where it is in the newly loaded map). So you will have to help the robot ‘relocalize’.
    • Please refer to the next item on how to do the relocalization.
  • Service name: /tb_node/debug/load_map
  • Service type: tb_msgs/srv_string
    • Request: string data where data should be the map-guid
    • Response:
      • bool success - whether the service all is successful or not
      • string message - verbose response
  • Example
    • Run rosservice call /tb_node/debug/load_map "data: '383998c4-70e9-4113-81f3-d4f13e69e616'"
    • You will receive the response:
      • success: True
      • message: "Load map done"
    • If the GUID is invalid, it will return False

Relocalization

  • Description
    • Relocalization is a process of letting the robot know where it is on the map, approximately.
    • It is a necessary process in order for the robot to navigate correctly and accident-free.
    • We do not need to do relocalization when:
      • Starting a new mapping session because the robot will know that it is at the origin of the new map
      • Finishing a mapping session, because it knows exactly where it is based on odometry, lidar data and the newly constructed map
    • However, we have to do relocalization if we load a previously recorded map, since the robot does not know where it is within this particular map.
    • There are 2 options:
      • Option 1 (Much easier) - You need to remember the initial position of the robot (when the mapping session of this particular map started). For example, if you started mapping from the door of the room, please place the bot there again with an approximately correct orientation. Then start the relocalization action with position (0, 0, 0)
      • Option 2 - You need to know the ground truth, i.e. the approximately correct position (x,y,z) of the robot on the map at that moment, then start the relocalization process with position (x,y,z).
      • I would recommend using option 1.
    • Relocalization process use ROS action protocol, instead of ROS service
    • It is a bit different compared to other APIs, as it is a ROS action instead of ROS service, i.e. the request (goal) and response (result) are separated into 2 different topics. There is also other associate topics like feedback, status, and cancel as well.
    • One big difference between action and service is that action call is not blocking!
  • Goal topic name: /ohmni_amr_localization/relocalization/goal
  • Goal topic type: luv_mission_control_msgs/RelocalizeActionGoal
    • You can view the full format of the message by running: rosmsg show luv_mission_control_msgs/RelocalizeActionGoal
    • There are a many data fields in the message, but you only need to pay attention to 3 fields:
      • frame_id in goal/pose/header/frame_id. It should always be map.
      • position in goal/pose/pose/pose/position. If you use option 1, leave it at {x: 0.0, y: 0.0, z: 0.0}
      • orientation in goal/pose/pose/pose/orientation. If you use option 1, you only need to change w value to 1.0 for the quaternion to be valid.
  • Result topic name: /ohmni_amr_localization/relocalization/result
  • Result topic type: luv_mission_control_msgs/RelocalizeResultGoal
    • Similarly, you can check the format by running rosmsg show luv_mission_control_msgs/RelocalizeActionResult
    • You only need to check if the action is successful or not:
      • bool isSuccess
  • Example - Relocalize the robot using option 1
    • Sending the goal
root@localhost:/app/maps# rostopic pub -1 /ohmni_amr_localization/relocalization/goal luv_mission_control_msgs/RelocalizeActionGoal "header:
  seq: 0
  stamp:
    secs: 0
    nsecs: 0
  frame_id: ''
goal_id:
  stamp:
    secs: 0
    nsecs: 0
  id: ''
goal:
  pose:
    header:
      seq: 0
      stamp:
        secs: 0
        nsecs: 0
      frame_id: 'map'
    pose:
      pose:
        position: {x: 0.0, y: 0.0, z: 0.0}
        orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
      covariance: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]"
- **Receiving the result**
root@localhost:/# rostopic echo /ohmni_amr_localization/relocalization/result 
header: 
seq: 5
stamp: 
  secs: 1676884280
  nsecs: 890267531
frame_id: ''
status: 
goal_id: 
  stamp: 
    secs: 1676884280
    nsecs:  26257562
  id: "/ohmni_amr_localization-11-1676884280.26257562"
status: 3
text: ''
result: 
state: 0
isSuccess: True
pose: 
  header: 
    seq: 0
    stamp: 
      secs: 0
      nsecs:         0
    frame_id: ''
  pose: 
    pose: 
      position: 
        x: 0.0
        y: 0.0
        z: 0.0
      orientation: 
        x: 0.0
        y: 0.0
        z: -0.000379007970308
        w: 0.999999928176
    covariance: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.025187496054571e-05]
message: "Action succeeded"

Set Moving Speed

  • Description
    • This is a service to change the robot moving speed (by using move_base, or by using the arrow keys on the Debug page)
    • It will not affect the speed of teleop_twist_keyboard if you decided to install that package
  • Service name: /tb_node/debug/set_speed
  • Service type: tb_msgs/srv_float
    • Request: float data where data is the absolute value of linear moving speed.
    • Response:
      • bool success - whether the service all is successful or not
      • string message - verbose response
  • Example - Set moving speed to 0.25m/s
    • Run rosservice call /tb_node/debug/set_speed "data: 0.25"
    • You will get the response:
      • success: True
      • message: "Speed is changed to 0.25 m/s"

Set move_base Goal

  • Description
    • Ohmni AMR robot uses move_base to navigate the environment (in both mapping and navigation mode)
    • move_base is an open-sourced package for ROS. Ohmnilabs has heavily modified it in our stack, but the structure remains largely unchanged.
    • You can read more about the package here.
    • move_base can be called via ROS action protocol.
  • Goal topic name: /move_base/goal
  • Goal topic type: move_base_msgs/MoveBaseActionGoal
    • The format of this message is long, with a lot of data fields. You can check the entire format by running rosmsg show move_base_msgs/MoveBaseActionGoal
    • However, you only need to pay attention to 3 of them:
      • frame_id in goal/target_pose/header/frame_id. It should always be map
      • position in goal/target_pose/pose/position. It contains the 3D coordinate of the goal
      • orientation in goal/target_pose/pose/orientation. It contains the orientation of the goal in quaternion format.
    • You do not need to fill in other data fields, as they have their built-in default value.
  • Result topic name: /move_base/result
  • Result topic type: move_base_msgs/MoveBaseActionResult
    • Similarly, you can check the full format of the message by running rosmsg show move_base_msgs/MoveBaseActionResult
    • You only need to pay attention to 3 fields:
      • bool is_success - whether the call is successful (if the robot reached the goal or not)
      • int8 result_id - the ID of the result (usually means the error code if the action failed)
      • string msg - verbose result
  • Example - Sending a move_base goal to position (1.0, 0.0, 0.0) on the map
    • Sending the goal
root@localhost:/# rostopic pub -1 /move_base/goal move_base_msgs/MoveBaseActionGoal "header:
seq: 0
stamp:
  secs: 0
  nsecs: 0
frame_id: ''
goal_id:
stamp:
  secs: 0
  nsecs: 0
id: ''
goal:
target_pose:
  header:
    seq: 0
    stamp: {secs: 0, nsecs: 0}
    frame_id: 'map'
  pose:
    position: {x: 1.0, y: 0.0, z: 0.0}
    orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
max_vel_x: 0.0
max_vel_theta: 0.0
max_accel_x: 0.0
max_accel_theta: 0.0
xy_tolerance: 0.0
yaw_tolerance: 0.0
keep_local_map: false"
- **Receiving the result**
root@localhost:/# rostopic echo /move_base/result
header: 
  seq: 2
  stamp: 
    secs: 1676882171
    nsecs:   5143280
  frame_id: ''
status: 
  goal_id: 
    stamp: 
      secs: 1676882170
      nsecs: 804236039
    id: "/move_base-3-1676882170.804236039"
  status: 3
  text: "Goal reached"
result: 
  is_success: True
  result_id: 1
  msg: "Goal reached"
  final_position: 
    header: 
      seq: 0
      stamp: 
        secs: 1676882170
        nsecs: 996000000
      frame_id: "map"
    pose: 
      position: 
        x: 0.99350843387543
        y: 0.00106917878151
        z: 0.0
      orientation: 
        x: 0.0
        y: 0.0
        z: -0.00441860178937
        w: 0.999990237931

Teleop

  • Ohmni AMR does not have a ROS keyboard teleop of its own.
  • Usually, the robot can be controlled by calling in via the web-app, by using arrow keys in the Debug page UI.
  • However, there is an open-sourced package that can meet your need, which is teleop_twist_keyboard
  • Install the package:
    • apt update
    • apt install ros-melodic-teleop-twist-keyboard
  • Using the package:
    • rosrun teleop_twist_keyboard teleop_twist_keyboard.py cmd_vel:/tb_cmd_vel
    • Please be careful as the default speed is very high

Set View Mode

  • Description
    • A service which used to change the camera perspective on Debug page UI
  • Service name: /tb_node/debug/set_view_mode
  • Service type: tb_msgs/srv_string
    • Request: string data - It can be free | follow | chase | center
    • Response:
      • bool success - whether the service call is successful or not
      • string message - verbose response (e.g. error message if any)
  • Example
    • Run rosservice call /tb_node/debug/set_view_mode "data: 'free'"
    • You will receive the response:
      • success: True
      • message: "Change camera view mode in debug page to free mode"
    • Of course you will have to open Debug page before hand for it to work

Toggle Global Costmap Visibility

  • Description
    • A service which is used to toggle the visibility of the global costmap on Debug page
  • Service name: /tb_node/debug/toggle_view_global_costmap
  • Service type: std_srvs/SetBool
    • Request: data: true|false
    • Response:
      • bool success
      • string message
  • Example - to turn on the visibility of the global costmap
    • Run rosservice call /tb_node/debug/toggle_view_global_costmap "data: true"

Note 1: Since these APIs communicate directly with the ROS stack, there are not a lot of exception handlers put in places to avoid user errors, so some invalid service calls might cause the stack to crash or not behave properly. If you encounter such cases, which return unsuccessful, you might need to restart the stack.

How to restart the stack - Note: this will kill the current docker and relaunch it

  • Exit the current docker container
  • In the OS shell, run: setprop ctl.restart tb-node
  • This will restart the nodejs app on the robot and then in turn restart the docker

Note 2: Technically, ROS can handle multiple service calls simultaneously. However, ROS service calls are synchronous by design. So if your application is another ROS node (written in C++ or Python), these service calls will block the code flow until they are done (and return the response). One way to avoid these blocking calls is to use nodejs or react as the front-end, then you can call the service and let it run without having to wait, but please make sure that there would be no race conditions occuring, otherwise it will cause the whole stack to not behave properly.


Path Following

Path Recording

The Path Recording module is coupled with Mapping module. After a map is created, there is a path recorded as well. You do not need to do a separated path recording session.
The path is stored in a json file in /app/paths directory. The filename follows this format: path-<map-id>-<path-id>.json. At the moment this documentation is created, we are still using one path for one map, so path-id is not used. It is there to be ready once we implement multiple paths in one map.
There are many fields within the json file (as it meant to be used for UV disinfection). The things that are needed for path following are header and poses of realtime_processed_path, which correspond to a geometry_msgs/PoseArray.

In order to retrieve the data from the path file, you will need to call service: /path_recording/load_path_from_json.

  • Service name: /path_recording/load_path_from_json
  • Service type: path_msgs/LoadPathFromJson
    • Request:
      • string map_id - Should be left empty if we want to use the path corresponding to the currently loaded map.
      • string path_id - Not used
      • string path_type - Should be left empty (it will automatically default to rtp which is the best option at the moment, raw type might not be correct due to loop closure during mapping)
    • Response:
      • bool isSuccess - whethere the service call is successful or not
      • string message - verbose response (e.g. error message if any)
      • geometry_msgs/PoseArray[ ] path_segments - An array of PoseArray - Recorded path was split into segments for UV Disinfection purpose. For normal mapping and path recording, there should be only 1 segment.
      • path_msgs/PathMetadata[ ] metadata - Only for UV Disinfection application

Path Following

There are 3 different ways to trigger path following:

  • Use service calls from tb_node (only available from version 0.7.22.14-jpmc-amr onward):
    • Start: /tb_node/debug/start_path_following, type: std_srvs/Trigger
    • Stop: /tb_node/debug/stop_path_following, type: std_srvs/Trigger
    • Pause: /tb_node/debug/pause_path_following, type: std_srvs/Trigger
    • Resume: /tb_node/debug/resume_path_following, type: std_srvs/Trigger
    • NOTE: A map has to be loaded first, and the robot has to be relocalized before calling the services.
  • Directly use the ROS action /move_base_array.
    • Please check test_debug.py function start_following_path() on how to create the action client and send the goal.
    • Goal: target_poses - A PoseArray which has map as frame_id in header.
    • Run ./test_debug.py, load map, then relocalize before calling start path following.
  • Use LUV action (also in test_debug.py):
    • It is meant for UV Disinfection but behavior-wise, it is basically the same thing as calling move_base_array. However, compared to method 1 (calling services in tb_node) or method 2 (calling move_base_array action directly), this method has a few advantages:
      • At the beginning, the bot does not strictly have to go to the first pose in the array (which sometimes causes the bot to go backward or rotate awkwardly). It will consider its own position, then smoothly join the recorded path (by minimizing the amount rotation & backward motion needed)
      • If the robot is blocked, the action will be paused automatically. For method 1 or 2, the action will fail, and the user will have to start from the beginning.

Revisions

Version Date Changes
0.1 2023-Feb-20 First Commit
0.2 2023-Apr-07 Added Path Following Related Functions