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
-
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 -
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 -
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
datastring, which will be the name of the map. - If the
datastring is empty, map name will followMap-<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)
- This service is used to start a mapping session. The request of the service only has 1 field, a
- 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 notstring message- verbose response (e.g. error message if any)
- Request:
- 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: Truemessage: "Preparation done - Mapping can start now"
- Afterward, you can use
move_baseorkeyboard_twist_teleopto control the robot.
- Run
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
- Compressed pgm file (image of static map):
- 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_mapservice 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 notstring 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: Truemessage: "1b8a77ee-fc19-405c-b2bd-b7f3c441a268"
- Run
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 notstring 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: Truemessage: "Clean up & restart done - Mapping can start now"
- Run
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 notstring message- verbose response
- Example
- Run
rosservice call /tb_node/debug/cancel_mapping - You will get a response:
success: Truemessage: "Mapping has been cancelled"
- Run
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 notstring message- verbose response
- Request:
- Example
- Run
rosservice call /tb_node/debug/delete_map "data: '1b8a77ee-fc19-405c-b2bd-b7f3c441a268'" - You will get the response:
success: Truemessage: "Map deleted"
- Run
Delete All Maps
- There is no service to handle this. However, you can easily achieve this by simply deleting every files within
/app/mapsand/app/pathsdirectories. - 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 datawheredatashould be the map-guid - Response:
bool success- whether the service all is successful or notstring message- verbose response
- Request:
- Example
- Run
rosservice call /tb_node/debug/load_map "data: '383998c4-70e9-4113-81f3-d4f13e69e616'" - You will receive the response:
success: Truemessage: "Load map done"
- If the GUID is invalid, it will return False
- Run
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 likefeedback,status, andcancelas 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_idingoal/pose/header/frame_id. It should always bemap.positioningoal/pose/pose/pose/position. If you use option 1, leave it at{x: 0.0, y: 0.0, z: 0.0}orientationingoal/pose/pose/pose/orientation. If you use option 1, you only need to changewvalue to1.0for the quaternion to be valid.
- You can view the full format of the message by running:
- 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
- Similarly, you can check the format by running
- 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_keyboardif you decided to install that package
- This is a service to change the robot moving speed (by using
- Service name:
/tb_node/debug/set_speed - Service type:
tb_msgs/srv_float- Request:
float datawheredatais the absolute value of linear moving speed. - Response:
bool success- whether the service all is successful or notstring message- verbose response
- Request:
- 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: Truemessage: "Speed is changed to 0.25 m/s"
- Run
Set move_base Goal
- Description
- Ohmni AMR robot uses
move_baseto navigate the environment (in both mapping and navigation mode) move_baseis 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_basecan be called via ROS action protocol.
- Ohmni AMR robot uses
- 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_idingoal/target_pose/header/frame_id. It should always bemappositioningoal/target_pose/pose/position. It contains the 3D coordinate of the goalorientationingoal/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.
- The format of this message is long, with a lot of data fields. You can check the entire format by running
- 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
- Similarly, you can check the full format of the message by running
- 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 updateapt 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 befree | follow | chase | center - Response:
bool success- whether the service call is successful or notstring message- verbose response (e.g. error message if any)
- Request:
- Example
- Run
rosservice call /tb_node/debug/set_view_mode "data: 'free'" - You will receive the response:
success: Truemessage: "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
- Run
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 successstring message
- Request:
- Example - to turn on the visibility of the global costmap
- Run
rosservice call /tb_node/debug/toggle_view_global_costmap "data: true"
- Run
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 usedstring path_type- Should be left empty (it will automatically default tortpwhich is the best option at the moment,rawtype might not be correct due to loop closure during mapping)
- Response:
bool isSuccess- whethere the service call is successful or notstring 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
- Request:
Path Following
There are 3 different ways to trigger path following:
- Use service calls from
tb_node(only available from version0.7.22.14-jpmc-amronward):- 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.
- Start:
- Directly use the ROS action
/move_base_array.- Please check
test_debug.pyfunctionstart_following_path()on how to create the action client and send the goal. - Goal:
target_poses- A PoseArray which hasmapasframe_idinheader. - Run
./test_debug.py, load map, then relocalize before calling start path following.
- Please check
- 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 intb_node) or method 2 (callingmove_base_arrayaction 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.
- It is meant for UV Disinfection but behavior-wise, it is basically the same thing as calling
Revisions
| Version | Date | Changes |
|---|---|---|
| 0.1 | 2023-Feb-20 | First Commit |
| 0.2 | 2023-Apr-07 | Added Path Following Related Functions |