View on GitHub

ece4960-fast-robots

Lab Reports and Assignments for ECE 4960: Fast Robots

Lab 10: Simulator

Click here to return to home page

Objective

Become familar with the robot simulator to do some basic tasks (and more advanced ones later)

Setup / Installation

I’m running macOS Monterey on an intel-based Mac and nearly everything worked smoothly. The only strange issue I had with the simulator was that running the Jupyter notebooks in Visual Studio Code like I usually do no longer works:

option error in jupyter starter

Although I wasn’t able to use VSCode like I usually do, I simply started the notebook with the jupyter notebook command in the new, activated virtual environment to complete the lab.

As instructed, all of my lab notebooks are in the notebooks/ directory. You can find all of my lab code on my Github repo. The notebooks are the only thing that had to be modified for this lab.

Task 1: Simulator Description

The simulator includes a GUI to help visualize a robot moving on a map like the one in class and plot where the robot is based on different data sources.

It has some arrow-key controls that alter the linear and angular velocities of the robot by a small step size. I used them to test out how the robot moves:

arrow key driving

I did the same thing with the plotter open in order to see how the plots looked:

plotter demo

The plotter has features to plot both the ground-truth position (green) and the odometry-based position (red). It looks like the odometry is realistic, as my work on robots in previous classes tell me that odometry will be very uncertain.

There is also a button to plot the map, and a few other buttons to plot other useful info, but I moved on to using the simulator programatically at that point.

Task 2: Open-Loop Control

To demonstrate open-loop control, I had my robot simply drive in a square by using time-based commands. The robot would drive for 1 second at 0.5 m/s, then rotate for 1 second at 90 degrees per second, and finally repeat these steps indefinitely to expose any drifting that might happen if the robot doesn’t rotate to exactly 90 degrees.

while cmdr.sim_is_running() and cmdr.plotter_is_running():
  for _ in range(4):
    plot_current_gt()
    cmdr.set_vel(0.5, 0)
    await asyncio.sleep(1)
    plot_current_gt()
    cmdr.set_vel(0, np.pi/2)
    await asyncio.sleep(1)
  cmdr.set_vel(0,0)

square driving

open loop plot

From the plot, the ground truth (green) suggests that the robot doesn’t rotate in exact 90-degree increments. This is likely due to how my code is time-based; if the velocity change is not instantaneous, the robot might rotate less than 90 degrees, and if asyncio.sleep() blocks for longer than 1 second due to how my computer’s OS prioritizes processes are prioritized, the robot might rotate more than 90 degrees. This causes some inconsistencies in how square the path can be.

Also, compared to the ground truth (green), the odometry (red) is wildly off. In the real world, this could be explained by ideas like wheel slippage, inconsistencies in wheel diameter, etc. The simulator likely has similar noisiness encoded into it to make the odometry match the real world.

I also know that the real world and the simulated world may have delays between executing a command on a computer and seeing the command being executed on the robot. In that sense, I tried to time how long it would take for a velocity command to execute:

while cmdr.sim_is_running() and cmdr.plotter_is_running():
  for i in range(100):
      time1 = time.time()
      cmdr.set_vel(0,np.pi/2 * (-1 if i % 2 == 1 else 1))
      times.append(time.time() - time1)
  print(f"Average time: {sum(times) / len(times)}")
  print(f"Std dev: {np.std(times)}")
Average time: 0.011290438175201416
Std dev: 0.007699923022536731

I used this code to do this in case the simulator would respond faster to asking it to set the velocity to the same thing all the time. However, I also tried that way and saw a negligible delay reduction:

while cmdr.sim_is_running() and cmdr.plotter_is_running():
  for i in range(100):
      time1 = time.time()
      cmdr.set_vel(0,1)
      times.append(time.time() - time1)
  print(f"Average time: {sum(times) / len(times)}")
  print(f"Std dev: {np.std(times)}")
Average time: 0.010912902355194092
Std dev: 0.00784329388949928

Task 3: Closed-Loop Control

To demonstrate closed-loop control, I used the robot’s front time-of-flight sensor to determine when the robot should turn away from an obstacle. Initially, I had it turn about 90 degrees; that produced an uninteresting end result, so I had it turn randomly instead.

while cmdr.sim_is_running() and cmdr.plotter_is_running():
    plot_current_gt()
    if cmdr.get_sensor()[0] < 0.3:
        cmdr.set_vel(0, random.uniform(-1 * np.pi, np.pi))
        await asyncio.sleep(1)
    else:
        cmdr.set_vel(1, 0)

From the code above, the robot tries to avoid obstacles as long as they’re closer than 30 cm away. Although this seems large, I noticed that when I crashed the robot into a wall head-on, the ToF sensor would still read anything from 9 to 19 cm. The extra 10 cm was designed for padding; if the robot is positioned close enough, it may see a wall around a corner that one side of the robot would crash into:

clip corner before

clip corner after

With this in mind, I tried to get the robot to randomly walk the map. It still crashed sometimes:

random walk crash

random walk

Although this shows a linear velocity of 1 m/s, I’ve been able to get to 5 m/s with some degree of success. However, the robot has difficulty with corners at any speed; this is because the time-of-flight sensor doesn’t account for the width of the robot.

To minimize crashes, there are a few things I can do: