Basic Motion Test Script#

In this example, we’ll write a basic script that commands the OSL to move the joints in simple, periodic motions. This script can be a helpful first step when comissioning a new OSL and making sure that the actuation systems are configured appropriately.

We’ll assume that this is the first example someone looks at, and therefore we’ll go through each set of commands in detail.

Imports#

For this example, we’ll import numpy, the OpenSourceLeg class, and the units class from the tools module.

1import numpy as np
2
3from opensourceleg.osl import OpenSourceLeg
4from opensourceleg.tools import units

Making an OSL object#

We’ll then create an instance of the OpenSourceLeg class and name it osl. We pass the desired control loop frequency (200 Hz in our case) as an argument. This sets the rate of data streaming for the actuators, as well as the frequency of the loop in osl.clock. We’ll see more on this below.

1osl = OpenSourceLeg(frequency=200)

Adding Joints#

Next, we add the knee and the ankle joints to the OSL via the add_joint method. We supply the name of the joint and the gear ratio. Our gear ratio is 9*83/18=41.5, which accounts for the internal ActPack ratio as well as the belt transmission.

1osl.add_joint("knee", gear_ratio=9 * 83 / 18)
2osl.add_joint("ankle", gear_ratio=9 * 83 / 18)

Note

If you’re only using one joint, you can modify this script by simply commenting out method calls for the joint you’re not using. For example, if you’re using ankle-only, you can comment out osl.add_joint("knee"...) and the subsequent knee related methods.

Generating Reference Trajectories#

Next, we define a funciton that returns a function handle for a simple harmonic trajectory. Given a time input t, instances of these functions return a reference position in degrees. We’ll make a function for both the knee and ankle joints with a period of 30 seconds. We’ll command the ankle to oscillate between -20 and 20 deg and the knee between 10 and 90 deg.

1def make_periodic_traj_func(period, minimum, maximum):
2    amplitude = (maximum - minimum) / 2
3    mean = amplitude + minimum
4    return lambda t: amplitude * np.cos(t * 2 * np.pi / period) + mean
5
6
7ankle_traj = make_periodic_traj_func(10, -20, 20)
8knee_traj = make_periodic_traj_func(10, 10, 90)

Configuring the OSL#

Now that the trajectories are configured, we’re ready to start sending commands to the OSL. We start with the syntax with osl:. This syntax allows the library to call certain functions on the entrance and exit of the block. This way, the library can ensure that the OSL turns off (returns to zero voltage mode) in the event of an error.

We then home the joints using osl.home(). The homing routine drives the joints towards their hardstops in order to initialize the encoders.

1with osl:
2    osl.home()

After homing, we wait for user input to begin moving the joints. Then we put both joints into position control mode and set the proportional gains to a low value for this basic example.

1    input("Homing complete: Press enter to continue")
2    osl.knee.set_mode(osl.knee.control_modes.position)
3    osl.ankle.set_mode(osl.ankle.control_modes.position)
4    osl.knee.set_position_gains(kp=5)
5    osl.ankle.set_position_gains(kp=5)

Main Loop#

Next, we can enter the main loop. The OpenSourceLeg class includes a built-in instance of a SoftRealTimeLoop. This loop, which is called clock, will execute the contents of a for loop at the frequency specified when creating the OpenSourceLeg object using the syntax for t in osl.clock:. The loop will run until either osl.clock.stop() is called, an exception is raised, or the user presses ctrl+c on the keyboard.

 1    for t in osl.clock:
 2        osl.update()
 3        knee_setpoint = units.convert_to_default(knee_traj(t), units.position.deg)
 4        ankle_setpoint = units.convert_to_default(ankle_traj(t), units.position.deg)
 5        osl.knee.set_output_position(knee_setpoint)
 6        osl.ankle.set_output_position(ankle_setpoint)
 7        print(
 8            "Ankle Desired {:+.2f} rad, Ankle Actual {:+.2f} rad, Knee Desired {:+.2f} rad, Ankle Desired {:+.2f} rad".format(
 9                ankle_setpoint,
10                osl.ankle.output_position,
11                knee_setpoint,
12                osl.knee.output_position,
13            ),
14            end="\r",
15        )

Within the loop, we first call osl.update() to query the actuators and other sensors for their latest values. We then update the knee and ankle position setpoints by calling our trajectory functions. Note that we convert the setpoint to default OSL units (radians in this case), e.g., units.convert_to_default(knee_traj(t), units.position.deg). Finally, we use the set_output_position() method to command the OSL joints to move to the new reference value. We also we print values to the screen for debugging.

Full Code for This Example#

 1"""
 2A basic motion script that moves the osl joints through their range of motion.
 3This script can be helpful when getting started to make sure the OSL is functional.
 4
 5Kevin Best
 6Neurobionics Lab
 7Robotics Department
 8University of Michigan
 9October 26, 2023
10"""
11
12import numpy as np
13
14from opensourceleg.osl import OpenSourceLeg
15from opensourceleg.tools import units
16
17osl = OpenSourceLeg(frequency=200)
18osl.add_joint("knee", gear_ratio=9 * 83 / 18)
19osl.add_joint("ankle", gear_ratio=9 * 83 / 18)
20
21
22def make_periodic_traj_func(period, minimum, maximum):
23    amplitude = (maximum - minimum) / 2
24    mean = amplitude + minimum
25    return lambda t: amplitude * np.cos(t * 2 * np.pi / period) + mean
26
27
28ankle_traj = make_periodic_traj_func(10, -20, 20)
29knee_traj = make_periodic_traj_func(10, 10, 90)
30
31with osl:
32    osl.home()
33    input("Homing complete: Press enter to continue")
34    osl.knee.set_mode(osl.knee.control_modes.position)
35    osl.ankle.set_mode(osl.ankle.control_modes.position)
36    osl.knee.set_position_gains(kp=5)
37    osl.ankle.set_position_gains(kp=5)
38
39    for t in osl.clock:
40        osl.update()
41        knee_setpoint = units.convert_to_default(knee_traj(t), units.position.deg)
42        ankle_setpoint = units.convert_to_default(ankle_traj(t), units.position.deg)
43        osl.knee.set_output_position(knee_setpoint)
44        osl.ankle.set_output_position(ankle_setpoint)
45        print(
46            "Ankle Desired {:+.2f} rad, Ankle Actual {:+.2f} rad, Knee Desired {:+.2f} rad, Ankle Desired {:+.2f} rad".format(
47                ankle_setpoint,
48                osl.ankle.output_position,
49                knee_setpoint,
50                osl.knee.output_position,
51            ),
52            end="\r",
53        )
54
55print("\n")