From e726b4166e53753b0fd2300d8e750bd20bcd7eb2 Mon Sep 17 00:00:00 2001 From: m-guberina <gubi.guberina@gmail.com> Date: Mon, 17 Feb 2025 17:24:26 +0100 Subject: [PATCH] single ee, just ee path following is ok. the relationship between the base and the arm would have to be weighted appropriately for this to be runnable on anything, but that's not our concern here --- development_plan.toml | 28 +++++++++++++- .../path_following_from_planner.py | 38 ++++++++++++++++--- .../optimal_control/abstract_croco_ocp.py | 2 - .../croco_mpc_path_following.py | 37 ++++-------------- .../croco_mpc_point_to_point.py | 6 ++- .../path_generation/path_math/path2d_to_6d.py | 4 +- .../path_math/path_to_trajectory.py | 6 +-- 7 files changed, 76 insertions(+), 45 deletions(-) diff --git a/development_plan.toml b/development_plan.toml index 8c4361b..c6cd1e0 100644 --- a/development_plan.toml +++ b/development_plan.toml @@ -94,7 +94,7 @@ dependencies = ["create/fix type-checking workflow"] purpose = """easier code writting and debugging, making library professional-grade""" # -1 means it should be a sum over sub-items hours_required = 8 -is_done = false +is_done = true [[project_plan.action_items.action_items]] name = "robotmanager abstract class" @@ -168,7 +168,19 @@ deadline = 2025-01-31 dependencies = ["robotmanager abstract class"] purpose = """making it easy, simple, and error-abstinent to only navigate the base""" hours_required = 4 -is_done = false +is_done = true + +[[project_plan.action_items.action_items]] +name = "whole-body feature" +description = """ +a generic class that has at least 1 manipulator plus a mobile base. what you do here is give me access to just the arm or just the base - that's it. you can do it by overriding setters or whatever. you don't need to finish it, it just needs to do what's necessary for cart pulling +""" +priority = 1 +deadline = 2025-01-31 +dependencies = ["robotmanager abstract class", "mobile_base_feature", "dual_arms_feature"] +purpose = """making it easy, simple, and error-abstinent to only navigate the base""" +hours_required = 4 +is_done = true [[project_plan.action_items.action_items]] @@ -289,6 +301,18 @@ street cred""" hours_required = 80 is_done = false +[[project_plan.action_items.action_items]] +name = "tidy up typing" +description = """ +sit down and get typing coverage to 100% or whatever is the limit of reason. +""" +priority = 3 +deadline = 2025-09-01 +dependencies = ["create/fix type-checking workflow"] +purpose = """easier code writting and debugging, making library professional-grade""" +# -1 means it should be a sum over sub-items +hours_required = 8 +is_done = true [[project_plan.action_items]] name = "python stdlib logging instead of printing" diff --git a/examples/cart_pulling/path_following_from_planner.py b/examples/cart_pulling/path_following_from_planner.py index 8470014..e3b7f77 100644 --- a/examples/cart_pulling/path_following_from_planner.py +++ b/examples/cart_pulling/path_following_from_planner.py @@ -1,9 +1,11 @@ from smc import getRobotFromArgs from smc import getMinimalArgParser from smc.path_generation.maps.premade_maps import createSampleStaticMap +from smc.path_generation.path_math.path2d_to_6d import path2D_to_SE3_fixed from smc.control.optimal_control.util import get_OCP_args from smc.control.cartesian_space import getClikArgs from smc.path_generation.planner import starPlanner, getPlanningArgs +from smc.control.optimal_control.croco_mpc_point_to_point import CrocoIKMPC from smc.control.optimal_control.croco_mpc_path_following import ( CrocoEndEffectorPathFollowingMPC, ) @@ -12,6 +14,7 @@ from smc.multiprocessing import ProcessManager import time import numpy as np from functools import partial +import pinocchio as pin def get_args(): @@ -32,19 +35,44 @@ def get_args(): if __name__ == "__main__": args = get_args() robot = getRobotFromArgs(args) - # robot._q[0] = 9.0 - # robot._q[1] = 4.0 + # TODO: HOW IS IT POSSIBLE THAT T_W_E IS WRONG WITHOUT STEP CALLED HERE????????????????? + robot._step() + T_w_e = robot.T_w_e + robot._q[0] = 9.0 + robot._q[1] = 4.0 robot._step() x0 = np.concatenate([robot.q, robot.v]) goal = np.array([0.5, 5.5]) planning_function = partial(starPlanner, goal) - # TODO: ensure alignment in orientation between planner and actual robot - path_planner = ProcessManager(args, planning_function, robot.q[:2], 3, None) + # here we're following T_w_e reference so that's what we send + path_planner = ProcessManager( + args, planning_function, T_w_e.translation[:2], 3, None + ) _, map_as_list = createSampleStaticMap() if args.visualizer: robot.sendRectangular2DMapToVisualizer(map_as_list) - time.sleep(5) + # time.sleep(5) + + T_w_e = robot.T_w_e + data = None + # get first path + while data is None: + path_planner.sendCommand(T_w_e.translation[:2]) + data = path_planner.getData() + # time.sleep(1 / args.ctrl_freq) + time.sleep(1) + + _, path2D = data + path2D = np.array(path2D).reshape((-1, 2)) + pathSE3 = path2D_to_SE3_fixed(path2D, args.handlebar_height) + if args.visualizer: + # TODO: document this somewhere + robot.visualizer_manager.sendCommand({"Mgoal": pathSE3[0]}) + if np.linalg.norm(pin.log6(T_w_e.actInv(pathSE3[0]))) > 1e-2: + print("going to initial path position") + CrocoIKMPC(args, robot, pathSE3[0]) + print("initialized!") CrocoEndEffectorPathFollowingMPC(args, robot, x0, path_planner) print("final position:", robot.T_w_e) diff --git a/python/smc/control/optimal_control/abstract_croco_ocp.py b/python/smc/control/optimal_control/abstract_croco_ocp.py index 59a267f..0d4fa0f 100644 --- a/python/smc/control/optimal_control/abstract_croco_ocp.py +++ b/python/smc/control/optimal_control/abstract_croco_ocp.py @@ -135,8 +135,6 @@ class CrocoOCP(abc.ABC): ): self.xlb = self.xlb[1:] self.xub = self.xub[1:] - print(self.xlb) - print(self.xub) def constructConstraints(self) -> None: if self.solver_name == "boxfddp": diff --git a/python/smc/control/optimal_control/croco_mpc_path_following.py b/python/smc/control/optimal_control/croco_mpc_path_following.py index 1f1338c..222dd98 100644 --- a/python/smc/control/optimal_control/croco_mpc_path_following.py +++ b/python/smc/control/optimal_control/croco_mpc_path_following.py @@ -67,25 +67,27 @@ def CrocoEndEffectorPathFollowingMPCControlLoop( if args.debug_prints: print("CTRL: got no path so i won't move") robot.sendVelocityCommand(np.zeros(robot.model.nv)) - log_item["qs"] = q.reshape((robot.model.nq,)) - log_item["dqs"] = robot.v.reshape((robot.model.nv,)) + log_item["qs"] = q + log_item["dqs"] = robot.v return breakFlag, save_past_item, log_item if data == "done": breakFlag = True + robot.sendVelocityCommand(np.zeros(robot.model.nv)) + log_item["qs"] = q + log_item["dqs"] = robot.v + return breakFlag, save_past_item, log_item - path_pol, path2D_untimed = data + _, path2D_untimed = data path2D_untimed = np.array(path2D_untimed).reshape((-1, 2)) # who cares if the velocity is right, # it should be kinda right so that we need less ocp iterations # and that's it - max_base_v = np.linalg.norm(robot.model.velocityLimit[:2]) + max_base_v = np.linalg.norm(robot._max_v[:2]) path2D = path2D_timed(args, path2D_untimed, max_base_v) # create a 3D reference out of the path pathSE3 = path2D_to_SE3_fixed(path2D, args.handlebar_height) - print("following from", pathSE3[0]) - exit() # TODO: EVIL AND HAS TO BE REMOVED FROM HERE if args.visualizer: @@ -136,29 +138,6 @@ def CrocoEndEffectorPathFollowingMPC( the path does NOT need to start from your current pose - you need to get to it yourself. """ - T_w_e = robot.T_w_e - data = None - # get first path - while data is None: - path_planner.sendCommand(T_w_e.translation[:2]) - data = path_planner.getData() - # time.sleep(1 / args.ctrl_freq) - time.sleep(1) - - _, path2D = data - print(path2D[0]) - path2D = np.array(path2D).reshape((-1, 2)) - pathSE3 = path2D_to_SE3_fixed(path2D, args.handlebar_height) - if args.visualizer: - # TODO: document this somewhere - robot.visualizer_manager.sendCommand({"Mgoal": pathSE3[0]}) - print("i'm at", T_w_e) - print("going to", pathSE3[0]) - if np.linalg.norm(pin.log6(T_w_e.actInv(pathSE3[0]))) > 1e-2: - print("running") - print("error", np.linalg.norm(pin.log6(T_w_e.actInv(pathSE3[0])))) - CrocoIKMPC(args, robot, pathSE3[0]) - print("initialized!") ocp = CrocoEEPathFollowingOCP(args, robot, x0) solver = ocp.getSolver() diff --git a/python/smc/control/optimal_control/croco_mpc_point_to_point.py b/python/smc/control/optimal_control/croco_mpc_point_to_point.py index 3088203..02bc018 100644 --- a/python/smc/control/optimal_control/croco_mpc_point_to_point.py +++ b/python/smc/control/optimal_control/croco_mpc_point_to_point.py @@ -50,8 +50,9 @@ def CrocoIKMPCControlLoop(args, robot: SingleArmInterface, solver, T_w_goal, _, log_item["qs"] = x0[: robot.model.nq] log_item["dqs"] = x0[robot.model.nq :] - log_item["dqs_cmd"] = vel_cmds + # log_item["dqs_cmd"] = vel_cmds log_item["u_tau"] = us[0] + log_item["err_norm"] = np.linalg.norm(err_vector).reshape((1,)) return breakFlag, save_past_dict, log_item @@ -80,8 +81,9 @@ def CrocoIKMPC(args, robot, T_w_goal, run=True): log_item = { "qs": np.zeros(robot.model.nq), "dqs": np.zeros(robot.model.nv), - "dqs_cmd": np.zeros(robot.model.nv), # we're assuming full actuation here + # "dqs_cmd": np.zeros(robot.model.nv), # we're assuming full actuation here "u_tau": np.zeros(robot.model.nv), + "err_norm": np.zeros(1), } save_past_dict = {} loop_manager = ControlLoopManager( diff --git a/python/smc/path_generation/path_math/path2d_to_6d.py b/python/smc/path_generation/path_math/path2d_to_6d.py index a90b9c5..7687282 100644 --- a/python/smc/path_generation/path_math/path2d_to_6d.py +++ b/python/smc/path_generation/path_math/path2d_to_6d.py @@ -35,8 +35,8 @@ def path2D_to_SE3_fixed(path2D: np.ndarray, path_height: float): # TODO: make sure this one makes sense rotation = np.array( [ - [-np.cos(thetas[i]), np.sin(thetas[i]), 0.0], - [-np.sin(thetas[i]), -np.cos(thetas[i]), 0.0], + [np.cos(thetas[i]), np.sin(thetas[i]), 0.0], + [np.sin(thetas[i]), -np.cos(thetas[i]), 0.0], [0.0, 0.0, -1.0], ] ) diff --git a/python/smc/path_generation/path_math/path_to_trajectory.py b/python/smc/path_generation/path_math/path_to_trajectory.py index 743f4c4..dace076 100644 --- a/python/smc/path_generation/path_math/path_to_trajectory.py +++ b/python/smc/path_generation/path_math/path_to_trajectory.py @@ -36,7 +36,7 @@ def path2D_timed(args, path2D_untimed, max_base_v): # the strategy is somewhat reasonable at least: # assume we're moving at 90% max velocity in the base, # and use that. - perc_of_max_v = 0.9 + perc_of_max_v = 0.5 base_v = perc_of_max_v * max_base_v # 1) the length of the path divided by 0.9 * max_vel @@ -69,11 +69,11 @@ def path2D_timed(args, path2D_untimed, max_base_v): # time is i * args.ocp_dt for i in range(args.n_knots + 1): # what it should be but gets stuck if we're not yet on path - # s = (i * args.ocp_dt) * t_to_s + s = (i * args.ocp_dt) * t_to_s # full path # NOTE: this should be wrong, and ocp_dt correct, # but it works better for some reason xd - s = i * (max_s / args.n_knots) + # s = i * (max_s / args.n_knots) path2D.append( np.array( [ -- GitLab