Reaching with Obstacle Avoidance
In this example, we will generate a controller for the sciurus17 robot to reach two targets while avoiding two obstacles. The reaching controller will be a saturating-spring, linear-damper virtual mechanism, with additional repulsive springs to avoid the obstacles.
The sciurus17 robot is a many-DOF robot 2 arms, a pivotting waist and 2DOF neck/head.
using DifferentialEquations
using GLMakie
using StaticArrays
using VMRobotControl
First, we load the URDF file for the sciurus17 robot, with warnings suppressed, as the URDF file contains some features that are not supported by the URDFParser, but are not necessary for this example. We then add joint limit springs to the robot, to prevent the robot from reaching its joint limits. These act like `buffers' around the joint limits, which will and push back with a spring force as the joint approaches its limits. We also add a linear damper to each joint to make the simulation more realistic, so that the robot does not oscillate indefinitely. Finally, we add the TCPs for the left and right arms of the robot, which will be used for the reaching controller.
cfg = URDFParserConfig(; suppress_warnings=true)
module_path = joinpath(splitpath(splitdir(pathof(VMRobotControl))[1])[1:end-1])
robot = parseURDF(joinpath(module_path, "URDFs/sciurus17_description/urdf/sciurus17.urdf"), cfg)
21DOF Mechanism{Float64} "sciurus17" with 27 frames, 26 joints, 44 coordinates, 74 components
Add joint limit springs
joint_limits = cfg.joint_limits
for joint_id in keys(joints(robot))
limits = joint_limits[joint_id]
isnothing(limits) && continue
add_coordinate!(robot, JointSubspace(joint_id); id="$(joint_id)_coord")
@assert ~isnothing(limits.lower) && ~isnothing(limits.upper)
add_deadzone_springs!(robot, 50.0, (limits.lower+0.1, limits.upper-0.1), "$(joint_id)_coord")
add_component!(robot, LinearDamper(0.01, "$(joint_id)_coord"); id="$(joint_id)_damper")
end
add_coordinate!(robot, FramePoint("l_link7", SVector(0., 0.08, 0.0)); id="L TCP")
add_coordinate!(robot, FramePoint("r_link7", SVector(0., -0.08, 0.0)); id="R TCP")
"R TCP"
Next we build the virtual mechanism controller. There is no virtual mechanism structure, as the controller we are building does not require any simulated links or joints, only springs/dampers and repulsive springs. We define two targets for the robot to reach, and the position error for each target. On top of these coordinates, we add a saturating spring-damper component, which will act as the reaching controller.
vms = VirtualMechanismSystem("sciurus_reaching", robot)
add_gravity_compensation!(vms, VMRobotControl.DEFAULT_GRAVITY)
vm = vms.virtual_mechanism
add_coordinate!(vm, ReferenceCoord(Ref(SVector(0.5, 0.0, 0.4))); id="target_1_pos")
add_coordinate!(vm, ReferenceCoord(Ref(SVector(0.5, -0.1, 0.2))); id="target_2_pos")
add_coordinate!(vms, CoordDifference(".robot.L TCP", ".virtual_mechanism.target_1_pos"); id="L pos error")
add_coordinate!(vms, CoordDifference(".robot.R TCP", ".virtual_mechanism.target_2_pos"); id="R pos error")
add_component!(vms, TanhSpring("L pos error"; max_force=10.0, stiffness=1000.0); id="L spring")
add_component!(vms, LinearDamper(10.0, "L pos error"); id="L damper")
add_component!(vms, TanhSpring("R pos error"; max_force=10.0, stiffness=1000.0); id="R spring")
add_component!(vms, LinearDamper(10.0, "R pos error"); id="R damper")
"R damper"
We define two obstacle positions, and for each frame in each arm attach a repulsive spring between the frame origin and the obstacle position. The repulsive spring will push the frame origin away from the obstacle, with a force that decreases as the frame origin moves away from the obstacle, according to the Gaussian potential energy of the spring.
obstacles = Dict(
"obstacle_1" => SVector(0.4, 0.1, 0.3),
"obstacle_2" => SVector(0.3, -0.2, 0.1)
)
collision_frames = String[]
for i in 1:7
push!(collision_frames, "l_link$i")
push!(collision_frames, "r_link$i")
end
for (id, pos) in obstacles
add_coordinate!(vm, FramePoint("root_frame", pos); id)
end
for id in collision_frames
add_coordinate!(robot, FrameOrigin("$id"); id="$id frame origin")
for obstacle in keys(obstacles)
add_coordinate!(vms, CoordDifference(".robot.$id frame origin", ".virtual_mechanism.$obstacle"); id="$obstacle $id error")
add_component!(vms, GaussianSpring("$obstacle $id error"; max_force=-10.0, width=0.05); id="$obstacle $id spring")
end
end
Setup simulation
We define functions to move the targets in a circular motion, and to set the target positions and velocities in the f_control
function. We then define the f_setup
function to get the coordinate IDs of the target positions and velocities, to be used in the f_control
function.
We then define the timespan, initial joint angles, joint velocities, and gravity vector for the simulation. We create a dynamics cache, and an ODE problem, and solve the ODE problem using the Tsit5 solver from DifferentialEquations.jl.
target_1_pos(t) = SVector(0.5, 0.0, 0.3) + SVector(0.2*cos(t), 0.0, 0.2*sin(t))
target_1_vel(t) = SVector(-0.2*sin(t), 0.0, 0.2*cos(t))
target_2_pos(t) = SVector(0.5, -0.1, 0.2) + SVector(0.0, 0.2*sin(t), 0.2*cos(t))
target_2_vel(t) = SVector(0.2*cos(t), 0.0, -0.2*sin(t))
function f_setup(cache)
id1 = get_compiled_coordID(cache, ".virtual_mechanism.target_1_pos")
id2 = get_compiled_coordID(cache, ".virtual_mechanism.target_2_pos")
val1 = cache[id1].coord_data.val
val2 = cache[id2].coord_data.val
vel1 = cache[id1].coord_data.vel
vel2 = cache[id2].coord_data.vel
(val1, val2, vel1, vel2)
end
function f_control(cache, t, args, extra)
(val1, val2, vel1, vel2) = args
val1[] = target_1_pos(t)
vel1[] = target_1_vel(t)
val2[] = target_2_pos(t)
vel2[] = target_2_vel(t)
false
end
tspan = (0., 6π)
dcache = new_dynamics_cache(compile(vms))
q = zero_q(dcache.vms)
q̇ = zero_q̇(dcache.vms)
g = VMRobotControl.DEFAULT_GRAVITY
prob = get_ode_problem(dcache, g, q, q̇, tspan; f_setup, f_control)
@info "Simulating Sciurus reaching with obstacle avoidance problem."
sol = solve(prob, Tsit5(); maxiters=1e5, abstol=1e-3, reltol=1e-3); # Low tol to speed up simulation
[ Info: Simulating Sciurus reaching with obstacle avoidance problem.
Plotting
We create a figure with two scenes, for two different camera angles. We plot the robot, the targets, the TCPs, and the obstacles in the scene. We use observables for the time and the kinematics cache, which will be updated in the function animate_robot_odesolution
, causing any plots that depend upon these observables to be updated.
fig = Figure(; size = (2*720, 720), figure_padding=1.5, fontsize=32)
display(fig)
lscenes = (LScene(fig[1, 1]; show_axis=false), LScene(fig[1, 2]; show_axis=false))
cams = map(ls -> cam3d!(ls; center=false), lscenes)
plotting_t = Observable(0.0)
plotting_kcache = Observable(new_kinematics_cache(compile(vms)))
target_scatter_kwargs = (;
color=:green,
marker=:+,
markersize=15,
label="Targets",
transparency=true ## Avoid ugly white outline artefact on markers
)
tcp_scatter_kwargs = (;
color=:blue,
marker=:x,
markersize=15,
label="TCPs",
transparency=true ## Avoid ugly white outline artefact on markers
)
for ls in lscenes
# Show robot
robotvisualize!(ls, plotting_kcache; overdraw=false)
# Label target and TCP
target_1_pos_id = get_compiled_coordID(plotting_kcache[], ".virtual_mechanism.target_1_pos")
target_2_pos_id = get_compiled_coordID(plotting_kcache[], ".virtual_mechanism.target_2_pos")
l_tcp_pos_id = get_compiled_coordID(plotting_kcache[], ".robot.L TCP")
r_tcp_pos_id = get_compiled_coordID(plotting_kcache[], ".robot.R TCP")
scatter!(ls, plotting_kcache, [target_1_pos_id, target_2_pos_id]; target_scatter_kwargs...)
scatter!(ls, plotting_kcache, [l_tcp_pos_id, r_tcp_pos_id]; tcp_scatter_kwargs...)
for (id, pos) in obstacles ## Show obstacles
small_sphere = Sphere(Point3f(pos...), 0.05)
large_sphere = Sphere(Point3f(pos...), 0.1)
mesh!(ls, small_sphere; color=:magenta, transparency=true)
poly!(ls, Point3f[]; color=:magenta, label="Obstacles") ## Just for legend entry
mesh!(ls, large_sphere; color=:cyan, alpha=0.1, transparency=true)
poly!(ls, Point3f[]; color=:cyan, label="Repulsive field") ## Just for legend entry
end
end
cams[1].lookat[] = [0., 0., 0.3]
cams[1].eyeposition[] = [1.5, 0., 0.3]
cams[2].lookat[] = [0.4, 0, 0.3]
cams[2].eyeposition[] = [0.4, 1.5, 0.3]
leg = Legend(fig[1, 1], lscenes[1]; merge=true, tellwidth=false, halign=:left, valign=:top)
savepath = joinpath(module_path, "docs/src/assets/sciurus_reaching.mp4")
animate_robot_odesolution(fig, sol, plotting_kcache, savepath; t=plotting_t, f_setup, f_control, fastforward=1.0, fps=20)
"/home/runner/work/VMRobotControl.jl/VMRobotControl.jl/docs/src/assets/sciurus_reaching.mp4"
This page was generated using Literate.jl.