Wheel Project

Setting up the Simulation Environment

Import necessary condynsate modules.

[1]:
from condynsate import Simulator as conSim
from condynsate import __assets__ as assets

Create a instance of condynsate.simulator that uses the keyboard, visualizer, and animator.

[2]:
simulator = conSim(keyboard = True,
                   visualization = True,
                   animation = True)
You can open the visualizer by visiting the following URL:
http://127.0.0.1:7003/static/

Load the wheel .urdf file from condynsate’s default assests.

[3]:
# Load the wheel
wheel = simulator.load_urdf(urdf_path = assets['wheel'],
                            position = [0., 0., 0.],
                            fixed = True, # Make the base of the wheel fixed in space
                            update_vis = True)

Add some friction to the wheel’s axle.

[4]:
# Set joint damping
simulator.set_joint_damping(urdf_obj = wheel,
                            joint_name = "ground_to_axle",
                            damping = 0.01)

Setting up the Animator

Here we will add a set of plots the the animator to visualizer the error between a target wheel angle and the measured wheel angle as well as a set of control gains.

[5]:
# plot_1 is a line plot that shows the wheel angle against the target angle
plot_1, plot_1_artists = simulator.add_plot(n_artists = 2,
                                            plot_type = 'line',
                                            y_lim = [-0.6283, 6.9115],
                                            title = "Angles vs Time",
                                            x_label = "Time [Seconds]",
                                            y_label = "Angles [Rad]",
                                            color = ["k", "m"],
                                            line_width = [2.5, 1.5],
                                            line_style = ["-", "--"],
                                            label = ["Measured", "Target"],
                                            h_zero_line = True)
[6]:
# plot_2 is a bar chart that shows the current value of the control gains
plot_2, plot_2_artists = simulator.add_plot(n_artists = 2,
                                            plot_type = 'bar',
                                            x_lim = [0, 5.0],
                                            title = "Control Gains",
                                            x_label = "Set Value",
                                            color = ["r", "b"],
                                            label = ["Proportional", "Derivative"])

Here we make a tuple of the plots and artists for easy accessing later.

[7]:
plots = (plot_1, plot_2)
artists = (plot_1_artists, plot_2_artists)

The plots are now set up how we want, so we can open the animator GUI. Once opened, no additional plots can be added. Note: %%capture is required to supress the animator drawing the first frame to this notebook. Though it is not required, it is reccomended. It must be the first line in the cell in which it’s called.

[8]:
%%capture
simulator.start_animator()

Setting up the Simulation Loop

Below we will make a set of functions that will be used in the simulation loop.

[9]:
def detect_keypresses(simulator):
    """
    Listens for keyboard presses and returns iteration values for
    the target angle, proportional control gain, and derivative
    control gain. Target angle is iterated up (increased) if 'e'
    is pressed and iterated down (decreased) if 'q' is pressed.
    Similarly for the proportional gain and 'r' and 'f'.
    Similarly for the derivative gain and 't' and 'g'.

    Parameters
    ----------
    simulator: condynsate.Simulator
        A member of the condynsate.Simulator class that is used
        to listen for key presses.

    Returns
    -------
    target_iter_val: float
        The value by which the target angle should be iterated
        during the current simulation step.
    P_iter_val: float
        The value by which the proportional gain should be iterated
        during the current simulation step.
    D_iter_val: float
        The value by which the derivative gain should be iterated
        during the current simulation step.

    """
    # Target angle iteration
    target_iter_val = 0.0
    if simulator.is_pressed('q'): target_iter_val -= 0.02
    if simulator.is_pressed('e'): target_iter_val += 0.02

    # Proportional gain iteration
    P_iter_val = 0.0
    if simulator.is_pressed('f'): P_iter_val -= 0.02
    if simulator.is_pressed('r'): P_iter_val += 0.02

    # Derivative gain iteration
    D_iter_val = 0.0
    if simulator.is_pressed('g'): D_iter_val -= 0.02
    if simulator.is_pressed('t'): D_iter_val += 0.02
    return (target_iter_val, P_iter_val, D_iter_val)
[10]:
def get_target_error(simulator, wheel, target_angle):
    """
    Calculates the current error between the current wheel
    angle and the target wheel angle as well as the error
    between the current wheel velocity and the target wheel
    velocity (which is always 0.0).

    Parameters
    ----------
    simulator: condynsate.Simulator
        The member of the condynsate.Simulator class that is
        running the simulation
    wheel: condynsate.core.simulator.URDF_Obj
        The wheel URDF object whose state is measured.
    target_angle: float
        The target wheel angle in radians.

    Returns
    -------
    angle_error: float
        The error between the current wheel angle and the
        target wheel angle in radians.
    velocity_error: float
        The error between the current wheel angular speed
        and the target wheel angular speed (0.0 rad/s) in
        radians per second.

    """
    # Extract the current wheel speed and angle
    wheel_state = simulator.get_joint_state(urdf_obj = wheel,
                                            joint_name = "ground_to_axle")
    wheel_angle = wheel_state['position']
    wheel_velocity = wheel_state['velocity']

    # Calculate the errors
    angle_error = target_angle - wheel_angle
    velocity_error = 0.0 - wheel_velocity
    return (angle_error, velocity_error)
[11]:
def set_target_angle(simulator, wheel, error):
    """
    Adjusts the "wheel_to_target" joint angle of the wheel
    URDF object so it reflects the current target wheel angle.

    Parameters
    ----------
    simulator: condynsate.Simulator
        The member of the condynsate.Simulator class that is
        running the simulation
    wheel: condynsate.core.simulator.URDF_Obj
        The wheel URDF object whose joint angle is set.
    error: tuple of floats
        A tuple of floats with the form (angle_error, velocity_error)
        where angle_error is the error between the current wheel angle
        and the target wheel angle in radians and velocity_error is
        the error between the current wheel angular speed
        and the target wheel angular speed (0.0 rad/s) in
        radians per second (as calculated by get_target_error).

    Returns
    -------
    None.

    """
    simulator.set_joint_position(urdf_obj = wheel,
                                 joint_name = "wheel_to_target",
                                 position = error[0])
[12]:
def set_torque(simulator, wheel, error, gains):
    """
    Sets the torque applied to the wheel URDF object through
    its axle based on the current control gains and the error
    in the wheel's angle and angular speed.

    Parameters
    ----------
    simulator: condynsate.Simulator
        The member of the condynsate.Simulator class that is
        running the simulation
    wheel: condynsate.core.simulator.URDF_Obj
        The wheel URDF object whose torque is set.
    error: tuple of floats
        A tuple of floats with the form (angle_error, velocity_error)
        where angle_error is the error between the current wheel angle
        and the target wheel angle in radians and velocity_error is
        the error between the current wheel angular speed
        and the target wheel angular speed (0.0 rad/s) in
        radians per second (as calculated by get_target_error).
    gains: tuple of floats
        A tuple of floats with the form (proportional gain, derivative gain).

    Returns
    -------
    None.

    """
    # Calculate the applied torque based on PD control
    torque = gains[0] * error[0] + gains[1] * error[1]
    simulator.set_joint_torque(urdf_obj = wheel,
                               joint_name = "ground_to_axle",
                               torque = torque)
[13]:
def update_angle_plot(simulator, plot, artists, target_angle, error):
    """
    Updates the angle plot to reflect the current wheel angle and the
    target wheel angle.

    Parameters
    ----------
    simulator: condynsate.Simulator
        The member of the condynsate.Simulator class that hosts the
        plot
    plot: int
        The plot descriptor of the line plot being updated.
    artists: tuple of ints
        A tuple of the two arist descriptors for each line in
        the plot. Has the form (wheel angle artist, target angle artist).
    target_angle: float
        The target wheel angle in radians.
    error: tuple of floats
        A tuple of floats with the form (angle_error, velocity_error)
        where angle_error is the error between the current wheel angle
        and the target wheel angle in radians and velocity_error is
        the error between the current wheel angular speed
        and the target wheel angular speed (0.0 rad/s) in
        radians per second (as calculated by get_target_error).

    Returns
    -------
    None.

    """
    time = simulator.time
    wheel_angle = target_angle-error[0]
    simulator.add_line_datum(plot, time, wheel_angle, artist_id=artists[0])
    simulator.add_line_datum(plot, time, target_angle, artist_id=artists[1])
[14]:
def update_gain_plot(simulator, plot, artists, gains):
    """
    Updates the control gain plot to reflect the current control gains

    Parameters
    ----------
    simulator: condynsate.Simulator
        The member of the condynsate.Simulator class that hosts the
        plot
    plot: int
        The plot descriptor of the bar plot being updated.
    artists: tuple of ints
        A tuple of the two arist descriptors for each bar in
        the plot. Has the form (proportional artist, derivative artist).
    gains: tuple of floats
        A tuple of floats with the form (proportional gain, derivative gain).

    Returns
    -------
    None.

    """
    for a,g in zip(artists, gains):
        simulator.set_bar_value(plot, g, artist_id=a)
[15]:
def step(simulator, plots, artists, target_angle, gains):
    """
    This function is called each simulation step. It does five things:
    1. Iterates the target angle and control gains based on key presses
    2. Calculates the error between the current wheel angle and the
       target wheel angle as well as the error between the current wheel
       angular speed and the target wheel angular speed and sets torque
       accordingly.
    3. Updates the target angle indicator arrow to reflect the current
       target angle.
    4. Updates both the wheel angle line plot and the control gain
       bar plot
    5. Returns the iterated target wheel angle and the iterated control gains.

    Parameters
    ----------
    simulator: condynsate.Simulator
        The member of the condynsate.Simulator class that is
        running the simulation.
    plots: tuple of ints
        A tuple of plot descriptors of the form
        (wheel angle plot, control gain plot).
    artists: tuple of tuple of ints
        A tuple of the artist descriptor tuples for each plot.
        Has the form ((wheel angle artist, target angle artist),
        (proportional gain artist, derivative gain artist)).
    target_angle: float
        The target wheel angle in radians.
    gains: tuple of floats
        A tuple of floats with the form (proportional gain, derivative gain).

    Returns
    -------
    target_angle: float
        The iterated target wheel angle in radians.
    gains: tuple of floats
        A tuple the iterated control gains with the form
        (proportional gain, derivative gain).

    """
    # Iterate the target angle and control gains based on key presses
    target_iter_val, P_iter_val, D_iter_val = detect_keypresses(simulator)
    target_angle = max(min(target_angle + target_iter_val, 6.2832), 0.0) # Clip to [0.0, 2pi]
    gains[0] = max(min(gains[0] + P_iter_val, 5.0), 0.0) # Clip to [0.0, 5.0]
    gains[1] = max(min(gains[1] + D_iter_val, 5.0), 0.0) # Clip to [0.0, 5.0]

    # Set the torque via PD control
    error = get_target_error(simulator, wheel, target_angle)
    set_torque(simulator, wheel, error, gains)

    # Update the target angle indicator arrow
    set_target_angle(simulator, wheel, error)

    # Update both plots
    update_angle_plot(simulator, plots[0], artists[0], target_angle, error)
    update_gain_plot(simulator, plots[1], artists[1], gains)

    # Return the iterated target angle and control gains
    return target_angle, gains

Running the Simulation Loop

Now that all the helper functions are defined, we can run the simulation loop. This simulation will start running when the user presses the ‘enter’ and continue running until the user presses the ‘esc’ key. During the simulation, the user can set the target angle of the wheel (indicated by the orange arrow) with the ‘q’ and ‘e’ keys. A PD controll will then apply torque to the wheel axle to attempt to enforce this angle based on a set of proportional and derivatve control gains. The proportional control gains can be adjusted by the user with the ‘f’ and ‘r’ keys and the derivative control gains can be adjusted with the ‘g’ and ‘t’ keys. The animator plots the target and current wheel angle is plotted as a function of time as well as the current values of the control gains.

[16]:
# Set the initial gains and target angle
target_angle = 0.0
gains = [0.0, 0.0]

# Reset before running a simulation loop
simulator.reset()

# Suspend execution until user presses enter key
simulator.await_keypress(key = 'enter')

# Run the simulation loop
while(not simulator.is_done):
    # Step the sim
    ret_code = simulator.step(real_time = True)

    # If successful step was taken
    if ret_code > 0:
        target_angle, gains = step(simulator, plots, artists, target_angle, gains)
PRESS ENTER TO START SIMULATION.
PRESS ESC TO QUIT.
PRESS SPACE TO PAUSE/RESUME SIMULATION.
PRESS BACKSPACE TO RESET SIMULATION.
CONTINUING...
QUITTING...

Once the simulation is done, we terminate the animator.

[17]:
simulator.terminate_animator()
Termination command detected. Terminating keyboard listener. Goodbye