diff --git a/docker/Dockerfile b/docker/Dockerfile
index 5e76c53bee717fbf83271a42a0a255fc51e2650d..2d2956b9073de3549538ae765e43ad3fca4fe31b 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -7,11 +7,6 @@ LABEL org.opencontainers.image.authors="marko.guberina@control.lth.se"
 ARG DEBIAN_FRONTEND=noninteractive
 ENV TZ=Europe/Stockholm
 
-# pinocchio install
-RUN apt install -qqy lsb-release curl
-RUN mkdir -p /etc/apt/keyrings curl http://robotpkg.openrobots.org/packages/debian/robotpkg.asc | tee /etc/apt/keyrings/robotpkg.asc
-RUN echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/robotpkg.asc] http://robotpkg.openrobots.org/packages/debian/pub $(lsb_release -cs) robotpkg" | sudo tee /etc/apt/sources.list.d/robotpkg.list
-
 RUN apt-get update && apt-get install -y --no-install-recommends \
         python3  \
         python3-pip \
@@ -32,10 +27,20 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
         zsh \
         zsh-syntax-highlighting \
         libarchive-tools \
-        libxcb-cursor0\
-        python3-python-qt-binding \
-        robotpkg-py3*-pinocchio
+        libxcb-cursor0 \
+#		python3-pyqt6 \
+# opencv dependencies
+		ffmpeg libsm6 libxext6 
+
+#        python3-python-qt-binding \
+#        robotpkg-py3*-pinocchio
+
 
+# pinocchio install as debian package - compiled with numpy 1.26 so incompatible with pip which has numpy 2.2
+# necessary for ubuntu 22
+#RUN apt install -qqy lsb-release curl
+#RUN mkdir -p /etc/apt/keyrings curl http://robotpkg.openrobots.org/packages/debian/robotpkg.asc | tee /etc/apt/keyrings/robotpkg.asc
+#RUN echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/robotpkg.asc] http://robotpkg.openrobots.org/packages/debian/pub $(lsb_release -cs) robotpkg" | sudo tee /etc/apt/sources.list.d/robotpkg.list
 
 
 # qt-binding is a really unnecessary 300MB, but i don't want
@@ -48,26 +53,26 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
 #RUN yes | unminimize 
 
 # make the environment more usable
-# create user
-RUN useradd -m -s /bin/zsh -G sudo -u 1000 student
+# create user - jammy comes with user ubuntu with UID 1000, so now we do 1001
+RUN useradd -m -s /bin/zsh -G sudo -u 1001 student
 
 WORKDIR /home/student/
 RUN passwd -d student
 USER student
 # copy repo to workdir
 RUN mkdir SimpleManipulatorControl
-COPY --chown=student .. ./SimpleManipulatorControl
+COPY --chown=student . ./SimpleManipulatorControl
 RUN mkdir -p .cache/zsh/
-COPY --chown=student /dot_files_for_docker/.vimrc /home/student/
-COPY --chown=student /dot_files_for_docker/.zshrc /home/student/
-COPY --chown=student /dot_files_for_docker/global_extra_conf.py /home/student/
+COPY --chown=student /docker/dot_files_for_docker/.vimrc /home/student/
+COPY --chown=student /docker/dot_files_for_docker/.zshrc /home/student/
+COPY --chown=student /docker/dot_files_for_docker/global_extra_conf.py /home/student/
 
 
 # this is enough to run clik
 WORKDIR /home/student/
 USER student
-RUN pip install -e ./SimpleManipulatorControl/python/
-RUN pip install crocoddyl matplotlib meshcat ur_rtde \
+RUN pip install --break-system-packages -e ./SimpleManipulatorControl/python/
+RUN pip install --break-system-packages pin crocoddyl matplotlib meshcat ur_rtde \
                 qpsolvers ecos example_robot_data meshcat_shapes \
                 pyqt6 opencv-python qpsolvers quadprog \
                 proxsuite casadi pin-pink
diff --git a/docs/installation.md b/docs/installation.md
index 3d92bd06e427f80dc7f045f046613a46fa4b0106..dce2c4d20cec307bb6fa3752fc5a85ecc2d46816 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -35,7 +35,7 @@ To proceed you need to have the Docker desktop GUI app running (open).
 --------------------------------
 5. [WINDOWS] open wsl (type wsl in powershell) and navigate to git folder (now in wsl) with "cd /mnt/Users/YOURUSERNAME/PATH_TO_GIT_FOLDER"
 5. [MAC] open a terminal and navigate to git folder with "cd /mnt/Users/YOURUSERNAME/PATH_TO_GIT_FOLDER"
-6. build the image with "docker build -t ur_simple_control ." NOTE: if you are on mac and have an apple silicone processor,
+6. build the image with "docker build -t smc -f docker/Dockerfile ." NOTE: if you are on mac and have an apple silicone processor,
     you might need to change the ubuntu version to one that works on the apple silicone processor.
 
 #### pulling the image
@@ -45,7 +45,7 @@ To proceed you need to have the Docker desktop GUI app running (open).
 
 ### using the image
 ----------------------
-7. to run the image run  "docker run --rm -it --net=host -e DISPLAY=$DISPLAY -v /tmp:/tmp ur_simple_control /bin/zsh".
+7. to run the image run  "docker run --rm -it --net=host -e DISPLAY=$DISPLAY -v /tmp:/tmp smc /bin/zsh".
    [Linux and MAC]: NOTE: FIRST RUN "xhost +" EVERY TIME before you run the image. otherwise docker can't open new windows,
    so real-time-plotting won't work. [MAC] you need to have xquartz installed to have the xhost + command (look at installation for more).
    NOTE! if you don't get real-time-plotting with xhost +, do xhost + 127.0.0.1 and replace -e DISPLAY=$DISPLAY with 
diff --git a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py
index 8414bc09ac264f10dbb95dcb77af4a0daa80ba0e..1a3f9dbc44f4365da06c867b03c9bd9e6f0fa1a0 100644
--- a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py
+++ b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py
@@ -14,7 +14,7 @@ from smc.control.cartesian_space.pink_p2p import (
 from smc.control.cartesian_space.cartesian_space_point_to_point import moveL
 
 from utils import get_args, constructInitialT_w_abs
-from dual_arm_cart_pulling_control_loop import DualArmCartPulling
+from dual_arm_cart_pulling_control_loop import DualArmCartPulling, DualArmCartPullingMPC
 from fixed_path_planner import contructPath, fixedPathPlanner
 
 import time
@@ -35,11 +35,11 @@ if __name__ == "__main__":
     # TODO: before running on the real robot change this to just reading the current position!!
     if args.planner:
         robot._step()
-        robot._q[0] = 9.0
-        robot._q[1] = 4.0
+        robot._q[0] = 1.0
+        robot._q[1] = 0.6
         robot._step()
         x0 = np.concatenate([robot.q, robot.v])
-        goal = np.array([0.5, 5.5])
+        goal = np.array([5.5, 5.0])
 
     ######################################################
     # 1. initialize path planner, or instantiate pre-made path
@@ -74,7 +74,7 @@ if __name__ == "__main__":
     ################################################
     pathSE3 = path2D_to_SE3(path_base, args.handlebar_height, np.pi)
     init_rot = pathSE3[0].rotation @ pin.rpy.rpyToMatrix(np.pi, 0.0, 0.0)
-    init_rot = init_rot @ pin.rpy.rpyToMatrix(0.0, 0.0, np.pi)
+    init_rot = init_rot @ pin.rpy.rpyToMatrix(0.0, 0.0, np.pi / 2)
     # T_w_absgoal = constructInitialT_w_abs(args, path_base, init_rot)
     T_w_absgoal = constructInitialT_w_abs(args, path_base, pathSE3[0].rotation)
 
@@ -138,6 +138,7 @@ if __name__ == "__main__":
     # TODO: for final demo replace this with disjoint controller
     robot._mode = robot.control_mode.whole_body
     DualArmCartPulling(args, robot, path_planner, T_absgoal_l, T_absgoal_r)
+    # DualArmCartPullingMPC(args, robot, path_planner, T_absgoal_l, T_absgoal_r)
 
     if args.real:
         robot.stopRobot()
diff --git a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py
index 3c0b05cd1047b010d6e258d516331e76ac7fc4a9..6d784574a212a5741e756bb716c02c39ad5e82a9 100644
--- a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py
+++ b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py
@@ -16,6 +16,10 @@ from smc.path_generation.path_math.path_to_trajectory import (
 from smc.control.controller_templates.path_following_template import (
     PathFollowingFromPlannerCtrllLoopTemplate,
 )
+from smc.path_generation.path_math.path2d_to_6d import (
+    path2D_to_SE3,
+)
+from smc.control.cartesian_space.ik_solvers import *
 
 import numpy as np
 from functools import partial
@@ -24,9 +28,186 @@ from argparse import Namespace
 from pinocchio import SE3, log6
 from collections import deque
 from utils import initializePastData
+import pinocchio as pin
 
 
 def DualArmCartPullingControlLoop(
+    T_absgoal_l: SE3,
+    T_absgoal_r: SE3,
+    ik_solver,
+    path2D_base: np.ndarray,
+    args: Namespace,
+    robot: DualArmWholeBodyInterface,
+    t: int,
+    past_data: dict[str, deque[np.ndarray]],
+) -> tuple[np.ndarray, dict[str, np.ndarray], dict[str, np.ndarray]]:
+    """
+    BaseAndDualEEPathFollowingMPCControlLoop
+    -----------------------------
+    cart pulling dual arm control loop
+    """
+    v_cmd = np.zeros(robot.model.nv)
+    p = robot.T_w_b.translation[:2]
+
+    max_base_v = np.linalg.norm(robot._max_v[:2])
+    # NOTE: can't initialize a priori now because we're not running immediately,
+    # hence nothing is initialized
+    if t == 0:
+        path2D_handlebar = initializePastData(
+            args, robot.T_w_abs, robot.T_w_b.translation[:2], float(max_base_v)
+        )
+        past_data["path2D"].clear()
+        past_data["path2D"].extend(
+            path2D_handlebar[i] for i in range(args.past_window_size)
+        )
+    trajectory_base = path2D_to_trajectory2D(args, path2D_base, max_base_v)
+    trajectory_base = np.hstack((trajectory_base, np.zeros((len(trajectory_base), 1))))
+
+    trajectorySE3_handlebar = construct_EE_path(args, p, past_data["path2D"])
+    trajectorySE3_l = []
+    trajectorySE3_r = []
+    for traj_pose in trajectorySE3_handlebar:
+        # trajectorySE3_l.append(T_absgoal_l.act(traj_pose))
+        # trajectorySE3_r.append(T_absgoal_r.act(traj_pose))
+        trajectorySE3_l.append(traj_pose.act(T_absgoal_l))
+        trajectorySE3_r.append(traj_pose.act(T_absgoal_r))
+
+    if args.visualizer:
+        if t % int(np.ceil(args.ctrl_freq / 25)) == 0:
+            robot.visualizer_manager.sendCommand({"path": trajectory_base})
+            robot.visualizer_manager.sendCommand(
+                {"frame_path": trajectorySE3_handlebar}
+            )
+
+    # CONTROL BASE
+    robot.mode = robot.control_mode.base_only
+    trajectory_base_SE3 = path2D_to_SE3(trajectory_base[:, :2], 0.0, 0.0)
+    rotm_z = pin.rpy.rpyToMatrix(0.0, 0.0, np.pi)
+    for z in range(len(trajectory_base_SE3)):
+        trajectory_base_SE3[z].rotation = rotm_z @ trajectory_base_SE3[z].rotation
+
+    SEerror = robot.T_w_e.actInv(trajectory_base_SE3[-1])
+    err_vector = pin.log6(SEerror).vector
+    if np.linalg.norm(err_vector) < 0.2:
+        V_path = (
+            5 * pin.log6(trajectory_base_SE3[1].actInv(trajectory_base_SE3[2])).vector
+        )
+        err_vector += V_path
+    err_vector[3:] = err_vector[3:] * 2
+    J = robot.getJacobian()
+    v_cmd_base = ik_solver(J, err_vector)
+    if v_cmd_base is None:
+        v_cmd_base = dampedPseudoinverse(1e-2, J, err_vector)
+    v_cmd[:3] = v_cmd_base
+
+    # CONTROL ARMS
+    robot._mode = robot.control_mode.upper_body
+    SEerror_left = robot.T_w_l.actInv(trajectorySE3_l[0])
+    SEerror_right = robot.T_w_r.actInv(trajectorySE3_r[0])
+    err_vector_left = pin.log6(SEerror_left).vector
+    err_vector_right = pin.log6(SEerror_right).vector
+
+    err_vector = np.concatenate((err_vector_left, err_vector_right))
+    J = robot.getJacobian()
+
+    if args.ik_solver == "QPManipMax":
+        v_cmd_arms = QPManipMax(
+            J,
+            err_vector,
+            robot.computeManipulabilityIndexQDerivative(),
+            lb=-1 * robot.max_v,
+            ub=robot.max_v,
+        )
+    else:
+        v_cmd_arms = ik_solver(J, err_vector)
+    if v_cmd_arms is None:
+        v_cmd_arms = dampedPseudoinverse(1e-2, J, err_vector)
+    v_cmd[3:] = v_cmd_arms
+
+    robot._mode = robot.control_mode.whole_body
+    err_vector_ee_l = log6(robot.T_w_l.actInv(trajectorySE3_l[0]))
+    err_norm_ee_l = np.linalg.norm(err_vector_ee_l)
+    err_vector_ee_r = log6(robot.T_w_r.actInv(trajectorySE3_r[0]))
+    err_norm_ee_r = np.linalg.norm(err_vector_ee_r)
+    err_vector_base = np.linalg.norm(p - trajectory_base[0][:2])  # z axis is irrelevant
+    log_item = {}
+    log_item["err_vec_ee_l"] = err_vector_ee_l.vector
+    log_item["err_norm_ee_l"] = err_norm_ee_l.reshape((1,))
+    log_item["err_vec_ee_r"] = err_vector_ee_r.vector
+    log_item["err_norm_ee_r"] = err_norm_ee_r.reshape((1,))
+    log_item["err_norm_base"] = np.linalg.norm(err_vector_base).reshape((1,))
+    save_past_item = {"path2D": p}
+    return v_cmd, save_past_item, log_item
+
+
+def DualArmCartPulling(
+    args: Namespace,
+    robot: DualArmWholeBodyInterface,
+    path_planner: ProcessManager | types.FunctionType,
+    T_absgoal_l: SE3,
+    T_absgoal_r: SE3,
+    run=True,
+) -> None | ControlLoopManager:
+    """
+    CrocoEndEffectorPathFollowingMPC
+    -----
+    run mpc for a point-to-point inverse kinematics.
+    note that the actual problem is solved on
+    a dynamics level, and velocities we command
+    are actually extracted from the state x(q,dq).
+    """
+    robot._mode = robot.control_mode.whole_body
+    # NOTE: i'm shoving these into the class here - bad style,
+    # but i don't
+    T_w_abs = robot.T_w_abs
+    ik_solver = getIKSolver(args, robot)
+
+    max_base_v = np.linalg.norm(robot._max_v[:2])
+
+    path2D_handlebar = initializePastData(
+        args, T_w_abs, robot.T_w_b.translation[:2], float(max_base_v)
+    )
+
+    get_position = lambda robot: robot.q[:2]
+    DualArmCartPullingControlLoop_with_l_r = partial(
+        DualArmCartPullingControlLoop, T_absgoal_l, T_absgoal_r
+    )
+    controlLoop = partial(
+        PathFollowingFromPlannerCtrllLoopTemplate,
+        path_planner,
+        get_position,
+        ik_solver,
+        DualArmCartPullingControlLoop_with_l_r,
+        args,
+        robot,
+    )
+
+    log_item = {
+        "qs": np.zeros(robot.model.nq),
+        "dqs": np.zeros(robot.model.nv),
+        "err_vec_ee_l": np.zeros((6,)),
+        "err_norm_ee_l": np.zeros((1,)),
+        "err_vec_ee_r": np.zeros((6,)),
+        "err_norm_ee_r": np.zeros((1,)),
+        "err_norm_base": np.zeros((1,)),
+    }
+    save_past_dict = {"path2D": T_w_abs.translation[:2]}
+    loop_manager = ControlLoopManager(
+        robot, controlLoop, args, save_past_dict, log_item
+    )
+    # actually put past data into the past window
+    loop_manager.past_data["path2D"].clear()
+    loop_manager.past_data["path2D"].extend(
+        path2D_handlebar[i] for i in range(args.past_window_size)
+    )
+
+    if run:
+        loop_manager.run()
+    else:
+        return loop_manager
+
+
+def DualArmCartPullingMPCControlLoop(
     T_absgoal_l: SE3,
     T_absgoal_r: SE3,
     ocp: CrocoOCP,
@@ -97,7 +278,7 @@ def DualArmCartPullingControlLoop(
     return v_cmd, save_past_item, log_item
 
 
-def DualArmCartPulling(
+def DualArmCartPullingMPC(
     args: Namespace,
     robot: DualArmWholeBodyInterface,
     path_planner: ProcessManager | types.FunctionType,
@@ -127,7 +308,7 @@ def DualArmCartPulling(
 
     get_position = lambda robot: robot.q[:2]
     DualArmCartPullingControlLoop_with_l_r = partial(
-        DualArmCartPullingControlLoop, T_absgoal_l, T_absgoal_r
+        DualArmCartPullingMPCControlLoop, T_absgoal_l, T_absgoal_r
     )
     controlLoop = partial(
         PathFollowingFromPlannerCtrllLoopTemplate,
diff --git a/examples/cart_pulling/control_sub_problems/fixed_path_planner.py b/examples/cart_pulling/control_sub_problems/fixed_path_planner.py
index 0116bc4edd42787726ab00630d8fa55ea765dbda..d13fbab073e5877bec2211f3b9bb06f1996525b8 100644
--- a/examples/cart_pulling/control_sub_problems/fixed_path_planner.py
+++ b/examples/cart_pulling/control_sub_problems/fixed_path_planner.py
@@ -26,6 +26,8 @@ def fixedPathPlanner(
     path_base: np.ndarray,
     p_base: np.ndarray,
 ) -> np.ndarray:
+    if p_base.shape[0] == 2:
+        p_base = np.append(p_base, 0.0)
     distances = np.linalg.norm(p_base - path_base, axis=1)
     index = np.argmin(distances)
     # NOTE: bullshit heuristic to deal with the path intersecting with itself.
diff --git a/examples/cart_pulling/control_sub_problems/ros_dual_arm_cart_pulling.py b/examples/cart_pulling/control_sub_problems/ros_dual_arm_cart_pulling.py
new file mode 100644
index 0000000000000000000000000000000000000000..0107f8e325a26020257b24c3da47bcbe14d3053f
--- /dev/null
+++ b/examples/cart_pulling/control_sub_problems/ros_dual_arm_cart_pulling.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+
+
+from smc.multiprocessing.smc_yumi_node import get_args, SMCMobileYuMiNode
+from smc.path_generation.maps.premade_maps import createSampleStaticMap
+from smc.path_generation.path_math.path2d_to_6d import path2D_to_SE3
+from smc.path_generation.planner import starPlanner
+from smc.multiprocessing import ProcessManager
+from smc.robots.abstract_robotmanager import AbstractRobotManager
+
+import numpy as np
+from smc.robots.implementations.mobile_yumi import RealMobileYumiRobotManager
+from smc.control.control_loop_manager import ControlLoopManager
+from smc.control.cartesian_space import getClikArgs, getIKSolver
+from smc.control.joint_space.joint_space_point_to_point import moveJP
+from smc.control.cartesian_space.cartesian_space_point_to_point import (
+    controlLoopClik,
+    moveL,
+    moveLDualArm,
+    moveL,
+)
+from smc.control.optimal_control.croco_point_to_point.mpc.base_reference_mpc import (
+    CrocoBaseP2PMPC,
+)
+from smc.control.optimal_control.croco_path_following.mpc.base_reference_mpc import (
+    CrocoBasePathFollowingMPC,
+)
+from smc.control.cartesian_space.cartesian_space_trajectory_following import (
+    cartesianPathFollowingWithPlanner,
+)
+from smc.control.cartesian_space.pink_p2p import (
+    DualArmIKSelfAvoidanceViaEndEffectorSpheres,
+)
+
+from fixed_path_planner import contructPath, fixedPathPlanner
+from utils import constructInitialT_w_abs
+from dual_arm_cart_pulling_control_loop import DualArmCartPulling
+
+import pinocchio as pin
+import matplotlib
+from functools import partial
+import rclpy
+from rclpy.executors import MultiThreadedExecutor
+import numpy as np
+import time
+from copy import deepcopy
+
+
+# def main(args=None):
+def main(args=None):
+    # evil and makes args unusable but what can you do
+    args_smc = get_args()
+    assert args_smc.robot == "myumi"
+    robot = RealMobileYumiRobotManager(args_smc)
+    robot._step()
+    modes_and_loops = []
+    # you can't do terminal input with ros
+    # goal = robot.defineGoalPointCLI()
+    # T_w_absgoal = pin.SE3.Identity()
+    # T_w_absgoal.translation = np.array([0.5, 0.0, 0.5])
+    T_absgoal_l = pin.SE3.Identity()
+    T_absgoal_l.translation = np.array([0.0, -0.2, 0.0])
+    T_absgoal_r = pin.SE3.Identity()
+    T_absgoal_r.translation = np.array([0.0, 0.2, 0.0])
+
+    goal = np.array([2.5, 0.0])
+
+    ######################################################
+    # 1. initialize path planner, or instantiate pre-made path
+    #    and make that a path planner, for the base
+    #################################################
+    if args_smc.planner:
+        planning_function = partial(starPlanner, goal)
+        # here we're following T_w_e reference so that's what we send
+        path_planner = ProcessManager(
+            args_smc, planning_function, robot.T_w_b.translation[:2], 3, None
+        )
+        _, map_as_list = createSampleStaticMap()
+        # TODO: put in real lab map
+        if args_smc.visualizer:
+            robot.sendRectangular2DMapToVisualizer(map_as_list)
+
+        data = None
+        while data is None:
+            path_planner.sendCommand(robot.T_w_b.translation[:2])
+            data = path_planner.getData()
+            time.sleep(1)
+
+        _, path_base = data
+        path_base = np.array(path_base).reshape((-1, 2))
+        path_base = np.hstack((path_base, np.zeros((len(path_base), 1))))
+    else:
+        path_base = contructPath(args_smc, robot)
+        path_planner = partial(fixedPathPlanner, [0], path_base)
+
+    ################################################
+    # 2. construct initial T_w_abs (starting middle of handlebar pose)
+    ################################################
+    pathSE3 = path2D_to_SE3(path_base, args_smc.handlebar_height, np.pi)
+    init_rot = pathSE3[0].rotation @ pin.rpy.rpyToMatrix(np.pi, 0.0, 0.0)
+    init_rot = init_rot @ pin.rpy.rpyToMatrix(0.0, 0.0, np.pi)
+    # T_w_absgoal = constructInitialT_w_abs(args, path_base, init_rot)
+    T_w_absgoal = constructInitialT_w_abs(args_smc, path_base, pathSE3[0].rotation)
+
+    if args_smc.visualizer:
+        robot.visualizer_manager.sendCommand({"Mgoal": pathSE3[0]})
+
+    ##################################################
+    # 3. get arms into comfy position
+    ####################################################
+    # print("putting arms to a comfy configuration")
+    mode_1 = AbstractRobotManager.control_mode.upper_body
+    loop_manager_jp = moveJP(robot._comfy_configuration, args_smc, robot, run=False)
+    modes_and_loops.append((mode_1, loop_manager_jp))
+
+    ###################################################
+    # 4. get base to starting pose
+    ###################################################
+    # print("getting base to start of path")
+    p_basegoal = path_base[0]
+    # NOTE: requires passing a reduced model to work because crocoddyl needs the model to formulate itself
+    # robot.mode = AbstractRobotManager.control_mode.base_only
+
+    ##########################################
+    # orient base to look at the goal (yes this should've be handled with basep2p)
+    ##########################################
+    # NOTE: gets stuck on real for some unknown reason
+    mode_2 = robot.control_mode.base_only
+    T_w_bgoal = pin.SE3(init_rot, p_basegoal)
+    args_smc_1 = deepcopy(args_smc)
+    args_smc_1.goal_error = 10
+    loop_manager_moveL_1 = moveL(args_smc_1, robot, T_w_bgoal.copy(), run=False)
+    modes_and_loops.append((mode_2, loop_manager_moveL_1))
+
+    # NOTE: alternative hack didn't work
+    # q_desired = np.zeros(robot.model.nq)
+    # q_desired[:2] = p_basegoal[:2]
+    # rp = pin.rpy.matrixToRpy(init_rot)
+    # q_desired[2] = np.cos(rp[2] + np.pi)
+    # q_desired[3] = np.sin(rp[2] + np.pi)
+    # q_desired[4:] = robot._comfy_configuration[4:]
+    # loop_manager_jp2 = moveJP(q_desired, args_smc, robot, run=False)
+    # modes_and_loops.append((mode_1, loop_manager_jp2))
+
+    ##########################################
+    # go down and grasp
+    #######################################
+    mode_3 = AbstractRobotManager.control_mode.upper_body
+    T_w_absgoal_1 = T_w_absgoal.copy()
+    T_w_absgoal_2 = T_w_absgoal.copy()
+    T_w_absgoal_1.translation[2] += 0.1
+    # print("going above handlebar")
+    loop_manager_ik_1 = DualArmIKSelfAvoidanceViaEndEffectorSpheres(
+        T_w_absgoal, T_absgoal_l, T_absgoal_r, args_smc, robot, run=False
+    )
+    modes_and_loops.append((mode_3, loop_manager_ik_1))
+
+    # DualArmMoveL down to handlebar
+    # print("going down to handlebar")
+    # T_w_absgoal.translation[2] -= 0.1
+    loop_manager_ik_2 = DualArmIKSelfAvoidanceViaEndEffectorSpheres(
+        T_w_absgoal_2, T_absgoal_l, T_absgoal_r, args_smc, robot, run=False
+    )
+    modes_and_loops.append((mode_3, loop_manager_ik_2))
+
+    mode_4 = robot.control_mode.whole_body
+    loop_manager_pulling = DualArmCartPulling(
+        args_smc, robot, path_planner, T_absgoal_l, T_absgoal_r, run=False
+    )
+    modes_and_loops.append((mode_4, loop_manager_pulling))
+
+    rclpy.init(args=args)
+
+    executor = MultiThreadedExecutor()
+    node = SMCMobileYuMiNode(args_smc, robot, modes_and_loops)
+    executor.add_node(node)
+    executor.spin()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/cart_pulling/technical_report/cart_pulling.log b/examples/cart_pulling/technical_report/cart_pulling.log
index 15cc14d968f6762a583cd13d62591af21d5b2bf1..aee3289971fdd93f7e561da6c05de72787bad83d 100644
--- a/examples/cart_pulling/technical_report/cart_pulling.log
+++ b/examples/cart_pulling/technical_report/cart_pulling.log
@@ -1,6 +1,6 @@
-This is pdfTeX, Version 3.141592653-2.6-1.40.26 (TeX Live 2024/Arch Linux) (preloaded format=pdflatex 2025.2.22)  26 FEB 2025 11:47
+This is pdfTeX, Version 3.141592653-2.6-1.40.26 (TeX Live 2024/Arch Linux) (preloaded format=pdflatex 2025.2.22)  13 MAR 2025 16:38
 entering extended mode
- \write18 enabled.
+ restricted \write18 enabled.
  %&-line parsing enabled.
 **cart_pulling.tex
 (./cart_pulling.tex
@@ -282,7 +282,7 @@ m-super/sfrm0500.pfb></usr/share/texmf-dist/fonts/type1/public/cm-super/sfrm070
 0.pfb></usr/share/texmf-dist/fonts/type1/public/cm-super/sfrm1000.pfb></usr/sha
 re/texmf-dist/fonts/type1/public/cm-super/sfrm1200.pfb></usr/share/texmf-dist/f
 onts/type1/public/cm-super/sfrm1728.pfb>
-Output written on cart_pulling.pdf (8 pages, 288786 bytes).
+Output written on cart_pulling.pdf (8 pages, 288863 bytes).
 PDF statistics:
  131 PDF objects out of 1000 (max. 8388607)
  81 compressed objects within 1 object stream
diff --git a/examples/cart_pulling/technical_report/cart_pulling.pdf b/examples/cart_pulling/technical_report/cart_pulling.pdf
index 9c4ffafbdc6ff237ebdc0e4f72fd896447bdadc1..eb4a4e10ca10d8d9554aeadd889292a37fd55baa 100644
Binary files a/examples/cart_pulling/technical_report/cart_pulling.pdf and b/examples/cart_pulling/technical_report/cart_pulling.pdf differ
diff --git a/examples/cart_pulling/technical_report/cart_pulling.synctex.gz b/examples/cart_pulling/technical_report/cart_pulling.synctex.gz
deleted file mode 100644
index fafbe7f80a80ebfacefe75076a23a4d1886c3977..0000000000000000000000000000000000000000
Binary files a/examples/cart_pulling/technical_report/cart_pulling.synctex.gz and /dev/null differ
diff --git a/examples/ros/myumi_cart_pulling.py b/examples/ros/myumi_cart_pulling.py
deleted file mode 100644
index 073bc7b61f16c13d0ad52602c5efe2655459aa00..0000000000000000000000000000000000000000
--- a/examples/ros/myumi_cart_pulling.py
+++ /dev/null
@@ -1,119 +0,0 @@
-#!/usr/bin/env python3
-
-import rclpy
-from rclpy.executors import MultiThreadedExecutor
-from smc_yumi_node import get_args, SMCMobileYuMiNode
-
-import numpy as np
-import argparse
-from smc.robots.implementations.mobile_yumi import RealMobileYumiRobotManager
-from smc import getMinimalArgParser
-from smc.control.control_loop_manager import ControlLoopManager
-from smc.control.cartesian_space import getClikArgs, getIKSolver
-from smc.control.joint_space.joint_space_point_to_point import moveJP
-from smc.control.cartesian_space.cartesian_space_point_to_point import (
-    controlLoopClik,
-    moveL,
-    moveLDualArm,
-    moveL,
-)
-from smc.control.optimal_control.croco_point_to_point.mpc.base_reference_mpc import (
-    CrocoBaseP2PMPC,
-)
-from smc.control.optimal_control.croco_path_following.mpc.base_reference_mpc import (
-    CrocoBasePathFollowingMPC,
-)
-from smc.util.draw_path import drawPath
-from smc.control.cartesian_space.cartesian_space_trajectory_following import (
-    cartesianPathFollowingWithPlanner,
-)
-
-import pinocchio as pin
-import matplotlib
-from functools import partial
-
-import numpy as np
-
-
-def fixedPathPlanner(
-    path_parameter: list[int], path2D: np.ndarray, p_base: np.ndarray
-) -> np.ndarray:
-    """
-    fixedPathPlanner
-    ----------------
-    we can assume robot is following this path and that it started the following controller at the first point.
-    to make the controller make sense (without a sense of timing), we need to find the closest point on the path,
-    and delete the already traversed portion of the path.
-    the assumption is that the controller constructs a trajectory from the path, starting with the first point.
-
-    the cleanest way to get this done is associate robot's current position with the path parameter s,
-    but i'll just implement it as quickly as possible, not in the most correct or the most efficient manner.
-
-    NOTE: path is (N,3) in shape, last column is 0, because subotimal code structure
-    """
-
-    p_base_3 = np.array([p_base[0], p_base[1], 0.0])
-    distances = np.linalg.norm(p_base_3 - path2D, axis=1)
-    index = np.argmin(distances)
-    if (index - path_parameter[0]) > 0 and (index - path_parameter[0]) < 10:
-        path_parameter[0] += 1
-    path2D = path2D[path_parameter[0] :]
-    return path2D
-
-
-# def main(args=None):
-def main(args=None):
-    # evil and makes args unusable but what can you do
-    args_smc = get_args()
-    assert args_smc.robot == "myumi"
-    robot = RealMobileYumiRobotManager(args_smc)
-    # you can't do terminal input with ros
-    # goal = robot.defineGoalPointCLI()
-    T_w_absgoal = pin.SE3.Identity()
-    T_w_absgoal.translation = np.array([0.5, 0.0, 0.5])
-    T_absgoal_l = pin.SE3.Identity()
-    T_absgoal_l.translation = np.array([0.0, 0.2, 0.0])
-    T_absgoal_r = pin.SE3.Identity()
-    T_absgoal_r.translation = np.array([0.0, -0.2, 0.0])
-    # loop_manager = CrocoIKMPC(args, robot, goal, run=False)
-    # robot._mode = robot.control_mode.upper_body
-    # loop_manager = moveJP(robot._comfy_configuration, args_smc, robot, run=False)
-    # loop_manager = moveLDualArm(
-    #    args_smc, robot, T_w_absgoal, T_absgoal_l, T_absgoal_r, run=False
-    # )
-    # loop_manager = moveL(args_smc, robot, T_w_absgoal, run=False)
-    robot._mode = robot.control_mode.whole_body
-    robot.setInitialPose()
-    robot._step()
-    if not args_smc.draw_new:
-        pixel_path_file_path = "./parameters/path_in_pixels.csv"
-        path2D = np.genfromtxt(pixel_path_file_path, delimiter=",")
-    else:
-        matplotlib.use("tkagg")
-        path2D = drawPath(args_smc)
-        matplotlib.use("qtagg")
-    path2D[:, 0] = path2D[:, 0] * args_smc.map_width
-    path2D[:, 1] = path2D[:, 1] * args_smc.map_height
-    path2D = np.hstack((path2D, np.zeros((len(path2D), 1))))
-    robot.updateViz({"fixed_path": path2D})
-    path_planner = partial(fixedPathPlanner, [0], path2D)
-
-    # x0 = np.concatenate([robot.q, robot.v])
-    # loop_manager = CrocoBasePathFollowingMPC(
-    #    args_smc, robot, x0, path_planner, run=False
-    # )
-    robot._mode = robot.control_mode.base_only
-    loop_manager = cartesianPathFollowingWithPlanner(
-        args_smc, robot, path_planner, 0.0, run=False
-    )
-
-    rclpy.init(args=args)
-
-    executor = MultiThreadedExecutor()
-    node = SMCMobileYuMiNode(args_smc, robot, loop_manager)
-    executor.add_node(node)
-    executor.spin()
-
-
-if __name__ == "__main__":
-    main()
diff --git a/examples/ros/navigation_example.py b/examples/ros/navigation_example.py
index c7439db84b0c56ff9ddf0d8e2574f7ccf496b66e..60ea6c2b5ef06e8f37285dd15050f18b69a5a146 100644
--- a/examples/ros/navigation_example.py
+++ b/examples/ros/navigation_example.py
@@ -3,7 +3,7 @@
 
 import rclpy
 from rclpy.executors import MultiThreadedExecutor
-from smc_yumi_node import get_args, SMCMobileYuMiNode
+from smc.multiprocessing.smc_yumi_node import get_args, SMCMobileYuMiNode
 
 import numpy as np
 import argparse
diff --git a/python/smc/control/cartesian_space/cartesian_space_point_to_point.py b/python/smc/control/cartesian_space/cartesian_space_point_to_point.py
index 6a26983fa6352abb1ec5c7bdda1a2e5e7e679e89..1a923c9ae8ec294ac431687214b370b3489eb662 100644
--- a/python/smc/control/cartesian_space/cartesian_space_point_to_point.py
+++ b/python/smc/control/cartesian_space/cartesian_space_point_to_point.py
@@ -15,6 +15,7 @@ import numpy as np
 from argparse import Namespace
 from collections import deque
 from typing import Callable
+from IPython import embed
 
 from smc.control.cartesian_space.ik_solvers import QPManipMax
 
@@ -38,6 +39,8 @@ def controlLoopClik(
     T_w_e = robot.T_w_e
     SEerror = T_w_e.actInv(T_w_goal)
     err_vector = pin.log6(SEerror).vector
+    if (robot.robot_name == "myumi") and (robot.mode == robot.control_mode.base_only):
+        err_vector[5] = err_vector[5] * 10
     J = robot.getJacobian()
     # compute the joint velocities based on controller you passed
     # qd = ik_solver(J, err_vector, past_qd=past_data['dqs_cmd'][-1])
diff --git a/python/smc/control/cartesian_space/ik_solvers.py b/python/smc/control/cartesian_space/ik_solvers.py
index d4e3880b47b0c8657d5d932185048017d1914df2..c07ff735c3bb67dcd2988d1639fbfaa8b1486ee6 100644
--- a/python/smc/control/cartesian_space/ik_solvers.py
+++ b/python/smc/control/cartesian_space/ik_solvers.py
@@ -137,8 +137,8 @@ def QPquadprog(
         b = (V_e_e / V_e_e_norm) * max_V_e_e_norm
     A = J
     # TODO: you probably want limits here
-    # lb = None
-    # ub = None
+    lb = None
+    ub = None
     # lb *= 20
     # ub *= 20
     h = None
diff --git a/python/smc/control/controller_templates/point_to_point.py b/python/smc/control/controller_templates/point_to_point.py
index 1cc63d15d3778382a1feb48f98606210d6d4f9d1..b6140ac4b2a9c9c73bd7314d1d6a6356c562d3cc 100644
--- a/python/smc/control/controller_templates/point_to_point.py
+++ b/python/smc/control/controller_templates/point_to_point.py
@@ -14,6 +14,7 @@ from smc.robots.interfaces.whole_body_dual_arm_interface import (
 from smc.robots.interfaces.whole_body_single_arm_interface import (
     SingleArmWholeBodyInterface,
 )
+
 global control_loop_return
 control_loop_return = tuple[bool, dict[str, np.ndarray], dict[str, np.ndarray]]
 
@@ -103,9 +104,9 @@ def EEP2PCtrlLoopTemplate(
     if err_vector_norm < robot.args.goal_error:
         breakFlag = True
     log_item["qs"] = robot.q
-    log_item["err_norm"] = err_vector_norm.reshape((1,))
     log_item["dqs"] = robot.v
-    log_item["dqs_cmd"] = v_cmd.reshape((robot.nv,))
+    log_item["dqs_cmd"] = v_cmd
+    log_item["err_norm"] = err_vector_norm.reshape((1,))
     return breakFlag, save_past_item, log_item
 
 
diff --git a/examples/ros/smc_yumi_node.py b/python/smc/multiprocessing/smc_yumi_node.py
similarity index 83%
rename from examples/ros/smc_yumi_node.py
rename to python/smc/multiprocessing/smc_yumi_node.py
index dfa1958ae9d9bd146f4e7bc40b320e94bf080205..b56957891cf70b8ff9cccd08475117fb5ff44392 100644
--- a/examples/ros/smc_yumi_node.py
+++ b/python/smc/multiprocessing/smc_yumi_node.py
@@ -1,6 +1,7 @@
 from smc.robots.implementations.mobile_yumi import RealMobileYumiRobotManager
 from smc.control.control_loop_manager import ControlLoopManager
 from smc import getMinimalArgParser
+from smc.robots.abstract_robotmanager import AbstractRobotManager
 
 import rclpy
 from rclpy.time import Time
@@ -13,6 +14,7 @@ from abb_python_utilities.names import get_rosified_name
 from rclpy.callback_groups import ReentrantCallbackGroup
 from smc.control.cartesian_space import getClikArgs
 from smc.control.optimal_control.util import get_OCP_args
+from smc.path_generation.planner import getPlanningArgs
 
 import numpy as np
 import argparse
@@ -24,6 +26,7 @@ def get_args():
     of various kinds. Make sure you know what the goal is before you run!"
     parser = getClikArgs(parser)
     parser = get_OCP_args(parser)
+    parser = getPlanningArgs(parser)
     parser.add_argument(
         "--ros-namespace",
         type=str,
@@ -60,6 +63,25 @@ def get_args():
         help="if in sim you need to set sim time, otherwise not",
         default=True,
     )
+    # TODO: move elsewhere
+    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.5,
+        help="prefered path arclength from mobile base position to handlebar",
+    )
+    parser.add_argument(
+        "--planner",
+        action=argparse.BooleanOptionalAction,
+        help="if on, you're in a pre-set map and a planner produce a plan to navigate. if off, you draw the path to be followed",
+        default=True,
+    )
     # parser.add_argument('--ros-args', action='extend', nargs="+")
     # parser.add_argument('-r',  action='extend', nargs="+")
     parser.add_argument("--ros-args", action="append", nargs="*")
@@ -80,7 +102,12 @@ def get_args():
 
 class SMCMobileYuMiNode(Node):
     def __init__(
-        self, args, robot: RealMobileYumiRobotManager, loop_manager: ControlLoopManager
+        self,
+        args,
+        robot: RealMobileYumiRobotManager,
+        modes_and_loops: list[
+            tuple[AbstractRobotManager.control_mode, ControlLoopManager]
+        ],
     ):
         super().__init__("SMCMobileYuMiNode")
         if args.sim:
@@ -93,7 +120,9 @@ class SMCMobileYuMiNode(Node):
             )
             self.wait_for_sim_time()
         self.robot = robot
-        self.loop_manager = loop_manager
+        mode, self.loop_manager = modes_and_loops.pop(0)
+        self.robot.mode = mode
+        self.modes_and_loops = modes_and_loops
         self.args = args
         # give me the latest thing even if there wasn't an update
         # qos_prof = rclpy.qos.QoSProfile(
@@ -165,6 +194,15 @@ class SMCMobileYuMiNode(Node):
         # self.get_logger().info("TIMER_CMD")
         self.current_iteration += 1
         breakFlag = self.loop_manager.run_one_iter(self.loop_manager.current_iteration)
+        if breakFlag:
+            if len(self.modes_and_loops) > 0:
+                mode, self.loop_manager = self.modes_and_loops.pop(0)
+                self.robot.mode = mode
+                self.get_logger().info(
+                    "about to run: " + str(self.loop_manager.controlLoop.func.__name__)
+                )
+            else:
+                self.robot._v_cmd[:] = 0.0
 
         if not self.odom_initialized:
             self.get_logger().info(
@@ -172,7 +210,7 @@ class SMCMobileYuMiNode(Node):
             )
 
         # self.get_logger().info("current iteration: " + str(self.current_iteration))
-        self.get_logger().info(str(self.robot._v_cmd))
+        # self.get_logger().info(str(self.robot._v_cmd))
         if self.args.unreal:
             msg = self.empty_msg
             msg.header.stamp = Time().to_msg()
@@ -196,6 +234,9 @@ class SMCMobileYuMiNode(Node):
             # REAL
             for i in range(3):
                 msg.velocity[i] = self.robot._v_cmd[i]
+                if self.robot._v_cmd[2] < 0.01:
+                    self.robot._v_cmd[2] = 0.0
+                    # self.get_logger().info("this velocity is too small for anything!!")
             for i in range(15, 29):
                 msg.velocity[i] = self.robot._v_cmd[i - 12]
             # self.get_logger().info(str(self.robot._q))
@@ -223,7 +264,7 @@ class SMCMobileYuMiNode(Node):
             self.init_odom[2] = th
             self.odom_initialized = True
             self.get_logger().info(str(self.init_odom))
-        if self.args.unreal and self.odom_initialized:
+        if (self.args.unreal and self.odom_initialized) or self.current_iteration < 50:
             T_odom = np.zeros((3, 3))
             T_odom[0, 0] = np.cos(self.init_odom[2])
             T_odom[0, 1] = -1 * np.sin(self.init_odom[2])
@@ -249,6 +290,9 @@ class SMCMobileYuMiNode(Node):
             self.robot._q[1] = p_ctrl[1]
             self.robot._q[2] = np.cos(self.init_odom[2] - th)
             self.robot._q[3] = np.sin(self.init_odom[2] - th)
+            # self.get_logger().info(str(self.robot._q))
+            # self.get_logger().info(str(th))
+            # self.get_logger().info(str(self.init_odom))
         # self.get_logger().info("CALLBACK_ODOM")
         # self.get_logger().info(str(self.robot._q[:4]))
         # self.get_logger().info(str(self.init_odom))
@@ -257,7 +301,6 @@ class SMCMobileYuMiNode(Node):
         if self.current_iteration < 200 or self.args.unreal:
             self.robot._q[-14:-7] = msg.position[-14:-7]
             self.robot._q[-7:] = msg.position[-7:]
-        # self.get_logger().info("CALLBACK_JOINT_STATE")
 
     def wait_for_sim_time(self):
         """Wait for the /clock topic to start publishing."""
diff --git a/python/smc/path_generation/maps/premade_maps.py b/python/smc/path_generation/maps/premade_maps.py
index 7037857efa21ad64937c6343838febd99021d2c8..511984b200b826bd4530e127d8a8db25bfdb00fc 100644
--- a/python/smc/path_generation/maps/premade_maps.py
+++ b/python/smc/path_generation/maps/premade_maps.py
@@ -8,11 +8,18 @@ def createSampleStaticMap():
     return obstacles that define the 2D map
     """
     # [lower_left, lower_right, top_right, top_left]
+    # map_as_list = [
+    #    [[2, 2], [8, 2], [8, 3], [2, 3]],
+    #    [[2, 3], [3, 3], [3, 4.25], [2, 4.25]],
+    #    [[2, 5], [8, 5], [8, 6], [2, 6]],
+    #    [[2, 8], [8, 8], [8, 9], [2, 9]],
+    # ]
     map_as_list = [
-        [[2, 2], [8, 2], [8, 3], [2, 3]],
-        [[2, 3], [3, 3], [3, 4.25], [2, 4.25]],
-        [[2, 5], [8, 5], [8, 6], [2, 6]],
-        [[2, 8], [8, 8], [8, 9], [2, 9]],
+        [[0.5, -1.0], [3.5, -1.0], [3.5, -0.5], [0.5, -0.5]],
+        [[3.5, -0.5], [4.0, -0.5], [4.0, 3.5], [3.5, 3.5]],
+        [[-1.0, 1.0], [2.5, 1.0], [2.5, 2.0], [-1.0, 2.0]],
+        [[0.0, 3.5], [4.0, 3.5], [4.0, 4.0], [0.0, 4.0]],
+        # [[-1.5, -1.0], [-1.0, -1.0], [-1.0, 5.0], [-1.5, 5.0]],
     ]
 
     obstacles = []
diff --git a/python/smc/robots/implementations/heron.py b/python/smc/robots/implementations/heron.py
index a409401f54292ccd83c760969696c748cf06ef92..21537035eb93e4c1bf36e592814eef6e6be81b15 100644
--- a/python/smc/robots/implementations/heron.py
+++ b/python/smc/robots/implementations/heron.py
@@ -6,7 +6,9 @@ from smc.robots.interfaces.force_torque_sensor_interface import (
 from smc.robots.interfaces.mobile_base_interface import (
     get_mobile_base_model,
 )
-from smc.robots.interfaces.whole_body_single_arm_interface import SingleArmWholeBodyInterface
+from smc.robots.interfaces.whole_body_single_arm_interface import (
+    SingleArmWholeBodyInterface,
+)
 from smc.robots.implementations.ur5e import get_model
 from smc.robots.grippers.robotiq.robotiq_gripper import RobotiqGripper
 from smc.robots.grippers.rs485_robotiq.rs485_robotiq import RobotiqHand
@@ -38,6 +40,20 @@ class AbstractHeronRobotManager(
         # TODO: CHANGE THIS TO REAL VALUES
         self._MAX_ACCELERATION = 1.7  # const
         self._MAX_QD = 3.14  # const
+        self._comfy_configuration = np.array(
+            [
+                0.0,
+                0.0,
+                1.0,
+                0.0,
+                1.54027569e00,
+                -1.95702042e00,
+                1.46127540e00,
+                -1.07315435e00,
+                -1.61189968e00,
+                -1.65158907e-03,
+            ]
+        )
         super().__init__(args)
 
 
diff --git a/python/smc/robots/implementations/ur5e.py b/python/smc/robots/implementations/ur5e.py
index 25d3ccf6a1983f93e60d29fa8fa852bd930b1055..4cd10706e423d07edbb64e2eff38927d0ed4c7dc 100644
--- a/python/smc/robots/implementations/ur5e.py
+++ b/python/smc/robots/implementations/ur5e.py
@@ -30,6 +30,16 @@ class AbstractUR5eRobotManager(ForceTorqueOnSingleArmWrist):
         self._ee_frame_id = self.model.getFrameId("tool0")
         self._MAX_ACCELERATION = 1.7  # const
         self._MAX_QD = 3.14  # const
+        self._comfy_configuration = np.array(
+            [
+                1.54027569e00,
+                -1.95702042e00,
+                1.46127540e00,
+                -1.07315435e00,
+                -1.61189968e00,
+                -1.65158907e-03,
+            ]
+        )
         super().__init__(args)
 
 
diff --git a/python/smc/robots/utils.py b/python/smc/robots/utils.py
index 4d539873ff433e9273545929448d88be2db02e4c..488c05f3c995b979a8d3377687b866f02260b29d 100644
--- a/python/smc/robots/utils.py
+++ b/python/smc/robots/utils.py
@@ -85,7 +85,7 @@ def getMinimalArgParser():
         type=str,
         help="gripper you're using (no gripper is the default)",
         default="none",
-        choices=["none", "robotiq", "onrobot"],
+        choices=["none", "robotiq", "onrobot", "rs485"],
     )
     # TODO: make controlloop manager run in a while True loop and remove this
     # ==> max-iterations actually needs to be an option. sometimes you want to simulate and exit