From 68a143bcd58ecb26dadb13ba92a82ae77db90c7e Mon Sep 17 00:00:00 2001
From: m-guberina <gubi.guberina@gmail.com>
Date: Wed, 4 Dec 2024 23:51:20 +0100
Subject: [PATCH] made the template for the behaviour tree. put in clicking to
 grasping and then pulling. that part seems to work just fine. need to
 initialize path for pulling for it to make sense. this can be done when the
 clik flag turns of (thus the priority_register should be saved in past data).
 constructing the path for the gripper does not exactly work, but the code is
 there and so it's just the logic that needs to be fixed. i will get more eyes
 on that. next up is to make add a controlLoopManager funciton which can be a
 callback to a ros2 node, and to add ros2 topic communication into
 robotmanager under sendqd and getq. after that we can do vision and tune
 stuff.

---
 python/examples/cart_pulling.py               | 139 +++++++++++-
 .../__pycache__/managers.cpython-312.pyc      | Bin 54489 -> 54697 bytes
 .../basics/__pycache__/basics.cpython-312.pyc | Bin 12790 -> 12790 bytes
 python/ur_simple_control/clik/clik.py         |   5 +-
 python/ur_simple_control/managers.py          |  10 +-
 .../optimal_control/crocoddyl_mpc.py          | 140 +++++++-----
 .../crocoddyl_optimal_control.py              |  34 +--
 .../path_generation/planner.py                | 214 ++++--------------
 8 files changed, 285 insertions(+), 257 deletions(-)

diff --git a/python/examples/cart_pulling.py b/python/examples/cart_pulling.py
index 103355a..08d59cf 100644
--- a/python/examples/cart_pulling.py
+++ b/python/examples/cart_pulling.py
@@ -5,14 +5,15 @@ import argparse, argcomplete
 from functools import partial
 from ur_simple_control.managers import getMinimalArgParser, ControlLoopManager, RobotManager, ProcessManager
 from ur_simple_control.optimal_control.get_ocp_args import get_OCP_args
-from ur_simple_control.optimal_control.crocoddyl_optimal_control import createCrocoEEPathFollowingOCP, solveCrocoOCP
-from ur_simple_control.optimal_control.crocoddyl_mpc import CrocoEndEffectorPathFollowingMPCControlLoop, CrocoEndEffectorPathFollowingMPC, BaseAndEEPathFollowingMPC
+from ur_simple_control.optimal_control.crocoddyl_optimal_control import *
+from ur_simple_control.optimal_control.crocoddyl_mpc import *
 from ur_simple_control.basics.basics import followKinematicJointTrajP
 from ur_simple_control.util.logging_utils import LogManager
 from ur_simple_control.visualize.visualize import plotFromDict
-from ur_simple_control.clik.clik import getClikArgs, cartesianPathFollowingWithPlanner
+from ur_simple_control.clik.clik import getClikArgs, cartesianPathFollowingWithPlanner, controlLoopClik, invKinmQP, dampedPseudoinverse
 import pinocchio as pin
 import crocoddyl
+from functools import partial
 import importlib.util
 from ur_simple_control.path_generation.planner import starPlanner, getPlanningArgs, createMap
 import yaml
@@ -26,7 +27,8 @@ def get_args():
     parser = get_OCP_args(parser)
     parser = getClikArgs(parser) # literally just for goal error
     parser = getPlanningArgs(parser)
-    parser.add_argument('--handlebar-height', type=float, default=0.5)
+    parser.add_argument('--handlebar-height', type=float, default=0.5,\
+                        help="heigh of handlebar of the cart to be pulled")
     parser.add_argument('--base-to-handlebar-preferred-distance', type=float, default=0.7, \
             help="prefered path arclength from mobile base position to handlebar")
     argcomplete.autocomplete(parser)
@@ -43,6 +45,127 @@ def get_args():
     args.n_pol = mpc_params['n_pol']
     return args
 
+def isGraspOK(args, robot, grasp_pose):
+    isOK = False
+    SEerror = robot.getT_w_e().actInv(grasp_pose)
+    err_vector = pin.log6(SEerror).vector 
+    # TODO: figure this out
+    # it seems you have to use just the arm to get to finish with this precision 
+    #if np.linalg.norm(err_vector) < robot.args.goal_error:
+    if np.linalg.norm(err_vector) < 2*1e-1:
+        isOK = True
+    return not isOK 
+
+def cartPullingControlLoop(args, robot : RobotManager, goal, solver_grasp, solver_pulling,
+                           path_planner : ProcessManager, i : int, past_data):
+    """
+    cartPulling
+    0) if obstacles present, don't move
+    1) if no obstacles, but not grasped/grasp is off-target, grasp the handlebar with a p2p strategy.
+    2) if no obstacles, and grasp ok, then pull toward goal with cartPulling mpc
+    3) parking?
+    4) ? always true (or do nothing is always true, whatever)
+    """
+    # we use binary as string representation (i don't want to deal with python's binary representation).
+    # the reason for this is that then we don't have disgusting nested ifs
+    priority_register = ['0','1','1']
+    # TODO implement this based on laser scanning or whatever
+    #priority_register[0] = str(int(areObstaclesTooClose()))
+    # TODO: get grasp pose from vision, this is initial hack to see movement
+    if i < 300:
+        grasp_pose = pin.SE3(np.eye(3), np.array([9.0 + args.base_to_handlebar_preferred_distance, 4.0, args.handlebar_height]))
+    else:
+        grasp_pose = robot.getT_w_e()
+    priority_register[1] = str(int(isGraspOK(args, robot, grasp_pose)))
+    # interpret string as base 2 number, return int in base 10
+    priority_int = ""
+    for prio in priority_register:
+        priority_int += prio
+    priority_int = int(priority_int, 2)
+    breakFlag = False
+    save_past_item = {}
+    log_item = {}
+
+    # case 0)
+    if priority_int >= 4:
+        robot.sendQd(np.zeros(robot.model.nv))
+
+    # case 1)
+    # TODO: make it an argument obviously
+    usempc = False
+    if (priority_int < 4) and (priority_int >= 2):
+        # TODO: make goal an argument, remove Mgoal from robot
+        robot.Mgoal = grasp_pose
+        if usempc:
+            # TODO: make it not hit the handlebar or other shit in the process
+            for i, runningModel in enumerate(solver_grasp.problem.runningModels):
+                runningModel.differential.costs.costs['gripperPose'].cost.residual.reference = grasp_pose
+            solver_grasp.problem.terminalModel.differential.costs.costs['gripperPose'].cost.residual.reference = grasp_pose
+            robot.Mgoal = grasp_pose
+            #breakFlag, save_past_item, log_item = CrocoIKMPCControlLoop(args, robot, solver_grasp, i, past_data)
+            CrocoIKMPCControlLoop(args, robot, solver_grasp, i, past_data)
+        else:
+            #controlLoopClik(robot, invKinmQP, i, past_data)
+            clikController = partial(dampedPseudoinverse, 1e-3)
+            controlLoopClik(robot, clikController, i, past_data)
+
+    # case 2)
+    # MASSIVE TODO: 
+    # WHEN STARTING, TO INITIALIZE PREPOPULATE THE PATH WITH AN INTERPOLATION OF 
+    # A LINE FROM WHERE THE GRIPPER IS NOW TO THE BASE
+    if priority_int < 2:
+        breakFlag, save_past_item, log_item = BaseAndEEPathFollowingMPCControlLoop(args, robot, solver_pulling, path_planner, i, past_data)
+        #BaseAndEEPathFollowingMPCControlLoop(args, robot, solver_pulling, path_planner, i, past_data)
+
+    q = robot.getQ()
+    p = q[:2]
+    # needed for cart pulling
+    save_past_item['path2D_untimed'] = p
+    log_item['qs'] = q.reshape((robot.model.nq,))
+    log_item['dqs'] = robot.getQd().reshape((robot.model.nv,))
+    return breakFlag, save_past_item, log_item
+
+
+def cartPulling(args, robot : RobotManager, goal, path_planner):
+    ############################
+    #  setup cart-pulling mpc  #
+    ############################
+    x0 = np.concatenate([robot.getQ(), robot.getQd()])
+    problem_pulling = createBaseAndEEPathFollowingOCP(args, robot, x0)
+    if args.solver == "boxfddp":
+        solver_pulling = crocoddyl.SolverBoxFDDP(problem_pulling)
+    if args.solver == "csqp":
+        solver_pulling = mim_solvers.SolverCSQP(problem_pulling)
+    xs_init = [x0] * (solver_pulling.problem.T + 1)
+    us_init = solver_pulling.problem.quasiStatic([x0] * solver_pulling.problem.T)
+    solver_pulling.solve(xs_init, us_init, args.max_solver_iter)
+
+    #############################################
+    #  setup point-to-point handlebar grasping  #
+    # TODO: have option to swith this for clik  #
+    #############################################
+    grasp_pose = robot.getT_w_e()
+    problem_grasp = createCrocoIKOCP(args, robot, x0, grasp_pose)
+    if args.solver == "boxfddp":
+        solver_grasp = crocoddyl.SolverBoxFDDP(problem_grasp)
+    if args.solver == "csqp":
+        solver_grasp = mim_solvers.SolverCSQP(problem_grasp)
+    xs_init = [x0] * (solver_grasp.problem.T + 1)
+    us_init = solver_grasp.problem.quasiStatic([x0] * solver_grasp.problem.T)
+    solver_grasp.solve(xs_init, us_init, args.max_solver_iter)
+    
+    controlLoop = partial(cartPullingControlLoop, args, robot, goal, solver_grasp, solver_pulling, path_planner)
+
+    log_item = {}
+    q = robot.getQ()
+    T_w_e = robot.getT_w_e()
+    log_item['qs'] = q.reshape((robot.model.nq,))
+    log_item['dqs'] = robot.getQd().reshape((robot.model.nv,))
+    save_past_item = {'path2D_untimed' : T_w_e.translation[:2]}
+    loop_manager = ControlLoopManager(robot, controlLoop, args, save_past_item, log_item)
+    loop_manager.run()
+
+    
 
 if __name__ == "__main__": 
     args = get_args()
@@ -51,7 +174,6 @@ if __name__ == "__main__":
     robot = RobotManager(args)
     robot.q[0] = 9.0
     robot.q[1] = 4.0
-    #time.sleep(5)
 
     x0 = np.concatenate([robot.getQ(), robot.getQd()])
     robot._step()
@@ -79,7 +201,9 @@ if __name__ == "__main__":
     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)
-    #time.sleep(5)
+    # wait for meshcat to initialize
+    if args.visualize_manipulator:
+        time.sleep(5)
     # clik version
     #cartesianPathFollowingWithPlanner(args, robot, path_planner)
     # end-effector tracking version
@@ -88,9 +212,10 @@ if __name__ == "__main__":
     # and also make the actual path for the cart and then construct the reference
     # for the mobile base out of a later part of the path)
     # atm this is just mobile base tracking 
-    BaseAndEEPathFollowingMPC(args, robot, x0, path_planner)
+    cartPulling(args, robot, goal, path_planner)
     print("final position:")
     print(robot.getT_w_e())
+    path_planner.terminateProcess()
 
     if args.save_log:
         robot.log_manager.plotAllControlLoops()
diff --git a/python/ur_simple_control/__pycache__/managers.cpython-312.pyc b/python/ur_simple_control/__pycache__/managers.cpython-312.pyc
index 0343be7262ccf487703d207bbfbaeb2cdb691335..e02e89e5479c52cad283376b57866178a3e4284e 100644
GIT binary patch
delta 2937
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

delta 2869
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
YGU|V10FmFzgtC|zof$tdfGMy&0QU$HoB#j-

diff --git a/python/ur_simple_control/basics/__pycache__/basics.cpython-312.pyc b/python/ur_simple_control/basics/__pycache__/basics.cpython-312.pyc
index e1f51ccc304659f0c256d15a324b24fc9c0a6ee0..5f7f84bdf9b600f8d0c911ebd0919c211ea1c011 100644
GIT binary patch
delta 20
bcmeyC{4JULG%qg~0|NuY`_6!k+^-D*Q7Z>c

delta 20
bcmeyC{4JULG%qg~0|NuYzfy;d+^-D*P|OE7

diff --git a/python/ur_simple_control/clik/clik.py b/python/ur_simple_control/clik/clik.py
index 7b53005..c26e75c 100644
--- a/python/ur_simple_control/clik/clik.py
+++ b/python/ur_simple_control/clik/clik.py
@@ -92,9 +92,8 @@ def invKinmQP(J, err_vector, lb=None, ub=None):
     # TODO: why is q all 0?
     q = np.array([0] * J.shape[1], dtype="double")
     G = None
-    # TODO: extend for orientation as well
-    b = err_vector#[:3]
-    A = J#[:3]
+    b = err_vector
+    A = J
     # TODO: you probably want limits here
     #lb = None
     #ub = None
diff --git a/python/ur_simple_control/managers.py b/python/ur_simple_control/managers.py
index f6731d0..70e8783 100644
--- a/python/ur_simple_control/managers.py
+++ b/python/ur_simple_control/managers.py
@@ -88,8 +88,10 @@ def getMinimalArgParser():
                     default="192.168.1.102")
     parser.add_argument('--pinocchio-only', action=argparse.BooleanOptionalAction, \
             help="whether you want to just integrate with pinocchio", default=True)
+    parser.add_argument('--ctrl-freq', type=int, \
+            help="frequency of the control loop", default=500)
     parser.add_argument('--fast-simulation', action=argparse.BooleanOptionalAction, \
-            help="do you want simulation to run over 500Hz? (real-time viz relies on 500Hz)", default=False)
+            help="do you want simulation to as fast as possible? (real-time viz relies on 500Hz)", default=False)
     parser.add_argument('--visualize-manipulator', action=argparse.BooleanOptionalAction, \
             help="whether you want to visualize the manipulator and workspace with meshcat", default=True)
     parser.add_argument('--real-time-plotting', action=argparse.BooleanOptionalAction, \
@@ -440,7 +442,8 @@ class RobotManager:
         #self.JOINT_ID = 6
         self.ee_frame_id = self.model.getFrameId("tool0")
         #self.ee_frame_id = self.model.getFrameId("hande_right_finger_joint")
-        self.update_rate = 500 #Hz
+        # TODO: add -1 option here meaning as fast as possible
+        self.update_rate = args.ctrl_freq #Hz
         self.dt = 1 / self.update_rate
         # you better not give me crazy stuff
         # and i'm not clipping it, you're fixing it
@@ -1135,7 +1138,8 @@ class ProcessManager:
             self.shm_cmd.unlink()
         if self.args.debug_prints:
             print(f"i am putting befree in {self.side_process.name}'s command queue to stop it")
-        self.command_queue.put_nowait("befree")
+        if self.comm_direction != 3:
+            self.command_queue.put_nowait("befree")
         try:
             self.side_process.terminate()
             if self.args.debug_prints:
diff --git a/python/ur_simple_control/optimal_control/crocoddyl_mpc.py b/python/ur_simple_control/optimal_control/crocoddyl_mpc.py
index d0a17c5..6446153 100644
--- a/python/ur_simple_control/optimal_control/crocoddyl_mpc.py
+++ b/python/ur_simple_control/optimal_control/crocoddyl_mpc.py
@@ -1,6 +1,6 @@
 from ur_simple_control.managers import getMinimalArgParser, ControlLoopManager, RobotManager, ProcessManager
 from ur_simple_control.optimal_control.crocoddyl_optimal_control import createCrocoIKOCP, createCrocoEEPathFollowingOCP, createBaseAndEEPathFollowingOCP
-from ur_simple_control.path_generation.planner import path2D_to_timed_SE3, pathPointFromPathParam, path2D_to_timed_SE3_base_and_ee
+from ur_simple_control.path_generation.planner import path2D_timed, pathPointFromPathParam, path2D_to_SE3
 import pinocchio as pin
 import crocoddyl
 import numpy as np
@@ -54,13 +54,13 @@ def CrocoIKMPCControlLoop(args, robot : RobotManager, solver, i, past_data):
     robot.sendQd(vel_cmds)
 
     log_item['qs'] = x0[:robot.model.nq]
-    log_item['dqs'] = x0[robot.model.nq:]
+    log_item['dqs'] = x0[robot.model.nv:]
     log_item['dqs_cmd'] = vel_cmds
     log_item['u_tau'] = us[0]
     
     return breakFlag, save_past_dict, log_item
 
-def CrocoIKMPC(args, robot, x0, goal):
+def CrocoIKMPC(args, robot, goal):
     """
     IKMPC
     -----
@@ -69,6 +69,7 @@ def CrocoIKMPC(args, robot, x0, goal):
     a dynamics level, and velocities we command
     are actually extracted from the state x(q,dq)
     """
+    x0 = np.concatenate([robot.getQ(), robot.getQd()])
     problem = createCrocoIKOCP(args, robot, x0, goal)
     if args.solver == "boxfddp":
         solver = crocoddyl.SolverBoxFDDP(problem)
@@ -78,7 +79,6 @@ def CrocoIKMPC(args, robot, x0, goal):
     # technically should be done in controlloop because now
     # it's solved 2 times before the first command,
     # but we don't have time for details rn
-    x0 = np.concatenate([robot.getQ(), robot.getQd()])
     xs_init = [x0] * (solver.problem.T + 1)
     us_init = solver.problem.quasiStatic([x0] * solver.problem.T)
     solver.solve(xs_init, us_init, args.max_solver_iter)
@@ -112,6 +112,7 @@ def CrocoEndEffectorPathFollowingMPCControlLoop(args, robot : RobotManager, solv
     T_w_e = robot.getT_w_e()
     p = T_w_e.translation[:2]
 
+    # TODO: replace with shm
     if not (type(path_planner) == types.FunctionType):
         path_planner.sendFreshestCommand({'p' : p})
 
@@ -124,7 +125,7 @@ def CrocoEndEffectorPathFollowingMPCControlLoop(args, robot : RobotManager, solv
         data = path_planner.getData()
         if data == None:
             if args.debug_prints:
-                print("got no path so no doing anything")
+                print("CTRL: got no path so not i won't move")
             robot.sendQd(np.zeros(robot.model.nv))
             log_item['qs'] = q.reshape((robot.model.nq,))
             log_item['dqs'] = robot.getQd().reshape((robot.model.nv,))
@@ -203,7 +204,7 @@ def CrocoEndEffectorPathFollowingMPC(args, robot, x0, path_planner):
     loop_manager.run()
 
 
-def BaseAndEEPathFollowingMPCControlLoop(args, robot : RobotManager, solver : crocoddyl.SolverBoxFDDP, path_planner : ProcessManager, i, past_data):
+def BaseAndEEPathFollowingMPCControlLoop(args, robot : RobotManager, solver : crocoddyl.SolverBoxFDDP, path_planner : ProcessManager, iter_n, past_data):
     """
     CrocoPathFollowingMPCControlLoop
     -----------------------------
@@ -219,54 +220,58 @@ def BaseAndEEPathFollowingMPCControlLoop(args, robot : RobotManager, solver : cr
 
     q = robot.getQ()
     T_w_e = robot.getT_w_e()
-    #p = T_w_e.translation[:2]
     p = q[:2]
-    #print("CTRL:", p)
     # NOTE: this is the actual position, not what the path suggested
     # whether this or path reference should be put in is debateable. 
     # this feels more correct to me.
     save_past_dict['path2D_untimed'] = p
+    path_planner.sendCommandViaSharedMemory(p)
 
-    if not (type(path_planner) == types.FunctionType):
-        #path_planner.sendFreshestCommand({'p' : p})
-        path_planner.sendCommandViaSharedMemory(p)
-
+    ###########################
+    #  get path from planner  #
+    ###########################
     # NOTE: it's pointless to recalculate the path every time 
     # if it's the same 2D path
-    # get the path from the base from the current base position onward
-    if type(path_planner) == types.FunctionType:
-        pathSE3 = path_planner(T_w_e, i)
-    else:
-        data = path_planner.getData()
-        if data == None:
-            if args.debug_prints:
-                print("got no path so no doing anything")
-            robot.sendQd(np.zeros(robot.model.nv))
-            log_item['qs'] = q.reshape((robot.model.nq,))
-            log_item['dqs'] = robot.getQd().reshape((robot.model.nv,))
-            return breakFlag, save_past_dict, log_item
-        if data == "done":
-            breakFlag = True
-
-        path_pol_base, path2D_untimed_base = data
-        #path2D_untimed_base.insert(0, p[1])
-        #path2D_untimed_base.insert(0, p[0])
-        path2D_untimed_base = np.array(path2D_untimed_base).reshape((-1,2))
-        # should be precomputed somewhere but this is nowhere near the biggest problem
-        max_base_v = np.linalg.norm(robot.model.velocityLimit[:2])
-
-        # 1) make 6D path out of [[x0,y0],...] 
-        # that represents the center of the cart
-        # TODO: USE THIS ONCE IMPLEMENTED
-        #pathSE3 = path2D_to_timed_SE3_base_and_ee(args, path_pol_base, path2D_untimed_base, max_base_v)
-        # TODO: separate out timing construction so that it can be used for the handlebar path as well
-        pathSE3_base = path2D_to_timed_SE3(args, path_pol_base, path2D_untimed_base, max_base_v, 0.0)
-    # TODO: EVIL AND HAS TO BE REMOVED FROM HERE
-    if args.visualize_manipulator:
-        if i % 20 == 0:
-            robot.visualizer_manager.sendCommand({"path": pathSE3_base})
+    # get the path from the base from the current base position onward.
+    # but we're still fast so who cares
+    data = path_planner.getData()
+    if data == None:
+        if args.debug_prints:
+            print("CTRL: got no path so not i won't move")
+        robot.sendQd(np.zeros(robot.model.nv))
+        log_item['qs'] = q.reshape((robot.model.nq,))
+        log_item['dqs'] = robot.getQd().reshape((robot.model.nv,))
+        return breakFlag, save_past_dict, log_item
+
+    if data == "done":
+        breakFlag = True
+        robot.sendQd(np.zeros(robot.model.nv))
+        log_item['qs'] = q.reshape((robot.model.nq,))
+        log_item['dqs'] = robot.getQd().reshape((robot.model.nv,))
+        return breakFlag, save_past_dict, log_item
+
+    ##########################################
+    #  construct timed 2D path for the base  #
+    ##########################################
+    path_pol_base, path2D_untimed_base = data
+    path2D_untimed_base = np.array(path2D_untimed_base).reshape((-1,2))
+    # ideally should be precomputed somewhere 
+    max_base_v = np.linalg.norm(robot.model.velocityLimit[:2])
+    # base just needs timing on the path,
+    # and it's of height 0 (i.e. the height of base's planar joint)
+    path_base = path2D_timed(args, path_pol_base, path2D_untimed_base, max_base_v, 0.0)
     
-    # now find the previous path point of arclength base_to_handlebar_preferred_distance
+    ###################################################
+    #  construct timed SE3 path for the end-effector  #
+    ###################################################
+    # this works as follow
+    # 1) find the previous path point of arclength base_to_handlebar_preferred_distance.
+    # first part of the path is from there to current base position,
+    # second is just the current base's plan.
+    # 2) construct timing on the first part.
+    # 3) join that with the already timed second part.
+    # 4) turn the timed 2D path into an SE3 trajectory
+
     # NOTE: this can be O(1) instead of O(n) but i can't be bothered
     path_arclength = np.linalg.norm(p - past_data['path2D_untimed'])
     handlebar_path_index = -1
@@ -276,11 +281,33 @@ def BaseAndEEPathFollowingMPCControlLoop(args, robot : RobotManager, solver : cr
             break
         path_arclength += np.linalg.norm(past_data['path2D_untimed'][i - 1] - past_data['path2D_untimed'][i])
     # i shouldn't need to copy-paste everything but what can you do
-    arra = np.array(past_data['path2D_untimed'])
-    path2D_handlebar = np.vstack((arra[i:], path2D_untimed_base))
-    # now we need to construct timing for this
-
-
+    path2D_handlebar_1_untimed = np.array(past_data['path2D_untimed'])
+    # NOTE: BIG ASSUMPTION
+    # let's say we're computing on time, and that's the time spacing
+    # of previous path points.
+    # this means you need to lower the control frequency argument
+    # if you're not meeting deadlines.
+    # if you really need timing information, you should actually
+    # get it from ControlLoopManager instead of i,
+    # but let's say this is better because you're forced to know
+    # how fast you are instead of ducktaping around delays.
+    # TODO: actually save timing, pass t instead of i to controlLoops
+    # from controlLoopManager
+    # NOTE: this might not working when rosified so check that first
+    time_past = np.linspace(0.0, args.past_window_size * robot.dt, args.past_window_size)
+    s = np.linspace(0.0, args.n_knots * args.ocp_dt, args.n_knots)
+    path2D_handlebar_1 = np.hstack((
+        np.interp(s, time_past, path2D_handlebar_1_untimed[:,0]).reshape((-1,1)), 
+        np.interp(s, time_past, path2D_handlebar_1_untimed[:,1]).reshape((-1,1))))
+
+    # TODO: combine with base for maximum correctness
+    pathSE3_handlebar = path2D_to_SE3(path2D_handlebar_1, args.handlebar_height)
+    # TODO: EVIL AND HAS TO BE REMOVED FROM HERE
+    # TODO: make it work with just translations too
+    if args.visualize_manipulator:
+        if iter_n % 20 == 0:
+            #robot.visualizer_manager.sendCommand({"path": path_base})
+            robot.visualizer_manager.sendCommand({"path": pathSE3_handlebar})
 
     x0 = np.concatenate([robot.getQ(), robot.getQd()])
     solver.problem.x0 = x0
@@ -290,10 +317,12 @@ def BaseAndEEPathFollowingMPCControlLoop(args, robot : RobotManager, solver : cr
     us_init = list(solver.us[1:]) + [solver.us[-1]]
 
     for i, runningModel in enumerate(solver.problem.runningModels):
-        runningModel.differential.costs.costs['gripperPose' + str(i)].cost.residual.reference = pathSE3_base[i]
+        runningModel.differential.costs.costs['base_translation' + str(i)].cost.residual.reference = path_base[i]
+        runningModel.differential.costs.costs['ee_pose' + str(i)].cost.residual.reference = pathSE3_handlebar[i]
 
     # idk if that's necessary
-    solver.problem.terminalModel.differential.costs.costs['gripperPose'+str(args.n_knots)].cost.residual.reference = pathSE3_base[-1]
+    solver.problem.terminalModel.differential.costs.costs['base_translation'+str(args.n_knots)].cost.residual.reference = path_base[-1]
+    solver.problem.terminalModel.differential.costs.costs['ee_pose'+str(args.n_knots)].cost.residual.reference = pathSE3_handlebar[-1]
 
     solver.solve(xs_init, us_init, args.max_solver_iter)
     xs = np.array(solver.xs)
@@ -306,7 +335,7 @@ def BaseAndEEPathFollowingMPCControlLoop(args, robot : RobotManager, solver : cr
     log_item['dqs'] = robot.getQd().reshape((robot.model.nv,))
     return breakFlag, save_past_dict, log_item
 
-def BaseAndEEPathFollowingMPC(args, robot, x0, path_planner):
+def BaseAndEEPathFollowingMPC(args, robot, path_planner):
     """
     CrocoEndEffectorPathFollowingMPC
     -----
@@ -316,16 +345,13 @@ def BaseAndEEPathFollowingMPC(args, robot, x0, path_planner):
     are actually extracted from the state x(q,dq).
     """
 
+    x0 = np.concatenate([robot.getQ(), robot.getQd()])
     problem = createBaseAndEEPathFollowingOCP(args, robot, x0)
     if args.solver == "boxfddp":
         solver = crocoddyl.SolverBoxFDDP(problem)
     if args.solver == "csqp":
         solver = mim_solvers.SolverCSQP(problem)
 
-    # technically should be done in controlloop because now
-    # it's solved 2 times before the first command,
-    # but we don't have time for details rn
-    x0 = np.concatenate([robot.getQ(), robot.getQd()])
     xs_init = [x0] * (solver.problem.T + 1)
     us_init = solver.problem.quasiStatic([x0] * solver.problem.T)
     solver.solve(xs_init, us_init, args.max_solver_iter)
diff --git a/python/ur_simple_control/optimal_control/crocoddyl_optimal_control.py b/python/ur_simple_control/optimal_control/crocoddyl_optimal_control.py
index e323d59..09157b3 100644
--- a/python/ur_simple_control/optimal_control/crocoddyl_optimal_control.py
+++ b/python/ur_simple_control/optimal_control/crocoddyl_optimal_control.py
@@ -396,7 +396,10 @@ def createBaseAndEEPathFollowingOCP(args, robot : RobotManager, x0):
     NOTE: the path MUST be time indexed with the SAME time used between the knots
     """
     T_w_e = robot.getT_w_e()
-    path = [T_w_e] * args.n_knots
+    # TODO there has to be a path for the base and 
+    # a different one for the ee
+    path_ee = [T_w_e] * args.n_knots
+    path_base = [np.append(x0[:2], 0.0)] * args.n_knots
     # underactuation is done by setting max-torque to 0 in the robot model,
     # and since we torques are constrained we're good
     state = crocoddyl.StateMultibody(robot.model)
@@ -486,23 +489,21 @@ def createBaseAndEEPathFollowingOCP(args, robot : RobotManager, x0):
         runningCostModel.addCost("uReg", uRegCost, 1e-3)
         if args.solver == "boxfddp":
             runningCostModel.addCost("limitCost", limitCost, 1e3)
-        # TODO: implement
-#        framePlacementResidual = crocoddyl.ResidualModelFramePlacement(
-#                state,
-#                robot.model.getFrameId("tool0"),
-#                path["ee"][i], # this better be done with the same time steps as the knots
-#                         # NOTE: this should be done outside of this function to clarity
-#                state.nv)
-        framePlacementResidual = crocoddyl.ResidualModelFramePlacement(
+        eePoseResidual = crocoddyl.ResidualModelFramePlacement(
+                state,
+                robot.model.getFrameId("tool0"),
+                path_ee[i], # this better be done with the same time steps as the knots
+                         # NOTE: this should be done outside of this function to clarity
+                state.nv)
+        eeTrackingCost = crocoddyl.CostModelResidual(state, eePoseResidual)
+        runningCostModel.addCost("ee_pose" + str(i), eeTrackingCost, 1e2)
+        baseTranslationResidual = crocoddyl.ResidualModelFrameTranslation(
                 state,
                 robot.model.getFrameId("mobile_base"),
-        # TODO: implement
-                #path["base"][i], # this better be done with the same time steps as the knots
-                #                 # NOTE: this should be done outside of this function to clarity
-                path[i],
+                path_base[i],
                 state.nv)
-        goalTrackingCost = crocoddyl.CostModelResidual(state, framePlacementResidual)
-        runningCostModel.addCost("gripperPose" + str(i), goalTrackingCost, 1e2)
+        baseTrackingCost = crocoddyl.CostModelResidual(state, baseTranslationResidual)
+        runningCostModel.addCost("base_translation" + str(i), baseTrackingCost, 1e2)
 
         if args.solver == "boxfddp":
             runningModel = crocoddyl.IntegratedActionModelEuler(
@@ -540,7 +541,8 @@ def createBaseAndEEPathFollowingOCP(args, robot : RobotManager, x0):
                 0.0,
             )
 
-    terminalCostModel.addCost("gripperPose" + str(args.n_knots), goalTrackingCost, 1e2)
+    terminalCostModel.addCost("ee_pose" + str(args.n_knots), eeTrackingCost, 1e2)
+    terminalCostModel.addCost("base_translation" + str(args.n_knots), baseTrackingCost, 1e2)
 
     # now we define the problem
     problem = crocoddyl.ShootingProblem(x0, runningModels, terminalModel)
diff --git a/python/ur_simple_control/path_generation/planner.py b/python/ur_simple_control/path_generation/planner.py
index a93d6d8..f48dca2 100644
--- a/python/ur_simple_control/path_generation/planner.py
+++ b/python/ur_simple_control/path_generation/planner.py
@@ -463,14 +463,12 @@ def pathVisualizer(x0, goal, map_as_list, positions):
 def pathPointFromPathParam(n_pol, path_dim, path_pol, s):
     return [np.polyval(path_pol[j*(n_pol+1):(j+1)*(n_pol+1)], s) for j in range(path_dim)]
 
-def path2D_to_timed_SE3(args, path_pol, path2D_untimed, max_base_v, path_height):
+def path2D_timed(args, path_pol, path2D_untimed, max_base_v, path_height):
     """
-    path2D_to_timed_SE3
+    path2D_timed
     ---------------------
-    we have a 2D path of (N,2) shape as reference
-    the OCP accepts SE3 (it could just rotations or translation too),
-    so this function constructs it from the 2D path.
-    it also times it as this is what the current ocp formulation needs:
+    we have a 2D path of (N,2) shape as reference.
+    it times it as this is what the current ocp formulation needs:
         there should be a timing associated with the path,
         because defining the cost function as the fit of the rollout to the path
         is complicated to do software-wise.
@@ -487,6 +485,11 @@ def path2D_to_timed_SE3(args, path_pol, path2D_untimed, max_base_v, path_height)
     trajectory here, it's a man-made heuristic,
     and we should leave that to the MPC,
     but that requires too much coding and there is no time rn.
+    the idea is to use compute the tangent of the path,
+    and use that to make a 2D frenet frame.
+    this is the put to some height, making it SE3.
+    i.e. roll and pitch are fixed to be 0,
+    but you could make them some other constant
     """
 
     ####################################################
@@ -530,28 +533,24 @@ def path2D_to_timed_SE3(args, path_pol, path2D_untimed, max_base_v, path_height)
         # what it should be but gets stuck if we're not yet on path
         #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)
         #path2D.append(pathPointFromPathParam(args.n_pol, args.np, path_pol, s))
         path2D.append(np.array([np.interp(s, s_vec, path2D_untimed[:,0]), 
-                                np.interp(s, s_vec, path2D_untimed[:,1])]))
+                                np.interp(s, s_vec, path2D_untimed[:,1]),
+                                path_height]))
     path2D = np.array(path2D)
+    return path2D
 
-    ####################################################
-    #  constructing the SE3 reference from [x,y] path  #
-    ####################################################
-    # 1) we need to add points so that there are args.n_knots of them
-    # the -1 is here because we're finite differencing to get theta,
-    # so we need one extra
-    #path2D = list(path2D)
-    ## in case there's too few
-    #while len(path2D) - 1 < args.n_knots:
-    #    path2D.append(path2D[-1])
-    ## in case there's too much
-    #while len(path2D) - 1> args.n_knots:
-    #    path2D.pop()
-    #path2D = np.array(path2D)
-
-
+def path2D_to_SE3(path2D, path_height):
+    """
+    path2D_SE3
+    ----------
+    we have a 2D path of (N,2) shape as reference
+    the OCP accepts SE3 (it could just rotations or translation too),
+    so this function constructs it from the 2D path.
+    """
     #########################
     #  path2D into pathSE2  #
     #########################
@@ -566,170 +565,36 @@ def path2D_to_timed_SE3(args, path_pol, path2D_untimed, max_base_v, path_height)
     # elementwise arctan2
     thetas = np.arctan2(x_diff, y_diff)
 
-    #thetas = thetas.reshape((-1, 1))
-    #path_SE2 = np.hstack((path2D, thetas))
-    
     ######################################
     #  construct SE3 from SE2 reference  #
     ######################################
     # the plan is parallel to the ground because it's a mobile
     # manipulation task
-    # TODO: enforce timing, interpolate the path accordingly
-    path = []
+    pathSE3 = []
     for i in range(len(path2D) - 1):
         rotation = pin.rpy.rpyToMatrix(0.0, 0.0, thetas[i])
         rotation = pin.rpy.rpyToMatrix(np.pi/2, np.pi/2,0.0) @ rotation
         #rotation = np.eye(3)
         translation = np.array([path2D[i][0], path2D[i][1], path_height])
-        path.append(pin.SE3(rotation, translation))
-
-    return path
+        pathSE3.append(pin.SE3(rotation, translation))
+    pathSE3.append(pin.SE3(rotation, translation))
+    return pathSE3
 
+def path2D_to_timed_SE3(todo):
+    pass
 
-def path2D_to_timed_SE3_base_and_ee(args, path_pol, path2D_untimed, max_base_v):
-    """
-    path2D_to_timed_SE3
-    ---------------------
-    we have a 2D path of (N,2) shape as reference.
-    from this we construct timed SE3 references for the cart, end-effector(s) and the mobile base,
-    in that order. this is done as follows.
-    0) the path starts at the cart. we align the end-effector and the mobile base to be 
-       ON this path as well. to we can follow the path, we need to make the path smooth enough.
-       that is considered a part of the planning problem.
-    1) when the initial plan is set, the base-endeffector-cart system needs to be aligned with 
-       the path. we can ensure AN initial alignment in the beginning when we grasp the cart.
-       furthermore, we can compute the initial path to see what the alignment should be.
-       then we can let either point-to-point clik or mpc align the bodies to this,
-       or let the controller-planner architecture run, 
-       BUT then we need to test the stability w.r.t. initial point.
-    2) the cart is a rigid body, so it doesn't matter which point of it is being referenced.
-       (one point localizes the whole rigid body because it's rigid).
-       we select the point(s) on the handlebar where we will perform the grasp.
-    3) DUE to rigid grasping, the end-effector(s)'s reference is the same 
-       as the one for the handlebar. thus we actually construct only 2 references from the 2D path.
-       the initial point of the path is this one.
-    4) the path is of some length (it's a variable in the planner). we ensure it is long enough
-       to fit the base on it. this does mean that we can not GUARANTEE we avoid all obstacles,
-       because the path-planning guarantees this only for the initial path point.
-    5) TODO for extra fun and profit: make an underactuated 2-point model in the path planner.
-    6) timing is obtained from the maximum velocity of the robot and total path length. 
-       this is a stupid heuristic. the time-scaling of the path should be a decision variable in the OCP.
-    7) TODO for extra fun and profit: make time-scaling of the path a decision variable in the OCP.
-    """
-
-    ####################################################
-    #  getting a timed 2D trajectory from a heuristic  #
-    ####################################################
-    # 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
-    base_v = perc_of_max_v * max_base_v 
-    
-    # 1) the length of the path divided by 0.9 * max_vel 
-    #    gives us the total time of the trajectory,
-    #    so we first compute that
-    # easiest possible way to get approximate path length
-    # (yes it should be from the polynomial approximation but that takes time to write)
-    x_i = path2D_untimed[:,0][:-1] # no last element
-    y_i = path2D_untimed[:,1][:-1] # no last element
-    x_i_plus_1 = path2D_untimed[:,0][1:] # no first element
-    y_i_plus_1 = path2D_untimed[:,1][1:] # no first element
-    x_diff = x_i_plus_1 - x_i
-    x_diff = x_diff.reshape((-1,1))
-    y_diff = y_i_plus_1 - y_i
-    y_diff = y_diff.reshape((-1,1))
-    path_length = np.sum(np.linalg.norm(np.hstack((x_diff, y_diff)), axis=1))
-    total_time = path_length / base_v
-    # 2) we find the correspondence between s (path parameter) and time
-    # TODO: read from where max_s should be 5, but seba checked that it's 5 for some reason
-    max_s = 5 
-    t_to_s = max_s / total_time
-    # 3) construct the ocp-timed 2D path for the cart  and for the mobile base of the robot
-    # TODO: tune this value!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
-    # TODO: tune this value!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
-    # TODO: tune this value!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
-    # TODO make this an argument
-    # TODO: we need a function that takes time as input and spits
-#    need to have set interpolation to implement that
-    # out a path point of distance base_to_handlebar_prefered_distance from time 0.
-    # (it's the classic integral for curve length) 
-    #      cart_to_base_prefered_distance as percentage of path length * size of s (again why the fuck isn't s=1????)
-    handlebar_to_base_as_s = (args.base_to_handlebar_preferred_distance / path_length) * max_s
-    path2D_mobile_base = []
-    path2D_cart = []
-    # time is i * args.ocp_dt
-    for i in range(args.n_knots + 1):
-        s_cart = (i * args.ocp_dt) * t_to_s
-        # since we're pulling the robot is in front of the cart
-        s_mobile_base = s_cart + cart_to_base_as_s
-        path2D_cart.append(pathPointFromPathParam(args.n_pol, args.np, path_pol, s))
-    path2D_cart = np.array(path2D_cart)
-
-
-    ####################################################
-    #  constructing the SE3 reference from [x,y] path  #
-    ####################################################
-    # 1) we need to add points so that there are args.n_knots of them
-    # the -1 is here because we're finite differencing to get theta,
-    # so we need one extra
-    #path2D = list(path2D)
-    ## in case there's too few
-    #while len(path2D) - 1 < args.n_knots:
-    #    path2D.append(path2D[-1])
-    ## in case there's too much
-    #while len(path2D) - 1> args.n_knots:
-    #    path2D.pop()
-    #path2D = np.array(path2D)
-
-
-    #########################
-    #  path2D into pathSE2  #
-    #########################
-    # construct theta 
-    # since it's a pairwise operation it can't be done on the last point
-    x_i = path2D_cart[:,0][:-1] # no last element
-    y_i = path2D_cart[:,1][:-1] # no last element
-    x_i_plus_1 = path2D_cart[:,0][1:] # no first element
-    y_i_plus_1 = path2D_cart[:,1][1:] # no first element
-    x_diff = x_i_plus_1 - x_i
-    y_diff = y_i_plus_1 - y_i
-    # elementwise arctan2
-    thetas = np.arctan2(x_diff, y_diff)
-
-
-    #thetas = thetas.reshape((-1, 1))
-    #path_SE2 = np.hstack((path2D, thetas))
-    
-    ######################################
-    #  construct SE3 from SE2 reference  #
-    ######################################
-    # the plan is parallel to the ground because it's a mobile
-    # manipulation task
-    # TODO: enforce timing, interpolate the path accordingly
-    path = []
-    for i in range(len(path2D_cart) - 1):
-        rotation = pin.rpy.rpyToMatrix(0.0, 0.0, thetas[i])
-        rotation = pin.rpy.rpyToMatrix(np.pi/2, np.pi/2,0.0) @ rotation
-        #rotation = np.eye(3)
-        translation = np.array([path2D_cart[i][0], path2D_cart[i][1], 
-                                args.handlebar_height])
-        path.append(pin.SE3(rotation, translation))
-
-    return path
-
-# function to be put into ProcessManager,
-# spitting out path points
-#def starPlanner(goal, args, init_cmd, cmd_queue : Queue, data_queue : Queue):
-# let's try shared memory
 def starPlanner(goal, args, init_cmd, shm_name, lock : Lock, shm_data):
     """
     starPlanner
     ------------
-    wild dark dynamical system magic done by albin,
-    with more dark magic on top to get it to spit
+    function to be put into ProcessManager,
+    spitting out path points.
+    it's wild dark dynamical system magic done by albin,
+    with software dark magic on top to get it to spit
     out path points and just the path points.
-    goal and path are [x,y]
+    goal and path are [x,y],
+    but there are utility functions to construct SE3 paths out of this
+    elsewhere in the library.
     """
     # shm stuff
     shm = shared_memory.SharedMemory(name=shm_name)
@@ -764,12 +629,19 @@ def starPlanner(goal, args, init_cmd, shm_name, lock : Lock, shm_data):
             # has to be a blocking call
             #cmd = cmd_queue.get()
             #p = cmd['p']
+            # TODO: make a sendCommand type thing which will
+            # handle locking, pickling or numpy-arraying for you
+            # if for no other reason than not copy-pasting code
             lock.acquire()
             p[:] = p_shared[:] 
             lock.release()
 
             if np.linalg.norm(p - goal) < convergence_threshold:
-                data_queue.put("done")
+                data_pickled = pickle.dumps("done")
+                lock.acquire()
+                shm_data.buf[:len(data_pickled)] = data_pickled
+                #shm_data.buf[len(data_pickled):] = bytes(0)
+                lock.release()
                 break
 
             # Update the scene
-- 
GitLab