<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nathaniel Handan</title>
    <description>The latest articles on DEV Community by Nathaniel Handan (@tinnyrobot).</description>
    <link>https://dev.to/tinnyrobot</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1040863%2F7d1161ee-2e10-4c7b-a2f2-2745739d772f.jpg</url>
      <title>DEV Community: Nathaniel Handan</title>
      <link>https://dev.to/tinnyrobot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tinnyrobot"/>
    <language>en</language>
    <item>
      <title>Building an Autonomous Robotic Vacuum from Scratch: A ROS 2 Sim-to-Real Engineering Log</title>
      <dc:creator>Nathaniel Handan</dc:creator>
      <pubDate>Fri, 07 Aug 2026 15:42:51 +0000</pubDate>
      <link>https://dev.to/tinnyrobot/building-an-autonomous-robotic-vacuum-from-scratch-a-ros-2-sim-to-real-engineering-log-454m</link>
      <guid>https://dev.to/tinnyrobot/building-an-autonomous-robotic-vacuum-from-scratch-a-ros-2-sim-to-real-engineering-log-454m</guid>
      <description>&lt;p&gt;I did not set out to build a better robot vacuum. There are already good ones on the market, and competing with iRobot or Roborock on a hobbyist budget is a losing game. What I actually wanted was a realistic, physically grounded platform to force myself through the full mobile robotics stack: URDF modeling, sensor fusion, SLAM, Nav2, ros2_control, and eventually sim-to-real transfer of learned navigation policies. A vacuum form factor turned out to be a convenient excuse to build that platform.&lt;/p&gt;

&lt;p&gt;This post documents where the project currently stands: the mechanical starting point, the ROS 2 architecture, the simulation setup in Gazebo Harmonic, the decisions I made and why, and the failures that shaped those decisions. This is not a tutorial. It is a log of an ongoing system, written the way I'd want to read about someone else's project before starting my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Vacuum Form Factor
&lt;/h2&gt;

&lt;p&gt;Designing a mobile robot chassis from a blank sheet is a rabbit hole. Wheel placement, center of mass, caster geometry, motor mounting, all of it consumes weeks before you've written a single line of navigation code. I wanted to spend my time on the software stack, not on reinventing a differential drive chassis that thousands of engineers have already solved well.&lt;/p&gt;

&lt;p&gt;So I pulled a CAD model of a robot vacuum (visually similar to the Xiaomi Mi Robot Vacuum, though this is not a Xiaomi product or a clone of their firmware) from &lt;a href="https://grabcad.com/library/xiaomi-mi-vacuum-cleaner-1s-1/details?folder_id=13338362" rel="noopener noreferrer"&gt;GrabCAD&lt;/a&gt; and imported it into Onshape. From there I started modifying the internal volume to fit a Raspberry Pi 5, a battery pack sized for my actual power budget, and mounting points for an RPLIDAR and an IMU that the stock design never accounted for.&lt;/p&gt;

&lt;p&gt;The reasoning here is purely pragmatic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The circular chassis with a differential drive base and a single rear caster is a well understood, well documented robot geometry. Every Nav2 tutorial, every &lt;code&gt;robot_localization&lt;/code&gt; example, every &lt;code&gt;ros2_control&lt;/code&gt; diff drive plugin assumes something close to this.&lt;/li&gt;
&lt;li&gt;A commercial design has already solved weight distribution and turning radius problems for indoor navigation. I don't need to relearn those from scratch.&lt;/li&gt;
&lt;li&gt;It gives me a hard physical constraint. I can't just theorize about SLAM performance, the chassis has a real height, a real sensor mounting position, and a real minimum turning radius that the planner has to respect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade-off is that I inherited some annoying constraints too. Internal volume is tight, which is already forcing compromises on where the RealSense camera goes relative to the LiDAR, and cable routing inside a vacuum shell is a genuinely fiddly problem that CAD renders don't warn you about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Hardware
&lt;/h2&gt;

&lt;p&gt;The hardware set is intentionally modest. Nothing here is exotic, and that is deliberate: I want the eventual sim-to-real gap to come from software and modeling decisions, not from working around unusual or unreliable components.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Subsystem&lt;/th&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chassis&lt;/td&gt;
&lt;td&gt;Circular vacuum-form platform (Onshape, modified from GrabCAD reference)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drive&lt;/td&gt;
&lt;td&gt;Differential drive, two powered wheels, rear caster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compute&lt;/td&gt;
&lt;td&gt;Raspberry Pi 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ranging&lt;/td&gt;
&lt;td&gt;RPLIDAR (2D planar LiDAR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inertial&lt;/td&gt;
&lt;td&gt;IMU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Odometry&lt;/td&gt;
&lt;td&gt;Wheel encoders on both drive motors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Depth (optional)&lt;/td&gt;
&lt;td&gt;Intel RealSense&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actuation&lt;/td&gt;
&lt;td&gt;Vacuum motor, brush motor, drive motors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Power&lt;/td&gt;
&lt;td&gt;Custom BMS and charging circuit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The RealSense is marked optional because right now the navigation stack is LiDAR-centric. The camera exists for future work: visual odometry as a cross-check against wheel encoders, and eventually as an input to a learned local planner. It is not load-bearing for the current SLAM or Nav2 pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ubuntu 24.04&lt;/li&gt;
&lt;li&gt;ROS 2 Jazzy&lt;/li&gt;
&lt;li&gt;Gazebo Harmonic&lt;/li&gt;
&lt;li&gt;Navigation2&lt;/li&gt;
&lt;li&gt;SLAM Toolbox&lt;/li&gt;
&lt;li&gt;robot_localization&lt;/li&gt;
&lt;li&gt;ros2_control&lt;/li&gt;
&lt;li&gt;URDF / Xacro&lt;/li&gt;
&lt;li&gt;RViz2&lt;/li&gt;
&lt;li&gt;Lifecycle nodes&lt;/li&gt;
&lt;li&gt;TF2&lt;/li&gt;
&lt;li&gt;Behavior Trees (via Nav2's BT navigator)&lt;/li&gt;
&lt;li&gt;OpenCV&lt;/li&gt;
&lt;li&gt;Python and C++, split by where each language actually earns its place&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The language split is worth a sentence. Anything performance sensitive, control loops, message filtering, custom &lt;code&gt;ros2_control&lt;/code&gt; hardware interfaces, goes in C++. Anything orchestration or logic heavy, behavior tree conditions, state machines, tooling scripts, goes in Python. I've seen projects standardize on one language across the whole stack and pay for it later, either in runtime overhead in the Python parts or in development speed in the C++ parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: One Set of Nodes, Two Environments
&lt;/h2&gt;

&lt;p&gt;The single design decision that shapes everything else in this project is: the same ROS 2 nodes and topics run in simulation and on the physical robot. Not "similar" nodes. The same navigation, localization, and control stack, unmodified, pointed at a different set of drivers underneath.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                        Gazebo Harmonic
                               │
                Same ROS 2 Nodes &amp;amp; Topics
           (Nav2, SLAM Toolbox, robot_localization,
                  TF2, Behavior Trees)
                               │
        ┌──────────────────────┴──────────────────────┐
        │                                             │
    SIMULATION                                  PHYSICAL ROBOT
    ─────────────                                ─────────────
    LiDAR plugin            ───────────────►     RPLIDAR driver
    Camera plugin            ───────────────►     RealSense driver
    Diff drive plugin        ───────────────►     Motor driver (ros2_control)
    IMU plugin                ───────────────►     Physical IMU driver
    Wheel encoder plugin      ───────────────►     Hardware encoder interface
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything above the dashed line, the planner, the costmaps, the EKF, the behavior tree, never knows whether it's talking to a Gazebo plugin or a physical driver. It just sees &lt;code&gt;/scan&lt;/code&gt;, &lt;code&gt;/imu&lt;/code&gt;, &lt;code&gt;/odom&lt;/code&gt;, &lt;code&gt;/cmd_vel&lt;/code&gt;, and &lt;code&gt;/tf&lt;/code&gt;. The abstraction boundary is the hardware interface layer, implemented through &lt;code&gt;ros2_control&lt;/code&gt;'s &lt;code&gt;SystemInterface&lt;/code&gt; on the real robot and through Gazebo's &lt;code&gt;ros2_control&lt;/code&gt; plugin in simulation.&lt;/p&gt;

&lt;p&gt;This is not a novel idea, it is how Clearpath and most serious ROS 2 platforms structure their stacks, but actually building it disciplined enough to hold up is harder than it sounds. The temptation to sneak a simulation-only shortcut into a launch file, or to hardcode a topic remap that only makes sense on hardware, is constant. Every time I catch myself doing that, it's a sign the abstraction has a hole in it.&lt;/p&gt;

&lt;h3&gt;
  
  
  URDF and Xacro Structure
&lt;/h3&gt;

&lt;p&gt;The robot description is split into modular Xacro files rather than one monolithic URDF, which matters once you're maintaining both a simulated and a physical variant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vacuum_bot_description/
├── urdf/
│   ├── vacuum_bot.urdf.xacro        # top level, includes everything below
│   ├── base.xacro                   # chassis, casters, inertial properties
│   ├── wheels.xacro                 # drive wheel joints and links
│   ├── lidar.xacro                  # RPLIDAR mount and frame
│   ├── imu.xacro                    # IMU mount and frame
│   └── ros2_control.xacro           # hardware interface tags, sim vs real toggle
├── meshes/
└── config/
    ├── controller_manager.yaml
    └── nav2_params.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ros2_control.xacro&lt;/code&gt; file is the one place where simulation and hardware genuinely diverge, gated by a Xacro argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;xacro:if&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"$(arg use_sim)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ros2_control&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"GazeboSystem"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"system"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;hardware&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;gz_ros2_control/GazeboSimSystem&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/hardware&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- joint definitions shared with the real robot below --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ros2_control&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/xacro:if&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;xacro:unless&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"$(arg use_sim)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ros2_control&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"RealRobot"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"system"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;hardware&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;vacuum_bot_hardware/VacuumBotSystemHardware&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;param&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"serial_port"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;/dev/ttyUSB0&lt;span class="nt"&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/hardware&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- same joint definitions --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ros2_control&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/xacro:unless&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else in the URDF, link geometry, joint limits, sensor frames, inertial tensors, stays identical between the two. That's the whole point: the divergence is quarantined to a single file and a single tag, not scattered across launch files and node parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  SLAM and Localization
&lt;/h2&gt;

&lt;p&gt;SLAM Toolbox runs in online async mode for mapping, and the localization stack is a two-layer setup that I think is underused in hobbyist projects: wheel encoder odometry fused with the IMU through &lt;code&gt;robot_localization&lt;/code&gt;'s EKF node, producing &lt;code&gt;odom -&amp;gt; base_link&lt;/code&gt;, with SLAM Toolbox (or AMCL against a saved map, depending on mode) supplying the &lt;code&gt;map -&amp;gt; odom&lt;/code&gt; correction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;map
 └── odom            (published by SLAM Toolbox / AMCL)
      └── base_link   (published by robot_localization EKF, fusing wheel odom + IMU)
           ├── lidar_link
           ├── imu_link
           └── camera_link (when RealSense is mounted)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This two-frame separation matters more than it looks like on paper. Early on I had SLAM Toolbox publishing directly into &lt;code&gt;base_link&lt;/code&gt; without an EKF in between, and the localization estimate was visibly jittery on carpet, since raw wheel odometry alone is noisy and the LiDAR match correction was fighting that noise at high frequency. Fusing wheel odometry with the IMU first gives SLAM Toolbox a much smoother prior to correct against, which is a cheap fix that made a disproportionate difference in map quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simulation Environment
&lt;/h2&gt;

&lt;p&gt;Gazebo Harmonic hosts the differential drive plugin, LiDAR sensor plugin, IMU plugin, and (optionally) the RGBD camera plugin, all wired through &lt;code&gt;gz_ros2_control&lt;/code&gt; so that the same &lt;code&gt;diff_drive_controller&lt;/code&gt; and &lt;code&gt;joint_state_broadcaster&lt;/code&gt; used on hardware also drive the simulated joints. Test worlds are simple apartment-style layouts built from primitive geometry: rooms, doorways, furniture obstacles, and a charging dock marker. Nothing photorealistic, since the goal right now is validating the navigation and control stack, not perception under realistic lighting.&lt;/p&gt;

&lt;p&gt;Simulation-first was not really a choice so much as the only sane option given the hardware situation. Bench testing a differential drive controller tuning by running it on carpet, watching the robot drift into a wall, powering down, adjusting a PID gain, and repeating, burns hours. In Gazebo, the same iteration loop is seconds, and I can reset the world state exactly, which matters when you're trying to isolate whether a bad turn came from controller tuning or from a bug in your costmap inflation layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Navigation Stack
&lt;/h2&gt;

&lt;p&gt;Nav2 runs with the standard lifecycle-managed node set: the controller server (DWB), the planner server (NavFn), behavior server, BT navigator, and costmap layers (static, obstacle, inflation) on both the local and global costmaps. Behavior tree XML is close to the Nav2 default &lt;code&gt;navigate_w_replanning_and_recovery&lt;/code&gt; tree for now, with the recovery behaviors tuned down since a circular, low-clearance vacuum chassis has different recovery needs than a taller differential drive robot: spin recovery is cheap, but backing up in a cluttered room is riskier given the low sensor mounting height and limited field of view behind the robot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# excerpt, nav2_params.yaml&lt;/span&gt;
&lt;span class="na"&gt;controller_server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ros__parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;controller_frequency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20.0&lt;/span&gt;
    &lt;span class="na"&gt;FollowPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;plugin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dwb_core::DWBLocalPlanner"&lt;/span&gt;
      &lt;span class="na"&gt;max_vel_x&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.26&lt;/span&gt;
      &lt;span class="na"&gt;max_vel_theta&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt;
      &lt;span class="na"&gt;acc_lim_x&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.5&lt;/span&gt;
      &lt;span class="na"&gt;acc_lim_theta&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.2&lt;/span&gt;
      &lt;span class="na"&gt;xy_goal_tolerance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.15&lt;/span&gt;
      &lt;span class="na"&gt;trajectory_generator_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dwb_plugins::StandardTrajectoryGenerator"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;max_vel_x&lt;/code&gt; is intentionally conservative at this stage. It is easy to tune a planner for aggressive speed in simulation where friction and wheel slip are idealized, and then watch that same configuration fail on carpet where actual traction is lower. I would rather start slow, get a sim-to-real gap measurement, and open the throttle once I know what the gap actually looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ROS 2 Package Architecture Actually Matters
&lt;/h2&gt;

&lt;p&gt;Early on I underweighted package organization, treating it as bookkeeping rather than engineering. That was a mistake worth describing, because the cost of a flat, poorly separated package structure doesn't show up immediately, it shows up three months in when you're trying to swap a component and discover everything is entangled.&lt;/p&gt;

&lt;p&gt;The workspace is currently split into distinct packages along clear responsibility boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vacuum_bot_ws/src/
├── vacuum_bot_description/     # URDF, Xacro, meshes, robot_state_publisher config
├── vacuum_bot_bringup/         # launch files, top-level orchestration
├── vacuum_bot_hardware/        # ros2_control hardware interface (real robot only)
├── vacuum_bot_gazebo/          # world files, sim-specific launch, sensor noise configs
├── vacuum_bot_navigation/      # nav2_params.yaml, BT XML, costmap configs
├── vacuum_bot_localization/    # EKF config, SLAM Toolbox params
└── vacuum_bot_msgs/            # custom messages (battery state, dock status)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule I've tried to hold myself to is that &lt;code&gt;vacuum_bot_description&lt;/code&gt; and &lt;code&gt;vacuum_bot_navigation&lt;/code&gt; should never need to know whether they're running against Gazebo or hardware. Only &lt;code&gt;vacuum_bot_bringup&lt;/code&gt; (which selects launch arguments) and &lt;code&gt;vacuum_bot_hardware&lt;/code&gt; versus &lt;code&gt;vacuum_bot_gazebo&lt;/code&gt; (which are mutually exclusive by design) are allowed to know that. When I catch a navigation-related parameter file with a sim-only assumption baked in, that's a sign the boundary leaked, and I go fix it rather than letting it slide.&lt;/p&gt;

&lt;p&gt;This also made testing dramatically easier. Because &lt;code&gt;vacuum_bot_navigation&lt;/code&gt; has no simulation dependency, I can write and run behavior tree condition unit tests, costmap layer configuration validation, and launch file argument checks in CI without spinning up Gazebo at all. Gazebo-dependent integration tests are a separate, slower tier that runs less frequently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Sensor Integration Was Harder Than Expected
&lt;/h2&gt;

&lt;p&gt;I underestimated this part going in, and it's worth being specific about where the difficulty actually came from, because "sensor integration is hard" is a vague complaint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frame conventions.&lt;/strong&gt; RPLIDAR's ROS 2 driver publishes in a frame convention that does not automatically agree with where I mounted the physical unit inside the chassis. Getting &lt;code&gt;lidar_link&lt;/code&gt;'s static transform right, including a 180 degree yaw correction, took longer than it should have because the symptom (a map that looked mirrored and rotated) didn't obviously point at the transform as the cause. It looked, at first glance, like a SLAM Toolbox configuration problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timing and message synchronization.&lt;/strong&gt; The IMU publishes at a different rate than the wheel encoders, and &lt;code&gt;robot_localization&lt;/code&gt;'s EKF is sensitive to how you configure the process noise covariance relative to those rates. My first EKF config trusted the IMU too much relative to wheel odometry, which showed up as orientation drift during pure rotation in place, a scenario where wheel odometry is actually quite reliable and the IMU's gyro bias was the weaker signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simulated sensor noise not matching reality.&lt;/strong&gt; Gazebo's default LiDAR and IMU noise models are close to ideal by default. If you tune a costmap inflation radius or an EKF covariance against noiseless simulated sensors, you are tuning against a fantasy. I've since added Gaussian noise to the simulated LiDAR and IMU that roughly matches the RPLIDAR and IMU datasheet specs, which is a small thing that meaningfully changes what "good tuning" looks like in sim.&lt;/p&gt;

&lt;p&gt;None of these are exotic problems. They're exactly the kind of unglamorous debugging that a tutorial skips over and that eats real project time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Workflow: TF, rqt, and Rosbag
&lt;/h2&gt;

&lt;p&gt;A few tools ended up carrying most of the debugging load, and it's worth naming them since they're unglamorous compared to whatever the current SLAM or planning algorithm is, but they're where most of the actual time goes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ros2 run tf2_tools view_frames&lt;/code&gt;&lt;/strong&gt; was the single most useful command during the sensor frame debugging described above. Generating the TF tree as a PDF and actually looking at it, rather than guessing at transforms from launch file arguments, caught the mirrored LiDAR frame issue faster than staring at RViz would have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;rqt_graph&lt;/code&gt;&lt;/strong&gt; earns its place whenever a topic isn't flowing where I expect. Nav2's node graph gets large enough that eyeballing launch files isn't reliable for confirming remaps actually took effect; &lt;code&gt;rqt_graph&lt;/code&gt; shows the ground truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rosbag recording of every hardware test run&lt;/strong&gt;, once hardware testing starts, is a policy I'm committing to before the first bring-up rather than after. It's tempting to skip recording during "just a quick test," and that's exactly when the interesting failure happens and there's no data to look back at. Every sim run already gets this treatment; I want hardware runs held to the same standard from day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RViz2 with a saved perspective per debugging context&lt;/strong&gt; (one for SLAM map-building, one for Nav2 costmap inspection, one for TF/sensor frame checks) sounds trivial but cut a meaningful amount of time spent reconfiguring displays mid-debug session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sim-to-Real: The Actual Goal
&lt;/h2&gt;

&lt;p&gt;Everything above exists in service of a specific question: once the stack runs cleanly in Gazebo, what breaks, and by how much, when the same nodes point at real hardware?&lt;/p&gt;

&lt;p&gt;The intended workflow, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the robot model (CAD, then URDF/Xacro)&lt;/li&gt;
&lt;li&gt;Validate the model in Gazebo (physics, joint limits, sensor placement)&lt;/li&gt;
&lt;li&gt;Tune controllers in simulation (&lt;code&gt;diff_drive_controller&lt;/code&gt;, DWB parameters)&lt;/li&gt;
&lt;li&gt;Integrate sensors (RPLIDAR, IMU, encoders, on the real hardware interface)&lt;/li&gt;
&lt;li&gt;Validate navigation in simulation (SLAM Toolbox, Nav2 end to end)&lt;/li&gt;
&lt;li&gt;Deploy the unchanged ROS 2 stack to the physical robot&lt;/li&gt;
&lt;li&gt;Measure the sim-to-real gap directly, rather than guessing at it&lt;/li&gt;
&lt;li&gt;Feed that measurement back into simulation fidelity and controller tuning&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1 through 5 are largely done. Step 6 (hardware deployment) and everything after it is the current frontier of the project. The specific gaps I expect to measure, and want to measure rather than assume, are wheel slip on hard floors versus carpet, localization drift over multi-minute runs, control loop timing differences between the RCLCPP executor on a Raspberry Pi 5 versus my development machine, and how much costmap inflation tuning needs to change once real LiDAR noise replaces the noise model I approximated in Gazebo.&lt;/p&gt;

&lt;p&gt;Domain randomization is explicitly future work, not something implemented yet. The plan is to randomize friction coefficients, sensor noise parameters, and lighting (once the RealSense is doing anything perception-relevant) across training episodes once the learned navigation component exists, so that a policy trained in simulation isn't brittle to the specific physics parameters of one Gazebo world.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Component: Augmenting, Not Replacing
&lt;/h2&gt;

&lt;p&gt;This is explicitly not an AI-first project, and I want to be direct about scope so it doesn't sound like vague roadmap padding. The traditional stack, Nav2's DWB local planner, NavFn global planner, SLAM Toolbox, &lt;code&gt;robot_localization&lt;/code&gt;, remains the backbone. It works, it's debuggable, and it doesn't require training data I don't have yet.&lt;/p&gt;

&lt;p&gt;The research question I actually want to explore is narrower: can a learned local planner, trained via imitation learning against DWB's own trajectories or via reinforcement learning in Gazebo, match or improve on classical local planning in cluttered, dynamic indoor scenes, specifically doorway navigation and tight furniture gaps where DWB's trajectory scoring sometimes gets conservative. That's a local planner replacement experiment, not a full navigation stack replacement. Global planning, costmaps, and localization stay classical.&lt;/p&gt;

&lt;p&gt;Nothing here is trained yet. This section is future work, clearly labeled as such, not a claim about current capability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs Worth Naming Explicitly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reusing a commercial chassis versus designing from scratch.&lt;/strong&gt; I saved months of mechanical design time, at the cost of inheriting a tight internal volume that constrains sensor placement and cable routing. For a software-focused project, that trade was correct. For a project where the mechanical design itself was the research question, it would not have been.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LiDAR-centric navigation versus camera-first.&lt;/strong&gt; 2D LiDAR plus Nav2's costmap stack is a mature, well-understood combination with predictable failure modes. A camera-first approach (visual SLAM, depth-based obstacle avoidance) would open up richer perception but at meaningfully higher software risk for a first working system. I chose to get LiDAR-based navigation solid first and treat the RealSense as an additive sensor for future work, not a dependency for the current pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raspberry Pi 5 versus a more capable embedded compute platform.&lt;/strong&gt; The Pi 5 is enough to run the current ROS 2 graph, but I am watching CPU headroom closely, particularly once any learned inference component gets added. I'd rather hit that ceiling and make a deliberate compute upgrade decision later than over-provision compute for a stack that doesn't need it yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simulation fidelity versus development speed.&lt;/strong&gt; Idealized simulated sensors made early development faster but produced tuning that didn't transfer well. Adding realistic noise models slowed simulation development slightly but should narrow the sim-to-real gap. I'd make the same trade again, just earlier next time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AWS Fits (and Where It Doesn't)
&lt;/h2&gt;

&lt;p&gt;I want to be clear that the robot's autonomy does not depend on the cloud in any way. Navigation, SLAM, and control all run on-device. Where cloud tooling actually earns a place in this project is around the development and evaluation loop rather than the robot's runtime behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storing Gazebo simulation logs and rosbag recordings from both sim runs and hardware test runs, so sim-to-real comparisons are reproducible instead of anecdotal&lt;/li&gt;
&lt;li&gt;A container registry for the ROS 2 workspace image, so the exact same environment that ran a given test is easy to rebuild later&lt;/li&gt;
&lt;li&gt;A lightweight CI pipeline that builds the ROS 2 packages and runs unit tests on push, catching build breaks before they show up as "why doesn't this launch anymore" during bench time&lt;/li&gt;
&lt;li&gt;Eventually, fleet-style telemetry if this project ever grows past a single unit, though that's speculative and not close to current scope&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this changes how the robot operates. It's infrastructure around the project, not infrastructure the robot leans on to function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Status and Immediate Next Steps
&lt;/h2&gt;

&lt;p&gt;As of this writing: the URDF/Xacro model is built and validated in Gazebo Harmonic, &lt;code&gt;ros2_control&lt;/code&gt; is wired for both the simulated and real hardware interface (with the real hardware interface implemented but not yet bench-tested end to end), SLAM Toolbox produces usable maps in simulated apartment-style worlds, and Nav2 completes point-to-point navigation goals in simulation with the DWB local planner and NavFn global planner.&lt;/p&gt;

&lt;p&gt;The immediate next milestone is step 6 in the sim-to-real workflow: flashing the same ROS 2 stack onto the Raspberry Pi 5, bringing up the real RPLIDAR and IMU drivers, and running the first physical SLAM mapping pass. That's where the real measurements start, and where I expect this write-up to get a follow-up post with actual numbers instead of projected ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Physical hardware bring-up and first real-world SLAM/Nav2 runs&lt;/li&gt;
&lt;li&gt;Quantified sim-to-real gap measurements (localization drift, wheel slip, control timing)&lt;/li&gt;
&lt;li&gt;Domain randomization in simulation once measured gaps identify what to randomize&lt;/li&gt;
&lt;li&gt;Imitation learning baseline for a learned local planner, benchmarked against DWB&lt;/li&gt;
&lt;li&gt;RealSense integration as a secondary localization and obstacle avoidance signal&lt;/li&gt;
&lt;li&gt;Charging dock detection and autonomous docking behavior&lt;/li&gt;
&lt;li&gt;Fleet-style telemetry and logging infrastructure, if the project scope grows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a long-horizon project, and I'd rather document it honestly in stages than wait for a finished product that may never arrive in the form I'm currently imagining. If you're working on something similar, particularly the sim-to-real side, I'd genuinely like to compare notes.&lt;/p&gt;

</description>
      <category>robotics</category>
      <category>ai</category>
      <category>nvidia</category>
      <category>navigation</category>
    </item>
    <item>
      <title>94 Million Hausa Speakers, and AI Still Barely Understands Them. What Three Years of Grassroots Work Taught Me.</title>
      <dc:creator>Nathaniel Handan</dc:creator>
      <pubDate>Sat, 25 Jul 2026 07:05:27 +0000</pubDate>
      <link>https://dev.to/tinnyrobot/94-million-hausa-speakers-and-ai-still-barely-understands-them-what-three-years-of-grassroots-4hob</link>
      <guid>https://dev.to/tinnyrobot/94-million-hausa-speakers-and-ai-still-barely-understands-them-what-three-years-of-grassroots-4hob</guid>
      <description>&lt;p&gt;Hausa is spoken by close to 94 million people in Nigeria alone, with tens of millions more across Niger and the wider Sahel. Nature has reported that ChatGPT and LLMs correctly understands only 10 to 20 percent of sentences written in Hausa.&lt;/p&gt;

&lt;p&gt;I've spent the last three years trying to close that gap. First alone, then with a community I founded in Bauchi State that's since grown past 500 people. This isn't really a project retrospective. It's an honest account of what it actually takes to build language technology for a language the industry has quietly decided isn't worth the training data, and what leading the people doing that work with me has taught me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the gap exists, and why scraping the web won't fix it
&lt;/h2&gt;

&lt;p&gt;The easy explanation for Hausa's AI gap is "not enough data." That's true, but it undersells the problem. Hausa has technical properties that make naive data collection actively misleading, not just insufficient.&lt;/p&gt;

&lt;p&gt;Start with orthography. Standard written Hausa uses three hooked consonants, ɓ, ɗ, and ƙ, that don't exist on a standard keyboard. In formal, edited text they're there. In the informal Hausa that actually dominates the web (social media, SMS, WhatsApp, the actual keyboards people type on) they're routinely flattened to plain b, d, and k. Scrape the open web for Hausa and you get a corpus quietly missing phonemic information in a large share of its examples, and that gap propagates into anything trained on it, from tokenizers to text to speech models.&lt;/p&gt;

&lt;p&gt;Then there's script. Hausa has been written in two systems for over a century: Boko, the Latin based script that's now standard, and Ajami, an Arabic based script still used in religious and literary contexts. A dataset built only from Boko sources is already excluding a real slice of the written language.&lt;/p&gt;

&lt;p&gt;Then code-switching. Everyday Hausa, especially online, mixes in English freely and carries a long history of Arabic loanwords from Islamic scholarly tradition. A model that treats Hausa as a clean, isolated language misses how the language is actually used in practice.&lt;/p&gt;

&lt;p&gt;Put those together and "low-resource language" starts to look less like "not enough Hausa exists" and more like "the Hausa that's easy to scrape is a degraded sample of the Hausa people actually speak." Standard multilingual tokenizers, trained on that same degraded sample, fragment Hausa words into far more subword pieces than an equivalent English sentence needs. That makes inference more expensive and generation noticeably worse, since the model has fewer clean examples to learn from per token.&lt;/p&gt;

&lt;p&gt;That's the actual problem. Not "AI doesn't know Hausa." AI has been fed a thin, flattened version of Hausa, and it shows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I actually started, and why not with an LLM
&lt;/h2&gt;

&lt;p&gt;I founded AI Bauchi in October 2022 out of Abubakar Tafawa Balewa University, with a mission that hasn't changed since day one: make AI indigenous and accessible in Bauchi State, not just consumed from outside it. The language work grew out of that mission, not the other way around.&lt;/p&gt;

&lt;p&gt;The tempting move, and the one I've watched well-funded initiatives make, is to announce a "Hausa LLM" on day one. I didn't do that, and I don't think a volunteer community should. A full generative language model needs enormous clean data and compute to be worth the name. What a community of volunteers with day jobs and a group chat actually has is time, native speaker knowledge, and the ability to ship something narrow and useful while the harder foundation gets built underneath it.&lt;/p&gt;

&lt;p&gt;So the sequence was deliberate. A Hausa text to speech model first, because it forces you to solve the orthography and pronunciation problem directly. A Hausa to Sayawa translator alongside it, because it forces the team to think about Hausa in relation to another indigenous language instead of only English. A media processing library, HausaMediaLab, that turns both into something a developer outside the project can actually pull in and use. Each of those was scoped to ship, not to impress. Each also produces cleaner labeled data as a byproduct, which is exactly the fuel a more ambitious language model eventually needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that's actually hard: building a team that isn't just engineers
&lt;/h2&gt;

&lt;p&gt;Here's the decision I'd defend hardest to another technical lead: the TTS project's contributors aren't only machine learning engineers. They're ML engineers, data scientists, linguists, and software developers, working together on purpose.&lt;/p&gt;

&lt;p&gt;That's not a diversity nicety, it's an engineering requirement. An ML engineer can build a text to speech pipeline. An ML engineer without a linguist on the team will get tone marking and hooked consonants subtly wrong in ways that are invisible to anyone who doesn't speak the language natively, and obvious to everyone who does.&lt;/p&gt;

&lt;p&gt;There's a second representation question underneath that one: which Hausa. The language spans real dialectal variation. The Kano variety often treated as a reference standard is not the only lived version of the language, and a single synthetic voice trained on one region's speech quietly tells everyone else their Hausa is the deviation. A linguist in the room is what catches that before it ships, not after.&lt;/p&gt;

&lt;p&gt;So the structure is: weekly progress meetings, native speakers reviewing linguistic decisions, engineers reviewing the model, and a public repo with issues explicitly tagged for newcomers so the contributor pipeline doesn't dry up when the founding few get busy. Alongside the language projects we run bootcamps, an eleven week AI bootcamp and a six week computer vision bootcamp, that exist specifically to grow that pipeline. Most contributors to the harder projects came up through one of those cohorts first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like outside the repository
&lt;/h2&gt;

&lt;p&gt;If I only talked about the model, I'd be underselling what actually moved. A few things I'd point to as the real impact:&lt;/p&gt;

&lt;p&gt;I spent six months in 2023 as an instructor for NITDA's AI Developer Training, which meant the ideas driving AI Bauchi's work weren't staying inside one community, they were reaching a national training cohort. At DevFest Bauchi 2023 I gave a talk called "Democratizing Indigenous AI," making the case to a room of general developers, most of whom had never thought about Hausa specifically, that inclusive AI development has to start by respecting the linguistic and cultural nuance of the communities it claims to serve. In 2025 we ran EmpowerHer AI Bauchi, built to get more young women into this pipeline early, because a community that only recruits people who already see themselves as engineers keeps producing the same blind spots. And we've taken this outside the university entirely, hosting secondary school students from I-Scholars International Academy for a STEM excursion at NCAIR, Nigeria's National Centre for Artificial Intelligence and Robotics, because the earliest point to fix "who gets to build this" is before someone's chosen a degree.&lt;/p&gt;

&lt;p&gt;None of that shows up in a commit history. All of it is why the commit history exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this sits next to the bigger picture
&lt;/h2&gt;

&lt;p&gt;In 2025, Nigeria's federal government unveiled its own multilingual model effort through NITDA and NCAIR, covering Hausa alongside Yoruba and Igbo. That's a good thing, and it's an enormous, well-funded validation of a problem AI Bauchi had already been working on, in public, since 2023.&lt;/p&gt;

&lt;p&gt;I don't think grassroots work like ours competes with that kind of national effort. I think it's what keeps it honest. A federally funded model can move fast and cover a lot of ground. A community-built one, with linguists in the room and native speakers reviewing every tone mark, is what makes sure the fast version doesn't flatten the same nuance the open web already flattened. Both are needed. I'd rather our work be the thing that keeps the bigger effort accountable than the thing racing it for a headline.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note to self before publishing:&lt;/strong&gt; I want this section grounded in exactly what I used, not a general gesture at the program. Naming the actual services, credits, or mentorship conversations that fed into this work is also the part AWS reviewers will check most closely.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What being an AWS Community Builder actually gave this
&lt;/h2&gt;

&lt;p&gt;The program's value to this work was never the swag. It was mentorship conversations with AWS specialists who'd already thought hard about deploying models into low connectivity, resource constrained environments, which is the exact condition Bauchi operates under, and credits that let a volunteer community run experiments it couldn't otherwise afford. S3, GPU training instances of Sagemaker&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell another builder trying this in their own community
&lt;/h2&gt;

&lt;p&gt;Start narrower than feels ambitious. A translator or a TTS model that ships is worth more than a language model that stays a roadmap item. Recruit people who aren't engineers before you think you need them, because by the time you obviously need a linguist, you've already shipped something linguistically wrong. Treat your constraints, low connectivity, volunteer time, no enterprise compute budget, as a design spec, not an excuse. Some of the most careful data hygiene decisions in this project exist specifically because we couldn't afford to throw compute at a sloppy dataset the way a better funded team might.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this goes next
&lt;/h2&gt;

&lt;p&gt;The text to speech model and the translator were never the destination. They were how a volunteer community earns the right to attempt something bigger, and that bigger thing is still the goal. If you're building language technology for a community that big tech has decided isn't worth the training data, or you're an AWS Community Builder working in a similarly underserved region or language, I'd genuinely like to compare notes. Drop a comment, or find AI Bauchi and say hello.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>opensource</category>
      <category>aws</category>
    </item>
    <item>
      <title>Cloud Robotics Development on AWS: Migrating from RoboMaker to Batch</title>
      <dc:creator>Nathaniel Handan</dc:creator>
      <pubDate>Thu, 18 Sep 2025 10:46:55 +0000</pubDate>
      <link>https://dev.to/tinnyrobot/cloud-robotics-development-on-aws-migrating-from-robomaker-to-batch-35gn</link>
      <guid>https://dev.to/tinnyrobot/cloud-robotics-development-on-aws-migrating-from-robomaker-to-batch-35gn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Traditional local robotics simulation (e.g. running Gazebo on a laptop or on-prem server) can be limited by hardware resources and parallelism. AWS &lt;strong&gt;RoboMaker&lt;/strong&gt; was launched to simplify cloud-based robotics development: it provided a fully managed service for ROS/Gazebo simulations, with built-in container images, random world generation (WorldForge), and integration with cloud services (e.g. Kinesis, CloudWatch) via ROS packages&lt;a href="https://aws.amazon.com/robomaker/resources/#:~:text=RoboMaker%20cloud%20extensions%20for%20ROS,in%20a%20familiar%20software%20framework" rel="noopener noreferrer"&gt;[1]&lt;/a&gt;. RoboMaker enabled automated scaling of compute for simulation workloads (“fully managed…scales underlying infrastructure”&lt;a href="https://aws.amazon.com/blogs/robotics/introducing-batch-simulation-api-for-aws-robomaker/#:~:text=AWS%20RoboMaker%20provides%20a%20fully,the%20use%20cases%20above%20is" rel="noopener noreferrer"&gt;[2]&lt;/a&gt;) and offered both headless and GUI (NICE DCV) simulation modes. However, AWS has announced that &lt;strong&gt;RoboMaker will be discontinued on September 10, 2025&lt;/strong&gt;&lt;a href="https://docs.aws.amazon.com/robomaker/latest/dg/chapter-support-policy.html#:~:text=End%20of%20support%20notice%3A%20On,simulations%2C%20visit%20this%20blog%20post" rel="noopener noreferrer"&gt;[3]&lt;/a&gt;. After that date, the RoboMaker console and APIs will no longer be available, and all simulation workloads must be migrated to alternatives – principally &lt;strong&gt;AWS Batch&lt;/strong&gt;, AWS’s general-purpose batch compute service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcx13un3gisbsfmfttzt9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcx13un3gisbsfmfttzt9.png" alt=" " width="643" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Figure: Example RoboMaker simulation architecture – two containers (a simulation app with NVIDIA Isaac Sim and a robot app with ROS2 Navigation) orchestrated by AWS RoboMaker, pulling images from Amazon ECR and outputting logs to CloudWatch.&lt;/p&gt;

&lt;p&gt;AWS Batch provides a container-based, highly scalable batch job scheduler. In contrast to RoboMaker’s robotics-specific interface, Batch requires users to define their own Docker images and compute environments, but offers flexibility and massive scale (including multi-container and multi-node parallel jobs). As AWS notes, &lt;strong&gt;“AWS Batch is best used for running headless batch simulations at scale”&lt;/strong&gt;, whereas interactive GUI simulations were a RoboMaker feature&lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=Note%20that%20AWS%20Batch%20is,we%20recommend%20AWS%20RoboMaker%20simulation" rel="noopener noreferrer"&gt;[5]&lt;/a&gt;. In practice, migrating to Batch means converting RoboMaker simulation workflows into a containerized CI/CD pipeline on Batch: containerize ROS/Gazebo apps, push images to ECR, define Batch compute environments and jobs, and orchestrate execution. This guide lays out the detailed migration path, compares features of RoboMaker vs Batch, and provides examples and best practices for setting up cloud robotics simulation on AWS Batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RoboMaker sunset:&lt;/strong&gt; AWS RoboMaker ends Sep 2025&lt;a href="https://docs.aws.amazon.com/robomaker/latest/dg/chapter-support-policy.html#:~:text=End%20of%20support%20notice%3A%20On,simulations%2C%20visit%20this%20blog%20post" rel="noopener noreferrer"&gt;[3]&lt;/a&gt;. All existing simulation apps, worlds, and jobs must be migrated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategy:&lt;/strong&gt; Move to AWS Batch: export any RoboMaker-generated assets (worlds, models), containerize your robot and simulation code (ROS2 Humble + Gazebo + TurtleBot3) into Docker images, push to ECR, then create AWS Batch compute environments, job queues, and job definitions to run those containers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architectural shift:&lt;/strong&gt; RoboMaker managed infrastructure and had built-in simulation tools; Batch is generic compute. You lose integrated features (e.g. worldforge GUI, on-demand development IDE) but gain control of instance types, unlimited scaling, and cost optimization (no Batch service fee&lt;a href="https://aws.amazon.com/batch/pricing/#:~:text=There%20is%20no%20additional%20charge,Discounts%20will" rel="noopener noreferrer"&gt;[6]&lt;/a&gt;, Spot instances, multi-node jobs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step-by-step:&lt;/strong&gt; (1) Export worlds/models from RoboMaker (via WorldForge ZIP to S3)&lt;a href="https://docs.aws.amazon.com/robomaker/latest/dg/worlds-using-export-simulation.html#:~:text=With%20Simulation%20WorldForge%2C%20you%20can,an%20exported%20world%20in%20an" rel="noopener noreferrer"&gt;[7]&lt;/a&gt;. (2) Build Docker images for your robot app and sim app (use &lt;code&gt;osrf/ros:humble-desktop&lt;/code&gt; or Ubuntu base, install TurtleBot3 packages and Gazebo). (3) Push images to ECR. (4) Set up AWS Batch: create a Compute Environment (choose EC2/Spot instances for CPU/GPU), a Job Queue linked to it, and a Job Definition specifying the Docker image, vCPU/memory, commands, and environment variables. (5) Submit and run Batch jobs (single-node or multi-node as needed) to execute your simulations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost &amp;amp; scaling:&lt;/strong&gt; Unlike RoboMaker’s per-job pricing model, AWS Batch has &lt;em&gt;no extra charge&lt;/em&gt; – you pay only for EC2 (or Fargate) resources&lt;a href="https://aws.amazon.com/batch/pricing/#:~:text=There%20is%20no%20additional%20charge,Discounts%20will" rel="noopener noreferrer"&gt;[6]&lt;/a&gt;. Use Spot for cheaper compute. Batch easily scales to hundreds or thousands of concurrent jobs (RoboMaker was limited to 10 concurrent sims by default&lt;a href="https://aws.amazon.com/blogs/robotics/introducing-batch-simulation-api-for-aws-robomaker/#:~:text=,an%20increase%20in%20this%20limit" rel="noopener noreferrer"&gt;[8]&lt;/a&gt;). Headless (non-GUI) simulations run on plain CPU/GPU instances; GUI/CDV sessions (rare in Batch) require custom setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring &amp;amp; automation:&lt;/strong&gt; Continue using CloudWatch for logs and metrics (e.g. ROS position/sensor data stream)&lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=Amazon%20CloudWatch%20robot%20monitoring%20sample,the%20robot%E2%80%99s%20position%20and%20speed" rel="noopener noreferrer"&gt;[9]&lt;/a&gt;. Store inputs/outputs and world files in S3. You can orchestrate workflows with AWS Step Functions or CodePipeline, similarly to RoboMaker pipelines&lt;a href="https://aws.amazon.com/robomaker/resources/#:~:text=Launch%20in%20GitHub" rel="noopener noreferrer"&gt;[10]&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Feature &amp;amp; Architecture Comparison: RoboMaker vs AWS Batch
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Service model:&lt;/strong&gt; RoboMaker is a specialized managed robotics service. It abstracts away servers and networks, providing a GUI for launching simulation jobs&lt;a href="https://aws.amazon.com/blogs/robotics/introducing-batch-simulation-api-for-aws-robomaker/#:~:text=AWS%20RoboMaker%20provides%20a%20fully,the%20use%20cases%20above%20is" rel="noopener noreferrer"&gt;[2]&lt;/a&gt;. Batch is a generic container batch scheduler (built on ECS/EKS and EC2). You &lt;strong&gt;must&lt;/strong&gt; provision Compute Environments and define job resources yourself. RoboMaker auto-creates instances behind the scenes; Batch lets you control instance types (CPU vs GPU, on-demand vs Spot) and autoscaling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workload support:&lt;/strong&gt; RoboMaker is built for ROS/Gazebo robot simulation. It used to offer prebuilt Docker images for ROS1/2 and Gazebo (though now customers must supply container images)&lt;a href="https://docs.aws.amazon.com/robomaker/latest/dg/chapter-support-policy.html#:~:text=End%20of%20support%20notice%3A%20On,simulations%2C%20visit%20this%20blog%20post" rel="noopener noreferrer"&gt;[3]&lt;/a&gt;&lt;a href="https://docs.aws.amazon.com/pdfs/robomaker/latest/dg/aws-robomaker-dg.pdf#:~:text=AWS%20RoboMaker%20Developer%20Guide%20End,xi" rel="noopener noreferrer"&gt;[11]&lt;/a&gt;. It introduced concepts of &lt;em&gt;Robot applications&lt;/em&gt; (the code running on a robot) and &lt;em&gt;Simulation applications&lt;/em&gt; (the Gazebo world and scenario). AWS Batch can run &lt;strong&gt;any&lt;/strong&gt; Linux container. You can build ROS2/Gazebo containers and run them on Batch, but there is no built-in “robot vs simulation app” distinction unless you implement it (e.g. separate containers or processes). Notably, AWS Batch now supports &lt;strong&gt;multi-container jobs&lt;/strong&gt; (started Apr 2024)&lt;a href="https://aws.amazon.com/blogs/hpc/run-simulations-using-multiple-containers-in-a-single-aws-batch-job/#:~:text=Recently%2C%20AWS%20Batch%20launched%20a,collaboration%2C%20and%20other%20advanced%20simulations" rel="noopener noreferrer"&gt;[12]&lt;/a&gt;: you can run multiple cooperating containers (e.g. one for Gazebo sim, others for different robot sensor pipelines) within a single Batch job. This enables modular simulation setups similar to RoboMaker’s multi-robot examples (see figure below)&lt;a href="https://aws.amazon.com/blogs/hpc/run-simulations-using-multiple-containers-in-a-single-aws-batch-job/#:~:text=runner%2C%20the%20simulation%20app%2C%20and,lidar%2C%20vision%20processing%20and%20control" rel="noopener noreferrer"&gt;[13]&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability:&lt;/strong&gt; RoboMaker allowed batching many simulation jobs via the BatchSimulation API, but had limits (default max 10 concurrent jobs)&lt;a href="https://aws.amazon.com/blogs/robotics/introducing-batch-simulation-api-for-aws-robomaker/#:~:text=,an%20increase%20in%20this%20limit" rel="noopener noreferrer"&gt;[8]&lt;/a&gt;. It scaled each job’s infrastructure automatically, but overall batch size was constrained by quotas. AWS Batch scales to as many parallel jobs as you request (limited only by account quotas and budget). It also supports &lt;strong&gt;multi-node parallel jobs&lt;/strong&gt; for distributed simulations (for example, spread 4 robots across 4 EC2 nodes). In practice, Batch can run hundreds or thousands of simulations in parallel easily. RoboMaker’s underlying hardware limits were lower and fixed per job (e.g. one c5.2xlarge per simulation, 4 vCPU/32GB by default). On Batch you choose any instance sizes you need (e.g. high-CPU c6i, GPU g4dn, or even the new inf series), and use Spot capacity for cost efficiency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless vs GUI:&lt;/strong&gt; RoboMaker supported both headless (no X-windows) and interactive GUI simulations via NICE DCV, which allowed streaming the Gazebo/ROS GUI remotely. AWS Batch is designed for &lt;strong&gt;headless&lt;/strong&gt; batch processing&lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=Note%20that%20AWS%20Batch%20is,we%20recommend%20AWS%20RoboMaker%20simulation" rel="noopener noreferrer"&gt;[5]&lt;/a&gt;. It can run GUI apps in containers (you can even run NICE DCV servers on GPU instances under Batch), but it doesn’t provide the interactive console integration that RoboMaker did. In migration you will likely run Gazebo in headless mode (gzserver only) and collect data/metrics, rather than interact with a live 3D view. Batch is optimized for “thousands of scenarios” at once&lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=Headless%20robotic%20simulations%20with%20AWS,what%20compute%20types%20to%20use" rel="noopener noreferrer"&gt;[14]&lt;/a&gt;, not manual GUI sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud services integration:&lt;/strong&gt; RoboMaker included ROS cloud extensions out of the box: packages for streaming video (Kinesis Video Streams), vision inference (Rekognition), voice (Lex/Polly), and metrics/logging (CloudWatch)&lt;a href="https://aws.amazon.com/robomaker/resources/#:~:text=RoboMaker%20cloud%20extensions%20for%20ROS,in%20a%20familiar%20software%20framework" rel="noopener noreferrer"&gt;[1]&lt;/a&gt;. These let robot code easily call AWS services. In AWS Batch, you lose that built-in convenience layer, but you &lt;em&gt;can&lt;/em&gt; still use the same AWS services by invoking SDKs or passing through Step Functions. For example, you can publish ROS data to CloudWatch logs or metrics exactly as before&lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=Amazon%20CloudWatch%20robot%20monitoring%20sample,the%20robot%E2%80%99s%20position%20and%20speed" rel="noopener noreferrer"&gt;[9]&lt;/a&gt;, just handled by your container code. Batch itself does not impose an additional fee – you pay only for the EC2/Fargate resources you use&lt;a href="https://aws.amazon.com/batch/pricing/#:~:text=There%20is%20no%20additional%20charge,Discounts%20will" rel="noopener noreferrer"&gt;[6]&lt;/a&gt;, whereas RoboMaker charged by simulation time. Batch supports IAM roles for tasks, VPC networking, and can integrate with S3, EFS, CloudWatch, Step Functions, etc., just like any other AWS compute environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Migration Checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Export simulation assets:&lt;/strong&gt; Use RoboMaker’s WorldForge export or manual methods to extract any custom world files, models, or assets you used. RoboMaker can package generated worlds into a ZIP and drop it in S3&lt;a href="https://docs.aws.amazon.com/robomaker/latest/dg/worlds-using-export-simulation.html#:~:text=With%20Simulation%20WorldForge%2C%20you%20can,an%20exported%20world%20in%20an" rel="noopener noreferrer"&gt;[7]&lt;/a&gt;. Download these files or point to their S3 locations for use in your Docker images or batch jobs. Likewise, retrieve any logs or output data from past RoboMaker runs for reference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containerize applications:&lt;/strong&gt; Build Docker images for your robot and simulation code. For example, start from an official ROS2 Humble base (e.g. &lt;code&gt;osrf/ros:humble-desktop&lt;/code&gt;), install Gazebo (Fortress) and TurtleBot3 packages (&lt;code&gt;ros-humble-turtlebot3&lt;/code&gt;*), and copy your ROS workspace in. A sample Dockerfile snippet might look like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ros:humble-ros-base&lt;/span&gt;

&lt;span class="c"&gt;# Set environment variables&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; ROS_DISTRO=humble&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; TURTLEBOT3_MODEL=burger&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    ros-humble-desktop &lt;span class="se"&gt;\
&lt;/span&gt;    ros-humble-turtlebot3-gazebo &lt;span class="se"&gt;\
&lt;/span&gt;    ros-humble-turtlebot3-description &lt;span class="se"&gt;\
&lt;/span&gt;    ros-humble-gazebo-ros-pkgs &lt;span class="se"&gt;\
&lt;/span&gt;    gazebo11 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Copy your workspace&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./simulation_ws /home/ros_ws&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /home/ros_ws&lt;/span&gt;

&lt;span class="c"&gt;# Build the workspace&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;/bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"source /opt/ros/&lt;/span&gt;&lt;span class="nv"&gt;$ROS_DISTRO&lt;/span&gt;&lt;span class="s2"&gt;/setup.bash &amp;amp;&amp;amp; colcon build"&lt;/span&gt;

&lt;span class="c"&gt;# Source both ROS and workspace overlays&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/bin/bash", "-c", \&lt;/span&gt;
    "source /opt/ros/$ROS_DISTRO/setup.bash &amp;amp;&amp;amp; \
     source /home/ros_ws/install/setup.bash &amp;amp;&amp;amp; \
     ros2 launch my_sim_pkg simulate.launch.py"]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Modify the &lt;code&gt;ENTRYPOINT&lt;/code&gt; or &lt;code&gt;CMD&lt;/code&gt; to start your simulation (headless). You can create separate images for the robot-app and sim-app if needed, but with Batch’s multi-container support you can also launch multiple containers from one job. Push these images to Amazon ECR (or any container registry)&lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=2,app%60%20respectively" rel="noopener noreferrer"&gt;[15]&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prepare Batch environment:&lt;/strong&gt; In the AWS Console (or via CloudFormation/CLI), create a &lt;strong&gt;Compute Environment&lt;/strong&gt; for AWS Batch. Choose EC2 (or Fargate) and select instance types needed (e.g., c6i.large for CPU sims, &lt;code&gt;g4dn.xlarge&lt;/code&gt; for GPU-enabled Gazebo). Consider using Spot instances for cost savings, since simulation workloads are interruptible. Attach an IAM instance role that allows pulling from ECR and writing to S3/CloudWatch. Create a &lt;strong&gt;Job Queue&lt;/strong&gt; and associate it with this compute environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define Batch jobs:&lt;/strong&gt; Create an AWS Batch &lt;strong&gt;Job Definition&lt;/strong&gt;. Specify &lt;code&gt;"type": "container"&lt;/code&gt; and in &lt;code&gt;containerProperties&lt;/code&gt; include: your Docker image URI (from ECR), vCPU count and memory, and the command to run (e.g. [&lt;code&gt;"ros2"&lt;/code&gt;, &lt;code&gt;"launch"&lt;/code&gt;, &lt;code&gt;"my_sim_pkg"&lt;/code&gt;, &lt;code&gt;"simulate.launch.py"&lt;/code&gt;]). Add any environment variables (e.g. &lt;code&gt;TURTLEBOT3_MODEL=burger&lt;/code&gt;). For example:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobDefinitionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"turtlebot3-sim-job"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"container"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"containerProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456789012.dkr.ecr.us-west-2.amazonaws.com/turtlebot3-sim:latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"vcpus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TURTLEBOT3_MODEL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"burger"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jobRoleArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:role/BatchJobRole"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Use the AWS Batch console or &lt;code&gt;RegisterJobDefinition&lt;/code&gt; API. Ensure the &lt;code&gt;jobRoleArn&lt;/code&gt; has permissions for any AWS calls (CloudWatch, S3). If your simulation needs shared storage (e.g. large maps or logging folders), you can configure EFS volumes in the job or let the container fetch from S3 at startup.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Multi-node or multi-container (optional):&lt;/strong&gt; For advanced scenarios (e.g. multi-robot swarms or separate sensor modules), use Batch’s &lt;strong&gt;Multi-Node Parallel (MNP)&lt;/strong&gt; jobs or multi-container job feature. MNP allows you to run a single job across multiple EC2 instances. Multi-container jobs (containers sharing a local network on the same node) enable separate processes for e.g. Gazebo, lidar processing, control, etc. This was not possible in RoboMaker (which ran all code in one container), but Batch now supports it&lt;a href="https://aws.amazon.com/blogs/hpc/run-simulations-using-multiple-containers-in-a-single-aws-batch-job/#:~:text=Recently%2C%20AWS%20Batch%20launched%20a,collaboration%2C%20and%20other%20advanced%20simulations" rel="noopener noreferrer"&gt;[12]&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring &amp;amp; logs:&lt;/strong&gt; Configure your containers to send output to STDOUT/STDERR so AWS Batch will push logs to CloudWatch Logs by default. For more detailed robot metrics, use ROS CloudWatch packages – e.g. send robot positions or sensor stats to CloudWatch Metrics as in the AWS sample&lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=Amazon%20CloudWatch%20robot%20monitoring%20sample,the%20robot%E2%80%99s%20position%20and%20speed" rel="noopener noreferrer"&gt;[9]&lt;/a&gt;. Set up a CloudWatch log group for your batch jobs. (RoboMaker automatically aggregated logs, but in Batch you manage the logging setup.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run and iterate:&lt;/strong&gt; Submit a Batch job (via Console, AWS CLI &lt;code&gt;submit-job&lt;/code&gt;, or through Step Functions). Watch CloudWatch for logs to debug. Tweak your Docker image or job definition if needed. Once a single robot sim works, you can scale up: run many Batch jobs in parallel (AWS Batch will queue them and launch on available EC2 instances).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automate workflows:&lt;/strong&gt; To orchestrate batch simulations (for example, sweeping over parameters or training RL policies), use AWS Step Functions or CodePipeline. AWS even published a RoboMaker &lt;em&gt;Sample&lt;/em&gt; showing a pipeline for launching simulation batches&lt;a href="https://aws.amazon.com/robomaker/resources/#:~:text=Launch%20in%20GitHub" rel="noopener noreferrer"&gt;[10]&lt;/a&gt; – you can adapt this by having Step Functions call &lt;code&gt;SubmitJob&lt;/code&gt; on AWS Batch instead. For CI/CD, put your Docker build and Batch job submission into a CodePipeline or GitHub Actions workflow.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cost and Performance Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pricing model:&lt;/strong&gt; AWS Batch itself is free – you only pay for the EC2/Fargate resources you use&lt;a href="https://aws.amazon.com/batch/pricing/#:~:text=There%20is%20no%20additional%20charge,Discounts%20will" rel="noopener noreferrer"&gt;[6]&lt;/a&gt;. RoboMaker charged per simulation-second at fixed hardware size, which could be expensive for large batches. With Batch, you can use Spot Instances for dramatic savings (up to 90% off) on fault-tolerant sims. You also control instance size: e.g. use 4 vCPU machines for a light TurtleBot sim, or 32 vCPU/8 GPU instances for a heavy physics simulation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling:&lt;/strong&gt; RoboMaker allowed parallel simulation but was capped by quotas (default 10 concurrent jobs&lt;a href="https://aws.amazon.com/blogs/robotics/introducing-batch-simulation-api-for-aws-robomaker/#:~:text=,an%20increase%20in%20this%20limit" rel="noopener noreferrer"&gt;[8]&lt;/a&gt;) and by its internal resource limits. AWS Batch can scale to hundreds of parallel jobs as long as you request it; there’s no per-job vCPU cap. Batch’s multi-node jobs also let you simulate many robots or heavy compute tasks in one coordinated job. For example, you could run a 4-robot warehouse sim on a 4-node MNP job (one robot per EC2) – something RoboMaker could not natively do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless vs GUI:&lt;/strong&gt; AWS Batch is designed for &lt;strong&gt;headless&lt;/strong&gt; processing. If you need a real-time GUI (NICE DCV) you must provision GPU instances with desktop streaming yourself. In most robotics dev, you run Gazebo headless and record data for later analysis. RoboMaker’s ease of GUI was convenient for debugging, but came at higher cost (always requiring at least one GPU server). On Batch, you would typically run the same Gazebo world without gzclient.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throughput &amp;amp; latency:&lt;/strong&gt; On similar instance types, raw simulation performance is comparable between RoboMaker and Batch (both run on AWS EC2 under the hood). However, Batch can batch-launch many instances quickly, whereas RoboMaker needed to spin up each sim job separately. This means large experiments (e.g. 1000 Monte Carlo sim runs) will complete faster on Batch. RoboMaker also introduced a concept of “simulation unit” (1 unit = 1 vCPU &amp;amp; 2GB RAM) to rate-limit usage, which Batch does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost example:&lt;/strong&gt; If you run a 4 vCPU/16GB sim for 1 hour, RoboMaker billed a fixed rate for that instance-hour. On AWS Batch you’d pay the on-demand (or Spot) EC2 rate for 4 vCPU for 1 hour. Using Spot can cut cost greatly for non-time-critical batches. Also, Batch allows scheduling interruptions or spot bids, whereas RoboMaker simply ran until the job finished or hit a limit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example AWS Batch Job Definition and Dockerfiles
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Example Dockerfile (ROS2 Humble + Gazebo + TurtleBot3):&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; osrf/ros:humble-desktop&lt;/span&gt;

&lt;span class="c"&gt;# Set environment variables&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; ROS_DISTRO=humble&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; TURTLEBOT3_MODEL=burger&lt;/span&gt;

&lt;span class="c"&gt;# Install Gazebo and TurtleBot3 packages&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    ros-humble-turtlebot3-bringup &lt;span class="se"&gt;\
&lt;/span&gt;    ros-humble-turtlebot3-gazebo &lt;span class="se"&gt;\
&lt;/span&gt;    ros-humble-gazebo-ros-pkgs &lt;span class="se"&gt;\
&lt;/span&gt;    gazebo11 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Copy simulation workspace&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./simulation_ws /home/ros_ws&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /home/ros_ws&lt;/span&gt;

&lt;span class="c"&gt;# Build the workspace&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;/bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"source /opt/ros/&lt;/span&gt;&lt;span class="nv"&gt;$ROS_DISTRO&lt;/span&gt;&lt;span class="s2"&gt;/setup.bash &amp;amp;&amp;amp; colcon build --symlink-install"&lt;/span&gt;

&lt;span class="c"&gt;# Create entrypoint script&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/bash\n&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="s1"&gt;set -e\n&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="s1"&gt;source /opt/ros/$ROS_DISTRO/setup.bash\n&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="s1"&gt;source /home/ros_ws/install/setup.bash\n&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="s1"&gt;exec "$@"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /ros_entrypoint.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; +x /ros_entrypoint.sh

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/ros_entrypoint.sh"]&lt;/span&gt;

&lt;span class="c"&gt;# Default command: launch TurtleBot3 world&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["ros2", "launch", "turtlebot3_gazebo", "turtlebot3_world.launch.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This image will launch a Gazebo world (&lt;code&gt;turtlebot3_world.launch.py&lt;/code&gt;) with TurtleBot3. Customize the workspace and launch files as needed. Build and push this image to ECR with a command like: &lt;code&gt;docker build -t turtlebot3-sim:latest .&lt;/code&gt; then &lt;code&gt;docker tag&lt;/code&gt; and &lt;code&gt;aws ecr create-repository&lt;/code&gt; &amp;amp; &lt;code&gt;aws ecr get-login-password&lt;/code&gt; to push.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example AWS Batch Job Definition (JSON):&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobDefinitionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TurtleBot3SimulationJob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"container"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"containerProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456789012.dkr.ecr.us-west-2.amazonaws.com/turtlebot3-sim:latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"vcpus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TURTLEBOT3_MODEL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"burger"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jobRoleArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:role/BatchJobRole"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we allocate 4 vCPUs and 16 GB RAM for the sim, and pass the model type via env. Adjust vcpus/memory based on your workload. The &lt;code&gt;jobRoleArn&lt;/code&gt; should grant permissions to read/write any needed AWS resources (e.g. an S3 bucket for world files or log output, and CloudWatch access).&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring, Storage, and Automation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Monitoring:&lt;/strong&gt; Instrument your ROS nodes to publish metrics or log to CloudWatch as needed. AWS provides ROS &lt;em&gt;CloudWatch&lt;/em&gt; packages (as in the sample) so you can push robot state (position, velocity) and key events to CloudWatch Metrics or Logs&lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=Amazon%20CloudWatch%20robot%20monitoring%20sample,the%20robot%E2%80%99s%20position%20and%20speed" rel="noopener noreferrer"&gt;[9]&lt;/a&gt;. Since Batch runs on ECS underneath, each container’s STDOUT goes to CloudWatch Logs automatically, which you can view in real time for debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage (S3/EFS):&lt;/strong&gt; Use Amazon S3 for storing large assets: keep your exported world ZIPs and static models in an S3 bucket and download them at container startup (or bake them into the Docker image). Simulation outputs (e.g. rosbag files, CSV data) can be written back to S3 at job completion. For shared file-system needs (e.g. sharing a map or checkpoints across multiple Batch job runs), you can mount an Amazon EFS volume in your job definition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration (Step Functions/CodePipeline):&lt;/strong&gt; For complex workflows (e.g. run simulation sweep, then train ML model on results, then run more sims), use AWS Step Functions to coordinate AWS Batch jobs. Step Functions has native support to start Batch jobs and wait for completion. AWS also provides sample pipelines (e.g. RoboMaker “Simulation launcher” using CodePipeline + Step Functions)&lt;a href="https://aws.amazon.com/robomaker/resources/#:~:text=Launch%20in%20GitHub" rel="noopener noreferrer"&gt;[10]&lt;/a&gt; – you can adapt these so that Step Functions calls Batch instead of RoboMaker APIs. For example, a Step Function state machine could have a “Submit Simulation Job” task (using Action=Batch:SubmitJob) followed by a “Wait for Job” and condition based on job success. Similarly, CodePipeline can trigger a new Docker build and then invoke a Batch job via a Lambda step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging &amp;amp; Alerts:&lt;/strong&gt; Tag your Batch jobs with meaningful tags and set up CloudWatch alarms on metrics (e.g. if jobs are failing or if S3 buckets grow). You can also export Batch job metrics (queued/running counts) to CloudWatch for dashboards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By following these steps &lt;br&gt;
– exporting your assets, containerizing your apps, setting up AWS Batch compute and jobs, and integrating logging/automation &lt;br&gt;
– you can replicate and extend your RoboMaker simulations on AWS Batch. &lt;/p&gt;

&lt;p&gt;Although the transition requires more setup, Batch gives you much greater flexibility and cost control for large-scale robotics workloads. Leveraging Batch’s multi-node and multi-container capabilities (see figure below) lets you simulate multi-robot systems at scale, and the AWS ecosystem (CloudWatch, S3, Step Functions) provides all the building blocks for a robust CI/CD pipeline for robotics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5kdkz34e7lisxmul0gh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5kdkz34e7lisxmul0gh.png" alt=" " width="799" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure: Example AWS Batch multi-robot simulation. A single AWS Batch multi-node job runs one test-scenario container and one Gazebo simulator container on the main node (Node 1), and four robot stacks (Nodes 2–5), each with separate Lidar, control, and vision containers&lt;/em&gt;&lt;a href="https://aws.amazon.com/blogs/hpc/run-simulations-using-multiple-containers-in-a-single-aws-batch-job/#:~:text=runner%2C%20the%20simulation%20app%2C%20and,lidar%2C%20vision%20processing%20and%20control" rel="noopener noreferrer"&gt;&lt;em&gt;[13]&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. AWS Batch’s multi-container and multi-node features enable complex simulations at scale.&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt; AWS documentation and blogs on RoboMaker deprecation and AWS Batch (including AWS Robotics and HPC blogs)&lt;a href="https://docs.aws.amazon.com/robomaker/latest/dg/chapter-support-policy.html#:~:text=End%20of%20support%20notice%3A%20On,simulations%2C%20visit%20this%20blog%20post" rel="noopener noreferrer"&gt;[3]&lt;/a&gt;&lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=Headless%20robotic%20simulations%20with%20AWS,what%20compute%20types%20to%20use" rel="noopener noreferrer"&gt;[14]&lt;/a&gt;&lt;a href="https://aws.amazon.com/blogs/hpc/run-simulations-using-multiple-containers-in-a-single-aws-batch-job/#:~:text=Recently%2C%20AWS%20Batch%20launched%20a,collaboration%2C%20and%20other%20advanced%20simulations" rel="noopener noreferrer"&gt;[12]&lt;/a&gt;&lt;a href="https://aws.amazon.com/robomaker/resources/#:~:text=RoboMaker%20cloud%20extensions%20for%20ROS,in%20a%20familiar%20software%20framework" rel="noopener noreferrer"&gt;[1]&lt;/a&gt;&lt;a href="https://docs.aws.amazon.com/robomaker/latest/dg/worlds-using-export-simulation.html#:~:text=With%20Simulation%20WorldForge%2C%20you%20can,an%20exported%20world%20in%20an" rel="noopener noreferrer"&gt;[7]&lt;/a&gt;&lt;a href="https://aws.amazon.com/batch/pricing/#:~:text=There%20is%20no%20additional%20charge,Discounts%20will" rel="noopener noreferrer"&gt;[6]&lt;/a&gt;&lt;a href="https://aws.amazon.com/blogs/robotics/introducing-batch-simulation-api-for-aws-robomaker/#:~:text=,an%20increase%20in%20this%20limit" rel="noopener noreferrer"&gt;[8]&lt;/a&gt;. These sources provided official information on service features, pricing, and migration guidance. All code examples and figures are illustrative.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/robomaker/resources/#:~:text=RoboMaker%20cloud%20extensions%20for%20ROS,in%20a%20familiar%20software%20framework" rel="noopener noreferrer"&gt;[1]&lt;/a&gt; &lt;a href="https://aws.amazon.com/robomaker/resources/#:~:text=Launch%20in%20GitHub" rel="noopener noreferrer"&gt;[10]&lt;/a&gt; AWS RoboMaker Resources – Amazon Web Services&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/robomaker/resources/" rel="noopener noreferrer"&gt;https://aws.amazon.com/robomaker/resources/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/robotics/introducing-batch-simulation-api-for-aws-robomaker/#:~:text=AWS%20RoboMaker%20provides%20a%20fully,the%20use%20cases%20above%20is" rel="noopener noreferrer"&gt;[2]&lt;/a&gt; &lt;a href="https://aws.amazon.com/blogs/robotics/introducing-batch-simulation-api-for-aws-robomaker/#:~:text=,an%20increase%20in%20this%20limit" rel="noopener noreferrer"&gt;[8]&lt;/a&gt; Introducing Batch Simulation API for AWS RoboMaker | AWS Robotics Blog&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/robotics/introducing-batch-simulation-api-for-aws-robomaker/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/robotics/introducing-batch-simulation-api-for-aws-robomaker/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/robomaker/latest/dg/chapter-support-policy.html#:~:text=End%20of%20support%20notice%3A%20On,simulations%2C%20visit%20this%20blog%20post" rel="noopener noreferrer"&gt;[3]&lt;/a&gt; Support policy - AWS RoboMaker&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/robomaker/latest/dg/chapter-support-policy.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/robomaker/latest/dg/chapter-support-policy.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/iot/orchestrate-nvidia-isaac-sim-ros-2-navigation-aws-robomaker-public-container-image/#:~:text=architecture%20diagram%20depicting%20how%20the,AWS%20RoboMaker%20in%20Amazon%20CloudWatch" rel="noopener noreferrer"&gt;[4]&lt;/a&gt; Orchestrate NVIDIA Isaac Sim and ROS 2 Navigation on AWS RoboMaker with a public container image | The Internet of Things on AWS – Official Blog&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/iot/orchestrate-nvidia-isaac-sim-ros-2-navigation-aws-robomaker-public-container-image/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/iot/orchestrate-nvidia-isaac-sim-ros-2-navigation-aws-robomaker-public-container-image/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=Note%20that%20AWS%20Batch%20is,we%20recommend%20AWS%20RoboMaker%20simulation" rel="noopener noreferrer"&gt;[5]&lt;/a&gt; &lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=Amazon%20CloudWatch%20robot%20monitoring%20sample,the%20robot%E2%80%99s%20position%20and%20speed" rel="noopener noreferrer"&gt;[9]&lt;/a&gt; &lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=Headless%20robotic%20simulations%20with%20AWS,what%20compute%20types%20to%20use" rel="noopener noreferrer"&gt;[14]&lt;/a&gt; &lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/#:~:text=2,app%60%20respectively" rel="noopener noreferrer"&gt;[15]&lt;/a&gt; Build headless robotic simulations with AWS Batch | AWS Robotics Blog&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/robotics/build-headless-robotic-simulations-with-aws-batch/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/batch/pricing/#:~:text=There%20is%20no%20additional%20charge,Discounts%20will" rel="noopener noreferrer"&gt;[6]&lt;/a&gt; AWS Batch Pricing&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/batch/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/batch/pricing/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/robomaker/latest/dg/worlds-using-export-simulation.html#:~:text=With%20Simulation%20WorldForge%2C%20you%20can,an%20exported%20world%20in%20an" rel="noopener noreferrer"&gt;[7]&lt;/a&gt; Using exported worlds in simulation - AWS RoboMaker&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/robomaker/latest/dg/worlds-using-export-simulation.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/robomaker/latest/dg/worlds-using-export-simulation.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/pdfs/robomaker/latest/dg/aws-robomaker-dg.pdf#:~:text=AWS%20RoboMaker%20Developer%20Guide%20End,xi" rel="noopener noreferrer"&gt;[11]&lt;/a&gt; AWS RoboMaker - Developer Guide&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/pdfs/robomaker/latest/dg/aws-robomaker-dg.pdf" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/pdfs/robomaker/latest/dg/aws-robomaker-dg.pdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/hpc/run-simulations-using-multiple-containers-in-a-single-aws-batch-job/#:~:text=Recently%2C%20AWS%20Batch%20launched%20a,collaboration%2C%20and%20other%20advanced%20simulations" rel="noopener noreferrer"&gt;[12]&lt;/a&gt; &lt;a href="https://aws.amazon.com/blogs/hpc/run-simulations-using-multiple-containers-in-a-single-aws-batch-job/#:~:text=runner%2C%20the%20simulation%20app%2C%20and,lidar%2C%20vision%20processing%20and%20control" rel="noopener noreferrer"&gt;[13]&lt;/a&gt; Run simulations using multiple containers in a single AWS Batch job | AWS HPC Blog&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/hpc/run-simulations-using-multiple-containers-in-a-single-aws-batch-job/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/hpc/run-simulations-using-multiple-containers-in-a-single-aws-batch-job/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>robotics</category>
      <category>rpa</category>
      <category>ros2</category>
      <category>robomaker</category>
    </item>
  </channel>
</rss>
