Logo designed by Daniel Pacada
Hermes is a mobile robot, acting as a BDI agent, that navigates a custom maze through the use of a LiDAR sensor and the various Bluetooth beacons placed along its way. The goal of Hermes is to showcase a realistic application of AgentSpeak and Jason BDI.
@inproceedings{bdimaildeliveryrobot,
author = {Bardia Parmoun and Babak Esfandiari},
title = {An Extended BDI Case Study Toward Campus Mail Delivery},
booktitle = {Proceedings of the Engineering Multi-Agent Systems (EMAS 2026) Workshop},
year = {2026},
url = {https://emas-workshop.github.io/2026/papers/008_An_Extended_BDI_Case_Study_Toward_Campus_Mail_Delivery.pdf}
}
Hermes has the ability to perform the following:
- Maintain a consistent distance with the wall.
- Navigate the maze with its many intersections and complete a full trip from point A to point B using its preloaded map of the beacon connections.
- Handle possible collisions and bypass obstacles through the use of its bumper sensor.
This robot is based on the iRobot CREATE 3 developed by the iRobot company.
In addition to that, Hermes uses the popular AgentSpeak language Jason to implement the BDI architecture.
Hermes can be run in two modes:
- Simulator mode (either locally or within Docker): This option utilizes ROS Gazebo to allow the user to fully simulate a maze environment with the iRobot CREATE 3 robot in it.
- Robot mode (requires the physical robot): This option allows the user to run the Jason BDI logic directly on the physical robot.
You can find demos of Hermes here!
-
Install ROS Foxy on an Ubuntu 20.04 system
-
Make sure to source your installation:
$ source /opt/ros/foxy/setup.bash- Install the Open-JDK 21:
$ sudo apt install -y openjdk-21-jdk openjdk-21-jre
$ export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-$(dpkg --print-architecture)
$ export PATH=$JAVA_HOME/bin:$PATH- Install gradle 8:
$ sudo mkdir /opt/gradle && cd /opt/gradle
$ sudo curl -L https://services.gradle.org/distributions/gradle-8.14.2-bin.zip -o gradle.zip
$ sudo unzip gradle.zip
$ export GRADLE_HOME=/opt/gradle/gradle-8.14.2
$ export PATH=$GRADLE_HOME/bin:$PATH- Install ROS Gazebo 11:
$ sudo apt install ros-foxy-gazebo-ros-pkgs- Install RViz2:
$ sudo apt install ros-foxy-rviz2- Create a ROS workspace for your system. Such as:
$ mkdir -p ~/hermes_ws/src
$ cd ~/hermes_ws/src- Clone hermes:
$ git clone git@github.com:NMAI-lab/hermes.git- Install all the required Python packages using:
$ cd ~/hermes_ws/src/hermes
$ pip install -r requirements.txt- Clone the appropriate ROS dependencies:
$ vcs import ~/hermes_ws/src/ < ~/hermes_ws/src/hermes/simulator_dependencies.repos- Install the ROS dependencies:
$ cd ~/hermes_ws
$ sudo rosdep init
$ rosdep update
$ rosdep install --from-paths src -yi --skip-keys "ament_tools"- Install the ROS2 ament Java gradle plugin:
$ cd src/ros2-java/ament_gradle_plugin
$ gradle publishToMavenLocal- Build all the ROS packages by doing:
$ cd ~/hermes_ws
$ colcon build --symlink-install
# Fix the classpath for the hermes_agent package (this is a bug in ament_gradle_plugin)
$ sed -i "/^CLASSPATH=/d" ./install/hermes_agent/lib/hermes_agent/hermes_agent- Source your installation by doing:
$ source ~/hermes_ws/install/local_setup.bash- Install the container as follows:
$ docker build \
--build-arg ARCH=$(dpkg --print-architecture) \
--build-arg ROS_DISTRO="${ros_version}" \
-f Dockerfile.simulator \
-t hermes-sim .
| iRobot CREATE 3 and Dock Station | RPLiDAR A1 | AprilBeacon N04 | SBC |
![]() |
![]() |
Raspberry Pi OR Nvidia Jetson |
-
Unscrew the faceplate of the CREATE 3 and ensure that the USB/BLE toggle on the adapter board is in the USB position. Screw the faceplate back into position.
-
Mount the SBC in the cargo bay:
-
Raspberry Pi: Screw the 3D printed Raspberry Pi 4B mount in the cargo bay and attach the Pi.
-
Nvidia Jetson: Screw the 3D printed Nvidia Jetson mount in the cargo bay and attach the jetson.
- Connect a USB-C cable between the USB-C port in the robot's cargo bay and the SBC's USB C input.
- NOTE: for Nvidia Jetson you also need to connect the board's DC input to the iRobot CREATE's battery using a JST-XH female connector to DC barrel plug cable.
-
Connect a USB-to-micro USB cable to the SBC and a micro USB-to-serial converter. Route the cable through the cable passthrough hole to the top of the robot.
-
Screw the 3D printed LiDAR mount and standoffs into the faceplate and attach the LiDAR.
-
Using the LiDAR serial cable, connect the LiDAR pins to the USB-to-serial converter.
Here is what the final robot setup looks like:
-
Download the latest version of the CREATE 3 firmware
-
Power on the robot by plugging it into the charging dock.
-
Press and hold both side buttons until the light ring turns blue.
-
Connect to the Create-[xxx] Wi-Fi network on your device.
-
Navigate to 192.168.10.1 to access the Create 3 web interface.
-
Navigate to the Update tab and follow the steps to upload the downloaded firmware.
-
Wait until the robot chimes and then navigate to the
Application -> Configuration tab. -
Ensure the RMW_IMPLEMENTATION dropdown is set to rmw_cyclonedds_cpp.
-
If you changed the RMW implementation, restart the application.
-
Navigate to the
Beta Features -> NTP sourcestab and addserver 192.168.186.3 iburstso the robot can receive NTP info from the SBC. -
Reboot the robot and disconnect from the Wi-Fi network.
-
Install Ubuntu 22.04 on your SBC.
-
Install ROS Humble the SBC.
-
Set up the SBC to share its network with the robot:
- Raspberry Pi: Setup an NTP server on the SBC by following this guide (steps 6 and 7 were already done in the previous section).
$ sudo apt install -y chrony
# Backup config
$ sudo cp /etc/chrony/chrony.conf /etc/chrony/chrony.conf.backup
# Add allow rule after the Ubuntu pool line
$ sudo sed -i '/^pool .*ubuntu.pool.ntp.org.*iburst.*/a\
\
# Enable serving time to ntp clients on 192.168.186.0 subnet.\
allow 192.168.186.0/24' /etc/chrony/chrony.conf
$ sudo sed -i '/^allow 192.168.186.0\/24/a\
\
# Serve time even if not synchronized to a time source\
local stratum 10' /etc/chrony/chrony.conf
$ sudo systemctl restart chrony- Nvidia Jetson: assign a static IP to the robot's serial port.
# Hard code the ip address of the robot
$ sudo bash -c 'echo "192.168.186.3" > /opt/nvidia/l4t-usb-device-mode/IP_ADDRESS_FOR_CREATE3_ROBOT.conf'
# Update the USB config of the port
$ sudo cp /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-runtime-start.sh \
/opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-runtime-start.sh.orig
$ sudo wget -O /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-runtime-start.sh \
https://iroboteducation.github.io/create3_docs/setup/data/nv-l4t-usb-device-mode-runtime-start.sh
$ sudo chmod +x /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-runtime-start.sh
# Simplify the USB interface on the board
$ sudo sed -i 's/^enable_rndis=1/enable_rndis=0/' /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-config.sh
$ sudo sed -i 's/^enable_acm=1/enable_acm=0/' /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-config.sh
$ sudo sed -i 's/^enable_ums=1/enable_ums=0/' /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-config.sh
$ export CYCLONEDDS_URI='<CycloneDDS><Domain><General><NetworkInterfaceAddress>l4tbr0</NetworkInterfaceAddress></General></Domain></CycloneDDS>'- Make sure to source your installation:
$ source /opt/ros/humble/setup.bash-
Ensure the correct RMW implementation is chosen by doing
RMW_IMPLEMENTATION=rmw_cyclonedds_cpp. -
Install the Open-JDK 21:
$ sudo apt install -y openjdk-21-jdk openjdk-21-jre
$ export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-$(dpkg --print-architecture)
$ export PATH=$JAVA_HOME/bin:$PATH- Install gradle 8:
$ sudo mkdir /opt/gradle && cd /opt/gradle
$ sudo curl -L https://services.gradle.org/distributions/gradle-8.14.2-bin.zip -o gradle.zip
$ sudo unzip gradle.zip
$ export GRADLE_HOME=/opt/gradle/gradle-8.14.2
$ export PATH=$GRADLE_HOME/bin:$PATH- Create a ROS workspace for your system. Such as:
$ mkdir -p ~/hermes_ws/src
$ cd ~/hermes_ws/src- Clone hermes:
$ git clone git@github.com:NMAI-lab/hermes.git- Install all the required Python packages using:
$ cd ~/hermes_ws/src/hermes
$ pip install -r requirements.txt- Clone the appropriate ROS dependencies:
$ vcs import ~/hermes_ws/src/ < ~/hermes_ws/src/hermes/robot_dependencies.repos- Install the ROS dependencies:
$ cd ~/hermes_ws
$ sudo rosdep init
$ rosdep update
$ rosdep install --from-paths src -yi --skip-keys "ament_tools"- Install the ROS2 ament Java gradle plugin:
$ cd src/ros2-java/ament_gradle_plugin
$ gradle publishToMavenLocal- Give the BluePy library permission to run without root
# Get /path/to/bluepy-helper (you can maybe use:)
$ find ~/.local -name bluepy-helper
$ sudo setcap 'cap_net_raw,cap_net_admin+eip' /path/to/bluepy-helper
$ sudo usermod -aG bluetooth $USER- Build all the ROS packages by doing:
$ cd ~/hermes_ws
$ colcon build --symlink-install
# Fix the classpath for the hermes_agent package (this is a bug in ament_gradle_plugin)
$ sed -i "/^CLASSPATH=/d" ./install/hermes_agent/lib/hermes_agent/hermes_agent- Source your installation by doing:
$ source ~/hermes_ws/install/local_setup.bash- Set up the SBC to share its network with the robot:
- Raspberry Pi: Setup an NTP server on the SBC by following this guide (steps 6 and 7 were already done in the previous section).
$ sudo apt install -y chrony
# Backup config
$ sudo cp /etc/chrony/chrony.conf /etc/chrony/chrony.conf.backup
# Add allow rule after the Ubuntu pool line
$ sudo sed -i '/^pool .*ubuntu.pool.ntp.org.*iburst.*/a\
\
# Enable serving time to ntp clients on 192.168.186.0 subnet.\
allow 192.168.186.0/24' /etc/chrony/chrony.conf
$ sudo sed -i '/^allow 192.168.186.0\/24/a\
\
# Serve time even if not synchronized to a time source\
local stratum 10' /etc/chrony/chrony.conf
$ sudo systemctl restart chrony- Nvidia Jetson: assign a static IP to the robot's serial port.
# Hard code the ip address of the robot
$ sudo bash -c 'echo "192.168.186.3" > /opt/nvidia/l4t-usb-device-mode/IP_ADDRESS_FOR_CREATE3_ROBOT.conf'
# Update the USB config of the port
$ sudo cp /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-runtime-start.sh \
/opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-runtime-start.sh.orig
$ sudo wget -O /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-runtime-start.sh \
https://iroboteducation.github.io/create3_docs/setup/data/nv-l4t-usb-device-mode-runtime-start.sh
$ sudo chmod +x /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-runtime-start.sh
# Simplify the USB interface on the board
$ sudo sed -i 's/^enable_rndis=1/enable_rndis=0/' /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-config.sh
$ sudo sed -i 's/^enable_acm=1/enable_acm=0/' /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-config.sh
$ sudo sed -i 's/^enable_ums=1/enable_ums=0/' /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-config.sh- Simply install the Docker container:
$ docker build \
--build-arg ARCH=$(dpkg --print-architecture) \
--build-arg ROS_DISTRO=humble \
-f Dockerfile.robot \
-t hermes-robot .
NOTE: Here are some useful guides:
NOTE: If at any point you face any issues with the installation process of these ROS dependencies, please refer to the README files of the appropriate repositories:
NOTE: To ensure compatability with the current ROS and Java versions, the following repositories have been customized:
NOTE: ament_gradle_plugin seems to still be in its Beta stage. Consecutive calls to colcon build --symlink-install can cause discrepencies in your Java CLASSPATH, resulting in your libraries getting lost at runtime. If you notice issues of that sort do the following (running these commands during every colcon install is strongly recommended):
$ cd ~/hermes_ws
$ rm -rf build/hermes_agent install/hermes_agent
$ colcon build --symlink-install
$ sed -i "/^CLASSPATH=/d" ./install/hermes_agent/lib/hermes_agent/hermes_agentMake sure to run $ source ~/hermes_ws/install/local_setup.bash before running Hermes again.
Make sure you have built the hermes-sim docker container.
- Connect
xhostto docker:
$ xhost +local:docker- Start the docker container:
$ docker run --name hermes-sim -it \
--env DISPLAY="$DISPLAY" \
--env QT_X11_NO_MITSHM=1 \
--gpus all \
--volume /tmp/.X11-unix:/tmp/.X11-unix:rw \
-p 9090:9090 \
--env LAUNCH_MODE=simulator \
--env START=$start \
--env END=$end \
--env DISPLAY_MAS=$display_mas \
--env LAUNCH_AGENT=$launch_agent \
hermes-simFirst try to fire up the simulator by doing:
$ source ~/hermes_ws/install/local_setup.bash
$ ros2 launch hermes_simulator simulator.launch.py start:=B3 end:=B1 display_mas:=trueYou should see the Gazebo and RViz windows pop up:
| RViz | Gazebo |
![]() |
![]() |
NOTE: you can pass the following flags:
- rviz: (boolean) should start rviz or not.
- gui: (boolean) display Gazebo GUI or not.
- display_mas: (boolean) whether to display Jason MAS GUI or not.
- map: (string) the environment description.
- start: (string) the first beacon for the robot.
- end: the final beacon for the robot.
- metrics_file: (string) where to store the metrics data.
- run_hermes_agent: (boolean) whether to launch the hermes_agent node.
- x: (int) x component of the robot's pose.
- y: (int) y component of the robot's pose.
- z: (int) z component of the robot's pose.
- yaw: (int) yaw component of the robot's pose.
A cleanup script has been included to ensure a proper cleanup of the shared memory and any leftover processes. If you run into any issues with the simulator, simply run:
$ ./perform_cleanup.sh-
Plug in the LiDAR sensor to the the SBC through USB
-
Turn on the iRobot CREATE 3
-
Have your Bluetooth Beacons ready and make sure their MAC addresses and orientation match beacons_list
-
Make sure the map matches map (you can ignore the obstacles and distance fields)
-
SSH the SBC on the robot
-
Make sure the robot is fully powered on and linked to ROS by doing a quick check:
- If running locally: run,
$ ros2 topic list- If running in Docker: start the container as follows:
Raspberry Pi:
$ docker run --name hermes-robot -it \
--env RMW_IMPLEMENTATION=rmw_cyclonedds_cpp \
--network host \
--privileged \
--env LAUNCH_MODE=robot \
--env END=$end \
--env LAUNCH_AGENT=$launch_agent \
hermes-robotNvidia Jetson:
$ docker run --name hermes-robot -it \
--env RMW_IMPLEMENTATION=rmw_cyclonedds_cpp \
--env CYCLONEDDS_URI="<CycloneDDS><Domain><General><NetworkInterfaceAddress>l4tbr0</NetworkInterfaceAddress></General></Domain></CycloneDDS>" \
--network host \
--privileged \
--env LAUNCH_MODE=robot \
--env END=$end \
--env LAUNCH_AGENT=$launch_agent \
hermes-robotThen inside the container run:
$ ros2 topic listYou should see:
/battery_state
/cliff_intensity
/cmd_audio
/cmd_lightring
/cmd_vel
/dock_status
/hazard_detection
/imu
/interface_buttons
/ir_intensity
/ir_opcode
/kidnap_status
/mobility_monitor/transition_event
/mouse
/odom
/parameter_events
/robot_state/transition_event
/rosout
/slip_status
/static_transform/transition_event
/stop_status
/tf
/tf_static
/wheel_status
/wheel_ticks
/wheel_vels- Start up Hermes, either in your local shell or inside the container by running:
$ source ~/hermes_ws/install/local_setup.bash
$ ros2 launch hermes_simulator robot.launch.py end:=B1NOTE: you can pass the following flags:
- map: (string) the environment description.
- end: (string) the final beacon for the robot.
- metrics_file: (string) where to store the metrics data.
- run_hermes_agent: (boolean) whether to launch the hermes_agent node.
Hermes is designed to be fully automated allowing it to run multiple headless runs. To demonstrate this capability, a simple experiment verifying different styles of simple trips has already been provided. To run this experiment run:
$ ./run_experiments.shThe results of this experiment along with an analysis of both the robot and Jason's performance are located at data_analysis
- hermes_robot_description: This package includes the Gazebo descriptions for the robotcs, sensors, and the dock station. It also includes the appropriate launch files for spawning these objects.
- hermes_environment: This package includes the implementation of the simulation environment with the various configs for loading the robot map.
- hermes_agent: This package includes the implementation of the agent which is in charge of parsing the agent brain files.
- hermes_simulator: This package includes the implementation of the simulator with the various sensors for interpreting the environment. This package invokes the environment, the agent, and the sensors.
- The inspiration for this project came from another similar project I worked on. Make sure to check out Carleton Mail Delivery Robot!
- The name Hermes is a nod to the previous major AgentSpeak projects namely, Jason and Peleus. I chose the name Hermes since the main purpose of this robot is to deliver mail from one place to another.
- iRobot's CREATE 3 Simulator for the main robot simulator.
- ros2_java to integrate ROS2 with the Java agent implementations.
- Jason an AgentSpeak interpreter for the agent definitions.




